commit 21a35b4f33e65c7472d80ed7f0539cfe0490d573
parent e2e2c5656c58542322525dc6e4cd34a0560b08bf
Author: Sean Enck <sean@ttypty.com>
Date: Wed, 8 Mar 2023 19:14:31 -0500
use unit test framework for integration tests
Diffstat:
9 files changed, 282 insertions(+), 243 deletions(-)
diff --git a/Makefile b/Makefile
@@ -14,12 +14,9 @@ $(TARGET): cmd/main.go internal/**/*.go go.* internal/cli/completions*
@git describe --tags --abbrev=0 | sed 's/^/version:/g'
@git tag --points-at HEAD | grep -q '' || echo "version:-1"
-unittest:
+check: $(TARGET)
go test -v ./...
-check: $(TARGET) unittest
- LB_BUILD=$(PWD)/$(TARGET) make -C $(TESTS)
-
clean:
rm -rf $(BUILD)
make -C $(TESTS) clean
diff --git a/internal/scripts/testing/Makefile b/internal/scripts/testing/Makefile
@@ -1,15 +0,0 @@
-BIN := bin
-ACTUAL := $(BIN)/actual.log
-RUNS := -keyfile=true -keyfile=false
-
-all: $(RUNS)
-
-$(RUNS):
- mkdir -p $(BIN)
- rm -f $(BIN)/*.kdbx
- LB_BUILD=$(LB_BUILD) go run check.go $@ -run=true -data $(BIN) > $(ACTUAL) 2>&1
- go run check.go -run=false -data $(ACTUAL)
- diff -u $(ACTUAL) expected.log
-
-clean:
- rm -rf $(BIN)
diff --git a/internal/scripts/testing/check.go b/internal/scripts/testing/check.go
@@ -1,224 +0,0 @@
-// package main runs the tests
-package main
-
-import (
- "bytes"
- "flag"
- "fmt"
- "os"
- "os/exec"
- "path/filepath"
- "regexp"
- "strings"
- "time"
-
- "github.com/enckse/pgl/os/exit"
- "github.com/enckse/pgl/os/paths"
-)
-
-var yes = []string{"y"}
-
-const (
- testKey = "plaintextkey"
-)
-
-func runCommand(args []string, data []string) {
- p := exec.Command(os.Getenv("LB_BUILD"), args...)
- var buf bytes.Buffer
- for _, d := range data {
- if _, err := buf.WriteString(fmt.Sprintf("%s\n", d)); err != nil {
- exit.Dief("failed to write stdin: %v", err)
- }
- }
- p.Stdout = os.Stdout
- p.Stderr = os.Stderr
- p.Stdin = &buf
- if err := p.Run(); err != nil {
- fmt.Println(err)
- }
-}
-
-func ls() {
- runCommand([]string{"ls"}, nil)
-}
-
-func rm(k string) {
- runCommand([]string{"rm", k}, yes)
-}
-
-func show(k string) {
- runCommand([]string{"show", k}, nil)
-}
-
-func insert(k string, d []string) {
- runCommand([]string{"insert", k}, d)
-}
-
-func totpList() {
- runCommand([]string{"totp", "-list"}, nil)
-}
-
-func main() {
- if err := execute(); err != nil {
- exit.Die(err)
- }
-}
-
-func replace(input string, re *regexp.Regexp, to string) string {
- matches := re.FindAllStringSubmatch(input, -1)
- res := input
- for _, match := range matches {
- for _, m := range match {
- res = strings.ReplaceAll(res, m, to)
- }
- }
- return res
-}
-
-func cleanup(dataFile, workDir string) error {
- data, err := os.ReadFile(dataFile)
- if err != nil {
- return err
- }
- totp, err := regexp.Compile("^[0-9][0-9][0-9][0-9][0-9][0-9]$")
- if err != nil {
- return err
- }
- date := fmt.Sprintf("modtime: %s", time.Now().Format("2006-01-02"))
- var results []string
- for _, l := range strings.Split(string(data), "\n") {
- payload := l
- payload = replace(payload, totp, "XXXXXX")
- if strings.Contains(payload, date) {
- prefix := ""
- if strings.HasPrefix(payload, " ") {
- prefix = " "
- }
- payload = fmt.Sprintf("%s%s", prefix, "modtime: XXXX-XX-XX")
- }
- results = append(results, payload)
- }
- return os.WriteFile(dataFile, []byte(strings.Join(results, "\n")), 0o644)
-}
-
-func execute() error {
- keyFile := flag.Bool("keyfile", false, "enable keyfile")
- dataPath := flag.String("data", "", "data area")
- runMode := flag.Bool("run", true, "execute tests")
- clipRetry := flag.Uint("clipboard-retry", 3, "number of tries for the clipboard test")
- clipWait := flag.Uint("clip-wait", 1000, "number of milliseconds to wait for the clipboard test")
- flag.Parse()
- path := *dataPath
- cwd, err := os.Getwd()
- if err != nil {
- return err
- }
- if !*runMode {
- return cleanup(path, cwd)
- }
- useKeyFile := ""
- if *keyFile {
- useKeyFile = filepath.Join(path, "test.key")
- if err := os.WriteFile(useKeyFile, []byte("thisisatest"), 0o644); err != nil {
- return err
- }
- }
- store := filepath.Join(path, fmt.Sprintf("%s.kdbx", time.Now().Format("20060102150405")))
- os.Setenv("LOCKBOX_HOOKDIR", "")
- os.Setenv("LOCKBOX_STORE", store)
- os.Setenv("LOCKBOX_KEY", testKey)
- os.Setenv("LOCKBOX_TOTP", "totp")
- os.Setenv("LOCKBOX_INTERACTIVE", "no")
- os.Setenv("LOCKBOX_READONLY", "no")
- os.Setenv("LOCKBOX_KEYMODE", "plaintext")
- os.Setenv("LOCKBOX_KEYFILE", useKeyFile)
- insert("keys/k/one2", []string{"test2"})
- for _, k := range []string{"keys/k/one", "key/a/one", "keys/k/one", "keys/k/one/", "/keys/k/one", "keys/aa/b//s///e"} {
- insert(k, []string{"test"})
- }
- insert("keys2/k/three", []string{"test3", "test4"})
- ls()
- rm("keys/k/one")
- fmt.Println()
- ls()
- runCommand([]string{"find", "e"}, nil)
- show("keys/k/one2")
- show("keys2/k/three")
- runCommand([]string{"stats", "keys2/k/three"}, nil)
- for _, k := range []string{"test/k", "test/k/totp"} {
- runCommand([]string{"insert", "-totp", k}, []string{"5ae472abqdekjqykoyxk7hvc2leklq5n"})
- }
- totpList()
- runCommand([]string{"totp", "test/k"}, nil)
- runCommand([]string{"hash", store}, nil)
- rm("keys2/k/three")
- fmt.Println()
- rm("test/k/totp")
- fmt.Println()
- rm("test/k/one")
- fmt.Println()
- fmt.Println()
- runCommand([]string{"mv", "key/a/one", "keyx/d/e"}, nil)
- ls()
- rm("keyx/d/e")
- fmt.Println()
- ls()
- insert("keys/k2/one2", []string{"test2"})
- insert("keys/k2/one", []string{"test"})
- insert("keys/k2/t1/one2", []string{"test2"})
- insert("keys/k2/t1/one", []string{"test"})
- insert("keys/k2/t2/one2", []string{"test2"})
- os.Setenv("LOCKBOX_HOOKDIR", filepath.Join(cwd, "hooks"))
- insert("keys/k2/t2/one", []string{"test"})
- fmt.Println()
- ls()
- rm("keys/k2/t1/*")
- fmt.Println()
- ls()
- rm("keys/k2/*")
- fmt.Println()
- ls()
- fmt.Println()
- reKeyStore := fmt.Sprintf("%s.rekey.kdbx", store)
- reKey := "rekey"
- os.Setenv("LOCKBOX_STORE_NEW", reKeyStore)
- os.Setenv("LOCKBOX_KEY_NEW", reKey)
- os.Setenv("LOCKBOX_KEYMODE_NEW", "plaintext")
- os.Setenv("LOCKBOX_KEYFILE_NEW", "")
- runCommand([]string{"rekey"}, yes)
- os.Setenv("LOCKBOX_STORE", reKeyStore)
- os.Setenv("LOCKBOX_KEYFILE", "")
- os.Setenv("LOCKBOX_KEY", reKey)
- fmt.Println()
- ls()
- testClipboard(path, *clipRetry, *clipWait)
- return nil
-}
-
-func testClipboard(dataPath string, tries uint, wait uint) {
- clipCopyFile := filepath.Join(dataPath, "clipboard")
- clipPasteFile := clipCopyFile + ".paste"
- clipFiles := []string{clipCopyFile, clipPasteFile}
- os.Setenv("LOCKBOX_CLIP_COPY", fmt.Sprintf("touch %s", clipCopyFile))
- os.Setenv("LOCKBOX_CLIP_PASTE", fmt.Sprintf("touch %s", clipPasteFile))
- os.Setenv("LOCKBOX_CLIP_MAX", "5")
- runCommand([]string{"clip", "keys/k/one2"}, nil)
- clipDur := time.Duration(wait) * time.Millisecond
- for {
- if tries == 0 {
- fmt.Println("missing clipboard files")
- break
- }
- foundClipCount := 0
- for _, f := range clipFiles {
- if paths.Exist(f) {
- foundClipCount++
- }
- }
- if foundClipCount == len(clipFiles) {
- break
- }
- time.Sleep(clipDur)
- tries--
- }
-}
diff --git a/internal/test/Makefile b/internal/test/Makefile
@@ -0,0 +1,6 @@
+$(RUNS):
+ mkdir -p $(BIN)
+ rm -f $(BIN)/*.kdbx
+ LB_BUILD=$(LB_BUILD) go run check.go $@ -run=true -data $(BIN) > $(ACTUAL) 2>&1
+ go run check.go -run=false -data $(ACTUAL)
+ diff -u $(ACTUAL) expected.log
diff --git a/internal/scripts/testing/expected.log b/internal/test/expected.log
diff --git a/internal/scripts/testing/hooks/all.sh b/internal/test/hooks/all.sh
diff --git a/internal/scripts/testing/hooks/test.sh b/internal/test/hooks/test.sh
diff --git a/internal/test/integration.go b/internal/test/integration.go
@@ -0,0 +1,229 @@
+// Package test has some integration tests for the binary lb variant
+package test
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "regexp"
+ "strings"
+ "time"
+
+ "github.com/enckse/pgl/os/exit"
+ "github.com/enckse/pgl/os/paths"
+)
+
+var yes = []string{"y"}
+
+const (
+ testKey = "plaintextkey"
+ clipRetry = 3
+ clipWait = 1000
+)
+
+type (
+ testRunner struct {
+ file *os.File
+ exe string
+ }
+)
+
+func (r testRunner) runCommand(args []string, data []string) {
+ p := exec.Command(r.exe, args...)
+ var buf bytes.Buffer
+ for _, d := range data {
+ if _, err := buf.WriteString(fmt.Sprintf("%s\n", d)); err != nil {
+ exit.Dief("failed to write stdin: %v", err)
+ }
+ }
+ p.Stdout = r.file
+ p.Stderr = r.file
+ p.Stdin = &buf
+ if err := p.Run(); err != nil {
+ fmt.Fprintf(r.file, "%v\n", err)
+ }
+}
+
+func (r testRunner) ls() {
+ r.runCommand([]string{"ls"}, nil)
+}
+
+func (r testRunner) rm(k string) {
+ r.runCommand([]string{"rm", k}, yes)
+}
+
+func (r testRunner) show(k string) {
+ r.runCommand([]string{"show", k}, nil)
+}
+
+func (r testRunner) insert(k string, d []string) {
+ r.runCommand([]string{"insert", k}, d)
+}
+
+func (r testRunner) totpList() {
+ r.runCommand([]string{"totp", "-list"}, nil)
+}
+
+func (r testRunner) ln() {
+ r.file.Write([]byte("\n"))
+}
+
+func replace(input string, re *regexp.Regexp, to string) string {
+ matches := re.FindAllStringSubmatch(input, -1)
+ res := input
+ for _, match := range matches {
+ for _, m := range match {
+ res = strings.ReplaceAll(res, m, to)
+ }
+ }
+ return res
+}
+
+// Cleanup will cleanup the data log outputs
+func Cleanup(dataFile string) error {
+ data, err := os.ReadFile(dataFile)
+ if err != nil {
+ return err
+ }
+ totp, err := regexp.Compile("^[0-9][0-9][0-9][0-9][0-9][0-9]$")
+ if err != nil {
+ return err
+ }
+ date := fmt.Sprintf("modtime: %s", time.Now().Format("2006-01-02"))
+ var results []string
+ for _, l := range strings.Split(string(data), "\n") {
+ payload := l
+ payload = replace(payload, totp, "XXXXXX")
+ if strings.Contains(payload, date) {
+ prefix := ""
+ if strings.HasPrefix(payload, " ") {
+ prefix = " "
+ }
+ payload = fmt.Sprintf("%s%s", prefix, "modtime: XXXX-XX-XX")
+ }
+ results = append(results, payload)
+ }
+ return os.WriteFile(dataFile, []byte(strings.Join(results, "\n")), 0o644)
+}
+
+// Execute will run a test
+func Execute(keyFile bool, exe, dataPath, logFile string) error {
+ f, err := os.Create(logFile)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ cwd, err := os.Getwd()
+ if err != nil {
+ return err
+ }
+ useKeyFile := ""
+ if keyFile {
+ useKeyFile = filepath.Join(dataPath, "test.key")
+ if err := os.WriteFile(useKeyFile, []byte("thisisatest"), 0o644); err != nil {
+ return err
+ }
+ }
+ store := filepath.Join(dataPath, fmt.Sprintf("%s.kdbx", time.Now().Format("20060102150405")))
+ os.Setenv("LOCKBOX_HOOKDIR", "")
+ os.Setenv("LOCKBOX_STORE", store)
+ os.Setenv("LOCKBOX_KEY", testKey)
+ os.Setenv("LOCKBOX_TOTP", "totp")
+ os.Setenv("LOCKBOX_INTERACTIVE", "no")
+ os.Setenv("LOCKBOX_READONLY", "no")
+ os.Setenv("LOCKBOX_KEYMODE", "plaintext")
+ os.Setenv("LOCKBOX_KEYFILE", useKeyFile)
+ runner := testRunner{file: f, exe: exe}
+ runner.insert("keys/k/one2", []string{"test2"})
+ for _, k := range []string{"keys/k/one", "key/a/one", "keys/k/one", "keys/k/one/", "/keys/k/one", "keys/aa/b//s///e"} {
+ runner.insert(k, []string{"test"})
+ }
+ runner.insert("keys2/k/three", []string{"test3", "test4"})
+ runner.ls()
+ runner.rm("keys/k/one")
+ runner.ln()
+ runner.ls()
+ runner.runCommand([]string{"find", "e"}, nil)
+ runner.show("keys/k/one2")
+ runner.show("keys2/k/three")
+ runner.runCommand([]string{"stats", "keys2/k/three"}, nil)
+ for _, k := range []string{"test/k", "test/k/totp"} {
+ runner.runCommand([]string{"insert", "-totp", k}, []string{"5ae472abqdekjqykoyxk7hvc2leklq5n"})
+ }
+ runner.totpList()
+ runner.runCommand([]string{"totp", "test/k"}, nil)
+ runner.runCommand([]string{"hash", store}, nil)
+ runner.rm("keys2/k/three")
+ runner.ln()
+ runner.rm("test/k/totp")
+ runner.ln()
+ runner.rm("test/k/one")
+ runner.ln()
+ runner.ln()
+ runner.runCommand([]string{"mv", "key/a/one", "keyx/d/e"}, nil)
+ runner.ls()
+ runner.rm("keyx/d/e")
+ runner.ln()
+ runner.ls()
+ runner.insert("keys/k2/one2", []string{"test2"})
+ runner.insert("keys/k2/one", []string{"test"})
+ runner.insert("keys/k2/t1/one2", []string{"test2"})
+ runner.insert("keys/k2/t1/one", []string{"test"})
+ runner.insert("keys/k2/t2/one2", []string{"test2"})
+ os.Setenv("LOCKBOX_HOOKDIR", filepath.Join(cwd, "hooks"))
+ runner.insert("keys/k2/t2/one", []string{"test"})
+ runner.ln()
+ runner.ls()
+ runner.rm("keys/k2/t1/*")
+ runner.ln()
+ runner.ls()
+ runner.rm("keys/k2/*")
+ runner.ln()
+ runner.ls()
+ runner.ln()
+ reKeyStore := fmt.Sprintf("%s.rekey.kdbx", store)
+ reKey := "rekey"
+ os.Setenv("LOCKBOX_STORE_NEW", reKeyStore)
+ os.Setenv("LOCKBOX_KEY_NEW", reKey)
+ os.Setenv("LOCKBOX_KEYMODE_NEW", "plaintext")
+ os.Setenv("LOCKBOX_KEYFILE_NEW", "")
+ runner.runCommand([]string{"rekey"}, yes)
+ os.Setenv("LOCKBOX_STORE", reKeyStore)
+ os.Setenv("LOCKBOX_KEYFILE", "")
+ os.Setenv("LOCKBOX_KEY", reKey)
+ runner.ln()
+ runner.ls()
+ return runner.clipboard(dataPath)
+}
+
+func (r testRunner) clipboard(dataPath string) error {
+ clipCopyFile := filepath.Join(dataPath, "clipboard")
+ clipPasteFile := clipCopyFile + ".paste"
+ clipFiles := []string{clipCopyFile, clipPasteFile}
+ os.Setenv("LOCKBOX_CLIP_COPY", fmt.Sprintf("touch %s", clipCopyFile))
+ os.Setenv("LOCKBOX_CLIP_PASTE", fmt.Sprintf("touch %s", clipPasteFile))
+ os.Setenv("LOCKBOX_CLIP_MAX", "5")
+ r.runCommand([]string{"clip", "keys/k/one2"}, nil)
+ clipDur := time.Duration(clipWait) * time.Millisecond
+ tries := clipRetry
+ for {
+ if tries == 0 {
+ return errors.New("missing clipboard files")
+ }
+ foundClipCount := 0
+ for _, f := range clipFiles {
+ if paths.Exist(f) {
+ foundClipCount++
+ }
+ }
+ if foundClipCount == len(clipFiles) {
+ break
+ }
+ time.Sleep(clipDur)
+ tries--
+ }
+ return nil
+}
diff --git a/internal/test/integration_test.go b/internal/test/integration_test.go
@@ -0,0 +1,46 @@
+package test_test
+
+import (
+ "os"
+ "os/exec"
+ "path/filepath"
+ "testing"
+
+ "github.com/enckse/lockbox/internal/test"
+ "github.com/enckse/pgl/os/paths"
+)
+
+const (
+ testDir = "bin"
+)
+
+func runTest(t *testing.T, keyFile bool) {
+ os.RemoveAll(testDir)
+ os.Mkdir(testDir, 0o755)
+ binary := filepath.Join("..", "..", testDir, "lb")
+ if !paths.Exist(binary) {
+ t.Error("no binary to test")
+ return
+ }
+ logFile := filepath.Join(testDir, "actual.log")
+ if err := test.Execute(keyFile, binary, testDir, logFile); err != nil {
+ t.Errorf("unexpected error: %v", err)
+ }
+ if err := test.Cleanup(logFile); err != nil {
+ t.Errorf("cleanup failed: %v", err)
+ }
+ diff := exec.Command("diff", "-u", logFile, "expected.log")
+ diff.Stdout = os.Stdout
+ diff.Stderr = os.Stderr
+ if err := diff.Run(); err != nil {
+ t.Errorf("diff failed: %v", err)
+ }
+}
+
+func TestKeyFile(t *testing.T) {
+ runTest(t, true)
+}
+
+func TestPasswordOnly(t *testing.T) {
+ runTest(t, false)
+}