1 package org.djutils.traceverifier;
2
3 import java.io.BufferedReader;
4 import java.io.BufferedWriter;
5 import java.io.Closeable;
6 import java.io.File;
7 import java.io.FileReader;
8 import java.io.FileWriter;
9 import java.io.IOException;
10
11 /**
12 * Create or verify identity of a trace of states of a system. <br>
13 * E.g., A simulation that is run with the same initial conditions (including random seed) should behave in a 100% predictable
14 * manner. If it does not, there is some source of randomness that probably should be eliminated. This package can help locate
15 * where the various runs of the simulation start to deviate from one another. <br>
16 * The simulation must be instrumented with calls to the <code>sample</code> method. The sample method either records these
17 * samples in a file, or compares these samples with the values that were stored in the file in a previous run. When a
18 * difference occurs, the sample method throws an exception. <br>
19 * <br>
20 * Copyright (c) 2020-2025 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
21 * for project information <a href="https://djutils.org" target="_blank"> https://djutils.org</a>. The DJUTILS project is
22 * distributed under a three-clause BSD-style license, which can be found at
23 * <a href="https://djutils.org/docs/license.html" target="_blank"> https://djutils.org/docs/license.html</a>. <br>
24 * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
25 * @author <a href="https://www.tudelft.nl/pknoppers">Peter Knoppers</a>
26 */
27 public class TraceVerifier implements Closeable
28 {
29 /** Name of the output file (when in writing mode). */
30 private final String outputFileName;
31
32 /** Reader for existing trace file. */
33 private final BufferedReader reader;
34
35 /**
36 * Create a new TraceVerifier.
37 * @param fileName name of the file for the trace
38 * @throws IOException when reading or writing fails
39 */
40 public TraceVerifier(final String fileName) throws IOException
41 {
42 // System.out.println("Creating TraceVerifier; file name is \"" + fileName + "\"");
43 File traceFile = new File(fileName);
44 if (traceFile.exists())
45 {
46 // Verify mode
47 this.reader = new BufferedReader(new FileReader(fileName));
48 this.outputFileName = null;
49 }
50 else
51 {
52 // Record mode
53 this.outputFileName = fileName;
54 BufferedWriter writer = new BufferedWriter(new FileWriter(fileName));
55 writer.close();
56 this.reader = null;
57 }
58 }
59
60 /**
61 * Add or compare one sample.
62 * @param description some kind of description of the sample (usually some kind of time stamp).
63 * @param state summary of the state of the process that is sampled.
64 * @throws IOException when reading or writing fails
65 * @throws TraceVerifierException on detection of a sample discrepancy
66 */
67 public void sample(final String description, final String state) throws IOException
68 {
69 String got = String.format("%s: %s", description, state);
70 if (this.reader != null)
71 {
72 String expected = this.reader.readLine();
73 if (expected.equals(got))
74 {
75 return;
76 }
77 int indexOfFirstDifference = 0;
78 while (got.charAt(indexOfFirstDifference) == expected.charAt(indexOfFirstDifference))
79 {
80 indexOfFirstDifference++;
81 }
82 String format =
83 indexOfFirstDifference == 0 ? "Discrepancy found.\n%%-8.8s: \"%%s\"\n%%-8.8s: \"%%s\"\n%%-8.8s: %%s^"
84 : String.format("Discrepancy found.\n%%-8.8s: \"%%s\"\n%%-8.8s: \"%%s\"\n%%-8.8s: %%%d.%ds^",
85 indexOfFirstDifference, indexOfFirstDifference);
86 String error = String.format(format, "Got", got, "Expected", expected, "1st diff", "");
87 throw new TraceVerifierException(error);
88 }
89 BufferedWriter writer = new BufferedWriter(new FileWriter(this.outputFileName, true));
90 writer.append(got);
91 writer.append('\n');
92 writer.close();
93 }
94
95 @Override
96 public void close() throws IOException
97 {
98 if (null != this.reader)
99 {
100 this.reader.close();
101 }
102 }
103
104 @Override
105 public String toString()
106 {
107 return "TraceVerifier [reader=" + this.reader + ", outputFileName=" + this.outputFileName + "]";
108 }
109
110 }