Uploaded image for project: 'Jenkins'
  1. Jenkins
  2. JENKINS-48466

Provide JUnit 5 support for JenkinsRule

    XMLWordPrintable

    Details

    • Similar Issues:

      Description

      JUnit 4 rules are not supported by JUnit 5. In order to use JenkinsRule in a test one cannot switch to JUnit 5 for these tests. You still need to have a dependency to JUnit 4.

        Attachments

          Issue Links

            Activity

            drulli Ulli Hafner created issue -
            Hide
            mkobit Mike Kobit added a comment -

            One way to at least make it simpler for plugins to start using JUnit 5 would be to separate some of the specific start/stop details from the apply methods (like in

            https://github.com/jenkinsci/jenkins-test-harness/blob/d7acb8e67e3da73297b6ca1d3c6987d5de0f8e6a/src/main/java/org/jvnet/hudson/test/JenkinsRule.java#L531-L588 ) so that a JUnit 5 extension can easily wrap those calls to the existing test bootstrap logic.

            I don't think it is a huge problem for JUnit 4 to still be present but untangling some of the test bootstrapping code from the JUnit 4 specific rules will enable the use of JUnit 5 (or any other testing framework) from a plugins standpoint.

            Show
            mkobit Mike Kobit added a comment - One way to at least make it simpler for plugins to start using JUnit 5 would be to separate some of the specific start/stop details from the apply methods (like in https://github.com/jenkinsci/jenkins-test-harness/blob/d7acb8e67e3da73297b6ca1d3c6987d5de0f8e6a/src/main/java/org/jvnet/hudson/test/JenkinsRule.java#L531-L588   )  so that a JUnit 5 extension can easily wrap those calls to the existing test bootstrap logic. I don't think it is a huge problem for JUnit 4 to still be present but untangling some of the test bootstrapping code from the JUnit 4 specific rules will enable the use of JUnit 5 (or any other testing framework) from a plugins standpoint.
            Hide
            mkobit Mike Kobit added a comment -

            Another possibility would be to implement ExternalResource to enable usage of https://junit.org/junit5/docs/current/user-guide/#migrating-from-junit4-rule-support 

            Show
            mkobit Mike Kobit added a comment - Another possibility would be to implement ExternalResource to enable usage of https://junit.org/junit5/docs/current/user-guide/#migrating-from-junit4-rule-support  
            Hide
            chrahunt Chris Hunt added a comment -

            I was able to get something started using a ParameterResolver, like so:

            package io.jenkins.plugins.websub;
            
            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.JenkinsRecipe;
            
            import java.util.Optional;
            
            public class TestUtils {
                public static class JenkinsRule extends org.jvnet.hudson.test.JenkinsRule {
                    private final ParameterContext context;
            
                    JenkinsRule(ParameterContext context) {
                        this.context = context;
                    }
            
                    @Override
                    public void recipe() throws Exception {
                        Optional<JenkinsRecipe> a = context.findAnnotation(JenkinsRecipe.class);
                        if (!a.isPresent()) return;
                        final JenkinsRecipe.Runner runner = a.get().value().newInstance();
                        recipes.add(runner);
                        tearDowns.add(() -> runner.tearDown(this, a.get()));
                    }
                }
            
                public static class JenkinsParameterResolver implements ParameterResolver, AfterEachCallback {
                    private static final String key = "jenkins-instance";
                    private static final ExtensionContext.Namespace ns =
                            ExtensionContext.Namespace.create(JenkinsParameterResolver.class);
            
                    @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 {
                        JenkinsRule instance = extensionContext.getStore(ns).getOrComputeIfAbsent(
                                key, key -> new JenkinsRule(parameterContext), JenkinsRule.class);
                        try {
                            instance.before();
                            return instance;
                        } catch (Throwable t) {
                            throw new ParameterResolutionException(t.toString());
                        }
                    }
            
                    @Override
                    public void afterEach(ExtensionContext context) throws Exception {
                        JenkinsRule rule = context.getStore(ns).remove(key, JenkinsRule.class);
                        if (rule != null)
                            rule.after();
                    }
                }
            }
            

            Used like

            package io.jenkins.plugins.websub;
            
            import hudson.model.FreeStyleBuild;
            import hudson.model.FreeStyleProject;
            import hudson.tasks.Shell;
            import org.apache.commons.io.FileUtils;
            import org.junit.jupiter.api.Test;
            import org.junit.jupiter.api.extension.ExtendWith;
            
            @ExtendWith(TestUtils.JenkinsParameterResolver.class)
            public class TestWebSubTrigger {
                @Test
                void testJenkinsParameterResolver(TestUtils.JenkinsRule j) throws Exception {
                    FreeStyleProject project = j.createFreeStyleProject();
                    project.getBuildersList().add(new Shell("echo hello"));
                    FreeStyleBuild build = project.scheduleBuild2(0).get();
                    System.out.println(build.getDisplayName() + " completed");
                    String s = FileUtils.readFileToString(build.getLogFile());
                }
            }
            

            I'm not sure if/where this kind of approach would fit in jenkins-test-harness. Oleg Nenashev, what do you think?

            Show
            chrahunt Chris Hunt added a comment - I was able to get something started using a ParameterResolver , like so: package io.jenkins.plugins.websub; 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.JenkinsRecipe; import java.util.Optional; public class TestUtils { public static class JenkinsRule extends org.jvnet.hudson.test.JenkinsRule { private final ParameterContext context; JenkinsRule(ParameterContext context) { this .context = context; } @Override public void recipe() throws Exception { Optional<JenkinsRecipe> a = context.findAnnotation(JenkinsRecipe.class); if (!a.isPresent()) return ; final JenkinsRecipe.Runner runner = a.get().value().newInstance(); recipes.add(runner); tearDowns.add(() -> runner.tearDown( this , a.get())); } } public static class JenkinsParameterResolver implements ParameterResolver, AfterEachCallback { private static final String key = "jenkins-instance" ; private static final ExtensionContext.Namespace ns = ExtensionContext.Namespace.create(JenkinsParameterResolver.class); @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 { JenkinsRule instance = extensionContext.getStore(ns).getOrComputeIfAbsent( key, key -> new JenkinsRule(parameterContext), JenkinsRule.class); try { instance.before(); return instance; } catch (Throwable t) { throw new ParameterResolutionException(t.toString()); } } @Override public void afterEach(ExtensionContext context) throws Exception { JenkinsRule rule = context.getStore(ns).remove(key, JenkinsRule.class); if (rule != null ) rule.after(); } } } Used like package io.jenkins.plugins.websub; import hudson.model.FreeStyleBuild; import hudson.model.FreeStyleProject; import hudson.tasks.Shell; import org.apache.commons.io.FileUtils; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(TestUtils.JenkinsParameterResolver.class) public class TestWebSubTrigger { @Test void testJenkinsParameterResolver(TestUtils.JenkinsRule j) throws Exception { FreeStyleProject project = j.createFreeStyleProject(); project.getBuildersList().add( new Shell( "echo hello" )); FreeStyleBuild build = project.scheduleBuild2(0).get(); System .out.println(build.getDisplayName() + " completed" ); String s = FileUtils.readFileToString(build.getLogFile()); } } I'm not sure if/where this kind of approach would fit in jenkins-test-harness. Oleg Nenashev , what do you think?
            Hide
            t8ch Thomas Weißschuh added a comment -

            To get it to work with TestExtension I had to modify the JenkinsRule from Chris Hunt:

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

            To make it work with @RegisterExtension (to interact with the rule from @BeforeEach methods) the following works:

            public static class JenkinsExtension extends org.jvnet.hudson.test.JenkinsRule implements BeforeEachCallback, AfterEachCallback {
            
                @Override
                public void beforeEach(ExtensionContext context) throws Exception {
                  this.testDescription = Description.createTestDescription(
                    context.getTestClass().map(Class::getName).orElse(null),
                    context.getTestMethod().map(Method::getName).orElse(null)
                  );
                  try {
                    before();
                  } catch (Throwable throwable) {
                    throw new Exception(throwable);
                  }
                }
            
                @Override
                public void afterEach(ExtensionContext context) throws Exception {
                  after();
                }
            
                @Override
                public void recipe() throws Exception {
                }
            
            Show
            t8ch Thomas Weißschuh added a comment - To get it to work with TestExtension I had to modify the JenkinsRule from Chris Hunt : JenkinsRule(ParameterContext context, ExtensionContext extensionContext) { this .context = context; this .testDescription = Description.createTestDescription( extensionContext.getTestClass().map( Class ::getName).orElse( null ), extensionContext.getTestMethod().map(Method::getName).orElse( null ) ); } To make it work with @RegisterExtension (to interact with the rule from @BeforeEach methods) the following works: public static class JenkinsExtension extends org.jvnet.hudson.test.JenkinsRule implements BeforeEachCallback, AfterEachCallback { @Override public void beforeEach(ExtensionContext context) throws Exception { this .testDescription = Description.createTestDescription( context.getTestClass().map( Class ::getName).orElse( null ), context.getTestMethod().map(Method::getName).orElse( null ) ); try { before(); } catch (Throwable throwable) { throw new Exception(throwable); } } @Override public void afterEach(ExtensionContext context) throws Exception { after(); } @Override public void recipe() throws Exception { }
            Hide
            drulli Ulli Hafner added a comment -

            Has someone the spare time to provide these ideas as a PR for the test harness?

            Show
            drulli Ulli Hafner added a comment - Has someone the spare time to provide these ideas as a PR for the test harness?
            drulli Ulli Hafner made changes -
            Field Original Value New Value
            Remote Link This issue links to "PR #303 (Web Link)" [ 26720 ]
            drulli Ulli Hafner made changes -
            Assignee Oliver Gondža [ olivergondza ]
            drulli Ulli Hafner made changes -
            Status Open [ 1 ] In Progress [ 3 ]
            drulli Ulli Hafner made changes -
            Status In Progress [ 3 ] In Review [ 10005 ]
            jglick Jesse Glick made changes -
            Resolution Fixed [ 1 ]
            Status In Review [ 10005 ] Resolved [ 5 ]
            Hide
            jglick Jesse Glick added a comment -

            Fix caused regressions in plugin-pom; reverting until that can be sorted out.

            Show
            jglick Jesse Glick added a comment - Fix caused regressions in plugin-pom ; reverting until that can be sorted out.
            jglick Jesse Glick made changes -
            Resolution Fixed [ 1 ]
            Status Resolved [ 5 ] Reopened [ 4 ]

              People

              Assignee:
              Unassigned Unassigned
              Reporter:
              drulli Ulli Hafner
              Votes:
              9 Vote for this issue
              Watchers:
              12 Start watching this issue

                Dates

                Created:
                Updated: