From 8d71105bb7eae178339607fe5d529f7e348fc0c1 Mon Sep 17 00:00:00 2001 From: "Baruch Odem (Rothkoff)" Date: Thu, 28 Sep 2023 11:03:19 +0300 Subject: [PATCH] test: add lint and e2e tests (#188) - test: prevent log.Fatal and fmt.Print - fix: using fmt.Print for report --- .github/workflows/security.yml | 2 +- CONTRIBUTING.md | 14 ++++++ README.md | 2 +- reporting/report.go | 4 +- tests/cli.go | 75 +++++++++++++++++++++++++++++ tests/cli_test.go | 31 ++++++++++++ tests/lint.go | 86 ++++++++++++++++++++++++++++++++++ tests/lint_test.go | 17 +++++++ 8 files changed, 227 insertions(+), 4 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 tests/cli.go create mode 100644 tests/cli_test.go create mode 100644 tests/lint.go create mode 100644 tests/lint_test.go diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index ebd0d6fb..293e435c 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -22,7 +22,7 @@ jobs: - name: Run Gosec Security Scanner uses: securego/gosec@master with: - args: "-no-fail -fmt sarif -out results.sarif ./..." + args: "-no-fail -fmt sarif -out results.sarif -exclude-dir=.ci -exclude-dir=tests ./..." - name: Upload Gosec Results uses: github/codeql-action/upload-sarif@v2 with: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..6f440b49 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,14 @@ +# Welcome to the 2ms club! + +> [!NOTE] +> This is the first version of the document, we will rewrite it on the fly. + +## Test + +Along with the regular unit tests, we also have a set of other tests: + +- `tests/cli` - e2e tests that build the CLI, run it, and check the output. + To skip these tests, run `go test -short ./...`. +- `tests/lint` - linter, to verify we are not using our forbidden functions (for example, using `fmt.Print` instead of `log.Info`) +- `.ci/check_new_rules.go` - compares the list of rules in the [latest _gitleaks_ release](https://github.com/gitleaks/gitleaks/releases/latest) with our list of rules, and fails if there are rules in the release that are not in our list. +- `.ci/update-readme.sh` - auto update the `help` message in the [README.md](README.md#command-line-interface) file. diff --git a/README.md b/README.md index 71a69c2c..ee5f39cf 100644 --- a/README.md +++ b/README.md @@ -313,7 +313,7 @@ docker run -v $(pwd)/.2ms.yml:/app/.2ms.yml checkmarx/2ms confluence --url https ## Contributing -`2ms` is extendable with the concept of plugins. We designed it like this so anyone can easily contribute, improve and extend `2ms` +`2ms` is extendable with the concept of plugins. We designed it like this so anyone can easily contribute, improve and extend `2ms`. Read more about contributing in our [CONTRIBUTING.md](CONTRIBUTING.md) file. ## Contact diff --git a/reporting/report.go b/reporting/report.go index 7c61bf6b..2e7cfb14 100644 --- a/reporting/report.go +++ b/reporting/report.go @@ -1,12 +1,12 @@ package reporting import ( - "fmt" "os" "path/filepath" "strings" "github.com/checkmarx/2ms/config" + "github.com/rs/zerolog/log" ) const ( @@ -41,7 +41,7 @@ func Init() *Report { func (r *Report) ShowReport(format string, cfg *config.Config) { output := r.getOutput(format, cfg) - fmt.Print(output) + log.Info().Msg(output) } func (r *Report) WriteFile(reportPath []string, cfg *config.Config) error { diff --git a/tests/cli.go b/tests/cli.go new file mode 100644 index 00000000..ef8f20b7 --- /dev/null +++ b/tests/cli.go @@ -0,0 +1,75 @@ +package tests + +import ( + "encoding/json" + "fmt" + "go/build" + "os" + "os/exec" + "path" + "runtime" + + "github.com/checkmarx/2ms/reporting" +) + +type cli struct { + executable string + resultsPath string +} + +func createCLI(outputDir string) (cli, error) { + executable := path.Join(outputDir, "2ms") + lib, err := build.Import("github.com/checkmarx/2ms", "", build.FindOnly) + if err != nil { + return cli{}, fmt.Errorf("failed to import 2ms: %s", err) + } + + cmd := exec.Command("go", "build", "-o", executable, lib.ImportPath) + cmd.Env = append(os.Environ(), fmt.Sprintf("GOOS=%s", runtime.GOOS), fmt.Sprintf("GOARCH=%s", runtime.GOARCH)) + + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + return cli{}, fmt.Errorf("failed to build 2ms: %s", err) + } + + return cli{ + executable: executable, + resultsPath: path.Join(outputDir, "results.json"), + }, + nil +} + +func generateProject(outputDir string) error { + token := "g" + "hp" + "_ixOl" + "iEFNK4O" + "brYB506" + "8oXFd" + "9JUF" + "iRy0RU" + "KNl" + content := "bla bla bla\nGitHubToken: " + token + "\nbla bla bla" + + if err := os.WriteFile(path.Join(outputDir, "secret.txt"), []byte(content), 0644); err != nil { + return err + } + + return nil +} + +func (c *cli) run(projectDir string, args ...string) error { + argsWithDefault := append([]string{"filesystem", "--path", projectDir, "--report-path", c.resultsPath}, args...) + cmd := exec.Command(c.executable, argsWithDefault...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +func (c *cli) getReport() (reporting.Report, error) { + report := reporting.Init() + + content, err := os.ReadFile(c.resultsPath) + if err != nil { + return reporting.Report{}, err + } + if err := json.Unmarshal(content, &report); err != nil { + return reporting.Report{}, err + } + + return *report, nil +} diff --git a/tests/cli_test.go b/tests/cli_test.go new file mode 100644 index 00000000..64555a8a --- /dev/null +++ b/tests/cli_test.go @@ -0,0 +1,31 @@ +package tests + +import "testing" + +func TestCLI(t *testing.T) { + executable, err := createCLI(t.TempDir()) + if err != nil { + t.Fatalf("failed to build CLI: %s", err) + } + + projectDir := t.TempDir() + + t.Run("found_one_secret", func(t *testing.T) { + if err := generateProject(projectDir); err != nil { + t.Fatalf("failed to generate project: %s", err) + } + + if err := executable.run(projectDir); err == nil { + t.Error("expected error, got nil") + } + + report, err := executable.getReport() + if err != nil { + t.Fatalf("failed to get report: %s", err) + } + + if len(report.Results) != 1 { + t.Errorf("expected one result, got %d", len(report.Results)) + } + }) +} diff --git a/tests/lint.go b/tests/lint.go new file mode 100644 index 00000000..68630da1 --- /dev/null +++ b/tests/lint.go @@ -0,0 +1,86 @@ +package tests + +import ( + "bufio" + "fmt" + "os" + "path/filepath" + "regexp" +) + +func walkGoFiles() <-chan string { + ignoredDirs := []string{ + ".git", + ".github", + ".vscode", + "vendor", + "tests", + ".ci", + } + + ch := make(chan string) + + go func() { + defer close(ch) + err := filepath.Walk("..", func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if filepath.Ext(path) == ".go" { + ch <- path + } + + if info.IsDir() { + for _, ignoredDir := range ignoredDirs { + if info.Name() == ignoredDir { + return filepath.SkipDir + } + } + } + return nil + }) + + if err != nil { + panic(err) + } + }() + + return ch +} + +var forbiddenPatterns = []*regexp.Regexp{ + regexp.MustCompile(`fmt\.Print`), + // regexp.MustCompile(`log\.Fatal`), +} + +var ignoreFiles = regexp.MustCompile(`_test\.go$`) + +func lintFile(path string) error { + if ignoreFiles.MatchString(path) { + return nil + } + + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + line := 1 + for scanner.Scan() { + lineText := scanner.Text() + for _, forbiddenPattern := range forbiddenPatterns { + if forbiddenPattern.MatchString(lineText) { + return fmt.Errorf("%s:%d: forbidden pattern found: %s", path, line, forbiddenPattern.String()) + } + } + line++ + } + + if err := scanner.Err(); err != nil { + return err + } + + return nil +} diff --git a/tests/lint_test.go b/tests/lint_test.go new file mode 100644 index 00000000..6f3a385e --- /dev/null +++ b/tests/lint_test.go @@ -0,0 +1,17 @@ +package tests + +import ( + "testing" +) + +func TestLintIntegration(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + for path := range walkGoFiles() { + if err := lintFile(path); err != nil { + t.Errorf("lint error: %s", err) + } + } +}