Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

BaseTestFunctionsFromSpreadsheet.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.ss.formula.functions;
  16. import java.io.PrintStream;
  17. import junit.framework.Assert;
  18. import junit.framework.AssertionFailedError;
  19. import junit.framework.TestCase;
  20. import org.apache.poi.hssf.HSSFTestDataSamples;
  21. import org.apache.poi.ss.formula.eval.ErrorEval;
  22. import org.apache.poi.hssf.usermodel.HSSFCell;
  23. import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
  24. import org.apache.poi.hssf.usermodel.HSSFRow;
  25. import org.apache.poi.hssf.usermodel.HSSFSheet;
  26. import org.apache.poi.hssf.usermodel.HSSFWorkbook;
  27. import org.apache.poi.hssf.util.CellReference;
  28. import org.apache.poi.ss.usermodel.CellValue;
  29. /**
  30. * @author Josh Micich
  31. * @author Cedric Walter at innoveo.com
  32. */
  33. public abstract class BaseTestFunctionsFromSpreadsheet extends TestCase {
  34. private static final class Result {
  35. public static final int SOME_EVALUATIONS_FAILED = -1;
  36. public static final int ALL_EVALUATIONS_SUCCEEDED = +1;
  37. public static final int NO_EVALUATIONS_FOUND = 0;
  38. }
  39. /**
  40. * This class defines constants for navigating around the test data spreadsheet used for these tests.
  41. */
  42. private static final class SS {
  43. /** Name of the test spreadsheet (found in the standard test data folder) */
  44. /** Name of the first sheet in the spreadsheet (contains comments) */
  45. public final static String README_SHEET_NAME = "Read Me";
  46. /** Row (zero-based) in each sheet where the evaluation cases start. */
  47. public static final int START_TEST_CASES_ROW_INDEX = 4; // Row '5'
  48. /** Index of the column that contains the function names */
  49. public static final int COLUMN_INDEX_MARKER = 0; // Column 'A'
  50. public static final int COLUMN_INDEX_EVALUATION = 1; // Column 'B'
  51. public static final int COLUMN_INDEX_EXPECTED_RESULT = 2; // Column 'C'
  52. public static final int COLUMN_ROW_COMMENT = 3; // Column 'D'
  53. /** Used to indicate when there are no more test cases on the current sheet */
  54. public static final String TEST_CASES_END_MARKER = "<end>";
  55. /** Used to indicate that the test on the current row should be ignored */
  56. public static final String SKIP_CURRENT_TEST_CASE_MARKER = "<skip>";
  57. }
  58. // Note - multiple failures are aggregated before ending.
  59. // If one or more functions fail, a single AssertionFailedError is thrown at the end
  60. private int _sheetFailureCount;
  61. private int _sheetSuccessCount;
  62. private int _evaluationFailureCount;
  63. private int _evaluationSuccessCount;
  64. private static void confirmExpectedResult(String msg, HSSFCell expected, CellValue actual) {
  65. if (expected == null) {
  66. throw new AssertionFailedError(msg + " - Bad setup data expected value is null");
  67. }
  68. if(actual == null) {
  69. throw new AssertionFailedError(msg + " - actual value was null");
  70. }
  71. if(expected.getCellType() == HSSFCell.CELL_TYPE_ERROR) {
  72. confirmErrorResult(msg, expected.getErrorCellValue(), actual);
  73. return;
  74. }
  75. if(actual.getCellType() == HSSFCell.CELL_TYPE_ERROR) {
  76. throw unexpectedError(msg, expected, actual.getErrorValue());
  77. }
  78. if(actual.getCellType() != expected.getCellType()) {
  79. throw wrongTypeError(msg, expected, actual);
  80. }
  81. switch (expected.getCellType()) {
  82. case HSSFCell.CELL_TYPE_BOOLEAN:
  83. assertEquals(msg, expected.getBooleanCellValue(), actual.getBooleanValue());
  84. break;
  85. case HSSFCell.CELL_TYPE_FORMULA: // will never be used, since we will call method after formula evaluation
  86. throw new IllegalStateException("Cannot expect formula as result of formula evaluation: " + msg);
  87. case HSSFCell.CELL_TYPE_NUMERIC:
  88. assertEquals(expected.getNumericCellValue(), actual.getNumberValue(), 0.0);
  89. break;
  90. case HSSFCell.CELL_TYPE_STRING:
  91. assertEquals(msg, expected.getRichStringCellValue().getString(), actual.getStringValue());
  92. break;
  93. }
  94. }
  95. private static AssertionFailedError wrongTypeError(String msgPrefix, HSSFCell expectedCell, CellValue actualValue) {
  96. return new AssertionFailedError(msgPrefix + " Result type mismatch. Evaluated result was "
  97. + actualValue.formatAsString()
  98. + " but the expected result was "
  99. + formatValue(expectedCell)
  100. );
  101. }
  102. private static AssertionFailedError unexpectedError(String msgPrefix, HSSFCell expected, int actualErrorCode) {
  103. return new AssertionFailedError(msgPrefix + " Error code ("
  104. + ErrorEval.getText(actualErrorCode)
  105. + ") was evaluated, but the expected result was "
  106. + formatValue(expected)
  107. );
  108. }
  109. private static void confirmErrorResult(String msgPrefix, int expectedErrorCode, CellValue actual) {
  110. if(actual.getCellType() != HSSFCell.CELL_TYPE_ERROR) {
  111. throw new AssertionFailedError(msgPrefix + " Expected cell error ("
  112. + ErrorEval.getText(expectedErrorCode) + ") but actual value was "
  113. + actual.formatAsString());
  114. }
  115. if(expectedErrorCode != actual.getErrorValue()) {
  116. throw new AssertionFailedError(msgPrefix + " Expected cell error code ("
  117. + ErrorEval.getText(expectedErrorCode)
  118. + ") but actual error code was ("
  119. + ErrorEval.getText(actual.getErrorValue())
  120. + ")");
  121. }
  122. }
  123. private static String formatValue(HSSFCell expecedCell) {
  124. switch (expecedCell.getCellType()) {
  125. case HSSFCell.CELL_TYPE_BLANK: return "<blank>";
  126. case HSSFCell.CELL_TYPE_BOOLEAN: return String.valueOf(expecedCell.getBooleanCellValue());
  127. case HSSFCell.CELL_TYPE_NUMERIC: return String.valueOf(expecedCell.getNumericCellValue());
  128. case HSSFCell.CELL_TYPE_STRING: return expecedCell.getRichStringCellValue().getString();
  129. }
  130. throw new RuntimeException("Unexpected cell type of expected value (" + expecedCell.getCellType() + ")");
  131. }
  132. protected void setUp() {
  133. _sheetFailureCount = 0;
  134. _sheetSuccessCount = 0;
  135. _evaluationFailureCount = 0;
  136. _evaluationSuccessCount = 0;
  137. }
  138. public void testFunctionsFromTestSpreadsheet() {
  139. HSSFWorkbook workbook = HSSFTestDataSamples.openSampleWorkbook(this.getFilename());
  140. confirmReadMeSheet(workbook);
  141. int nSheets = workbook.getNumberOfSheets();
  142. for(int i=1; i< nSheets; i++) {
  143. int sheetResult = processTestSheet(workbook, i, workbook.getSheetName(i));
  144. switch(sheetResult) {
  145. case Result.ALL_EVALUATIONS_SUCCEEDED: _sheetSuccessCount ++; break;
  146. case Result.SOME_EVALUATIONS_FAILED: _sheetFailureCount ++; break;
  147. }
  148. }
  149. // confirm results
  150. String successMsg = "There were "
  151. + _sheetSuccessCount + " successful sheets(s) and "
  152. + _evaluationSuccessCount + " function(s) without error";
  153. if(_sheetFailureCount > 0) {
  154. String msg = _sheetFailureCount + " sheets(s) failed with "
  155. + _evaluationFailureCount + " evaluation(s). " + successMsg;
  156. throw new AssertionFailedError(msg);
  157. }
  158. if(false) { // normally no output for successful tests
  159. System.out.println(getClass().getName() + ": " + successMsg);
  160. }
  161. }
  162. protected abstract String getFilename();
  163. private int processTestSheet(HSSFWorkbook workbook, int sheetIndex, String sheetName) {
  164. HSSFSheet sheet = workbook.getSheetAt(sheetIndex);
  165. HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(workbook);
  166. int maxRows = sheet.getLastRowNum()+1;
  167. int result = Result.NO_EVALUATIONS_FOUND; // so far
  168. String currentGroupComment = null;
  169. for(int rowIndex=SS.START_TEST_CASES_ROW_INDEX; rowIndex<maxRows; rowIndex++) {
  170. HSSFRow r = sheet.getRow(rowIndex);
  171. String newMarkerValue = getMarkerColumnValue(r);
  172. if(r == null) {
  173. continue;
  174. }
  175. if(SS.TEST_CASES_END_MARKER.equalsIgnoreCase(newMarkerValue)) {
  176. // normal exit point
  177. return result;
  178. }
  179. if(SS.SKIP_CURRENT_TEST_CASE_MARKER.equalsIgnoreCase(newMarkerValue)) {
  180. // currently disabled test case row
  181. continue;
  182. }
  183. if(newMarkerValue != null) {
  184. currentGroupComment = newMarkerValue;
  185. }
  186. HSSFCell c = r.getCell(SS.COLUMN_INDEX_EVALUATION);
  187. if (c == null || c.getCellType() != HSSFCell.CELL_TYPE_FORMULA) {
  188. continue;
  189. }
  190. CellValue actualValue = evaluator.evaluate(c);
  191. HSSFCell expectedValueCell = r.getCell(SS.COLUMN_INDEX_EXPECTED_RESULT);
  192. String rowComment = getRowCommentColumnValue(r);
  193. String msgPrefix = formatTestCaseDetails(sheetName, r.getRowNum(), c, currentGroupComment, rowComment);
  194. try {
  195. confirmExpectedResult(msgPrefix, expectedValueCell, actualValue);
  196. _evaluationSuccessCount ++;
  197. if(result != Result.SOME_EVALUATIONS_FAILED) {
  198. result = Result.ALL_EVALUATIONS_SUCCEEDED;
  199. }
  200. } catch (RuntimeException e) {
  201. _evaluationFailureCount ++;
  202. printShortStackTrace(System.err, e);
  203. result = Result.SOME_EVALUATIONS_FAILED;
  204. } catch (AssertionFailedError e) {
  205. _evaluationFailureCount ++;
  206. printShortStackTrace(System.err, e);
  207. result = Result.SOME_EVALUATIONS_FAILED;
  208. }
  209. }
  210. throw new RuntimeException("Missing end marker '" + SS.TEST_CASES_END_MARKER
  211. + "' on sheet '" + sheetName + "'");
  212. }
  213. private static String formatTestCaseDetails(String sheetName, int rowIndex, HSSFCell c, String currentGroupComment,
  214. String rowComment) {
  215. StringBuffer sb = new StringBuffer();
  216. CellReference cr = new CellReference(sheetName, rowIndex, c.getColumnIndex(), false, false);
  217. sb.append(cr.formatAsString());
  218. sb.append(" {=").append(c.getCellFormula()).append("}");
  219. if(currentGroupComment != null) {
  220. sb.append(" '");
  221. sb.append(currentGroupComment);
  222. if(rowComment != null) {
  223. sb.append(" - ");
  224. sb.append(rowComment);
  225. }
  226. sb.append("' ");
  227. } else {
  228. if(rowComment != null) {
  229. sb.append(" '");
  230. sb.append(rowComment);
  231. sb.append("' ");
  232. }
  233. }
  234. return sb.toString();
  235. }
  236. /**
  237. * Asserts that the 'read me' comment page exists, and has this class' name in one of the
  238. * cells. This back-link is to make it easy to find this class if a reader encounters the
  239. * spreadsheet first.
  240. */
  241. private void confirmReadMeSheet(HSSFWorkbook workbook) {
  242. String firstSheetName = workbook.getSheetName(0);
  243. if(!firstSheetName.equalsIgnoreCase(SS.README_SHEET_NAME)) {
  244. throw new RuntimeException("First sheet's name was '" + firstSheetName + "' but expected '" + SS.README_SHEET_NAME + "'");
  245. }
  246. HSSFSheet sheet = workbook.getSheetAt(0);
  247. String specifiedClassName = sheet.getRow(2).getCell(0).getRichStringCellValue().getString();
  248. assertEquals("Test class name in spreadsheet comment", getClass().getName(), specifiedClassName);
  249. }
  250. /**
  251. * Useful to keep output concise when expecting many failures to be reported by this test case
  252. */
  253. private static void printShortStackTrace(PrintStream ps, Throwable e) {
  254. StackTraceElement[] stes = e.getStackTrace();
  255. int startIx = 0;
  256. // skip any top frames inside junit.framework.Assert
  257. while(startIx<stes.length) {
  258. if(!stes[startIx].getClassName().equals(Assert.class.getName())) {
  259. break;
  260. }
  261. startIx++;
  262. }
  263. // skip bottom frames (part of junit framework)
  264. int endIx = startIx+1;
  265. while(endIx < stes.length) {
  266. if(stes[endIx].getClassName().equals(TestCase.class.getName())) {
  267. break;
  268. }
  269. endIx++;
  270. }
  271. if(startIx >= endIx) {
  272. // something went wrong. just print the whole stack trace
  273. e.printStackTrace(ps);
  274. }
  275. endIx -= 4; // skip 4 frames of reflection invocation
  276. ps.println(e.toString());
  277. for(int i=startIx; i<endIx; i++) {
  278. ps.println("\tat " + stes[i].toString());
  279. }
  280. }
  281. private static String getRowCommentColumnValue(HSSFRow r) {
  282. return getCellTextValue(r, SS.COLUMN_ROW_COMMENT, "row comment");
  283. }
  284. private static String getMarkerColumnValue(HSSFRow r) {
  285. return getCellTextValue(r, SS.COLUMN_INDEX_MARKER, "marker");
  286. }
  287. /**
  288. * @return <code>null</code> if cell is missing, empty or blank
  289. */
  290. private static String getCellTextValue(HSSFRow r, int colIndex, String columnName) {
  291. if(r == null) {
  292. return null;
  293. }
  294. HSSFCell cell = r.getCell(colIndex);
  295. if(cell == null) {
  296. return null;
  297. }
  298. if(cell.getCellType() == HSSFCell.CELL_TYPE_BLANK) {
  299. return null;
  300. }
  301. if(cell.getCellType() == HSSFCell.CELL_TYPE_STRING) {
  302. return cell.getRichStringCellValue().getString();
  303. }
  304. throw new RuntimeException("Bad cell type for '" + columnName + "' column: ("
  305. + cell.getCellType() + ") row (" + (r.getRowNum() +1) + ")");
  306. }
  307. }