diff --git a/itests/integration.feature b/itests/integration.feature new file mode 100644 index 000000000..ae16ad4b2 --- /dev/null +++ b/itests/integration.feature @@ -0,0 +1,29 @@ +Feature: integration + + Scenario: integration success + * command('jbang integration/inttest.java') + * match err !contains 'Integration... (out)' + * match err contains 'Integration... (err)' + * match out contains "Integration test" + + Scenario: integration success, verbose + * command('jbang --verbose integration/inttest.java') + * match err contains 'Integration... (out)' + * match err contains 'Integration... (err)' + * match out contains "Integration test" + + Scenario: integration failure + * command('jbang -Dfailintegration=1 integration/inttest.java') + * match err !contains 'Integration... (out)' + * match err contains 'Integration... (err)' + * match err contains 'Issue running postBuild()' + * match err !contains 'Failing integration...' + * match exit == 1 + + Scenario: integration failure, verbose + * command('jbang -Dfailintegration=1 --verbose integration/inttest.java') + * match err contains 'Integration... (out)' + * match err contains 'Integration... (err)' + * match err contains 'Issue running postBuild()' + * match err contains 'Failing integration...' + * match exit == 1 diff --git a/itests/integration/IntegrationTest.java b/itests/integration/IntegrationTest.java new file mode 100644 index 000000000..fa1a53689 --- /dev/null +++ b/itests/integration/IntegrationTest.java @@ -0,0 +1,23 @@ +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class IntegrationTest { + public static Map postBuild(Path temporaryJar, + Path pomFile, + List> repositories, + List> dependencies, + List comments, + boolean nativeImage) { + System.out.println("Integration... (out)"); + System.err.println("Integration... (err)"); + if (System.getProperty("failintegration") != null) { + throw new RuntimeException("Failing integration..."); + } else { + System.out.println("Integration OK (out)"); + System.err.println("Integration OK (err)"); + return Collections.emptyMap(); + } + } +} diff --git a/itests/integration/inttest.java b/itests/integration/inttest.java new file mode 100644 index 000000000..74f1cb4df --- /dev/null +++ b/itests/integration/inttest.java @@ -0,0 +1,9 @@ + +//SOURCES IntegrationTest.java +//FILES META-INF/jbang-integration.list=jb-int.list + +class inttest { + public static void main(String... args) { + System.out.println("Integration test"); + } +} diff --git a/itests/integration/jb-int.list b/itests/integration/jb-int.list new file mode 100644 index 000000000..a0b2f88fe --- /dev/null +++ b/itests/integration/jb-int.list @@ -0,0 +1 @@ +IntegrationTest diff --git a/src/main/java/dev/jbang/spi/IntegrationInput.java b/src/main/java/dev/jbang/spi/IntegrationInput.java index 840fa0e5d..de94a2494 100644 --- a/src/main/java/dev/jbang/spi/IntegrationInput.java +++ b/src/main/java/dev/jbang/spi/IntegrationInput.java @@ -17,10 +17,11 @@ public class IntegrationInput { public final List comments; @SerializedName(value = "native") public final boolean nativeRequested; + public final boolean verbose; public IntegrationInput(String integrationClassName, Path source, Path classes, Path pom, Map repositories, Map dependencies, List comments, - boolean nativeRequested) { + boolean nativeRequested, boolean verbose) { this.integrationClassName = integrationClassName; this.source = source; this.classes = classes; @@ -29,5 +30,6 @@ public IntegrationInput(String integrationClassName, Path source, Path classes, this.dependencies = dependencies; this.comments = comments; this.nativeRequested = nativeRequested; + this.verbose = verbose; } } diff --git a/src/main/java/dev/jbang/spi/IntegrationManager.java b/src/main/java/dev/jbang/spi/IntegrationManager.java index 461eb9d61..f483700b8 100644 --- a/src/main/java/dev/jbang/spi/IntegrationManager.java +++ b/src/main/java/dev/jbang/spi/IntegrationManager.java @@ -6,6 +6,7 @@ import java.io.BufferedReader; import java.io.BufferedWriter; +import java.io.File; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; @@ -25,6 +26,7 @@ import java.nio.file.Path; import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.annotation.Nonnull; @@ -78,7 +80,7 @@ public static IntegrationResult runIntegrations(Project prj, Path tmpJarDir, Pat ClassLoader old = Thread.currentThread().getContextClassLoader(); PrintStream oldout = System.out; try { - URLClassLoader integrationCl = getClassLoader(deps.values()); + URLClassLoader integrationCl = getClassLoader(tmpJarDir, deps.values()); Thread.currentThread().setContextClassLoader(integrationCl); String requestedJavaVersion = prj.getJavaVersion(); Set classNames = loadIntegrationClassNames(integrationCl); @@ -87,8 +89,7 @@ public static IntegrationResult runIntegrations(Project prj, Path tmpJarDir, Pat ? source.getResourceRef().getFile().toAbsolutePath() : null; IntegrationInput input = new IntegrationInput(className, srcPath, tmpJarDir, pomPath, repos, deps, - comments, - prj.isNativeImage()); + comments, prj.isNativeImage(), Util.isVerbose()); IntegrationResult ir = requestedJavaVersion == null || JavaUtil.satisfiesRequestedVersion( requestedJavaVersion, JavaUtil.getCurrentMajorJavaVersion()) ? runIntegrationEmbedded(input, integrationCl) @@ -112,8 +113,9 @@ public static IntegrationResult runIntegrations(Project prj, Path tmpJarDir, Pat } @Nonnull - private static URLClassLoader getClassLoader(Collection deps) { - URL[] urls = deps.stream().map(path -> { + private static URLClassLoader getClassLoader(Path classes, Collection deps) { + Stream paths = Stream.concat(Stream.of(classes), deps.stream()); + URL[] urls = paths.map(path -> { try { return path.toUri().toURL(); } catch (MalformedURLException e) { @@ -221,8 +223,19 @@ private static IntegrationResult runIntegrationExternal(IntegrationInput input, for (Map.Entry entry : properties.entrySet()) { args.add("-D" + entry.getKey() + "=" + entry.getValue()); } + + Path jbangJar = Util.getJarLocation(); args.add("-cp"); - args.add(Util.getJarLocation().toString()); + if (jbangJar.toString().endsWith(".jar")) { + args.add(jbangJar.toString()); + } else { + // We will assume that we're running inside an IDE or + // some kind of test environment and need to manually + // add the Gson dependency + Path gsonJar = Util.getJarLocation(Gson.class); + args.add(jbangJar + File.pathSeparator + gsonJar); + } + args.add("dev.jbang.spi.IntegrationManager"); if (Util.isVerbose()) { @@ -267,10 +280,11 @@ public static void main(String... args) { IntegrationInput input = parser.fromJson(new InputStreamReader(System.in), IntegrationInput.class); ClassLoader old = Thread.currentThread().getContextClassLoader(); PrintStream oldout = System.out; + Util.setVerbose(input.verbose); String output = ""; boolean ok = false; try { - URLClassLoader integrationCl = getClassLoader(input.dependencies.values()); + URLClassLoader integrationCl = getClassLoader(input.classes, input.dependencies.values()); Thread.currentThread().setContextClassLoader(integrationCl); IntegrationResult result = runIntegrationEmbedded(input, integrationCl); output = parser.toJson(result); @@ -281,6 +295,9 @@ public static void main(String... args) { output = "Integration class missing method with signature public static Map postBuild(Path classesDir, Path pomFile, List> dependencies)"; } catch (Exception e) { output = "Issue running postBuild()"; + if (input.verbose) { + e.printStackTrace(System.err); + } } finally { Thread.currentThread().setContextClassLoader(old); System.setOut(oldout); diff --git a/src/main/java/dev/jbang/util/Util.java b/src/main/java/dev/jbang/util/Util.java index 50d755692..21ec45f3f 100644 --- a/src/main/java/dev/jbang/util/Util.java +++ b/src/main/java/dev/jbang/util/Util.java @@ -1689,8 +1689,17 @@ public static boolean runningManagedJBang() { * @return An actual Path if it was found, or an empty path if it was not */ public static Path getJarLocation() { + return getJarLocation(VersionChecker.class); + } + + /** + * Determines the path to the JAR that contains the given class + * + * @return An actual Path if it was found, or an empty path if it was not + */ + public static Path getJarLocation(Class klazz) { try { - File jarFile = new File(VersionChecker.class.getProtectionDomain().getCodeSource().getLocation().toURI()); + File jarFile = new File(klazz.getProtectionDomain().getCodeSource().getLocation().toURI()); return jarFile.toPath(); } catch (URISyntaxException e) { // ignore diff --git a/src/test/java/dev/jbang/cli/TestJdk.java b/src/test/java/dev/jbang/cli/TestJdk.java index f7147657b..b2efc58df 100644 --- a/src/test/java/dev/jbang/cli/TestJdk.java +++ b/src/test/java/dev/jbang/cli/TestJdk.java @@ -14,7 +14,6 @@ import java.nio.file.Path; import java.util.Arrays; import java.util.function.BiConsumer; -import java.util.function.BiFunction; import java.util.function.Function; import org.junit.jupiter.api.BeforeEach; @@ -442,7 +441,6 @@ private void createMockJdk(int jdkVersion, BiConsumer init) { } } - private void initMockJdkDirRuntime(Path jdkPath, String version) { initMockJdkDir(jdkPath, version, "JAVA_RUNTIME_VERSION"); } @@ -450,6 +448,7 @@ private void initMockJdkDirRuntime(Path jdkPath, String version) { private void initMockJdkDir(Path jdkPath, String version) { initMockJdkDir(jdkPath, version, "JAVA_VERSION"); } + private void initMockJdkDir(Path jdkPath, String version, String key) { Util.mkdirs(jdkPath); String rawJavaVersion = key + "=\"" + version + "\"";