Skip to content

Add MigrateHudsonTestCaseToJenkinsRule recipe#1718

Open
Fikri-20 wants to merge 3 commits intojenkins-infra:mainfrom
Fikri-20:feat/migrate-hudson-test-case
Open

Add MigrateHudsonTestCaseToJenkinsRule recipe#1718
Fikri-20 wants to merge 3 commits intojenkins-infra:mainfrom
Fikri-20:feat/migrate-hudson-test-case

Conversation

@Fikri-20
Copy link
Copy Markdown
Contributor

@Fikri-20 Fikri-20 commented Apr 18, 2026

Adds a new OpenRewrite recipe MigrateHudsonTestCaseToJenkinsRule that migrates JUnit 3-style Jenkins test classes extending HudsonTestCase to the modern JUnit 4 @Rule JenkinsRule pattern.

The recipe performs the following transformations on any class extending HudsonTestCase:

  • Removes the extends HudsonTestCase clause
  • Inserts @Rule public JenkinsRule j = new JenkinsRule(); as the first field
  • Converts setUp() to @Before and strips super.setUp()
  • Converts tearDown() to @After and strips super.tearDown()
  • Annotates JUnit 3-style public void test*() methods with @Test
  • Prefixes 14 known delegated HudsonTestCase method calls with j.

The recipe is registered standalone and chained as the first step of MigrateToJUnit5 so HudsonTestCase classes are modernized before the JUnit 5 transformations run.

Testing done

  1. Unit tests (5/5 passing)
mvn test -pl plugin-modernizer-core -Dtest=MigrateHudsonTestCaseToJenkinsRuleTest
[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS
  1. End-to-end on a real plugin
    Verified against jenkinsci/backlog-plugin, which still extends HudsonTestCase.

Install the recipe locally:

cd ~/Desktop/jenkins/plugin-modernizer-tool
mvn install -pl plugin-modernizer-core -am -DskipTests -q

Clone the target plugin and run the recipe:

git clone --depth=1 https://github.com/jenkinsci/backlog-plugin.git /tmp/backlog-plugin
cd /tmp/backlog-plugin && mvn \
"org.openrewrite.maven:rewrite-maven-plugin:6.36.0:run" \
-Denforcer.skip=true \
-Dhpi.validate.skip=true \
-Dmaven.antrun.skip=true \
-Drewrite.activeRecipes="io.jenkins.tools.pluginmodernizer.core.recipes.MigrateHudsonTestCaseToJenkinsRule" \
-Drewrite.recipeArtifactCoordinates="io.jenkins.plugin-modernizer:plugin-modernizer-core:999999-SNAPSHOT"

Recipe execution output (relevant lines):

[INFO] Found HudsonTestCase subclass: hudson.plugins.backlog.BacklogProjectPropertyTest
[INFO] Found HudsonTestCase subclass: hudson.plugins.backlog.BacklogLinkActionTest
[INFO] Added @Rule JenkinsRule j field to BacklogProjectPropertyTest
[INFO] Added @Rule JenkinsRule j field to BacklogLinkActionTest
[WARNING] Changes have been made to src/test/java/hudson/plugins/backlog/BacklogProjectPropertyTest.java by: io.jenkins.tools.pluginmodernizer.core.recipes.MigrateHudsonTestCaseToJenkinsRule
[WARNING] Changes have been made to src/test/java/hudson/plugins/backlog/BacklogLinkActionTest.java by: io.jenkins.tools.pluginmodernizer.core.recipes.MigrateHudsonTestCaseToJenkinsRule
[WARNING] Estimate time saved: 10m
[INFO] BUILD SUCCESS

Resulting diff:

git diff src/test/java/hudson/plugins/backlog/BacklogLinkActionTest.java
diff --git a/src/test/java/hudson/plugins/backlog/BacklogLinkActionTest.java b/src/test/java/hudson/plugins/backlog/BacklogLinkActionTest.java
--- a/src/test/java/hudson/plugins/backlog/BacklogLinkActionTest.java
+++ b/src/test/java/hudson/plugins/backlog/BacklogLinkActionTest.java
@@ -4,12 +4,16 @@ import com.nulabinc.backlog4j.*;
 import com.nulabinc.backlog4j.api.option.PullRequestQueryParams;
 import com.nulabinc.backlog4j.conf.BacklogConfigure;
 import com.nulabinc.backlog4j.conf.BacklogJpConfigure;
+import org.junit.Rule;
 import org.junit.Test;
-import org.jvnet.hudson.test.HudsonTestCase;
+import org.jvnet.hudson.test.JenkinsRule;
 import java.util.Arrays;
-public class BacklogLinkActionTest extends HudsonTestCase {
+public class BacklogLinkActionTest {
+
+    @Rule
+    public JenkinsRule j = new JenkinsRule();

The recipe correctly removed extends HudsonTestCase, swapped the HudsonTestCase import for JenkinsRule, added the org.junit.Rule import, and inserted the @Rule public JenkinsRule j = new JenkinsRule(); field as the first member.

Submitter checklist

  • Make sure you are opening from a topic/feature/bugfix branch (right side) and not your main branch!
  • Ensure that the pull request title represents the desired changelog entry
  • Please describe what you did
  • Link to relevant issues in GitHub or Jira
  • Link to relevant pull requests, esp. upstream and downstream changes
  • Ensure you have provided tests - that demonstrates feature works or fixes the issue

Implements an OpenRewrite ScanningRecipe that migrates JUnit 3-style
test classes extending HudsonTestCase to the modern JUnit 4 @rule
JenkinsRule pattern. Removes the extends clause, inserts the @rule
field, converts setUp/tearDown lifecycle methods, annotates JUnit 3
test methods with @test, and prefixes delegated HudsonTestCase method
calls with j.
Covers the five core scenarios: extends removal with @rule field
insertion, setUp/tearDown lifecycle conversion, prefixing delegated
method calls with j., no-op behavior on already-modern classes, and
setUp methods without a super call.
Exposes the new recipe under the
io.jenkins.tools.pluginmodernizer.MigrateHudsonTestCaseToJenkinsRule
declarative name and chains it as the first step of MigrateToJUnit5
so that HudsonTestCase classes are modernized to JenkinsRule before
the JUnit 5 transformations run.
@Fikri-20 Fikri-20 requested a review from jonesbusy as a code owner April 18, 2026 06:59
@jonesbusy
Copy link
Copy Markdown
Collaborator

Mhh what is the reason to migrate to JUnit 4 when there is on on-going migration to ban and migrate to JUnit Jupiter 5 (and more). Can you explain further?

@Fikri-20
Copy link
Copy Markdown
Contributor Author

Fikri-20 commented Apr 18, 2026

You're right that JUnit 5 is the end goal, and this recipe isn't trying to compete with that, it's actually meant to feed into the existing MigrateToJUnit5 pipeline.

The issue is that MigrateToJUnit5 works great on classes that already have @Test, @Before, etc., but it can't do much with old classes that still extends HudsonTestCase, since those are JUnit 3-style and don't have any of those annotations to transform yet, so those plugins get stuck.

This recipe handles that first hop: JUnit 3 (HudsonTestCase) → JUnit 4 (@Rule JenkinsRule), and then I chained it as the first step inside MigrateToJUnit5 (recipes.yml change), so when someone runs the full migration they get JUnit 3 → 4 → 5 end-to-end in one go, nothing is left sitting on JUnit 4. It can also be run standalone if a maintainer wants to modernize step by step rather than jumping straight to JUnit 5.

Let me know if that makes sense, or if you'd prefer a different approach, happy to adjust!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants