From 433c8d0631b312768f2268cd046e1ef005f148e3 Mon Sep 17 00:00:00 2001 From: Dominic Burger Date: Tue, 5 Dec 2023 15:30:37 +0100 Subject: [PATCH 01/10] Add log4j to configure logging --- build.gradle | 4 ++++ .../interlis/testbed/runner/Main.java | 5 ++++- src/main/resources/log4j2.xml | 16 ++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/log4j2.xml diff --git a/build.gradle b/build.gradle index d0670c3..81c468f 100644 --- a/build.gradle +++ b/build.gradle @@ -14,6 +14,10 @@ repositories { } dependencies { + implementation platform('org.apache.logging.log4j:log4j-bom:2.22.0') + implementation 'org.apache.logging.log4j:log4j-api' + runtimeOnly 'org.apache.logging.log4j:log4j-core' + testImplementation platform('org.junit:junit-bom:5.9.1') testImplementation 'org.junit.jupiter:junit-jupiter' } diff --git a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Main.java b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Main.java index 84f3f62..f9c2a55 100644 --- a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Main.java +++ b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Main.java @@ -1,5 +1,7 @@ package ch.geowerkstatt.interlis.testbed.runner; +import org.apache.logging.log4j.LogManager; + public final class Main { private Main() { } @@ -10,6 +12,7 @@ private Main() { * @param args the command line arguments. */ public static void main(String[] args) { - System.out.println("Hello world!"); + var logger = LogManager.getLogger(); + logger.info("Hello World!"); } } diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml new file mode 100644 index 0000000..eb9b6c4 --- /dev/null +++ b/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + From 3ee936088a182a70a7ea19f3c8c92b8e2c80d16e Mon Sep 17 00:00:00 2001 From: Dominic Burger Date: Wed, 6 Dec 2023 09:20:06 +0100 Subject: [PATCH 02/10] Parse arguments using Apache Commons CLI --- build.gradle | 2 + .../interlis/testbed/runner/Main.java | 70 ++++++++++++++++++- .../interlis/testbed/runner/TestOptions.java | 15 ++++ 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 src/main/java/ch/geowerkstatt/interlis/testbed/runner/TestOptions.java diff --git a/build.gradle b/build.gradle index 81c468f..805a4f6 100644 --- a/build.gradle +++ b/build.gradle @@ -18,6 +18,8 @@ dependencies { implementation 'org.apache.logging.log4j:log4j-api' runtimeOnly 'org.apache.logging.log4j:log4j-core' + implementation 'commons-cli:commons-cli:1.6.0' + testImplementation platform('org.junit:junit-bom:5.9.1') testImplementation 'org.junit.jupiter:junit-jupiter' } diff --git a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Main.java b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Main.java index f9c2a55..94d52e0 100644 --- a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Main.java +++ b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Main.java @@ -1,8 +1,19 @@ package ch.geowerkstatt.interlis.testbed.runner; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.apache.logging.log4j.LogManager; +import java.nio.file.Path; + public final class Main { + private static final String VALIDATOR_PATH_OPTION = "validator"; + private static final String TESTBED_PATH_OPTION = "testbed"; + private Main() { } @@ -12,7 +23,64 @@ private Main() { * @param args the command line arguments. */ public static void main(String[] args) { + if (args.length == 0) { + printUsage(createCliOptions()); + return; + } + + var testOptions = parseTestOptions(args); var logger = LogManager.getLogger(); - logger.info("Hello World!"); + logger.info("base path: " + testOptions.basePath()); + logger.info("validator path: " + testOptions.ilivalidatorPath()); + } + + private static TestOptions parseTestOptions(String[] args) { + var options = createCliOptions(); + try { + var parser = new DefaultParser(); + var commandLine = parser.parse(options, args); + return getTestOptions(commandLine); + } catch (ParseException e) { + System.err.println("Error parsing command line arguments: " + e.getMessage()); + printUsage(options); + System.exit(1); + return null; + } + } + + private static void printUsage(Options options) { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("java -jar interlis-testbed-runner.jar [options]", options); + } + + private static TestOptions getTestOptions(CommandLine commandLine) throws ParseException { + var basePath = commandLine.hasOption(TESTBED_PATH_OPTION) + ? Path.of(commandLine.getOptionValue(TESTBED_PATH_OPTION)) + : Path.of("."); + var validatorPath = Path.of(commandLine.getOptionValue(VALIDATOR_PATH_OPTION)); + return new TestOptions(basePath, validatorPath); + } + + private static Options createCliOptions() { + var options = new Options(); + + var validatorPathOption = Option.builder("v") + .argName("path") + .longOpt(VALIDATOR_PATH_OPTION) + .hasArg() + .required() + .desc("path to ilivalidator.jar") + .build(); + options.addOption(validatorPathOption); + + var basePathOption = Option.builder("t") + .argName("path") + .longOpt(TESTBED_PATH_OPTION) + .hasArg() + .desc("base directory of testbed (default: current directory)") + .build(); + options.addOption(basePathOption); + + return options; } } diff --git a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/TestOptions.java b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/TestOptions.java new file mode 100644 index 0000000..8a68635 --- /dev/null +++ b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/TestOptions.java @@ -0,0 +1,15 @@ +package ch.geowerkstatt.interlis.testbed.runner; + +import java.nio.file.Path; + +public record TestOptions(Path basePath, Path ilivalidatorPath) { + /** + * Creates a new instance of the RunnerOptions class. + * + * @param basePath the base path of the testbed. + */ + public TestOptions { + basePath = basePath.toAbsolutePath().normalize(); + ilivalidatorPath = ilivalidatorPath.toAbsolutePath().normalize(); + } +} From 1e6cb80ad3ec5cad3e41ee5370b48aedd3412794 Mon Sep 17 00:00:00 2001 From: Dominic Burger Date: Wed, 6 Dec 2023 09:23:58 +0100 Subject: [PATCH 03/10] Add test runner that validates base data file --- .../testbed/runner/InterlisValidator.java | 28 +++++++++++++ .../interlis/testbed/runner/Main.java | 7 ++-- .../interlis/testbed/runner/Runner.java | 42 +++++++++++++++++++ .../interlis/testbed/runner/TestOptions.java | 11 +++++ .../interlis/testbed/runner/Validator.java | 13 ++++++ 5 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 src/main/java/ch/geowerkstatt/interlis/testbed/runner/InterlisValidator.java create mode 100644 src/main/java/ch/geowerkstatt/interlis/testbed/runner/Runner.java create mode 100644 src/main/java/ch/geowerkstatt/interlis/testbed/runner/Validator.java diff --git a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/InterlisValidator.java b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/InterlisValidator.java new file mode 100644 index 0000000..40bf711 --- /dev/null +++ b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/InterlisValidator.java @@ -0,0 +1,28 @@ +package ch.geowerkstatt.interlis.testbed.runner; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.nio.file.Path; + +public final class InterlisValidator implements Validator { + private static final Logger LOGGER = LogManager.getLogger(); + + private final TestOptions options; + + /** + * Creates a new instance of the InterlisValidator class. + * + * @param options the test options. + */ + public InterlisValidator(TestOptions options) { + this.options = options; + + LOGGER.info("Using ilivalidator at " + options.ilivalidatorPath()); + } + + @Override + public boolean validate(Path filePath) { + throw new UnsupportedOperationException("Not implemented yet."); + } +} diff --git a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Main.java b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Main.java index 94d52e0..9d84f5e 100644 --- a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Main.java +++ b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Main.java @@ -6,7 +6,6 @@ import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; -import org.apache.logging.log4j.LogManager; import java.nio.file.Path; @@ -29,9 +28,9 @@ public static void main(String[] args) { } var testOptions = parseTestOptions(args); - var logger = LogManager.getLogger(); - logger.info("base path: " + testOptions.basePath()); - logger.info("validator path: " + testOptions.ilivalidatorPath()); + var validator = new InterlisValidator(testOptions); + var runner = new Runner(testOptions, validator); + runner.run(); } private static TestOptions parseTestOptions(String[] args) { diff --git a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Runner.java b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Runner.java new file mode 100644 index 0000000..a4ae162 --- /dev/null +++ b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Runner.java @@ -0,0 +1,42 @@ +package ch.geowerkstatt.interlis.testbed.runner; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public final class Runner { + private static final Logger LOGGER = LogManager.getLogger(); + + private final TestOptions options; + private final Validator validator; + + /** + * Creates a new instance of the Runner class. + * + * @param options the test options. + * @param validator the validator to use. + */ + public Runner(TestOptions options, Validator validator) { + this.options = options; + this.validator = validator; + } + + /** + * Runs the testbed validation. + */ + public void run() { + LOGGER.info("Starting validation of testbed at " + options.basePath()); + + if (!validateSuccessfulData()) { + LOGGER.error("Validation of base data failed."); + return; + } + + LOGGER.info("Validation of testbed completed."); + } + + private boolean validateSuccessfulData() { + var filePath = options.baseDataFilePath(); + LOGGER.info("Validating base data file " + filePath); + return validator.validate(filePath); + } +} diff --git a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/TestOptions.java b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/TestOptions.java index 8a68635..d472247 100644 --- a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/TestOptions.java +++ b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/TestOptions.java @@ -3,6 +3,8 @@ import java.nio.file.Path; public record TestOptions(Path basePath, Path ilivalidatorPath) { + private static final String BASE_DATA_FILENAME = "Successful_Data.xtf"; + /** * Creates a new instance of the RunnerOptions class. * @@ -12,4 +14,13 @@ public record TestOptions(Path basePath, Path ilivalidatorPath) { basePath = basePath.toAbsolutePath().normalize(); ilivalidatorPath = ilivalidatorPath.toAbsolutePath().normalize(); } + + /** + * Gets the path to the data file that is used as the base for all validations. + * + * @return the path to the base data file. + */ + public Path baseDataFilePath() { + return basePath.resolve(BASE_DATA_FILENAME); + } } diff --git a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Validator.java b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Validator.java new file mode 100644 index 0000000..4159d24 --- /dev/null +++ b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Validator.java @@ -0,0 +1,13 @@ +package ch.geowerkstatt.interlis.testbed.runner; + +import java.nio.file.Path; + +public interface Validator { + /** + * Validates the given file. + * + * @param filePath the path to the file to validate. + * @return true if the validation was successful, false otherwise. + */ + boolean validate(Path filePath); +} From fb2a17fb4ec1cf2625029a41ab534352341a2891 Mon Sep 17 00:00:00 2001 From: Dominic Burger Date: Wed, 6 Dec 2023 09:47:19 +0100 Subject: [PATCH 04/10] Start ilivalidator process to validate files --- .../testbed/runner/InterlisValidator.java | 34 +++++++++++++++++-- .../interlis/testbed/runner/Main.java | 4 ++- .../interlis/testbed/runner/Runner.java | 16 ++++++--- .../interlis/testbed/runner/StringUtils.java | 21 ++++++++++++ .../interlis/testbed/runner/TestOptions.java | 10 ++++++ .../interlis/testbed/runner/Validator.java | 2 +- .../testbed/runner/ValidatorException.java | 34 +++++++++++++++++++ 7 files changed, 112 insertions(+), 9 deletions(-) create mode 100644 src/main/java/ch/geowerkstatt/interlis/testbed/runner/StringUtils.java create mode 100644 src/main/java/ch/geowerkstatt/interlis/testbed/runner/ValidatorException.java diff --git a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/InterlisValidator.java b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/InterlisValidator.java index 40bf711..71ddbe4 100644 --- a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/InterlisValidator.java +++ b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/InterlisValidator.java @@ -3,6 +3,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; public final class InterlisValidator implements Validator { @@ -22,7 +24,35 @@ public InterlisValidator(TestOptions options) { } @Override - public boolean validate(Path filePath) { - throw new UnsupportedOperationException("Not implemented yet."); + 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"); + + try { + Files.createDirectories(logDirectory); + + var processBuilder = new ProcessBuilder() + .command( + "java", "-jar", options.ilivalidatorPath().toString(), + "--log", logFile.toString(), + filePath.toString()) + .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; + } + } catch (IOException | InterruptedException e) { + throw new ValidatorException(e); + } } } diff --git a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Main.java b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Main.java index 9d84f5e..f489df9 100644 --- a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Main.java +++ b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Main.java @@ -30,7 +30,9 @@ public static void main(String[] args) { var testOptions = parseTestOptions(args); var validator = new InterlisValidator(testOptions); var runner = new Runner(testOptions, validator); - runner.run(); + if (!runner.run()) { + System.exit(1); + } } private static TestOptions parseTestOptions(String[] args) { diff --git a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Runner.java b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Runner.java index a4ae162..0155ab3 100644 --- a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Runner.java +++ b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Runner.java @@ -23,18 +23,24 @@ public Runner(TestOptions options, Validator validator) { /** * Runs the testbed validation. */ - public void run() { + public boolean run() { LOGGER.info("Starting validation of testbed at " + options.basePath()); - if (!validateSuccessfulData()) { - LOGGER.error("Validation of base data failed."); - return; + try { + if (!validateSuccessfulData()) { + LOGGER.error("Validation of base data failed."); + return false; + } + } 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 validateSuccessfulData() { + private boolean validateSuccessfulData() throws ValidatorException { var filePath = options.baseDataFilePath(); LOGGER.info("Validating base data file " + filePath); return validator.validate(filePath); diff --git a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/StringUtils.java b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/StringUtils.java new file mode 100644 index 0000000..534822e --- /dev/null +++ b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/StringUtils.java @@ -0,0 +1,21 @@ +package ch.geowerkstatt.interlis.testbed.runner; + +public final class StringUtils { + private StringUtils() { + } + + /** + * Gets the filename without the extension. + * + * @param filename the filename to get the name from. + * @return the filename without the extension. + */ + public static String getFilenameWithoutExtension(String filename) { + var lastDotIndex = filename.lastIndexOf('.'); + if (lastDotIndex == -1) { + return filename; + } + + return filename.substring(0, lastDotIndex); + } +} diff --git a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/TestOptions.java b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/TestOptions.java index d472247..da0e93f 100644 --- a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/TestOptions.java +++ b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/TestOptions.java @@ -4,6 +4,7 @@ public record TestOptions(Path basePath, Path ilivalidatorPath) { private static final String BASE_DATA_FILENAME = "Successful_Data.xtf"; + private static final String OUTPUT_DIR_NAME = "output"; /** * Creates a new instance of the RunnerOptions class. @@ -23,4 +24,13 @@ public record TestOptions(Path basePath, Path ilivalidatorPath) { public Path baseDataFilePath() { return basePath.resolve(BASE_DATA_FILENAME); } + + /** + * Gets the path to the output directory. + * + * @return the path to the output directory. + */ + public Path outputPath() { + return basePath.resolve(OUTPUT_DIR_NAME); + } } diff --git a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Validator.java b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Validator.java index 4159d24..76735d5 100644 --- a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Validator.java +++ b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Validator.java @@ -9,5 +9,5 @@ public interface Validator { * @param filePath the path to the file to validate. * @return true if the validation was successful, false otherwise. */ - boolean validate(Path filePath); + boolean validate(Path filePath) throws ValidatorException; } diff --git a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/ValidatorException.java b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/ValidatorException.java new file mode 100644 index 0000000..9cddb72 --- /dev/null +++ b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/ValidatorException.java @@ -0,0 +1,34 @@ +package ch.geowerkstatt.interlis.testbed.runner; + +/** + * Exception that is thrown when the validation failed to run. + */ +public final class ValidatorException extends Exception { + /** + * Creates a new instance of the ValidatorException class. + * + * @param message the message of the exception. + */ + public ValidatorException(String message) { + super(message); + } + + /** + * Creates a new instance of the ValidatorException class. + * + * @param message the message of the exception. + * @param cause the cause of the exception. + */ + public ValidatorException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Creates a new instance of the ValidatorException class. + * + * @param cause the cause of the exception. + */ + public ValidatorException(Throwable cause) { + super(cause); + } +} From a87f45e5fc5d0d2b5548049fa1ba520e771459aa Mon Sep 17 00:00:00 2001 From: Dominic Burger Date: Wed, 6 Dec 2023 09:49:59 +0100 Subject: [PATCH 05/10] Add tests for Runner --- build.gradle | 1 + .../interlis/testbed/runner/RunnerTest.java | 75 +++++++++++++++++++ .../testbed/runner/TestLogAppender.java | 52 +++++++++++++ 3 files changed, 128 insertions(+) create mode 100644 src/test/java/ch/geowerkstatt/interlis/testbed/runner/RunnerTest.java create mode 100644 src/test/java/ch/geowerkstatt/interlis/testbed/runner/TestLogAppender.java diff --git a/build.gradle b/build.gradle index 805a4f6..78fe234 100644 --- a/build.gradle +++ b/build.gradle @@ -16,6 +16,7 @@ repositories { dependencies { implementation platform('org.apache.logging.log4j:log4j-bom:2.22.0') implementation 'org.apache.logging.log4j:log4j-api' + testImplementation 'org.apache.logging.log4j:log4j-core' runtimeOnly 'org.apache.logging.log4j:log4j-core' implementation 'commons-cli:commons-cli:1.6.0' diff --git a/src/test/java/ch/geowerkstatt/interlis/testbed/runner/RunnerTest.java b/src/test/java/ch/geowerkstatt/interlis/testbed/runner/RunnerTest.java new file mode 100644 index 0000000..055b7e0 --- /dev/null +++ b/src/test/java/ch/geowerkstatt/interlis/testbed/runner/RunnerTest.java @@ -0,0 +1,75 @@ +package ch.geowerkstatt.interlis.testbed.runner; + +import org.apache.logging.log4j.Level; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +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; + +public final class RunnerTest { + private static final String BASE_PATH = "src/test/data/testbed"; + + private TestLogAppender appender; + private TestOptions options; + + @BeforeEach + public void setup() { + appender = TestLogAppender.registerAppender(Runner.class); + options = new TestOptions(Path.of(BASE_PATH), Path.of("ilivalidator.jar")); + } + + @AfterEach + public void teardown() { + appender.stop(); + appender.unregister(); + } + + @Test + public void runValidatesBaseData() { + var validatedFiles = new ArrayList(); + + var runner = new Runner(options, file -> { + validatedFiles.add(file.toAbsolutePath().normalize()); + return true; + }); + + var runResult = runner.run(); + + assertTrue(runResult, "Testbed run should have been successful."); + + var baseDataFile = Path.of(BASE_PATH, "Successful_Data.xtf").toAbsolutePath().normalize(); + assertEquals(baseDataFile, options.baseDataFilePath()); + + var expectedFiles = List.of(baseDataFile); + assertIterableEquals(expectedFiles, validatedFiles); + + var errors = appender.getMessages() + .stream() + .filter(e -> e.level().equals(Level.ERROR)); + assertEquals(0, errors.count(), "No errors should have been logged."); + } + + @Test + public void runLogsValidationError() { + var runner = new Runner(options, file -> false); + + var runResult = runner.run(); + + assertFalse(runResult, "Testbed run should have failed."); + + var errors = appender.getMessages() + .stream() + .filter(e -> e.level().equals(Level.ERROR)) + .toList(); + assertEquals(1, errors.size(), "One error should have been logged."); + assertEquals("Validation of base data failed.", errors.get(0).message()); + } +} diff --git a/src/test/java/ch/geowerkstatt/interlis/testbed/runner/TestLogAppender.java b/src/test/java/ch/geowerkstatt/interlis/testbed/runner/TestLogAppender.java new file mode 100644 index 0000000..8e0e0bd --- /dev/null +++ b/src/test/java/ch/geowerkstatt/interlis/testbed/runner/TestLogAppender.java @@ -0,0 +1,52 @@ +package ch.geowerkstatt.interlis.testbed.runner; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.appender.AbstractAppender; + +import java.util.ArrayList; +import java.util.List; + +/** + * Appender that can be used to capture log messages from the logger registered to a specific class. + */ +public final class TestLogAppender extends AbstractAppender { + private final Class loggerClass; + private final List messages = new ArrayList<>(); + + public record LogEntry(Level level, String message) { + } + + private TestLogAppender(Class loggerClass) { + super("TestLogAppender", null, null, true, null); + + this.loggerClass = loggerClass; + } + + public static TestLogAppender registerAppender(Class loggerClass) { + var mockedAppender = new TestLogAppender(loggerClass); + + var logger = (Logger) LogManager.getLogger(loggerClass); + logger.addAppender(mockedAppender); + logger.setLevel(Level.ALL); + + mockedAppender.start(); + return mockedAppender; + } + + public void unregister() { + var logger = (Logger) LogManager.getLogger(loggerClass); + logger.removeAppender(this); + } + + public List getMessages() { + return messages; + } + + @Override + public void append(LogEvent event) { + messages.add(new LogEntry(event.getLevel(), event.getMessage().getFormattedMessage())); + } +} From f1601339ce020a8be5153db9bdedc43c854a7f9f Mon Sep 17 00:00:00 2001 From: Dominic Burger Date: Wed, 6 Dec 2023 10:00:54 +0100 Subject: [PATCH 06/10] Add missing javadoc for return and throws --- .../java/ch/geowerkstatt/interlis/testbed/runner/Runner.java | 1 + .../java/ch/geowerkstatt/interlis/testbed/runner/Validator.java | 1 + 2 files changed, 2 insertions(+) diff --git a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Runner.java b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Runner.java index 0155ab3..8a48ed3 100644 --- a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Runner.java +++ b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Runner.java @@ -22,6 +22,7 @@ public Runner(TestOptions options, Validator validator) { /** * 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()); diff --git a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Validator.java b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Validator.java index 76735d5..0603842 100644 --- a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Validator.java +++ b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Validator.java @@ -8,6 +8,7 @@ public interface Validator { * * @param filePath the path to the file to validate. * @return true if the validation was successful, false otherwise. + * @throws ValidatorException if the validation could not be performed. */ boolean validate(Path filePath) throws ValidatorException; } From 06b94ad80aa9b93163d5a99f59e364e8061a65b7 Mon Sep 17 00:00:00 2001 From: Dominic Burger Date: Wed, 6 Dec 2023 10:34:21 +0100 Subject: [PATCH 07/10] Define main class in manifest to make jar executable --- build.gradle | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/build.gradle b/build.gradle index 78fe234..6f4e828 100644 --- a/build.gradle +++ b/build.gradle @@ -34,6 +34,14 @@ java { withJavadocJar() } +jar { + manifest { + attributes( + 'Main-Class': application.mainClass + ) + } +} + test { useJUnitPlatform() } From 59a65682e676abe58504f2e893f5e76804deb679 Mon Sep 17 00:00:00 2001 From: Dominic Burger Date: Wed, 6 Dec 2023 10:38:11 +0100 Subject: [PATCH 08/10] Describe how to start the runner in readme --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9b99942..ba121b6 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,7 @@ Der Testbed-Runner ermöglicht das Testen von Constraints bzw. der dazugehörigen Methoden basierend auf Testdaten in einer definierten Ordnerstruktur. ## Anwendung -Der Runner funktioniert generisch auf einer entsprechenden Verzeichnisstruktur und globalen ilivalidator-Installation. - -Diese Struktur ist folgendermassen aufgebaut: +Der Runner funktioniert generisch auf einer entsprechenden Verzeichnisstruktur mit diesem Aufbau: ``` TestSuiteA @@ -23,3 +21,8 @@ TestSuiteA FailCase-1_Merged.xtf FailCase-1.log ``` + +Der Runner kann mit folgendem Befehl ausgeführt werden: +```bash +java -jar interlis-testbed-runner.jar --validator --testbed +``` From 3ec9be1afda99582044ae9b610abd83dabeabb93 Mon Sep 17 00:00:00 2001 From: Dominic Burger Date: Wed, 6 Dec 2023 12:10:14 +0100 Subject: [PATCH 09/10] Pass testbed path on CLI without --testbed option --- README.md | 2 +- .../interlis/testbed/runner/Main.java | 16 +++------------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index ba121b6..e7502ca 100644 --- a/README.md +++ b/README.md @@ -24,5 +24,5 @@ TestSuiteA Der Runner kann mit folgendem Befehl ausgeführt werden: ```bash -java -jar interlis-testbed-runner.jar --validator --testbed +java -jar interlis-testbed-runner.jar --validator ``` diff --git a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Main.java b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Main.java index f489df9..f205282 100644 --- a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Main.java +++ b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Main.java @@ -11,7 +11,6 @@ public final class Main { private static final String VALIDATOR_PATH_OPTION = "validator"; - private static final String TESTBED_PATH_OPTION = "testbed"; private Main() { } @@ -51,13 +50,12 @@ private static TestOptions parseTestOptions(String[] args) { private static void printUsage(Options options) { HelpFormatter formatter = new HelpFormatter(); - formatter.printHelp("java -jar interlis-testbed-runner.jar [options]", options); + formatter.printHelp("java -jar interlis-testbed-runner.jar [options] [testbed directory (default: current directory)]", options); } private static TestOptions getTestOptions(CommandLine commandLine) throws ParseException { - var basePath = commandLine.hasOption(TESTBED_PATH_OPTION) - ? Path.of(commandLine.getOptionValue(TESTBED_PATH_OPTION)) - : Path.of("."); + var remainingArgs = commandLine.getArgList(); + var basePath = remainingArgs.isEmpty() ? Path.of(".") : Path.of(remainingArgs.get(0)); var validatorPath = Path.of(commandLine.getOptionValue(VALIDATOR_PATH_OPTION)); return new TestOptions(basePath, validatorPath); } @@ -74,14 +72,6 @@ private static Options createCliOptions() { .build(); options.addOption(validatorPathOption); - var basePathOption = Option.builder("t") - .argName("path") - .longOpt(TESTBED_PATH_OPTION) - .hasArg() - .desc("base directory of testbed (default: current directory)") - .build(); - options.addOption(basePathOption); - return options; } } From 9df5f36c7fdb6434d7a579ed8f7454d6cbf774e9 Mon Sep 17 00:00:00 2001 From: Dominic Burger Date: Wed, 6 Dec 2023 12:35:54 +0100 Subject: [PATCH 10/10] Use first xtf file in testbed as base data --- .../interlis/testbed/runner/Runner.java | 25 +++++++++++++++---- .../interlis/testbed/runner/TestOptions.java | 16 +++++++++--- src/test/data/testbed/data.xtf | 10 ++++++++ .../interlis/testbed/runner/RunnerTest.java | 11 +++++--- 4 files changed, 50 insertions(+), 12 deletions(-) create mode 100644 src/test/data/testbed/data.xtf diff --git a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Runner.java b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Runner.java index 8a48ed3..29020c1 100644 --- a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Runner.java +++ b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/Runner.java @@ -3,6 +3,10 @@ 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; + public final class Runner { private static final Logger LOGGER = LogManager.getLogger(); @@ -28,7 +32,7 @@ public boolean run() { LOGGER.info("Starting validation of testbed at " + options.basePath()); try { - if (!validateSuccessfulData()) { + if (!validateBaseData()) { LOGGER.error("Validation of base data failed."); return false; } @@ -41,9 +45,20 @@ public boolean run() { return true; } - private boolean validateSuccessfulData() throws ValidatorException { - var filePath = options.baseDataFilePath(); - LOGGER.info("Validating base data file " + filePath); - return validator.validate(filePath); + private boolean validateBaseData() throws ValidatorException { + Optional filePath; + try { + filePath = options.baseDataFilePath(); + } catch (IOException e) { + throw new ValidatorException(e); + } + + if (filePath.isEmpty()) { + LOGGER.error("No base data file found."); + return false; + } + + LOGGER.info("Validating base data file " + filePath.get()); + return validator.validate(filePath.get()); } } diff --git a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/TestOptions.java b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/TestOptions.java index da0e93f..056e5d1 100644 --- a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/TestOptions.java +++ b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/TestOptions.java @@ -1,9 +1,13 @@ package ch.geowerkstatt.interlis.testbed.runner; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; +import java.util.Optional; +import java.util.stream.Stream; public record TestOptions(Path basePath, Path ilivalidatorPath) { - private static final String BASE_DATA_FILENAME = "Successful_Data.xtf"; + private static final String DATA_FILE_EXTENSION = ".xtf"; private static final String OUTPUT_DIR_NAME = "output"; /** @@ -21,8 +25,10 @@ public record TestOptions(Path basePath, Path ilivalidatorPath) { * * @return the path to the base data file. */ - public Path baseDataFilePath() { - return basePath.resolve(BASE_DATA_FILENAME); + public Optional baseDataFilePath() throws IOException { + try (var dataFiles = findDataFiles(basePath)) { + return dataFiles.findFirst(); + } } /** @@ -33,4 +39,8 @@ public Path baseDataFilePath() { public Path outputPath() { return basePath.resolve(OUTPUT_DIR_NAME); } + + private static Stream findDataFiles(Path basePath) throws IOException { + return Files.find(basePath, 1, (path, attributes) -> path.getFileName().toString().toLowerCase().endsWith(DATA_FILE_EXTENSION)); + } } diff --git a/src/test/data/testbed/data.xtf b/src/test/data/testbed/data.xtf new file mode 100644 index 0000000..3146760 --- /dev/null +++ b/src/test/data/testbed/data.xtf @@ -0,0 +1,10 @@ + + + + + + interlis-testbed-runner + + + + diff --git a/src/test/java/ch/geowerkstatt/interlis/testbed/runner/RunnerTest.java b/src/test/java/ch/geowerkstatt/interlis/testbed/runner/RunnerTest.java index 055b7e0..579cca3 100644 --- a/src/test/java/ch/geowerkstatt/interlis/testbed/runner/RunnerTest.java +++ b/src/test/java/ch/geowerkstatt/interlis/testbed/runner/RunnerTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; @@ -33,7 +34,7 @@ public void teardown() { } @Test - public void runValidatesBaseData() { + public void runValidatesBaseData() throws IOException { var validatedFiles = new ArrayList(); var runner = new Runner(options, file -> { @@ -45,10 +46,12 @@ public void runValidatesBaseData() { assertTrue(runResult, "Testbed run should have been successful."); - var baseDataFile = Path.of(BASE_PATH, "Successful_Data.xtf").toAbsolutePath().normalize(); - assertEquals(baseDataFile, options.baseDataFilePath()); + var expectedBaseDataFile = Path.of(BASE_PATH, "data.xtf").toAbsolutePath().normalize(); + var baseDataFile = options.baseDataFilePath(); + assertFalse(baseDataFile.isEmpty(), "Base data file should have been found."); + assertEquals(expectedBaseDataFile, baseDataFile.get()); - var expectedFiles = List.of(baseDataFile); + var expectedFiles = List.of(expectedBaseDataFile); assertIterableEquals(expectedFiles, validatedFiles); var errors = appender.getMessages()