From 305f574f155afa5bd32aa56d29a42d2c1e064981 Mon Sep 17 00:00:00 2001 From: perennial Date: Tue, 24 Sep 2024 16:40:53 +1000 Subject: [PATCH] Refactor proxy checker initialisation and shutdown Restructure the proxy checking logic to use a ticker for periodic checks when a non-zero interval is set. Improve handling of zero interval case by waiting for a stop signal. Improve stop mechanism and initialisation of stopChan. Also introduce a new test file proxy_checker_test.go for unit testing. --- server/proxy_checker/proxy_checker.go | 42 ++++--- server/proxy_checker/proxy_checker_test.go | 140 +++++++++++++++++++++ 2 files changed, 163 insertions(+), 19 deletions(-) create mode 100644 server/proxy_checker/proxy_checker_test.go diff --git a/server/proxy_checker/proxy_checker.go b/server/proxy_checker/proxy_checker.go index a1a98eb..c76ebe4 100644 --- a/server/proxy_checker/proxy_checker.go +++ b/server/proxy_checker/proxy_checker.go @@ -19,39 +19,43 @@ const ( var ( workingProxies []string workingProxiesMutex sync.RWMutex - stopChan chan struct{} = make(chan struct{}) + stopChan chan struct{} ) func InitializeProxyChecker() chan struct{} { + stopChan = make(chan struct{}) firstCheckDone := make(chan struct{}) + go func() { - for { - select { - case <-stopChan: - log.Print("Stopping proxy checker...") - return - default: - checkProxies() + checkProxies() + close(firstCheckDone) // Signal that the first check is done + + if t := config.GlobalConfig.ProxyCheckInterval; t > 0 { + ticker := time.NewTicker(t) + defer ticker.Stop() + + for { select { - case <-firstCheckDone: - // First check already done, do nothing - default: - close(firstCheckDone) // Signal that the first check is done - } - if t := config.GlobalConfig.ProxyCheckInterval; t > 0 { - time.Sleep(t) - } else { - log.Print("Proxy check interval set to 0, disabling auto-check from now on.") - select {} // Sweet dreams! + case <-stopChan: + log.Print("Stop signal received. Stopping proxy checker...") + return + case <-ticker.C: + checkProxies() } } + } else { + log.Print("Proxy check interval set to 0, no further checks will be performed.") + <-stopChan // Wait for stop signal } }() + return firstCheckDone } func StopProxyChecker() { - close(stopChan) + if stopChan != nil { + close(stopChan) + } } func checkProxies() { diff --git a/server/proxy_checker/proxy_checker_test.go b/server/proxy_checker/proxy_checker_test.go new file mode 100644 index 0000000..0fc024f --- /dev/null +++ b/server/proxy_checker/proxy_checker_test.go @@ -0,0 +1,140 @@ +package proxy_checker + +import ( + "net/http" + "net/http/httptest" + "sync" + "testing" + "time" + + "codeberg.org/vnpower/pixivfe/v2/config" +) + +func TestUpdateAndGetWorkingProxies(t *testing.T) { + t.Parallel() + newProxies := []string{"http://proxy1.invalid", "http://proxy2.invalid"} + updateWorkingProxies(newProxies) + + result := GetWorkingProxies() + if len(result) != len(newProxies) { + t.Errorf("Expected %d proxies, got %d", len(newProxies), len(result)) + } + + for i, proxy := range result { + if proxy != newProxies[i] { + t.Errorf("Expected proxy %s, got %s", newProxies[i], proxy) + } + } + + // Test concurrent access + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + _ = GetWorkingProxies() + }() + } + wg.Wait() +} + +func TestTestProxy(t *testing.T) { + t.Parallel() + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + })) + defer server.Close() + + isWorking, resp := testProxy(server.URL) + if !isWorking { + t.Errorf("Expected proxy to be working") + } + if resp.StatusCode != http.StatusOK { + t.Errorf("Expected status code %d, got %d", http.StatusOK, resp.StatusCode) + } + + isWorking, resp = testProxy("http://nonexistentproxy.invalid") + if isWorking { + t.Errorf("Expected proxy to be not working") + } + if resp != nil { + t.Errorf("Expected nil response for non-working proxy") + } + + // Test with a server that returns an error status + errorServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + })) + defer errorServer.Close() + + isWorking, resp = testProxy(errorServer.URL) + if isWorking { + t.Errorf("Expected proxy to be not working due to error status") + } + if resp == nil || resp.StatusCode != http.StatusInternalServerError { + t.Errorf("Expected error status response") + } +} + +func TestProxyCheckerBehavior(t *testing.T) { + // Set up test configuration + config.GlobalConfig.ProxyCheckInterval = 10 * time.Millisecond + config.BuiltinProxyList = []string{"http://proxy1.invalid", "http://proxy2.invalid"} + + var wg sync.WaitGroup + wg.Add(1) + + go func() { + defer wg.Done() + done := InitializeProxyChecker() + <-done // Wait for first check to complete + + time.Sleep(15 * time.Millisecond) // Wait for one more check + + StopProxyChecker() + }() + + wg.Wait() + + proxies := GetWorkingProxies() + if len(proxies) > 0 { + t.Errorf("Expected no working proxies, got %d", len(proxies)) + } +} + +func TestInitializeProxyChecker(t *testing.T) { + config.GlobalConfig.ProxyCheckInterval = 10 * time.Millisecond + config.BuiltinProxyList = []string{"http://proxy1.invalid", "http://proxy2.invalid"} + + done := InitializeProxyChecker() + <-done // Wait for first check to complete + + time.Sleep(15 * time.Millisecond) // Wait for one more check + + StopProxyChecker() + + // Ensure that the proxy checker can be restarted + done = InitializeProxyChecker() + <-done + + StopProxyChecker() +} + +func TestProxyCheckerWithZeroInterval(t *testing.T) { + config.GlobalConfig.ProxyCheckInterval = 0 + config.BuiltinProxyList = []string{"http://proxy1.invalid", "http://proxy2.invalid"} + + done := InitializeProxyChecker() + <-done // Wait for first check to complete + + initialProxies := len(GetWorkingProxies()) + + time.Sleep(50 * time.Millisecond) // Wait to ensure no more checks are performed + + if len(GetWorkingProxies()) != initialProxies { + t.Errorf("Expected proxy count to remain %d, but got %d", initialProxies, len(GetWorkingProxies())) + } + + // StopProxyChecker should not cause any issues + StopProxyChecker() +}