Skip to content

Commit

Permalink
test: add lint and e2e tests (#188)
Browse files Browse the repository at this point in the history
- test: prevent log.Fatal and fmt.Print
- fix: using fmt.Print for report
  • Loading branch information
Baruch Odem (Rothkoff) authored Sep 28, 2023
1 parent 81c49a0 commit 8d71105
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
14 changes: 14 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions reporting/report.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package reporting

import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/checkmarx/2ms/config"
"github.com/rs/zerolog/log"
)

const (
Expand Down Expand Up @@ -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 {
Expand Down
75 changes: 75 additions & 0 deletions tests/cli.go
Original file line number Diff line number Diff line change
@@ -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
}
31 changes: 31 additions & 0 deletions tests/cli_test.go
Original file line number Diff line number Diff line change
@@ -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))
}
})
}
86 changes: 86 additions & 0 deletions tests/lint.go
Original file line number Diff line number Diff line change
@@ -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
}
17 changes: 17 additions & 0 deletions tests/lint_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}

0 comments on commit 8d71105

Please sign in to comment.