blob: 4119740e6c90b0b1dda88c3852f7adb0063dd1c1 [file] [log] [blame]
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.21
package main
import (
"bufio"
"bytes"
"context"
"net"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"testing"
)
func init() {
// We reexec the test binary with CMD_INTEROP_MAIN=1 to run main.
if os.Getenv("CMD_INTEROP_MAIN") == "1" {
main()
os.Exit(0)
}
}
var (
tryExecOnce sync.Once
tryExecErr error
)
// needsExec skips the test if we can't use exec.Command.
func needsExec(t *testing.T) {
tryExecOnce.Do(func() {
cmd := exec.Command(os.Args[0], "-test.list=^$")
cmd.Env = []string{}
tryExecErr = cmd.Run()
})
if tryExecErr != nil {
t.Skipf("skipping test: cannot exec subprocess: %v", tryExecErr)
}
}
type interopTest struct {
donec chan struct{}
addr string
cmd *exec.Cmd
}
func run(ctx context.Context, t *testing.T, name, testcase string, args []string) *interopTest {
needsExec(t)
ctx, cancel := context.WithCancel(ctx)
cmd := exec.CommandContext(ctx, os.Args[0], args...)
out, err := cmd.StderrPipe()
if err != nil {
t.Fatal(err)
}
cmd.Stdout = cmd.Stderr
cmd.Env = []string{
"CMD_INTEROP_MAIN=1",
"TESTCASE=" + testcase,
}
t.Logf("run %v: %v", name, args)
err = cmd.Start()
if err != nil {
t.Fatal(err)
}
addrc := make(chan string, 1)
donec := make(chan struct{})
go func() {
defer close(addrc)
defer close(donec)
defer t.Logf("%v done", name)
s := bufio.NewScanner(out)
for s.Scan() {
line := s.Text()
t.Logf("%v: %v", name, line)
_, addr, ok := strings.Cut(line, "listening on ")
if ok {
select {
case addrc <- addr:
default:
}
}
}
}()
t.Cleanup(func() {
cancel()
<-donec
})
addr, ok := <-addrc
if !ok {
t.Fatal(cmd.Wait())
}
_, port, _ := net.SplitHostPort(addr)
addr = net.JoinHostPort("localhost", port)
iop := &interopTest{
cmd: cmd,
donec: donec,
addr: addr,
}
return iop
}
func (iop *interopTest) wait() {
<-iop.donec
}
func TestTransfer(t *testing.T) {
ctx := context.Background()
src := t.TempDir()
dst := t.TempDir()
certs := t.TempDir()
certFile := filepath.Join(certs, "cert.pem")
keyFile := filepath.Join(certs, "key.pem")
sourceName := "source"
content := []byte("hello, world\n")
os.WriteFile(certFile, localhostCert, 0600)
os.WriteFile(keyFile, localhostKey, 0600)
os.WriteFile(filepath.Join(src, sourceName), content, 0600)
srv := run(ctx, t, "server", "transfer", []string{
"-listen", "localhost:0",
"-cert", filepath.Join(certs, "cert.pem"),
"-key", filepath.Join(certs, "key.pem"),
"-root", src,
})
cli := run(ctx, t, "client", "transfer", []string{
"-output", dst, "https://" + srv.addr + "/" + sourceName,
})
cli.wait()
got, err := os.ReadFile(filepath.Join(dst, "source"))
if err != nil {
t.Fatalf("reading downloaded file: %v", err)
}
if !bytes.Equal(got, content) {
t.Fatalf("got downloaded file: %q, want %q", string(got), string(content))
}
}
// localhostCert is a PEM-encoded TLS cert with SAN IPs
// "127.0.0.1" and "[::1]", expiring at Jan 29 16:00:00 2084 GMT.
// generated from src/crypto/tls:
// go run generate_cert.go --ecdsa-curve P256 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
var localhostCert = []byte(`-----BEGIN CERTIFICATE-----
MIIBrDCCAVKgAwIBAgIPCvPhO+Hfv+NW76kWxULUMAoGCCqGSM49BAMCMBIxEDAO
BgNVBAoTB0FjbWUgQ28wIBcNNzAwMTAxMDAwMDAwWhgPMjA4NDAxMjkxNjAwMDBa
MBIxEDAOBgNVBAoTB0FjbWUgQ28wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARh
WRF8p8X9scgW7JjqAwI9nYV8jtkdhqAXG9gyEgnaFNN5Ze9l3Tp1R9yCDBMNsGms
PyfMPe5Jrha/LmjgR1G9o4GIMIGFMA4GA1UdDwEB/wQEAwIChDATBgNVHSUEDDAK
BggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSOJri/wLQxq6oC
Y6ZImms/STbTljAuBgNVHREEJzAlggtleGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAA
AAAAAAAAAAAAATAKBggqhkjOPQQDAgNIADBFAiBUguxsW6TGhixBAdORmVNnkx40
HjkKwncMSDbUaeL9jQIhAJwQ8zV9JpQvYpsiDuMmqCuW35XXil3cQ6Drz82c+fvE
-----END CERTIFICATE-----`)
// localhostKey is the private key for localhostCert.
var localhostKey = []byte(testingKey(`-----BEGIN TESTING KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgY1B1eL/Bbwf/MDcs
rnvvWhFNr1aGmJJR59PdCN9lVVqhRANCAARhWRF8p8X9scgW7JjqAwI9nYV8jtkd
hqAXG9gyEgnaFNN5Ze9l3Tp1R9yCDBMNsGmsPyfMPe5Jrha/LmjgR1G9
-----END TESTING KEY-----`))
// testingKey helps keep security scanners from getting excited about a private key in this file.
func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }