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

[JENKINS-45047] Support for plugin-to-plugin integration tests #66

Closed
wants to merge 9 commits into from
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ target/
.classpath
.settings
.project
nbactions.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
invoker.goals=test
51 changes: 51 additions & 0 deletions src/it/override-test-dependencies-smokes/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>2.30</version>
<relativePath/>
</parent>
<groupId>org.jenkins-ci.tools.hpi.its</groupId>
<artifactId>override-test-dependencies-smokes</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>hpi</packaging>
<properties>
<jenkins.version>1.642.3</jenkins.version>
<hpi-plugin.version>@project.version@</hpi-plugin.version>
<!-- TODO cannot include `invoker.goals=-DoverrideVersions=…,… test` in invoker.properties since then that is misinterpreted as multiple arguments for invoker.goals -->
<overrideVersions>org.jenkins-ci.plugins.workflow:workflow-api:2.17,org.jenkins-ci.plugins.workflow:workflow-cps:2.32</overrideVersions>
<!-- TODO <useUpperBounds>true</useUpperBounds> -->
<test>SampleTest</test>
<maven.test.redirectTestOutputToFile>false</maven.test.redirectTestOutputToFile>
<surefire.rerunFailingTestsCount>0</surefire.rerunFailingTestsCount>
</properties>
<repositories>
<repository>
<id>repo.jenkins-ci.org</id>
<url>https://repo.jenkins-ci.org/public/</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>repo.jenkins-ci.org</id>
<url>https://repo.jenkins-ci.org/public/</url>
</pluginRepository>
</pluginRepositories>
<dependencies>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-step-api</artifactId>
<version>2.10</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-cps</artifactId>
<version>2.30</version>
<scope>test</scope>
</dependency>
<!-- TODO also need to test a dependency with <classifier>tests</classifier>, maybe set via common property -->
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?jelly escape-by-default='true'?>
<div/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package test;

import com.google.common.collect.ImmutableMap;
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.Map;
import java.util.jar.Manifest;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.jvnet.hudson.test.JenkinsRule;

public class SampleTest {

@Rule
public JenkinsRule r = new JenkinsRule();

@Test
public void smokes() throws Exception {
Map<String, String> expectedVersions = ImmutableMap.of("workflow-cps", "2.32", "workflow-api", "2.17");
Enumeration<URL> manifests = SampleTest.class.getClassLoader().getResources("META-INF/MANIFEST.MF");
while (manifests.hasMoreElements()) {
URL url = manifests.nextElement();
try (InputStream is = url.openStream()) {
Manifest mf = new Manifest(is);
String pluginName = mf.getMainAttributes().getValue("Short-Name");
String expectedVersion = expectedVersions.get(pluginName);
if (expectedVersion != null) {
assertEquals("wrong version for " + pluginName + " as classpath entry", expectedVersion, mf.getMainAttributes().getValue("Plugin-Version"));
}
}
}
for (Map.Entry<String, String> entry : expectedVersions.entrySet()) {
assertEquals("wrong version for " + entry.getKey() + " as plugin", entry.getValue(), r.jenkins.pluginManager.getPlugin(entry.getKey()).getVersion());
}
}

}
3 changes: 3 additions & 0 deletions src/it/override-test-dependencies-smokes/verify.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
def log = new File(basedir, 'build.log').text
// TODO add anything needed, or delete this file
true
102 changes: 97 additions & 5 deletions src/main/java/org/jenkinsci/maven/plugins/hpi/TestDependencyMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,65 @@

import org.apache.commons.io.FileUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;

import java.io.*;
import java.util.Set;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.artifact.DefaultArtifact;
import org.apache.maven.artifact.resolver.AbstractArtifactResolutionException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.plugins.annotations.Parameter;

