Ensure that tests assert a lack of leaked connections

This commit is contained in:
David Dworken 2022-11-26 22:40:43 -08:00
parent f77eb38618
commit 22330f3ee1
No known key found for this signature in database
3 changed files with 59 additions and 1 deletions

View File

@ -442,12 +442,20 @@ func applyDeletionRequestsToBackend(ctx context.Context, request shared.Deletion
} }
func wipeDbHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { func wipeDbHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
if r.Host == "api.hishtory.dev" { if r.Host == "api.hishtory.dev" || isProductionEnvironment() {
panic("refusing to wipe the DB for prod") panic("refusing to wipe the DB for prod")
} }
checkGormResult(GLOBAL_DB.WithContext(ctx).Exec("DELETE FROM enc_history_entries")) checkGormResult(GLOBAL_DB.WithContext(ctx).Exec("DELETE FROM enc_history_entries"))
} }
func getNumConnectionsHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
sqlDb, err := GLOBAL_DB.DB()
if err != nil {
panic(err)
}
w.Write([]byte(fmt.Sprintf("%#v", sqlDb.Stats().OpenConnections)))
}
func isTestEnvironment() bool { func isTestEnvironment() bool {
return os.Getenv("HISHTORY_TEST") != "" return os.Getenv("HISHTORY_TEST") != ""
} }
@ -636,6 +644,9 @@ func InitDB() {
if isProductionEnvironment() { if isProductionEnvironment() {
sqlDb.SetMaxIdleConns(10) sqlDb.SetMaxIdleConns(10)
} }
if isTestEnvironment() {
sqlDb.SetMaxIdleConns(1)
}
} }
func decrementVersion(version string) (string, error) { func decrementVersion(version string) (string, error) {
@ -828,6 +839,7 @@ func main() {
mux.Handle("/internal/api/v1/stats", withLogging(statsHandler)) mux.Handle("/internal/api/v1/stats", withLogging(statsHandler))
if isTestEnvironment() { if isTestEnvironment() {
mux.Handle("/api/v1/wipe-db", withLogging(wipeDbHandler)) mux.Handle("/api/v1/wipe-db", withLogging(wipeDbHandler))
mux.Handle("/api/v1/get-num-connections", withLogging(getNumConnectionsHandler))
} }
fmt.Println("Listening on localhost:8080") fmt.Println("Listening on localhost:8080")
log.Fatal(http.ListenAndServe(":8080", mux)) log.Fatal(http.ListenAndServe(":8080", mux))

View File

@ -16,6 +16,7 @@ import (
"github.com/ddworken/hishtory/shared/testutils" "github.com/ddworken/hishtory/shared/testutils"
"github.com/go-test/deep" "github.com/go-test/deep"
"github.com/google/uuid" "github.com/google/uuid"
"gorm.io/gorm"
) )
func TestESubmitThenQuery(t *testing.T) { func TestESubmitThenQuery(t *testing.T) {
@ -113,6 +114,9 @@ func TestESubmitThenQuery(t *testing.T) {
if len(retrievedEntries) != 2 { if len(retrievedEntries) != 2 {
t.Fatalf("Expected to retrieve 2 entries, found %d", len(retrievedEntries)) t.Fatalf("Expected to retrieve 2 entries, found %d", len(retrievedEntries))
} }
// Assert that we aren't leaking connections
assertNoLeakedConnections(t, GLOBAL_DB)
} }
func TestDumpRequestAndResponse(t *testing.T) { func TestDumpRequestAndResponse(t *testing.T) {
@ -279,6 +283,9 @@ func TestDumpRequestAndResponse(t *testing.T) {
t.Fatalf("DB data is different than input! \ndb =%#v\nentry1=%#v\nentry2=%#v", *dbEntry, entry1Dec, entry2Dec) t.Fatalf("DB data is different than input! \ndb =%#v\nentry1=%#v\nentry2=%#v", *dbEntry, entry1Dec, entry2Dec)
} }
} }
// Assert that we aren't leaking connections
assertNoLeakedConnections(t, GLOBAL_DB)
} }
func TestUpdateReleaseVersion(t *testing.T) { func TestUpdateReleaseVersion(t *testing.T) {
@ -308,6 +315,9 @@ func TestUpdateReleaseVersion(t *testing.T) {
if !strings.HasPrefix(ReleaseVersion, "v0.") { if !strings.HasPrefix(ReleaseVersion, "v0.") {
t.Fatalf("ReleaseVersion wasn't updated to contain a version: %#v", ReleaseVersion) t.Fatalf("ReleaseVersion wasn't updated to contain a version: %#v", ReleaseVersion)
} }
// Assert that we aren't leaking connections
assertNoLeakedConnections(t, GLOBAL_DB)
} }
func TestDeletionRequests(t *testing.T) { func TestDeletionRequests(t *testing.T) {
@ -488,4 +498,18 @@ func TestDeletionRequests(t *testing.T) {
if diff := deep.Equal(*deletionRequest, expected); diff != nil { if diff := deep.Equal(*deletionRequest, expected); diff != nil {
t.Error(diff) t.Error(diff)
} }
// Assert that we aren't leaking connections
assertNoLeakedConnections(t, GLOBAL_DB)
}
func assertNoLeakedConnections(t *testing.T, db *gorm.DB) {
sqlDB, err := db.DB()
if err != nil {
t.Fatal(err)
}
numConns := sqlDB.Stats().OpenConnections
if numConns > 1 {
t.Fatalf("expected DB to have not leak connections, actually have %d", numConns)
}
} }

View File

@ -172,6 +172,9 @@ 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) })
// Assert there are no leaked connections
assertNoLeakedConnections(t)
} }
func testIntegration(t *testing.T, tester shellTester, onlineStatus OnlineStatus) { func testIntegration(t *testing.T, tester shellTester, onlineStatus OnlineStatus) {
@ -298,6 +301,9 @@ yes | hishtory init `+userSecret)
tester.RunInteractiveShell(t, `hishtory config-set displayed-columns Hostname 'Exit Code' Command`) tester.RunInteractiveShell(t, `hishtory config-set displayed-columns Hostname 'Exit Code' Command`)
out = tester.RunInteractiveShell(t, `hishtory query -pipefail | grep -v 'hishtory init ' | grep -v 'ls /'`) out = tester.RunInteractiveShell(t, `hishtory query -pipefail | grep -v 'hishtory init ' | grep -v 'ls /'`)
compareGoldens(t, out, "testIntegrationWithNewDevice-table"+tester.ShellName()) compareGoldens(t, out, "testIntegrationWithNewDevice-table"+tester.ShellName())
// Assert there are no leaked connections
assertNoLeakedConnections(t)
} }
func installHishtory(t *testing.T, tester shellTester, userSecret string) string { func installHishtory(t *testing.T, tester shellTester, userSecret string) string {
@ -453,6 +459,9 @@ echo thisisrecorded`)
t.Fatalf("hishtory query doesn't contain the expected complex command, out=%#v", out) t.Fatalf("hishtory query doesn't contain the expected complex command, out=%#v", out)
} }
// Assert there are no leaked connections
assertNoLeakedConnections(t)
return userSecret return userSecret
} }
@ -2078,6 +2087,9 @@ func testControlR(t *testing.T, tester shellTester, shellName string, onlineStat
if !testutils.IsGithubAction() { if !testutils.IsGithubAction() {
compareGoldens(t, out, "testControlR-SelectMultiline-"+shellName) compareGoldens(t, out, "testControlR-SelectMultiline-"+shellName)
} }
// Assert there are no leaked connections
assertNoLeakedConnections(t)
} }
func testCustomColumns(t *testing.T, tester shellTester) { func testCustomColumns(t *testing.T, tester shellTester) {
@ -2524,6 +2536,16 @@ func FuzzTestMultipleUsers(f *testing.F) {
}) })
} }
func assertNoLeakedConnections(t *testing.T) {
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)
}
}
// TODO: somehow test/confirm that hishtory works even if only bash/only zsh is installed // TODO: somehow test/confirm that hishtory works even if only bash/only zsh is installed
// TODO: TUI tests for multiline commands // TODO: TUI tests for multiline commands