// Copyright (C) 2014 The Syncthing Authors. // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free // Software Foundation, either version 3 of the License, or (at your option) // any later version. // // This program is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for // more details. // // You should have received a copy of the GNU General Public License along // with this program. If not, see . // +build integration package integration import ( "bytes" "crypto/tls" "errors" "io/ioutil" "log" "net" "net/http" "sync" "testing" "time" ) func TestStressHTTP(t *testing.T) { log.Println("Cleaning...") err := removeAll("s2", "h2/index") if err != nil { t.Fatal(err) } log.Println("Starting up...") sender := syncthingProcess{ // id1 log: "2.out", argv: []string{"-home", "h2"}, port: 8082, apiKey: apiKey, } err = sender.start() if err != nil { t.Fatal(err) } // Create a client with reasonable timeouts on all stages of the request. tc := &tls.Config{InsecureSkipVerify: true} tr := &http.Transport{ TLSClientConfig: tc, DisableKeepAlives: true, ResponseHeaderTimeout: 10 * time.Second, TLSHandshakeTimeout: 10 * time.Second, } client := &http.Client{ Transport: tr, Timeout: 10 * time.Second, } var ( requestsOK = map[string]int{} requestsError = map[string]int{} firstError error lock sync.Mutex ) gotError := func(ctx string, err error) { lock.Lock() requestsError[ctx]++ if firstError == nil { firstError = err } lock.Unlock() } requestOK := func(ctx string) { lock.Lock() requestsOK[ctx]++ lock.Unlock() } log.Println("Testing...") var wg sync.WaitGroup t0 := time.Now() // One thread with immediately closed connections wg.Add(1) go func() { defer wg.Done() for time.Since(t0).Seconds() < 30 { conn, err := net.Dial("tcp", "localhost:8082") if err != nil { gotError("Dial", err) } else { requestOK("Dial") conn.Close() } // At most 100 connects/sec time.Sleep(10 * time.Millisecond) } }() // 50 threads doing mixed HTTP and HTTPS requests for i := 0; i < 50; i++ { i := i wg.Add(1) go func() { defer wg.Done() for time.Since(t0).Seconds() < 30 { proto := "http" if i%2 == 0 { proto = "https" } url := proto + "://localhost:8082/" resp, err := client.Get(url) if err != nil { gotError("Get "+proto, err) continue } bs, err := ioutil.ReadAll(resp.Body) resp.Body.Close() if err != nil { gotError("Read "+proto, err) continue } if !bytes.Contains(bs, []byte("")) { err := errors.New("Incorrect response") gotError("Get "+proto, err) continue } requestOK(url) // At most 100 requests/sec time.Sleep(10 * time.Millisecond) } }() } wg.Wait() t.Logf("OK: %v reqs", requestsOK) t.Logf("Err: %v reqs", requestsError) if firstError != nil { t.Error(firstError) } err = sender.stop() if err != nil { t.Error(err) } }