Skip to content

Commit 9b097c5

Browse files
committed
test(manifest): stub the JVM fixture deps (v1.x)
The maven-compat smoke fixture declared commons-io, junit and hamcrest as real artifacts from Maven Central purely to get a graph shape: an external prod dep, a test-scope dep and its transitive. No fixture code imports them, so they were never compiled against or executed - but they still aged into GHSA-78wr-2p64-hpwj, a Dependabot alert, and a version bump. gradle-compat already solved this: make-localrepo.sh generated a tiny file-based Maven repo of empty-jar stubs at test time. That generator moves up a directory to test/make-stub-repo.sh, gains the ability to give a stub its own dependency, and now serves all three fixtures. maven-compat resolves demo.ext:tool (prod), demo.ext:harness (test) and its transitive demo.ext:harness-core through a <repository> whose URL smoke-test.sh passes as -Dstub.repo.url; sbt-compat resolves demo.ext:tool and demo.ext:harness the same way. The suites also stop using the developer's home caches. compat-cache.sh resolves one cache root under the temp dir, overridable by SOCKET_COMPAT_CACHE, and each tool is pointed at it: Maven by -Dmaven.repo.local, Gradle by -g, sbt by -Dsbt.ivy.home and COURSIER_CACHE. The root is stable so each tool's own closure is downloaded once, but the stub artifacts are evicted before every run, so a run can never pass on a stale copy of the thing under test. Nothing is read from or written to ~/.m2, ~/.gradle or ~/.ivy2, and records.tsv no longer embeds a path under the developer's home. Assertions prove exactly what they proved before, still matching on groupId:artifactId with no version written in any test, and still keying on the .jar suffix rather than any path. The transitive check is now unconditional: with a stub graph it is deterministic, so its absence is a defect rather than a junit-version detail. The internal reactor module's lookup now pins the field count as well as the prefix. 'demo:lib:' is a prefix of the typed 'demo:lib:jar:1.0' as well as the bare 'demo:lib:1.0', so on the prefix alone a regression that stopped emitting the bare id would still have passed a check whose message claims bare-id coverage. maven-compat's first assertion block moves out of its heredoc into assert-records.py, so the unit suite can run those assertions over synthetic records without Maven or a JDK. Nothing in CI runs the compat fixtures, so that unit test is the only automated cover the assertions have. Each build tool still fetches its own closure from Central - Maven's plugins pull commons-io themselves - so this is not a fully offline fixture. It declares no third-party dependency of its own. The fixture-generated paths also gain .gitignore entries, which this branch of the tree never had - which is how 256 files of Gradle cache came to be committed here in the first place. .gradle-home/ stays ignored rather than being dropped as it was on main, because smoke-test-workspaces.sh still keeps its Gradle user home inside the fixture directory on this line.
1 parent a0f78d3 commit 9b097c5

19 files changed

Lines changed: 446 additions & 134 deletions

File tree

.gitignore

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,20 @@ test/fixtures/commands/fix/e2e-test-py-temp-*
3030
!/.claude/skills/
3131

