]> source.dussan.org Git - poi.git/commitdiff
HSSF and XSSF Multi-Sheet formula reference tests from Radoslav from bug #55906
authorNick Burch <nick@apache.org>
Sat, 26 Jul 2014 15:20:06 +0000 (15:20 +0000)
committerNick Burch <nick@apache.org>
Sat, 26 Jul 2014 15:20:06 +0000 (15:20 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1613654 13f79535-47bb-0310-9956-ffa450edef68

src/ooxml/testcases/org/apache/poi/xssf/usermodel/AllXSSFUsermodelTests.java
src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestMultiSheetFormulaEvaluatorOnXSSF.java [new file with mode: 0644]
src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaEvaluation.java
src/testcases/org/apache/poi/ss/formula/eval/TestMultiSheetEval.java [new file with mode: 0644]
test-data/spreadsheet/FormulaSheetRange.xls [new file with mode: 0644]
test-data/spreadsheet/FormulaSheetRange.xlsx [new file with mode: 0644]

index e05b2f9a1eae22a5b722de7e49c862517e6605e6..eb0985d7a92620adc7aea2e9d9254721a17d58d2 100644 (file)
@@ -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 (file)
index 0000000..d75961f
--- /dev/null
@@ -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 = "<END>";
+
+               /**
+                * 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 <code>null</code> 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<stes.length) {
+                       if(!stes[startIx].getClassName().equals(Assert.class.getName())) {
+                               break;
+                       }
+                       startIx++;
+               }
+               // skip bottom frames (part of junit framework)
+               int endIx = startIx+1;
+               while(endIx < stes.length) {
+                       if(stes[endIx].getClassName().equals(TestCase.class.getName())) {
+                               break;
+                       }
+                       endIx++;
+               }
+               if(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; i<endIx; i++) {
+                       ps.println("\tat " + stes[i].toString());
+               }
+       }
+
+       /**
+        * @return <code>null</code> 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 <code>null</code> 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) + ")");
+       }
+       
+}
index aaba1643d6fc47e5d06bfd92933b6b24d218859b..e5cf432fbe734af5586d31d83e6343ac9bc385cc 100644 (file)
@@ -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 (file)
index 0000000..a3e8104
--- /dev/null
@@ -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 = "<END>";
+
+               /**
+                * 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 <code>null</code> to test all functions
+        */
+       private void processFunctionGroup(int startRowIndex, String testFocusFunctionName) {
+               HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(workbook);
+        Collection<String> 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<stes.length) {
+                       if(!stes[startIx].getClassName().equals(Assert.class.getName())) {
+                               break;
+                       }
+                       startIx++;
+               }
+               // skip bottom frames (part of junit framework)
+               int endIx = startIx+1;
+               while(endIx < stes.length) {
+                       if(stes[endIx].getClassName().equals(TestCase.class.getName())) {
+                               break;
+                       }
+                       endIx++;
+               }
+               if(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; i<endIx; i++) {
+                       ps.println("\tat " + stes[i].toString());
+               }
+       }
+
+       /**
+        * @return <code>null</code> 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 <code>null</code> 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 (file)
index 0000000..bbac5f2
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 (file)
index 0000000..3da4eb1
Binary files /dev/null and b/test-data/spreadsheet/FormulaSheetRange.xlsx differ