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
124 lines
3.6 KiB
Go
124 lines
3.6 KiB
Go
//go:build unix
|
|
|
|
//nolint:testpackage // white-box tests exercise unexported internals
|
|
package mfer
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/spf13/afero"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// pipeDeadline is how long a test lets a run go before it opens the test's
|
|
// named pipes for writing.
|
|
const 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() })
|
|
}
|
|
|
|
// TestScannerSkipsPipes scans a tree holding a named pipe and a symlink to
|
|
// another one, following symlinks: the manifest lists the regular file only.
|
|
func TestScannerSkipsPipes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir := t.TempDir()
|
|
require.NoError(t, os.WriteFile(filepath.Join(dir, testFile1), []byte("a"), 0o600))
|
|
makePipe(t, filepath.Join(dir, "pipe"))
|
|
|
|
// The symlink points outside the tree, so no pipe is read twice.
|
|
target := filepath.Join(t.TempDir(), "pipe")
|
|
makePipe(t, target)
|
|
require.NoError(t, os.Symlink(target, filepath.Join(dir, "link")))
|
|
|
|
s := NewScannerWithOptions(&ScannerOptions{FollowSymLinks: true})
|
|
require.NoError(t, s.EnumeratePath(dir, nil))
|
|
|
|
var buf bytes.Buffer
|
|
require.NoError(t, s.ToManifest(context.Background(), &buf, nil))
|
|
|
|
m, err := NewManifestFromReader(&buf)
|
|
require.NoError(t, err)
|
|
require.Len(t, m.Files(), 1)
|
|
assert.Equal(t, testFile1, m.Files()[0].GetPath())
|
|
}
|
|
|
|
// TestCheckPipes checks a tree against a manifest that lists, as an empty
|
|
// file, a path that is a named pipe on disk. The tree holds another pipe the
|
|
// manifest does not list. check reports the listed pipe as an error without
|
|
// reading it, and the search for extra files does not report the other.
|
|
func TestCheckPipes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir := t.TempDir()
|
|
manifestPath := filepath.Join(dir, "index.mf")
|
|
content := []byte("in manifest")
|
|
listedPipe := RelFilePath("listed-pipe")
|
|
|
|
fs := afero.NewOsFs()
|
|
createTestManifest(t, fs, manifestPath, map[string][]byte{
|
|
testFile1: content,
|
|
string(listedPipe): {},
|
|
})
|
|
require.NoError(t, afero.WriteFile(fs, filepath.Join(dir, testFile1), content, 0o600))
|
|
makePipe(t, filepath.Join(dir, string(listedPipe)))
|
|
makePipe(t, filepath.Join(dir, "unlisted-pipe"))
|
|
|
|
chk, err := NewChecker(&CheckerOptions{
|
|
ManifestPath: manifestPath,
|
|
BasePath: dir,
|
|
Fs: fs,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
results := make(chan Result, 10)
|
|
require.NoError(t, chk.Check(context.Background(), results, nil))
|
|
|
|
byPath := map[RelFilePath]Result{}
|
|
for r := range results {
|
|
byPath[r.Path] = r
|
|
}
|
|
|
|
assert.Equal(t, StatusOK, byPath[testFile1].Status)
|
|
assert.Equal(t, Result{
|
|
Path: listedPipe,
|
|
Status: StatusError,
|
|
Message: "not a regular file",
|
|
}, byPath[listedPipe])
|
|
|
|
extras := make(chan Result, 10)
|
|
require.NoError(t, chk.FindExtraFiles(context.Background(), extras))
|
|
|
|
var extraPaths []RelFilePath
|
|
for r := range extras {
|
|
extraPaths = append(extraPaths, r.Path)
|
|
}
|
|
|
|
assert.Empty(t, extraPaths)
|
|
}
|