/* ******************************************************************************* * Copyright (C) 2015, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* */ package com.ibm.icu.dev.test.format; import java.io.BufferedReader; import java.io.IOException; import java.util.ArrayList; import java.util.List; import com.ibm.icu.dev.test.TestFmwk; import com.ibm.icu.dev.test.TestUtil; import com.ibm.icu.impl.Utility; /** * A collection of methods to run the data driven number format test suite. */ public class DataDrivenNumberFormatTestSuite extends TestFmwk { /** * Base class for code under test. */ public static abstract class CodeUnderTest { /** * Returns the ID of the code under test. This ID is used to identify * tests that are known to fail for this particular code under test. * This implementation returns null which means that by default all * tests should work with this code under test. * @return 'J' means ICU4J, 'K' means JDK */ public Character Id() { return null; } /** * Runs a single formatting test. On success, returns null. * On failure, returns the error. This implementation just returns null. * Subclasses should override. * @param tuple contains the parameters of the format test. */ public String format(NumberFormatTestTuple tuple) { return null; } /** * Runs a single toPattern test. On success, returns null. * On failure, returns the error. This implementation just returns null. * Subclasses should override. * @param tuple contains the parameters of the format test. */ public String toPattern(NumberFormatTestTuple tuple) { return null; } /** * Runs a single parse test. On success, returns null. * On failure, returns the error. This implementation just returns null. * Subclasses should override. * @param tuple contains the parameters of the format test. */ public String parse(NumberFormatTestTuple tuple) { return null; } /** * Runs a single parse currency test. On success, returns null. * On failure, returns the error. This implementation just returns null. * Subclasses should override. * @param tuple contains the parameters of the format test. */ public String parseCurrency(NumberFormatTestTuple tuple) { return null; } /** * Runs a single select test. On success, returns null. * On failure, returns the error. This implementation just returns null. * Subclasses should override. * @param tuple contains the parameters of the format test. */ public String select(NumberFormatTestTuple tuple) { return null; } } private static enum RunMode { SKIP_KNOWN_FAILURES, INCLUDE_KNOWN_FAILURES } private final CodeUnderTest codeUnderTest; private String fileLine = null; private int fileLineNumber = 0; private String fileTestName = ""; private NumberFormatTestTuple tuple = new NumberFormatTestTuple(); /** * Runs all the tests in the data driven test suite against codeUnderTest. * @param params this.params from TestFmwk. * @param fileName The name of the test file. A relative file name under * com/ibm/icu/dev/data such as "data.txt" * @param codeUnderTest the code under test */ static void runSuite( TestParams params, String fileName, CodeUnderTest codeUnderTest) { new DataDrivenNumberFormatTestSuite(params, codeUnderTest) .run(fileName, RunMode.SKIP_KNOWN_FAILURES); } /** * Runs every format test in data driven test suite including those * that are known to fail. * * @param params this.params from TestFmwk * @param fileName The name of the test file. A relative file name under * com/ibm/icu/dev/data such as "data.txt" * @param codeUnderTest the code under test */ static void runFormatSuiteIncludingKnownFailures( TestParams params, String fileName, CodeUnderTest codeUnderTest) { new DataDrivenNumberFormatTestSuite(params, codeUnderTest) .run(fileName, RunMode.INCLUDE_KNOWN_FAILURES); } private DataDrivenNumberFormatTestSuite( TestParams params, CodeUnderTest codeUnderTest) { this.params = params; this.codeUnderTest = codeUnderTest; } private void run(String fileName, RunMode runMode) { Character codeUnderTestIdObj = codeUnderTest.Id(); char codeUnderTestId = codeUnderTestIdObj == null ? 0 : Character.toUpperCase(codeUnderTestIdObj.charValue()); BufferedReader in = null; try { in = TestUtil.getDataReader("numberformattestspecification.txt", "UTF-8"); // read first line and remove BOM if present readLine(in); if (fileLine != null && fileLine.charAt(0) == '\uFEFF') { fileLine = fileLine.substring(1); } int state = 0; List columnValues; List columnNames = null; while (true) { if (fileLine == null || fileLine.length() == 0) { if (!readLine(in)) { break; } if (fileLine.length() == 0 && state == 2) { state = 0; } continue; } if (fileLine.startsWith("//")) { fileLine = null; continue; } // Initial setup of test. if (state == 0) { if (fileLine.startsWith("test ")) { fileTestName = fileLine; tuple = new NumberFormatTestTuple(); } else if (fileLine.startsWith("set ")) { if (!setTupleField()) { return; } } else if(fileLine.startsWith("begin")) { state = 1; } else { showError("Unrecognized verb."); return; } // column specification } else if (state == 1) { columnNames = splitBy((char) 0x09); state = 2; // run the tests } else { int columnNamesSize = columnNames.size(); columnValues = splitBy(columnNamesSize, (char) 0x09); int columnValuesSize = columnValues.size(); for (int i = 0; i < columnValuesSize; ++i) { if (!setField(columnNames.get(i), columnValues.get(i))) { return; } } for (int i = columnValuesSize; i < columnNamesSize; ++i) { if (!clearField(columnNames.get(i))) { return; } } if (runMode == RunMode.INCLUDE_KNOWN_FAILURES || !breaks(codeUnderTestId)) { String errorMessage = isPass(tuple); if (errorMessage != null) { showError(errorMessage); } } } fileLine = null; } } catch (Exception e) { showError(e.toString()); e.printStackTrace(); } finally { try { if (in != null) { in.close(); } } catch (IOException e) { e.printStackTrace(); } } } private boolean breaks(char code) { String breaks = tuple.breaks == null ? "" : tuple.breaks; return (breaks.toUpperCase().indexOf(code) != -1); } private static boolean isSpace(char c) { return (c == 0x09 || c == 0x20 || c == 0x3000); } private boolean setTupleField() { List parts = splitBy(3, (char) 0x20); if (parts.size() < 3) { showError("Set expects 2 parameters"); return false; } return setField(parts.get(1), parts.get(2)); } private boolean setField(String name, String value) { try { tuple.setField(name, Utility.unescape(value)); return true; } catch (Exception e) { showError("No such field: " + name + ", or bad value: " + value); return false; } } private boolean clearField(String name) { try { tuple.clearField(name); return true; } catch (Exception e) { showError("Field cannot be clared: " + name); return false; } } private void showError(String message) { errln(String.format("line %d: %s\n%s\n%s", fileLineNumber, Utility.escape(message), fileTestName,fileLine)); } private List splitBy(char delimiter) { return splitBy(Integer.MAX_VALUE, delimiter); } private List splitBy(int max, char delimiter) { ArrayList result = new ArrayList(); int colIdx = 0; int colStart = 0; int len = fileLine.length(); for (int idx = 0; colIdx < max - 1 && idx < len; ++idx) { char ch = fileLine.charAt(idx); if (ch == delimiter) { result.add( fileLine.substring(colStart, idx)); ++colIdx; colStart = idx + 1; } } result.add(fileLine.substring(colStart, len)); return result; } private boolean readLine(BufferedReader in) throws IOException { String line = in.readLine(); if (line == null) { fileLine = null; return false; } ++fileLineNumber; // Strip trailing comments and spaces int idx = line.length(); for (; idx > 0; idx--) { if (!isSpace(line.charAt(idx -1))) { break; } } fileLine = idx == 0 ? "" : line; return true; } private String isPass(NumberFormatTestTuple tuple) { StringBuilder result = new StringBuilder(); if (tuple.format != null && tuple.output != null) { String errorMessage = codeUnderTest.format(tuple); if (errorMessage != null) { result.append(errorMessage); } } else if (tuple.toPattern != null || tuple.toLocalizedPattern != null) { String errorMessage = codeUnderTest.toPattern(tuple); if (errorMessage != null) { result.append(errorMessage); } } else if (tuple.parse != null && tuple.output != null && tuple.outputCurrency != null) { String errorMessage = codeUnderTest.parseCurrency(tuple); if (errorMessage != null) { result.append(errorMessage); } } else if (tuple.parse != null && tuple.output != null) { String errorMessage = codeUnderTest.parse(tuple); if (errorMessage != null) { result.append(errorMessage); } } else if (tuple.plural != null) { String errorMessage = codeUnderTest.select(tuple); if (errorMessage != null) { result.append(errorMessage); } } else { result.append("Unrecognized test type."); } if (result.length() > 0) { result.append(": "); result.append(tuple); return result.toString(); } return null; } }