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
11 changes: 10 additions & 1 deletion .github/scripts/dependency_age.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ def validate_lockfiles(args: argparse.Namespace) -> int:
elif published_at > cutoff:
hours_remaining = int((published_at - cutoff).total_seconds() / 3600) + 1
group_id, artifact_id, version = gav.split(":", 2)
baseline_version = next((c[len(f"{group_id}:{artifact_id}:"):] for c in baseline_coords if c.startswith(f"{group_id}:{artifact_id}:")), None)
baseline_version = highest_baseline_version(baseline_coords, group_id, artifact_id)
eligible = find_eligible_version(
group_id=group_id, artifact_id=artifact_id,
too_new_version=version, baseline_version=baseline_version,
Expand Down Expand Up @@ -678,6 +678,15 @@ def fetch_available_versions(group_id: str, artifact_id: str, repo_urls: list[st
return []


# select the highest baseline version of group:artifact present in a lockfile.
def highest_baseline_version(baseline_coords: set[str], group_id: str, artifact_id: str) -> str | None:
prefix = f"{group_id}:{artifact_id}:"
versions = [coord[len(prefix):] for coord in baseline_coords if coord.startswith(prefix)]
if not versions:
return None
return max(versions, key=_version_sort_key)


# for a too-new coordinate, walk backward through available versions to find the newest one
# that meets the age cutoff and is newer than the baseline version
def find_eligible_version(
Expand Down
23 changes: 23 additions & 0 deletions .github/scripts/tests/test_dependency_age.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import argparse
import contextlib
import io
import json
import os
import re
Expand All @@ -6,7 +9,9 @@
import sys
import tempfile
import unittest
from datetime import datetime, timezone
from pathlib import Path
from unittest import mock

REPO_ROOT = Path(__file__).resolve().parents[3]
SCRIPT = REPO_ROOT / ".github/scripts/dependency_age.py"
Expand Down Expand Up @@ -469,6 +474,24 @@ def test_summary_groups_outcomes_into_sections(self) -> None:
self.assertIn("com.example:update-lib:4.0.0", updated_block)
self.assertIn("updated to `3.9.0`", updated_block)

def test_highest_baseline_version_picks_newest_of_coexisting_pins(self) -> None:
# A single lockfile can pin the same artifact at several versions (one per Gradle
# configuration). The baseline should be the newest so that only versions higher than
# the highest existing version are considered upgrades.
baseline_coords = {
"ch.qos.logback:logback-core:1.1.11",
"ch.qos.logback:logback-core:1.2.13",
"ch.qos.logback:logback-core:1.5.34",
"com.example:unrelated:9.9.9",
}
self.assertEqual(
dependency_age.highest_baseline_version(baseline_coords, "ch.qos.logback", "logback-core"),
"1.5.34",
)
self.assertIsNone(
dependency_age.highest_baseline_version(baseline_coords, "ch.qos.logback", "logback-classic")
)

def test_summary_omits_empty_sections(self) -> None:
# only too-new violations -> only the "reverted" section should appear
summary = dependency_age.build_validation_summary(
Expand Down