diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/AllXSSFUsermodelTests.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/AllXSSFUsermodelTests.java index e05b2f9a1e..eb0985d7a9 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/AllXSSFUsermodelTests.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/AllXSSFUsermodelTests.java @@ -31,6 +31,7 @@ import org.junit.runners.Suite; @RunWith(Suite.class) @Suite.SuiteClasses({ TestFormulaEvaluatorOnXSSF.class, + TestMultiSheetFormulaEvaluatorOnXSSF.class, TestSheetHiding.class, TestXSSFBugs.class, TestXSSFDataFormat.class, diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestMultiSheetFormulaEvaluatorOnXSSF.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestMultiSheetFormulaEvaluatorOnXSSF.java new file mode 100644 index 0000000000..d75961f3cb --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestMultiSheetFormulaEvaluatorOnXSSF.java @@ -0,0 +1,334 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.xssf.usermodel; + +import java.io.InputStream; +import java.io.PrintStream; +import java.util.Collection; + +import junit.framework.Assert; +import junit.framework.AssertionFailedError; +import junit.framework.TestCase; + +import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.ss.formula.eval.TestFormulasFromSpreadsheet; +import org.apache.poi.ss.formula.functions.TestMathX; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellValue; +import org.apache.poi.ss.usermodel.FormulaEvaluator; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.openxml4j.opc.OPCPackage; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; + +/** + * Tests formulas for multi sheet reference (i.e. SUM(Sheet1:Sheet5!A1)) + */ +public final class TestMultiSheetFormulaEvaluatorOnXSSF extends TestCase { + private static final POILogger logger = POILogFactory.getLogger(TestFormulasFromSpreadsheet.class); + + private static final class Result { + public static final int SOME_EVALUATIONS_FAILED = -1; + public static final int ALL_EVALUATIONS_SUCCEEDED = +1; + public static final int NO_EVALUATIONS_FOUND = 0; + } + + /** + * This class defines constants for navigating around the test data spreadsheet used for these tests. + */ + private static final class SS { + + /** + * Name of the test spreadsheet (found in the standard test data folder) + */ + public final static String FILENAME = "FormulaSheetRange.xlsx"; + /** + * Row (zero-based) in the test spreadsheet where the function examples start. + */ + public static final int START_FUNCTIONS_ROW_INDEX = 10; // Row '11' + /** + * Index of the column that contains the function names + */ + public static final int COLUMN_INDEX_FUNCTION_NAME = 0; // Column 'A' + /** + * Index of the column that contains the test names + */ + public static final int COLUMN_INDEX_TEST_NAME = 1; // Column 'B' + /** + * Used to indicate when there are no more functions left + */ + public static final String FUNCTION_NAMES_END_SENTINEL = ""; + + /** + * Index of the column where the test expected value is present + */ + public static final short COLUMN_INDEX_EXPECTED_VALUE = 2; // Column 'C' + /** + * Index of the column where the test actual value is present + */ + public static final short COLUMN_INDEX_ACTUAL_VALUE = 3; // Column 'D' + /** + * Test sheet name (sheet with all test formulae) + */ + public static final String TEST_SHEET_NAME = "test"; + } + + private XSSFWorkbook workbook; + private Sheet sheet; + // Note - multiple failures are aggregated before ending. + // If one or more functions fail, a single AssertionFailedError is thrown at the end + private int _functionFailureCount; + private int _functionSuccessCount; + private int _evaluationFailureCount; + private int _evaluationSuccessCount; + + private static void confirmExpectedResult(String msg, Cell expected, CellValue actual) { + if (expected == null) { + throw new AssertionFailedError(msg + " - Bad setup data expected value is null"); + } + if(actual == null) { + throw new AssertionFailedError(msg + " - actual value was null"); + } + + switch (expected.getCellType()) { + case Cell.CELL_TYPE_BLANK: + assertEquals(msg, Cell.CELL_TYPE_BLANK, actual.getCellType()); + break; + case Cell.CELL_TYPE_BOOLEAN: + assertEquals(msg, Cell.CELL_TYPE_BOOLEAN, actual.getCellType()); + assertEquals(msg, expected.getBooleanCellValue(), actual.getBooleanValue()); + break; + case Cell.CELL_TYPE_ERROR: + assertEquals(msg, Cell.CELL_TYPE_ERROR, actual.getCellType()); + if(false) { // TODO: fix ~45 functions which are currently returning incorrect error values + assertEquals(msg, expected.getErrorCellValue(), actual.getErrorValue()); + } + break; + case Cell.CELL_TYPE_FORMULA: // will never be used, since we will call method after formula evaluation + throw new AssertionFailedError("Cannot expect formula as result of formula evaluation: " + msg); + case Cell.CELL_TYPE_NUMERIC: + assertEquals(msg, Cell.CELL_TYPE_NUMERIC, actual.getCellType()); + TestMathX.assertEquals(msg, expected.getNumericCellValue(), actual.getNumberValue(), TestMathX.POS_ZERO, TestMathX.DIFF_TOLERANCE_FACTOR); +// double delta = Math.abs(expected.getNumericCellValue()-actual.getNumberValue()); +// double pctExpected = Math.abs(0.00001*expected.getNumericCellValue()); +// assertTrue(msg, delta <= pctExpected); + break; + case Cell.CELL_TYPE_STRING: + assertEquals(msg, Cell.CELL_TYPE_STRING, actual.getCellType()); + assertEquals(msg, expected.getRichStringCellValue().getString(), actual.getStringValue()); + break; + } + } + + + protected void setUp() throws Exception { + if (workbook == null) { + InputStream is = HSSFTestDataSamples.openSampleFileStream(SS.FILENAME); + OPCPackage pkg = OPCPackage.open(is); + workbook = new XSSFWorkbook( pkg ); + sheet = workbook.getSheet( SS.TEST_SHEET_NAME ); + } + _functionFailureCount = 0; + _functionSuccessCount = 0; + _evaluationFailureCount = 0; + _evaluationSuccessCount = 0; + } + + public void testFunctionsFromTestSpreadsheet() { + + processFunctionGroup(SS.START_FUNCTIONS_ROW_INDEX, null); + + // confirm results + String successMsg = "There were " + + _evaluationSuccessCount + " successful evaluation(s) and " + + _functionSuccessCount + " function(s) without error"; + if(_functionFailureCount > 0) { + String msg = _functionFailureCount + " function(s) failed in " + + _evaluationFailureCount + " evaluation(s). " + successMsg; + throw new AssertionFailedError(msg); + } + logger.log(POILogger.INFO, getClass().getName() + ": " + successMsg); + } + + /** + * @param startRowIndex row index in the spreadsheet where the first function/operator is found + * @param testFocusFunctionName name of a single function/operator to test alone. + * Typically pass null to test all functions + */ + private void processFunctionGroup(int startRowIndex, String testFocusFunctionName) { + FormulaEvaluator evaluator = new XSSFFormulaEvaluator(workbook); + + int rowIndex = startRowIndex; + while (true) { + Row r = sheet.getRow(rowIndex); + + // only evaluate non empty row + if( r != null ) + { + String targetFunctionName = getTargetFunctionName(r); + String targetTestName = getTargetTestName(r); + if(targetFunctionName == null) { + throw new AssertionFailedError("Test spreadsheet cell empty on row (" + + (rowIndex+1) + "). Expected function name or '" + + SS.FUNCTION_NAMES_END_SENTINEL + "'"); + } + if(targetFunctionName.equals(SS.FUNCTION_NAMES_END_SENTINEL)) { + // found end of functions list + break; + } + if(testFocusFunctionName == null || targetFunctionName.equalsIgnoreCase(testFocusFunctionName)) { + + // expected results are on the row below + Cell expectedValueCell = r.getCell(SS.COLUMN_INDEX_EXPECTED_VALUE); + if(expectedValueCell == null) { + int missingRowNum = rowIndex + 1; + throw new AssertionFailedError("Missing expected values cell for function '" + + targetFunctionName + ", test" + targetTestName + " (row " + + missingRowNum + ")"); + } + + switch(processFunctionRow(evaluator, targetFunctionName, targetTestName, r, expectedValueCell)) { + case Result.ALL_EVALUATIONS_SUCCEEDED: _functionSuccessCount++; break; + case Result.SOME_EVALUATIONS_FAILED: _functionFailureCount++; break; + default: + throw new RuntimeException("unexpected result"); + case Result.NO_EVALUATIONS_FOUND: // do nothing + break; + } + } + } + rowIndex ++; + } + } + + /** + * + * @return a constant from the local Result class denoting whether there were any evaluation + * cases, and whether they all succeeded. + */ + private int processFunctionRow(FormulaEvaluator evaluator, String targetFunctionName, + String targetTestName, Row formulasRow, Cell expectedValueCell) { + + int result = Result.NO_EVALUATIONS_FOUND; // so far + + Cell c = formulasRow.getCell(SS.COLUMN_INDEX_ACTUAL_VALUE); + if (c == null || c.getCellType() != Cell.CELL_TYPE_FORMULA) { + return result; + } + + CellValue actualValue = evaluator.evaluate(c); + + try { + confirmExpectedResult("Function '" + targetFunctionName + "': Test: '" + targetTestName + "' Formula: " + c.getCellFormula() + + " @ " + formulasRow.getRowNum() + ":" + SS.COLUMN_INDEX_ACTUAL_VALUE, + expectedValueCell, actualValue); + _evaluationSuccessCount ++; + if(result != Result.SOME_EVALUATIONS_FAILED) { + result = Result.ALL_EVALUATIONS_SUCCEEDED; + } + } catch (AssertionFailedError e) { + _evaluationFailureCount ++; + printShortStackTrace(System.err, e); + result = Result.SOME_EVALUATIONS_FAILED; + } + + return result; + } + + /** + * Useful to keep output concise when expecting many failures to be reported by this test case + */ + private static void printShortStackTrace(PrintStream ps, AssertionFailedError e) { + StackTraceElement[] stes = e.getStackTrace(); + + int startIx = 0; + // skip any top frames inside junit.framework.Assert + while(startIx= endIx) { + // something went wrong. just print the whole stack trace + e.printStackTrace(ps); + } + endIx -= 4; // skip 4 frames of reflection invocation + ps.println(e.toString()); + for(int i=startIx; inull if cell is missing, empty or blank + */ + private static String getTargetFunctionName(Row r) { + if(r == null) { + System.err.println("Warning - given null row, can't figure out function name"); + return null; + } + Cell cell = r.getCell(SS.COLUMN_INDEX_FUNCTION_NAME); + if(cell == null) { + System.err.println("Warning - Row " + r.getRowNum() + " has no cell " + SS.COLUMN_INDEX_FUNCTION_NAME + ", can't figure out function name"); + return null; + } + if(cell.getCellType() == Cell.CELL_TYPE_BLANK) { + return null; + } + if(cell.getCellType() == Cell.CELL_TYPE_STRING) { + return cell.getRichStringCellValue().getString(); + } + + throw new AssertionFailedError("Bad cell type for 'function name' column: (" + + cell.getCellType() + ") row (" + (r.getRowNum() +1) + ")"); + } + /** + * @return null if cell is missing, empty or blank + */ + private static String getTargetTestName(Row r) { + if(r == null) { + System.err.println("Warning - given null row, can't figure out test name"); + return null; + } + Cell cell = r.getCell(SS.COLUMN_INDEX_TEST_NAME); + if(cell == null) { + System.err.println("Warning - Row " + r.getRowNum() + " has no cell " + SS.COLUMN_INDEX_TEST_NAME + ", can't figure out test name"); + return null; + } + if(cell.getCellType() == Cell.CELL_TYPE_BLANK) { + return null; + } + if(cell.getCellType() == Cell.CELL_TYPE_STRING) { + return cell.getRichStringCellValue().getString(); + } + + throw new AssertionFailedError("Bad cell type for 'test name' column: (" + + cell.getCellType() + ") row (" + (r.getRowNum() +1) + ")"); + } + +} diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaEvaluation.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaEvaluation.java index aaba1643d6..e5cf432fbe 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaEvaluation.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaEvaluation.java @@ -287,4 +287,49 @@ public final class TestXSSFFormulaEvaluation extends BaseTestFormulaEvaluator { assertEquals("4.0", evaluator.evaluate(countFA).formatAsString()); } } + + public void testMultisheetFormulaEval() { + XSSFWorkbook wb = new XSSFWorkbook(); + XSSFSheet sheet1 = wb.createSheet("Sheet1"); + XSSFSheet sheet2 = wb.createSheet("Sheet2"); + XSSFSheet sheet3 = wb.createSheet("Sheet3"); + + // sheet1 A1 + XSSFCell cell = sheet1.createRow(0).createCell(0); + cell.setCellType(Cell.CELL_TYPE_NUMERIC); + cell.setCellValue(1.0); + + // sheet2 A1 + cell = sheet2.createRow(0).createCell(0); + cell.setCellType(Cell.CELL_TYPE_NUMERIC); + cell.setCellValue(1.0); + + // sheet2 B1 + cell = sheet2.getRow(0).createCell(1); + cell.setCellType(Cell.CELL_TYPE_NUMERIC); + cell.setCellValue(1.0); + + // sheet3 A1 + cell = sheet3.createRow(0).createCell(0); + cell.setCellType(Cell.CELL_TYPE_NUMERIC); + cell.setCellValue(1.0); + + // sheet1 A2 formulae + cell = sheet1.createRow(1).createCell(0); + cell.setCellType(Cell.CELL_TYPE_FORMULA); + cell.setCellFormula("SUM(Sheet1:Sheet3!A1)"); + + // sheet1 A3 formulae + cell = sheet1.createRow(2).createCell(0); + cell.setCellType(Cell.CELL_TYPE_FORMULA); + cell.setCellFormula("SUM(Sheet1:Sheet3!A1:B1)"); + + wb.getCreationHelper().createFormulaEvaluator().evaluateAll(); + + cell = sheet1.getRow(1).getCell(0); + assertEquals(3.0, cell.getNumericCellValue()); + + cell = sheet1.getRow(2).getCell(0); + assertEquals(4.0, cell.getNumericCellValue()); + } } diff --git a/src/testcases/org/apache/poi/ss/formula/eval/TestMultiSheetEval.java b/src/testcases/org/apache/poi/ss/formula/eval/TestMultiSheetEval.java new file mode 100644 index 0000000000..a3e810474d --- /dev/null +++ b/src/testcases/org/apache/poi/ss/formula/eval/TestMultiSheetEval.java @@ -0,0 +1,331 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.ss.formula.eval; + +import java.io.PrintStream; +import java.util.Collection; + +import junit.framework.Assert; +import junit.framework.AssertionFailedError; +import junit.framework.TestCase; + +import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.formula.functions.TestMathX; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellValue; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; + +/** + * Tests formulas for multi sheet reference (i.e. SUM(Sheet1:Sheet5!A1)) + */ +public final class TestMultiSheetEval extends TestCase { + private static final POILogger logger = POILogFactory.getLogger(TestFormulasFromSpreadsheet.class); + + private static final class Result { + public static final int SOME_EVALUATIONS_FAILED = -1; + public static final int ALL_EVALUATIONS_SUCCEEDED = +1; + public static final int NO_EVALUATIONS_FOUND = 0; + } + + /** + * This class defines constants for navigating around the test data spreadsheet used for these tests. + */ + private static final class SS { + + /** + * Name of the test spreadsheet (found in the standard test data folder) + */ + public final static String FILENAME = "FormulaSheetRange.xls"; + /** + * Row (zero-based) in the test spreadsheet where the function examples start. + */ + public static final int START_FUNCTIONS_ROW_INDEX = 10; // Row '11' + /** + * Index of the column that contains the function names + */ + public static final int COLUMN_INDEX_FUNCTION_NAME = 0; // Column 'A' + /** + * Index of the column that contains the test names + */ + public static final int COLUMN_INDEX_TEST_NAME = 1; // Column 'B' + /** + * Used to indicate when there are no more functions left + */ + public static final String FUNCTION_NAMES_END_SENTINEL = ""; + + /** + * Index of the column where the test expected value is present + */ + public static final short COLUMN_INDEX_EXPECTED_VALUE = 2; // Column 'C' + /** + * Index of the column where the test actual value is present + */ + public static final short COLUMN_INDEX_ACTUAL_VALUE = 3; // Column 'D' + /** + * Test sheet name (sheet with all test formulae) + */ + public static final String TEST_SHEET_NAME = "test"; + } + + private HSSFWorkbook workbook; + private Sheet sheet; + // Note - multiple failures are aggregated before ending. + // If one or more functions fail, a single AssertionFailedError is thrown at the end + private int _functionFailureCount; + private int _functionSuccessCount; + private int _evaluationFailureCount; + private int _evaluationSuccessCount; + + private static void confirmExpectedResult(String msg, Cell expected, CellValue actual) { + if (expected == null) { + throw new AssertionFailedError(msg + " - Bad setup data expected value is null"); + } + if(actual == null) { + throw new AssertionFailedError(msg + " - actual value was null"); + } + + switch (expected.getCellType()) { + case Cell.CELL_TYPE_BLANK: + assertEquals(msg, Cell.CELL_TYPE_BLANK, actual.getCellType()); + break; + case Cell.CELL_TYPE_BOOLEAN: + assertEquals(msg, Cell.CELL_TYPE_BOOLEAN, actual.getCellType()); + assertEquals(msg, expected.getBooleanCellValue(), actual.getBooleanValue()); + break; + case Cell.CELL_TYPE_ERROR: + assertEquals(msg, Cell.CELL_TYPE_ERROR, actual.getCellType()); + assertEquals(msg, ErrorEval.getText(expected.getErrorCellValue()), ErrorEval.getText(actual.getErrorValue())); + break; + case Cell.CELL_TYPE_FORMULA: // will never be used, since we will call method after formula evaluation + throw new AssertionFailedError("Cannot expect formula as result of formula evaluation: " + msg); + case Cell.CELL_TYPE_NUMERIC: + assertEquals(msg, Cell.CELL_TYPE_NUMERIC, actual.getCellType()); + TestMathX.assertEquals(msg, expected.getNumericCellValue(), actual.getNumberValue(), TestMathX.POS_ZERO, TestMathX.DIFF_TOLERANCE_FACTOR); + break; + case Cell.CELL_TYPE_STRING: + assertEquals(msg, Cell.CELL_TYPE_STRING, actual.getCellType()); + assertEquals(msg, expected.getRichStringCellValue().getString(), actual.getStringValue()); + break; + } + } + + + protected void setUp() { + if (workbook == null) { + workbook = HSSFTestDataSamples.openSampleWorkbook(SS.FILENAME); + sheet = workbook.getSheet( SS.TEST_SHEET_NAME ); + } + _functionFailureCount = 0; + _functionSuccessCount = 0; + _evaluationFailureCount = 0; + _evaluationSuccessCount = 0; + } + + public void testFunctionsFromTestSpreadsheet() { + + processFunctionGroup(SS.START_FUNCTIONS_ROW_INDEX, null); + + // confirm results + String successMsg = "There were " + + _evaluationSuccessCount + " successful evaluation(s) and " + + _functionSuccessCount + " function(s) without error"; + if(_functionFailureCount > 0) { + String msg = _functionFailureCount + " function(s) failed in " + + _evaluationFailureCount + " evaluation(s). " + successMsg; + throw new AssertionFailedError(msg); + } + logger.log(POILogger.INFO, getClass().getName() + ": " + successMsg); + } + + /** + * @param startRowIndex row index in the spreadsheet where the first function/operator is found + * @param testFocusFunctionName name of a single function/operator to test alone. + * Typically pass null to test all functions + */ + private void processFunctionGroup(int startRowIndex, String testFocusFunctionName) { + HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(workbook); + Collection funcs = FunctionEval.getSupportedFunctionNames(); + + int rowIndex = startRowIndex; + while (true) { + Row r = sheet.getRow(rowIndex); + + // only evaluate non empty row + if( r != null ) + { + String targetFunctionName = getTargetFunctionName(r); + String targetTestName = getTargetTestName(r); + if(targetFunctionName == null) { + throw new AssertionFailedError("Test spreadsheet cell empty on row (" + + (rowIndex+1) + "). Expected function name or '" + + SS.FUNCTION_NAMES_END_SENTINEL + "'"); + } + if(targetFunctionName.equals(SS.FUNCTION_NAMES_END_SENTINEL)) { + // found end of functions list + break; + } + if(testFocusFunctionName == null || targetFunctionName.equalsIgnoreCase(testFocusFunctionName)) { + + // expected results are on the row below + Cell expectedValueCell = r.getCell(SS.COLUMN_INDEX_EXPECTED_VALUE); + if(expectedValueCell == null) { + int missingRowNum = rowIndex + 1; + throw new AssertionFailedError("Missing expected values cell for function '" + + targetFunctionName + ", test" + targetTestName + " (row " + + missingRowNum + ")"); + } + + switch(processFunctionRow(evaluator, targetFunctionName, targetTestName, r, expectedValueCell)) { + case Result.ALL_EVALUATIONS_SUCCEEDED: _functionSuccessCount++; break; + case Result.SOME_EVALUATIONS_FAILED: _functionFailureCount++; break; + default: + throw new RuntimeException("unexpected result"); + case Result.NO_EVALUATIONS_FOUND: // do nothing + String uname = targetFunctionName.toUpperCase(); + if(startRowIndex >= SS.START_FUNCTIONS_ROW_INDEX && + funcs.contains(uname)) { + logger.log(POILogger.WARN, uname + ": function is supported but missing test data"); + } + break; + } + } + } + rowIndex ++; + } + } + + /** + * + * @return a constant from the local Result class denoting whether there were any evaluation + * cases, and whether they all succeeded. + */ + private int processFunctionRow(HSSFFormulaEvaluator evaluator, String targetFunctionName, + String targetTestName, Row formulasRow, Cell expectedValueCell) { + + int result = Result.NO_EVALUATIONS_FOUND; // so far + + Cell c = formulasRow.getCell(SS.COLUMN_INDEX_ACTUAL_VALUE); + if (c == null || c.getCellType() != Cell.CELL_TYPE_FORMULA) { + return result; + } + + CellValue actualValue = evaluator.evaluate(c); + + try { + confirmExpectedResult("Function '" + targetFunctionName + "': Test: '" + targetTestName + "' Formula: " + c.getCellFormula() + + " @ " + formulasRow.getRowNum() + ":" + SS.COLUMN_INDEX_ACTUAL_VALUE, + expectedValueCell, actualValue); + _evaluationSuccessCount ++; + if(result != Result.SOME_EVALUATIONS_FAILED) { + result = Result.ALL_EVALUATIONS_SUCCEEDED; + } + } catch (AssertionFailedError e) { + _evaluationFailureCount ++; + printShortStackTrace(System.err, e); + result = Result.SOME_EVALUATIONS_FAILED; + } + + return result; + } + + /** + * Useful to keep output concise when expecting many failures to be reported by this test case + */ + private static void printShortStackTrace(PrintStream ps, AssertionFailedError e) { + StackTraceElement[] stes = e.getStackTrace(); + + int startIx = 0; + // skip any top frames inside junit.framework.Assert + while(startIx= endIx) { + // something went wrong. just print the whole stack trace + e.printStackTrace(ps); + } + endIx -= 4; // skip 4 frames of reflection invocation + ps.println(e.toString()); + for(int i=startIx; inull if cell is missing, empty or blank + */ + private static String getTargetFunctionName(Row r) { + if(r == null) { + System.err.println("Warning - given null row, can't figure out function name"); + return null; + } + Cell cell = r.getCell(SS.COLUMN_INDEX_FUNCTION_NAME); + if(cell == null) { + System.err.println("Warning - Row " + r.getRowNum() + " has no cell " + SS.COLUMN_INDEX_FUNCTION_NAME + ", can't figure out function name"); + return null; + } + if(cell.getCellType() == Cell.CELL_TYPE_BLANK) { + return null; + } + if(cell.getCellType() == Cell.CELL_TYPE_STRING) { + return cell.getRichStringCellValue().getString(); + } + + throw new AssertionFailedError("Bad cell type for 'function name' column: (" + + cell.getCellType() + ") row (" + (r.getRowNum() +1) + ")"); + } + /** + * @return null if cell is missing, empty or blank + */ + private static String getTargetTestName(Row r) { + if(r == null) { + System.err.println("Warning - given null row, can't figure out test name"); + return null; + } + Cell cell = r.getCell(SS.COLUMN_INDEX_TEST_NAME); + if(cell == null) { + System.err.println("Warning - Row " + r.getRowNum() + " has no cell " + SS.COLUMN_INDEX_TEST_NAME + ", can't figure out test name"); + return null; + } + if(cell.getCellType() == Cell.CELL_TYPE_BLANK) { + return null; + } + if(cell.getCellType() == Cell.CELL_TYPE_STRING) { + return cell.getRichStringCellValue().getString(); + } + + throw new AssertionFailedError("Bad cell type for 'test name' column: (" + + cell.getCellType() + ") row (" + (r.getRowNum() +1) + ")"); + } + +} diff --git a/test-data/spreadsheet/FormulaSheetRange.xls b/test-data/spreadsheet/FormulaSheetRange.xls new file mode 100644 index 0000000000..bbac5f239b Binary files /dev/null and b/test-data/spreadsheet/FormulaSheetRange.xls differ diff --git a/test-data/spreadsheet/FormulaSheetRange.xlsx b/test-data/spreadsheet/FormulaSheetRange.xlsx new file mode 100644 index 0000000000..3da4eb1f8b Binary files /dev/null and b/test-data/spreadsheet/FormulaSheetRange.xlsx differ