Skip to content

fix(gltest): support GenVM 0.3.0 runner-bundle asset rename#79

Closed
danieljrc888 wants to merge 5 commits into
mainfrom
fix/direct-runner-pin-genvm-version
Closed

fix(gltest): support GenVM 0.3.0 runner-bundle asset rename#79
danieljrc888 wants to merge 5 commits into
mainfrom
fix/direct-runner-pin-genvm-version

Conversation

@danieljrc888
Copy link
Copy Markdown
Contributor

@danieljrc888 danieljrc888 commented May 21, 2026

Problem

The direct test runner (gltest/direct/sdk_loader.py, also used by GLSim) resolved GitHub's bare genvm/releases/latest and unconditionally downloaded genvm-universal.tar.xz.

GenVM v0.3.0-rc0 renamed that asset to genvm-runners-all.tar.xz (and was published as a non-prerelease), so every consumer that resolved "latest" broke with HTTP Error 404: Not Found — gltest direct mode and GLSim alike.

The two archives are otherwise identical: same internal runners/{type}/{hash}.tar layout, and genvm-runners-all.tar.xz is a strict superset of the old runner hashes — a contract that pins a runner hash in its Depends header resolves the same runner from either bundle.

Changes

  • download_artifacts() now tries each known bundle asset name (genvm-runners-all.tar.xz, then genvm-universal.tar.xz) and uses whichever the release publishes. The local cache filename is normalized, so list_cached_versions() / extract_runner() are unaffected.
  • get_latest_version() queries the releases API and returns the newest non-prerelease release shipping a known bundle asset, instead of trusting GitHub's /releases/latest pointer.
  • resolve_version() (new) adds a GENVM_VERSION env var for deterministic pinning. Precedence: GENVM_VERSION > newest cached version > latest resolvable release.
  • CI now runs the direct-runner unit tests.

Tests

tests/gltest_direct/test_sdk_loader.py — 11 fast unit tests (no network): version-resolution precedence, pre-release / assetless-release skipping, the renamed-asset fallback, and cached-tarball reuse.

Verified locally: unit tests pass; test_deploy_storage_contract passes end-to-end with both GENVM_VERSION=v0.2.16 (old genvm-universal.tar.xz) and GENVM_VERSION=v0.3.0-rc0 (new genvm-runners-all.tar.xz).

Summary by CodeRabbit

  • New Features

    • Improved version resolution now uses GitHub Releases API with environment-based pinning and automatic fallback mechanisms.
    • Enhanced artifact downloading with automatic fallback to alternative asset sources when preferred options are unavailable.
  • Bug Fixes

    • Fixed version ordering to ensure deterministic, numeric-component sorting of cached versions.
  • Tests

    • Expanded test coverage for version resolution, caching, and artifact downloading behavior.

Review Change Stack

The direct runner resolved GitHub's bare /releases/latest and always
downloaded genvm-universal.tar.xz. GenVM v0.3.0-rc0 shipped as a
non-prerelease and dropped that asset, so consumers (gltest direct mode
and GLSim, which share this loader) broke with HTTP 404.

- get_latest_version() now queries the releases API and returns the
  newest release that is not a pre-release and still ships
  genvm-universal.tar.xz, instead of trusting GitHub's "latest" pointer.
- resolve_version() adds a GENVM_VERSION env var so callers can pin a
  deterministic version regardless of what GitHub marks as latest.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

📝 Walkthrough

Walkthrough

Replaces /latest HEAD discovery with GitHub Releases iteration that skips drafts/prereleases and requires expected runner bundle assets, adds ENV-pinning and fallback, deterministic cached-version sorting, asset-fallback downloads with atomic streaming, and corresponding unit tests plus a CI step.

Changes

SDK Version Resolution via GitHub API

Layer / File(s) Summary
Module constants and GitHub API endpoint
gltest/direct/sdk_loader.py
Adds GITHUB_API_RELEASES, RUNNER_BUNDLE_ASSETS, GENVM_VERSION_ENV, and FALLBACK_VERSION and related imports.
Version resolution and release discovery
gltest/direct/sdk_loader.py
Implements get_latest_version() that iterates GitHub Releases (skip drafts/prereleases; require accepted bundle assets) and resolve_version() that prefers GENVM_VERSION env var, then newest cached version, then the GitHub-derived version.
Cached ordering & streaming helper
gltest/direct/sdk_loader.py
list_cached_versions() sorts versions by numeric components; adds _download_to() to stream downloads into atomic temp files.
Artifact download with asset fallback
gltest/direct/sdk_loader.py
download_artifacts() tries each asset name in RUNNER_BUNDLE_ASSETS, treats 404 as “try next”, writes atomically, and raises FileNotFoundError if none match.
Integration with SDK path setup
gltest/direct/sdk_loader.py
setup_sdk_paths() now uses resolve_version() when version is not provided.
Unit tests for resolution, listing, and downloads
tests/gltest_direct/test_sdk_loader.py
Adds _FakeResponse HTTP mock and test suites for resolve_version(), get_latest_version(), list_cached_versions(), and download_artifacts() covering env precedence, cached fallback, asset filtering, numeric ordering, atomic downloads, and 404 fallbacks.
CI: run direct unit tests
.github/workflows/tests.yml
Adds a gltest step that runs tests/gltest_direct/test_sdk_loader.py as unit tests.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hop through tags and releases wide,
Env pins, cached ranks, and assets tried,
I stream each tar to a temp with care,
Fallbacks and tests keep surprises rare,
A rabbit-approved resolver, snug and spry.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and specifically addresses the main change: supporting GenVM 0.3.0's renamed runner-bundle asset, which is the core problem the PR solves.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/direct-runner-pin-genvm-version

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

