From 6537feba01e91c2209c874c10b0bacd1a21153ce Mon Sep 17 00:00:00 2001 From: Steven Yuan Date: Thu, 18 Apr 2024 18:54:17 -0700 Subject: [PATCH] Add Smithy Diff test runner This is strongly based on `SmithyTestSuite` and `SmithyTestCase`. Also adds example diff tests. --- .../diff/testrunner/SmithyDiffTestCase.java | 296 ++++++++++++++ .../diff/testrunner/SmithyDiffTestSuite.java | 381 ++++++++++++++++++ .../software/amazon/smithy/diff/DiffTest.java | 47 +++ .../testrunner/SmithyDiffTestCaseTest.java | 156 +++++++ .../testrunner/SmithyDiffTestSuiteTest.java | 50 +++ .../jsonName/change-jsonName-value.a.smithy | 8 + .../jsonName/change-jsonName-value.b.smithy | 8 + .../jsonName/change-jsonName-value.events | 3 + .../diffs/tags/change-tags-content.a.smithy | 12 + .../diffs/tags/change-tags-content.b.smithy | 12 + .../diffs/tags/change-tags-content.events | 38 ++ .../testrunner/invalid/diff1.a.smithy | 7 + .../testrunner/invalid/diff1.b.smithy | 7 + .../testrunner/invalid/diff1.events | 0 .../testrunner/invalid/diff2.a.smithy | 7 + .../testrunner/invalid/diff2.b.smithy | 7 + .../testrunner/invalid/diff2.events | 0 .../testrunner/invalid/nested/diff3.a.smithy | 7 + .../testrunner/invalid/nested/diff3.b.smithy | 7 + .../testrunner/invalid/nested/diff3.events | 1 + .../testrunner/valid/diff1.a.smithy | 7 + .../testrunner/valid/diff1.b.smithy | 7 + .../testrunner/testrunner/valid/diff1.events | 0 .../testrunner/valid/diff2.a.smithy | 7 + .../testrunner/valid/diff2.b.smithy | 7 + .../testrunner/testrunner/valid/diff2.events | 1 + .../testrunner/valid/nested/diff3.a.smithy | 7 + .../testrunner/valid/nested/diff3.b.smithy | 7 + .../testrunner/valid/nested/diff3.events | 1 + 29 files changed, 1098 insertions(+) create mode 100644 smithy-diff/src/main/java/software/amazon/smithy/diff/testrunner/SmithyDiffTestCase.java create mode 100644 smithy-diff/src/main/java/software/amazon/smithy/diff/testrunner/SmithyDiffTestSuite.java create mode 100644 smithy-diff/src/test/java/software/amazon/smithy/diff/DiffTest.java create mode 100644 smithy-diff/src/test/java/software/amazon/smithy/diff/testrunner/SmithyDiffTestCaseTest.java create mode 100644 smithy-diff/src/test/java/software/amazon/smithy/diff/testrunner/SmithyDiffTestSuiteTest.java create mode 100644 smithy-diff/src/test/resources/software/amazon/smithy/diff/diffs/jsonName/change-jsonName-value.a.smithy create mode 100644 smithy-diff/src/test/resources/software/amazon/smithy/diff/diffs/jsonName/change-jsonName-value.b.smithy create mode 100644 smithy-diff/src/test/resources/software/amazon/smithy/diff/diffs/jsonName/change-jsonName-value.events create mode 100644 smithy-diff/src/test/resources/software/amazon/smithy/diff/diffs/tags/change-tags-content.a.smithy create mode 100644 smithy-diff/src/test/resources/software/amazon/smithy/diff/diffs/tags/change-tags-content.b.smithy create mode 100644 smithy-diff/src/test/resources/software/amazon/smithy/diff/diffs/tags/change-tags-content.events create mode 100644 smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/diff1.a.smithy create mode 100644 smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/diff1.b.smithy create mode 100644 smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/diff1.events create mode 100644 smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/diff2.a.smithy create mode 100644 smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/diff2.b.smithy create mode 100644 smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/diff2.events create mode 100644 smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/nested/diff3.a.smithy create mode 100644 smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/nested/diff3.b.smithy create mode 100644 smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/nested/diff3.events create mode 100644 smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/diff1.a.smithy create mode 100644 smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/diff1.b.smithy create mode 100644 smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/diff1.events create mode 100644 smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/diff2.a.smithy create mode 100644 smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/diff2.b.smithy create mode 100644 smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/diff2.events create mode 100644 smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/nested/diff3.a.smithy create mode 100644 smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/nested/diff3.b.smithy create mode 100644 smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/nested/diff3.events diff --git a/smithy-diff/src/main/java/software/amazon/smithy/diff/testrunner/SmithyDiffTestCase.java b/smithy-diff/src/main/java/software/amazon/smithy/diff/testrunner/SmithyDiffTestCase.java new file mode 100644 index 00000000000..9230ad8b0e5 --- /dev/null +++ b/smithy-diff/src/main/java/software/amazon/smithy/diff/testrunner/SmithyDiffTestCase.java @@ -0,0 +1,296 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.diff.testrunner; + +import static java.lang.String.format; + +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.TreeSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import software.amazon.smithy.model.SourceLocation; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.validation.Severity; +import software.amazon.smithy.model.validation.ValidationEvent; +import software.amazon.smithy.utils.IoUtils; + +/** + * Runs a single test case by loading corresponding models `a` and `b` and + * ensuring the resulting events match the diff events stored in a `-----` + * separated file. + */ +public final class SmithyDiffTestCase { + private static final Pattern EVENT_PATTERN = Pattern.compile( + "^\\[(?SUPPRESSED|NOTE|WARNING|DANGER|ERROR)] " + + "(?[^ ]+): " + + "?(?.*) " + + "\\| " + + "(?[^)]+)", + Pattern.DOTALL); + + private final Path path; + private final String name; + private final List expectedEvents; + + /** + * @param path Parent path of where the model and event files are stored. + * @param name Name of the test case + * @param expectedEvents The expected diff events to encounter. + */ + public SmithyDiffTestCase( + Path path, + String name, + List expectedEvents + ) { + this.path = Objects.requireNonNull(path); + this.name = Objects.requireNonNull(name); + this.expectedEvents = Collections.unmodifiableList(expectedEvents); + } + + /** + * Creates a test case from a test case path and name. + * + *

The models and events file are expected to be stored in the same + * directory as the model and events file are assumed to be named the same + * barring the file extensions: `.a.(json|smithy)`, `.b.(json|smithy)`, + * `.events`. + * + *

The accompanying events file is a `-----` separated list of event + * strings, where each event is defined in the following format: + * {@code [SEVERITY] shapeId message | EventId filename:line:column}. + * A shapeId of "-" means that a specific shape is not targeted. + * + * @param path Parent path of where the model and event files are stored. + * @param name Name of the test case + * @return Returns the created test case. + */ + public static SmithyDiffTestCase from(Path path, String name) { + List expectedEvents = loadExpectedEvents(path, name); + return new SmithyDiffTestCase(path, name, expectedEvents); + } + + /** + * Gets the parent path of the test case. + * + * @return parent path of the test case. + */ + public Path getPath() { + return path; + } + + /** + * Gets the name of the test case. + * + * @return name of the test case. + */ + public String getName() { + return name; + } + + /** + * Gets the expected validation events. + * + * @return Expected validation events. + */ + public List getExpectedEvents() { + return expectedEvents; + } + + /** + * Creates a test case result from a list of model diff events. + * + *

The diff events encountered are compared against the expected + * validation events. An actual event (A) is considered a match with an + * expected event (E) if A and E target the same shape, have the same + * severity, the eventId of A contains the eventId of E, and the message + * of E starts with the suppression reason or message of A. + * + * @param actualEvents List of actual diff events. + * @return Returns the created test case result. + */ + public Result createResult(List actualEvents) { + List unmatchedEvents = expectedEvents.stream() + .filter(expectedEvent -> actualEvents.stream() + .noneMatch(actualEvent -> compareEvents(expectedEvent, actualEvent))) + .collect(Collectors.toList()); + + List extraEvents = actualEvents.stream() + .filter(actualEvent -> expectedEvents.stream() + .noneMatch(expectedEvent -> compareEvents(expectedEvent, actualEvent))) + // Exclude suppressed events from needing to be defined as acceptable events. + // However, these can still be defined as required events. + .filter(event -> event.getSeverity() != Severity.SUPPRESSED) + .collect(Collectors.toList()); + + return new SmithyDiffTestCase.Result(name, unmatchedEvents, extraEvents); + } + + private static boolean compareEvents(ValidationEvent expected, ValidationEvent actual) { + String normalizedActualMessage = normalizeMessage(actual.getMessage()); + if (actual.getSuppressionReason().isPresent()) { + normalizedActualMessage += " (" + actual.getSuppressionReason().get() + ")"; + } + normalizedActualMessage = normalizeMessage(normalizedActualMessage); + + String comparedMessage = normalizeMessage(expected.getMessage()); + return expected.getSeverity() == actual.getSeverity() + && actual.containsId(expected.getId()) + && expected.getShapeId().equals(actual.getShapeId()) + // Normalize new lines. + && normalizedActualMessage.startsWith(comparedMessage); + } + + // Newlines in persisted validation events are escaped. + private static String normalizeMessage(String message) { + return message.replace("\n", "\\n").replace("\r", "\\n"); + } + + private static List loadExpectedEvents(Path path, String name) { + String fileName = path.resolve(name + SmithyDiffTestSuite.EVENTS).toString(); + String contents = IoUtils.readUtf8File(fileName); + return Arrays.stream(contents.split("-----")) + .map(chunk -> chunk.trim()) + .filter(chunk -> !chunk.isEmpty()) + .map(chunk -> parseValidationEvent(chunk, fileName)) + .collect(Collectors.toList()); + } + + static ValidationEvent parseValidationEvent(String event, String fileName) { + Matcher matcher = EVENT_PATTERN.matcher(event); + if (!matcher.find()) { + throw new IllegalArgumentException(format("Invalid validation event in file `%s`, the following event did " + + "not match the expected regular expression `%s`: %s", + fileName, EVENT_PATTERN.pattern(), event)); + } + + // Construct a dummy source location since we don't validate it. + SourceLocation location = new SourceLocation("/", 0, 0); + + ValidationEvent.Builder builder = ValidationEvent.builder() + .severity(Severity.fromString(matcher.group("severity")).get()) + .sourceLocation(location) + .id(matcher.group("id")) + .message(matcher.group("message")); + + // A shape ID of "-" means no shape. + if (!matcher.group("shape").equals("-")) { + builder.shapeId(ShapeId.from(matcher.group("shape"))); + } + + return builder.build(); + } + + /** + * Output of diffing a model against a test case. + */ + public static final class Result { + private final String name; + private final Collection unmatchedEvents; + private final Collection extraEvents; + + Result( + String name, + Collection unmatchedEvents, + Collection extraEvents + ) { + this.name = name; + this.unmatchedEvents = Collections.unmodifiableCollection(new TreeSet<>(unmatchedEvents)); + this.extraEvents = Collections.unmodifiableCollection(new TreeSet<>(extraEvents)); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + + builder + .append("============================\n" + + "Model Diff Validation Result\n" + + "============================\n") + .append(name) + .append('\n'); + + if (!unmatchedEvents.isEmpty()) { + builder.append("\nDid not match the following events\n" + + "----------------------------------\n"); + for (ValidationEvent event : unmatchedEvents) { + builder.append(event.toString()).append("\n\n"); + } + } + + if (!extraEvents.isEmpty()) { + builder.append("\nEncountered unexpected events\n" + + "-----------------------------\n"); + for (ValidationEvent event : extraEvents) { + builder.append(event.toString()).append("\n\n"); + } + } + + return builder.toString(); + } + + /** + * @return Returns the name of the test case. + */ + public String getName() { + return name; + } + + /** + * @return Returns the events that were expected but not encountered. + */ + public Collection getUnmatchedEvents() { + return unmatchedEvents; + } + + /** + * @return Returns the events that were encountered but not expected. + */ + public Collection getExtraEvents() { + return extraEvents; + } + + /** + * Checks if the result does not match expected results. + * + * @return True if there are extra or unmatched events. + */ + public boolean isInvalid() { + return !unmatchedEvents.isEmpty() || !extraEvents.isEmpty(); + } + + /** + * Throws an exception if the result is invalid, otherwise returns the result. + * + * @return Returns the result if it is ok. + * @throws Error if the result contains invalid events. + */ + public Result unwrap() { + if (isInvalid()) { + throw new Error(this); + } + + return this; + } + } + + /** + * Thrown when errors are encountered while unwrapping a test case. + */ + public static final class Error extends RuntimeException { + public final Result result; + + Error(Result result) { + super(result.toString()); + this.result = result; + } + } +} diff --git a/smithy-diff/src/main/java/software/amazon/smithy/diff/testrunner/SmithyDiffTestSuite.java b/smithy-diff/src/main/java/software/amazon/smithy/diff/testrunner/SmithyDiffTestSuite.java new file mode 100644 index 00000000000..ed1ea1554ac --- /dev/null +++ b/smithy-diff/src/main/java/software/amazon/smithy/diff/testrunner/SmithyDiffTestSuite.java @@ -0,0 +1,381 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.diff.testrunner; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.Future; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import software.amazon.smithy.diff.ModelDiff; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.loader.ModelAssembler; + +/** + * Runs diff test cases against corresponding model `a`, model `b`, and validation `events` files. + */ +public final class SmithyDiffTestSuite { + static final String EVENTS = ".events"; + private static final String DEFAULT_TEST_CASE_LOCATION = "diffs"; + private static final String EXT_SMITHY = ".smithy"; + private static final String EXT_JSON = ".json"; + private static final String MODEL_A = ".a"; + private static final String MODEL_B = ".b"; + + private final List cases = new ArrayList(); + private Supplier modelAssemblerFactory = ModelAssembler::new; + + private SmithyDiffTestSuite() {} + + /** + * Creates a new Smithy diff test suite. + * + * @return Returns the created test suite. + */ + public static SmithyDiffTestSuite runner() { + return new SmithyDiffTestSuite(); + } + + + /** + * Factory method used to easily create a JUnit 5 {@code ParameterizedTest} + * {@code MethodSource} based on the given {@code Class}. + * + *

This method assumes that there is a resource named {@code diffs} + * relative to the given class that contains test cases. It also assumes + * validators and traits should be loaded using the {@code ClassLoader} + * of the given {@code contextClass}, and that model discovery should be + * used using the given {@code contextClass}. + * + *

Each returned {@code Object[]} contains the filename of the test as + * the first argument, followed by a {@code Callable} + * as the second argument. All a parameterized test needs to do is call + * {@code call} on the provided {@code Callable} to execute the test and + * fail if the test case is invalid. + * + *

For example, the following can be used as a unit test: + * + *

{@code
+     * import java.util.concurrent.Callable;
+     * import java.util.stream.Stream;
+     * import org.junit.jupiter.params.ParameterizedTest;
+     * import org.junit.jupiter.params.provider.MethodSource;
+     * import software.amazon.smithy.diff.testrunner.SmithyDiffTestCase;
+     * import software.amazon.smithy.diff.testrunner.SmithyDiffTestSuite;
+     *
+     * public class TestRunnerTest {
+     *     \@ParameterizedTest(name = "\{0\}")
+     *     \@MethodSource("source")
+     *     public void testRunner(String filename, Callable<SmithyDiffTestCase.Result> callable)
+     *         throws Exception {
+     *         callable.call();
+     *     }
+     *
+     *     public static Stream<?> source() {
+     *         return SmithyDiffTestSuite.defaultParameterizedTestSource(TestRunnerTest.class);
+     *     }
+     * }
+     * }
+ * + * @param contextClass The class to use for loading diffs and model discovery. + * @return Returns the Stream that should be used as a JUnit 5 {@code MethodSource} return value. + */ + public static Stream defaultParameterizedTestSource(Class contextClass) { + ClassLoader classLoader = contextClass.getClassLoader(); + ModelAssembler assembler = Model.assembler(classLoader).discoverModels(classLoader); + return SmithyDiffTestSuite.runner() + .setModelAssemblerFactory(assembler::copy) + .addTestCasesFromUrl(contextClass.getResource(DEFAULT_TEST_CASE_LOCATION)) + .parameterizedTestSource(); + } + + /** + * Factory method used to create a JUnit 5 {@code ParameterizedTest} + * {@code MethodSource}. + * + *

Test cases need to be added to the test suite before calling this, + * for example by using {@link #addTestCasesFromDirectory(Path)}. + * + *

Each returned {@code Object[]} contains the name of the test as + * the first argument, followed by a {@code Callable} + * as the second argument. All a parameterized test needs to do is call + * {@code call} on the provided {@code Callable} to execute the test and + * fail if the test case is invalid. + * + *

For example, the following can be used as a unit test: + * + *

{@code
+     * import java.util.concurrent.Callable;
+     * import java.util.stream.Stream;
+     * import org.junit.jupiter.params.ParameterizedTest;
+     * import org.junit.jupiter.params.provider.MethodSource;
+     * import software.amazon.smithy.diff.testrunner.SmithyDiffTestCase;
+     * import software.amazon.smithy.diff.testrunner.SmithyDiffTestSuite;
+     *
+     * public class TestRunnerTest {
+     *     \@ParameterizedTest(name = "\{0\}")
+     *     \@MethodSource("source")
+     *     public void testRunner(String filename, Callable<SmithyDiffTestCase.Result> callable)
+     *         throws Exception {
+     *         callable.call();
+     *     }
+     *
+     *     public static Stream<?> source() {
+     *         ModelAssembler assembler = Model.assembler(TestRunnerTest.class.getClassLoader());
+     *         return SmithyDiffTestSuite.runner()
+     *                 .setModelAssemblerFactory(assembler::copy)
+     *                 .addTestCasesFromUrl(TestRunnerTest.class.getResource("errorfiles"))
+     *                 .parameterizedTestSource();
+     *     }
+     * }
+     * }
+ * + * @return Returns the Stream that should be used as a JUnit 5 {@code MethodSource} return value. + */ + public Stream parameterizedTestSource() { + return cases.stream().map(testCase -> { + Callable callable = createTestCaseCallable(testCase); + Callable wrappedCallable = () -> callable.call().unwrap(); + return new Object[] {testCase.getName(), wrappedCallable}; + }); + } + + /** + * Adds a test case to the test suite. + * + * @param testCase Test case to add. + * @return Returns the test suite. + */ + public SmithyDiffTestSuite addTestCase(SmithyDiffTestCase testCase) { + cases.add(testCase); + return this; + } + + /** + * Adds test cases by crawling a directory and looking for events files + * that end with ".events". Corresponding ".a.(json|smithy)" and ".b.(json|smithy)" + * files are expected to be found for each found events file. + * + *

See {@link SmithyDiffTestCase#from} for a description of how + * the events file is expected to be formatted. + * + * @param modelDirectory Directory that contains diff models. + * @return Returns the test suite. + * @see SmithyDiffTestCase#from + */ + public SmithyDiffTestSuite addTestCasesFromDirectory(Path modelDirectory) { + try (Stream files = Files.walk(modelDirectory)) { + String modelDirectoryName = modelDirectory.toString(); + files + .filter(Files::isRegularFile) + .map(Path::toString) + .filter(fileName -> fileName.endsWith(EVENTS)) + .map(fileName -> SmithyDiffTestCase.from( + modelDirectory, + fileName.substring(modelDirectoryName.length() + 1, fileName.length() - EVENTS.length()))) + .forEach(this::addTestCase); + return this; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Convenience method for supplying a directory using a class loader. + * + * @param url URL that contains diff models. + * @return Returns the test suite. + * @throws IllegalArgumentException if a non-file scheme URL is provided. + * @see #addTestCasesFromDirectory + */ + public SmithyDiffTestSuite addTestCasesFromUrl(URL url) { + if (!url.getProtocol().equals("file")) { + throw new IllegalArgumentException("Only file URLs are supported by the testrunner: " + url); + } + + try { + return addTestCasesFromDirectory(Paths.get(url.toURI())); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + + /** + * Sets a custom {@link ModelAssembler} factory to use to create a + * {@code ModelAssembler} for each test case. + * + *

The supplier must return a new instance of a Model assembler + * each time it is called. Model assemblers are mutated and execute + * in parallel. + * + * @param modelAssemblerFactory Model assembler factory to use. + * @return Returns the test suite. + */ + public SmithyDiffTestSuite setModelAssemblerFactory(Supplier modelAssemblerFactory) { + this.modelAssemblerFactory = Objects.requireNonNull(modelAssemblerFactory); + return this; + } + + /** + * Creates a {@code Stream} of {@code Callable} objects that can be used + * to execute each test case. + * + *

The {@link SmithyDiffTestCase.Result#unwrap()} method must be called on + * the result of each callable in order to actually assert that the test + * case result is OK. + * + * @return Returns a stream of test case callables. + */ + public Stream> testCaseCallables() { + return cases.stream().map(this::createTestCaseCallable); + } + + private Callable createTestCaseCallable(SmithyDiffTestCase testCase) { + return () -> testCase.createResult(ModelDiff.compare( + getModel(testCase, modelAssemblerFactory.get(), MODEL_A), + getModel(testCase, modelAssemblerFactory.get(), MODEL_B))); + } + + private static Model getModel(SmithyDiffTestCase testCase, ModelAssembler assembler, String infix) { + Path modelPath = testCase.getPath().resolve(testCase.getName() + infix + EXT_SMITHY); + if (!Files.exists(modelPath)) { + modelPath = modelPath.resolveSibling(testCase.getName() + infix + EXT_JSON); + } + return assembler + .addImport(modelPath) + .assemble() + .unwrap(); + } + + /** + * Executes the test runner. + * + * @return Returns the test case result object on success. + * @throws Error if the validation events do not match expectations. + */ + public Result run() { + return run(ForkJoinPool.commonPool()); + } + + /** + * Executes the test runner with a specific {@code ExecutorService}. + * + *

Tests ideally should use JUnit 5's ParameterizedTest as described + * in {@link #parameterizedTestSource()}. However, this method can be + * used to run tests in parallel in other scenarios (like if you aren't + * using JUnit, or not running tests cases during unit tests). + * + * @param executorService Executor service to execute tests with. + * @return Returns the test case result object on success. + * @throws Error if the validation events do not match expectations. + */ + public Result run(ExecutorService executorService) { + List failedResults = Collections.synchronizedList(new ArrayList<>()); + List> callables = testCaseCallables().collect(Collectors.toList()); + + try { + for (Future future : executorService.invokeAll(callables)) { + SmithyDiffTestCase.Result testCaseResult = waitOnFuture(future); + if (testCaseResult.isInvalid()) { + failedResults.add(testCaseResult); + } + } + + Result result = new Result(callables.size() - failedResults.size(), failedResults); + if (failedResults.isEmpty()) { + return result; + } + + throw new Error(result); + } catch (InterruptedException e) { + executorService.shutdownNow(); + throw new Error("Error executing test suite: " + e.getMessage(), e); + } finally { + executorService.shutdown(); + } + } + + private SmithyDiffTestCase.Result waitOnFuture(Future future) + throws InterruptedException { + try { + return future.get(); + } catch (ExecutionException e) { + Throwable cause = e.getCause() != null ? e.getCause() : e; + // Try to throw the original exception as-is if possible. + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } else { + throw new Error("Error executing test case: " + e.getMessage(), cause); + } + } + } + + /** + * Value result of executing the test suite. + */ + public static final class Result { + private final int successCount; + private final List failedResults; + + Result(int successCount, List failedResults) { + this.successCount = successCount; + this.failedResults = Collections.unmodifiableList(failedResults); + } + + /** + * @return Returns the number of test cases that passed. + */ + public int getSuccessCount() { + return successCount; + } + + /** + * @return Returns the test cases that failed. + */ + public List getFailedResults() { + return failedResults; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(String.format( + "Smithy diff test runner encountered %d successful result(s), and %d failed result(s)", + successCount, failedResults.size())); + failedResults.forEach(failed -> builder.append('\n').append(failed.toString()).append('\n')); + return builder.toString(); + } + } + + /** + * Thrown when errors are encountered in the test runner. + */ + public static final class Error extends RuntimeException { + public final Result result; + + Error(Result result) { + super(result.toString()); + this.result = result; + } + + Error(String message, Throwable previous) { + super(message, previous); + this.result = new Result(0, Collections.emptyList()); + } + } +} diff --git a/smithy-diff/src/test/java/software/amazon/smithy/diff/DiffTest.java b/smithy-diff/src/test/java/software/amazon/smithy/diff/DiffTest.java new file mode 100644 index 00000000000..40c932ace0e --- /dev/null +++ b/smithy-diff/src/test/java/software/amazon/smithy/diff/DiffTest.java @@ -0,0 +1,47 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.diff; + +import static java.lang.String.format; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Callable; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import software.amazon.smithy.diff.ModelDiff; +import software.amazon.smithy.diff.testrunner.SmithyDiffTestCase; +import software.amazon.smithy.diff.testrunner.SmithyDiffTestSuite; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.SourceLocation; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.validation.Severity; +import software.amazon.smithy.model.validation.ValidationEvent; +import software.amazon.smithy.utils.IoUtils; +import software.amazon.smithy.utils.Pair; + +public class DiffTest { + @ParameterizedTest(name = "{0}") + @MethodSource("source") + public void testRunner(String filename, Callable callable) throws Exception { + callable.call(); + } + + public static Stream source() { + return SmithyDiffTestSuite.defaultParameterizedTestSource(DiffTest.class); + } +} diff --git a/smithy-diff/src/test/java/software/amazon/smithy/diff/testrunner/SmithyDiffTestCaseTest.java b/smithy-diff/src/test/java/software/amazon/smithy/diff/testrunner/SmithyDiffTestCaseTest.java new file mode 100644 index 00000000000..fcead75fd10 --- /dev/null +++ b/smithy-diff/src/test/java/software/amazon/smithy/diff/testrunner/SmithyDiffTestCaseTest.java @@ -0,0 +1,156 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package software.amazon.smithy.diff.testrunner; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Paths; +import java.util.Collections; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.validation.Severity; +import software.amazon.smithy.model.validation.ValidationEvent; +import software.amazon.smithy.utils.ListUtils; + +public class SmithyDiffTestCaseTest { + @Test + public void validatesThatEventsAreValid() { + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> + SmithyDiffTestCase.parseValidationEvent("[ERROR] - m", "filename")); + + assertTrue(e.getMessage().contains("`filename`")); + assertTrue(e.getMessage().contains("SUPPRESSED|NOTE|WARNING|DANGER|ERROR")); + } + + @Test + public void parsesValidEvents() { + SmithyDiffTestCase.parseValidationEvent("[ERROR] -: message | EventId /filename:0:0", "filename"); + } + + @Test + public void throwsOnNonExistentFiles() { + assertThrows(Exception.class, () -> + SmithyDiffTestCase.from(Paths.get("."), "nonexistent")); + } + + @Test + public void matchesMessageUsingPrefix() { + ValidationEvent actual = ValidationEvent.builder() + .id("FooBar") + .severity(Severity.DANGER) + .message("This is a test") + .build(); + ValidationEvent expected = actual.toBuilder().message("This is").build(); + SmithyDiffTestCase testCase = new SmithyDiffTestCase(Paths.get("."), "test", Collections.singletonList(expected)); + SmithyDiffTestCase.Result result = testCase.createResult(Collections.singletonList(actual)); + + assertThat(result.isInvalid(), is(false)); + } + + @Test + public void failsWhenMessageDoesNotMatchPrefix() { + ValidationEvent actual = ValidationEvent.builder() + .id("FooBar") + .severity(Severity.DANGER) + .message("Not a test") + .build(); + ValidationEvent expected = actual.toBuilder().message("This is").build(); + SmithyDiffTestCase testCase = new SmithyDiffTestCase(Paths.get("."), "test", Collections.singletonList(expected)); + SmithyDiffTestCase.Result result = testCase.createResult(Collections.singletonList(actual)); + + assertThat(result.isInvalid(), is(true)); + } + + @Test + public void matchesOnShapeId() { + ValidationEvent actual = ValidationEvent.builder() + .id("FooBar") + .severity(Severity.DANGER) + .message("abc") + .shapeId(ShapeId.from("foo.baz#Bar")) + .build(); + SmithyDiffTestCase testCase = new SmithyDiffTestCase(Paths.get("."), "test", Collections.singletonList(actual)); + SmithyDiffTestCase.Result result = testCase.createResult(Collections.singletonList(actual)); + + assertThat(result.isInvalid(), is(false)); + } + + @Test + public void failsWhenShapeIdDoesNotMatch() { + ValidationEvent actual = ValidationEvent.builder() + .id("FooBar") + .severity(Severity.DANGER) + .message("abc") + .shapeId(ShapeId.from("foo.baz#Bar")) + .build(); + ValidationEvent expected = actual.toBuilder().shapeId(null).build(); + SmithyDiffTestCase testCase = new SmithyDiffTestCase(Paths.get("."), "test", Collections.singletonList(expected)); + SmithyDiffTestCase.Result result = testCase.createResult(Collections.singletonList(actual)); + + assertThat(result.isInvalid(), is(true)); + } + + @Test + public void multilineEventsPrintedWhenFormatting() { + ValidationEvent e1 = ValidationEvent.builder() + .id("FooBar") + .severity(Severity.DANGER) + .message( + "1: first line\n" + + "1: second line\n" + + "1: third line\n") + .shapeId(ShapeId.from("foo.baz#Bar")) + .build(); + ValidationEvent e2 = ValidationEvent.builder() + .id("FooBar") + .severity(Severity.DANGER) + .message( + "2: first line\n" + + "2: second line\n" + + "2: third line\n") + .shapeId(ShapeId.from("foo.baz#Bar")) + .build(); + + SmithyDiffTestCase.Result result = new SmithyDiffTestCase.Result( + "test", + ListUtils.of(e1, e2), + ListUtils.of(e1, e2)); + + assertThat(result.toString(), equalTo("============================\n" + + "Model Diff Validation Result\n" + + "============================\n" + + "test\n" + + "\n" + + "Did not match the following events\n" + + "----------------------------------\n" + + "[DANGER] foo.baz#Bar: 1: first line\n" + + "1: second line\n" + + "1: third line\n" + + " | FooBar N/A:0:0\n" + + "\n" + + "[DANGER] foo.baz#Bar: 2: first line\n" + + "2: second line\n" + + "2: third line\n" + + " | FooBar N/A:0:0\n" + + "\n" + + "\n" + + "Encountered unexpected events\n" + + "-----------------------------\n" + + "[DANGER] foo.baz#Bar: 1: first line\n" + + "1: second line\n" + + "1: third line\n" + + " | FooBar N/A:0:0\n" + + "\n" + + "[DANGER] foo.baz#Bar: 2: first line\n" + + "2: second line\n" + + "2: third line\n" + + " | FooBar N/A:0:0\n" + + "\n")); + } +} diff --git a/smithy-diff/src/test/java/software/amazon/smithy/diff/testrunner/SmithyDiffTestSuiteTest.java b/smithy-diff/src/test/java/software/amazon/smithy/diff/testrunner/SmithyDiffTestSuiteTest.java new file mode 100644 index 00000000000..34f16088dde --- /dev/null +++ b/smithy-diff/src/test/java/software/amazon/smithy/diff/testrunner/SmithyDiffTestSuiteTest.java @@ -0,0 +1,50 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.diff.testrunner; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +import java.net.MalformedURLException; +import java.net.URL; +import org.junit.jupiter.api.Test; + +public class SmithyDiffTestSuiteTest { + @Test + public void throwsWhenFailed() { + try { + SmithyDiffTestSuite.runner() + .addTestCasesFromUrl(getClass().getResource("testrunner/invalid")) + .run(); + fail("Expected to throw"); + } catch (SmithyDiffTestSuite.Error e) { + assertThat(e.result.getSuccessCount(), is(1)); + assertThat(e.result.getFailedResults().size(), is(2)); + assertThat(e.getMessage(), containsString("Did not match the")); + assertThat(e.getMessage(), containsString("Encountered unexpected")); + } + } + + @Test + public void runsCaseWithFile() { + SmithyDiffTestSuite.Result result = SmithyDiffTestSuite.runner() + .addTestCasesFromUrl(getClass().getResource("testrunner/valid")) + .run(); + + assertThat(result.getFailedResults().size(), is(0)); + assertThat(result.getSuccessCount(), is(3)); + } + + @Test + public void onlySupportsFiles() throws MalformedURLException { + assertThrows(IllegalArgumentException.class, () -> { + SmithyDiffTestSuite.runner().addTestCasesFromUrl(new URL("https://127.0.0.1/foo")); + }); + } +} diff --git a/smithy-diff/src/test/resources/software/amazon/smithy/diff/diffs/jsonName/change-jsonName-value.a.smithy b/smithy-diff/src/test/resources/software/amazon/smithy/diff/diffs/jsonName/change-jsonName-value.a.smithy new file mode 100644 index 00000000000..eeb8fa5777c --- /dev/null +++ b/smithy-diff/src/test/resources/software/amazon/smithy/diff/diffs/jsonName/change-jsonName-value.a.smithy @@ -0,0 +1,8 @@ +$version: "2.0" + +namespace ns.foo + +structure Test { + @jsonName("a") + member: String +} diff --git a/smithy-diff/src/test/resources/software/amazon/smithy/diff/diffs/jsonName/change-jsonName-value.b.smithy b/smithy-diff/src/test/resources/software/amazon/smithy/diff/diffs/jsonName/change-jsonName-value.b.smithy new file mode 100644 index 00000000000..bc8a11f5345 --- /dev/null +++ b/smithy-diff/src/test/resources/software/amazon/smithy/diff/diffs/jsonName/change-jsonName-value.b.smithy @@ -0,0 +1,8 @@ +$version: "2.0" + +namespace ns.foo + +structure Test { + @jsonName("b") + member: String +} diff --git a/smithy-diff/src/test/resources/software/amazon/smithy/diff/diffs/jsonName/change-jsonName-value.events b/smithy-diff/src/test/resources/software/amazon/smithy/diff/diffs/jsonName/change-jsonName-value.events new file mode 100644 index 00000000000..68b28ad4313 --- /dev/null +++ b/smithy-diff/src/test/resources/software/amazon/smithy/diff/diffs/jsonName/change-jsonName-value.events @@ -0,0 +1,3 @@ +[ERROR] ns.foo#Test$member: Changed trait `smithy.api#jsonName` from `a` to `b` | TraitBreakingChange.Update.smithy.api#jsonName +----- +[WARNING] ns.foo#Test$member: Changed trait `smithy.api#jsonName` from `a` to `b` | ModifiedTrait.Update.smithy.api#jsonName diff --git a/smithy-diff/src/test/resources/software/amazon/smithy/diff/diffs/tags/change-tags-content.a.smithy b/smithy-diff/src/test/resources/software/amazon/smithy/diff/diffs/tags/change-tags-content.a.smithy new file mode 100644 index 00000000000..ffe2c1d2c5a --- /dev/null +++ b/smithy-diff/src/test/resources/software/amazon/smithy/diff/diffs/tags/change-tags-content.a.smithy @@ -0,0 +1,12 @@ +$version: "2.0" + +namespace ns.foo + +@tags(["toAdd"]) +structure TestA {} + +@tags(["toRemove"]) +structure TestB {} + +@tags(["to", "switch"]) +structure TestC {} diff --git a/smithy-diff/src/test/resources/software/amazon/smithy/diff/diffs/tags/change-tags-content.b.smithy b/smithy-diff/src/test/resources/software/amazon/smithy/diff/diffs/tags/change-tags-content.b.smithy new file mode 100644 index 00000000000..9a01a41d42d --- /dev/null +++ b/smithy-diff/src/test/resources/software/amazon/smithy/diff/diffs/tags/change-tags-content.b.smithy @@ -0,0 +1,12 @@ +$version: "2.0" + +namespace ns.foo + +@tags(["toAdd", "added"]) +structure TestA {} + +@tags([]) +structure TestB {} + +@tags(["switch", "to"]) +structure TestC {} diff --git a/smithy-diff/src/test/resources/software/amazon/smithy/diff/diffs/tags/change-tags-content.events b/smithy-diff/src/test/resources/software/amazon/smithy/diff/diffs/tags/change-tags-content.events new file mode 100644 index 00000000000..5c53e99230f --- /dev/null +++ b/smithy-diff/src/test/resources/software/amazon/smithy/diff/diffs/tags/change-tags-content.events @@ -0,0 +1,38 @@ +[NOTE] ns.foo#TestA: Changed trait `smithy.api#tags` from +``` +[ + "toAdd" +] +``` + to +``` +[ + "toAdd", + "added" +] +``` + | ModifiedTrait.Update.smithy.api#tags +----- +[NOTE] ns.foo#TestB: Changed trait `smithy.api#tags` from +``` +[ + "toRemove" +] +``` + to `[]` | ModifiedTrait.Update.smithy.api#tags +----- +[NOTE] ns.foo#TestC: Changed trait `smithy.api#tags` from +``` +[ + "to", + "switch" +] +``` + to +``` +[ + "switch", + "to" +] +``` + | ModifiedTrait.Update.smithy.api#tags diff --git a/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/diff1.a.smithy b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/diff1.a.smithy new file mode 100644 index 00000000000..967d0ce282e --- /dev/null +++ b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/diff1.a.smithy @@ -0,0 +1,7 @@ +$version: "2" + +namespace smithy.example + +structure MyStructure { + foo: String +} diff --git a/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/diff1.b.smithy b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/diff1.b.smithy new file mode 100644 index 00000000000..967d0ce282e --- /dev/null +++ b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/diff1.b.smithy @@ -0,0 +1,7 @@ +$version: "2" + +namespace smithy.example + +structure MyStructure { + foo: String +} diff --git a/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/diff1.events b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/diff1.events new file mode 100644 index 00000000000..e69de29bb2d diff --git a/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/diff2.a.smithy b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/diff2.a.smithy new file mode 100644 index 00000000000..7c61d42339b --- /dev/null +++ b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/diff2.a.smithy @@ -0,0 +1,7 @@ +$version: "2" + +namespace smithy.example + +structure MyStructure { + foo: Integer +} diff --git a/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/diff2.b.smithy b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/diff2.b.smithy new file mode 100644 index 00000000000..967d0ce282e --- /dev/null +++ b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/diff2.b.smithy @@ -0,0 +1,7 @@ +$version: "2" + +namespace smithy.example + +structure MyStructure { + foo: String +} diff --git a/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/diff2.events b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/diff2.events new file mode 100644 index 00000000000..e69de29bb2d diff --git a/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/nested/diff3.a.smithy b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/nested/diff3.a.smithy new file mode 100644 index 00000000000..7c61d42339b --- /dev/null +++ b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/nested/diff3.a.smithy @@ -0,0 +1,7 @@ +$version: "2" + +namespace smithy.example + +structure MyStructure { + foo: Integer +} diff --git a/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/nested/diff3.b.smithy b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/nested/diff3.b.smithy new file mode 100644 index 00000000000..967d0ce282e --- /dev/null +++ b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/nested/diff3.b.smithy @@ -0,0 +1,7 @@ +$version: "2" + +namespace smithy.example + +structure MyStructure { + foo: String +} diff --git a/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/nested/diff3.events b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/nested/diff3.events new file mode 100644 index 00000000000..648705cc258 --- /dev/null +++ b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/invalid/nested/diff3.events @@ -0,0 +1 @@ +[ERROR] smithy.example#MyStructure$foo: The shape targeted by the member `smithy.example#MyStructure$foo` changed from `smithy.api#Boolean` (boolean) to `smithy.api#String` (string). The type of the targeted shape changed from boolean to string. | ChangedMemberTarget diff --git a/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/diff1.a.smithy b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/diff1.a.smithy new file mode 100644 index 00000000000..967d0ce282e --- /dev/null +++ b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/diff1.a.smithy @@ -0,0 +1,7 @@ +$version: "2" + +namespace smithy.example + +structure MyStructure { + foo: String +} diff --git a/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/diff1.b.smithy b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/diff1.b.smithy new file mode 100644 index 00000000000..967d0ce282e --- /dev/null +++ b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/diff1.b.smithy @@ -0,0 +1,7 @@ +$version: "2" + +namespace smithy.example + +structure MyStructure { + foo: String +} diff --git a/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/diff1.events b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/diff1.events new file mode 100644 index 00000000000..e69de29bb2d diff --git a/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/diff2.a.smithy b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/diff2.a.smithy new file mode 100644 index 00000000000..7c61d42339b --- /dev/null +++ b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/diff2.a.smithy @@ -0,0 +1,7 @@ +$version: "2" + +namespace smithy.example + +structure MyStructure { + foo: Integer +} diff --git a/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/diff2.b.smithy b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/diff2.b.smithy new file mode 100644 index 00000000000..967d0ce282e --- /dev/null +++ b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/diff2.b.smithy @@ -0,0 +1,7 @@ +$version: "2" + +namespace smithy.example + +structure MyStructure { + foo: String +} diff --git a/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/diff2.events b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/diff2.events new file mode 100644 index 00000000000..110f7e34601 --- /dev/null +++ b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/diff2.events @@ -0,0 +1 @@ +[ERROR] smithy.example#MyStructure$foo: The shape targeted by the member `smithy.example#MyStructure$foo` changed from `smithy.api#Integer` (integer) to `smithy.api#String` (string). The type of the targeted shape changed from integer to string. | ChangedMemberTarget diff --git a/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/nested/diff3.a.smithy b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/nested/diff3.a.smithy new file mode 100644 index 00000000000..7c61d42339b --- /dev/null +++ b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/nested/diff3.a.smithy @@ -0,0 +1,7 @@ +$version: "2" + +namespace smithy.example + +structure MyStructure { + foo: Integer +} diff --git a/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/nested/diff3.b.smithy b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/nested/diff3.b.smithy new file mode 100644 index 00000000000..967d0ce282e --- /dev/null +++ b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/nested/diff3.b.smithy @@ -0,0 +1,7 @@ +$version: "2" + +namespace smithy.example + +structure MyStructure { + foo: String +} diff --git a/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/nested/diff3.events b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/nested/diff3.events new file mode 100644 index 00000000000..110f7e34601 --- /dev/null +++ b/smithy-diff/src/test/resources/software/amazon/smithy/diff/testrunner/testrunner/valid/nested/diff3.events @@ -0,0 +1 @@ +[ERROR] smithy.example#MyStructure$foo: The shape targeted by the member `smithy.example#MyStructure$foo` changed from `smithy.api#Integer` (integer) to `smithy.api#String` (string). The type of the targeted shape changed from integer to string. | ChangedMemberTarget