Skip to content

Handle Go module pseudo-versions in dependency constraint checking - #5855

Merged
mstoykov merged 2 commits into
masterfrom
k6-pseudo-versions
Apr 23, 2026
Merged

mstoykov merged 2 commits into
masterfrom
k6-pseudo-versions

Conversation

@mstoykov

Copy link
Copy Markdown
Contributor

What?

When a script manifest specifies a dependency as a Go module pseudo-version (v0.0.0+somesha or v0.0.0-20241015123456-abcdef123456), k6 was always triggering binary provisioning even when the running binary already satisfied the constraint. This PR fixes two root causes and ensures the version k6 compares against constraints matches what it displays in version output.

Why?

Build metadata is invisible to semver comparison. The semver spec ignores build metadata, so v0.0.0+abc and v0.0.0+xyz are treated as equal โ€” a mismatched SHA would silently pass, and a matching SHA would also pass, but for the wrong reason.

Pre-release pseudo-versions always fail range checks. v0.0.0-timestamp-sha is less than v0.0.0 in semver ordering, so any range constraint fails even when the constraint names the exact same commit.

The compared version didn't match the displayed version. checkBuiltinDependencies was comparing against the static build.Version constant, while the version command was printing the actual build-info version, which may be a pseudo-version when k6 is installed via go install go.k6.io/k6@<sha>.

Checklist

  • I have performed a self-review of my code.
  • I have commented on my code, particularly in hard-to-understand areas.
  • I have added tests for my changes.
  • I have run linter and tests locally (make check) and all pass.

Checklist: Documentation (only for k6 maintainers and if relevant)

Please do not merge this PR until the following items are filled out.

  • I have added the correct milestone and labels to the PR.
  • I have updated the release notes: link
  • I have updated or added an issue to the k6-documentation: grafana/k6-docs#NUMBER if applicable
  • I have updated or added an issue to the TypeScript definitions: grafana/k6-DefinitelyTyped#NUMBER if applicable

Related PR(s)/Issue(s)

๐Ÿค– Generated with Claude Code

@mstoykov
mstoykov requested a review from a team as a code owner April 17, 2026 13:23
@mstoykov
mstoykov requested review from codebien and oleiade and removed request for a team April 17, 2026 13:23
@mstoykov
mstoykov temporarily deployed to azure-trusted-signing April 17, 2026 13:29 — with GitHub Actions Inactive
@mstoykov
mstoykov temporarily deployed to azure-trusted-signing April 17, 2026 13:31 — with GitHub Actions Inactive
@codebien
codebien requested a review from Copilot April 17, 2026 14:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes dependency constraint checking for Go module pseudo-versions so k6 avoids unnecessary binary provisioning and compares dependency constraints against the same effective version string that k6 version displays.

Changes:

  • Centralize runtime k6 version resolution via Go build info (including pseudo-versions) and reuse it for both version output and dependency checks.
  • Replace the boolean โ€œcustom build requiredโ€ check with an error-returning dependency validator that can handle pseudo-version SHA matching.
  • Add/expand tests covering pseudo-version and build-metadata SHA matching behavior.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
internal/cmd/version.go Adds build-info-based runtime version reader and aligns versionDetails() output with it.
internal/cmd/test_load.go Switches auto-extension resolution to validate against runtimeK6Version() via the new dependency checker.
internal/cmd/deps.go Uses the new dependency checker (and runtime version) to determine customBuildRequired.
internal/cmd/launcher.go Implements SHA-aware pseudo-version satisfaction and converts dependency checking to return descriptive errors.
internal/cmd/launcher_test.go Updates/expands unit tests for the new dependency checking and pseudo-version SHA logic.

