nl.hsac.fitnesse.fixture.fit.MapColumnFixture Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hsac-fitnesse-fixtures Show documentation
Show all versions of hsac-fitnesse-fixtures Show documentation
Fixtures to assist in testing via FitNesse
package nl.hsac.fitnesse.fixture.fit;
import fit.Binding;
import fit.Fixture;
import fit.Parse;
import fit.exception.FitFailureException;
import nl.hsac.fitnesse.fixture.util.HttpResponse;
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Base class for Fixtures that use a Map to store the set column values,
* instead of fields.
*/
public class MapColumnFixture extends OurColumnFixture {
private final Map currentRowValues = new HashMap();
public static final String DEFAULT_ARRAY_SEPARATOR = ",";
private static final String REGEX_STRING_WITH_SYMBOL = "(.*?)(\\$\\[)(.*?)(\\])(.*)"; // (Lazy) match randomText$[symbol]...
private static final Pattern STRING_WITH_SYMBOL_PATTERN = Pattern.compile(REGEX_STRING_WITH_SYMBOL);
@Override
public void reset() {
currentRowValues.clear();
setDefaults(getCurrentRowValues());
}
/**
* Adds new key/value pairs to currentRowValues based on table supplied. If value in first column is present,
* and there is no value for key of second column, the value in second column is added, and vice versa.
* The first row describes the key for each column's values.
* @param translationTableWithHeader table describing equivalent key/value pairs.
*/
public void translateFromTable(String[][] translationTableWithHeader) {
translateFromTable(getCurrentRowValues(), translationTableWithHeader);
}
/**
* Adds new key/value pairs to rowValues based on table supplied. If value in first column is present,
* and there is no value for key of second column, the value in second column is added, and vice versa.
* The first row describes the key for each column's values.
* @param rowValues row to add values to.
* @param translationTableWithHeader table describing equivalent key/value pairs.
*/
public static void translateFromTable(Map rowValues, String[][] translationTableWithHeader) {
translateFromTable(rowValues, translationTableWithHeader, 0, 1);
translateFromTable(rowValues, translationTableWithHeader, 1, 0);
}
private static void translateFromTable(Map rowValues, String[][] translationTableWithHeader,
int fromColumn, int toColumn) {
String fromKey = translationTableWithHeader[0][fromColumn];
String toKey = translationTableWithHeader[0][toColumn];
if (rowValues.get(toKey) == null) {
Object valuePresent = rowValues.get(fromKey);
String toValue = findToValue(translationTableWithHeader, fromColumn, toColumn, valuePresent);
if (toValue != null) {
rowValues.put(toKey, toValue);
}
}
}
private static String findToValue(String[][] translationTableWithHeader, int fromColumn, int toColumn, Object fromValue) {
String toValue = null;
for (int i = 1; i < translationTableWithHeader.length; i++) { // row at i==0 contains headers
String[] row = translationTableWithHeader[i];
if (row[fromColumn].equals(fromValue)) {
toValue = row[toColumn];
break;
}
}
return toValue;
}
/**
* Replaces values found in the currentRowValues based on the supplied table.
* Cell 0,1 defines the key to work with.
* Other rows define a replacement value: if value in first column is found
* it is replaced with the one from the second.
* @param leftAcceptedForRight table describing replacements.
*/
public void acceptAlternativeInputValuesFromTable(String[][] leftAcceptedForRight) {
acceptAlternativeInputValuesFromTable(getCurrentRowValues(), leftAcceptedForRight);
}
/**
* Replaces values found in the rowValues based on the supplied table.
* Cell 0,1 defines the key to work with.
* Other rows define a replacement value: if value in first column is found
* it is replaced with the one from the second.
* @param rowValues values to replace in.
* @param leftAcceptedForRight table describing replacements.
*/
public static void acceptAlternativeInputValuesFromTable(Map rowValues, String[][] leftAcceptedForRight) {
String key = leftAcceptedForRight[0][1];
Object valuePresent = rowValues.get(key);
String translation = findToValue(leftAcceptedForRight, 0, 1, valuePresent);
if (translation != null) {
rowValues.put(key, translation);
}
}
/**
* Places default values (if any) before each row.
*
* @param values
* map to put defaults in.
*/
protected void setDefaults(Map values) {
}
/**
* @param columnName
* column to get current row's value of
* @return value for specified column.
*/
public Object get(String columnName) {
return getCurrentRowValues().get(columnName);
}
/**
* @param columnName
* column to get current row's value of
* @return value for specified column.
*/
@SuppressWarnings("unchecked")
public Map getNested(String columnName) {
Object value = get(columnName);
if (value == null) {
throw new IllegalArgumentException("No value for: " + columnName);
}
if (!(value instanceof Map)) {
throw new IllegalArgumentException("Value for: " + columnName + " is not a Map, but a: " + value.getClass());
}
return (Map) value;
}
/**
* @param columnName
* column to get current row's value of
* @return value for specified column split by its spaces and optional whitespace.
*/
protected List getList(String columnName) {
Object value = get(columnName);
if (value == null) {
throw new IllegalArgumentException("No value for: " + columnName);
}
return Arrays.asList(value.toString().split("\\s*,\\s*"));
}
@Override
protected Binding createBinding(int column, Parse heads) throws Throwable {
Binding result;
String header = heads.text();
if (header.endsWith("=")) {
result = new ParameterBinding(header.substring(0, header.length() - 1));
} else if (header.endsWith("?")) {
// use default Fixture behavior
result = originalCreateBinding(column, heads);
if (result instanceof Binding.SaveBinding) {
// we want our own SaveBinding that makes anchors
Binding saveBinding = new SaveBinding();
saveBinding.adapter = result.adapter;
result = saveBinding;
}
} else if (header.endsWith("?$")) { // compare return with value in symbol
String originalBody = heads.body; // Save originalBody so we can show it in testresult page
heads.body = header.substring(0, header.length()-1); // Remove the $ so originalCreateBinding will create a Binding.QueryBinding
result = originalCreateBinding(column, heads); // use originalCreateBinding so we can set the adapter
if (result instanceof Binding.QueryBinding) {
// We want to compare value in symbol with return value and made our own QueryBinding to do so.
Binding queryBinding = new QueryBinding();
queryBinding.adapter = result.adapter; // I think adapter is necessary so it can see if values matches or not
result = queryBinding;
}
heads.body = originalBody;
} else {
return new MapBinding(header);
}
return result;
}
protected final Binding originalCreateBinding(int column, Parse heads) throws Throwable {
return super.createBinding(column, heads);
}
protected class SaveBinding extends Binding {
@Override
public void doCell(Fixture fixture, Parse cell) {
try {
executeIfNeeded();
Object valueObj = getValue();
String symbolValue = String.valueOf(valueObj);
String symbolName = cell.text();
if (valueObj instanceof Object[]) {
// Store return value as array.
Fixture.setSymbol(symbolName, valueObj);
symbolValue = Arrays.toString((Object[]) valueObj);
} else {
Fixture.setSymbol(symbolName, symbolValue);
}
cell.addToBody(Fixture.gray(" = " + symbolValue + ""));
} catch (Exception e) {
handleException(fixture, cell, e);
}
}
/**
* @return value to store.
* @throws Exception
* if value could not be retrieved
*/
protected Object getValue() throws Exception {
return adapter.get(); // ...might be validly null
}
}
public class ParameterBinding extends Binding {
private final String header;
public ParameterBinding(String headerName) {
header = headerName;
}
@Override
public void doCell(Fixture fixture, Parse cell) throws Throwable {
String symbolName = cell.text();
if (!"".equals(symbolName)) {
String[] path = symbolName.split("\\.");
String[] items = symbolName.split("\\s*,\\s*");
if (items.length > 1 || !Fixture.hasSymbol(path[0])) {
String result = null;
for (String name : items) {
String[] nestedPath = name.split("\\.");
if (Fixture.hasSymbol(nestedPath[0])) {
String symbolValue = (String) Fixture.getSymbol(nestedPath[0]);
if (nestedPath.length > 1) {
symbolValue = unmarshallParamValue(nestedPath, symbolValue).toString();
}
if (result == null) {
result = symbolValue;
cell.addToBody(" = ");
} else {
result = result + DEFAULT_ARRAY_SEPARATOR + symbolValue;
cell.addToBody(", ");
}
cell.addToBody(paramHRef(nestedPath[0], symbolValue));
} else { //symbol not found
fixture.exception(cell, new FitFailureException("No such symbol: " + symbolName));
}
}
getCurrentRowValues().put(header, result);
} else {
Object symbol = Fixture.getSymbol(path[0]);
String valueString = "";
Object value = null;
if (symbol instanceof String) {
valueString = (String) Fixture.getSymbol(path[0]);
if (!"null".equals(valueString)) {
value = unmarshallParamValue(path, valueString);
}
} else if (symbol instanceof Object[]) {
// Stored value is array.
if (path.length > 1) {
int index = getIndexFromSymbolArray(path);
value = getSymbolArrayValue(symbol, index);
valueString = (String) value;
} else {
value = (symbol);
valueString = Arrays.toString((Object[]) value);
}
}
getCurrentRowValues().put(header, value);
cell.addToBody(" = " + paramHRef(symbolName, valueString));
}
}
}
private String paramHRef(String symbolName, String value) {
return Fixture.gray("" + value + "");
}
private Object unmarshallParamValue(String[] paramNamePath, String valueString) {
Object value;
MapParameter mapValue = MapParameter.parse(valueString);
if (mapValue != null) {
if (paramNamePath.length == 1) {
value = mapValue;
} else {
String nestedKey = paramNamePath[1];
value = mapValue.get(nestedKey);
}
} else {
HttpResponse resp = HttpResponse.parse(valueString);
if (resp != null) {
value = resp;
} else {
value = valueString;
}
}
return value;
}
}
private class MapBinding extends Binding {
private final String header;
public MapBinding(String headerName) {
header = headerName;
}
@Override
public void doCell(Fixture aFixture, Parse aCell) throws Throwable {
String text = aCell.text();
if ("null".equals(text) || "".equals(text)) {
text = null;
} else if ("blank".equals(text)) {
text = "";
}
currentRowValues.put(header, text);
}
}
private class QueryBinding extends Binding.QueryBinding {
/**
* Special behavior: assumes the celltext is the name of a symbol and the return value of the column must be compared with the symbol value.
* The special fitnesse values 'blank', 'null', '' are handled in the standard way though.
*/
@Override
public void doCell(Fixture aFixture, Parse aCell) {
String originalCellText = aCell.text();
String newText = null;
String extraCellText = "";
try {
if (!isSpecialBlankValueForFitnesse(originalCellText)) { // don't interfere with standard special behavior
// Assume the text is a symbol name of an array of those
if (originalCellText.startsWith("Array[") && originalCellText.endsWith("]")) {
// array of symbols and/of strings
newText = originalCellText.substring(6, originalCellText.length());
String separator = getArraySeperator();
// match string $variable1,variable2] Note: ',' can be another array_seperator
Pattern pattern = Pattern.compile(String.format("\\$(.*?)(%s|])", separator));
Matcher matcher = pattern.matcher(newText);
while (matcher.find()) {
// replace all symbol entries by values
String symbolName = matcher.group();
int seperatorLength = matcher.group(2).length();
// replace the $variable name with value of getSymbolValue(variable_name)
newText = newText.replace(symbolName.substring(0, symbolName.length() - seperatorLength),
getSymbolValue(symbolName.substring(1, symbolName.length() - seperatorLength)));
}
newText = newText.substring(0, newText.length() - 1);
} else {
newText = resolveStringWithSymbols(originalCellText);
if (newText == null){
// single element, we assume the text is a symbol
newText = getSymbolValue(originalCellText);
}
}
aCell.body = newText;
extraCellText = String.format(" (%s)", originalCellText);
}
super.doCell(aFixture, aCell);
aCell.addToBody(extraCellText);
} catch (NoSuchSymbolException e) {
aCell.body = e.getMessage();
aFixture.wrong(aCell);
}
}
}
/**
* Functionality to compare returning cell value with a dynamic (with symbols) text.
* See also HsacExamples.FitTests.ArraysAndSymbolsComparison#Compare result with string containing a symbol
* @param originalCellText, ie randomText$[symbol1]MoreText where symbol1=ValueOfSymbol1
* @return originalCellText with the symbols resolved to values, ie: randomTextValueOfSymbol1MoreText
* or null if the originalCellText doesn't contains text with symbols
* @throws NoSuchSymbolException if the used symbol(s) don't exist
*/
private String resolveStringWithSymbols(String originalCellText) throws NoSuchSymbolException {
Matcher m = STRING_WITH_SYMBOL_PATTERN.matcher(originalCellText);
String newText = null;
while (m.matches()) {
String symbolValue = getSymbolValue(m.group(3));
newText = m.group(1) + symbolValue + m.group(5);
m = STRING_WITH_SYMBOL_PATTERN.matcher(newText);
}
return newText;
}
private String getSymbolValue(String originalSymbolName) throws NoSuchSymbolException {
String[] cellContent = null;
String symbolName = null;
String newText = null;
if (originalSymbolName.contains(".")) {
cellContent = StringUtils.split(originalSymbolName, '.');
symbolName= cellContent[0];
} else {
symbolName = originalSymbolName;
}
Object symbolValue = Fixture.getSymbol(symbolName);
if (symbolValue instanceof Object[]) {
// cell text is element of array
int arrayIndex = getIndexFromSymbolArray(cellContent);
newText = (String) getSymbolArrayValue(symbolValue, arrayIndex);
} else if (cellContent != null && cellContent.length == 2) {
// cell text is nested object
newText = (String) symbolValue;
MapParameter mapValue = MapParameter.parse(newText);
if (mapValue != null) {
newText = mapValue.get(cellContent[1]).toString();
}
} else {
newText = (String) (symbolValue);
}
if (newText == null) {
throw new NoSuchSymbolException(String.format("No value for symbol '%s' found.", originalSymbolName));
}
return newText;
}
private void handleException(Fixture fixture, Parse cell, Exception e) {
if (cell.text().isEmpty()) {
cell.addToBody(gray("error"));
} else {
fixture.exception(cell, e);
}
}
private class NoSuchSymbolException extends Exception {
private static final long serialVersionUID = 1L;
public NoSuchSymbolException(String message) {
super(message);
}
}
/**
* Gets fixture parameter value by parameter key match.
* Parameter should be defined in format =.
* @param paramKey parameter key
* @param defaultValue default parameter value
* @return the fixture parameter value
*/
private String getParameter(String paramKey, String defaultValue) {
String paramValue = defaultValue;
if (StringUtils.isNotBlank(paramKey) && args != null && args.length > 0) {
for (String arg : args) {
String[] parameter = arg.split("=");
if (parameter != null && parameter.length == 2 && paramKey.equals(parameter[0])) {
paramValue = parameter[1];
break;
}
}
}
return paramValue;
}
/**
* Fetch the array separator, which has a default value if not configured.
* @return the array seperator
*/
public String getArraySeperator() {
return getParameter("ARRAY_SEPARATOR", DEFAULT_ARRAY_SEPARATOR);
}
/**
* When cellContent contains a symbol that is an array, the specific element can be addressed
* in the cell. However the index doesn't start at 0 but on 1 to make it more readable.
* @param cellContent symbol of array with specification of element: ie arrayname.1 stands for arrayname[0]
* @return the java index of the array (arrayname.1 returns 0, arrayname.2 return 1, etc)
*/
private int getIndexFromSymbolArray(String[] cellContent) {
int index = Integer.valueOf(cellContent[1]) - 1;
return index;
}
/**
* Fetch the value from arraySymbol on specified index.
* @param arraySymbol symbol from Fixture that is array
* @param index to find element from array
* @return the element from the symbol array
*/
private Object getSymbolArrayValue(Object arraySymbol, int index) {
Object result = null;
if (index > -1 && index < ((Object[]) arraySymbol).length) {
result = ((Object[]) arraySymbol)[index];
}
return result;
}
boolean isSpecialBlankValueForFitnesse(String text) {
if ("".equals(text)) {
return true;
} else if ("blank".equalsIgnoreCase(text)) {
return true;
} else if ("null".equalsIgnoreCase(text)) {
return true;
}
return false;
}
/**
* @return the currentRowValues
*/
public Map getCurrentRowValues() {
return currentRowValues;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy