-
-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add a simple integration test using Selenium and a headless Chrome (#28)
* add an integration test using unidirectional streams * run interop tests on CI
- Loading branch information
1 parent
edc888a
commit b0873bb
Showing
5 changed files
with
307 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
on: [push, pull_request] | ||
name: Interop | ||
|
||
jobs: | ||
interop: | ||
runs-on: "ubuntu-latest" | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- uses: nanasess/setup-chromedriver@v1 | ||
with: | ||
# Optional: do not specify to match Chrome's version | ||
chromedriver-version: '105.0.5195.52' | ||
- uses: actions/setup-go@v3 | ||
with: | ||
go-version: "1.19.x" | ||
- name: Build interop server | ||
run: go build -o interopserver interop/main.go | ||
- uses: actions/setup-python@v4 | ||
with: | ||
python-version: '3.10' | ||
- name: Install Python dependencies | ||
run: pip install -r interop/requirements.txt | ||
- name: Run interop tests | ||
run: | | ||
./interopserver & | ||
timeout 120 python interop/interop.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
<html> | ||
<body> | ||
<script> | ||
function testSucceeded() { | ||
let elemDiv = document.createElement('div'); | ||
elemDiv.id = "done"; | ||
document.body.appendChild(elemDiv); | ||
} | ||
|
||
async function establishSession(url) { | ||
const transport = new WebTransport(url, { | ||
"serverCertificateHashes": [{ | ||
"algorithm": "sha-256", | ||
"value": new Uint8Array(%%CERTHASH%%) | ||
}] | ||
}); | ||
|
||
// Optionally, set up functions to respond to | ||
// the connection closing: | ||
transport.closed.then(() => { | ||
console.log(`The HTTP/3 connection to ${url} closed gracefully.`); | ||
}).catch((error) => { | ||
console.error(`The HTTP/3 connection to ${url} closed due to ${error}.`); | ||
}); | ||
|
||
// Once .ready fulfills, the connection can be used. | ||
await transport.ready; | ||
return transport; | ||
} | ||
|
||
// In this test, we open 5 unidirectional streams, and send the data back to the server. | ||
async function runUnidirectionalTest() { | ||
const transport = await establishSession('https://127.0.0.1:12345/unidirectional'); | ||
const data = new Uint8Array(%%DATA%%); | ||
|
||
let failed = false | ||
for(let i = 0; i < 5; i++) { | ||
const stream = await transport.createUnidirectionalStream(); | ||
console.log(`Opened stream ${i}.`) | ||
const writer = stream.getWriter(); | ||
writer.write(data); | ||
try { | ||
await writer.close(); | ||
console.log(`All data has been sent on stream ${i}.`); | ||
} catch (error) { | ||
console.error(`An error occurred: ${error}`); | ||
failed = true | ||
} | ||
} | ||
if(!failed) { testSucceeded() } | ||
transport.close() | ||
} | ||
|
||
(async function() { | ||
switch("%%TEST%%") { | ||
case "unidirectional": | ||
await runUnidirectionalTest() | ||
break | ||
} | ||
})() | ||
</script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
#!/usr/bin/env python3 | ||
|
||
import sys | ||
|
||
from selenium import webdriver | ||
from selenium.webdriver.chrome.service import Service | ||
from webdriver_manager.chrome import ChromeDriverManager | ||
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities | ||
from selenium.webdriver.support.ui import WebDriverWait | ||
from selenium.webdriver.support import expected_conditions | ||
from selenium.webdriver.common.by import By | ||
from selenium.common.exceptions import TimeoutException | ||
|
||
chrome_loc = "/usr/bin/google-chrome" | ||
if sys.platform == "darwin": | ||
chrome_loc = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" | ||
|
||
options = webdriver.ChromeOptions() | ||
options.gpu = False | ||
options.headless = True | ||
options.binary_location = chrome_loc | ||
options.add_argument("--no-sandbox") | ||
options.add_argument("--enable-quic") | ||
options.add_argument("--origin-to-force-quic-on=localhost:12345") | ||
options.add_argument("--host-resolver-rules='MAP localhost:12345 127.0.0.1:12345'") | ||
|
||
dc = DesiredCapabilities.CHROME | ||
dc["goog:loggingPrefs"] = {"browser": "ALL"} | ||
|
||
driver = webdriver.Chrome( | ||
service=Service(ChromeDriverManager().install()), | ||
options=options, | ||
desired_capabilities=dc, | ||
) | ||
driver.get("http://localhost:8080/webtransport") | ||
|
||
delay = 5 | ||
failed = False | ||
try: | ||
# when the test finishes successfully, it adds a div#done to the body | ||
myElem = WebDriverWait(driver, delay).until( | ||
expected_conditions.presence_of_element_located((By.ID, "done")) | ||
) | ||
print("Test succeeded!") | ||
except TimeoutException: | ||
failed = True | ||
print("Test timed out.") | ||
|
||
# for debugging, print all the console messages | ||
for entry in driver.get_log("browser"): | ||
print(entry) | ||
|
||
driver.quit() | ||
|
||
if failed: | ||
sys.exit(1) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
package main | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"crypto/ecdsa" | ||
"crypto/elliptic" | ||
"crypto/rand" | ||
"crypto/sha256" | ||
"crypto/tls" | ||
"crypto/x509" | ||
"crypto/x509/pkix" | ||
_ "embed" | ||
"encoding/binary" | ||
"fmt" | ||
"io" | ||
"log" | ||
"math/big" | ||
"net/http" | ||
"strings" | ||
"time" | ||
|
||
"github.com/lucas-clemente/quic-go/http3" | ||
"github.com/marten-seemann/webtransport-go" | ||
) | ||
|
||
//go:embed index.html | ||
var indexHTML string | ||
|
||
var data []byte | ||
|
||
func init() { | ||
data = make([]byte, 1<<20) | ||
rand.Read(data) | ||
} | ||
|
||
func main() { | ||
tlsConf, err := getTLSConf(time.Now(), time.Now().Add(10*24*time.Hour)) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
hash := sha256.Sum256(tlsConf.Certificates[0].Leaf.Raw) | ||
|
||
go runHTTPServer(hash) | ||
|
||
wmux := http.NewServeMux() | ||
s := webtransport.Server{ | ||
H3: http3.Server{ | ||
TLSConfig: tlsConf, | ||
Addr: "localhost:12345", | ||
Handler: wmux, | ||
}, | ||
CheckOrigin: func(r *http.Request) bool { return true }, | ||
} | ||
defer s.Close() | ||
|
||
wmux.HandleFunc("/unidirectional", func(w http.ResponseWriter, r *http.Request) { | ||
conn, err := s.Upgrade(w, r) | ||
if err != nil { | ||
log.Printf("upgrading failed: %s", err) | ||
w.WriteHeader(500) | ||
return | ||
} | ||
runUnidirectionalTest(conn) | ||
}) | ||
if err := s.ListenAndServe(); err != nil { | ||
log.Fatal(err) | ||
} | ||
} | ||
|
||
func runHTTPServer(certHash [32]byte) { | ||
mux := http.NewServeMux() | ||
mux.HandleFunc("/webtransport", func(w http.ResponseWriter, _ *http.Request) { | ||
fmt.Println("handler hit") | ||
content := strings.ReplaceAll(indexHTML, "%%CERTHASH%%", formatByteSlice(certHash[:])) | ||
content = strings.ReplaceAll(content, "%%DATA%%", formatByteSlice(data)) | ||
content = strings.ReplaceAll(content, "%%TEST%%", "unidirectional") | ||
w.Write([]byte(content)) | ||
}) | ||
http.ListenAndServe("localhost:8080", mux) | ||
} | ||
|
||
func runUnidirectionalTest(sess *webtransport.Session) { | ||
for i := 0; i < 5; i++ { | ||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) | ||
defer cancel() | ||
|
||
str, err := sess.AcceptUniStream(ctx) | ||
if err != nil { | ||
log.Fatalf("failed to accept unidirectional stream: %v", err) | ||
} | ||
rvcd, err := io.ReadAll(str) | ||
if err != nil { | ||
log.Fatalf("failed to read all data: %v", err) | ||
} | ||
if !bytes.Equal(rvcd, data) { | ||
log.Fatal("data doesn't match") | ||
} | ||
} | ||
select { | ||
case <-sess.Context().Done(): | ||
fmt.Println("done") | ||
case <-time.After(5 * time.Second): | ||
log.Fatal("timed out waiting for the session to be closed") | ||
} | ||
} | ||
|
||
func getTLSConf(start, end time.Time) (*tls.Config, error) { | ||
cert, priv, err := generateCert(start, end) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &tls.Config{ | ||
Certificates: []tls.Certificate{{ | ||
Certificate: [][]byte{cert.Raw}, | ||
PrivateKey: priv, | ||
Leaf: cert, | ||
}}, | ||
}, nil | ||
} | ||
|
||
func generateCert(start, end time.Time) (*x509.Certificate, *ecdsa.PrivateKey, error) { | ||
b := make([]byte, 8) | ||
if _, err := rand.Read(b); err != nil { | ||
return nil, nil, err | ||
} | ||
serial := int64(binary.BigEndian.Uint64(b)) | ||
if serial < 0 { | ||
serial = -serial | ||
} | ||
certTempl := &x509.Certificate{ | ||
SerialNumber: big.NewInt(serial), | ||
Subject: pkix.Name{}, | ||
NotBefore: start, | ||
NotAfter: end, | ||
IsCA: true, | ||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, | ||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, | ||
BasicConstraintsValid: true, | ||
} | ||
caPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
caBytes, err := x509.CreateCertificate(rand.Reader, certTempl, certTempl, &caPrivateKey.PublicKey, caPrivateKey) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
ca, err := x509.ParseCertificate(caBytes) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
return ca, caPrivateKey, nil | ||
} | ||
|
||
func formatByteSlice(b []byte) string { | ||
s := strings.ReplaceAll(fmt.Sprintf("%#v", b[:]), "[]byte{", "[") | ||
s = strings.ReplaceAll(s, "}", "]") | ||
return s | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
selenium | ||
webdriver_manager |