diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..badd0a1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,24 @@ +version: 2 + +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: daily + commit-message: + prefix: "ci:" + groups: + dependencies: + patterns: + - "*" + + - package-ecosystem: gomod + directory: / + schedule: + interval: daily + commit-message: + prefix: "go:" + groups: + dependencies: + patterns: + - "*" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 76ef607..84777c9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,26 +1,35 @@ -name: release +name: tagpr on: push: - tags: - - "v[0-9]+.[0-9]+.[0-9]+" + branches: + - main jobs: - goreleaser: + tagpr: runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + issues: write steps: - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 + + - uses: Songmu/tagpr@b3fb89424646b06c8aa460f50307c60b6a541425 # v1 + id: tagpr + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Setup Go - uses: actions/setup-go@v2 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v4 with: - go-version: 1.15 + go-version-file: go.mod + if: ${{ steps.tagpr.outputs.tag != ''}} - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v2 + uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7.0.0 with: version: latest - args: release --rm-dist + args: release --clean env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CGO_ENABLED: 0 + if: ${{ steps.tagpr.outputs.tag != ''}} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c241ec3..f229ed8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,16 +6,19 @@ on: jobs: test: runs-on: ubuntu-latest + permissions: + contents: read steps: - - name: Checkout - uses: actions/checkout@v2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 + - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v4 with: - fetch-depth: 0 - - - name: Setup Go - uses: actions/setup-go@v2 - with: - go-version: 1.15 + go-version-file: go.mod - name: test run: make check + + - name: Snapshot GoReleaser + uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7.0.0 + with: + version: latest + args: build --snapshot diff --git a/.goreleaser.yml b/.goreleaser.yml index 4614622..f73c945 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,18 +1,14 @@ builds: - binary: check-diff goos: - - darwin - linux goarch: - amd64 - arm64 - ignore: - - goos: darwin - goarch: arm64 archives: - format: zip name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}" release: github: - owner: kazeburo + owner: monitoring-forge name: check-diff diff --git a/Makefile b/Makefile index d2e2922..652283c 100644 --- a/Makefile +++ b/Makefile @@ -1,23 +1,17 @@ VERSION=0.0.5 -LDFLAGS=-ldflags "-w -s -X main.version=${VERSION}" +GITCOMMIT?=$(shell git describe --dirty --always) +LDFLAGS=-ldflags "-w -s -X main.version=${VERSION} -X main.commit=${GITCOMMIT}" all: check-diff .PHONY: check-diff -check-diff: main.go - go build $(LDFLAGS) -o check-diff main.go +check-diff: main.go open_unix.go open_windows.go + go build $(LDFLAGS) -o check-diff . -linux: main.go - GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o check-diff main.go +linux: main.go open_unix.go + GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o check-diff open_unix.go main.go check: - go test ./... + go test -v ./... -fmt: - go fmt ./... - -tag: - git tag v${VERSION} - git push origin v${VERSION} - git push origin master diff --git a/README.md b/README.md index 8fa14e7..b8983fe 100644 --- a/README.md +++ b/README.md @@ -47,4 +47,4 @@ command = "/usr/local/bin/check-diff -- cat /etc/passwd" ## Install -Please download release page or `mkr plugin install kazeburo/check-diff`. \ No newline at end of file +Please download release page or `mkr plugin install monitoring-forge/check-diff`. diff --git a/go.mod b/go.mod index 4c94ad7..4493079 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,15 @@ -module github.com/kazeburo/check-diff +module github.com/monitoring-forge/check-diff -go 1.14 +go 1.25.0 require ( - github.com/jessevdk/go-flags v1.4.0 - github.com/mackerelio/checkers v0.0.0-20200428063449-52cfb2c2c52c + github.com/jessevdk/go-flags v1.6.1 + github.com/mackerelio/checkers v0.2.1 +) + +require github.com/cubicdaiya/gonp v1.0.4 + +require ( + github.com/mackerelio/golib v1.2.2 + golang.org/x/sys v0.47.0 // indirect ) diff --git a/go.sum b/go.sum index 89117e7..3ff45c5 100644 --- a/go.sum +++ b/go.sum @@ -1,28 +1,10 @@ -github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/mackerelio/checkers v0.0.0-20200428063449-52cfb2c2c52c h1:gTJ7KeXinv4dbCCh5zXV7clWNjk0ESazARKhm87U0NE= -github.com/mackerelio/checkers v0.0.0-20200428063449-52cfb2c2c52c/go.mod h1:h/TgaBtgIw4WZMY8OiHhxhd49mY4hEPo6r2IaqpMGS8= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b h1:zSzQJAznWxAh9fZxiPy2FZo+ZZEYoYFYYDYdOrU7AaM= -golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +github.com/cubicdaiya/gonp v1.0.4 h1:ky2uIAJh81WiLcGKBVD5R7KsM/36W6IqqTy6Bo6rGws= +github.com/cubicdaiya/gonp v1.0.4/go.mod h1:iWGuP/7+JVTn02OWhRemVbMmG1DOUnmrGTYYACpOI0I= +github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4= +github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc= +github.com/mackerelio/checkers v0.2.1 h1:vRXzAT+CEyaLjSrHVxKUBjyWq1kfo8QcI8ZoSKZATQs= +github.com/mackerelio/checkers v0.2.1/go.mod h1:CW3k/5bvHhxDrfKgWvMvNH0R51zco141ZVxlI7o/KAc= +github.com/mackerelio/golib v1.2.2 h1:fQfW1o2KvPyIsAH/7+M2J0gpf2jLu+NyYpDCN79zxW4= +github.com/mackerelio/golib v1.2.2/go.mod h1:itcSOfrsT5XvkzflFPebT43RrliaKDz2T6YGYt3jclw= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= diff --git a/main.go b/main.go index bffb71d..e4408f1 100644 --- a/main.go +++ b/main.go @@ -1,37 +1,39 @@ package main import ( + "bufio" "bytes" - "crypto/md5" + "crypto/sha256" "fmt" - "io/ioutil" "os" "os/exec" "os/user" "path/filepath" - "regexp" "runtime" "strings" + "github.com/cubicdaiya/gonp" "github.com/jessevdk/go-flags" "github.com/mackerelio/checkers" + "github.com/mackerelio/golib/pluginutil" ) -// version by Makefile var version string - -type cmdOpts struct { - OptArgs []string - OptCommand string - OptIdentifier string `long:"identifier" description:"indetify a file store the command result with given string"` - OptWarn bool `short:"w" long:"warn" description:"Set the error level to warning"` - Version bool `short:"v" long:"version" description:"Show version"` +var commit string + +type Opt struct { + Args []string + Command string + Identifier string `long:"identifier" description:"identify the file used to store the command result with the given string"` + Warn bool `short:"w" long:"warn" description:"Set the error level to warning"` + Workdir string `long:"workdir" description:"Set the working directory"` + Version bool `short:"v" long:"version" description:"Show version"` } -func runCmd(curFile *os.File, opts cmdOpts) error { - cmd := exec.Command(opts.OptCommand, opts.OptArgs...) +func (opt *Opt) cmd(file *os.File) error { + cmd := exec.Command(opt.Command, opt.Args...) var stderr bytes.Buffer - cmd.Stdout = curFile + cmd.Stdout = file cmd.Stderr = &stderr if err := cmd.Start(); err != nil { return err @@ -43,126 +45,168 @@ func runCmd(curFile *os.File, opts cmdOpts) error { return nil } -func calcDiff(diffCmd, from, to string) ([]byte, error) { - return exec.Command(diffCmd, "-U", "1", from, to).Output() -} +func (opt *Opt) run() *checkers.Checker { -func fileExists(filename string) bool { - _, err := os.Stat(filename) - return err == nil -} + identifier := strings.Join([]string{ + opt.Identifier, + opt.Command, + strings.Join(opt.Args, "-"), + }, "-") + hasher := sha256.New() + hasher.Write([]byte(identifier)) -func checkDiff(opts cmdOpts) *checkers.Checker { - diffCmd, err := exec.LookPath("diff") + curUser, err := user.Current() if err != nil { return checkers.Critical(err.Error()) } - tmpDir := os.TempDir() - - hasher := md5.New() - hasher.Write([]byte(opts.OptIdentifier)) - hasher.Write([]byte(opts.OptCommand)) - for _, v := range opts.OptArgs { - hasher.Write([]byte(v)) + prevPath := filepath.Join(opt.Workdir, fmt.Sprintf("check-diff-%s-%x", curUser.Uid, hasher.Sum(nil))) + newFile, err := os.CreateTemp(opt.Workdir, "check-diff-") + if err != nil { + return checkers.Critical(err.Error()) } - commandKey := fmt.Sprintf("%x", hasher.Sum(nil)) - - curUser, _ := user.Current() - prevPath := filepath.Join(tmpDir, "check-diff-"+curUser.Uid+"-"+commandKey) - curFile, err := ioutil.TempFile(tmpDir, "temp") + err = opt.cmd(newFile) if err != nil { + newFile.Close() + os.Remove(newFile.Name()) return checkers.Critical(err.Error()) } - defer os.Remove(curFile.Name()) - - err = runCmd(curFile, opts) + err = newFile.Close() if err != nil { return checkers.Critical(err.Error()) } if !fileExists(prevPath) { - err = os.Rename(curFile.Name(), prevPath) + err = os.Rename(newFile.Name(), prevPath) if err != nil { return checkers.Critical(err.Error()) } - msg := "" - if len(opts.OptArgs) > 0 { - msg = fmt.Sprintf("first time execution command: '%s %s'", opts.OptCommand, strings.Join(opts.OptArgs, " ")) - } else { - msg = fmt.Sprintf("first time execution command: '%s'", opts.OptCommand) + if len(opt.Args) > 0 { + return checkers.Ok(fmt.Sprintf("first time execution command: '%s %s'", opt.Command, strings.Join(opt.Args, " "))) } - return checkers.Ok(msg) + return checkers.Ok(fmt.Sprintf("first time execution command: '%s'", opt.Command)) } - // diff - diffOut, diffError := calcDiff(diffCmd, prevPath, curFile.Name()) - err = os.Rename(curFile.Name(), prevPath) + diff, err := diff(prevPath, newFile.Name()) if err != nil { return checkers.Critical(err.Error()) } - if diffError == nil { - // no difference - curOpen, err := os.Open(curFile.Name()) - if err != nil { - return checkers.Critical(err.Error()) - } - defer curOpen.Close() + err = os.Rename(newFile.Name(), prevPath) + if err != nil { + return checkers.Critical(err.Error()) + } - fileinfo, _ := curOpen.Stat() - data := make([]byte, 128) - count, err := curOpen.Read(data) + if diff == "" { + msg, err := buildNoDifferenceMsg(prevPath) if err != nil { return checkers.Critical(err.Error()) } - cur := string(data[0:count]) - cur = regexp.MustCompile("(\r\n|\r|\n)$").ReplaceAllString(cur, "") - msg := "" - if fileinfo.Size() > 128 { - msg = fmt.Sprintf("no difference: ```%s...```\n", cur) - } else { - msg = fmt.Sprintf("no difference: ```%s```\n", cur) - } return checkers.Ok(msg) - } else if regexp.MustCompile("exit status 1").MatchString(diffError.Error()) { - // found diff - diffRet := strings.Split(string(diffOut), "\n") - diffRetString := strings.Join(diffRet[2:], "\n") - diffRetString = regexp.MustCompile("(\r\n|\r|\n)$").ReplaceAllString(diffRetString, "") - msg := "" - if len(diffRetString) > 512 { - msg = fmt.Sprintf("found difference: ```%s...```\n", diffRetString[0:512]) - } else { - msg = fmt.Sprintf("found difference: ```%s```\n", diffRetString) - } - if opts.OptWarn { - return checkers.Warning(msg) - } - return checkers.Critical(msg) } - return checkers.Critical(diffError.Error()) + + diffMsg := buildDiffMsg(diff) + if opt.Warn { + return checkers.Warning(diffMsg) + } + return checkers.Critical(diffMsg) } -func printVersion() { - fmt.Printf(`%s %s -Compiler: %s %s -`, - os.Args[0], - version, - runtime.Compiler, - runtime.Version()) +func buildNoDifferenceMsg(filename string) (string, error) { + file, err := openRD(filename) + if err != nil { + return "", err + } + defer file.Close() + + fileinfo, err := file.Stat() + if err != nil { + return "", err + } + b := make([]byte, 128) + count, err := file.Read(b) + if err != nil { + return "", err + } + o := string(strings.TrimRight(string(b[0:count]), "\r\n")) + if fileinfo.Size() > 128 { + return fmt.Sprintf("no difference: ```%s...```", o), nil + } + return fmt.Sprintf("no difference: ```%s```", o), nil +} + +func buildDiffMsg(diff string) string { + o := diff + if len(diff) > 512 { + o = diff[0:512] + } + o = strings.TrimRight(o, "\r\n") + + if len(diff) > 512 { + return fmt.Sprintf("found difference: ```%s...```", o) + } + return fmt.Sprintf("found difference: ```%s```", o) +} + +func getLines(filename string) ([]string, error) { + file, err := openRD(filename) + if err != nil { + return nil, err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + scanner.Buffer(make([]byte, 64*1024), 10*1024*1024) + lines := make([]string, 0) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + if err := scanner.Err(); err != nil { + return nil, err + } + return lines, nil +} + +func diff(prev, new string) (string, error) { + prevLines, err := getLines(prev) + if err != nil { + return "", err + } + newLines, err := getLines(new) + if err != nil { + return "", err + } + + diff := gonp.New(prevLines, newLines) + diff.Compose() + + return diff.SprintUniHunks(diff.UnifiedHunks()), nil +} + +func fileExists(filename string) bool { + _, err := os.Stat(filename) + return err == nil } func main() { - opts := cmdOpts{} - psr := flags.NewParser(&opts, flags.HelpFlag|flags.PassDoubleDash) + opt := &Opt{} + psr := flags.NewParser(opt, flags.HelpFlag|flags.PassDoubleDash) psr.Usage = "[OPTIONS] -- command args1 args2" args, err := psr.Parse() - if opts.Version { - printVersion() + if opt.Version { + if commit == "" { + commit = "dev" + } + fmt.Printf( + "%s-%s\n%s/%s, %s, %s\n", + filepath.Base(os.Args[0]), + version, + runtime.GOOS, + runtime.GOARCH, + runtime.Version(), + commit) os.Exit(0) } if err != nil { @@ -173,12 +217,17 @@ func main() { psr.WriteHelp(os.Stderr) os.Exit(1) } - opts.OptCommand = args[0] + opt.Args = []string{} + opt.Command = args[0] if len(args) > 1 { - opts.OptArgs = args[1:] + opt.Args = args[1:] + } + + if opt.Workdir == "" { + opt.Workdir = pluginutil.PluginWorkDir() } - ckr := checkDiff(opts) + ckr := opt.run() ckr.Name = "check-diff" ckr.Exit() } diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..7356404 --- /dev/null +++ b/main_test.go @@ -0,0 +1,203 @@ +package main + +import ( + "bufio" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/mackerelio/checkers" +) + +func getCmdOpt(t *testing.T, command string, args []string) (Opt, *os.File) { + workdir := t.TempDir() + filename := filepath.Join(workdir, "test.txt") + file, err := os.Create(filename) + if err != nil { + t.Fatalf("Failed to create file: %v", err) + } + return Opt{ + Command: command, + Args: args, + Identifier: "test", + Workdir: workdir, + }, file +} + +// Test cmd +func TestCmd(t *testing.T) { + opt, file := getCmdOpt(t, "echo", []string{"Hello, World!"}) + defer file.Close() + + if err := opt.cmd(file); err != nil { + t.Fatalf("cmd failed: %v", err) + } + + file.Seek(0, 0) // Reset file pointer to the beginning + scanner := bufio.NewScanner(file) + if scanner.Scan() { + output := scanner.Text() + expected := "Hello, World!" + if output != expected { + t.Errorf("Expected output %q, got %q", expected, output) + } + } else { + t.Errorf("No output from command") + } +} + +func TestCmdFailedCase(t *testing.T) { + opt, file := getCmdOpt(t, "nonexistent_command", []string{}) + defer file.Close() + + if err := opt.cmd(file); err == nil { + t.Fatalf("Expected cmd to fail, but it succeeded") + } +} + +func TestCmdFailedCommand(t *testing.T) { + opt, file := getCmdOpt(t, "ls", []string{"nonexistent_directory"}) + defer file.Close() + if err := opt.cmd(file); err == nil { + t.Fatalf("Expected cmd to fail, but it succeeded") + } +} + +func createTempFile(t *testing.T, content string) (*os.File, func()) { + tmpfile, err := os.CreateTemp("", "testfile") + if err != nil { + t.Fatalf("Failed to create temp file: %v", err) + } + if _, err := tmpfile.WriteString(content); err != nil { + t.Fatalf("Failed to write to temp file: %v", err) + } + if err := tmpfile.Close(); err != nil { + t.Fatalf("Failed to close temp file: %v", err) + } + return tmpfile, func() { + os.Remove(tmpfile.Name()) + } +} + +func TestBuildNoDifferenceMsg(t *testing.T) { + shortMsg := "This is a test message." + file, cleanup := createTempFile(t, shortMsg+"\n") + defer cleanup() + expected := "no difference: ```" + shortMsg + "```" + result, err := buildNoDifferenceMsg(file.Name()) + if err != nil { + t.Fatalf("buildNoDifferenceMsg failed: %v", err) + } + if result != expected { + t.Errorf("Expected %q, got %q", expected, result) + } +} + +func TestBuildNoDifferenceMsgLargeResult(t *testing.T) { + largeMsg := strings.Repeat("A", 600) + file, cleanup := createTempFile(t, largeMsg+"\n") + defer cleanup() + + expected := "no difference: ```" + largeMsg[:128] + "...```" + result, err := buildNoDifferenceMsg(file.Name()) + if err != nil { + t.Fatalf("buildNoDifferenceMsg failed: %v", err) + } + if result != expected { + t.Errorf("Expected %q, got %q", expected, result) + } +} + +func TestBuildDiffMsg(t *testing.T) { + diff := "This is a test diff message.\nThis is the second line of the diff.\n" + expected := "found difference: ```This is a test diff message.\nThis is the second line of the diff.```" + result := buildDiffMsg(diff) + if result != expected { + t.Errorf("Expected %q, got %q", expected, result) + } +} + +func TestBuildDiffMsgLargeResult(t *testing.T) { + diff := strings.Repeat("A", 600) + expected := "found difference: ```" + diff[:512] + "...```" + result := buildDiffMsg(diff) + if result != expected { + t.Errorf("Expected %q, got %q", expected, result) + } +} + +func TestDiff(t *testing.T) { + file1, cleanup1 := createTempFile(t, "Hello, World!\n") + defer cleanup1() + + file2, cleanup2 := createTempFile(t, "Hello, Go!\n") + defer cleanup2() + + diffResult, err := diff(file1.Name(), file2.Name()) + if err != nil { + t.Fatalf("diff failed: %v", err) + } + if diffResult == "" { + t.Errorf("Expected a diff result, got empty string") + } + if !strings.Contains(diffResult, "Hello, World!") || !strings.Contains(diffResult, "Hello, Go!") { + t.Errorf("Diff result does not contain expected content: %q", diffResult) + } +} + +func TestDiffNoDifference(t *testing.T) { + file1, cleanup1 := createTempFile(t, "Hello, World!\n") + defer cleanup1() + + file2, cleanup2 := createTempFile(t, "Hello, World!\n") + defer cleanup2() + + diffResult, err := diff(file1.Name(), file2.Name()) + if err != nil { + t.Fatalf("diff failed: %v", err) + } + if diffResult != "" { + t.Errorf("Expected no diff result, got: %q", diffResult) + } +} + +func TestRun(t *testing.T) { + opt, file := getCmdOpt(t, "echo", []string{"Hello, World!"}) + defer file.Close() + ckr := opt.run() + if ckr.Status != checkers.OK { + t.Errorf("Expected OK status, got %v", ckr.Status) + } + if !strings.Contains(ckr.Message, "first time execution command") { + t.Errorf("Expected first time execution message, got %q", ckr.Message) + } + + // Run again to check for no difference + ckr = opt.run() + if ckr.Status != checkers.OK { + t.Errorf("Expected OK status, got %v", ckr.Status) + } + if !strings.Contains(ckr.Message, "no difference") { + t.Errorf("Expected no difference message, got %q", ckr.Message) + } +} + +func TestRunWithDifference(t *testing.T) { + opt, file := getCmdOpt(t, "date", []string{}) + defer file.Close() + // First run to create the initial state + opt.run() + + // Change the command to produce a different output + time.Sleep(2 * time.Second) // Ensure the date command produces a different output + opt.Command = "date" + ckr := opt.run() + if ckr.Status != checkers.CRITICAL { + t.Errorf("Expected CRITICAL status, got %v", ckr.Status) + } + if !strings.Contains(ckr.Message, "found difference") { + t.Errorf("Expected found difference message, got %q", ckr.Message) + } +} diff --git a/open_unix.go b/open_unix.go new file mode 100644 index 0000000..d4ff433 --- /dev/null +++ b/open_unix.go @@ -0,0 +1,15 @@ +//go:build !windows +// +build !windows + +package main + +import ( + "os" + "syscall" +) + +// openRD opens a file for reading with O_NOFOLLOW flag to prevent symlink attacks. +// This function is specific to Unix-like systems where syscall.O_NOFOLLOW is available. +func openRD(filename string) (*os.File, error) { + return os.OpenFile(filename, os.O_RDONLY|syscall.O_NOFOLLOW, 0) +} diff --git a/open_windows.go b/open_windows.go new file mode 100644 index 0000000..1f6d94d --- /dev/null +++ b/open_windows.go @@ -0,0 +1,12 @@ +//go:build windows +// +build windows + +package main + +import "os" + +// openRD opens a file for reading with appropriate flags for Windows. +// Since Windows doesn't support O_NOFOLLOW, we simply open the file with O_RDONLY. +func openRD(filename string) (*os.File, error) { + return os.OpenFile(filename, os.O_RDONLY, 0) +}