package main import ( "fmt" "log" "net/http" "sync" ) var sem chan Empty // semaphore to limit requess in flight // test accepts a slice of type Redirect and returns an error // it performs actual HTTP GET requests on each source URL and validates that a redirect occurs // to the destination URL and that the redirect type/status code is correct func test(redirects []Redirect) error { wg := &sync.WaitGroup{} mu := &sync.Mutex{} var summaryOutput string sem = make(Semaphore, *maxConcurrentRequests) for _, redirect := range redirects { wg.Add(1) P(1) go func(redirect Redirect) { defer V(1) defer wg.Done() client := &http.Client{ CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, } // writing to std out is critical section mu.Lock() fmt.Printf("Checking redirect for: %s\n", redirect.sourceURL) mu.Unlock() // Make the request resp, err := client.Get(redirect.sourceURL) if err != nil { log.Printf("HTTP GET failed for source URL '%s': %v", redirect.sourceURL, err) } // Check the status code if resp.StatusCode != redirect.statusCode { // Modifying output is critical section mu.Lock() summaryOutput += fmt.Sprintf("redirect for source URL '%s': expected status code'%d': got '%d\n", redirect.sourceURL, redirect.statusCode, resp.StatusCode) mu.Unlock() return } // Check that the redirect went to the correct location destURL, err := resp.Location() if err != nil { log.Printf("failed to parse response location to URL: %v", err) } if destURL.String() != redirect.destinationURL { // Modifying output is critical section mu.Lock() summaryOutput += fmt.Sprintf("redirect for source URL '%s': expected '%s': got '%s\n", redirect.sourceURL, redirect.destinationURL, destURL.String()) mu.Unlock() return } }(redirect) } wg.Wait() fmt.Printf("\ndone tests.\n---------------------------------------------\n") if len(summaryOutput) > 0 { fmt.Printf("Summary:\n\n%s", summaryOutput) } else { fmt.Println("All redirect tests succeeded.") } return nil } // Semaphore helper functions // Empty is an empty struct used by the semaphores type Empty struct{} // Semaphore is a channel which passes empty structs and acts as a resource lock type Semaphore chan Empty // P acquire n resources - standard semaphore design pattern to limit number of requests in flight func P(n int) { e := Empty{} for i := 0; i < n; i++ { sem <- e } } // V release n resources - standard semaphore design pattern to limit number of requests in flight func V(n int) { for i := 0; i < n; i++ { <-sem } }