The sdk_loader version-resolution tests live in tests/gltest_direct/,
which CI did not run. Add a step for the fast unit tests only; the
integration tests there download the GenVM SDK and stay out of CI.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 56e70b8b98

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

req = urllib.request.Request(
f"{GITHUB_RELEASES_URL}/latest",
method="HEAD",
f"{GITHUB_API_RELEASES}?per_page=100",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Paginate release lookup before falling back

get_latest_version() only fetches ?per_page=100 once and then falls back to FALLBACK_VERSION if no matching asset is found in that single page. Since GitHub release listing is paginated (max 100 per page), once there are more than 100 newer releases without genvm-universal.tar.xz, this code will incorrectly return the hardcoded fallback instead of the newest compatible release, leading the direct runner to use a stale pinned version (or fail if that fallback tag/asset is removed).

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@gltest/direct/sdk_loader.py`:
- Around line 93-95: The cached selection assumes cached[0] is newest but
list_cached_versions() returns lexicographically-sorted strings, so change the
selection logic to parse and compare versions semantically (e.g., use
packaging.version.parse or semantic_version.Version) and pick the max by
semantic comparison instead of cached[0]; update the code that currently does
"cached = list_cached_versions(); if cached: return cached[0]" to compute the
semantically-largest entry (and optionally ignore unparsable entries or fall
back to lexicographic if parsing fails).
- Around line 78-80: The current broad "except Exception:" block around the
release resolution silently discards all errors and always returns
FALLBACK_VERSION; change it to either (A) catch only expected exceptions (e.g.,
network/HTTP and parsing errors such as requests.RequestException, ValueError)
and return FALLBACK_VERSION for those, or (B) if keeping a general except, at
minimum log the full exception and traceback (using logging.exception or
similar) before returning FALLBACK_VERSION, and re-raise truly unexpected
errors; update the except block that currently contains "except Exception: pass"
to implement one of these approaches so errors are not silently swallowed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f7633d09-ca1f-462f-b430-92d3290b364a

📥 Commits

Reviewing files that changed from the base of the PR and between b23480b and 56e70b8.

📒 Files selected for processing (2)
  • gltest/direct/sdk_loader.py
  • tests/gltest_direct/test_sdk_loader.py

Comment thread gltest/direct/sdk_loader.py Outdated
Comment thread gltest/direct/sdk_loader.py
GenVM 0.3.0 renamed the runner bundle from genvm-universal.tar.xz to
genvm-runners-all.tar.xz. Both archives have the identical internal
runners/{type}/{hash}.tar layout and the new bundle is a strict
superset of the old runner hashes, so a contract that pins a runner
hash resolves the same runner from either.

download_artifacts() now tries each known bundle asset name and uses
whichever the release publishes, normalizing the cache filename so the
rest of the loader is unaffected. get_latest_version() accepts a
release shipping either asset. This lets the direct runner work across
the asset rename without freezing on a pre-0.3.0 release.
@danieljrc888 danieljrc888 changed the title fix(gltest): pin GenVM version for direct runner via GENVM_VERSION fix(gltest): support GenVM 0.3.0 runner-bundle asset rename May 21, 2026
Reduce comment noise: collapse multi-line docstrings to one line and
drop comments that just restate the code.
…ilure

Address review feedback on PR #79:
- list_cached_versions() sorted lexicographically, so cached[0] could pick
  an older release (e.g. v0.2.9 ahead of v0.2.16). Sort by numeric
  components instead.
- get_latest_version() swallowed every exception silently; emit a stderr
  warning before falling back so resolution failures are visible.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
gltest/direct/sdk_loader.py (1)

90-105: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle prereleases explicitly in cached version sorting.

re.findall() makes v0.3.0-rc0 sort after v0.3.0, so once both tarballs are cached resolve_version() will default back to the RC on the unpinned path.

Suggested fix
 def _version_sort_key(version: str) -> tuple:
-    """Numeric-component sort key so v0.2.16 ranks above v0.2.9."""
-    return tuple(int(n) for n in re.findall(r"\d+", version))
+    """Sort stable releases above prereleases for the same base version."""
+    core, sep, suffix = version.partition("-")
+    core_parts = tuple(int(n) for n in re.findall(r"\d+", core))
+    suffix_parts = tuple(int(n) for n in re.findall(r"\d+", suffix))
+    return core_parts, 1 if not sep else 0, suffix_parts
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gltest/direct/sdk_loader.py` around lines 90 - 105, The current
_version_sort_key uses re.findall to extract digits which treats prereleases
like "v0.3.0-rc0" the same as "v0.3.0", causing RCs to be ordered after stables;
update _version_sort_key to explicitly parse the main numeric version and an
optional prerelease suffix (e.g., split with a regex capturing the numeric part
and an optional "-..." prerelease), produce a sort key of (numeric_tuple,
is_prerelease_flag, prerelease_tuple) where is_prerelease_flag is 0 for stable
and 1 for prerelease so stables rank above prereleases when reverse=True, and
parse prerelease components into comparable ints/strings (fallback to strings
for non-numeric parts); keep list_cached_versions unchanged except it will now
rely on the improved _version_sort_key to sort correctly.
🧹 Nitpick comments (1)
tests/gltest_direct/test_sdk_loader.py (1)

50-97: ⚡ Quick win

Add an explicit draft-release test for get_latest_version().

The suite verifies prerelease and asset filtering, but not draft filtering. Since draft skipping is part of the release-selection contract, it’s worth locking with one test to prevent regressions.

Proposed test addition
 class TestGetLatestVersion:
     """get_latest_version() skips pre-releases and assetless releases."""
@@
     def test_skips_prereleases(self, monkeypatch):
         releases = [
@@
         assert sdk_loader.get_latest_version() == "v0.2.16"
+
+    def test_skips_draft_releases(self, monkeypatch):
+        releases = [
+            {
+                "tag_name": "v0.3.1",
+                "draft": True,
+                "prerelease": False,
+                "assets": [{"name": "genvm-runners-all.tar.xz"}],
+            },
+            {
+                "tag_name": "v0.2.16",
+                "draft": False,
+                "prerelease": False,
+                "assets": [{"name": "genvm-universal.tar.xz"}],
+            },
+        ]
+        monkeypatch.setattr(
+            "urllib.request.urlopen", lambda *a, **k: _FakeResponse(releases)
+        )
+        assert sdk_loader.get_latest_version() == "v0.2.16"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/gltest_direct/test_sdk_loader.py` around lines 50 - 97, Add a new test
method on TestGetLatestVersion that verifies get_latest_version() skips draft
releases: create a releases list where the newest tag (e.g., "v0.4.0") has
"draft": True and an appropriate asset (e.g., "genvm-universal.tar.xz") and an
older non-draft release (e.g., "v0.2.16") is present; monkeypatch
urllib.request.urlopen to return _FakeResponse(releases) and assert
sdk_loader.get_latest_version() returns the non-draft tag ("v0.2.16"). Ensure
the new test follows the existing pattern (use monkeypatch, _FakeResponse) and
name it something like test_skips_draft_releases to match the suite.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@gltest/direct/sdk_loader.py`:
- Around line 90-105: The current _version_sort_key uses re.findall to extract
digits which treats prereleases like "v0.3.0-rc0" the same as "v0.3.0", causing
RCs to be ordered after stables; update _version_sort_key to explicitly parse
the main numeric version and an optional prerelease suffix (e.g., split with a
regex capturing the numeric part and an optional "-..." prerelease), produce a
sort key of (numeric_tuple, is_prerelease_flag, prerelease_tuple) where
is_prerelease_flag is 0 for stable and 1 for prerelease so stables rank above
prereleases when reverse=True, and parse prerelease components into comparable
ints/strings (fallback to strings for non-numeric parts); keep
list_cached_versions unchanged except it will now rely on the improved
_version_sort_key to sort correctly.

---

Nitpick comments:
In `@tests/gltest_direct/test_sdk_loader.py`:
- Around line 50-97: Add a new test method on TestGetLatestVersion that verifies
get_latest_version() skips draft releases: create a releases list where the
newest tag (e.g., "v0.4.0") has "draft": True and an appropriate asset (e.g.,
"genvm-universal.tar.xz") and an older non-draft release (e.g., "v0.2.16") is
present; monkeypatch urllib.request.urlopen to return _FakeResponse(releases)
and assert sdk_loader.get_latest_version() returns the non-draft tag
("v0.2.16"). Ensure the new test follows the existing pattern (use monkeypatch,
_FakeResponse) and name it something like test_skips_draft_releases to match the
suite.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1b54bc3c-b261-409b-95f7-78847b10d54d

📥 Commits

Reviewing files that changed from the base of the PR and between 56e70b8 and 1b97051.

📒 Files selected for processing (3)
  • .github/workflows/tests.yml
  • gltest/direct/sdk_loader.py
  • tests/gltest_direct/test_sdk_loader.py

@MuncleUscles MuncleUscles deleted the branch main May 22, 2026 12:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants