Skip to content

Commit f7c8586

Browse files
committed
Address codex reviews
1 parent 5318987 commit f7c8586

4 files changed

Lines changed: 102 additions & 64 deletions

File tree

.github/scripts/dependency_age.py

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ def emit_outputs(outputs: dict[str, Any], github_output: str | None) -> None:
121121
def load_json(file_path: str | None, url: str | None) -> Any:
122122
if file_path:
123123
text = Path(file_path).read_text(encoding="utf-8")
124-
text = re.sub(r"[ \t]*//[^\n]*", "", text) # strip // line comments
124+
text = re.sub(r"(?<!:)//[^\n]*", "", text) # strip // line comments, preserve ://
125125
return json.loads(text)
126126
if not url:
127127
raise ValueError("either file_path or url is required")
@@ -295,7 +295,7 @@ def validate_lockfiles(args: argparse.Namespace) -> int:
295295
for relative_path, gav in changed:
296296
gav_to_files.setdefault(gav, set()).add(relative_path)
297297

298-
reverted: list[tuple[str, list[str], str]] = []
298+
violations: list[tuple[str, list[str], str]] = []
299299
for gav in sorted(gav_to_files):
300300
published_at, reason = resolve_gav_timestamp(
301301
gav=gav,
@@ -304,10 +304,10 @@ def validate_lockfiles(args: argparse.Namespace) -> int:
304304
)
305305
affected_files = sorted(gav_to_files[gav])
306306
if published_at is None:
307-
reverted.append((gav, affected_files, reason or "Unable to determine publish timestamp."))
307+
violations.append((gav, affected_files, reason or "Unable to determine publish timestamp."))
308308
continue
309309
if published_at > cutoff:
310-
reverted.append(
310+
violations.append(
311311
(
312312
gav,
313313
affected_files,
@@ -323,21 +323,21 @@ def validate_lockfiles(args: argparse.Namespace) -> int:
323323
f"(published {format_datetime(published_at)}, cutoff {format_datetime(cutoff)})"
324324
)
325325

326-
if reverted:
327-
outputs["reverted_coordinates"] = len(reverted)
326+
if violations:
327+
outputs["reverted_coordinates"] = len(violations)
328328
revert_violations_in_lockfiles(
329-
violations=reverted,
329+
violations=violations,
330330
baseline_dir=baseline_dir,
331331
current_dir=current_dir,
332332
)
333-
for gav, affected_files, message in reverted:
333+
for gav, affected_files, message in violations:
334334
for path in affected_files:
335335
print(f"::warning file={path}::{gav}: {message} Reverted to prior version.")
336336

337337
emit_outputs(outputs, args.github_output)
338338
print(
339339
f"Validated {len(changed)} changed dependency selections against cutoff {format_datetime(cutoff)}. "
340-
f"{len(reverted)} reverted."
340+
f"{len(violations)} reverted."
341341
)
342342
return 0
343343

@@ -363,16 +363,26 @@ def revert_violations_in_lockfiles(
363363
baseline_by_gav = read_lockfile_lines(baseline_path) if baseline_path.exists() else {}
364364
current_by_gav = read_lockfile_lines(current_path)
365365

366-
# For each violated GAV, find which baseline coordinate(s) it replaced: those
367-
# that share the same group:artifact but are absent from the current lockfile.
368-
predecessors_by_violated: dict[str, list[str]] = {}
369-
for violated_gav in violated_gavs:
370-
ga = ":".join(violated_gav.split(":")[:2])
371-
removed = sorted(
372-
b for b in baseline_by_gav
373-
if ":".join(b.split(":")[:2]) == ga and b not in current_by_gav
374-
)
375-
predecessors_by_violated[violated_gav] = removed
366+
# Group removed baseline GAVs and violated GAVs by group:artifact.
367+
removed_by_ga: dict[str, list[str]] = {}
368+
for b in sorted(baseline_by_gav):
369+
if b not in current_by_gav:
370+
ga = ":".join(b.split(":")[:2])
371+
removed_by_ga.setdefault(ga, []).append(b)
372+
373+
violations_by_ga: dict[str, list[str]] = {}
374+
for v in sorted(violated_gavs):
375+
ga = ":".join(v.split(":")[:2])
376+
violations_by_ga.setdefault(ga, []).append(v)
377+
378+
# Pair each removed predecessor with the violation at the same sorted position.
379+
# Excess predecessors (consolidation) pile onto the last violation.
380+
# Violations with no corresponding predecessor are brand-new dependencies.
381+
predecessors_by_violated: dict[str, list[str]] = {v: [] for v in violated_gavs}
382+
for ga, ga_violations in violations_by_ga.items():
383+
for i, pred in enumerate(removed_by_ga.get(ga, [])):
384+
target = ga_violations[min(i, len(ga_violations) - 1)]
385+
predecessors_by_violated[target].append(pred)
376386

377387
output_lines: list[str] = []
378388
for raw_line in current_path.read_text(encoding="utf-8").splitlines():

.github/scripts/tests/fixtures/maven-prerelease-filter.json

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

.github/scripts/tests/test_dependency_age.py

Lines changed: 71 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
SCRIPT = REPO_ROOT / ".github/scripts/dependency_age.py"
1212
FIXTURES = Path(__file__).resolve().parent / "fixtures"
1313
NOW = "2026-04-24T12:00:00Z"
14-
OUTPUT_PATTERN = re.compile(r"^(cutoff_at|found|version|published_at|reason)=(.*)$")
14+
OUTPUT_PATTERN = re.compile(
15+
r"^(cutoff_at|found|version|published_at|reason|validated_coordinates|reverted_coordinates)=(.*)$"
16+
)
1517

1618

1719
class DependencyAgeScriptTest(unittest.TestCase):
@@ -61,29 +63,6 @@ def test_reports_when_no_eligible_gradle_release_exists(self) -> None:
6163
self.assertEqual(outputs["found"], "false")
6264
self.assertIn("No eligible stable Gradle release", outputs["reason"])
6365

64-
def test_maven_prerelease_filtering_still_excludes_alpha_beta_and_rc(self) -> None:
65-
result = self.run_script(
66-
"select-maven",
67-
"--now",
68-
NOW,
69-
"--group-id",
70-
"org.apache.maven",
71-
"--artifact-id",
72-
"apache-maven",
73-
"--search-response-file",
74-
str(FIXTURES / "maven-prerelease-filter.json"),
75-
"--prerelease-pattern",
76-
"alpha",
77-
"--prerelease-pattern",
78-
"beta",
79-
"--prerelease-pattern",
80-
"rc",
81-
)
82-
83-
self.assertEqual(result.returncode, 0, result.stderr)
84-
outputs = self.parse_outputs(result.stdout)
85-
self.assertEqual(outputs["version"], "3.9.9")
86-
8766
def test_selects_previous_maven_release_when_newest_is_too_new(self) -> None:
8867
result = self.run_script(
8968
"select-maven",
@@ -129,7 +108,6 @@ def test_exact_48_hour_boundary_is_accepted(self) -> None:
129108
self.assertEqual(outputs["version"], "3.5.5")
130109
self.assertEqual(outputs["published_at"], "2026-04-22T12:00:00Z")
131110

132-
133111
def run_validate_lockfiles(
134112
self,
135113
*,
@@ -172,6 +150,38 @@ def run_validate_lockfiles(
172150
)
173151
return result, current_dir
174152

153+
def test_validates_changed_lockfiles_when_all_updates_are_old_enough(self) -> None:
154+
lockfile = "module/gradle.lockfile"
155+
baseline_content = "\n".join([
156+
"# Gradle lockfile",
157+
"com.example:lib-a:1.0.0=runtimeClasspath",
158+
"com.example:lib-b:1.0.0=runtimeClasspath",
159+
"",
160+
])
161+
current_content = "\n".join([
162+
"# Gradle lockfile",
163+
"com.example:lib-a:1.1.0=runtimeClasspath", # valid upgrade
164+
"com.example:lib-b:1.1.0=runtimeClasspath", # valid upgrade
165+
"",
166+
])
167+
metadata = {
168+
"com.example:lib-a:1.1.0": "2026-04-20T12:00:00Z", # old enough
169+
"com.example:lib-b:1.1.0": "2026-04-20T11:00:00Z", # old enough
170+
}
171+
172+
result, current_dir = self.run_validate_lockfiles(
173+
baseline={"module/gradle.lockfile": baseline_content},
174+
current={"module/gradle.lockfile": current_content},
175+
metadata=metadata,
176+
)
177+
178+
self.assertEqual(result.returncode, 0, result.stderr)
179+
outputs = self.parse_outputs(result.stdout)
180+
self.assertEqual(outputs["validated_coordinates"], "2")
181+
self.assertEqual(outputs["reverted_coordinates"], "0")
182+
final = (current_dir / lockfile).read_text(encoding="utf-8")
183+
self.assertEqual(final, current_content)
184+
175185
def test_reverts_too_new_version_upgrade_keeps_valid_changes(self) -> None:
176186
lockfile = "module/gradle.lockfile"
177187
baseline_content = "\n".join([
@@ -236,6 +246,42 @@ def test_reverts_correct_version_when_multiple_versions_coexist(self) -> None:
236246
self.assertIn("com.typesafe:config:1.4.4=runtimeClasspath,testRuntimeClasspath", final)
237247
self.assertNotIn("com.typesafe:config:1.5.0", final)
238248

249+
def test_reverts_each_version_independently_for_same_group_artifact(self) -> None:
250+
# Both versions of the same group:artifact are upgraded to too-new versions.
251+
# Each must be reverted to its own predecessor, not both to the same line.
252+
lockfile = "module/gradle.lockfile"
253+
baseline_content = "\n".join([
254+
"# Gradle lockfile",
255+
"com.example:lib:1.0.0=compileClasspath",
256+
"com.example:lib:2.0.0=runtimeClasspath",
257+
"",
258+
])
259+
current_content = "\n".join([
260+
"# Gradle lockfile",
261+
"com.example:lib:1.1.0=compileClasspath", # too new, should revert to 1.0.0
262+
"com.example:lib:2.1.0=runtimeClasspath", # too new, should revert to 2.0.0
263+
"",
264+
])
265+
metadata = {
266+
"com.example:lib:1.1.0": "2026-04-24T11:00:00Z", # too new
267+
"com.example:lib:2.1.0": "2026-04-24T11:00:00Z", # too new
268+
}
269+
270+
result, current_dir = self.run_validate_lockfiles(
271+
baseline={"module/gradle.lockfile": baseline_content},
272+
current={"module/gradle.lockfile": current_content},
273+
metadata=metadata,
274+
)
275+
276+
self.assertEqual(result.returncode, 0, result.stderr)
277+
final = (current_dir / lockfile).read_text(encoding="utf-8")
278+
self.assertIn("com.example:lib:1.0.0=compileClasspath", final)
279+
self.assertIn("com.example:lib:2.0.0=runtimeClasspath", final)
280+
self.assertNotIn("com.example:lib:1.1.0", final)
281+
self.assertNotIn("com.example:lib:2.1.0", final)
282+
# Exactly two entries — no duplication
283+
self.assertEqual(final.count("com.example:lib:"), 2)
284+
239285
def test_removes_brand_new_dependency_that_is_too_new(self) -> None:
240286
lockfile = "module/gradle.lockfile"
241287
baseline_content = "\n".join([

.github/workflows/update-gradle-dependencies.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ jobs:
116116
# What Does This Do
117117
118118
This PR updates the Gradle dependency locks for common and product modules.
119-
Changed dependencies were validated to be at least ${{ env.MIN_DEPENDENCY_AGE_HOURS }} hours old.
119+
Dependency resolution was performed through delayed proxies, and changed dependencies were validated to be at least ${{ env.MIN_DEPENDENCY_AGE_HOURS }} hours old.
120120
121121
# Motivation
122122
@@ -177,7 +177,7 @@ jobs:
177177
# What Does This Do
178178
179179
This PR updates the Gradle dependency locks for instrumentations and their tests.
180-
Changed dependencies were validated to be at least ${{ env.MIN_DEPENDENCY_AGE_HOURS }} hours old.
180+
Dependency resolution was performed through delayed proxies, and changed dependencies were validated to be at least ${{ env.MIN_DEPENDENCY_AGE_HOURS }} hours old.
181181
182182
# Motivation
183183

0 commit comments

Comments
 (0)