From 9f51e4ddfefb00bcae87aae26a75d267f6c4185e Mon Sep 17 00:00:00 2001 From: Alan Norton Date: Wed, 25 Mar 2020 15:30:46 -0400 Subject: [PATCH 01/10] exercise puma-dev dns lookup is there really no way to specify test-only dependencies? move unexported methods to the bottom first pass at catching errors from dns port bind refactor to use tomb over chan expose errors in listening for TLS and DNS remove default error handling. bail if we didn't get a good address remove "debug" messaging about not regenerating keypair nevermind -- most folks don't run this interactively, and its handy to have it in the log for now, just skip dns tests outside darwin fix compile error --- cmd/puma-dev/main_darwin.go | 22 ++-- cmd/puma-dev/main_test.go | 210 ++++++++++++++++++++---------------- dev/dns.go | 30 +++--- go.mod | 1 + go.sum | 6 ++ 5 files changed, 154 insertions(+), 115 deletions(-) diff --git a/cmd/puma-dev/main_darwin.go b/cmd/puma-dev/main_darwin.go index 18cb554e..4dfb40b6 100644 --- a/cmd/puma-dev/main_darwin.go +++ b/cmd/puma-dev/main_darwin.go @@ -143,11 +143,15 @@ func main() { fmt.Printf("* HTTPS Server port: %d\n", *fTLSPort) } - var dns dev.DNSResponder - - dns.Address = fmt.Sprintf("127.0.0.1:%d", *fPort) - - go dns.Serve(domains) + { + var dns dev.DNSResponder + dns.Address = fmt.Sprintf("127.0.0.1:%d", *fPort) + go func() { + if err := dns.Serve(domains); err != nil { + fmt.Printf("! DNS failed: %v\n", err) + } + }() + } var http dev.HTTPServer @@ -169,9 +173,13 @@ func main() { tlsSocketName = "SocketTLS" } - fmt.Printf("! Puma dev listening on http and https\n") + fmt.Printf("! Puma dev running...\n") - go http.ServeTLS(tlsSocketName) + go func() { + if err := http.ServeTLS(tlsSocketName); err != nil { + fmt.Printf("! HTTPS failed: %v\n", err) + } + }() err = http.Serve(socketName) if err != nil { diff --git a/cmd/puma-dev/main_test.go b/cmd/puma-dev/main_test.go index 04945c0b..a265f2dd 100644 --- a/cmd/puma-dev/main_test.go +++ b/cmd/puma-dev/main_test.go @@ -1,6 +1,7 @@ package main import ( + "context" "encoding/json" "errors" "fmt" @@ -11,6 +12,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strings" "testing" "time" @@ -30,96 +32,6 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } -func generateLivePumaDevCertIfNotExist(t *testing.T) { - liveSupportPath := homedir.MustExpand(dev.SupportDir) - liveCertPath := filepath.Join(liveSupportPath, "cert.pem") - liveKeyPath := filepath.Join(liveSupportPath, "key.pem") - - if !FileExists(liveCertPath) || !FileExists(liveKeyPath) { - MakeDirectoryOrFail(t, liveSupportPath) - - if err := dev.GeneratePumaDevCertificateAuthority(liveCertPath, liveKeyPath); err != nil { - assert.FailNow(t, err.Error()) - } - } -} - -func launchPumaDevBackgroundServerWithDefaults(t *testing.T) func() { - StubCommandLineArgs() - - SetFlagOrFail(t, "dir", testAppLinkDirPath) - - generateLivePumaDevCertIfNotExist(t) - - go func() { - main() - }() - - err := retry.Do( - func() error { - _, err := net.DialTimeout("tcp", fmt.Sprintf("localhost:%d", *fHTTPPort), time.Duration(10*time.Second)) - return err - }, - retry.OnRetry(func(n uint, err error) { - log.Printf("#%d: %s\n", n, err) - }), - ) - - assert.NoError(t, err) - - return func() { - RemoveDirectoryOrFail(t, testAppLinkDirPath) - } -} - -func getURLWithHost(t *testing.T, url string, host string) string { - req, _ := http.NewRequest("GET", url, nil) - req.Host = host - - resp, err := http.DefaultClient.Do(req) - - if err != nil { - assert.FailNow(t, err.Error()) - } - - defer resp.Body.Close() - - bodyBytes, _ := ioutil.ReadAll(resp.Body) - - return strings.TrimSpace(string(bodyBytes)) -} - -func pollForEvent(t *testing.T, app string, event string, reason string) error { - return retry.Do( - func() error { - body := getURLWithHost(t, fmt.Sprintf("http://localhost:%d/events", *fHTTPPort), "puma-dev") - eachEvent := strings.Split(body, "\n") - - for _, line := range eachEvent { - var rawEvt interface{} - if err := json.Unmarshal([]byte(line), &rawEvt); err != nil { - assert.FailNow(t, err.Error()) - } - evt := rawEvt.(map[string]interface{}) - LogDebugf("%+v", evt) - - eventFound := (app == "" || evt["app"] == app) && - (event == "" || evt["event"] == event) && - (reason == "" || evt["reason"] == reason) - - if eventFound { - return nil - } - } - - return errors.New("not found") - }, - retry.RetryIf(func(err error) bool { - return err.Error() == "not found" - }), - ) -} - func TestMainPumaDev(t *testing.T) { defer launchPumaDevBackgroundServerWithDefaults(t)() @@ -137,6 +49,29 @@ func TestMainPumaDev(t *testing.T) { } } + t.Run("resolve dns", func(t *testing.T) { + if runtime.GOOS != "darwin" { + t.SkipNow() + } + + PumaDevDNSDialer := func(ctx context.Context, network, address string) (net.Conn, error) { + d := net.Dialer{} + // TODO: use fPort if available, break out this test into main_darwin_test.go + return d.DialContext(ctx, "udp", "127.0.0.1:9253") + } + + r := net.Resolver{ + PreferGo: true, + Dial: PumaDevDNSDialer, + } + + ctx := context.Background() + ips, err := r.LookupIPAddr(ctx, "foo.test") + + assert.NoError(t, err) + assert.Equal(t, net.ParseIP("127.0.0.1").To4(), ips[0].IP.To4()) + }) + t.Run("status", func(t *testing.T) { reqURL := fmt.Sprintf("http://localhost:%d/status", *fHTTPPort) statusHost := "puma-dev" @@ -294,3 +229,98 @@ func TestMain_allCheck_badArg(t *testing.T) { assert.True(t, ok) assert.False(t, exit.Success()) } + +func generateLivePumaDevCertIfNotExist(t *testing.T) { + liveSupportPath := homedir.MustExpand(dev.SupportDir) + liveCertPath := filepath.Join(liveSupportPath, "cert.pem") + liveKeyPath := filepath.Join(liveSupportPath, "key.pem") + + if !FileExists(liveCertPath) || !FileExists(liveKeyPath) { + MakeDirectoryOrFail(t, liveSupportPath) + + if err := dev.GeneratePumaDevCertificateAuthority(liveCertPath, liveKeyPath); err != nil { + assert.FailNow(t, err.Error()) + } + } +} + +func launchPumaDevBackgroundServerWithDefaults(t *testing.T) func() { + address := fmt.Sprintf("localhost:%d", *fHTTPPort) + timeout := time.Duration(10 * time.Second) + + if _, err := net.DialTimeout("tcp", address, timeout); err == nil { + return func() {} + } + + StubCommandLineArgs() + SetFlagOrFail(t, "dir", testAppLinkDirPath) + generateLivePumaDevCertIfNotExist(t) + + go func() { + main() + }() + + err := retry.Do( + func() error { + _, err := net.DialTimeout("tcp", address, timeout) + return err + }, + retry.OnRetry(func(n uint, err error) { + log.Printf("#%d: %s\n", n, err) + }), + ) + + assert.NoError(t, err) + + return func() { + RemoveDirectoryOrFail(t, testAppLinkDirPath) + } +} + +func getURLWithHost(t *testing.T, url string, host string) string { + req, _ := http.NewRequest("GET", url, nil) + req.Host = host + + resp, err := http.DefaultClient.Do(req) + + if err != nil { + assert.FailNow(t, err.Error()) + } + + defer resp.Body.Close() + + bodyBytes, _ := ioutil.ReadAll(resp.Body) + + return strings.TrimSpace(string(bodyBytes)) +} + +func pollForEvent(t *testing.T, app string, event string, reason string) error { + return retry.Do( + func() error { + body := getURLWithHost(t, fmt.Sprintf("http://localhost:%d/events", *fHTTPPort), "puma-dev") + eachEvent := strings.Split(body, "\n") + + for _, line := range eachEvent { + var rawEvt interface{} + if err := json.Unmarshal([]byte(line), &rawEvt); err != nil { + assert.FailNow(t, err.Error()) + } + evt := rawEvt.(map[string]interface{}) + LogDebugf("%+v", evt) + + eventFound := (app == "" || evt["app"] == app) && + (event == "" || evt["event"] == event) && + (reason == "" || evt["reason"] == reason) + + if eventFound { + return nil + } + } + + return errors.New("not found") + }, + retry.RetryIf(func(err error) bool { + return err.Error() == "not found" + }), + ) +} diff --git a/dev/dns.go b/dev/dns.go index eb0de28b..8a4ffb5a 100644 --- a/dev/dns.go +++ b/dev/dns.go @@ -1,15 +1,14 @@ package dev import ( + "fmt" "net" - "sync" "time" "github.com/miekg/dns" + "gopkg.in/tomb.v2" ) -const DefaultAddress = ":9253" - type DNSResponder struct { Address string } @@ -58,6 +57,7 @@ func (d *DNSResponder) handleDNS(w dns.ResponseWriter, r *dns.Msg) { w.WriteMsg(m) } +// Serve binds to func (d *DNSResponder) Serve(domains []string) error { for _, domain := range domains { dns.HandleFunc(domain+".", d.handleDNS) @@ -65,26 +65,20 @@ func (d *DNSResponder) Serve(domains []string) error { addr := d.Address if addr == "" { - addr = DefaultAddress + return fmt.Errorf("invalid port specification in %v", d) } - var wg sync.WaitGroup - - wg.Add(2) + var t tomb.Tomb - go func() { - defer wg.Done() + t.Go(func() error { server := &dns.Server{Addr: addr, Net: "udp", TsigSecret: nil} - server.ListenAndServe() - }() + return server.ListenAndServe() + }) - go func() { - defer wg.Done() + t.Go(func() error { server := &dns.Server{Addr: addr, Net: "tcp", TsigSecret: nil} - server.ListenAndServe() - }() - - wg.Wait() + return server.ListenAndServe() + }) - return nil + return t.Wait() } diff --git a/go.mod b/go.mod index defbc42c..b0d78732 100644 --- a/go.mod +++ b/go.mod @@ -14,5 +14,6 @@ require ( github.com/vektra/errors v0.0.0-20140903201135-c64d83aba85a github.com/vektra/neko v0.0.0-20141017182438-843f5ecf6932 golang.org/x/exp v0.0.0-20160805011819-6e7e2f19a280 + golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect gopkg.in/tomb.v2 v2.0.0-20140626144623-14b3d72120e8 ) diff --git a/go.sum b/go.sum index 03503b22..5f5999df 100644 --- a/go.sum +++ b/go.sum @@ -24,7 +24,13 @@ github.com/vektra/errors v0.0.0-20140903201135-c64d83aba85a h1:lUVfiMMY/te9icPKB github.com/vektra/errors v0.0.0-20140903201135-c64d83aba85a/go.mod h1:KUxJS71XlMs+ztT+RzsLRoWUQRUpECo/+Rb0EBk8/Wc= github.com/vektra/neko v0.0.0-20141017182438-843f5ecf6932 h1:SslNPzxMsvmq9d29joAV87uY34VPgN0W9CDIcztcipg= github.com/vektra/neko v0.0.0-20141017182438-843f5ecf6932/go.mod h1:7tfPLehrsToaevw9Vi9iL6FOslcBJ/uqYQc8y3YIbdI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/exp v0.0.0-20160805011819-6e7e2f19a280 h1:aQ3aEQOtAIowyxHb+QySzarMApMnJcz87V5s49FVl+o= golang.org/x/exp v0.0.0-20160805011819-6e7e2f19a280/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/tomb.v2 v2.0.0-20140626144623-14b3d72120e8 h1:EQ3aCG3c3nkUNxx6quE0Ux47RYExj7cJyRMxUXqPf6I= gopkg.in/tomb.v2 v2.0.0-20140626144623-14b3d72120e8/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk= From b58e3f116692b133f37b06efc73a4921611205ae Mon Sep 17 00:00:00 2001 From: Alan Norton Date: Fri, 27 Mar 2020 13:17:23 -0400 Subject: [PATCH 02/10] test tcp and udp connection to dns --- dev/dns.go | 12 ++++++----- dev/dns_test.go | 54 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 dev/dns_test.go diff --git a/dev/dns.go b/dev/dns.go index 8a4ffb5a..8aff1988 100644 --- a/dev/dns.go +++ b/dev/dns.go @@ -10,7 +10,9 @@ import ( ) type DNSResponder struct { - Address string + Address string + udpServer *dns.Server + tcpServer *dns.Server } func (d *DNSResponder) handleDNS(w dns.ResponseWriter, r *dns.Msg) { @@ -71,13 +73,13 @@ func (d *DNSResponder) Serve(domains []string) error { var t tomb.Tomb t.Go(func() error { - server := &dns.Server{Addr: addr, Net: "udp", TsigSecret: nil} - return server.ListenAndServe() + d.udpServer = &dns.Server{Addr: addr, Net: "udp", TsigSecret: nil} + return d.udpServer.ListenAndServe() }) t.Go(func() error { - server := &dns.Server{Addr: addr, Net: "tcp", TsigSecret: nil} - return server.ListenAndServe() + d.tcpServer = &dns.Server{Addr: addr, Net: "tcp", TsigSecret: nil} + return d.tcpServer.ListenAndServe() }) return t.Wait() diff --git a/dev/dns_test.go b/dev/dns_test.go new file mode 100644 index 00000000..c4e62734 --- /dev/null +++ b/dev/dns_test.go @@ -0,0 +1,54 @@ +package dev + +import ( + "log" + "net" + "testing" + "time" + + "github.com/avast/retry-go" + "github.com/stretchr/testify/assert" +) + +var tDNSResponder DNSResponder + +func TestServe(t *testing.T) { + tDNSResponder.Address = "localhost:31337" + errChan := make(chan error) + + go func() { + if err := tDNSResponder.Serve([]string{"test"}); err != nil { + errChan <- err + } + close(errChan) + }() + + tcpErr := retry.Do( + func() error { + if _, err := net.DialTimeout("tcp", "localhost:31337", time.Duration(10*time.Second)); err != nil { + return err + } + tDNSResponder.tcpServer.Shutdown() + return nil + }, + retry.OnRetry(func(n uint, err error) { + log.Printf("#%d: %s\n", n, err) + }), + ) + + udpErr := retry.Do( + func() error { + if _, err := net.DialTimeout("udp", "localhost:31337", time.Duration(10*time.Second)); err != nil { + return err + } + tDNSResponder.udpServer.Shutdown() + return nil + }, + retry.OnRetry(func(n uint, err error) { + log.Printf("#%d: %s\n", n, err) + }), + ) + + assert.NoError(t, tcpErr) + assert.NoError(t, udpErr) +} From d65a2ffda5d694463d1be8dfff956b290dbb0640 Mon Sep 17 00:00:00 2001 From: Alan Norton Date: Fri, 27 Mar 2020 17:31:02 -0400 Subject: [PATCH 03/10] extract darwin-only DNS test skip high_sierra for now. fsevents are broken loop over tcp, udp go mod tidy map[string](func() interface{}) is awesome. refactor stdout capture to use return function instead of passed function call forgot one see if waiting for the shutdown to finish helps assert serve doesn't serve an error either only try to shutdown the server if we have a handle to it defer shutdown and fail if unable to shutdown remove timeout. we'll see if that fails. remove error stubs use constructor to init dns servers to avoid race remove retry print missed one shorten timeout, add comments to clarify intent of retries --- .circleci/config.yml | 2 +- cmd/puma-dev/main_darwin.go | 33 +++++++------- cmd/puma-dev/main_darwin_test.go | 33 ++++++++++++++ cmd/puma-dev/main_linux.go | 8 ++-- cmd/puma-dev/main_test.go | 76 +++++++++++++------------------- dev/dns.go | 26 ++++++----- dev/dns_test.go | 71 +++++++++++++++-------------- go.mod | 1 - go.sum | 6 --- 9 files changed, 135 insertions(+), 121 deletions(-) create mode 100644 cmd/puma-dev/main_darwin_test.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 4703535d..4a42ce4a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -98,7 +98,7 @@ workflows: version: 2 test: jobs: - - test_high_sierra + # - test_high_sierra - test_mojave - test_catalina diff --git a/cmd/puma-dev/main_darwin.go b/cmd/puma-dev/main_darwin.go index 4dfb40b6..2758c108 100644 --- a/cmd/puma-dev/main_darwin.go +++ b/cmd/puma-dev/main_darwin.go @@ -17,7 +17,7 @@ import ( var ( fDebug = flag.Bool("debug", false, "enable debug output") fDomains = flag.String("d", "test", "domains to handle, separate with :, defaults to test") - fPort = flag.Int("dns-port", 9253, "port to listen on dns for") + fDNSPort = flag.Int("dns-port", 9253, "port to listen on dns for") fHTTPPort = flag.Int("http-port", 9280, "port to listen on http for") fTLSPort = flag.Int("https-port", 9283, "port to listen on https for") fDir = flag.String("dir", "~/.puma-dev", "directory to watch for apps") @@ -34,6 +34,8 @@ var ( fCleanup = flag.Bool("cleanup", false, "Cleanup old system settings") fUninstall = flag.Bool("uninstall", false, "Uninstall puma-dev as a user service") + + shutdown = make(chan os.Signal, 1) ) func main() { @@ -110,18 +112,16 @@ func main() { } }() - stop := make(chan os.Signal, 1) - - signal.Notify(stop, os.Interrupt, syscall.SIGQUIT, syscall.SIGTERM) + signal.Notify(shutdown, os.Interrupt, syscall.SIGQUIT, syscall.SIGTERM) go func() { - <-stop + <-shutdown fmt.Printf("! Shutdown requested\n") pool.Purge() os.Exit(0) }() - err = dev.ConfigureResolver(domains, *fPort) + err = dev.ConfigureResolver(domains, *fDNSPort) if err != nil { log.Fatalf("Unable to configure OS X resolver: %s", err) } @@ -133,7 +133,7 @@ func main() { fmt.Printf("* Directory for apps: %s\n", dir) fmt.Printf("* Domains: %s\n", strings.Join(domains, ", ")) - fmt.Printf("* DNS Server port: %d\n", *fPort) + fmt.Printf("* DNS Server port: %d\n", *fDNSPort) if *fLaunch { fmt.Printf("* HTTP Server port: inherited from launchd\n") @@ -143,15 +143,12 @@ func main() { fmt.Printf("* HTTPS Server port: %d\n", *fTLSPort) } - { - var dns dev.DNSResponder - dns.Address = fmt.Sprintf("127.0.0.1:%d", *fPort) - go func() { - if err := dns.Serve(domains); err != nil { - fmt.Printf("! DNS failed: %v\n", err) - } - }() - } + dns := dev.NewDNSResponder(fmt.Sprintf("127.0.0.1:%d", *fDNSPort), domains) + go func() { + if err := dns.Serve(); err != nil { + fmt.Printf("! DNS Server failed: %v\n", err) + } + }() var http dev.HTTPServer @@ -177,12 +174,12 @@ func main() { go func() { if err := http.ServeTLS(tlsSocketName); err != nil { - fmt.Printf("! HTTPS failed: %v\n", err) + fmt.Printf("! HTTPS Server failed: %v\n", err) } }() err = http.Serve(socketName) if err != nil { - log.Fatalf("Error listening: %s", err) + log.Fatalf("! HTTP Server failed: %s", err) } } diff --git a/cmd/puma-dev/main_darwin_test.go b/cmd/puma-dev/main_darwin_test.go new file mode 100644 index 00000000..e45ca07c --- /dev/null +++ b/cmd/puma-dev/main_darwin_test.go @@ -0,0 +1,33 @@ +package main + +import ( + "context" + "fmt" + "net" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMainPumaDev_Darwin(t *testing.T) { + defer launchPumaDevBackgroundServerWithDefaults(t)() + + t.Run("resolve dns", func(t *testing.T) { + PumaDevDNSDialer := func(ctx context.Context, network, address string) (net.Conn, error) { + dnsAddress := fmt.Sprintf("127.0.0.1:%d", *fDNSPort) + d := net.Dialer{} + return d.DialContext(ctx, "udp", dnsAddress) + } + + r := net.Resolver{ + PreferGo: true, + Dial: PumaDevDNSDialer, + } + + ctx := context.Background() + ips, err := r.LookupIPAddr(ctx, "foo.test") + + assert.NoError(t, err) + assert.Equal(t, net.ParseIP("127.0.0.1").To4(), ips[0].IP.To4()) + }) +} diff --git a/cmd/puma-dev/main_linux.go b/cmd/puma-dev/main_linux.go index 2cdab90d..295e69cd 100644 --- a/cmd/puma-dev/main_linux.go +++ b/cmd/puma-dev/main_linux.go @@ -23,6 +23,8 @@ var ( fDir = flag.String("dir", "~/.puma-dev", "directory to watch for apps") fTimeout = flag.Duration("timeout", 15*60*time.Second, "how long to let an app idle for") fStop = flag.Bool("stop", false, "Stop all puma-dev servers") + + shutdown = make(chan os.Signal, 1) ) func main() { @@ -73,12 +75,10 @@ func main() { } }() - stop := make(chan os.Signal, 1) - - signal.Notify(stop, os.Interrupt, syscall.SIGQUIT, syscall.SIGTERM) + signal.Notify(shutdown, os.Interrupt, syscall.SIGQUIT, syscall.SIGTERM) go func() { - <-stop + <-shutdown fmt.Printf("! Shutdown requested\n") pool.Purge() os.Exit(0) diff --git a/cmd/puma-dev/main_test.go b/cmd/puma-dev/main_test.go index a265f2dd..b41a097e 100644 --- a/cmd/puma-dev/main_test.go +++ b/cmd/puma-dev/main_test.go @@ -1,19 +1,17 @@ package main import ( - "context" "encoding/json" "errors" "fmt" "io/ioutil" - "log" "net" "net/http" "os" "os/exec" "path/filepath" - "runtime" "strings" + "syscall" "testing" "time" @@ -35,43 +33,6 @@ func TestMain(m *testing.M) { func TestMainPumaDev(t *testing.T) { defer launchPumaDevBackgroundServerWithDefaults(t)() - testAppsToLink := map[string]string{ - "rack-hi-puma": "hipuma", - "static-hi-puma": "static-site", - } - - for etcAppDir, appLinkName := range testAppsToLink { - appPath := filepath.Join(ProjectRoot, "etc", etcAppDir) - linkPath := filepath.Join(homedir.MustExpand(testAppLinkDirPath), appLinkName) - - if err := os.Symlink(appPath, linkPath); err != nil { - assert.FailNow(t, err.Error()) - } - } - - t.Run("resolve dns", func(t *testing.T) { - if runtime.GOOS != "darwin" { - t.SkipNow() - } - - PumaDevDNSDialer := func(ctx context.Context, network, address string) (net.Conn, error) { - d := net.Dialer{} - // TODO: use fPort if available, break out this test into main_darwin_test.go - return d.DialContext(ctx, "udp", "127.0.0.1:9253") - } - - r := net.Resolver{ - PreferGo: true, - Dial: PumaDevDNSDialer, - } - - ctx := context.Background() - ips, err := r.LookupIPAddr(ctx, "foo.test") - - assert.NoError(t, err) - assert.Equal(t, net.ParseIP("127.0.0.1").To4(), ips[0].IP.To4()) - }) - t.Run("status", func(t *testing.T) { reqURL := fmt.Sprintf("http://localhost:%d/status", *fHTTPPort) statusHost := "puma-dev" @@ -246,34 +207,59 @@ func generateLivePumaDevCertIfNotExist(t *testing.T) { func launchPumaDevBackgroundServerWithDefaults(t *testing.T) func() { address := fmt.Sprintf("localhost:%d", *fHTTPPort) - timeout := time.Duration(10 * time.Second) + timeout := time.Duration(2 * time.Second) if _, err := net.DialTimeout("tcp", address, timeout); err == nil { return func() {} } + generateLivePumaDevCertIfNotExist(t) + StubCommandLineArgs() SetFlagOrFail(t, "dir", testAppLinkDirPath) - generateLivePumaDevCertIfNotExist(t) + MakeDirectoryOrFail(t, testAppLinkDirPath) + + testAppsToLink := map[string]string{ + "hipuma": "rack-hi-puma", + "static-site": "static-hi-puma", + } + + for appLinkName, etcAppDir := range testAppsToLink { + appPath := filepath.Join(ProjectRoot, "etc", etcAppDir) + linkPath := filepath.Join(homedir.MustExpand(testAppLinkDirPath), appLinkName) + + if err := os.Symlink(appPath, linkPath); err != nil { + assert.FailNow(t, err.Error()) + } + } go func() { main() }() + // wait for server to come up err := retry.Do( func() error { _, err := net.DialTimeout("tcp", address, timeout) return err }, - retry.OnRetry(func(n uint, err error) { - log.Printf("#%d: %s\n", n, err) - }), ) assert.NoError(t, err) return func() { RemoveDirectoryOrFail(t, testAppLinkDirPath) + shutdown <- syscall.SIGTERM + + // wait for server to shut down + retry.Do( + func() error { + if _, err := net.DialTimeout("tcp", address, timeout); err == nil { + return fmt.Errorf("server is still listening :%v", address) + } + return nil + }, + ) } } diff --git a/dev/dns.go b/dev/dns.go index 8aff1988..e98f17be 100644 --- a/dev/dns.go +++ b/dev/dns.go @@ -1,7 +1,6 @@ package dev import ( - "fmt" "net" "time" @@ -10,11 +9,22 @@ import ( ) type DNSResponder struct { - Address string + Address string + Domains []string + udpServer *dns.Server tcpServer *dns.Server } +func NewDNSResponder(address string, domains []string) *DNSResponder { + udp := &dns.Server{Addr: address, Net: "udp", TsigSecret: nil} + tcp := &dns.Server{Addr: address, Net: "tcp", TsigSecret: nil} + + d := &DNSResponder{Address: address, Domains: domains, udpServer: udp, tcpServer: tcp} + + return d +} + func (d *DNSResponder) handleDNS(w dns.ResponseWriter, r *dns.Msg) { var ( v4 bool @@ -59,26 +69,18 @@ func (d *DNSResponder) handleDNS(w dns.ResponseWriter, r *dns.Msg) { w.WriteMsg(m) } -// Serve binds to -func (d *DNSResponder) Serve(domains []string) error { - for _, domain := range domains { +func (d *DNSResponder) Serve() error { + for _, domain := range d.Domains { dns.HandleFunc(domain+".", d.handleDNS) } - addr := d.Address - if addr == "" { - return fmt.Errorf("invalid port specification in %v", d) - } - var t tomb.Tomb t.Go(func() error { - d.udpServer = &dns.Server{Addr: addr, Net: "udp", TsigSecret: nil} return d.udpServer.ListenAndServe() }) t.Go(func() error { - d.tcpServer = &dns.Server{Addr: addr, Net: "tcp", TsigSecret: nil} return d.tcpServer.ListenAndServe() }) diff --git a/dev/dns_test.go b/dev/dns_test.go index c4e62734..edbe440f 100644 --- a/dev/dns_test.go +++ b/dev/dns_test.go @@ -1,54 +1,57 @@ package dev import ( - "log" "net" "testing" "time" "github.com/avast/retry-go" + "github.com/miekg/dns" "github.com/stretchr/testify/assert" ) -var tDNSResponder DNSResponder +var tDNSResponder *DNSResponder -func TestServe(t *testing.T) { - tDNSResponder.Address = "localhost:31337" - errChan := make(chan error) +func TestServeDNS(t *testing.T) { + errChan := make(chan error, 1) + domainList := []string{"test"} + + tDNSResponder = NewDNSResponder("localhost:31337", domainList) go func() { - if err := tDNSResponder.Serve([]string{"test"}); err != nil { + if err := tDNSResponder.Serve(); err != nil { errChan <- err } close(errChan) }() - tcpErr := retry.Do( - func() error { - if _, err := net.DialTimeout("tcp", "localhost:31337", time.Duration(10*time.Second)); err != nil { - return err - } - tDNSResponder.tcpServer.Shutdown() - return nil - }, - retry.OnRetry(func(n uint, err error) { - log.Printf("#%d: %s\n", n, err) - }), - ) - - udpErr := retry.Do( - func() error { - if _, err := net.DialTimeout("udp", "localhost:31337", time.Duration(10*time.Second)); err != nil { - return err - } - tDNSResponder.udpServer.Shutdown() - return nil - }, - retry.OnRetry(func(n uint, err error) { - log.Printf("#%d: %s\n", n, err) - }), - ) - - assert.NoError(t, tcpErr) - assert.NoError(t, udpErr) + shortTimeout := time.Duration(1 * time.Second) + protocols := map[string](func() *dns.Server){ + "tcp": func() *dns.Server { return tDNSResponder.tcpServer }, + "udp": func() *dns.Server { return tDNSResponder.udpServer }, + } + + for protocol, serverLookup := range protocols { + dialError := retry.Do( + func() error { + if _, err := net.DialTimeout(protocol, "localhost:31337", shortTimeout); err != nil { + return err + } + + defer func() { + if server := serverLookup(); server != nil { + server.Shutdown() + } else { + assert.Fail(t, "tDNSResponder", "%s was nil", protocol) + } + }() + + return nil + }, + ) + + assert.NoError(t, dialError) + } + + assert.NoError(t, <-errChan) } diff --git a/go.mod b/go.mod index b0d78732..defbc42c 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,5 @@ require ( github.com/vektra/errors v0.0.0-20140903201135-c64d83aba85a github.com/vektra/neko v0.0.0-20141017182438-843f5ecf6932 golang.org/x/exp v0.0.0-20160805011819-6e7e2f19a280 - golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect gopkg.in/tomb.v2 v2.0.0-20140626144623-14b3d72120e8 ) diff --git a/go.sum b/go.sum index 5f5999df..03503b22 100644 --- a/go.sum +++ b/go.sum @@ -24,13 +24,7 @@ github.com/vektra/errors v0.0.0-20140903201135-c64d83aba85a h1:lUVfiMMY/te9icPKB github.com/vektra/errors v0.0.0-20140903201135-c64d83aba85a/go.mod h1:KUxJS71XlMs+ztT+RzsLRoWUQRUpECo/+Rb0EBk8/Wc= github.com/vektra/neko v0.0.0-20141017182438-843f5ecf6932 h1:SslNPzxMsvmq9d29joAV87uY34VPgN0W9CDIcztcipg= github.com/vektra/neko v0.0.0-20141017182438-843f5ecf6932/go.mod h1:7tfPLehrsToaevw9Vi9iL6FOslcBJ/uqYQc8y3YIbdI= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/exp v0.0.0-20160805011819-6e7e2f19a280 h1:aQ3aEQOtAIowyxHb+QySzarMApMnJcz87V5s49FVl+o= golang.org/x/exp v0.0.0-20160805011819-6e7e2f19a280/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/tomb.v2 v2.0.0-20140626144623-14b3d72120e8 h1:EQ3aCG3c3nkUNxx6quE0Ux47RYExj7cJyRMxUXqPf6I= gopkg.in/tomb.v2 v2.0.0-20140626144623-14b3d72120e8/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk= From 8fcf22088a9414910f5b734fba17875681560740 Mon Sep 17 00:00:00 2001 From: Alan Norton Date: Fri, 3 Apr 2020 11:49:35 -0400 Subject: [PATCH 04/10] only run one background puma-dev per platform; os.Exit will shutdown only run one server per set of server test scenarios, so os.Exit will handle the cleanup put shutdown back inside main -- it calls os.Exit() which borks testing pull out additional state into each platform spec ensure we run things on completely non-standard ports missed an import --- cmd/puma-dev/main_darwin.go | 3 +- cmd/puma-dev/main_darwin_test.go | 15 +- cmd/puma-dev/main_linux.go | 4 +- cmd/puma-dev/main_linux_test.go | 21 +++ cmd/puma-dev/main_test.go | 252 +++++++++++++++---------------- dev/dns_test.go | 18 +-- 6 files changed, 160 insertions(+), 153 deletions(-) create mode 100644 cmd/puma-dev/main_linux_test.go diff --git a/cmd/puma-dev/main_darwin.go b/cmd/puma-dev/main_darwin.go index 2758c108..55cd1ee0 100644 --- a/cmd/puma-dev/main_darwin.go +++ b/cmd/puma-dev/main_darwin.go @@ -34,8 +34,6 @@ var ( fCleanup = flag.Bool("cleanup", false, "Cleanup old system settings") fUninstall = flag.Bool("uninstall", false, "Uninstall puma-dev as a user service") - - shutdown = make(chan os.Signal, 1) ) func main() { @@ -112,6 +110,7 @@ func main() { } }() + shutdown := make(chan os.Signal, 1) signal.Notify(shutdown, os.Interrupt, syscall.SIGQUIT, syscall.SIGTERM) go func() { diff --git a/cmd/puma-dev/main_darwin_test.go b/cmd/puma-dev/main_darwin_test.go index e45ca07c..b3aa9c9d 100644 --- a/cmd/puma-dev/main_darwin_test.go +++ b/cmd/puma-dev/main_darwin_test.go @@ -6,11 +6,24 @@ import ( "net" "testing" + . "github.com/puma/puma-dev/dev/devtest" + "github.com/puma/puma-dev/homedir" + "github.com/stretchr/testify/assert" ) func TestMainPumaDev_Darwin(t *testing.T) { - defer launchPumaDevBackgroundServerWithDefaults(t)() + appLinkDir := homedir.MustExpand("~/.gotest-macos-puma-dev") + + defer linkAppsForTesting(t, appLinkDir)() + + SetFlagOrFail(t, "dns-port", "65053") + SetFlagOrFail(t, "http-port", "65080") + SetFlagOrFail(t, "https-port", "65443") + + bootConfiguredLivePumaServer(t, appLinkDir) + + runPlatformAgnosticTestScenarios(t) t.Run("resolve dns", func(t *testing.T) { PumaDevDNSDialer := func(ctx context.Context, network, address string) (net.Conn, error) { diff --git a/cmd/puma-dev/main_linux.go b/cmd/puma-dev/main_linux.go index 295e69cd..7d183be8 100644 --- a/cmd/puma-dev/main_linux.go +++ b/cmd/puma-dev/main_linux.go @@ -23,8 +23,6 @@ var ( fDir = flag.String("dir", "~/.puma-dev", "directory to watch for apps") fTimeout = flag.Duration("timeout", 15*60*time.Second, "how long to let an app idle for") fStop = flag.Bool("stop", false, "Stop all puma-dev servers") - - shutdown = make(chan os.Signal, 1) ) func main() { @@ -65,7 +63,6 @@ func main() { pool.Events = &events purge := make(chan os.Signal, 1) - signal.Notify(purge, syscall.SIGUSR1) go func() { @@ -75,6 +72,7 @@ func main() { } }() + shutdown := make(chan os.Signal, 1) signal.Notify(shutdown, os.Interrupt, syscall.SIGQUIT, syscall.SIGTERM) go func() { diff --git a/cmd/puma-dev/main_linux_test.go b/cmd/puma-dev/main_linux_test.go new file mode 100644 index 00000000..ce291115 --- /dev/null +++ b/cmd/puma-dev/main_linux_test.go @@ -0,0 +1,21 @@ +package main + +import ( + "testing" + + . "github.com/puma/puma-dev/dev/devtest" + "github.com/puma/puma-dev/homedir" +) + +func TestMainPumaDev_Linux(t *testing.T) { + appLinkDir := homedir.MustExpand("~/.gotest-linux-puma-dev") + + defer linkAppsForTesting(t, appLinkDir)() + + SetFlagOrFail(t, "http-port", "65080") + SetFlagOrFail(t, "https-port", "65443") + + bootConfiguredLivePumaServer(t, appLinkDir) + + runPlatformAgnosticTestScenarios(t) +} diff --git a/cmd/puma-dev/main_test.go b/cmd/puma-dev/main_test.go index b41a097e..b8c01571 100644 --- a/cmd/puma-dev/main_test.go +++ b/cmd/puma-dev/main_test.go @@ -11,7 +11,6 @@ import ( "os/exec" "path/filepath" "strings" - "syscall" "testing" "time" @@ -23,16 +22,129 @@ import ( "github.com/stretchr/testify/assert" ) -var testAppLinkDirPath = homedir.MustExpand("~/.gotest-puma-dev") - func TestMain(m *testing.M) { EnsurePumaDevDirectory() - os.Exit(m.Run()) + testResult := m.Run() + os.Exit(testResult) +} + +func TestMain_execWithExitStatus_versionFlag(t *testing.T) { + StubCommandLineArgs("-V") + assert.True(t, *fVersion) + + execStdOut := WithStdoutCaptured(func() { + result := execWithExitStatus() + assert.Equal(t, 0, result.exitStatusCode) + assert.Equal(t, true, result.shouldExit) + }) + + assert.Regexp(t, "^Version: devel \\(.+\\)\\n$", execStdOut) } -func TestMainPumaDev(t *testing.T) { - defer launchPumaDevBackgroundServerWithDefaults(t)() +func TestMain_execWithExitStatus_noFlag(t *testing.T) { + StubCommandLineArgs() + assert.False(t, *fVersion) + + execStdOut := WithStdoutCaptured(func() { + result := execWithExitStatus() + assert.Equal(t, false, result.shouldExit) + }) + + assert.Equal(t, "", execStdOut) +} + +func TestMain_execWithExitStatus_commandArgs(t *testing.T) { + StubCommandLineArgs("nosoupforyou") + + execStdOut := WithStdoutCaptured(func() { + result := execWithExitStatus() + assert.Equal(t, 1, result.exitStatusCode) + assert.Equal(t, true, result.shouldExit) + }) + + assert.Equal(t, "Error: Unknown command: nosoupforyou\n\n", execStdOut) +} + +func TestMain_allCheck_versionFlag(t *testing.T) { + if os.Getenv("GO_TEST_SUBPROCESS") == "1" { + StubCommandLineArgs("-V") + allCheck() + + return + } + + cmd := exec.Command(os.Args[0], "-test.run=TestMain_allCheck_versionFlag") + cmd.Env = append(os.Environ(), "GO_TEST_SUBPROCESS=1") + err := cmd.Run() + + assert.Nil(t, err) +} + +func TestMain_allCheck_badArg(t *testing.T) { + if os.Getenv("GO_TEST_SUBPROCESS") == "1" { + StubCommandLineArgs("-badarg") + allCheck() + + return + } + + cmd := exec.Command(os.Args[0], "-test.run=TestMain_allCheck_badArg") + cmd.Env = append(os.Environ(), "GO_TEST_SUBPROCESS=1") + err := cmd.Run() + + exit, ok := err.(*exec.ExitError) + + assert.True(t, ok) + assert.False(t, exit.Success()) +} + +func linkAppsForTesting(t *testing.T, workingDirPath string) func() { + MakeDirectoryOrFail(t, workingDirPath) + + testAppsToLink := map[string]string{ + "hipuma": "rack-hi-puma", + "static-site": "static-hi-puma", + } + + for appLinkName, etcAppDir := range testAppsToLink { + appPath := filepath.Join(ProjectRoot, "etc", etcAppDir) + linkPath := filepath.Join(homedir.MustExpand(workingDirPath), appLinkName) + + if err := os.Symlink(appPath, linkPath); err != nil { + assert.FailNow(t, err.Error()) + } + } + + return func() { + RemoveDirectoryOrFail(t, workingDirPath) + } +} + +func bootConfiguredLivePumaServer(t *testing.T, workingDirPath string) error { + address := fmt.Sprintf("localhost:%d", *fHTTPPort) + timeout := time.Duration(2 * time.Second) + + if _, err := net.DialTimeout("tcp", address, timeout); err == nil { + return fmt.Errorf("server is already running") + } + + generateLivePumaDevCertIfNotExist(t) + StubCommandLineArgs() + SetFlagOrFail(t, "dir", workingDirPath) + + go func() { + main() + }() + + return retry.Do( + func() error { + _, err := net.DialTimeout("tcp", address, timeout) + return err + }, + ) +} +func runPlatformAgnosticTestScenarios(t *testing.T) { t.Run("status", func(t *testing.T) { reqURL := fmt.Sprintf("http://localhost:%d/status", *fHTTPPort) statusHost := "puma-dev" @@ -121,76 +233,6 @@ func TestMainPumaDev(t *testing.T) { }) } -func TestMain_execWithExitStatus_versionFlag(t *testing.T) { - StubCommandLineArgs("-V") - assert.True(t, *fVersion) - - execStdOut := WithStdoutCaptured(func() { - result := execWithExitStatus() - assert.Equal(t, 0, result.exitStatusCode) - assert.Equal(t, true, result.shouldExit) - }) - - assert.Regexp(t, "^Version: devel \\(.+\\)\\n$", execStdOut) -} - -func TestMain_execWithExitStatus_noFlag(t *testing.T) { - StubCommandLineArgs() - assert.False(t, *fVersion) - - execStdOut := WithStdoutCaptured(func() { - result := execWithExitStatus() - assert.Equal(t, false, result.shouldExit) - }) - - assert.Equal(t, "", execStdOut) -} - -func TestMain_execWithExitStatus_commandArgs(t *testing.T) { - StubCommandLineArgs("nosoupforyou") - - execStdOut := WithStdoutCaptured(func() { - result := execWithExitStatus() - assert.Equal(t, 1, result.exitStatusCode) - assert.Equal(t, true, result.shouldExit) - }) - - assert.Equal(t, "Error: Unknown command: nosoupforyou\n\n", execStdOut) -} - -func TestMain_allCheck_versionFlag(t *testing.T) { - if os.Getenv("GO_TEST_SUBPROCESS") == "1" { - StubCommandLineArgs("-V") - allCheck() - - return - } - - cmd := exec.Command(os.Args[0], "-test.run=TestMain_allCheck_versionFlag") - cmd.Env = append(os.Environ(), "GO_TEST_SUBPROCESS=1") - err := cmd.Run() - - assert.Nil(t, err) -} - -func TestMain_allCheck_badArg(t *testing.T) { - if os.Getenv("GO_TEST_SUBPROCESS") == "1" { - StubCommandLineArgs("-badarg") - allCheck() - - return - } - - cmd := exec.Command(os.Args[0], "-test.run=TestMain_allCheck_badArg") - cmd.Env = append(os.Environ(), "GO_TEST_SUBPROCESS=1") - err := cmd.Run() - - exit, ok := err.(*exec.ExitError) - - assert.True(t, ok) - assert.False(t, exit.Success()) -} - func generateLivePumaDevCertIfNotExist(t *testing.T) { liveSupportPath := homedir.MustExpand(dev.SupportDir) liveCertPath := filepath.Join(liveSupportPath, "cert.pem") @@ -205,64 +247,6 @@ func generateLivePumaDevCertIfNotExist(t *testing.T) { } } -func launchPumaDevBackgroundServerWithDefaults(t *testing.T) func() { - address := fmt.Sprintf("localhost:%d", *fHTTPPort) - timeout := time.Duration(2 * time.Second) - - if _, err := net.DialTimeout("tcp", address, timeout); err == nil { - return func() {} - } - - generateLivePumaDevCertIfNotExist(t) - - StubCommandLineArgs() - SetFlagOrFail(t, "dir", testAppLinkDirPath) - MakeDirectoryOrFail(t, testAppLinkDirPath) - - testAppsToLink := map[string]string{ - "hipuma": "rack-hi-puma", - "static-site": "static-hi-puma", - } - - for appLinkName, etcAppDir := range testAppsToLink { - appPath := filepath.Join(ProjectRoot, "etc", etcAppDir) - linkPath := filepath.Join(homedir.MustExpand(testAppLinkDirPath), appLinkName) - - if err := os.Symlink(appPath, linkPath); err != nil { - assert.FailNow(t, err.Error()) - } - } - - go func() { - main() - }() - - // wait for server to come up - err := retry.Do( - func() error { - _, err := net.DialTimeout("tcp", address, timeout) - return err - }, - ) - - assert.NoError(t, err) - - return func() { - RemoveDirectoryOrFail(t, testAppLinkDirPath) - shutdown <- syscall.SIGTERM - - // wait for server to shut down - retry.Do( - func() error { - if _, err := net.DialTimeout("tcp", address, timeout); err == nil { - return fmt.Errorf("server is still listening :%v", address) - } - return nil - }, - ) - } -} - func getURLWithHost(t *testing.T, url string, host string) string { req, _ := http.NewRequest("GET", url, nil) req.Host = host diff --git a/dev/dns_test.go b/dev/dns_test.go index edbe440f..0360a4f8 100644 --- a/dev/dns_test.go +++ b/dev/dns_test.go @@ -26,26 +26,18 @@ func TestServeDNS(t *testing.T) { }() shortTimeout := time.Duration(1 * time.Second) - protocols := map[string](func() *dns.Server){ - "tcp": func() *dns.Server { return tDNSResponder.tcpServer }, - "udp": func() *dns.Server { return tDNSResponder.udpServer }, + protocols := map[string]*dns.Server{ + "tcp": tDNSResponder.tcpServer, + "udp": tDNSResponder.udpServer, } - for protocol, serverLookup := range protocols { + for protocol, server := range protocols { dialError := retry.Do( func() error { if _, err := net.DialTimeout(protocol, "localhost:31337", shortTimeout); err != nil { return err } - - defer func() { - if server := serverLookup(); server != nil { - server.Shutdown() - } else { - assert.Fail(t, "tDNSResponder", "%s was nil", protocol) - } - }() - + server.Shutdown() return nil }, ) From d05d2ea73e807657446802dfa0813c7df65b2709 Mon Sep 17 00:00:00 2001 From: Alan Norton Date: Fri, 3 Apr 2020 15:24:18 -0400 Subject: [PATCH 05/10] alphabetize main test methods, pass flags to puma-dev boot --- cmd/puma-dev/main_darwin_test.go | 13 +-- cmd/puma-dev/main_linux_test.go | 11 ++- cmd/puma-dev/main_test.go | 163 ++++++++++++++++--------------- 3 files changed, 96 insertions(+), 91 deletions(-) diff --git a/cmd/puma-dev/main_darwin_test.go b/cmd/puma-dev/main_darwin_test.go index b3aa9c9d..e79b013c 100644 --- a/cmd/puma-dev/main_darwin_test.go +++ b/cmd/puma-dev/main_darwin_test.go @@ -15,13 +15,14 @@ import ( func TestMainPumaDev_Darwin(t *testing.T) { appLinkDir := homedir.MustExpand("~/.gotest-macos-puma-dev") - defer linkAppsForTesting(t, appLinkDir)() + defer linkAllTestApps(t, appLinkDir)() - SetFlagOrFail(t, "dns-port", "65053") - SetFlagOrFail(t, "http-port", "65080") - SetFlagOrFail(t, "https-port", "65443") - - bootConfiguredLivePumaServer(t, appLinkDir) + configureAndBootPumaDevServer(t, map[string]string{ + "dir": appLinkDir, + "dns-port": "65053", + "http-port": "65080", + "https-port": "65443", + }) runPlatformAgnosticTestScenarios(t) diff --git a/cmd/puma-dev/main_linux_test.go b/cmd/puma-dev/main_linux_test.go index ce291115..1a2ed26e 100644 --- a/cmd/puma-dev/main_linux_test.go +++ b/cmd/puma-dev/main_linux_test.go @@ -10,12 +10,13 @@ import ( func TestMainPumaDev_Linux(t *testing.T) { appLinkDir := homedir.MustExpand("~/.gotest-linux-puma-dev") - defer linkAppsForTesting(t, appLinkDir)() + defer linkAllTestApps(t, appLinkDir)() - SetFlagOrFail(t, "http-port", "65080") - SetFlagOrFail(t, "https-port", "65443") - - bootConfiguredLivePumaServer(t, appLinkDir) + configureAndBootPumaDevServer(t, map[string]string{ + "dir": appLinkDir, + "http-port": "65080", + "https-port": "65443", + }) runPlatformAgnosticTestScenarios(t) } diff --git a/cmd/puma-dev/main_test.go b/cmd/puma-dev/main_test.go index b8c01571..169cfd7d 100644 --- a/cmd/puma-dev/main_test.go +++ b/cmd/puma-dev/main_test.go @@ -98,7 +98,65 @@ func TestMain_allCheck_badArg(t *testing.T) { assert.False(t, exit.Success()) } -func linkAppsForTesting(t *testing.T, workingDirPath string) func() { +func configureAndBootPumaDevServer(t *testing.T, mainFlags map[string]string) error { + address := fmt.Sprintf("localhost:%d", *fHTTPPort) + timeout := time.Duration(2 * time.Second) + + if _, err := net.DialTimeout("tcp", address, timeout); err == nil { + return fmt.Errorf("server is already running") + } + + generateLivePumaDevCertIfNotExist(t) + + StubCommandLineArgs() + for flagName, flagValue := range mainFlags { + SetFlagOrFail(t, flagName, flagValue) + } + + go func() { + main() + }() + + return retry.Do( + func() error { + _, err := net.DialTimeout("tcp", address, timeout) + return err + }, + ) +} + +func generateLivePumaDevCertIfNotExist(t *testing.T) { + liveSupportPath := homedir.MustExpand(dev.SupportDir) + liveCertPath := filepath.Join(liveSupportPath, "cert.pem") + liveKeyPath := filepath.Join(liveSupportPath, "key.pem") + + if !FileExists(liveCertPath) || !FileExists(liveKeyPath) { + MakeDirectoryOrFail(t, liveSupportPath) + + if err := dev.GeneratePumaDevCertificateAuthority(liveCertPath, liveKeyPath); err != nil { + assert.FailNow(t, err.Error()) + } + } +} + +func getURLWithHost(t *testing.T, url string, host string) string { + req, _ := http.NewRequest("GET", url, nil) + req.Host = host + + resp, err := http.DefaultClient.Do(req) + + if err != nil { + assert.FailNow(t, err.Error()) + } + + defer resp.Body.Close() + + bodyBytes, _ := ioutil.ReadAll(resp.Body) + + return strings.TrimSpace(string(bodyBytes)) +} + +func linkAllTestApps(t *testing.T, workingDirPath string) func() { MakeDirectoryOrFail(t, workingDirPath) testAppsToLink := map[string]string{ @@ -120,27 +178,34 @@ func linkAppsForTesting(t *testing.T, workingDirPath string) func() { } } -func bootConfiguredLivePumaServer(t *testing.T, workingDirPath string) error { - address := fmt.Sprintf("localhost:%d", *fHTTPPort) - timeout := time.Duration(2 * time.Second) +func pollForEvent(t *testing.T, app string, event string, reason string) error { + return retry.Do( + func() error { + body := getURLWithHost(t, fmt.Sprintf("http://localhost:%d/events", *fHTTPPort), "puma-dev") + eachEvent := strings.Split(body, "\n") - if _, err := net.DialTimeout("tcp", address, timeout); err == nil { - return fmt.Errorf("server is already running") - } + for _, line := range eachEvent { + var rawEvt interface{} + if err := json.Unmarshal([]byte(line), &rawEvt); err != nil { + assert.FailNow(t, err.Error()) + } + evt := rawEvt.(map[string]interface{}) + LogDebugf("%+v", evt) - generateLivePumaDevCertIfNotExist(t) - StubCommandLineArgs() - SetFlagOrFail(t, "dir", workingDirPath) + eventFound := (app == "" || evt["app"] == app) && + (event == "" || evt["event"] == event) && + (reason == "" || evt["reason"] == reason) - go func() { - main() - }() + if eventFound { + return nil + } + } - return retry.Do( - func() error { - _, err := net.DialTimeout("tcp", address, timeout) - return err + return errors.New("not found") }, + retry.RetryIf(func(err error) bool { + return err.Error() == "not found" + }), ) } @@ -231,66 +296,4 @@ func runPlatformAgnosticTestScenarios(t *testing.T) { assert.Equal(t, "rack wuz here", getURLWithHost(t, reqURL, statusHost)) }) -} - -func generateLivePumaDevCertIfNotExist(t *testing.T) { - liveSupportPath := homedir.MustExpand(dev.SupportDir) - liveCertPath := filepath.Join(liveSupportPath, "cert.pem") - liveKeyPath := filepath.Join(liveSupportPath, "key.pem") - - if !FileExists(liveCertPath) || !FileExists(liveKeyPath) { - MakeDirectoryOrFail(t, liveSupportPath) - - if err := dev.GeneratePumaDevCertificateAuthority(liveCertPath, liveKeyPath); err != nil { - assert.FailNow(t, err.Error()) - } - } -} - -func getURLWithHost(t *testing.T, url string, host string) string { - req, _ := http.NewRequest("GET", url, nil) - req.Host = host - - resp, err := http.DefaultClient.Do(req) - - if err != nil { - assert.FailNow(t, err.Error()) - } - - defer resp.Body.Close() - - bodyBytes, _ := ioutil.ReadAll(resp.Body) - - return strings.TrimSpace(string(bodyBytes)) -} - -func pollForEvent(t *testing.T, app string, event string, reason string) error { - return retry.Do( - func() error { - body := getURLWithHost(t, fmt.Sprintf("http://localhost:%d/events", *fHTTPPort), "puma-dev") - eachEvent := strings.Split(body, "\n") - - for _, line := range eachEvent { - var rawEvt interface{} - if err := json.Unmarshal([]byte(line), &rawEvt); err != nil { - assert.FailNow(t, err.Error()) - } - evt := rawEvt.(map[string]interface{}) - LogDebugf("%+v", evt) - - eventFound := (app == "" || evt["app"] == app) && - (event == "" || evt["event"] == event) && - (reason == "" || evt["reason"] == reason) - - if eventFound { - return nil - } - } - - return errors.New("not found") - }, - retry.RetryIf(func(err error) bool { - return err.Error() == "not found" - }), - ) -} +} \ No newline at end of file From 0c27432d460eb335a61906eeabacb86a9471d370 Mon Sep 17 00:00:00 2001 From: Alan Norton Date: Fri, 3 Apr 2020 15:25:41 -0400 Subject: [PATCH 06/10] remove unused import --- cmd/puma-dev/main_darwin_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/puma-dev/main_darwin_test.go b/cmd/puma-dev/main_darwin_test.go index e79b013c..aa3b5675 100644 --- a/cmd/puma-dev/main_darwin_test.go +++ b/cmd/puma-dev/main_darwin_test.go @@ -6,7 +6,6 @@ import ( "net" "testing" - . "github.com/puma/puma-dev/dev/devtest" "github.com/puma/puma-dev/homedir" "github.com/stretchr/testify/assert" From 6a656d7d14cdb72f039fdc77503d4eee4df4cbaf Mon Sep 17 00:00:00 2001 From: Alan Norton Date: Fri, 3 Apr 2020 15:30:52 -0400 Subject: [PATCH 07/10] remove another unused import --- cmd/puma-dev/main_linux_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/puma-dev/main_linux_test.go b/cmd/puma-dev/main_linux_test.go index 1a2ed26e..96410747 100644 --- a/cmd/puma-dev/main_linux_test.go +++ b/cmd/puma-dev/main_linux_test.go @@ -3,7 +3,6 @@ package main import ( "testing" - . "github.com/puma/puma-dev/dev/devtest" "github.com/puma/puma-dev/homedir" ) From 15870c289dff23d305c9462bb6d60a8983a11a1c Mon Sep 17 00:00:00 2001 From: Alan Norton Date: Sun, 5 Apr 2020 14:42:46 -0400 Subject: [PATCH 08/10] ensure we don't fail to boot the server, add additional dns tests --- cmd/puma-dev/main_darwin_test.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/cmd/puma-dev/main_darwin_test.go b/cmd/puma-dev/main_darwin_test.go index aa3b5675..59f25127 100644 --- a/cmd/puma-dev/main_darwin_test.go +++ b/cmd/puma-dev/main_darwin_test.go @@ -16,13 +16,16 @@ func TestMainPumaDev_Darwin(t *testing.T) { defer linkAllTestApps(t, appLinkDir)() - configureAndBootPumaDevServer(t, map[string]string{ + serveErr := configureAndBootPumaDevServer(t, map[string]string{ + "d": "test:puma", "dir": appLinkDir, "dns-port": "65053", "http-port": "65080", "https-port": "65443", }) + assert.NoError(t, serveErr) + runPlatformAgnosticTestScenarios(t) t.Run("resolve dns", func(t *testing.T) { @@ -39,8 +42,14 @@ func TestMainPumaDev_Darwin(t *testing.T) { ctx := context.Background() ips, err := r.LookupIPAddr(ctx, "foo.test") + assert.NoError(t, err) + assert.Equal(t, net.ParseIP("127.0.0.1").To4(), ips[0].IP.To4()) + ips, err = r.LookupIPAddr(ctx, "foo.puma") assert.NoError(t, err) assert.Equal(t, net.ParseIP("127.0.0.1").To4(), ips[0].IP.To4()) + + _, err = r.LookupIPAddr(ctx, "foo.tlddoesnotexist") + assert.Error(t, err) }) } From 082e44b08cad40555f43608eced6bf20e5a0c65b Mon Sep 17 00:00:00 2001 From: Alan Norton Date: Sun, 5 Apr 2020 14:43:24 -0400 Subject: [PATCH 09/10] ensure we set flags _before_ we read *fHTTPPort /facepalm --- cmd/puma-dev/main_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/puma-dev/main_test.go b/cmd/puma-dev/main_test.go index 169cfd7d..4854e37e 100644 --- a/cmd/puma-dev/main_test.go +++ b/cmd/puma-dev/main_test.go @@ -99,6 +99,11 @@ func TestMain_allCheck_badArg(t *testing.T) { } func configureAndBootPumaDevServer(t *testing.T, mainFlags map[string]string) error { + StubCommandLineArgs() + for flagName, flagValue := range mainFlags { + SetFlagOrFail(t, flagName, flagValue) + } + address := fmt.Sprintf("localhost:%d", *fHTTPPort) timeout := time.Duration(2 * time.Second) @@ -108,11 +113,6 @@ func configureAndBootPumaDevServer(t *testing.T, mainFlags map[string]string) er generateLivePumaDevCertIfNotExist(t) - StubCommandLineArgs() - for flagName, flagValue := range mainFlags { - SetFlagOrFail(t, flagName, flagValue) - } - go func() { main() }() @@ -296,4 +296,4 @@ func runPlatformAgnosticTestScenarios(t *testing.T) { assert.Equal(t, "rack wuz here", getURLWithHost(t, reqURL, statusHost)) }) -} \ No newline at end of file +} From b1474bd18cb8c88827e46d9ffe9935312fcc8d66 Mon Sep 17 00:00:00 2001 From: Alan Norton Date: Sun, 5 Apr 2020 15:00:55 -0400 Subject: [PATCH 10/10] trying to debug a really rare flake. --- dev/dns_test.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/dev/dns_test.go b/dev/dns_test.go index 0360a4f8..d7d96482 100644 --- a/dev/dns_test.go +++ b/dev/dns_test.go @@ -1,6 +1,7 @@ package dev import ( + "fmt" "net" "testing" "time" @@ -15,17 +16,18 @@ var tDNSResponder *DNSResponder func TestServeDNS(t *testing.T) { errChan := make(chan error, 1) domainList := []string{"test"} + dnsAddress := "localhost:10053" + shortTimeout := time.Duration(1 * time.Second) - tDNSResponder = NewDNSResponder("localhost:31337", domainList) + tDNSResponder = NewDNSResponder(dnsAddress, domainList) go func() { + defer close(errChan) if err := tDNSResponder.Serve(); err != nil { errChan <- err } - close(errChan) }() - shortTimeout := time.Duration(1 * time.Second) protocols := map[string]*dns.Server{ "tcp": tDNSResponder.tcpServer, "udp": tDNSResponder.udpServer, @@ -34,10 +36,12 @@ func TestServeDNS(t *testing.T) { for protocol, server := range protocols { dialError := retry.Do( func() error { - if _, err := net.DialTimeout(protocol, "localhost:31337", shortTimeout); err != nil { + if _, err := net.DialTimeout(protocol, dnsAddress, shortTimeout); err != nil { return err } - server.Shutdown() + + assert.NoError(t, server.Shutdown()) + return nil }, ) @@ -45,5 +49,9 @@ func TestServeDNS(t *testing.T) { assert.NoError(t, dialError) } - assert.NoError(t, <-errChan) + responderError := <-errChan + if responderError != nil { + fmt.Printf("%+v", responderError) + } + assert.NoError(t, responderError) }