-
Notifications
You must be signed in to change notification settings - Fork 49
fix: skip non-regular files matched by glob in model test #742
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
Merged
SoulPancake
merged 8 commits into
openfga:main
from
terry-writer:fix/skip-non-regular-glob-matches
Aug 20, 2026
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
70c9dfa
fix: skip non-regular files matched by glob in model test
terry-writer 9f924a1
Merge branch 'main' into fix/skip-non-regular-glob-matches
SoulPancake 78ef08d
fix: address review feedback on glob FIFO fix
terry-writer a24e8be
Merge branch 'fix/skip-non-regular-glob-matches' of github.com:terry-…
terry-writer d9ea022
Merge branch 'main' into fix/skip-non-regular-glob-matches
SoulPancake 447f110
test: exercise the real command path and address lint
terry-writer 411a142
Merge branch 'fix/skip-non-regular-glob-matches' of github.com:terry-…
terry-writer b4ac941
fix: honor explicitly named non-regular paths in model test
terry-writer 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| package model | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "os" | ||
| "path/filepath" | ||
| "testing" | ||
| ) | ||
|
|
||
| // writeRegularFile writes a minimal regular test file at path. Shared by both this file and | ||
| // test_unix_test.go. | ||
| func writeRegularFile(t *testing.T, path string) error { | ||
| t.Helper() | ||
|
|
||
| if err := os.WriteFile(path, []byte("name: ok\n"), 0o600); err != nil { | ||
| return fmt.Errorf("failed to write test file %s: %w", path, err) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // A glob pattern (contains metacharacters) that matches no files should fail with a | ||
| // not-found/no-match error rather than being treated as a literal path or returning an empty | ||
| // result. | ||
| func TestResolveTestFilesNoMatchesFails(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| dir := t.TempDir() | ||
| pattern := filepath.Join(dir, "*.fga.yaml") | ||
|
|
||
| fileNames, err := resolveTestFiles(pattern) | ||
| if err == nil { | ||
| t.Fatalf("expected an error, got fileNames=%v", fileNames) | ||
| } | ||
| } | ||
|
|
||
| // An explicitly named, existing regular file (no glob metacharacters) is honored as-is - this | ||
| // preserves existing behavior for explicitly named paths. | ||
| func TestResolveTestFilesLiteralExistingPath(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| dir := t.TempDir() | ||
| path := filepath.Join(dir, "ok.fga.yaml") | ||
|
|
||
| if err := writeRegularFile(t, path); err != nil { | ||
| t.Fatalf("failed to write test file: %v", err) | ||
| } | ||
|
|
||
| fileNames, err := resolveTestFiles(path) | ||
| if err != nil { | ||
| t.Fatalf("unexpected error: %v", err) | ||
| } | ||
|
|
||
| if len(fileNames) != 1 || fileNames[0] != path { | ||
| t.Fatalf("expected [%s], got %v", path, fileNames) | ||
| } | ||
| } |
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,177 @@ | ||
| //go:build unix | ||
|
|
||
| package model | ||
|
|
||
| import ( | ||
| "path/filepath" | ||
| "sort" | ||
| "syscall" | ||
| "testing" | ||
| "time" | ||
| ) | ||
|
|
||
| // Regression test for https://github.com/openfga/cli/issues/739 | ||
| // A glob match that is a FIFO with no writer must never be handed to | ||
| // storetest.ReadFromFile, since os.ReadFile on such a FIFO blocks forever. | ||
| // | ||
| // This lives in a Unix-only file because syscall.Mkfifo does not exist on Windows, so a | ||
| // runtime GOOS skip would not prevent a compile failure there. | ||
| func TestResolveTestFilesSkipsNonRegularGlobMatches(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| dir := t.TempDir() | ||
|
|
||
| regularPath := filepath.Join(dir, "ok.fga.yaml") | ||
| if err := writeRegularFile(t, regularPath); err != nil { | ||
| t.Fatalf("failed to write regular test file: %v", err) | ||
| } | ||
|
|
||
| fifoPath := filepath.Join(dir, "evil.fga.yaml") | ||
| if err := syscall.Mkfifo(fifoPath, 0o600); err != nil { | ||
| t.Skipf("unable to create FIFO: %v", err) | ||
| } | ||
|
|
||
| pattern := filepath.Join(dir, "*.fga.yaml") | ||
|
|
||
| fileNames, err := resolveTestFiles(pattern) | ||
| if err != nil { | ||
| t.Fatalf("unexpected error: %v", err) | ||
| } | ||
|
|
||
| if len(fileNames) != 1 || fileNames[0] != regularPath { | ||
| t.Fatalf("expected [%s], got %v", regularPath, fileNames) | ||
| } | ||
| } | ||
|
|
||
| // Multiple regular files matched by the glob should all be returned, in addition to any | ||
| // non-regular files being dropped. | ||
| func TestResolveTestFilesMultipleRegularFiles(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| dir := t.TempDir() | ||
|
|
||
| first := filepath.Join(dir, "a.fga.yaml") | ||
| second := filepath.Join(dir, "b.fga.yaml") | ||
|
|
||
| for _, p := range []string{first, second} { | ||
| if err := writeRegularFile(t, p); err != nil { | ||
| t.Fatalf("failed to write regular test file: %v", err) | ||
| } | ||
| } | ||
|
|
||
| if err := syscall.Mkfifo(filepath.Join(dir, "c.fga.yaml"), 0o600); err != nil { | ||
| t.Skipf("unable to create FIFO: %v", err) | ||
| } | ||
|
|
||
| pattern := filepath.Join(dir, "*.fga.yaml") | ||
|
|
||
| fileNames, err := resolveTestFiles(pattern) | ||
| if err != nil { | ||
| t.Fatalf("unexpected error: %v", err) | ||
| } | ||
|
|
||
| sort.Strings(fileNames) | ||
|
|
||
| if len(fileNames) != 2 || fileNames[0] != first || fileNames[1] != second { | ||
| t.Fatalf("expected [%s %s], got %v", first, second, fileNames) | ||
| } | ||
| } | ||
|
|
||
| // If every glob match is non-regular, resolution must fail with a clear error rather than | ||
| // silently falling back to treating the glob pattern string itself as a literal path. | ||
| func TestResolveTestFilesAllMatchesNonRegular(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| dir := t.TempDir() | ||
|
|
||
| if err := syscall.Mkfifo(filepath.Join(dir, "evil.fga.yaml"), 0o600); err != nil { | ||
| t.Skipf("unable to create FIFO: %v", err) | ||
| } | ||
|
|
||
| pattern := filepath.Join(dir, "*.fga.yaml") | ||
|
|
||
| fileNames, err := resolveTestFiles(pattern) | ||
| if err == nil { | ||
| t.Fatalf("expected an error, got fileNames=%v", fileNames) | ||
| } | ||
| } | ||
|
|
||
| // End-to-end regression test through the actual modelTestCmd path (not resolveTestFiles | ||
| // directly), per review feedback. Before the fix, a FIFO picked up by the --tests glob made | ||
| // the command block forever in os.ReadFile. Here the command must return within a short | ||
| // deadline instead of hanging; whatever error it returns afterwards (e.g. from running the | ||
| // tests) is irrelevant to this regression. | ||
| func TestModelTestCmdDoesNotHangOnFifoGlobMatch(t *testing.T) { //nolint:paralleltest // mutates the shared global modelTestCmd, so it must not run in parallel | ||
| dir := t.TempDir() | ||
|
|
||
| regularPath := filepath.Join(dir, "ok.fga.yaml") | ||
| if err := writeRegularFile(t, regularPath); err != nil { | ||
| t.Fatalf("failed to write regular test file: %v", err) | ||
| } | ||
|
|
||
| if err := syscall.Mkfifo(filepath.Join(dir, "evil.fga.yaml"), 0o600); err != nil { | ||
| t.Skipf("unable to create FIFO: %v", err) | ||
| } | ||
|
|
||
| modelTestCmd.SetArgs([]string{"--tests", filepath.Join(dir, "*.fga.yaml")}) | ||
|
|
||
| done := make(chan struct{}) | ||
|
|
||
| go func() { | ||
| // The command may return an error (e.g. no reachable FGA server); we only care that | ||
| // it returns at all rather than blocking on the FIFO read. | ||
| _ = modelTestCmd.Execute() | ||
|
|
||
| close(done) | ||
| }() | ||
|
|
||
| select { | ||
| case <-done: | ||
| case <-time.After(10 * time.Second): | ||
| t.Fatal("modelTestCmd hung on a FIFO glob match instead of skipping it") | ||
| } | ||
| } | ||
|
|
||
| // An explicitly named non-regular path (no glob metacharacters) must be honored as-is, not | ||
| // rejected by the regular-file filter. This is what keeps process substitution | ||
| // (--tests <(...), which the shell turns into a FIFO like /dev/fd/11) working. The literal- | ||
| // path test in test_test.go uses a regular file, so it cannot catch this case. | ||
| func TestResolveTestFilesLiteralFifoPathHonored(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| dir := t.TempDir() | ||
| fifoPath := filepath.Join(dir, "pipe.fga.yaml") | ||
|
|
||
| if err := syscall.Mkfifo(fifoPath, 0o600); err != nil { | ||
| t.Skipf("unable to create FIFO: %v", err) | ||
| } | ||
|
|
||
| fileNames, err := resolveTestFiles(fifoPath) | ||
| if err != nil { | ||
| t.Fatalf("unexpected error: %v", err) | ||
| } | ||
|
|
||
| if len(fileNames) != 1 || fileNames[0] != fifoPath { | ||
| t.Fatalf("expected [%s], got %v", fifoPath, fileNames) | ||
| } | ||
| } | ||
|
|
||
| // A FIFO literally named "*.fga.yaml" must be treated as a glob match and filtered out, not | ||
| // mistaken for a literal path - otherwise it would be read and hang. This guards the | ||
| // metacharacter-based branch against a naive "single match equals the pattern" shortcut. | ||
| func TestResolveTestFilesFifoNamedLikeGlobStillFiltered(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| dir := t.TempDir() | ||
|
|
||
| if err := syscall.Mkfifo(filepath.Join(dir, "*.fga.yaml"), 0o600); err != nil { | ||
| t.Skipf("unable to create FIFO: %v", err) | ||
| } | ||
|
|
||
| pattern := filepath.Join(dir, "*.fga.yaml") | ||
|
|
||
| fileNames, err := resolveTestFiles(pattern) | ||
| if err == nil { | ||
| t.Fatalf("expected an error, got fileNames=%v", fileNames) | ||
| } | ||
| } |
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.