2022-01-09 20:00:53 +01:00
package main
import (
"bytes"
2023-11-12 06:13:03 +01:00
"encoding/json"
2022-04-07 07:44:10 +02:00
"fmt"
2024-02-19 18:34:33 +01:00
"io"
"net/http"
2022-04-07 07:43:07 +02:00
"os"
2022-04-07 07:44:10 +02:00
"os/exec"
2022-04-16 09:56:08 +02:00
"path"
2022-01-09 23:34:59 +01:00
"regexp"
2022-04-12 07:36:52 +02:00
"runtime"
2022-01-09 23:34:59 +01:00
"strings"
2022-12-12 04:15:29 +01:00
"sync"
2022-01-09 20:00:53 +01:00
"testing"
2022-04-15 05:18:49 +02:00
"time"
"github.com/google/go-cmp/cmp"
2022-09-17 20:49:31 +02:00
"github.com/google/uuid"
2023-09-02 00:42:02 +02:00
"gorm.io/gorm"
2022-01-09 20:00:53 +01:00
2023-10-16 03:30:39 +02:00
"github.com/ddworken/hishtory/client/cmd"
2022-04-08 05:59:40 +02:00
"github.com/ddworken/hishtory/client/data"
2022-09-21 07:28:40 +02:00
"github.com/ddworken/hishtory/client/hctx"
2022-05-02 04:37:26 +02:00
"github.com/ddworken/hishtory/client/lib"
2023-10-10 06:41:30 +02:00
"github.com/ddworken/hishtory/shared"
2023-11-12 05:59:45 +01:00
"github.com/ddworken/hishtory/shared/ai"
2022-10-28 06:53:47 +02:00
"github.com/ddworken/hishtory/shared/testutils"
2023-08-29 04:27:29 +02:00
"github.com/stretchr/testify/require"
2022-01-09 20:00:53 +01:00
)
2022-09-28 07:03:37 +02:00
func skipSlowTests ( ) bool {
return os . Getenv ( "FAST" ) != ""
}
2022-04-15 20:20:23 +02:00
func TestMain ( m * testing . M ) {
2023-09-08 03:30:47 +02:00
// Configure key environment variables
2022-11-14 00:38:02 +01:00
defer testutils . BackupAndRestoreEnv ( "HISHTORY_TEST" ) ( )
2022-11-12 00:12:23 +01:00
os . Setenv ( "HISHTORY_TEST" , "1" )
2022-11-14 00:38:02 +01:00
defer testutils . BackupAndRestoreEnv ( "HISHTORY_SKIP_INIT_IMPORT" ) ( )
2022-11-12 00:12:23 +01:00
os . Setenv ( "HISHTORY_SKIP_INIT_IMPORT" , "1" )
2023-09-08 03:30:47 +02:00
// Start the test server
2022-10-28 06:53:47 +02:00
defer testutils . RunTestServer ( ) ( )
2023-09-08 03:30:47 +02:00
// Build the client so it is available in /tmp/client
2022-04-16 03:12:26 +02:00
cmd := exec . Command ( "go" , "build" , "-o" , "/tmp/client" )
2022-09-22 04:58:10 +02:00
cmd . Env = os . Environ ( )
cmd . Env = append ( cmd . Env , "CGO_ENABLED=0" )
2022-04-16 03:12:26 +02:00
err := cmd . Run ( )
if err != nil {
panic ( fmt . Sprintf ( "failed to build client: %v" , err ) )
}
2023-09-08 03:30:47 +02:00
// Start the tests
2022-04-15 20:20:23 +02:00
m . Run ( )
}
2022-04-18 04:54:17 +02:00
var shellTesters [ ] shellTester = [ ] shellTester { bashTester { } , zshTester { } }
2023-09-01 20:23:03 +02:00
func TestParam ( t * testing . T ) {
2022-09-28 07:03:37 +02:00
if skipSlowTests ( ) {
shellTesters = shellTesters [ : 1 ]
}
2022-04-18 04:54:17 +02:00
for _ , tester := range shellTesters {
2024-02-11 20:54:27 +01:00
t . Run ( "testRepeatedCommandThenQuery/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testRepeatedCommandThenQuery ( t , tester ) } ) )
t . Run ( "testRepeatedCommandAndQuery/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testRepeatedCommandAndQuery ( t , tester ) } ) )
t . Run ( "testRepeatedEnableDisable/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testRepeatedEnableDisable ( t , tester ) } ) )
t . Run ( "testExcludeHiddenCommand/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testExcludeHiddenCommand ( t , tester ) } ) )
t . Run ( "testUpdate/head->release/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testUpdateFromHeadToRelease ( t , tester ) } ) )
t . Run ( "testUpdate/prev->release/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testUpdateFromPrevToRelease ( t , tester ) } ) )
t . Run ( "testUpdate/prev->release/prod/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testUpdateFromPrevToReleaseViaProd ( t , tester ) } ) )
t . Run ( "testUpdate/prev->current/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testUpdateFromPrevToCurrent ( t , tester ) } ) )
t . Run ( "testAdvancedQuery/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testAdvancedQuery ( t , tester ) } ) )
t . Run ( "testIntegration/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testIntegration ( t , tester , Online ) } ) )
t . Run ( "testIntegration/offline/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testIntegration ( t , tester , Offline ) } ) )
t . Run ( "testIntegrationWithNewDevice/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testIntegrationWithNewDevice ( t , tester ) } ) )
t . Run ( "testHishtoryBackgroundSaving/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testHishtoryBackgroundSaving ( t , tester ) } ) )
t . Run ( "testDisplayTable/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testDisplayTable ( t , tester ) } ) )
t . Run ( "testTableDisplayCwd/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testTableDisplayCwd ( t , tester ) } ) )
t . Run ( "testTimestampsAreReasonablyCorrect/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testTimestampsAreReasonablyCorrect ( t , tester ) } ) )
t . Run ( "testRequestAndReceiveDbDump/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testRequestAndReceiveDbDump ( t , tester ) } ) )
t . Run ( "testInstallViaPythonScript/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testInstallViaPythonScript ( t , tester ) } ) )
t . Run ( "testExportWithQuery/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testExportWithQuery ( t , tester ) } ) )
t . Run ( "testHelpCommand/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testHelpCommand ( t , tester ) } ) )
t . Run ( "testReuploadHistoryEntries/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testReuploadHistoryEntries ( t , tester ) } ) )
t . Run ( "testHishtoryOffline/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testHishtoryOffline ( t , tester ) } ) )
t . Run ( "testInitialHistoryImport/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testInitialHistoryImport ( t , tester ) } ) )
t . Run ( "testLocalRedaction/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testLocalRedaction ( t , tester , Online ) } ) )
t . Run ( "testLocalRedaction/offline/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testLocalRedaction ( t , tester , Offline ) } ) )
t . Run ( "testRemoteRedaction/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testRemoteRedaction ( t , tester ) } ) )
t . Run ( "testMultipleUsers/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testMultipleUsers ( t , tester ) } ) )
t . Run ( "testConfigGetSet/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testConfigGetSet ( t , tester ) } ) )
t . Run ( "testHandleUpgradedFeatures/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testHandleUpgradedFeatures ( t , tester ) } ) )
t . Run ( "testCustomColumns/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testCustomColumns ( t , tester ) } ) )
t . Run ( "testUninstall/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testUninstall ( t , tester ) } ) )
t . Run ( "testPresaving/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testPresaving ( t , tester , tester . ShellName ( ) ) } ) )
t . Run ( "testPresavingOffline/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testPresavingOffline ( t , tester ) } ) )
t . Run ( "testPresavingDisabled/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testPresavingDisabled ( t , tester ) } ) )
t . Run ( "testControlR/online/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testControlR ( t , tester , tester . ShellName ( ) , Online ) } ) )
t . Run ( "testControlR/offline/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testControlR ( t , tester , tester . ShellName ( ) , Offline ) } ) )
t . Run ( "testTabCompletion/" + tester . ShellName ( ) , wrapTestForSharding ( func ( t * testing . T ) { testTabCompletion ( t , tester , tester . ShellName ( ) ) } ) )
}
t . Run ( "testTabCompletion/fish" , wrapTestForSharding ( func ( t * testing . T ) { testTabCompletion ( t , zshTester { } , "fish" ) } ) )
t . Run ( "testPresaving/fish" , wrapTestForSharding ( func ( t * testing . T ) { testPresaving ( t , zshTester { } , "fish" ) } ) )
t . Run ( "testControlR/fish" , wrapTestForSharding ( func ( t * testing . T ) { testControlR ( t , bashTester { } , "fish" , Online ) } ) )
t . Run ( "testTui/search/online" , wrapTestForSharding ( func ( t * testing . T ) { testTui_search ( t , Online ) } ) )
t . Run ( "testTui/search/offline" , wrapTestForSharding ( func ( t * testing . T ) { testTui_search ( t , Offline ) } ) )
t . Run ( "testTui/general/online" , wrapTestForSharding ( func ( t * testing . T ) { testTui_general ( t , Online ) } ) )
t . Run ( "testTui/general/offline" , wrapTestForSharding ( func ( t * testing . T ) { testTui_general ( t , Offline ) } ) )
t . Run ( "testTui/scroll" , wrapTestForSharding ( testTui_scroll ) )
t . Run ( "testTui/resize" , wrapTestForSharding ( testTui_resize ) )
t . Run ( "testTui/delete" , wrapTestForSharding ( testTui_delete ) )
t . Run ( "testTui/color" , wrapTestForSharding ( testTui_color ) )
t . Run ( "testTui/errors" , wrapTestForSharding ( testTui_errors ) )
t . Run ( "testTui/ai" , wrapTestForSharding ( testTui_ai ) )
t . Run ( "testTui/defaultFilter" , wrapTestForSharding ( testTui_defaultFilter ) )
2023-08-30 08:23:21 +02:00
2022-11-27 07:40:43 +01:00
// Assert there are no leaked connections
assertNoLeakedConnections ( t )
2022-04-18 04:54:17 +02:00
}
2022-11-03 21:16:45 +01:00
func testIntegration ( t * testing . T , tester shellTester , onlineStatus OnlineStatus ) {
2022-01-09 20:00:53 +01:00
// Set up
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2022-03-30 06:56:28 +02:00
2022-04-07 03:18:46 +02:00
// Run the test
2022-11-03 21:16:45 +01:00
testBasicUserFlow ( t , tester , onlineStatus )
2022-04-06 08:31:24 +02:00
}
2022-01-09 20:00:53 +01:00
2022-04-18 04:54:17 +02:00
func testIntegrationWithNewDevice ( t * testing . T , tester shellTester ) {
2022-04-06 08:31:24 +02:00
// Set up
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2022-04-06 08:31:24 +02:00
2022-04-07 03:18:46 +02:00
// Run the test
2022-11-03 21:16:45 +01:00
userSecret := testBasicUserFlow ( t , tester , Online )
2022-04-06 08:31:24 +02:00
// Install it again
2023-11-19 17:56:02 +01:00
testutils . ResetLocalState ( t )
2022-04-18 04:54:17 +02:00
installHishtory ( t , tester , userSecret )
2022-01-09 23:34:59 +01:00
2022-04-07 03:18:46 +02:00
// Querying should show the history from the previous run
2022-11-03 21:16:45 +01:00
out := tester . RunInteractiveShell ( t , ` hishtory query ` )
2022-04-06 08:31:24 +02:00
expected := [ ] string { "echo thisisrecorded" , "hishtory enable" , "echo bar" , "echo foo" , "ls /foo" , "ls /bar" , "ls /a" }
for _ , item := range expected {
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , item )
2022-04-06 08:31:24 +02:00
if strings . Count ( out , item ) != 1 {
t . Fatalf ( "output has %#v in it multiple times! out=%#v" , item , out )
}
}
2022-04-18 04:54:17 +02:00
tester . RunInteractiveShell ( t , "echo mynewcommand" )
out = hishtoryQuery ( t , tester , "" )
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , "echo mynewcommand" )
2022-04-06 08:31:24 +02:00
if strings . Count ( out , "echo mynewcommand" ) != 1 {
t . Fatalf ( "output has `echo mynewcommand` the wrong number of times" )
}
2022-04-07 02:47:21 +02:00
// Install it a 3rd time
2023-11-19 17:56:02 +01:00
testutils . ResetLocalState ( t )
2022-04-18 04:54:17 +02:00
installHishtory ( t , tester , "adifferentsecret" )
2022-04-07 02:47:21 +02:00
2022-04-07 07:43:07 +02:00
// Run a command that shouldn't be in the hishtory later on
2022-04-18 04:54:17 +02:00
tester . RunInteractiveShell ( t , ` echo notinthehistory ` )
out = hishtoryQuery ( t , tester , "" )
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , "echo notinthehistory" )
2023-11-19 17:56:02 +01:00
require . NotContains ( t , out , "mynewcommand" )
require . NotContains ( t , out , "thisisrecorded" )
2022-04-07 07:43:07 +02:00
2022-04-07 03:18:46 +02:00
// Set the secret key to the previous secret key
2022-11-12 16:50:06 +01:00
out , err := tester . RunInteractiveShellRelaxed ( t , ` export HISHTORY_SKIP_INIT_IMPORT = 1
yes | hishtory init ` + userSecret )
2023-09-30 03:21:23 +02:00
require . NoError ( t , err )
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , "Setting secret hishtory key to " + userSecret , "Failed to re-init with the user secret" )
2022-04-07 02:47:21 +02:00
2022-11-12 03:46:22 +01:00
// Querying shouldn't show the entry from the previous account
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , "" )
2023-08-29 05:55:15 +02:00
require . NotContains ( t , out , "notinthehistory" , "output contains the unexpected item: notinthehistory" )
2022-11-12 03:46:22 +01:00
// And it should show the history from the previous run on this account
2022-04-07 02:47:21 +02:00
expected = [ ] string { "echo thisisrecorded" , "echo mynewcommand" , "hishtory enable" , "echo bar" , "echo foo" , "ls /foo" , "ls /bar" , "ls /a" }
for _ , item := range expected {
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , item , "output is missing expected item" )
2022-04-07 02:47:21 +02:00
if strings . Count ( out , item ) != 1 {
t . Fatalf ( "output has %#v in it multiple times! out=%#v" , item , out )
}
}
2022-04-18 04:54:17 +02:00
tester . RunInteractiveShell ( t , "echo mynewercommand" )
out = hishtoryQuery ( t , tester , "" )
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , "echo mynewercommand" )
2022-04-07 02:47:21 +02:00
if strings . Count ( out , "echo mynewercommand" ) != 1 {
t . Fatalf ( "output has `echo mynewercommand` the wrong number of times" )
}
2022-04-06 08:31:24 +02:00
2022-04-07 07:49:45 +02:00
// Manually submit an event that isn't in the local DB, and then we'll
2022-04-07 08:17:58 +02:00
// check if we see it when we do a query without ever having done an init
2022-10-28 06:53:47 +02:00
newEntry := testutils . MakeFakeHistoryEntry ( "othercomputer" )
2022-10-28 06:20:33 +02:00
newEntry . StartTime = time . Now ( )
newEntry . EndTime = time . Now ( )
2022-04-11 02:38:20 +02:00
manuallySubmitHistoryEntry ( t , userSecret , newEntry )
2022-04-07 08:17:58 +02:00
// Now check if that is in there when we do hishtory query
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , "" )
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , "othercomputer" , "hishtory query doesn't contain cmd run on another machine" )
2022-04-07 08:05:30 +02:00
2022-10-09 21:13:05 +02:00
// Run a reupload just to test that flow
tester . RunInteractiveShell ( t , "hishtory reupload" )
2022-04-07 08:05:30 +02:00
// Finally, test the export command
2022-04-18 04:54:17 +02:00
out = tester . RunInteractiveShell ( t , ` hishtory export | grep -v pipefail | grep -v '/tmp/client install' ` )
2023-08-29 05:55:15 +02:00
require . NotContains ( t , out , "thisisnotrecorded" , "hishtory export contains a command that should not have been recorded" )
2023-11-19 17:56:02 +01:00
expectedOutputWithoutKey := "hishtory status\nhishtory query\nls /a\nls /bar\nls /foo\necho foo\necho bar\nhishtory disable\nhishtory enable\necho thisisrecorded\nhishtory query\nhishtory query foo\necho hello | grep complex | sed s/h/i/g; echo baz && echo \"fo 'o\" # mycommand\nhishtory query complex\nhishtory query\necho mynewcommand\nhishtory query\nyes | hishtory init %s\nhishtory query\necho mynewercommand\nhishtory query\nothercomputer\nhishtory query\nhishtory reupload\n"
2022-04-15 05:18:49 +02:00
expectedOutput := fmt . Sprintf ( expectedOutputWithoutKey , userSecret )
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
2022-04-16 07:54:38 +02:00
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
2022-04-07 08:05:30 +02:00
}
2022-11-01 18:45:16 +01:00
// And test the export for each shell without anything filtered out
2022-11-13 02:27:24 +01:00
out = tester . RunInteractiveShell ( t , ` hishtory export -pipefail | grep -v 'hishtory init ' ` )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testIntegrationWithNewDevice-" + tester . ShellName ( ) )
2022-11-01 18:45:16 +01:00
// And test the table but with a subset of columns that is static
tester . RunInteractiveShell ( t , ` hishtory config-set displayed-columns Hostname 'Exit Code' Command ` )
2022-11-13 15:25:16 +01:00
out = tester . RunInteractiveShell ( t , ` hishtory query -pipefail | grep -v 'hishtory init ' | grep -v 'ls /' ` )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testIntegrationWithNewDevice-table" + tester . ShellName ( ) )
2022-11-27 07:40:43 +01:00
// Assert there are no leaked connections
assertNoLeakedConnections ( t )
2022-04-06 08:31:24 +02:00
}
2023-08-30 08:23:21 +02:00
func installWithOnlineStatus ( t testing . TB , tester shellTester , onlineStatus OnlineStatus ) string {
2022-11-03 21:16:45 +01:00
if onlineStatus == Online {
return installHishtory ( t , tester , "" )
} else {
return installHishtory ( t , tester , "--offline" )
}
}
func testBasicUserFlow ( t * testing . T , tester shellTester , onlineStatus OnlineStatus ) string {
2022-04-08 07:53:39 +02:00
// Test install
2022-11-16 08:20:19 +01:00
userSecret := installWithOnlineStatus ( t , tester , onlineStatus )
2022-11-03 21:16:45 +01:00
assertOnlineStatus ( t , onlineStatus )
2022-04-06 08:31:24 +02:00
2022-04-07 07:43:07 +02:00
// Test the status subcommand
2022-04-18 04:54:17 +02:00
out := tester . RunInteractiveShell ( t , ` hishtory status ` )
2022-09-27 07:55:59 +02:00
if out != fmt . Sprintf ( "hiSHtory: v0.Unknown\nEnabled: true\nSecret Key: %s\nCommit Hash: Unknown\n" , userSecret ) {
2022-04-07 02:47:21 +02:00
t . Fatalf ( "status command has unexpected output: %#v" , out )
}
2022-04-16 09:56:08 +02:00
// Assert that hishtory is correctly using the dev config.sh
homedir , err := os . UserHomeDir ( )
2023-09-09 21:28:01 +02:00
require . NoError ( t , err )
2022-12-17 07:22:57 +01:00
dat , err := os . ReadFile ( path . Join ( homedir , data . GetHishtoryPath ( ) , "config.sh" ) )
2023-09-09 21:28:01 +02:00
require . NoError ( t , err , "failed to read config.sh" )
2023-08-29 05:55:15 +02:00
require . NotContains ( t , string ( dat ) , "# Background Run" , "config.sh is the prod version when it shouldn't be" )
2022-04-16 09:56:08 +02:00
2022-04-07 07:44:10 +02:00
// Test the banner
2022-11-03 21:16:45 +01:00
if onlineStatus == Online {
os . Setenv ( "FORCED_BANNER" , "HELLO_FROM_SERVER" )
defer os . Setenv ( "FORCED_BANNER" , "" )
out = hishtoryQuery ( t , tester , "" )
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , "HELLO_FROM_SERVER\nHostname" , "hishtory query didn't show the banner message" )
2022-11-03 21:16:45 +01:00
os . Setenv ( "FORCED_BANNER" , "" )
2022-04-07 07:43:07 +02:00
}
2022-01-09 23:34:59 +01:00
// Test recording commands
2022-04-20 06:05:54 +02:00
out , err = tester . RunInteractiveShellRelaxed ( t , ` ls / a
2022-04-14 06:48:52 +02:00
ls / bar
ls / foo
echo foo
echo bar
hishtory disable
echo thisisnotrecorded
2022-04-15 05:18:49 +02:00
sleep 0.5
2022-04-14 06:48:52 +02:00
hishtory enable
echo thisisrecorded ` )
2023-08-29 04:27:29 +02:00
require . NoError ( t , err )
2022-01-09 23:34:59 +01:00
if out != "foo\nbar\nthisisnotrecorded\nthisisrecorded\n" {
t . Fatalf ( "unexpected output from running commands: %#v" , out )
}
// Test querying for all commands
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , "" )
2022-01-10 00:48:20 +01:00
expected := [ ] string { "echo thisisrecorded" , "hishtory enable" , "echo bar" , "echo foo" , "ls /foo" , "ls /bar" , "ls /a" }
for _ , item := range expected {
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , item , "output is missing expected item" )
2022-01-10 00:48:20 +01:00
}
2022-09-17 21:19:29 +02:00
// Test the actual table output
hostnameMatcher := ` \S+ `
tableDividerMatcher := ` \s+ `
2022-11-05 07:14:34 +01:00
pathMatcher := ` ~?/[a-zA-Z_0-9/-]+ `
2022-10-01 08:56:04 +02:00
datetimeMatcher := ` [a-zA-Z] { 3}\s\d { 1,2}\s\d { 4}\s[0-9:]+\s([A-Z] { 3}|[+-]\d { 4}) `
2022-09-17 21:37:02 +02:00
runtimeMatcher := ` [0-9.ms]+ `
2022-09-17 21:19:29 +02:00
exitCodeMatcher := ` 0 `
2022-09-17 21:49:27 +02:00
pipefailMatcher := ` set -em?o pipefail `
2022-09-21 08:46:17 +02:00
line1Matcher := ` Hostname ` + tableDividerMatcher + ` CWD ` + tableDividerMatcher + ` Timestamp ` + tableDividerMatcher + ` Runtime ` + tableDividerMatcher + ` Exit Code ` + tableDividerMatcher + ` Command\s*\n `
2023-11-19 10:25:48 +01:00
line2Matcher := hostnameMatcher + tableDividerMatcher + pathMatcher + tableDividerMatcher + datetimeMatcher + tableDividerMatcher + ` N/A ` + tableDividerMatcher + exitCodeMatcher + tableDividerMatcher + ` hishtory query ` + tableDividerMatcher + ` \n `
2023-11-12 16:56:55 +01:00
line3Matcher := hostnameMatcher + tableDividerMatcher + pathMatcher + tableDividerMatcher + datetimeMatcher + tableDividerMatcher + runtimeMatcher + tableDividerMatcher + exitCodeMatcher + tableDividerMatcher + pipefailMatcher + tableDividerMatcher + ` \n `
line4Matcher := hostnameMatcher + tableDividerMatcher + pathMatcher + tableDividerMatcher + datetimeMatcher + tableDividerMatcher + runtimeMatcher + tableDividerMatcher + exitCodeMatcher + tableDividerMatcher + ` echo thisisrecorded ` + tableDividerMatcher + ` \n `
2023-11-19 09:54:41 +01:00
require . Regexp ( t , regexp . MustCompile ( line1Matcher ) , out )
require . Regexp ( t , regexp . MustCompile ( line2Matcher ) , out )
require . Regexp ( t , regexp . MustCompile ( line3Matcher ) , out )
require . Regexp ( t , regexp . MustCompile ( line4Matcher ) , out )
require . Regexp ( t , regexp . MustCompile ( line1Matcher + line2Matcher + line3Matcher + line4Matcher ) , out )
2022-01-10 00:48:20 +01:00
// Test querying for a specific command
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , "foo" )
2022-01-10 00:48:20 +01:00
expected = [ ] string { "echo foo" , "ls /foo" }
unexpected := [ ] string { "echo thisisrecorded" , "hishtory enable" , "echo bar" , "ls /bar" , "ls /a" }
2022-01-09 23:34:59 +01:00
for _ , item := range expected {
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , item , "output is missing expected item" )
2022-04-06 08:31:24 +02:00
if strings . Count ( out , item ) != 1 {
t . Fatalf ( "output has %#v in it multiple times! out=%#v" , item , out )
}
2022-01-09 20:00:53 +01:00
}
2022-01-10 00:48:20 +01:00
for _ , item := range unexpected {
2023-08-29 05:55:15 +02:00
require . NotContains ( t , out , item , "output is containing unexpected item" )
2022-01-10 00:48:20 +01:00
}
2022-04-06 08:31:24 +02:00
2022-04-16 03:24:58 +02:00
// Add a complex command
2022-11-05 04:34:36 +01:00
complexCommand := "echo hello | grep complex | sed s/h/i/g; echo baz && echo \"fo 'o\" # mycommand"
2022-04-18 04:54:17 +02:00
_ , _ = tester . RunInteractiveShellRelaxed ( t , complexCommand )
2022-04-16 03:24:58 +02:00
// Query for it
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , "complex" )
2023-11-19 10:34:36 +01:00
if strings . Count ( out , "\n" ) != 3 {
2022-04-16 03:24:58 +02:00
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , complexCommand , "hishtory query doesn't contain the expected complex command" )
2023-11-19 10:34:36 +01:00
require . Contains ( t , out , "hishtory query complex" )
2022-04-16 03:24:58 +02:00
2022-11-27 07:40:43 +01:00
// Assert there are no leaked connections
assertNoLeakedConnections ( t )
2022-04-06 08:31:24 +02:00
return userSecret
2022-01-09 20:00:53 +01:00
}
2022-04-08 07:53:39 +02:00
2022-04-18 04:54:17 +02:00
func testAdvancedQuery ( t * testing . T , tester shellTester ) {
2022-04-08 07:53:39 +02:00
// Set up
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2022-04-08 07:53:39 +02:00
// Install hishtory
2022-04-18 04:54:17 +02:00
userSecret := installHishtory ( t , tester , "" )
2022-04-08 07:53:39 +02:00
// Run some commands we can query for
2022-04-18 08:31:08 +02:00
_ , err := tester . RunInteractiveShellRelaxed ( t , ` echo nevershouldappear
2022-04-15 05:18:49 +02:00
notacommand
cd / tmp /
echo querybydir
hishtory disable ` )
2023-08-29 04:27:29 +02:00
require . NoError ( t , err )
2022-04-08 07:53:39 +02:00
2022-04-13 06:24:25 +02:00
// A super basic query just to ensure the basics are working
2022-04-18 04:54:17 +02:00
out := hishtoryQuery ( t , tester , ` echo ` )
2023-11-19 10:13:20 +01:00
require . Contains ( t , out , "echo querybydir" )
require . Contains ( t , out , "echo nevershouldappear" )
if strings . Count ( out , "\n" ) != 3 {
2022-04-13 06:24:25 +02:00
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
2022-04-08 07:53:39 +02:00
// Query based on cwd
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` cwd:/tmp ` )
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , "echo querybydir" , "hishtory query doesn't contain result matching cwd:/tmp" )
require . NotContains ( t , out , "nevershouldappear" )
2023-11-12 16:56:55 +01:00
if strings . Count ( out , "\n" ) != 4 {
2022-04-08 07:53:39 +02:00
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
2022-06-06 03:05:06 +02:00
// And again, but with a strailing slash
out = hishtoryQuery ( t , tester , ` cwd:/tmp/ ` )
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , "echo querybydir" , "hishtory query doesn't contain result matching cwd:/tmp/" )
require . NotContains ( t , out , "nevershouldappear" )
2023-11-12 16:56:55 +01:00
if strings . Count ( out , "\n" ) != 4 {
2022-06-06 03:05:06 +02:00
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
2022-04-08 07:53:39 +02:00
2022-04-09 03:23:17 +02:00
// Query based on cwd without the slash
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` cwd:tmp ` )
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , "echo querybydir" )
2023-11-12 16:56:55 +01:00
if strings . Count ( out , "\n" ) != 4 {
2022-04-09 03:23:17 +02:00
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
2022-04-08 07:53:39 +02:00
// Query based on cwd and another term
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` cwd:/tmp querybydir ` )
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , "echo querybydir" )
require . NotContains ( t , out , "nevershouldappear" )
2022-04-08 07:53:39 +02:00
if strings . Count ( out , "\n" ) != 2 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
2022-04-08 08:25:13 +02:00
// Query based on exit_code
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` exit_code:127 ` )
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , "notacommand" )
2022-04-08 08:25:13 +02:00
if strings . Count ( out , "\n" ) != 2 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
2022-04-08 08:30:31 +02:00
// Query based on exit_code and something else that matches nothing
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` exit_code:127 foo ` )
2022-04-08 08:30:31 +02:00
if strings . Count ( out , "\n" ) != 1 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
2022-04-09 03:23:17 +02:00
// Query based on before: and cwd:
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` before:2125-07-02 cwd:/tmp ` )
2023-11-19 10:25:48 +01:00
if strings . Count ( out , "\n" ) != 4 {
2022-04-09 03:23:17 +02:00
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` before:2125-07-02 cwd:tmp ` )
2023-11-19 10:34:36 +01:00
if strings . Count ( out , "\n" ) != 4 {
2022-04-09 03:23:17 +02:00
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` before:2125-07-02 cwd:mp ` )
2023-11-19 10:34:36 +01:00
if strings . Count ( out , "\n" ) != 4 {
2022-04-09 03:23:17 +02:00
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
// Query based on after: and cwd:
2022-04-19 08:07:39 +02:00
out = hishtoryQuery ( t , tester , ` after:1980-07-02 cwd:/tmp ` )
2023-11-19 10:34:36 +01:00
if strings . Count ( out , "\n" ) != 4 {
2022-04-19 08:07:39 +02:00
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
2022-04-09 03:23:17 +02:00
}
// Query based on after: that returns no results
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` after:2120-07-02 cwd:/tmp ` )
2022-04-09 03:23:17 +02:00
if strings . Count ( out , "\n" ) != 1 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
2022-04-11 02:38:20 +02:00
// Manually submit an entry with a different hostname and username so we can test those atoms
2022-10-28 06:53:47 +02:00
entry := testutils . MakeFakeHistoryEntry ( "cmd_with_diff_hostname_and_username" )
2022-04-11 02:38:20 +02:00
entry . LocalUsername = "otheruser"
entry . Hostname = "otherhostname"
manuallySubmitHistoryEntry ( t , userSecret , entry )
// Query based on the username that exists
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` user:otheruser ` )
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , "cmd_with_diff_hostname_and_username" )
2022-04-11 02:38:20 +02:00
if strings . Count ( out , "\n" ) != 2 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
// Query based on the username that doesn't exist
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` user:noexist ` )
2022-04-11 02:38:20 +02:00
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
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` hostname:otherhostname ` )
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , "cmd_with_diff_hostname_and_username" )
2022-04-11 02:38:20 +02:00
if strings . Count ( out , "\n" ) != 2 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
2022-04-12 07:36:52 +02:00
// Test filtering out a search item
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , "" )
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , "cmd_with_diff_hostname_and_username" )
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` -cmd_with_diff_hostname_and_username ` )
2023-08-29 05:55:15 +02:00
require . NotContains ( t , out , "cmd_with_diff_hostname_and_username" )
2022-11-13 02:27:24 +01:00
out = hishtoryQuery ( t , tester , ` -echo -pipefail ` )
2023-08-29 05:55:15 +02:00
require . NotContains ( t , out , "echo" )
2023-11-19 10:44:48 +01:00
require . NotContains ( t , out , "pipefail" )
require . Contains ( t , out , "cmd_with_diff_hostname_and_username" )
if strings . Count ( out , "\n" ) != 6 {
2022-04-18 08:31:08 +02:00
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
2022-04-12 07:36:52 +02:00
}
// Test filtering out with an atom
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` -hostname:otherhostname ` )
2023-08-29 05:55:15 +02:00
require . NotContains ( t , out , "cmd_with_diff_hostname_and_username" )
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` -user:otheruser ` )
2023-08-29 05:55:15 +02:00
require . NotContains ( t , out , "cmd_with_diff_hostname_and_username" )
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` -exit_code:0 ` )
2022-04-12 07:36:52 +02:00
if strings . Count ( out , "\n" ) != 3 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
// Test filtering out a search item that also looks like it could be a search for a flag
2022-10-28 06:53:47 +02:00
entry = testutils . MakeFakeHistoryEntry ( "foo -echo" )
2022-04-12 07:36:52 +02:00
manuallySubmitHistoryEntry ( t , userSecret , entry )
2022-11-13 16:04:34 +01:00
out = hishtoryQuery ( t , tester , ` -echo -install -pipefail ` )
2023-08-29 05:55:15 +02:00
require . NotContains ( t , out , "echo" )
2023-11-19 11:21:43 +01:00
if strings . Count ( out , "\n" ) != 6 {
2022-04-12 07:36:52 +02:00
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
2022-09-08 08:20:31 +02:00
// Search for a cwd based on the home directory
2022-10-28 06:53:47 +02:00
entry = testutils . MakeFakeHistoryEntry ( "foobar" )
2022-09-08 08:20:31 +02:00
entry . HomeDirectory = "/home/david/"
entry . CurrentWorkingDirectory = "~/dir/"
manuallySubmitHistoryEntry ( t , userSecret , entry )
out = tester . RunInteractiveShell ( t , ` hishtory export cwd:~/dir ` )
expectedOutput := "foobar\n"
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
// And search with the fully expanded path
out = tester . RunInteractiveShell ( t , ` hishtory export cwd:/home/david/dir ` )
expectedOutput = "foobar\n"
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
2023-02-14 07:43:55 +01:00
// Search using an escaped dash
out = tester . RunInteractiveShell ( t , ` hishtory export \\-echo ` )
expectedOutput = "foo -echo\n"
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
// Search using a colon that doesn't match a column name
manuallySubmitHistoryEntry ( t , userSecret , testutils . MakeFakeHistoryEntry ( "foo:bar" ) )
out = tester . RunInteractiveShell ( t , ` hishtory export foo\\:bar ` )
expectedOutput = "foo:bar\n"
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
2022-04-08 07:53:39 +02:00
}
2022-04-09 08:47:13 +02:00
2023-10-10 06:41:30 +02:00
func installFromHead ( t * testing . T , tester shellTester ) ( string , string ) {
return installHishtory ( t , tester , "" ) , "v0.Unknown"
}
func installFromPrev ( t * testing . T , tester shellTester ) ( string , string ) {
defer testutils . BackupAndRestoreEnv ( "HISHTORY_FORCE_CLIENT_VERSION" ) ( )
2023-10-16 03:30:39 +02:00
dd , err := cmd . GetDownloadData ( makeTestOnlyContextWithFakeConfig ( ) )
2023-10-10 06:41:30 +02:00
require . NoError ( t , err )
pv , err := shared . ParseVersionString ( dd . Version )
require . NoError ( t , err )
previousVersion := pv . Decrement ( )
os . Setenv ( "HISHTORY_FORCE_CLIENT_VERSION" , previousVersion . String ( ) )
userSecret := installHishtory ( t , tester , "" )
out := tester . RunInteractiveShell ( t , ` hishtory update ` )
2023-10-10 16:43:37 +02:00
require . Regexp ( t , regexp . MustCompile ( ` ^Successfully updated hishtory from v0[.]Unknown to ` + previousVersion . String ( ) + ` \n$ ` ) , out )
2023-10-10 06:41:30 +02:00
return userSecret , previousVersion . String ( )
}
func updateToRelease ( t * testing . T , tester shellTester ) string {
2023-10-16 03:30:39 +02:00
dd , err := cmd . GetDownloadData ( makeTestOnlyContextWithFakeConfig ( ) )
2023-10-10 06:41:30 +02:00
require . NoError ( t , err )
// Update
2023-10-10 16:35:23 +02:00
out := tester . RunInteractiveShell ( t , " hishtory update\necho postupdate" )
2023-10-10 16:43:37 +02:00
require . Regexp ( t , regexp . MustCompile ( ` ^Successfully updated hishtory from v0[.][a-zA-Z0-9]+ to ` + dd . Version + ` \npostupdate\n$ ` ) , out )
2023-10-10 06:41:30 +02:00
require . NotContains ( t , out , "skipping SLSA validation" )
// Update again and assert that it skipped the update
2023-10-10 16:35:23 +02:00
out = tester . RunInteractiveShell ( t , " hishtory update" )
2023-10-10 16:43:37 +02:00
require . Equal ( t , fmt . Sprintf ( "Latest version (%s) is already installed\n" , dd . Version ) , out )
2023-10-10 06:41:30 +02:00
return dd . Version
}
func updateToHead ( t * testing . T , tester shellTester ) string {
2023-10-10 16:35:23 +02:00
out := tester . RunInteractiveShell ( t , " /tmp/client install\necho postupdate" )
require . Equal ( t , "postupdate\n" , out )
2023-10-10 06:41:30 +02:00
return "v0.Unknown"
}
func testUpdateFromHeadToRelease ( t * testing . T , tester shellTester ) {
testGenericUpdate ( t , tester , installFromHead , updateToRelease )
}
func testUpdateFromPrevToRelease ( t * testing . T , tester shellTester ) {
testGenericUpdate ( t , tester , installFromPrev , updateToRelease )
}
func testUpdateFromPrevToCurrent ( t * testing . T , tester shellTester ) {
testGenericUpdate ( t , tester , installFromPrev , updateToHead )
}
2023-10-11 07:14:51 +02:00
func testUpdateFromPrevToReleaseViaProd ( t * testing . T , tester shellTester ) {
defer testutils . BackupAndRestoreEnv ( "HISHTORY_SERVER" ) ( )
os . Setenv ( "HISHTORY_SERVER" , "https://api.hishtory.dev" )
testGenericUpdate ( t , tester , installFromPrev , updateToRelease )
}
2023-10-10 06:41:30 +02:00
func testGenericUpdate ( t * testing . T , tester shellTester , installInitialVersion func ( * testing . T , shellTester ) ( string , string ) , installUpdatedVersion func ( * testing . T , shellTester ) string ) {
defer testutils . BackupAndRestoreEnv ( "HISHTORY_FORCE_CLIENT_VERSION" ) ( )
2022-10-28 06:53:47 +02:00
if ! testutils . IsOnline ( ) {
2022-04-28 20:26:55 +02:00
t . Skip ( "skipping because we're currently offline" )
}
2022-04-21 05:56:01 +02:00
if runtime . GOOS == "linux" && runtime . GOARCH == "arm64" {
t . Skip ( "skipping on linux/arm64 which is unsupported" )
}
2022-04-09 21:50:01 +02:00
// Set up
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2023-10-10 06:41:30 +02:00
userSecret , initialVersion := installInitialVersion ( t , tester )
2022-04-09 21:50:01 +02:00
2022-04-17 08:00:04 +02:00
// Record a command before the update
2022-04-18 04:54:17 +02:00
tester . RunInteractiveShell ( t , "echo hello" )
2022-04-17 08:00:04 +02:00
2022-04-09 21:50:01 +02:00
// Check the status command
2022-04-18 04:54:17 +02:00
out := tester . RunInteractiveShell ( t , ` hishtory status ` )
2023-10-10 06:41:30 +02:00
require . Contains ( t , out , fmt . Sprintf ( "hiSHtory: %s\nEnabled: true\nSecret Key: %s\nCommit Hash: " , initialVersion , userSecret ) )
if initialVersion == "v0.Unknown" {
require . Contains ( t , out , "Commit Hash: Unknown" )
} else {
require . NotContains ( t , out , "Commit Hash: Unknown" )
2022-04-09 21:50:01 +02:00
}
// Update
2023-10-10 06:41:30 +02:00
updatedVersion := installUpdatedVersion ( t , tester )
2022-04-09 21:50:01 +02:00
// Then check the status command again to confirm the update worked
2022-04-18 04:54:17 +02:00
out = tester . RunInteractiveShell ( t , ` hishtory status ` )
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , fmt . Sprintf ( "\nEnabled: true\nSecret Key: %s\nCommit Hash: " , userSecret ) )
2023-10-10 06:41:30 +02:00
if updatedVersion != "v0.Unknown" {
require . NotContains ( t , out , "\nCommit Hash: Unknown\n" )
}
2022-04-17 08:00:04 +02:00
// Check that the history was preserved after the update
2022-11-12 00:12:23 +01:00
out = tester . RunInteractiveShell ( t , "hishtory export -pipefail | grep -v '/tmp/client install'" )
2023-10-10 16:35:23 +02:00
expectedOutput := "echo hello\nhishtory status\necho postupdate\nhishtory status\n"
2022-04-19 08:07:39 +02:00
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
2022-04-17 08:00:04 +02:00
}
2022-04-09 21:50:01 +02:00
}
2022-04-18 04:54:17 +02:00
func testRepeatedCommandThenQuery ( t * testing . T , tester shellTester ) {
2022-04-15 05:18:49 +02:00
// Set up
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2022-04-18 04:54:17 +02:00
userSecret := installHishtory ( t , tester , "" )
2022-04-15 05:18:49 +02:00
// Check the status command
2022-04-18 04:54:17 +02:00
out := tester . RunInteractiveShell ( t , ` hishtory status ` )
2022-09-27 07:55:59 +02:00
if out != fmt . Sprintf ( "hiSHtory: v0.Unknown\nEnabled: true\nSecret Key: %s\nCommit Hash: Unknown\n" , userSecret ) {
2022-04-15 05:18:49 +02:00
t . Fatalf ( "status command has unexpected output: %#v" , out )
}
// Run a command many times
for i := 0 ; i < 25 ; i ++ {
2022-04-18 04:54:17 +02:00
tester . RunInteractiveShell ( t , fmt . Sprintf ( "echo mycommand-%d" , i ) )
2022-04-15 05:18:49 +02:00
}
// Check that it shows up correctly
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` mycommand ` )
2022-04-15 05:18:49 +02:00
if strings . Count ( out , "\n" ) != 26 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
2023-11-12 16:56:55 +01:00
if strings . Count ( out , "echo mycommand" ) != 24 {
2022-04-15 05:18:49 +02:00
t . Fatalf ( "hishtory query has the wrong number of commands=%d, out=%#v" , strings . Count ( out , "echo mycommand" ) , out )
}
2023-11-12 16:56:55 +01:00
require . Contains ( t , out , "hishtory query mycommand" )
2022-04-15 05:18:49 +02:00
2022-10-24 00:40:30 +02:00
// Run a few more commands including some empty lines that don't get recorded
2022-04-18 04:54:17 +02:00
tester . RunInteractiveShell ( t , ` echo mycommand - 30
2022-10-24 00:40:30 +02:00
2022-04-15 05:18:49 +02:00
echo mycommand - 31
echo mycommand - 3 ` )
2022-04-18 04:54:17 +02:00
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"
2022-04-15 05:18:49 +02:00
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
}
2022-04-18 04:54:17 +02:00
func testRepeatedCommandAndQuery ( t * testing . T , tester shellTester ) {
2022-04-15 05:18:49 +02:00
// Set up
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2022-04-18 04:54:17 +02:00
userSecret := installHishtory ( t , tester , "" )
2022-04-15 05:18:49 +02:00
// Check the status command
2022-04-18 04:54:17 +02:00
out := tester . RunInteractiveShell ( t , ` hishtory status ` )
2022-09-27 07:55:59 +02:00
if out != fmt . Sprintf ( "hiSHtory: v0.Unknown\nEnabled: true\nSecret Key: %s\nCommit Hash: Unknown\n" , userSecret ) {
2022-04-15 05:18:49 +02:00
t . Fatalf ( "status command has unexpected output: %#v" , out )
}
// Run a command many times
for i := 0 ; i < 25 ; i ++ {
2022-04-18 04:54:17 +02:00
tester . RunInteractiveShell ( t , fmt . Sprintf ( "echo mycommand-%d" , i ) )
out = hishtoryQuery ( t , tester , fmt . Sprintf ( "mycommand-%d" , i ) )
2023-11-12 17:08:31 +01:00
if strings . Count ( out , "\n" ) != 3 {
2022-04-15 05:18:49 +02:00
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 )
}
2023-11-19 09:54:41 +01:00
require . Contains ( t , out , "hishtory query mycommand-" )
2022-04-15 05:18:49 +02:00
}
}
2022-04-18 04:54:17 +02:00
func testRepeatedEnableDisable ( t * testing . T , tester shellTester ) {
2022-04-15 05:18:49 +02:00
// Set up
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2022-04-18 04:54:17 +02:00
installHishtory ( t , tester , "" )
2022-04-15 05:18:49 +02:00
// Run a command many times
for i := 0 ; i < 25 ; i ++ {
2022-04-18 04:54:17 +02:00
tester . RunInteractiveShell ( t , fmt . Sprintf ( ` echo mycommand - % d
2023-11-19 09:54:41 +01:00
hishtory disable
2022-04-15 05:18:49 +02:00
echo shouldnotshowup
sleep 0.5
hishtory enable ` , i ) )
2022-04-18 04:54:17 +02:00
out := hishtoryQuery ( t , tester , fmt . Sprintf ( "mycommand-%d" , i ) )
2023-11-12 16:56:55 +01:00
if strings . Count ( out , "\n" ) != 3 {
2022-04-15 05:18:49 +02:00
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 )
}
2023-11-12 17:08:31 +01:00
require . Contains ( t , out , "hishtory query mycommand-" )
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , "" )
2023-08-29 05:55:15 +02:00
require . NotContains ( t , out , "shouldnotshowup" )
2022-04-15 05:18:49 +02:00
}
2022-04-18 04:54:17 +02:00
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"
2022-04-16 09:44:47 +02:00
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
2022-04-15 05:18:49 +02:00
}
}
2022-04-18 04:54:17 +02:00
func testExcludeHiddenCommand ( t * testing . T , tester shellTester ) {
2022-04-15 09:04:49 +02:00
// Set up
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2022-04-18 04:54:17 +02:00
installHishtory ( t , tester , "" )
2022-04-15 09:04:49 +02:00
2022-04-18 04:54:17 +02:00
tester . RunInteractiveShell ( t , ` echo hello1
2022-04-15 09:04:49 +02:00
echo hidden
echo hello2
echo hidden ` )
2022-04-18 04:54:17 +02:00
tester . RunInteractiveShell ( t , " echo hidden" )
2022-04-18 06:04:44 +02:00
out := hishtoryQuery ( t , tester , "-pipefail" )
if strings . Count ( out , "\n" ) != 3 {
2022-04-19 06:28:41 +02:00
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v, bash hishtory file=%#v" , strings . Count ( out , "\n" ) , out , tester . RunInteractiveShell ( t , "cat ~/.bash_history" ) )
2022-04-15 09:04:49 +02:00
}
if strings . Count ( out , "echo hello" ) != 2 {
t . Fatalf ( "hishtory query has the wrong number of commands=%d, out=%#v" , strings . Count ( out , "echo mycommand" ) , out )
}
if strings . Count ( out , "echo hello1" ) != 1 {
t . Fatalf ( "hishtory query has the wrong number of commands=%d, out=%#v" , strings . Count ( out , "echo mycommand" ) , out )
}
if strings . Count ( out , "echo hello2" ) != 1 {
t . Fatalf ( "hishtory query has the wrong number of commands=%d, out=%#v" , strings . Count ( out , "echo mycommand" ) , out )
}
2023-08-29 05:55:15 +02:00
require . NotContains ( t , out , "hidden" )
2022-04-15 09:04:49 +02:00
2022-04-18 04:54:17 +02:00
out = tester . RunInteractiveShell ( t , "hishtory export | grep -v pipefail | grep -v '/tmp/client install'" )
2022-04-18 06:04:44 +02:00
expectedOutput := "echo hello1\necho hello2\n"
2022-04-15 09:04:49 +02:00
if out != expectedOutput {
t . Fatalf ( "hishtory export has unexpected output=%#v" , out )
}
}
2023-08-30 08:23:21 +02:00
func waitForBackgroundSavesToComplete ( t testing . TB ) {
2022-10-23 09:20:47 +02:00
lastOut := ""
lastErr := ""
for i := 0 ; i < 20 ; i ++ {
2022-04-17 20:24:55 +02:00
cmd := exec . Command ( getPidofCommand ( ) , "hishtory" )
2022-04-15 05:18:49 +02:00
var stdout bytes . Buffer
var stderr bytes . Buffer
cmd . Stdout = & stdout
cmd . Stderr = & stderr
err := cmd . Run ( )
if err != nil && err . Error ( ) != "exit status 1" {
t . Fatalf ( "failed to check if hishtory was running: %v, stdout=%#v, stderr=%#v" , err , stdout . String ( ) , stderr . String ( ) )
}
if ! strings . Contains ( stdout . String ( ) , "\n" ) {
2022-04-21 05:56:01 +02:00
// pidof had no output, so hishtory isn't running and we're done waiting
time . Sleep ( 1000 * time . Millisecond )
2022-04-15 05:18:49 +02:00
return
}
2022-10-23 09:20:47 +02:00
lastOut = stdout . String ( )
2022-10-23 09:46:56 +02:00
lastErr = stderr . String ( )
2022-04-15 05:18:49 +02:00
time . Sleep ( 50 * time . Millisecond )
}
2022-10-23 09:20:47 +02:00
t . Fatalf ( "failed to wait until hishtory wasn't running (lastOut=%#v, lastErr=%#v)" , lastOut , lastErr )
2022-04-15 05:18:49 +02:00
}
2022-04-19 07:07:10 +02:00
func testTimestampsAreReasonablyCorrect ( t * testing . T , tester shellTester ) {
// Setup
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2022-04-19 07:07:10 +02:00
installHishtory ( t , tester , "" )
// Record a command
out := tester . RunInteractiveShell ( t , "echo hello" )
if out != "hello\n" {
t . Fatalf ( "running echo hello had unexpected out=%#v" , out )
}
// Query for it and check that the timestamp that gets recorded looks reasonable
out = hishtoryQuery ( t , tester , "echo hello" )
2023-11-12 16:56:55 +01:00
if strings . Count ( out , "\n" ) != 3 {
2022-04-19 07:07:10 +02:00
t . Fatalf ( "hishtory query has unexpected number of lines: out=%#v" , out )
}
expectedDate := time . Now ( ) . Format ( "Jan 2 2006" )
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , expectedDate )
2022-04-19 07:07:10 +02:00
}
2022-04-19 07:45:07 +02:00
func testTableDisplayCwd ( t * testing . T , tester shellTester ) {
// Setup
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2022-04-19 07:45:07 +02:00
installHishtory ( t , tester , "" )
// Record a command
out := tester . RunInteractiveShell ( t , ` cd ~ / . hishtory /
echo hello
cd / tmp /
echo other ` )
if out != "hello\nother\n" {
t . Fatalf ( "running echo hello had unexpected out=%#v" , out )
}
// Query for it and check that the directory gets recorded correctly
out = hishtoryQuery ( t , tester , "echo hello" )
2023-11-12 16:56:55 +01:00
if strings . Count ( out , "\n" ) != 3 {
2022-04-19 07:45:07 +02:00
t . Fatalf ( "hishtory query has unexpected number of lines: out=%#v" , out )
}
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , "~/" + data . GetHishtoryPath ( ) )
2022-04-19 07:45:07 +02:00
out = hishtoryQuery ( t , tester , "echo other" )
2023-11-12 16:56:55 +01:00
if strings . Count ( out , "\n" ) != 3 {
2022-04-19 07:45:07 +02:00
t . Fatalf ( "hishtory query has unexpected number of lines: out=%#v" , out )
}
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , "/tmp" )
2022-04-19 07:45:07 +02:00
}
2022-04-18 04:54:17 +02:00
func testHishtoryBackgroundSaving ( t * testing . T , tester shellTester ) {
2022-04-15 05:18:49 +02:00
// Setup
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2022-04-15 05:18:49 +02:00
2022-09-23 03:09:51 +02:00
// Check that we can find the go binary
_ , err := exec . LookPath ( "go" )
2023-09-30 03:21:23 +02:00
require . NoError ( t , err )
2022-09-22 06:49:08 +02:00
2022-04-15 05:18:49 +02:00
// Test install with an unset HISHTORY_TEST var so that we save in the background (this is likely to be flakey!)
2022-04-18 04:54:17 +02:00
out := tester . RunInteractiveShell ( t , ` unset HISHTORY_TEST
2022-09-22 05:30:50 +02:00
CGO_ENABLED = 0 go build - o / tmp / client
2022-04-15 05:18:49 +02:00
/ tmp / client install ` )
r := regexp . MustCompile ( ` Setting secret hishtory key to (.*) ` )
matches := r . FindStringSubmatch ( out )
if len ( matches ) != 2 {
2022-09-21 08:30:57 +02:00
t . Fatalf ( "Failed to extract userSecret from output=%#v: matches=%#v" , out , matches )
2022-04-15 05:18:49 +02:00
}
userSecret := matches [ 1 ]
// Assert that config.sh isn't the dev version
2022-04-16 09:56:08 +02:00
homedir , err := os . UserHomeDir ( )
2023-09-09 21:28:01 +02:00
require . NoError ( t , err )
2022-12-17 07:22:57 +01:00
dat , err := os . ReadFile ( path . Join ( homedir , data . GetHishtoryPath ( ) , "config.sh" ) )
2023-09-09 21:28:01 +02:00
require . NoError ( t , err , "failed to read config.sh" )
2023-08-29 05:55:15 +02:00
require . NotContains ( t , string ( dat ) , "except it doesn't run the save process in the background" , "config.sh is the testing version when it shouldn't be" )
2022-04-15 05:18:49 +02:00
// Test the status subcommand
2022-04-18 04:54:17 +02:00
out = tester . RunInteractiveShell ( t , ` hishtory status ` )
2022-09-27 07:55:59 +02:00
if out != fmt . Sprintf ( "hiSHtory: v0.Unknown\nEnabled: true\nSecret Key: %s\nCommit Hash: Unknown\n" , userSecret ) {
2022-04-15 05:18:49 +02:00
t . Fatalf ( "status command has unexpected output: %#v" , out )
}
// Test recording commands
2022-04-20 06:05:54 +02:00
out , err = tester . RunInteractiveShellRelaxed ( t , ` ls / a
2022-04-15 05:18:49 +02:00
echo foo ` )
2023-08-29 04:27:29 +02:00
require . NoError ( t , err )
2022-04-15 05:18:49 +02:00
if out != "foo\n" {
t . Fatalf ( "unexpected output from running commands: %#v" , out )
}
// Test querying for all commands
2022-04-15 20:20:23 +02:00
waitForBackgroundSavesToComplete ( t )
2022-04-22 07:25:24 +02:00
time . Sleep ( time . Second )
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , "" )
2022-04-15 05:18:49 +02:00
expected := [ ] string { "echo foo" , "ls /a" }
for _ , item := range expected {
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , item , "output is missing expected item" )
2022-04-15 05:18:49 +02:00
}
// Test querying for a specific command
2022-04-15 20:20:23 +02:00
waitForBackgroundSavesToComplete ( t )
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , "foo" )
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , "echo foo" )
require . NotContains ( t , out , "ls /a" )
2022-04-15 05:18:49 +02:00
}
2022-04-16 10:09:25 +02:00
2022-04-18 04:54:17 +02:00
func testDisplayTable ( t * testing . T , tester shellTester ) {
2022-04-16 10:09:25 +02:00
// Setup
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2022-04-18 04:54:17 +02:00
userSecret := installHishtory ( t , tester , "" )
2022-04-16 10:09:25 +02:00
// Submit two fake entries
2022-04-16 19:46:57 +02:00
tmz , err := time . LoadLocation ( "America/Los_Angeles" )
2023-09-09 21:28:01 +02:00
require . NoError ( t , err )
2022-10-28 06:53:47 +02:00
entry1 := testutils . MakeFakeHistoryEntry ( "table_cmd1" )
2022-04-16 19:46:57 +02:00
entry1 . StartTime = time . Unix ( 1650096186 , 0 ) . In ( tmz )
entry1 . EndTime = time . Unix ( 1650096190 , 0 ) . In ( tmz )
2022-04-16 10:09:25 +02:00
manuallySubmitHistoryEntry ( t , userSecret , entry1 )
2022-10-28 06:53:47 +02:00
entry2 := testutils . MakeFakeHistoryEntry ( "table_cmd2" )
2022-04-16 19:46:57 +02:00
entry2 . StartTime = time . Unix ( 1650096196 , 0 ) . In ( tmz )
entry2 . EndTime = time . Unix ( 1650096220 , 0 ) . In ( tmz )
2022-04-16 10:09:25 +02:00
entry2 . CurrentWorkingDirectory = "~/foo/"
entry2 . ExitCode = 3
manuallySubmitHistoryEntry ( t , userSecret , entry2 )
// Query and check the table
2022-10-30 02:34:16 +01:00
tester . RunInteractiveShell ( t , ` hishtory disable ` )
2022-04-18 04:54:17 +02:00
out := hishtoryQuery ( t , tester , "table" )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testDisplayTable-defaultColumns" )
2022-10-28 06:28:37 +02:00
// Adjust the columns that should be displayed
tester . RunInteractiveShell ( t , ` hishtory config-set displayed-columns Hostname Command ` )
// And check the table again
out = hishtoryQuery ( t , tester , "table" )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testDisplayTable-customColumns" )
2022-10-28 06:57:09 +02:00
// And again
tester . RunInteractiveShell ( t , ` hishtory config-set displayed-columns Hostname 'Exit Code' Command ` )
out = hishtoryQuery ( t , tester , "table" )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testDisplayTable-customColumns-2" )
2022-10-28 06:58:39 +02:00
// And again
tester . RunInteractiveShell ( t , ` hishtory config-add displayed-columns CWD ` )
out = hishtoryQuery ( t , tester , "table" )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testDisplayTable-customColumns-3" )
2022-10-28 07:02:19 +02:00
// Test displaying a command with multiple lines
entry3 := testutils . MakeFakeHistoryEntry ( "while :\ndo\nls /table/\ndone" )
manuallySubmitHistoryEntry ( t , userSecret , entry3 )
out = hishtoryQuery ( t , tester , "table" )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testDisplayTable-customColumns-multiLineCommand" )
2022-10-28 07:31:59 +02:00
// Add a custom column
tester . RunInteractiveShell ( t , ` hishtory config-add custom-columns foo "echo aaaaaaaaaaaaa" ` )
2023-09-30 03:21:23 +02:00
require . NoError ( t , os . Chdir ( "/" ) )
2022-10-28 07:31:59 +02:00
tester . RunInteractiveShell ( t , ` hishtory enable ` )
tester . RunInteractiveShell ( t , ` echo table-1 ` )
tester . RunInteractiveShell ( t , ` echo table-2 ` )
tester . RunInteractiveShell ( t , ` echo bar ` )
tester . RunInteractiveShell ( t , ` hishtory disable ` )
tester . RunInteractiveShell ( t , ` hishtory config-add displayed-columns foo ` )
// And run a query and confirm it is displayed
out = hishtoryQuery ( t , tester , "table" )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testDisplayTable-customColumns-trulyCustom" )
2022-04-16 10:09:25 +02:00
}
2022-05-02 04:37:26 +02:00
func testRequestAndReceiveDbDump ( t * testing . T , tester shellTester ) {
// Set up
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2022-05-02 04:37:26 +02:00
secretKey := installHishtory ( t , tester , "" )
2022-05-23 04:45:46 +02:00
// Confirm there are no pending dump requests
2023-10-14 19:52:35 +02:00
ctx := hctx . MakeContext ( )
config := hctx . GetConf ( ctx )
2022-06-05 08:03:05 +02:00
deviceId1 := config . DeviceId
2023-10-14 19:52:35 +02:00
respBytes , err := lib . ApiGet ( ctx , "/api/v1/get-dump-requests?user_id=" + data . UserId ( secretKey ) + "&device_id=" + deviceId1 )
2023-09-11 22:34:22 +02:00
resp := strings . TrimSpace ( string ( respBytes ) )
2023-09-09 21:28:01 +02:00
require . NoError ( t , err , "failed to get pending dump requests" )
2023-09-11 22:34:22 +02:00
require . Equalf ( t , "[]" , resp , "there are pending dump requests! user_id=%#v, resp=%#v" , data . UserId ( secretKey ) , resp )
2022-05-23 04:45:46 +02:00
2022-05-02 04:37:26 +02:00
// Record two commands and then query for them
out := tester . RunInteractiveShell ( t , ` echo hello
2022-05-23 04:45:46 +02:00
echo other ` )
if out != "hello\nother\n" {
t . Fatalf ( "running echo had unexpected out=%#v" , out )
}
2022-05-02 04:37:26 +02:00
// Query for it and check that the directory gets recorded correctly
out = hishtoryQuery ( t , tester , "echo" )
2023-11-12 16:56:55 +01:00
if strings . Count ( out , "\n" ) != 4 {
2022-05-02 04:37:26 +02:00
t . Fatalf ( "hishtory query has unexpected number of lines: out=%#v" , out )
}
2023-11-12 16:56:55 +01:00
require . Contains ( t , out , "hishtory query echo" )
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , "echo hello" )
require . Contains ( t , out , "echo other" )
2022-05-02 04:37:26 +02:00
// Back up this copy
2022-10-28 06:53:47 +02:00
restoreFirstInstallation := testutils . BackupAndRestoreWithId ( t , "-install1" )
2022-05-02 04:37:26 +02:00
// Wipe the DB to simulate entries getting deleted because they've already been read and expired
2023-10-14 19:52:35 +02:00
_ , err = lib . ApiGet ( ctx , "/api/v1/wipe-db-entries" )
2023-09-09 21:28:01 +02:00
require . NoError ( t , err , "failed to wipe the remote DB" )
2022-05-02 04:37:26 +02:00
// Install a new one (with the same secret key but a diff device id)
installHishtory ( t , tester , secretKey )
2022-06-05 08:03:05 +02:00
// Confirm there is now a pending dump requests that the first device should respond to
2023-10-14 19:52:35 +02:00
respBytes , err = lib . ApiGet ( ctx , "/api/v1/get-dump-requests?user_id=" + data . UserId ( secretKey ) + "&device_id=" + deviceId1 )
2023-09-11 22:34:22 +02:00
resp = strings . TrimSpace ( string ( respBytes ) )
2023-09-09 21:28:01 +02:00
require . NoError ( t , err , "failed to get pending dump requests" )
2023-09-11 22:34:22 +02:00
require . NotEqualf ( t , "[]" , resp , "There are no pending dump requests! user_id=%#v, resp=%#v" , data . UserId ( secretKey ) , string ( resp ) )
2022-05-23 04:45:46 +02:00
2022-05-02 04:37:26 +02:00
// Check that the new one doesn't have the commands yet
out = hishtoryQuery ( t , tester , "echo" )
2023-11-12 17:08:31 +01:00
if strings . Count ( out , "\n" ) != 2 {
2022-05-02 04:37:26 +02:00
t . Fatalf ( "hishtory query has unexpected number of lines, should contain no entries: out=%#v" , out )
}
2023-11-12 17:08:31 +01:00
require . Contains ( t , out , "hishtory query echo" )
2023-10-21 23:59:12 +02:00
require . NotContains ( t , out , "echo hello" , "hishtory query contains unexpected command" )
require . NotContains ( t , out , "echo other" , "hishtory query contains unexpected command" )
2022-05-02 04:37:26 +02:00
out = tester . RunInteractiveShell ( t , ` hishtory export | grep -v pipefail ` )
if out != "hishtory query echo\n" {
t . Fatalf ( "hishtory export has unexpected out=%#v" , out )
}
// Restore the first copy
2022-10-28 06:53:47 +02:00
restoreSecondInstallation := testutils . BackupAndRestoreWithId ( t , "-install2" )
2022-05-02 04:37:26 +02:00
restoreFirstInstallation ( )
// Confirm it still has the correct entries via hishtory export (and this runs a command to trigger it to dump the DB)
out = tester . RunInteractiveShell ( t , ` hishtory export | grep -v pipefail ` )
2022-05-23 04:45:46 +02:00
expectedOutput := "echo hello\necho other\nhishtory query echo\nhishtory query echo\n"
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
2022-06-05 08:03:05 +02:00
// Confirm there are no pending dump requests for the first device
2023-10-14 19:52:35 +02:00
respBytes , err = lib . ApiGet ( ctx , "/api/v1/get-dump-requests?user_id=" + data . UserId ( secretKey ) + "&device_id=" + deviceId1 )
2023-09-11 22:34:22 +02:00
resp = strings . TrimSpace ( string ( respBytes ) )
2023-09-09 21:28:01 +02:00
require . NoError ( t , err , "failed to get pending dump requests" )
2023-09-11 22:34:22 +02:00
require . Equalf ( t , "[]" , resp , "There are pending dump requests! user_id=%#v, resp=%#v" , data . UserId ( secretKey ) , string ( resp ) )
2022-05-02 04:37:26 +02:00
// Restore the second copy and confirm it has the commands
restoreSecondInstallation ( )
out = hishtoryQuery ( t , tester , "ech" )
2023-11-19 09:54:41 +01:00
if strings . Count ( out , "\n" ) != 6 {
2022-05-02 04:37:26 +02:00
t . Fatalf ( "hishtory query has unexpected number of lines=%d: out=%#v" , strings . Count ( out , "\n" ) , out )
}
2023-11-19 10:13:20 +01:00
require . Contains ( t , out , "hishtory query ech" )
expected := [ ] string { "echo hello" , "echo other" }
2022-05-02 04:37:26 +02:00
for _ , item := range expected {
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , item )
2022-05-02 04:37:26 +02:00
if strings . Count ( out , item ) != 1 {
t . Fatalf ( "output has %#v in it multiple times! out=%#v" , item , out )
}
}
// And check hishtory export too for good measure
2023-11-19 09:54:41 +01:00
out = tester . RunInteractiveShell ( t , ` hishtory export | grep -v pipefail ` )
2022-05-23 04:45:46 +02:00
expectedOutput = "echo hello\necho other\nhishtory query echo\nhishtory query echo\nhishtory query ech\n"
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
2022-05-02 04:37:26 +02:00
}
}
2022-12-17 07:22:57 +01:00
func TestInstallViaPythonScriptWithCustomHishtoryPath ( t * testing . T ) {
2024-02-11 20:54:27 +01:00
markTestForSharding ( t , 0 )
2022-12-17 07:22:57 +01:00
defer testutils . BackupAndRestore ( t ) ( )
defer testutils . BackupAndRestoreEnv ( "HISHTORY_PATH" ) ( )
2023-09-07 05:13:36 +02:00
altHishtoryPath := ".other-path"
os . Setenv ( "HISHTORY_PATH" , altHishtoryPath )
// Make sure ~/$HISHTORY_PATH/ is also cleared out and empty
homedir , err := os . UserHomeDir ( )
require . NoError ( t , err )
require . NoError ( t , os . RemoveAll ( path . Join ( homedir , altHishtoryPath ) ) )
2023-12-22 15:33:09 +01:00
testInstallViaPythonScriptChild ( t , zshTester { } )
}
func TestInstallViaPythonScriptInOfflineMode ( t * testing . T ) {
2024-02-11 20:54:27 +01:00
markTestForSharding ( t , 1 )
2023-12-22 15:33:09 +01:00
defer testutils . BackupAndRestore ( t ) ( )
defer testutils . BackupAndRestoreEnv ( "HISHTORY_OFFLINE" ) ( )
os . Setenv ( "HISHTORY_OFFLINE" , "1" )
2023-12-22 16:41:44 +01:00
tester := zshTester { }
2023-12-22 15:33:09 +01:00
2023-12-22 16:41:44 +01:00
// Check that installing works
testInstallViaPythonScriptChild ( t , tester )
// And check that it installed in offline mode
2023-12-22 17:02:27 +01:00
out := tester . RunInteractiveShell ( t , ` hishtory status -v ` )
2023-12-22 16:41:44 +01:00
require . Contains ( t , out , "\nSync Mode: Disabled\n" )
2022-12-17 07:22:57 +01:00
}
2022-06-05 07:27:04 +02:00
func testInstallViaPythonScript ( t * testing . T , tester shellTester ) {
2022-12-17 07:22:57 +01:00
defer testutils . BackupAndRestore ( t ) ( )
testInstallViaPythonScriptChild ( t , tester )
2023-12-22 16:41:44 +01:00
// And check that it installed in online mode
2023-12-22 17:02:27 +01:00
out := tester . RunInteractiveShell ( t , ` hishtory status -v ` )
2023-12-22 16:41:44 +01:00
require . Contains ( t , out , "\nSync Mode: Enabled\n" )
2022-12-17 07:22:57 +01:00
}
func testInstallViaPythonScriptChild ( t * testing . T , tester shellTester ) {
2022-11-15 05:55:10 +01:00
if ! testutils . IsOnline ( ) {
t . Skip ( "skipping because we're currently offline" )
}
2022-06-05 07:27:04 +02:00
// Set up
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestoreEnv ( "HISHTORY_TEST" ) ( )
2022-06-05 07:27:04 +02:00
// Install via the python script
out := tester . RunInteractiveShell ( t , ` curl https://hishtory.dev/install.py | python3 - ` )
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , "Succesfully installed hishtory" )
2022-06-05 07:27:04 +02:00
r := regexp . MustCompile ( ` Setting secret hishtory key to (.*) ` )
matches := r . FindStringSubmatch ( out )
if len ( matches ) != 2 {
2022-09-21 08:30:57 +02:00
t . Fatalf ( "Failed to extract userSecret from output=%#v: matches=%#v" , out , matches )
2022-06-05 07:27:04 +02:00
}
userSecret := matches [ 1 ]
// Test the status subcommand
2023-10-16 03:30:39 +02:00
downloadData , err := cmd . GetDownloadData ( makeTestOnlyContextWithFakeConfig ( ) )
2023-08-29 04:27:29 +02:00
require . NoError ( t , err )
2022-06-05 07:27:04 +02:00
out = tester . RunInteractiveShell ( t , ` hishtory status ` )
2022-09-27 07:55:59 +02:00
expectedOut := fmt . Sprintf ( "hiSHtory: %s\nEnabled: true\nSecret Key: %s\nCommit Hash: " , downloadData . Version , userSecret )
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , expectedOut )
2022-06-05 07:27:04 +02:00
// And test that it recorded that command
2022-10-16 18:56:14 +02:00
time . Sleep ( time . Second )
2022-11-13 03:46:30 +01:00
out = tester . RunInteractiveShell ( t , ` hishtory export -pipefail ` )
2022-06-05 07:27:04 +02:00
if out != "hishtory status\n" {
t . Fatalf ( "unexpected output from hishtory export=%#v" , out )
}
}
2024-02-10 02:41:52 +01:00
func TestInstallViaPythonScriptFromHead ( t * testing . T ) {
2024-02-11 20:54:27 +01:00
markTestForSharding ( t , 2 )
2024-02-10 02:41:52 +01:00
defer testutils . BackupAndRestore ( t ) ( )
tester := zshTester { }
// Set up
defer testutils . BackupAndRestoreEnv ( "HISHTORY_TEST" ) ( )
// Install via the python script
out := tester . RunInteractiveShell ( t , ` cat backend/web/landing/www/install.py | python3 - ` )
require . Contains ( t , out , "Succesfully installed hishtory" )
r := regexp . MustCompile ( ` Setting secret hishtory key to (.*) ` )
matches := r . FindStringSubmatch ( out )
if len ( matches ) != 2 {
t . Fatalf ( "Failed to extract userSecret from output=%#v: matches=%#v" , out , matches )
}
userSecret := matches [ 1 ]
// Test the status subcommand
downloadData , err := cmd . GetDownloadData ( makeTestOnlyContextWithFakeConfig ( ) )
require . NoError ( t , err )
out = tester . RunInteractiveShell ( t , ` hishtory status ` )
expectedOut := fmt . Sprintf ( "hiSHtory: %s\nEnabled: true\nSecret Key: %s\nCommit Hash: " , downloadData . Version , userSecret )
require . Contains ( t , out , expectedOut )
// And test that it recorded that command
time . Sleep ( time . Second )
out = tester . RunInteractiveShell ( t , ` hishtory export -pipefail ` )
if out != "hishtory status\n" {
t . Fatalf ( "unexpected output from hishtory export=%#v" , out )
}
// And check that it installed in online mode
out = tester . RunInteractiveShell ( t , ` hishtory status -v ` )
require . Contains ( t , out , "\nSync Mode: Enabled\n" )
}
2022-06-06 03:05:06 +02:00
func testExportWithQuery ( t * testing . T , tester shellTester ) {
// Setup
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2022-06-06 03:05:06 +02:00
installHishtory ( t , tester , "" )
// Test recording commands
out , err := tester . RunInteractiveShellRelaxed ( t , ` ls / a
ls / bar
ls / foo
echo foo
echo bar
hishtory disable
echo thisisnotrecorded
sleep 0.5
cd / tmp /
hishtory enable
2022-10-20 06:33:31 +02:00
echo thisisrecorded
echo bar &
sleep 1 ` )
2023-08-29 04:27:29 +02:00
require . NoError ( t , err )
2022-10-20 06:33:31 +02:00
if out != "foo\nbar\nthisisnotrecorded\nthisisrecorded\nbar\n" {
2022-06-06 03:05:06 +02:00
t . Fatalf ( "unexpected output from running commands: %#v" , out )
}
// Test querying for all commands
out = hishtoryQuery ( t , tester , "" )
2022-10-20 06:33:31 +02:00
expected := [ ] string { "echo thisisrecorded" , "hishtory enable" , "echo bar" , "echo foo" , "ls /foo" , "ls /bar" , "ls /a" , "echo bar &" , "sleep 1" }
2022-06-06 03:05:06 +02:00
for _ , item := range expected {
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , item )
2022-06-06 03:05:06 +02:00
}
// Test querying for a specific command
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 {
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , item )
2022-06-06 03:05:06 +02:00
if strings . Count ( out , item ) != 1 {
t . Fatalf ( "output has %#v in it multiple times! out=%#v" , item , out )
}
}
for _ , item := range unexpected {
2023-08-29 05:55:15 +02:00
require . NotContains ( t , out , item )
2022-06-06 03:05:06 +02:00
}
// Test using export with a query
out = tester . RunInteractiveShell ( t , ` hishtory export foo ` )
2023-11-12 16:56:55 +01:00
if out != "ls /foo\necho foo\nhishtory query foo\nhishtory export foo\n" {
2022-06-06 03:05:06 +02:00
t . Fatalf ( "expected hishtory export to equal out=%#v" , out )
}
// Test a more complex query with export
out = tester . RunInteractiveShell ( t , ` hishtory export cwd:/tmp/ ` )
2022-10-20 06:33:31 +02:00
if out != "hishtory enable\necho thisisrecorded\necho bar &\nsleep 1\n" {
2022-06-06 03:05:06 +02:00
t . Fatalf ( "expected hishtory export to equal out=%#v" , out )
}
}
2022-06-06 03:26:02 +02:00
func testHelpCommand ( t * testing . T , tester shellTester ) {
// Setup
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2022-06-06 03:26:02 +02:00
installHishtory ( t , tester , "" )
// Test the help command
out := tester . RunInteractiveShell ( t , ` hishtory help ` )
2022-11-15 05:55:10 +01:00
if ! strings . HasPrefix ( out , "hiSHtory: Better shell history" ) {
2022-06-06 03:26:02 +02:00
t . Fatalf ( "expected hishtory help to contain intro, actual=%#v" , out )
}
out2 := tester . RunInteractiveShell ( t , ` hishtory -h ` )
if out != out2 {
t . Fatalf ( "expected hishtory -h to equal help" )
}
}
2022-11-06 07:40:25 +01:00
func TestStripBashTimePrefix ( t * testing . T ) {
2022-06-13 06:28:19 +02:00
// Setup
2024-02-11 20:54:27 +01:00
markTestForSharding ( t , 4 )
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2022-11-06 07:40:25 +01:00
tester := bashTester { }
2022-06-13 06:28:19 +02:00
installHishtory ( t , tester , "" )
// Add a HISTTIMEFORMAT to the bashrc
homedir , err := os . UserHomeDir ( )
2023-08-29 04:27:29 +02:00
require . NoError ( t , err )
2022-12-17 07:22:57 +01:00
f , err := os . OpenFile ( path . Join ( homedir , data . GetHishtoryPath ( ) , "config.sh" ) ,
2022-06-13 06:28:19 +02:00
os . O_APPEND | os . O_WRONLY , 0644 )
2023-08-29 04:27:29 +02:00
require . NoError ( t , err )
2022-06-13 06:28:19 +02:00
defer f . Close ( )
_ , err = f . WriteString ( "\nexport HISTTIMEFORMAT='%F %T '\n" )
2023-08-29 04:27:29 +02:00
require . NoError ( t , err )
2022-06-13 06:28:19 +02:00
// Record a command
tester . RunInteractiveShell ( t , ` ls -Slah ` )
// Check it shows up correctly
out := tester . RunInteractiveShell ( t , "hishtory export ls" )
2023-11-12 16:56:55 +01:00
if out != "ls -Slah\nhishtory export ls\n" {
2022-06-13 06:28:19 +02:00
t . Fatalf ( "hishtory had unexpected output=%#v" , out )
}
// Update it to another complex one
homedir , err = os . UserHomeDir ( )
2023-08-29 04:27:29 +02:00
require . NoError ( t , err )
2022-12-17 07:22:57 +01:00
f , err = os . OpenFile ( path . Join ( homedir , data . GetHishtoryPath ( ) , "config.sh" ) ,
2022-06-13 06:28:19 +02:00
os . O_APPEND | os . O_WRONLY , 0644 )
2023-08-29 04:27:29 +02:00
require . NoError ( t , err )
2022-06-13 06:28:19 +02:00
defer f . Close ( )
_ , err = f . WriteString ( "\nexport HISTTIMEFORMAT='[%c] '\n" )
2023-08-29 04:27:29 +02:00
require . NoError ( t , err )
2022-06-13 06:28:19 +02:00
// Record a command
tester . RunInteractiveShell ( t , ` echo foo ` )
// Check it shows up correctly
out = tester . RunInteractiveShell ( t , "hishtory export echo" )
2023-11-19 09:54:41 +01:00
if out != "echo foo\nhishtory export echo\n" {
2022-06-13 06:28:19 +02:00
t . Fatalf ( "hishtory had unexpected output=%#v" , out )
}
}
2022-09-05 03:37:46 +02:00
func testReuploadHistoryEntries ( t * testing . T , tester shellTester ) {
// Setup
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2022-09-05 03:37:46 +02:00
// Init an initial device
userSecret := installHishtory ( t , tester , "" )
// Set up a second device
2022-10-28 06:53:47 +02:00
restoreFirstProfile := testutils . BackupAndRestoreWithId ( t , "-install1" )
2022-09-05 03:37:46 +02:00
installHishtory ( t , tester , userSecret )
// Device 2: Record a command
tester . RunInteractiveShell ( t , ` echo 1 ` )
// Device 2: Record a command with a simulated network error
2023-10-22 20:28:41 +02:00
tester . RunInteractiveShell ( t , ` echo 2 ` )
os . Setenv ( "HISHTORY_SIMULATE_NETWORK_ERROR" , "1" )
tester . RunInteractiveShell ( t , ` echo 3 ` )
os . Setenv ( "HISHTORY_SIMULATE_NETWORK_ERROR" , "" )
2022-09-05 03:37:46 +02:00
// Device 1: Run an export and confirm that the network only contains the first command
2022-10-28 06:53:47 +02:00
restoreSecondProfile := testutils . BackupAndRestoreWithId ( t , "-install2" )
2022-09-05 03:37:46 +02:00
restoreFirstProfile ( )
out := tester . RunInteractiveShell ( t , "hishtory export | grep -v pipefail" )
2023-10-23 02:59:21 +02:00
expectedOutput := "echo 1\necho 2\n"
2022-09-05 03:37:46 +02:00
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
// Device 2: Run another command but with the network re-enabled
2022-10-28 06:53:47 +02:00
restoreFirstProfile = testutils . BackupAndRestoreWithId ( t , "-install1" )
2022-09-05 03:37:46 +02:00
restoreSecondProfile ( )
2023-10-22 20:28:41 +02:00
tester . RunInteractiveShell ( t , ` echo 4 ` )
2022-09-05 03:37:46 +02:00
// Device 2: Run export which contains all results (as it did all along since it is stored offline)
out = tester . RunInteractiveShell ( t , "hishtory export | grep -v pipefail" )
2023-10-23 02:59:21 +02:00
expectedOutput = "echo 1\necho 2\necho 3\necho 4\n"
2022-09-05 03:37:46 +02:00
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
// Device 1: Now it too sees all the results
restoreFirstProfile ( )
out = tester . RunInteractiveShell ( t , "hishtory export | grep -v pipefail" )
2023-10-23 02:59:21 +02:00
expectedOutput = "echo 1\necho 2\necho 3\necho 4\n"
2022-09-05 03:37:46 +02:00
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
}
2022-09-22 05:09:49 +02:00
func testHishtoryOffline ( t * testing . T , tester shellTester ) {
// Setup
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2022-09-22 05:09:49 +02:00
// Init an initial device
userSecret := installHishtory ( t , tester , "" )
// Set up a second device
2022-10-28 06:53:47 +02:00
restoreFirstProfile := testutils . BackupAndRestoreWithId ( t , "-install1" )
2022-09-22 05:09:49 +02:00
installHishtory ( t , tester , userSecret )
// Device 2: Record a command
tester . RunInteractiveShell ( t , ` echo dev2 ` )
// Device 1: Run a command
2022-10-28 06:53:47 +02:00
restoreSecondProfile := testutils . BackupAndRestoreWithId ( t , "-install2" )
2022-09-22 05:09:49 +02:00
restoreFirstProfile ( )
tester . RunInteractiveShell ( t , ` echo dev1-a ` )
// Device 1: Query while offline
2023-10-22 20:28:41 +02:00
os . Setenv ( "HISHTORY_SIMULATE_NETWORK_ERROR" , "1" )
out := tester . RunInteractiveShell ( t , ` hishtory export | grep -v pipefail ` )
os . Setenv ( "HISHTORY_SIMULATE_NETWORK_ERROR" , "" )
2022-09-22 05:09:49 +02:00
expectedOutput := "echo dev2\necho dev1-a\n"
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
// Device 2: Record another command
2022-10-28 06:53:47 +02:00
restoreFirstProfile = testutils . BackupAndRestoreWithId ( t , "-install1" )
2022-09-22 05:09:49 +02:00
restoreSecondProfile ( )
tester . RunInteractiveShell ( t , ` echo dev2-b ` )
// Device 1: Query while offline before ever retrieving the command
restoreFirstProfile ( )
2023-10-22 20:28:41 +02:00
os . Setenv ( "HISHTORY_SIMULATE_NETWORK_ERROR" , "1" )
out = tester . RunInteractiveShell ( t , ` hishtory export | grep -v pipefail ` )
os . Setenv ( "HISHTORY_SIMULATE_NETWORK_ERROR" , "" )
2022-09-22 05:09:49 +02:00
expectedOutput = "echo dev2\necho dev1-a\n"
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
// Device 1: Query while online and get the command
out = tester . RunInteractiveShell ( t , ` hishtory export | grep -v pipefail ` )
expectedOutput = "echo dev2\necho dev1-a\necho dev2-b\n"
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
}
2022-09-17 20:49:31 +02:00
func testInitialHistoryImport ( t * testing . T , tester shellTester ) {
// Setup
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2022-11-14 00:38:02 +01:00
defer testutils . BackupAndRestoreEnv ( "HISHTORY_SKIP_INIT_IMPORT" ) ( )
2022-11-12 00:12:23 +01:00
os . Setenv ( "HISHTORY_SKIP_INIT_IMPORT" , "" )
2022-09-17 20:49:31 +02:00
// Record some commands before installing hishtory
randomCmdUuid := uuid . Must ( uuid . NewRandom ( ) ) . String ( )
2022-11-12 00:12:23 +01:00
captureTerminalOutputWithShellName ( t , tester , "fish" , [ ] string { fmt . Sprintf ( "echo SPACE %s-fishcommand ENTER" , randomCmdUuid ) } )
2022-09-17 20:49:31 +02:00
randomCmd := fmt . Sprintf ( ` echo % v - foo
echo % v - bar ` , randomCmdUuid , randomCmdUuid )
tester . RunInteractiveShell ( t , randomCmd )
// Install hishtory
installHishtory ( t , tester , "" )
2022-11-12 00:12:23 +01:00
// Check that hishtory export has the commands
out := tester . RunInteractiveShell ( t , ` hishtory export ` + randomCmdUuid [ : 5 ] )
2023-10-13 06:51:18 +02:00
expectedOutput := strings . ReplaceAll ( ` echo UUID - fishcommand
echo UUID - foo
2022-11-12 00:12:23 +01:00
echo UUID - bar
2023-11-12 16:56:55 +01:00
hishtory export ` +randomCmdUuid[:5]+ `
2022-11-12 00:12:23 +01:00
` , "UUID" , randomCmdUuid )
2022-09-17 20:49:31 +02:00
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
2022-11-12 00:12:23 +01:00
// Compare the rest of the hishtory export
2022-11-13 01:39:21 +01:00
out = tester . RunInteractiveShell ( t , ` hishtory export -pipefail -/tmp/client - ` + randomCmdUuid [ : 5 ] )
2022-11-12 00:12:23 +01:00
if out != "" {
t . Fatalf ( "expected hishtory export to be empty, was=%v" , out )
2022-09-17 20:49:31 +02:00
}
}
2022-09-20 07:49:48 +02:00
2022-11-03 21:16:45 +01:00
func testLocalRedaction ( t * testing . T , tester shellTester , onlineStatus OnlineStatus ) {
2022-09-20 07:49:48 +02:00
// Setup
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2022-11-16 08:20:19 +01:00
installWithOnlineStatus ( t , tester , onlineStatus )
2022-11-03 21:16:45 +01:00
assertOnlineStatus ( t , onlineStatus )
2022-09-20 07:49:48 +02:00
// Record some commands
randomCmdUuid := uuid . Must ( uuid . NewRandom ( ) ) . String ( )
randomCmd := fmt . Sprintf ( ` echo % v - foo
echo % v - bas
echo foo
ls / tmp ` , randomCmdUuid , randomCmdUuid )
tester . RunInteractiveShell ( t , randomCmd )
// Check that the previously recorded commands are in hishtory
2022-11-12 00:12:23 +01:00
out := tester . RunInteractiveShell ( t , ` hishtory export -pipefail ` )
2022-09-20 07:49:48 +02:00
expectedOutput := fmt . Sprintf ( "echo %s-foo\necho %s-bas\necho foo\nls /tmp\n" , randomCmdUuid , randomCmdUuid )
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
// Redact foo
2022-11-15 05:31:20 +01:00
out = tester . RunInteractiveShell ( t , ` HISHTORY_REDACT_FORCE=1 hishtory redact foo ` )
2023-11-12 16:56:55 +01:00
if out != "Permanently deleting 3 entries\n" {
2022-09-20 07:49:48 +02:00
t . Fatalf ( "hishtory redact gave unexpected output=%#v" , out )
}
// Check that the commands are redacted
out = tester . RunInteractiveShell ( t , ` hishtory export | grep -v pipefail ` )
2022-11-15 05:31:20 +01:00
expectedOutput = fmt . Sprintf ( "echo %s-bas\nls /tmp\nHISHTORY_REDACT_FORCE=1 hishtory redact foo\n" , randomCmdUuid )
2022-09-20 07:49:48 +02:00
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
// Redact s
2022-11-15 05:31:20 +01:00
out = tester . RunInteractiveShell ( t , ` HISHTORY_REDACT_FORCE=1 hishtory redact s ` )
2022-11-13 02:27:24 +01:00
if out != "Permanently deleting 10 entries\n" && out != "Permanently deleting 11 entries\n" {
2022-09-20 07:49:48 +02:00
t . Fatalf ( "hishtory redact gave unexpected output=%#v" , out )
}
// Check that the commands are redacted
out = tester . RunInteractiveShell ( t , ` hishtory export | grep -v pipefail ` )
2022-11-15 05:31:20 +01:00
expectedOutput = "HISHTORY_REDACT_FORCE=1 hishtory redact s\n"
2022-09-20 07:49:48 +02:00
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
2022-09-23 03:22:06 +02:00
// Record another command
tester . RunInteractiveShell ( t , ` echo hello ` )
out = tester . RunInteractiveShell ( t , ` hishtory export | grep -v pipefail ` )
2022-11-15 05:31:20 +01:00
expectedOutput = "HISHTORY_REDACT_FORCE=1 hishtory redact s\necho hello\n"
2022-09-23 03:22:06 +02:00
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
2022-11-15 05:31:20 +01:00
// Redact it without HISHTORY_REDACT_FORCE
2022-09-23 03:22:06 +02:00
out , err := tester . RunInteractiveShellRelaxed ( t , ` yes | hishtory redact hello ` )
2023-09-30 03:21:23 +02:00
require . NoError ( t , err )
2023-11-19 10:44:48 +01:00
require . Regexp ( t , regexp . MustCompile ( ` This will permanently delete (1|2) entries, are you sure\? \[y/N] ` ) , out )
2022-09-23 03:22:06 +02:00
// And check it was redacted
out = tester . RunInteractiveShell ( t , ` hishtory export | grep -v pipefail ` )
2022-11-15 05:31:20 +01:00
expectedOutput = "HISHTORY_REDACT_FORCE=1 hishtory redact s\nyes | hishtory redact hello\n"
2022-09-23 03:22:06 +02:00
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
2022-09-20 07:49:48 +02:00
}
func testRemoteRedaction ( t * testing . T , tester shellTester ) {
// Setup
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2022-09-20 07:49:48 +02:00
// Install hishtory client 1
userSecret := installHishtory ( t , tester , "" )
// Record some commands
randomCmdUuid := uuid . Must ( uuid . NewRandom ( ) ) . String ( )
randomCmd := fmt . Sprintf ( ` echo % v - foo
2022-09-21 06:38:04 +02:00
echo % v - bas
echo foo
ls / tmp ` , randomCmdUuid , randomCmdUuid )
2022-09-20 07:49:48 +02:00
tester . RunInteractiveShell ( t , randomCmd )
// Check that the previously recorded commands are in hishtory
out := tester . RunInteractiveShell ( t , ` hishtory export | grep -v pipefail ` )
expectedOutput := fmt . Sprintf ( "echo %s-foo\necho %s-bas\necho foo\nls /tmp\n" , randomCmdUuid , randomCmdUuid )
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
// Install hishtory client 2
2022-10-28 06:53:47 +02:00
restoreInstall1 := testutils . BackupAndRestoreWithId ( t , "-1" )
2022-09-20 07:49:48 +02:00
installHishtory ( t , tester , userSecret )
// And confirm that it has the commands too
2022-11-12 00:12:23 +01:00
out = tester . RunInteractiveShell ( t , ` hishtory export -pipefail ` )
2022-09-20 07:49:48 +02:00
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
// Restore the first client, and redact some commands
2022-10-28 06:53:47 +02:00
restoreInstall2 := testutils . BackupAndRestoreWithId ( t , "-2" )
2022-09-20 07:49:48 +02:00
restoreInstall1 ( )
2022-11-15 05:31:20 +01:00
out = tester . RunInteractiveShell ( t , ` HISHTORY_REDACT_FORCE=1 hishtory redact ` + randomCmdUuid )
2023-11-12 17:08:31 +01:00
if out != "Permanently deleting 3 entries\n" {
2022-09-20 07:49:48 +02:00
t . Fatalf ( "hishtory redact gave unexpected output=%#v" , out )
}
// Confirm that client1 doesn't have the commands
out = tester . RunInteractiveShell ( t , ` hishtory export | grep -v pipefail ` )
2022-11-15 05:31:20 +01:00
expectedOutput = fmt . Sprintf ( "echo foo\nls /tmp\nHISHTORY_REDACT_FORCE=1 hishtory redact %s\n" , randomCmdUuid )
2022-09-20 07:49:48 +02:00
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
// Swap back to the second client and then confirm it processed the deletion request
restoreInstall2 ( )
out = tester . RunInteractiveShell ( t , ` hishtory export | grep -v pipefail ` )
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
}
2022-09-22 05:30:50 +02:00
2022-10-16 21:43:16 +02:00
func testConfigGetSet ( t * testing . T , tester shellTester ) {
// Setup
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2022-10-16 21:43:16 +02:00
installHishtory ( t , tester , "" )
2022-10-28 08:07:00 +02:00
// Config-get and set for enable-control-r
2022-10-16 21:43:16 +02:00
out := tester . RunInteractiveShell ( t , ` hishtory config-get enable-control-r ` )
2022-11-14 03:26:23 +01:00
if out != "true\n" {
2022-10-24 01:51:39 +02:00
t . Fatalf ( "unexpected config-get output: %#v" , out )
}
tester . RunInteractiveShell ( t , ` hishtory config-set enable-control-r false ` )
out = tester . RunInteractiveShell ( t , ` hishtory config-get enable-control-r ` )
2022-11-14 03:26:23 +01:00
if out != "false\n" {
2022-10-16 21:43:16 +02:00
t . Fatalf ( "unexpected config-get output: %#v" , out )
}
tester . RunInteractiveShell ( t , ` hishtory config-set enable-control-r true ` )
out = tester . RunInteractiveShell ( t , ` hishtory config-get enable-control-r ` )
2022-11-14 03:26:23 +01:00
if out != "true\n" {
2022-10-16 21:43:16 +02:00
t . Fatalf ( "unexpected config-get output: %#v" , out )
}
2022-10-28 08:07:00 +02:00
// config for displayed-columns
out = tester . RunInteractiveShell ( t , ` hishtory config-get displayed-columns ` )
if out != "Hostname CWD Timestamp Runtime \"Exit Code\" Command \n" {
t . Fatalf ( "unexpected config-get output: %#v" , out )
}
tester . RunInteractiveShell ( t , ` hishtory config-set displayed-columns Hostname Command 'Exit Code' ` )
out = tester . RunInteractiveShell ( t , ` hishtory config-get displayed-columns ` )
if out != "Hostname Command \"Exit Code\" \n" {
t . Fatalf ( "unexpected config-get output: %#v" , out )
}
tester . RunInteractiveShell ( t , ` hishtory config-add displayed-columns Timestamp ` )
out = tester . RunInteractiveShell ( t , ` hishtory config-get displayed-columns ` )
if out != "Hostname Command \"Exit Code\" Timestamp \n" {
t . Fatalf ( "unexpected config-get output: %#v" , out )
}
tester . RunInteractiveShell ( t , ` hishtory config-delete displayed-columns Hostname ` )
out = tester . RunInteractiveShell ( t , ` hishtory config-get displayed-columns ` )
if out != "Command \"Exit Code\" Timestamp \n" {
t . Fatalf ( "unexpected config-get output: %#v" , out )
}
tester . RunInteractiveShell ( t , ` hishtory config-add displayed-columns foobar ` )
out = tester . RunInteractiveShell ( t , ` hishtory config-get displayed-columns ` )
if out != "Command \"Exit Code\" Timestamp foobar \n" {
t . Fatalf ( "unexpected config-get output: %#v" , out )
}
2024-03-27 06:13:57 +01:00
// For OpenAI endpoints
out = tester . RunInteractiveShell ( t , ` hishtory config-get ai-completion-endpoint ` )
if out != "https://api.openai.com/v1/chat/completions\n" {
t . Fatalf ( "unexpected config-get output: %#v" , out )
}
tester . RunInteractiveShell ( t , ` hishtory config-set ai-completion-endpoint https://example.com/foo/bar ` )
out = tester . RunInteractiveShell ( t , ` hishtory config-get ai-completion-endpoint ` )
if out != "https://example.com/foo/bar\n" {
t . Fatalf ( "unexpected config-get output: %#v" , out )
}
2022-10-24 01:51:39 +02:00
}
2022-10-16 21:43:16 +02:00
2023-08-30 08:23:21 +02:00
func clearControlRSearchFromConfig ( t testing . TB ) {
2022-10-24 01:51:39 +02:00
configContents , err := hctx . GetConfigContents ( )
2023-09-30 03:21:23 +02:00
require . NoError ( t , err )
2022-10-24 01:51:39 +02:00
configContents = [ ] byte ( strings . ReplaceAll ( string ( configContents ) , "enable_control_r_search" , "something-else" ) )
homedir , err := os . UserHomeDir ( )
2023-09-30 03:21:23 +02:00
require . NoError ( t , err )
2022-12-17 07:22:57 +01:00
err = os . WriteFile ( path . Join ( homedir , data . GetHishtoryPath ( ) , data . CONFIG_PATH ) , configContents , 0 o644 )
2023-09-30 03:21:23 +02:00
require . NoError ( t , err )
2022-10-24 01:51:39 +02:00
}
2022-10-24 04:29:29 +02:00
func testHandleUpgradedFeatures ( t * testing . T , tester shellTester ) {
2022-10-24 01:51:39 +02:00
// Setup
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2022-10-24 01:51:39 +02:00
installHishtory ( t , tester , "" )
2022-10-24 04:29:29 +02:00
// Install, and there is no prompt since the config already mentions control-r
_ , err := tester . RunInteractiveShellRelaxed ( t , ` /tmp/client install ` )
2023-09-30 03:21:23 +02:00
require . NoError ( t , err )
2022-10-24 04:29:29 +02:00
_ , err = tester . RunInteractiveShellRelaxed ( t , ` hishtory disable ` )
2023-09-30 03:21:23 +02:00
require . NoError ( t , err )
2022-10-24 01:51:39 +02:00
// Ensure that the config doesn't mention control-r
clearControlRSearchFromConfig ( t )
// And check that hishtory says it is false by default
out := tester . RunInteractiveShell ( t , ` hishtory config-get enable-control-r ` )
2022-11-15 05:55:10 +01:00
if out != "false\n" {
2022-10-24 01:51:39 +02:00
t . Fatalf ( "unexpected config-get output: %#v" , out )
}
2022-10-24 04:29:29 +02:00
// And install again, this time it will get set to true by default
2022-10-24 01:51:39 +02:00
clearControlRSearchFromConfig ( t )
2022-10-24 04:29:29 +02:00
tester . RunInteractiveShell ( t , ` /tmp/client install ` )
2022-10-24 01:51:39 +02:00
// Now it should be enabled
out = tester . RunInteractiveShell ( t , ` hishtory config-get enable-control-r ` )
2022-11-15 05:55:10 +01:00
if out != "true\n" {
2022-10-24 01:51:39 +02:00
t . Fatalf ( "unexpected config-get output: %#v" , out )
}
2022-10-16 21:43:16 +02:00
}
2022-10-19 06:00:30 +02:00
func TestFish ( t * testing . T ) {
// Setup
2024-02-11 20:54:27 +01:00
markTestForSharding ( t , 5 )
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2022-10-22 08:16:27 +02:00
tester := bashTester { }
2022-10-19 06:00:30 +02:00
installHishtory ( t , tester , "" )
// Test recording in fish
2023-09-30 03:21:23 +02:00
require . NoError ( t , os . Chdir ( "/" ) )
2022-10-31 02:01:53 +01:00
out := captureTerminalOutputWithShellName ( t , tester , "fish" , [ ] string {
"echo SPACE foo ENTER" ,
"ENTER" ,
"SPACE echo SPACE baz ENTER" ,
"echo SPACE bar ENTER" ,
2022-11-01 18:37:01 +01:00
"echo SPACE '\"foo\"' ENTER" ,
2022-10-31 02:01:53 +01:00
"SPACE echo SPACE foobar ENTER" ,
2022-11-01 20:00:40 +01:00
"ls SPACE /tmp/ SPACE '&' ENTER" ,
2022-10-31 02:01:53 +01:00
} )
2023-10-21 17:59:27 +02:00
require . Contains ( t , out , "Welcome to fish, the friendly interactive shell" )
require . Contains ( t , out , "\nfoo\n" )
require . Contains ( t , out , "\nbar\n" )
require . Contains ( t , out , "\nbaz\n" )
require . Contains ( t , out , "\nfoobar\n" )
// And test that fish exits properly, for #117
out = captureTerminalOutputWithShellName ( t , tester , "bash" , [ ] string {
"fish ENTER" ,
"echo SPACE foo ENTER" ,
"exit ENTER" ,
} )
require . Contains ( t , out , "Welcome to fish, the friendly interactive shell" )
require . Contains ( t , out , "\nfoo\n" )
require . NotContains ( t , out , "There are still jobs active" )
require . NotContains ( t , out , "A second attempt to exit will terminate them." )
2023-10-21 22:44:08 +02:00
if runtime . GOOS == "darwin" {
require . Contains ( t , out , "exit\nbash" )
} else {
require . Contains ( t , out , "exit\nrunner@ghaction-runner-hostname:/$" )
}
2022-10-19 06:00:30 +02:00
2022-10-31 02:40:25 +01:00
// Check export
2022-10-19 06:00:30 +02:00
out = tester . RunInteractiveShell ( t , ` hishtory export | grep -v pipefail | grep -v ps ` )
2023-11-12 16:56:55 +01:00
expectedOutput := "echo foo\necho bar\necho \"foo\"\nls /tmp/ &\nfish\necho foo\nexit\n"
2022-10-19 06:00:30 +02:00
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
2022-10-31 02:40:25 +01:00
// Check a table to see some other metadata
tester . RunInteractiveShell ( t , ` hishtory config-set displayed-columns CWD Hostname 'Exit Code' Command ` )
out = hishtoryQuery ( t , tester , "-pipefail" )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "TestFish-table" )
2022-10-28 06:20:33 +02:00
}
2023-10-26 05:44:26 +02:00
func setupTestTui ( t testing . TB , onlineStatus OnlineStatus ) ( shellTester , string , * gorm . DB ) {
2022-10-21 09:16:25 +02:00
tester := zshTester { }
2023-10-26 05:44:26 +02:00
userSecret := installWithOnlineStatus ( t , tester , onlineStatus )
assertOnlineStatus ( t , onlineStatus )
2022-10-18 06:58:56 +02:00
// Disable recording so that all our testing commands don't get recorded
_ , _ = tester . RunInteractiveShellRelaxed ( t , ` hishtory disable ` )
// Insert a couple hishtory entries
db := hctx . GetDb ( hctx . MakeContext ( ) )
2023-09-05 03:13:22 +02:00
e1 := testutils . MakeFakeHistoryEntry ( "ls ~/" )
2023-09-30 03:21:23 +02:00
require . NoError ( t , db . Create ( e1 ) . Error )
2023-10-26 05:44:26 +02:00
if onlineStatus == Online {
manuallySubmitHistoryEntry ( t , userSecret , e1 )
}
2023-09-05 03:13:22 +02:00
e2 := testutils . MakeFakeHistoryEntry ( "echo 'aaaaaa bbbb'" )
2023-09-30 03:21:23 +02:00
require . NoError ( t , db . Create ( e2 ) . Error )
2023-10-26 05:44:26 +02:00
if onlineStatus == Online {
manuallySubmitHistoryEntry ( t , userSecret , e2 )
}
2023-09-02 00:42:02 +02:00
return tester , userSecret , db
}
2022-10-18 06:58:56 +02:00
2023-10-22 00:41:32 +02:00
func testTui_resize ( t * testing . T ) {
2023-09-02 00:42:02 +02:00
// Setup
defer testutils . BackupAndRestore ( t ) ( )
2023-10-26 05:44:26 +02:00
tester , userSecret , _ := setupTestTui ( t , Online )
2022-11-27 17:54:34 +01:00
2023-09-02 00:42:02 +02:00
// Check the output when the size is smaller
out := captureTerminalOutputWithShellNameAndDimensions ( t , tester , tester . ShellName ( ) , 100 , 20 , [ ] TmuxCommand {
{ Keys : "hishtory SPACE tquery ENTER" } ,
2022-11-27 17:54:34 +01:00
} )
2023-11-12 09:40:24 +01:00
out = stripTuiCommandPrefix ( t , out )
2023-09-02 00:42:02 +02:00
testutils . CompareGoldens ( t , out , "TestTui-SmallTerminal" )
2022-11-27 17:54:34 +01:00
2023-12-04 06:56:45 +01:00
// Check the output when the size is tiny
out = captureTerminalOutputWithShellNameAndDimensions ( t , tester , tester . ShellName ( ) , 100 , 15 , [ ] TmuxCommand {
{ Keys : "hishtory SPACE tquery ENTER" } ,
} )
out = stripTuiCommandPrefix ( t , out )
testutils . CompareGoldens ( t , out , "TestTui-TinyTerminal" )
// Check the output when the size is tiny and the help page is open
out = captureTerminalOutputWithShellNameAndDimensions ( t , tester , tester . ShellName ( ) , 100 , 15 , [ ] TmuxCommand {
{ Keys : "hishtory SPACE tquery ENTER" } ,
{ Keys : "C-h" } ,
} )
out = stripTuiCommandPrefix ( t , out )
testutils . CompareGoldens ( t , out , "TestTui-TinyTerminalHelp" )
2023-12-07 01:12:41 +01:00
// Check the output when the size is extra tiny
out = captureTerminalOutputWithShellNameAndDimensions ( t , tester , tester . ShellName ( ) , 100 , 11 , [ ] TmuxCommand {
{ Keys : "hishtory SPACE tquery ENTER" } ,
} )
testutils . CompareGoldens ( t , out , "TestTui-TiniestTerminal" )
// Check the output when the size is tiny and the user tries to open the help page, which doesn't work
out = captureTerminalOutputWithShellNameAndDimensions ( t , tester , tester . ShellName ( ) , 100 , 11 , [ ] TmuxCommand {
{ Keys : "hishtory SPACE tquery ENTER" } ,
{ Keys : "C-h" } ,
} )
testutils . CompareGoldens ( t , out , "TestTui-TiniestTerminal" )
2023-09-02 00:42:02 +02:00
// Check that it resizes after the terminal size is adjusted
manuallySubmitHistoryEntry ( t , userSecret , testutils . MakeFakeHistoryEntry ( "echo 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc'" ) )
out = captureTerminalOutputWithShellNameAndDimensions ( t , tester , tester . ShellName ( ) , 100 , 20 , [ ] TmuxCommand {
{ Keys : "hishtory SPACE tquery ENTER" } ,
{ ResizeX : 300 , ResizeY : 100 } ,
2023-02-20 07:08:38 +01:00
} )
2023-11-12 09:40:24 +01:00
out = stripTuiCommandPrefix ( t , out )
2023-09-02 00:42:02 +02:00
testutils . CompareGoldens ( t , out , "TestTui-Resize" )
2023-02-20 07:08:38 +01:00
2023-09-02 00:42:02 +02:00
// Check that the cursor position is maintained after it is resized
2022-12-18 09:34:25 +01:00
out = captureTerminalOutputWithShellNameAndDimensions ( t , tester , tester . ShellName ( ) , 100 , 20 , [ ] TmuxCommand {
2022-12-18 10:01:05 +01:00
{ Keys : "hishtory SPACE tquery ENTER" } ,
2023-09-02 00:42:02 +02:00
{ Keys : "Down" } ,
2023-09-09 20:50:07 +02:00
{ ResizeX : 300 , ResizeY : 100 , ExtraDelay : 1.0 } ,
2023-09-02 00:42:02 +02:00
{ Keys : "Enter" } ,
2022-11-03 02:50:27 +01:00
} )
2023-09-02 00:42:02 +02:00
require . Contains ( t , out , "\necho 'aaaaaa bbbb'\n" )
2023-09-15 06:14:16 +02:00
// Check that it supports a very long search query
out = captureTerminalOutputWithShellNameAndDimensions ( t , tester , tester . ShellName ( ) , 100 , 20 , [ ] TmuxCommand {
{ Keys : "hishtory SPACE tquery ENTER" } ,
{ Keys : "1234567890qwertyuip1234567890qwertyuip1234567890qwertyuip1234567890qwertyuip1234567890qwertyuip" } ,
} )
2023-11-12 09:40:24 +01:00
out = stripTuiCommandPrefix ( t , out )
2023-09-15 06:14:16 +02:00
testutils . CompareGoldens ( t , out , "TestTui-LongQuery" )
2023-09-02 00:42:02 +02:00
}
2023-10-22 00:41:32 +02:00
func testTui_scroll ( t * testing . T ) {
2023-09-02 00:42:02 +02:00
// Setup
defer testutils . BackupAndRestore ( t ) ( )
2023-10-26 05:44:26 +02:00
tester , userSecret , _ := setupTestTui ( t , Online )
2022-12-17 21:14:28 +01:00
// Check that we can use left arrow keys to scroll
2023-09-02 00:42:02 +02:00
out := captureTerminalOutput ( t , tester , [ ] string {
2022-12-17 21:14:28 +01:00
"hishtory SPACE tquery ENTER" ,
"s" ,
"Left" ,
"l" ,
} )
2023-11-12 09:40:24 +01:00
out = stripTuiCommandPrefix ( t , out )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "TestTui-LeftScroll" )
2022-12-18 09:27:29 +01:00
2023-09-02 00:42:02 +02:00
// Test horizontal scrolling by one to the right
2023-10-16 06:45:20 +02:00
veryLongEntry := testutils . MakeFakeHistoryEntry ( "echo '1234567890qwertyuiopasdfghjklzxxcvbnm0987654321_0_1234567890qwertyuiopasdfghjklzxxcvbnm0987654321_1_1234567890qwertyuiopasdfghjklzxxcvbnm0987654321_2_1234567890qwertyuiopasdfghjklzxxcvbnm0987654321'" )
manuallySubmitHistoryEntry ( t , userSecret , veryLongEntry )
require . NoError ( t , hctx . GetDb ( hctx . MakeContext ( ) ) . Create ( veryLongEntry ) . Error )
2022-12-18 09:27:29 +01:00
out = captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
2023-09-02 00:42:02 +02:00
"S-Left S-Right S-Right S-Left" ,
2022-12-18 09:27:29 +01:00
} )
2023-11-12 09:40:24 +01:00
out = stripTuiCommandPrefix ( t , out )
2023-09-02 00:42:02 +02:00
testutils . CompareGoldens ( t , out , "TestTui-RightScroll" )
2022-12-18 10:01:05 +01:00
2023-09-02 00:42:02 +02:00
// Test horizontal scrolling by two
out = captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
"S-Right S-Right" ,
2022-12-18 10:01:05 +01:00
} )
2023-11-12 09:40:24 +01:00
out = stripTuiCommandPrefix ( t , out )
2023-09-02 00:42:02 +02:00
testutils . CompareGoldens ( t , out , "TestTui-RightScrollTwo" )
2022-12-19 07:02:29 +01:00
2024-04-06 01:21:37 +02:00
// Set up to test horizontal scrolling for other columns
veryLongDirEntry := testutils . MakeFakeHistoryEntry ( "echo 'short'" )
veryLongDirEntry . CurrentWorkingDirectory = "/tmp/1234567890qwertyuiopasdfghjklzxxcvbnm0987654321_0_1234567890qwertyuiopasdfghjklzxxcvbnm0987654321_1_1234567890qwertyuiopasdfghjklzxxcvbnm0987654321_2_1234567890qwertyuiopasdfghjklzxxcvbnm0987654321"
manuallySubmitHistoryEntry ( t , userSecret , veryLongDirEntry )
require . NoError ( t , hctx . GetDb ( hctx . MakeContext ( ) ) . Create ( veryLongDirEntry ) . Error )
// Test displaying long other columns
out = captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
} )
out = stripTuiCommandPrefix ( t , out )
testutils . CompareGoldens ( t , out , "TestTui-LongDirectoryName" )
// Test horizontal scrolling for other columns
out = captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
"S-Right S-Right" ,
} )
out = stripTuiCommandPrefix ( t , out )
testutils . CompareGoldens ( t , out , "TestTui-RightScrollDirectoryTwo" )
2024-03-26 19:40:25 +01:00
2023-09-02 00:42:02 +02:00
// Assert there are no leaked connections
assertNoLeakedConnections ( t )
}
2024-01-08 06:06:22 +01:00
func testTui_defaultFilter ( t * testing . T ) {
// Setup
defer testutils . BackupAndRestore ( t ) ( )
tester , userSecret , _ := setupTestTui ( t , Online )
db := hctx . GetDb ( hctx . MakeContext ( ) )
e1 := testutils . MakeFakeHistoryEntry ( "exit 0" )
e1 . ExitCode = 0
require . NoError ( t , db . Create ( e1 ) . Error )
manuallySubmitHistoryEntry ( t , userSecret , e1 )
e2 := testutils . MakeFakeHistoryEntry ( "exit 1" )
e2 . ExitCode = 1
require . NoError ( t , db . Create ( e2 ) . Error )
manuallySubmitHistoryEntry ( t , userSecret , e2 )
// Configure a default filter
require . Equal ( t , "\"\"" , strings . TrimSpace ( tester . RunInteractiveShell ( t , ` hishtory config-get default-filter ` ) ) )
tester . RunInteractiveShell ( t , ` hishtory config-set default-filter "exit_code:0" ` )
require . Equal ( t , "\"exit_code:0\"" , strings . TrimSpace ( tester . RunInteractiveShell ( t , ` hishtory config-get default-filter ` ) ) )
// Run a search query with no additional query
out := stripTuiCommandPrefix ( t , captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
} ) )
testutils . CompareGoldens ( t , out , "TestTui-DefaultFilter-Enabled" )
// Run a search query with an additional query
out = stripTuiCommandPrefix ( t , captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
"exit" ,
} ) )
testutils . CompareGoldens ( t , out , "TestTui-DefaultFilter-EnabledAdditionalQuery" )
// Run a search query and delete the default filter
out = stripTuiCommandPrefix ( t , captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
"BSpace" ,
} ) )
testutils . CompareGoldens ( t , out , "TestTui-DefaultFilter-Deleted" )
// Run a search query, type something, and then delete the default filter
out = stripTuiCommandPrefix ( t , captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
2024-04-21 20:44:18 +02:00
"exit" ,
"Left Left Left Left Left" ,
"BSpace BSpace" ,
2024-01-08 06:06:22 +01:00
} ) )
testutils . CompareGoldens ( t , out , "TestTui-DefaultFilter-DeletedWithText" )
}
2023-10-22 00:41:32 +02:00
func testTui_color ( t * testing . T ) {
2023-09-30 03:21:23 +02:00
// Setup
defer testutils . BackupAndRestore ( t ) ( )
2023-10-26 05:44:26 +02:00
tester , _ , _ := setupTestTui ( t , Online )
2023-10-12 03:18:56 +02:00
tester . RunInteractiveShell ( t , ` hishtory config-set highlight-matches false ` )
2023-09-30 03:21:23 +02:00
// Capture the TUI with full colored output, note that this golden will be harder to undersand
// from inspection and primarily servers to detect unintended changes in hishtory's output.
out := captureTerminalOutputComplex ( t , TmuxCaptureConfig { tester : tester , complexCommands : [ ] TmuxCommand { { Keys : "hishtory SPACE tquery ENTER" } } , includeEscapeSequences : true } )
2023-11-12 09:40:24 +01:00
out = stripTuiCommandPrefix ( t , out )
2024-03-28 16:03:04 +01:00
testutils . CompareGoldens ( t , out , "TestTui-ColoredOutput-" + runtime . GOOS + "-" + testutils . GetOsVersion ( t ) )
2023-09-30 03:21:23 +02:00
// And the same once a search query has been typed in
out = captureTerminalOutputComplex ( t , TmuxCaptureConfig { tester : tester , complexCommands : [ ] TmuxCommand { { Keys : "hishtory SPACE tquery ENTER" } , { Keys : "ech" } } , includeEscapeSequences : true } )
2023-11-12 09:40:24 +01:00
out = stripTuiCommandPrefix ( t , out )
2024-03-28 16:03:04 +01:00
testutils . CompareGoldens ( t , out , "TestTui-ColoredOutputWithSearch-" + runtime . GOOS + "-" + testutils . GetOsVersion ( t ) )
2023-09-30 03:21:23 +02:00
2023-10-12 03:18:56 +02:00
// And one more time with highlight-matches
tester . RunInteractiveShell ( t , ` hishtory config-set highlight-matches true ` )
require . Equal ( t , "true" , strings . TrimSpace ( tester . RunInteractiveShell ( t , ` hishtory config-get highlight-matches ` ) ) )
2023-09-30 03:21:23 +02:00
out = captureTerminalOutputComplex ( t , TmuxCaptureConfig { tester : tester , complexCommands : [ ] TmuxCommand { { Keys : "hishtory SPACE tquery ENTER" } , { Keys : "ech" } } , includeEscapeSequences : true } )
2023-11-12 09:40:24 +01:00
out = stripTuiCommandPrefix ( t , out )
2024-03-28 16:03:04 +01:00
testutils . CompareGoldens ( t , out , "TestTui-ColoredOutputWithSearch-Highlight-" + runtime . GOOS + "-" + testutils . GetOsVersion ( t ) )
2023-12-19 05:32:11 +01:00
// And one more time with customized colors
testutils . CompareGoldens ( t , tester . RunInteractiveShell ( t , ` hishtory config-get color-scheme ` ) , "TestTui-DefaultColorScheme" )
tester . RunInteractiveShell ( t , ` hishtory config-set color-scheme selected-text #45f542 ` )
tester . RunInteractiveShell ( t , ` hishtory config-set color-scheme selected-background #4842f5 ` )
tester . RunInteractiveShell ( t , ` hishtory config-set color-scheme border-color #f54272 ` )
out = captureTerminalOutputComplex ( t , TmuxCaptureConfig { tester : tester , complexCommands : [ ] TmuxCommand { { Keys : "hishtory SPACE tquery ENTER" } , { Keys : "ech" } } , includeEscapeSequences : true } )
out = stripTuiCommandPrefix ( t , out )
2024-03-28 16:03:04 +01:00
testutils . CompareGoldens ( t , out , "TestTui-ColoredOutputWithCustomColorScheme-" + runtime . GOOS + "-" + testutils . GetOsVersion ( t ) )
2024-01-08 06:06:22 +01:00
// And one more time with a default filter
require . Equal ( t , "\"\"" , strings . TrimSpace ( tester . RunInteractiveShell ( t , ` hishtory config-get default-filter ` ) ) )
tester . RunInteractiveShell ( t , ` hishtory config-set default-filter "exit_code:0" ` )
require . Equal ( t , "\"exit_code:0\"" , strings . TrimSpace ( tester . RunInteractiveShell ( t , ` hishtory config-get default-filter ` ) ) )
out = captureTerminalOutputComplex ( t , TmuxCaptureConfig { tester : tester , complexCommands : [ ] TmuxCommand { { Keys : "hishtory SPACE tquery ENTER" } , { Keys : "ech" } } , includeEscapeSequences : true } )
out = stripTuiCommandPrefix ( t , out )
2024-03-28 16:03:04 +01:00
testutils . CompareGoldens ( t , out , "TestTui-ColoredOutputWithDefaultFilter-" + runtime . GOOS + "-" + testutils . GetOsVersion ( t ) )
2023-09-30 03:21:23 +02:00
}
2023-10-22 00:41:32 +02:00
func testTui_delete ( t * testing . T ) {
2023-09-02 00:42:02 +02:00
// Setup
defer testutils . BackupAndRestore ( t ) ( )
2023-10-26 05:44:26 +02:00
tester , userSecret , _ := setupTestTui ( t , Online )
2023-09-02 00:42:02 +02:00
manuallySubmitHistoryEntry ( t , userSecret , testutils . MakeFakeHistoryEntry ( "echo 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc'" ) )
2023-08-30 03:59:20 +02:00
2022-12-19 07:02:29 +01:00
// Check that we can delete an entry
2023-09-09 04:44:36 +02:00
out := captureTerminalOutputWithComplexCommands ( t , tester , [ ] TmuxCommand {
{ Keys : "hishtory SPACE tquery ENTER" } ,
// ExtraDelay so that the search query finishes before we hit delete
{ Keys : "aaaaaa" , ExtraDelay : 1.0 } ,
{ Keys : "C-K" } ,
2022-12-19 07:02:29 +01:00
} )
2023-11-12 09:40:24 +01:00
out = stripTuiCommandPrefix ( t , out )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "TestTui-Delete" )
2022-12-19 07:02:29 +01:00
// And that it stays deleted
out = captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
} )
2023-11-12 09:40:24 +01:00
out = stripTuiCommandPrefix ( t , out )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "TestTui-DeleteStill" )
2022-12-19 07:02:29 +01:00
// And that we can then delete another entry
out = captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
"C-K" ,
} )
2023-11-12 09:40:24 +01:00
out = stripTuiCommandPrefix ( t , out )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "TestTui-DeleteAgain" )
2022-12-19 07:02:29 +01:00
// And that it stays deleted
2023-09-23 19:58:48 +02:00
out = captureTerminalOutputWithComplexCommands ( t , tester , [ ] TmuxCommand {
2023-09-24 15:29:47 +02:00
{ Keys : "hishtory SPACE tquery ENTER" , ExtraDelay : 1.5 } ,
2022-12-19 07:02:29 +01:00
} )
2023-11-12 09:40:24 +01:00
out = stripTuiCommandPrefix ( t , out )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "TestTui-DeleteAgainStill" )
2022-12-19 07:02:29 +01:00
2023-09-02 00:42:02 +02:00
// Assert there are no leaked connections
assertNoLeakedConnections ( t )
}
2023-10-26 05:44:26 +02:00
func testTui_search ( t * testing . T , onlineStatus OnlineStatus ) {
2023-09-02 00:42:02 +02:00
// Setup
defer testutils . BackupAndRestore ( t ) ( )
2023-10-26 05:44:26 +02:00
tester , _ , _ := setupTestTui ( t , onlineStatus )
2023-09-02 00:42:02 +02:00
2023-09-08 07:18:16 +02:00
// Check hishtory export to confirm the right commands are in the initial set of history entries
out := tester . RunInteractiveShell ( t , ` hishtory export ` )
expected := "ls ~/\necho 'aaaaaa bbbb'\n"
if diff := cmp . Diff ( expected , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s" , diff )
}
2023-09-05 06:21:13 +02:00
2023-09-02 00:42:02 +02:00
// Check the output when there is a search
2023-10-25 07:47:36 +02:00
out = captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
"ls" ,
2023-02-12 08:09:40 +01:00
} )
2023-11-12 09:40:24 +01:00
out = stripTuiCommandPrefix ( t , out )
2023-09-02 00:42:02 +02:00
testutils . CompareGoldens ( t , out , "TestTui-Search" )
2023-02-12 08:09:40 +01:00
2023-09-02 00:42:02 +02:00
// Check the output when there is a selected result
2023-09-09 20:50:07 +02:00
out = captureTerminalOutputWithComplexCommands ( t , tester , [ ] TmuxCommand {
{ Keys : "hishtory SPACE tquery ENTER" } ,
// Extra delay to ensure that the search for 'ls' finishes before we select an entry
2023-10-25 07:47:36 +02:00
{ Keys : "ls" , ExtraDelay : 2.0 } ,
2023-09-09 20:50:07 +02:00
{ Keys : "ENTER" } ,
2023-09-02 00:42:02 +02:00
} )
2023-11-12 09:40:24 +01:00
out = strings . Split ( stripTuiCommandPrefix ( t , out ) , "\n" ) [ 0 ]
2023-09-08 07:18:16 +02:00
expected = ` ls ~/ `
2023-09-02 00:42:02 +02:00
if diff := cmp . Diff ( expected , out ) ; diff != "" {
2023-09-08 07:18:16 +02:00
t . Fatalf ( "hishtory tquery selection mismatch (-expected +got):\n%s" , diff )
2023-09-02 00:42:02 +02:00
}
// Check the output when the initial search is invalid
2023-09-08 19:09:01 +02:00
out = captureTerminalOutputWithComplexCommands ( t , tester , [ ] TmuxCommand {
2023-09-09 20:50:07 +02:00
// ExtraDelay to ensure that after searching for 'foo:' it gets the real results for an empty query
2023-09-30 03:21:23 +02:00
{ Keys : "hishtory SPACE tquery SPACE foo: ENTER" , ExtraDelay : 1.5 } ,
2023-10-25 07:47:36 +02:00
{ Keys : "ls" , ExtraDelay : 1.0 } ,
2023-02-14 05:24:20 +01:00
} )
2023-12-22 02:43:26 +01:00
out = stripRequiredPrefix ( t , out , "hishtory tquery foo:" )
2023-09-02 00:42:02 +02:00
testutils . CompareGoldens ( t , out , "TestTui-InitialInvalidSearch" )
// Check the output when the search is invalid
2023-09-08 18:53:39 +02:00
out = captureTerminalOutputWithComplexCommands ( t , tester , [ ] TmuxCommand {
2023-10-16 06:47:01 +02:00
{ Keys : "hishtory SPACE tquery ENTER" , ExtraDelay : 1.0 } ,
2023-09-09 20:50:07 +02:00
// ExtraDelay to ensure that the search for 'ls' finishes before we make it invalid by adding ':'
2023-10-25 07:47:36 +02:00
{ Keys : "ls" , ExtraDelay : 1.5 } ,
2023-09-09 20:50:07 +02:00
{ Keys : ":" } ,
2023-09-02 00:42:02 +02:00
} )
2023-11-12 09:40:24 +01:00
out = stripTuiCommandPrefix ( t , out )
2023-09-02 00:42:02 +02:00
testutils . CompareGoldens ( t , out , "TestTui-InvalidSearch" )
// Check the output when the search is invalid and then edited to become valid
out = captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
"ls: BSpace" ,
} )
2023-11-12 09:40:24 +01:00
out = stripTuiCommandPrefix ( t , out )
2023-09-02 00:42:02 +02:00
testutils . CompareGoldens ( t , out , "TestTui-InvalidSearchBecomesValid" )
2023-12-13 07:20:49 +01:00
// Record a couple commands that we can use to test for supporting quoted searches
db := hctx . GetDb ( hctx . MakeContext ( ) )
require . NoError ( t , db . Create ( testutils . MakeFakeHistoryEntry ( "for i in 1" ) ) . Error )
require . NoError ( t , db . Create ( testutils . MakeFakeHistoryEntry ( "for i in 2" ) ) . Error )
require . NoError ( t , db . Create ( testutils . MakeFakeHistoryEntry ( "i for in" ) ) . Error )
out = tester . RunInteractiveShell ( t , ` hishtory export ` )
testutils . CompareGoldens ( t , out , "TestTui-ExportWithAdditionalEntries" )
// Check the behavior when it is unquoted and fuzzy
out = stripTuiCommandPrefix ( t , captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
"for SPACE i SPACE in" ,
} ) )
testutils . CompareGoldens ( t , out , "TestTui-SearchUnquoted" )
// Check the behavior when it is quoted and exact
out = stripTuiCommandPrefix ( t , captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
"'\"'for SPACE i SPACE in'\"'" ,
} ) )
testutils . CompareGoldens ( t , out , "TestTui-SearchQuoted" )
// Check the behavior when it is backslashed
out = stripTuiCommandPrefix ( t , captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
"for\\\\ SPACE i\\\\ SPACE in" ,
} ) )
testutils . CompareGoldens ( t , out , "TestTui-SearchBackslash" )
2024-01-02 20:07:24 +01:00
2024-01-08 03:56:30 +01:00
// Add another entry for testing quoting a colon
require . NoError ( t , db . Create ( testutils . MakeFakeHistoryEntry ( "foo:bar" ) ) . Error )
out = tester . RunInteractiveShell ( t , ` hishtory export ` )
testutils . CompareGoldens ( t , out , "TestTui-ExportWithEvenMoreEntries" )
// And check that we can quote colons
out = stripTuiCommandPrefix ( t , captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
2024-04-21 03:47:30 +02:00
"foo" ,
":" ,
"bar" ,
2024-01-08 03:56:30 +01:00
} ) )
testutils . CompareGoldens ( t , out , "TestTui-SearchColonError" )
out = stripTuiCommandPrefix ( t , captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
"foo\\\\:bar" ,
} ) )
testutils . CompareGoldens ( t , out , "TestTui-SearchColonEscaped" )
out = stripTuiCommandPrefix ( t , captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
"'\"'foo:bar'\"'" ,
} ) )
testutils . CompareGoldens ( t , out , "TestTui-SearchColonDoubleQuoted" )
2023-09-02 01:28:41 +02:00
}
2023-10-26 05:44:26 +02:00
func testTui_general ( t * testing . T , onlineStatus OnlineStatus ) {
2023-09-02 01:28:41 +02:00
// Setup
defer testutils . BackupAndRestore ( t ) ( )
2023-10-26 05:44:26 +02:00
tester , _ , _ := setupTestTui ( t , onlineStatus )
2023-09-02 01:28:41 +02:00
// Check the initial output when there is no search
out := captureTerminalOutput ( t , tester , [ ] string { "hishtory SPACE tquery ENTER" } )
2023-11-12 09:40:24 +01:00
out = stripTuiCommandPrefix ( t , out )
2023-09-02 01:28:41 +02:00
testutils . CompareGoldens ( t , out , "TestTui-Initial" )
2023-09-02 00:42:02 +02:00
// Check that we can exit the TUI via pressing esc
out = captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
"Escape" ,
} )
require . NotContains ( t , out , "Search Query:" )
2023-12-29 16:31:56 +01:00
if testutils . IsGithubAction ( ) {
testutils . CompareGoldens ( t , out , "TestTui-Exit-" + runtime . GOOS )
2023-09-02 00:42:02 +02:00
}
2023-02-14 05:24:20 +01:00
2023-02-20 21:15:27 +01:00
// Test opening the help page
out = captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
"C-h" ,
} )
2023-11-12 09:40:24 +01:00
out = stripTuiCommandPrefix ( t , out )
2023-02-20 21:15:27 +01:00
testutils . CompareGoldens ( t , out , "TestTui-HelpPage" )
// Test closing the help page
out = captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
"C-h C-h" ,
} )
2023-11-12 09:40:24 +01:00
out = stripTuiCommandPrefix ( t , out )
2023-02-20 21:15:27 +01:00
testutils . CompareGoldens ( t , out , "TestTui-HelpPageClosed" )
2023-05-20 02:14:33 +02:00
// Test selecting and cd-ing
out = captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
"C-x" ,
} )
2023-11-12 09:40:24 +01:00
out = strings . Split ( stripTuiCommandPrefix ( t , out ) , "\n" ) [ 0 ]
2023-05-20 02:14:33 +02:00
testutils . CompareGoldens ( t , out , "TestTui-SelectAndCd" )
2024-02-19 21:26:44 +01:00
// Test jumping around the cursor via shortcuts
out = captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
2024-02-19 22:54:42 +01:00
"foo C-a AAA SPACE C-e SPACE ZZZ" ,
2024-02-19 21:26:44 +01:00
} )
out = strings . Split ( stripTuiCommandPrefix ( t , out ) , "\n" ) [ 0 ]
testutils . CompareGoldens ( t , out , "TestTui-JumpCursor" )
2023-08-29 04:27:29 +02:00
// Test the User column
tester . RunInteractiveShell ( t , ` hishtory config-add displayed-columns User ` )
out = captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
} )
2023-11-12 09:40:24 +01:00
out = stripTuiCommandPrefix ( t , out )
2023-08-29 04:27:29 +02:00
require . Contains ( t , out , " User" )
2023-09-02 00:42:02 +02:00
require . Contains ( t , out , " david " )
2023-08-29 04:27:29 +02:00
2022-12-19 07:02:29 +01:00
// Assert there are no leaked connections
assertNoLeakedConnections ( t )
2022-10-22 09:17:21 +02:00
}
2022-10-18 06:58:56 +02:00
2023-10-27 02:42:50 +02:00
func testTui_errors ( t * testing . T ) {
// Setup
defer testutils . BackupAndRestore ( t ) ( )
tester , _ , _ := setupTestTui ( t , Online )
// Check the output when the device is offline
os . Setenv ( "HISHTORY_SIMULATE_NETWORK_ERROR" , "1" )
out := captureTerminalOutput ( t , tester , [ ] string { "hishtory SPACE tquery ENTER" } )
os . Setenv ( "HISHTORY_SIMULATE_NETWORK_ERROR" , "" )
2023-11-12 09:40:24 +01:00
out = stripTuiCommandPrefix ( t , out )
2023-10-27 02:42:50 +02:00
testutils . CompareGoldens ( t , out , "TestTui-Offline" )
// Check the output when the device is offline AND there is an invalid search
os . Setenv ( "HISHTORY_SIMULATE_NETWORK_ERROR" , "1" )
2024-04-21 03:19:28 +02:00
out = captureTerminalOutput ( t , tester , [ ] string { "hishtory SPACE tquery ENTER" , "ls" , ":" } )
2023-10-27 02:42:50 +02:00
os . Setenv ( "HISHTORY_SIMULATE_NETWORK_ERROR" , "" )
2023-11-12 09:40:24 +01:00
out = stripTuiCommandPrefix ( t , out )
2023-10-27 02:42:50 +02:00
testutils . CompareGoldens ( t , out , "TestTui-OfflineInvalid" )
}
2023-11-12 05:59:45 +01:00
func testTui_ai ( t * testing . T ) {
// Setup
defer testutils . BackupAndRestore ( t ) ( )
2023-11-12 11:11:37 +01:00
defer testutils . BackupAndRestoreEnv ( "OPENAI_API_KEY" ) ( )
os . Setenv ( "OPENAI_API_KEY" , "" )
2023-11-12 05:59:45 +01:00
tester , _ , _ := setupTestTui ( t , Online )
2023-11-12 06:13:03 +01:00
req , err := json . Marshal (
ai . TestOnlyOverrideAiSuggestionRequest { Query : "myQuery" , Suggestions : [ ] string { "result 1" , "result 2" , "longer result 3" } } ,
)
require . NoError ( t , err )
_ , err = lib . ApiPost ( hctx . MakeContext ( ) , "/api/v1/ai-suggest-override" , "application/json" , req )
require . NoError ( t , err )
2023-11-12 05:59:45 +01:00
// Test running an AI query
2023-11-12 12:09:56 +01:00
require . Equal ( t , "true" , strings . TrimSpace ( tester . RunInteractiveShell ( t , ` hishtory config-get ai-completion ` ) ) )
2023-11-12 10:13:22 +01:00
out := captureTerminalOutputWithComplexCommands ( t , tester , [ ] TmuxCommand {
{ Keys : "hishtory SPACE tquery ENTER" } ,
// ExtraDelay since AI queries are debounced and thus slower
{ Keys : "'?myQuery'" , ExtraDelay : 1.0 } ,
2023-11-12 05:59:45 +01:00
} )
2023-11-12 09:40:24 +01:00
out = stripTuiCommandPrefix ( t , out )
2023-11-12 05:59:45 +01:00
testutils . CompareGoldens ( t , out , "TestTui-AiQuery" )
2023-11-12 12:09:56 +01:00
// Test that when it is disabled, no AI queries are run
tester . RunInteractiveShell ( t , ` hishtory config-set ai-completion false ` )
out = captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
"'?myQuery'" ,
} )
out = stripTuiCommandPrefix ( t , out )
testutils . CompareGoldens ( t , out , "TestTui-AiQuery-Disabled" )
2023-11-12 05:59:45 +01:00
}
2023-10-22 00:41:32 +02:00
func testControlR ( t * testing . T , tester shellTester , shellName string , onlineStatus OnlineStatus ) {
2022-10-21 09:14:28 +02:00
// Setup
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2022-11-16 08:20:19 +01:00
installWithOnlineStatus ( t , tester , onlineStatus )
2022-11-03 21:16:45 +01:00
assertOnlineStatus ( t , onlineStatus )
2022-10-21 09:14:28 +02:00
// Disable recording so that all our testing commands don't get recorded
_ , _ = tester . RunInteractiveShellRelaxed ( t , ` hishtory disable ` )
_ , _ = tester . RunInteractiveShellRelaxed ( t , ` hishtory config-set enable-control-r true ` )
2023-11-23 04:02:35 +01:00
tester . RunInteractiveShell ( t , ` HISHTORY_REDACT_FORCE=true hishtory redact set emo pipefail ` )
2022-10-21 09:14:28 +02:00
2023-11-19 10:13:20 +01:00
// Insert a few hishtory entries that we'll use for testing into an empty DB
2022-10-21 09:14:28 +02:00
db := hctx . GetDb ( hctx . MakeContext ( ) )
2023-11-19 10:34:36 +01:00
require . NoError ( t , db . Where ( "true" ) . Delete ( & data . HistoryEntry { } ) . Error )
2023-09-02 00:57:11 +02:00
e1 := testutils . MakeFakeHistoryEntry ( "ls ~/" )
2022-10-21 09:14:28 +02:00
e1 . CurrentWorkingDirectory = "/etc/"
e1 . Hostname = "server"
e1 . ExitCode = 127
2023-09-30 03:21:23 +02:00
require . NoError ( t , db . Create ( e1 ) . Error )
require . NoError ( t , db . Create ( testutils . MakeFakeHistoryEntry ( "ls ~/foo/" ) ) . Error )
require . NoError ( t , db . Create ( testutils . MakeFakeHistoryEntry ( "ls ~/bar/" ) ) . Error )
require . NoError ( t , db . Create ( testutils . MakeFakeHistoryEntry ( "echo 'aaaaaa bbbb'" ) ) . Error )
require . NoError ( t , db . Create ( testutils . MakeFakeHistoryEntry ( "echo 'bar' &" ) ) . Error )
2022-11-16 08:20:19 +01:00
2023-11-21 06:00:37 +01:00
// Check that they're there (and there aren't any other entries)
2022-11-16 08:20:19 +01:00
var historyEntries [ ] * data . HistoryEntry
db . Model ( & data . HistoryEntry { } ) . Find ( & historyEntries )
2023-11-19 10:44:48 +01:00
if len ( historyEntries ) != 5 {
2023-11-12 16:56:55 +01:00
t . Fatalf ( "expected to find 6 history entries, actual found %d: %#v" , len ( historyEntries ) , historyEntries )
2022-11-16 08:20:19 +01:00
}
2023-11-21 06:00:37 +01:00
out := tester . RunInteractiveShell ( t , ` hishtory export ` )
testutils . CompareGoldens ( t , out , "testControlR-InitialExport" )
2022-10-21 09:14:28 +02:00
// And check that the control-r binding brings up the search
2023-11-21 06:00:37 +01:00
out = captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "C-R" } )
2023-09-24 15:25:08 +02:00
split := strings . Split ( out , "\n\n\n" )
out = strings . TrimSpace ( split [ len ( split ) - 1 ] )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testControlR-Initial" )
2022-10-22 09:27:53 +02:00
// And check that we can scroll down and select an option
2022-10-28 06:20:33 +02:00
out = captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "C-R" , "Down Down" , "Enter" } )
2022-10-22 09:27:53 +02:00
if ! strings . HasSuffix ( out , " ls ~/bar/" ) {
t . Fatalf ( "hishtory tquery returned the wrong result, out=%#v" , out )
}
2022-10-28 06:20:33 +02:00
// And that the above works, but also with an ENTER to actually execute the selected command
2022-10-22 20:42:37 +02:00
out = captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "C-R" , "Down" , "Enter" , "Enter" } )
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , "echo 'aaaaaa bbbb'\naaaaaa bbbb\n" , "hishtory tquery executed the wrong result" )
2022-10-22 09:34:19 +02:00
// Search for something more specific and select it
2022-10-22 20:42:37 +02:00
out = captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "C-R" , "foo" , "Enter" } )
2022-10-22 09:34:19 +02:00
if ! strings . HasSuffix ( out , " ls ~/foo/" ) {
t . Fatalf ( "hishtory tquery returned the wrong result, out=%#v" , out )
}
2022-12-17 06:20:00 +01:00
// Search for something more specific, and then unsearch, and then search for something else (using an alternate key binding for the down key)
out = captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "C-R" , "fo" , "BSpace BSpace" , "bar" , "C-N" , "Enter" } )
2022-10-22 09:34:19 +02:00
if ! strings . HasSuffix ( out , " ls ~/bar/" ) {
t . Fatalf ( "hishtory tquery returned the wrong result, out=%#v" , out )
}
2022-10-22 09:50:34 +02:00
// Search using an atom
2022-10-22 20:58:59 +02:00
out = captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "C-R" , "fo" , "BSpace BSpace" , "exit_code:2" , "Enter" } )
if ! strings . HasSuffix ( out , " echo 'bar' &" ) {
t . Fatalf ( "hishtory tquery returned the wrong result, out=%#v" , out )
2022-10-22 09:50:34 +02:00
}
// Search and check that the table is updated
2023-10-23 21:24:43 +02:00
out = stripShellPrefix ( captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "C-R" , "echo" } ) )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testControlR-Search" )
2022-10-23 09:53:47 +02:00
2022-10-28 07:07:11 +02:00
// An advanced search and check that the table is updated
2023-10-23 21:24:43 +02:00
out = stripShellPrefix ( captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "C-R" , "cwd:/tmp/ SPACE ls" } ) )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testControlR-AdvancedSearch" )
2022-10-28 07:07:11 +02:00
2022-10-28 07:11:57 +02:00
// Set some different columns to be displayed and check that the table displays those
tester . RunInteractiveShell ( t , ` hishtory config-set displayed-columns Hostname 'Exit Code' Command ` )
2023-10-23 21:24:43 +02:00
out = stripShellPrefix ( captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "C-R" } ) )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testControlR-displayedColumns" )
2022-10-28 07:20:53 +02:00
// Add a custom column
tester . RunInteractiveShell ( t , ` hishtory config-add custom-columns foo "echo foo" ` )
tester . RunInteractiveShell ( t , ` hishtory enable ` )
tester . RunInteractiveShell ( t , ` ls / ` )
tester . RunInteractiveShell ( t , ` hishtory disable ` )
// And run a query and confirm it is displayed
tester . RunInteractiveShell ( t , ` hishtory config-add displayed-columns foo ` )
2023-10-23 21:24:43 +02:00
out = stripShellPrefix ( captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "C-R" , "-pipefail" } ) )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testControlR-customColumn" )
2022-10-28 07:11:57 +02:00
2022-11-12 00:57:13 +01:00
// Start with a search query, and then press control-r and it shows results for that query
2023-10-23 21:24:43 +02:00
out = stripShellPrefix ( captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "ls" , "C-R" } ) )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testControlR-InitialSearch" )
2022-11-12 00:57:13 +01:00
// Start with a search query, and then press control-r, then make the query more specific
2023-10-23 21:24:43 +02:00
out = stripShellPrefix ( captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "e" , "C-R" , "cho" } ) )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testControlR-InitialSearchExpanded" )
2022-11-12 00:57:13 +01:00
// Start with a search query for which there are no results
2023-10-23 21:24:43 +02:00
out = stripShellPrefix ( captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "asdf" , "C-R" } ) )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testControlR-InitialSearchNoResults" )
2022-11-12 00:57:13 +01:00
// Start with a search query for which there are no results
2023-10-23 21:24:43 +02:00
out = stripShellPrefix ( captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "asdf" , "C-R" , "BSpace BSpace BSpace BSpace echo" } ) )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testControlR-InitialSearchNoResultsThenFoundResults" )
2022-11-12 00:57:13 +01:00
2022-11-12 01:42:07 +01:00
// Search, hit control-c, and the table should be cleared
2023-10-23 21:24:43 +02:00
out = stripShellPrefix ( captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "echo" , "C-R" , "c" , "C-C" } ) )
2023-10-21 23:59:12 +02:00
require . NotContains ( t , out , "Search Query" , "hishtory is showing a table even after control-c?" )
require . NotContains ( t , out , "─────" , "hishtory is showing a table even after control-c?" )
require . NotContains ( t , out , "Exit Code" , "hishtory is showing a table even after control-c?" )
2023-12-29 16:31:56 +01:00
if testutils . IsGithubAction ( ) {
if shellName == "fish" {
require . Contains ( t , out , "Welcome to fish, the friendly interactive shell" )
require . Contains ( t , out , "> echo " )
} else {
testutils . CompareGoldens ( t , out , "testControlR-ControlC-" + shellName + "-" + runtime . GOOS )
}
2022-11-12 03:34:42 +01:00
}
2022-11-12 01:42:07 +01:00
2022-10-23 09:53:47 +02:00
// Disable control-r
_ , _ = tester . RunInteractiveShellRelaxed ( t , ` hishtory config-set enable-control-r false ` )
// And it shouldn't pop up
out = captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "C-R" } )
2023-10-21 23:59:12 +02:00
require . NotContains ( t , out , "Search Query" , "hishtory overrode control-r even when this was disabled?" )
require . NotContains ( t , out , "─────" , "hishtory overrode control-r even when this was disabled?" )
require . NotContains ( t , out , "Exit Code" , "hishtory overrode control-r even when this was disabled?" )
2023-12-29 16:31:56 +01:00
if testutils . IsGithubAction ( ) && shellName != "fish" {
testutils . CompareGoldens ( t , out , "testControlR-" + shellName + "-Disabled-" + runtime . GOOS )
2022-10-30 02:53:40 +02:00
}
2022-11-16 08:20:19 +01:00
// Re-enable control-r
2022-11-18 08:14:33 +01:00
_ , err := tester . RunInteractiveShellRelaxed ( t , ` hishtory config-set enable-control-r true ` )
2023-09-30 03:21:23 +02:00
require . NoError ( t , err )
2022-11-16 08:20:19 +01:00
// And check that the control-r bindings work again
2023-10-23 21:24:43 +02:00
out = stripShellPrefix ( captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "C-R" , "-pipefail SPACE -exit_code:0" } ) )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testControlR-Final" )
2022-11-20 07:27:08 +01:00
// Record a multi-line command
tester . RunInteractiveShell ( t , ` hishtory enable ` )
tester . RunInteractiveShell ( t , ` ls \
- Slah \
/ ` )
tester . RunInteractiveShell ( t , ` hishtory disable ` )
// Check that we display it in the table reasonably
2023-10-23 21:24:43 +02:00
out = stripShellPrefix ( captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "C-R" , "Slah" } ) )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testControlR-DisplayMultiline-" + shellName )
2022-11-20 07:27:08 +01:00
// Check that we can select it correctly
2023-10-23 21:24:43 +02:00
out = stripShellPrefix ( captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "C-R" , "Slah" , "Enter" } ) )
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , "-Slah" , "out has unexpected output missing the selected row" )
2023-12-29 16:31:56 +01:00
if testutils . IsGithubAction ( ) && shellName != "fish" {
testutils . CompareGoldens ( t , out , "testControlR-SelectMultiline-" + shellName + "-" + runtime . GOOS )
2022-11-20 07:27:08 +01:00
}
2022-11-27 07:40:43 +01:00
// Assert there are no leaked connections
assertNoLeakedConnections ( t )
2022-10-18 06:58:56 +02:00
}
2022-10-31 01:55:48 +01:00
func testCustomColumns ( t * testing . T , tester shellTester ) {
// Setup
defer testutils . BackupAndRestore ( t ) ( )
installHishtory ( t , tester , "" )
// Record a few commands with no custom columns
out := tester . RunInteractiveShell ( t , ` export FOOBAR = ' hello '
echo $ FOOBAR world
cd /
echo baz ` )
if out != "hello world\nbaz\n" {
t . Fatalf ( "unexpected command output=%#v" , out )
}
// Check that the hishtory is saved correctly
out = tester . RunInteractiveShell ( t , ` hishtory export | grep -v pipefail ` )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testCustomColumns-initHistory" )
2022-10-31 01:55:48 +01:00
// Configure a custom column
2022-11-14 03:26:23 +01:00
tester . RunInteractiveShell ( t , ` hishtory config-add custom-columns git_remote '(git remote -v 2>/dev/null | grep origin 1>/dev/null ) && git remote get-url origin || true' ` )
2022-10-31 01:55:48 +01:00
// Run a few commands, some of which will have a git_remote
out = tester . RunInteractiveShell ( t , ` echo foo
cd /
echo bar ` )
if out != "foo\nbar\n" {
t . Fatalf ( "unexpected command output=%#v" , out )
}
// And check that it is all recorded correctly
2023-11-12 11:05:51 +01:00
tester . RunInteractiveShell ( t , ` hishtory config-set displayed-columns 'Exit Code' git_remote Command ` )
2022-10-31 01:55:48 +01:00
out = tester . RunInteractiveShell ( t , ` hishtory query -pipefail ` )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , fmt . Sprintf ( "testCustomColumns-query-isAction=%v" , testutils . IsGithubAction ( ) ) )
2022-10-31 01:55:48 +01:00
out = captureTerminalOutput ( t , tester , [ ] string { "hishtory SPACE tquery SPACE -pipefail ENTER" } )
2023-11-12 09:55:53 +01:00
out = stripRequiredPrefix ( t , out , "hishtory tquery -pipefail" )
2022-11-04 05:27:01 +01:00
testName := "testCustomColumns-tquery-" + tester . ShellName ( )
2022-11-12 15:46:37 +01:00
if testutils . IsGithubAction ( ) {
2022-11-04 05:27:01 +01:00
testName += "-isAction"
}
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , testName )
2023-11-12 11:05:51 +01:00
// And check that we can delete the custom column and that it also gets automatically removed from displayed-columns
require . Equal ( t , ` "Exit Code" git_remote Command ` , strings . TrimSpace ( tester . RunInteractiveShell ( t , "hishtory config-get displayed-columns" ) ) )
require . Equal ( t , "git_remote: (git remote -v 2>/dev/null | grep origin 1>/dev/null ) && git remote get-url origin || true" , strings . TrimSpace ( tester . RunInteractiveShell ( t , "hishtory config-get custom-columns" ) ) )
tester . RunInteractiveShell ( t , ` hishtory config-delete custom-columns git_remote ` )
require . Equal ( t , ` "Exit Code" Command ` , strings . TrimSpace ( tester . RunInteractiveShell ( t , "hishtory config-get displayed-columns" ) ) )
2022-10-31 01:55:48 +01:00
}
2023-11-24 18:46:26 +01:00
func testPresavingDisabled ( t * testing . T , tester shellTester ) {
// Setup
defer testutils . BackupAndRestore ( t ) ( )
installHishtory ( t , tester , "" )
// Disable the presaving feature
require . Equal ( t , "true" , strings . TrimSpace ( tester . RunInteractiveShell ( t , ` hishtory config-get presaving ` ) ) )
tester . RunInteractiveShell ( t , ` hishtory config-set presaving false ` )
require . Equal ( t , "false" , strings . TrimSpace ( tester . RunInteractiveShell ( t , ` hishtory config-get presaving ` ) ) )
// Start a command that will take a long time to execute in the background, so
// we can check that it wasn't recorded even though it never finished.
require . NoError ( t , os . Chdir ( "/" ) )
require . NoError ( t , tester . RunInteractiveShellBackground ( t , ` sleep 13371338 ` ) )
time . Sleep ( time . Millisecond * 500 )
// Test that it shows up in hishtory export
out := tester . RunInteractiveShell ( t , ` hishtory export sleep -export ` )
expectedOutput := ""
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
}
2023-11-25 01:07:54 +01:00
func testPresavingOffline ( t * testing . T , tester shellTester ) {
// Setup
defer testutils . BackupAndRestore ( t ) ( )
defer testutils . BackupAndRestoreEnv ( "HISHTORY_SIMULATE_NETWORK_ERROR" ) ( )
userSecret := installHishtory ( t , tester , "" )
// Enable the presaving feature
require . Equal ( t , "true" , strings . TrimSpace ( tester . RunInteractiveShell ( t , ` hishtory config-get presaving ` ) ) )
tester . RunInteractiveShell ( t , ` hishtory config-set presaving true ` )
require . Equal ( t , "true" , strings . TrimSpace ( tester . RunInteractiveShell ( t , ` hishtory config-get presaving ` ) ) )
// Simulate a network error when presaving
os . Setenv ( "HISHTORY_SIMULATE_NETWORK_ERROR" , "1" )
require . NoError ( t , os . Chdir ( "/" ) )
require . NoError ( t , tester . RunInteractiveShellBackground ( t , ` sleep 13371336 ` ) )
// Check the exported data locally
tester . RunInteractiveShell ( t , ` hishtory config-set displayed-columns CWD Runtime Command ` )
out := tester . RunInteractiveShell ( t , ` hishtory query sleep -tquery -query ` )
testutils . CompareGoldens ( t , out , "testPresavingOffline-query-present" )
// And check it on another device where it isn't yet available
os . Setenv ( "HISHTORY_SIMULATE_NETWORK_ERROR" , "" )
restoreDevice1 := testutils . BackupAndRestoreWithId ( t , "device1" )
installHishtory ( t , tester , userSecret )
tester . RunInteractiveShell ( t , ` hishtory config-set displayed-columns CWD Runtime Command ` )
out = tester . RunInteractiveShell ( t , ` hishtory query sleep -tquery -query ` )
testutils . CompareGoldens ( t , out , "testPresavingOffline-query-missing" )
// Then go back to the first device and restore the internet connection so that it uploads the presaved entry
restoreDevice2 := testutils . BackupAndRestoreWithId ( t , "device2" )
restoreDevice1 ( )
tester . RunInteractiveShell ( t , ` echo any_command_to_trigger_reupload ` )
out = tester . RunInteractiveShell ( t , ` hishtory query sleep -tquery -query ` )
testutils . CompareGoldens ( t , out , "testPresavingOffline-query-present" )
// And check that it is now present on the second device
restoreDevice2 ( )
out = tester . RunInteractiveShell ( t , ` hishtory query sleep -tquery -query ` )
testutils . CompareGoldens ( t , out , "testPresavingOffline-query-present" )
}
2023-11-24 18:58:30 +01:00
func testPresaving ( t * testing . T , tester shellTester , shellName string ) {
2023-09-14 05:29:06 +02:00
// Setup
defer testutils . BackupAndRestore ( t ) ( )
userSecret := installHishtory ( t , tester , "" )
manuallySubmitHistoryEntry ( t , userSecret , testutils . MakeFakeHistoryEntry ( "table_sizing aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ) )
2023-11-12 14:19:31 +01:00
// Enable the presaving feature
require . Equal ( t , "true" , strings . TrimSpace ( tester . RunInteractiveShell ( t , ` hishtory config-get presaving ` ) ) )
tester . RunInteractiveShell ( t , ` hishtory config-set presaving true ` )
require . Equal ( t , "true" , strings . TrimSpace ( tester . RunInteractiveShell ( t , ` hishtory config-get presaving ` ) ) )
2023-09-14 05:29:06 +02:00
// Start a command that will take a long time to execute in the background, so
// we can check that it was recorded even though it never finished.
require . NoError ( t , os . Chdir ( "/" ) )
2023-11-24 18:58:30 +01:00
if tester . ShellName ( ) == shellName {
require . NoError ( t , tester . RunInteractiveShellBackground ( t , ` sleep 13371337 ` ) )
} else {
tmuxCommandToRunInBackground := buildTmuxInputCommands ( t , TmuxCaptureConfig {
tester : tester ,
overriddenShellName : shellName ,
commands : [ ] string { ` sleep SPACE 13371337 ENTER ` } ,
} )
tester . RunInteractiveShell ( t , tmuxCommandToRunInBackground )
}
2023-09-14 05:29:06 +02:00
time . Sleep ( time . Millisecond * 500 )
// Test that it shows up in hishtory export
2023-09-30 03:21:23 +02:00
out := tester . RunInteractiveShell ( t , ` hishtory export sleep -export ` )
2023-09-14 05:29:06 +02:00
expectedOutput := "sleep 13371337\n"
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
// Test that it shows up in hishtory query and that the runtime is displayed correctly
tester . RunInteractiveShell ( t , ` hishtory config-set displayed-columns CWD Runtime Command ` )
out = tester . RunInteractiveShell ( t , ` hishtory query sleep 13371337 -export -tquery ` )
testutils . CompareGoldens ( t , out , "testPresaving-query" )
2023-09-30 03:21:23 +02:00
// And then record a few other commands, and run an export of all commands, to ensure no funkiness happened
tester . RunInteractiveShell ( t , ` ls / ` )
time . Sleep ( time . Second )
tester . RunInteractiveShell ( t , ` sleep 0.5 ` )
out = tester . RunInteractiveShell ( t , ` hishtory export -hishtory -table_sizing -pipefail ` )
expectedOutput = "sleep 13371337\nls /\nsleep 0.5\n"
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
2023-09-22 22:13:46 +02:00
// Create a new device, and confirm it shows up there too
restoreDevice1 := testutils . BackupAndRestoreWithId ( t , "device1" )
installHishtory ( t , tester , userSecret )
tester . RunInteractiveShell ( t , ` hishtory config-set displayed-columns CWD Runtime Command ` )
out = tester . RunInteractiveShell ( t , ` hishtory query sleep 13371337 -export -tquery ` )
testutils . CompareGoldens ( t , out , "testPresaving-query" )
2023-09-30 03:21:23 +02:00
// And that all the other commands do to
out = tester . RunInteractiveShell ( t , ` hishtory export -hishtory -table_sizing -pipefail ` )
expectedOutput = "sleep 13371337\nls /\nsleep 0.5\n"
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
2023-09-22 22:13:46 +02:00
// And then redact it from device2
tester . RunInteractiveShell ( t , ` HISHTORY_REDACT_FORCE=true hishtory redact sleep 13371337 ` )
// And confirm it was redacted
out = tester . RunInteractiveShell ( t , ` hishtory export sleep -export ` )
2023-09-30 03:21:23 +02:00
require . Equal ( t , "sleep 0.5\n" , out )
2023-09-22 22:13:46 +02:00
// Then go back to device1 and confirm it was redacted there too
restoreDevice1 ( )
out = tester . RunInteractiveShell ( t , ` hishtory export sleep -export ` )
2023-09-30 03:21:23 +02:00
require . Equal ( t , "sleep 0.5\n" , out )
// And then record a few commands, and run a final export of all commands, to ensure no funkiness happened
out = tester . RunInteractiveShell ( t , ` hishtory export -hishtory -table_sizing -pipefail ` )
expectedOutput = "ls /\nsleep 0.5\n"
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
2023-09-14 05:29:06 +02:00
}
2023-12-21 02:18:00 +01:00
func testTabCompletion ( t * testing . T , tester shellTester , shellName string ) {
2023-12-21 04:51:25 +01:00
if shellName == "bash" {
// TODO: Enable tab completions for bash by adding the below line to config.sh
// type _get_comp_words_by_ref &>/dev/null && source <(hishtory completion bash)
t . Skip ( )
}
2023-12-21 02:08:30 +01:00
// Setup
defer testutils . BackupAndRestore ( t ) ( )
installHishtory ( t , tester , "" )
// Check that tab completions work to complete a command
2023-12-21 02:18:00 +01:00
out := captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "hishtory SPACE config-g Tab" } )
2023-12-21 02:08:30 +01:00
expected := "hishtory config-get"
require . True ( t , strings . HasSuffix ( out , expected ) , fmt . Sprintf ( "Expected out=%#v to end with %#v" , out , expected ) )
// Check that tab completions work to view suggestions
2023-12-21 02:18:00 +01:00
out = captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "hishtory SPACE config- Tab" } )
testutils . TestLog ( t , "testTabCompletion: Pre-stripping: " + out )
if shellName == "fish" {
out = strings . Join ( strings . Split ( out , "\n" ) [ 3 : ] , "\n" )
} else {
out = strings . Join ( strings . Split ( out , "\n" ) [ 1 : ] , "\n" )
}
testutils . CompareGoldens ( t , out , "testTabCompletion-suggestions-" + shellName )
2023-12-21 02:08:30 +01:00
}
2022-11-03 03:41:49 +01:00
func testUninstall ( t * testing . T , tester shellTester ) {
// Setup
defer testutils . BackupAndRestore ( t ) ( )
installHishtory ( t , tester , "" )
// Record a few commands and check that they get recorded
tester . RunInteractiveShell ( t , ` echo foo
echo baz ` )
out := tester . RunInteractiveShell ( t , ` hishtory export -pipefail ` )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testUninstall-recorded" )
2022-11-03 03:41:49 +01:00
// And then uninstall
out , err := tester . RunInteractiveShellRelaxed ( t , ` yes | hishtory uninstall ` )
2023-09-30 03:21:23 +02:00
require . NoError ( t , err )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testUninstall-uninstall" )
2022-11-03 03:41:49 +01:00
// And check that hishtory has been uninstalled
out , err = tester . RunInteractiveShellRelaxed ( t , ` echo foo
hishtory
echo bar ` )
2023-09-30 03:21:23 +02:00
require . NoError ( t , err )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testUninstall-post-uninstall" )
2022-11-03 03:41:49 +01:00
// And check again, but in a way that shows the full terminal output
2023-12-29 16:31:56 +01:00
if testutils . IsGithubAction ( ) {
2022-11-03 03:41:49 +01:00
out = captureTerminalOutput ( t , tester , [ ] string {
"echo SPACE foo ENTER" ,
"hishtory ENTER" ,
"echo SPACE bar ENTER" ,
} )
2023-12-29 16:31:56 +01:00
testutils . CompareGoldens ( t , out , "testUninstall-post-uninstall-" + tester . ShellName ( ) + "-" + runtime . GOOS )
2022-11-03 03:41:49 +01:00
}
}
2022-11-12 02:17:54 +01:00
func TestTimestampFormat ( t * testing . T ) {
// Setup
2024-02-11 20:54:27 +01:00
markTestForSharding ( t , 6 )
2022-11-12 02:17:54 +01:00
tester := zshTester { }
defer testutils . BackupAndRestore ( t ) ( )
userSecret := installHishtory ( t , tester , "" )
2022-11-12 15:18:05 +01:00
// Add an entry just to ensure we get consistent table sizing
tester . RunInteractiveShell ( t , "echo tablesizing" )
2022-11-12 02:17:54 +01:00
// Add some entries with fixed timestamps
tmz , err := time . LoadLocation ( "America/Los_Angeles" )
2023-09-09 21:28:01 +02:00
require . NoError ( t , err )
2022-11-12 16:50:06 +01:00
entry1 := testutils . MakeFakeHistoryEntry ( "table_cmd1 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" )
2022-11-12 02:17:54 +01:00
entry1 . StartTime = time . Unix ( 1650096186 , 0 ) . In ( tmz )
entry1 . EndTime = time . Unix ( 1650096190 , 0 ) . In ( tmz )
manuallySubmitHistoryEntry ( t , userSecret , entry1 )
entry2 := testutils . MakeFakeHistoryEntry ( "table_cmd2" )
entry2 . StartTime = time . Unix ( 1650096196 , 0 ) . In ( tmz )
entry2 . EndTime = time . Unix ( 1650096220 , 0 ) . In ( tmz )
entry2 . CurrentWorkingDirectory = "~/foo/"
entry2 . ExitCode = 3
manuallySubmitHistoryEntry ( t , userSecret , entry2 )
2023-12-12 05:49:28 +01:00
// Check the init the timestamp format
require . Equal ( t , "Jan 2 2006 15:04:05 MST" , strings . TrimSpace ( tester . RunInteractiveShell ( t , ` hishtory config-get timestamp-format ` ) ) )
2022-11-12 02:17:54 +01:00
// Set a custom timestamp format
tester . RunInteractiveShell ( t , ` hishtory config-set timestamp-format '2006/Jan/2 15:04' ` )
// And check that it is displayed in both the tui and the classic view
2022-11-12 15:18:05 +01:00
out := hishtoryQuery ( t , tester , "-pipefail -tablesizing" )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "TestTimestampFormat-query" )
2022-11-12 15:18:05 +01:00
out = captureTerminalOutput ( t , tester , [ ] string { "hishtory SPACE tquery SPACE -pipefail SPACE -tablesizing ENTER" } )
2023-11-12 09:55:53 +01:00
out = stripRequiredPrefix ( t , out , "hishtory tquery -pipefail -tablesizing" )
2023-09-09 20:56:06 +02:00
testutils . CompareGoldens ( t , out , "TestTimestampFormat-tquery" )
2022-11-12 02:17:54 +01:00
}
2023-09-09 21:28:01 +02:00
func TestSortByConsistentTimezone ( t * testing . T ) {
// Setup
2024-02-11 20:54:27 +01:00
markTestForSharding ( t , 7 )
2023-09-09 21:28:01 +02:00
tester := zshTester { }
defer testutils . BackupAndRestore ( t ) ( )
installHishtory ( t , tester , "" )
2023-09-09 23:02:47 +02:00
// Add an entry just to ensure we get consistent table sizing
tester . RunInteractiveShell ( t , "echo tablesizing" )
2023-09-09 21:28:01 +02:00
// Add some entries with timestamps in different timezones
db := hctx . GetDb ( hctx . MakeContext ( ) )
timestamp := int64 ( 1650096186 )
la_time , err := time . LoadLocation ( "America/Los_Angeles" )
require . NoError ( t , err )
ny_time , err := time . LoadLocation ( "America/New_York" )
require . NoError ( t , err )
entry1 := testutils . MakeFakeHistoryEntry ( "first_entry" )
entry1 . StartTime = time . Unix ( timestamp , 0 ) . In ( ny_time )
entry1 . EndTime = time . Unix ( timestamp + 1 , 0 ) . In ( ny_time )
2023-09-30 03:21:23 +02:00
require . NoError ( t , lib . ReliableDbCreate ( db , entry1 ) )
2023-09-09 21:28:01 +02:00
entry2 := testutils . MakeFakeHistoryEntry ( "second_entry" )
entry2 . StartTime = time . Unix ( timestamp + 1000 , 0 ) . In ( la_time )
entry2 . EndTime = time . Unix ( timestamp + 1001 , 0 ) . In ( la_time )
2023-09-30 03:21:23 +02:00
require . NoError ( t , lib . ReliableDbCreate ( db , entry2 ) )
2023-09-09 21:28:01 +02:00
entry3 := testutils . MakeFakeHistoryEntry ( "third_entry" )
entry3 . StartTime = time . Unix ( timestamp + 2000 , 0 ) . In ( ny_time )
entry3 . EndTime = time . Unix ( timestamp + 2001 , 0 ) . In ( ny_time )
2023-09-30 03:21:23 +02:00
require . NoError ( t , lib . ReliableDbCreate ( db , entry3 ) )
2023-09-09 21:28:01 +02:00
// And check that they're displayed in the correct order
2023-09-09 23:02:47 +02:00
out := hishtoryQuery ( t , tester , "-pipefail -tablesizing" )
2023-09-09 21:28:01 +02:00
testutils . CompareGoldens ( t , out , "TestSortByConsistentTimezone-query" )
2023-09-13 04:09:01 +02:00
out = tester . RunInteractiveShell ( t , ` hishtory export -pipefail -tablesizing ` )
testutils . CompareGoldens ( t , out , "TestSortByConsistentTimezone-export" )
2023-09-09 23:02:47 +02:00
out = captureTerminalOutput ( t , tester , [ ] string { "hishtory SPACE tquery SPACE -pipefail SPACE -tablesizing ENTER" } )
2023-11-12 09:40:24 +01:00
out = stripTuiCommandPrefix ( t , out )
2023-09-13 04:09:01 +02:00
require . Regexp ( t , regexp . MustCompile ( ` Timestamp[\s\S]*Command[\s\S]*Apr 16 2022 01:36:26 PDT[\s\S]*third_entry[\s\S]*Apr 16 2022 01:19:46 PDT[\s\S]*second_entry[\s\S]*Apr 16 2022 01:03:06 PDT[\s\S]*first_entry ` ) , out )
2023-09-09 21:28:01 +02:00
}
2022-11-14 00:59:59 +01:00
func TestZDotDir ( t * testing . T ) {
// Setup
2024-02-11 20:54:27 +01:00
markTestForSharding ( t , 8 )
2022-11-14 00:59:59 +01:00
tester := zshTester { }
defer testutils . BackupAndRestore ( t ) ( )
defer testutils . BackupAndRestoreEnv ( "ZDOTDIR" ) ( )
homedir , err := os . UserHomeDir ( )
2023-09-30 03:21:23 +02:00
require . NoError ( t , err )
2022-12-12 03:51:57 +01:00
zdotdir := path . Join ( homedir , "foo" )
2023-09-30 03:21:23 +02:00
require . NoError ( t , os . MkdirAll ( zdotdir , 0 o744 ) )
2022-12-12 03:51:57 +01:00
os . Setenv ( "ZDOTDIR" , zdotdir )
userSecret := installHishtory ( t , tester , "" )
2022-12-12 06:59:41 +01:00
defer func ( ) {
2023-09-30 03:21:23 +02:00
require . NoError ( t , os . Remove ( path . Join ( zdotdir , ".zshrc" ) ) )
2022-12-12 06:59:41 +01:00
} ( )
2022-12-12 03:51:57 +01:00
// Check the status command
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 )
}
2022-11-14 00:59:59 +01:00
// Run a command and check that it was recorded
tester . RunInteractiveShell ( t , ` echo foo ` )
2022-12-12 06:59:41 +01:00
out = tester . RunInteractiveShell ( t , ` hishtory export -pipefail -install -status ` )
2022-11-14 00:59:59 +01:00
if out != "echo foo\n" {
t . Fatalf ( "hishtory export had unexpected out=%#v" , out )
}
// Check that hishtory respected ZDOTDIR
2022-12-12 03:51:57 +01:00
zshrc , err := os . ReadFile ( path . Join ( zdotdir , ".zshrc" ) )
2023-09-30 03:21:23 +02:00
require . NoError ( t , err )
2023-08-29 05:55:15 +02:00
require . Contains ( t , string ( zshrc ) , "# Hishtory Config:" , "zshrc had unexpected contents" )
2022-11-14 00:59:59 +01:00
}
2022-11-04 04:36:36 +01:00
func TestRemoveDuplicateRows ( t * testing . T ) {
// Setup
2024-02-11 20:54:27 +01:00
markTestForSharding ( t , 9 )
2022-11-04 05:16:53 +01:00
tester := zshTester { }
2022-11-04 04:36:36 +01:00
defer testutils . BackupAndRestore ( t ) ( )
installHishtory ( t , tester , "" )
2023-12-12 05:49:28 +01:00
// Check the default
require . Equal ( t , "false" , strings . TrimSpace ( tester . RunInteractiveShell ( t , ` hishtory config-get filter-duplicate-commands ` ) ) )
2022-11-04 04:36:36 +01:00
// Record a few commands and check that they get recorded and all are displayed in a table
tester . RunInteractiveShell ( t , ` echo foo
echo foo
2022-11-11 17:16:12 +01:00
echo baz
2022-11-04 04:36:36 +01:00
echo baz
echo foo ` )
out := tester . RunInteractiveShell ( t , ` hishtory export -pipefail ` )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testRemoveDuplicateRows-export" )
2022-11-04 04:36:36 +01:00
tester . RunInteractiveShell ( t , ` hishtory config-set displayed-columns 'Exit Code' Command ` )
out = tester . RunInteractiveShell ( t , ` hishtory query -pipefail ` )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testRemoveDuplicateRows-query" )
2022-11-04 04:36:36 +01:00
out = captureTerminalOutput ( t , tester , [ ] string { "hishtory SPACE tquery SPACE -pipefail ENTER" } )
2023-11-12 09:55:53 +01:00
out = stripRequiredPrefix ( t , out , "hishtory tquery -pipefail" )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testRemoveDuplicateRows-tquery" )
2022-11-04 04:36:36 +01:00
// And change the config to filter out duplicate rows
tester . RunInteractiveShell ( t , ` hishtory config-set filter-duplicate-commands true ` )
2023-12-20 06:05:27 +01:00
// Check export
2022-11-04 04:36:36 +01:00
out = tester . RunInteractiveShell ( t , ` hishtory export -pipefail ` )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testRemoveDuplicateRows-enabled-export" )
2023-12-20 06:05:27 +01:00
// Check query
2022-11-04 04:36:36 +01:00
out = tester . RunInteractiveShell ( t , ` hishtory query -pipefail ` )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testRemoveDuplicateRows-enabled-query" )
2023-12-20 06:05:27 +01:00
// Check tquery
2022-11-04 04:36:36 +01:00
out = captureTerminalOutput ( t , tester , [ ] string { "hishtory SPACE tquery SPACE -pipefail ENTER" } )
2023-11-12 09:55:53 +01:00
out = stripRequiredPrefix ( t , out , "hishtory tquery -pipefail" )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testRemoveDuplicateRows-enabled-tquery" )
2023-12-20 06:05:27 +01:00
// Check actually selecting it with query
2023-10-16 04:02:57 +02:00
out = captureTerminalOutputWithComplexCommands ( t , tester , [ ] TmuxCommand {
{ Keys : "hishtory SPACE tquery SPACE -pipefail ENTER" , ExtraDelay : 1.0 } ,
{ Keys : "Down Down" } ,
{ Keys : "ENTER" , ExtraDelay : 1.0 } ,
} )
2023-11-12 09:40:24 +01:00
out = stripTuiCommandPrefix ( t , out )
2023-12-20 22:39:52 +01:00
require . Contains ( t , out , "\necho foo\n" )
2023-12-20 23:58:10 +01:00
require . NotContains ( t , out , "echo baz" )
require . NotContains ( t , out , "config-set" )
2022-11-04 04:36:36 +01:00
}
2022-12-12 04:15:29 +01:00
func TestSetConfigNoCorruption ( t * testing . T ) {
// Setup
2024-02-11 20:54:27 +01:00
markTestForSharding ( t , 10 )
2022-12-12 04:15:29 +01:00
tester := zshTester { }
defer testutils . BackupAndRestore ( t ) ( )
installHishtory ( t , tester , "" )
// A test that tries writing a config many different times in parallel, and confirms there is no corruption
conf , err := hctx . GetConfig ( )
2023-09-30 03:21:23 +02:00
require . NoError ( t , err )
2022-12-12 04:15:29 +01:00
var doneWg sync . WaitGroup
for i := 0 ; i < 10 ; i ++ {
doneWg . Add ( 1 )
go func ( i int ) {
// Make a new config of a varied length
c := conf
c . LastSavedHistoryLine = strings . Repeat ( "A" , i )
c . DeviceId = strings . Repeat ( "B" , i * 2 )
c . HaveMissedUploads = ( i % 2 ) == 0
// Write it
2023-09-30 03:21:23 +02:00
require . NoError ( t , hctx . SetConfig ( & c ) )
2023-09-09 21:28:01 +02:00
require . NoError ( t , err )
2022-12-12 04:15:29 +01:00
// Check that we can read
c2 , err := hctx . GetConfig ( )
2023-09-09 21:28:01 +02:00
require . NoError ( t , err )
2022-12-12 04:15:29 +01:00
if c2 . UserSecret != c . UserSecret {
panic ( "user secret mismatch" )
}
doneWg . Done ( )
} ( i )
}
doneWg . Wait ( )
}
2023-09-23 21:40:57 +02:00
// Test that the config retrieved from the context is a reference and there are no consistency issues with it getting out of sync
func TestCtxConfigIsReference ( t * testing . T ) {
// Setup
2024-02-11 20:54:27 +01:00
markTestForSharding ( t , 11 )
2023-09-23 21:40:57 +02:00
tester := zshTester { }
defer testutils . BackupAndRestore ( t ) ( )
installHishtory ( t , tester , "" )
// Get two copies of the conifg
ctx := hctx . MakeContext ( )
c1 := hctx . GetConf ( ctx )
c2 := hctx . GetConf ( ctx )
require . Equal ( t , * c1 , * c2 )
// Change one and check that the other is changed
c1 . LastSavedHistoryLine = "foobar"
require . Equal ( t , c1 . LastSavedHistoryLine , "foobar" )
require . Equal ( t , c2 . LastSavedHistoryLine , "foobar" )
// Persist that one, and then get the config again, and that one should also contain the change
require . NoError ( t , hctx . SetConfig ( c1 ) )
c3 := hctx . GetConf ( ctx )
require . Equal ( t , * c1 , * c3 )
require . Equal ( t , c1 . LastSavedHistoryLine , "foobar" )
require . Equal ( t , c2 . LastSavedHistoryLine , "foobar" )
require . Equal ( t , c3 . LastSavedHistoryLine , "foobar" )
}
2022-09-23 06:45:20 +02:00
func testMultipleUsers ( t * testing . T , tester shellTester ) {
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2022-09-23 06:45:20 +02:00
// Create all our devices
var deviceMap map [ device ] deviceOp = make ( map [ device ] deviceOp )
var devices deviceSet = deviceSet { }
devices . deviceMap = & deviceMap
devices . currentDevice = nil
u1d1 := device { key : "user1" , deviceId : "1" }
createDevice ( t , tester , & devices , u1d1 . key , u1d1 . deviceId )
u1d2 := device { key : "user1" , deviceId : "2" }
createDevice ( t , tester , & devices , u1d2 . key , u1d2 . deviceId )
u2d1 := device { key : "user2" , deviceId : "1" }
createDevice ( t , tester , & devices , u2d1 . key , u2d1 . deviceId )
u2d2 := device { key : "user2" , deviceId : "2" }
createDevice ( t , tester , & devices , u2d2 . key , u2d2 . deviceId )
u2d3 := device { key : "user2" , deviceId : "3" }
createDevice ( t , tester , & devices , u2d3 . key , u2d3 . deviceId )
// Run commands on user1
switchToDevice ( & devices , u1d1 )
_ , _ = tester . RunInteractiveShellRelaxed ( t , ` echo u1d1 ` )
switchToDevice ( & devices , u1d2 )
_ , _ = tester . RunInteractiveShellRelaxed ( t , ` echo u1d2 ` )
// Run commands on user2
switchToDevice ( & devices , u2d1 )
_ , _ = tester . RunInteractiveShellRelaxed ( t , ` echo u2d1 ` )
switchToDevice ( & devices , u2d2 )
_ , _ = tester . RunInteractiveShellRelaxed ( t , ` echo u2d2 ` )
switchToDevice ( & devices , u2d3 )
_ , _ = tester . RunInteractiveShellRelaxed ( t , ` echo u2d3 ` )
// Run more commands on user1
switchToDevice ( & devices , u1d1 )
_ , _ = tester . RunInteractiveShellRelaxed ( t , ` echo u1d1-b ` )
_ , _ = tester . RunInteractiveShellRelaxed ( t , ` echo u1d1-c ` )
switchToDevice ( & devices , u1d2 )
_ , _ = tester . RunInteractiveShellRelaxed ( t , ` echo u1d2-b ` )
_ , _ = tester . RunInteractiveShellRelaxed ( t , ` echo u1d2-c ` )
// Check that the right commands were recorded for user1
2022-11-13 15:25:16 +01:00
for _ , d := range [ ] device { u1d1 , u1d2 } {
2022-09-23 06:45:20 +02:00
switchToDevice ( & devices , d )
2022-11-13 15:25:16 +01:00
out , err := tester . RunInteractiveShellRelaxed ( t , ` hishtory export -pipefail -export ` )
2023-09-30 03:21:23 +02:00
require . NoError ( t , err )
2022-09-23 06:45:20 +02:00
expectedOutput := "echo u1d1\necho u1d2\necho u1d1-b\necho u1d1-c\necho u1d2-b\necho u1d2-c\n"
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
}
// Run more commands on user2
switchToDevice ( & devices , u2d1 )
_ , _ = tester . RunInteractiveShellRelaxed ( t , ` echo u1d1-b ` )
_ , _ = tester . RunInteractiveShellRelaxed ( t , ` echo u1d1-c ` )
switchToDevice ( & devices , u2d3 )
_ , _ = tester . RunInteractiveShellRelaxed ( t , ` echo u2d3-b ` )
_ , _ = tester . RunInteractiveShellRelaxed ( t , ` echo u2d3-c ` )
// Check that the right commands were recorded for user2
2022-11-13 15:49:57 +01:00
for _ , d := range [ ] device { u2d1 , u2d2 , u2d3 } {
2022-09-23 06:45:20 +02:00
switchToDevice ( & devices , d )
2022-11-13 15:42:37 +01:00
out , err := tester . RunInteractiveShellRelaxed ( t , ` hishtory export -export -pipefail ` )
2023-09-30 03:21:23 +02:00
require . NoError ( t , err )
2022-09-23 06:45:20 +02:00
expectedOutput := "echo u2d1\necho u2d2\necho u2d3\necho u1d1-b\necho u1d1-c\necho u2d3-b\necho u2d3-c\n"
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
}
}
2023-10-03 17:00:16 +02:00
func createSyntheticImportEntries ( t testing . TB , numSyntheticEntries int ) {
homedir , err := os . UserHomeDir ( )
require . NoError ( t , err )
f , err := os . OpenFile ( path . Join ( homedir , ".bash_history" ) , os . O_APPEND | os . O_CREATE | os . O_WRONLY , 0644 )
require . NoError ( t , err )
defer f . Close ( )
for i := 1 ; i <= numSyntheticEntries ; i ++ {
_ , err := f . WriteString ( fmt . Sprintf ( "echo command-%d\n" , i ) )
require . NoError ( t , err )
}
require . NoError ( t , f . Close ( ) )
}
func TestImportHistory ( t * testing . T ) {
// Setup
2024-02-11 20:54:27 +01:00
markTestForSharding ( t , 11 )
2023-10-03 17:00:16 +02:00
tester := bashTester { }
defer testutils . BackupAndRestore ( t ) ( )
userSecret := installHishtory ( t , tester , "" )
numSyntheticEntries := 305
createSyntheticImportEntries ( t , numSyntheticEntries )
// Run the import
ctx := hctx . MakeContext ( )
numImported , err := lib . ImportHistory ( ctx , false , true )
require . NoError ( t , err )
require . Equal ( t , numImported , numSyntheticEntries + 1 )
// Check that it imported all of them
out := tester . RunInteractiveShell ( t , ` hishtory export -pipefail ` )
testutils . CompareGoldens ( t , out , "TestImportHistory-export" )
// Check that it was uploaded so that another user can get it
testutils . ResetLocalState ( t )
installHishtory ( t , tester , userSecret )
out = strings . TrimSpace ( tester . RunInteractiveShell ( t , ` hishtory export -pipefail | wc -l ` ) )
require . Equal ( t , "305" , out )
out = tester . RunInteractiveShell ( t , ` hishtory export -pipefail ` )
require . Contains ( t , out , "echo command-305" )
2023-10-07 20:05:19 +02:00
out = tester . RunInteractiveShell ( t , ` hishtory export -pipefail ` )
testutils . CompareGoldens ( t , out , "TestImportHistory-export" )
2023-10-03 17:00:16 +02:00
}
2023-10-03 07:27:24 +02:00
func BenchmarkImport ( b * testing . B ) {
b . StopTimer ( )
// Setup
tester := bashTester { }
defer testutils . BackupAndRestore ( b ) ( )
// Benchmark it
for n := 0 ; n < b . N ; n ++ {
// Setup
testutils . ResetLocalState ( b )
installHishtory ( b , tester , "" )
// Create a large history in bash that we will pre-import
numSyntheticEntries := 100_000
2023-10-03 17:00:16 +02:00
createSyntheticImportEntries ( b , numSyntheticEntries )
2023-10-03 07:27:24 +02:00
// Benchmarked code:
b . StartTimer ( )
ctx := hctx . MakeContext ( )
numImported , err := lib . ImportHistory ( ctx , false , true )
require . NoError ( b , err )
require . GreaterOrEqual ( b , numImported , numSyntheticEntries )
b . StopTimer ( )
}
}
2024-04-14 18:57:44 +02:00
func BenchmarkQuery ( b * testing . B ) {
b . StopTimer ( )
// Setup with an install with a lot of entries
tester := zshTester { }
defer testutils . BackupAndRestore ( b ) ( )
testutils . ResetLocalState ( b )
installHishtory ( b , tester , "" )
numSyntheticEntries := 100_000
createSyntheticImportEntries ( b , numSyntheticEntries )
ctx := hctx . MakeContext ( )
numImported , err := lib . ImportHistory ( ctx , false , true )
require . NoError ( b , err )
require . GreaterOrEqual ( b , numImported , numSyntheticEntries )
// Benchmark it
for n := 0 ; n < b . N ; n ++ {
// Benchmarked code:
b . StartTimer ( )
ctx := hctx . MakeContext ( )
2024-04-15 07:53:28 +02:00
err := lib . RetrieveAdditionalEntriesFromRemote ( ctx , "tui" )
require . NoError ( b , err )
_ , err = lib . Search ( ctx , hctx . GetDb ( ctx ) , "echo" , 100 )
2024-04-14 18:57:44 +02:00
require . NoError ( b , err )
b . StopTimer ( )
}
}
2023-11-10 08:00:20 +01:00
func TestAugmentedIsOfflineError ( t * testing . T ) {
2024-02-11 20:54:27 +01:00
markTestForSharding ( t , 12 )
2023-11-10 08:00:20 +01:00
defer testutils . BackupAndRestore ( t ) ( )
installHishtory ( t , zshTester { } , "" )
defer testutils . BackupAndRestoreEnv ( "HISHTORY_SIMULATE_NETWORK_ERROR" ) ( )
ctx := hctx . MakeContext ( )
// By default, when the hishtory server is up, then IsOfflineError checks the error msg
require . True ( t , lib . CanReachHishtoryServer ( ctx ) )
require . False ( t , lib . IsOfflineError ( ctx , fmt . Errorf ( "unchecked error type" ) ) )
// When the hishtory server is down, then all error messages are treated as being due to offline errors
os . Setenv ( "HISHTORY_SIMULATE_NETWORK_ERROR" , "1" )
require . False ( t , lib . CanReachHishtoryServer ( ctx ) )
require . True ( t , lib . IsOfflineError ( ctx , fmt . Errorf ( "unchecked error type" ) ) )
}
2024-02-19 18:34:33 +01:00
func TestWebUi ( t * testing . T ) {
markTestForSharding ( t , 13 )
defer testutils . BackupAndRestore ( t ) ( )
tester := zshTester { }
installHishtory ( t , tester , "" )
// Run a few commands to search for
tester . RunInteractiveShell ( t , ` echo foobar ` )
// Start the server
2024-04-15 03:18:11 +02:00
require . NoError ( t , tester . RunInteractiveShellBackground ( t , ` hishtory start-web-ui --port 8001 --force-creds hishtory:my_password ` ) )
2024-02-19 18:34:33 +01:00
time . Sleep ( time . Second )
defer tester . RunInteractiveShell ( t , ` killall hishtory ` )
// And check that the server seems to be returning valid data
2024-04-15 03:18:11 +02:00
req , err := http . NewRequest ( "GET" , "http://localhost:8001?q=foobar" , nil )
2024-02-19 18:34:33 +01:00
require . NoError ( t , err )
2024-02-19 20:14:32 +01:00
req . SetBasicAuth ( "hishtory" , "my_password" )
2024-02-19 18:34:33 +01:00
resp , err := http . DefaultClient . Do ( req )
require . NoError ( t , err )
require . Equal ( t , 200 , resp . StatusCode )
defer resp . Body . Close ( )
respBody , err := io . ReadAll ( resp . Body )
require . NoError ( t , err )
require . Contains ( t , string ( respBody ) , "echo foobar" )
// And that it rejects requests without auth
2024-04-15 03:18:11 +02:00
resp , err = http . Get ( "http://localhost:8001?q=foobar" )
2024-02-19 18:34:33 +01:00
require . NoError ( t , err )
require . Equal ( t , 401 , resp . StatusCode )
// And requests with incorrect auth
2024-04-15 03:18:11 +02:00
req , err = http . NewRequest ( "GET" , "http://localhost:8001?q=foobar" , nil )
2024-02-19 18:34:33 +01:00
require . NoError ( t , err )
req . SetBasicAuth ( "hishtory" , "wrong-password" )
resp , err = http . DefaultClient . Do ( req )
require . NoError ( t , err )
require . Equal ( t , 401 , resp . StatusCode )
}
2024-04-13 18:51:07 +02:00
func TestForceInit ( t * testing . T ) {
markTestForSharding ( t , 13 )
defer testutils . BackupAndRestore ( t ) ( )
tester := zshTester { }
initialSecret := installHishtory ( t , tester , "" )
secondaryUserSecret := initialSecret + "-second"
// Run a commands to search for and confirm it was recorded
tester . RunInteractiveShell ( t , ` echo foobar ` )
require . Equal ( t , "echo foobar\n" , tester . RunInteractiveShell ( t , ` hishtory export -pipefail -export ` ) )
// Init as the other user with --force
out , err := tester . RunInteractiveShellRelaxed ( t , ` export HISHTORY_SKIP_INIT_IMPORT = 1
hishtory init -- force ` + secondaryUserSecret )
require . NoError ( t , err )
require . Contains ( t , out , "Setting secret hishtory key to " + secondaryUserSecret , "Failed to re-init with the user secret" )
// Check that the history was cleared
require . NotContains ( t , tester . RunInteractiveShell ( t , ` hishtory export ` ) , "echo foobar" )
}
2022-10-19 04:55:41 +02:00
// TODO: somehow test/confirm that hishtory works even if only bash/only zsh is installed