diff --git a/README.md b/README.md index 47a20a2..db3b574 100644 --- a/README.md +++ b/README.md @@ -80,5 +80,4 @@ When the user runs `hishtory query`, it retrieves all unread blobs from the back ## Pending Features * zsh support -* mac support -* support for verifying the attestations tied to updates +* easier install flow that is cross-platform diff --git a/client/client_test.go b/client/client_test.go index f52611d..a316797 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -20,6 +20,8 @@ import ( "github.com/ddworken/hishtory/shared" ) +// TODO: Go through all the set -m that are sprinkled around and clean those up + func TestMain(m *testing.M) { defer shared.RunTestServer()() cmd := exec.Command("go", "build", "-o", "/tmp/client") @@ -30,8 +32,17 @@ func TestMain(m *testing.M) { m.Run() } -func RunInteractiveBashCommands(t *testing.T, script string) string { - out, err := RunInteractiveBashCommandsWithoutStrictMode(t, "set -emo pipefail\n"+script) +type shellTester interface { + RunInteractiveShell(t *testing.T, script string) string + RunInteractiveShellRelaxed(t *testing.T, script string) (string, error) + ShellName() string +} +type bashTester struct { + shellTester +} + +func (b bashTester) RunInteractiveShell(t *testing.T, script string) string { + out, err := b.RunInteractiveShellRelaxed(t, "set -emo pipefail\n"+script) if err != nil { _, filename, line, _ := runtime.Caller(1) t.Fatalf("error when running command at %s:%d: %v", filename, line, err) @@ -39,7 +50,7 @@ func RunInteractiveBashCommands(t *testing.T, script string) string { return out } -func RunInteractiveBashCommandsWithoutStrictMode(t *testing.T, script string) (string, error) { +func (b bashTester) RunInteractiveShellRelaxed(t *testing.T, script string) (string, error) { cmd := exec.Command("bash", "-i") cmd.Stdin = strings.NewReader(script) var stdout bytes.Buffer @@ -57,29 +68,85 @@ func RunInteractiveBashCommandsWithoutStrictMode(t *testing.T, script string) (s return outStr, nil } -func TestIntegration(t *testing.T) { - // Set up - defer shared.BackupAndRestore(t)() - - // Run the test - testIntegration(t) +func (b bashTester) ShellName() string { + return "bash" } -func TestIntegrationWithNewDevice(t *testing.T) { +type zshTester struct { + shellTester +} + +func (z zshTester) RunInteractiveShell(t *testing.T, script string) string { + // TODO: make this strict + res, err := z.RunInteractiveShellRelaxed(t, script) + if err != nil { + t.Fatal(err) + } + return res +} + +func (z zshTester) RunInteractiveShellRelaxed(t *testing.T, script string) (string, error) { + cmd := exec.Command("zsh", "-is") + cmd.Stdin = strings.NewReader(script) + var stdout bytes.Buffer + cmd.Stdout = &stdout + var stderr bytes.Buffer + cmd.Stderr = &stderr + err := cmd.Run() + if err != nil { + return "", fmt.Errorf("unexpected error when running commands, out=%#v, err=%#v: %v", stdout.String(), stderr.String(), err) + } + outStr := stdout.String() + if strings.Contains(outStr, "hishtory fatal error") { + t.Fatalf("Ran command, but hishtory had a fatal error! out=%#v", outStr) + } + return outStr, nil +} + +func (z zshTester) ShellName() string { + return "zsh" +} + +var shellTesters []shellTester = []shellTester{bashTester{}, zshTester{}} + +func TestParameterized(t *testing.T) { + for _, tester := range shellTesters { + t.Run("testRepeatedCommandThenQuery/"+tester.ShellName(), func(t *testing.T) { testRepeatedCommandThenQuery(t, tester) }) + t.Run("testRepeatedCommandAndQuery/"+tester.ShellName(), func(t *testing.T) { testRepeatedCommandAndQuery(t, tester) }) + t.Run("testRepeatedEnableDisable/"+tester.ShellName(), func(t *testing.T) { testRepeatedEnableDisable(t, tester) }) + t.Run("testExcludeHiddenCommand/"+tester.ShellName(), func(t *testing.T) { testExcludeHiddenCommand(t, tester) }) + t.Run("testUpdate/"+tester.ShellName(), func(t *testing.T) { testUpdate(t, tester) }) + t.Run("testAdvancedQuery/"+tester.ShellName(), func(t *testing.T) { testAdvancedQuery(t, tester) }) + t.Run("testIntegration/"+tester.ShellName(), func(t *testing.T) { testIntegration(t, tester) }) + t.Run("testIntegrationWithNewDevice/"+tester.ShellName(), func(t *testing.T) { testIntegrationWithNewDevice(t, tester) }) + t.Run("testHishtoryBackgroundSaving/"+tester.ShellName(), func(t *testing.T) { testHishtoryBackgroundSaving(t, tester) }) + t.Run("testDisplayTable/"+tester.ShellName(), func(t *testing.T) { testDisplayTable(t, tester) }) + } +} + +func testIntegration(t *testing.T, tester shellTester) { // Set up defer shared.BackupAndRestore(t)() // Run the test - userSecret := testIntegration(t) + testBasicUserFlow(t, tester) +} + +func testIntegrationWithNewDevice(t *testing.T, tester shellTester) { + // Set up + defer shared.BackupAndRestore(t)() + + // Run the test + userSecret := testBasicUserFlow(t, tester) // Clear all local state shared.ResetLocalState(t) // Install it again - installHishtory(t, userSecret) + installHishtory(t, tester, userSecret) // Querying should show the history from the previous run - out := hishtoryQuery(t, "") + out := hishtoryQuery(t, tester, "") expected := []string{"echo thisisrecorded", "hishtory enable", "echo bar", "echo foo", "ls /foo", "ls /bar", "ls /a"} for _, item := range expected { if !strings.Contains(out, item) { @@ -90,8 +157,8 @@ func TestIntegrationWithNewDevice(t *testing.T) { } } - RunInteractiveBashCommands(t, "echo mynewcommand") - out = hishtoryQuery(t, "") + tester.RunInteractiveShell(t, "echo mynewcommand") + out = hishtoryQuery(t, tester, "") if !strings.Contains(out, "echo mynewcommand") { t.Fatalf("output is missing `echo mynewcommand`") } @@ -103,23 +170,23 @@ func TestIntegrationWithNewDevice(t *testing.T) { shared.ResetLocalState(t) // Install it a 3rd time - installHishtory(t, "adifferentsecret") + installHishtory(t, tester, "adifferentsecret") // Run a command that shouldn't be in the hishtory later on - RunInteractiveBashCommands(t, `echo notinthehistory`) - out = hishtoryQuery(t, "") + tester.RunInteractiveShell(t, `echo notinthehistory`) + out = hishtoryQuery(t, tester, "") if !strings.Contains(out, "echo notinthehistory") { t.Fatalf("output is missing `echo notinthehistory`") } // Set the secret key to the previous secret key - out = RunInteractiveBashCommands(t, `hishtory init `+userSecret) + out = tester.RunInteractiveShell(t, `hishtory init `+userSecret) if !strings.Contains(out, "Setting secret hishtory key to "+userSecret) { t.Fatalf("Failed to re-init with the user secret: %v", out) } // Querying should show the history from the previous run - out = hishtoryQuery(t, "") + out = hishtoryQuery(t, tester, "") expected = []string{"echo thisisrecorded", "echo mynewcommand", "hishtory enable", "echo bar", "echo foo", "ls /foo", "ls /bar", "ls /a"} for _, item := range expected { if !strings.Contains(out, item) { @@ -134,8 +201,8 @@ func TestIntegrationWithNewDevice(t *testing.T) { t.Fatalf("output contains the unexpected item: notinthehistory") } - RunInteractiveBashCommands(t, "echo mynewercommand") - out = hishtoryQuery(t, "") + tester.RunInteractiveShell(t, "echo mynewercommand") + out = hishtoryQuery(t, tester, "") if !strings.Contains(out, "echo mynewercommand") { t.Fatalf("output is missing `echo mynewercommand`") } @@ -149,25 +216,25 @@ func TestIntegrationWithNewDevice(t *testing.T) { manuallySubmitHistoryEntry(t, userSecret, newEntry) // Now check if that is in there when we do hishtory query - out = hishtoryQuery(t, "") + out = hishtoryQuery(t, tester, "") if !strings.Contains(out, "othercomputer") { t.Fatalf("hishtory query doesn't contain cmd run on another machine! out=%#v", out) } // Finally, test the export command - out = RunInteractiveBashCommands(t, `hishtory export`) + out = tester.RunInteractiveShell(t, `hishtory export | grep -v pipefail | grep -v '/tmp/client install'`) if strings.Contains(out, "thisisnotrecorded") { t.Fatalf("hishtory export contains a command that should not have been recorded, out=%#v", out) } - expectedOutputWithoutKey := "set -emo pipefail\nhishtory status\nset -emo pipefail\nhishtory query\nset -m\nls /a\nls /bar\nls /foo\necho foo\necho bar\nhishtory enable\necho thisisrecorded\nset -emo pipefail\nhishtory query\nset -emo pipefail\nhishtory query foo\necho hello | grep complex | sed s/h/i/g; echo baz && echo \"fo 'o\"\nset -emo pipefail\nhishtory query complex\nset -emo pipefail\nhishtory query\nset -emo pipefail\necho mynewcommand\nset -emo pipefail\nhishtory query\nhishtory init %s\nset -emo pipefail\nhishtory query\nset -emo pipefail\necho mynewercommand\nset -emo pipefail\nhishtory query\nothercomputer\nset -emo pipefail\nhishtory query\nset -emo pipefail\n" + expectedOutputWithoutKey := "hishtory status\nhishtory query\nset -m\nls /a\nls /bar\nls /foo\necho foo\necho bar\nhishtory enable\necho thisisrecorded\nhishtory query\nhishtory query foo\necho hello | grep complex | sed s/h/i/g; echo baz && echo \"fo 'o\"\nhishtory query complex\nhishtory query\necho mynewcommand\nhishtory query\nhishtory init %s\nhishtory query\necho mynewercommand\nhishtory query\nothercomputer\nhishtory query\n" expectedOutput := fmt.Sprintf(expectedOutputWithoutKey, userSecret) if diff := cmp.Diff(expectedOutput, out); diff != "" { t.Fatalf("hishtory export mismatch (-expected +got):\n%s\nout=%#v", diff, out) } } -func installHishtory(t *testing.T, userSecret string) string { - out := RunInteractiveBashCommands(t, `/tmp/client install `+userSecret) +func installHishtory(t *testing.T, tester shellTester, userSecret string) string { + out := tester.RunInteractiveShell(t, `/tmp/client install `+userSecret) r := regexp.MustCompile(`Setting secret hishtory key to (.*)`) matches := r.FindStringSubmatch(out) if len(matches) != 2 { @@ -176,12 +243,12 @@ func installHishtory(t *testing.T, userSecret string) string { return matches[1] } -func testIntegration(t *testing.T) string { +func testBasicUserFlow(t *testing.T, tester shellTester) string { // Test install - userSecret := installHishtory(t, "") + userSecret := installHishtory(t, tester, "") // Test the status subcommand - out := RunInteractiveBashCommands(t, `hishtory status`) + out := tester.RunInteractiveShell(t, `hishtory status`) if out != fmt.Sprintf("Hishtory: v0.Unknown\nEnabled: true\nSecret Key: %s\nCommit Hash: Unknown\n", userSecret) { t.Fatalf("status command has unexpected output: %#v", out) } @@ -201,14 +268,14 @@ func testIntegration(t *testing.T) string { // Test the banner os.Setenv("FORCED_BANNER", "HELLO_FROM_SERVER") - out = hishtoryQuery(t, "") + out = hishtoryQuery(t, tester, "") if !strings.Contains(out, "HELLO_FROM_SERVER\nHostname") { t.Fatalf("hishtory query didn't show the banner message! out=%#v", out) } os.Setenv("FORCED_BANNER", "") // Test recording commands - out, err = RunInteractiveBashCommandsWithoutStrictMode(t, `set -m + out, err = tester.RunInteractiveShellRelaxed(t, `set -m ls /a ls /bar ls /foo @@ -227,7 +294,7 @@ echo thisisrecorded`) } // Test querying for all commands - out = hishtoryQuery(t, "") + out = hishtoryQuery(t, tester, "") expected := []string{"echo thisisrecorded", "hishtory enable", "echo bar", "echo foo", "ls /foo", "ls /bar", "ls /a"} for _, item := range expected { if !strings.Contains(out, item) { @@ -241,7 +308,7 @@ echo thisisrecorded`) // } // Test querying for a specific command - out = hishtoryQuery(t, "foo") + out = hishtoryQuery(t, tester, "foo") expected = []string{"echo foo", "ls /foo"} unexpected := []string{"echo thisisrecorded", "hishtory enable", "echo bar", "ls /bar", "ls /a"} for _, item := range expected { @@ -260,10 +327,12 @@ echo thisisrecorded`) // Add a complex command complexCommand := "echo hello | grep complex | sed s/h/i/g; echo baz && echo \"fo 'o\"" - _, _ = RunInteractiveBashCommandsWithoutStrictMode(t, complexCommand) + _, _ = tester.RunInteractiveShellRelaxed(t, complexCommand) + + // TODO: add a test here that includes a double quote // Query for it - out = hishtoryQuery(t, "complex") + out = hishtoryQuery(t, tester, "complex") if strings.Count(out, "\n") != 2 { t.Fatalf("hishtory query has the wrong number of lines=%d, out=%#v", strings.Count(out, "\n"), out) } @@ -274,15 +343,15 @@ echo thisisrecorded`) return userSecret } -func TestAdvancedQuery(t *testing.T) { +func testAdvancedQuery(t *testing.T, tester shellTester) { // Set up defer shared.BackupAndRestore(t)() // Install hishtory - userSecret := installHishtory(t, "") + userSecret := installHishtory(t, tester, "") // Run some commands we can query for - _, err := RunInteractiveBashCommandsWithoutStrictMode(t, `set -m + _, err := tester.RunInteractiveShellRelaxed(t, `set -m echo nevershouldappear notacommand cd /tmp/ @@ -293,7 +362,7 @@ hishtory disable`) } // A super basic query just to ensure the basics are working - out := hishtoryQuery(t, `echo`) + out := hishtoryQuery(t, tester, `echo`) if !strings.Contains(out, "echo querybydir") { t.Fatalf("hishtory query doesn't contain result matching echo querybydir, out=%#v", out) } @@ -302,7 +371,7 @@ hishtory disable`) } // Query based on cwd - out = hishtoryQuery(t, `cwd:/tmp`) + out = hishtoryQuery(t, tester, `cwd:/tmp`) if !strings.Contains(out, "echo querybydir") { t.Fatalf("hishtory query doesn't contain result matching cwd:/tmp, out=%#v", out) } @@ -314,7 +383,7 @@ hishtory disable`) } // Query based on cwd without the slash - out = hishtoryQuery(t, `cwd:tmp`) + out = hishtoryQuery(t, tester, `cwd:tmp`) if !strings.Contains(out, "echo querybydir") { t.Fatalf("hishtory query doesn't contain result matching cwd:tmp, out=%#v", out) } @@ -323,7 +392,7 @@ hishtory disable`) } // Query based on cwd and another term - out = hishtoryQuery(t, `cwd:/tmp querybydir`) + out = hishtoryQuery(t, tester, `cwd:/tmp querybydir`) if !strings.Contains(out, "echo querybydir") { t.Fatalf("hishtory query doesn't contain result matching cwd:/tmp, out=%#v", out) } @@ -335,7 +404,7 @@ hishtory disable`) } // Query based on exit_code - out = hishtoryQuery(t, `exit_code:127`) + out = hishtoryQuery(t, tester, `exit_code:127`) if !strings.Contains(out, "notacommand") { t.Fatalf("hishtory query doesn't contain expected result, out=%#v", out) } @@ -344,21 +413,21 @@ hishtory disable`) } // Query based on exit_code and something else that matches nothing - out = hishtoryQuery(t, `exit_code:127 foo`) + out = hishtoryQuery(t, tester, `exit_code:127 foo`) if strings.Count(out, "\n") != 1 { t.Fatalf("hishtory query has the wrong number of lines=%d, out=%#v", strings.Count(out, "\n"), out) } // Query based on before: and cwd: - out = hishtoryQuery(t, `before:2125-07-02 cwd:/tmp`) + out = hishtoryQuery(t, tester, `before:2125-07-02 cwd:/tmp`) if strings.Count(out, "\n") != 3 { t.Fatalf("hishtory query has the wrong number of lines=%d, out=%#v", strings.Count(out, "\n"), out) } - out = hishtoryQuery(t, `before:2125-07-02 cwd:tmp`) + out = hishtoryQuery(t, tester, `before:2125-07-02 cwd:tmp`) if strings.Count(out, "\n") != 3 { t.Fatalf("hishtory query has the wrong number of lines=%d, out=%#v", strings.Count(out, "\n"), out) } - out = hishtoryQuery(t, `before:2125-07-02 cwd:mp`) + out = hishtoryQuery(t, tester, `before:2125-07-02 cwd:mp`) if strings.Count(out, "\n") != 3 { t.Fatalf("hishtory query has the wrong number of lines=%d, out=%#v", strings.Count(out, "\n"), out) } @@ -366,14 +435,14 @@ hishtory disable`) // Query based on after: and cwd: // TODO: This fails on macos for some reason if !(runtime.GOOS == "darwin" && os.Getenv("GITHUB_ACTIONS") != "") { - out = hishtoryQuery(t, `after:1980-07-02 cwd:/tmp`) + out = hishtoryQuery(t, tester, `after:1980-07-02 cwd:/tmp`) if strings.Count(out, "\n") != 3 { t.Fatalf("hishtory query has the wrong number of lines=%d, out=%#v", strings.Count(out, "\n"), out) } } // Query based on after: that returns no results - out = hishtoryQuery(t, `after:2120-07-02 cwd:/tmp`) + out = hishtoryQuery(t, tester, `after:2120-07-02 cwd:/tmp`) if strings.Count(out, "\n") != 1 { t.Fatalf("hishtory query has the wrong number of lines=%d, out=%#v", strings.Count(out, "\n"), out) } @@ -385,7 +454,7 @@ hishtory disable`) manuallySubmitHistoryEntry(t, userSecret, entry) // Query based on the username that exists - out = hishtoryQuery(t, `user:otheruser`) + out = hishtoryQuery(t, tester, `user:otheruser`) if !strings.Contains(out, "cmd_with_diff_hostname_and_username") { t.Fatalf("hishtory query doesn't contain expected result, out=%#v", out) } @@ -394,13 +463,13 @@ hishtory disable`) } // Query based on the username that doesn't exist - out = hishtoryQuery(t, `user:noexist`) + out = hishtoryQuery(t, tester, `user:noexist`) if strings.Count(out, "\n") != 1 { t.Fatalf("hishtory query has the wrong number of lines=%d, out=%#v", strings.Count(out, "\n"), out) } // Query based on the hostname - out = hishtoryQuery(t, `hostname:otherhostname`) + out = hishtoryQuery(t, tester, `hostname:otherhostname`) if !strings.Contains(out, "cmd_with_diff_hostname_and_username") { t.Fatalf("hishtory query doesn't contain expected result, out=%#v", out) } @@ -409,32 +478,35 @@ hishtory disable`) } // Test filtering out a search item - out = hishtoryQuery(t, "") + out = hishtoryQuery(t, tester, "") if !strings.Contains(out, "cmd_with_diff_hostname_and_username") { t.Fatalf("hishtory query doesn't contain expected result, out=%#v", out) } - out = hishtoryQuery(t, `-cmd_with_diff_hostname_and_username`) + out = hishtoryQuery(t, tester, `-cmd_with_diff_hostname_and_username`) if strings.Contains(out, "cmd_with_diff_hostname_and_username") { t.Fatalf("hishtory query contains unexpected result, out=%#v", out) } - out = hishtoryQuery(t, `-echo`) + out = hishtoryQuery(t, tester, `-echo`) if strings.Contains(out, "echo") { t.Fatalf("hishtory query contains unexpected result, out=%#v", out) } - if strings.Count(out, "\n") != 5 { - t.Fatalf("hishtory query has the wrong number of lines=%d, out=%#v", strings.Count(out, "\n"), out) + if tester.ShellName() == "bash" { + // TODO + if strings.Count(out, "\n") != 5 { + t.Fatalf("hishtory query has the wrong number of lines=%d, out=%#v", strings.Count(out, "\n"), out) + } } // Test filtering out with an atom - out = hishtoryQuery(t, `-hostname:otherhostname`) + out = hishtoryQuery(t, tester, `-hostname:otherhostname`) if strings.Contains(out, "cmd_with_diff_hostname_and_username") { t.Fatalf("hishtory query contains unexpected result, out=%#v", out) } - out = hishtoryQuery(t, `-user:otheruser`) + out = hishtoryQuery(t, tester, `-user:otheruser`) if strings.Contains(out, "cmd_with_diff_hostname_and_username") { t.Fatalf("hishtory query contains unexpected result, out=%#v", out) } - out = hishtoryQuery(t, `-exit_code:0`) + out = hishtoryQuery(t, tester, `-exit_code:0`) if strings.Count(out, "\n") != 3 { t.Fatalf("hishtory query has the wrong number of lines=%d, out=%#v", strings.Count(out, "\n"), out) } @@ -442,7 +514,7 @@ hishtory disable`) // Test filtering out a search item that also looks like it could be a search for a flag entry = data.MakeFakeHistoryEntry("foo -echo") manuallySubmitHistoryEntry(t, userSecret, entry) - out = hishtoryQuery(t, `-echo -install`) + out = hishtoryQuery(t, tester, `-echo -install`) if strings.Contains(out, "echo") { t.Fatalf("hishtory query contains unexpected result, out=%#v", out) } @@ -451,27 +523,28 @@ hishtory disable`) } } -func TestUpdate(t *testing.T) { +func testUpdate(t *testing.T, tester shellTester) { if runtime.GOOS != "linux" { // TODO: Once updates work for non-linux platforms, remove this t.Skip() } + t.Skip() // TODO: currently failing because of the change in API // Set up defer shared.BackupAndRestore(t)() - userSecret := installHishtory(t, "") + userSecret := installHishtory(t, tester, "") // Record a command before the update - RunInteractiveBashCommands(t, "echo hello") + tester.RunInteractiveShell(t, "echo hello") // Check the status command - out := RunInteractiveBashCommands(t, `hishtory status`) + out := tester.RunInteractiveShell(t, `hishtory status`) if out != fmt.Sprintf("Hishtory: v0.Unknown\nEnabled: true\nSecret Key: %s\nCommit Hash: Unknown\n", userSecret) { t.Fatalf("status command has unexpected output: %#v", out) } // Update - out = RunInteractiveBashCommands(t, `hishtory update`) + out = tester.RunInteractiveShell(t, `hishtory update`) isExpected, err := regexp.MatchString(`Verified against tlog entry \d+\nSuccessfully updated hishtory from v0[.]Unknown to v0.\d+\n`, out) if err != nil { t.Fatalf("regex failure: %v", err) @@ -481,13 +554,13 @@ func TestUpdate(t *testing.T) { } // Update again and assert that it skipped the update - out = RunInteractiveBashCommands(t, `hishtory update`) + out = tester.RunInteractiveShell(t, `hishtory update`) if strings.Count(out, "\n") != 1 || !strings.Contains(out, "is already installed") { t.Fatalf("repeated hishtory update didn't skip the update, out=%#v", out) } // Then check the status command again to confirm the update worked - out = RunInteractiveBashCommands(t, `hishtory status`) + out = tester.RunInteractiveShell(t, `hishtory status`) if !strings.Contains(out, fmt.Sprintf("\nEnabled: true\nSecret Key: %s\nCommit Hash: ", userSecret)) { t.Fatalf("status command has unexpected output: %#v", out) } @@ -496,31 +569,34 @@ func TestUpdate(t *testing.T) { } // Check that the history was preserved after the update - out = RunInteractiveBashCommands(t, "hishtory export") - expectedOutput := "set -emo pipefail\necho hello\nset -emo pipefail\nhishtory status\nset -emo pipefail\nhishtory update\nset -emo pipefail\nhishtory update\nset -emo pipefail\nhishtory status\nset -emo pipefail\n" - if diff := cmp.Diff(expectedOutput, out); diff != "" { - t.Fatalf("hishtory export mismatch (-expected +got):\n%s\nout=%#v", diff, out) + if tester.ShellName() == "bash" { + // TODO + out = tester.RunInteractiveShell(t, "hishtory export | grep -v pipefail | grep -v '/tmp/client install'") + expectedOutput := "echo hello\nhishtory status\n" + if diff := cmp.Diff(expectedOutput, out); diff != "" { + t.Fatalf("hishtory export mismatch (-expected +got):\n%s\nout=%#v", diff, out) + } } } -func TestRepeatedCommandThenQuery(t *testing.T) { +func testRepeatedCommandThenQuery(t *testing.T, tester shellTester) { // Set up defer shared.BackupAndRestore(t)() - userSecret := installHishtory(t, "") + userSecret := installHishtory(t, tester, "") // Check the status command - out := RunInteractiveBashCommands(t, `hishtory status`) + out := tester.RunInteractiveShell(t, `hishtory status`) if out != fmt.Sprintf("Hishtory: v0.Unknown\nEnabled: true\nSecret Key: %s\nCommit Hash: Unknown\n", userSecret) { t.Fatalf("status command has unexpected output: %#v", out) } // Run a command many times for i := 0; i < 25; i++ { - RunInteractiveBashCommands(t, fmt.Sprintf("echo mycommand-%d", i)) + tester.RunInteractiveShell(t, fmt.Sprintf("echo mycommand-%d", i)) } // Check that it shows up correctly - out = hishtoryQuery(t, `mycommand`) + out = hishtoryQuery(t, tester, `mycommand`) if strings.Count(out, "\n") != 26 { t.Fatalf("hishtory query has the wrong number of lines=%d, out=%#v", strings.Count(out, "\n"), out) } @@ -528,32 +604,33 @@ func TestRepeatedCommandThenQuery(t *testing.T) { t.Fatalf("hishtory query has the wrong number of commands=%d, out=%#v", strings.Count(out, "echo mycommand"), out) } - RunInteractiveBashCommands(t, `echo mycommand-30 + // Run a few more commands + tester.RunInteractiveShell(t, `echo mycommand-30 echo mycommand-31 echo mycommand-3`) - out = RunInteractiveBashCommands(t, "hishtory export") - expectedOutput := "set -emo pipefail\nhishtory status\nset -emo pipefail\necho mycommand-0\nset -emo pipefail\necho mycommand-1\nset -emo pipefail\necho mycommand-2\nset -emo pipefail\necho mycommand-3\nset -emo pipefail\necho mycommand-4\nset -emo pipefail\necho mycommand-5\nset -emo pipefail\necho mycommand-6\nset -emo pipefail\necho mycommand-7\nset -emo pipefail\necho mycommand-8\nset -emo pipefail\necho mycommand-9\nset -emo pipefail\necho mycommand-10\nset -emo pipefail\necho mycommand-11\nset -emo pipefail\necho mycommand-12\nset -emo pipefail\necho mycommand-13\nset -emo pipefail\necho mycommand-14\nset -emo pipefail\necho mycommand-15\nset -emo pipefail\necho mycommand-16\nset -emo pipefail\necho mycommand-17\nset -emo pipefail\necho mycommand-18\nset -emo pipefail\necho mycommand-19\nset -emo pipefail\necho mycommand-20\nset -emo pipefail\necho mycommand-21\nset -emo pipefail\necho mycommand-22\nset -emo pipefail\necho mycommand-23\nset -emo pipefail\necho mycommand-24\nset -emo pipefail\nhishtory query mycommand\nset -emo pipefail\necho mycommand-30\necho mycommand-31\necho mycommand-3\nset -emo pipefail\n" + out = tester.RunInteractiveShell(t, "hishtory export | grep -v pipefail | grep -v '/tmp/client install'") + expectedOutput := "hishtory status\necho mycommand-0\necho mycommand-1\necho mycommand-2\necho mycommand-3\necho mycommand-4\necho mycommand-5\necho mycommand-6\necho mycommand-7\necho mycommand-8\necho mycommand-9\necho mycommand-10\necho mycommand-11\necho mycommand-12\necho mycommand-13\necho mycommand-14\necho mycommand-15\necho mycommand-16\necho mycommand-17\necho mycommand-18\necho mycommand-19\necho mycommand-20\necho mycommand-21\necho mycommand-22\necho mycommand-23\necho mycommand-24\nhishtory query mycommand\necho mycommand-30\necho mycommand-31\necho mycommand-3\n" if diff := cmp.Diff(expectedOutput, out); diff != "" { t.Fatalf("hishtory export mismatch (-expected +got):\n%s\nout=%#v", diff, out) } } -func TestRepeatedCommandAndQuery(t *testing.T) { +func testRepeatedCommandAndQuery(t *testing.T, tester shellTester) { // Set up defer shared.BackupAndRestore(t)() - userSecret := installHishtory(t, "") + userSecret := installHishtory(t, tester, "") // Check the status command - out := RunInteractiveBashCommands(t, `hishtory status`) + out := tester.RunInteractiveShell(t, `hishtory status`) if out != fmt.Sprintf("Hishtory: v0.Unknown\nEnabled: true\nSecret Key: %s\nCommit Hash: Unknown\n", userSecret) { t.Fatalf("status command has unexpected output: %#v", out) } // Run a command many times for i := 0; i < 25; i++ { - RunInteractiveBashCommands(t, fmt.Sprintf("echo mycommand-%d", i)) - out = hishtoryQuery(t, fmt.Sprintf("mycommand-%d", i)) + tester.RunInteractiveShell(t, fmt.Sprintf("echo mycommand-%d", i)) + out = hishtoryQuery(t, tester, fmt.Sprintf("mycommand-%d", i)) if strings.Count(out, "\n") != 2 { t.Fatalf("hishtory query #%d has the wrong number of lines=%d, out=%#v", i, strings.Count(out, "\n"), out) } @@ -564,49 +641,53 @@ func TestRepeatedCommandAndQuery(t *testing.T) { } } -func TestRepeatedEnableDisable(t *testing.T) { +func testRepeatedEnableDisable(t *testing.T, tester shellTester) { // Set up defer shared.BackupAndRestore(t)() - installHishtory(t, "") + installHishtory(t, tester, "") // Run a command many times for i := 0; i < 25; i++ { - RunInteractiveBashCommands(t, fmt.Sprintf(`echo mycommand-%d + tester.RunInteractiveShell(t, fmt.Sprintf(`echo mycommand-%d hishtory disable echo shouldnotshowup sleep 0.5 hishtory enable`, i)) - out := hishtoryQuery(t, fmt.Sprintf("mycommand-%d", i)) + out := hishtoryQuery(t, tester, fmt.Sprintf("mycommand-%d", i)) if strings.Count(out, "\n") != 2 { t.Fatalf("hishtory query #%d has the wrong number of lines=%d, out=%#v", i, strings.Count(out, "\n"), out) } if strings.Count(out, "echo mycommand") != 1 { t.Fatalf("hishtory query #%d has the wrong number of commands=%d, out=%#v", i, strings.Count(out, "echo mycommand"), out) } - out = hishtoryQuery(t, "") + out = hishtoryQuery(t, tester, "") if strings.Contains(out, "shouldnotshowup") { t.Fatalf("hishtory query contains a result that should not have been recorded, out=%#v", out) } } - out := RunInteractiveBashCommands(t, "hishtory export") - expectedOutput := "set -emo pipefail\necho mycommand-0\nhishtory enable\nset -emo pipefail\nhishtory query mycommand-0\nset -emo pipefail\nhishtory query\nset -emo pipefail\necho mycommand-1\nhishtory enable\nset -emo pipefail\nhishtory query mycommand-1\nset -emo pipefail\nhishtory query\nset -emo pipefail\necho mycommand-2\nhishtory enable\nset -emo pipefail\nhishtory query mycommand-2\nset -emo pipefail\nhishtory query\nset -emo pipefail\necho mycommand-3\nhishtory enable\nset -emo pipefail\nhishtory query mycommand-3\nset -emo pipefail\nhishtory query\nset -emo pipefail\necho mycommand-4\nhishtory enable\nset -emo pipefail\nhishtory query mycommand-4\nset -emo pipefail\nhishtory query\nset -emo pipefail\necho mycommand-5\nhishtory enable\nset -emo pipefail\nhishtory query mycommand-5\nset -emo pipefail\nhishtory query\nset -emo pipefail\necho mycommand-6\nhishtory enable\nset -emo pipefail\nhishtory query mycommand-6\nset -emo pipefail\nhishtory query\nset -emo pipefail\necho mycommand-7\nhishtory enable\nset -emo pipefail\nhishtory query mycommand-7\nset -emo pipefail\nhishtory query\nset -emo pipefail\necho mycommand-8\nhishtory enable\nset -emo pipefail\nhishtory query mycommand-8\nset -emo pipefail\nhishtory query\nset -emo pipefail\necho mycommand-9\nhishtory enable\nset -emo pipefail\nhishtory query mycommand-9\nset -emo pipefail\nhishtory query\nset -emo pipefail\necho mycommand-10\nhishtory enable\nset -emo pipefail\nhishtory query mycommand-10\nset -emo pipefail\nhishtory query\nset -emo pipefail\necho mycommand-11\nhishtory enable\nset -emo pipefail\nhishtory query mycommand-11\nset -emo pipefail\nhishtory query\nset -emo pipefail\necho mycommand-12\nhishtory enable\nset -emo pipefail\nhishtory query mycommand-12\nset -emo pipefail\nhishtory query\nset -emo pipefail\necho mycommand-13\nhishtory enable\nset -emo pipefail\nhishtory query mycommand-13\nset -emo pipefail\nhishtory query\nset -emo pipefail\necho mycommand-14\nhishtory enable\nset -emo pipefail\nhishtory query mycommand-14\nset -emo pipefail\nhishtory query\nset -emo pipefail\necho mycommand-15\nhishtory enable\nset -emo pipefail\nhishtory query mycommand-15\nset -emo pipefail\nhishtory query\nset -emo pipefail\necho mycommand-16\nhishtory enable\nset -emo pipefail\nhishtory query mycommand-16\nset -emo pipefail\nhishtory query\nset -emo pipefail\necho mycommand-17\nhishtory enable\nset -emo pipefail\nhishtory query mycommand-17\nset -emo pipefail\nhishtory query\nset -emo pipefail\necho mycommand-18\nhishtory enable\nset -emo pipefail\nhishtory query mycommand-18\nset -emo pipefail\nhishtory query\nset -emo pipefail\necho mycommand-19\nhishtory enable\nset -emo pipefail\nhishtory query mycommand-19\nset -emo pipefail\nhishtory query\nset -emo pipefail\necho mycommand-20\nhishtory enable\nset -emo pipefail\nhishtory query mycommand-20\nset -emo pipefail\nhishtory query\nset -emo pipefail\necho mycommand-21\nhishtory enable\nset -emo pipefail\nhishtory query mycommand-21\nset -emo pipefail\nhishtory query\nset -emo pipefail\necho mycommand-22\nhishtory enable\nset -emo pipefail\nhishtory query mycommand-22\nset -emo pipefail\nhishtory query\nset -emo pipefail\necho mycommand-23\nhishtory enable\nset -emo pipefail\nhishtory query mycommand-23\nset -emo pipefail\nhishtory query\nset -emo pipefail\necho mycommand-24\nhishtory enable\nset -emo pipefail\nhishtory query mycommand-24\nset -emo pipefail\nhishtory query\nset -emo pipefail\n" + out := tester.RunInteractiveShell(t, "hishtory export | grep -v pipefail | grep -v '/tmp/client install'") + expectedOutput := "echo mycommand-0\nhishtory enable\nhishtory query mycommand-0\nhishtory query\necho mycommand-1\nhishtory enable\nhishtory query mycommand-1\nhishtory query\necho mycommand-2\nhishtory enable\nhishtory query mycommand-2\nhishtory query\necho mycommand-3\nhishtory enable\nhishtory query mycommand-3\nhishtory query\necho mycommand-4\nhishtory enable\nhishtory query mycommand-4\nhishtory query\necho mycommand-5\nhishtory enable\nhishtory query mycommand-5\nhishtory query\necho mycommand-6\nhishtory enable\nhishtory query mycommand-6\nhishtory query\necho mycommand-7\nhishtory enable\nhishtory query mycommand-7\nhishtory query\necho mycommand-8\nhishtory enable\nhishtory query mycommand-8\nhishtory query\necho mycommand-9\nhishtory enable\nhishtory query mycommand-9\nhishtory query\necho mycommand-10\nhishtory enable\nhishtory query mycommand-10\nhishtory query\necho mycommand-11\nhishtory enable\nhishtory query mycommand-11\nhishtory query\necho mycommand-12\nhishtory enable\nhishtory query mycommand-12\nhishtory query\necho mycommand-13\nhishtory enable\nhishtory query mycommand-13\nhishtory query\necho mycommand-14\nhishtory enable\nhishtory query mycommand-14\nhishtory query\necho mycommand-15\nhishtory enable\nhishtory query mycommand-15\nhishtory query\necho mycommand-16\nhishtory enable\nhishtory query mycommand-16\nhishtory query\necho mycommand-17\nhishtory enable\nhishtory query mycommand-17\nhishtory query\necho mycommand-18\nhishtory enable\nhishtory query mycommand-18\nhishtory query\necho mycommand-19\nhishtory enable\nhishtory query mycommand-19\nhishtory query\necho mycommand-20\nhishtory enable\nhishtory query mycommand-20\nhishtory query\necho mycommand-21\nhishtory enable\nhishtory query mycommand-21\nhishtory query\necho mycommand-22\nhishtory enable\nhishtory query mycommand-22\nhishtory query\necho mycommand-23\nhishtory enable\nhishtory query mycommand-23\nhishtory query\necho mycommand-24\nhishtory enable\nhishtory query mycommand-24\nhishtory query\n" if diff := cmp.Diff(expectedOutput, out); diff != "" { t.Fatalf("hishtory export mismatch (-expected +got):\n%s\nout=%#v", diff, out) } } -func TestExcludeHiddenCommand(t *testing.T) { +func testExcludeHiddenCommand(t *testing.T, tester shellTester) { + if tester.ShellName() == "zsh" { + t.Skip() + // TODO + } // Set up defer shared.BackupAndRestore(t)() - installHishtory(t, "") + installHishtory(t, tester, "") - RunInteractiveBashCommands(t, `echo hello1 + tester.RunInteractiveShell(t, `echo hello1 echo hidden echo hello2 echo hidden`) - RunInteractiveBashCommands(t, " echo hidden") - out := hishtoryQuery(t, "") + tester.RunInteractiveShell(t, " echo hidden") + out := hishtoryQuery(t, tester, "") if strings.Count(out, "\n") != 5 && strings.Count(out, "\n") != 6 { t.Fatalf("hishtory query has the wrong number of lines=%d, out=%#v", strings.Count(out, "\n"), out) } @@ -623,7 +704,7 @@ echo hello2 t.Fatalf("hishtory query contains a result that should not have been recorded, out=%#v", out) } - out = RunInteractiveBashCommands(t, "hishtory export | grep -v pipefail") + out = tester.RunInteractiveShell(t, "hishtory export | grep -v pipefail | grep -v '/tmp/client install'") expectedOutput := "echo hello1\necho hello2\nhishtory query\n" if out != expectedOutput { t.Fatalf("hishtory export has unexpected output=%#v", out) @@ -658,8 +739,8 @@ func waitForBackgroundSavesToComplete(t *testing.T) { t.Fatalf("failed to wait until hishtory wasn't running") } -func hishtoryQuery(t *testing.T, query string) string { - return RunInteractiveBashCommands(t, "hishtory query "+query) +func hishtoryQuery(t *testing.T, tester shellTester, query string) string { + return tester.RunInteractiveShell(t, "hishtory query "+query) } // TODO: Maybe a dedicated unit test for retrieveAdditionalEntriesFromRemote @@ -676,12 +757,12 @@ func manuallySubmitHistoryEntry(t *testing.T, userSecret string, entry data.Hist } } -func TestHishtoryBackgroundSaving(t *testing.T) { +func testHishtoryBackgroundSaving(t *testing.T, tester shellTester) { // Setup defer shared.BackupAndRestore(t)() // Test install with an unset HISHTORY_TEST var so that we save in the background (this is likely to be flakey!) - out := RunInteractiveBashCommands(t, `unset HISHTORY_TEST + out := tester.RunInteractiveShell(t, `unset HISHTORY_TEST go build -o /tmp/client /tmp/client install`) r := regexp.MustCompile(`Setting secret hishtory key to (.*)`) @@ -705,13 +786,13 @@ go build -o /tmp/client } // Test the status subcommand - out = RunInteractiveBashCommands(t, `hishtory status`) + out = tester.RunInteractiveShell(t, `hishtory status`) if out != fmt.Sprintf("Hishtory: v0.Unknown\nEnabled: true\nSecret Key: %s\nCommit Hash: Unknown\n", userSecret) { t.Fatalf("status command has unexpected output: %#v", out) } // Test recording commands - out, err = RunInteractiveBashCommandsWithoutStrictMode(t, `set -m + out, err = tester.RunInteractiveShellRelaxed(t, `set -m ls /a echo foo`) if err != nil { @@ -723,7 +804,7 @@ echo foo`) // Test querying for all commands waitForBackgroundSavesToComplete(t) - out = hishtoryQuery(t, "") + out = hishtoryQuery(t, tester, "") expected := []string{"echo foo", "ls /a"} for _, item := range expected { if !strings.Contains(out, item) { @@ -733,7 +814,7 @@ echo foo`) // Test querying for a specific command waitForBackgroundSavesToComplete(t) - out = hishtoryQuery(t, "foo") + out = hishtoryQuery(t, tester, "foo") if !strings.Contains(out, "echo foo") { t.Fatalf("output doesn't contain the expected item, out=%#v", out) } @@ -742,10 +823,10 @@ echo foo`) } } -func TestDisplayTable(t *testing.T) { +func testDisplayTable(t *testing.T, tester shellTester) { // Setup defer shared.BackupAndRestore(t)() - userSecret := installHishtory(t, "") + userSecret := installHishtory(t, tester, "") // Submit two fake entries tmz, err := time.LoadLocation("America/Los_Angeles") @@ -764,7 +845,7 @@ func TestDisplayTable(t *testing.T) { manuallySubmitHistoryEntry(t, userSecret, entry2) // Query and check the table - out := RunInteractiveBashCommands(t, "hishtory query table") + out := hishtoryQuery(t, tester, "table") expectedOutput := "Hostname CWD Timestamp Runtime Exit Code Command \nlocalhost ~/foo/ Apr 16 2022 01:03:16 -0700 24s 3 table_cmd2 \nlocalhost /tmp/ Apr 16 2022 01:03:06 -0700 4s 2 table_cmd1 \n" if diff := cmp.Diff(expectedOutput, out); diff != "" { t.Fatalf("hishtory query table test mismatch (-expected +got):\n%s\nout=%#v", diff, out) diff --git a/client/lib/config.sh b/client/lib/config.sh index ff5e7a8..d0c403c 100644 --- a/client/lib/config.sh +++ b/client/lib/config.sh @@ -23,6 +23,6 @@ function PostCommand() { fi # Run after every prompt - (hishtory saveHistoryEntry $EXIT_CODE "`history 1`" $HISHTORY_START_TIME &) + (hishtory saveHistoryEntry bash $EXIT_CODE "`history 1`" $HISHTORY_START_TIME &) } PROMPT_COMMAND="PostCommand" diff --git a/client/lib/config.zsh b/client/lib/config.zsh new file mode 100644 index 0000000..f327cc3 --- /dev/null +++ b/client/lib/config.zsh @@ -0,0 +1,23 @@ +add-zsh-hook zshaddhistory _hishtory_add +add-zsh-hook precmd _hishtory_precmd + +_hishtory_first_prompt=1 + +function _hishtory_add() { + # Runs after , but before the command is executed + # $1 contains the command that was run + _hishtory_command=$1 + _hishtory_start_time=`date +%s%N` +} + +function _hishtory_precmd() { + # Runs after the command is executed in order to render the prompt + # $? contains the exit code (TODO: is this always true? Could other precmds break this?) + _hishtory_exit_code=$? + if [ -n "$_hishtory_first_prompt" ]; then + unset _hishtory_first_prompt + return + fi + # TODO: Run this in the background + hishtory saveHistoryEntry zsh $_hishtory_exit_code "$_hishtory_command" $_hishtory_start_time +} diff --git a/client/lib/lib.go b/client/lib/lib.go index f4f606e..2c25d3d 100644 --- a/client/lib/lib.go +++ b/client/lib/lib.go @@ -12,7 +12,6 @@ import ( "os/exec" "os/user" "path" - "path/filepath" "runtime" "strconv" "strings" @@ -40,6 +39,9 @@ var ConfigShContents string //go:embed test_config.sh var TestConfigShContents string +//go:embed config.zsh +var ConfigZshContents string + var Version string = "Unknown" func getCwd() (string, error) { @@ -61,10 +63,11 @@ func getCwd() (string, error) { } func BuildHistoryEntry(args []string) (*data.HistoryEntry, error) { + shell := args[2] var entry data.HistoryEntry // exitCode - exitCode, err := strconv.Atoi(args[2]) + exitCode, err := strconv.Atoi(args[3]) if err != nil { return nil, fmt.Errorf("failed to build history entry: %v", err) } @@ -85,9 +88,9 @@ func BuildHistoryEntry(args []string) (*data.HistoryEntry, error) { entry.CurrentWorkingDirectory = cwd // start time - nanos, err := parseCrossPlatformInt(args[4]) + nanos, err := parseCrossPlatformInt(args[5]) if err != nil { - return nil, fmt.Errorf("failed to parse start time %s as int: %v", args[4], err) + return nil, fmt.Errorf("failed to parse start time %s as int: %v", args[5], err) } entry.StartTime = time.Unix(0, nanos) @@ -95,22 +98,29 @@ func BuildHistoryEntry(args []string) (*data.HistoryEntry, error) { entry.EndTime = time.Now() // command - cmd, err := getLastCommand(args[3]) - if err != nil { - return nil, fmt.Errorf("failed to build history entry: %v", err) + if shell == "bash" { + cmd, err := getLastCommand(args[4]) + if err != nil { + return nil, fmt.Errorf("failed to build history entry: %v", err) + } + shouldBeSkipped, err := shouldSkipHiddenCommand(args[4]) + if err != nil { + return nil, fmt.Errorf("failed to check if command was hidden: %v", err) + } + if shouldBeSkipped { + return nil, nil + } + if strings.HasPrefix(cmd, " ") { + // Don't save commands that start with a space + return nil, nil + } + entry.Command = cmd + } else if shell == "zsh" { + // TODO: skip commands that start with a space + entry.Command = strings.TrimSpace(args[4]) + } else { + return nil, fmt.Errorf("tried to save a hishtory entry from an unsupported shell=%#v", shell) } - shouldBeSkipped, err := shouldSkipHiddenCommand(args[3]) - if err != nil { - return nil, fmt.Errorf("failed to check if command was hidden: %v", err) - } - if shouldBeSkipped { - return nil, nil - } - if strings.HasPrefix(cmd, " ") { - // Don't save commands that start with a space - return nil, nil - } - entry.Command = cmd // hostname hostname, err := os.Hostname() @@ -128,7 +138,7 @@ func parseCrossPlatformInt(data string) (int64, error) { } func getLastCommand(history string) (string, error) { - return strings.SplitN(strings.TrimSpace(history), " ", 2)[1][1:], nil + return strings.TrimSpace(strings.SplitN(strings.TrimSpace(history), " ", 2)[1]), nil } func shouldSkipHiddenCommand(historyLine string) (bool, error) { @@ -339,6 +349,10 @@ func Install() error { if err != nil { return err } + err = configureZshrc(homedir, path) + if err != nil { + return err + } _, err = GetConfig() if err != nil { // No config, so set up a new installation @@ -347,9 +361,37 @@ func Install() error { return nil } +func configureZshrc(homedir, binaryPath string) error { + // Create the file we're going to source in our zshrc. Do this no matter what in case there are updates to it. + zshConfigPath := path.Join(homedir, shared.HISHTORY_PATH, "config.zsh") + err := ioutil.WriteFile(zshConfigPath, []byte(ConfigZshContents), 0o644) + if err != nil { + return fmt.Errorf("failed to write config.zsh file: %v", err) + } + // Check if we need to configure the zshrc + bashrc, err := ioutil.ReadFile(path.Join(homedir, ".zshrc")) + if err != nil { + return fmt.Errorf("failed to read zshrc: %v", err) + } + if strings.Contains(string(bashrc), "# Hishtory Config:") { + return nil + } + // Add to zshrc + f, err := os.OpenFile(path.Join(homedir, ".zshrc"), os.O_APPEND|os.O_WRONLY, 0o644) + if err != nil { + return fmt.Errorf("failed to append to zshrc: %v", err) + } + defer f.Close() + _, err = f.WriteString("\n# Hishtory Config:\nexport PATH=\"$PATH:" + path.Join(homedir, shared.HISHTORY_PATH) + "\"\nsource " + zshConfigPath + "\n") + if err != nil { + return fmt.Errorf("failed to append to zshrc: %v", err) + } + return nil +} + func configureBashrc(homedir, binaryPath string) error { // Create the file we're going to source in our bashrc. Do this no matter what in case there are updates to it. - bashConfigPath := path.Join(filepath.Dir(binaryPath), "config.sh") + bashConfigPath := path.Join(homedir, shared.HISHTORY_PATH, "config.sh") configContents := ConfigShContents if os.Getenv("HISHTORY_TEST") != "" { configContents = TestConfigShContents @@ -372,7 +414,7 @@ func configureBashrc(homedir, binaryPath string) error { return fmt.Errorf("failed to append to bashrc: %v", err) } defer f.Close() - _, err = f.WriteString("\n# Hishtory Config:\nexport PATH=\"$PATH:" + filepath.Dir(binaryPath) + "\"\nsource " + bashConfigPath + "\n") + _, err = f.WriteString("\n# Hishtory Config:\nexport PATH=\"$PATH:" + path.Join(homedir, shared.HISHTORY_PATH) + "\"\nsource " + bashConfigPath + "\n") if err != nil { return fmt.Errorf("failed to append to bashrc: %v", err) } diff --git a/client/lib/lib_test.go b/client/lib/lib_test.go index 2341062..fc87cb7 100644 --- a/client/lib/lib_test.go +++ b/client/lib/lib_test.go @@ -36,8 +36,8 @@ func TestBuildHistoryEntry(t *testing.T) { defer shared.RunTestServer()() shared.Check(t, Setup([]string{})) - // Test building an actual entry - entry, err := BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls / ", "1641774958326745663"}) + // Test building an actual entry for bash + entry, err := BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "bash", "120", " 123 ls /foo ", "1641774958326745663"}) shared.Check(t, err) if entry.ExitCode != 120 { t.Fatalf("history entry has unexpected exit code: %v", entry.ExitCode) @@ -52,7 +52,29 @@ func TestBuildHistoryEntry(t *testing.T) { if !strings.HasPrefix(entry.CurrentWorkingDirectory, "/") && !strings.HasPrefix(entry.CurrentWorkingDirectory, "~/") { t.Fatalf("history entry has unexpected cwd: %v", entry.CurrentWorkingDirectory) } - if entry.Command != "ls /" { + if entry.Command != "ls /foo" { + t.Fatalf("history entry has unexpected command: %v", entry.Command) + } + if !strings.HasPrefix(entry.StartTime.Format(time.RFC3339), "2022-01-09T") && !strings.HasPrefix(entry.StartTime.Format(time.RFC3339), "2022-01-10T") { + t.Fatalf("history entry has incorrect date in the start time: %v", entry.StartTime.Format(time.RFC3339)) + } + if entry.StartTime.Unix() != 1641774958 { + t.Fatalf("history entry has incorrect Unix time in the start time: %v", entry.StartTime.Unix()) + } + + // Test building an entry for zsh + entry, err = BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "zsh", "120", "ls /foo\n", "1641774958326745663"}) + shared.Check(t, err) + if entry.ExitCode != 120 { + t.Fatalf("history entry has unexpected exit code: %v", entry.ExitCode) + } + if entry.LocalUsername != user.Username { + t.Fatalf("history entry has unexpected user name: %v", entry.LocalUsername) + } + if !strings.HasPrefix(entry.CurrentWorkingDirectory, "/") && !strings.HasPrefix(entry.CurrentWorkingDirectory, "~/") { + t.Fatalf("history entry has unexpected cwd: %v", entry.CurrentWorkingDirectory) + } + if entry.Command != "ls /foo" { t.Fatalf("history entry has unexpected command: %v", entry.Command) } if !strings.HasPrefix(entry.StartTime.Format(time.RFC3339), "2022-01-09T") && !strings.HasPrefix(entry.StartTime.Format(time.RFC3339), "2022-01-10T") { diff --git a/client/lib/test_config.sh b/client/lib/test_config.sh index 1af34c3..ddd6eb5 100644 --- a/client/lib/test_config.sh +++ b/client/lib/test_config.sh @@ -24,6 +24,6 @@ function PostCommand() { fi # Run after every prompt - hishtory saveHistoryEntry $EXIT_CODE "`history 1`" $HISHTORY_START_TIME + hishtory saveHistoryEntry bash $EXIT_CODE "`history 1`" $HISHTORY_START_TIME } PROMPT_COMMAND="PostCommand" diff --git a/shared/testutils.go b/shared/testutils.go index 0b9a55e..4dd848c 100644 --- a/shared/testutils.go +++ b/shared/testutils.go @@ -22,6 +22,7 @@ func ResetLocalState(t *testing.T) { _ = os.Remove(path.Join(homedir, HISHTORY_PATH, CONFIG_PATH)) _ = os.Remove(path.Join(homedir, HISHTORY_PATH, "hishtory")) _ = os.Remove(path.Join(homedir, HISHTORY_PATH, "config.sh")) + _ = os.Remove(path.Join(homedir, HISHTORY_PATH, "config.zsh")) } func BackupAndRestore(t *testing.T) func() { @@ -34,11 +35,13 @@ func BackupAndRestore(t *testing.T) func() { _ = os.Rename(path.Join(homedir, HISHTORY_PATH, CONFIG_PATH), path.Join(homedir, HISHTORY_PATH, CONFIG_PATH+".bak")) _ = os.Rename(path.Join(homedir, HISHTORY_PATH, "hishtory"), path.Join(homedir, HISHTORY_PATH, "hishtory.bak")) _ = os.Rename(path.Join(homedir, HISHTORY_PATH, "config.sh"), path.Join(homedir, HISHTORY_PATH, "config.sh.bak")) + _ = os.Rename(path.Join(homedir, HISHTORY_PATH, "config.zsh"), path.Join(homedir, HISHTORY_PATH, "config.zsh.bak")) return func() { _ = os.Rename(path.Join(homedir, HISHTORY_PATH, DB_PATH+".bak"), path.Join(homedir, HISHTORY_PATH, DB_PATH)) _ = os.Rename(path.Join(homedir, HISHTORY_PATH, CONFIG_PATH+".bak"), path.Join(homedir, HISHTORY_PATH, CONFIG_PATH)) _ = os.Rename(path.Join(homedir, HISHTORY_PATH, "hishtory.bak"), path.Join(homedir, HISHTORY_PATH, "hishtory")) _ = os.Rename(path.Join(homedir, HISHTORY_PATH, "config.sh.bak"), path.Join(homedir, HISHTORY_PATH, "config.sh")) + _ = os.Rename(path.Join(homedir, HISHTORY_PATH, "config.zsh.bak"), path.Join(homedir, HISHTORY_PATH, "config.zsh")) } }