[cli] Accept absolute paths
For easier integration with fuchsia's static analysis tooling, which
prefers absolute paths, and for general user-friendliness, accept
absolute paths for the positional file arguments to shac.
Files outside the root directory are not supported. We can reconsider if
this becomes an issue.
Change-Id: If12ad94f6f7741c7c876ceccc09f81edcf40a73e
Reviewed-on: https://fuchsia-review.googlesource.com/c/shac-project/shac/+/910677
Reviewed-by: Anthony Fandrianto <atyfto@google.com>
Commit-Queue: Auto-Submit <auto-submit@fuchsia-infra.iam.gserviceaccount.com>
Fuchsia-Auto-Submit: Oliver Newman <olivernewman@google.com>
diff --git a/internal/engine/run.go b/internal/engine/run.go
index dcee4eb..346d3ce 100644
--- a/internal/engine/run.go
+++ b/internal/engine/run.go
@@ -204,9 +204,40 @@
}
patterns = append(patterns, gitignore.ParsePattern(p, nil))
}
+
+ files := o.Files
+ if len(files) > 0 {
+ var cwd string
+ cwd, err = os.Getwd()
+ if err != nil {
+ return err
+ }
+ // Make all file paths relative to the project root.
+ var newFiles []string
+ for _, orig := range files {
+ f := orig
+ if !filepath.IsAbs(f) {
+ // Relative paths are interpreted relative to the cwd, rather
+ // than relative to the root.
+ f = filepath.Join(cwd, f)
+ }
+ var rel string
+ rel, err = filepath.Rel(root, f)
+ if err != nil {
+ return err
+ }
+ // Validates that the path is within the root directory (i.e.
+ // doesn't start with "..").
+ if !filepath.IsLocal(rel) {
+ return fmt.Errorf("cannot analyze file outside root: %s", orig)
+ }
+ newFiles = append(newFiles, rel)
+ }
+ files = newFiles
+ }
scm = &cachingSCM{
scm: &filteredSCM{
- files: o.Files,
+ files: files,
matcher: gitignore.NewMatcher(patterns),
scm: scm,
},
diff --git a/internal/engine/run_test.go b/internal/engine/run_test.go
index ed93f01..f69ff83 100644
--- a/internal/engine/run_test.go
+++ b/internal/engine/run_test.go
@@ -117,10 +117,12 @@
}
func TestRun_SpecificFiles(t *testing.T) {
- t.Parallel()
+ // Not parallelized because it calls os.Chdir.
+
root := t.TempDir()
writeFile(t, root, "shac.textproto", prototext.Format(&Document{
Ignore: []string{
+ // Specifying files on the command line should override ignores.
"*.py",
},
}))
@@ -131,54 +133,84 @@
copySCM(t, root)
data := []struct {
- name string
- want string
- files []string
+ name string
+ starlarkFile string
+ want string
+ files []string
+ workDir string
}{
{
- "ctx-scm-affected_files.star",
- "[//ctx-scm-affected_files.star:19] \n" +
+ name: "affected files (no files specified)",
+ starlarkFile: "ctx-scm-affected_files.star",
+ want: "[//ctx-scm-affected_files.star:19] \n" +
scmStarlarkFiles("") +
"rust.rs: \n" +
"shac.textproto: \n" +
"\n",
- nil,
+ files: nil,
+ workDir: root,
},
{
- "ctx-scm-all_files.star",
- "[//ctx-scm-all_files.star:19] \n" +
+ name: "affected files (relative path specified)",
+ starlarkFile: "ctx-scm-affected_files.star",
+ want: "[//ctx-scm-affected_files.star:19] \n" +
+ "python.py: \n" +
+ "rust.rs: \n" +
+ "\n",
+ files: []string{"python.py", "rust.rs"},
+ workDir: root,
+ },
+ {
+ name: "affected files (absolute path specified)",
+ starlarkFile: "ctx-scm-affected_files.star",
+ want: "[//ctx-scm-affected_files.star:19] \n" +
+ "python.py: \n" +
+ "rust.rs: \n" +
+ "\n",
+ files: []string{filepath.Join(root, "python.py"), filepath.Join(root, "rust.rs")},
+ // Absolute paths should work even outside the root.
+ workDir: t.TempDir(),
+ },
+ {
+ name: "all files",
+ starlarkFile: "ctx-scm-all_files.star",
+ want: "[//ctx-scm-all_files.star:19] \n" +
scmStarlarkFiles("") +
+ "python.py: \n" +
"rust.rs: \n" +
"shac.textproto: \n" +
"\n",
- nil,
+ files: []string{"python.py", "rust.rs"},
+ workDir: root,
},
}
for i := range data {
i := i
t.Run(data[i].name, func(t *testing.T) {
- t.Parallel()
- testStarlarkPrint(t, root, data[i].name, false, data[i].want)
+ originalWd, err := os.Getwd()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := os.Chdir(data[i].workDir); err != nil {
+ t.Fatal(err)
+ }
+ testStarlarkPrint(t, root, data[i].starlarkFile, false, data[i].want, data[i].files...)
+ if err := os.Chdir(originalWd); err != nil {
+ t.Fatal(err)
+ }
})
}
- t.Run("empty ignore field", func(t *testing.T) {
- t.Parallel()
- root := t.TempDir()
- writeFile(t, root, "shac.textproto", prototext.Format(&Document{
- Ignore: []string{
- "*.foo",
- "",
- },
- }))
-
+ t.Run("path outside root rejected", func(t *testing.T) {
r := reportPrint{reportNoPrint: reportNoPrint{t: t}}
- o := Options{Report: &r, Root: root, AllFiles: false, main: "shac.star"}
+ files := []string{filepath.Join(t.TempDir(), "outside-root.txt")}
+ o := Options{Report: &r, Root: root, main: "shac.star", Files: files}
err := Run(context.Background(), &o)
+ wantErr := fmt.Sprintf("cannot analyze file outside root: %s", files[0])
if err == nil {
- t.Fatal("Expected empty ignore field to be rejected")
- } else if !errors.Is(err, errEmptyIgnore) {
- t.Fatalf("Expected error %q, got %q", errEmptyIgnore, err)
+ t.Fatal("Expected file outside root to be rejected")
+ } else if err.Error() != wantErr {
+ t.Fatalf("Expected error %q, got %q", wantErr, err)
}
})
}
@@ -1800,9 +1832,9 @@
// Utilities
// testStarlarkPrint test a starlark file that calls print().
-func testStarlarkPrint(t testing.TB, root, name string, all bool, want string) {
+func testStarlarkPrint(t testing.TB, root, name string, all bool, want string, files ...string) {
r := reportPrint{reportNoPrint: reportNoPrint{t: t}}
- o := Options{Report: &r, Root: root, AllFiles: all, main: name}
+ o := Options{Report: &r, Root: root, AllFiles: all, main: name, Files: files}
if err := Run(context.Background(), &o); err != nil {
t.Helper()
t.Fatal(err)