uquic/integrationtests/integrationtests_suite_test.go
2017-06-13 18:07:22 +02:00

342 lines
9.2 KiB
Go

package integrationtests
import (
"bytes"
"crypto/md5"
"encoding/hex"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"mime/multipart"
"net"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"runtime"
"strconv"
"time"
"github.com/lucas-clemente/quic-go/h2quic"
"github.com/lucas-clemente/quic-go/internal/utils"
"github.com/lucas-clemente/quic-go/protocol"
"github.com/lucas-clemente/quic-go/testdata"
"github.com/tebeka/selenium"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gbytes"
"github.com/onsi/gomega/gexec"
"testing"
)
const (
dataLen = 500 * 1024 // 500 KB
dataLongLen = 50 * 1024 * 1024 // 50 MB
)
var (
server *h2quic.Server
dataMan dataManager
port string
uploadDir string
clientPath string // path of the quic_client
serverPath string // path of the quic_server
logFileName string // the log file set in the ginkgo flags
logFile *os.File
docker *gexec.Session
)
func TestIntegration(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Integration Tests Suite")
}
var _ = BeforeSuite(func() {
setupHTTPHandlers()
setupQuicServer()
setupSelenium()
})
var _ = AfterSuite(func() {
err := server.Close()
Expect(err).NotTo(HaveOccurred())
stopSelenium()
}, 10)
// read the logfile command line flag
// to set call ginkgo -- -logfile=log.txt
func init() {
flag.StringVar(&logFileName, "logfile", "", "log file")
}
var _ = BeforeEach(func() {
// set custom time format for logs
utils.SetLogTimeFormat("15:04:05.000")
// create a new uploadDir for every test
var err error
uploadDir, err = ioutil.TempDir("", "quic-upload-dest")
Expect(err).ToNot(HaveOccurred())
err = os.MkdirAll(uploadDir, os.ModeDir|0777)
Expect(err).ToNot(HaveOccurred())
_, thisfile, _, ok := runtime.Caller(0)
if !ok {
Fail("Failed to get current path")
}
clientPath = filepath.Join(thisfile, fmt.Sprintf("../../../quic-clients/client-%s-debug", runtime.GOOS))
serverPath = filepath.Join(thisfile, fmt.Sprintf("../../../quic-clients/server-%s-debug", runtime.GOOS))
if len(logFileName) > 0 {
logFile, err = os.Create("./log.txt")
Expect(err).ToNot(HaveOccurred())
log.SetOutput(logFile)
utils.SetLogLevel(utils.LogLevelDebug)
}
})
var _ = AfterEach(func() {
// remove uploadDir
if len(uploadDir) < 20 {
panic("uploadDir too short")
}
os.RemoveAll(uploadDir)
// remove downloaded file in docker container
removeDownload("data")
if len(logFileName) > 0 {
_ = logFile.Close()
}
})
func setupHTTPHandlers() {
defer GinkgoRecover()
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
_, err := io.WriteString(w, "Hello, World!\n")
Expect(err).NotTo(HaveOccurred())
})
http.HandleFunc("/data", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
data := dataMan.GetData()
Expect(data).ToNot(HaveLen(0))
_, err := w.Write(data)
Expect(err).NotTo(HaveOccurred())
})
http.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
body, err := ioutil.ReadAll(r.Body)
Expect(err).NotTo(HaveOccurred())
_, err = w.Write(body)
Expect(err).NotTo(HaveOccurred())
})
// requires the num GET parameter, e.g. /uploadform?num=2
// will create num input fields for uploading files
http.HandleFunc("/uploadform", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
num, err := strconv.Atoi(r.URL.Query().Get("num"))
Expect(err).ToNot(HaveOccurred())
response := "<html><body>\n<form id='form' action='https://quic.clemente.io/uploadhandler' method='post' enctype='multipart/form-data'>"
for i := 0; i < num; i++ {
response += "<input type='file' id='upload_" + strconv.Itoa(i) + "' name='uploadfile_" + strconv.Itoa(i) + "' />"
}
response += "</form><body></html>"
_, err = io.WriteString(w, response)
Expect(err).NotTo(HaveOccurred())
})
http.HandleFunc("/uploadhandler", func(w http.ResponseWriter, r *http.Request) {
defer GinkgoRecover()
err := r.ParseMultipartForm(100 * (1 << 20)) // max. 100 MB
Expect(err).ToNot(HaveOccurred())
count := 0
for {
var file multipart.File
var handler *multipart.FileHeader
file, handler, err = r.FormFile("uploadfile_" + strconv.Itoa(count))
if err != nil {
break
}
count++
f, err2 := os.OpenFile(path.Join(uploadDir, handler.Filename), os.O_WRONLY|os.O_CREATE, 0666)
Expect(err2).ToNot(HaveOccurred())
io.Copy(f, file)
f.Close()
file.Close()
}
Expect(count).ToNot(BeZero()) // there have been at least one uploaded file in this request
_, err = io.WriteString(w, "")
Expect(err).NotTo(HaveOccurred())
})
}
func setupQuicServer() {
server = &h2quic.Server{
Server: &http.Server{
TLSConfig: testdata.GetTLSConfig(),
},
}
addr, err := net.ResolveUDPAddr("udp", "0.0.0.0:0")
Expect(err).NotTo(HaveOccurred())
conn, err := net.ListenUDP("udp", addr)
Expect(err).NotTo(HaveOccurred())
port = strconv.Itoa(conn.LocalAddr().(*net.UDPAddr).Port)
go func() {
defer GinkgoRecover()
server.Serve(conn)
}()
}
func setupSelenium() {
var err error
pullCmd := exec.Command("docker", "pull", "lclemente/standalone-chrome:dev")
pull, err := gexec.Start(pullCmd, GinkgoWriter, GinkgoWriter)
Expect(err).NotTo(HaveOccurred())
// Assuming a download at 10 Mbit/s
Eventually(pull, 10*time.Minute).Should(gexec.Exit(0))
dockerCmd := exec.Command(
"docker",
"run",
"-i",
"--rm",
"-p=4444:4444",
"--name", "quic-test-selenium",
"lclemente/standalone-chrome:dev",
)
docker, err = gexec.Start(dockerCmd, GinkgoWriter, GinkgoWriter)
Expect(err).NotTo(HaveOccurred())
Eventually(docker.Out, 10).Should(gbytes.Say("Selenium Server is up and running"))
}
func stopSelenium() {
docker.Interrupt().Wait(10)
}
func getWebdriverForVersion(version protocol.VersionNumber) selenium.WebDriver {
caps := selenium.Capabilities{
"browserName": "chrome",
"chromeOptions": map[string]interface{}{
"args": []string{
"--enable-quic",
"--no-proxy-server",
"--origin-to-force-quic-on=quic.clemente.io:443",
fmt.Sprintf(`--host-resolver-rules=MAP quic.clemente.io:443 %s:%s`, GetLocalIP(), port),
fmt.Sprintf(`--quic-version=QUIC_VERSION_%d`, version),
},
},
}
wd, err := selenium.NewRemote(caps, "http://localhost:4444/wd/hub")
Expect(err).NotTo(HaveOccurred())
return wd
}
func GetLocalIP() string {
// First, try finding interface docker0
i, err := net.InterfaceByName("docker0")
if err == nil {
var addrs []net.Addr
addrs, err = i.Addrs()
Expect(err).NotTo(HaveOccurred())
return addrs[0].(*net.IPNet).IP.String()
}
addrs, err := net.InterfaceAddrs()
Expect(err).NotTo(HaveOccurred())
for _, address := range addrs {
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return ipnet.IP.String()
}
}
}
panic("no addr")
}
func removeDownload(filename string) {
cmd := exec.Command("docker", "exec", "-i", "quic-test-selenium", "rm", "-f", "/home/seluser/Downloads/"+filename)
session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
Expect(err).NotTo(HaveOccurred())
Eventually(session, 5).Should(gexec.Exit(0))
}
// getDownloadSize gets the file size of a file in the /home/seluser/Downloads folder in the docker container
func getDownloadSize(filename string) int {
var out bytes.Buffer
cmd := exec.Command("docker", "exec", "-i", "quic-test-selenium", "stat", "--printf=%s", "/home/seluser/Downloads/"+filename)
session, err := gexec.Start(cmd, &out, GinkgoWriter)
Expect(err).NotTo(HaveOccurred())
Eventually(session, 5).Should(gexec.Exit())
if session.ExitCode() != 0 {
return 0
}
Expect(out.Bytes()).ToNot(BeEmpty())
size, err := strconv.Atoi(string(out.Bytes()))
Expect(err).NotTo(HaveOccurred())
return size
}
// getFileSize gets the file size of a file on the local file system
func getFileSize(filename string) int {
file, err := os.Open(filename)
Expect(err).ToNot(HaveOccurred())
fi, err := file.Stat()
Expect(err).ToNot(HaveOccurred())
return int(fi.Size())
}
// getDownloadMD5 gets the md5 sum file of a file in the /home/seluser/Downloads folder in the docker container
func getDownloadMD5(filename string) []byte {
var out bytes.Buffer
cmd := exec.Command("docker", "exec", "-i", "quic-test-selenium", "md5sum", "/home/seluser/Downloads/"+filename)
session, err := gexec.Start(cmd, &out, GinkgoWriter)
Expect(err).NotTo(HaveOccurred())
Eventually(session, 5).Should(gexec.Exit())
if session.ExitCode() != 0 {
return nil
}
Expect(out.Bytes()).ToNot(BeEmpty())
res, err := hex.DecodeString(string(out.Bytes()[0:32]))
Expect(err).NotTo(HaveOccurred())
return res
}
// getFileMD5 gets the md5 sum of a file on the local file system
func getFileMD5(filepath string) []byte {
var result []byte
file, err := os.Open(filepath)
Expect(err).ToNot(HaveOccurred())
defer file.Close()
hash := md5.New()
_, err = io.Copy(hash, file)
Expect(err).ToNot(HaveOccurred())
return hash.Sum(result)
}
// copyFileToDocker copies a file from the local file system into the /home/seluser/ directory in the docker container
func copyFileToDocker(filepath string) {
cmd := exec.Command("docker", "cp", filepath, "quic-test-selenium:/home/seluser/")
session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
Expect(err).NotTo(HaveOccurred())
Eventually(session, 5).Should(gexec.Exit(0))
}