001package daikon.test; 002 003import static java.nio.charset.StandardCharsets.UTF_8; 004import static java.util.logging.Level.INFO; 005import static org.junit.Assert.fail; 006 007import daikon.Daikon; 008import java.io.BufferedReader; 009import java.io.ByteArrayOutputStream; 010import java.io.File; 011import java.io.IOException; 012import java.io.InputStream; 013import java.io.InputStreamReader; 014import java.io.LineNumberReader; 015import java.io.OutputStream; 016import java.io.PrintStream; 017import java.util.ArrayList; 018import java.util.List; 019import junit.framework.TestSuite; 020import org.checkerframework.checker.nullness.qual.Nullable; 021import org.checkerframework.dataflow.qual.Pure; 022import org.junit.Test; 023import org.plumelib.options.Option; 024import org.plumelib.options.Options; 025 026/** 027 * This is a tester for the formatting of invariants in different modes that is configurable by file 028 * input. It can test practically any invariant in the Daikon system given the appropriate commands. 029 * The test are configured from the {@code InvariantFormatTest.commands} file and errors that occur 030 * are written to the InvariantFormatTest.diffs file. More detail on the expected formats of these 031 * files is in Daikon developer manual. 032 */ 033public class InvariantFormatTester { 034 035 /** 036 * Maximum file size that can currently be examined by the program. It is arbitrary, but a length 037 * must be supplied to LineNumberReader.mark(). 038 */ 039 private static final int MAX_FILE_SIZE = 262144; 040 041 /** Indicates a string that when it starts a line signifies that the line is a comment. */ 042 public static final String COMMENT_STARTER_STRING = ";"; 043 044 /** A list containing all of the test formats. */ 045 public static final List<String> TEST_FORMAT_LIST = getTestFormatList(); 046 047 /** File that contains the format test commands. Must be found as a resource. */ 048 private static String command_file = "InvariantFormatTest.commands"; 049 050 @Option("-d File to write any differences to. Will be deleted on success") 051 public static File diff_file = new File("InvariantFormatTest.diffs"); 052 053 /** Determines whether the object will generate goal statements. */ 054 @Option("-g Filename to write goals to") 055 public static @Nullable File generate_goals = null; 056 057 /** 058 * This function allows this test to be run from the command line instead of its usual method, 059 * which is through the Daikon MasterTester. 060 * 061 * @param args arguments to the main function, which control options to the program. As of now 062 * there is only one option, "--generate_goals", which will generate goal information for the 063 * selected tests assuming the output that the tests provide is the correct output. 064 */ 065 public static void main(String[] args) { 066 daikon.LogHelper.setupLogs(INFO); 067 068 String usage = "java daikon.test.InvariantFormatTester"; 069 Options options = new Options(usage, InvariantFormatTester.class); 070 String[] other_args = options.parse(true, args); 071 if (other_args.length > 0) { 072 System.out.println("unexpected arguments"); 073 options.printUsage(); 074 return; 075 } 076 077 junit.textui.TestRunner.run(new TestSuite(InvariantFormatTester.class)); 078 } 079 080 /** 081 * This function produces the format list for intialization of the static format list variable. 082 */ 083 static List<String> getTestFormatList() { 084 List<String> result = new ArrayList<>(); 085 086 // Add test formats - hard coded in 087 result.add("daikon"); 088 result.add("java"); 089 result.add("esc"); 090 result.add("jml"); 091 result.add("dbc"); 092 result.add("simplify"); 093 result.add("csharpcontract"); 094 095 return result; 096 } 097 098 /** This function is the actual function performed when this class is run through JUnit. */ 099 @Test 100 public void testFormats() { 101 102 // Don't care about comparability info because we are only 103 // creating variables for the purpose of being compared (thus they 104 // should all be comparable) 105 Daikon.ignore_comparability = true; 106 107 // run the actual test 108 109 if (!execute()) { 110 fail("At least one test failed. Inspect " + diff_file + " for error report."); 111 } 112 } 113 114 /** 115 * Returns the next non-comment, non-whitespace line of the input buffer. 116 * 117 * @param input the input buffer 118 * @return the next non-comment, non-whitespace line of the input buffer or null if the end of the 119 * buffer is reached before such a line can be found 120 */ 121 static @Nullable String getNextRealLine(BufferedReader input) { 122 String currentLine = ""; 123 124 try { 125 while (currentLine != null) { 126 currentLine = input.readLine(); 127 if (currentLine != null && !isComment(currentLine) && !isWhitespace(currentLine)) { 128 return currentLine; 129 } 130 } 131 } catch (IOException e) { 132 throw new RuntimeException(e.toString()); 133 } 134 return null; 135 } 136 137 /** 138 * This function performs the testing for a particular format indicated by the format string. It 139 * subsequently sets up appropriate input and output streams for the format test, performs the 140 * test, and the compares the test results to the goals. If the goals differ from the actual 141 * results the test fails. 142 */ 143 private boolean execute() { 144 145 // Open the input stream. 146 InputStream inputStream = InvariantFormatTester.class.getResourceAsStream(command_file); 147 if (inputStream == null) { 148 fail( 149 "Input file for invariant format tests missing." 150 + " (Should be in " 151 + command_file 152 + " and it must be within the classpath)"); 153 throw new Error("This can't happen"); // to quiet Findbugs 154 } 155 LineNumberReader commandReader = 156 new LineNumberReader(new InputStreamReader(inputStream, UTF_8)); 157 158 // Create a stream for the output 159 OutputStream out = new ByteArrayOutputStream(); 160 boolean result; 161 162 // Run the test 163 try { 164 result = performTest(commandReader, new PrintStream(out)); 165 } catch (Throwable e) { 166 throw new RuntimeException( 167 "Error detected on line " 168 + commandReader.getLineNumber() 169 + " of " 170 + InvariantFormatTester.class.getResource(command_file), 171 e); 172 } 173 174 // Close the command file 175 try { 176 inputStream.close(); 177 } catch (IOException e) { 178 // Can't write the goals into the commands file if it can't be cleared, 179 // otherwise not important. Only matters if output file is the same 180 // as the input file 181 if (generate_goals != null) { 182 throw new RuntimeException( 183 "Can't close commands file " + InvariantFormatTester.class.getResource(command_file)); 184 } 185 } 186 187 // Get all of the output as a string 188 String output = out.toString(); 189 190 // If we are generating a new goal file 191 if (generate_goals != null) { 192 193 // Create the goal file and write the output to it 194 try { 195 PrintStream out_fp = new PrintStream(generate_goals); 196 out_fp.printf("%s", output); 197 out_fp.close(); 198 System.out.println("Goals generated"); 199 } catch (Exception e) { 200 throw new RuntimeException("Can't write goal file " + generate_goals, e); 201 } 202 } else { // handle any differences 203 204 // Delete any previous diffs 205 diff_file.delete(); 206 207 // If the test failed, write the differences to the diff file 208 if (!result) { 209 try { 210 PrintStream diff_fp = new PrintStream(diff_file); 211 diff_fp.printf("%s", output); 212 diff_fp.close(); 213 } catch (Exception e) { 214 throw new RuntimeException("Can't write diff file " + diff_file, e); 215 } 216 return false; 217 } 218 } 219 return true; 220 } 221 222 /** 223 * This function performs an individual formatting test after the input and output streams have 224 * been created. 225 * 226 * @param commands the input that decides which tests to perform 227 * @param output the place to where the test output is written 228 */ 229 private boolean performTest(LineNumberReader commands, PrintStream output) { 230 List<FormatTestCase> invariantTestCases = new ArrayList<>(); 231 boolean noTestFailed = true; 232 233 // Need to be able to go to beginning of buffer for combining goals with the input 234 if (generate_goals != null) { 235 try { 236 commands.mark(MAX_FILE_SIZE); 237 } catch (IOException e) { 238 throw new RuntimeException("Cannot mark file in order to generate goals"); 239 } 240 } 241 242 while (true) { 243 // Create a new test case 244 FormatTestCase currentCase = FormatTestCase.readFromFile(commands, (generate_goals != null)); 245 if (currentCase == null) { 246 break; 247 } else { 248 invariantTestCases.add(currentCase); 249 if ((generate_goals == null) && !currentCase.passes()) { 250 output.print(currentCase.getDiffString()); 251 noTestFailed = false; 252 } 253 } 254 } 255 256 if (generate_goals != null) { 257 258 // Go to beginning of the commands buffer 259 try { 260 commands.reset(); 261 } catch (IOException e) { 262 throw new RuntimeException("Cannot reset to mark, thus cannot write goals"); 263 } 264 265 String debugTemp; 266 267 try { 268 for (FormatTestCase currentCase : invariantTestCases) { 269 // System.out.println("Goal output #" + i); 270 debugTemp = currentCase.generateGoalOutput(commands); 271 // System.out.println(debugTemp); 272 273 output.println(debugTemp); 274 } 275 276 String currentLineOfText = commands.readLine(); 277 278 while (currentLineOfText != null) { 279 if (FormatTestCase.parseGoal(currentLineOfText) == null) { 280 output.println(currentLineOfText); 281 } 282 currentLineOfText = commands.readLine(); 283 } 284 } catch (IOException e) { 285 throw new RuntimeException("Writing goal output failed"); 286 } 287 } 288 return noTestFailed; 289 } 290 291 /** 292 * Determines whether a line is a comment or not. 293 * 294 * @param line the line in question 295 * @return true if the line is a comment (that is, not to be interpretted as a command); false 296 * otherwise 297 */ 298 @Pure 299 static boolean isComment(String line) { 300 return line.startsWith(COMMENT_STARTER_STRING); 301 } 302 303 /** 304 * Determines whether a given line is made only of whitespace. 305 * 306 * @param line the line in question 307 * @return true if the line is made up only of whitespace, false otherwise 308 */ 309 @Pure 310 static boolean isWhitespace(String line) { 311 for (int x = 0; x < line.length(); x++) { 312 if (!Character.isWhitespace(line.charAt(x))) { 313 return false; 314 } 315 } 316 return true; 317 } 318}