Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce new hook when jetty 12 hook doesn't trigger #746

Merged
merged 9 commits into from
Jan 24, 2025
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package org.jenkins.tools.test.hook;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.util.VersionNumber;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.stream.Collectors;
import org.jenkins.tools.test.exception.PluginCompatibilityTesterException;
import org.jenkins.tools.test.exception.PomExecutionException;
import org.jenkins.tools.test.maven.ExpressionEvaluator;
import org.jenkins.tools.test.maven.ExternalMavenRunner;
import org.jenkins.tools.test.model.hook.BeforeExecutionContext;
import org.jenkins.tools.test.model.hook.PluginCompatTesterHookBeforeExecution;
import org.kohsuke.MetaInfServices;

@MetaInfServices(PluginCompatTesterHookBeforeExecution.class)
public class JenkinsTestHarnessHook extends PluginCompatTesterHookBeforeExecution {
public static final String VERSION_WITH_WEB_FRAGMENTS = "2386.v82359624ea_05";
public static final String VERSION_BACKPORT_2244 = "2244.2247.ve6b_a_8191b_95f";
public static final String VERSION_BACKPORT_2270 = "2270.2272.vd890c8c611b_3";
public static final List<String> VALID_VERSIONS =
List.of(VERSION_BACKPORT_2244, VERSION_BACKPORT_2270, VERSION_WITH_WEB_FRAGMENTS);
private static final String PROPERTY_NAME = "jenkins-test-harness.version";

private static boolean usesWebFragment(@NonNull BeforeExecutionContext context) {
// We only want this hook to be enabled if the Jetty 12 hook is not enabled at the same time
var war = context.getConfig().getWar();
try (var jarFile = new JarFile(war)) {
var jenkinsCoreEntry = getJenkinsCoreEntry(jarFile);
if (jenkinsCoreEntry == null) {

Check warning on line 36 in src/main/java/org/jenkins/tools/test/hook/JenkinsTestHarnessHook.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 36 is only partially covered, one branch is missing
throw new IllegalArgumentException("Failed to find jenkins-core jar in " + war);

Check warning on line 37 in src/main/java/org/jenkins/tools/test/hook/JenkinsTestHarnessHook.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 37 is not covered by tests
}
try (var is = jarFile.getInputStream(jenkinsCoreEntry);
var bis = new BufferedInputStream(is);
var jis = new JarInputStream(bis)) {
return hasWebFragment(jis);
}
} catch (IOException e) {
throw new UncheckedIOException("Failed to determine whether " + war + " uses web fragments", e);

Check warning on line 45 in src/main/java/org/jenkins/tools/test/hook/JenkinsTestHarnessHook.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 44-45 are not covered by tests
}
}

@NonNull
private static VersionNumber getPropertyAsVersion(
@NonNull BeforeExecutionContext context, @NonNull String propertyName) throws PomExecutionException {
var expressionEvaluator = new ExpressionEvaluator(
context.getCloneDirectory(),
context.getPlugin().getModule(),
new ExternalMavenRunner(context.getConfig()));
return new VersionNumber(expressionEvaluator.evaluateString(propertyName));
}

private static boolean hasWebFragment(JarInputStream jis) throws IOException {
for (var entry = jis.getNextEntry(); entry != null; entry = jis.getNextEntry()) {
if ("META-INF/web-fragment.xml".equals(entry.getName())) {

Check warning on line 61 in src/main/java/org/jenkins/tools/test/hook/JenkinsTestHarnessHook.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 61 is only partially covered, one branch is missing
return true;

Check warning on line 62 in src/main/java/org/jenkins/tools/test/hook/JenkinsTestHarnessHook.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 62 is not covered by tests
}
}
return false;
}

private static JarEntry getJenkinsCoreEntry(JarFile jarFile) {
for (var entries = jarFile.entries(); entries.hasMoreElements(); ) {

Check warning on line 69 in src/main/java/org/jenkins/tools/test/hook/JenkinsTestHarnessHook.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 69 is only partially covered, one branch is missing
var entry = entries.nextElement();
if (entry.getName().startsWith("WEB-INF/lib/jenkins-core-")) {
return entry;
}
}
return null;

Check warning on line 75 in src/main/java/org/jenkins/tools/test/hook/JenkinsTestHarnessHook.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 75 is not covered by tests
}

private static VersionNumber getWinstoneVersion(File war) {
try (var jarFile = new JarFile(war)) {
var zipEntry = jarFile.getEntry("executable/winstone.jar");
if (zipEntry == null) {

Check warning on line 81 in src/main/java/org/jenkins/tools/test/hook/JenkinsTestHarnessHook.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 81 is only partially covered, one branch is missing
throw new IllegalArgumentException("Failed to find winstone.jar in " + war);

Check warning on line 82 in src/main/java/org/jenkins/tools/test/hook/JenkinsTestHarnessHook.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 82 is not covered by tests
}
try (var is = jarFile.getInputStream(zipEntry);
var bis = new BufferedInputStream(is);
var jis = new JarInputStream(bis)) {
var manifest = jis.getManifest();
if (manifest == null) {

Check warning on line 88 in src/main/java/org/jenkins/tools/test/hook/JenkinsTestHarnessHook.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 88 is only partially covered, one branch is missing
throw new IllegalArgumentException("Failed to read manifest in " + war);

Check warning on line 89 in src/main/java/org/jenkins/tools/test/hook/JenkinsTestHarnessHook.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 89 is not covered by tests
}
var version = manifest.getMainAttributes().getValue("Implementation-Version");
if (version == null) {

Check warning on line 92 in src/main/java/org/jenkins/tools/test/hook/JenkinsTestHarnessHook.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 92 is only partially covered, one branch is missing
throw new IllegalArgumentException("Failed to read Winstone version from manifest in " + war);

Check warning on line 93 in src/main/java/org/jenkins/tools/test/hook/JenkinsTestHarnessHook.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 93 is not covered by tests
}
return new VersionNumber(version);
}
} catch (IOException e) {
throw new UncheckedIOException("Failed to read Winstone version in " + war, e);

Check warning on line 98 in src/main/java/org/jenkins/tools/test/hook/JenkinsTestHarnessHook.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 97-98 are not covered by tests
}
}

/**
* Determines the version of Jenkins Test Harness to use depending on the original version.
*/
@NonNull
static VersionNumber determineNextVersion(@NonNull VersionNumber version) {
var first = true;
VersionNumber older = null;
for (var validVersion : VALID_VERSIONS.stream().map(VersionNumber::new).collect(Collectors.toList())) {
if (first) {
if (validVersion.isNewerThan(version)) {
return validVersion;
}
first = false;
}
if (validVersion.isOlderThan(version)) {
older = validVersion;
} else {
if (older != null) {

Check warning on line 119 in src/main/java/org/jenkins/tools/test/hook/JenkinsTestHarnessHook.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 119 is only partially covered, one branch is missing
return validVersion;
}
}
}
// Keep the version as is. If that happens, it means check() method returned true by mistake.
return version;
}

@Override
public boolean check(@NonNull BeforeExecutionContext context) {
var winstoneVersion = getWinstoneVersion(context.getConfig().getWar());
if (winstoneVersion.getDigitAt(0) < 7) {

Check warning on line 131 in src/main/java/org/jenkins/tools/test/hook/JenkinsTestHarnessHook.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 131 is only partially covered, one branch is missing
// Don't upgrade anything if winstone version is too old.
return false;

Check warning on line 133 in src/main/java/org/jenkins/tools/test/hook/JenkinsTestHarnessHook.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 133 is not covered by tests
}
try {
var existingVersion = getPropertyAsVersion(context, PROPERTY_NAME);
// If core uses web fragments, we need a version of jth with web fragments support
if (!usesWebFragment(context)) {

Check warning on line 138 in src/main/java/org/jenkins/tools/test/hook/JenkinsTestHarnessHook.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 138 is only partially covered, one branch is missing
return existingVersion.isOlderThan(new VersionNumber(VERSION_WITH_WEB_FRAGMENTS));
} else {
return VALID_VERSIONS.stream().map(VersionNumber::new).anyMatch(existingVersion::equals)
|| existingVersion.isOlderThan(new VersionNumber(VERSION_WITH_WEB_FRAGMENTS));
}
} catch (PomExecutionException e) {
return false;

Check warning on line 145 in src/main/java/org/jenkins/tools/test/hook/JenkinsTestHarnessHook.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 141-145 are not covered by tests
}
}

@Override
public void action(@NonNull BeforeExecutionContext context) throws PluginCompatibilityTesterException {
var version = getPropertyAsVersion(context, PROPERTY_NAME);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't forget to call super.action() and/or super.check() if needed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed.

context.getArgs().add(String.format("-D%s=%s", PROPERTY_NAME, determineNextVersion(version)));
/*
* The version of JUnit 5 used at runtime must match the version of JUnit 5 used to compile the tests, but the
* inclusion of a newer test harness might cause the HPI plugin to try to use a newer version of JUnit 5 at
* runtime to satisfy upper bounds checks, so exclude JUnit 5 from upper bounds analysis.
*/
context.getUpperBoundsExcludes().add("org.junit.jupiter:junit-jupiter-api");
}
}
77 changes: 0 additions & 77 deletions src/main/java/org/jenkins/tools/test/hook/Jetty12Hook.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,16 @@ public abstract class PropertyVersionHook extends PluginCompatTesterHookBeforeEx

