mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-06 21:00:22 +00:00
means of creating diff
This commit is contained in:
parent
023f975455
commit
0673969304
@ -52,6 +52,10 @@
|
||||
<!-- 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"/>
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -35,6 +35,8 @@ import java.util.stream.Collectors;
|
||||
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,9 +110,31 @@ public class MainTestRunner extends TestCase {
|
||||
|
||||
if (!CollectionUtils.isEmpty(config.getTestSuites())) {
|
||||
for (TestSuiteConfig testSuiteConfig : config.getTestSuites()) {
|
||||
for (CaseType caseType : IntegrationCaseType.getCaseTypes(testSuiteConfig.getCaseTypes())) {
|
||||
try {
|
||||
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();
|
||||
|
||||
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(
|
||||
@ -118,50 +143,32 @@ public class MainTestRunner extends TestCase {
|
||||
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.",
|
||||
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()));
|
||||
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,
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -241,6 +248,7 @@ public class MainTestRunner extends TestCase {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@ -312,12 +320,13 @@ public class MainTestRunner extends TestCase {
|
||||
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 @@ public class MainTestRunner extends TestCase {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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 class 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 class EnvConfig {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user