mirror of
https://github.com/ddworken/hishtory.git
synced 2025-01-23 06:38:52 +01:00
Add automatic retries for the TestTui method which is sadly inherently flaky since it is akin to screenshot tests of a terminal
This commit is contained in:
parent
a53485f04b
commit
ddddff0f1b
@ -48,15 +48,15 @@ func TestMain(m *testing.M) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type shellTester interface {
|
type shellTester interface {
|
||||||
RunInteractiveShell(t *testing.T, script string) string
|
RunInteractiveShell(t testing.TB, script string) string
|
||||||
RunInteractiveShellRelaxed(t *testing.T, script string) (string, error)
|
RunInteractiveShellRelaxed(t testing.TB, script string) (string, error)
|
||||||
ShellName() string
|
ShellName() string
|
||||||
}
|
}
|
||||||
type bashTester struct {
|
type bashTester struct {
|
||||||
shellTester
|
shellTester
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b bashTester) RunInteractiveShell(t *testing.T, script string) string {
|
func (b bashTester) RunInteractiveShell(t testing.TB, script string) string {
|
||||||
out, err := b.RunInteractiveShellRelaxed(t, "set -emo pipefail\n"+script)
|
out, err := b.RunInteractiveShellRelaxed(t, "set -emo pipefail\n"+script)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_, filename, line, _ := runtime.Caller(1)
|
_, filename, line, _ := runtime.Caller(1)
|
||||||
@ -65,7 +65,7 @@ func (b bashTester) RunInteractiveShell(t *testing.T, script string) string {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b bashTester) RunInteractiveShellRelaxed(t *testing.T, script string) (string, error) {
|
func (b bashTester) RunInteractiveShellRelaxed(t testing.TB, script string) (string, error) {
|
||||||
cmd := exec.Command("bash", "-i")
|
cmd := exec.Command("bash", "-i")
|
||||||
cmd.Stdin = strings.NewReader(script)
|
cmd.Stdin = strings.NewReader(script)
|
||||||
var stdout bytes.Buffer
|
var stdout bytes.Buffer
|
||||||
@ -89,13 +89,13 @@ type zshTester struct {
|
|||||||
shellTester
|
shellTester
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z zshTester) RunInteractiveShell(t *testing.T, script string) string {
|
func (z zshTester) RunInteractiveShell(t testing.TB, script string) string {
|
||||||
res, err := z.RunInteractiveShellRelaxed(t, "set -eo pipefail\n"+script)
|
res, err := z.RunInteractiveShellRelaxed(t, "set -eo pipefail\n"+script)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z zshTester) RunInteractiveShellRelaxed(t *testing.T, script string) (string, error) {
|
func (z zshTester) RunInteractiveShellRelaxed(t testing.TB, script string) (string, error) {
|
||||||
cmd := exec.Command("zsh", "-is")
|
cmd := exec.Command("zsh", "-is")
|
||||||
cmd.Stdin = strings.NewReader(script)
|
cmd.Stdin = strings.NewReader(script)
|
||||||
var stdout bytes.Buffer
|
var stdout bytes.Buffer
|
||||||
@ -162,10 +162,43 @@ func TestP(t *testing.T) {
|
|||||||
t.Run("testControlR/offline/bash", func(t *testing.T) { testControlR(t, bashTester{}, "bash", Offline) })
|
t.Run("testControlR/offline/bash", func(t *testing.T) { testControlR(t, bashTester{}, "bash", Offline) })
|
||||||
t.Run("testControlR/fish", func(t *testing.T) { testControlR(t, bashTester{}, "fish", Online) })
|
t.Run("testControlR/fish", func(t *testing.T) { testControlR(t, bashTester{}, "fish", Online) })
|
||||||
|
|
||||||
|
runTestsWithRetries(t, "testTui", testTui)
|
||||||
|
|
||||||
// Assert there are no leaked connections
|
// Assert there are no leaked connections
|
||||||
assertNoLeakedConnections(t)
|
assertNoLeakedConnections(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runTestsWithRetries(parentT *testing.T, testName string, testFunc func(t testing.TB)) {
|
||||||
|
numRetries := 3
|
||||||
|
for i := 1; i <= numRetries; i++ {
|
||||||
|
rt := &retryingTester{nil, i == 2, true}
|
||||||
|
parentT.Run(fmt.Sprintf("%s/%d", testName, i), func(t *testing.T) {
|
||||||
|
rt.T = t
|
||||||
|
testFunc(rt)
|
||||||
|
})
|
||||||
|
if rt.succeeded {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type retryingTester struct {
|
||||||
|
*testing.T
|
||||||
|
isFinalRun bool
|
||||||
|
succeeded bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *retryingTester) Fatalf(format string, args ...any) {
|
||||||
|
t.T.Helper()
|
||||||
|
t.succeeded = false
|
||||||
|
if t.isFinalRun {
|
||||||
|
t.T.Fatalf(format, args...)
|
||||||
|
} else {
|
||||||
|
testutils.TestLog(t.T, fmt.Sprintf("retryingTester: Ignoring failure for non-final run: %#v", fmt.Sprintf(format, args...)))
|
||||||
|
}
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
|
||||||
func testIntegration(t *testing.T, tester shellTester, onlineStatus OnlineStatus) {
|
func testIntegration(t *testing.T, tester shellTester, onlineStatus OnlineStatus) {
|
||||||
// Set up
|
// Set up
|
||||||
defer testutils.BackupAndRestore(t)()
|
defer testutils.BackupAndRestore(t)()
|
||||||
@ -277,7 +310,7 @@ yes | hishtory init `+userSecret)
|
|||||||
assertNoLeakedConnections(t)
|
assertNoLeakedConnections(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func installHishtory(t *testing.T, tester shellTester, userSecret string) string {
|
func installHishtory(t testing.TB, tester shellTester, userSecret string) string {
|
||||||
out := tester.RunInteractiveShell(t, ` /tmp/client install `+userSecret)
|
out := tester.RunInteractiveShell(t, ` /tmp/client install `+userSecret)
|
||||||
r := regexp.MustCompile(`Setting secret hishtory key to (.*)`)
|
r := regexp.MustCompile(`Setting secret hishtory key to (.*)`)
|
||||||
matches := r.FindStringSubmatch(out)
|
matches := r.FindStringSubmatch(out)
|
||||||
@ -287,7 +320,7 @@ func installHishtory(t *testing.T, tester shellTester, userSecret string) string
|
|||||||
return matches[1]
|
return matches[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func installWithOnlineStatus(t *testing.T, tester shellTester, onlineStatus OnlineStatus) string {
|
func installWithOnlineStatus(t testing.TB, tester shellTester, onlineStatus OnlineStatus) string {
|
||||||
if onlineStatus == Online {
|
if onlineStatus == Online {
|
||||||
return installHishtory(t, tester, "")
|
return installHishtory(t, tester, "")
|
||||||
} else {
|
} else {
|
||||||
@ -295,7 +328,7 @@ func installWithOnlineStatus(t *testing.T, tester shellTester, onlineStatus Onli
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertOnlineStatus(t *testing.T, onlineStatus OnlineStatus) {
|
func assertOnlineStatus(t testing.TB, onlineStatus OnlineStatus) {
|
||||||
config := hctx.GetConf(hctx.MakeContext())
|
config := hctx.GetConf(hctx.MakeContext())
|
||||||
if onlineStatus == Online && config.IsOffline == true {
|
if onlineStatus == Online && config.IsOffline == true {
|
||||||
t.Fatalf("We're supposed to be online, yet config.IsOffline=%#v (config=%#v)", config.IsOffline, config)
|
t.Fatalf("We're supposed to be online, yet config.IsOffline=%#v (config=%#v)", config.IsOffline, config)
|
||||||
@ -792,7 +825,7 @@ func getPidofCommand() string {
|
|||||||
return "pidof"
|
return "pidof"
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitForBackgroundSavesToComplete(t *testing.T) {
|
func waitForBackgroundSavesToComplete(t testing.TB) {
|
||||||
lastOut := ""
|
lastOut := ""
|
||||||
lastErr := ""
|
lastErr := ""
|
||||||
for i := 0; i < 20; i++ {
|
for i := 0; i < 20; i++ {
|
||||||
@ -817,11 +850,11 @@ func waitForBackgroundSavesToComplete(t *testing.T) {
|
|||||||
t.Fatalf("failed to wait until hishtory wasn't running (lastOut=%#v, lastErr=%#v)", lastOut, lastErr)
|
t.Fatalf("failed to wait until hishtory wasn't running (lastOut=%#v, lastErr=%#v)", lastOut, lastErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func hishtoryQuery(t *testing.T, tester shellTester, query string) string {
|
func hishtoryQuery(t testing.TB, tester shellTester, query string) string {
|
||||||
return tester.RunInteractiveShell(t, "hishtory query "+query)
|
return tester.RunInteractiveShell(t, "hishtory query "+query)
|
||||||
}
|
}
|
||||||
|
|
||||||
func manuallySubmitHistoryEntry(t *testing.T, userSecret string, entry data.HistoryEntry) {
|
func manuallySubmitHistoryEntry(t testing.TB, userSecret string, entry data.HistoryEntry) {
|
||||||
encEntry, err := data.EncryptHistoryEntry(userSecret, entry)
|
encEntry, err := data.EncryptHistoryEntry(userSecret, entry)
|
||||||
testutils.Check(t, err)
|
testutils.Check(t, err)
|
||||||
if encEntry.Date != entry.EndTime {
|
if encEntry.Date != entry.EndTime {
|
||||||
@ -1584,7 +1617,7 @@ func testConfigGetSet(t *testing.T, tester shellTester) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func clearControlRSearchFromConfig(t *testing.T) {
|
func clearControlRSearchFromConfig(t testing.TB) {
|
||||||
configContents, err := hctx.GetConfigContents()
|
configContents, err := hctx.GetConfigContents()
|
||||||
testutils.Check(t, err)
|
testutils.Check(t, err)
|
||||||
configContents = []byte(strings.ReplaceAll(string(configContents), "enable_control_r_search", "something-else"))
|
configContents = []byte(strings.ReplaceAll(string(configContents), "enable_control_r_search", "something-else"))
|
||||||
@ -1661,7 +1694,7 @@ func TestFish(t *testing.T) {
|
|||||||
|
|
||||||
// TODO(ddworken): Run TestTui in online and offline mode
|
// TODO(ddworken): Run TestTui in online and offline mode
|
||||||
|
|
||||||
func TestTui(t *testing.T) {
|
func testTui(t testing.TB) {
|
||||||
// Setup
|
// Setup
|
||||||
defer testutils.BackupAndRestore(t)()
|
defer testutils.BackupAndRestore(t)()
|
||||||
tester := zshTester{}
|
tester := zshTester{}
|
||||||
@ -1856,7 +1889,7 @@ func TestTui(t *testing.T) {
|
|||||||
assertNoLeakedConnections(t)
|
assertNoLeakedConnections(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func captureTerminalOutput(t *testing.T, tester shellTester, commands []string) string {
|
func captureTerminalOutput(t testing.TB, tester shellTester, commands []string) string {
|
||||||
return captureTerminalOutputWithShellName(t, tester, tester.ShellName(), commands)
|
return captureTerminalOutputWithShellName(t, tester, tester.ShellName(), commands)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1866,7 +1899,7 @@ type TmuxCommand struct {
|
|||||||
ResizeY int
|
ResizeY int
|
||||||
}
|
}
|
||||||
|
|
||||||
func captureTerminalOutputWithShellName(t *testing.T, tester shellTester, overriddenShellName string, commands []string) string {
|
func captureTerminalOutputWithShellName(t testing.TB, tester shellTester, overriddenShellName string, commands []string) string {
|
||||||
sCommands := make([]TmuxCommand, 0)
|
sCommands := make([]TmuxCommand, 0)
|
||||||
for _, command := range commands {
|
for _, command := range commands {
|
||||||
sCommands = append(sCommands, TmuxCommand{Keys: command})
|
sCommands = append(sCommands, TmuxCommand{Keys: command})
|
||||||
@ -1874,7 +1907,7 @@ func captureTerminalOutputWithShellName(t *testing.T, tester shellTester, overri
|
|||||||
return captureTerminalOutputWithShellNameAndDimensions(t, tester, overriddenShellName, 200, 50, sCommands)
|
return captureTerminalOutputWithShellNameAndDimensions(t, tester, overriddenShellName, 200, 50, sCommands)
|
||||||
}
|
}
|
||||||
|
|
||||||
func captureTerminalOutputWithShellNameAndDimensions(t *testing.T, tester shellTester, overriddenShellName string, width, height int, commands []TmuxCommand) string {
|
func captureTerminalOutputWithShellNameAndDimensions(t testing.TB, tester shellTester, overriddenShellName string, width, height int, commands []TmuxCommand) string {
|
||||||
sleepAmount := "0.5"
|
sleepAmount := "0.5"
|
||||||
fullCommand := ""
|
fullCommand := ""
|
||||||
fullCommand += " tmux kill-session -t foo || true\n"
|
fullCommand += " tmux kill-session -t foo || true\n"
|
||||||
@ -2332,11 +2365,11 @@ type deviceOp struct {
|
|||||||
restore func()
|
restore func()
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDevice(t *testing.T, tester shellTester, devices *deviceSet, key, deviceId string) {
|
func createDevice(t testing.TB, tester shellTester, devices *deviceSet, key, deviceId string) {
|
||||||
d := device{key, deviceId}
|
d := device{key, deviceId}
|
||||||
_, ok := (*devices.deviceMap)[d]
|
_, ok := (*devices.deviceMap)[d]
|
||||||
if ok {
|
if ok {
|
||||||
t.Fatal(fmt.Errorf("cannot create device twice for key=%s deviceId=%s", key, deviceId))
|
t.Fatalf("cannot create device twice for key=%s deviceId=%s", key, deviceId)
|
||||||
}
|
}
|
||||||
installHishtory(t, tester, key)
|
installHishtory(t, tester, key)
|
||||||
(*devices.deviceMap)[d] = deviceOp{
|
(*devices.deviceMap)[d] = deviceOp{
|
||||||
@ -2585,7 +2618,7 @@ func FuzzTestMultipleUsers(f *testing.F) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertNoLeakedConnections(t *testing.T) {
|
func assertNoLeakedConnections(t testing.TB) {
|
||||||
resp, err := lib.ApiGet("/api/v1/get-num-connections")
|
resp, err := lib.ApiGet("/api/v1/get-num-connections")
|
||||||
testutils.Check(t, err)
|
testutils.Check(t, err)
|
||||||
numConnections, err := strconv.Atoi(string(resp))
|
numConnections, err := strconv.Atoi(string(resp))
|
||||||
|
@ -54,7 +54,7 @@ func ResetLocalState(t *testing.T) {
|
|||||||
_ = os.RemoveAll(path.Join(homedir, data.GetHishtoryPath()))
|
_ = os.RemoveAll(path.Join(homedir, data.GetHishtoryPath()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func BackupAndRestore(t *testing.T) func() {
|
func BackupAndRestore(t testing.TB) func() {
|
||||||
return BackupAndRestoreWithId(t, "")
|
return BackupAndRestoreWithId(t, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ func getBackPath(file, id string) string {
|
|||||||
return file + ".bak" + id
|
return file + ".bak" + id
|
||||||
}
|
}
|
||||||
|
|
||||||
func BackupAndRestoreWithId(t *testing.T, id string) func() {
|
func BackupAndRestoreWithId(t testing.TB, id string) func() {
|
||||||
ResetFakeHistoryTimestamp()
|
ResetFakeHistoryTimestamp()
|
||||||
homedir, err := os.UserHomeDir()
|
homedir, err := os.UserHomeDir()
|
||||||
Check(t, err)
|
Check(t, err)
|
||||||
@ -266,7 +266,7 @@ func RunTestServer() func() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Check(t *testing.T, err error) {
|
func Check(t testing.TB, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_, filename, line, _ := runtime.Caller(1)
|
_, filename, line, _ := runtime.Caller(1)
|
||||||
t.Fatalf("Unexpected error at %s:%d: %v", filename, line, err)
|
t.Fatalf("Unexpected error at %s:%d: %v", filename, line, err)
|
||||||
@ -309,7 +309,7 @@ func IsGithubAction() bool {
|
|||||||
return os.Getenv("GITHUB_ACTION") != ""
|
return os.Getenv("GITHUB_ACTION") != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLog(t *testing.T, line string) {
|
func TestLog(t testing.TB, line string) {
|
||||||
f, err := os.OpenFile("/tmp/test.log", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
|
f, err := os.OpenFile("/tmp/test.log", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Check(t, err)
|
Check(t, err)
|
||||||
@ -338,7 +338,7 @@ func persistLog() {
|
|||||||
checkError(err)
|
checkError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CompareGoldens(t *testing.T, out, goldenName string) {
|
func CompareGoldens(t testing.TB, out, goldenName string) {
|
||||||
out = normalizeHostnames(out)
|
out = normalizeHostnames(out)
|
||||||
goldenPath := path.Join(initialWd, "client/lib/goldens/", goldenName)
|
goldenPath := path.Join(initialWd, "client/lib/goldens/", goldenName)
|
||||||
expected, err := os.ReadFile(goldenPath)
|
expected, err := os.ReadFile(goldenPath)
|
||||||
|
Loading…
Reference in New Issue
Block a user