check / check (push) Waiting to run
Opening a named pipe that has no writer blocks, so gen and freshen hung on a tree holding one, and check hung on a listed path that is a pipe on disk. gen and freshen now list regular files only (with --follow-symlinks, what a symlink points to must be one) and name what they skip with -v. check reports a listed path that is not a regular file as an error without opening it, and its search for extra files leaves out pipes, sockets and devices. Judgement call: the pipe tests build on unix only, where syscall.Mkfifo exists. Unverified: sockets and devices are not tested; the tests use pipes. Model: opus-5-5
95 lines
3.0 KiB
Go
95 lines
3.0 KiB
Go
//go:build unix
|
|
|
|
//nolint:testpackage // white-box tests exercise unexported internals
|
|
package cli
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/spf13/afero"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
const (
|
|
// testPipe is the name of the named pipe the pipe tests make in a tree.
|
|
testPipe = "pipe"
|
|
|
|
// pipeDeadline is how long a test lets a run go before it opens the
|
|
// test's named pipes for writing.
|
|
pipeDeadline = 5 * time.Second
|
|
)
|
|
|
|
// makePipe makes a named pipe at path. Opening a named pipe for reading
|
|
// blocks until it is opened for writing, so once pipeDeadline has passed,
|
|
// unless the test has ended, makePipe opens it for writing and closes it
|
|
// again. A run that wrongly reads the pipe then ends, and the test fails on
|
|
// what it reports instead of hanging. That lets one read through, so each
|
|
// path a wrong run would read gets a pipe of its own.
|
|
func makePipe(t *testing.T, path string) {
|
|
t.Helper()
|
|
|
|
require.NoError(t, syscall.Mkfifo(path, 0o600))
|
|
|
|
timer := time.AfterFunc(pipeDeadline, func() {
|
|
f, err := os.OpenFile(path, os.O_WRONLY, 0) //nolint:gosec // test-controlled path
|
|
if err == nil {
|
|
_ = f.Close()
|
|
}
|
|
})
|
|
|
|
t.Cleanup(func() { timer.Stop() })
|
|
}
|
|
|
|
// TestGenerateSkipsPipe runs gen -v on a tree holding a named pipe: the
|
|
// manifest lists the regular file only, and gen names the pipe it skipped.
|
|
func TestGenerateSkipsPipe(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir := t.TempDir()
|
|
fs := afero.NewOsFs()
|
|
writeTestFile(t, fs, filepath.Join(dir, testFileTxt), "regular")
|
|
makePipe(t, filepath.Join(dir, testPipe))
|
|
|
|
opts := testOpts([]string{testApp, cmdGenerate, "-v", dir}, fs)
|
|
require.Equal(t, 0, runCLI(opts), "stderr: %s", testStderr(t, opts))
|
|
|
|
assert.Equal(t, []string{testFileTxt},
|
|
manifestPaths(t, fs, filepath.Join(dir, defaultManifestName)))
|
|
assert.Contains(t, testStderr(t, opts), "skipping pipe: not a regular file")
|
|
}
|
|
|
|
// TestFreshenSkipsPipes adds a named pipe to a tree after gen made its
|
|
// manifest, and a symlink to another pipe. freshen -v --follow-symlinks
|
|
// leaves the manifest listing the regular file only, and names both entries
|
|
// it skipped.
|
|
func TestFreshenSkipsPipes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
fs := afero.NewOsFs()
|
|
root, manifestPath := setupFreshenDir(t, fs,
|
|
map[string]string{testFileTxt: "regular"})
|
|
makePipe(t, filepath.Join(root, testPipe))
|
|
|
|
// The symlink points outside the tree, so no pipe is read twice.
|
|
target := filepath.Join(t.TempDir(), testPipe)
|
|
makePipe(t, target)
|
|
require.NoError(t, os.Symlink(target, filepath.Join(root, testLink)))
|
|
|
|
opts := testOpts([]string{
|
|
testApp, cmdFreshen, "-v", "--follow-symlinks",
|
|
testFlagBase, root, manifestPath,
|
|
}, fs)
|
|
require.Equal(t, 0, runCLI(opts), "stderr: %s", testStderr(t, opts))
|
|
|
|
assert.Equal(t, []string{testFileTxt}, manifestPaths(t, fs, manifestPath))
|
|
|
|
stderr := testStderr(t, opts)
|
|
assert.Contains(t, stderr, "skipping pipe: not a regular file")
|
|
assert.Contains(t, stderr, "skipping link: not a regular file")
|
|
}
|