PageRenderTime 26ms CodeModel.GetById 35ms RepoModel.GetById 0ms app.codeStats 0ms

/src/fitnesse/testsystems/slim/tables/QueryTable.java

http://github.com/unclebob/fitnesse
Java | 365 lines | 299 code | 64 blank | 2 comment | 59 complexity | 454a4ff52891025bc964d3ae5b3ad073 MD5 | raw file
Possible License(s): BSD-2-Clause, BSD-3-Clause, GPL-2.0
  1. // Copyright (C) 2003-2009 by Object Mentor, Inc. All rights reserved.
  2. // Released under the terms of the CPL Common Public License version 1.0.
  3. package fitnesse.testsystems.slim.tables;
  4. import java.util.*;
  5. import fitnesse.testsystems.ExecutionResult;
  6. import fitnesse.testsystems.TestResult;
  7. import fitnesse.testsystems.slim.SlimTestContext;
  8. import fitnesse.testsystems.slim.Table;
  9. import fitnesse.testsystems.slim.results.SlimExceptionResult;
  10. import fitnesse.testsystems.slim.results.SlimTestResult;
  11. import fitnesse.util.StringUtils;
  12. public class QueryTable extends SlimTable {
  13. private static final String COMMENT_COLUMN_MARKER = "#";
  14. protected List<String> fieldNames = new ArrayList<>();
  15. public QueryTable(Table table, String id, SlimTestContext testContext) {
  16. super(table, id, testContext);
  17. }
  18. @Override
  19. protected String getTableType() {
  20. return "queryTable";
  21. }
  22. public boolean matches(String actual, String expected) {
  23. if (actual == null || expected == null)
  24. return false;
  25. if (actual.equals(replaceSymbols(expected)))
  26. return true;
  27. Comparator c = new Comparator(actual, expected);
  28. return c.matches();
  29. }
  30. public SlimTestResult matchMessage(String actual, String expected) {
  31. if (actual == null)
  32. return SlimTestResult.fail("NULL");
  33. if (actual.equals(replaceSymbols(expected)))
  34. return SlimTestResult.pass(replaceSymbolsWithFullExpansion(expected));
  35. Comparator c = new Comparator(actual, expected);
  36. return c.evaluate();
  37. }
  38. @Override
  39. public List<SlimAssertion> getAssertions() throws SyntaxError {
  40. if (table.getRowCount() < 2)
  41. throw new SyntaxError("Query tables must have at least two rows.");
  42. assignColumns();
  43. SlimAssertion make = constructFixture(getFixtureName());
  44. SlimAssertion ti = makeAssertion(callFunction(getTableName(), "table", tableAsList()),
  45. new SilentReturnExpectation(0, 0));
  46. SlimAssertion qi = makeAssertion(callFunction(getTableName(), "query"),
  47. new QueryTableExpectation());
  48. return Arrays.asList(make, ti, qi);
  49. }
  50. private void assignColumns() {
  51. int cols = table.getColumnCountInRow(1);
  52. for (int col = 0; col < cols; col++)
  53. fieldNames.add(table.getCellContents(col, 1));
  54. }
  55. public class QueryTableExpectation implements SlimExpectation {
  56. @Override
  57. public TestResult evaluateExpectation(Object queryReturn) {
  58. SlimTestResult testResult;
  59. if (queryReturn == null) {
  60. testResult = SlimTestResult.testNotRun();
  61. } else if (queryReturn instanceof List) {
  62. testResult = new SlimTestResult(scanRowsForMatches((List<List<List<Object>>>) queryReturn));
  63. testResult.setVariables(getSymbolsToStore());
  64. } else {
  65. testResult = SlimTestResult.error(String.format("The query method returned: %s", queryReturn));
  66. table.updateContent(0, 0, testResult);
  67. getTestContext().increment(testResult.getExecutionResult());
  68. }
  69. return testResult;
  70. }
  71. @Override
  72. public SlimExceptionResult evaluateException(SlimExceptionResult exceptionResult) {
  73. table.updateContent(0, 0, exceptionResult);
  74. getTestContext().incrementErroredTestsCount();
  75. return exceptionResult;
  76. }
  77. }
  78. private ExecutionResult scanRowsForMatches(List<List<List<Object>>> queryResultList) {
  79. final QueryResults queryResults = new QueryResults(queryResultList);
  80. Collection<MatchedResult> potentialMatches = queryResults.scorePotentialMatches();
  81. List<MatchedResult> potentialMatchesByScore = new ArrayList<>(potentialMatches);
  82. Collections.sort(potentialMatchesByScore, MatchedResult.compareByScore());
  83. return markRows(queryResults, potentialMatchesByScore);
  84. }
  85. protected ExecutionResult markRows(QueryResults queryResults, Iterable<MatchedResult> potentialMatchesByScore) {
  86. List<Integer> unmatchedTableRows = unmatchedRows(table.getRowCount());
  87. unmatchedTableRows.remove(Integer.valueOf(0));
  88. unmatchedTableRows.remove(Integer.valueOf(1));
  89. List<Integer> unmatchedResultRows = unmatchedRows(queryResults.getRows().size());
  90. markMatchedRows(queryResults, potentialMatchesByScore, unmatchedTableRows, unmatchedResultRows);
  91. markMissingRows(unmatchedTableRows);
  92. markSurplusRows(queryResults, unmatchedResultRows);
  93. return !unmatchedTableRows.isEmpty() || !unmatchedResultRows.isEmpty() ? ExecutionResult.FAIL : ExecutionResult.PASS;
  94. }
  95. protected void markMatchedRows(QueryResults queryResults, Iterable<MatchedResult> potentialMatchesByScore, List<Integer> unmatchedTableRows, List<Integer> unmatchedResultRows) {
  96. while (!isEmpty(potentialMatchesByScore)) {
  97. MatchedResult bestMatch = takeBestMatch(potentialMatchesByScore);
  98. markFieldsInMatchedRow(bestMatch.tableRow, bestMatch.resultRow, queryResults);
  99. unmatchedTableRows.remove(bestMatch.tableRow);
  100. unmatchedResultRows.remove(bestMatch.resultRow);
  101. }
  102. }
  103. protected MatchedResult takeBestMatch(Iterable<MatchedResult> potentialMatchesByScore) {
  104. MatchedResult bestResult = potentialMatchesByScore.iterator().next();
  105. removeOtherwiseMatchedResults(potentialMatchesByScore, bestResult);
  106. return bestResult;
  107. }
  108. protected boolean isEmpty(Iterable<MatchedResult> iterable) {
  109. return !iterable.iterator().hasNext();
  110. }
  111. protected void removeOtherwiseMatchedResults(Iterable<MatchedResult> potentialMatchesByScore, MatchedResult bestResult) {
  112. Iterator<MatchedResult> iterator = potentialMatchesByScore.iterator();
  113. while (iterator.hasNext()) {
  114. MatchedResult otherResult = iterator.next();
  115. if (otherResult.tableRow.equals(bestResult.tableRow) || otherResult.resultRow.equals(bestResult.resultRow))
  116. iterator.remove();
  117. }
  118. }
  119. protected List<Integer> unmatchedRows(int rowCount) {
  120. List<Integer> result = new ArrayList<>(rowCount);
  121. for (int i = 0; i < rowCount; i++) {
  122. result.add(i);
  123. }
  124. return result;
  125. }
  126. protected void markMissingRows(List<Integer> missingRows) {
  127. for (int missingRow : missingRows) {
  128. markMissingRow(missingRow);
  129. }
  130. }
  131. protected void markMissingRow(int missingRow) {
  132. replaceAllvariablesInRow(missingRow);
  133. SlimTestResult testResult = SlimTestResult.fail(null, table.getCellContents(0, missingRow), "missing");
  134. table.updateContent(0, missingRow, testResult);
  135. getTestContext().increment(testResult.getExecutionResult());
  136. }
  137. protected void markSurplusRows(final QueryResults queryResults, List<Integer> unmatchedRows) {
  138. for (int unmatchedRow : unmatchedRows) {
  139. List<String> surplusRow = queryResults.getList(fieldNames, unmatchedRow);
  140. int newTableRow = table.addRow(surplusRow);
  141. SlimTestResult testResult = SlimTestResult.fail(surplusRow.get(0), null, "surplus");
  142. table.updateContent(0, newTableRow, testResult);
  143. getTestContext().increment(ExecutionResult.FAIL);
  144. markMissingFields(surplusRow, newTableRow);
  145. }
  146. }
  147. private void markMissingFields(List<String> surplusRow, int newTableRow) {
  148. for (int col = 0; col < surplusRow.size(); col++) {
  149. String surplusField = surplusRow.get(col);
  150. if (surplusField == null) {
  151. String fieldName = fieldNames.get(col);
  152. SlimTestResult testResult = SlimTestResult.fail(String.format("field %s not present", fieldName));
  153. table.updateContent(col, newTableRow, testResult);
  154. getTestContext().increment(testResult.getExecutionResult());
  155. }
  156. }
  157. }
  158. protected void replaceAllvariablesInRow(int tableRow) {
  159. int columns = table.getColumnCountInRow(tableRow);
  160. for (int col = 0; col < columns; col++) {
  161. String contents = table.getCellContents(col, tableRow);
  162. table.substitute(col, tableRow, replaceSymbolsWithFullExpansion(contents));
  163. }
  164. }
  165. protected void markFieldsInMatchedRow(int tableRow, int matchedRow, QueryResults queryResults) {
  166. int columns = table.getColumnCountInRow(tableRow);
  167. for (int col = 0; col < columns; col++) {
  168. markField(tableRow, matchedRow, col, queryResults);
  169. }
  170. }
  171. protected TestResult markField(int tableRow, int matchedRow, int col, QueryResults queryResults) {
  172. if (col >= fieldNames.size())
  173. return null; // ignore strange table geometry.
  174. String fieldName = fieldNames.get(col);
  175. String actualValue = queryResults.getCell(fieldName, matchedRow);
  176. String expectedValue = table.getCellContents(col, tableRow);
  177. SlimTestResult testResult;
  178. if (fieldName.startsWith(COMMENT_COLUMN_MARKER))
  179. testResult = SlimTestResult.plain();
  180. else if (actualValue == null)
  181. testResult = SlimTestResult.fail(String.format("field %s not present", fieldName), expectedValue);
  182. else if (expectedValue == null || expectedValue.isEmpty())
  183. testResult = SlimTestResult.ignore(actualValue);
  184. else {
  185. String symbolName = isSymbolAssignment(expectedValue);
  186. if (symbolName != null) {
  187. setSymbol(symbolName, actualValue, true);
  188. testResult = SlimTestResult.ignore(String.format("$%s<-[%s]", symbolName, actualValue));
  189. } else {
  190. testResult = matchMessage(actualValue, expectedValue);
  191. if (testResult == null)
  192. testResult = SlimTestResult.fail(actualValue, replaceSymbolsWithFullExpansion(expectedValue));
  193. else if (testResult.getExecutionResult() == ExecutionResult.PASS)
  194. testResult = markMatch(tableRow, matchedRow, col, testResult.getMessage());
  195. }
  196. }
  197. table.updateContent(col, tableRow, testResult);
  198. getTestContext().increment(testResult.getExecutionResult());
  199. return testResult;
  200. }
  201. protected SlimTestResult markMatch(int tableRow, int matchedRow, int col, String message) {
  202. return SlimTestResult.pass(message);
  203. }
  204. protected class QueryResults {
  205. private List<QueryResultRow> rows = new ArrayList<>();
  206. public QueryResults(List<List<List<Object>>> queryResultTable) {
  207. for (int i = 0; i < queryResultTable.size(); i++) {
  208. rows.add(new QueryResultRow(i, queryResultTable.get(i)));
  209. }
  210. rows = Collections.unmodifiableList(rows);
  211. }
  212. public Collection<MatchedResult> scorePotentialMatches() {
  213. Collection<MatchedResult> result = new ArrayList<>();
  214. int rows = table.getRowCount();
  215. for (int tableRow = 2; tableRow < rows; tableRow++)
  216. result.addAll(new QueryMatcher(fieldNames).scoreMatches(tableRow));
  217. return result;
  218. }
  219. public List<String> getList(List<String> fieldNames, int row) {
  220. List<String> result = new ArrayList<>();
  221. for (String name : fieldNames)
  222. result.add(rows.get(row).get(name));
  223. return result;
  224. }
  225. public String getCell(String name, int row) {
  226. return rows.get(row).get(name);
  227. }
  228. public List<QueryResultRow> getRows() {
  229. return rows;
  230. }
  231. private class QueryMatcher {
  232. private final List<String> fields;
  233. private QueryMatcher(List<String> fields) {
  234. this.fields = fields;
  235. }
  236. public Collection<MatchedResult> scoreMatches(int tableRow) {
  237. Collection<MatchedResult> result = new ArrayList<>();
  238. for (QueryResultRow row : rows) {
  239. MatchedResult match = scoreMatch(table, tableRow, row);
  240. if (match.score > 0)
  241. result.add(match);
  242. }
  243. return result;
  244. }
  245. private MatchedResult scoreMatch(Table table, int tableRow, QueryResultRow row) {
  246. int score = 0;
  247. for (int fieldIndex = 0; fieldIndex < fields.size(); fieldIndex++) {
  248. String fieldName = fields.get(fieldIndex);
  249. if (!fieldName.startsWith(COMMENT_COLUMN_MARKER)) {
  250. String actualValue = row.get(fieldName);
  251. String expectedValue = table.getCellContents(fieldIndex, tableRow);
  252. if(isSymbolAssignment(expectedValue) != null) {
  253. continue;
  254. }
  255. if (matches(actualValue, expectedValue)) {
  256. score++;
  257. } else if (!StringUtils.isBlank(expectedValue)) {
  258. break;
  259. }
  260. }
  261. }
  262. return new MatchedResult(tableRow, row.index, score);
  263. }
  264. }
  265. private class QueryResultRow {
  266. private final int index;
  267. private final Map<String, String> values;
  268. public QueryResultRow(int index, List<List<Object>> values) {
  269. this.index = index;
  270. Map<String, String> rowMap = new HashMap<>();
  271. for (List<Object> columnPair : values) {
  272. String fieldName = (String) columnPair.get(0);
  273. String fieldValue = (String) columnPair.get(1);
  274. rowMap.put(fieldName, fieldValue);
  275. }
  276. this.values = rowMap;
  277. }
  278. public String get(String fieldName) {
  279. return values.get(fieldName);
  280. }
  281. }
  282. }
  283. protected static class MatchedResult {
  284. final Integer tableRow;
  285. final Integer resultRow;
  286. final int score;
  287. public MatchedResult(int tableRow, int resultRow, int score) {
  288. this.tableRow = tableRow;
  289. this.resultRow = resultRow;
  290. this.score = score;
  291. }
  292. public static java.util.Comparator<MatchedResult> compareByScore() {
  293. return new java.util.Comparator<MatchedResult>() {
  294. @Override
  295. public int compare(MatchedResult o1, MatchedResult o2) {
  296. return o2.score - o1.score;
  297. }
  298. };
  299. }
  300. }
  301. }