2022-01-09 20:00:53 +01:00
package main
import (
"bytes"
2022-04-07 08:17:58 +02:00
"encoding/json"
2022-04-07 07:44:10 +02:00
"fmt"
2023-09-21 20:35:24 +02:00
"io"
2022-04-07 08:17:58 +02:00
"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-09-17 20:49:31 +02:00
"strconv"
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"
2023-09-08 03:30:47 +02:00
"github.com/DataDog/datadog-go/statsd"
2022-04-15 05:18:49 +02:00
"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
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"
2022-01-09 20:00:53 +01:00
"github.com/ddworken/hishtory/shared"
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
)
2023-09-08 03:30:47 +02:00
var GLOBAL_STATSD * statsd . Client
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
// Configure the integration to export test failures to datadog for better monitoring
2023-09-08 18:59:55 +02:00
if _ , has_dd_api_key := os . LookupEnv ( "DD_API_KEY" ) ; testutils . IsGithubAction ( ) && has_dd_api_key {
2023-09-08 03:30:47 +02:00
ddStats , err := statsd . New ( "localhost:8125" )
if err != nil {
2023-09-09 04:27:16 +02:00
err := fmt . Errorf ( "Failed to start DataDog statsd: %w\n" , err )
if runtime . GOOS == "darwin" {
fmt . Printf ( "%v" , err )
} else {
panic ( err )
}
2023-09-08 03:30:47 +02:00
}
GLOBAL_STATSD = ddStats
}
// Start the tests
2022-04-15 20:20:23 +02:00
m . Run ( )
}
2022-04-18 04:54:17 +02:00
type shellTester interface {
2023-08-30 08:23:21 +02:00
RunInteractiveShell ( t testing . TB , script string ) string
RunInteractiveShellRelaxed ( t testing . TB , script string ) ( string , error )
2022-04-18 04:54:17 +02:00
ShellName ( ) string
}
type bashTester struct {
shellTester
}
2023-08-30 08:23:21 +02:00
func ( b bashTester ) RunInteractiveShell ( t testing . TB , script string ) string {
2022-04-18 04:54:17 +02:00
out , err := b . RunInteractiveShellRelaxed ( t , "set -emo pipefail\n" + script )
2022-04-08 08:25:13 +02:00
if err != nil {
2022-04-12 07:36:52 +02:00
_ , filename , line , _ := runtime . Caller ( 1 )
t . Fatalf ( "error when running command at %s:%d: %v" , filename , line , err )
2022-04-08 08:25:13 +02:00
}
return out
}
2023-08-30 08:23:21 +02:00
func ( b bashTester ) RunInteractiveShellRelaxed ( t testing . TB , script string ) ( string , error ) {
2022-01-09 23:34:59 +01:00
cmd := exec . Command ( "bash" , "-i" )
cmd . Stdin = strings . NewReader ( script )
2022-04-08 08:25:13 +02:00
var stdout bytes . Buffer
cmd . Stdout = & stdout
var stderr bytes . Buffer
cmd . Stderr = & stderr
err := cmd . Run ( )
if err != nil {
2023-09-05 21:08:55 +02:00
return "" , fmt . Errorf ( "unexpected error when running commands, out=%#v, err=%#v: %w" , stdout . String ( ) , stderr . String ( ) , err )
2022-04-08 08:25:13 +02:00
}
outStr := stdout . String ( )
2023-08-29 05:55:15 +02:00
require . NotContains ( t , outStr , "hishtory fatal error" , "Ran command, but hishtory had a fatal error!" )
2022-04-08 08:25:13 +02:00
return outStr , nil
2022-01-09 23:34:59 +01:00
}
2022-04-18 04:54:17 +02:00
func ( b bashTester ) ShellName ( ) string {
return "bash"
}
type zshTester struct {
shellTester
}
2023-08-30 08:23:21 +02:00
func ( z zshTester ) RunInteractiveShell ( t testing . TB , script string ) string {
2022-06-05 08:11:26 +02:00
res , err := z . RunInteractiveShellRelaxed ( t , "set -eo pipefail\n" + script )
2023-08-29 04:27:29 +02:00
require . NoError ( t , err )
2022-04-18 04:54:17 +02:00
return res
}
2023-08-30 08:23:21 +02:00
func ( z zshTester ) RunInteractiveShellRelaxed ( t testing . TB , script string ) ( string , error ) {
2022-04-18 04:54:17 +02:00
cmd := exec . Command ( "zsh" , "-is" )
cmd . Stdin = strings . NewReader ( script )
var stdout bytes . Buffer
cmd . Stdout = & stdout
var stderr bytes . Buffer
cmd . Stderr = & stderr
err := cmd . Run ( )
if err != nil {
2023-09-05 21:08:55 +02:00
return stdout . String ( ) , fmt . Errorf ( "unexpected error when running command=%#v, out=%#v, err=%#v: %w" , script , stdout . String ( ) , stderr . String ( ) , err )
2022-04-18 04:54:17 +02:00
}
outStr := stdout . String ( )
2023-08-29 05:55:15 +02:00
require . NotContains ( t , outStr , "hishtory fatal error" )
2022-04-18 04:54:17 +02:00
return outStr , nil
}
func ( z zshTester ) ShellName ( ) string {
return "zsh"
}
var shellTesters [ ] shellTester = [ ] shellTester { bashTester { } , zshTester { } }
2022-11-03 21:16:45 +01:00
type OnlineStatus int64
const (
Online OnlineStatus = iota
Offline
)
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 {
t . Run ( "testRepeatedCommandThenQuery/" + tester . ShellName ( ) , func ( t * testing . T ) { testRepeatedCommandThenQuery ( t , tester ) } )
t . Run ( "testRepeatedCommandAndQuery/" + tester . ShellName ( ) , func ( t * testing . T ) { testRepeatedCommandAndQuery ( t , tester ) } )
t . Run ( "testRepeatedEnableDisable/" + tester . ShellName ( ) , func ( t * testing . T ) { testRepeatedEnableDisable ( t , tester ) } )
t . Run ( "testExcludeHiddenCommand/" + tester . ShellName ( ) , func ( t * testing . T ) { testExcludeHiddenCommand ( t , tester ) } )
t . Run ( "testUpdate/" + tester . ShellName ( ) , func ( t * testing . T ) { testUpdate ( t , tester ) } )
t . Run ( "testAdvancedQuery/" + tester . ShellName ( ) , func ( t * testing . T ) { testAdvancedQuery ( t , tester ) } )
2022-11-03 21:16:45 +01:00
t . Run ( "testIntegration/" + tester . ShellName ( ) , func ( t * testing . T ) { testIntegration ( t , tester , Online ) } )
t . Run ( "testIntegration/offline/" + tester . ShellName ( ) , func ( t * testing . T ) { testIntegration ( t , tester , Offline ) } )
2022-04-18 04:54:17 +02:00
t . Run ( "testIntegrationWithNewDevice/" + tester . ShellName ( ) , func ( t * testing . T ) { testIntegrationWithNewDevice ( t , tester ) } )
t . Run ( "testHishtoryBackgroundSaving/" + tester . ShellName ( ) , func ( t * testing . T ) { testHishtoryBackgroundSaving ( t , tester ) } )
t . Run ( "testDisplayTable/" + tester . ShellName ( ) , func ( t * testing . T ) { testDisplayTable ( t , tester ) } )
2022-04-19 08:07:39 +02:00
t . Run ( "testTableDisplayCwd/" + tester . ShellName ( ) , func ( t * testing . T ) { testTableDisplayCwd ( t , tester ) } )
2022-04-19 07:07:10 +02:00
t . Run ( "testTimestampsAreReasonablyCorrect/" + tester . ShellName ( ) , func ( t * testing . T ) { testTimestampsAreReasonablyCorrect ( t , tester ) } )
2022-05-02 04:37:26 +02:00
t . Run ( "testRequestAndReceiveDbDump/" + tester . ShellName ( ) , func ( t * testing . T ) { testRequestAndReceiveDbDump ( t , tester ) } )
2022-06-05 07:27:04 +02:00
t . Run ( "testInstallViaPythonScript/" + tester . ShellName ( ) , func ( t * testing . T ) { testInstallViaPythonScript ( t , tester ) } )
2022-06-06 03:05:06 +02:00
t . Run ( "testExportWithQuery/" + tester . ShellName ( ) , func ( t * testing . T ) { testExportWithQuery ( t , tester ) } )
2022-06-06 03:26:02 +02:00
t . Run ( "testHelpCommand/" + tester . ShellName ( ) , func ( t * testing . T ) { testHelpCommand ( t , tester ) } )
2022-09-05 03:37:46 +02:00
t . Run ( "testReuploadHistoryEntries/" + tester . ShellName ( ) , func ( t * testing . T ) { testReuploadHistoryEntries ( t , tester ) } )
2022-09-22 05:09:49 +02:00
t . Run ( "testHishtoryOffline/" + tester . ShellName ( ) , func ( t * testing . T ) { testHishtoryOffline ( t , tester ) } )
2022-09-17 20:49:31 +02:00
t . Run ( "testInitialHistoryImport/" + tester . ShellName ( ) , func ( t * testing . T ) { testInitialHistoryImport ( t , tester ) } )
2022-11-03 21:16:45 +01:00
t . Run ( "testLocalRedaction/" + tester . ShellName ( ) , func ( t * testing . T ) { testLocalRedaction ( t , tester , Online ) } )
t . Run ( "testLocalRedaction/offline/" + tester . ShellName ( ) , func ( t * testing . T ) { testLocalRedaction ( t , tester , Offline ) } )
2022-09-20 07:49:48 +02:00
t . Run ( "testRemoteRedaction/" + tester . ShellName ( ) , func ( t * testing . T ) { testRemoteRedaction ( t , tester ) } )
2022-09-23 06:45:20 +02:00
t . Run ( "testMultipleUsers/" + tester . ShellName ( ) , func ( t * testing . T ) { testMultipleUsers ( t , tester ) } )
2022-10-16 21:43:16 +02:00
t . Run ( "testConfigGetSet/" + tester . ShellName ( ) , func ( t * testing . T ) { testConfigGetSet ( t , tester ) } )
2022-10-24 04:29:29 +02:00
t . Run ( "testHandleUpgradedFeatures/" + tester . ShellName ( ) , func ( t * testing . T ) { testHandleUpgradedFeatures ( t , tester ) } )
2022-10-31 01:55:48 +01:00
t . Run ( "testCustomColumns/" + tester . ShellName ( ) , func ( t * testing . T ) { testCustomColumns ( t , tester ) } )
2022-11-03 03:41:49 +01:00
t . Run ( "testUninstall/" + tester . ShellName ( ) , func ( t * testing . T ) { testUninstall ( t , tester ) } )
2023-09-14 05:29:06 +02:00
t . Run ( "testPresaving/" + tester . ShellName ( ) , func ( t * testing . T ) { testPresaving ( t , tester ) } )
2023-09-02 00:55:13 +02:00
runTestsWithRetries ( t , "testControlR/" + tester . ShellName ( ) , func ( t testing . TB ) { testControlR ( t , tester , tester . ShellName ( ) , Online ) } )
2022-04-18 04:54:17 +02:00
}
2023-09-02 00:55:13 +02:00
runTestsWithRetries ( t , "testControlR/offline/bash" , func ( t testing . TB ) { testControlR ( t , bashTester { } , "bash" , Offline ) } )
runTestsWithRetries ( t , "testControlR/fish" , func ( t testing . TB ) { testControlR ( t , bashTester { } , "fish" , Online ) } )
2023-09-10 00:17:55 +02:00
runTestsWithExtraRetries ( t , "testTui/search" , testTui_search , 10 )
2023-09-02 00:42:02 +02:00
runTestsWithRetries ( t , "testTui/general" , testTui_general )
runTestsWithRetries ( t , "testTui/scroll" , testTui_scroll )
runTestsWithRetries ( t , "testTui/resize" , testTui_resize )
runTestsWithRetries ( t , "testTui/delete" , testTui_delete )
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
}
2023-08-30 08:23:21 +02:00
func runTestsWithRetries ( parentT * testing . T , testName string , testFunc func ( t testing . TB ) ) {
numRetries := 3
2023-09-05 06:21:13 +02:00
if testutils . IsGithubAction ( ) {
numRetries = 5
}
2023-09-05 03:17:02 +02:00
runTestsWithExtraRetries ( parentT , testName , testFunc , numRetries )
}
func runTestsWithExtraRetries ( parentT * testing . T , testName string , testFunc func ( t testing . TB ) , numRetries int ) {
2023-08-30 08:23:21 +02:00
for i := 1 ; i <= numRetries ; i ++ {
2023-09-18 08:07:10 +02:00
rt := & retryingTester { nil , i == numRetries , true , testName , numRetries }
2023-08-30 08:23:21 +02:00
parentT . Run ( fmt . Sprintf ( "%s/%d" , testName , i ) , func ( t * testing . T ) {
rt . T = t
testFunc ( rt )
} )
if rt . succeeded {
2023-09-08 03:30:47 +02:00
if GLOBAL_STATSD != nil {
2023-09-08 07:03:03 +02:00
GLOBAL_STATSD . Incr ( "test_status" , [ ] string { "result:passed" , "test:" + testName , "os:" + runtime . GOOS } , 1.0 )
GLOBAL_STATSD . Distribution ( "test_retry_count" , float64 ( i ) , [ ] string { "test:" + testName , "os:" + runtime . GOOS } , 1.0 )
2023-09-08 03:30:47 +02:00
}
2023-08-30 08:23:21 +02:00
break
2023-09-08 03:30:47 +02:00
} else {
if GLOBAL_STATSD != nil {
2023-09-08 07:03:03 +02:00
GLOBAL_STATSD . Incr ( "test_status" , [ ] string { "result:failed" , "test:" + testName , "os:" + runtime . GOOS } , 1.0 )
2023-09-08 03:30:47 +02:00
}
2023-08-30 08:23:21 +02:00
}
}
}
type retryingTester struct {
* testing . T
isFinalRun bool
succeeded bool
2023-09-09 04:40:10 +02:00
testName string
2023-09-18 08:07:10 +02:00
numRetries int
2023-08-30 08:23:21 +02:00
}
func ( t * retryingTester ) Fatalf ( format string , args ... any ) {
t . T . Helper ( )
t . succeeded = false
if t . isFinalRun {
2023-09-09 04:40:10 +02:00
if GLOBAL_STATSD != nil {
GLOBAL_STATSD . Incr ( "test_failure" , [ ] string { "test:" + t . testName , "os:" + runtime . GOOS } , 1.0 )
2023-09-18 08:07:10 +02:00
GLOBAL_STATSD . Distribution ( "test_retry_count" , float64 ( t . numRetries ) , [ ] string { "test:" + t . testName , "os:" + runtime . GOOS } , 1.0 )
2023-09-09 04:40:10 +02:00
}
2023-08-30 08:23:21 +02:00
t . T . Fatalf ( format , args ... )
} else {
2023-09-03 02:49:31 +02:00
testutils . TestLog ( t . T , fmt . Sprintf ( "retryingTester: Ignoring fatalf for non-final run: %#v" , fmt . Sprintf ( format , args ... ) ) )
2023-08-30 08:23:21 +02:00
}
t . SkipNow ( )
}
2023-09-03 02:49:31 +02:00
func ( t * retryingTester ) Errorf ( format string , args ... any ) {
t . T . Helper ( )
t . succeeded = false
if t . isFinalRun {
t . T . Errorf ( format , args ... )
} else {
testutils . TestLog ( t . T , fmt . Sprintf ( "retryingTester: Ignoring errorf for non-final run: %#v" , fmt . Sprintf ( format , args ... ) ) )
}
t . SkipNow ( )
}
func ( t * retryingTester ) FailNow ( ) {
t . succeeded = false
if t . isFinalRun {
t . T . FailNow ( )
} else {
testutils . TestLog ( t . T , "retryingTester: Ignoring FailNow for non-final run" )
// Still terminate execution via SkipNow() since FailNow() means we should stop the current test
t . T . SkipNow ( )
}
}
func ( t * retryingTester ) Fail ( ) {
t . succeeded = false
if t . isFinalRun {
t . T . Fail ( )
} else {
testutils . TestLog ( t . T , "retryingTester: Ignoring Fail for non-final run" )
}
}
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
// Clear all local state
2022-10-28 06:53:47 +02:00
testutils . ResetLocalState ( t )
2022-04-06 08:31:24 +02:00
// Install it again
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 03:18:46 +02:00
// Clear local state again
2022-10-28 06:53:47 +02:00
testutils . ResetLocalState ( t )
2022-04-07 02:47:21 +02:00
// Install it a 3rd time
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" )
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 )
2022-10-28 06:53:47 +02:00
testutils . Check ( 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" )
2022-11-05 05:05:44 +01:00
expectedOutputWithoutKey := "hishtory status\nhishtory query\nls /a\nls /bar\nls /foo\necho foo\necho bar\nhishtory enable\necho thisisrecorded\nhishtory query\nhishtory query foo\necho hello | grep complex | sed s/h/i/g; echo baz && echo \"fo 'o\" # 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 installHishtory ( t testing . TB , tester shellTester , userSecret string ) string {
2022-11-13 02:05:50 +01:00
out := tester . RunInteractiveShell ( t , ` /tmp/client install ` + userSecret )
2022-04-06 08:31:24 +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-04-06 08:31:24 +02:00
}
2022-04-08 07:53:39 +02:00
return matches [ 1 ]
}
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" )
}
}
2023-08-30 08:23:21 +02:00
func assertOnlineStatus ( t testing . TB , onlineStatus OnlineStatus ) {
2022-11-03 21:16:45 +01:00
config := hctx . GetConf ( hctx . MakeContext ( ) )
if onlineStatus == Online && config . IsOffline == true {
t . Fatalf ( "We're supposed to be online, yet config.IsOffline=%#v (config=%#v)" , config . IsOffline , config )
}
if onlineStatus == Offline && config . IsOffline == false {
t . Fatalf ( "We're supposed to be offline, yet config.IsOffline=%#v (config=%#v)" , config . IsOffline , config )
}
}
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 `
line2Matcher := hostnameMatcher + tableDividerMatcher + pathMatcher + tableDividerMatcher + datetimeMatcher + tableDividerMatcher + runtimeMatcher + tableDividerMatcher + exitCodeMatcher + tableDividerMatcher + pipefailMatcher + tableDividerMatcher + ` \n `
line3Matcher := hostnameMatcher + tableDividerMatcher + pathMatcher + tableDividerMatcher + datetimeMatcher + tableDividerMatcher + runtimeMatcher + tableDividerMatcher + exitCodeMatcher + tableDividerMatcher + ` echo thisisrecorded ` + tableDividerMatcher + ` \n `
2022-09-17 21:49:27 +02:00
match , err := regexp . MatchString ( line3Matcher , out )
2022-10-28 06:53:47 +02:00
testutils . Check ( t , err )
2022-09-17 21:19:29 +02:00
if ! match {
t . Fatalf ( "output is missing the row for `echo thisisrecorded`: %v" , out )
}
2022-09-21 08:46:17 +02:00
match , err = regexp . MatchString ( line1Matcher , out )
2022-10-28 06:53:47 +02:00
testutils . Check ( t , err )
2022-09-21 08:46:17 +02:00
if ! match {
t . Fatalf ( "output is missing the headings: %v" , out )
}
match , err = regexp . MatchString ( line2Matcher , out )
2022-10-28 06:53:47 +02:00
testutils . Check ( t , err )
2022-09-21 08:46:17 +02:00
if ! match {
t . Fatalf ( "output is missing the pipefail: %v" , out )
}
2022-09-17 21:49:27 +02:00
match , err = regexp . MatchString ( line1Matcher + line2Matcher + line3Matcher , out )
2022-10-28 06:53:47 +02:00
testutils . Check ( t , err )
2022-09-17 21:49:27 +02:00
if ! match {
t . Fatalf ( "output doesn't match the expected table: %v" , 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" )
2022-04-16 03:24:58 +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 )
}
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , complexCommand , "hishtory query doesn't contain the expected complex command" )
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-08-29 05:55:15 +02:00
require . Contains ( t , out , "echo querybydir" , "hishtory query doesn't contain result matching echo querybydir" )
2022-04-13 06:24:25 +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 )
}
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" )
2022-04-08 07:53:39 +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 )
}
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" )
2022-06-06 03:05:06 +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 )
}
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" )
2022-04-09 03:23:17 +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 )
}
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 ` )
2022-04-09 03:23:17 +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 )
}
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` before:2125-07-02 cwd:tmp ` )
2022-04-09 03:23:17 +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 )
}
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` before:2125-07-02 cwd:mp ` )
2022-04-09 03:23:17 +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 )
}
// Query based on after: and cwd:
2022-04-19 08:07:39 +02:00
out = hishtoryQuery ( t , tester , ` after:1980-07-02 cwd:/tmp ` )
if strings . Count ( out , "\n" ) != 3 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
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" )
2022-04-18 08:31:08 +02:00
if strings . Count ( out , "\n" ) != 4 {
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" )
2022-04-18 08:31:08 +02:00
if strings . Count ( out , "\n" ) != 4 {
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
2022-04-18 04:54:17 +02:00
func testUpdate ( t * testing . T , tester shellTester ) {
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-09-28 07:03:37 +02:00
if skipSlowTests ( ) {
t . Skip ( "skipping slow tests" )
}
2022-04-09 21:50:01 +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-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 ` )
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-09 21:50:01 +02:00
t . Fatalf ( "status command has unexpected output: %#v" , out )
}
// Update
2022-04-18 04:54:17 +02:00
out = tester . RunInteractiveShell ( t , ` hishtory update ` )
2022-06-05 06:42:40 +02:00
isExpected , err := regexp . MatchString ( ` Successfully updated hishtory from v0[.]Unknown to v0.\d+ ` , out )
2023-09-09 21:28:01 +02:00
require . NoError ( t , err , "regex failure" )
2022-04-17 08:00:04 +02:00
if ! isExpected {
t . Fatalf ( "hishtory update returned unexpected out=%#v" , out )
}
2023-08-29 05:55:15 +02:00
require . NotContains ( t , out , "skipping SLSA validation" )
2022-04-17 08:00:04 +02:00
// Update again and assert that it skipped the update
2022-04-18 04:54:17 +02:00
out = tester . RunInteractiveShell ( t , ` hishtory update ` )
2022-04-17 08:00:04 +02:00
if strings . Count ( out , "\n" ) != 1 || ! strings . Contains ( out , "is already installed" ) {
t . Fatalf ( "repeated hishtory update didn't skip the update, out=%#v" , out )
}
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 ) )
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'" )
2022-04-19 08:07:39 +02:00
expectedOutput := "echo hello\nhishtory status\nhishtory update\nhishtory update\nhishtory status\n"
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-11-01 19:03:49 +01:00
// TODO: write a test that updates from v.prev to latest rather than v.Unknown to latest
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 )
}
if strings . Count ( out , "echo mycommand" ) != 25 {
t . Fatalf ( "hishtory query has the wrong number of commands=%d, out=%#v" , strings . Count ( out , "echo mycommand" ) , out )
}
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 ) )
2022-04-15 05:18:49 +02:00
if strings . Count ( out , "\n" ) != 2 {
t . Fatalf ( "hishtory query #%d has the wrong number of lines=%d, out=%#v" , i , strings . Count ( out , "\n" ) , out )
}
if strings . Count ( out , "echo mycommand" ) != 1 {
t . Fatalf ( "hishtory query #%d has the wrong number of commands=%d, out=%#v" , i , strings . Count ( out , "echo mycommand" ) , out )
}
}
}
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
2022-04-15 05:18:49 +02:00
hishtory disable
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 ) )
2022-04-15 05:18:49 +02:00
if strings . Count ( out , "\n" ) != 2 {
t . Fatalf ( "hishtory query #%d has the wrong number of lines=%d, out=%#v" , i , strings . Count ( out , "\n" ) , out )
}
if strings . Count ( out , "echo mycommand" ) != 1 {
t . Fatalf ( "hishtory query #%d has the wrong number of commands=%d, out=%#v" , i , strings . Count ( out , "echo mycommand" ) , out )
}
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 )
}
}
2022-04-17 20:24:55 +02:00
func getPidofCommand ( ) string {
2022-11-05 07:14:34 +01:00
if runtime . GOOS == "darwin" {
2022-04-17 20:24:55 +02:00
// MacOS doesn't have pidof by default
return "pgrep"
}
return "pidof"
}
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
}
2023-08-30 08:23:21 +02:00
func hishtoryQuery ( t testing . TB , tester shellTester , query string ) string {
2022-04-18 04:54:17 +02:00
return tester . RunInteractiveShell ( t , "hishtory query " + query )
2022-04-15 05:18:49 +02:00
}
2023-08-30 08:23:21 +02:00
func manuallySubmitHistoryEntry ( t testing . TB , userSecret string , entry data . HistoryEntry ) {
2022-04-11 02:38:20 +02:00
encEntry , err := data . EncryptHistoryEntry ( userSecret , entry )
2022-10-28 06:53:47 +02:00
testutils . Check ( t , err )
2022-09-20 07:49:48 +02:00
if encEntry . Date != entry . EndTime {
t . Fatalf ( "encEntry.Date does not match the entry" )
}
2022-04-11 02:38:20 +02:00
jsonValue , err := json . Marshal ( [ ] shared . EncHistoryEntry { encEntry } )
2022-10-28 06:53:47 +02:00
testutils . Check ( t , err )
2023-09-21 20:35:24 +02:00
require . NotEqual ( t , "" , entry . DeviceId )
resp , err := http . Post ( "http://localhost:8080/api/v1/submit?source_device_id=" + entry . DeviceId , "application/json" , bytes . NewBuffer ( jsonValue ) )
2022-10-28 06:53:47 +02:00
testutils . Check ( t , err )
2022-04-11 02:38:20 +02:00
if resp . StatusCode != 200 {
t . Fatalf ( "failed to submit result to backend, status_code=%d" , resp . StatusCode )
2022-04-09 08:47:13 +02:00
}
2023-09-21 20:35:24 +02:00
defer resp . Body . Close ( )
respBody , err := io . ReadAll ( resp . Body )
if err != nil {
t . Fatalf ( "failed to read resp.Body: %v" , err )
}
submitResp := shared . SubmitResponse { }
err = json . Unmarshal ( respBody , & submitResp )
if err != nil {
t . Fatalf ( "failed to deserialize SubmitResponse: %v" , err )
}
2022-04-09 08:47:13 +02:00
}
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" )
if strings . Count ( out , "\n" ) != 2 {
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" )
if strings . Count ( out , "\n" ) != 2 {
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" )
if strings . Count ( out , "\n" ) != 2 {
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-09-23 03:09:51 +02:00
if runtime . GOOS == "darwin" {
t . Skip ( "skip testing background saving since it is flakey on MacOs" )
2022-04-28 20:26:55 +02:00
}
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" )
2022-10-28 06:53:47 +02:00
testutils . Check ( 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" ` )
2022-10-30 02:53:40 +02:00
testutils . Check ( 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
2022-09-21 07:28:40 +02:00
config := hctx . GetConf ( hctx . MakeContext ( ) )
2022-06-05 08:03:05 +02:00
deviceId1 := config . DeviceId
2023-09-11 22:34:22 +02:00
respBytes , err := lib . ApiGet ( "/api/v1/get-dump-requests?user_id=" + data . UserId ( secretKey ) + "&device_id=" + deviceId1 )
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" )
if strings . Count ( out , "\n" ) != 3 {
t . Fatalf ( "hishtory query has unexpected number of lines: out=%#v" , out )
}
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
2022-12-12 05:31:50 +01:00
_ , err = lib . ApiGet ( "/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-09-11 22:34:22 +02:00
respBytes , err = lib . ApiGet ( "/api/v1/get-dump-requests?user_id=" + data . UserId ( secretKey ) + "&device_id=" + deviceId1 )
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" )
if strings . Count ( out , "\n" ) != 1 {
t . Fatalf ( "hishtory query has unexpected number of lines, should contain no entries: out=%#v" , out )
}
if strings . Contains ( out , "echo hello" ) || strings . Contains ( "echo other" , out ) {
t . Fatalf ( "hishtory query contains unexpected command, out=%#v" , out )
}
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-09-11 22:34:22 +02:00
respBytes , err = lib . ApiGet ( "/api/v1/get-dump-requests?user_id=" + data . UserId ( secretKey ) + "&device_id=" + deviceId1 )
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" )
if strings . Count ( out , "\n" ) != 5 {
t . Fatalf ( "hishtory query has unexpected number of lines=%d: out=%#v" , strings . Count ( out , "\n" ) , out )
}
expected := [ ] string { "echo hello" , "echo other" }
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
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 ) {
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 ) ) )
2022-12-17 07:22:57 +01:00
testInstallViaPythonScriptChild ( t , bashTester { } )
}
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 )
}
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
downloadData , err := lib . GetDownloadData ( )
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 )
}
}
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 ` )
if out != "ls /foo\necho foo\nhishtory query foo\n" {
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
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" )
if out != "ls -Slah\n" {
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" )
if out != "echo foo\n" {
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
tester . RunInteractiveShell ( t , ` echo 2; export HISHTORY_SIMULATE_NETWORK_ERROR=1; echo 3 ` )
// 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" )
expectedOutput := "echo 1\n"
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 ( )
tester . RunInteractiveShell ( t , ` unset HISHTORY_SIMULATE_NETWORK_ERROR; echo 4 ` )
// 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" )
expectedOutput = "echo 1\necho 2; export HISHTORY_SIMULATE_NETWORK_ERROR=1; echo 3\nunset HISHTORY_SIMULATE_NETWORK_ERROR; echo 4\n"
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" )
expectedOutput = "echo 1\necho 2; export HISHTORY_SIMULATE_NETWORK_ERROR=1; echo 3\nunset HISHTORY_SIMULATE_NETWORK_ERROR; echo 4\n"
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
out := tester . RunInteractiveShell ( t , ` export HISHTORY_SIMULATE_NETWORK_ERROR=1; hishtory export | grep -v pipefail ` )
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 ( )
out = tester . RunInteractiveShell ( t , ` export HISHTORY_SIMULATE_NETWORK_ERROR=1; hishtory export | grep -v pipefail ` )
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-11-13 01:30:59 +01:00
// TODO: tests for hishtory import
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 ] )
expectedOutput := strings . ReplaceAll ( ` echo UUID - foo
echo UUID - bar
echo UUID - fishcommand
` , "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 ` )
2022-09-22 04:58:10 +02:00
if out != "Permanently deleting 2 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 ` )
2022-10-28 06:53:47 +02:00
testutils . Check ( t , err )
2022-09-23 03:22:06 +02:00
if out != "This will permanently delete 1 entries, are you sure? [y/N]" {
t . Fatalf ( "hishtory redact gave unexpected output=%#v" , out )
}
// 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 )
2022-09-22 04:58:10 +02:00
if out != "Permanently deleting 2 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 )
}
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 ( )
2022-10-28 06:53:47 +02:00
testutils . Check ( 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 ( )
2022-10-28 06:53:47 +02:00
testutils . Check ( t , err )
2022-12-17 07:22:57 +01:00
err = os . WriteFile ( path . Join ( homedir , data . GetHishtoryPath ( ) , data . CONFIG_PATH ) , configContents , 0 o644 )
2022-10-28 06:53:47 +02:00
testutils . Check ( 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 ` )
2022-10-28 06:53:47 +02:00
testutils . Check ( t , err )
2022-10-24 04:29:29 +02:00
_ , err = tester . RunInteractiveShellRelaxed ( t , ` hishtory disable ` )
2022-10-28 06:53:47 +02:00
testutils . Check ( 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
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
2022-10-31 02:40:25 +01:00
testutils . Check ( 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
} )
2022-11-01 20:00:40 +01:00
if ! strings . Contains ( out , "Welcome to fish, the friendly interactive shell" ) || ! strings . Contains ( out , "foo" ) || ! strings . Contains ( out , "bar" ) || ! strings . Contains ( out , "baz" ) {
t . Fatalf ( "fish output looks wrong" )
2022-11-01 18:37:01 +01:00
}
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 ` )
2022-11-01 20:00:40 +01:00
expectedOutput := "echo foo\necho bar\necho \"foo\"\nls /tmp/ &\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-04-30 17:50:17 +02:00
// TODO(ddworken): Run TestTui in online and offline mode
2023-09-02 00:42:02 +02:00
func setupTestTui ( t testing . TB ) ( shellTester , string , * gorm . DB ) {
2022-10-21 09:16:25 +02:00
tester := zshTester { }
2023-09-02 00:42:02 +02:00
userSecret := installHishtory ( t , tester , "" )
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 ~/" )
testutils . Check ( t , db . Create ( e1 ) . Error )
manuallySubmitHistoryEntry ( t , userSecret , e1 )
e2 := testutils . MakeFakeHistoryEntry ( "echo 'aaaaaa bbbb'" )
testutils . Check ( t , db . Create ( e2 ) . Error )
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-09-02 00:42:02 +02:00
func testTui_resize ( t testing . TB ) {
// Setup
defer testutils . BackupAndRestore ( t ) ( )
tester , userSecret , _ := setupTestTui ( t )
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
} )
out = strings . TrimSpace ( strings . Split ( out , "hishtory tquery" ) [ 1 ] )
2023-09-02 00:42:02 +02:00
testutils . CompareGoldens ( t , out , "TestTui-SmallTerminal" )
2022-11-27 17:54:34 +01:00
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
} )
out = strings . TrimSpace ( strings . Split ( out , "hishtory tquery" ) [ 1 ] )
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" } ,
} )
out = strings . TrimSpace ( strings . Split ( out , "hishtory tquery" ) [ 1 ] )
testutils . CompareGoldens ( t , out , "TestTui-LongQuery" )
2023-09-02 00:42:02 +02:00
// Assert there are no leaked connections
assertNoLeakedConnections ( t )
}
func testTui_scroll ( t testing . TB ) {
// Setup
defer testutils . BackupAndRestore ( t ) ( )
tester , userSecret , _ := setupTestTui ( t )
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" ,
} )
out = strings . TrimSpace ( strings . Split ( out , "hishtory tquery" ) [ 1 ] )
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
manuallySubmitHistoryEntry ( t , userSecret , testutils . MakeFakeHistoryEntry ( "echo '1234567890qwertyuiopasdfghjklzxxcvbnm0987654321_0_1234567890qwertyuiopasdfghjklzxxcvbnm0987654321_1_1234567890qwertyuiopasdfghjklzxxcvbnm0987654321_2_1234567890qwertyuiopasdfghjklzxxcvbnm0987654321'" ) )
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-09-02 00:42:02 +02:00
out = strings . TrimSpace ( strings . Split ( out , "hishtory tquery" ) [ 1 ] )
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
} )
out = strings . TrimSpace ( strings . Split ( out , "hishtory tquery" ) [ 1 ] )
2023-09-02 00:42:02 +02:00
testutils . CompareGoldens ( t , out , "TestTui-RightScrollTwo" )
2022-12-19 07:02:29 +01:00
2023-09-02 00:42:02 +02:00
// Assert there are no leaked connections
assertNoLeakedConnections ( t )
}
func testTui_delete ( t testing . TB ) {
// Setup
defer testutils . BackupAndRestore ( t ) ( )
tester , userSecret , _ := setupTestTui ( t )
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
} )
out = strings . TrimSpace ( strings . Split ( out , "hishtory tquery" ) [ 1 ] )
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" ,
} )
out = strings . TrimSpace ( strings . Split ( out , "hishtory tquery" ) [ 1 ] )
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" ,
} )
out = strings . TrimSpace ( strings . Split ( out , "hishtory tquery" ) [ 1 ] )
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
out = captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
} )
out = strings . TrimSpace ( strings . Split ( out , "hishtory tquery" ) [ 1 ] )
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-09-02 01:28:41 +02:00
func testTui_search ( t testing . TB ) {
2023-09-02 00:42:02 +02:00
// Setup
defer testutils . BackupAndRestore ( t ) ( )
tester , _ , _ := setupTestTui ( t )
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-09-08 07:18:16 +02:00
out = captureTerminalOutput ( t , tester , [ ] string {
2023-02-12 08:09:40 +01:00
"hishtory SPACE tquery ENTER" ,
2023-09-02 00:42:02 +02:00
"ls" ,
2023-02-12 08:09:40 +01:00
} )
out = strings . TrimSpace ( strings . Split ( out , "hishtory tquery" ) [ 1 ] )
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
{ Keys : "ls" , ExtraDelay : 1.0 } ,
{ Keys : "ENTER" } ,
2023-09-02 00:42:02 +02:00
} )
out = strings . Split ( strings . TrimSpace ( strings . Split ( out , "hishtory tquery" ) [ 1 ] ) , "\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-08 19:09:01 +02:00
{ Keys : "hishtory SPACE tquery SPACE foo: ENTER" , ExtraDelay : 1.0 } ,
{ Keys : "ls" , ExtraDelay : 1.0 } ,
2023-02-14 05:24:20 +01:00
} )
out = strings . TrimSpace ( strings . Split ( out , "hishtory tquery" ) [ 1 ] )
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 {
{ Keys : "hishtory SPACE tquery ENTER" } ,
2023-09-09 20:50:07 +02:00
// ExtraDelay to ensure that the search for 'ls' finishes before we make it invalid by adding ':'
{ Keys : "ls" , ExtraDelay : 1.0 } ,
{ Keys : ":" } ,
2023-09-02 00:42:02 +02:00
} )
out = strings . TrimSpace ( strings . Split ( out , "hishtory tquery" ) [ 1 ] )
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" ,
} )
out = strings . TrimSpace ( strings . Split ( out , "hishtory tquery" ) [ 1 ] )
testutils . CompareGoldens ( t , out , "TestTui-InvalidSearchBecomesValid" )
2023-09-02 01:28:41 +02:00
}
func testTui_general ( t testing . TB ) {
// Setup
defer testutils . BackupAndRestore ( t ) ( )
tester , _ , _ := setupTestTui ( t )
// Check the initial output when there is no search
out := captureTerminalOutput ( t , tester , [ ] string { "hishtory SPACE tquery ENTER" } )
if len ( strings . Split ( out , "hishtory tquery" ) ) != 2 {
t . Fatalf ( "failed to split out=%#v" , out )
}
out = strings . TrimSpace ( strings . Split ( out , "hishtory tquery" ) [ 1 ] )
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:" )
if ! testutils . IsGithubAction ( ) {
testutils . CompareGoldens ( t , out , "TestTui-Exit" )
}
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" ,
} )
out = strings . TrimSpace ( strings . Split ( out , "hishtory tquery" ) [ 1 ] )
testutils . CompareGoldens ( t , out , "TestTui-HelpPage" )
// Test closing the help page
out = captureTerminalOutput ( t , tester , [ ] string {
"hishtory SPACE tquery ENTER" ,
"C-h C-h" ,
} )
out = strings . TrimSpace ( strings . Split ( out , "hishtory tquery" ) [ 1 ] )
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" ,
} )
out = strings . Split ( strings . TrimSpace ( strings . Split ( out , "hishtory tquery" ) [ 1 ] ) , "\n" ) [ 0 ]
testutils . CompareGoldens ( t , out , "TestTui-SelectAndCd" )
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" ,
} )
out = strings . TrimSpace ( strings . Split ( out , "hishtory tquery" ) [ 1 ] )
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-08-30 08:23:21 +02:00
func captureTerminalOutput ( t testing . TB , tester shellTester , commands [ ] string ) string {
2022-10-22 20:42:37 +02:00
return captureTerminalOutputWithShellName ( t , tester , tester . ShellName ( ) , commands )
}
2023-09-08 18:53:39 +02:00
func captureTerminalOutputWithComplexCommands ( t testing . TB , tester shellTester , commands [ ] TmuxCommand ) string {
return captureTerminalOutputWithShellNameAndDimensions ( t , tester , tester . ShellName ( ) , 200 , 50 , commands )
}
2022-12-18 09:34:25 +01:00
type TmuxCommand struct {
2023-09-08 18:53:39 +02:00
Keys string
ResizeX int
ResizeY int
ExtraDelay float64
2022-12-18 09:34:25 +01:00
}
2023-08-30 08:23:21 +02:00
func captureTerminalOutputWithShellName ( t testing . TB , tester shellTester , overriddenShellName string , commands [ ] string ) string {
2022-12-18 09:34:25 +01:00
sCommands := make ( [ ] TmuxCommand , 0 )
for _ , command := range commands {
sCommands = append ( sCommands , TmuxCommand { Keys : command } )
}
return captureTerminalOutputWithShellNameAndDimensions ( t , tester , overriddenShellName , 200 , 50 , sCommands )
2022-11-03 02:50:27 +01:00
}
2023-08-30 08:23:21 +02:00
func captureTerminalOutputWithShellNameAndDimensions ( t testing . TB , tester shellTester , overriddenShellName string , width , height int , commands [ ] TmuxCommand ) string {
2023-09-01 20:23:03 +02:00
sleepAmount := "0.1"
if runtime . GOOS == "linux" {
sleepAmount = "0.2"
}
if overriddenShellName == "fish" {
// Fish is considerably slower so this is sadly necessary
sleepAmount = "0.5"
}
2023-09-05 03:09:11 +02:00
if testutils . IsGithubAction ( ) {
2023-09-01 20:23:03 +02:00
sleepAmount = "0.5"
2023-08-30 08:47:22 +02:00
}
2022-10-22 09:17:21 +02:00
fullCommand := ""
fullCommand += " tmux kill-session -t foo || true\n"
2022-11-03 02:50:27 +01:00
fullCommand += fmt . Sprintf ( " tmux -u new-session -d -x %d -y %d -s foo %s\n" , width , height , overriddenShellName )
2022-10-23 23:58:47 +02:00
fullCommand += " sleep 1\n"
2022-10-22 20:42:37 +02:00
if overriddenShellName == "bash" {
2022-11-12 04:06:00 +01:00
fullCommand += " tmux send -t foo SPACE source SPACE ~/.bashrc ENTER\n"
2022-10-22 09:17:21 +02:00
}
2022-10-22 20:42:37 +02:00
fullCommand += " sleep " + sleepAmount + "\n"
2022-10-22 09:17:21 +02:00
for _ , cmd := range commands {
2022-12-18 10:01:05 +01:00
if cmd . Keys != "" {
fullCommand += " tmux send -t foo -- "
fullCommand += cmd . Keys
fullCommand += "\n"
}
if cmd . ResizeX != 0 && cmd . ResizeY != 0 {
fullCommand += fmt . Sprintf ( " tmux resize-window -t foo -x %d -y %d\n" , cmd . ResizeX , cmd . ResizeY )
}
2023-09-08 18:53:39 +02:00
if cmd . ExtraDelay != 0 {
fullCommand += fmt . Sprintf ( " sleep %f\n" , cmd . ExtraDelay )
}
2022-10-22 20:42:37 +02:00
fullCommand += " sleep " + sleepAmount + "\n"
2022-10-22 09:17:21 +02:00
}
2023-09-01 20:23:03 +02:00
fullCommand += " sleep 2.5\n"
if testutils . IsGithubAction ( ) {
fullCommand += " sleep 2.5\n"
}
2022-11-12 16:20:05 +01:00
fullCommand += " tmux capture-pane -t foo -p\n"
2022-10-22 09:17:21 +02:00
fullCommand += " tmux kill-session -t foo\n"
2022-11-16 08:20:19 +01:00
testutils . TestLog ( t , "Running tmux command: " + fullCommand )
2022-10-22 09:17:21 +02:00
return strings . TrimSpace ( tester . RunInteractiveShell ( t , fullCommand ) )
2022-10-21 09:14:28 +02:00
}
2023-09-02 00:55:13 +02:00
func testControlR ( t testing . TB , 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 ` )
// Insert a few hishtory entries
db := hctx . GetDb ( hctx . MakeContext ( ) )
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
2022-11-16 08:20:19 +01:00
testutils . Check ( t , db . Create ( e1 ) . Error )
testutils . Check ( t , db . Create ( testutils . MakeFakeHistoryEntry ( "ls ~/foo/" ) ) . Error )
testutils . Check ( t , db . Create ( testutils . MakeFakeHistoryEntry ( "ls ~/bar/" ) ) . Error )
testutils . Check ( t , db . Create ( testutils . MakeFakeHistoryEntry ( "echo 'aaaaaa bbbb'" ) ) . Error )
testutils . Check ( t , db . Create ( testutils . MakeFakeHistoryEntry ( "echo 'bar' &" ) ) . Error )
// Check that they're there
var historyEntries [ ] * data . HistoryEntry
db . Model ( & data . HistoryEntry { } ) . Find ( & historyEntries )
if len ( historyEntries ) != 5 {
t . Fatalf ( "expected to find 5 history entries, actual found %d: %#v" , len ( historyEntries ) , historyEntries )
}
2022-10-21 09:14:28 +02:00
// And check that the control-r binding brings up the search
2022-10-22 20:42:37 +02:00
out := captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "C-R" } )
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , "\n\n\n" , "failed to find separator" )
2022-10-28 06:20:33 +02:00
out = strings . TrimSpace ( strings . Split ( out , "\n\n\n" ) [ 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
2022-10-22 20:42:37 +02:00
out = captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "C-R" , "echo" } )
2022-10-28 07:28:43 +02:00
if strings . Contains ( out , "\n\n\n" ) {
2022-10-28 06:20:33 +02:00
out = strings . TrimSpace ( strings . Split ( out , "\n\n\n" ) [ 1 ] )
2022-10-22 09:50:34 +02:00
}
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
out = captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "C-R" , "cwd:/tmp/ SPACE ls" } )
2022-10-28 07:28:43 +02:00
if strings . Contains ( out , "\n\n\n" ) {
2022-10-28 07:07:11 +02:00
out = strings . TrimSpace ( strings . Split ( out , "\n\n\n" ) [ 1 ] )
}
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 ` )
out = captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "C-R" } )
2022-10-28 07:28:43 +02:00
if strings . Contains ( out , "\n\n\n" ) {
2022-10-28 07:11:57 +02:00
out = strings . TrimSpace ( strings . Split ( out , "\n\n\n" ) [ 1 ] )
}
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 ` )
out = captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "C-R" , "-pipefail" } )
out = strings . TrimSpace ( out )
if tester . ShellName ( ) == "bash" {
out = strings . TrimSpace ( strings . Split ( out , "\n\n\n" ) [ 1 ] )
}
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
out = captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "ls" , "C-R" } )
if strings . Contains ( out , "\n\n\n" ) {
out = strings . TrimSpace ( strings . Split ( out , "\n\n\n" ) [ 1 ] )
}
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
out = captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "e" , "C-R" , "cho" } )
if strings . Contains ( out , "\n\n\n" ) {
out = strings . TrimSpace ( strings . Split ( out , "\n\n\n" ) [ 1 ] )
}
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
out = captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "asdf" , "C-R" } )
if strings . Contains ( out , "\n\n\n" ) {
out = strings . TrimSpace ( strings . Split ( out , "\n\n\n" ) [ 1 ] )
}
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
out = captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "asdf" , "C-R" , "BSpace BSpace BSpace BSpace echo" } )
if strings . Contains ( out , "\n\n\n" ) {
out = strings . TrimSpace ( strings . Split ( out , "\n\n\n" ) [ 1 ] )
}
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
out = captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "echo" , "C-R" , "c" , "C-C" } )
if strings . Contains ( out , "\n\n\n" ) {
out = strings . TrimSpace ( strings . Split ( out , "\n\n\n" ) [ 1 ] )
}
2022-11-12 03:34:42 +01:00
if strings . Contains ( out , "Search Query" ) || strings . Contains ( out , "─────" ) || strings . Contains ( out , "Exit Code" ) {
t . Fatalf ( "hishtory is showing a table even after control-c? out=%#v" , out )
}
2022-11-12 15:46:37 +01:00
if ! testutils . IsGithubAction ( ) {
2022-11-12 03:34:42 +01:00
// This bit is broken on actions since actions run as a different user
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testControlR-ControlC-" + shellName )
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" } )
if strings . Contains ( out , "Search Query" ) || strings . Contains ( out , "─────" ) || strings . Contains ( out , "Exit Code" ) {
t . Fatalf ( "hishtory overrode control-r even when this was disabled? out=%#v" , out )
}
2022-11-12 15:46:37 +01:00
if ! testutils . IsGithubAction ( ) {
2022-10-30 02:53:40 +02:00
// This bit is broken on actions since actions run as a different user
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testControlR-" + shellName + "-Disabled" )
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 ` )
2022-11-16 08:20:19 +01:00
testutils . Check ( t , err )
// And check that the control-r bindings work again
2022-11-20 07:27:08 +01:00
out = captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "C-R" , "-pipefail SPACE -exit_code:0" } )
if strings . Contains ( out , "\n\n\n" ) {
out = strings . TrimSpace ( strings . Split ( out , "\n\n\n" ) [ 1 ] )
2022-11-16 08:20:19 +01:00
}
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
out = captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "C-R" , "Slah" } )
if strings . Contains ( out , "\n\n\n" ) {
out = strings . TrimSpace ( strings . Split ( out , "\n\n\n" ) [ 1 ] )
}
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
out = captureTerminalOutputWithShellName ( t , tester , shellName , [ ] string { "C-R" , "Slah" , "Enter" } )
if strings . Contains ( out , "\n\n\n" ) {
out = strings . TrimSpace ( strings . Split ( out , "\n\n\n" ) [ 1 ] )
}
2023-08-29 05:55:15 +02:00
require . Contains ( t , out , "-Slah" , "out has unexpected output missing the selected row" )
2022-11-20 07:27:08 +01:00
if ! testutils . IsGithubAction ( ) {
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testControlR-SelectMultiline-" + shellName )
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
tester . RunInteractiveShell ( t , ` hishtory config-set displayed-columns 'Exit Code' git_remote Command ` )
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" } )
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"
testName += "-" + runtime . GOOS
}
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , testName )
2022-10-31 01:55:48 +01:00
}
2023-09-14 05:29:06 +02:00
func testPresaving ( t * testing . T , tester shellTester ) {
// Setup
defer testutils . BackupAndRestore ( t ) ( )
userSecret := installHishtory ( t , tester , "" )
manuallySubmitHistoryEntry ( t , userSecret , testutils . MakeFakeHistoryEntry ( "table_sizing aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ) )
// Enable beta-mode since presaving is behind that feature flag
out := strings . TrimSpace ( tester . RunInteractiveShell ( t , ` hishtory config-get beta-mode ` ) )
require . Equal ( t , out , "false" )
tester . RunInteractiveShell ( t , ` hishtory config-set beta-mode true ` )
out = strings . TrimSpace ( tester . RunInteractiveShell ( t , ` hishtory config-get beta-mode ` ) )
require . Equal ( t , out , "true" )
// 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 ( "/" ) )
go tester . RunInteractiveShell ( t , ` sleep 13371337 ` )
time . Sleep ( time . Millisecond * 500 )
// Test that it shows up in hishtory export
out = tester . RunInteractiveShell ( t , ` hishtory export sleep -export ` )
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" )
// And the same for tquery
2023-09-14 06:56:58 +02:00
// out = captureTerminalOutputWithComplexCommands(t, tester,
// []TmuxCommand{
// {Keys: "hishtory SPACE tquery ENTER", ExtraDelay: 2.0},
// {Keys: "sleep SPACE 13371337 SPACE -export SPACE -tquery"}})
// out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
// testutils.CompareGoldens(t, out, "testPresaving-tquery")
//
// TODO: Debug why ^ is failing with flaky differences on Github Actions, see https://pastebin.com/BUa1btnh
2023-09-14 05:29:06 +02: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 ` )
testutils . Check ( 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 ` )
testutils . Check ( 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
2022-11-12 15:46:37 +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-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testUninstall-post-uninstall-" + tester . ShellName ( ) )
2022-11-03 03:41:49 +01:00
}
}
2022-11-12 02:17:54 +01:00
func TestTimestampFormat ( t * testing . T ) {
// Setup
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 )
// 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" } )
2022-11-12 02:17:54 +01:00
out = strings . TrimSpace ( strings . Split ( out , "hishtory tquery" ) [ 1 ] )
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
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 )
testutils . Check ( t , lib . ReliableDbCreate ( db , entry1 ) )
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 )
testutils . Check ( t , lib . ReliableDbCreate ( db , entry2 ) )
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 )
testutils . Check ( t , lib . ReliableDbCreate ( db , entry3 ) )
// 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-09-09 21:28:01 +02:00
out = strings . TrimSpace ( strings . Split ( out , "hishtory tquery" ) [ 1 ] )
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
tester := zshTester { }
defer testutils . BackupAndRestore ( t ) ( )
defer testutils . BackupAndRestoreEnv ( "ZDOTDIR" ) ( )
homedir , err := os . UserHomeDir ( )
2022-11-27 20:59:06 +01:00
testutils . Check ( t , err )
2022-12-12 03:51:57 +01:00
zdotdir := path . Join ( homedir , "foo" )
testutils . Check ( t , os . MkdirAll ( zdotdir , 0 o744 ) )
os . Setenv ( "ZDOTDIR" , zdotdir )
userSecret := installHishtory ( t , tester , "" )
2022-12-12 06:59:41 +01:00
defer func ( ) {
testutils . Check ( t , os . Remove ( path . Join ( zdotdir , ".zshrc" ) ) )
} ( )
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" ) )
2022-11-14 00:59:59 +01:00
testutils . Check ( 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
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 , "" )
// 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-09-19 07:21:10 +02:00
out = strings . TrimSpace ( strings . Split ( out , "hishtory tquery -pipefail" ) [ 1 ] )
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 ` )
out = tester . RunInteractiveShell ( t , ` hishtory export -pipefail ` )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testRemoveDuplicateRows-enabled-export" )
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" )
2022-11-04 04:36:36 +01:00
out = captureTerminalOutput ( t , tester , [ ] string { "hishtory SPACE tquery SPACE -pipefail ENTER" } )
out = strings . TrimSpace ( strings . Split ( out , "hishtory tquery" ) [ 1 ] )
2023-02-11 18:09:48 +01:00
testutils . CompareGoldens ( t , out , "testRemoveDuplicateRows-enabled-tquery" )
2023-05-17 02:34:25 +02:00
out = captureTerminalOutput ( t , tester , [ ] string { "hishtory SPACE tquery SPACE -pipefail ENTER" , "Down Down" , "ENTER" } )
out = strings . TrimSpace ( strings . Split ( out , "hishtory tquery" ) [ 1 ] )
2023-05-17 17:38:47 +02:00
out = strings . Split ( out , "\n" ) [ 1 ]
2023-05-17 02:34:25 +02:00
testutils . CompareGoldens ( t , out , "testRemoveDuplicateRows-enabled-tquery-select" )
2022-11-04 04:36:36 +01:00
}
2022-12-12 04:15:29 +01:00
func TestSetConfigNoCorruption ( t * testing . T ) {
// Setup
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 ( )
testutils . Check ( t , err )
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
err := 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 ( )
}
2022-09-23 06:45:20 +02:00
type deviceSet struct {
deviceMap * map [ device ] deviceOp
currentDevice * device
}
type device struct {
key string
deviceId string
}
type deviceOp struct {
backup func ( )
restore func ( )
}
2023-08-30 08:23:21 +02:00
func createDevice ( t testing . TB , tester shellTester , devices * deviceSet , key , deviceId string ) {
2022-09-23 06:45:20 +02:00
d := device { key , deviceId }
_ , ok := ( * devices . deviceMap ) [ d ]
if ok {
2023-08-30 08:23:21 +02:00
t . Fatalf ( "cannot create device twice for key=%s deviceId=%s" , key , deviceId )
2022-09-23 06:45:20 +02:00
}
installHishtory ( t , tester , key )
( * devices . deviceMap ) [ d ] = deviceOp {
2022-10-28 06:53:47 +02:00
backup : func ( ) { testutils . BackupAndRestoreWithId ( t , key + deviceId ) } ,
restore : testutils . BackupAndRestoreWithId ( t , key + deviceId ) ,
2022-09-23 06:45:20 +02:00
}
}
func switchToDevice ( devices * deviceSet , d device ) {
2022-09-27 07:55:59 +02:00
if devices . currentDevice != nil && d == * devices . currentDevice {
return
}
2022-09-23 06:45:20 +02:00
if devices . currentDevice != nil {
( * devices . deviceMap ) [ * devices . currentDevice ] . backup ( )
}
devices . currentDevice = & d
( * devices . deviceMap ) [ d ] . restore ( )
}
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 ` )
2022-10-28 06:53:47 +02:00
testutils . Check ( 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 ` )
2022-10-28 06:53:47 +02:00
testutils . Check ( 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 )
}
}
}
2022-09-23 08:06:28 +02:00
type operation struct {
2022-09-25 01:00:03 +02:00
device device
cmd string
redactQuery string
2022-09-23 08:06:28 +02:00
}
2022-09-23 08:12:41 +02:00
var tmp int = 0
var runCounter * int = & tmp
func fuzzTest ( t * testing . T , tester shellTester , input string ) {
* runCounter += 1
// Parse the input
if len ( input ) > 1_000 {
return
}
input = strings . TrimSpace ( input )
ops := make ( [ ] operation , 0 )
for _ , line := range strings . Split ( input , "\n" ) {
split1 := strings . SplitN ( line , "|" , 2 )
if len ( split1 ) != 2 {
2022-09-25 01:00:03 +02:00
panic ( "malformed: split1" )
2022-09-23 08:12:41 +02:00
}
split2 := strings . SplitN ( split1 [ 0 ] , ";" , 2 )
if len ( split2 ) != 2 {
2022-09-25 01:00:03 +02:00
panic ( "malformed: split2" )
}
thingToDo := split1 [ 1 ]
cmd := ""
redactQuery := ""
if strings . HasPrefix ( thingToDo , "!" ) {
redactQuery = thingToDo [ 1 : ]
} else {
cmd = "echo " + thingToDo
2022-09-23 08:12:41 +02:00
}
re := regexp . MustCompile ( ` [a-zA-Z]+ ` )
2022-09-25 01:00:03 +02:00
if ! re . MatchString ( cmd ) && cmd != "" {
panic ( "malformed: re" )
2022-09-23 08:12:41 +02:00
}
key := split2 [ 0 ]
if strings . Contains ( key , "-" ) {
2022-09-25 01:00:03 +02:00
panic ( "malformed: key-" )
2022-09-23 08:12:41 +02:00
}
2022-09-25 01:00:03 +02:00
op := operation { device : device { key : key + "-" + strconv . Itoa ( * runCounter ) , deviceId : split2 [ 1 ] } , cmd : cmd , redactQuery : redactQuery }
2022-09-23 08:12:41 +02:00
ops = append ( ops , op )
}
// Set up and create the devices
2022-10-28 06:53:47 +02:00
defer testutils . BackupAndRestore ( t ) ( )
2022-09-23 08:12:41 +02:00
var deviceMap map [ device ] deviceOp = make ( map [ device ] deviceOp )
var devices deviceSet = deviceSet { }
devices . deviceMap = & deviceMap
devices . currentDevice = nil
for _ , op := range ops {
_ , ok := ( * devices . deviceMap ) [ op . device ]
if ok {
continue
}
createDevice ( t , tester , & devices , op . device . key , op . device . deviceId )
}
// Persist our basic in-memory copy of expected shell commands
keyToCommands := make ( map [ string ] string )
// Run the commands
for _ , op := range ops {
// Run the command
switchToDevice ( & devices , op . device )
2022-09-25 01:00:03 +02:00
if op . cmd != "" {
_ , err := tester . RunInteractiveShellRelaxed ( t , op . cmd )
2022-10-28 06:53:47 +02:00
testutils . Check ( t , err )
2022-09-25 01:00:03 +02:00
}
if op . redactQuery != "" {
2022-11-15 05:31:20 +01:00
_ , err := tester . RunInteractiveShellRelaxed ( t , ` HISHTORY_REDACT_FORCE=1 hishtory redact ` + op . redactQuery )
2022-10-28 06:53:47 +02:00
testutils . Check ( t , err )
2022-09-25 01:00:03 +02:00
}
2022-09-23 08:12:41 +02:00
// Calculate the expected output of hishtory export
val , ok := keyToCommands [ op . device . key ]
if ! ok {
val = ""
}
2022-09-25 01:00:03 +02:00
if op . cmd != "" {
val += op . cmd
val += "\n"
}
if op . redactQuery != "" {
lines := strings . Split ( val , "\n" )
filteredLines := make ( [ ] string , 0 )
for _ , line := range lines {
if strings . Contains ( line , op . redactQuery ) {
continue
}
filteredLines = append ( filteredLines , line )
}
val = strings . Join ( filteredLines , "\n" )
2022-11-15 05:31:20 +01:00
val += ` HISHTORY_REDACT_FORCE=1 hishtory redact ` + op . redactQuery + "\n"
2022-09-25 01:00:03 +02:00
}
2022-09-23 08:12:41 +02:00
keyToCommands [ op . device . key ] = val
// Run hishtory export and check the output
2022-11-13 03:46:30 +01:00
out , err := tester . RunInteractiveShellRelaxed ( t , ` hishtory export -export -pipefail ` )
2022-10-28 06:53:47 +02:00
testutils . Check ( t , err )
2022-09-23 08:12:41 +02:00
expectedOutput := keyToCommands [ op . device . key ]
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch for input=%#v key=%s (-expected +got):\n%s\nout=%#v" , input , op . device . key , diff , out )
}
}
// Check that hishtory export has the expected results
for _ , op := range ops {
switchToDevice ( & devices , op . device )
2022-11-13 03:46:30 +01:00
out , err := tester . RunInteractiveShellRelaxed ( t , ` hishtory export -export -pipefail ` )
2022-10-28 06:53:47 +02:00
testutils . Check ( t , err )
2022-09-23 08:12:41 +02:00
expectedOutput := keyToCommands [ op . device . key ]
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch for key=%s (-expected +got):\n%s\nout=%#v" , op . device . key , diff , out )
}
}
}
2022-09-23 08:06:28 +02:00
func FuzzTestMultipleUsers ( f * testing . F ) {
2022-09-28 07:03:37 +02:00
if skipSlowTests ( ) {
f . Skip ( "skipping slow tests" )
}
2022-11-13 15:25:16 +01:00
defer testutils . RunTestServer ( ) ( )
2022-09-25 01:00:03 +02:00
// Format:
// $Op = $Key;$Device|$Command\n
// $Key;$Device|$Command\n$Op
// $Command = !$ThingToRedact
// $CommandToRun
//
// Running repeated commands
2022-09-23 08:06:28 +02:00
f . Add ( "a;b|2\n" )
f . Add ( "a;b|aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" )
f . Add ( "a;b|aaaBBcccDD\n" )
f . Add ( "a;a|hello\na;a|world" )
f . Add ( "a;a|hello\na;a|world\na;b|3" )
f . Add ( "a;a|1\na;a|2\na;b|3\nb;a|4\na;b|5" )
f . Add ( "a;a|1\na;a|2\na;b|1\n" )
f . Add ( "a;a|1\na;a|2\na;b|1\nz;z|1\na;a|1\n" )
f . Add ( "a;a|hello\na;a|wobld" )
2022-09-23 08:12:41 +02:00
f . Add ( "a;a|hello\na;a|hello" )
2022-09-23 08:06:28 +02:00
f . Add ( "a;a|1\nb;a|2\nc;a|2\nd;a|2\na;b|2\na;b|3\na;b|4\na;b|8\na;d|2\nb;a|1" )
f . Add ( "a;a|1\na;b|1\na;c|1\na;d|1\na;e|1\na;f|1\na;g|1\na;b|1\na;b|1\na;b|1\na;b|1" )
2022-09-23 08:25:06 +02:00
f . Add ( "a;a|1\nb;b|1\na;c|1\na;d|1\na;e|1\na;f|1\na;g|1\na;b|1\na;b|1\na;b|1\na;b|1" )
f . Add ( "a;a|1\na;a|1\na;c|1\na;d|1\na;e|1\na;f|1\na;g|1\na;b|1\na;b|1\na;b|1\na;b|1" )
2022-09-25 01:00:03 +02:00
// Running repeated commands with redaction
f . Add ( "a;b|!hello\n" )
f . Add ( "a;b|hello\na;b|world\na;b|!hello\n" )
f . Add ( "a;b|hello\na;b|world\na;a|hello2\na;b|!hello\na;b|hello3\na;b|hello4\n" )
f . Add ( "a;b|hello\na;b|world\na;a|hello2\na;b|!h\na;b|!h\na;b|hello3\na;b|hello4\n" )
2022-09-23 08:06:28 +02:00
f . Fuzz ( func ( t * testing . T , input string ) {
2022-09-23 08:12:41 +02:00
fuzzTest ( t , bashTester { } , input )
fuzzTest ( t , zshTester { } , input )
2022-09-23 08:06:28 +02:00
} )
}
2022-10-19 04:55:41 +02:00
2023-08-30 08:23:21 +02:00
func assertNoLeakedConnections ( t testing . TB ) {
2022-11-27 07:40:43 +01:00
resp , err := lib . ApiGet ( "/api/v1/get-num-connections" )
testutils . Check ( t , err )
numConnections , err := strconv . Atoi ( string ( resp ) )
testutils . Check ( t , err )
if numConnections > 1 {
t . Fatalf ( "DB has %d open connections, expected to have 1 or less" , numConnections )
}
}
2022-10-19 04:55:41 +02:00
// TODO: somehow test/confirm that hishtory works even if only bash/only zsh is installed