From 85dacbe2dead9d0016ad8bb225c8e635bbe7eaca Mon Sep 17 00:00:00 2001 From: Steven Polley Date: Fri, 29 May 2020 21:44:20 -0600 Subject: [PATCH] add support for testing redirection --- test.go | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/test.go b/test.go index 1fcca18..8d2539e 100644 --- a/test.go +++ b/test.go @@ -1,9 +1,100 @@ 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 { //TBD: implement + 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---------------------------------------------\nSummary:\n\n%s", summaryOutput) + 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 + } +}