Add taskfile for building and running tests#192
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces a Taskfile.yml to provide a cross-platform task runner interface for building artifacts and running tests, and refactors the integration test runners to support per-test execution with forwarded flags (including a Windows PowerShell runner).
Changes:
- Add
Taskfile.ymlwith build/test/lint/proto/clean tasks, including integration test support viagotestsum. - Update
integration/test.shto discover tests dynamically and forward selected flags. - Add
integration/test.ps1for running integration tests on Windows; ignore Task’s.task/state directory.
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
Taskfile.yml |
Adds task-based build/test/lint/proto workflows intended to replace/augment Make targets |
integration/test.sh |
Switches integration runner to dynamic test discovery + flag parsing, runs tests one-by-one |
integration/test.ps1 |
New Windows integration runner with per-test process execution and flag parsing |
.gitignore |
Ignores .task/ directory created by Task |
Comments suppressed due to low confidence (4)
integration/test.sh:33
- If test discovery fails or produces zero tests, the script currently exits successfully (loop runs zero times) and also may terminate early because
grepreturns exit code 1 when there are no matches. It would be safer to handle the-test.listpipeline explicitly (|| true) and then fail with a clear message whentestsis empty.
# Discover tests from the binary (respects build tags).
readarray -t tests < <(../_output/integration.test -test.list '.*' 2>/dev/null | grep '^Test')
integration/test.sh:60
- The
-runfiltering uses Bash regex matching ([[ ... =~ ... ]]), which doesn’t match Go’s-run/-test.listregexp semantics (RE2). To keep behavior consistent withgo test -run, consider passing the pattern tointegration.test -test.list <pattern>and using that output as the filtered test list instead of re-implementing matching in Bash.
if [ -n "$run_pattern" ]; then
filtered=()
for test in "${tests[@]}"; do
[[ "$test" =~ $run_pattern ]] && filtered+=("$test")
done
tests=("${filtered[@]}")
integration/test.ps1:29
- When the test binary can’t be executed or
-test.listreturns nothing,$testsbecomes empty and the script will currently report success (no loop iterations). Consider treating an empty test list as an error with a helpful message so CI/local runs don’t silently skip integration tests.
# Discover tests from the binary (respects build tags).
# Pass flags as an array to prevent PowerShell splitting on dots.
$tests = & $testBin @('-test.list', '.*') 2>$null | Where-Object { $_ -match '^Test' }
integration/test.ps1:52
- The
-runfiltering uses PowerShell/.NET regex via-match, which can differ from Go’s-runregexp semantics (RE2). For consistency withgo test -run, consider discovering tests with-test.list <pattern>(using the provided pattern) instead of filtering the full list with a different regex engine.
# Parse TESTFLAGS forwarded from the task invocation.
# -run <pattern> filters which discovered tests to run (not passed to binary)
# -v changes gotestsum output format (handled in Taskfile; skip here)
# anything else is normalised to -test.<flag> and forwarded to the binary
$runPattern = $null
$binaryFlags = @()
if ($env:TESTFLAGS) {
$tokens = ($env:TESTFLAGS -split '\s+') | Where-Object { $_ }
for ($i = 0; $i -lt $tokens.Count; $i++) {
switch -Regex ($tokens[$i]) {
'^-run$' { $runPattern = $tokens[++$i]; break }
'^-run=(.+)' { $runPattern = $Matches[1]; break }
'^-v$' { break } # handled by gotestsum format in Taskfile
'^-test\.' { $binaryFlags += $tokens[$i]; break }
'^-(.+)' { $binaryFlags += "-test.$($Matches[1])"; break }
}
}
}
if ($runPattern) { $tests = $tests | Where-Object { $_ -match $runPattern } }
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| test:unit: | ||
| desc: Run unit tests (excludes integration package) | ||
| cmds: | ||
| - go test -count=1 ./api/... ./cmd/... ./internal/... ./pkg/... ./plugins/... |
Add powershell version Signed-off-by: Derek McGowan <derek@mcg.dev>
Signed-off-by: Derek McGowan <derek@mcg.dev>
Support passing arguments to test binaries Signed-off-by: Derek McGowan <derek@mcg.dev>
Signed-off-by: Derek McGowan <derek@mcg.dev>
Signed-off-by: Derek McGowan <derek@mcg.dev>
Signed-off-by: Derek McGowan <derek@mcg.dev>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 9 changed files in this pull request and generated 9 comments.
Comments suppressed due to low confidence (4)
.github/workflows/ci.yml:68
- GitHub Actions best practice is to pin third-party actions to a full commit SHA for supply-chain safety.
arduino/setup-task@v2is referenced by tag here; consider pinning it to a specific commit SHA.
- uses: arduino/setup-task@v2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
.github/workflows/ci.yml:103
- GitHub Actions best practice is to pin third-party actions to a full commit SHA for supply-chain safety.
arduino/setup-task@v2is referenced by tag here; consider pinning it to a specific commit SHA.
- uses: arduino/setup-task@v2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
.github/workflows/ci.yml:144
- GitHub Actions best practice is to pin third-party actions to a full commit SHA for supply-chain safety.
arduino/setup-task@v2is referenced by tag here; consider pinning it to a specific commit SHA.
- uses: arduino/setup-task@v2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
.github/workflows/ci.yml:283
- GitHub Actions best practice is to pin third-party actions to a full commit SHA for supply-chain safety.
arduino/setup-task@v2is referenced by tag here; consider pinning it to a specific commit SHA.
- uses: arduino/setup-task@v2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
| func dlClose(_ uintptr) error { | ||
| // On Windows, krun_start_enter may still be executing inside the library | ||
| // when Shutdown is called — krun_free_ctx initiates the VM stop but does | ||
| // not synchronously join the calling goroutine. Calling FreeLibrary while | ||
| // a goroutine is still inside the DLL causes an access violation. | ||
| // | ||
| // Since nerdbox runs exactly one VM per process (the shim model), the | ||
| // library handle is released naturally when the process exits. There is | ||
| // no need to call FreeLibrary explicitly. | ||
| return nil |
| desc: Regenerate protobuf bindings | ||
| dir: api | ||
| cmds: | ||
| - buf generate |
| test:unit: | ||
| desc: Run unit tests (excludes integration package) | ||
| cmds: | ||
| - go test -count=1 ./api/... ./cmd/... ./internal/... ./pkg/... ./plugins/... |
| vars: | ||
| OUTPUT_DIR: '_output' | ||
| GO_BUILDTAGS: 'no_grpc' | ||
| GO_TAGS: '-tags "no_grpc"' |
| -test.*) binary_flags+=("$tok") ;; | ||
| -*) binary_flags+=("-test.${tok#-}") ;; |
| '^-run=(.+)' { $runPattern = $Matches[1]; break } | ||
| '^-v$' { break } # handled by gotestsum format in Taskfile | ||
| '^-test\.' { $binaryFlags += $tokens[$i]; break } | ||
| '^-(.+)' { $binaryFlags += "-test.$($Matches[1])"; break } |
| build: | ||
| @echo "$(WHALE) $@" | ||
| HOST_OS=$(shell uname -s | tr '[:upper:]' '[:lower:]') KERNEL_ARCH=$(ARCH) $(BUILDX) bake | ||
| @task build | ||
|
|
||
| _output/containerd-shim-nerdbox-v1: cmd/containerd-shim-nerdbox-v1 FORCE | ||
| @echo "$(WHALE) $@" | ||
| $(GO) build ${DEBUG_GO_GCFLAGS} ${GO_GCFLAGS} ${GO_BUILD_FLAGS} -o $@ ${GO_LDFLAGS} ${GO_TAGS} ./$< | ||
| ifeq ($(OS),Darwin) | ||
| codesign --entitlements cmd/containerd-shim-nerdbox-v1/containerd-shim-nerdbox-v1.entitlements --force -s - $@ | ||
| endif | ||
| @task build:shim |
| - uses: arduino/setup-task@v2 | ||
| with: | ||
| version: 3.x | ||
| repo-token: ${{ secrets.GITHUB_TOKEN }} |
| } | ||
| }() | ||
|
|
||
| v.shutdownCallbacks = []func(context.Context) error{ | ||
| func(context.Context) error { | ||
| cerr := v.vmc.Shutdown() | ||
| select { | ||
| case err := <-errC: | ||
| if err != nil { | ||
| return fmt.Errorf("failure running vm: %w", err) | ||
| } | ||
| default: | ||
| } | ||
| return cerr | ||
| }, | ||
| } | ||
|
|
||
| // Accept a single connection from vminitd connecting back via vsock. | ||
| type acceptResult struct { |
Taskfile is better for cross platform building and provides a cleaner interface for defining build functionality.
This cleans up the integration test runners
For example