Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// This example fails because smithyFormat is run on a model with a syntax error.
// format is intentionally left enabled (the default) to exercise the smithyFormat task path.
// the build must fail with the real syntax error message, not a secondary NoClassDefFoundError
// caused by smithy-model types escaping the isolated classloader.

plugins {
id("java-library")
id("software.amazon.smithy.gradle.smithy-base").version("1.3.0")
}

repositories {
mavenLocal()
mavenCentral()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace smithy.example

// Intentional syntax error: bare keyword with no identifier following it.
// The smithyFormat task must fail with a clear "Cannot format invalid models" message,
// not a secondary NoClassDefFoundError (regression for SMITHY-3541).
structure

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
rootProject.name = "syntax-error-format"

pluginManagement {
repositories {
mavenLocal()
mavenCentral()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"version": "1.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package software.amazon.smithy.gradle;

import org.gradle.testkit.runner.BuildResult;
import org.gradle.testkit.runner.GradleRunner;
import org.gradle.testkit.runner.TaskOutcome;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

/**
* Regression test for case when smithyFormat runs on a model with a syntax error,
* the build must fail with the real "Cannot format invalid models" message rather than a
* secondary NoClassDefFoundError caused by smithy-model types (e.g. ShapeId) escaping the
* isolated URLClassLoader and failing Gradle's daemon exception serializer.
*/
public class SyntaxErrorFormatTest {
@Test
public void formatTaskFailsWithReadableMessageOnSyntaxError() {
Utils.withCopy("base-plugin/failure-cases/syntax-error-format", buildDir -> {
BuildResult result = GradleRunner.create()
.forwardOutput()
.withProjectDir(buildDir)
.withArguments("smithyFormat", "--stacktrace")
.buildAndFail();

// The real error from the Smithy formatter must be present.
Assertions.assertTrue(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using assertThat with containsString from hamcrest will give better errors

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, can do it as follow up.

result.getOutput().contains("Cannot format invalid models"),
"Expected 'Cannot format invalid models' in :smithyFormat output but got:\n" + result.getOutput());

// A NoClassDefFoundError for ShapeId would indicate the classloader isolation bug has regressed.
Assertions.assertFalse(
result.getOutput().contains("NoClassDefFoundError"),
"Unexpected NoClassDefFoundError in output -- classloader isolation regression:\n"
+ result.getOutput());

// The smithyFormat task itself must be the one that failed, not some other task.
Assertions.assertEquals(TaskOutcome.FAILED, result.task(":smithyFormat").getOutcome());
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,20 @@ public void execute() {
smithyCliClass.getDeclaredMethod("run", List.class)
.invoke(cli, getParameters().getArguments().get());
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
// Unwrap to the root cause message. We intentionally do NOT reuse
// unwrapException() here and do NOT attach the cause to GradleException:
// smithy-model types (e.g. ModelSyntaxException, which references ShapeId)
// are loaded only in the isolated URLClassLoader, so Gradle's daemon
// serializer cannot see them. Attaching the cause chain would produce a
// secondary NoClassDefFoundError that obscures the real error.
Throwable cause = e;
while (cause.getCause() != null) {
cause = cause.getCause();
}
String message = cause.getMessage() != null
? cause.getMessage()
: cause.getClass().getName();
throw new GradleException(message);
}
});
});
Expand Down