From 4e4caef10f509421240a32bea66c9d4ab32fa3e1 Mon Sep 17 00:00:00 2001 From: David Dworken Date: Thu, 22 Sep 2022 23:06:28 -0700 Subject: [PATCH] Add initial implementation of fuzz testing that fuzzes multiple devices and multiple users --- Makefile | 3 ++ client/client_test.go | 104 ++++++++++++++++++++++++++++++++++++++++++ shared/testutils.go | 14 ++++++ 3 files changed, 121 insertions(+) diff --git a/Makefile b/Makefile index 56928e6..1261af1 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,9 @@ forcetest: test: HISHTORY_TEST=1 go test -p 1 ./... +fuzz: + HISHTORY_TEST=1 go test -p 1 -fuzz=FuzzTestMultipleUsers -run FuzzTestMultipleUsers client/client_test.go + acttest: act push -j test -e .github/push_event.json --reuse --container-architecture linux/amd64 diff --git a/client/client_test.go b/client/client_test.go index d2ab9f3..c395592 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -1643,4 +1643,108 @@ func testMultipleUsers(t *testing.T, tester shellTester) { } } +type operation struct { + device device + cmd string +} + +func FuzzTestMultipleUsers(f *testing.F) { + f.Add("a;b|2\n") + f.Add("a;b|aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n") + f.Add("a;b|aaaBBcccDD\n") + f.Add("a;a|hello\na;a|world") + f.Add("a;a|hello\na;a|world\na;b|3") + f.Add("a;a|1\na;a|2\na;b|3\nb;a|4\na;b|5") + f.Add("a;a|1\na;a|2\na;b|1\n") + f.Add("a;a|1\na;a|2\na;b|1\nz;z|1\na;a|1\n") + f.Add("a;a|hello\na;a|wobld") + f.Add("a;a|1\nb;a|2\nc;a|2\nd;a|2\na;b|2\na;b|3\na;b|4\na;b|8\na;d|2\nb;a|1") + f.Add("a;a|1\na;b|1\na;c|1\na;d|1\na;e|1\na;f|1\na;g|1\na;b|1\na;b|1\na;b|1\na;b|1") + tmp := 0 + var runCounter *int = &tmp + f.Fuzz(func(t *testing.T, input string) { + *runCounter += 1 + // Parse the input + if len(input) > 1_000 { + return + } + input = strings.TrimSpace(input) + ops := make([]operation, 0) + for _, line := range strings.Split(input, "\n") { + split1 := strings.SplitN(line, "|", 2) + if len(split1) != 2 { + return + } + split2 := strings.SplitN(split1[0], ";", 2) + if len(split2) != 2 { + return + } + cmd := split1[1] + re := regexp.MustCompile(`[a-zA-Z]+`) + if !re.MatchString(cmd) { + return + } + key := split2[0] + if strings.Contains(key, "-") { + return + } + op := operation{device: device{key: key + "-" + strconv.Itoa(*runCounter), deviceId: split2[1]}, cmd: "echo " + cmd} + ops = append(ops, op) + } + + // Set up and create the devices + defer shared.BackupAndRestore(t)() + tester := bashTester{} + var deviceMap map[device]deviceOp = make(map[device]deviceOp) + var devices deviceSet = deviceSet{} + devices.deviceMap = &deviceMap + devices.currentDevice = nil + for _, op := range ops { + _, ok := (*devices.deviceMap)[op.device] + if ok { + continue + } + createDevice(t, tester, &devices, op.device.key, op.device.deviceId) + } + + // Persist our basic in-memory copy of expected shell commands + keyToCommands := make(map[string]string) + + // Run the commands + for _, op := range ops { + // Run the command + switchToDevice(&devices, op.device) + _, _ = tester.RunInteractiveShellRelaxed(t, op.cmd) + + // Calculate the expected output of hishtory export + val, ok := keyToCommands[op.device.key] + if !ok { + val = "" + } + val += op.cmd + val += "\n" + keyToCommands[op.device.key] = val + + // Run hishtory export and check the output + out, err := tester.RunInteractiveShellRelaxed(t, `hishtory export | grep -v export`) + shared.Check(t, err) + expectedOutput := keyToCommands[op.device.key] + if diff := cmp.Diff(expectedOutput, out); diff != "" { + t.Fatalf("hishtory export mismatch for input=%#v key=%s (-expected +got):\n%s\nout=%#v", input, op.device.key, diff, out) + } + } + + // Check that hishtory export has the expected results + for _, op := range ops { + switchToDevice(&devices, op.device) + out, err := tester.RunInteractiveShellRelaxed(t, `hishtory export | grep -v export`) + shared.Check(t, err) + expectedOutput := keyToCommands[op.device.key] + if diff := cmp.Diff(expectedOutput, out); diff != "" { + t.Fatalf("hishtory export mismatch for key=%s (-expected +got):\n%s\nout=%#v", op.device.key, diff, out) + } + } + }) +} + // TODO(future): Can we do a fuzz test with lots of users and lots of devices? diff --git a/shared/testutils.go b/shared/testutils.go index 85f93aa..0ceaa61 100644 --- a/shared/testutils.go +++ b/shared/testutils.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "io" + "io/ioutil" "log" "net/http" "os" @@ -39,6 +40,19 @@ func BackupAndRestore(t *testing.T) func() { return BackupAndRestoreWithId(t, "") } +func DeleteBakFiles(t *testing.T) { + homedir, err := os.UserHomeDir() + checkError(err) + entries, err := ioutil.ReadDir(path.Join(homedir, HISHTORY_PATH)) + checkError(err) + for _, entry := range entries { + fmt.Println(entry.Name()) + if strings.HasSuffix(entry.Name(), ".bak") { + checkError(os.Remove(path.Join(homedir, HISHTORY_PATH, entry.Name()))) + } + } +} + func BackupAndRestoreWithId(t *testing.T, id string) func() { homedir, err := os.UserHomeDir() if err != nil {