999999-SNAPSHOT
-
2.361
1.37
jenkinsci/${project.artifactId}
diff --git a/src/main/java/org/jvnet/hudson/test/PluginUtils.java b/src/main/java/org/jvnet/hudson/test/PluginUtils.java
new file mode 100644
index 000000000..de6e128a7
--- /dev/null
+++ b/src/main/java/org/jvnet/hudson/test/PluginUtils.java
@@ -0,0 +1,75 @@
+package org.jvnet.hudson.test;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+class PluginUtils {
+
+ /**
+ * Creates the plugin used by RealJenkinsRule
+ * @param destinationDirectory directory to write the plugin to.
+ * @param baseline the version of Jenkins to target
+ * @throws IOException if something goes wrong whilst creating the plugin.
+ * @return File the plugin we just created
+ */
+ @SuppressFBWarnings(value="PATH_TRAVERSAL_IN", justification = "jth is a test utility, this is package scope code")
+ static File createRealJenkinsRulePlugin(File destinationDirectory, String baseline) throws IOException {
+ final String pluginName = RealJenkinsRuleInit.class.getSimpleName();
+
+ // The manifest is reused in the plugin and the classes jar.
+ Manifest mf = new Manifest();
+ Attributes mainAttributes = mf.getMainAttributes();
+ mainAttributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
+ mainAttributes.putValue("Plugin-Class", RealJenkinsRuleInit.class.getName());
+ mainAttributes.putValue("Extension-Name", pluginName);
+ mainAttributes.putValue("Short-Name", pluginName);
+ mainAttributes.putValue("Long-Name", "RealJenkinsRule initialization wrapper");
+ mainAttributes.putValue("Plugin-Version", "0-SNAPSHOT (private rjr)");
+ mainAttributes.putValue("Support-Dynamic-Loading", "true");
+ mainAttributes.putValue("Jenkins-Version", baseline);
+
+ // we need to create a jar for the classes which we can then put into the plugin.
+ Path tmpClassesJar = Files.createTempFile("rjr", "jar");
+ try {
+ try (FileOutputStream fos = new FileOutputStream(tmpClassesJar.toFile());
+ JarOutputStream classesJarOS = new JarOutputStream(fos, mf)) {
+ // the actual class
+ try (InputStream classIS = RealJenkinsRuleInit.class.getResourceAsStream(RealJenkinsRuleInit.class.getSimpleName() + ".class")) {
+ String path = RealJenkinsRuleInit.class.getPackageName().replace('.', '/');
+ createJarEntry(classesJarOS, path + '/' + RealJenkinsRuleInit.class.getSimpleName() + ".class", classIS);
+ }
+ }
+
+ // the actual JPI
+ File jpi = new File(destinationDirectory, pluginName+".jpi");
+ try (FileOutputStream fos = new FileOutputStream(jpi); JarOutputStream jos = new JarOutputStream(fos, mf)) {
+ try (FileInputStream fis = new FileInputStream(tmpClassesJar.toFile())) {
+ createJarEntry(jos, "WEB-INF/lib/" + pluginName + ".jar", fis);
+ }
+ }
+ return jpi;
+ } finally {
+ Files.delete(tmpClassesJar);
+ }
+ }
+
+ private static void createJarEntry(JarOutputStream jos, String entryName, InputStream data) throws IOException {
+ JarEntry je = new JarEntry(entryName);
+ jos.putNextEntry(je);
+ data.transferTo(jos);
+ jos.closeEntry();
+ }
+
+}
diff --git a/src/main/java/org/jvnet/hudson/test/RealJenkinsRule.java b/src/main/java/org/jvnet/hudson/test/RealJenkinsRule.java
index 65aa6278f..76a035b4c 100644
--- a/src/main/java/org/jvnet/hudson/test/RealJenkinsRule.java
+++ b/src/main/java/org/jvnet/hudson/test/RealJenkinsRule.java
@@ -75,6 +75,7 @@
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
import java.util.logging.ConsoleHandler;
@@ -512,9 +513,14 @@ private void provision() throws Exception {
if (localData != null) {
new HudsonHomeLoader.Local(description.getTestClass().getMethod(description.getMethodName()), localData.value()).copy(getHome());
}
+
File plugins = new File(getHome(), "plugins");
Files.createDirectories(plugins.toPath());
- FileUtils.copyURLToFile(RealJenkinsRule.class.getResource("RealJenkinsRuleInit.jpi"), new File(plugins, "RealJenkinsRuleInit.jpi"));
+ try (JarFile jf = new JarFile(war)) {
+ // set the version to the version of jenkins used for testing to avoid dragging in detached plugins
+ String targetJenkinsVersion = jf.getManifest().getMainAttributes().getValue("Jenkins-Version");
+ PluginUtils.createRealJenkinsRulePlugin(plugins, targetJenkinsVersion);
+ }
if (includeTestClasspathPlugins) {
// Adapted from UnitTestSupportingPluginManager & JenkinsRule.recipeLoadCurrentPlugin:
diff --git a/RealJenkinsRuleInit/src/main/java/org/jvnet/hudson/test/RealJenkinsRuleInit.java b/src/main/java/org/jvnet/hudson/test/RealJenkinsRuleInit.java
similarity index 70%
rename from RealJenkinsRuleInit/src/main/java/org/jvnet/hudson/test/RealJenkinsRuleInit.java
rename to src/main/java/org/jvnet/hudson/test/RealJenkinsRuleInit.java
index a0ffd80bb..dd357488d 100644
--- a/RealJenkinsRuleInit/src/main/java/org/jvnet/hudson/test/RealJenkinsRuleInit.java
+++ b/src/main/java/org/jvnet/hudson/test/RealJenkinsRuleInit.java
@@ -27,8 +27,19 @@
import hudson.Plugin;
import java.net.URL;
import java.net.URLClassLoader;
+
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+
import jenkins.model.Jenkins;
+/**
+ * Plugin for use internally only by {@link RealJenkinsRule}, do not use this from plugin test code!
+ *
+ * NOTE: this and only this class is added into a dynamically generated plugin, see {@link PluginUtils#createRealJenkinsRulePlugin(java.io.File, String)}.
+ * In order for this to occur correctly there need to be no inner classes or other code dependencies here (except what can be loaded by reflection).
+ */
+@Restricted(NoExternalUse.class)
public class RealJenkinsRuleInit extends Plugin {
@SuppressWarnings("deprecation") // @Initializer just gets run too late, even with before = InitMilestone.PLUGINS_PREPARED
@@ -36,7 +47,7 @@ public RealJenkinsRuleInit() {}
@Override
public void start() throws Exception {
- new URLClassLoader(new URL[] {new URL(System.getProperty("RealJenkinsRule.location"))}, ClassLoader.getSystemClassLoader().getParent()).
+ new URLClassLoader("RealJenkinsRule",new URL[] {new URL(System.getProperty("RealJenkinsRule.location"))}, ClassLoader.getSystemClassLoader().getParent()).
loadClass("org.jvnet.hudson.test.RealJenkinsRule$Init2").
getMethod("run", Object.class).
invoke(null, Jenkins.get());
diff --git a/src/main/resources/org/jvnet/hudson/test/RealJenkinsRuleInit.jpi b/src/main/resources/org/jvnet/hudson/test/RealJenkinsRuleInit.jpi
deleted file mode 100644
index 7b8a1e626..000000000
Binary files a/src/main/resources/org/jvnet/hudson/test/RealJenkinsRuleInit.jpi and /dev/null differ
diff --git a/src/test/java/org/jvnet/hudson/test/PluginUtilsTest.java b/src/test/java/org/jvnet/hudson/test/PluginUtilsTest.java
new file mode 100644
index 000000000..eb909e570
--- /dev/null
+++ b/src/test/java/org/jvnet/hudson/test/PluginUtilsTest.java
@@ -0,0 +1,26 @@
+package org.jvnet.hudson.test;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.jar.JarFile;
+import java.util.jar.JarInputStream;
+
+import org.hamcrest.Matchers;
+import org.hamcrest.io.FileMatchers;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import static org.hamcrest.Matchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+class PluginUtilsTest {
+
+ @Test
+ void createRealJenkinsRulePlugin(@TempDir File tmpDir) throws IOException {
+ File plugin = PluginUtils.createRealJenkinsRulePlugin(tmpDir, "3.6666");
+ assertThat(plugin, FileMatchers.anExistingFile());
+ try (JarFile hpi = new JarFile(plugin)) {
+ assertThat(hpi.getManifest().getMainAttributes(), hasEntry(hasToString("Jenkins-Version"), is("3.6666")));
+ }
+ }
+}
diff --git a/src/test/java/org/jvnet/hudson/test/RealJenkinsRuleTest.java b/src/test/java/org/jvnet/hudson/test/RealJenkinsRuleTest.java
index 657264da9..d4ee20649 100644
--- a/src/test/java/org/jvnet/hudson/test/RealJenkinsRuleTest.java
+++ b/src/test/java/org/jvnet/hudson/test/RealJenkinsRuleTest.java
@@ -27,6 +27,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
@@ -43,6 +44,7 @@
import hudson.Functions;
import hudson.Launcher;
import hudson.Main;
+import hudson.PluginWrapper;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.FreeStyleProject;
@@ -58,6 +60,7 @@
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
+import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import javax.servlet.Filter;
@@ -356,4 +359,16 @@ private static void hangs(JenkinsRule r) throws Throwable {
Thread.sleep(Long.MAX_VALUE);
}
+ @Test
+ public void noDetachedPlugins() throws Throwable {
+ // we should be the only plugin in Jenkins.
+ rr.then(RealJenkinsRuleTest::_noDetachedPlugins);
+ }
+
+ private static void _noDetachedPlugins(JenkinsRule r) throws Throwable {
+ // only RealJenkinsRuleInit should be present
+ List plugins = r.jenkins.getPluginManager().getPlugins();
+ assertThat(plugins, hasSize(1));
+ assertThat(plugins.get(0).getShortName(), is("RealJenkinsRuleInit"));
+ }
}