Skip to content

test(systest): port taskenv_test.sh to Go - #1614

Merged
ALRubinger merged 1 commit into
mainfrom
test/port-taskenv-test-go-1603
Jun 24, 2026
Merged

ALRubinger merged 1 commit into
mainfrom
test/port-taskenv-test-go-1603

Conversation

@ALRubinger

@ALRubinger ALRubinger commented Jun 24, 2026 •

Copy link
Copy Markdown
Owner

Summary

Ports the env/var wiring regression contract from the POSIX-shell test/system/lib/taskenv_test.sh into a Go test (test/system/lib/taskenv_test.go) in the systestlib module, per #1603. The shell test is left in place (its deletion belongs to #1604); Taskfile.yml targets are unchanged; GO_TEST_PACKAGES already lists ./test/system/lib/....

The port keeps the shell test's two-halves split:

  • STRUCTURAL checks (B, C, E1) parse the real Taskfile.yml with gopkg.in/yaml.v3 and assert on the parsed task/var/cmd tree, so they survive reformatting (comment edits, indentation changes) the shell's awk/indent matching could not. Where an invariant is inherently about raw template text (-o build/...{{.HOST_EXE}} outputs, forbidden date +%s/$$/mktemp//tmp tokens), the assertion is scoped to the relevant cmd/var scalar node from the YAML model, not a flat file grep, so comment text can't satisfy or break it.
  • BEHAVIORAL checks (A, D, E2) drive the real task binary via exec.Command against tiny Taskfiles written to t.TempDir(), with a Go-controlled cmd.Env/PATH. No ln -s symlinks (repo-forbidden); no external sh/awk/sed/mktemp/date.

Contracts pinned (unchanged from the shell test)

Behavioral cases: kept behavioral vs converted to structural

Per the brief's Windows-clean fallback rule, here is what each behavioral case did:

  • (A) env scope — KEPT BEHAVIORAL, Windows-clean. The probe is echo $AILERON_BIN, whose $ expansion runs in go-task's built-in portable shell (mvdan.cc/sh), so no external sh/probe.sh is needed. Positive (task-scoped env surfaces) and negative (command-scoped env dropped) both run with a controlled cmd.Env.
  • (D) vars resolve with date/mktemp off PATH — KEPT BEHAVIORAL, Windows-clean. The sandbox PATH holds only a copied task binary (no symlinks); the pure-template SESSION/WORKSPACE vars resolve entirely inside go-task, so the sim needs neither date, mktemp, nor an external sh. This is a more faithful Windows simulation than the shell test, which had to symlink sh only because it ran sh probe.sh. A sim-validity subtest first asserts the sandbox truly lacks date/mktemp.
  • (E2) HOST_EXE resolution — KEPT BEHAVIORAL, hermetic. Resolves HOST_EXE: { sh: go env GOEXE } under forced GOOS=windows (expect build/aileron.exe) and forced native GOOS (expect build/aileron<native GOEXE>) via cmd.Env, so it is deterministic regardless of host OS.

No case had to be converted to structural; all three stayed behavioral and Windows-clean.

No-silent-skip

If task or go is not resolvable on PATH the test calls t.Fatal (loud failure), matching the shell test's exit 1. Never t.Skip.

Test plan

  • go test ./test/system/lib/... green on macOS (go 1.26.4, task 3.49.1), and via the full task test:go workspace run.
  • go vet ./... clean; gofmt -l clean.
  • Mutation-checked teeth: temporarily breaking the Taskfile (drop {{.HOST_EXE}} from launch AILERON_BIN; inject date +%s into a SESSION; delete a SESSION var) each produces the expected red, then the Taskfile restores byte-for-byte.
  • CodeRabbit review: clean (0 findings) after addressing two major findings (presence/shape assertions for SESSION/WORKSPACE; dropped the unnecessary sh copy from the sandbox).
  • Taskfile.yml and taskenv_test.sh are byte-unchanged (git diff empty for both).

Coverage note

This is a test-only port: taskenv_test.go is itself the unit under test (it asserts against the real Taskfile.yml and the real task/go binaries). No production (.go non-test) code is added, so there is no separate library surface to cover beyond the _test.go exercised directly by go test.

Closes #1603

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Tests
    • Added a new system regression test suite to validate task environment behavior, template handling, and platform-specific executable naming.
    • Expanded coverage to ensure launch targets and build outputs use the expected paths and suffixes across environments.
  • Chores
    • Updated system test dependencies to support the new YAML-based validation and test helpers.

