package testutils import ( "bytes" "fmt" "io" "log" "net/http" "os" "os/exec" "path" "path/filepath" "runtime" "strings" "testing" "time" "github.com/ddworken/hishtory/client/data" ) const ( DB_WAL_PATH = data.DB_PATH + "-wal" DB_SHM_PATH = data.DB_PATH + "-shm" ) func ResetLocalState(t *testing.T) { homedir, err := os.UserHomeDir() if err != nil { t.Fatalf("failed to retrieve homedir: %v", err) } _ = BackupAndRestoreWithId(t, "-reset-local-state") _ = os.RemoveAll(path.Join(homedir, data.HISHTORY_PATH)) } func BackupAndRestore(t *testing.T) func() { return BackupAndRestoreWithId(t, "") } func getBackPath(file, id string) string { if strings.Contains(file, "/"+data.HISHTORY_PATH+"/") { return strings.Replace(file, data.HISHTORY_PATH, data.HISHTORY_PATH+".test", 1) + id } return file + ".bak" + id } func BackupAndRestoreWithId(t *testing.T, id string) func() { ResetFakeHistoryTimestamp() homedir, err := os.UserHomeDir() Check(t, err) initialWd, err := os.Getwd() Check(t, err) Check(t, os.MkdirAll(path.Join(homedir, data.HISHTORY_PATH+".test"), os.ModePerm)) renameFiles := []string{ path.Join(homedir, data.HISHTORY_PATH, data.DB_PATH), path.Join(homedir, data.HISHTORY_PATH, DB_WAL_PATH), path.Join(homedir, data.HISHTORY_PATH, DB_SHM_PATH), path.Join(homedir, data.HISHTORY_PATH, data.CONFIG_PATH), path.Join(homedir, data.HISHTORY_PATH, "hishtory"), path.Join(homedir, data.HISHTORY_PATH, "config.sh"), path.Join(homedir, data.HISHTORY_PATH, "config.zsh"), path.Join(homedir, data.HISHTORY_PATH, "config.fish"), path.Join(homedir, ".bash_history"), path.Join(homedir, ".zsh_history"), path.Join(homedir, ".local/share/fish/fish_history"), } for _, file := range renameFiles { touchFile(file) _ = os.Rename(file, getBackPath(file, id)) } copyFiles := []string{ path.Join(homedir, ".zshrc"), path.Join(homedir, ".bashrc"), path.Join(homedir, ".bash_profile"), } for _, file := range copyFiles { touchFile(file) _ = copy(file, getBackPath(file, id)) } configureZshrc(homedir) touchFile(path.Join(homedir, ".bash_history")) touchFile(path.Join(homedir, ".zsh_history")) touchFile(path.Join(homedir, ".local/share/fish/fish_history")) return func() { Check(t, os.MkdirAll(path.Join(homedir, data.HISHTORY_PATH), os.ModePerm)) for _, file := range renameFiles { checkError(os.Rename(getBackPath(file, id), file)) } for _, file := range copyFiles { checkError(copy(getBackPath(file, id), file)) } if runtime.GOOS != "windows" { cmd := exec.Command("killall", "hishtory") stdout, err := cmd.Output() if err != nil && err.Error() != "exit status 1" { t.Fatalf("failed to execute killall hishtory, stdout=%#v: %v", string(stdout), err) } } checkError(os.Chdir(initialWd)) } } func touchFile(p string) { _, err := os.Stat(p) if os.IsNotExist(err) { checkError(os.MkdirAll(filepath.Dir(p), os.ModePerm)) file, err := os.Create(p) checkError(err) defer file.Close() } else { currentTime := time.Now().Local() err := os.Chtimes(p, currentTime, currentTime) checkError(err) } } func configureZshrc(homedir string) { f, err := os.OpenFile(path.Join(homedir, ".zshrc"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) checkError(err) defer f.Close() _, err = f.WriteString(`export HISTFILE=~/.zsh_history export HISTSIZE=10000 export SAVEHIST=1000 setopt SHARE_HISTORY `) checkError(err) } func copy(src, dst string) error { in, err := os.Open(src) if err != nil { return err } defer in.Close() out, err := os.Create(dst) if err != nil { return err } defer out.Close() _, err = io.Copy(out, in) if err != nil { return err } return out.Close() } func BackupAndRestoreEnv(k string) func() { origValue := os.Getenv(k) return func() { os.Setenv(k, origValue) } } func checkError(err error) { if err != nil { _, filename, line, _ := runtime.Caller(1) _, cf, cl, _ := runtime.Caller(2) log.Fatalf("testutils fatal error at %s:%d (caller: %s:%d): %v", filename, line, cf, cl, err) } } func buildServer() string { for i := 0; i < 100; i++ { wd, err := os.Getwd() if err != nil { panic(fmt.Sprintf("failed to getwd: %v", err)) } if strings.HasSuffix(wd, "hishtory") { break } err = os.Chdir("../") if err != nil { panic(fmt.Sprintf("failed to chdir: %v", err)) } if wd == "/" { panic("failed to cd into hishtory dir!") } } version, err := os.ReadFile("VERSION") if err != nil { if runtime.GOOS == "windows" { // TODO: Figure out why it can't read the VERSION file version = []byte("174") } else { panic(fmt.Sprintf("failed to read VERSION file: %v", err)) } } f, err := os.CreateTemp("", "server") checkError(err) fn := f.Name() cmd := exec.Command("go", "build", "-o", fn, "-ldflags", fmt.Sprintf("-X main.ReleaseVersion=v0.%s", version), "backend/server/server.go") var stdout bytes.Buffer cmd.Stdout = &stdout var stderr bytes.Buffer cmd.Stderr = &stderr err = cmd.Start() if err != nil { panic(fmt.Sprintf("failed to start to build server: %v, stderr=%#v, stdout=%#v", err, stderr.String(), stdout.String())) } err = cmd.Wait() if err != nil { wd, _ := os.Getwd() panic(fmt.Sprintf("failed to build server: %v, wd=%#v, stderr=%#v, stdout=%#v", err, wd, stderr.String(), stdout.String())) } return fn } func RunTestServer() func() { os.Setenv("HISHTORY_SERVER", "http://localhost:8080") fn := buildServer() cmd := exec.Command(fn) var stdout bytes.Buffer cmd.Stdout = &stdout var stderr bytes.Buffer cmd.Stderr = &stderr err := cmd.Start() if err != nil { panic(fmt.Sprintf("failed to start server: %v", err)) } time.Sleep(time.Second * 5) go func() { _ = cmd.Wait() }() return func() { err := cmd.Process.Kill() if err != nil && err.Error() != "os: process already finished" { panic(fmt.Sprintf("failed to kill server process: %v", err)) } if strings.Contains(stderr.String()+stdout.String(), "failed to") && IsOnline() { panic(fmt.Sprintf("server failed to do something: stderr=%#v, stdout=%#v", stderr.String(), stdout.String())) } if strings.Contains(stderr.String()+stdout.String(), "ERROR:") { panic(fmt.Sprintf("server experienced an error: stderr=%#v, stdout=%#v", stderr.String(), stdout.String())) } // fmt.Printf("stderr=%#v, stdout=%#v\n", stderr.String(), stdout.String()) } } func Check(t *testing.T, err error) { if err != nil { _, filename, line, _ := runtime.Caller(1) t.Fatalf("Unexpected error at %s:%d: %v", filename, line, err) } } func CheckWithInfo(t *testing.T, err error, additionalInfo string) { if err != nil { _, filename, line, _ := runtime.Caller(1) t.Fatalf("Unexpected error: %v at %s:%d! Additional info: %v", err, filename, line, additionalInfo) } } func IsOnline() bool { _, err := http.Get("https://hishtory.dev") return err == nil } var fakeHistoryTimestamp int64 = 1666068191 func ResetFakeHistoryTimestamp() { fakeHistoryTimestamp = 1666068191 } func MakeFakeHistoryEntry(command string) data.HistoryEntry { fakeHistoryTimestamp += 5 return data.HistoryEntry{ LocalUsername: "david", Hostname: "localhost", Command: command, CurrentWorkingDirectory: "/tmp/", HomeDirectory: "/home/david/", ExitCode: 2, StartTime: time.Unix(fakeHistoryTimestamp, 0), EndTime: time.Unix(fakeHistoryTimestamp+3, 0), } } func IsGithubAction() bool { return os.Getenv("GITHUB_ACTION") != "" }