// 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") }