-
Notifications
You must be signed in to change notification settings - Fork 244
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(compliance): produce compliance test reports (#1368)
Generates reports (xUnit or JSON) out of compliance test executions so that we can assert conformance based on the spec.
- Loading branch information
1 parent
10ddd10
commit 11ef55d
Showing
21 changed files
with
419 additions
and
131 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
59 changes: 59 additions & 0 deletions
59
.../java-runtime-test/project/src/test/java/software/amazon/jsii/ComplianceSuiteHarness.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package software.amazon.jsii; | ||
|
||
import com.fasterxml.jackson.core.util.DefaultIndenter; | ||
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.fasterxml.jackson.databind.node.ObjectNode; | ||
import org.junit.jupiter.api.extension.AfterAllCallback; | ||
import org.junit.jupiter.api.extension.AfterEachCallback; | ||
import org.junit.jupiter.api.extension.BeforeEachCallback; | ||
import org.junit.jupiter.api.extension.ExtensionContext; | ||
|
||
import java.io.*; | ||
import java.util.ArrayList; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
public final class ComplianceSuiteHarness implements BeforeEachCallback, AfterEachCallback, AfterAllCallback { | ||
private final ObjectMapper objectMapper = new ObjectMapper(); | ||
private final ObjectNode result = objectMapper.createObjectNode(); | ||
private final Map<String, List<String>> kernelTraces = new HashMap<>(); | ||
|
||
@Override | ||
public void beforeEach(final ExtensionContext extensionContext) throws Exception { | ||
final List<String> trace = new ArrayList<>(); | ||
kernelTraces.put(extensionContext.getUniqueId(), trace); | ||
JsiiRuntime.messageInspector.set((message, type) -> { | ||
final String prefix = type == MessageInspector.MessageType.Request ? "<" : ">"; | ||
try { | ||
trace.add(String.format("%s %s%n", prefix, objectMapper.writeValueAsString(message))); | ||
System.err.printf("%s %s%n", prefix, objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(message)); | ||
} catch (final IOException e) { | ||
throw new UncheckedIOException(e); | ||
} | ||
}); | ||
|
||
JsiiEngine.reset(); | ||
} | ||
|
||
@Override | ||
public void afterEach(final ExtensionContext extensionContext) { | ||
JsiiRuntime.messageInspector.remove(); | ||
final ObjectNode entry = result.putObject(String.format("%s.%s", | ||
extensionContext.getRequiredTestClass().getSimpleName(), extensionContext.getRequiredTestMethod().getName())); | ||
entry.put("status", extensionContext.getExecutionException().isPresent() ? "failure" : "success"); | ||
entry.putPOJO("kernelTrace", kernelTraces.remove(extensionContext.getUniqueId())); | ||
} | ||
|
||
@Override | ||
public void afterAll(final ExtensionContext extensionContext) throws IOException { | ||
final File file = new File("./compliance-report.json"); | ||
try (final OutputStream os = new FileOutputStream(file)) { | ||
this.objectMapper.writer(new DefaultPrettyPrinter().withArrayIndenter(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE)).writeValue(os, this.result); | ||
} catch (IOException ioe) { | ||
System.err.println("Failed writing test report: " + ioe.getMessage()); | ||
throw ioe; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
...ii/java-runtime-test/project/src/test/java/software/amazon/jsii/ReloadingClassLoader.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package software.amazon.jsii; | ||
|
||
import java.net.URL; | ||
import java.net.URLClassLoader; | ||
import java.security.AccessController; | ||
import java.util.stream.Stream; | ||
|
||
/** | ||
* This wonderful utility can be used to reload classes regardless of whether it was already loaded by the current | ||
* ClassLoader or not. This is particularly useful when a test needs to go through the burden of checking static | ||
* initialization is happening as designed. | ||
* | ||
* It leverages black magic in the form of shameless down-casting of classloaders to URLClassLoader and may or may not | ||
* spectacularly blow up on new JVM major versions. | ||
* | ||
* THIS IS A DRAGONS LAIR AND YOU SHOULD TREAD CAREFULLY, SO AS NOT TO STEP ON A DRAGON'S TAIL. | ||
*/ | ||
public final class ReloadingClassLoader extends URLClassLoader { | ||
/** | ||
* Reloads one or more classes, returning the newly loaded version of the first one. | ||
* | ||
* @param parent is the parent ClassLoader to use for classes that do not need to be re-loaded. | ||
* @param clazz is the first class that needs to be reloaded. | ||
* @param others a list of any other class that also needs to be reloaded. | ||
* @param <T> the static type of the reloaded {@code class}. | ||
* | ||
* @return the reloaded version of {@code class}. | ||
*/ | ||
@SuppressWarnings("unchecked") | ||
public static <T> Class<T> reload(final ClassLoader parent, final Class<T> clazz, final Class<?> ...others) { | ||
final ClassLoader cl = new ReloadingClassLoader(parent, Stream.concat(Stream.of(clazz), Stream.of(others)).toArray(Class[]::new)); | ||
try { | ||
return (Class<T>)cl.loadClass(binaryNameOf(clazz)); | ||
} catch (final ClassNotFoundException cnfe) { | ||
// This is theoretically impossible! | ||
throw new RuntimeException(cnfe); | ||
} | ||
} | ||
|
||
/** | ||
* Obtains the "binary name" of a class. This is needed because ClassLoaders expect a binary name, and not a | ||
* canonical class name. Binary names have the pesky "$" separator instead of a "." for member classes. | ||
* | ||
* @param clazz the class which binary name is needed. | ||
* | ||
* @return {@code clazz}' binary name. | ||
*/ | ||
private static String binaryNameOf(final Class<?> clazz) { | ||
if (!clazz.isMemberClass()) { | ||
return clazz.getCanonicalName(); | ||
} | ||
final Class<?> declaringClass = clazz.getDeclaringClass(); | ||
return String.format("%s$%s", binaryNameOf(declaringClass), clazz.getSimpleName()); | ||
} | ||
|
||
private final Class<?>[] toReload; | ||
|
||
private ReloadingClassLoader(final ClassLoader parent, final Class<?> ...toReload) { | ||
super( | ||
Stream.of(toReload) | ||
.flatMap(clazz -> Stream.of(((URLClassLoader) clazz.getClassLoader()).getURLs())) | ||
.toArray(URL[]::new), | ||
parent | ||
); | ||
this.toReload = toReload; | ||
} | ||
|
||
@Override | ||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { | ||
if (Stream.of(this.toReload).map(ReloadingClassLoader::binaryNameOf).noneMatch(clazz -> clazz.equals(name))) { | ||
// Not to be reloaded - delegate to the standard flow. | ||
return super.loadClass(name, resolve); | ||
} | ||
// Class is to be reloaded. Conveniently, "findClass" does just that! | ||
final Class<?> result = this.findClass(name); | ||
if (resolve) { | ||
this.resolveClass(result); | ||
} | ||
return result; | ||
} | ||
} |
Oops, something went wrong.