@Override
public boolean check(@NonNull BeforeExecutionContext context) {
return check(context, getProperty(), getMinimumVersion());
}

static boolean check(BeforeExecutionContext context, String property, String minimumVersion) {
MavenRunner runner = new ExternalMavenRunner(context.getConfig());
ExpressionEvaluator expressionEvaluator = new ExpressionEvaluator(
context.getCloneDirectory(), context.getPlugin().getModule(), runner);
try {
String version = expressionEvaluator.evaluateString(getProperty());
return new VersionNumber(version).isOlderThan(new VersionNumber(getMinimumVersion()));
String version = expressionEvaluator.evaluateString(property);
return new VersionNumber(version).isOlderThan(new VersionNumber(minimumVersion));
} catch (PomExecutionException e) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.jenkins.tools.test.hook;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasToString;

import hudson.util.VersionNumber;
import org.junit.jupiter.api.Test;

class JenkinsTestHarnessHookTest {
@Test
void nextVersion() {
assertThat(
JenkinsTestHarnessHook.determineNextVersion(new VersionNumber("2243")),
hasToString(JenkinsTestHarnessHook.VERSION_BACKPORT_2244));
assertThat(
JenkinsTestHarnessHook.determineNextVersion(new VersionNumber("2244")),
hasToString(JenkinsTestHarnessHook.VERSION_BACKPORT_2244));
assertThat(
JenkinsTestHarnessHook.determineNextVersion(new VersionNumber("2269")),
hasToString(JenkinsTestHarnessHook.VERSION_BACKPORT_2270));
assertThat(
JenkinsTestHarnessHook.determineNextVersion(new VersionNumber("2270")),
hasToString(JenkinsTestHarnessHook.VERSION_BACKPORT_2270));
assertThat(
JenkinsTestHarnessHook.determineNextVersion(new VersionNumber("2271")),
hasToString(JenkinsTestHarnessHook.VERSION_WITH_WEB_FRAGMENTS));
assertThat(JenkinsTestHarnessHook.determineNextVersion(new VersionNumber("2387")), hasToString("2387"));
}
}