git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1457243 13f79535-47bb-0310-9956-ffa450edef68tags/3.10-beta1
* </p> | * </p> | ||||
* | * | ||||
* @author Josh Micich | * @author Josh Micich | ||||
* @author Cedric Walter at innoveo.com | |||||
*/ | */ | ||||
public final class Countif extends Fixed2ArgFunction { | public final class Countif extends Fixed2ArgFunction { | ||||
return false; | return false; | ||||
} | } | ||||
} | } | ||||
private static final class StringMatcher extends MatcherBase { | |||||
public static final class StringMatcher extends MatcherBase { | |||||
private final String _value; | private final String _value; | ||||
private final Pattern _pattern; | private final Pattern _pattern; | ||||
* Translates Excel countif wildcard strings into java regex strings | * Translates Excel countif wildcard strings into java regex strings | ||||
* @return <code>null</code> if the specified value contains no special wildcard characters. | * @return <code>null</code> if the specified value contains no special wildcard characters. | ||||
*/ | */ | ||||
private static Pattern getWildCardPattern(String value) { | |||||
public static Pattern getWildCardPattern(String value) { | |||||
int len = value.length(); | int len = value.length(); | ||||
StringBuffer sb = new StringBuffer(len); | StringBuffer sb = new StringBuffer(len); | ||||
boolean hasWildCard = false; | boolean hasWildCard = false; | ||||
for(int i=0; i<len; i++) { | for(int i=0; i<len; i++) { | ||||
char ch = value.charAt(i); | char ch = value.charAt(i); | ||||
switch(ch) { | switch(ch) { | ||||
case '?': | |||||
case '?': //Any single character | |||||
hasWildCard = true; | hasWildCard = true; | ||||
// match exactly one character | // match exactly one character | ||||
sb.append('.'); | sb.append('.'); | ||||
continue; | continue; | ||||
case '*': | |||||
case '*': //Zero or more characters | |||||
hasWildCard = true; | hasWildCard = true; | ||||
// match one or more occurrences of any character | // match one or more occurrences of any character | ||||
sb.append(".*"); | sb.append(".*"); |
import org.apache.poi.ss.formula.eval.ValueEval; | import org.apache.poi.ss.formula.eval.ValueEval; | ||||
import org.apache.poi.ss.formula.TwoDEval; | import org.apache.poi.ss.formula.TwoDEval; | ||||
import java.util.regex.Matcher; | |||||
import java.util.regex.Pattern; | |||||
/** | /** | ||||
* Common functionality used by VLOOKUP, HLOOKUP, LOOKUP and MATCH | * Common functionality used by VLOOKUP, HLOOKUP, LOOKUP and MATCH | ||||
* | * | ||||
* @author Josh Micich | * @author Josh Micich | ||||
* @author Cedric Walter at innoveo.com | |||||
*/ | */ | ||||
final class LookupUtils { | final class LookupUtils { | ||||
return EQUAL; | return EQUAL; | ||||
} | } | ||||
public static final CompareResult valueOf(boolean matches) { | |||||
if(matches) { | |||||
return EQUAL ; | |||||
} | |||||
return LESS_THAN; | |||||
} | |||||
public boolean isTypeMismatch() { | public boolean isTypeMismatch() { | ||||
return _isTypeMismatch; | return _isTypeMismatch; | ||||
} | } | ||||
protected abstract String getValueAsString(); | protected abstract String getValueAsString(); | ||||
} | } | ||||
private static final class StringLookupComparer extends LookupValueComparerBase { | |||||
private String _value; | |||||
protected StringLookupComparer(StringEval se) { | |||||
private static final class StringLookupComparer extends LookupValueComparerBase { | |||||
private String _value; | |||||
private final Pattern _wildCardPattern; | |||||
private boolean _matchExact; | |||||
private boolean _isMatchFunction; | |||||
protected StringLookupComparer(StringEval se, boolean matchExact, boolean isMatchFunction) { | |||||
super(se); | super(se); | ||||
_value = se.getStringValue(); | _value = se.getStringValue(); | ||||
_wildCardPattern = Countif.StringMatcher.getWildCardPattern(_value); | |||||
_matchExact = matchExact; | |||||
_isMatchFunction = isMatchFunction; | |||||
} | } | ||||
protected CompareResult compareSameType(ValueEval other) { | protected CompareResult compareSameType(ValueEval other) { | ||||
StringEval se = (StringEval) other; | |||||
return CompareResult.valueOf(_value.compareToIgnoreCase(se.getStringValue())); | |||||
StringEval se = (StringEval) other; | |||||
String stringValue = se.getStringValue(); | |||||
if (_wildCardPattern != null) { | |||||
Matcher matcher = _wildCardPattern.matcher(stringValue); | |||||
boolean matches = matcher.matches(); | |||||
if (_isMatchFunction || | |||||
!_matchExact) { | |||||
return CompareResult.valueOf(matches); | |||||
} | |||||
} | |||||
return CompareResult.valueOf(_value.compareToIgnoreCase(stringValue)); | |||||
} | } | ||||
protected String getValueAsString() { | protected String getValueAsString() { | ||||
return _value; | return _value; | ||||
} | } | ||||
public static int lookupIndexOfValue(ValueEval lookupValue, ValueVector vector, boolean isRangeLookup) throws EvaluationException { | public static int lookupIndexOfValue(ValueEval lookupValue, ValueVector vector, boolean isRangeLookup) throws EvaluationException { | ||||
LookupValueComparer lookupComparer = createLookupComparer(lookupValue); | |||||
LookupValueComparer lookupComparer = createLookupComparer(lookupValue, isRangeLookup, false); | |||||
int result; | int result; | ||||
if(isRangeLookup) { | if(isRangeLookup) { | ||||
result = performBinarySearch(vector, lookupComparer); | result = performBinarySearch(vector, lookupComparer); | ||||
/** | /** | ||||
* Finds first (lowest index) exact occurrence of specified value. | * Finds first (lowest index) exact occurrence of specified value. | ||||
* @param lookupValue the value to be found in column or row vector | |||||
* @param lookupComparer the value to be found in column or row vector | |||||
* @param vector the values to be searched. For VLOOKUP this is the first column of the | * @param vector the values to be searched. For VLOOKUP this is the first column of the | ||||
* tableArray. For HLOOKUP this is the first row of the tableArray. | * tableArray. For HLOOKUP this is the first row of the tableArray. | ||||
* @return zero based index into the vector, -1 if value cannot be found | * @return zero based index into the vector, -1 if value cannot be found | ||||
return maxIx - 1; | return maxIx - 1; | ||||
} | } | ||||
public static LookupValueComparer createLookupComparer(ValueEval lookupValue) { | |||||
public static LookupValueComparer createLookupComparer(ValueEval lookupValue, boolean matchExact, boolean isMatchFunction) { | |||||
if (lookupValue == BlankEval.instance) { | if (lookupValue == BlankEval.instance) { | ||||
// blank eval translates to zero | // blank eval translates to zero | ||||
return new NumberLookupComparer(NumberEval.ZERO); | return new NumberLookupComparer(NumberEval.ZERO); | ||||
} | } | ||||
if (lookupValue instanceof StringEval) { | if (lookupValue instanceof StringEval) { | ||||
return new StringLookupComparer((StringEval) lookupValue); | |||||
//TODO eventually here return a WildcardStringLookupComparer | |||||
return new StringLookupComparer((StringEval) lookupValue, matchExact, isMatchFunction); | |||||
} | } | ||||
if (lookupValue instanceof NumberEval) { | if (lookupValue instanceof NumberEval) { | ||||
return new NumberLookupComparer((NumberEval) lookupValue); | return new NumberLookupComparer((NumberEval) lookupValue); |
* | * | ||||
* | * | ||||
* @author Josh Micich | * @author Josh Micich | ||||
* @author Cedric Walter at innoveo.com | |||||
*/ | */ | ||||
public final class Match extends Var2or3ArgFunction { | public final class Match extends Var2or3ArgFunction { | ||||
} | } | ||||
private static LookupValueComparer createLookupComparer(ValueEval lookupValue, boolean matchExact) { | private static LookupValueComparer createLookupComparer(ValueEval lookupValue, boolean matchExact) { | ||||
if (matchExact && lookupValue instanceof StringEval) { | |||||
String stringValue = ((StringEval) lookupValue).getStringValue(); | |||||
if(isLookupValueWild(stringValue)) { | |||||
throw new RuntimeException("Wildcard lookup values '" + stringValue + "' not supported yet"); | |||||
} | |||||
} | |||||
return LookupUtils.createLookupComparer(lookupValue); | |||||
return LookupUtils.createLookupComparer(lookupValue, matchExact, true); | |||||
} | } | ||||
private static boolean isLookupValueWild(String stringValue) { | private static boolean isLookupValueWild(String stringValue) { |
* the lookup_value. If FALSE, only exact matches will be considered<br/> | * the lookup_value. If FALSE, only exact matches will be considered<br/> | ||||
* | * | ||||
* @author Josh Micich | * @author Josh Micich | ||||
* @author Cedric Walter at innoveo.com | |||||
*/ | */ | ||||
public final class Vlookup extends Var3or4ArgFunction { | public final class Vlookup extends Var3or4ArgFunction { | ||||
private static final ValueEval DEFAULT_ARG3 = BoolEval.TRUE; | private static final ValueEval DEFAULT_ARG3 = BoolEval.TRUE; | ||||
return evaluate(srcRowIndex, srcColumnIndex, arg0, arg1, arg2, DEFAULT_ARG3); | return evaluate(srcRowIndex, srcColumnIndex, arg0, arg1, arg2, DEFAULT_ARG3); | ||||
} | } | ||||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, | |||||
ValueEval arg2, ValueEval arg3) { | |||||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval lookup_value, ValueEval table_array, | |||||
ValueEval col_index, ValueEval range_lookup) { | |||||
try { | try { | ||||
// Evaluation order: | // Evaluation order: | ||||
// arg0 lookup_value, arg1 table_array, arg3 range_lookup, find lookup value, arg2 col_index, fetch result | |||||
ValueEval lookupValue = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex); | |||||
TwoDEval tableArray = LookupUtils.resolveTableArrayArg(arg1); | |||||
boolean isRangeLookup = LookupUtils.resolveRangeLookupArg(arg3, srcRowIndex, srcColumnIndex); | |||||
// lookup_value , table_array, range_lookup, find lookup value, col_index, fetch result | |||||
ValueEval lookupValue = OperandResolver.getSingleValue(lookup_value, srcRowIndex, srcColumnIndex); | |||||
TwoDEval tableArray = LookupUtils.resolveTableArrayArg(table_array); | |||||
boolean isRangeLookup = LookupUtils.resolveRangeLookupArg(range_lookup, srcRowIndex, srcColumnIndex); | |||||
int rowIndex = LookupUtils.lookupIndexOfValue(lookupValue, LookupUtils.createColumnVector(tableArray, 0), isRangeLookup); | int rowIndex = LookupUtils.lookupIndexOfValue(lookupValue, LookupUtils.createColumnVector(tableArray, 0), isRangeLookup); | ||||
int colIndex = LookupUtils.resolveRowOrColIndexArg(arg2, srcRowIndex, srcColumnIndex); | |||||
int colIndex = LookupUtils.resolveRowOrColIndexArg(col_index, srcRowIndex, srcColumnIndex); | |||||
ValueVector resultCol = createResultColumnVector(tableArray, colIndex); | ValueVector resultCol = createResultColumnVector(tableArray, colIndex); | ||||
return resultCol.getItem(rowIndex); | return resultCol.getItem(rowIndex); | ||||
} catch (EvaluationException e) { | } catch (EvaluationException e) { |
/* ==================================================================== | |||||
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.functions; | |||||
import java.io.PrintStream; | |||||
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.ErrorEval; | |||||
import org.apache.poi.hssf.usermodel.HSSFCell; | |||||
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; | |||||
import org.apache.poi.hssf.usermodel.HSSFRow; | |||||
import org.apache.poi.hssf.usermodel.HSSFSheet; | |||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook; | |||||
import org.apache.poi.hssf.util.CellReference; | |||||
import org.apache.poi.ss.usermodel.CellValue; | |||||
/** | |||||
* @author Josh Micich | |||||
* @author Cedric Walter at innoveo.com | |||||
*/ | |||||
public abstract class BaseTestFunctionsFromSpreadsheet extends TestCase { | |||||
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) */ | |||||
/** Name of the first sheet in the spreadsheet (contains comments) */ | |||||
public final static String README_SHEET_NAME = "Read Me"; | |||||
/** Row (zero-based) in each sheet where the evaluation cases start. */ | |||||
public static final int START_TEST_CASES_ROW_INDEX = 4; // Row '5' | |||||
/** Index of the column that contains the function names */ | |||||
public static final int COLUMN_INDEX_MARKER = 0; // Column 'A' | |||||
public static final int COLUMN_INDEX_EVALUATION = 1; // Column 'B' | |||||
public static final int COLUMN_INDEX_EXPECTED_RESULT = 2; // Column 'C' | |||||
public static final int COLUMN_ROW_COMMENT = 3; // Column 'D' | |||||
/** Used to indicate when there are no more test cases on the current sheet */ | |||||
public static final String TEST_CASES_END_MARKER = "<end>"; | |||||
/** Used to indicate that the test on the current row should be ignored */ | |||||
public static final String SKIP_CURRENT_TEST_CASE_MARKER = "<skip>"; | |||||
} | |||||
// Note - multiple failures are aggregated before ending. | |||||
// If one or more functions fail, a single AssertionFailedError is thrown at the end | |||||
private int _sheetFailureCount; | |||||
private int _sheetSuccessCount; | |||||
private int _evaluationFailureCount; | |||||
private int _evaluationSuccessCount; | |||||
private static void confirmExpectedResult(String msg, HSSFCell 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"); | |||||
} | |||||
if(expected.getCellType() == HSSFCell.CELL_TYPE_ERROR) { | |||||
confirmErrorResult(msg, expected.getErrorCellValue(), actual); | |||||
return; | |||||
} | |||||
if(actual.getCellType() == HSSFCell.CELL_TYPE_ERROR) { | |||||
throw unexpectedError(msg, expected, actual.getErrorValue()); | |||||
} | |||||
if(actual.getCellType() != expected.getCellType()) { | |||||
throw wrongTypeError(msg, expected, actual); | |||||
} | |||||
switch (expected.getCellType()) { | |||||
case HSSFCell.CELL_TYPE_BOOLEAN: | |||||
assertEquals(msg, expected.getBooleanCellValue(), actual.getBooleanValue()); | |||||
break; | |||||
case HSSFCell.CELL_TYPE_FORMULA: // will never be used, since we will call method after formula evaluation | |||||
throw new IllegalStateException("Cannot expect formula as result of formula evaluation: " + msg); | |||||
case HSSFCell.CELL_TYPE_NUMERIC: | |||||
assertEquals(expected.getNumericCellValue(), actual.getNumberValue(), 0.0); | |||||
break; | |||||
case HSSFCell.CELL_TYPE_STRING: | |||||
assertEquals(msg, expected.getRichStringCellValue().getString(), actual.getStringValue()); | |||||
break; | |||||
} | |||||
} | |||||
private static AssertionFailedError wrongTypeError(String msgPrefix, HSSFCell expectedCell, CellValue actualValue) { | |||||
return new AssertionFailedError(msgPrefix + " Result type mismatch. Evaluated result was " | |||||
+ actualValue.formatAsString() | |||||
+ " but the expected result was " | |||||
+ formatValue(expectedCell) | |||||
); | |||||
} | |||||
private static AssertionFailedError unexpectedError(String msgPrefix, HSSFCell expected, int actualErrorCode) { | |||||
return new AssertionFailedError(msgPrefix + " Error code (" | |||||
+ ErrorEval.getText(actualErrorCode) | |||||
+ ") was evaluated, but the expected result was " | |||||
+ formatValue(expected) | |||||
); | |||||
} | |||||
private static void confirmErrorResult(String msgPrefix, int expectedErrorCode, CellValue actual) { | |||||
if(actual.getCellType() != HSSFCell.CELL_TYPE_ERROR) { | |||||
throw new AssertionFailedError(msgPrefix + " Expected cell error (" | |||||
+ ErrorEval.getText(expectedErrorCode) + ") but actual value was " | |||||
+ actual.formatAsString()); | |||||
} | |||||
if(expectedErrorCode != actual.getErrorValue()) { | |||||
throw new AssertionFailedError(msgPrefix + " Expected cell error code (" | |||||
+ ErrorEval.getText(expectedErrorCode) | |||||
+ ") but actual error code was (" | |||||
+ ErrorEval.getText(actual.getErrorValue()) | |||||
+ ")"); | |||||
} | |||||
} | |||||
private static String formatValue(HSSFCell expecedCell) { | |||||
switch (expecedCell.getCellType()) { | |||||
case HSSFCell.CELL_TYPE_BLANK: return "<blank>"; | |||||
case HSSFCell.CELL_TYPE_BOOLEAN: return String.valueOf(expecedCell.getBooleanCellValue()); | |||||
case HSSFCell.CELL_TYPE_NUMERIC: return String.valueOf(expecedCell.getNumericCellValue()); | |||||
case HSSFCell.CELL_TYPE_STRING: return expecedCell.getRichStringCellValue().getString(); | |||||
} | |||||
throw new RuntimeException("Unexpected cell type of expected value (" + expecedCell.getCellType() + ")"); | |||||
} | |||||
protected void setUp() { | |||||
_sheetFailureCount = 0; | |||||
_sheetSuccessCount = 0; | |||||
_evaluationFailureCount = 0; | |||||
_evaluationSuccessCount = 0; | |||||
} | |||||
public void testFunctionsFromTestSpreadsheet() { | |||||
HSSFWorkbook workbook = HSSFTestDataSamples.openSampleWorkbook(this.getFilename()); | |||||
confirmReadMeSheet(workbook); | |||||
int nSheets = workbook.getNumberOfSheets(); | |||||
for(int i=1; i< nSheets; i++) { | |||||
int sheetResult = processTestSheet(workbook, i, workbook.getSheetName(i)); | |||||
switch(sheetResult) { | |||||
case Result.ALL_EVALUATIONS_SUCCEEDED: _sheetSuccessCount ++; break; | |||||
case Result.SOME_EVALUATIONS_FAILED: _sheetFailureCount ++; break; | |||||
} | |||||
} | |||||
// confirm results | |||||
String successMsg = "There were " | |||||
+ _sheetSuccessCount + " successful sheets(s) and " | |||||
+ _evaluationSuccessCount + " function(s) without error"; | |||||
if(_sheetFailureCount > 0) { | |||||
String msg = _sheetFailureCount + " sheets(s) failed with " | |||||
+ _evaluationFailureCount + " evaluation(s). " + successMsg; | |||||
throw new AssertionFailedError(msg); | |||||
} | |||||
if(false) { // normally no output for successful tests | |||||
System.out.println(getClass().getName() + ": " + successMsg); | |||||
} | |||||
} | |||||
protected abstract String getFilename(); | |||||
private int processTestSheet(HSSFWorkbook workbook, int sheetIndex, String sheetName) { | |||||
HSSFSheet sheet = workbook.getSheetAt(sheetIndex); | |||||
HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(workbook); | |||||
int maxRows = sheet.getLastRowNum()+1; | |||||
int result = Result.NO_EVALUATIONS_FOUND; // so far | |||||
String currentGroupComment = null; | |||||
for(int rowIndex=SS.START_TEST_CASES_ROW_INDEX; rowIndex<maxRows; rowIndex++) { | |||||
HSSFRow r = sheet.getRow(rowIndex); | |||||
String newMarkerValue = getMarkerColumnValue(r); | |||||
if(r == null) { | |||||
continue; | |||||
} | |||||
if(SS.TEST_CASES_END_MARKER.equalsIgnoreCase(newMarkerValue)) { | |||||
// normal exit point | |||||
return result; | |||||
} | |||||
if(SS.SKIP_CURRENT_TEST_CASE_MARKER.equalsIgnoreCase(newMarkerValue)) { | |||||
// currently disabled test case row | |||||
continue; | |||||
} | |||||
if(newMarkerValue != null) { | |||||
currentGroupComment = newMarkerValue; | |||||
} | |||||
HSSFCell c = r.getCell(SS.COLUMN_INDEX_EVALUATION); | |||||
if (c == null || c.getCellType() != HSSFCell.CELL_TYPE_FORMULA) { | |||||
continue; | |||||
} | |||||
CellValue actualValue = evaluator.evaluate(c); | |||||
HSSFCell expectedValueCell = r.getCell(SS.COLUMN_INDEX_EXPECTED_RESULT); | |||||
String rowComment = getRowCommentColumnValue(r); | |||||
String msgPrefix = formatTestCaseDetails(sheetName, r.getRowNum(), c, currentGroupComment, rowComment); | |||||
try { | |||||
confirmExpectedResult(msgPrefix, expectedValueCell, actualValue); | |||||
_evaluationSuccessCount ++; | |||||
if(result != Result.SOME_EVALUATIONS_FAILED) { | |||||
result = Result.ALL_EVALUATIONS_SUCCEEDED; | |||||
} | |||||
} catch (RuntimeException e) { | |||||
_evaluationFailureCount ++; | |||||
printShortStackTrace(System.err, e); | |||||
result = Result.SOME_EVALUATIONS_FAILED; | |||||
} catch (AssertionFailedError e) { | |||||
_evaluationFailureCount ++; | |||||
printShortStackTrace(System.err, e); | |||||
result = Result.SOME_EVALUATIONS_FAILED; | |||||
} | |||||
} | |||||
throw new RuntimeException("Missing end marker '" + SS.TEST_CASES_END_MARKER | |||||
+ "' on sheet '" + sheetName + "'"); | |||||
} | |||||
private static String formatTestCaseDetails(String sheetName, int rowIndex, HSSFCell c, String currentGroupComment, | |||||
String rowComment) { | |||||
StringBuffer sb = new StringBuffer(); | |||||
CellReference cr = new CellReference(sheetName, rowIndex, c.getColumnIndex(), false, false); | |||||
sb.append(cr.formatAsString()); | |||||
sb.append(" {=").append(c.getCellFormula()).append("}"); | |||||
if(currentGroupComment != null) { | |||||
sb.append(" '"); | |||||
sb.append(currentGroupComment); | |||||
if(rowComment != null) { | |||||
sb.append(" - "); | |||||
sb.append(rowComment); | |||||
} | |||||
sb.append("' "); | |||||
} else { | |||||
if(rowComment != null) { | |||||
sb.append(" '"); | |||||
sb.append(rowComment); | |||||
sb.append("' "); | |||||
} | |||||
} | |||||
return sb.toString(); | |||||
} | |||||
/** | |||||
* Asserts that the 'read me' comment page exists, and has this class' name in one of the | |||||
* cells. This back-link is to make it easy to find this class if a reader encounters the | |||||
* spreadsheet first. | |||||
*/ | |||||
private void confirmReadMeSheet(HSSFWorkbook workbook) { | |||||
String firstSheetName = workbook.getSheetName(0); | |||||
if(!firstSheetName.equalsIgnoreCase(SS.README_SHEET_NAME)) { | |||||
throw new RuntimeException("First sheet's name was '" + firstSheetName + "' but expected '" + SS.README_SHEET_NAME + "'"); | |||||
} | |||||
HSSFSheet sheet = workbook.getSheetAt(0); | |||||
String specifiedClassName = sheet.getRow(2).getCell(0).getRichStringCellValue().getString(); | |||||
assertEquals("Test class name in spreadsheet comment", getClass().getName(), specifiedClassName); | |||||
} | |||||
/** | |||||
* Useful to keep output concise when expecting many failures to be reported by this test case | |||||
*/ | |||||
private static void printShortStackTrace(PrintStream ps, Throwable 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()); | |||||
} | |||||
} | |||||
private static String getRowCommentColumnValue(HSSFRow r) { | |||||
return getCellTextValue(r, SS.COLUMN_ROW_COMMENT, "row comment"); | |||||
} | |||||
private static String getMarkerColumnValue(HSSFRow r) { | |||||
return getCellTextValue(r, SS.COLUMN_INDEX_MARKER, "marker"); | |||||
} | |||||
/** | |||||
* @return <code>null</code> if cell is missing, empty or blank | |||||
*/ | |||||
private static String getCellTextValue(HSSFRow r, int colIndex, String columnName) { | |||||
if(r == null) { | |||||
return null; | |||||
} | |||||
HSSFCell cell = r.getCell(colIndex); | |||||
if(cell == null) { | |||||
return null; | |||||
} | |||||
if(cell.getCellType() == HSSFCell.CELL_TYPE_BLANK) { | |||||
return null; | |||||
} | |||||
if(cell.getCellType() == HSSFCell.CELL_TYPE_STRING) { | |||||
return cell.getRichStringCellValue().getString(); | |||||
} | |||||
throw new RuntimeException("Bad cell type for '" + columnName + "' column: (" | |||||
+ cell.getCellType() + ") row (" + (r.getRowNum() +1) + ")"); | |||||
} | |||||
} |
* Test cases for COUNT(), COUNTA() COUNTIF(), COUNTBLANK() | * Test cases for COUNT(), COUNTA() COUNTIF(), COUNTBLANK() | ||||
* | * | ||||
* @author Josh Micich | * @author Josh Micich | ||||
* @author Cedric Walter at innoveo.com | |||||
*/ | */ | ||||
public final class TestCountFuncs extends TestCase { | public final class TestCountFuncs extends TestCase { | ||||
package org.apache.poi.ss.formula.functions; | package org.apache.poi.ss.formula.functions; | ||||
import java.io.PrintStream; | |||||
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.ErrorEval; | |||||
import org.apache.poi.hssf.usermodel.HSSFCell; | |||||
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; | |||||
import org.apache.poi.hssf.usermodel.HSSFRow; | |||||
import org.apache.poi.hssf.usermodel.HSSFSheet; | |||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook; | |||||
import org.apache.poi.hssf.util.CellReference; | |||||
import org.apache.poi.ss.usermodel.CellValue; | |||||
/** | /** | ||||
* Tests INDEX() as loaded from a test data spreadsheet.<p/> | * Tests INDEX() as loaded from a test data spreadsheet.<p/> | ||||
* | * | ||||
* @author Josh Micich | * @author Josh Micich | ||||
* @author Cedric Walter at innoveo.com | |||||
*/ | */ | ||||
public final class TestIndexFunctionFromSpreadsheet extends TestCase { | |||||
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 = "IndexFunctionTestCaseData.xls"; | |||||
public static final int COLUMN_INDEX_EVALUATION = 2; // Column 'C' | |||||
public static final int COLUMN_INDEX_EXPECTED_RESULT = 3; // Column 'D' | |||||
} | |||||
// Note - multiple failures are aggregated before ending. | |||||
// If one or more functions fail, a single AssertionFailedError is thrown at the end | |||||
private int _evaluationFailureCount; | |||||
private int _evaluationSuccessCount; | |||||
private static void confirmExpectedResult(String msg, HSSFCell 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"); | |||||
} | |||||
if(expected.getCellType() == HSSFCell.CELL_TYPE_ERROR) { | |||||
confirmErrorResult(msg, expected.getErrorCellValue(), actual); | |||||
return; | |||||
} | |||||
if(actual.getCellType() == HSSFCell.CELL_TYPE_ERROR) { | |||||
throw unexpectedError(msg, expected, actual.getErrorValue()); | |||||
} | |||||
if(actual.getCellType() != expected.getCellType()) { | |||||
throw wrongTypeError(msg, expected, actual); | |||||
} | |||||
switch (expected.getCellType()) { | |||||
case HSSFCell.CELL_TYPE_BOOLEAN: | |||||
assertEquals(msg, expected.getBooleanCellValue(), actual.getBooleanValue()); | |||||
break; | |||||
case HSSFCell.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 HSSFCell.CELL_TYPE_NUMERIC: | |||||
assertEquals(msg, expected.getNumericCellValue(), actual.getNumberValue(), 0.0); | |||||
break; | |||||
case HSSFCell.CELL_TYPE_STRING: | |||||
assertEquals(msg, expected.getRichStringCellValue().getString(), actual.getStringValue()); | |||||
break; | |||||
} | |||||
} | |||||
private static AssertionFailedError wrongTypeError(String msgPrefix, HSSFCell expectedCell, CellValue actualValue) { | |||||
return new AssertionFailedError(msgPrefix + " Result type mismatch. Evaluated result was " | |||||
+ actualValue.formatAsString() | |||||
+ " but the expected result was " | |||||
+ formatValue(expectedCell) | |||||
); | |||||
} | |||||
private static AssertionFailedError unexpectedError(String msgPrefix, HSSFCell expected, int actualErrorCode) { | |||||
return new AssertionFailedError(msgPrefix + " Error code (" | |||||
+ ErrorEval.getText(actualErrorCode) | |||||
+ ") was evaluated, but the expected result was " | |||||
+ formatValue(expected) | |||||
); | |||||
} | |||||
private static void confirmErrorResult(String msgPrefix, int expectedErrorCode, CellValue actual) { | |||||
if(actual.getCellType() != HSSFCell.CELL_TYPE_ERROR) { | |||||
throw new AssertionFailedError(msgPrefix + " Expected cell error (" | |||||
+ ErrorEval.getText(expectedErrorCode) + ") but actual value was " | |||||
+ actual.formatAsString()); | |||||
} | |||||
if(expectedErrorCode != actual.getErrorValue()) { | |||||
throw new AssertionFailedError(msgPrefix + " Expected cell error code (" | |||||
+ ErrorEval.getText(expectedErrorCode) | |||||
+ ") but actual error code was (" | |||||
+ ErrorEval.getText(actual.getErrorValue()) | |||||
+ ")"); | |||||
} | |||||
} | |||||
private static String formatValue(HSSFCell expecedCell) { | |||||
switch (expecedCell.getCellType()) { | |||||
case HSSFCell.CELL_TYPE_BLANK: return "<blank>"; | |||||
case HSSFCell.CELL_TYPE_BOOLEAN: return String.valueOf(expecedCell.getBooleanCellValue()); | |||||
case HSSFCell.CELL_TYPE_NUMERIC: return String.valueOf(expecedCell.getNumericCellValue()); | |||||
case HSSFCell.CELL_TYPE_STRING: return expecedCell.getRichStringCellValue().getString(); | |||||
} | |||||
throw new RuntimeException("Unexpected cell type of expected value (" + expecedCell.getCellType() + ")"); | |||||
} | |||||
protected void setUp() { | |||||
_evaluationFailureCount = 0; | |||||
_evaluationSuccessCount = 0; | |||||
} | |||||
public void testFunctionsFromTestSpreadsheet() { | |||||
HSSFWorkbook workbook = HSSFTestDataSamples.openSampleWorkbook(SS.FILENAME); | |||||
processTestSheet(workbook, workbook.getSheetName(0)); | |||||
// confirm results | |||||
String successMsg = "There were " | |||||
+ _evaluationSuccessCount + " function(s) without error"; | |||||
if(_evaluationFailureCount > 0) { | |||||
String msg = _evaluationFailureCount + " evaluation(s) failed. " + successMsg; | |||||
throw new AssertionFailedError(msg); | |||||
} | |||||
if(false) { // normally no output for successful tests | |||||
System.out.println(getClass().getName() + ": " + successMsg); | |||||
} | |||||
} | |||||
private void processTestSheet(HSSFWorkbook workbook, String sheetName) { | |||||
HSSFSheet sheet = workbook.getSheetAt(0); | |||||
HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(workbook); | |||||
int maxRows = sheet.getLastRowNum()+1; | |||||
int result = Result.NO_EVALUATIONS_FOUND; // so far | |||||
for(int rowIndex=0; rowIndex<maxRows; rowIndex++) { | |||||
HSSFRow r = sheet.getRow(rowIndex); | |||||
if(r == null) { | |||||
continue; | |||||
} | |||||
HSSFCell c = r.getCell(SS.COLUMN_INDEX_EVALUATION); | |||||
if (c == null || c.getCellType() != HSSFCell.CELL_TYPE_FORMULA) { | |||||
continue; | |||||
} | |||||
HSSFCell expectedValueCell = r.getCell(SS.COLUMN_INDEX_EXPECTED_RESULT); | |||||
String msgPrefix = formatTestCaseDetails(sheetName, r.getRowNum(), c); | |||||
try { | |||||
CellValue actualValue = evaluator.evaluate(c); | |||||
confirmExpectedResult(msgPrefix, expectedValueCell, actualValue); | |||||
_evaluationSuccessCount ++; | |||||
if(result != Result.SOME_EVALUATIONS_FAILED) { | |||||
result = Result.ALL_EVALUATIONS_SUCCEEDED; | |||||
} | |||||
} catch (RuntimeException e) { | |||||
_evaluationFailureCount ++; | |||||
printShortStackTrace(System.err, e, msgPrefix); | |||||
result = Result.SOME_EVALUATIONS_FAILED; | |||||
} catch (AssertionFailedError e) { | |||||
_evaluationFailureCount ++; | |||||
printShortStackTrace(System.err, e, msgPrefix); | |||||
result = Result.SOME_EVALUATIONS_FAILED; | |||||
} | |||||
} | |||||
} | |||||
private static String formatTestCaseDetails(String sheetName, int rowIndex, HSSFCell c) { | |||||
StringBuffer sb = new StringBuffer(); | |||||
CellReference cr = new CellReference(sheetName, rowIndex, c.getColumnIndex(), false, false); | |||||
sb.append(cr.formatAsString()); | |||||
sb.append(" [formula: ").append(c.getCellFormula()).append(" ]"); | |||||
return sb.toString(); | |||||
} | |||||
/** | |||||
* Useful to keep output concise when expecting many failures to be reported by this test case | |||||
*/ | |||||
private static void printShortStackTrace(PrintStream ps, Throwable e, String msgPrefix) { | |||||
System.err.println("Problem with " + msgPrefix); | |||||
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()); | |||||
} | |||||
} | |||||
} | |||||
public final class TestIndexFunctionFromSpreadsheet extends BaseTestFunctionsFromSpreadsheet { | |||||
@Override | |||||
protected String getFilename() { | |||||
return "IndexFunctionTestCaseData.xls"; | |||||
} | |||||
} |
package org.apache.poi.ss.formula.functions; | package org.apache.poi.ss.formula.functions; | ||||
import java.io.PrintStream; | |||||
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.ErrorEval; | |||||
import org.apache.poi.hssf.usermodel.HSSFCell; | |||||
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; | |||||
import org.apache.poi.hssf.usermodel.HSSFRow; | |||||
import org.apache.poi.hssf.usermodel.HSSFSheet; | |||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook; | |||||
import org.apache.poi.hssf.util.CellReference; | |||||
import org.apache.poi.ss.usermodel.CellValue; | |||||
/** | /** | ||||
* Tests lookup functions (VLOOKUP, HLOOKUP, LOOKUP, MATCH) as loaded from a test data spreadsheet.<p/> | * Tests lookup functions (VLOOKUP, HLOOKUP, LOOKUP, MATCH) as loaded from a test data spreadsheet.<p/> | ||||
* more easily. | * more easily. | ||||
* | * | ||||
* @author Josh Micich | * @author Josh Micich | ||||
* @author Cedric Walter at innoveo.com | |||||
*/ | */ | ||||
public final class TestLookupFunctionsFromSpreadsheet extends TestCase { | |||||
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 = "LookupFunctionsTestCaseData.xls"; | |||||
/** Name of the first sheet in the spreadsheet (contains comments) */ | |||||
public final static String README_SHEET_NAME = "Read Me"; | |||||
/** Row (zero-based) in each sheet where the evaluation cases start. */ | |||||
public static final int START_TEST_CASES_ROW_INDEX = 4; // Row '5' | |||||
/** Index of the column that contains the function names */ | |||||
public static final int COLUMN_INDEX_MARKER = 0; // Column 'A' | |||||
public static final int COLUMN_INDEX_EVALUATION = 1; // Column 'B' | |||||
public static final int COLUMN_INDEX_EXPECTED_RESULT = 2; // Column 'C' | |||||
public static final int COLUMN_ROW_COMMENT = 3; // Column 'D' | |||||
/** Used to indicate when there are no more test cases on the current sheet */ | |||||
public static final String TEST_CASES_END_MARKER = "<end>"; | |||||
/** Used to indicate that the test on the current row should be ignored */ | |||||
public static final String SKIP_CURRENT_TEST_CASE_MARKER = "<skip>"; | |||||
} | |||||
// Note - multiple failures are aggregated before ending. | |||||
// If one or more functions fail, a single AssertionFailedError is thrown at the end | |||||
private int _sheetFailureCount; | |||||
private int _sheetSuccessCount; | |||||
private int _evaluationFailureCount; | |||||
private int _evaluationSuccessCount; | |||||
private static void confirmExpectedResult(String msg, HSSFCell 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"); | |||||
} | |||||
if(expected.getCellType() == HSSFCell.CELL_TYPE_ERROR) { | |||||
confirmErrorResult(msg, expected.getErrorCellValue(), actual); | |||||
return; | |||||
} | |||||
if(actual.getCellType() == HSSFCell.CELL_TYPE_ERROR) { | |||||
throw unexpectedError(msg, expected, actual.getErrorValue()); | |||||
} | |||||
if(actual.getCellType() != expected.getCellType()) { | |||||
throw wrongTypeError(msg, expected, actual); | |||||
} | |||||
switch (expected.getCellType()) { | |||||
case HSSFCell.CELL_TYPE_BOOLEAN: | |||||
assertEquals(msg, expected.getBooleanCellValue(), actual.getBooleanValue()); | |||||
break; | |||||
case HSSFCell.CELL_TYPE_FORMULA: // will never be used, since we will call method after formula evaluation | |||||
throw new IllegalStateException("Cannot expect formula as result of formula evaluation: " + msg); | |||||
case HSSFCell.CELL_TYPE_NUMERIC: | |||||
assertEquals(expected.getNumericCellValue(), actual.getNumberValue(), 0.0); | |||||
break; | |||||
case HSSFCell.CELL_TYPE_STRING: | |||||
assertEquals(msg, expected.getRichStringCellValue().getString(), actual.getStringValue()); | |||||
break; | |||||
} | |||||
} | |||||
private static AssertionFailedError wrongTypeError(String msgPrefix, HSSFCell expectedCell, CellValue actualValue) { | |||||
return new AssertionFailedError(msgPrefix + " Result type mismatch. Evaluated result was " | |||||
+ actualValue.formatAsString() | |||||
+ " but the expected result was " | |||||
+ formatValue(expectedCell) | |||||
); | |||||
} | |||||
private static AssertionFailedError unexpectedError(String msgPrefix, HSSFCell expected, int actualErrorCode) { | |||||
return new AssertionFailedError(msgPrefix + " Error code (" | |||||
+ ErrorEval.getText(actualErrorCode) | |||||
+ ") was evaluated, but the expected result was " | |||||
+ formatValue(expected) | |||||
); | |||||
} | |||||
private static void confirmErrorResult(String msgPrefix, int expectedErrorCode, CellValue actual) { | |||||
if(actual.getCellType() != HSSFCell.CELL_TYPE_ERROR) { | |||||
throw new AssertionFailedError(msgPrefix + " Expected cell error (" | |||||
+ ErrorEval.getText(expectedErrorCode) + ") but actual value was " | |||||
+ actual.formatAsString()); | |||||
} | |||||
if(expectedErrorCode != actual.getErrorValue()) { | |||||
throw new AssertionFailedError(msgPrefix + " Expected cell error code (" | |||||
+ ErrorEval.getText(expectedErrorCode) | |||||
+ ") but actual error code was (" | |||||
+ ErrorEval.getText(actual.getErrorValue()) | |||||
+ ")"); | |||||
} | |||||
} | |||||
private static String formatValue(HSSFCell expecedCell) { | |||||
switch (expecedCell.getCellType()) { | |||||
case HSSFCell.CELL_TYPE_BLANK: return "<blank>"; | |||||
case HSSFCell.CELL_TYPE_BOOLEAN: return String.valueOf(expecedCell.getBooleanCellValue()); | |||||
case HSSFCell.CELL_TYPE_NUMERIC: return String.valueOf(expecedCell.getNumericCellValue()); | |||||
case HSSFCell.CELL_TYPE_STRING: return expecedCell.getRichStringCellValue().getString(); | |||||
} | |||||
throw new RuntimeException("Unexpected cell type of expected value (" + expecedCell.getCellType() + ")"); | |||||
} | |||||
protected void setUp() { | |||||
_sheetFailureCount = 0; | |||||
_sheetSuccessCount = 0; | |||||
_evaluationFailureCount = 0; | |||||
_evaluationSuccessCount = 0; | |||||
} | |||||
public void testFunctionsFromTestSpreadsheet() { | |||||
HSSFWorkbook workbook = HSSFTestDataSamples.openSampleWorkbook(SS.FILENAME); | |||||
confirmReadMeSheet(workbook); | |||||
int nSheets = workbook.getNumberOfSheets(); | |||||
for(int i=1; i< nSheets; i++) { | |||||
int sheetResult = processTestSheet(workbook, i, workbook.getSheetName(i)); | |||||
switch(sheetResult) { | |||||
case Result.ALL_EVALUATIONS_SUCCEEDED: _sheetSuccessCount ++; break; | |||||
case Result.SOME_EVALUATIONS_FAILED: _sheetFailureCount ++; break; | |||||
} | |||||
} | |||||
// confirm results | |||||
String successMsg = "There were " | |||||
+ _sheetSuccessCount + " successful sheets(s) and " | |||||
+ _evaluationSuccessCount + " function(s) without error"; | |||||
if(_sheetFailureCount > 0) { | |||||
String msg = _sheetFailureCount + " sheets(s) failed with " | |||||
+ _evaluationFailureCount + " evaluation(s). " + successMsg; | |||||
throw new AssertionFailedError(msg); | |||||
} | |||||
if(false) { // normally no output for successful tests | |||||
System.out.println(getClass().getName() + ": " + successMsg); | |||||
} | |||||
} | |||||
private int processTestSheet(HSSFWorkbook workbook, int sheetIndex, String sheetName) { | |||||
HSSFSheet sheet = workbook.getSheetAt(sheetIndex); | |||||
HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(workbook); | |||||
int maxRows = sheet.getLastRowNum()+1; | |||||
int result = Result.NO_EVALUATIONS_FOUND; // so far | |||||
String currentGroupComment = null; | |||||
for(int rowIndex=SS.START_TEST_CASES_ROW_INDEX; rowIndex<maxRows; rowIndex++) { | |||||
HSSFRow r = sheet.getRow(rowIndex); | |||||
String newMarkerValue = getMarkerColumnValue(r); | |||||
if(r == null) { | |||||
continue; | |||||
} | |||||
if(SS.TEST_CASES_END_MARKER.equalsIgnoreCase(newMarkerValue)) { | |||||
// normal exit point | |||||
return result; | |||||
} | |||||
if(SS.SKIP_CURRENT_TEST_CASE_MARKER.equalsIgnoreCase(newMarkerValue)) { | |||||
// currently disabled test case row | |||||
continue; | |||||
} | |||||
if(newMarkerValue != null) { | |||||
currentGroupComment = newMarkerValue; | |||||
} | |||||
HSSFCell c = r.getCell(SS.COLUMN_INDEX_EVALUATION); | |||||
if (c == null || c.getCellType() != HSSFCell.CELL_TYPE_FORMULA) { | |||||
continue; | |||||
} | |||||
CellValue actualValue = evaluator.evaluate(c); | |||||
HSSFCell expectedValueCell = r.getCell(SS.COLUMN_INDEX_EXPECTED_RESULT); | |||||
String rowComment = getRowCommentColumnValue(r); | |||||
String msgPrefix = formatTestCaseDetails(sheetName, r.getRowNum(), c, currentGroupComment, rowComment); | |||||
try { | |||||
confirmExpectedResult(msgPrefix, expectedValueCell, actualValue); | |||||
_evaluationSuccessCount ++; | |||||
if(result != Result.SOME_EVALUATIONS_FAILED) { | |||||
result = Result.ALL_EVALUATIONS_SUCCEEDED; | |||||
} | |||||
} catch (RuntimeException e) { | |||||
_evaluationFailureCount ++; | |||||
printShortStackTrace(System.err, e); | |||||
result = Result.SOME_EVALUATIONS_FAILED; | |||||
} catch (AssertionFailedError e) { | |||||
_evaluationFailureCount ++; | |||||
printShortStackTrace(System.err, e); | |||||
result = Result.SOME_EVALUATIONS_FAILED; | |||||
} | |||||
} | |||||
throw new RuntimeException("Missing end marker '" + SS.TEST_CASES_END_MARKER | |||||
+ "' on sheet '" + sheetName + "'"); | |||||
} | |||||
private static String formatTestCaseDetails(String sheetName, int rowIndex, HSSFCell c, String currentGroupComment, | |||||
String rowComment) { | |||||
StringBuffer sb = new StringBuffer(); | |||||
CellReference cr = new CellReference(sheetName, rowIndex, c.getColumnIndex(), false, false); | |||||
sb.append(cr.formatAsString()); | |||||
sb.append(" {=").append(c.getCellFormula()).append("}"); | |||||
if(currentGroupComment != null) { | |||||
sb.append(" '"); | |||||
sb.append(currentGroupComment); | |||||
if(rowComment != null) { | |||||
sb.append(" - "); | |||||
sb.append(rowComment); | |||||
} | |||||
sb.append("' "); | |||||
} else { | |||||
if(rowComment != null) { | |||||
sb.append(" '"); | |||||
sb.append(rowComment); | |||||
sb.append("' "); | |||||
} | |||||
} | |||||
return sb.toString(); | |||||
} | |||||
/** | |||||
* Asserts that the 'read me' comment page exists, and has this class' name in one of the | |||||
* cells. This back-link is to make it easy to find this class if a reader encounters the | |||||
* spreadsheet first. | |||||
*/ | |||||
private void confirmReadMeSheet(HSSFWorkbook workbook) { | |||||
String firstSheetName = workbook.getSheetName(0); | |||||
if(!firstSheetName.equalsIgnoreCase(SS.README_SHEET_NAME)) { | |||||
throw new RuntimeException("First sheet's name was '" + firstSheetName + "' but expected '" + SS.README_SHEET_NAME + "'"); | |||||
} | |||||
HSSFSheet sheet = workbook.getSheetAt(0); | |||||
String specifiedClassName = sheet.getRow(2).getCell(0).getRichStringCellValue().getString(); | |||||
assertEquals("Test class name in spreadsheet comment", getClass().getName(), specifiedClassName); | |||||
} | |||||
/** | |||||
* Useful to keep output concise when expecting many failures to be reported by this test case | |||||
*/ | |||||
private static void printShortStackTrace(PrintStream ps, Throwable 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()); | |||||
} | |||||
} | |||||
private static String getRowCommentColumnValue(HSSFRow r) { | |||||
return getCellTextValue(r, SS.COLUMN_ROW_COMMENT, "row comment"); | |||||
} | |||||
private static String getMarkerColumnValue(HSSFRow r) { | |||||
return getCellTextValue(r, SS.COLUMN_INDEX_MARKER, "marker"); | |||||
} | |||||
/** | |||||
* @return <code>null</code> if cell is missing, empty or blank | |||||
*/ | |||||
private static String getCellTextValue(HSSFRow r, int colIndex, String columnName) { | |||||
if(r == null) { | |||||
return null; | |||||
} | |||||
HSSFCell cell = r.getCell(colIndex); | |||||
if(cell == null) { | |||||
return null; | |||||
} | |||||
if(cell.getCellType() == HSSFCell.CELL_TYPE_BLANK) { | |||||
return null; | |||||
} | |||||
if(cell.getCellType() == HSSFCell.CELL_TYPE_STRING) { | |||||
return cell.getRichStringCellValue().getString(); | |||||
} | |||||
public final class TestLookupFunctionsFromSpreadsheet extends BaseTestFunctionsFromSpreadsheet { | |||||
throw new RuntimeException("Bad cell type for '" + columnName + "' column: (" | |||||
+ cell.getCellType() + ") row (" + (r.getRowNum() +1) + ")"); | |||||
} | |||||
@Override | |||||
protected String getFilename() { | |||||
return "LookupFunctionsTestCaseData.xls"; | |||||
} | |||||
} | } |
import junit.framework.TestCase; | import junit.framework.TestCase; | ||||
import org.apache.poi.hssf.HSSFTestDataSamples; | |||||
import org.apache.poi.hssf.usermodel.*; | |||||
import org.apache.poi.ss.formula.eval.AreaEval; | import org.apache.poi.ss.formula.eval.AreaEval; | ||||
import org.apache.poi.ss.formula.eval.BoolEval; | import org.apache.poi.ss.formula.eval.BoolEval; | ||||
import org.apache.poi.ss.formula.eval.ErrorEval; | import org.apache.poi.ss.formula.eval.ErrorEval; | ||||
import org.apache.poi.ss.formula.eval.NumericValueEval; | import org.apache.poi.ss.formula.eval.NumericValueEval; | ||||
import org.apache.poi.ss.formula.eval.StringEval; | import org.apache.poi.ss.formula.eval.StringEval; | ||||
import org.apache.poi.ss.formula.eval.ValueEval; | import org.apache.poi.ss.formula.eval.ValueEval; | ||||
import org.apache.poi.ss.usermodel.CellValue; | |||||
/** | /** | ||||
* Test cases for MATCH() | * Test cases for MATCH() | ||||
* | * | ||||
* @author Josh Micich | * @author Josh Micich | ||||
* @author Cedric Walter at innoveo.com | |||||
*/ | */ | ||||
public final class TestMatch extends TestCase { | public final class TestMatch extends TestCase { | ||||
/** less than or equal to */ | /** less than or equal to */ | ||||
} | } | ||||
public void testSimpleString() { | public void testSimpleString() { | ||||
// Arrange | |||||
ValueEval[] values = { | ValueEval[] values = { | ||||
new StringEval("Albert"), | new StringEval("Albert"), | ||||
new StringEval("Charles"), | new StringEval("Charles"), | ||||
confirmInt(3, invokeMatch(new StringEval("eD"), ae, MATCH_LARGEST_LTE)); | confirmInt(3, invokeMatch(new StringEval("eD"), ae, MATCH_LARGEST_LTE)); | ||||
confirmInt(3, invokeMatch(new StringEval("Ed"), ae, MATCH_EXACT)); | confirmInt(3, invokeMatch(new StringEval("Ed"), ae, MATCH_EXACT)); | ||||
confirmInt(3, invokeMatch(new StringEval("ed"), ae, MATCH_EXACT)); | confirmInt(3, invokeMatch(new StringEval("ed"), ae, MATCH_EXACT)); | ||||
confirmInt(4, invokeMatch(new StringEval("Hugh"), ae, MATCH_LARGEST_LTE)); | |||||
assertEquals(ErrorEval.NA, invokeMatch(new StringEval("Hugh"), ae, MATCH_EXACT)); | assertEquals(ErrorEval.NA, invokeMatch(new StringEval("Hugh"), ae, MATCH_EXACT)); | ||||
} | } | ||||
public void testSimpleWildcardValuesString() { | |||||
// Arrange | |||||
ValueEval[] values = { | |||||
new StringEval("Albert"), | |||||
new StringEval("Charles"), | |||||
new StringEval("Ed"), | |||||
new StringEval("Greg"), | |||||
new StringEval("Ian"), | |||||
}; | |||||
AreaEval ae = EvalFactory.createAreaEval("A1:A5", values); | |||||
// Note String comparisons are case insensitive | |||||
confirmInt(3, invokeMatch(new StringEval("e*"), ae, MATCH_EXACT)); | |||||
confirmInt(3, invokeMatch(new StringEval("*d"), ae, MATCH_EXACT)); | |||||
confirmInt(1, invokeMatch(new StringEval("Al*"), ae, MATCH_EXACT)); | |||||
confirmInt(2, invokeMatch(new StringEval("Char*"), ae, MATCH_EXACT)); | |||||
confirmInt(4, invokeMatch(new StringEval("*eg"), ae, MATCH_EXACT)); | |||||
confirmInt(4, invokeMatch(new StringEval("G?eg"), ae, MATCH_EXACT)); | |||||
confirmInt(4, invokeMatch(new StringEval("??eg"), ae, MATCH_EXACT)); | |||||
confirmInt(4, invokeMatch(new StringEval("G*?eg"), ae, MATCH_EXACT)); | |||||
confirmInt(4, invokeMatch(new StringEval("Hugh"), ae, MATCH_LARGEST_LTE)); | |||||
confirmInt(5, invokeMatch(new StringEval("*Ian*"), ae, MATCH_EXACT)); | |||||
confirmInt(5, invokeMatch(new StringEval("*Ian*"), ae, MATCH_LARGEST_LTE)); | |||||
} | |||||
public void testTildeString() { | |||||
ValueEval[] values = { | |||||
new StringEval("what?"), | |||||
new StringEval("all*") | |||||
}; | |||||
AreaEval ae = EvalFactory.createAreaEval("A1:A2", values); | |||||
confirmInt(1, invokeMatch(new StringEval("what~?"), ae, MATCH_EXACT)); | |||||
confirmInt(2, invokeMatch(new StringEval("all~*"), ae, MATCH_EXACT)); | |||||
} | |||||
public void testSimpleBoolean() { | public void testSimpleBoolean() { | ||||
ValueEval[] values = { | ValueEval[] values = { | ||||
confirmInt(3, invokeMatch(new NumberEval(5), ae, MATCH_EXACT)); | confirmInt(3, invokeMatch(new NumberEval(5), ae, MATCH_EXACT)); | ||||
confirmInt(8, invokeMatch(new StringEval("CHARLES"), ae, MATCH_EXACT)); | confirmInt(8, invokeMatch(new StringEval("CHARLES"), ae, MATCH_EXACT)); | ||||
//wildcard values | |||||
confirmInt(8, invokeMatch(new StringEval("CHAR*"), ae, MATCH_EXACT)); | |||||
confirmInt(8, invokeMatch(new StringEval("*CHARLES"), ae, MATCH_EXACT)); | |||||
confirmInt(4, invokeMatch(new StringEval("Ben"), ae, MATCH_LARGEST_LTE)); | confirmInt(4, invokeMatch(new StringEval("Ben"), ae, MATCH_LARGEST_LTE)); | ||||
confirmInt(13, invokeMatch(new StringEval("ED"), ae, MATCH_LARGEST_LTE)); | confirmInt(13, invokeMatch(new StringEval("ED"), ae, MATCH_LARGEST_LTE)); | ||||
confirmInt(13, invokeMatch(new StringEval("ED*"), ae, MATCH_LARGEST_LTE)); | |||||
confirmInt(13, invokeMatch(new StringEval("*ED"), ae, MATCH_LARGEST_LTE)); | |||||
confirmInt(9, invokeMatch(new StringEval("ED"), ae, MATCH_EXACT)); | confirmInt(9, invokeMatch(new StringEval("ED"), ae, MATCH_EXACT)); | ||||
confirmInt(9, invokeMatch(new StringEval("ED*"), ae, MATCH_EXACT)); | |||||
confirmInt(13, invokeMatch(new StringEval("Hugh"), ae, MATCH_LARGEST_LTE)); | confirmInt(13, invokeMatch(new StringEval("Hugh"), ae, MATCH_LARGEST_LTE)); | ||||
assertEquals(ErrorEval.NA, invokeMatch(new StringEval("Hugh"), ae, MATCH_EXACT)); | assertEquals(ErrorEval.NA, invokeMatch(new StringEval("Hugh"), ae, MATCH_EXACT)); |
/* ==================================================================== | |||||
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.functions; | |||||
/** | |||||
* Tests lookup functions (VLOOKUP, HLOOKUP, LOOKUP, MATCH) as loaded from a test data spreadsheet.<p/> | |||||
* These tests have been separated from the common function and operator tests because the lookup | |||||
* functions have more complex test cases and test data setup. | |||||
* | |||||
* Tests for bug fixes and specific/tricky behaviour can be found in the corresponding test class | |||||
* (<tt>TestXxxx</tt>) of the target (<tt>Xxxx</tt>) implementor, where execution can be observed | |||||
* more easily. | |||||
* | |||||
* @author Josh Micich | |||||
* @author Cedric Walter at innoveo.com | |||||
*/ | |||||
public final class TestMatchFunctionsFromSpreadsheet extends BaseTestFunctionsFromSpreadsheet { | |||||
@Override | |||||
protected String getFilename() { | |||||
return "MatchFunctionTestCaseData.xls"; | |||||
} | |||||
} |