3232
!/.vscode/extensions.json
33+
34+
# Generated by the JVM compat fixtures (src/commands/manifest/scripts/test).
35+
# The stub repos and caches are rebuilt on every run; smoke-test-workspaces.sh
36+
# still keeps its Gradle user home inside the fixture directory.
37+
/src/commands/manifest/scripts/test/gradle-compat/.gradle-home/
38+
/src/commands/manifest/scripts/test/gradle-compat/project/.gradle/
39+
/src/commands/manifest/scripts/test/gradle-compat/project/build/
40+
/src/commands/manifest/scripts/test/gradle-compat/project/localrepo/
41+
/src/commands/manifest/scripts/test/gradle-compat/project/records.tsv
42+
/src/commands/manifest/scripts/test/gradle-compat/project/workspaces-records.tsv
43+
/src/commands/manifest/scripts/test/maven-compat/project/localrepo/
44+
/src/commands/manifest/scripts/test/maven-compat/project/records.tsv
45+
/src/commands/manifest/scripts/test/maven-compat/project/target/
46+
/src/commands/manifest/scripts/test/maven-compat/project/*/target/
47+
/src/commands/manifest/scripts/test/sbt-compat/project/localrepo/
48+
/src/commands/manifest/scripts/test/sbt-compat/project/records.tsv
49+
/src/commands/manifest/scripts/test/sbt-compat/project/target/

src/commands/manifest/scripts/test/README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,35 @@ sbt rows also need the `sbt` launcher on `PATH`.
2626
The runner downloads the build-tool distributions and invokes the per-ecosystem
2727
`smoke-test.sh`. The unit-level assembler/sidecar behavior is covered separately
2828
by the `*.test.mts` unit tests.
29+
30+
## Stub dependencies
31+
32+
All three fixtures declare their dependencies as stub artifacts — empty jars plus
33+
generated poms — that `make-stub-repo.sh` writes into a file-based Maven repo at
34+
test time. The fixtures only need the *shape* of a dependency graph (a prod dep, a
35+
test dep, a transitive), never the code, so a stub is behaviourally identical here
36+
and can never age into a CVE alert or a version bump. The generated repos are
37+
gitignored; nothing binary is committed.
38+
39+
Each build tool still fetches its own closure — Maven's plugins, sbt's
40+
scala-library, the Gradle distribution — from the network, so these fixtures are
41+
not "fully offline"; they simply declare no third-party dependencies of their own.
42+
Gradle is the exception: it also passes `--offline` and resolves everything it
43+
needs for the smoke test from the stub repo.
44+
45+
## Caches
46+
47+
No suite reads or writes the developer's own caches. Maven gets its local
48+
repository from `-Dmaven.repo.local`, Gradle its user home from `-g`, and sbt its
49+
Ivy home from `-Dsbt.ivy.home` plus `COURSIER_CACHE`, all under one root that
50+
`compat-cache.sh` resolves:
51+
52+
```
53+
${SOCKET_COMPAT_CACHE:-${TMPDIR:-/tmp}/socket-manifest-compat}
54+
```
55+
56+
That root is stable, so each tool's own closure is downloaded once and reused; the
57+
first run after clearing it pays for the download. The *stub* artifacts are evicted
58+
from it before every run, so a run can never pass on a stale copy of the thing
59+
under test. Set `SOCKET_COMPAT_CACHE` to a fresh `mktemp -d` for a completely cold
60+
run.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/usr/bin/env bash
2+
# Shared cache root for the JVM compat suites: the downloaded build-tool distributions plus each
3+
# tool's dependency cache. Keeping the caches here rather than in ~/.m2, ~/.gradle and ~/.ivy2 means
4+
# a run can't pass off a warm home cache, can't write a home path into the records it emits, and
5+
# can't leave anything behind in the developer's own caches. Point SOCKET_COMPAT_CACHE at a fresh
6+
# `mktemp -d` for a cold run.
7+
SOCKET_COMPAT_CACHE="${SOCKET_COMPAT_CACHE:-${TMPDIR:-/tmp}/socket-manifest-compat}"
8+
mkdir -p "$SOCKET_COMPAT_CACHE"

src/commands/manifest/scripts/test/gradle-compat/.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,4 @@ project/workspaces-records.tsv
55
project/.socket.facts.json
66
project/.gradle/
77
project/build/
8-
.gradle-home/
98
.populate-for.txt

src/commands/manifest/scripts/test/gradle-compat/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Those fallback branches never execute on modern Gradle, so without a test on *ol
1212
could silently rot. This smoke test exercises them.
1313

1414
## What it does
15-
`smoke-test.sh <path-to-gradle>` generates a tiny **local** Maven repo (`make-localrepo.sh` — two
15+
`smoke-test.sh <path-to-gradle>` generates a tiny **local** Maven repo (`../make-stub-repo.sh` — two
1616
transitive-free artifacts, a prod `demo.lib:foo` and a test `demo.test:bar`), runs the init script's
1717
`socketFacts` task against `project/` **fully offline**, and asserts the emitted RECORDS (the script's
1818
only output — the TS assembler that turns records into `.socket.facts.json` is covered by

src/commands/manifest/scripts/test/gradle-compat/make-localrepo.sh

Lines changed: 0 additions & 31 deletions
This file was deleted.

src/commands/manifest/scripts/test/gradle-compat/project/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Hermetic smoke-test project for ../../socket-facts.init.gradle.
22
//
3-
// Resolves two artifacts from a generated local Maven repo (see ../make-localrepo.sh)
3+
// Resolves two artifacts from a generated local Maven repo (see ../../make-stub-repo.sh)
44
// so it runs fully OFFLINE on every Gradle version — including pre-3.3, which the
55
// script must still support (old Gradle can't negotiate modern Maven Central's TLS,
66
// so a network repo would make the old-version matrix entries impossible to test).

src/commands/manifest/scripts/test/gradle-compat/smoke-test.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ HERE="$(cd "$(dirname "$0")" && pwd)"
1616
GRADLE="${1:?usage: smoke-test.sh <path-to-gradle-binary>}"
1717
INIT="$HERE/../../socket-facts.init.gradle"
1818
PROJECT="$HERE/project"
19-
GUH="$HERE/.gradle-home" # isolated Gradle user home -> hermetic, no global init scripts
2019
RECORDS="$PROJECT/records.tsv"
20+
# shellcheck source=SCRIPTDIR/../compat-cache.sh
21+
. "$HERE/../compat-cache.sh"
22+
GUH="$SOCKET_COMPAT_CACHE/gradle-home" # isolated Gradle user home -> hermetic, no global init scripts
2123

22-
bash "$HERE/make-localrepo.sh"
24+
bash "$HERE/../make-stub-repo.sh" "$PROJECT/localrepo" 'demo.lib:foo:1.0' 'demo.test:bar:1.0'
2325
rm -rf "$GUH" "$RECORDS" "$PROJECT/.gradle" "$PROJECT/build"
2426

2527
echo "+ $("$GRADLE" --version 2>/dev/null | sed -n 's/^Gradle //p' | head -1)"
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#!/usr/bin/env bash
2+
# Generate a hermetic file-based Maven repository of stub artifacts for the JVM compat fixtures.
3+
# Each stub gets a .pom and an empty-but-valid .jar in standard repo layout, so a build tool can
4+
# resolve the declared graph shape without reaching a network repository. The fixtures never compile
5+
# against or execute these artifacts — only their coordinates and scopes are observed — so stubs are
6+
# behaviourally identical to the real thing here, and they can never age into a CVE or a version bump.
7+
#
8+
# Text-only in git: everything under <repo-dir> is produced at test time and gitignored.
9+
#
10+
# Usage: make-stub-repo.sh <repo-dir> <spec>...
11+
# spec = group:artifact:version[+dep-group:dep-artifact:dep-version[,...]]
12+
# Deps listed after `+` are written as compile-scope dependencies of that stub, so a consumer that
13+
# depends on the stub also pulls them transitively.
14+
set -euo pipefail
15+
16+
REPO="${1:?usage: make-stub-repo.sh <repo-dir> <group:artifact:version[+dep,...]>...}"
17+
shift
18+
if [ "$#" -eq 0 ]; then
19+
echo "make-stub-repo.sh: no artifact specs given" >&2
20+
exit 1
21+
fi
22+
23+
rm -rf "$REPO"
24+
mkdir -p "$REPO"
25+
REPO="$(cd "$REPO" && pwd)" # `jar` runs from an empty dir, so the output path must be absolute
26+
27+
EMPTY="$(mktemp -d)"
28+
trap 'rm -rf "$EMPTY"' EXIT
29+
30+
for spec in "$@"; do
31+
coord="${spec%%+*}"
32+
deps=""
33+
[ "$coord" = "$spec" ] || deps="${spec#*+}"
34+
IFS=: read -r group art ver <<<"$coord"
35+
if [ -z "${group:-}" ] || [ -z "${art:-}" ] || [ -z "${ver:-}" ]; then
36+
echo "make-stub-repo.sh: malformed spec '$spec' (want group:artifact:version[+dep,...])" >&2
37+
exit 1
38+
fi
39+
40+
dir="$REPO/${group//.//}/$art/$ver"
41+
mkdir -p "$dir"
42+
43+
{
44+
echo '<project xmlns="http://maven.apache.org/POM/4.0.0">'
45+
echo ' <modelVersion>4.0.0</modelVersion>'
46+
echo " <groupId>$group</groupId>"
47+
echo " <artifactId>$art</artifactId>"
48+
echo " <version>$ver</version>"
49+
echo ' <packaging>jar</packaging>'
50+
if [ -n "$deps" ]; then
51+
echo ' <dependencies>'
52+
IFS=, read -r -a dep_list <<<"$deps"
53+
for dep in "${dep_list[@]}"; do
54+
IFS=: read -r dgroup dart dver <<<"$dep"
55+
if [ -z "${dgroup:-}" ] || [ -z "${dart:-}" ] || [ -z "${dver:-}" ]; then
56+
echo "make-stub-repo.sh: malformed dependency '$dep' in spec '$spec'" >&2
57+
exit 1
58+
fi
59+
echo ' <dependency>'
60+
echo " <groupId>$dgroup</groupId>"
61+
echo " <artifactId>$dart</artifactId>"
62+
echo " <version>$dver</version>"
63+
echo ' </dependency>'
64+
done
65+
echo ' </dependencies>'
66+
fi
67+
echo '</project>'
68+
} >"$dir/$art-$ver.pom"
69+
70+
# empty but valid jar (jar ships with every JDK, which these fixtures already need)
71+
( cd "$EMPTY" && jar cf "$dir/$art-$ver.jar" . )
72+
done
73+
74+
echo "built stub repo at $REPO"

src/commands/manifest/scripts/test/maven-compat/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
project/localrepo/
12
project/records.tsv
23
project/workspaces-records.tsv
34
project/target/

0 commit comments

Comments
 (0)