Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions .claude/skills/migrate-groovy-to-java/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Migrate test Groovy files to Java using JUnit 5
2. Convert Groovy files to Java using JUnit 5
3. Make sure the tests are still passing after migration and that the test count has not changed
4. Remove Groovy files
5. Add the migrated module path(s) to `.github/g2j-migrated-modules.txt`

When converting Groovy code to Java code, make sure that:
- The Java code generated is compatible with JDK 8
Expand Down
9 changes: 9 additions & 0 deletions .github/g2j-migrated-modules.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# This file lists modules that have been migrated from Groovy to Java / JUnit 5.
# New *.groovy files under any src/<*test*>/groovy/ path
# in these modules will fail the 'enforce-groovy-migration' PR check.
#
# After a module is migrated, add it on a new line here.
# Use the filesystem path prefix as seen below.

buildSrc/call-site-instrumentation-plugin
components/json
13 changes: 13 additions & 0 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,19 @@ _Action:_

_Recovery:_ Check at the milestone for the related issues and update them manually.

### enforce-groovy-migration [🔗](enforce-groovy-migration.yaml)

_Trigger:_ When creating or updating a pull request targeting `master`, or when labels are updated.

_Actions:_

* Fail the PR if a new Groovy test file is added to a module listed in [`.github/g2j-migrated-modules.txt`](../g2j-migrated-modules.txt) (hard enforcement),
* Post a warning comment on the PR if a new Groovy test file is added to any other non-exempt module (soft warning). Instrumentation (`dd-java-agent/instrumentation/`) and smoke-test (`dd-smoke-tests/`) modules are exempt from this warning.

_Recovery:_ Re-write the Groovy test files in Java / JUnit 5. To override this check entirely, add the `tag: override-groovy-enforcement` label to the PR. Remove the label to re-enable enforcement.

_Notes:_ The migrated modules list is always read from `master`. Add a new entry to `.github/g2j-migrated-modules.txt` each time a module is migrated to Java / JUnit 5.

## Code Quality and Security

### analyze-changes [🔗](analyze-changes.yaml)
Expand Down
169 changes: 169 additions & 0 deletions .github/workflows/enforce-groovy-migration.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
name: Enforce Groovy Migration
on:
pull_request:
types: [opened, edited, ready_for_review, labeled, unlabeled, synchronize]
branches:
- master

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
enforce_groovy_migration:
name: Enforce Groovy migration
permissions:
issues: write # Required to create a comment on the pull request
pull-requests: write # Required to create a comment on the pull request
contents: read # Required to read migrated modules file
runs-on: ubuntu-latest
steps:
- name: Check for Groovy regressions
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # 8.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
// Skip draft pull requests
if (context.payload.pull_request.draft) {
return
}

// Check for override label — skip all checks if label present
const labels = context.payload.pull_request.labels.map(l => l.name)
if (labels.includes('tag: override-groovy-enforcement')) {
console.log('tag: override-groovy-enforcement label detected — skipping all checks.')
return
}

// Read migrated modules list from master
const migratedMods = await github.rest.repos.getContent({
owner: context.repo.owner,
repo: context.repo.repo,
path: '.github/g2j-migrated-modules.txt',
ref: 'master'
})
const migratedPrefixes = Buffer.from(migratedMods.data.content, 'base64')
.toString()
.split('\n')
.map(l => l.trim())
.filter(l => l && !l.startsWith('#'))

// Get all files changed in this PR
const allFiles = await github.paginate(github.rest.pulls.listFiles, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number
})

// Filter these changed files to newly added Groovy files in any test source set
const addedGroovy = allFiles.filter(f =>
f.status === 'added' &&
/\/src\/[^/]*[tT]est[^/]*\/groovy\/.*\.groovy$/.test(f.filename)
)

// Extract module prefix from file path (everything before /src/(test|testFixtures)/groovy/)
const moduleOf = path => {
const m = path.match(/^(.*?)\/src\/(test|testFixtures)\/groovy\//)
return m ? m[1] : null
}

// Classify each added Groovy file
const regressions = []
const warnings = []
for (const file of addedGroovy) {
const path = file.filename
const mod = moduleOf(path)
if (migratedPrefixes.some(prefix => path.startsWith(prefix + '/'))) {
regressions.push({ path, mod })
} else if (
path.startsWith('dd-java-agent/instrumentation/') ||
path.startsWith('dd-smoke-tests/')
) {
// ignore Groovy file additions to instrumentations and smoke-tests for now
} else {
warnings.push({ path, mod })
}
}

// Fetch existing comments once
const comments = await github.rest.issues.listComments({
issue_number: context.payload.pull_request.number,
owner: context.repo.owner,
repo: context.repo.repo
})

const regressionMarker = '<!-- dd-trace-java-groovy-regression -->'
const warningMarker = '<!-- dd-trace-java-groovy-warning -->'
const existingRegressionComment = comments.data.find(c => c.body.includes(regressionMarker))
const existingWarningComment = comments.data.find(c => c.body.includes(warningMarker))

// Handle regression comment
if (regressions.length > 0) {
const fileList = regressions
.map(({ path, mod }) => `- \`${path}\` (module: \`${mod}\`)`)
.join('\n')
const body = `**❌ Groovy Test Regression Detected**\n\n` +
`The following files add Groovy tests to modules that have been fully migrated to Java / JUnit 5:\n\n` +
`${fileList}\n\n` +
`These modules no longer accept Groovy test files. Please rewrite the test in Java / JUnit 5 instead.\n\n` +
regressionMarker
if (existingRegressionComment) {
await github.rest.issues.updateComment({
comment_id: existingRegressionComment.id,
owner: context.repo.owner,
repo: context.repo.repo,
body
})
} else {
await github.rest.issues.createComment({
issue_number: context.payload.pull_request.number,
owner: context.repo.owner,
repo: context.repo.repo,
body
})
}
} else if (existingRegressionComment) {
await github.rest.issues.deleteComment({
comment_id: existingRegressionComment.id,
owner: context.repo.owner,
repo: context.repo.repo
})
}

// Handle warning comment
if (warnings.length > 0) {
const fileList = warnings
.map(({ path, mod }) => `- \`${path}\` (module: \`${mod}\`)`)
.join('\n')
const body = `**⚠️ New Groovy Test Files Added**\n\n` +
`The following files add Groovy tests to modules that are candidates for migration to Java / JUnit 5:\n\n` +
`${fileList}\n\n` +
`Consider writing these tests in Java / JUnit 5 instead to help with the ongoing migration effort.\n\n` +
warningMarker
if (existingWarningComment) {
await github.rest.issues.updateComment({
comment_id: existingWarningComment.id,
owner: context.repo.owner,
repo: context.repo.repo,
body
})
} else {
await github.rest.issues.createComment({
issue_number: context.payload.pull_request.number,
owner: context.repo.owner,
repo: context.repo.repo,
body
})
}
} else if (existingWarningComment) {
await github.rest.issues.deleteComment({
comment_id: existingWarningComment.id,
owner: context.repo.owner,
repo: context.repo.repo
})
}

// Fail the check if there are regressions
if (regressions.length > 0) {
core.setFailed(`${regressions.length} Groovy regression(s) detected in migrated module(s). See PR comment for details. To skip this check entirely, add the 'tag: override-groovy-enforcement' label.`)
}