๐Ÿ’ก Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/cmd/launcher.go
Comment on lines +278 to +291
func checkConstraintBySHA(vSHA string, constraint *semver.Constraints) (bool, bool) {
cs := constraint.String()
if strings.ContainsAny(cs, " ,") {
return false, false
}
cv, err := semver.NewVersion(constraintVersionStr(cs))
if err != nil {
return false, false
}
cSHA, ok := versionSHA(cv)
if !ok {
return false, false
}
return strings.HasPrefix(vSHA, cSHA) || strings.HasPrefix(cSHA, vSHA), true

Copilot AI Apr 17, 2026

Copy link

Choose a reason for hiding this comment

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

checkConstraintBySHA() currently treats any single-token constraint (e.g. ">=v0.0.0+", "!=v...", "<v...") as eligible for SHA-based matching. This can produce incorrect results by marking range constraints as satisfied solely because the embedded SHAs match, even when the semver comparison should fail (e.g. ">v0.0.0+sha" vs "v0.0.0+sha"). Restrict SHA-based matching to exact-version constraints only (operator "=" or no operator), and fall back to normal semver checking for all other operators.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

0c2072d makes this even more tight. I opted to disallow more stuff that do not seem reasonable than to allow more.

Comment thread internal/cmd/version.go
Comment on lines +31 to +35
// readK6VersionInfo reads Go build info once and returns everything needed by
// both the version display and the dependency constraint check. This is the
// single source of truth so that what k6 prints and what it compares against
// are always the same string.
func readK6VersionInfo() k6VersionInfo {

Copilot AI Apr 17, 2026

Copy link

Choose a reason for hiding this comment

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

readK6VersionInfo()'s comment says it "reads Go build info once", but the function calls debug.ReadBuildInfo() each time it's invoked (e.g., via both versionDetails() and runtimeK6Version()). Either cache the result (e.g., with sync.Once) or adjust the comment so it doesn't imply one-time reading/caching.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I kind of think this is good enough . And definitely won't be adding sync.Once for thi.s This is usually called once eitehr way.

@mstoykov
mstoykov temporarily deployed to azure-trusted-signing April 17, 2026 16:47 — with GitHub Actions Inactive
@mstoykov
mstoykov temporarily deployed to azure-trusted-signing April 17, 2026 16:49 — with GitHub Actions Inactive
joanlopez
joanlopez previously approved these changes Apr 22, 2026
mstoykov and others added 2 commits April 22, 2026 18:14
When a script manifest specifies a dependency as a pseudo-version
(v0.0.0+somesha or v0.0.0-20241015123456-abcdef123456), the previous
code always triggered binary provisioning because it could not match
pseudo-version strings against constraints. Two root causes were fixed.

First, the dependency checker now compares commit SHAs directly when
both the built-in version and the constraint embed one. This handles
the case where the semver library silently ignores build metadata
(v0.0.0+abc == v0.0.0+xyz under the semver spec), and the case where
a Go pseudo-version pre-release (v0.0.0-timestamp-sha) fails a normal
semver range check even when the constraint names the exact same commit.
SHA comparison is attempted as a fallback in both situations. Short SHAs
are matched as prefixes so that 7-char and 12-char forms interoperate.

Second, k6's own version is now read from Go build info at runtime via
runtimeK6Version() rather than the static build.Version constant. When
k6 is installed from a specific commit (go install go.k6.io/k6@sha), the
binary carries a pseudo-version in its build info; previously, the static
constant was always compared against constraints instead, masking the
true version. runtimeK6Version() is guaranteed to return the same string
that the version command prints, so the compared version and the
displayed version are always in sync.

This runtime version lookup is currently only applied to k6 itself.
Extension versions are already sourced from build info by the extension
registry (ext.extractModuleInfo), but a similar approach could be applied
to verify their versions more robustly in the future.

Additional changes:
- isCustomBuildRequired renamed to checkBuiltinDependencies and changed
  to return an error describing the first unsatisfied dependency instead
  of a bool, making failures easier to diagnose via debug logging
- readK6VersionInfo() consolidates the build-info reading that was
  previously duplicated between the version display path and the new
  dependency checking path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two correctness fixes to checkConstraintBySHA:

First, SHA-based matching is now restricted to exact-version constraints
(operator "=" or no operator). Previously, any single-token constraint
was eligible, so ">v0.0.0+sha" or "!=v0.0.0+sha" with a matching SHA
would be incorrectly reported as satisfied. The operator is extracted as
the prefix before the first "v" in the constraint string and checked
before any SHA comparison is attempted.

Second, compound constraints (containing spaces or commas) now return a
definitive failure (false, true) instead of falling through to semver
(false, false). For pseudo-versions, semver's handling of compound
constraints is unreliable: build metadata is silently stripped, and
pre-release versions are excluded from non-pre-release constraints
regardless of the base version. Refusing early avoids these pitfalls
and produces consistent, predictable behaviour.

Also expands the test suite with cases that document:
- non-exact operators (>, !=) with matching SHAs
- pseudo-versions against non-pre-release range constraints, contrasted
  with the >=vX.Y.Z-0 form that correctly admits pre-release versions
- compound constraints combining an exact SHA with an upper limit
- the asymmetry between go pseudo-versions (pre-release form) and
  build-metadata pseudo-versions (+sha) under range constraints

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@mstoykov
mstoykov force-pushed the k6-pseudo-versions branch from 0c2072d to afdff06 Compare April 22, 2026 15:17
@mstoykov
mstoykov requested a review from joanlopez April 22, 2026 15:20
@mstoykov
mstoykov temporarily deployed to azure-trusted-signing April 22, 2026 15:23 — with GitHub Actions Inactive
@mstoykov
mstoykov temporarily deployed to azure-trusted-signing April 22, 2026 15:25 — with GitHub Actions Inactive
@mstoykov mstoykov added this to the v2.0.0-rc1 milestone Apr 23, 2026
@mstoykov
mstoykov merged commit 5e90494 into master Apr 23, 2026
58 of 59 checks passed
@mstoykov
mstoykov deleted the k6-pseudo-versions branch April 23, 2026 15:01

This branch was previously deployed

1 inactive deployment
azure-trusted-signing โ€” afdff062 Deployed Apr 22, 2026 by mstoykov via sign-packages #9093
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.

4 participants