Skip to content

Commit

Permalink
Merge and validate XTF files to test constraints (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
domi-b authored Dec 14, 2023
2 parents 4ca313e + 031b415 commit 88c7f10
Show file tree
Hide file tree
Showing 12 changed files with 346 additions and 106 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ dependencies {
testImplementation platform('org.junit:junit-bom:5.9.1')
testImplementation 'org.junit.jupiter:junit-jupiter'
testImplementation 'org.xmlunit:xmlunit-core:2.9.1'
testImplementation "org.mockito:mockito-core:5.+"
}

application {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,33 +24,24 @@ public InterlisValidator(TestOptions options) {
}

@Override
public boolean validate(Path filePath) throws ValidatorException {
var relativePath = options.basePath().relativize(filePath.getParent());
var logDirectory = options.outputPath().resolve(relativePath);

var filenameWithoutExtension = StringUtils.getFilenameWithoutExtension(filePath.getFileName().toString());
var logFile = logDirectory.resolve(filenameWithoutExtension + ".log");

public boolean validate(Path filePath, Path logFile) throws ValidatorException {
LOGGER.info("Validating " + filePath + " with log file " + logFile);
try {
Files.createDirectories(logDirectory);
Files.createDirectories(logFile.getParent());

var processBuilder = new ProcessBuilder()
.command(
"java", "-jar", options.ilivalidatorPath().toString(),
"--log", logFile.toString(),
"--modeldir", options.basePath() + ";%ITF_DIR;http://models.interlis.ch/;%JAR_DIR/ilimodels",
filePath.toString())
.redirectOutput(ProcessBuilder.Redirect.DISCARD)
.redirectError(ProcessBuilder.Redirect.DISCARD)
.directory(options.basePath().toFile());

var process = processBuilder.start();
var exitCode = process.waitFor();

if (exitCode == 0) {
LOGGER.info("Validation of " + filePath + " completed successfully.");
return true;
} else {
LOGGER.error("Validation of " + filePath + " failed with exit code " + exitCode + ". See " + logFile + " for details.");
return false;
}
return exitCode == 0;
} catch (IOException | InterruptedException e) {
throw new ValidatorException(e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ch.geowerkstatt.interlis.testbed.runner;

import ch.geowerkstatt.interlis.testbed.runner.xtf.XtfFileMerger;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
Expand Down Expand Up @@ -28,7 +29,8 @@ public static void main(String[] args) {

var testOptions = parseTestOptions(args);
var validator = new InterlisValidator(testOptions);
var runner = new Runner(testOptions, validator);
var xtfMerger = new XtfFileMerger();
var runner = new Runner(testOptions, validator, xtfMerger);
if (!runner.run()) {
System.exit(1);
}
Expand Down
76 changes: 63 additions & 13 deletions src/main/java/ch/geowerkstatt/interlis/testbed/runner/Runner.java
Original file line number Diff line number Diff line change
@@ -1,64 +1,114 @@
package ch.geowerkstatt.interlis.testbed.runner;

import ch.geowerkstatt.interlis.testbed.runner.xtf.XtfMerger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
import java.util.List;

public final class Runner {
private static final Logger LOGGER = LogManager.getLogger();

private final TestOptions options;
private final Validator validator;
private final XtfMerger xtfMerger;
private Path baseFilePath;

/**
* Creates a new instance of the Runner class.
*
* @param options the test options.
* @param options the test options.
* @param validator the validator to use.
* @param xtfMerger the XTF merger to use.
*/
public Runner(TestOptions options, Validator validator) {
public Runner(TestOptions options, Validator validator, XtfMerger xtfMerger) {
this.options = options;
this.validator = validator;
this.xtfMerger = xtfMerger;
}

/**
* Runs the testbed validation.
*
* @return {@code true} if the validation was successful, {@code false} otherwise.
*/
public boolean run() {
LOGGER.info("Starting validation of testbed at " + options.basePath());

try {
if (!validateBaseData()) {
LOGGER.error("Validation of base data failed.");
return false;
}

if (!mergeAndValidateTransferFiles()) {
return false;
}

LOGGER.info("Validation of testbed completed.");
return true;
} catch (ValidatorException e) {
LOGGER.error("Validation could not run, check the configuration.", e);
return false;
}

LOGGER.info("Validation of testbed completed.");
return true;
}

private boolean validateBaseData() throws ValidatorException {
Optional<Path> filePath;
try {
filePath = options.baseDataFilePath();
var baseFilePath = options.baseDataFilePath();
if (baseFilePath.isEmpty()) {
LOGGER.error("No base data file found.");
return false;
}
this.baseFilePath = baseFilePath.get();
} catch (IOException e) {
throw new ValidatorException(e);
}

LOGGER.info("Validating base data file " + baseFilePath);
var filenameWithoutExtension = StringUtils.getFilenameWithoutExtension(baseFilePath.getFileName().toString());
var logFile = options.resolveOutputFilePath(baseFilePath, filenameWithoutExtension + ".log");

var valid = validator.validate(baseFilePath, logFile);
if (valid) {
LOGGER.info("Validation of " + baseFilePath + " completed successfully.");
} else {
LOGGER.error("Validation of " + baseFilePath + " failed. See " + logFile + " for details.");
}
return valid;
}

private boolean mergeAndValidateTransferFiles() throws ValidatorException {
List<Path> patchFiles;
try {
patchFiles = options.patchDataFiles();
} catch (IOException e) {
throw new ValidatorException(e);
}

if (filePath.isEmpty()) {
LOGGER.error("No base data file found.");
if (patchFiles.isEmpty()) {
LOGGER.error("No patch files found.");
return false;
}

LOGGER.info("Validating base data file " + filePath.get());
return validator.validate(filePath.get());
var valid = true;
for (var patchFile : patchFiles) {
var patchFileNameWithoutExtension = StringUtils.getFilenameWithoutExtension(patchFile.getFileName().toString());
var mergedFile = options.resolveOutputFilePath(patchFile, patchFileNameWithoutExtension + "_merged.xtf");
if (!xtfMerger.merge(baseFilePath, patchFile, mergedFile)) {
valid = false;
continue;
}

var logFile = mergedFile.getParent().resolve(patchFileNameWithoutExtension + ".log");
var mergedFileValid = validator.validate(mergedFile, logFile);
if (mergedFileValid) {
LOGGER.error("Validation of " + mergedFile + " was expected to fail but completed successfully.");
valid = false;
}
}

return valid;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;

Expand All @@ -26,11 +28,37 @@ public record TestOptions(Path basePath, Path ilivalidatorPath) {
* @return the path to the base data file.
*/
public Optional<Path> baseDataFilePath() throws IOException {
try (var dataFiles = findDataFiles(basePath)) {
try (var dataFiles = findDataFiles(basePath, 1)) {
return dataFiles.findFirst();
}
}

/**
* Gets the paths to the patch data files.
*
* @return the paths to the patch data files.
*/
public List<Path> patchDataFiles() throws IOException {
try (var dataFiles = findDataFiles(basePath, 2)) {
var outputPath = outputPath();
return dataFiles
.filter(path -> !path.getParent().equals(basePath) && !path.startsWith(outputPath))
.toList();
}
}

/**
* Resolves the path to the output file based on the relative path of the input file.
*
* @param filePath the path to the input file.
* @param newFileName the name of the output file.
* @return the path to the output file.
*/
public Path resolveOutputFilePath(Path filePath, String newFileName) {
var relativePath = basePath.relativize(filePath.getParent());
return outputPath().resolve(relativePath).resolve(newFileName);
}

/**
* Gets the path to the output directory.
*
Expand All @@ -40,7 +68,11 @@ public Path outputPath() {
return basePath.resolve(OUTPUT_DIR_NAME);
}

private static Stream<Path> findDataFiles(Path basePath) throws IOException {
return Files.find(basePath, 1, (path, attributes) -> path.getFileName().toString().toLowerCase().endsWith(DATA_FILE_EXTENSION));
private static Stream<Path> findDataFiles(Path basePath, int maxDepth) throws IOException {
return Files.find(basePath, maxDepth, TestOptions::isDataFile);
}

private static boolean isDataFile(Path path, BasicFileAttributes attributes) {
return attributes.isRegularFile() && path.getFileName().toString().toLowerCase().endsWith(DATA_FILE_EXTENSION);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ public interface Validator {
* Validates the given file.
*
* @param filePath the path to the file to validate.
* @param logFile the path to the log file.
* @return true if the validation was successful, false otherwise.
* @throws ValidatorException if the validation could not be performed.
*/
boolean validate(Path filePath) throws ValidatorException;
boolean validate(Path filePath, Path logFile) throws ValidatorException;
}
5 changes: 5 additions & 0 deletions src/test/data/testbed-with-patches/constraintA/testcase-1.xtf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ili:transfer xmlns:ili="http://www.interlis.ch/xtf/2.4/INTERLIS">
<ili:datasection>
</ili:datasection>
</ili:transfer>
10 changes: 10 additions & 0 deletions src/test/data/testbed-with-patches/data.xtf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<ili:transfer xmlns:ili="http://www.interlis.ch/xtf/2.4/INTERLIS">
<ili:headersection>
<ili:models>
</ili:models>
<ili:sender>interlis-testbed-runner</ili:sender>
</ili:headersection>
<ili:datasection>
</ili:datasection>
</ili:transfer>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package ch.geowerkstatt.interlis.testbed.runner;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.mockito.Mockito;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;

public abstract class MockitoTestBase {
private MockitoSession mockito;

@BeforeEach
public final void startMockitoSession() {
mockito = Mockito.mockitoSession()
.initMocks(this)
.strictness(Strictness.STRICT_STUBS)
.startMocking();
}

@AfterEach
public final void finishMockitoSession() {
mockito.finishMocking();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package ch.geowerkstatt.interlis.testbed.runner;

import ch.geowerkstatt.interlis.testbed.runner.xtf.XtfMerger;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;

import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.regex.Pattern;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertIterableEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public final class RunnerBaseDataTest extends MockitoTestBase {
private static final Path BASE_PATH = Path.of("src/test/data/testbed").toAbsolutePath().normalize();
private static final Path BASE_DATA_FILE = BASE_PATH.resolve("data.xtf");

private TestOptions options;
private TestLogAppender appender;
@Mock
private Validator validatorMock;
@Mock
private XtfMerger mergerMock;

@BeforeEach
public void setup() {
options = new TestOptions(BASE_PATH, Path.of("ilivalidator.jar"));
appender = TestLogAppender.registerAppender(Runner.class);
}

@AfterEach
public void teardown() {
appender.stop();
appender.unregister();
}

@Test
public void runValidatesBaseData() throws IOException, ValidatorException {
when(validatorMock.validate(any(), any())).thenReturn(true);

var runner = new Runner(options, validatorMock, mergerMock);

var runResult = runner.run();

assertFalse(runResult, "Testbed run should have failed without patch files.");

var errors = appender.getErrorMessages();
assertIterableEquals(List.of("No patch files found."), errors);

var baseDataFile = options.baseDataFilePath();
assertFalse(baseDataFile.isEmpty(), "Base data file should have been found.");
assertEquals(BASE_DATA_FILE, baseDataFile.get());

verify(validatorMock).validate(eq(BASE_DATA_FILE), any());
}

@Test
public void runLogsValidationError() throws ValidatorException {
when(validatorMock.validate(any(), any())).thenReturn(false);

var runner = new Runner(options, validatorMock, mergerMock);

var runResult = runner.run();

assertFalse(runResult, "Testbed run should have failed.");

var errors = appender.getErrorMessages();
assertEquals(1, errors.size(), "One error should have been logged.");
var errorMessage = errors.getFirst();
assertTrue(
Pattern.matches("Validation of .*? failed\\..*", errorMessage),
"Error message should start with 'Validation of <base data file> failed.', actual value: '" + errorMessage + "'."
);

verify(validatorMock).validate(eq(BASE_DATA_FILE), any());
}
}
Loading

0 comments on commit 88c7f10

Please sign in to comment.