Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@
import io.jenkins.lib.support_log_formatter.SupportLogFormatter;
import java.util.logging.LogRecord;

class DeltaSupportLogFormatter extends SupportLogFormatter {
public class DeltaSupportLogFormatter extends SupportLogFormatter {

static long start = System.currentTimeMillis();

static String elapsedTime() {
public static String elapsedTime() {
return String.format("%8.3f", (System.currentTimeMillis() - start) / 1_000.0);
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/jvnet/hudson/test/HudsonHomeLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public File allocate() throws Exception {
return target;
}

void copy(File target) throws Exception {
public void copy(File target) throws Exception {
URL res = findDataResource();
if (!res.getProtocol().equals("file")) {
throw new AssertionError("Test data is not available in the file system: " + res);
Expand Down
13 changes: 10 additions & 3 deletions src/main/java/org/jvnet/hudson/test/JenkinsRule.java
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ public static void _configureJenkinsForTest(Jenkins jenkins) throws Exception {
jenkins.getJDKs().add(new JDK("default", System.getProperty("java.home")));
}

static void dumpThreads() {
public static void dumpThreads() {
ThreadInfo[] threadInfos = Functions.getThreadInfos();
Functions.ThreadGroupMap m = Functions.sortThreadsAndGetGroupMap(threadInfos);
for (ThreadInfo ti : threadInfos) {
Expand Down Expand Up @@ -1172,6 +1172,13 @@ public void showAgentLogs(Slave s, LoggerRule loggerRule) throws Exception {
showAgentLogs(s, loggerRule.getRecordedLevels());
}

/**
* Same as {@link #showAgentLogs(Slave, Map)} but taking a preconfigured list of loggers as a convenience.
*/
public void showAgentLogs(Slave s, LogRecorder logRecorder) throws Exception {
showAgentLogs(s, logRecorder.getRecordedLevels());
}

/**
* Forward agent logs to standard error of the test process.
* Otherwise log messages would be sent only to {@link Computer#getLogText} etc.,
Expand All @@ -1183,7 +1190,7 @@ public void showAgentLogs(Slave s, Map<String, Level> loggers) throws Exception
s.getChannel().call(new RemoteLogDumper(s.getNodeName(), loggers, true));
}

static final class RemoteLogDumper extends MasterToSlaveCallable<Void, RuntimeException> {
public static final class RemoteLogDumper extends MasterToSlaveCallable<Void, RuntimeException> {
private final String name;
private final Map<String, Level> loggers;
private final TaskListener stderr;
Expand All @@ -1192,7 +1199,7 @@ static final class RemoteLogDumper extends MasterToSlaveCallable<Void, RuntimeEx
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
private static final List<Logger> loggerReferences = new LinkedList<>();

RemoteLogDumper(String name, Map<String, Level> loggers, boolean forward) {
public RemoteLogDumper(String name, Map<String, Level> loggers, boolean forward) {
this.name = name;
this.loggers = loggers;
stderr = forward ? StreamTaskListener.fromStderr() : null;
Expand Down
44 changes: 29 additions & 15 deletions src/main/java/org/jvnet/hudson/test/PluginUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,18 @@
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;

class PluginUtils {
public class PluginUtils {

/**
* Creates the plugin used by RealJenkinsExtension
* @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
*/
public static File createRealJenkinsExtensionPlugin(File destinationDirectory, String baseline) throws IOException {
return createRealJenkinsPlugin("RealJenkinsExtension", destinationDirectory, baseline);
}

/**
* Creates the plugin used by RealJenkinsRule
Expand All @@ -22,44 +33,47 @@ class PluginUtils {
* @throws IOException if something goes wrong whilst creating the plugin.
* @return File the plugin we just created
*/
static File createRealJenkinsRulePlugin(File destinationDirectory, String baseline) throws IOException {
return createRealJenkinsPlugin("RealJenkinsRule", destinationDirectory, baseline);
}

@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();
static File createRealJenkinsPlugin(String target, File destinationDirectory, String baseline) throws IOException {
Class<RealJenkinsRuleInit> pluginClass = RealJenkinsRuleInit.class;

// 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("Plugin-Class", pluginClass.getName());
mainAttributes.putValue("Extension-Name", pluginClass.getSimpleName());
mainAttributes.putValue("Short-Name", pluginClass.getSimpleName());
mainAttributes.putValue("Long-Name", target + " initialization wrapper");
mainAttributes.putValue("Plugin-Version", "0-SNAPSHOT (private rj)");
mainAttributes.putValue("Support-Dynamic-Loading", "true");
mainAttributes.putValue("Jenkins-Version", baseline);
mainAttributes.putValue("Init-Target", target);

// 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);
try (InputStream classIS = pluginClass.getResourceAsStream(pluginClass.getSimpleName() + ".class")) {
String path = pluginClass.getPackageName().replace('.', '/');
createJarEntry(classesJarOS, path + '/' + pluginClass.getSimpleName() + ".class", classIS);
}
}

// the actual JPI
File jpi = new File(destinationDirectory, pluginName + ".jpi");
File jpi = new File(destinationDirectory, pluginClass.getSimpleName() + ".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);
createJarEntry(jos, "WEB-INF/lib/" + pluginClass.getSimpleName() + ".jar", fis);
}
}
return jpi;
Expand Down
3 changes: 1 addition & 2 deletions src/main/java/org/jvnet/hudson/test/RealJenkinsRule.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.SocketException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
Expand Down Expand Up @@ -1251,7 +1250,7 @@ public void stopJenkins() throws Throwable {
decorateConnection(endpoint("exit").openConnection())
.getInputStream()
.close();
} catch (SocketException e) {
} catch (IOException e) {
System.err.println("Unable to connect to the Jenkins process to stop it: " + e);
}
} else {
Expand Down
38 changes: 28 additions & 10 deletions src/main/java/org/jvnet/hudson/test/RealJenkinsRuleInit.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,34 +24,52 @@

package org.jvnet.hudson.test;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Plugin;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.jar.Manifest;
import jenkins.model.Jenkins;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;

/**
* Plugin for use <b>internally only</b> by {@link RealJenkinsRule}, do not use this from plugin test code!
* <p>
* <strong>NOTE</strong>: this and only this class is added into a dynamically generated plugin, see {@link PluginUtils#createRealJenkinsRulePlugin(java.io.File, String)}.
* <strong>NOTE</strong>: this and only this class is added into a dynamically generated plugin, see {@link PluginUtils#createRealJenkinsPlugin(String, 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
@SuppressWarnings("deprecation")
// @Initializer just gets run too late, even with before = InitMilestone.PLUGINS_PREPARED
public RealJenkinsRuleInit() {}

@Override
@SuppressFBWarnings(value = "URLCONNECTION_SSRF_FD", justification = "jth is a test utility")
public void start() throws Exception {
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());
URL url = ((URLClassLoader) getClass().getClassLoader()).findResource("META-INF/MANIFEST.MF");
Manifest manifest = new Manifest(url.openStream());
String target = manifest.getMainAttributes().getValue("Init-Target");

if ("RealJenkinsRule".equals(target)) {
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());
} else if ("RealJenkinsExtension".equals(target)) {
new URLClassLoader(
"RealJenkinsExtension",
new URL[] {new URL(System.getProperty("RealJenkinsExtension.location"))},
ClassLoader.getSystemClassLoader().getParent())
.loadClass("org.jvnet.hudson.test.junit.jupiter.RealJenkinsExtension$Init2")
.getMethod("run", Object.class)
.invoke(null, Jenkins.get());
}
}
}
10 changes: 10 additions & 0 deletions src/main/java/org/jvnet/hudson/test/TailLog.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.input.Tailer;
import org.apache.commons.io.input.TailerListenerAdapter;
import org.jvnet.hudson.test.junit.jupiter.RealJenkinsExtension;
import org.jvnet.hudson.test.recipes.LocalData;

/**
Expand Down Expand Up @@ -81,6 +82,15 @@ public TailLog(RealJenkinsRule rjr, String job, int number) {
this(runRootDir(rjr.getHome(), job, number), job, number);
}

/**
* Watch a build expected to be loaded in a controller JVM.
* <em>Note</em>: this constructor will not work for a branch project (child of {@code MultiBranchProject}).
* @param job a {@link Job#getFullName}
*/
public TailLog(RealJenkinsExtension rje, String job, int number) {
this(runRootDir(rje.getHome(), job, number), job, number);
}

private static File runRootDir(File home, String job, int number) {
// For MultiBranchProject the last segment would be "branches" not "jobs":
return new File(home, "jobs/" + job.replace("/", "/jobs/") + "/builds/" + number);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public File allocate() throws IOException {
return allocate(withoutSpace ? "jkh" : "j h");
}

synchronized File allocate(String name) throws IOException {
public synchronized File allocate(String name) throws IOException {
try {
File f = Files.createTempDirectory(base.toPath(), name).toFile();
tmpDirectories.add(f);
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/jvnet/hudson/test/WarExploder.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public static synchronized File getExplodedDir() throws Exception {

private static File EXPLODE_DIR;

static File findJenkinsWar() throws Exception {
public static File findJenkinsWar() throws Exception {
File war;
if (JENKINS_WAR_PATH != null) {
war = new File(JENKINS_WAR_PATH).getAbsoluteFile();
Expand Down
Loading
Loading