Skip to content

Commit

Permalink
Verify that log files contain constraint errors (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
domi-b authored Dec 18, 2023
2 parents 88c7f10 + c8a1da7 commit c190e21
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.regex.Pattern;

public final class InterlisValidator implements Validator {
private static final Logger LOGGER = LogManager.getLogger();
Expand Down Expand Up @@ -46,4 +47,21 @@ public boolean validate(Path filePath, Path logFile) throws ValidatorException {
throw new ValidatorException(e);
}
}

@Override
public boolean containsConstraintError(Path logFile, String constraintName) throws ValidatorException {
var constraintPattern = Pattern.compile("^Error: .*\\b" + Pattern.quote(constraintName) + "\\b");

try (var lines = Files.lines(logFile)) {
return lines.anyMatch(line -> {
if (constraintPattern.matcher(line).find()) {
LOGGER.info("Found expected error for constraint " + constraintName + " in log file: " + line);
return true;
}
return false;
});
} catch (IOException e) {
throw new ValidatorException(e);
}
}
}
20 changes: 17 additions & 3 deletions src/main/java/ch/geowerkstatt/interlis/testbed/runner/Runner.java
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,27 @@ private boolean mergeAndValidateTransferFiles() throws ValidatorException {
}

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.");
var constraintName = patchFile.getParent().getFileName().toString();
if (!validateMergedFile(mergedFile, logFile, constraintName)) {
valid = false;
}
}

return valid;
}

private boolean validateMergedFile(Path mergedFile, Path logFile, String constraintName) throws ValidatorException {
if (validator.validate(mergedFile, logFile)) {
LOGGER.error("Validation of " + mergedFile + " was expected to fail but completed successfully.");
return false;
}

if (!validator.containsConstraintError(logFile, constraintName)) {
LOGGER.error("Could not verify constraint " + constraintName + " for merged file " + mergedFile + ". Check the log file at " + logFile + " for details.");
return false;
}

LOGGER.info("Validation of " + mergedFile + " failed as expected.");
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,17 @@ public interface Validator {
*
* @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.
* @return {@code true} if the validation was successful, {@code false} otherwise.
* @throws ValidatorException if the validation could not be performed.
*/
boolean validate(Path filePath, Path logFile) throws ValidatorException;

/**
* Checks if the log file contains an error for the provided constraint.
*
* @param logFile the path to the log file.
* @param constraintName the name of the constraint to check.
* @return {@code true} if the log file contains an error for the constraint, {@code false} otherwise.
*/
boolean containsConstraintError(Path logFile, String constraintName) throws ValidatorException;
}
7 changes: 7 additions & 0 deletions src/test/data/validator/logfile.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Info: validate data...
Info: assume unknown external objects
Info: first validation pass...
Info: second validation pass...
Info: validate mandatory constraint ModelA.TopicA.ClassA.ConstraintA...
Error: line 10: ModelA.TopicA.ClassA: tid 1: Mandatory Constraint ModelA.TopicA.ClassA.ConstraintA is not true.
Info: validate mandatory constraint ModelA.TopicA.ClassA.ConstraintB...
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public void teardown() {
public void runMergesAndValidatesPatchFiles() throws ValidatorException {
when(validatorMock.validate(eq(BASE_DATA_FILE), any())).thenReturn(true);
when(validatorMock.validate(MERGED_FILE, MERGED_LOG_FILE)).thenReturn(false);
when(validatorMock.containsConstraintError(MERGED_LOG_FILE, CONSTRAINT_NAME)).thenReturn(true);

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

Expand Down Expand Up @@ -107,4 +108,31 @@ public void runFailsIfMergedFileIsValid() throws ValidatorException {

verify(mergerMock).merge(eq(BASE_DATA_FILE), eq(PATCH_FILE), eq(MERGED_FILE));
}

@Test
public void runFailsIfNoConstraintErrorInLog() throws ValidatorException {
when(validatorMock.validate(eq(BASE_DATA_FILE), any())).thenReturn(true);
when(validatorMock.validate(MERGED_FILE, MERGED_LOG_FILE)).thenReturn(false);
when(validatorMock.containsConstraintError(MERGED_LOG_FILE, CONSTRAINT_NAME)).thenReturn(false);

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

var runResult = runner.run();

assertFalse(runResult, "Testbed run should have failed if log file does not contain a constraint error.");

var errors = appender.getErrorMessages();
assertEquals(1, errors.size(), "One error should have been logged.");
var errorMessage = errors.getFirst();
var expectedErrorMessageStart = "Could not verify constraint " + CONSTRAINT_NAME + " for merged file";
assertTrue(
errorMessage.startsWith(expectedErrorMessageStart),
"Expected error to start with: " + expectedErrorMessageStart + ". Actual: '" + errorMessage + "'.");

verify(validatorMock).validate(eq(BASE_DATA_FILE), any());
verify(validatorMock).validate(eq(MERGED_FILE), eq(MERGED_LOG_FILE));
verify(validatorMock).containsConstraintError(eq(MERGED_LOG_FILE), eq(CONSTRAINT_NAME));

verify(mergerMock).merge(eq(BASE_DATA_FILE), eq(PATCH_FILE), eq(MERGED_FILE));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package ch.geowerkstatt.interlis.testbed.runner;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

public final class ValidatorTest {
private static final String CONSTRAINT_A_NAME = "ModelA.TopicA.ClassA.ConstraintA";
private static final String CONSTRAINT_B_NAME = "ModelA.TopicA.ClassA.ConstraintB";
private static final Path BASE_PATH = Path.of("src/test/data/validator");
private static final Path LOG_FILE = BASE_PATH.resolve("logfile.log");

private TestOptions options;

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

@Test
public void containsConstraintError() throws ValidatorException {
var validator = new InterlisValidator(options);

var result = validator.containsConstraintError(LOG_FILE, CONSTRAINT_A_NAME);

assertTrue(result, "The log file should contain an error for constraint A.");
}

@Test
public void noErrorForValidConstraint() throws ValidatorException, IOException {
var validator = new InterlisValidator(options);

var result = validator.containsConstraintError(LOG_FILE, CONSTRAINT_B_NAME);

assertFalse(result, "The log file should not contain an error for constraint B.");

try (var lines = Files.lines(LOG_FILE)) {
var hasConstraintInfo = lines.anyMatch(line -> line.startsWith("Info:") && line.contains(CONSTRAINT_B_NAME));
assertTrue(hasConstraintInfo, "The log file should contain an info message for the constraint.");
}
}
}

0 comments on commit c190e21

Please sign in to comment.