Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97875a82bc | ||
|
|
0a9963002c |
@@ -295,7 +295,11 @@ func (mfa *CLIApp) checkManifestOperation(ctx *cli.Context) error {
|
|||||||
log.Infof("checking manifest %s with base %s", manifestPath, basePath)
|
log.Infof("checking manifest %s with base %s", manifestPath, basePath)
|
||||||
|
|
||||||
// Create checker
|
// Create checker
|
||||||
chk, err := mfer.NewChecker(manifestPath, basePath, mfa.Fs)
|
chk, err := mfer.NewChecker(&mfer.CheckerOptions{
|
||||||
|
ManifestPath: manifestPath,
|
||||||
|
BasePath: basePath,
|
||||||
|
Fs: mfa.Fs,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load manifest: %w", err)
|
return fmt.Errorf("failed to load manifest: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -505,7 +505,10 @@ func TestGenerateExcludesDotfilesByDefault(t *testing.T) {
|
|||||||
assert.True(t, exists)
|
assert.True(t, exists)
|
||||||
|
|
||||||
// Verify manifest only has 1 file (the non-dotfile)
|
// Verify manifest only has 1 file (the non-dotfile)
|
||||||
manifest, err := mfer.NewManifestFromFile(fs, testMF)
|
manifest, err := mfer.NewManifestFromFile(&mfer.ManifestFromFileOptions{
|
||||||
|
Path: testMF,
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, manifest.Files(), 1)
|
assert.Len(t, manifest.Files(), 1)
|
||||||
assert.Equal(t, "file1.txt", manifest.Files()[0].GetPath())
|
assert.Equal(t, "file1.txt", manifest.Files()[0].GetPath())
|
||||||
@@ -529,7 +532,10 @@ func TestGenerateWithIncludeDotfiles(t *testing.T) {
|
|||||||
require.Equal(t, 0, exitCode)
|
require.Equal(t, 0, exitCode)
|
||||||
|
|
||||||
// Verify manifest has 2 files (including dotfile)
|
// Verify manifest has 2 files (including dotfile)
|
||||||
manifest, err := mfer.NewManifestFromFile(fs, testMF)
|
manifest, err := mfer.NewManifestFromFile(&mfer.ManifestFromFileOptions{
|
||||||
|
Path: testMF,
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, manifest.Files(), 2)
|
assert.Len(t, manifest.Files(), 2)
|
||||||
}
|
}
|
||||||
@@ -742,7 +748,10 @@ func TestGenerateFailsWithoutForceWhenOutputExists(t *testing.T) {
|
|||||||
func manifestPaths(t *testing.T, fs afero.Fs, path string) []string {
|
func manifestPaths(t *testing.T, fs afero.Fs, path string) []string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
manifest, err := mfer.NewManifestFromFile(fs, path)
|
manifest, err := mfer.NewManifestFromFile(&mfer.ManifestFromFileOptions{
|
||||||
|
Path: path,
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
paths := make([]string, 0, len(manifest.Files()))
|
paths := make([]string, 0, len(manifest.Files()))
|
||||||
@@ -812,6 +821,26 @@ func TestGenerateDefaultOutputLeftOutOfListing(t *testing.T) {
|
|||||||
assert.Equal(t, []string{testFileTxt}, manifestPaths(t, fs, "index.mf"))
|
assert.Equal(t, []string{testFileTxt}, manifestPaths(t, fs, "index.mf"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestGenerateLeavesLeftoverTempFileOutOfListing runs gen where an
|
||||||
|
// interrupted run left its temp file, index.mf.tmp, beside the output:
|
||||||
|
// the leftover is not listed, and gen does not fail when it overwrites
|
||||||
|
// it.
|
||||||
|
func TestGenerateLeavesLeftoverTempFileOutOfListing(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
root := t.TempDir()
|
||||||
|
output := filepath.Join(root, "index.mf")
|
||||||
|
|
||||||
|
fs := afero.NewOsFs()
|
||||||
|
writeTestFile(t, fs, filepath.Join(root, testFileTxt), "hello")
|
||||||
|
writeTestFile(t, fs, output+".tmp", "part of a manifest")
|
||||||
|
|
||||||
|
opts := testOpts([]string{testApp, cmdGenerate, "-q", "-o", output, root}, fs)
|
||||||
|
require.Equal(t, 0, runCLI(opts), "stderr: %s", testStderr(t, opts))
|
||||||
|
|
||||||
|
assert.Equal(t, []string{testFileTxt}, manifestPaths(t, fs, output))
|
||||||
|
}
|
||||||
|
|
||||||
func TestGenerateAtomicWriteUsesTemp(t *testing.T) {
|
func TestGenerateAtomicWriteUsesTemp(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,11 @@ func unsignedChecker(t *testing.T) *mfer.Checker {
|
|||||||
require.NoError(t, s.ToManifest(context.Background(), &buf, nil))
|
require.NoError(t, s.ToManifest(context.Background(), &buf, nil))
|
||||||
require.NoError(t, afero.WriteFile(fs, "/d/index.mf", buf.Bytes(), 0o644))
|
require.NoError(t, afero.WriteFile(fs, "/d/index.mf", buf.Bytes(), 0o644))
|
||||||
|
|
||||||
chk, err := mfer.NewChecker("/d/index.mf", "/d", fs)
|
chk, err := mfer.NewChecker(&mfer.CheckerOptions{
|
||||||
|
ManifestPath: "/d/index.mf",
|
||||||
|
BasePath: "/d",
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.False(t, chk.IsSigned())
|
require.False(t, chk.IsSigned())
|
||||||
|
|
||||||
@@ -167,7 +171,11 @@ func signedChecker(t *testing.T) *mfer.Checker {
|
|||||||
fs := afero.NewMemMapFs()
|
fs := afero.NewMemMapFs()
|
||||||
require.NoError(t, afero.WriteFile(fs, "/index.mf", buf.Bytes(), 0o644))
|
require.NoError(t, afero.WriteFile(fs, "/index.mf", buf.Bytes(), 0o644))
|
||||||
|
|
||||||
chk, err := mfer.NewChecker("/index.mf", "/", fs)
|
chk, err := mfer.NewChecker(&mfer.CheckerOptions{
|
||||||
|
ManifestPath: "/index.mf",
|
||||||
|
BasePath: "/",
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.True(t, chk.IsSigned())
|
require.True(t, chk.IsSigned())
|
||||||
|
|
||||||
|
|||||||
+20
-4
@@ -58,6 +58,7 @@ type freshenScanner struct {
|
|||||||
fs afero.Fs
|
fs afero.Fs
|
||||||
absBase string
|
absBase string
|
||||||
manifestInfo fs.FileInfo // the manifest being freshened
|
manifestInfo fs.FileInfo // the manifest being freshened
|
||||||
|
tmpInfo fs.FileInfo // its leftover temp file; nil when there is none
|
||||||
includeDotfiles bool
|
includeDotfiles bool
|
||||||
followSymlinks bool
|
followSymlinks bool
|
||||||
showProgress bool
|
showProgress bool
|
||||||
@@ -181,9 +182,10 @@ func (s *freshenScanner) walk(path string, info fs.FileInfo, walkErr error) erro
|
|||||||
info = realInfo
|
info = realInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip the manifest file itself, however its path is spelled; gen
|
// Skip the manifest file itself and a temp file left beside it by an
|
||||||
// leaves out its output file the same way.
|
// interrupted run, however their paths are spelled; gen leaves out its
|
||||||
if os.SameFile(info, s.manifestInfo) {
|
// output file the same way. os.SameFile never matches a nil FileInfo.
|
||||||
|
if os.SameFile(info, s.manifestInfo) || os.SameFile(info, s.tmpInfo) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,10 +373,21 @@ func (mfa *CLIApp) freshenScan(
|
|||||||
return nil, 0, fmt.Errorf("freshen: %w", err)
|
return nil, 0, fmt.Errorf("freshen: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The temp file writeFreshenedManifest writes, if an interrupted run
|
||||||
|
// left one. One that cannot be stat'd, normally because it does not
|
||||||
|
// exist, needs no leaving out.
|
||||||
|
var tmpInfo fs.FileInfo
|
||||||
|
|
||||||
|
info, err := mfa.Fs.Stat(manifestPath + ".tmp")
|
||||||
|
if err == nil {
|
||||||
|
tmpInfo = info
|
||||||
|
}
|
||||||
|
|
||||||
scanner := &freshenScanner{
|
scanner := &freshenScanner{
|
||||||
fs: mfa.Fs,
|
fs: mfa.Fs,
|
||||||
absBase: absBase,
|
absBase: absBase,
|
||||||
manifestInfo: manifestInfo,
|
manifestInfo: manifestInfo,
|
||||||
|
tmpInfo: tmpInfo,
|
||||||
includeDotfiles: ctx.Bool("include-dotfiles"),
|
includeDotfiles: ctx.Bool("include-dotfiles"),
|
||||||
followSymlinks: ctx.Bool("follow-symlinks"),
|
followSymlinks: ctx.Bool("follow-symlinks"),
|
||||||
showProgress: showProgress,
|
showProgress: showProgress,
|
||||||
@@ -452,7 +465,10 @@ func (mfa *CLIApp) loadExistingEntries(
|
|||||||
log.Infof("loading manifest from %s", manifestPath)
|
log.Infof("loading manifest from %s", manifestPath)
|
||||||
|
|
||||||
// Load existing manifest
|
// Load existing manifest
|
||||||
manifest, err := mfer.NewManifestFromFile(mfa.Fs, manifestPath)
|
manifest, err := mfer.NewManifestFromFile(&mfer.ManifestFromFileOptions{
|
||||||
|
Path: manifestPath,
|
||||||
|
Fs: mfa.Fs,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to load manifest: %w", err)
|
return nil, fmt.Errorf("failed to load manifest: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,10 @@ func TestFreshenUnchanged(t *testing.T) {
|
|||||||
setupFreshenDir(t, fs)
|
setupFreshenDir(t, fs)
|
||||||
|
|
||||||
// Parse manifest to verify
|
// Parse manifest to verify
|
||||||
manifest, err := mfer.NewManifestFromFile(fs, "/testdir/index.mf")
|
manifest, err := mfer.NewManifestFromFile(&mfer.ManifestFromFileOptions{
|
||||||
|
Path: "/testdir/index.mf",
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, manifest.Files(), 2)
|
assert.Len(t, manifest.Files(), 2)
|
||||||
}
|
}
|
||||||
@@ -71,7 +74,10 @@ func TestFreshenWithChanges(t *testing.T) {
|
|||||||
setupFreshenDir(t, fs)
|
setupFreshenDir(t, fs)
|
||||||
|
|
||||||
// Verify initial manifest has 2 files
|
// Verify initial manifest has 2 files
|
||||||
manifest, err := mfer.NewManifestFromFile(fs, "/testdir/index.mf")
|
manifest, err := mfer.NewManifestFromFile(&mfer.ManifestFromFileOptions{
|
||||||
|
Path: "/testdir/index.mf",
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, manifest.Files(), 2)
|
assert.Len(t, manifest.Files(), 2)
|
||||||
|
|
||||||
@@ -126,6 +132,34 @@ func TestFreshenLeavesManifestOutOfListing(t *testing.T) {
|
|||||||
manifestPaths(t, fs, manifestPath))
|
manifestPaths(t, fs, manifestPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestFreshenLeavesLeftoverTempFileOutOfListing freshens a manifest where
|
||||||
|
// an interrupted run left its temp file, index.mf.tmp, beside it: the
|
||||||
|
// leftover is not listed.
|
||||||
|
func TestFreshenLeavesLeftoverTempFileOutOfListing(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
root := t.TempDir()
|
||||||
|
manifestPath := filepath.Join(root, "index.mf")
|
||||||
|
|
||||||
|
fs := afero.NewOsFs()
|
||||||
|
writeTestFile(t, fs, filepath.Join(root, testFileTxt), "hello")
|
||||||
|
|
||||||
|
opts := testOpts([]string{testApp, cmdGenerate, "-q", "-o", manifestPath, root}, fs)
|
||||||
|
require.Equal(t, 0, runCLI(opts), "stderr: %s", testStderr(t, opts))
|
||||||
|
|
||||||
|
writeTestFile(t, fs, manifestPath+".tmp", "part of a manifest")
|
||||||
|
// A new file gives freshen something to write.
|
||||||
|
writeTestFile(t, fs, filepath.Join(root, "added.txt"), "added")
|
||||||
|
|
||||||
|
opts = testOpts([]string{
|
||||||
|
testApp, "freshen", "-q", testFlagBase, root, manifestPath,
|
||||||
|
}, fs)
|
||||||
|
require.Equal(t, 0, runCLI(opts), "stderr: %s", testStderr(t, opts))
|
||||||
|
|
||||||
|
assert.ElementsMatch(t, []string{testFileTxt, "added.txt"},
|
||||||
|
manifestPaths(t, fs, manifestPath))
|
||||||
|
}
|
||||||
|
|
||||||
// TestFreshenRecordEntryMtimePresence pins the behavior of recordEntry
|
// TestFreshenRecordEntryMtimePresence pins the behavior of recordEntry
|
||||||
// with respect to MFFilePath.Mtime, which is a message pointer with
|
// with respect to MFFilePath.Mtime, which is a message pointer with
|
||||||
// proto3 field presence and may legitimately be absent.
|
// proto3 field presence and may legitimately be absent.
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/afero"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -397,6 +399,29 @@ func TestNewManifestFromReaderTruncated(t *testing.T) {
|
|||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewManifestFromFileRequiresPath(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
_, err := NewManifestFromFile(nil)
|
||||||
|
require.ErrorIs(t, err, errManifestPathEmpty)
|
||||||
|
|
||||||
|
_, err = NewManifestFromFile(&ManifestFromFileOptions{Fs: afero.NewMemMapFs()})
|
||||||
|
require.ErrorIs(t, err, errManifestPathEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewManifestFromFileNilFsUsesOsFs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
path := filepath.Join(t.TempDir(), "index.mf")
|
||||||
|
createTestManifest(t, afero.NewOsFs(), path, map[string][]byte{
|
||||||
|
testFileName: []byte("hello"),
|
||||||
|
})
|
||||||
|
|
||||||
|
m, err := NewManifestFromFile(&ManifestFromFileOptions{Path: path})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Len(t, m.Files(), 1)
|
||||||
|
}
|
||||||
|
|
||||||
func TestManifestString(t *testing.T) {
|
func TestManifestString(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
+34
-8
@@ -14,7 +14,11 @@ import (
|
|||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errNoSigningPubKey = errors.New("manifest has no signing public key")
|
var (
|
||||||
|
errNoSigningPubKey = errors.New("manifest has no signing public key")
|
||||||
|
errManifestPathEmpty = errors.New("manifest path cannot be empty")
|
||||||
|
errBasePathEmpty = errors.New("base path cannot be empty")
|
||||||
|
)
|
||||||
|
|
||||||
// Result represents the outcome of checking a single file.
|
// Result represents the outcome of checking a single file.
|
||||||
type Result struct {
|
type Result struct {
|
||||||
@@ -82,20 +86,42 @@ type Checker struct {
|
|||||||
signingPubKey []byte
|
signingPubKey []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewChecker creates a new Checker for the given manifest, base path, and filesystem.
|
// CheckerOptions configures a Checker.
|
||||||
// The basePath is the directory relative to which manifest paths are resolved.
|
type CheckerOptions struct {
|
||||||
// If fs is nil, the real filesystem (OsFs) is used.
|
// ManifestPath is the manifest file to check against (required).
|
||||||
func NewChecker(manifestPath string, basePath string, fs afero.Fs) (*Checker, error) {
|
ManifestPath string
|
||||||
|
// BasePath is the directory relative to which manifest paths are
|
||||||
|
// resolved (required).
|
||||||
|
BasePath string
|
||||||
|
// Fs is the filesystem to use, defaults to OsFs if nil.
|
||||||
|
Fs afero.Fs
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewChecker creates a new Checker with the given options. It returns an
|
||||||
|
// error if opts is nil or either path is empty.
|
||||||
|
func NewChecker(opts *CheckerOptions) (*Checker, error) {
|
||||||
|
if opts == nil || opts.ManifestPath == "" {
|
||||||
|
return nil, errManifestPathEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.BasePath == "" {
|
||||||
|
return nil, errBasePathEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
fs := opts.Fs
|
||||||
if fs == nil {
|
if fs == nil {
|
||||||
fs = afero.NewOsFs()
|
fs = afero.NewOsFs()
|
||||||
}
|
}
|
||||||
|
|
||||||
m, err := NewManifestFromFile(fs, manifestPath)
|
m, err := NewManifestFromFile(&ManifestFromFileOptions{
|
||||||
|
Path: opts.ManifestPath,
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
abs, err := filepath.Abs(basePath)
|
abs, err := filepath.Abs(opts.BasePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -108,7 +134,7 @@ func NewChecker(manifestPath string, basePath string, fs afero.Fs) (*Checker, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compute manifest's relative path from basePath for exclusion in FindExtraFiles
|
// Compute manifest's relative path from basePath for exclusion in FindExtraFiles
|
||||||
absManifest, err := filepath.Abs(manifestPath)
|
absManifest, err := filepath.Abs(opts.ManifestPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+195
-49
@@ -5,6 +5,8 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -14,9 +16,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
testFile1 = "file1.txt"
|
testFile1 = "file1.txt"
|
||||||
testFile2 = "file2.txt"
|
testFile2 = "file2.txt"
|
||||||
testExistsFile = "exists.txt"
|
testExistsFile = "exists.txt"
|
||||||
|
testManifestPath = "/manifest.mf"
|
||||||
|
testDataDir = "/data"
|
||||||
|
// testDataManifestPath is a manifest kept inside the checked tree.
|
||||||
|
testDataManifestPath = testDataDir + "/index.mf"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStatusString(t *testing.T) {
|
func TestStatusString(t *testing.T) {
|
||||||
@@ -66,15 +72,13 @@ func createTestManifest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// createFilesOnDisk creates the given files on the filesystem under
|
// createFilesOnDisk creates the given files on the filesystem under
|
||||||
// /data.
|
// testDataDir.
|
||||||
func createFilesOnDisk(t *testing.T, fs afero.Fs, files map[string][]byte) {
|
func createFilesOnDisk(t *testing.T, fs afero.Fs, files map[string][]byte) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
basePath := "/data"
|
|
||||||
|
|
||||||
for path, content := range files {
|
for path, content := range files {
|
||||||
fullPath := basePath + "/" + path
|
fullPath := testDataDir + "/" + path
|
||||||
require.NoError(t, fs.MkdirAll(basePath, 0o755))
|
require.NoError(t, fs.MkdirAll(testDataDir, 0o755))
|
||||||
require.NoError(t, afero.WriteFile(fs, fullPath, content, 0o644))
|
require.NoError(t, afero.WriteFile(fs, fullPath, content, 0o644))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,9 +94,13 @@ func TestNewChecker(t *testing.T) {
|
|||||||
testFile1: []byte("hello"),
|
testFile1: []byte("hello"),
|
||||||
testFile2: []byte("world"),
|
testFile2: []byte("world"),
|
||||||
}
|
}
|
||||||
createTestManifest(t, fs, "/manifest.mf", files)
|
createTestManifest(t, fs, testManifestPath, files)
|
||||||
|
|
||||||
chk, err := NewChecker("/manifest.mf", "/", fs)
|
chk, err := NewChecker(&CheckerOptions{
|
||||||
|
ManifestPath: testManifestPath,
|
||||||
|
BasePath: "/",
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NotNil(t, chk)
|
assert.NotNil(t, chk)
|
||||||
assert.Equal(t, FileCount(2), chk.FileCount())
|
assert.Equal(t, FileCount(2), chk.FileCount())
|
||||||
@@ -102,7 +110,11 @@ func TestNewChecker(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
fs := afero.NewMemMapFs()
|
fs := afero.NewMemMapFs()
|
||||||
_, err := NewChecker("/nonexistent.mf", "/", fs)
|
_, err := NewChecker(&CheckerOptions{
|
||||||
|
ManifestPath: "/nonexistent.mf",
|
||||||
|
BasePath: "/",
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -111,11 +123,73 @@ func TestNewChecker(t *testing.T) {
|
|||||||
|
|
||||||
fs := afero.NewMemMapFs()
|
fs := afero.NewMemMapFs()
|
||||||
require.NoError(t, afero.WriteFile(fs, "/bad.mf", []byte("not a manifest"), 0o644))
|
require.NoError(t, afero.WriteFile(fs, "/bad.mf", []byte("not a manifest"), 0o644))
|
||||||
_, err := NewChecker("/bad.mf", "/", fs)
|
_, err := NewChecker(&CheckerOptions{
|
||||||
|
ManifestPath: "/bad.mf",
|
||||||
|
BasePath: "/",
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewCheckerRequiredPaths(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
opts *CheckerOptions
|
||||||
|
want string
|
||||||
|
is error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil options",
|
||||||
|
opts: nil,
|
||||||
|
want: "manifest path cannot be empty",
|
||||||
|
is: errManifestPathEmpty,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty manifest path",
|
||||||
|
opts: &CheckerOptions{BasePath: testDataDir},
|
||||||
|
want: "manifest path cannot be empty",
|
||||||
|
is: errManifestPathEmpty,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty base path",
|
||||||
|
opts: &CheckerOptions{ManifestPath: testManifestPath},
|
||||||
|
want: "base path cannot be empty",
|
||||||
|
is: errBasePathEmpty,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
chk, err := NewChecker(tc.opts)
|
||||||
|
require.ErrorIs(t, err, tc.is)
|
||||||
|
require.EqualError(t, err, tc.want)
|
||||||
|
assert.Nil(t, chk)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewCheckerNilFsUsesOsFs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dir := t.TempDir()
|
||||||
|
manifestPath := filepath.Join(dir, "index.mf")
|
||||||
|
content := []byte("hello")
|
||||||
|
createTestManifest(t, afero.NewOsFs(), manifestPath, map[string][]byte{
|
||||||
|
testFile1: content,
|
||||||
|
})
|
||||||
|
require.NoError(t, os.WriteFile(filepath.Join(dir, testFile1), content, 0o600))
|
||||||
|
|
||||||
|
chk, err := NewChecker(&CheckerOptions{ManifestPath: manifestPath, BasePath: dir})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
results := make(chan Result, 1)
|
||||||
|
require.NoError(t, chk.Check(context.Background(), results, nil))
|
||||||
|
assert.Equal(t, StatusOK, (<-results).Status)
|
||||||
|
}
|
||||||
|
|
||||||
func TestCheckerFileCountAndTotalBytes(t *testing.T) {
|
func TestCheckerFileCountAndTotalBytes(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -125,9 +199,13 @@ func TestCheckerFileCountAndTotalBytes(t *testing.T) {
|
|||||||
"medium.txt": []byte("hello world"),
|
"medium.txt": []byte("hello world"),
|
||||||
"large.txt": bytes.Repeat([]byte("x"), 1000),
|
"large.txt": bytes.Repeat([]byte("x"), 1000),
|
||||||
}
|
}
|
||||||
createTestManifest(t, fs, "/manifest.mf", files)
|
createTestManifest(t, fs, testManifestPath, files)
|
||||||
|
|
||||||
chk, err := NewChecker("/manifest.mf", "/", fs)
|
chk, err := NewChecker(&CheckerOptions{
|
||||||
|
ManifestPath: testManifestPath,
|
||||||
|
BasePath: "/",
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, FileCount(3), chk.FileCount())
|
assert.Equal(t, FileCount(3), chk.FileCount())
|
||||||
@@ -142,10 +220,14 @@ func TestCheckAllFilesOK(t *testing.T) {
|
|||||||
testFile1: []byte("content one"),
|
testFile1: []byte("content one"),
|
||||||
testFile2: []byte("content two"),
|
testFile2: []byte("content two"),
|
||||||
}
|
}
|
||||||
createTestManifest(t, fs, "/manifest.mf", files)
|
createTestManifest(t, fs, testManifestPath, files)
|
||||||
createFilesOnDisk(t, fs, files)
|
createFilesOnDisk(t, fs, files)
|
||||||
|
|
||||||
chk, err := NewChecker("/manifest.mf", "/data", fs)
|
chk, err := NewChecker(&CheckerOptions{
|
||||||
|
ManifestPath: testManifestPath,
|
||||||
|
BasePath: testDataDir,
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
results := make(chan Result, 10)
|
results := make(chan Result, 10)
|
||||||
@@ -172,13 +254,17 @@ func TestCheckMissingFile(t *testing.T) {
|
|||||||
testExistsFile: []byte("I exist"),
|
testExistsFile: []byte("I exist"),
|
||||||
"missing.txt": []byte("I don't exist on disk"),
|
"missing.txt": []byte("I don't exist on disk"),
|
||||||
}
|
}
|
||||||
createTestManifest(t, fs, "/manifest.mf", files)
|
createTestManifest(t, fs, testManifestPath, files)
|
||||||
// Only create one file
|
// Only create one file
|
||||||
createFilesOnDisk(t, fs, map[string][]byte{
|
createFilesOnDisk(t, fs, map[string][]byte{
|
||||||
testExistsFile: []byte("I exist"),
|
testExistsFile: []byte("I exist"),
|
||||||
})
|
})
|
||||||
|
|
||||||
chk, err := NewChecker("/manifest.mf", "/data", fs)
|
chk, err := NewChecker(&CheckerOptions{
|
||||||
|
ManifestPath: testManifestPath,
|
||||||
|
BasePath: testDataDir,
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
results := make(chan Result, 10)
|
results := make(chan Result, 10)
|
||||||
@@ -211,13 +297,17 @@ func TestCheckSizeMismatch(t *testing.T) {
|
|||||||
files := map[string][]byte{
|
files := map[string][]byte{
|
||||||
testFileName: []byte("original content"),
|
testFileName: []byte("original content"),
|
||||||
}
|
}
|
||||||
createTestManifest(t, fs, "/manifest.mf", files)
|
createTestManifest(t, fs, testManifestPath, files)
|
||||||
// Create file with different size
|
// Create file with different size
|
||||||
createFilesOnDisk(t, fs, map[string][]byte{
|
createFilesOnDisk(t, fs, map[string][]byte{
|
||||||
testFileName: []byte("short"),
|
testFileName: []byte("short"),
|
||||||
})
|
})
|
||||||
|
|
||||||
chk, err := NewChecker("/manifest.mf", "/data", fs)
|
chk, err := NewChecker(&CheckerOptions{
|
||||||
|
ManifestPath: testManifestPath,
|
||||||
|
BasePath: testDataDir,
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
results := make(chan Result, 10)
|
results := make(chan Result, 10)
|
||||||
@@ -237,7 +327,7 @@ func TestCheckHashMismatch(t *testing.T) {
|
|||||||
files := map[string][]byte{
|
files := map[string][]byte{
|
||||||
testFileName: originalContent,
|
testFileName: originalContent,
|
||||||
}
|
}
|
||||||
createTestManifest(t, fs, "/manifest.mf", files)
|
createTestManifest(t, fs, testManifestPath, files)
|
||||||
// Create file with same size but different content
|
// Create file with same size but different content
|
||||||
differentContent := []byte("different contnt") // same length (16 bytes) but different
|
differentContent := []byte("different contnt") // same length (16 bytes) but different
|
||||||
require.Len(t, differentContent, len(originalContent), "test requires same length")
|
require.Len(t, differentContent, len(originalContent), "test requires same length")
|
||||||
@@ -245,7 +335,11 @@ func TestCheckHashMismatch(t *testing.T) {
|
|||||||
testFileName: differentContent,
|
testFileName: differentContent,
|
||||||
})
|
})
|
||||||
|
|
||||||
chk, err := NewChecker("/manifest.mf", "/data", fs)
|
chk, err := NewChecker(&CheckerOptions{
|
||||||
|
ManifestPath: testManifestPath,
|
||||||
|
BasePath: testDataDir,
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
results := make(chan Result, 10)
|
results := make(chan Result, 10)
|
||||||
@@ -265,10 +359,14 @@ func TestCheckWithProgress(t *testing.T) {
|
|||||||
testFile1: bytes.Repeat([]byte("a"), 100),
|
testFile1: bytes.Repeat([]byte("a"), 100),
|
||||||
testFile2: bytes.Repeat([]byte("b"), 200),
|
testFile2: bytes.Repeat([]byte("b"), 200),
|
||||||
}
|
}
|
||||||
createTestManifest(t, fs, "/manifest.mf", files)
|
createTestManifest(t, fs, testManifestPath, files)
|
||||||
createFilesOnDisk(t, fs, files)
|
createFilesOnDisk(t, fs, files)
|
||||||
|
|
||||||
chk, err := NewChecker("/manifest.mf", "/data", fs)
|
chk, err := NewChecker(&CheckerOptions{
|
||||||
|
ManifestPath: testManifestPath,
|
||||||
|
BasePath: testDataDir,
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
results := make(chan Result, 10)
|
results := make(chan Result, 10)
|
||||||
@@ -305,10 +403,14 @@ func TestCheckContextCancellation(t *testing.T) {
|
|||||||
files[string(rune('a'+i%26))+".txt"] = bytes.Repeat([]byte("x"), 1000)
|
files[string(rune('a'+i%26))+".txt"] = bytes.Repeat([]byte("x"), 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
createTestManifest(t, fs, "/manifest.mf", files)
|
createTestManifest(t, fs, testManifestPath, files)
|
||||||
createFilesOnDisk(t, fs, files)
|
createFilesOnDisk(t, fs, files)
|
||||||
|
|
||||||
chk, err := NewChecker("/manifest.mf", "/data", fs)
|
chk, err := NewChecker(&CheckerOptions{
|
||||||
|
ManifestPath: testManifestPath,
|
||||||
|
BasePath: testDataDir,
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
@@ -327,7 +429,7 @@ func TestFindExtraFiles(t *testing.T) {
|
|||||||
manifestFiles := map[string][]byte{
|
manifestFiles := map[string][]byte{
|
||||||
testFile1: []byte("in manifest"),
|
testFile1: []byte("in manifest"),
|
||||||
}
|
}
|
||||||
createTestManifest(t, fs, "/manifest.mf", manifestFiles)
|
createTestManifest(t, fs, testManifestPath, manifestFiles)
|
||||||
|
|
||||||
// Disk has file1 and file2
|
// Disk has file1 and file2
|
||||||
createFilesOnDisk(t, fs, map[string][]byte{
|
createFilesOnDisk(t, fs, map[string][]byte{
|
||||||
@@ -335,7 +437,11 @@ func TestFindExtraFiles(t *testing.T) {
|
|||||||
testFile2: []byte("extra file"),
|
testFile2: []byte("extra file"),
|
||||||
})
|
})
|
||||||
|
|
||||||
chk, err := NewChecker("/manifest.mf", "/data", fs)
|
chk, err := NewChecker(&CheckerOptions{
|
||||||
|
ManifestPath: testManifestPath,
|
||||||
|
BasePath: testDataDir,
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
results := make(chan Result, 10)
|
results := make(chan Result, 10)
|
||||||
@@ -360,7 +466,7 @@ func TestFindExtraFilesSkipsManifestAndDotfiles(t *testing.T) {
|
|||||||
manifestFiles := map[string][]byte{
|
manifestFiles := map[string][]byte{
|
||||||
testFile1: []byte("in manifest"),
|
testFile1: []byte("in manifest"),
|
||||||
}
|
}
|
||||||
createTestManifest(t, fs, "/data/index.mf", manifestFiles)
|
createTestManifest(t, fs, testDataManifestPath, manifestFiles)
|
||||||
createFilesOnDisk(t, fs, map[string][]byte{
|
createFilesOnDisk(t, fs, map[string][]byte{
|
||||||
testFile1: []byte("in manifest"),
|
testFile1: []byte("in manifest"),
|
||||||
})
|
})
|
||||||
@@ -368,10 +474,14 @@ func TestFindExtraFilesSkipsManifestAndDotfiles(t *testing.T) {
|
|||||||
require.NoError(t, afero.WriteFile(fs, "/data/.hidden", []byte("hidden"), 0o644))
|
require.NoError(t, afero.WriteFile(fs, "/data/.hidden", []byte("hidden"), 0o644))
|
||||||
require.NoError(t, afero.WriteFile(fs, "/data/.config/settings", []byte("cfg"), 0o644))
|
require.NoError(t, afero.WriteFile(fs, "/data/.config/settings", []byte("cfg"), 0o644))
|
||||||
// Create a real extra file
|
// Create a real extra file
|
||||||
require.NoError(t, fs.MkdirAll("/data", 0o755))
|
require.NoError(t, fs.MkdirAll(testDataDir, 0o755))
|
||||||
require.NoError(t, afero.WriteFile(fs, "/data/extra.txt", []byte("extra"), 0o644))
|
require.NoError(t, afero.WriteFile(fs, "/data/extra.txt", []byte("extra"), 0o644))
|
||||||
|
|
||||||
chk, err := NewChecker("/data/index.mf", "/data", fs)
|
chk, err := NewChecker(&CheckerOptions{
|
||||||
|
ManifestPath: testDataManifestPath,
|
||||||
|
BasePath: testDataDir,
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
results := make(chan Result, 10)
|
results := make(chan Result, 10)
|
||||||
@@ -400,10 +510,14 @@ func TestFindExtraFilesContextCancellation(t *testing.T) {
|
|||||||
|
|
||||||
fs := afero.NewMemMapFs()
|
fs := afero.NewMemMapFs()
|
||||||
files := map[string][]byte{testFileName: []byte("data")}
|
files := map[string][]byte{testFileName: []byte("data")}
|
||||||
createTestManifest(t, fs, "/manifest.mf", files)
|
createTestManifest(t, fs, testManifestPath, files)
|
||||||
createFilesOnDisk(t, fs, files)
|
createFilesOnDisk(t, fs, files)
|
||||||
|
|
||||||
chk, err := NewChecker("/manifest.mf", "/data", fs)
|
chk, err := NewChecker(&CheckerOptions{
|
||||||
|
ManifestPath: testManifestPath,
|
||||||
|
BasePath: testDataDir,
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
@@ -419,10 +533,14 @@ func TestCheckNilChannels(t *testing.T) {
|
|||||||
|
|
||||||
fs := afero.NewMemMapFs()
|
fs := afero.NewMemMapFs()
|
||||||
files := map[string][]byte{testFileName: []byte("data")}
|
files := map[string][]byte{testFileName: []byte("data")}
|
||||||
createTestManifest(t, fs, "/manifest.mf", files)
|
createTestManifest(t, fs, testManifestPath, files)
|
||||||
createFilesOnDisk(t, fs, files)
|
createFilesOnDisk(t, fs, files)
|
||||||
|
|
||||||
chk, err := NewChecker("/manifest.mf", "/data", fs)
|
chk, err := NewChecker(&CheckerOptions{
|
||||||
|
ManifestPath: testManifestPath,
|
||||||
|
BasePath: testDataDir,
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Should not panic with nil channels
|
// Should not panic with nil channels
|
||||||
@@ -435,10 +553,14 @@ func TestFindExtraFilesNilChannel(t *testing.T) {
|
|||||||
|
|
||||||
fs := afero.NewMemMapFs()
|
fs := afero.NewMemMapFs()
|
||||||
files := map[string][]byte{testFileName: []byte("data")}
|
files := map[string][]byte{testFileName: []byte("data")}
|
||||||
createTestManifest(t, fs, "/manifest.mf", files)
|
createTestManifest(t, fs, testManifestPath, files)
|
||||||
createFilesOnDisk(t, fs, files)
|
createFilesOnDisk(t, fs, files)
|
||||||
|
|
||||||
chk, err := NewChecker("/manifest.mf", "/data", fs)
|
chk, err := NewChecker(&CheckerOptions{
|
||||||
|
ManifestPath: testManifestPath,
|
||||||
|
BasePath: testDataDir,
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Should not panic with nil channel
|
// Should not panic with nil channel
|
||||||
@@ -455,7 +577,7 @@ func TestCheckSubdirectories(t *testing.T) {
|
|||||||
"dir1/dir2/file2.txt": []byte("content2"),
|
"dir1/dir2/file2.txt": []byte("content2"),
|
||||||
"dir1/dir2/dir3/deep.txt": []byte("deep content"),
|
"dir1/dir2/dir3/deep.txt": []byte("deep content"),
|
||||||
}
|
}
|
||||||
createTestManifest(t, fs, "/manifest.mf", files)
|
createTestManifest(t, fs, testManifestPath, files)
|
||||||
|
|
||||||
// Create files with full directory structure
|
// Create files with full directory structure
|
||||||
for path, content := range files {
|
for path, content := range files {
|
||||||
@@ -465,7 +587,11 @@ func TestCheckSubdirectories(t *testing.T) {
|
|||||||
require.NoError(t, afero.WriteFile(fs, fullPath, content, 0o644))
|
require.NoError(t, afero.WriteFile(fs, fullPath, content, 0o644))
|
||||||
}
|
}
|
||||||
|
|
||||||
chk, err := NewChecker("/manifest.mf", "/data", fs)
|
chk, err := NewChecker(&CheckerOptions{
|
||||||
|
ManifestPath: testManifestPath,
|
||||||
|
BasePath: testDataDir,
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
results := make(chan Result, 10)
|
results := make(chan Result, 10)
|
||||||
@@ -493,13 +619,17 @@ func TestCheckMissingFileDetectedWithoutFallback(t *testing.T) {
|
|||||||
testExistsFile: []byte("here"),
|
testExistsFile: []byte("here"),
|
||||||
"missing.txt": []byte("not on disk"),
|
"missing.txt": []byte("not on disk"),
|
||||||
}
|
}
|
||||||
createTestManifest(t, fs, "/manifest.mf", files)
|
createTestManifest(t, fs, testManifestPath, files)
|
||||||
// Only create one file on disk
|
// Only create one file on disk
|
||||||
createFilesOnDisk(t, fs, map[string][]byte{
|
createFilesOnDisk(t, fs, map[string][]byte{
|
||||||
testExistsFile: []byte("here"),
|
testExistsFile: []byte("here"),
|
||||||
})
|
})
|
||||||
|
|
||||||
chk, err := NewChecker("/manifest.mf", "/data", fs)
|
chk, err := NewChecker(&CheckerOptions{
|
||||||
|
ManifestPath: testManifestPath,
|
||||||
|
BasePath: testDataDir,
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
results := make(chan Result, 10)
|
results := make(chan Result, 10)
|
||||||
@@ -528,7 +658,7 @@ func TestFindExtraFilesSkipsDotfiles(t *testing.T) {
|
|||||||
files := map[string][]byte{
|
files := map[string][]byte{
|
||||||
testFile1: []byte("in manifest"),
|
testFile1: []byte("in manifest"),
|
||||||
}
|
}
|
||||||
createTestManifest(t, fs, "/data/index.mf", files)
|
createTestManifest(t, fs, testDataManifestPath, files)
|
||||||
createFilesOnDisk(t, fs, files)
|
createFilesOnDisk(t, fs, files)
|
||||||
|
|
||||||
// Add dotfiles and manifest file on disk
|
// Add dotfiles and manifest file on disk
|
||||||
@@ -537,7 +667,11 @@ func TestFindExtraFilesSkipsDotfiles(t *testing.T) {
|
|||||||
require.NoError(t,
|
require.NoError(t,
|
||||||
afero.WriteFile(fs, "/data/.git/config", []byte("git config"), 0o644))
|
afero.WriteFile(fs, "/data/.git/config", []byte("git config"), 0o644))
|
||||||
|
|
||||||
chk, err := NewChecker("/data/index.mf", "/data", fs)
|
chk, err := NewChecker(&CheckerOptions{
|
||||||
|
ManifestPath: testDataManifestPath,
|
||||||
|
BasePath: testDataDir,
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
results := make(chan Result, 10)
|
results := make(chan Result, 10)
|
||||||
@@ -563,10 +697,14 @@ func TestFindExtraFilesSkipsManifestFile(t *testing.T) {
|
|||||||
files := map[string][]byte{
|
files := map[string][]byte{
|
||||||
testFile1: []byte("content"),
|
testFile1: []byte("content"),
|
||||||
}
|
}
|
||||||
createTestManifest(t, fs, "/data/index.mf", files)
|
createTestManifest(t, fs, testDataManifestPath, files)
|
||||||
createFilesOnDisk(t, fs, files)
|
createFilesOnDisk(t, fs, files)
|
||||||
|
|
||||||
chk, err := NewChecker("/data/index.mf", "/data", fs)
|
chk, err := NewChecker(&CheckerOptions{
|
||||||
|
ManifestPath: testDataManifestPath,
|
||||||
|
BasePath: testDataDir,
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
results := make(chan Result, 10)
|
results := make(chan Result, 10)
|
||||||
@@ -587,9 +725,13 @@ func TestCheckEmptyManifest(t *testing.T) {
|
|||||||
|
|
||||||
fs := afero.NewMemMapFs()
|
fs := afero.NewMemMapFs()
|
||||||
// Create manifest with no files
|
// Create manifest with no files
|
||||||
createTestManifest(t, fs, "/manifest.mf", map[string][]byte{})
|
createTestManifest(t, fs, testManifestPath, map[string][]byte{})
|
||||||
|
|
||||||
chk, err := NewChecker("/manifest.mf", "/data", fs)
|
chk, err := NewChecker(&CheckerOptions{
|
||||||
|
ManifestPath: testManifestPath,
|
||||||
|
BasePath: testDataDir,
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, FileCount(0), chk.FileCount())
|
assert.Equal(t, FileCount(0), chk.FileCount())
|
||||||
@@ -621,10 +763,14 @@ func TestCheckProgressRateLimited(t *testing.T) {
|
|||||||
files[name] = []byte("content")
|
files[name] = []byte("content")
|
||||||
}
|
}
|
||||||
|
|
||||||
createTestManifest(t, fs, "/manifest.mf", files)
|
createTestManifest(t, fs, testManifestPath, files)
|
||||||
createFilesOnDisk(t, fs, files)
|
createFilesOnDisk(t, fs, files)
|
||||||
|
|
||||||
chk, err := NewChecker("/manifest.mf", "/data", fs)
|
chk, err := NewChecker(&CheckerOptions{
|
||||||
|
ManifestPath: testManifestPath,
|
||||||
|
BasePath: testDataDir,
|
||||||
|
Fs: fs,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
results := make(chan Result, 200)
|
results := make(chan Result, 200)
|
||||||
|
|||||||
+17
-4
@@ -352,16 +352,29 @@ func NewManifestFromReader(input io.Reader) (*manifest, error) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewManifestFromFile reads a manifest from a file path using the given filesystem.
|
// ManifestFromFileOptions configures NewManifestFromFile.
|
||||||
// If fs is nil, the real filesystem (OsFs) is used.
|
type ManifestFromFileOptions struct {
|
||||||
|
// Path is the manifest file to read (required).
|
||||||
|
Path string
|
||||||
|
// Fs is the filesystem to use, defaults to OsFs if nil.
|
||||||
|
Fs afero.Fs
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewManifestFromFile reads a manifest from a file. It returns an error if
|
||||||
|
// opts is nil or its path is empty.
|
||||||
//
|
//
|
||||||
//nolint:revive // unexported-return: exporting manifest is owner question 13
|
//nolint:revive // unexported-return: exporting manifest is owner question 13
|
||||||
func NewManifestFromFile(fs afero.Fs, path string) (*manifest, error) {
|
func NewManifestFromFile(opts *ManifestFromFileOptions) (*manifest, error) {
|
||||||
|
if opts == nil || opts.Path == "" {
|
||||||
|
return nil, errManifestPathEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
fs := opts.Fs
|
||||||
if fs == nil {
|
if fs == nil {
|
||||||
fs = afero.NewOsFs()
|
fs = afero.NewOsFs()
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := fs.Open(path)
|
f, err := fs.Open(opts.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-8
@@ -59,10 +59,11 @@ type ScannerOptions struct {
|
|||||||
// Seed, if set, derives a deterministic UUID from this seed.
|
// Seed, if set, derives a deterministic UUID from this seed.
|
||||||
Seed string
|
Seed string
|
||||||
// OutputPath, if set, is the file the manifest will be written to.
|
// OutputPath, if set, is the file the manifest will be written to.
|
||||||
// If that file exists when the scanner is created, enumeration leaves
|
// Enumeration leaves out that file and OutputPath + ".tmp", the temp
|
||||||
// it out however its path is spelled (through a symlink, a hard link,
|
// file mfer gen writes first, if they exist when the scanner is
|
||||||
// or in different letter case), so a manifest being replaced is never
|
// created, however their paths are spelled (through a symlink, a hard
|
||||||
// listed.
|
// link, or in different letter case), so neither a manifest being
|
||||||
|
// replaced nor a temp file left by an interrupted run is ever listed.
|
||||||
// Files are matched with os.SameFile, which only recognizes files on
|
// Files are matched with os.SameFile, which only recognizes files on
|
||||||
// the operating system's filesystem.
|
// the operating system's filesystem.
|
||||||
OutputPath string
|
OutputPath string
|
||||||
@@ -85,6 +86,7 @@ type Scanner struct {
|
|||||||
options *ScannerOptions
|
options *ScannerOptions
|
||||||
fs afero.Fs
|
fs afero.Fs
|
||||||
outputInfo fs.FileInfo // the existing output file; nil when there is none
|
outputInfo fs.FileInfo // the existing output file; nil when there is none
|
||||||
|
tmpInfo fs.FileInfo // its leftover temp file; nil when there is none
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewScanner creates a new Scanner with default options.
|
// NewScanner creates a new Scanner with default options.
|
||||||
@@ -110,12 +112,17 @@ func NewScannerWithOptions(opts *ScannerOptions) *Scanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if opts.OutputPath != "" {
|
if opts.OutputPath != "" {
|
||||||
// If the output file cannot be stat'd, normally because it does
|
// A file that cannot be stat'd, normally because it does not
|
||||||
// not exist yet, there is nothing to leave out.
|
// exist, needs no leaving out.
|
||||||
info, err := s.fs.Stat(opts.OutputPath)
|
info, err := s.fs.Stat(opts.OutputPath)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
s.outputInfo = info
|
s.outputInfo = info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info, err = s.fs.Stat(opts.OutputPath + ".tmp")
|
||||||
|
if err == nil {
|
||||||
|
s.tmpInfo = info
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return s
|
return s
|
||||||
@@ -456,8 +463,9 @@ func (s *Scanner) enumerateFileWithInfo(
|
|||||||
info = realInfo
|
info = realInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// The manifest being written is not one of the files it lists.
|
// Neither the manifest being written nor its temp file is one of the
|
||||||
if s.outputInfo != nil && os.SameFile(info, s.outputInfo) {
|
// files it lists. os.SameFile never matches a nil FileInfo.
|
||||||
|
if os.SameFile(info, s.outputInfo) || os.SameFile(info, s.tmpInfo) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user