mirror of
https://github.com/ddworken/hishtory.git
synced 2025-06-23 21:41:38 +02:00
Merge remote-tracking branch 'origin/master' into sergio/db
This commit is contained in:
commit
3c0d3561fb
10
.github/workflows/docker-compose-test.yml
vendored
10
.github/workflows/docker-compose-test.yml
vendored
@ -36,14 +36,12 @@ jobs:
|
|||||||
set -eo pipefail
|
set -eo pipefail
|
||||||
export HISHTORY_SERVER=http://localhost
|
export HISHTORY_SERVER=http://localhost
|
||||||
source ~/.bashrc
|
source ~/.bashrc
|
||||||
# Record a command that we'll check was persisted
|
|
||||||
ls -Slah /
|
|
||||||
# Assert that the entry is syncing properly
|
|
||||||
./hishtory status -v | grep 'Sync Status: Synced'
|
|
||||||
# Check that hishtory query runs without errors
|
# Check that hishtory query runs without errors
|
||||||
./hishtory query
|
./hishtory query
|
||||||
# Check that hishtory export recorded the above ls command
|
# And check that entries get recorded properly
|
||||||
./hishtory export | grep "ls -Slah /"
|
echo -e 'ls -Slah /\nhishtory export\n' | zsh -is | grep "ls -Slah /"
|
||||||
|
# Assert that the entry is syncing properly
|
||||||
|
./hishtory status -v | grep 'Sync Status: Synced'
|
||||||
- name: Setup tmate session
|
- name: Setup tmate session
|
||||||
if: ${{ failure() }}
|
if: ${{ failure() }}
|
||||||
uses: mxschmitt/action-tmate@v3
|
uses: mxschmitt/action-tmate@v3
|
||||||
|
5
.github/workflows/go-test.yml
vendored
5
.github/workflows/go-test.yml
vendored
@ -43,7 +43,10 @@ jobs:
|
|||||||
|
|
||||||
# Set a consistent hostname so we can run tests that depend on it
|
# Set a consistent hostname so we can run tests that depend on it
|
||||||
sudo scutil --set HostName ghaction-runner-hostname
|
sudo scutil --set HostName ghaction-runner-hostname
|
||||||
|
- name: MacOS Docker Setup
|
||||||
|
if: ${{ !startsWith(github.event.head_commit.message, 'Release') && matrix.os == 'macos-latest'}}
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
# Install docker so it can be used for datadog
|
# Install docker so it can be used for datadog
|
||||||
brew install docker
|
brew install docker
|
||||||
colima start
|
colima start
|
||||||
|
@ -130,7 +130,7 @@ Download the latest binary from [Github Releases](https://github.com/ddworken/hi
|
|||||||
|
|
||||||
By default, hiSHtory relies on a backend for syncing. All data is end-to-end encrypted, so the backend can't view your history.
|
By default, hiSHtory relies on a backend for syncing. All data is end-to-end encrypted, so the backend can't view your history.
|
||||||
|
|
||||||
But if you'd like to self-host the hishtory backend, you can! The backend is a simple go binary in `backend/server/server.go` (with [prebuilt binaries here](https://github.com/ddworken/hishtory/releases)). It can either use SQLite or Postgres for persistence.
|
But if you'd like to self-host the hishtory backend, you can! The backend is a simple go binary in `backend/server/server.go` (with [prebuilt binaries here](https://github.com/ddworken/hishtory/tags)). It can either use SQLite or Postgres for persistence.
|
||||||
|
|
||||||
Check out the [`docker-compose.yml`](https://github.com/ddworken/hishtory/blob/master/backend/server/docker-compose.yml) file for an example config to start a hiSHtory server using postgres.
|
Check out the [`docker-compose.yml`](https://github.com/ddworken/hishtory/blob/master/backend/server/docker-compose.yml) file for an example config to start a hiSHtory server using postgres.
|
||||||
|
|
||||||
|
@ -58,7 +58,12 @@ func TestMain(m *testing.M) {
|
|||||||
if _, has_dd_api_key := os.LookupEnv("DD_API_KEY"); testutils.IsGithubAction() && has_dd_api_key {
|
if _, has_dd_api_key := os.LookupEnv("DD_API_KEY"); testutils.IsGithubAction() && has_dd_api_key {
|
||||||
ddStats, err := statsd.New("localhost:8125")
|
ddStats, err := statsd.New("localhost:8125")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("Failed to start DataDog statsd: %w\n", err))
|
err := fmt.Errorf("Failed to start DataDog statsd: %w\n", err)
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
fmt.Printf("%v", err)
|
||||||
|
} else {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
GLOBAL_STATSD = ddStats
|
GLOBAL_STATSD = ddStats
|
||||||
}
|
}
|
||||||
@ -181,7 +186,7 @@ func TestParam(t *testing.T) {
|
|||||||
}
|
}
|
||||||
runTestsWithRetries(t, "testControlR/offline/bash", func(t testing.TB) { testControlR(t, bashTester{}, "bash", Offline) })
|
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) })
|
runTestsWithRetries(t, "testControlR/fish", func(t testing.TB) { testControlR(t, bashTester{}, "fish", Online) })
|
||||||
runTestsWithExtraRetries(t, "testTui/search", testTui_search, 7)
|
runTestsWithExtraRetries(t, "testTui/search", testTui_search, 10)
|
||||||
runTestsWithRetries(t, "testTui/general", testTui_general)
|
runTestsWithRetries(t, "testTui/general", testTui_general)
|
||||||
runTestsWithRetries(t, "testTui/scroll", testTui_scroll)
|
runTestsWithRetries(t, "testTui/scroll", testTui_scroll)
|
||||||
runTestsWithRetries(t, "testTui/resize", testTui_resize)
|
runTestsWithRetries(t, "testTui/resize", testTui_resize)
|
||||||
@ -201,7 +206,7 @@ func runTestsWithRetries(parentT *testing.T, testName string, testFunc func(t te
|
|||||||
|
|
||||||
func runTestsWithExtraRetries(parentT *testing.T, testName string, testFunc func(t testing.TB), numRetries int) {
|
func runTestsWithExtraRetries(parentT *testing.T, testName string, testFunc func(t testing.TB), numRetries int) {
|
||||||
for i := 1; i <= numRetries; i++ {
|
for i := 1; i <= numRetries; i++ {
|
||||||
rt := &retryingTester{nil, i == numRetries, true}
|
rt := &retryingTester{nil, i == numRetries, true, testName}
|
||||||
parentT.Run(fmt.Sprintf("%s/%d", testName, i), func(t *testing.T) {
|
parentT.Run(fmt.Sprintf("%s/%d", testName, i), func(t *testing.T) {
|
||||||
rt.T = t
|
rt.T = t
|
||||||
testFunc(rt)
|
testFunc(rt)
|
||||||
@ -224,12 +229,16 @@ type retryingTester struct {
|
|||||||
*testing.T
|
*testing.T
|
||||||
isFinalRun bool
|
isFinalRun bool
|
||||||
succeeded bool
|
succeeded bool
|
||||||
|
testName string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *retryingTester) Fatalf(format string, args ...any) {
|
func (t *retryingTester) Fatalf(format string, args ...any) {
|
||||||
t.T.Helper()
|
t.T.Helper()
|
||||||
t.succeeded = false
|
t.succeeded = false
|
||||||
if t.isFinalRun {
|
if t.isFinalRun {
|
||||||
|
if GLOBAL_STATSD != nil {
|
||||||
|
GLOBAL_STATSD.Incr("test_failure", []string{"test:" + t.testName, "os:" + runtime.GOOS}, 1.0)
|
||||||
|
}
|
||||||
t.T.Fatalf(format, args...)
|
t.T.Fatalf(format, args...)
|
||||||
} else {
|
} else {
|
||||||
testutils.TestLog(t.T, fmt.Sprintf("retryingTester: Ignoring fatalf for non-final run: %#v", fmt.Sprintf(format, args...)))
|
testutils.TestLog(t.T, fmt.Sprintf("retryingTester: Ignoring fatalf for non-final run: %#v", fmt.Sprintf(format, args...)))
|
||||||
@ -420,13 +429,9 @@ func testBasicUserFlow(t *testing.T, tester shellTester, onlineStatus OnlineStat
|
|||||||
|
|
||||||
// Assert that hishtory is correctly using the dev config.sh
|
// Assert that hishtory is correctly using the dev config.sh
|
||||||
homedir, err := os.UserHomeDir()
|
homedir, err := os.UserHomeDir()
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("failed to get homedir: %v", err)
|
|
||||||
}
|
|
||||||
dat, err := os.ReadFile(path.Join(homedir, data.GetHishtoryPath(), "config.sh"))
|
dat, err := os.ReadFile(path.Join(homedir, data.GetHishtoryPath(), "config.sh"))
|
||||||
if err != nil {
|
require.NoError(t, err, "failed to read config.sh")
|
||||||
t.Fatalf("failed to read config.sh: %v", err)
|
|
||||||
}
|
|
||||||
require.NotContains(t, string(dat), "# Background Run", "config.sh is the prod version when it shouldn't be")
|
require.NotContains(t, string(dat), "# Background Run", "config.sh is the prod version when it shouldn't be")
|
||||||
|
|
||||||
// Test the banner
|
// Test the banner
|
||||||
@ -731,9 +736,7 @@ func testUpdate(t *testing.T, tester shellTester) {
|
|||||||
// Update
|
// Update
|
||||||
out = tester.RunInteractiveShell(t, `hishtory update`)
|
out = tester.RunInteractiveShell(t, `hishtory update`)
|
||||||
isExpected, err := regexp.MatchString(`Successfully updated hishtory from v0[.]Unknown to v0.\d+`, out)
|
isExpected, err := regexp.MatchString(`Successfully updated hishtory from v0[.]Unknown to v0.\d+`, out)
|
||||||
if err != nil {
|
require.NoError(t, err, "regex failure")
|
||||||
t.Fatalf("regex failure: %v", err)
|
|
||||||
}
|
|
||||||
if !isExpected {
|
if !isExpected {
|
||||||
t.Fatalf("hishtory update returned unexpected out=%#v", out)
|
t.Fatalf("hishtory update returned unexpected out=%#v", out)
|
||||||
}
|
}
|
||||||
@ -1010,13 +1013,9 @@ CGO_ENABLED=0 go build -o /tmp/client
|
|||||||
|
|
||||||
// Assert that config.sh isn't the dev version
|
// Assert that config.sh isn't the dev version
|
||||||
homedir, err := os.UserHomeDir()
|
homedir, err := os.UserHomeDir()
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("failed to get homedir: %v", err)
|
|
||||||
}
|
|
||||||
dat, err := os.ReadFile(path.Join(homedir, data.GetHishtoryPath(), "config.sh"))
|
dat, err := os.ReadFile(path.Join(homedir, data.GetHishtoryPath(), "config.sh"))
|
||||||
if err != nil {
|
require.NoError(t, err, "failed to read config.sh")
|
||||||
t.Fatalf("failed to read config.sh: %v", err)
|
|
||||||
}
|
|
||||||
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")
|
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")
|
||||||
|
|
||||||
// Test the status subcommand
|
// Test the status subcommand
|
||||||
@ -1056,9 +1055,7 @@ func testDisplayTable(t *testing.T, tester shellTester) {
|
|||||||
|
|
||||||
// Submit two fake entries
|
// Submit two fake entries
|
||||||
tmz, err := time.LoadLocation("America/Los_Angeles")
|
tmz, err := time.LoadLocation("America/Los_Angeles")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("failed to load timezone: %v", err)
|
|
||||||
}
|
|
||||||
entry1 := testutils.MakeFakeHistoryEntry("table_cmd1")
|
entry1 := testutils.MakeFakeHistoryEntry("table_cmd1")
|
||||||
entry1.StartTime = time.Unix(1650096186, 0).In(tmz)
|
entry1.StartTime = time.Unix(1650096186, 0).In(tmz)
|
||||||
entry1.EndTime = time.Unix(1650096190, 0).In(tmz)
|
entry1.EndTime = time.Unix(1650096190, 0).In(tmz)
|
||||||
@ -1122,9 +1119,7 @@ func testRequestAndReceiveDbDump(t *testing.T, tester shellTester) {
|
|||||||
config := hctx.GetConf(hctx.MakeContext())
|
config := hctx.GetConf(hctx.MakeContext())
|
||||||
deviceId1 := config.DeviceId
|
deviceId1 := config.DeviceId
|
||||||
resp, err := lib.ApiGet("/api/v1/get-dump-requests?user_id=" + data.UserId(secretKey) + "&device_id=" + deviceId1)
|
resp, err := lib.ApiGet("/api/v1/get-dump-requests?user_id=" + data.UserId(secretKey) + "&device_id=" + deviceId1)
|
||||||
if err != nil {
|
require.NoError(t, err, "failed to get pending dump requests")
|
||||||
t.Fatalf("failed to get pending dump requests: %v", err)
|
|
||||||
}
|
|
||||||
if string(resp) != "[]" {
|
if string(resp) != "[]" {
|
||||||
t.Fatalf("There are pending dump requests! user_id=%#v, resp=%#v", data.UserId(secretKey), string(resp))
|
t.Fatalf("There are pending dump requests! user_id=%#v, resp=%#v", data.UserId(secretKey), string(resp))
|
||||||
}
|
}
|
||||||
@ -1149,18 +1144,14 @@ echo other`)
|
|||||||
|
|
||||||
// Wipe the DB to simulate entries getting deleted because they've already been read and expired
|
// Wipe the DB to simulate entries getting deleted because they've already been read and expired
|
||||||
_, err = lib.ApiGet("/api/v1/wipe-db-entries")
|
_, err = lib.ApiGet("/api/v1/wipe-db-entries")
|
||||||
if err != nil {
|
require.NoError(t, err, "failed to wipe the remote DB")
|
||||||
t.Fatalf("failed to wipe the DB: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Install a new one (with the same secret key but a diff device id)
|
// Install a new one (with the same secret key but a diff device id)
|
||||||
installHishtory(t, tester, secretKey)
|
installHishtory(t, tester, secretKey)
|
||||||
|
|
||||||
// Confirm there is now a pending dump requests that the first device should respond to
|
// Confirm there is now a pending dump requests that the first device should respond to
|
||||||
resp, err = lib.ApiGet("/api/v1/get-dump-requests?user_id=" + data.UserId(secretKey) + "&device_id=" + deviceId1)
|
resp, err = lib.ApiGet("/api/v1/get-dump-requests?user_id=" + data.UserId(secretKey) + "&device_id=" + deviceId1)
|
||||||
if err != nil {
|
require.NoError(t, err, "failed to get pending dump requests")
|
||||||
t.Fatalf("failed to get pending dump requests: %v", err)
|
|
||||||
}
|
|
||||||
if string(resp) == "[]" {
|
if string(resp) == "[]" {
|
||||||
t.Fatalf("There are no pending dump requests! user_id=%#v, resp=%#v", data.UserId(secretKey), string(resp))
|
t.Fatalf("There are no pending dump requests! user_id=%#v, resp=%#v", data.UserId(secretKey), string(resp))
|
||||||
}
|
}
|
||||||
@ -1191,9 +1182,7 @@ echo other`)
|
|||||||
|
|
||||||
// Confirm there are no pending dump requests for the first device
|
// Confirm there are no pending dump requests for the first device
|
||||||
resp, err = lib.ApiGet("/api/v1/get-dump-requests?user_id=" + data.UserId(secretKey) + "&device_id=" + deviceId1)
|
resp, err = lib.ApiGet("/api/v1/get-dump-requests?user_id=" + data.UserId(secretKey) + "&device_id=" + deviceId1)
|
||||||
if err != nil {
|
require.NoError(t, err, "failed to get pending dump requests")
|
||||||
t.Fatalf("failed to get pending dump requests: %v", err)
|
|
||||||
}
|
|
||||||
if string(resp) != "[]" {
|
if string(resp) != "[]" {
|
||||||
t.Fatalf("There are pending dump requests! user_id=%#v, resp=%#v", data.UserId(secretKey), string(resp))
|
t.Fatalf("There are pending dump requests! user_id=%#v, resp=%#v", data.UserId(secretKey), string(resp))
|
||||||
}
|
}
|
||||||
@ -1813,7 +1802,7 @@ func testTui_resize(t testing.TB) {
|
|||||||
out = captureTerminalOutputWithShellNameAndDimensions(t, tester, tester.ShellName(), 100, 20, []TmuxCommand{
|
out = captureTerminalOutputWithShellNameAndDimensions(t, tester, tester.ShellName(), 100, 20, []TmuxCommand{
|
||||||
{Keys: "hishtory SPACE tquery ENTER"},
|
{Keys: "hishtory SPACE tquery ENTER"},
|
||||||
{Keys: "Down"},
|
{Keys: "Down"},
|
||||||
{ResizeX: 300, ResizeY: 100},
|
{ResizeX: 300, ResizeY: 100, ExtraDelay: 1.0},
|
||||||
{Keys: "Enter"},
|
{Keys: "Enter"},
|
||||||
})
|
})
|
||||||
require.Contains(t, out, "\necho 'aaaaaa bbbb'\n")
|
require.Contains(t, out, "\necho 'aaaaaa bbbb'\n")
|
||||||
@ -1866,10 +1855,11 @@ func testTui_delete(t testing.TB) {
|
|||||||
manuallySubmitHistoryEntry(t, userSecret, testutils.MakeFakeHistoryEntry("echo 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc'"))
|
manuallySubmitHistoryEntry(t, userSecret, testutils.MakeFakeHistoryEntry("echo 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc'"))
|
||||||
|
|
||||||
// Check that we can delete an entry
|
// Check that we can delete an entry
|
||||||
out := captureTerminalOutput(t, tester, []string{
|
out := captureTerminalOutputWithComplexCommands(t, tester, []TmuxCommand{
|
||||||
"hishtory SPACE tquery ENTER",
|
{Keys: "hishtory SPACE tquery ENTER"},
|
||||||
"aaaaaa",
|
// ExtraDelay so that the search query finishes before we hit delete
|
||||||
"C-K",
|
{Keys: "aaaaaa", ExtraDelay: 1.0},
|
||||||
|
{Keys: "C-K"},
|
||||||
})
|
})
|
||||||
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
|
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
|
||||||
testutils.CompareGoldens(t, out, "TestTui-Delete")
|
testutils.CompareGoldens(t, out, "TestTui-Delete")
|
||||||
@ -1921,9 +1911,11 @@ func testTui_search(t testing.TB) {
|
|||||||
testutils.CompareGoldens(t, out, "TestTui-Search")
|
testutils.CompareGoldens(t, out, "TestTui-Search")
|
||||||
|
|
||||||
// Check the output when there is a selected result
|
// Check the output when there is a selected result
|
||||||
out = captureTerminalOutput(t, tester, []string{
|
out = captureTerminalOutputWithComplexCommands(t, tester, []TmuxCommand{
|
||||||
"hishtory SPACE tquery ENTER",
|
{Keys: "hishtory SPACE tquery ENTER"},
|
||||||
"ls", "", "ENTER",
|
// Extra delay to ensure that the search for 'ls' finishes before we select an entry
|
||||||
|
{Keys: "ls", ExtraDelay: 1.0},
|
||||||
|
{Keys: "ENTER"},
|
||||||
})
|
})
|
||||||
out = strings.Split(strings.TrimSpace(strings.Split(out, "hishtory tquery")[1]), "\n")[0]
|
out = strings.Split(strings.TrimSpace(strings.Split(out, "hishtory tquery")[1]), "\n")[0]
|
||||||
expected = `ls ~/`
|
expected = `ls ~/`
|
||||||
@ -1932,9 +1924,10 @@ func testTui_search(t testing.TB) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check the output when the initial search is invalid
|
// Check the output when the initial search is invalid
|
||||||
out = captureTerminalOutput(t, tester, []string{
|
out = captureTerminalOutputWithComplexCommands(t, tester, []TmuxCommand{
|
||||||
"hishtory SPACE tquery SPACE foo: ENTER",
|
// ExtraDelay to ensure that after searching for 'foo:' it gets the real results for an empty query
|
||||||
"ls",
|
{Keys: "hishtory SPACE tquery SPACE foo: ENTER", ExtraDelay: 1.0},
|
||||||
|
{Keys: "ls", ExtraDelay: 1.0},
|
||||||
})
|
})
|
||||||
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
|
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
|
||||||
testutils.CompareGoldens(t, out, "TestTui-InitialInvalidSearch")
|
testutils.CompareGoldens(t, out, "TestTui-InitialInvalidSearch")
|
||||||
@ -1942,7 +1935,9 @@ func testTui_search(t testing.TB) {
|
|||||||
// Check the output when the search is invalid
|
// Check the output when the search is invalid
|
||||||
out = captureTerminalOutputWithComplexCommands(t, tester, []TmuxCommand{
|
out = captureTerminalOutputWithComplexCommands(t, tester, []TmuxCommand{
|
||||||
{Keys: "hishtory SPACE tquery ENTER"},
|
{Keys: "hishtory SPACE tquery ENTER"},
|
||||||
{Keys: "ls", ExtraDelay: 1.0}, {Keys: ":"},
|
// ExtraDelay to ensure that the search for 'ls' finishes before we make it invalid by adding ':'
|
||||||
|
{Keys: "ls", ExtraDelay: 1.0},
|
||||||
|
{Keys: ":"},
|
||||||
})
|
})
|
||||||
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
|
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
|
||||||
testutils.CompareGoldens(t, out, "TestTui-InvalidSearch")
|
testutils.CompareGoldens(t, out, "TestTui-InvalidSearch")
|
||||||
@ -2363,9 +2358,7 @@ func TestTimestampFormat(t *testing.T) {
|
|||||||
|
|
||||||
// Add some entries with fixed timestamps
|
// Add some entries with fixed timestamps
|
||||||
tmz, err := time.LoadLocation("America/Los_Angeles")
|
tmz, err := time.LoadLocation("America/Los_Angeles")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("failed to load timezone: %v", err)
|
|
||||||
}
|
|
||||||
entry1 := testutils.MakeFakeHistoryEntry("table_cmd1 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
|
entry1 := testutils.MakeFakeHistoryEntry("table_cmd1 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
|
||||||
entry1.StartTime = time.Unix(1650096186, 0).In(tmz)
|
entry1.StartTime = time.Unix(1650096186, 0).In(tmz)
|
||||||
entry1.EndTime = time.Unix(1650096190, 0).In(tmz)
|
entry1.EndTime = time.Unix(1650096190, 0).In(tmz)
|
||||||
@ -2385,11 +2378,44 @@ func TestTimestampFormat(t *testing.T) {
|
|||||||
testutils.CompareGoldens(t, out, "TestTimestampFormat-query")
|
testutils.CompareGoldens(t, out, "TestTimestampFormat-query")
|
||||||
out = captureTerminalOutput(t, tester, []string{"hishtory SPACE tquery SPACE -pipefail SPACE -tablesizing ENTER"})
|
out = captureTerminalOutput(t, tester, []string{"hishtory SPACE tquery SPACE -pipefail SPACE -tablesizing ENTER"})
|
||||||
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
|
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
|
||||||
goldenName := "TestTimestampFormat-tquery"
|
testutils.CompareGoldens(t, out, "TestTimestampFormat-tquery")
|
||||||
if testutils.IsGithubAction() {
|
}
|
||||||
goldenName += "-isAction"
|
|
||||||
}
|
func TestSortByConsistentTimezone(t *testing.T) {
|
||||||
testutils.CompareGoldens(t, out, goldenName)
|
// Setup
|
||||||
|
tester := zshTester{}
|
||||||
|
defer testutils.BackupAndRestore(t)()
|
||||||
|
installHishtory(t, tester, "")
|
||||||
|
|
||||||
|
// Add an entry just to ensure we get consistent table sizing
|
||||||
|
tester.RunInteractiveShell(t, "echo tablesizing")
|
||||||
|
|
||||||
|
// 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
|
||||||
|
out := hishtoryQuery(t, tester, "-pipefail -tablesizing")
|
||||||
|
testutils.CompareGoldens(t, out, "TestSortByConsistentTimezone-query")
|
||||||
|
out = captureTerminalOutput(t, tester, []string{"hishtory SPACE tquery SPACE -pipefail SPACE -tablesizing ENTER"})
|
||||||
|
out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1])
|
||||||
|
testutils.CompareGoldens(t, out, fmt.Sprintf("TestSortByConsistentTimezone-tquery-isAction=%v", testutils.IsGithubAction()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestZDotDir(t *testing.T) {
|
func TestZDotDir(t *testing.T) {
|
||||||
@ -2482,14 +2508,10 @@ func TestSetConfigNoCorruption(t *testing.T) {
|
|||||||
c.HaveMissedUploads = (i % 2) == 0
|
c.HaveMissedUploads = (i % 2) == 0
|
||||||
// Write it
|
// Write it
|
||||||
err := hctx.SetConfig(c)
|
err := hctx.SetConfig(c)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
// Check that we can read
|
// Check that we can read
|
||||||
c2, err := hctx.GetConfig()
|
c2, err := hctx.GetConfig()
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if c2.UserSecret != c.UserSecret {
|
if c2.UserSecret != c.UserSecret {
|
||||||
panic("user secret mismatch")
|
panic("user secret mismatch")
|
||||||
}
|
}
|
||||||
|
@ -105,8 +105,8 @@ func presaveHistoryEntry(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
startTime, err := parseCrossPlatformInt(os.Args[4])
|
startTime, err := parseCrossPlatformInt(os.Args[4])
|
||||||
lib.CheckFatalError(err)
|
lib.CheckFatalError(err)
|
||||||
entry.StartTime = time.Unix(startTime, 0)
|
entry.StartTime = time.Unix(startTime, 0).UTC()
|
||||||
entry.EndTime = time.Unix(0, 0)
|
entry.EndTime = time.Unix(0, 0).UTC()
|
||||||
|
|
||||||
// And persist it locally.
|
// And persist it locally.
|
||||||
db := hctx.GetDb(ctx)
|
db := hctx.GetDb(ctx)
|
||||||
@ -281,10 +281,10 @@ func buildHistoryEntry(ctx context.Context, args []string) (*data.HistoryEntry,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse start time %s as int: %w", args[5], err)
|
return nil, fmt.Errorf("failed to parse start time %s as int: %w", args[5], err)
|
||||||
}
|
}
|
||||||
entry.StartTime = time.Unix(seconds, 0)
|
entry.StartTime = time.Unix(seconds, 0).UTC()
|
||||||
|
|
||||||
// end time
|
// end time
|
||||||
entry.EndTime = time.Now()
|
entry.EndTime = time.Now().UTC()
|
||||||
|
|
||||||
// command
|
// command
|
||||||
if shell == "bash" {
|
if shell == "bash" {
|
||||||
|
4
client/lib/goldens/TestSortByConsistentTimezone-query
Normal file
4
client/lib/goldens/TestSortByConsistentTimezone-query
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
Hostname CWD Timestamp Runtime Exit Code Command
|
||||||
|
localhost /tmp/ Apr 16 2022 01:36:26 PDT 1s 2 third_entry
|
||||||
|
localhost /tmp/ Apr 16 2022 01:19:46 PDT 1s 2 second_entry
|
||||||
|
localhost /tmp/ Apr 16 2022 01:03:06 PDT 1s 2 first_entry
|
@ -4,28 +4,28 @@
|
|||||||
|
|
||||||
Search Query: > -pipefail -tablesizing
|
Search Query: > -pipefail -tablesizing
|
||||||
|
|
||||||
┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
│ Hostname CWD Timestamp Runtime Exit Code Command │
|
│ Hostname CWD Timestamp Runtime Exit Code Command │
|
||||||
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
│────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
│ localhost ~/foo/ 2022/Apr/16 01:03 24s 3 table_cmd2 │
|
│ localhost /tmp/ Apr 16 2022 01:36:26 PDT 1s 2 third_entry │
|
||||||
│ localhost /tmp/ 2022/Apr/16 01:03 4s 2 table_cmd1 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa… │
|
│ localhost /tmp/ Apr 16 2022 01:19:46 PDT 1s 2 second_entry │
|
||||||
│ │
|
│ localhost /tmp/ Apr 16 2022 01:03:06 PDT 1s 2 first_entry │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
hiSHtory: Search your shell history • ctrl+h help
|
hiSHtory: Search your shell history • ctrl+h help
|
@ -0,0 +1,31 @@
|
|||||||
|
-pipefail -tablesizing
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Search Query: > -pipefail -tablesizing
|
||||||
|
|
||||||
|
┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Hostname CWD Timestamp Runtime Exit Code Command │
|
||||||
|
│────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
|
│ localhost /tmp/ Apr 16 2022 01:36:26 PDT 1s 2 third_entry │
|
||||||
|
│ localhost /tmp/ Apr 16 2022 01:19:46 PDT 1s 2 second_entry │
|
||||||
|
│ localhost /tmp/ Apr 16 2022 01:03:06 PDT 1s 2 first_entry │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
hiSHtory: Search your shell history • ctrl+h help
|
@ -117,7 +117,7 @@ func AddToDbIfNew(db *gorm.DB, entry data.HistoryEntry) {
|
|||||||
var results []data.HistoryEntry
|
var results []data.HistoryEntry
|
||||||
tx.Limit(1).Find(&results)
|
tx.Limit(1).Find(&results)
|
||||||
if len(results) == 0 {
|
if len(results) == 0 {
|
||||||
db.Create(entry)
|
db.Create(normalizeEntryTimezone(entry))
|
||||||
// TODO: check the error here and bubble it up
|
// TODO: check the error here and bubble it up
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -146,13 +146,13 @@ func buildTableRow(ctx context.Context, columnNames []string, entry data.History
|
|||||||
case "CWD":
|
case "CWD":
|
||||||
row = append(row, entry.CurrentWorkingDirectory)
|
row = append(row, entry.CurrentWorkingDirectory)
|
||||||
case "Timestamp":
|
case "Timestamp":
|
||||||
row = append(row, entry.StartTime.Format(hctx.GetConf(ctx).TimestampFormat))
|
row = append(row, entry.StartTime.Local().Format(hctx.GetConf(ctx).TimestampFormat))
|
||||||
case "Runtime":
|
case "Runtime":
|
||||||
if entry.EndTime == time.Unix(0, 0) {
|
if entry.EndTime == time.Unix(0, 0) {
|
||||||
// An EndTime of zero means this is a pre-saved entry that never finished
|
// An EndTime of zero means this is a pre-saved entry that never finished
|
||||||
row = append(row, "N/A")
|
row = append(row, "N/A")
|
||||||
} else {
|
} else {
|
||||||
row = append(row, entry.EndTime.Sub(entry.StartTime).Round(time.Millisecond).String())
|
row = append(row, entry.EndTime.Local().Sub(entry.StartTime.Local()).Round(time.Millisecond).String())
|
||||||
}
|
}
|
||||||
case "Exit Code":
|
case "Exit Code":
|
||||||
row = append(row, fmt.Sprintf("%d", entry.ExitCode))
|
row = append(row, fmt.Sprintf("%d", entry.ExitCode))
|
||||||
@ -301,8 +301,8 @@ func ImportHistory(ctx context.Context, shouldReadStdin, force bool) (int, error
|
|||||||
CurrentWorkingDirectory: "Unknown",
|
CurrentWorkingDirectory: "Unknown",
|
||||||
HomeDirectory: homedir,
|
HomeDirectory: homedir,
|
||||||
ExitCode: 0,
|
ExitCode: 0,
|
||||||
StartTime: time.Now(),
|
StartTime: time.Now().UTC(),
|
||||||
EndTime: time.Now(),
|
EndTime: time.Now().UTC(),
|
||||||
DeviceId: config.DeviceId,
|
DeviceId: config.DeviceId,
|
||||||
}
|
}
|
||||||
err = ReliableDbCreate(db, entry)
|
err = ReliableDbCreate(db, entry)
|
||||||
@ -699,7 +699,14 @@ func IsOfflineError(err error) bool {
|
|||||||
strings.Contains(err.Error(), "net/http: TLS handshake timeout")
|
strings.Contains(err.Error(), "net/http: TLS handshake timeout")
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReliableDbCreate(db *gorm.DB, entry interface{}) error {
|
func normalizeEntryTimezone(entry data.HistoryEntry) data.HistoryEntry {
|
||||||
|
entry.StartTime = entry.StartTime.UTC()
|
||||||
|
entry.EndTime = entry.EndTime.UTC()
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReliableDbCreate(db *gorm.DB, entry data.HistoryEntry) error {
|
||||||
|
entry = normalizeEntryTimezone(entry)
|
||||||
var err error = nil
|
var err error = nil
|
||||||
i := 0
|
i := 0
|
||||||
for i = 0; i < 10; i++ {
|
for i = 0; i < 10; i++ {
|
||||||
@ -892,7 +899,6 @@ func Search(ctx context.Context, db *gorm.DB, query string, limit int) ([]*data.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// TODO: This ordering isn't sufficient if some computers are in different timezones. Add better sorting here.
|
|
||||||
if hctx.GetConf(ctx).BetaMode {
|
if hctx.GetConf(ctx).BetaMode {
|
||||||
tx = tx.Order("start_time DESC")
|
tx = tx.Order("start_time DESC")
|
||||||
} else {
|
} else {
|
||||||
|
@ -311,8 +311,8 @@ func MakeFakeHistoryEntry(command string) data.HistoryEntry {
|
|||||||
CurrentWorkingDirectory: "/tmp/",
|
CurrentWorkingDirectory: "/tmp/",
|
||||||
HomeDirectory: "/home/david/",
|
HomeDirectory: "/home/david/",
|
||||||
ExitCode: 2,
|
ExitCode: 2,
|
||||||
StartTime: time.Unix(fakeHistoryTimestamp, 0),
|
StartTime: time.Unix(fakeHistoryTimestamp, 0).UTC(),
|
||||||
EndTime: time.Unix(fakeHistoryTimestamp+3, 0),
|
EndTime: time.Unix(fakeHistoryTimestamp+3, 0).UTC(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user