Resolve check and freshen paths against the manifest's directory (closes #177)
check / check (push) Waiting to run

Without --base, check and freshen now look for a manifest's files in the
directory that holds the manifest, the file named or the one found in a
directory argument, instead of the current directory. check of a manifest
given by URL still uses the current directory, and a --base given on the
command line, the current directory included, still overrides. The --base
help text of both commands and the README's Tool Examples state the
default; the README gains a freshen entry to do so.

Model: opus-5-5
This commit is contained in:
2026-10-07 14:12:53 +00:00
parent dce5e050c3
commit 256881c571
6 changed files with 148 additions and 8 deletions
+78
View File
@@ -8,6 +8,7 @@ import (
"fmt"
"io"
"math/rand"
"net/http/httptest"
"os"
"path/filepath"
"slices"
@@ -979,6 +980,83 @@ func TestCheckNeverReportsManifest(t *testing.T) {
}
}
// setupManifestInSubdir makes a temp dir holding a.txt and sub/b.txt, where
// sub/index.mf is the manifest gen writes for sub, and makes it the working
// directory, so a test calling it cannot run in parallel. It returns the
// temp dir.
func setupManifestInSubdir(t *testing.T) string {
t.Helper()
root := t.TempDir()
sub := filepath.Join(root, "sub")
fs := afero.NewOsFs()
require.NoError(t, fs.MkdirAll(sub, 0o750))
writeTestFile(t, fs, filepath.Join(root, "a.txt"), "not in the manifest")
writeTestFile(t, fs, filepath.Join(sub, "b.txt"), "in the manifest")
opts := testOpts([]string{
testApp, cmdGenerate, "-q", "-o", filepath.Join(sub, defaultManifestName), sub,
}, fs)
require.Equal(t, 0, runCLI(opts), "stderr: %s", testStderr(t, opts))
t.Chdir(root)
return root
}
// TestCheckResolvesEntriesAgainstManifestDirectory runs check from the
// directory above sub, on the manifest in sub. Without --base, the
// manifest's entries are looked for in sub, whether check is given the
// manifest or sub, and the files above sub are not reported. --base names
// the directory to look in instead, the current one included.
//
//nolint:paralleltest // changes the process-global working directory
func TestCheckResolvesEntriesAgainstManifestDirectory(t *testing.T) {
root := setupManifestInSubdir(t)
for _, tc := range []struct {
args []string
exitCode int
failure string // a line check must print, if any
}{
{[]string{"sub"}, 0, ""},
{[]string{"sub/index.mf"}, 0, ""},
{[]string{filepath.Join(root, "sub")}, 0, ""},
{[]string{testFlagBase, "sub", "sub/index.mf"}, 0, ""},
{[]string{testFlagBase, ".", "sub/index.mf"}, 1, "MISSING: b.txt"},
} {
t.Run(strings.Join(tc.args, " "), func(t *testing.T) {
opts := testOpts(slices.Concat(
[]string{testApp, cmdCheck, testFlagNoExtra}, tc.args,
), afero.NewOsFs())
assert.Equal(t, tc.exitCode, runCLI(opts), "stderr: %s", testStderr(t, opts))
assert.Contains(t, testStderr(t, opts), tc.failure)
})
}
}
// TestCheckURLManifestResolvesEntriesAgainstCurrentDirectory runs check on
// a manifest given by URL, from a directory holding the file it lists: the
// file is looked for there.
//
//nolint:paralleltest // changes the process-global working directory
func TestCheckURLManifestResolvesEntriesAgainstCurrentDirectory(t *testing.T) {
files := map[string][]byte{testFileTxt: []byte("hello")}
server := httptest.NewServer(fetchTestHandler(manifestOf(t, files), files))
defer server.Close()
cwd := chdirTemp(t)
require.NoError(t,
os.WriteFile(filepath.Join(cwd, testFileTxt), files[testFileTxt], 0o600))
opts := testOpts([]string{
testApp, cmdCheck, testFlagNoExtra, server.URL + "/" + defaultManifestName,
}, afero.NewOsFs())
assert.Equal(t, 0, runCLI(opts), "stderr: %s", testStderr(t, opts))
}
// unlistableDirFs is a filesystem on which one directory cannot be listed.
type unlistableDirFs struct {
afero.Fs