From 0673969304bb8de894929b74149d3853773f4324 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro <gregd@basistech.com> Date: Tue, 20 Oct 2020 17:18:41 -0400 Subject: [PATCH] means of creating diff --- Core/ivy.xml | 26 ++- Core/nbproject/project.properties | 1 + Core/nbproject/project.xml | 6 +- .../integrationtesting/DiffService.java | 196 ++++++++++++++++++ .../integrationtesting/MainTestRunner.java | 148 ++++++++----- .../integrationtesting/config/EnvConfig.java | 29 ++- 6 files changed, 340 insertions(+), 66 deletions(-) create mode 100644 Core/test/qa-functional/src/org/sleuthkit/autopsy/integrationtesting/DiffService.java diff --git a/Core/ivy.xml b/Core/ivy.xml index a1118e0f61..24b6e0b7e5 100644 --- a/Core/ivy.xml +++ b/Core/ivy.xml @@ -3,30 +3,30 @@ <configurations > <!-- module dependencies --> <conf name="core"/> - + </configurations> <dependencies > <dependency conf="core->default" org="com.github.vlsi.mxgraph" name="jgraphx" rev="4.1.0" /> - + <dependency conf="core->default" org="org.apache.activemq" name="activemq-all" rev="5.11.1"/> <dependency conf="core->default" org="org.apache.curator" name="curator-client" rev="2.8.0"/> <dependency conf="core->default" org="org.apache.curator" name="curator-framework" rev="2.8.0"/> <dependency conf="core->default" org="org.apache.curator" name="curator-recipes" rev="2.8.0"/> - + <dependency conf="core->default" org="org.python" name="jython-standalone" rev="2.7.0" /> - + <dependency conf="core->default" org="com.adobe.xmp" name="xmpcore" rev="5.1.2"/> <dependency conf="core->default" org="org.apache.zookeeper" name="zookeeper" rev="3.4.6"/> <dependency conf="core->default" org="com.healthmarketscience.jackcess" name="jackcess" rev="2.2.0"/> <dependency conf="core->default" org="com.healthmarketscience.jackcess" name="jackcess-encrypt" rev="2.1.4"/> - + <dependency conf="core->default" org="org.apache.commons" name="commons-dbcp2" rev="2.1.1"/> <dependency conf="core->default" org="org.apache.commons" name="commons-pool2" rev="2.4.2"/> <dependency conf="core->default" org="commons-codec" name="commons-codec" rev="1.11"/> <dependency conf="core->default" org="org.jsoup" name="jsoup" rev="1.10.3"/> - + <dependency conf="core->default" org="com.fasterxml.jackson.core" name="jackson-databind" rev="2.9.7"/> <dependency conf="core->default" org="com.drewnoakes" name="metadata-extractor" rev="2.11.0"/> @@ -34,7 +34,7 @@ <dependency conf="core->default" org="org.apache.opennlp" name="opennlp-tools" rev="1.9.1"/> <dependency conf="core->default" org="com.ethteck.decodetect" name="decodetect-core" rev="0.3"/> - + <dependency conf="core->default" org="org.sejda.webp-imageio" name="webp-imageio-sejda" rev="0.1.0"/> <dependency conf="core->default" org="com.googlecode.libphonenumber" name="libphonenumber" rev="3.5" /> <dependency conf="core->default" org="commons-validator" name="commons-validator" rev="1.6"/> @@ -45,13 +45,17 @@ <!-- for yaml reading/writing --> <dependency org="org.yaml" name="snakeyaml" rev="1.27"/> - + <!-- map support for geolocation --> - <dependency conf="core->default" org="org.jxmapviewer" name="jxmapviewer2" rev="2.4"/> - + <dependency conf="core->default" org="org.jxmapviewer" name="jxmapviewer2" rev="2.4"/> + <!-- For Discovery testing --> <dependency conf="core->default" org="org.mockito" name="mockito-core" rev="3.5.7"/> - + + <!-- for handling diffs --> + <dependency org="io.github.java-diff-utils" name="java-diff-utils" rev="4.8"/> + + <!-- https://mvnrepository.com/artifact/javax.ws.rs/javax.ws.rs-api --> <dependency conf="core->default" org="javax.ws.rs" name="javax.ws.rs-api" rev="2.0"/> <override org="jakarta.ws.rs" module="jakarta.ws.rs-api" rev="2.1.5"/> diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index def49783e5..f15316a7bd 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -21,6 +21,7 @@ file.reference.commons-dbcp2-2.1.1.jar=release\\modules\\ext\\commons-dbcp2-2.1. file.reference.commons-digester-1.8.1.jar=release\\modules\\ext\\commons-digester-1.8.1.jar file.reference.commons-logging-1.2.jar=release\\modules\\ext\\commons-logging-1.2.jar file.reference.commons-pool2-2.4.2.jar=release\\modules\\ext\\commons-pool2-2.4.2.jar +file.reference.java-diff-utils-4.8.jar=release\\modules\\ext\\java-diff-utils-4.8.jar file.reference.commons-validator-1.6.jar=release\\modules\\ext\\commons-validator-1.6.jar file.reference.curator-client-2.8.0.jar=release\\modules\\ext\\curator-client-2.8.0.jar file.reference.curator-framework-2.8.0.jar=release\\modules\\ext\\curator-framework-2.8.0.jar diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index b7471c6a9e..597af6c073 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -643,6 +643,10 @@ <runtime-relative-path>ext/commons-collections-3.2.2.jar</runtime-relative-path> <binary-origin>release\modules\ext\commons-collections-3.2.2.jar</binary-origin> </class-path-extension> + <class-path-extension> + <runtime-relative-path>ext/java-diff-utils-4.8.jar</runtime-relative-path> + <binary-origin>release\modules\ext\java-diff-utils-4.8.jar</binary-origin> + </class-path-extension> <class-path-extension> <runtime-relative-path>ext/SparseBitSet-1.1.jar</runtime-relative-path> <binary-origin>release\modules\ext\SparseBitSet-1.1.jar</binary-origin> @@ -811,7 +815,7 @@ <runtime-relative-path>ext/grpc-netty-shaded-1.19.0.jar</runtime-relative-path> <binary-origin>release\modules\ext\grpc-netty-shaded-1.19.0.jar</binary-origin> </class-path-extension> - <class-path-extension> + <class-path-extension> <runtime-relative-path>ext/snakeyaml-1.27.jar</runtime-relative-path> <binary-origin>release\modules\ext\snakeyaml-1.27.jar</binary-origin> </class-path-extension> diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/integrationtesting/DiffService.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/integrationtesting/DiffService.java new file mode 100644 index 0000000000..80e72335b5 --- /dev/null +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/integrationtesting/DiffService.java @@ -0,0 +1,196 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.integrationtesting; + +import com.github.difflib.DiffUtils; +import com.github.difflib.patch.AbstractDelta; +import com.github.difflib.patch.Chunk; +import com.github.difflib.patch.Patch; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.commons.lang3.tuple.Pair; + +/** + * Handles creating diffs with files. + */ +public class DiffService { + + private static final Logger logger = Logger.getLogger(DiffUtils.class.getName()); + private static final String ORIG_LINE_PREFIX = "< "; + private static final String CUR_LINE_PREFIX = "> "; + private static final String[] DIFF_BREAK = new String[]{"", "", ""}; + private static final String[] FILE_DIFF_BREAK = new String[]{"", "", "", ""}; + private static final String NEW_LINE = System.getProperty("line.separator"); + + /** + * Creates a diff of all the files found in the directories provided or + * between two files. + * + * @param prevResult The previous file or directory. Must be of same type as + * curResult (file/directory). + * @param curResult The current file or directory. Must be of same type as + * prevResult (file/directory). + * @return The string contents of the diff. + */ + String diffFilesOrDirs(File prevResult, File curResult) { + if (prevResult.isDirectory() && curResult.isDirectory()) { + final Map<String, File> prevFiles = FileUtils.listFiles(prevResult, null, true).stream() + .collect(Collectors.toMap(f -> getRelative(prevResult, f), f -> f, (f1, f2) -> f1)); + + final Map<String, File> curFiles = FileUtils.listFiles(curResult, null, true).stream() + .collect(Collectors.toMap(f -> getRelative(curResult, f), f -> f, (f1, f2) -> f1)); + + Map<String, Pair<File, File>> prevCurMapping = Stream.of(prevFiles, curFiles) + .flatMap((map) -> map.keySet().stream()) + .collect(Collectors.toMap(k -> k, k -> Pair.of(prevFiles.get(k), curFiles.get(k)), (v1, v2) -> v1)); + + String fullDiff = prevCurMapping.entrySet().stream() + .map((entry) -> getFileDiffs(entry.getValue().getLeft(), entry.getValue().getRight(), entry.getKey())) + .filter((val) -> val != null) + .collect(Collectors.joining(String.join(NEW_LINE, FILE_DIFF_BREAK))); + + return fullDiff; + + } else if (prevResult.isFile() && curResult.isFile()) { + return getFileDiffs(prevResult, curResult, prevResult.toString() + " / " + curResult.toString()); + + } else { + logger.log(Level.WARNING, String.format("%s and %s must be of same type (directory/file).", prevResult.toString(), curResult.toString())); + return null; + } + } + + /** + * Handles creating a diff between files noting if one of them is not + * present. If both are not present or both are the same, null is returned. + * + * @param orig The original file. + * @param cur The current file. + * @param identifier The identifier for the header. + * @return The String representing the differences. + */ + private String getFileDiffs(File orig, File cur, String identifier) { + boolean hasOrig = (orig != null && orig.exists()); + boolean hasCur = (cur != null && cur.exists()); + if (!hasOrig && !hasCur) { + return null; + } else if (!hasOrig && hasCur) { + return getHeaderWithDivider("MISSING FILE IN CURRENT: " + identifier); + } else if (hasOrig && !hasCur) { + return getHeaderWithDivider("ADDITIONAL FILE IN CURRENT: " + identifier); + } else { + try { + return diffLines(Files.readAllLines(orig.toPath()), Files.readAllLines(cur.toPath()), getHeaderWithDivider(identifier + ":")); + } catch (IOException ex) { + return getHeaderWithDivider(String.format("ERROR reading files at %s / %s %s%s", + orig.toString(), cur.toString(), NEW_LINE, ExceptionUtils.getStackTrace(ex))); + } + } + } + + private String getChunkLineNumString(Chunk<?> chunk) { + return String.format("%d,%d", chunk.getPosition() + 1, chunk.getLines().size()); + } + + /** + * Gets a github-like line difference (i.e. -88,3 +90,3) of the form + * -orig_line_num,orig_lines, +new_line_num,new_lines. + * + * @param orig The previous chunk. + * @param cur The current chunk. + * @return The line number difference. + */ + private String getDiffLineNumString(Chunk<?> orig, Chunk<?> cur) { + return String.format("-%s +%s", getChunkLineNumString(orig), getChunkLineNumString(cur)); + } + + /** + * Creates a line by line difference similar to integration tests like: + * < original + * > new + * + * @param orig The original chunk. + * @param cur The new chunk. + * @return The lines representing the diff. + */ + private List<String> getLinesDiff(Chunk<String> orig, Chunk<String> cur) { + Stream<String> origPrefixed = orig.getLines().stream() + .map((line) -> ORIG_LINE_PREFIX + line); + + Stream<String> curPrefixed = cur.getLines().stream() + .map((line) -> CUR_LINE_PREFIX + line); + + return Stream.concat(origPrefixed, curPrefixed) + .collect(Collectors.toList()); + } + + private String getLinesDiffString(AbstractDelta<String> delta) { + String lineNums = getDiffLineNumString(delta.getSource(), delta.getTarget()); + List<String> linesDiff = getLinesDiff(delta.getSource(), delta.getTarget()); + + return Stream.concat(Stream.of(lineNums), linesDiff.stream()) + .collect(Collectors.joining(NEW_LINE)) + NEW_LINE; + } + + /** + * Creates a line difference String with a header if non-null. Null is + * returned if there is no diff. + * + * @param orig The original lines. + * @param cur The current lines. + * @param header The header to be used if non-null diff. If header is null, + * no header included. + * @return The pretty-printed diff. + */ + private String diffLines(List<String> orig, List<String> cur, String header) { + //compute the patch: this is the diffutils part + Patch<String> patch = DiffUtils.diff(orig, cur); + + String diff = patch.getDeltas().stream() + .map(delta -> getLinesDiffString(delta)) + .collect(Collectors.joining(String.join(NEW_LINE, DIFF_BREAK))); + + if (StringUtils.isBlank(diff)) { + return null; + } + + return (header != null) + ? header + NEW_LINE + diff + : diff; + } + + private String getHeaderWithDivider(String remark) { + String divider = "-----------------------------------------------------------"; + return String.join(NEW_LINE, divider, remark, divider); + } + + private String getRelative(File rootDirectory, File file) { + return rootDirectory.toURI().relativize(file.toURI()).getPath(); + } +} diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/integrationtesting/MainTestRunner.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/integrationtesting/MainTestRunner.java index 13d351c1b6..fbba921197 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/integrationtesting/MainTestRunner.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/integrationtesting/MainTestRunner.java @@ -35,6 +35,8 @@ import java.util.stream.Stream; import junit.framework.Test; import junit.framework.TestCase; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; import org.apache.cxf.common.util.CollectionUtils; import org.netbeans.junit.NbModuleSuite; import org.openide.util.Lookup; @@ -65,6 +67,7 @@ public class MainTestRunner extends TestCase { private static final Logger logger = Logger.getLogger(MainTestRunner.class.getName()); private static final String CONFIG_FILE_KEY = "integrationConfigFile"; private static final ConfigDeserializer configDeserializer = new ConfigDeserializer(); + private static final DiffService diffService = new DiffService(); private static final ConfigurationModuleManager configurationModuleManager = new ConfigurationModuleManager(); /** @@ -107,63 +110,67 @@ public void runIntegrationTests() { if (!CollectionUtils.isEmpty(config.getTestSuites())) { for (TestSuiteConfig testSuiteConfig : config.getTestSuites()) { - String caseName = testSuiteConfig.getName(); - for (CaseType caseType : IntegrationCaseType.getCaseTypes(testSuiteConfig.getCaseTypes())) { - // create an autopsy case for each case in the config and for each case type for the specified case. - // then run ingest for the case. - Case autopsyCase = createCaseWithDataSources( - envConfig.getWorkingDirectory(), - envConfig.getRootCaseOutputPath(), - caseName, - caseType, - testSuiteConfig.getDataSources()); - - if (autopsyCase == null || autopsyCase != Case.getCurrentCase()) { - logger.log(Level.WARNING, - String.format("Case was not properly ingested or setup correctly for environment. Case is %s and current case is %s.", - autopsyCase, Case.getCurrentCase())); - return; - } - - // run configuration modules and get result - Pair<IngestJobSettings, List<ConfigurationModule<?>>> configurationResult - = configurationModuleManager.runConfigurationModules(caseName, testSuiteConfig.getConfigurationModules()); - - IngestJobSettings ingestSettings = configurationResult.first(); - List<ConfigurationModule<?>> configModules = configurationResult.second(); - - // run ingest with ingest settings derived from configuration modules. - runIngest(autopsyCase, ingestSettings, caseName); - - // once ingested, run integration tests to produce output. - OutputResults results = runIntegrationTests(testSuiteConfig.getIntegrationTests()); - - // revert any autopsy environment changes made by configuration modules. - configurationModuleManager.revertConfigurationModules(configModules); - - String outputFolder = PathUtil.getAbsolutePath(envConfig.getWorkingDirectory(), envConfig.getRootTestOutputPath()); - - // write the results for the case to a file - results.serializeToFile( - envConfig.getUseRelativeOutput() == true ? - Paths.get(outputFolder, testSuiteConfig.getRelativeOutputPath()).toString() : - outputFolder, - testSuiteConfig.getName(), - caseType - ); - try { - Case.closeCurrentCase(); - } catch (CaseActionException ex) { - logger.log(Level.WARNING, "There was an error while trying to close current case: {0}", caseName); - return; + runIntegrationTestSuite(envConfig, caseType, testSuiteConfig); + } catch (CaseActionException | IllegalStateException ex) { + logger.log(Level.WARNING, "There was an error working with current case: " + testSuiteConfig.getName(), ex); } } } + + // write diff to file if requested + writeDiff(envConfig); } } + /** + * Runs a single test suite. + * + * @param envConfig The integrationt test environment config. + * @param caseType The type of case (single user, multi user). + * @param testSuiteConfig The configuration for the case. + */ + private void runIntegrationTestSuite(EnvConfig envConfig, CaseType caseType, TestSuiteConfig testSuiteConfig) throws CaseActionException, IllegalStateException { + + String caseName = testSuiteConfig.getName(); + + // create an autopsy case for each case in the config and for each case type for the specified case. + // then run ingest for the case. + Case autopsyCase = createCaseWithDataSources( + envConfig.getWorkingDirectory(), + envConfig.getRootCaseOutputPath(), + caseName, + caseType, + testSuiteConfig.getDataSources()); + if (autopsyCase == null || autopsyCase != Case.getCurrentCase()) { + throw new IllegalStateException(String.format("Case was not properly ingested or setup correctly for environment. Case is %s and current case is %s.", + autopsyCase, Case.getCurrentCase())); + } + // run configuration modules and get result + Pair<IngestJobSettings, List<ConfigurationModule<?>>> configurationResult + = configurationModuleManager.runConfigurationModules(caseName, testSuiteConfig.getConfigurationModules()); + IngestJobSettings ingestSettings = configurationResult.first(); + List<ConfigurationModule<?>> configModules = configurationResult.second(); + // run ingest with ingest settings derived from configuration modules. + runIngest(autopsyCase, ingestSettings, caseName); + // once ingested, run integration tests to produce output. + OutputResults results = runIntegrationTests(testSuiteConfig.getIntegrationTests()); + // revert any autopsy environment changes made by configuration modules. + configurationModuleManager.revertConfigurationModules(configModules); + String outputFolder = PathUtil.getAbsolutePath(envConfig.getWorkingDirectory(), envConfig.getRootTestOutputPath()); + // write the results for the case to a file + results.serializeToFile( + envConfig.getUseRelativeOutput() == true + ? Paths.get(outputFolder, testSuiteConfig.getRelativeOutputPath()).toString() + : outputFolder, + testSuiteConfig.getName(), + caseType + ); + + Case.closeCurrentCase(); + } + /** * Creates a case with the given data sources. * @@ -241,6 +248,7 @@ private void addDataSourcesToCase(List<String> pathStrings, String caseName) { /** * Runs ingest on the current case. + * * @param openCase The currently open case. * @param ingestJobSettings The ingest job settings to be used. * @param caseName The name of the case to be used for error messages. @@ -291,7 +299,7 @@ private OutputResults runIntegrationTests(TestingConfig testSuiteConfig) { if (testMethod.getParameters().length > 1) { throw new IllegalArgumentException(String.format("Could not call method %s in class %s. Multiple parameters cannot be handled.", testMethod.getName(), testGroup.getClass().getCanonicalName())); - // if there is a parameter, deserialize parameters to the specified type. + // if there is a parameter, deserialize parameters to the specified type. } else if (testMethod.getParameters().length > 0) { parameters = new Object[]{configDeserializer.convertToObj(parametersMap, testMethod.getParameterTypes()[0])}; } @@ -312,12 +320,13 @@ private OutputResults runIntegrationTests(TestingConfig testSuiteConfig) { return results; } - /** * Runs a test method in a test suite on the current case. + * * @param testGroup The test suite to which the method belongs. * @param testMethod The java reflection method to run. - * @param parameters The parameters to use with this method or none/empty array. + * @param parameters The parameters to use with this method or none/empty + * array. * @return The results of running the method. */ private Object runIntegrationTestMethod(IntegrationTestGroup testGroup, Method testMethod, Object[] parameters) { @@ -343,4 +352,39 @@ private Object runIntegrationTestMethod(IntegrationTestGroup testGroup, Method t return serializableResult; } + + /** + * Writes any differences found between gold and output to a diff file. Only + * works if a gold and diff location are specified in the EnvConfig. + * + * @param envConfig The env config. + */ + private void writeDiff(EnvConfig envConfig) { + if (StringUtils.isBlank(envConfig.getRootGoldPath()) || StringUtils.isBlank(envConfig.getDiffOutputPath())) { + logger.log(Level.INFO, "gold path or diff output path not specified. Not creating diff."); + } + + String goldPath = PathUtil.getAbsolutePath(envConfig.getWorkingDirectory(), envConfig.getRootGoldPath()); + File goldDir = new File(goldPath); + if (!goldDir.exists()) { + logger.log(Level.WARNING, String.format("Gold does not exist at location: %s. Not creating diff.", goldDir.toString())); + } + + String outputPath = PathUtil.getAbsolutePath(envConfig.getWorkingDirectory(), envConfig.getRootCaseOutputPath()); + File outputDir = new File(outputPath); + if (!outputDir.exists()) { + logger.log(Level.WARNING, String.format("Output path does not exist at location: %s. Not creating diff.", outputDir.toString())); + } + + String diffPath = PathUtil.getAbsolutePath(envConfig.getWorkingDirectory(), envConfig.getDiffOutputPath()); + String diff = diffService.diffFilesOrDirs(goldDir, outputDir); + if (StringUtils.isNotBlank(diff)) { + try { + FileUtils.writeStringToFile(new File(diffPath), diff, "UTF-8"); + } catch (IOException ex) { + logger.log(Level.SEVERE, "Unable to write diff file to " + diffPath); + } + } + + } } diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/integrationtesting/config/EnvConfig.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/integrationtesting/config/EnvConfig.java index ccfefd6ba5..0c725d2f28 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/integrationtesting/config/EnvConfig.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/integrationtesting/config/EnvConfig.java @@ -29,12 +29,15 @@ public class EnvConfig { private final String rootCaseOutputPath; private final String rootTestOutputPath; private final String rootTestSuitesPath; - + private final String rootGoldPath; + private final String diffOutputPath; + private final ConnectionConfig connectionInfo; private String workingDirectory; private Boolean useRelativeOutput; + /** * Main constructor. * @@ -50,6 +53,8 @@ public class EnvConfig { * the same relative path structure as the file (i.e. if file was found at * /rootTestSuitesPath/folderX/fileY.json then it will now be outputted in * /rootTestOutputPath/folderX/fileY/) + * @param rootGoldPath The path to the gold data for diff comparison. + * @param diffOutputPath The file location for diff output. */ @JsonCreator public EnvConfig( @@ -58,11 +63,15 @@ public EnvConfig( @JsonProperty("rootTestOutputPath") String rootTestOutputPath, @JsonProperty("connectionInfo") ConnectionConfig connectionInfo, @JsonProperty("workingDirectory") String workingDirectory, - @JsonProperty("useRelativeOutput") Boolean useRelativeOutput) { + @JsonProperty("useRelativeOutput") Boolean useRelativeOutput, + @JsonProperty("rootGoldPath") String rootGoldPath, + @JsonProperty("diffOutputPath") String diffOutputPath) { this.rootCaseOutputPath = rootCaseOutputPath; this.rootTestOutputPath = rootTestOutputPath; this.rootTestSuitesPath = rootTestSuitesPath; + this.rootGoldPath = rootGoldPath; + this.diffOutputPath = diffOutputPath; this.connectionInfo = connectionInfo; this.workingDirectory = workingDirectory; @@ -139,4 +148,20 @@ public boolean getUseRelativeOutput() { public void setUseRelativeOutput(boolean useRelativeOutput) { this.useRelativeOutput = useRelativeOutput; } + + /** + * @return The path to the gold data for diff comparison. + */ + public String getRootGoldPath() { + return rootGoldPath; + } + + /** + * @return The file location for diff output. + */ + public String getDiffOutputPath() { + return diffOutputPath; + } + + } -- GitLab