Read only regular files in gen, freshen and check (closes #189)
check / check (push) Failing after 14s
check / check (push) Failing after 14s
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
This commit was merged in pull request #199.
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
//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)
|
||||
}
|
||||
Reference in New Issue
Block a user