From 28a721887c4bbe79b8298be9aac206954119d6fa Mon Sep 17 00:00:00 2001 From: partisan Date: Fri, 6 Dec 2024 15:33:02 +0100 Subject: [PATCH] added concurrent search test --- tests/integration_test.go | 153 +++++++++++++++++++++++++++++++++++--- 1 file changed, 141 insertions(+), 12 deletions(-) diff --git a/tests/integration_test.go b/tests/integration_test.go index 805add8..588e630 100644 --- a/tests/integration_test.go +++ b/tests/integration_test.go @@ -3,12 +3,17 @@ package tests import ( "bufio" "context" + "crypto/rand" "encoding/json" + "fmt" "io" + "math/big" "net/http" + "net/url" "os" "os/exec" "path/filepath" + "sync" "syscall" "testing" "time" @@ -17,11 +22,12 @@ import ( ) type TestSummary struct { - Passed []string - Failed []string - CPUUsage []float64 - RAMUsage []uint64 - CacheTests []CacheTestResult + Passed []string + Failed []string + CPUUsage []float64 + RAMUsage []uint64 + CacheTests []CacheTestResult + ConcurrentTestStats ConcurrentTestStats } type CacheTestResult struct { @@ -31,9 +37,21 @@ type CacheTestResult struct { IsCacheEffective bool } +type ConcurrentTestStats struct { + TotalRequests int + TotalFailures int + RequestsPerType map[string]int + FailuresPerType map[string]int +} + func TestApplication(t *testing.T) { // Initialize test summary - summary := &TestSummary{} + summary := &TestSummary{ + ConcurrentTestStats: ConcurrentTestStats{ + RequestsPerType: make(map[string]int), + FailuresPerType: make(map[string]int), + }, + } // Ensure the test runs from the root directory rootDir := "../" // Path to the root directory of the repository @@ -136,6 +154,9 @@ func TestApplication(t *testing.T) { runTest(t, summary, "Test Search Types and Cache Effectiveness", func(t *testing.T) { testSearchTypesAndCache(t, summary) }) + runTest(t, summary, "Test Concurrent Random Requests", func(t *testing.T) { + testConcurrentRandomRequests(t, summary) + }) runTest(t, summary, "Check Resource Usage After Tests", func(t *testing.T) { checkResourceUsage(t, summary, appProcess) }) @@ -187,6 +208,14 @@ func printSummary(summary *TestSummary, t *testing.T) { t.Logf(" Cache Effective: No") } } + + t.Logf("\nConcurrent Random Requests Test Stats:") + t.Logf(" Total Requests Sent: %d", summary.ConcurrentTestStats.TotalRequests) + t.Logf(" Total Failures: %d", summary.ConcurrentTestStats.TotalFailures) + for stype, reqCount := range summary.ConcurrentTestStats.RequestsPerType { + failCount := summary.ConcurrentTestStats.FailuresPerType[stype] + t.Logf(" Type '%s': %d requests, %d failures", stype, reqCount, failCount) + } t.Logf("\n======================\n") } @@ -199,7 +228,6 @@ func checkResourceUsage(t *testing.T, summary *TestSummary, appProcess *process. } else { // Sum the CPU usage of the main process and its children totalCPU := cpuPercent - // Get child processes children, err := appProcess.Children() if err != nil { t.Logf("Failed to get child processes: %v", err) @@ -224,7 +252,6 @@ func checkResourceUsage(t *testing.T, summary *TestSummary, appProcess *process. summary.RAMUsage = append(summary.RAMUsage, 0) } else { totalRAM := memInfo.RSS - // Get memory info for child processes children, err := appProcess.Children() if err != nil { t.Logf("Failed to get child processes: %v", err) @@ -278,12 +305,16 @@ func testEndpoints(t *testing.T) { func testSearchTypesAndCache(t *testing.T, summary *TestSummary) { searchTypes := []string{"text", "image", "video", "forum", "map", "file"} + client := &http.Client{ + Timeout: 10 * time.Second, + } + for _, searchType := range searchTypes { url := "http://localhost:5000/search?q=test&t=" + searchType // Initial search start := time.Now() - resp, err := http.Get(url) + resp, err := client.Get(url) if err != nil { t.Errorf("Failed to GET %s: %v", url, err) continue @@ -303,10 +334,16 @@ func testSearchTypesAndCache(t *testing.T, summary *TestSummary) { } t.Logf("Search type '%s' took %v", searchType, initialDuration) + // If initial search took longer than 10 seconds, skip + if initialDuration > 10*time.Second { + t.Logf("Search type '%s' took too long (%v), skipping cached search", searchType, initialDuration) + continue + } + // Cached search - time.Sleep(1 * time.Second) // Short delay to simulate time between searches + time.Sleep(1 * time.Second) start = time.Now() - resp, err = http.Get(url) + resp, err = client.Get(url) if err != nil { t.Errorf("Failed to GET %s (cached): %v", url, err) continue @@ -332,7 +369,6 @@ func testSearchTypesAndCache(t *testing.T, summary *TestSummary) { t.Errorf("Cache not effective for search type '%s'", searchType) } - // Record the results summary.CacheTests = append(summary.CacheTests, CacheTestResult{ SearchType: searchType, InitialDuration: initialDuration, @@ -368,3 +404,96 @@ func testSuggestionsAPI(t *testing.T) { func bToMb(b uint64) uint64 { return b / 1024 / 1024 } + +func testConcurrentRandomRequests(t *testing.T, summary *TestSummary) { + searchTypes := []string{"text", "image", "video", "forum", "map", "file"} + numRequests := 10 // Number of requests per search type + client := &http.Client{ + Timeout: 10 * time.Second, + } + + var wg sync.WaitGroup + + for _, stype := range searchTypes { + summary.ConcurrentTestStats.RequestsPerType[stype] = numRequests + for i := 0; i < numRequests; i++ { + wg.Add(1) + go func(searchType string) { + defer wg.Done() + query := getRandomUserQuery(searchType) + fullURL := fmt.Sprintf("http://localhost:5000/search?q=%s&t=%s", url.QueryEscape(query), searchType) + resp, err := client.Get(fullURL) + if err != nil { + t.Errorf("Failed to GET %s: %v", fullURL, err) + summary.ConcurrentTestStats.TotalFailures++ + summary.ConcurrentTestStats.FailuresPerType[searchType]++ + return + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + t.Errorf("Search type '%s' with query '%s' returned status code %d", searchType, query, resp.StatusCode) + summary.ConcurrentTestStats.TotalFailures++ + summary.ConcurrentTestStats.FailuresPerType[searchType]++ + } + }(stype) + } + } + + // Wait for all requests to complete + wg.Wait() + + summary.ConcurrentTestStats.TotalRequests = numRequests * len(searchTypes) + t.Logf("Concurrent random requests test completed: %d requests sent", summary.ConcurrentTestStats.TotalRequests) + if summary.ConcurrentTestStats.TotalFailures > 0 { + t.Errorf("Number of failed requests: %d", summary.ConcurrentTestStats.TotalFailures) + } +} + +// getRandomUserQuery returns a random user-like search query based on the search type +func getRandomUserQuery(searchType string) string { + var queries []string + switch searchType { + case "text": + queries = []string{ + "weather forecast", "latest tech news", "open source software", "how to cook pasta", + "learn golang", "famous quotes", "best laptops 2024", "history of linux", + "simple bread recipe", "mountain climbing tips", + } + case "image": + queries = []string{ + "cute cat pictures", "beautiful landscapes", "famous paintings", "colorful birds", + "space wallpapers", "vintage cars", "r/unixporn", "minimalist backgrounds", + } + case "video": + queries = []string{ + "cute cats", "music videos", "sports highlights", "coding tutorials", + "documentaries about space", "stand-up comedy clips", "top movie trailers", + } + case "forum": + queries = []string{ + "linux help forum", "DIY electronics discussion", "programming Q&A", + "travel tips community", "best gaming computers", "homebrewing advice", + "gardening support", "car maintenance forum", "best homelab setup", + } + case "map": + queries = []string{ + "coffee shops in New York", "best pizza places in Chicago", "tourist attractions Tokyo", + "Brazil", "public parks in Berlin", "bookstores in London", "Japan", + } + case "file": + queries = []string{ + "debian iso", "free ebooks", "open source fonts", "linux distribution torrents", + "arch iso", "alpine linux", + } + default: + // fallback if unknown type + queries = []string{"test query", "random search", "hello world"} + } + + // Pick a random query + n, err := rand.Int(rand.Reader, big.NewInt(int64(len(queries)))) + if err != nil { + return queries[0] // fallback to first if error occurs + } + return queries[n.Int64()] +}