-
Notifications
You must be signed in to change notification settings - Fork 0
test(ci): add native hosted smoke checks #89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ben-ranford
wants to merge
3
commits into
main
Choose a base branch
from
feat/59-native-smokes
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # Native hosted smoke checks | ||
|
|
||
| The CI workflow runs bounded root-module smoke tests on GitHub-hosted | ||
| `macos-14` and `windows-2025` runners. They execute startup, cancellation, | ||
| EOF, newline input, no-colour/non-TTY, and Unicode line-input tests selected by | ||
| `scripts/rigor/native-smoke`. | ||
|
|
||
| The helper consumes `go test -json` and requires a terminal passing result for every named | ||
| smoke test. A skipped or failed selected root or subtest fails accounting. It does not replace Linux minimum/current-Go, race, | ||
| security, cross-compile, or full verification jobs. | ||
|
|
||
| Each native job has a 12-minute timeout. The pair therefore has a maximum | ||
| budget of 24 hosted runner minutes per workflow run; actual usage is visible in | ||
| the GitHub Actions run and should remain well below that ceiling. There are no | ||
| platform skips in this smoke set. Any future unsupported native behavior must | ||
| be skipped with a tracked issue and a documented rationale rather than treated | ||
| as a passing native check. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "encoding/json" | ||
| "fmt" | ||
| "os" | ||
| "os/exec" | ||
| "path/filepath" | ||
| "strings" | ||
| ) | ||
|
|
||
| var expected = map[string]bool{ | ||
| "TestRuntimeRestoresOnEOFAndClosesOnce": true, | ||
| "TestLineDriverEmitsCanonicalTextAndShutdown": true, | ||
| "TestLineDriverDrawUsesSafePlainWriter": true, | ||
| "TestLineDriverDoesNotInventUnsupportedTTYCapabilities": true, | ||
| "TestServeCancellationInterruptsBlockingReader": true, | ||
| "TestCapabilityEnumsRejectInvalidWireValues": true, | ||
| "TestDetectExplicitEnvironment": true, | ||
| "TestNonTTYMachineOutputIsNotReclassifiedAsPlain": true, | ||
| "TestNonTTYAccessibleOutputIsNotReclassifiedAsPlain": true, | ||
| "TestCrossPlatformTerminalColourDetection": true, | ||
| } | ||
|
|
||
| const selectedTests = "^(TestRuntimeRestoresOnEOFAndClosesOnce|TestLineDriverEmitsCanonicalTextAndShutdown|TestLineDriverDrawUsesSafePlainWriter|TestLineDriverDoesNotInventUnsupportedTTYCapabilities|TestServeCancellationInterruptsBlockingReader|TestCapabilityEnumsRejectInvalidWireValues|TestDetectExplicitEnvironment|TestNonTTYMachineOutputIsNotReclassifiedAsPlain|TestNonTTYAccessibleOutputIsNotReclassifiedAsPlain|TestCrossPlatformTerminalColourDetection)$" | ||
|
|
||
| type testEvent struct { | ||
| Action string | ||
| Test string | ||
| } | ||
|
|
||
| func main() { | ||
| executable, err := goExecutable() | ||
| if err != nil { | ||
| fmt.Fprintln(os.Stderr, err) | ||
| os.Exit(1) | ||
| } | ||
| command := exec.Command(executable, "test", "-json", "-count=1", "-run", selectedTests, "./runtime/human", "./runtime/agent", "./capability") | ||
| output, err := command.Output() | ||
| os.Stdout.Write(output) | ||
| if err != nil { | ||
| fmt.Fprintf(os.Stderr, "native smoke tests failed: %v\n", err) | ||
| os.Exit(1) | ||
| } | ||
| seen, err := selectedTestPasses(output) | ||
| if err != nil { | ||
| fmt.Fprintln(os.Stderr, err) | ||
| os.Exit(1) | ||
| } | ||
| for name := range expected { | ||
| if !seen[name] { | ||
| fmt.Fprintf(os.Stderr, "native smoke has no passing result for %s\n", name) | ||
| os.Exit(1) | ||
| } | ||
| } | ||
| fmt.Printf("native smoke selected and passed %d tests\n", len(expected)) | ||
| } | ||
|
|
||
| // goExecutable preserves the operator-selected Go installation while | ||
| // resolving it before execution. A relative PATH entry is not a stable trust | ||
| // boundary for a hosted smoke runner, so only an absolute executable is used. | ||
| func goExecutable() (string, error) { | ||
| executable, err := exec.LookPath("go") | ||
| if err != nil { | ||
| return "", fmt.Errorf("locate Go executable: %w", err) | ||
| } | ||
| if !filepath.IsAbs(executable) { | ||
| return "", fmt.Errorf("resolved Go executable path %q must be absolute", executable) | ||
| } | ||
| return executable, nil | ||
| } | ||
|
|
||
| func selectedTestPasses(output []byte) (map[string]bool, error) { | ||
| seen := make(map[string]bool, len(expected)) | ||
| for _, line := range bytes.Split(output, []byte{'\n'}) { | ||
| if len(line) == 0 { | ||
| continue | ||
| } | ||
| var event testEvent | ||
| if err := json.Unmarshal(line, &event); err != nil { | ||
| return nil, fmt.Errorf("decode go test event: %w", err) | ||
| } | ||
| root, _, _ := strings.Cut(event.Test, "/") | ||
| if !expected[root] { | ||
| continue | ||
| } | ||
| if event.Action == "skip" || event.Action == "fail" { | ||
| return nil, fmt.Errorf("native smoke %s reported %s", event.Test, event.Action) | ||
| } | ||
| if event.Action == "pass" && expected[event.Test] { | ||
| seen[event.Test] = true | ||
| } | ||
| } | ||
| return seen, nil | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "os" | ||
| "path/filepath" | ||
| "runtime" | ||
| "strings" | ||
| "testing" | ||
| ) | ||
|
|
||
| func TestSelectedTestsRequirePassingRoots(t *testing.T) { | ||
| output := []byte(`{"Action":"run","Test":"TestRuntimeRestoresOnEOFAndClosesOnce"} | ||
| {"Action":"pass","Test":"TestRuntimeRestoresOnEOFAndClosesOnce"} | ||
| {"Action":"run","Test":"TestCrossPlatformTerminalColourDetection/linux_truecolor"} | ||
| {"Action":"run","Test":"TestCrossPlatformTerminalColourDetection"} | ||
| {"Action":"pass","Test":"TestCrossPlatformTerminalColourDetection"} | ||
| `) | ||
| seen, err := selectedTestPasses(output) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| if !seen["TestRuntimeRestoresOnEOFAndClosesOnce"] || !seen["TestCrossPlatformTerminalColourDetection"] { | ||
| t.Fatalf("expected root test events, got %#v", seen) | ||
| } | ||
| if seen["TestLineDriverEmitsCanonicalTextAndShutdown"] { | ||
| t.Fatalf("unexpected test event counted: %#v", seen) | ||
| } | ||
| } | ||
|
|
||
| func TestSelectedTestsRejectSkippedOrFailedRootsAndChildren(t *testing.T) { | ||
| for _, action := range []string{"skip", "fail"} { | ||
| for _, suffix := range []string{"", "/native"} { | ||
| t.Run(action+suffix, func(t *testing.T) { | ||
| output := []byte(fmt.Sprintf(`{"Action":"run","Test":"TestRuntimeRestoresOnEOFAndClosesOnce"} | ||
| {"Action":%q,"Test":%q} | ||
| `, action, "TestRuntimeRestoresOnEOFAndClosesOnce"+suffix)) | ||
| if _, err := selectedTestPasses(output); err == nil { | ||
| t.Fatal("accepted skipped or failed selected test") | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestSelectedTestsDoNotCountStartsAsPasses(t *testing.T) { | ||
| seen, err := selectedTestPasses([]byte(`{"Action":"run","Test":"TestRuntimeRestoresOnEOFAndClosesOnce"}`)) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| if len(seen) != 0 { | ||
| t.Fatal("counted incomplete test as passed") | ||
| } | ||
| } | ||
|
|
||
| func TestGoExecutableReportsMissingLookup(t *testing.T) { | ||
| t.Setenv("PATH", t.TempDir()) | ||
| if _, err := goExecutable(); err == nil || !strings.Contains(err.Error(), "locate Go executable") { | ||
| t.Fatalf("missing Go executable error = %v", err) | ||
| } | ||
| } | ||
|
|
||
| func TestGoExecutableRejectsRelativePathResolution(t *testing.T) { | ||
| if runtime.GOOS == "windows" { | ||
| t.Skip("relative executable lookup is platform specific") | ||
| } | ||
| directory := t.TempDir() | ||
| bin := filepath.Join(directory, "bin") | ||
| if err := os.Mkdir(bin, 0o755); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| if err := os.WriteFile(filepath.Join(bin, "go"), []byte("#!/bin/sh\n"), 0o755); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| workingDirectory, err := os.Getwd() | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| if err := os.Chdir(directory); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| defer func() { | ||
| if err := os.Chdir(workingDirectory); err != nil { | ||
| t.Error(err) | ||
| } | ||
| }() | ||
| t.Setenv("PATH", "bin") | ||
| t.Setenv("GODEBUG", "execerrdot=0") | ||
| if _, err := goExecutable(); err == nil || !strings.Contains(err.Error(), "must be absolute") { | ||
| t.Fatalf("relative Go path was not rejected: %v", err) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.