Skip to content

Commit c18dd83

Browse files
committed
test additional user agent workarounds
1 parent 07e99d6 commit c18dd83

3 files changed

Lines changed: 160 additions & 9 deletions

File tree

.github/workflows/groovy-build-coverage.yml

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,40 @@ jobs:
4646
#
4747
# Same key prefix as `groovy-build-test.yml` so the two workflows
4848
# share their accumulated Grape cache.
49-
- name: "🍇 Cache @Grab artifacts (~/.groovy/grapes)"
49+
- name: "🍇 Cache @Grab artifacts (~/.groovy/grapes + ~/.m2/repository)"
5050
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
5151
with:
52-
path: ~/.groovy/grapes
52+
path: |
53+
~/.groovy/grapes
54+
~/.m2/repository
5355
key: ${{ runner.os }}-grape-${{ github.run_id }}
5456
restore-keys: |
5557
${{ runner.os }}-grape-
5658
- uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
59+
- name: "🌡 Pre-warm @Grab artifacts via Maven"
60+
shell: bash
61+
run: |
62+
set +e
63+
coords=$(grep -rhEo "@Grab\(\s*(value\s*=\s*)?['\"][^'\"]+['\"]" src/test subprojects/*/src/test 2>/dev/null \
64+
| sed -E "s/.*@Grab\(\s*(value\s*=\s*)?['\"]([^'\"]+)['\"].*/\2/" \
65+
| grep -E '^[a-zA-Z0-9._-]+:[a-zA-Z0-9._-]+:[a-zA-Z0-9._+-]+$' \
66+
| sort -u)
67+
[ -z "$coords" ] && { echo "No @Grab coords — skipping"; exit 0; }
68+
n=$(printf '%s\n' "$coords" | wc -l | tr -d ' ')
69+
echo "Pre-warming $n coords"
70+
ok=0; fail=0
71+
while IFS= read -r c; do
72+
if mvn -B -q dependency:get -Dartifact="$c" -Dtransitive=true >/dev/null 2>&1; then
73+
ok=$((ok+1))
74+
else
75+
fail=$((fail+1)); echo " ⚠ $c"
76+
fi
77+
done <<< "$coords"
78+
echo "Pre-warm: $ok ok / $fail failed"
79+
exit 0
80+
timeout-minutes: 15
5781
- name: Test with Gradle
58-
run: ./gradlew -Pcoverage=true jacocoAllReport
82+
run: ./gradlew -Pgroovy.grape.bridge-cache=true -Pcoverage=true jacocoAllReport
5983
timeout-minutes: 60
6084
- name: Upload coverage to Codecov
6185
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0

