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-48466] Provide JUnit 5 support for JenkinsRule #438

Merged
merged 7 commits into from
Jul 18, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ THE SOFTWARE.
<artifactId>annotation-indexer</artifactId>
<version>1.16</version>
</dependency>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.8.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.kohsuke</groupId>
<artifactId>access-modifier-annotation</artifactId>
Expand Down Expand Up @@ -163,6 +170,20 @@ THE SOFTWARE.
<artifactId>hamcrest-library</artifactId>
<version>${hamcrest.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jvnet.hudson</groupId>
<artifactId>embedded-rhino-debugger</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package org.jvnet.hudson.test.junit.jupiter;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.extension.ExtendWith;

/**
* JUnit 5 meta annotation providing {@link org.jvnet.hudson.test.JenkinsRule JenkinsRule} integration.
*
* <p>Test methods using the rule extension need to accept it by {@link org.jvnet.hudson.test.JenkinsRule JenkinsRule} parameter;
* each test case gets a new rule object.
basil marked this conversation as resolved.
Show resolved Hide resolved
*
* <p>Annotating a <em>class</em> provides access for all of its tests.
* Unrelated test cases can omit the parameter.
*
* <blockquote>
*
* <pre>
* &#64;EnableJenkins
* class ExampleJUnit5Test {
*
* &#64;Test
* public void example(JenkinsRule r) {
* // use 'r' ...
* }
*
* &#64;Test
* public void exampleNotUsingRule() {
* // ...
* }
* }
* </pre>
*
* </blockquote>
*
* <p>Annotating a <i>method</i> limits access to the method.
*
* <blockquote>
*
* <pre>
* class ExampleJUnit5Test {
*
* &#64;EnableJenkins
* &#64;Test
* public void example(JenkinsRule r) {
* // use 'r' ...
* }
* }
* </pre>
*
* </blockquote>
*
* @see JenkinsExtension
* @see org.junit.jupiter.api.extension.ExtendWith
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ExtendWith(JenkinsExtension.class)
public @interface EnableJenkins {}
basil marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.jvnet.hudson.test.junit.jupiter;

import edu.umd.cs.findbugs.annotations.NonNull;
import java.lang.reflect.Method;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.runner.Description;
import org.jvnet.hudson.test.JenkinsRecipe;
import org.jvnet.hudson.test.JenkinsRule;

/**
* Provides JUnit 5 compatibility for {@link JenkinsRule}.
*/
class JUnit5JenkinsRule extends JenkinsRule {
private final ParameterContext context;

JUnit5JenkinsRule(@NonNull ParameterContext context, @NonNull ExtensionContext extensionContext) {
this.context = context;
this.testDescription = Description.createTestDescription(
extensionContext.getTestClass().map(Class::getName).orElse(null),
extensionContext.getTestMethod().map(Method::getName).orElse(null));
}

@Override
public void recipe() throws Exception {
JenkinsRecipe jenkinsRecipe = context.findAnnotation(JenkinsRecipe.class).orElse(null);
if (jenkinsRecipe == null) {
return;
}
@SuppressWarnings("unchecked")
final JenkinsRecipe.Runner<JenkinsRecipe> runner =
(JenkinsRecipe.Runner<JenkinsRecipe>) jenkinsRecipe.value().getDeclaredConstructor().newInstance();
recipes.add(runner);
tearDowns.add(() -> runner.tearDown(this, jenkinsRecipe));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.jvnet.hudson.test.junit.jupiter;

import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.jvnet.hudson.test.JenkinsRule;

/**
* JUnit 5 extension providing {@link JenkinsRule} integration.
*
* @see EnableJenkins
*/
class JenkinsExtension implements ParameterResolver, AfterEachCallback {

private static final String KEY = "jenkins-instance";
private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create(JenkinsExtension.class);

@Override
public void afterEach(ExtensionContext context) throws Exception {
final JenkinsRule rule = context.getStore(NAMESPACE).remove(KEY, JenkinsRule.class);
if (rule == null) {
return;
}
rule.after();
}

@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return parameterContext.getParameter().getType().equals(JenkinsRule.class);
}

@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
final JenkinsRule rule =
extensionContext
.getStore(NAMESPACE)
.getOrComputeIfAbsent(
KEY,
key -> new JUnit5JenkinsRule(parameterContext, extensionContext),
JenkinsRule.class);

try {
rule.before();
return rule;
} catch (Throwable t) {
throw new ParameterResolutionException(t.getMessage(), t);
}
}
}
2 changes: 2 additions & 0 deletions src/spotbugs/excludesFilter.xml
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@
<Class name="org.jvnet.hudson.test.JenkinsRecipe$Runner"/>
<Class name="org.jvnet.hudson.test.JenkinsSessionRule$CustomJenkinsRule"/>
<Class name="org.jvnet.hudson.test.junit.GroupedTest"/>
<Class name="org.jvnet.hudson.test.junit.jupiter.JenkinsExtension"/>
<Class name="org.jvnet.hudson.test.junit.jupiter.JUnit5JenkinsRule"/>
<Class name="org.jvnet.hudson.test.LenientRunnable"/>
<Class name="org.jvnet.hudson.test.MemoryAssert"/>
<Class name="org.jvnet.hudson.test.NoListenerConfiguration"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.jvnet.hudson.test.junit.jupiter;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.hamcrest.collection.IsEmptyCollection.empty;

import java.io.IOException;
import org.junit.jupiter.api.Test;
import org.jvnet.hudson.test.JenkinsRule;

@EnableJenkins
class JenkinsRuleResolverTest {

@Test
void jenkinsRuleIsAccessible(JenkinsRule rule) throws IOException {
assertThat(rule.jenkins.getJobNames(), empty());
rule.createFreeStyleProject("job-0");
assertThat(rule.jenkins.getJobNames(), hasSize(1));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.jvnet.hudson.test.junit.jupiter;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.hamcrest.collection.IsEmptyCollection.empty;

import java.io.IOException;
import org.junit.jupiter.api.Test;
import org.jvnet.hudson.test.JenkinsRule;

class JenkinsRuleResolverWithMethodScopeTest {

@EnableJenkins
@Test
void jenkinsRuleIsAccessible(JenkinsRule rule) throws IOException {
assertThat(rule.jenkins.getJobNames(), empty());
rule.createFreeStyleProject("job-0");
assertThat(rule.jenkins.getJobNames(), hasSize(1));
}
}