Update golangci-lint to v2.12.2 with canonical config (closes #60)
Some checks failed
check / check (push) Has been cancelled
Some checks failed
check / check (push) Has been cancelled
Adopts golangci-lint v2.12.2 and the canonical .golangci.yml (default: all), and fixes all resulting findings across the tree. Two intended behavior changes: absent MFFilePath.Mtime is handled explicitly in freshen, list and export rather than dereferenced (main panicked); gpg positional key IDs now follow an explicit -- end-of-options marker. All twelve reworded user-visible error messages restored to byte-identical parity with main and pinned by tests.
This commit was merged in pull request #59.
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
//nolint:testpackage // white-box tests exercise unexported internals
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
@@ -13,19 +17,53 @@ import (
|
||||
"sneak.berlin/go/mfer/mfer"
|
||||
)
|
||||
|
||||
func init() {
|
||||
const (
|
||||
testApp = "mfer"
|
||||
testDir = "/testdir"
|
||||
testFile1 = "/testdir/file1.txt"
|
||||
testMF = "/testdir/test.mf"
|
||||
testOutput = "/output.mf"
|
||||
testOutputTmp = "/output.mf.tmp"
|
||||
testManifest = "/manifest.mf"
|
||||
testFlagBase = "--base"
|
||||
testFlagNoExtra = "--no-extra-files"
|
||||
)
|
||||
|
||||
var errSimulatedWrite = errors.New("simulated write failure")
|
||||
|
||||
// runMu serializes CLI runs: RunWithOptions wires the process-global
|
||||
// logger to the run's I/O streams, so parallel runs would cross-wire
|
||||
// captured output between tests.
|
||||
//
|
||||
//nolint:gochecknoglobals // guards process-global logger state in tests
|
||||
var runMu sync.Mutex
|
||||
|
||||
// runCLI invokes RunWithOptions while holding runMu so parallel tests
|
||||
// capture their own output.
|
||||
func runCLI(opts *RunOptions) int {
|
||||
runMu.Lock()
|
||||
defer runMu.Unlock()
|
||||
|
||||
return RunWithOptions(opts)
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// Prevent urfave/cli from calling os.Exit during tests
|
||||
urfcli.OsExiter = func(code int) {}
|
||||
urfcli.OsExiter = func(_ int) {}
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := &CLIApp{}
|
||||
assert.NotNil(t, m)
|
||||
}
|
||||
|
||||
func testOpts(args []string, fs afero.Fs) *RunOptions {
|
||||
return &RunOptions{
|
||||
Appname: "mfer",
|
||||
Appname: testApp,
|
||||
Version: "1.0.0",
|
||||
Gitrev: "abc123",
|
||||
Args: args,
|
||||
@@ -36,374 +74,451 @@ func testOpts(args []string, fs afero.Fs) *RunOptions {
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionCommand(t *testing.T) {
|
||||
fs := afero.NewMemMapFs()
|
||||
opts := testOpts([]string{"mfer", "version"}, fs)
|
||||
func testStdout(t *testing.T, opts *RunOptions) string {
|
||||
t.Helper()
|
||||
|
||||
exitCode := RunWithOptions(opts)
|
||||
buf, ok := opts.Stdout.(*bytes.Buffer)
|
||||
require.True(t, ok)
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func testStderr(t *testing.T, opts *RunOptions) string {
|
||||
t.Helper()
|
||||
|
||||
buf, ok := opts.Stderr.(*bytes.Buffer)
|
||||
require.True(t, ok)
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func writeTestFile(t *testing.T, fs afero.Fs, path, content string) {
|
||||
t.Helper()
|
||||
|
||||
require.NoError(t, afero.WriteFile(fs, path, []byte(content), 0o644))
|
||||
}
|
||||
|
||||
func TestVersionCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
opts := testOpts([]string{testApp, "version"}, fs)
|
||||
|
||||
exitCode := runCLI(opts)
|
||||
|
||||
assert.Equal(t, 0, exitCode)
|
||||
stdout := opts.Stdout.(*bytes.Buffer).String()
|
||||
|
||||
stdout := testStdout(t, opts)
|
||||
assert.Contains(t, stdout, mfer.Version)
|
||||
assert.Contains(t, stdout, "abc123")
|
||||
}
|
||||
|
||||
func TestHelpCommand(t *testing.T) {
|
||||
fs := afero.NewMemMapFs()
|
||||
opts := testOpts([]string{"mfer", "--help"}, fs)
|
||||
t.Parallel()
|
||||
|
||||
exitCode := RunWithOptions(opts)
|
||||
fs := afero.NewMemMapFs()
|
||||
opts := testOpts([]string{testApp, "--help"}, fs)
|
||||
|
||||
exitCode := runCLI(opts)
|
||||
|
||||
assert.Equal(t, 0, exitCode)
|
||||
stdout := opts.Stdout.(*bytes.Buffer).String()
|
||||
assert.Contains(t, stdout, "generate")
|
||||
assert.Contains(t, stdout, "check")
|
||||
|
||||
stdout := testStdout(t, opts)
|
||||
assert.Contains(t, stdout, cmdGenerate)
|
||||
assert.Contains(t, stdout, cmdCheck)
|
||||
assert.Contains(t, stdout, "fetch")
|
||||
}
|
||||
|
||||
func TestGenerateCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
|
||||
// Create test files in memory filesystem
|
||||
require.NoError(t, fs.MkdirAll("/testdir", 0o755))
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/file1.txt", []byte("hello world"), 0o644))
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/file2.txt", []byte("test content"), 0o644))
|
||||
require.NoError(t, fs.MkdirAll(testDir, 0o755))
|
||||
writeTestFile(t, fs, testFile1, "hello world")
|
||||
writeTestFile(t, fs, "/testdir/file2.txt", "test content")
|
||||
|
||||
opts := testOpts([]string{"mfer", "generate", "-q", "-o", "/testdir/test.mf", "/testdir"}, fs)
|
||||
opts := testOpts([]string{testApp, cmdGenerate, "-q", "-o", testMF, testDir}, fs)
|
||||
|
||||
exitCode := RunWithOptions(opts)
|
||||
exitCode := runCLI(opts)
|
||||
|
||||
assert.Equal(t, 0, exitCode, "stderr: %s", opts.Stderr.(*bytes.Buffer).String())
|
||||
assert.Equal(t, 0, exitCode, "stderr: %s", testStderr(t, opts))
|
||||
|
||||
// Verify manifest was created
|
||||
exists, err := afero.Exists(fs, "/testdir/test.mf")
|
||||
exists, err := afero.Exists(fs, testMF)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, exists)
|
||||
}
|
||||
|
||||
func TestGenerateAndCheckCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
|
||||
// Create test files with subdirectory
|
||||
require.NoError(t, fs.MkdirAll("/testdir/subdir", 0o755))
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/file1.txt", []byte("hello world"), 0o644))
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/subdir/file2.txt", []byte("test content"), 0o644))
|
||||
writeTestFile(t, fs, testFile1, "hello world")
|
||||
writeTestFile(t, fs, "/testdir/subdir/file2.txt", "test content")
|
||||
|
||||
// Generate manifest
|
||||
opts := testOpts([]string{"mfer", "generate", "-q", "-o", "/testdir/test.mf", "/testdir"}, fs)
|
||||
exitCode := RunWithOptions(opts)
|
||||
require.Equal(t, 0, exitCode, "generate failed: %s", opts.Stderr.(*bytes.Buffer).String())
|
||||
opts := testOpts([]string{testApp, cmdGenerate, "-q", "-o", testMF, testDir}, fs)
|
||||
exitCode := runCLI(opts)
|
||||
require.Equal(t, 0, exitCode, "generate failed: %s", testStderr(t, opts))
|
||||
|
||||
// Check manifest
|
||||
opts = testOpts([]string{"mfer", "check", "-q", "--base", "/testdir", "/testdir/test.mf"}, fs)
|
||||
exitCode = RunWithOptions(opts)
|
||||
assert.Equal(t, 0, exitCode, "check failed: %s", opts.Stderr.(*bytes.Buffer).String())
|
||||
opts = testOpts([]string{testApp, cmdCheck, "-q", testFlagBase, testDir, testMF}, fs)
|
||||
exitCode = runCLI(opts)
|
||||
assert.Equal(t, 0, exitCode, "check failed: %s", testStderr(t, opts))
|
||||
}
|
||||
|
||||
func TestCheckCommandWithMissingFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
|
||||
// Create test file
|
||||
require.NoError(t, fs.MkdirAll("/testdir", 0o755))
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/file1.txt", []byte("hello world"), 0o644))
|
||||
require.NoError(t, fs.MkdirAll(testDir, 0o755))
|
||||
writeTestFile(t, fs, testFile1, "hello world")
|
||||
|
||||
// Generate manifest
|
||||
opts := testOpts([]string{"mfer", "generate", "-q", "-o", "/testdir/test.mf", "/testdir"}, fs)
|
||||
exitCode := RunWithOptions(opts)
|
||||
require.Equal(t, 0, exitCode, "generate failed: %s", opts.Stderr.(*bytes.Buffer).String())
|
||||
opts := testOpts([]string{testApp, cmdGenerate, "-q", "-o", testMF, testDir}, fs)
|
||||
exitCode := runCLI(opts)
|
||||
require.Equal(t, 0, exitCode, "generate failed: %s", testStderr(t, opts))
|
||||
|
||||
// Delete the file
|
||||
require.NoError(t, fs.Remove("/testdir/file1.txt"))
|
||||
require.NoError(t, fs.Remove(testFile1))
|
||||
|
||||
// Check manifest - should fail
|
||||
opts = testOpts([]string{"mfer", "check", "-q", "--base", "/testdir", "/testdir/test.mf"}, fs)
|
||||
exitCode = RunWithOptions(opts)
|
||||
opts = testOpts([]string{testApp, cmdCheck, "-q", testFlagBase, testDir, testMF}, fs)
|
||||
exitCode = runCLI(opts)
|
||||
assert.Equal(t, 1, exitCode, "check should have failed for missing file")
|
||||
}
|
||||
|
||||
func TestCheckCommandWithCorruptedFile(t *testing.T) {
|
||||
func runCheckAfterRewrite(t *testing.T, rewritten, msg string) {
|
||||
t.Helper()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
|
||||
// Create test file
|
||||
require.NoError(t, fs.MkdirAll("/testdir", 0o755))
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/file1.txt", []byte("hello world"), 0o644))
|
||||
require.NoError(t, fs.MkdirAll(testDir, 0o755))
|
||||
writeTestFile(t, fs, testFile1, "hello world")
|
||||
|
||||
// Generate manifest
|
||||
opts := testOpts([]string{"mfer", "generate", "-q", "-o", "/testdir/test.mf", "/testdir"}, fs)
|
||||
exitCode := RunWithOptions(opts)
|
||||
require.Equal(t, 0, exitCode, "generate failed: %s", opts.Stderr.(*bytes.Buffer).String())
|
||||
opts := testOpts([]string{testApp, cmdGenerate, "-q", "-o", testMF, testDir}, fs)
|
||||
exitCode := runCLI(opts)
|
||||
require.Equal(t, 0, exitCode, "generate failed: %s", testStderr(t, opts))
|
||||
|
||||
// Rewrite the file, then check the manifest - it must fail
|
||||
writeTestFile(t, fs, testFile1, rewritten)
|
||||
|
||||
opts = testOpts([]string{testApp, cmdCheck, "-q", testFlagBase, testDir, testMF}, fs)
|
||||
exitCode = runCLI(opts)
|
||||
assert.Equal(t, 1, exitCode, msg)
|
||||
}
|
||||
|
||||
func TestCheckCommandWithCorruptedFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Corrupt the file (change content but keep same size)
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/file1.txt", []byte("HELLO WORLD"), 0o644))
|
||||
|
||||
// Check manifest - should fail with hash mismatch
|
||||
opts = testOpts([]string{"mfer", "check", "-q", "--base", "/testdir", "/testdir/test.mf"}, fs)
|
||||
exitCode = RunWithOptions(opts)
|
||||
assert.Equal(t, 1, exitCode, "check should have failed for corrupted file")
|
||||
runCheckAfterRewrite(t, "HELLO WORLD",
|
||||
"check should have failed for corrupted file")
|
||||
}
|
||||
|
||||
func TestCheckCommandWithSizeMismatch(t *testing.T) {
|
||||
fs := afero.NewMemMapFs()
|
||||
|
||||
// Create test file
|
||||
require.NoError(t, fs.MkdirAll("/testdir", 0o755))
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/file1.txt", []byte("hello world"), 0o644))
|
||||
|
||||
// Generate manifest
|
||||
opts := testOpts([]string{"mfer", "generate", "-q", "-o", "/testdir/test.mf", "/testdir"}, fs)
|
||||
exitCode := RunWithOptions(opts)
|
||||
require.Equal(t, 0, exitCode, "generate failed: %s", opts.Stderr.(*bytes.Buffer).String())
|
||||
t.Parallel()
|
||||
|
||||
// Change file size
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/file1.txt", []byte("different size content here"), 0o644))
|
||||
|
||||
// Check manifest - should fail with size mismatch
|
||||
opts = testOpts([]string{"mfer", "check", "-q", "--base", "/testdir", "/testdir/test.mf"}, fs)
|
||||
exitCode = RunWithOptions(opts)
|
||||
assert.Equal(t, 1, exitCode, "check should have failed for size mismatch")
|
||||
runCheckAfterRewrite(t, "different size content here",
|
||||
"check should have failed for size mismatch")
|
||||
}
|
||||
|
||||
func TestBannerOutput(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
|
||||
// Create test file
|
||||
require.NoError(t, fs.MkdirAll("/testdir", 0o755))
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/file1.txt", []byte("hello"), 0o644))
|
||||
require.NoError(t, fs.MkdirAll(testDir, 0o755))
|
||||
writeTestFile(t, fs, testFile1, "hello")
|
||||
|
||||
// Run without -q to see banner
|
||||
opts := testOpts([]string{"mfer", "generate", "-o", "/testdir/test.mf", "/testdir"}, fs)
|
||||
exitCode := RunWithOptions(opts)
|
||||
opts := testOpts([]string{testApp, cmdGenerate, "-o", testMF, testDir}, fs)
|
||||
exitCode := runCLI(opts)
|
||||
assert.Equal(t, 0, exitCode)
|
||||
|
||||
// Banner ASCII art should be in stdout
|
||||
stdout := opts.Stdout.(*bytes.Buffer).String()
|
||||
stdout := testStdout(t, opts)
|
||||
assert.Contains(t, stdout, "___")
|
||||
assert.Contains(t, stdout, "\\")
|
||||
}
|
||||
|
||||
func TestUnknownCommand(t *testing.T) {
|
||||
fs := afero.NewMemMapFs()
|
||||
opts := testOpts([]string{"mfer", "unknown"}, fs)
|
||||
t.Parallel()
|
||||
|
||||
exitCode := RunWithOptions(opts)
|
||||
fs := afero.NewMemMapFs()
|
||||
opts := testOpts([]string{testApp, "unknown"}, fs)
|
||||
|
||||
exitCode := runCLI(opts)
|
||||
assert.Equal(t, 1, exitCode)
|
||||
}
|
||||
|
||||
func TestGenerateExcludesDotfilesByDefault(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
|
||||
// Create test files including dotfiles
|
||||
require.NoError(t, fs.MkdirAll("/testdir", 0o755))
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/file1.txt", []byte("hello"), 0o644))
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/.hidden", []byte("secret"), 0o644))
|
||||
require.NoError(t, fs.MkdirAll(testDir, 0o755))
|
||||
writeTestFile(t, fs, testFile1, "hello")
|
||||
writeTestFile(t, fs, "/testdir/.hidden", "secret")
|
||||
|
||||
// Generate manifest without --include-dotfiles (default excludes dotfiles)
|
||||
opts := testOpts([]string{"mfer", "generate", "-q", "-o", "/testdir/test.mf", "/testdir"}, fs)
|
||||
exitCode := RunWithOptions(opts)
|
||||
opts := testOpts([]string{testApp, cmdGenerate, "-q", "-o", testMF, testDir}, fs)
|
||||
exitCode := runCLI(opts)
|
||||
require.Equal(t, 0, exitCode)
|
||||
|
||||
// Check that manifest exists
|
||||
exists, _ := afero.Exists(fs, "/testdir/test.mf")
|
||||
exists, _ := afero.Exists(fs, testMF)
|
||||
assert.True(t, exists)
|
||||
|
||||
// Verify manifest only has 1 file (the non-dotfile)
|
||||
manifest, err := mfer.NewManifestFromFile(fs, "/testdir/test.mf")
|
||||
manifest, err := mfer.NewManifestFromFile(fs, testMF)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, manifest.Files(), 1)
|
||||
assert.Equal(t, "file1.txt", manifest.Files()[0].Path)
|
||||
assert.Equal(t, "file1.txt", manifest.Files()[0].GetPath())
|
||||
}
|
||||
|
||||
func TestGenerateWithIncludeDotfiles(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
|
||||
// Create test files including dotfiles
|
||||
require.NoError(t, fs.MkdirAll("/testdir", 0o755))
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/file1.txt", []byte("hello"), 0o644))
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/.hidden", []byte("secret"), 0o644))
|
||||
require.NoError(t, fs.MkdirAll(testDir, 0o755))
|
||||
writeTestFile(t, fs, testFile1, "hello")
|
||||
writeTestFile(t, fs, "/testdir/.hidden", "secret")
|
||||
|
||||
// Generate manifest with --include-dotfiles
|
||||
opts := testOpts([]string{"mfer", "generate", "-q", "--include-dotfiles", "-o", "/testdir/test.mf", "/testdir"}, fs)
|
||||
exitCode := RunWithOptions(opts)
|
||||
opts := testOpts([]string{
|
||||
testApp, cmdGenerate, "-q", "--include-dotfiles", "-o", testMF, testDir,
|
||||
}, fs)
|
||||
exitCode := runCLI(opts)
|
||||
require.Equal(t, 0, exitCode)
|
||||
|
||||
// Verify manifest has 2 files (including dotfile)
|
||||
manifest, err := mfer.NewManifestFromFile(fs, "/testdir/test.mf")
|
||||
manifest, err := mfer.NewManifestFromFile(fs, testMF)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, manifest.Files(), 2)
|
||||
}
|
||||
|
||||
func TestMultipleInputPaths(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
|
||||
// Create test files in multiple directories
|
||||
require.NoError(t, fs.MkdirAll("/dir1", 0o755))
|
||||
require.NoError(t, fs.MkdirAll("/dir2", 0o755))
|
||||
require.NoError(t, afero.WriteFile(fs, "/dir1/file1.txt", []byte("content1"), 0o644))
|
||||
require.NoError(t, afero.WriteFile(fs, "/dir2/file2.txt", []byte("content2"), 0o644))
|
||||
writeTestFile(t, fs, "/dir1/file1.txt", "content1")
|
||||
writeTestFile(t, fs, "/dir2/file2.txt", "content2")
|
||||
|
||||
// Generate manifest from multiple paths
|
||||
opts := testOpts([]string{"mfer", "generate", "-q", "-o", "/output.mf", "/dir1", "/dir2"}, fs)
|
||||
exitCode := RunWithOptions(opts)
|
||||
assert.Equal(t, 0, exitCode, "stderr: %s", opts.Stderr.(*bytes.Buffer).String())
|
||||
opts := testOpts([]string{
|
||||
testApp, cmdGenerate, "-q", "-o", testOutput, "/dir1", "/dir2",
|
||||
}, fs)
|
||||
exitCode := runCLI(opts)
|
||||
assert.Equal(t, 0, exitCode, "stderr: %s", testStderr(t, opts))
|
||||
|
||||
exists, _ := afero.Exists(fs, "/output.mf")
|
||||
exists, _ := afero.Exists(fs, testOutput)
|
||||
assert.True(t, exists)
|
||||
}
|
||||
|
||||
func TestNoExtraFilesPass(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
|
||||
// Create test files
|
||||
require.NoError(t, fs.MkdirAll("/testdir", 0o755))
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/file1.txt", []byte("hello"), 0o644))
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/file2.txt", []byte("world"), 0o644))
|
||||
require.NoError(t, fs.MkdirAll(testDir, 0o755))
|
||||
writeTestFile(t, fs, testFile1, "hello")
|
||||
writeTestFile(t, fs, "/testdir/file2.txt", "world")
|
||||
|
||||
// Generate manifest
|
||||
opts := testOpts([]string{"mfer", "generate", "-q", "-o", "/manifest.mf", "/testdir"}, fs)
|
||||
exitCode := RunWithOptions(opts)
|
||||
opts := testOpts([]string{testApp, cmdGenerate, "-q", "-o", testManifest, testDir}, fs)
|
||||
exitCode := runCLI(opts)
|
||||
require.Equal(t, 0, exitCode)
|
||||
|
||||
// Check with --no-extra-files (should pass - no extra files)
|
||||
opts = testOpts([]string{"mfer", "check", "-q", "--no-extra-files", "--base", "/testdir", "/manifest.mf"}, fs)
|
||||
exitCode = RunWithOptions(opts)
|
||||
opts = testOpts([]string{
|
||||
testApp, cmdCheck, "-q", testFlagNoExtra, testFlagBase, testDir, testManifest,
|
||||
}, fs)
|
||||
exitCode = runCLI(opts)
|
||||
assert.Equal(t, 0, exitCode)
|
||||
}
|
||||
|
||||
func TestNoExtraFilesFail(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
|
||||
// Create test files
|
||||
require.NoError(t, fs.MkdirAll("/testdir", 0o755))
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/file1.txt", []byte("hello"), 0o644))
|
||||
require.NoError(t, fs.MkdirAll(testDir, 0o755))
|
||||
writeTestFile(t, fs, testFile1, "hello")
|
||||
|
||||
// Generate manifest
|
||||
opts := testOpts([]string{"mfer", "generate", "-q", "-o", "/manifest.mf", "/testdir"}, fs)
|
||||
exitCode := RunWithOptions(opts)
|
||||
opts := testOpts([]string{testApp, cmdGenerate, "-q", "-o", testManifest, testDir}, fs)
|
||||
exitCode := runCLI(opts)
|
||||
require.Equal(t, 0, exitCode)
|
||||
|
||||
// Add an extra file after manifest generation
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/extra.txt", []byte("extra"), 0o644))
|
||||
writeTestFile(t, fs, "/testdir/extra.txt", "extra")
|
||||
|
||||
// Check with --no-extra-files (should fail - extra file exists)
|
||||
opts = testOpts([]string{"mfer", "check", "-q", "--no-extra-files", "--base", "/testdir", "/manifest.mf"}, fs)
|
||||
exitCode = RunWithOptions(opts)
|
||||
opts = testOpts([]string{
|
||||
testApp, cmdCheck, "-q", testFlagNoExtra, testFlagBase, testDir, testManifest,
|
||||
}, fs)
|
||||
exitCode = runCLI(opts)
|
||||
assert.Equal(t, 1, exitCode, "check should fail when extra files exist")
|
||||
}
|
||||
|
||||
func TestNoExtraFilesWithSubdirectory(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
|
||||
// Create test files with subdirectory
|
||||
require.NoError(t, fs.MkdirAll("/testdir/subdir", 0o755))
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/file1.txt", []byte("hello"), 0o644))
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/subdir/file2.txt", []byte("world"), 0o644))
|
||||
writeTestFile(t, fs, testFile1, "hello")
|
||||
writeTestFile(t, fs, "/testdir/subdir/file2.txt", "world")
|
||||
|
||||
// Generate manifest
|
||||
opts := testOpts([]string{"mfer", "generate", "-q", "-o", "/manifest.mf", "/testdir"}, fs)
|
||||
exitCode := RunWithOptions(opts)
|
||||
opts := testOpts([]string{testApp, cmdGenerate, "-q", "-o", testManifest, testDir}, fs)
|
||||
exitCode := runCLI(opts)
|
||||
require.Equal(t, 0, exitCode)
|
||||
|
||||
// Add extra file in subdirectory
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/subdir/extra.txt", []byte("extra"), 0o644))
|
||||
writeTestFile(t, fs, "/testdir/subdir/extra.txt", "extra")
|
||||
|
||||
// Check with --no-extra-files (should fail)
|
||||
opts = testOpts([]string{"mfer", "check", "-q", "--no-extra-files", "--base", "/testdir", "/manifest.mf"}, fs)
|
||||
exitCode = RunWithOptions(opts)
|
||||
assert.Equal(t, 1, exitCode, "check should fail when extra files exist in subdirectory")
|
||||
opts = testOpts([]string{
|
||||
testApp, cmdCheck, "-q", testFlagNoExtra, testFlagBase, testDir, testManifest,
|
||||
}, fs)
|
||||
exitCode = runCLI(opts)
|
||||
assert.Equal(t, 1, exitCode,
|
||||
"check should fail when extra files exist in subdirectory")
|
||||
}
|
||||
|
||||
func TestCheckWithoutNoExtraFilesIgnoresExtra(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
|
||||
// Create test file
|
||||
require.NoError(t, fs.MkdirAll("/testdir", 0o755))
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/file1.txt", []byte("hello"), 0o644))
|
||||
require.NoError(t, fs.MkdirAll(testDir, 0o755))
|
||||
writeTestFile(t, fs, testFile1, "hello")
|
||||
|
||||
// Generate manifest
|
||||
opts := testOpts([]string{"mfer", "generate", "-q", "-o", "/manifest.mf", "/testdir"}, fs)
|
||||
exitCode := RunWithOptions(opts)
|
||||
opts := testOpts([]string{testApp, cmdGenerate, "-q", "-o", testManifest, testDir}, fs)
|
||||
exitCode := runCLI(opts)
|
||||
require.Equal(t, 0, exitCode)
|
||||
|
||||
// Add extra file
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/extra.txt", []byte("extra"), 0o644))
|
||||
writeTestFile(t, fs, "/testdir/extra.txt", "extra")
|
||||
|
||||
// Check WITHOUT --no-extra-files (should pass - extra files ignored)
|
||||
opts = testOpts([]string{"mfer", "check", "-q", "--base", "/testdir", "/manifest.mf"}, fs)
|
||||
exitCode = RunWithOptions(opts)
|
||||
assert.Equal(t, 0, exitCode, "check without --no-extra-files should ignore extra files")
|
||||
opts = testOpts([]string{
|
||||
testApp, cmdCheck, "-q", testFlagBase, testDir, testManifest,
|
||||
}, fs)
|
||||
exitCode = runCLI(opts)
|
||||
assert.Equal(t, 0, exitCode,
|
||||
"check without --no-extra-files should ignore extra files")
|
||||
}
|
||||
|
||||
func TestGenerateAtomicWriteNoTempFileOnSuccess(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
|
||||
// Create test file
|
||||
require.NoError(t, fs.MkdirAll("/testdir", 0o755))
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/file1.txt", []byte("hello"), 0o644))
|
||||
require.NoError(t, fs.MkdirAll(testDir, 0o755))
|
||||
writeTestFile(t, fs, testFile1, "hello")
|
||||
|
||||
// Generate manifest
|
||||
opts := testOpts([]string{"mfer", "generate", "-q", "-o", "/output.mf", "/testdir"}, fs)
|
||||
exitCode := RunWithOptions(opts)
|
||||
opts := testOpts([]string{testApp, cmdGenerate, "-q", "-o", testOutput, testDir}, fs)
|
||||
exitCode := runCLI(opts)
|
||||
require.Equal(t, 0, exitCode)
|
||||
|
||||
// Verify output file exists
|
||||
exists, err := afero.Exists(fs, "/output.mf")
|
||||
exists, err := afero.Exists(fs, testOutput)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, exists, "output file should exist")
|
||||
|
||||
// Verify temp file does NOT exist
|
||||
tmpExists, err := afero.Exists(fs, "/output.mf.tmp")
|
||||
tmpExists, err := afero.Exists(fs, testOutputTmp)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, tmpExists, "temp file should not exist after successful generation")
|
||||
assert.False(t, tmpExists,
|
||||
"temp file should not exist after successful generation")
|
||||
}
|
||||
|
||||
func TestGenerateAtomicWriteOverwriteWithForce(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
|
||||
// Create test file
|
||||
require.NoError(t, fs.MkdirAll("/testdir", 0o755))
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/file1.txt", []byte("hello"), 0o644))
|
||||
require.NoError(t, fs.MkdirAll(testDir, 0o755))
|
||||
writeTestFile(t, fs, testFile1, "hello")
|
||||
|
||||
// Create existing manifest with different content
|
||||
require.NoError(t, afero.WriteFile(fs, "/output.mf", []byte("old content"), 0o644))
|
||||
writeTestFile(t, fs, testOutput, "old content")
|
||||
|
||||
// Generate manifest with --force
|
||||
opts := testOpts([]string{"mfer", "generate", "-q", "-f", "-o", "/output.mf", "/testdir"}, fs)
|
||||
exitCode := RunWithOptions(opts)
|
||||
opts := testOpts([]string{
|
||||
testApp, cmdGenerate, "-q", "-f", "-o", testOutput, testDir,
|
||||
}, fs)
|
||||
exitCode := runCLI(opts)
|
||||
require.Equal(t, 0, exitCode)
|
||||
|
||||
// Verify output file exists and was overwritten
|
||||
content, err := afero.ReadFile(fs, "/output.mf")
|
||||
content, err := afero.ReadFile(fs, testOutput)
|
||||
require.NoError(t, err)
|
||||
assert.NotEqual(t, "old content", string(content), "manifest should be overwritten")
|
||||
assert.NotEqual(t, "old content", string(content),
|
||||
"manifest should be overwritten")
|
||||
|
||||
// Verify temp file does NOT exist
|
||||
tmpExists, err := afero.Exists(fs, "/output.mf.tmp")
|
||||
tmpExists, err := afero.Exists(fs, testOutputTmp)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, tmpExists, "temp file should not exist after successful generation")
|
||||
assert.False(t, tmpExists,
|
||||
"temp file should not exist after successful generation")
|
||||
}
|
||||
|
||||
func TestGenerateFailsWithoutForceWhenOutputExists(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
|
||||
// Create test file
|
||||
require.NoError(t, fs.MkdirAll("/testdir", 0o755))
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/file1.txt", []byte("hello"), 0o644))
|
||||
require.NoError(t, fs.MkdirAll(testDir, 0o755))
|
||||
writeTestFile(t, fs, testFile1, "hello")
|
||||
|
||||
// Create existing manifest
|
||||
require.NoError(t, afero.WriteFile(fs, "/output.mf", []byte("existing"), 0o644))
|
||||
writeTestFile(t, fs, testOutput, "existing")
|
||||
|
||||
// Generate manifest WITHOUT --force (should fail)
|
||||
opts := testOpts([]string{"mfer", "generate", "-q", "-o", "/output.mf", "/testdir"}, fs)
|
||||
exitCode := RunWithOptions(opts)
|
||||
opts := testOpts([]string{testApp, cmdGenerate, "-q", "-o", testOutput, testDir}, fs)
|
||||
exitCode := runCLI(opts)
|
||||
assert.Equal(t, 1, exitCode, "should fail when output exists without --force")
|
||||
|
||||
// Verify original content is preserved
|
||||
content, err := afero.ReadFile(fs, "/output.mf")
|
||||
content, err := afero.ReadFile(fs, testOutput)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "existing", string(content), "original file should be preserved")
|
||||
}
|
||||
|
||||
func TestGenerateAtomicWriteUsesTemp(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// This test verifies that generate uses a temp file by checking
|
||||
// that the output file doesn't exist until generation completes.
|
||||
// We do this by generating to a path and verifying the temp file
|
||||
@@ -411,183 +526,239 @@ func TestGenerateAtomicWriteUsesTemp(t *testing.T) {
|
||||
fs := afero.NewMemMapFs()
|
||||
|
||||
// Create test file
|
||||
require.NoError(t, fs.MkdirAll("/testdir", 0o755))
|
||||
require.NoError(t, afero.WriteFile(fs, "/testdir/file1.txt", []byte("hello"), 0o644))
|
||||
require.NoError(t, fs.MkdirAll(testDir, 0o755))
|
||||
writeTestFile(t, fs, testFile1, "hello")
|
||||
|
||||
// Generate manifest
|
||||
opts := testOpts([]string{"mfer", "generate", "-q", "-o", "/output.mf", "/testdir"}, fs)
|
||||
exitCode := RunWithOptions(opts)
|
||||
opts := testOpts([]string{testApp, cmdGenerate, "-q", "-o", testOutput, testDir}, fs)
|
||||
exitCode := runCLI(opts)
|
||||
require.Equal(t, 0, exitCode)
|
||||
|
||||
// Both output file should exist and temp should not
|
||||
exists, _ := afero.Exists(fs, "/output.mf")
|
||||
exists, _ := afero.Exists(fs, testOutput)
|
||||
assert.True(t, exists, "output file should exist")
|
||||
|
||||
tmpExists, _ := afero.Exists(fs, "/output.mf.tmp")
|
||||
tmpExists, _ := afero.Exists(fs, testOutputTmp)
|
||||
assert.False(t, tmpExists, "temp file should be cleaned up")
|
||||
|
||||
// Verify manifest is valid (not empty)
|
||||
content, err := afero.ReadFile(fs, "/output.mf")
|
||||
content, err := afero.ReadFile(fs, testOutput)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, len(content) > 0, "manifest should not be empty")
|
||||
assert.NotEmpty(t, content, "manifest should not be empty")
|
||||
}
|
||||
|
||||
// failingWriterFs wraps a filesystem and makes writes fail after N bytes
|
||||
type failingWriterFs struct {
|
||||
afero.Fs
|
||||
|
||||
failAfter int64
|
||||
written int64
|
||||
}
|
||||
|
||||
type failingFile struct {
|
||||
afero.File
|
||||
|
||||
fs *failingWriterFs
|
||||
}
|
||||
|
||||
func (f *failingFile) Write(p []byte) (int, error) {
|
||||
f.fs.written += int64(len(p))
|
||||
if f.fs.written > f.fs.failAfter {
|
||||
return 0, fmt.Errorf("simulated write failure")
|
||||
return 0, errSimulatedWrite
|
||||
}
|
||||
|
||||
return f.File.Write(p)
|
||||
}
|
||||
|
||||
//nolint:ireturn // Create must return afero.File to satisfy afero.Fs.
|
||||
func (fs *failingWriterFs) Create(name string) (afero.File, error) {
|
||||
f, err := fs.Fs.Create(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &failingFile{File: f, fs: fs}, nil
|
||||
}
|
||||
|
||||
func TestGenerateAtomicWriteCleansUpOnError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
baseFs := afero.NewMemMapFs()
|
||||
|
||||
// Create test files - need enough content to trigger the write failure
|
||||
require.NoError(t, baseFs.MkdirAll("/testdir", 0o755))
|
||||
require.NoError(t, afero.WriteFile(baseFs, "/testdir/file1.txt", []byte("hello world this is a test file"), 0o644))
|
||||
require.NoError(t, baseFs.MkdirAll(testDir, 0o755))
|
||||
writeTestFile(t, baseFs, testFile1, "hello world this is a test file")
|
||||
|
||||
// Wrap with failing writer that fails after writing some bytes
|
||||
fs := &failingWriterFs{Fs: baseFs, failAfter: 10}
|
||||
|
||||
// Generate manifest - should fail during write
|
||||
opts := testOpts([]string{"mfer", "generate", "-q", "-o", "/output.mf", "/testdir"}, fs)
|
||||
exitCode := RunWithOptions(opts)
|
||||
opts := testOpts([]string{testApp, cmdGenerate, "-q", "-o", testOutput, testDir}, fs)
|
||||
exitCode := runCLI(opts)
|
||||
assert.Equal(t, 1, exitCode, "should fail due to write error")
|
||||
|
||||
// With atomic writes: output.mf should NOT exist (temp was cleaned up)
|
||||
// With non-atomic writes: output.mf WOULD exist (partial/empty)
|
||||
exists, _ := afero.Exists(baseFs, "/output.mf")
|
||||
assert.False(t, exists, "output file should not exist after failed generation (atomic write)")
|
||||
exists, _ := afero.Exists(baseFs, testOutput)
|
||||
assert.False(t, exists,
|
||||
"output file should not exist after failed generation (atomic write)")
|
||||
|
||||
// Temp file should also not exist
|
||||
tmpExists, _ := afero.Exists(baseFs, "/output.mf.tmp")
|
||||
assert.False(t, tmpExists, "temp file should be cleaned up after failed generation")
|
||||
tmpExists, _ := afero.Exists(baseFs, testOutputTmp)
|
||||
assert.False(t, tmpExists,
|
||||
"temp file should be cleaned up after failed generation")
|
||||
}
|
||||
|
||||
func TestGenerateValidatesInputPaths(t *testing.T) {
|
||||
fs := afero.NewMemMapFs()
|
||||
t.Parallel()
|
||||
|
||||
// Create one valid directory
|
||||
require.NoError(t, fs.MkdirAll("/validdir", 0o755))
|
||||
require.NoError(t, afero.WriteFile(fs, "/validdir/file.txt", []byte("content"), 0o644))
|
||||
seedValidDir := func(t *testing.T, fs afero.Fs) {
|
||||
t.Helper()
|
||||
|
||||
require.NoError(t, fs.MkdirAll("/validdir", 0o755))
|
||||
writeTestFile(t, fs, "/validdir/file.txt", "content")
|
||||
}
|
||||
|
||||
t.Run("nonexistent path fails fast", func(t *testing.T) {
|
||||
opts := testOpts([]string{"mfer", "generate", "-q", "-o", "/output.mf", "/nonexistent"}, fs)
|
||||
exitCode := RunWithOptions(opts)
|
||||
t.Parallel()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
seedValidDir(t, fs)
|
||||
|
||||
opts := testOpts([]string{
|
||||
testApp, cmdGenerate, "-q", "-o", testOutput, "/nonexistent",
|
||||
}, fs)
|
||||
exitCode := runCLI(opts)
|
||||
assert.Equal(t, 1, exitCode)
|
||||
stderr := opts.Stderr.(*bytes.Buffer).String()
|
||||
|
||||
stderr := testStderr(t, opts)
|
||||
assert.Contains(t, stderr, "path does not exist")
|
||||
assert.Contains(t, stderr, "/nonexistent")
|
||||
})
|
||||
|
||||
t.Run("mix of valid and invalid paths fails fast", func(t *testing.T) {
|
||||
opts := testOpts([]string{"mfer", "generate", "-q", "-o", "/output.mf", "/validdir", "/alsononexistent"}, fs)
|
||||
exitCode := RunWithOptions(opts)
|
||||
t.Parallel()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
seedValidDir(t, fs)
|
||||
|
||||
opts := testOpts([]string{
|
||||
testApp, cmdGenerate, "-q", "-o", testOutput,
|
||||
"/validdir", "/alsononexistent",
|
||||
}, fs)
|
||||
exitCode := runCLI(opts)
|
||||
assert.Equal(t, 1, exitCode)
|
||||
stderr := opts.Stderr.(*bytes.Buffer).String()
|
||||
|
||||
stderr := testStderr(t, opts)
|
||||
assert.Contains(t, stderr, "path does not exist")
|
||||
assert.Contains(t, stderr, "/alsononexistent")
|
||||
|
||||
// Output file should not have been created
|
||||
exists, _ := afero.Exists(fs, "/output.mf")
|
||||
assert.False(t, exists, "output file should not exist when path validation fails")
|
||||
exists, _ := afero.Exists(fs, testOutput)
|
||||
assert.False(t, exists,
|
||||
"output file should not exist when path validation fails")
|
||||
})
|
||||
|
||||
t.Run("valid paths succeed", func(t *testing.T) {
|
||||
opts := testOpts([]string{"mfer", "generate", "-q", "-o", "/output.mf", "/validdir"}, fs)
|
||||
exitCode := RunWithOptions(opts)
|
||||
t.Parallel()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
seedValidDir(t, fs)
|
||||
|
||||
opts := testOpts([]string{
|
||||
testApp, cmdGenerate, "-q", "-o", testOutput, "/validdir",
|
||||
}, fs)
|
||||
exitCode := runCLI(opts)
|
||||
assert.Equal(t, 0, exitCode)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCheckDetectsManifestCorruption(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
rng := rand.New(rand.NewSource(42))
|
||||
rng := rand.New(rand.NewSource(42)) //nolint:gosec // deterministic test data
|
||||
|
||||
// Create many small files with random names to generate a ~1MB manifest
|
||||
// Each manifest entry is roughly 50-60 bytes, so we need ~20000 files
|
||||
require.NoError(t, fs.MkdirAll("/testdir", 0o755))
|
||||
require.NoError(t, fs.MkdirAll(testDir, 0o755))
|
||||
|
||||
numFiles := 20000
|
||||
for i := 0; i < numFiles; i++ {
|
||||
for range numFiles {
|
||||
// Generate random filename
|
||||
filename := fmt.Sprintf("/testdir/%08x%08x%08x.dat", rng.Uint32(), rng.Uint32(), rng.Uint32())
|
||||
filename := fmt.Sprintf("/testdir/%08x%08x%08x.dat",
|
||||
rng.Uint32(), rng.Uint32(), rng.Uint32())
|
||||
// Small random content
|
||||
content := make([]byte, 16+rng.Intn(48))
|
||||
rng.Read(content)
|
||||
_, _ = rng.Read(content)
|
||||
require.NoError(t, afero.WriteFile(fs, filename, content, 0o644))
|
||||
}
|
||||
|
||||
// Generate manifest outside of testdir
|
||||
opts := testOpts([]string{"mfer", "generate", "-q", "-o", "/manifest.mf", "/testdir"}, fs)
|
||||
exitCode := RunWithOptions(opts)
|
||||
opts := testOpts([]string{testApp, cmdGenerate, "-q", "-o", testManifest, testDir}, fs)
|
||||
exitCode := runCLI(opts)
|
||||
require.Equal(t, 0, exitCode, "generate should succeed")
|
||||
|
||||
// Read the valid manifest and verify it's approximately 1MB
|
||||
validManifest, err := afero.ReadFile(fs, "/manifest.mf")
|
||||
validManifest, err := afero.ReadFile(fs, testManifest)
|
||||
require.NoError(t, err)
|
||||
require.True(t, len(validManifest) >= 1024*1024, "manifest should be at least 1MB, got %d bytes", len(validManifest))
|
||||
require.GreaterOrEqual(t, len(validManifest), 1024*1024,
|
||||
"manifest should be at least 1MB, got %d bytes", len(validManifest))
|
||||
t.Logf("manifest size: %d bytes (%d files)", len(validManifest), numFiles)
|
||||
|
||||
// First corruption: truncate the manifest
|
||||
require.NoError(t, afero.WriteFile(fs, "/manifest.mf", validManifest[:len(validManifest)/2], 0o644))
|
||||
require.NoError(t, afero.WriteFile(fs, testManifest,
|
||||
validManifest[:len(validManifest)/2], 0o644))
|
||||
|
||||
// Check should fail with truncated manifest
|
||||
opts = testOpts([]string{"mfer", "check", "-q", "--base", "/testdir", "/manifest.mf"}, fs)
|
||||
exitCode = RunWithOptions(opts)
|
||||
opts = testOpts([]string{
|
||||
testApp, cmdCheck, "-q", testFlagBase, testDir, testManifest,
|
||||
}, fs)
|
||||
exitCode = runCLI(opts)
|
||||
assert.Equal(t, 1, exitCode, "check should fail with truncated manifest")
|
||||
|
||||
// Verify check passes with valid manifest
|
||||
require.NoError(t, afero.WriteFile(fs, "/manifest.mf", validManifest, 0o644))
|
||||
opts = testOpts([]string{"mfer", "check", "-q", "--base", "/testdir", "/manifest.mf"}, fs)
|
||||
exitCode = RunWithOptions(opts)
|
||||
require.NoError(t, afero.WriteFile(fs, testManifest, validManifest, 0o644))
|
||||
|
||||
opts = testOpts([]string{
|
||||
testApp, cmdCheck, "-q", testFlagBase, testDir, testManifest,
|
||||
}, fs)
|
||||
exitCode = runCLI(opts)
|
||||
require.Equal(t, 0, exitCode, "check should pass with valid manifest")
|
||||
|
||||
// Now do 500 random corruption iterations
|
||||
for i := 0; i < 500; i++ {
|
||||
for i := range 500 {
|
||||
// Corrupt: write a random byte at a random offset
|
||||
corrupted := make([]byte, len(validManifest))
|
||||
copy(corrupted, validManifest)
|
||||
|
||||
offset := rng.Intn(len(corrupted))
|
||||
originalByte := corrupted[offset]
|
||||
|
||||
// Make sure we actually change the byte
|
||||
newByte := byte(rng.Intn(256))
|
||||
buf := make([]byte, 1)
|
||||
|
||||
newByte := originalByte
|
||||
for newByte == originalByte {
|
||||
newByte = byte(rng.Intn(256))
|
||||
_, _ = rng.Read(buf)
|
||||
newByte = buf[0]
|
||||
}
|
||||
|
||||
corrupted[offset] = newByte
|
||||
|
||||
require.NoError(t, afero.WriteFile(fs, "/manifest.mf", corrupted, 0o644))
|
||||
require.NoError(t, afero.WriteFile(fs, testManifest, corrupted, 0o644))
|
||||
|
||||
// Check should fail with corrupted manifest
|
||||
opts = testOpts([]string{"mfer", "check", "-q", "--base", "/testdir", "/manifest.mf"}, fs)
|
||||
exitCode = RunWithOptions(opts)
|
||||
assert.Equal(t, 1, exitCode, "iteration %d: check should fail with corrupted manifest (offset %d, 0x%02x -> 0x%02x)",
|
||||
opts = testOpts([]string{
|
||||
testApp, cmdCheck, "-q", testFlagBase, testDir, testManifest,
|
||||
}, fs)
|
||||
exitCode = runCLI(opts)
|
||||
assert.Equal(t, 1, exitCode,
|
||||
"iteration %d: check should fail with corrupted manifest "+
|
||||
"(offset %d, 0x%02x -> 0x%02x)",
|
||||
i, offset, originalByte, newByte)
|
||||
|
||||
// Restore valid manifest for next iteration
|
||||
require.NoError(t, afero.WriteFile(fs, "/manifest.mf", validManifest, 0o644))
|
||||
require.NoError(t, afero.WriteFile(fs, testManifest, validManifest, 0o644))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user