.github/workflows/groovy-build-test.yml

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,19 +64,60 @@ jobs:
6464
# fresh entry; the next run finds the most recent via prefix
6565
# fallback. The cache grows with new @Grab coordinates over time and
6666
# never gets invalidated by older ones being removed.
67-
- name: "🍇 Cache @Grab artifacts (~/.groovy/grapes)"
67+
- name: "🍇 Cache @Grab artifacts (~/.groovy/grapes + ~/.m2/repository)"
6868
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
6969
with:
70-
path: ~/.groovy/grapes
70+
# ~/.groovy/grapes is what Grape/Ivy reads from at test time (via the
71+
# bridge in org.apache.groovy-tested.gradle). ~/.m2/repository is what
72+
# the pre-warm step below populates with mvn dependency:get — the bridge
73+
# also copies it into the test JVM's isolated localm2 root.
74+
path: |
75+
~/.groovy/grapes
76+
~/.m2/repository
7177
key: ${{ runner.os }}-grape-${{ github.run_id }}
7278
restore-keys: |
7379
${{ runner.os }}-grape-
7480
- name: "🐘 Setup Gradle"
7581
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
7682
- name: "🔍 Setup TestLens"
7783
uses: testlens-app/setup-testlens@v1
84+
# Pre-warm the @Grab artifact cache by fetching each unique Maven-shorthand
85+
# coordinate referenced in test sources via `mvn dependency:get`. Maven uses
86+
# Apache HttpClient (vs Ivy's java.net.URLConnection), so it currently
87+
# passes Cloudflare's WAF in front of Maven Central while Ivy gets HTTP 404.
88+
# Once any run succeeds, actions/cache saves ~/.m2/repository for the next
89+
# run, and the org.apache.groovy-tested.gradle bridge copies the artifacts
90+
# into each test task's isolated localm2 root so tests stay off the network.
91+
# Failures here are non-fatal — Ivy will retry at test time and may succeed
92+
# on any individual artifact even when bursts are throttled.
93+
- name: "🌡 Pre-warm @Grab artifacts via Maven"
94+
shell: bash
95+
run: |
96+
set +e
97+
coords=$(grep -rhEo "@Grab\(\s*(value\s*=\s*)?['\"][^'\"]+['\"]" src/test subprojects/*/src/test 2>/dev/null \
98+
| sed -E "s/.*@Grab\(\s*(value\s*=\s*)?['\"]([^'\"]+)['\"].*/\2/" \
99+
| grep -E '^[a-zA-Z0-9._-]+:[a-zA-Z0-9._-]+:[a-zA-Z0-9._+-]+$' \
100+
| sort -u)
101+
if [ -z "$coords" ]; then
102+
echo "No @Grab coords discovered — skipping pre-warm"
103+
exit 0
104+
fi
105+
n=$(printf '%s\n' "$coords" | wc -l | tr -d ' ')
106+
echo "Pre-warming $n @Grab coords via mvn dependency:get"
107+
ok=0; fail=0
108+
while IFS= read -r coord; do
109+
if mvn -B -q dependency:get -Dartifact="$coord" -Dtransitive=true >/dev/null 2>&1; then
110+
ok=$((ok+1))
111+
else
112+
fail=$((fail+1))
113+
echo " ⚠ $coord"
114+
fi
115+
done <<< "$coords"
116+
echo "Pre-warm complete: $ok ok / $fail failed (failures retried by Ivy at test time)"
117+
exit 0
118+
timeout-minutes: 15
78119
- name: "🏃Test with Gradle"
79-
run: ./gradlew test ${{ matrix.junit-network }} -Ptarget.java.home="$JAVA_HOME_${{ matrix.java }}_${{ runner.arch }}"
120+
run: ./gradlew test ${{ matrix.junit-network }} -Pgroovy.grape.bridge-cache=true -Ptarget.java.home="$JAVA_HOME_${{ matrix.java }}_${{ runner.arch }}"
80121
shell: bash
81122
timeout-minutes: 60
82123
- name: "🚀Upload reports"
@@ -105,14 +146,38 @@ jobs:
105146
check-latest: true
106147
# See the lts job for rationale; same prefix lets both jobs share
107148
# the cache.
108-
- name: "🍇 Cache @Grab artifacts (~/.groovy/grapes)"
149+
- name: "🍇 Cache @Grab artifacts (~/.groovy/grapes + ~/.m2/repository)"
109150
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
110151
with:
111-
path: ~/.groovy/grapes
152+
path: |
153+
~/.groovy/grapes
154+
~/.m2/repository
112155
key: ${{ runner.os }}-grape-${{ github.run_id }}
113156
restore-keys: |
114157
${{ runner.os }}-grape-
115158
- uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
159+
- name: "🌡 Pre-warm @Grab artifacts via Maven"
160+
shell: bash
161+
run: |
162+
set +e
163+
coords=$(grep -rhEo "@Grab\(\s*(value\s*=\s*)?['\"][^'\"]+['\"]" src/test subprojects/*/src/test 2>/dev/null \
164+
| sed -E "s/.*@Grab\(\s*(value\s*=\s*)?['\"]([^'\"]+)['\"].*/\2/" \
165+
| grep -E '^[a-zA-Z0-9._-]+:[a-zA-Z0-9._-]+:[a-zA-Z0-9._+-]+$' \
166+
| sort -u)
167+
[ -z "$coords" ] && { echo "No @Grab coords — skipping"; exit 0; }
168+
n=$(printf '%s\n' "$coords" | wc -l | tr -d ' ')
169+
echo "Pre-warming $n coords"
170+
ok=0; fail=0
171+
while IFS= read -r c; do
172+
if mvn -B -q dependency:get -Dartifact="$c" -Dtransitive=true >/dev/null 2>&1; then
173+
ok=$((ok+1))
174+
else
175+
fail=$((fail+1)); echo " ⚠ $c"
176+
fi
177+
done <<< "$coords"
178+
echo "Pre-warm: $ok ok / $fail failed"
179+
exit 0
180+
timeout-minutes: 15
116181
- name: "🏃Test with Gradle"
117-
run: ./gradlew test -Ptarget.java.home="$JAVA_HOME_${{ matrix.java }}_X64"
182+
run: ./gradlew test -Pgroovy.grape.bridge-cache=true -Ptarget.java.home="$JAVA_HOME_${{ matrix.java }}_X64"
118183
timeout-minutes: 60