/**
* Places test-dependency plugins into somewhere the test harness can pick up.
*
* <p>
* See {@code TestPluginManager.loadBundledPlugins()} where the test harness uses it.
*
* @author Kohsuke Kawaguchi
* <p>Additionally, it may adjust the classpath for {@code surefire:test} to run tests
* against different versions of various dependencies than what was configured in the POM.
*/
@Mojo(name="resolve-test-dependencies", requiresDependencyResolution = ResolutionScope.TEST)
public class TestDependencyMojo extends AbstractHpiMojo {

/**
* List of dependency version overrides in the form {@code groupId:artifactId:version} to apply during testing.
* Must correspond to dependencies already present in the project model.
*/
@Parameter(property="overrideVersions")
private List<String> overrideVersions;

/**
* Whether to update all transitive dependencies to the upper bounds.
* Effectively causes same behavior as the {@code requireUpperBoundDeps} Enforcer rule would,
* if the specified dependencies were to be written to the POM.
* Intended for use in conjunction with {@link #overrideVersions}.
*/
@Parameter(property="useUpperBounds")
private boolean useUpperBounds;

@Override
public void execute() throws MojoExecutionException, MojoFailureException {
Map<String, String> overrides = new HashMap<>(); // groupId:artifactId → version
if (overrideVersions != null) {
for (String override : overrideVersions) {
Matcher m = Pattern.compile("([^:]+:[^:]+):([^:]+)").matcher(override);
if (!m.matches()) {
throw new MojoExecutionException("illegal override: " + override);
}
overrides.put(m.group(1), m.group(2));
}
}
File testDir = new File(project.getBuild().getTestOutputDirectory(),"test-dependencies");
testDir.mkdirs();

Expand All @@ -41,13 +79,67 @@ public void execute() throws MojoExecutionException, MojoFailureException {

getLog().debug("Copying " + artifactId + " as a test dependency");
File dst = new File(testDir, artifactId + ".hpi");
FileUtils.copyFile(a.getHpi().getFile(),dst);
File src;
String version = overrides.get(a.getGroupId() + ":" + artifactId);
if (version != null) {
src = replace(a.getHpi().artifact, version).getFile();
} else {
src = a.getHpi().getFile();
}
FileUtils.copyFile(src, dst);
w.write(artifactId + "\n");
}

w.close();
} catch (IOException e) {
throw new MojoExecutionException("Failed to copy dependency plugins",e);
}

if (overrideVersions != null) {
if (useUpperBounds) {
throw new MojoExecutionException("TODO useUpperBounds not yet supported");
}
List<String> additionalClasspathElements = new ArrayList<>();
List<String> classpathDependencyExcludes = new ArrayList<>();
for (Map.Entry<String, String> entry : overrides.entrySet()) {
String key = entry.getKey();
classpathDependencyExcludes.add(key);
String[] groupArt = key.split(":");
String groupId = groupArt[0];
String artifactId = groupArt[1];
String version = entry.getValue();
// Cannot use MavenProject.getArtifactMap since we may have multiple dependencies of different classifiers.
boolean found = false;
for (Object _a : project.getArtifacts()) {
Artifact a = (Artifact) _a;
if (!a.getGroupId().equals(groupId) || !a.getArtifactId().equals(artifactId)) {
continue;
}
found = true;
if (a.getArtifactHandler().isAddedToClasspath()) { // everything is added to test CP, so no need to check scope
additionalClasspathElements.add(replace(a, version).getFile().getAbsolutePath());
}
}
if (!found) {
throw new MojoExecutionException("could not find dependency " + key);
}
}
Properties properties = project.getProperties();
getLog().info("Replacing POM-defined classpath elements " + classpathDependencyExcludes + " with " + additionalClasspathElements);
// cf. http://maven.apache.org/surefire/maven-surefire-plugin/test-mojo.html
properties.setProperty("maven.test.additionalClasspath", StringUtils.join(additionalClasspathElements, ','));
properties.setProperty("maven.test.dependency.excludes", StringUtils.join(classpathDependencyExcludes, ','));
}
}

private Artifact replace(Artifact a, String version) throws MojoExecutionException {
Artifact a2 = new DefaultArtifact(a.getGroupId(), a.getArtifactId(), VersionRange.createFromVersion(version), a.getScope(), a.getType(), a.getClassifier(), a.getArtifactHandler(), a.isOptional());
try {
artifactResolver.resolve(a2, remoteRepos, localRepository);
} catch (AbstractArtifactResolutionException x) {
throw new MojoExecutionException("could not find " + a + " in version " + version + ": " + x, x);
}
return a2;
}

}