diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 5be0499ac..507ca5ae5 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -89,5 +89,5 @@ jobs: - name: Run e2e tests working-directory: quesma - run: go test --tags=integration -v ./... + run: go test -race --tags=integration -v ./... diff --git a/quesma/quesma/search_norace_test.go b/quesma/quesma/search_norace_test.go new file mode 100644 index 000000000..9698404c5 --- /dev/null +++ b/quesma/quesma/search_norace_test.go @@ -0,0 +1,120 @@ +//go:build !race + +/* + This file contains tests which can raise a race condition. +*/ + +package quesma + +import ( + "context" + "github.com/DATA-DOG/go-sqlmock" + "github.com/stretchr/testify/assert" + "math/rand" + "mitmproxy/quesma/clickhouse" + "mitmproxy/quesma/concurrent" + "mitmproxy/quesma/logger" + "mitmproxy/quesma/model" + "mitmproxy/quesma/quesma/config" + "mitmproxy/quesma/quesma/ui" + "mitmproxy/quesma/telemetry" + "mitmproxy/quesma/testdata" + "mitmproxy/quesma/tracing" + "testing" + "time" +) + +// TestAllUnsupportedQueryTypesAreProperlyRecorded tests if all unsupported query types are properly recorded. +// It runs |testdata.UnsupportedAggregationsTests| tests, each of them sends one query of unsupported type. +// It ensures that this query type is recorded in the management console, and that all other query types are not. +func TestAllUnsupportedQueryTypesAreProperlyRecorded(t *testing.T) { + for _, tt := range testdata.UnsupportedQueriesTests { + t.Run(tt.TestName, func(t *testing.T) { + if tt.QueryType == "script" { + t.Skip("Only 1 test. We can't deal with scripts inside queries yet. It fails very early, during JSON unmarshalling, so we can't even know the type of aggregation.") + } + db, _, err := sqlmock.New() + if err != nil { + t.Fatal(err) + } + defer db.Close() + assert.NoError(t, err) + + lm := clickhouse.NewLogManagerWithConnection(db, table) + cfg := config.QuesmaConfiguration{IndexConfig: map[string]config.IndexConfiguration{tableName: {Enabled: true}}} + logChan := logger.InitOnlyChannelLoggerForTests(cfg, &tracing.AsyncTraceLogger{AsyncQueryTrace: concurrent.NewMap[string, tracing.TraceCtx]()}) + managementConsole := ui.NewQuesmaManagementConsole(cfg, nil, nil, logChan, telemetry.NewPhoneHomeEmptyAgent()) + go managementConsole.RunOnlyChannelProcessor() + + queryRunner := NewQueryRunner(lm, cfg, nil, managementConsole) + newCtx := context.WithValue(ctx, tracing.RequestIdCtxKey, tracing.GetRequestId()) + _, _ = queryRunner.handleSearch(newCtx, tableName, []byte(tt.QueryRequestJson)) + + for _, queryType := range model.AllQueryTypes { + if queryType != tt.QueryType { + assert.Len(t, managementConsole.QueriesWithUnsupportedType(queryType), 0) + } + } + + // Update of the count below is done asynchronously in another goroutine + // (go managementConsole.RunOnlyChannelProcessor() above), so we might need to wait a bit + assert.Eventually(t, func() bool { + return len(managementConsole.QueriesWithUnsupportedType(tt.QueryType)) == 1 + }, 250*time.Millisecond, 1*time.Millisecond) + assert.Equal(t, 1, managementConsole.GetTotalUnsupportedQueries()) + assert.Equal(t, 1, managementConsole.GetSavedUnsupportedQueries()) + assert.Equal(t, 1, len(managementConsole.GetUnsupportedTypesWithCount())) + }) + } +} + +// TestDifferentUnsupportedQueries tests if different unsupported queries are properly recorded. +// I randomly select requestsNr queries from testdata.UnsupportedAggregationsTests, run them, and check +// if all of them are properly recorded in the management console. +func TestDifferentUnsupportedQueries(t *testing.T) { + const maxSavedQueriesPerQueryType = 10 + const requestsNr = 50 + + // generate random |requestsNr| queries to send + testNrs := make([]int, 0, requestsNr) + testCounts := make([]int, len(testdata.UnsupportedQueriesTests)) + for range requestsNr { + randInt := rand.Intn(len(testdata.UnsupportedQueriesTests)) + if testdata.UnsupportedQueriesTests[randInt].QueryType == "script" { + // We can't deal with scripts inside queries yet. It fails very early, during JSON unmarshalling, so we can't even know the type of aggregation. + continue + } + testNrs = append(testNrs, randInt) + testCounts[randInt]++ + } + + db, _, err := sqlmock.New() + if err != nil { + t.Fatal(err) + } + defer db.Close() + assert.NoError(t, err) + + lm := clickhouse.NewLogManagerWithConnection(db, table) + cfg := config.QuesmaConfiguration{IndexConfig: map[string]config.IndexConfiguration{tableName: {Enabled: true}}} + logChan := logger.InitOnlyChannelLoggerForTests(cfg, &tracing.AsyncTraceLogger{AsyncQueryTrace: concurrent.NewMap[string, tracing.TraceCtx]()}) + managementConsole := ui.NewQuesmaManagementConsole(cfg, nil, nil, logChan, telemetry.NewPhoneHomeEmptyAgent()) + go managementConsole.RunOnlyChannelProcessor() + + queryRunner := NewQueryRunner(lm, cfg, nil, managementConsole) + for _, testNr := range testNrs { + newCtx := context.WithValue(ctx, tracing.RequestIdCtxKey, tracing.GetRequestId()) + _, _ = queryRunner.handleSearch(newCtx, tableName, []byte(testdata.UnsupportedQueriesTests[testNr].QueryRequestJson)) + } + + for i, tt := range testdata.UnsupportedQueriesTests { + // Update of the count below is done asynchronously in another goroutine + // (go managementConsole.RunOnlyChannelProcessor() above), so we might need to wait a bit + assert.Eventually(t, func() bool { + return len(managementConsole.QueriesWithUnsupportedType(tt.QueryType)) == min(testCounts[i], maxSavedQueriesPerQueryType) + }, 600*time.Millisecond, 1*time.Millisecond, + tt.TestName+": wanted: %d, got: %d", min(testCounts[i], maxSavedQueriesPerQueryType), + len(managementConsole.QueriesWithUnsupportedType(tt.QueryType)), + ) + } +} diff --git a/quesma/quesma/search_test.go b/quesma/quesma/search_test.go index 6330a4363..401638ddf 100644 --- a/quesma/quesma/search_test.go +++ b/quesma/quesma/search_test.go @@ -6,10 +6,8 @@ import ( "fmt" "github.com/DATA-DOG/go-sqlmock" "github.com/stretchr/testify/assert" - "math/rand" "mitmproxy/quesma/clickhouse" "mitmproxy/quesma/concurrent" - "mitmproxy/quesma/logger" "mitmproxy/quesma/model" "mitmproxy/quesma/queryparser" "mitmproxy/quesma/quesma/config" @@ -20,7 +18,6 @@ import ( "strconv" "strings" "testing" - "time" ) const defaultAsyncSearchTimeout = 1000 @@ -407,98 +404,3 @@ func TestNumericFacetsQueries(t *testing.T) { } } } - -// TestAllUnsupportedQueryTypesAreProperlyRecorded tests if all unsupported query types are properly recorded. -// It runs |testdata.UnsupportedAggregationsTests| tests, each of them sends one query of unsupported type. -// It ensures that this query type is recorded in the management console, and that all other query types are not. -func TestAllUnsupportedQueryTypesAreProperlyRecorded(t *testing.T) { - for _, tt := range testdata.UnsupportedQueriesTests { - t.Run(tt.TestName, func(t *testing.T) { - if tt.QueryType == "script" { - t.Skip("Only 1 test. We can't deal with scripts inside queries yet. It fails very early, during JSON unmarshalling, so we can't even know the type of aggregation.") - } - db, _, err := sqlmock.New() - if err != nil { - t.Fatal(err) - } - defer db.Close() - assert.NoError(t, err) - - lm := clickhouse.NewLogManagerWithConnection(db, table) - cfg := config.QuesmaConfiguration{IndexConfig: map[string]config.IndexConfiguration{tableName: {Enabled: true}}} - logChan := logger.InitOnlyChannelLoggerForTests(cfg, &tracing.AsyncTraceLogger{AsyncQueryTrace: concurrent.NewMap[string, tracing.TraceCtx]()}) - managementConsole := ui.NewQuesmaManagementConsole(cfg, nil, nil, logChan, telemetry.NewPhoneHomeEmptyAgent()) - go managementConsole.RunOnlyChannelProcessor() - - queryRunner := NewQueryRunner(lm, cfg, nil, managementConsole) - newCtx := context.WithValue(ctx, tracing.RequestIdCtxKey, tracing.GetRequestId()) - _, _ = queryRunner.handleSearch(newCtx, tableName, []byte(tt.QueryRequestJson)) - - for _, queryType := range model.AllQueryTypes { - if queryType != tt.QueryType { - assert.Len(t, managementConsole.QueriesWithUnsupportedType(queryType), 0) - } - } - - // Update of the count below is done asynchronously in another goroutine - // (go managementConsole.RunOnlyChannelProcessor() above), so we might need to wait a bit - assert.Eventually(t, func() bool { - return len(managementConsole.QueriesWithUnsupportedType(tt.QueryType)) == 1 - }, 250*time.Millisecond, 1*time.Millisecond) - assert.Equal(t, 1, managementConsole.GetTotalUnsupportedQueries()) - assert.Equal(t, 1, managementConsole.GetSavedUnsupportedQueries()) - assert.Equal(t, 1, len(managementConsole.GetUnsupportedTypesWithCount())) - }) - } -} - -// TestDifferentUnsupportedQueries tests if different unsupported queries are properly recorded. -// I randomly select requestsNr queries from testdata.UnsupportedAggregationsTests, run them, and check -// if all of them are properly recorded in the management console. -func TestDifferentUnsupportedQueries(t *testing.T) { - const maxSavedQueriesPerQueryType = 10 - const requestsNr = 50 - - // generate random |requestsNr| queries to send - testNrs := make([]int, 0, requestsNr) - testCounts := make([]int, len(testdata.UnsupportedQueriesTests)) - for range requestsNr { - randInt := rand.Intn(len(testdata.UnsupportedQueriesTests)) - if testdata.UnsupportedQueriesTests[randInt].QueryType == "script" { - // We can't deal with scripts inside queries yet. It fails very early, during JSON unmarshalling, so we can't even know the type of aggregation. - continue - } - testNrs = append(testNrs, randInt) - testCounts[randInt]++ - } - - db, _, err := sqlmock.New() - if err != nil { - t.Fatal(err) - } - defer db.Close() - assert.NoError(t, err) - - lm := clickhouse.NewLogManagerWithConnection(db, table) - cfg := config.QuesmaConfiguration{IndexConfig: map[string]config.IndexConfiguration{tableName: {Enabled: true}}} - logChan := logger.InitOnlyChannelLoggerForTests(cfg, &tracing.AsyncTraceLogger{AsyncQueryTrace: concurrent.NewMap[string, tracing.TraceCtx]()}) - managementConsole := ui.NewQuesmaManagementConsole(cfg, nil, nil, logChan, telemetry.NewPhoneHomeEmptyAgent()) - go managementConsole.RunOnlyChannelProcessor() - - queryRunner := NewQueryRunner(lm, cfg, nil, managementConsole) - for _, testNr := range testNrs { - newCtx := context.WithValue(ctx, tracing.RequestIdCtxKey, tracing.GetRequestId()) - _, _ = queryRunner.handleSearch(newCtx, tableName, []byte(testdata.UnsupportedQueriesTests[testNr].QueryRequestJson)) - } - - for i, tt := range testdata.UnsupportedQueriesTests { - // Update of the count below is done asynchronously in another goroutine - // (go managementConsole.RunOnlyChannelProcessor() above), so we might need to wait a bit - assert.Eventually(t, func() bool { - return len(managementConsole.QueriesWithUnsupportedType(tt.QueryType)) == min(testCounts[i], maxSavedQueriesPerQueryType) - }, 600*time.Millisecond, 1*time.Millisecond, - tt.TestName+": wanted: %d, got: %d", min(testCounts[i], maxSavedQueriesPerQueryType), - len(managementConsole.QueriesWithUnsupportedType(tt.QueryType)), - ) - } -}