golang.org/x/net@v0.25.1-0.20240516223405-c87a5b62e243/internal/quic/cmd/interop/main_test.go (about)

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build go1.21
     6  
     7  package main
     8  
     9  import (
    10  	"bufio"
    11  	"bytes"
    12  	"context"
    13  	"net"
    14  	"os"
    15  	"os/exec"
    16  	"path/filepath"
    17  	"strings"
    18  	"sync"
    19  	"testing"
    20  )
    21  
    22  func init() {
    23  	// We reexec the test binary with CMD_INTEROP_MAIN=1 to run main.
    24  	if os.Getenv("CMD_INTEROP_MAIN") == "1" {
    25  		main()
    26  		os.Exit(0)
    27  	}
    28  }
    29  
    30  var (
    31  	tryExecOnce sync.Once
    32  	tryExecErr  error
    33  )
    34  
    35  // needsExec skips the test if we can't use exec.Command.
    36  func needsExec(t *testing.T) {
    37  	tryExecOnce.Do(func() {
    38  		cmd := exec.Command(os.Args[0], "-test.list=^$")
    39  		cmd.Env = []string{}
    40  		tryExecErr = cmd.Run()
    41  	})
    42  	if tryExecErr != nil {
    43  		t.Skipf("skipping test: cannot exec subprocess: %v", tryExecErr)
    44  	}
    45  }
    46  
    47  type interopTest struct {
    48  	donec chan struct{}
    49  	addr  string
    50  	cmd   *exec.Cmd
    51  }
    52  
    53  func run(ctx context.Context, t *testing.T, name, testcase string, args []string) *interopTest {
    54  	needsExec(t)
    55  	ctx, cancel := context.WithCancel(ctx)
    56  	cmd := exec.CommandContext(ctx, os.Args[0], args...)
    57  	out, err := cmd.StderrPipe()
    58  	if err != nil {
    59  		t.Fatal(err)
    60  	}
    61  	cmd.Stdout = cmd.Stderr
    62  	cmd.Env = []string{
    63  		"CMD_INTEROP_MAIN=1",
    64  		"TESTCASE=" + testcase,
    65  	}
    66  	t.Logf("run %v: %v", name, args)
    67  	err = cmd.Start()
    68  	if err != nil {
    69  		t.Fatal(err)
    70  	}
    71  
    72  	addrc := make(chan string, 1)
    73  	donec := make(chan struct{})
    74  	go func() {
    75  		defer close(addrc)
    76  		defer close(donec)
    77  		defer t.Logf("%v done", name)
    78  		s := bufio.NewScanner(out)
    79  		for s.Scan() {
    80  			line := s.Text()
    81  			t.Logf("%v: %v", name, line)
    82  			_, addr, ok := strings.Cut(line, "listening on ")
    83  			if ok {
    84  				select {
    85  				case addrc <- addr:
    86  				default:
    87  				}
    88  			}
    89  		}
    90  	}()
    91  
    92  	t.Cleanup(func() {
    93  		cancel()
    94  		<-donec
    95  	})
    96  
    97  	addr, ok := <-addrc
    98  	if !ok {
    99  		t.Fatal(cmd.Wait())
   100  	}
   101  	_, port, _ := net.SplitHostPort(addr)
   102  	addr = net.JoinHostPort("localhost", port)
   103  
   104  	iop := &interopTest{
   105  		cmd:   cmd,
   106  		donec: donec,
   107  		addr:  addr,
   108  	}
   109  	return iop
   110  }
   111  
   112  func (iop *interopTest) wait() {
   113  	<-iop.donec
   114  }
   115  
   116  func TestTransfer(t *testing.T) {
   117  	ctx := context.Background()
   118  	src := t.TempDir()
   119  	dst := t.TempDir()
   120  	certs := t.TempDir()
   121  	certFile := filepath.Join(certs, "cert.pem")
   122  	keyFile := filepath.Join(certs, "key.pem")
   123  	sourceName := "source"
   124  	content := []byte("hello, world\n")
   125  
   126  	os.WriteFile(certFile, localhostCert, 0600)
   127  	os.WriteFile(keyFile, localhostKey, 0600)
   128  	os.WriteFile(filepath.Join(src, sourceName), content, 0600)
   129  
   130  	srv := run(ctx, t, "server", "transfer", []string{
   131  		"-listen", "localhost:0",
   132  		"-cert", filepath.Join(certs, "cert.pem"),
   133  		"-key", filepath.Join(certs, "key.pem"),
   134  		"-root", src,
   135  	})
   136  	cli := run(ctx, t, "client", "transfer", []string{
   137  		"-output", dst, "https://" + srv.addr + "/" + sourceName,
   138  	})
   139  	cli.wait()
   140  
   141  	got, err := os.ReadFile(filepath.Join(dst, "source"))
   142  	if err != nil {
   143  		t.Fatalf("reading downloaded file: %v", err)
   144  	}
   145  	if !bytes.Equal(got, content) {
   146  		t.Fatalf("got downloaded file: %q, want %q", string(got), string(content))
   147  	}
   148  }
   149  
   150  // localhostCert is a PEM-encoded TLS cert with SAN IPs
   151  // "127.0.0.1" and "[::1]", expiring at Jan 29 16:00:00 2084 GMT.
   152  // generated from src/crypto/tls:
   153  // 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
   154  var localhostCert = []byte(`-----BEGIN CERTIFICATE-----
   155  MIIBrDCCAVKgAwIBAgIPCvPhO+Hfv+NW76kWxULUMAoGCCqGSM49BAMCMBIxEDAO
   156  BgNVBAoTB0FjbWUgQ28wIBcNNzAwMTAxMDAwMDAwWhgPMjA4NDAxMjkxNjAwMDBa
   157  MBIxEDAOBgNVBAoTB0FjbWUgQ28wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARh
   158  WRF8p8X9scgW7JjqAwI9nYV8jtkdhqAXG9gyEgnaFNN5Ze9l3Tp1R9yCDBMNsGms
   159  PyfMPe5Jrha/LmjgR1G9o4GIMIGFMA4GA1UdDwEB/wQEAwIChDATBgNVHSUEDDAK
   160  BggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSOJri/wLQxq6oC
   161  Y6ZImms/STbTljAuBgNVHREEJzAlggtleGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAA
   162  AAAAAAAAAAAAATAKBggqhkjOPQQDAgNIADBFAiBUguxsW6TGhixBAdORmVNnkx40
   163  HjkKwncMSDbUaeL9jQIhAJwQ8zV9JpQvYpsiDuMmqCuW35XXil3cQ6Drz82c+fvE
   164  -----END CERTIFICATE-----`)
   165  
   166  // localhostKey is the private key for localhostCert.
   167  var localhostKey = []byte(testingKey(`-----BEGIN TESTING KEY-----
   168  MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgY1B1eL/Bbwf/MDcs
   169  rnvvWhFNr1aGmJJR59PdCN9lVVqhRANCAARhWRF8p8X9scgW7JjqAwI9nYV8jtkd
   170  hqAXG9gyEgnaFNN5Ze9l3Tp1R9yCDBMNsGmsPyfMPe5Jrha/LmjgR1G9
   171  -----END TESTING KEY-----`))
   172  
   173  // testingKey helps keep security scanners from getting excited about a private key in this file.
   174  func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }