From c43fb49b7ccca57c385eea7f7b8f5fde455d7871 Mon Sep 17 00:00:00 2001 From: Will Beason Date: Mon, 28 Jun 2021 20:49:51 -0500 Subject: [PATCH] Create gator root command (#1403) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * read test files Signed-off-by: Will Beason * Make gator root command Per decision in weekly meeting, make the "gator" root command and "test" the subcommand in charge of running the tests. This frees us up to add other subcommands that we want to do later. Signed-off-by: Will Beason Co-authored-by: Sertaç Özercan <852750+sozercan@users.noreply.github.com> --- .../gatekeeper-test-alpha.go | 75 ------------ cmd/gator/gator.go | 32 +++++ cmd/gator/test/test.go | 114 ++++++++++++++++++ pkg/gktest/files.go | 11 -- pkg/gktest/filter.go | 8 +- pkg/gktest/read_suites.go | 18 +++ pkg/gktest/run.go | 6 - pkg/gktest/suite.go | 31 +++++ 8 files changed, 199 insertions(+), 96 deletions(-) delete mode 100644 cmd/gatekeeper-test-alpha/gatekeeper-test-alpha.go create mode 100644 cmd/gator/gator.go create mode 100644 cmd/gator/test/test.go delete mode 100644 pkg/gktest/files.go create mode 100644 pkg/gktest/read_suites.go delete mode 100644 pkg/gktest/run.go create mode 100644 pkg/gktest/suite.go diff --git a/cmd/gatekeeper-test-alpha/gatekeeper-test-alpha.go b/cmd/gatekeeper-test-alpha/gatekeeper-test-alpha.go deleted file mode 100644 index 9c1bf00faee..00000000000 --- a/cmd/gatekeeper-test-alpha/gatekeeper-test-alpha.go +++ /dev/null @@ -1,75 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "os" - - "github.com/open-policy-agent/gatekeeper/pkg/gktest" - "github.com/spf13/cobra" -) - -var run string - -func init() { - rootCmd.Flags().StringVarP(&run, "run", "r", "", - `regular expression which filters tests to run by name`) -} - -var rootCmd = &cobra.Command{ - Use: "gatekeeper-test-alpha path [--run=name]", - Short: "Gatekeeper Test Alpha is a unit test CLI for Gatekeeper Constraints", - Example: ` # Run all tests in label-tests.yaml - gatekeeper-test-alpha label-tests.yaml - - # Run all suites whose names contain "forbid-labels". - gatekeeper-test-alpha tests/... --run forbid-labels// - - # Run all tests whose names contain "nginx-deployment". - gatekeeper-test-alpha tests/... --run //nginx-deployment - - # Run all tests whose names exactly match "nginx-deployment". - gatekeeper-test-alpha tests/... --run '//^nginx-deployment$' - - # Run all tests that are either named "forbid-labels" or are - # in suites named "forbid-labels". - gatekeeper-test-alpha tests/... --run '^forbid-labels$'`, - Version: "alpha", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - path := args[0] - - testFiles, err := gktest.ToTestFiles(path) - if err != nil { - return fmt.Errorf("listing test files: %w", err) - } - filter, err := gktest.NewFilter(run) - if err != nil { - return fmt.Errorf("compiling filter: %w", err) - } - - isFailure := false - for _, f := range testFiles { - result := gktest.Run(f, filter) - // If Result contains an error status, it is safe to execute tests in other - // files so we can continue execution. - isFailure = isFailure || result.IsFailure() - fmt.Println(result.String()) - } - if isFailure { - // At least one test failed or there was a problem executing tests in at - // least one file. - return errors.New("FAIL") - } - - return nil - }, -} - -func main() { - err := rootCmd.Execute() - if err != nil { - fmt.Println(err) - os.Exit(1) - } -} diff --git a/cmd/gator/gator.go b/cmd/gator/gator.go new file mode 100644 index 00000000000..45fd505dbf7 --- /dev/null +++ b/cmd/gator/gator.go @@ -0,0 +1,32 @@ +package main + +import ( + "fmt" + "os" + + "github.com/open-policy-agent/gatekeeper/cmd/gator/test" + "github.com/spf13/cobra" +) + +const version = "alpha" + +func init() { + rootCmd.AddCommand(test.Cmd) +} + +var rootCmd = &cobra.Command{ + Use: "gator subcommand", + Short: "gator is a suite of authorship tools for Gatekeeper", + Version: version, + RunE: func(cmd *cobra.Command, args []string) error { + return nil + }, +} + +func main() { + err := rootCmd.Execute() + if err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/cmd/gator/test/test.go b/cmd/gator/test/test.go new file mode 100644 index 00000000000..473ddab9b72 --- /dev/null +++ b/cmd/gator/test/test.go @@ -0,0 +1,114 @@ +package test + +import ( + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + + "github.com/open-policy-agent/gatekeeper/pkg/gktest" + "github.com/spf13/cobra" +) + +const ( + examples = ` # Run all tests in label-tests.yaml + gator test label-tests.yaml + + # Run all suites whose names contain "forbid-labels". + gator test tests/... --run forbid-labels// + + # Run all tests whose names contain "nginx-deployment". + gator test tests/... --run //nginx-deployment + + # Run all tests whose names exactly match "nginx-deployment". + gator test tests/... --run '//^nginx-deployment$' + + # Run all tests that are either named "forbid-labels" or are + # in suites named "forbid-labels". + gator test tests/... --run '^forbid-labels$'` +) + +var run string + +func init() { + Cmd.Flags().StringVarP(&run, "run", "r", "", + `regular expression which filters tests to run by name`) +} + +// Cmd is the gator test subcommand. +var Cmd = &cobra.Command{ + Use: "test path [--run=name]", + Short: "test runs suites of tests on Gatekeeper Constraints", + Example: examples, + Args: cobra.ExactArgs(1), + RunE: runE, +} + +func runE(_ *cobra.Command, args []string) error { + path := args[0] + + // Convert path to be absolute. Allowing for relative and absolute paths + // everywhere in the code leads to unnecessary complexity, so the first + // thing we do on encountering a path is to convert it to an absolute path. + var err error + if !filepath.IsAbs(path) { + path, err = filepath.Abs(path) + if err != nil { + return fmt.Errorf("getting absolute path: %w", err) + } + } + + // Create the base file system. We use fs.FS rather than direct calls to + // os.ReadFile or filepath.WalkDir to make testing easier and keep logic + // os-independent. + fileSystem := getFS(path) + + suites, err := gktest.ReadSuites(fileSystem, path) + if err != nil { + return fmt.Errorf("listing test files: %w", err) + } + filter, err := gktest.NewFilter(run) + if err != nil { + return fmt.Errorf("compiling filter: %w", err) + } + + return runSuites(fileSystem, suites, filter) +} + +func runSuites(fileSystem fs.FS, suites []gktest.Suite, filter gktest.Filter) error { + isFailure := false + for _, s := range suites { + if !filter.MatchesSuite(s) { + continue + } + + results := s.Run(fileSystem, filter) + for _, result := range results { + if result.IsFailure() { + isFailure = true + } + fmt.Println(result.String()) + } + } + + if isFailure { + // At least one test failed or there was a problem executing tests in at + // least one file. + return errors.New("FAIL") + } + return nil +} + +func getFS(path string) fs.FS { + // TODO(#1397): Check that this produces the correct file system string on + // Windows. We may need to add a trailing `/` for fs.FS to function properly. + root := filepath.VolumeName(path) + if root == "" { + // We are running on a unix-like filesystem without volume names, so the + // file system root is `/`. + root = "/" + } + + return os.DirFS(root) +} diff --git a/pkg/gktest/files.go b/pkg/gktest/files.go deleted file mode 100644 index da316b5345b..00000000000 --- a/pkg/gktest/files.go +++ /dev/null @@ -1,11 +0,0 @@ -package gktest - -// ToTestFiles returns the set of test files selected by path. -// -// 1) If path is a path to a YAML, runs the suites in that file. -// 2) If the path is a directory, runs suites in that directory (not recursively). -// 3) If the path is a directory followed by "...", recursively runs suites -// in that directory and its subdirectories. -func ToTestFiles(path string) ([]string, error) { - return nil, nil -} diff --git a/pkg/gktest/filter.go b/pkg/gktest/filter.go index e54f2c36a22..5cc38df4331 100644 --- a/pkg/gktest/filter.go +++ b/pkg/gktest/filter.go @@ -39,20 +39,20 @@ func NewFilter(run string) (Filter, error) { return Filter{}, nil } -// Suite filters the set of test suites to run by suite name and the tests +// MatchesSuite filters the set of test suites to run by suite name and the tests // contained in the suite. Returns true if the suite should be run. // // If a suite regex was specified, returns true if the suite regex matches // `suite`. // If a suite regex was not specified but a test regex was, returns true if at // least one test in `tests` matches the test regex. -func (f Filter) Suite(suite string, tests []string) bool { +func (f Filter) MatchesSuite(suite Suite) bool { return true } -// Test filters the set of tests to run by name. +// MatchesTest filters the set of tests to run by name. // // Returns true if the test regex matches test. -func (f Filter) Test(test string) bool { +func (f Filter) MatchesTest(testCase TestCase) bool { return true } diff --git a/pkg/gktest/read_suites.go b/pkg/gktest/read_suites.go new file mode 100644 index 00000000000..cbf67b89db2 --- /dev/null +++ b/pkg/gktest/read_suites.go @@ -0,0 +1,18 @@ +package gktest + +import "io/fs" + +// ReadSuites returns the set of test Suites selected by path. +// +// 1) If path is a path to a Suite, parses and returns the Suite. +// 2) If the path is a directory, returns the Suites defined in that directory +// (not recursively). +// 3) If the path is a directory followed by "...", returns all Suites in that +// directory and its subdirectories. +// +// Returns an error if: +// - path is a file that does not define a Suite +// - any matched files containing Suites are not parseable +func ReadSuites(f fs.FS, path string) ([]Suite, error) { + return nil, nil +} diff --git a/pkg/gktest/run.go b/pkg/gktest/run.go deleted file mode 100644 index 5d4593c3cf8..00000000000 --- a/pkg/gktest/run.go +++ /dev/null @@ -1,6 +0,0 @@ -package gktest - -// Run executes all suites and tests in file which match filter. -func Run(file string, filter Filter) Result { - return Result{} -} diff --git a/pkg/gktest/suite.go b/pkg/gktest/suite.go new file mode 100644 index 00000000000..9550430ad4d --- /dev/null +++ b/pkg/gktest/suite.go @@ -0,0 +1,31 @@ +package gktest + +import "io/fs" + +// Suite defines a set of TestCases which all use the same ConstraintTemplate +// and Constraint. +type Suite struct { + TestCases []TestCase +} + +// Run executes every TestCase in the Suite. Returns the results for every +// TestCase. +func (s Suite) Run(f fs.FS, filter Filter) []Result { + results := make([]Result, len(s.TestCases)) + for i, tc := range s.TestCases { + if !filter.MatchesTest(tc) { + continue + } + + results[i] = tc.Run(f) + } + return results +} + +// TestCase runs Constraint against a YAML object +type TestCase struct{} + +// Run executes the TestCase and returns the Result of the run. +func (tc TestCase) Run(f fs.FS) Result { + return Result{} +}