git » debian:dnss » commit 448f59f

testing: Add tests for HTTPS mode

author Alberto Bertogli
2016-05-22 23:11:36 UTC
committer Alberto Bertogli
2016-05-22 23:20:48 UTC
parent 7372ac64587dad0d02daee305bbf5d5ed25b8e6c

testing: Add tests for HTTPS mode

This patch moves the existing tests (covering GRPC modes) to a new
testing/grpc directory, and adds new tests for HTTPS mode in testing/https.

testing/grpc/grpc.go +4 -0
dnss_test.go => testing/grpc/grpc_test.go +6 -32
testing/https/https.go +4 -0
testing/https/https_test.go +163 -0
testing/util/util.go +40 -0

diff --git a/testing/grpc/grpc.go b/testing/grpc/grpc.go
new file mode 100644
index 0000000..268365b
--- /dev/null
+++ b/testing/grpc/grpc.go
@@ -0,0 +1,4 @@
+package grpc
+
+// Dummy file so "go build ./..." does not complain about the directory not
+// having buildable files.
diff --git a/dnss_test.go b/testing/grpc/grpc_test.go
similarity index 84%
rename from dnss_test.go
rename to testing/grpc/grpc_test.go
index f7ca6d6..95c3a36 100644
--- a/dnss_test.go
+++ b/testing/grpc/grpc_test.go
@@ -1,5 +1,5 @@
-// Tests for dnss.
-package main
+// Tests for dnss in GRPC modes.
+package grpc
 
 import (
 	"crypto/rand"
@@ -18,6 +18,7 @@ import (
 
 	"blitiri.com.ar/go/dnss/internal/dnstox"
 	"blitiri.com.ar/go/dnss/internal/grpctodns"
+	"blitiri.com.ar/go/dnss/testing/util"
 
 	"github.com/golang/glog"
 	"github.com/miekg/dns"
@@ -75,11 +76,11 @@ func manyDNSQueries(b *testing.B, addr string) {
 	}
 }
 
-func BenchmarkDirect(b *testing.B) {
+func BenchmarkGRPCDirect(b *testing.B) {
 	manyDNSQueries(b, dnsSrvAddr)
 }
 
-func BenchmarkWithProxy(b *testing.B) {
+func BenchmarkGRPCWithProxy(b *testing.B) {
 	manyDNSQueries(b, dnsToGrpcAddr)
 }
 
@@ -182,33 +183,6 @@ func generateCert(path string) error {
 	return nil
 }
 
-// waitForServers waits 5 seconds for the servers to start, and returns an
-// error if they fail to do so.
-// It does this by repeatedly querying the dns-to-grpc server until it either
-// replies or times out. Note we do not do any validation of the reply.
-func waitForServers() error {
-	conn, err := dns.DialTimeout("udp", dnsToGrpcAddr, 1*time.Second)
-	if err != nil {
-		return fmt.Errorf("dns.Dial error: %v", err)
-	}
-	defer conn.Close()
-
-	after := time.After(5 * time.Second)
-	tick := time.Tick(100 * time.Millisecond)
-	select {
-	case <-after:
-		return fmt.Errorf("timed out")
-	case <-tick:
-		conn.SetDeadline(time.Now().Add(1 * time.Second))
-		err := dnsQuery(conn)
-		if err == nil {
-			return nil
-		}
-	}
-
-	return fmt.Errorf("not reachable")
-}
-
 // realMain is the real main function, which returns the value to pass to
 // os.Exit(). We have to do this so we can use defer.
 func realMain(m *testing.M) int {
@@ -251,7 +225,7 @@ func realMain(m *testing.M) int {
 	go dnsSrv.ListenAndServe()
 
 	// Wait for the servers to start up.
-	err = waitForServers()
+	err = util.WaitForDNSServer(dnsToGrpcAddr)
 	if err != nil {
 		fmt.Printf("Error waiting for the test servers to start: %v\n", err)
 		fmt.Printf("Check the INFO logs for more details\n")
diff --git a/testing/https/https.go b/testing/https/https.go
new file mode 100644
index 0000000..cc8acf1
--- /dev/null
+++ b/testing/https/https.go
@@ -0,0 +1,4 @@
+package https
+
+// Dummy file so "go build ./..." does not complain about the directory not
+// having buildable files.
diff --git a/testing/https/https_test.go b/testing/https/https_test.go
new file mode 100644
index 0000000..1794d56
--- /dev/null
+++ b/testing/https/https_test.go
@@ -0,0 +1,163 @@
+// Tests for dnss in HTTPS mode.
+package https
+
+import (
+	"flag"
+	"fmt"
+	"net/http"
+	"net/http/httptest"
+	"os"
+	"testing"
+
+	"blitiri.com.ar/go/dnss/internal/dnstox"
+	"blitiri.com.ar/go/dnss/testing/util"
+
+	"github.com/golang/glog"
+	"github.com/miekg/dns"
+)
+
+//
+// === Tests ===
+//
+func dnsQuery(addr string, qtype uint16) (*dns.Msg, dns.RR, error) {
+	m := new(dns.Msg)
+	m.SetQuestion(addr, qtype)
+	in, err := dns.Exchange(m, DNSAddr)
+
+	if err != nil {
+		return nil, nil, err
+	} else if len(in.Answer) > 0 {
+		return in, in.Answer[0], nil
+	} else {
+		return in, nil, nil
+	}
+}
+
+func TestSimple(t *testing.T) {
+	_, ans, err := dnsQuery("test.blah.", dns.TypeA)
+	if err != nil {
+		t.Errorf("dns query returned error: %v", err)
+	}
+	if ans.(*dns.A).A.String() != "1.2.3.4" {
+		t.Errorf("unexpected result: %q", ans)
+	}
+
+	_, ans, err = dnsQuery("test.blah.", dns.TypeMX)
+	if err != nil {
+		t.Errorf("dns query returned error: %v", err)
+	}
+	if ans.(*dns.MX).Mx != "mail.test.blah." {
+		t.Errorf("unexpected result: %q", ans.(*dns.MX).Mx)
+	}
+
+	in, _, err := dnsQuery("unknown.", dns.TypeA)
+	if err != nil {
+		t.Errorf("dns query returned error: %v", err)
+	}
+	if in.Rcode != dns.RcodeNameError {
+		t.Errorf("unexpected result: %q", in)
+	}
+}
+
+//
+// === Benchmarks ===
+//
+
+func BenchmarkHTTPSimple(b *testing.B) {
+	var err error
+	for i := 0; i < b.N; i++ {
+		_, _, err = dnsQuery("test.blah.", dns.TypeA)
+		if err != nil {
+			b.Errorf("dns query returned error: %v", err)
+		}
+	}
+}
+
+//
+// === Test environment ===
+//
+
+// DNSHandler handles DNS-over-HTTP requests, and returns json data.
+// This is used as the test server for our resolver.
+func DNSHandler(w http.ResponseWriter, r *http.Request) {
+	err := r.ParseForm()
+	if err != nil {
+		panic(err)
+	}
+
+	w.Header().Set("Content-Type", "text/json")
+
+	resp := jsonNXDOMAIN
+
+	if r.Form["name"][0] == "test.blah." {
+		switch r.Form["type"][0] {
+		case "1", "A":
+			resp = jsonA
+		case "15", "MX":
+			resp = jsonMX
+		default:
+			resp = jsonNXDOMAIN
+		}
+	}
+
+	w.Write([]byte(resp))
+}
+
+// A record.
+const jsonA = ` {
+  "Status": 0, "TC": false, "RD": true, "RA": true, "AD": false, "CD": false,
+  "Question": [ { "name": "test.blah.", "type": 1 }
+  ],
+  "Answer": [ { "name": "test.blah.", "type": 1, "TTL": 21599,
+	  "data": "1.2.3.4" } ] }
+`
+
+// MX record.
+const jsonMX = ` {
+  "Status": 0, "TC": false, "RD": true, "RA": true, "AD": false, "CD": false,
+  "Question": [ { "name": "test.blah.", "type": 15 } ],
+  "Answer": [ { "name": "test.blah.", "type": 15, "TTL": 21599,
+	  "data": "10 mail.test.blah." } ] }
+`
+
+// NXDOMAIN error.
+const jsonNXDOMAIN = ` {
+  "Status": 3, "TC": false, "RD": true, "RA": true, "AD": true, "CD": false,
+  "Question": [ { "name": "doesnotexist.", "type": 15 } ],
+  "Authority": [ { "name": ".", "type": 6, "TTL": 1798,
+	  "data": "root. nstld. 2016052201 1800 900 604800 86400" } ] }
+`
+
+const (
+	// TODO: don't hardcode these.
+	DNSAddr string = "127.0.0.1:19251"
+)
+
+// realMain is the real main function, which returns the value to pass to
+// os.Exit(). We have to do this so we can use defer.
+func realMain(m *testing.M) int {
+	flag.Parse()
+	defer glog.Flush()
+
+	// Test http server.
+	httpsrv := httptest.NewServer(http.HandlerFunc(DNSHandler))
+
+	// DNS to HTTPS server.
+	r := dnstox.NewHTTPSResolver(httpsrv.URL, "")
+	dth := dnstox.New(DNSAddr, r, "")
+	go dth.ListenAndServe()
+
+	// Wait for the servers to start up.
+	err := util.WaitForDNSServer(DNSAddr)
+	if err != nil {
+		fmt.Printf("Error waiting for the test servers to start: %v\n", err)
+		fmt.Printf("Check the INFO logs for more details\n")
+		return 1
+	}
+
+	return m.Run()
+}
+
+func TestMain(m *testing.M) {
+	os.Exit(realMain(m))
+}
diff --git a/testing/util/util.go b/testing/util/util.go
new file mode 100644
index 0000000..9964691
--- /dev/null
+++ b/testing/util/util.go
@@ -0,0 +1,40 @@
+// Package util implements common testing utilities.
+package util
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/miekg/dns"
+)
+
+// WaitForDNSServer waits 5 seconds for a DNS server to start, and returns an
+// error if it fails to do so.
+// It does this by repeatedly querying the DNS server until it either replies
+// or times out. Note we do not do any validation of the reply.
+func WaitForDNSServer(addr string) error {
+	conn, err := dns.DialTimeout("udp", addr, 1*time.Second)
+	if err != nil {
+		return fmt.Errorf("dns.Dial error: %v", err)
+	}
+	defer conn.Close()
+
+	m := &dns.Msg{}
+	m.SetQuestion("unused.", dns.TypeA)
+
+	after := time.After(5 * time.Second)
+	tick := time.Tick(100 * time.Millisecond)
+	select {
+	case <-after:
+		return fmt.Errorf("timed out")
+	case <-tick:
+		conn.SetDeadline(time.Now().Add(1 * time.Second))
+		conn.WriteMsg(m)
+		_, err := conn.ReadMsg()
+		if err == nil {
+			return nil
+		}
+	}
+
+	return fmt.Errorf("not reachable")
+}