build-logic/src/main/groovy/org.apache.groovy-tested.gradle

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,19 @@ dependencies {
4646
def aggregator = TestResultAggregatorService.register(
4747
objects.newInstance(TestServices).buildEventsListenerRegistry, gradle)
4848

49+
// Opt-in: bridge the runner's ~/.groovy/grapes (populated by actions/cache in
50+
// .github/workflows/groovy-build-*.yml) into each Test task's per-task grape
51+
// root at task start, and fold newly-resolved artifacts back at task end.
52+
// Without bridging, the actions/cache restore lands at a path the test JVM
53+
// never reads (test JVMs run with -Duser.home=<task-temp>, so $user.home/.groovy
54+
// resolves to <task-temp>/.groovy — not the runner's real home). Enabling
55+
// bridging lets tests reuse previously-cached artifacts instead of re-hitting
56+
// Maven Central (mitigates HTTP 429 throttling). Disabled by default locally
57+
// so a developer's polluted ~/.groovy/grapes doesn't leak into tests.
58+
// Enable on CI with: ./gradlew test -Pgroovy.grape.bridge-cache=true
59+
def grapeBridgeCache = (findProperty('groovy.grape.bridge-cache') ?:
60+
System.properties['groovy.grape.bridge-cache']) == 'true'
61+
4962
tasks.withType(Test).configureEach {
5063
def fs = objects.newInstance(TestServices).fileSystemOperations
5164
def grapeDirectory = new File(temporaryDir, '.groovy')
@@ -115,10 +128,59 @@ tasks.withType(Test).configureEach {
115128
// delete if it exists already to be in a clean state
116129
delete(grapeDirectory)
117130
}
131+
if (grapeBridgeCache) {
132+
// Copy the actions/cache-restored ~/.groovy/grapes into the test
133+
// task's grape root so resolutions can hit the local filesystem
134+
// resolver (`cachedGrapes` in defaultGrapeConfig.xml) instead of
135+
// re-fetching from Maven Central. Exclude lock files and Maven's
136+
// "lastUpdated" markers so we don't propagate orphan locks or
137+
// stale negative-resolution state across runs.
138+
def sharedGrapes = new File(System.getProperty('user.home'), '.groovy/grapes')
139+
if (sharedGrapes.isDirectory()) {
140+
fs.copy {
141+
from(sharedGrapes)
142+
into(new File(grapeDirectory, 'grapes'))
143+
exclude '**/*.lck', '**/*.lastUpdated'
144+
}
145+
logger.lifecycle "Bridged ~/.groovy/grapes -> ${grapeDirectory}/grapes"
146+
}
147+
// Also bridge ~/.m2/repository so the test JVM's localm2 Ivy resolver
148+
// (configured as file:${user.home}/.m2/repository/) finds artifacts
149+
// that the workflow's pre-warm step populated via `mvn dependency:get`.
150+
// Tests run with -Duser.home=<task-temp>, so without this they'd see
151+
// an empty localm2 and fall through to Maven Central — exactly the
152+
// path that's currently being 404-throttled by Cloudflare for Java's
153+
// URLConnection client.
154+
def sharedM2 = new File(System.getProperty('user.home'), '.m2/repository')
155+
def m2Target = new File(temporaryDir, '.m2/repository')
156+
if (sharedM2.isDirectory()) {
157+
fs.copy {
158+
from(sharedM2)
159+
into(m2Target)
160+
exclude '**/*.lastUpdated', '**/_remote.repositories'
161+
}
162+
logger.lifecycle "Bridged ~/.m2/repository -> ${m2Target}"
163+
}
164+
}
118165
logger.debug "Grape directory: ${grapeDirectory.absolutePath}"
119166
}
120167

121168
doLast {
169+
if (grapeBridgeCache) {
170+
// Fold newly-resolved artifacts back into the shared cache so the
171+
// workflow's `actions/cache` save step persists them for next run.
172+
// The forward bridge filtered locks/lastUpdated; do the same here.
173+
def sharedGrapes = new File(System.getProperty('user.home'), '.groovy/grapes')
174+
def testGrapes = new File(grapeDirectory, 'grapes')
175+
if (testGrapes.isDirectory()) {
176+
sharedGrapes.mkdirs()
177+
fs.copy {
178+
from(testGrapes)
179+
into(sharedGrapes)
180+
exclude '**/*.lck', '**/*.lastUpdated'
181+
}
182+
}
183+
}
122184
fs.delete {
123185
delete(files(".").filter { it.name.endsWith '.class' })
124186
}

0 commit comments

Comments
 (0)