Port the env/var wiring regression contract from the POSIX-shell
test/system/lib/taskenv_test.sh into a Go test (taskenv_test.go) in the
systestlib module. Keeps the shell test's two-halves split:

- STRUCTURAL checks parse the real Taskfile.yml with gopkg.in/yaml.v3 and
  assert on the parsed task/var/cmd tree, so they survive reformatting
  (comment edits, indentation) that the shell's awk/indent matching could
  not. Raw-template invariants (-o build/... outputs, forbidden
  date/$$/mktemp//tmp tokens) are scoped to the relevant cmd/var scalar
  node, not a flat file grep.
- BEHAVIORAL checks drive the real `task` binary against tiny Taskfiles in
  t.TempDir() with a Go-controlled cmd.Env/PATH. No symlinks (repo-forbidden);
  the date/mktemp-off-PATH sandbox is a copied `task` binary only.

Adds gopkg.in/yaml.v3 to the test/system/lib module (its first external
dependency). Taskfile.yml and taskenv_test.sh are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 24, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a Go test port for the Taskfile env and HOST_EXE contract checks, plus Go module dependencies for YAML parsing. The new suite combines structural checks against Taskfile.yml with runtime probes that execute generated Taskfiles through task.

Changes

Taskfile env contract port

Layer / File(s) Summary
Harness setup and YAML helpers
test/system/lib/go.mod, test/system/lib/taskenv_test.go
Adds the YAML dependency and helper functions for loading Taskfile.yml, traversing YAML nodes, and reading scalar template and shell bodies.
Task env and portable vars
test/system/lib/taskenv_test.go
Checks that launch tasks place required env keys at task scope and that SESSION and WORKSPACE definitions avoid the listed non-portable tokens.
HOST_EXE structural checks
test/system/lib/taskenv_test.go
Validates HOST_EXE sourcing and task-local use, plus build output suffixes, mcp:setup, and launch-task AILERON_BIN wiring.
Runtime task probes
test/system/lib/taskenv_test.go
Runs generated Taskfiles through task to verify env scoping, PATH-restricted SESSION and WORKSPACE resolution, and HOST_EXE resolution under forced GOOS.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • Port taskenv_test.sh to Go — This PR adds the Go replacement test suite with the same Taskfile env, template, and HOST_EXE checks.
  • System lib test wiring for the Go port — This PR adds the new Go test package dependency and runtime probes that the wiring change is meant to execute.

Possibly related PRs

  • ALRubinger/aileron#1484: Adds Go-based Taskfile system-test coverage for env scoping and template values, overlapping this PR’s contract checks.
  • ALRubinger/aileron#1548: Covers the same SESSION/WORKSPACE portability contract that this PR revalidates in Go.

Poem

🐰 I hopped through Taskfile lanes so neat,
YAML and task now share the beat.
HOST_EXE wore its suffix bright,
SESSION stayed portable tonight.
The burrow hums with tests complete.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: porting taskenv_test.sh to Go.
Linked Issues check ✅ Passed The changes match the issue: a shell-free Go port with YAML parsing, Taskfile invariants, and real task-binary behavioral checks.
Out of Scope Changes check ✅ Passed The only changes are the new Go test and its YAML dependency, both directly related to the port.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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 test/port-taskenv-test-go-1603

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain modules listed in go.work or their selected dependencies"


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
test/system/lib/taskenv_test.go (2)

722-733: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Nit: close parameter shadows the builtin.

extractBracketed(s, open, close string) shadows the predeclared close. Harmless here, but closeTok/end reads cleaner and avoids linter noise.

🤖 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 `@test/system/lib/taskenv_test.go` around lines 722 - 733, The helper
extractBracketed currently uses a parameter named close, which shadows the
predeclared builtin and can trigger lint noise. Rename that parameter to a
clearer name like closeTok or end in extractBracketed, and update the local
references within the function accordingly so the behavior stays unchanged.

609-625: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Sandbox task copy is unused and mis-named on Windows.

runTask always resolves the real task via taskBin (Line 512) and runs it by absolute path; the sandbox is only injected as PATH env. So the copy written here is never executed. Separately, on Windows exec.LookPath("task") returns a .exe path but the copy is written as task (no extension), and the comment at Lines 638-640 claims "two binaries we copy" though only one is copied. Either drop the copy (the sandbox just needs to be an otherwise-empty dir to exclude date/mktemp) or document why it must exist.

🤖 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 `@test/system/lib/taskenv_test.go` around lines 609 - 625, The sandbox helper
in sandboxPATH is creating a copied task binary that runTask never uses, since
taskBin runs the real executable by absolute path and only the temp dir is added
to PATH. Simplify sandboxPATH to just return an empty temp directory for PATH
isolation, and update any nearby documentation/comments in the runTask test
setup to stop claiming there are “two binaries” copied. If the copy must remain
for a specific reason, ensure the name matches platform expectations, especially
on Windows where exec.LookPath("task") may resolve to an .exe.
🤖 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 `@test/system/lib/taskenv_test.go`:
- Around line 584-594: The filterEnv helper currently removes only exact-key
matches, so on Windows PATH entries like Path= can survive and interfere with
taskEnvPATH. Update filterEnv to use a case-insensitive key comparison when
matching against drop, and keep the behavior localized to this helper so callers
like the PATH setup in the task environment tests get a fully filtered
environment.

---

Nitpick comments:
In `@test/system/lib/taskenv_test.go`:
- Around line 722-733: The helper extractBracketed currently uses a parameter
named close, which shadows the predeclared builtin and can trigger lint noise.
Rename that parameter to a clearer name like closeTok or end in
extractBracketed, and update the local references within the function
accordingly so the behavior stays unchanged.
- Around line 609-625: The sandbox helper in sandboxPATH is creating a copied
task binary that runTask never uses, since taskBin runs the real executable by
absolute path and only the temp dir is added to PATH. Simplify sandboxPATH to
just return an empty temp directory for PATH isolation, and update any nearby
documentation/comments in the runTask test setup to stop claiming there are “two
binaries” copied. If the copy must remain for a specific reason, ensure the name
matches platform expectations, especially on Windows where exec.LookPath("task")
may resolve to an .exe.
🪄 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 Plus

Run ID: 6f87f2f2-b4aa-4d07-a703-935a503497a7

📥 Commits

Reviewing files that changed from the base of the PR and between 518faf9 and ca95774.

⛔ Files ignored due to path filters (1)
  • test/system/lib/go.sum is excluded by !**/*.sum
📒 Files selected for processing (2)
  • test/system/lib/go.mod
  • test/system/lib/taskenv_test.go

Comment on lines +584 to +594
func filterEnv(env []string, drop string) []string {
out := make([]string, 0, len(env))
prefix := drop + "="
for _, kv := range env {
if strings.HasPrefix(kv, prefix) {
continue
}
out = append(out, kv)
}
return out
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

sed -n '560,700p' test/system/lib/taskenv_test.go

Repository: ALRubinger/aileron

Length of output: 5955


🏁 Script executed:

sed -n '560,700p' test/system/lib/taskenv_test.go

Repository: ALRubinger/aileron

Length of output: 5955


🌐 Web query:

Windows environment variables case-insensitive duplicate PATH vs Path precedence CreateProcess environment block

💡 Result:

Windows environment variables are case-insensitive, meaning that PATH, Path, and path are treated as the same variable [1][2][3]. When a process is created via the Windows API, such as CreateProcess, the environment block passed to the new process is expected to be a null-terminated block of null-terminated strings [4]. While the system documentation specifies that this block should be sorted alphabetically by name (case-insensitive, Unicode order) [4], the CreateProcess function itself does not automatically sanitize or de-duplicate the environment block provided by the caller [5][6][7]. Precedence and Duplicate Handling: 1. Handling of Duplicates: If an environment block containing multiple entries for the same variable (e.g., both PATH and Path) is passed to a new process, the behavior is largely determined by how the reading process accesses the variables [7]. Many Win32 APIs and standard library functions (like GetEnvironmentVariable and getenv) that scan the environment block will typically honor the first instance encountered during a linear search [6][8][7][9]. 2. Resolution: Because the system does not enforce de-duplication during the CreateProcess call, a process may receive an environment block containing "illegal" duplicates [8]. This can cause failures in applications that parse the environment block expecting unique keys [6][8]. 3. PATH specifically: The PATH environment variable is a special case in how it is used by the system to locate executables, but it adheres to the same case-insensitive rules as all other environment variables [1][2]. When resolving a command, Windows searches directories listed in the PATH in the order they appear, but the variable itself is retrieved by the system using case-insensitive lookup [1][10]. Best Practice: It is the responsibility of the calling process (the parent) to ensure the environment block is correctly formed—specifically that it is sorted and free of duplicate keys—before invoking CreateProcess [5][6][4][9]. If you are managing environment variables in your own code, you should normalize keys to a consistent casing or de-duplicate them before passing the block to child processes to avoid compatibility issues with utilities that scan the block directly [6][8][9].

Citations:


🌐 Web query:

Windows environment variables case-insensitive duplicate PATH vs Path precedence CreateProcess environment block

💡 Result:

In Windows, environment variable names are case-insensitive [1][2]. When an application accesses an environment variable by name—for example, via the GetEnvironmentVariable API—the system typically searches the process's environment block and returns the value associated with the first matching name found, regardless of case [3]. Regarding the behavior of duplicate variable names (e.g., having both "PATH" and "Path" in the environment block) and their precedence: 1. Environment Block Structure: The environment block is a null-terminated block of null-terminated strings in the format name=value [4][5]. Windows does not natively prevent the inclusion of duplicate names that differ only by case within this block [3]. 2. Precedence and Retrieval: If an environment block contains multiple entries for the same variable name (differing only by case), the Win32 GetEnvironmentVariable function generally returns the value of the first occurrence it encounters in the block [3]. 3. PATH Special Handling: While environment variables are generally treated as case-insensitive, the PATH variable is a known exception in how the system constructs the environment for a new process [6]. When the system merges User and System environment variables, it specifically appends the User PATH to the System PATH [6][7]. This is a special behavior of the environment variable construction process, not a generic rule for all variables [6][7]. When spawning a process via CreateProcess, the parent process can provide a custom environment block [4][8]. If this block contains duplicate keys, the child process will inherit them exactly as provided [3]. Because some applications and runtime environments (such as older.NET Framework versions or specific build tools) may not handle duplicate case-insensitive keys correctly, it is a best practice for parent processes to sanitize the environment block by removing duplicates before passing it to CreateProcess [3][9].

Citations:


Make filterEnv case-insensitive for PATH on Windows

filterEnv(os.Environ(), "PATH") leaves Path=... intact on Windows, so taskEnvPATH can end up with both Path and PATH. Since Windows treats env keys case-insensitively, the inherited PATH may still take precedence and the sandboxed PATH test can remain on the host PATH. Use a case-insensitive key match when filtering env entries.

🤖 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 `@test/system/lib/taskenv_test.go` around lines 584 - 594, The filterEnv helper
currently removes only exact-key matches, so on Windows PATH entries like Path=
can survive and interfere with taskEnvPATH. Update filterEnv to use a
case-insensitive key comparison when matching against drop, and keep the
behavior localized to this helper so callers like the PATH setup in the task
environment tests get a fully filtered environment.

@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.35%. Comparing base (a024591) to head (ca95774).
⚠️ Report is 2 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1614      +/-   ##
==========================================
- Coverage   82.37%   82.35%   -0.03%     
==========================================
  Files         317      325       +8     
  Lines       31843    32399     +556     
==========================================
+ Hits        26232    26683     +451     
- Misses       4033     4100      +67     
- Partials     1578     1616      +38     
Flag Coverage Δ
integration 7.49% <ø> (-0.03%) ⬇️
unit 80.06% <ø> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ALRubinger
ALRubinger merged commit 513602f into main Jun 24, 2026
21 checks passed
@ALRubinger
ALRubinger deleted the test/port-taskenv-test-go-1603 branch June 24, 2026 19:57
ALRubinger added a commit that referenced this pull request Jun 24, 2026
The committed root go.work lists ./test/system/lib, and its go.mod now
requires gopkg.in/yaml.v3 (#1614). Both Go jobs that exercise the full
GO_TEST_PACKAGES set (lint's `task vet:go`, and Unit Tests' `task
test:go:ci:*`) now compile and run that module, but the setup-go module
cache key omitted test/system/lib, leaving its yaml.v3 dependency outside
the cache key. Append test/system/lib/go.sum to both cache-dependency-path
blocks so the cache key stays honest for the dependency actually pulled.

Operator-answered cw-review-residual decision for #1603 (issue #1610).

Closes #1610

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

Port taskenv_test.sh to Go (Taskfile env-scope, portable SESSION/WORKSPACE, HOST_EXE invariants)

1 participant