diff --git a/RealJenkinsRuleInit/pom.xml b/RealJenkinsRuleInit/pom.xml deleted file mode 100644 index 2559202a3..000000000 --- a/RealJenkinsRuleInit/pom.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - 4.0.0 - - org.jenkins-ci.plugins - plugin - 4.83 - - - io.jenkins.plugins - RealJenkinsRuleInit - 0-SNAPSHOT - hpi - - 2.361 - true - true - - RealJenkinsRule initialization wrapper - - - - org.apache.maven.plugins - maven-dependency-plugin - 3.1.2 - - - copy-installed - install - - copy - - - - - ${project.groupId} - ${project.artifactId} - ${project.version} - ${project.packaging} - ${project.basedir}/../src/main/resources/org/jvnet/hudson/test - RealJenkinsRuleInit.jpi - - - - - - - - - - - repo.jenkins-ci.org - https://repo.jenkins-ci.org/public/ - - - - - repo.jenkins-ci.org - https://repo.jenkins-ci.org/public/ - - - diff --git a/pom.xml b/pom.xml index 09ef7f2f8..1842784cc 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,6 @@ THE SOFTWARE. 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")); + } }