cmd/tailscale/cli: refactor TestServeDevConfigMutations

The TestServeDevConfigMutations test has 63 steps that all run
under the same scope. This tests breaks them out into isolated
subtests that can be run independently.

Updates #8489

Signed-off-by: Marwan Sulaiman <marwan@tailscale.com>
This commit is contained in:
Marwan Sulaiman 2023-10-24 17:14:49 -04:00 committed by Marwan Sulaiman
parent d79e0fde9c
commit a7e4cebb90
3 changed files with 803 additions and 773 deletions

View File

@ -171,6 +171,7 @@ type serveEnv struct {
// optional stuff for tests: // optional stuff for tests:
testFlagOut io.Writer testFlagOut io.Writer
testStdout io.Writer testStdout io.Writer
testStderr io.Writer
} }
// getSelfDNSName returns the DNS name of the current node. // getSelfDNSName returns the DNS name of the current node.
@ -681,13 +682,6 @@ func (e *serveEnv) runServeStatus(ctx context.Context, args []string) error {
return nil return nil
} }
func (e *serveEnv) stdout() io.Writer {
if e.testStdout != nil {
return e.testStdout
}
return os.Stdout
}
func printTCPStatusTree(ctx context.Context, sc *ipn.ServeConfig, st *ipnstate.Status) error { func printTCPStatusTree(ctx context.Context, sc *ipn.ServeConfig, st *ipnstate.Status) error {
dnsName := strings.TrimSuffix(st.Self.DNSName, ".") dnsName := strings.TrimSuffix(st.Self.DNSName, ".")
for p, h := range sc.TCP { for p, h := range sc.TCP {

View File

@ -156,26 +156,26 @@ func newServeV2Command(e *serveEnv, subcmd serveMode) *ffcli.Command {
} }
} }
func validateArgs(subcmd serveMode, args []string) error { func (e *serveEnv) validateArgs(subcmd serveMode, args []string) error {
if translation, ok := isLegacyInvocation(subcmd, args); ok { if translation, ok := isLegacyInvocation(subcmd, args); ok {
fmt.Fprint(os.Stderr, "Error: the CLI for serve and funnel has changed.") fmt.Fprint(e.stderr(), "Error: the CLI for serve and funnel has changed.")
if translation != "" { if translation != "" {
fmt.Fprint(os.Stderr, " You can run the following command instead:\n") fmt.Fprint(e.stderr(), " You can run the following command instead:\n")
fmt.Fprintf(os.Stderr, "\t- %s\n", translation) fmt.Fprintf(e.stderr(), "\t- %s\n", translation)
} }
fmt.Fprint(os.Stderr, "\nPlease see https://tailscale.com/kb/1242/tailscale-serve for more information.\n") fmt.Fprint(e.stderr(), "\nPlease see https://tailscale.com/kb/1242/tailscale-serve for more information.\n")
return errHelpFunc(subcmd) return errHelpFunc(subcmd)
} }
if len(args) == 0 { if len(args) == 0 {
return flag.ErrHelp return flag.ErrHelp
} }
if len(args) > 2 { if len(args) > 2 {
fmt.Fprintf(os.Stderr, "Error: invalid number of arguments (%d)\n", len(args)) fmt.Fprintf(e.stderr(), "Error: invalid number of arguments (%d)\n", len(args))
return errHelpFunc(subcmd) return errHelpFunc(subcmd)
} }
turnOff := args[len(args)-1] == "off" turnOff := args[len(args)-1] == "off"
if len(args) == 2 && !turnOff { if len(args) == 2 && !turnOff {
fmt.Fprintln(os.Stderr, "Error: invalid argument format") fmt.Fprintln(e.stderr(), "Error: invalid argument format")
return errHelpFunc(subcmd) return errHelpFunc(subcmd)
} }
@ -203,7 +203,7 @@ func (e *serveEnv) runServeCombined(subcmd serveMode) execFunc {
return e.lc.SetServeConfig(ctx, sc) return e.lc.SetServeConfig(ctx, sc)
} }
if err := validateArgs(subcmd, args); err != nil { if err := e.validateArgs(subcmd, args); err != nil {
return err return err
} }
@ -230,7 +230,7 @@ func (e *serveEnv) runServeCombined(subcmd serveMode) execFunc {
srvType, srvPort, err := srvTypeAndPortFromFlags(e) srvType, srvPort, err := srvTypeAndPortFromFlags(e)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n\n", err) fmt.Fprintf(e.stderr(), "error: %v\n\n", err)
return errHelpFunc(subcmd) return errHelpFunc(subcmd)
} }
@ -300,19 +300,19 @@ func (e *serveEnv) runServeCombined(subcmd serveMode) execFunc {
msg = e.messageForPort(sc, st, dnsName, srvType, srvPort) msg = e.messageForPort(sc, st, dnsName, srvType, srvPort)
} }
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n\n", err) fmt.Fprintf(e.stderr(), "error: %v\n\n", err)
return errHelpFunc(subcmd) return errHelpFunc(subcmd)
} }
if err := e.lc.SetServeConfig(ctx, parentSC); err != nil { if err := e.lc.SetServeConfig(ctx, parentSC); err != nil {
if tailscale.IsPreconditionsFailedError(err) { if tailscale.IsPreconditionsFailedError(err) {
fmt.Fprintln(os.Stderr, "Another client is changing the serve config; please try again.") fmt.Fprintln(e.stderr(), "Another client is changing the serve config; please try again.")
} }
return err return err
} }
if msg != "" { if msg != "" {
fmt.Fprintln(os.Stderr, msg) fmt.Fprintln(e.stdout(), msg)
} }
if watcher != nil { if watcher != nil {
@ -621,7 +621,7 @@ func (e *serveEnv) applyFunnel(sc *ipn.ServeConfig, dnsName string, srvPort uint
if allowFunnel { if allowFunnel {
mak.Set(&sc.AllowFunnel, hp, true) mak.Set(&sc.AllowFunnel, hp, true)
} else if _, exists := sc.AllowFunnel[hp]; exists { } else if _, exists := sc.AllowFunnel[hp]; exists {
fmt.Printf("Removing Funnel for %s\n", hp) fmt.Fprintf(e.stderr(), "Removing Funnel for %s\n", hp)
delete(sc.AllowFunnel, hp) delete(sc.AllowFunnel, hp)
} }
} }
@ -953,3 +953,17 @@ func (s serveType) String() string {
return "unknownServeType" return "unknownServeType"
} }
} }
func (e *serveEnv) stdout() io.Writer {
if e.testStdout != nil {
return e.testStdout
}
return os.Stdout
}
func (e *serveEnv) stderr() io.Writer {
if e.testStderr != nil {
return e.testStderr
}
return os.Stderr
}

View File

@ -6,41 +6,55 @@ package cli
import ( import (
"bytes" "bytes"
"context" "context"
"encoding/json"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"runtime"
"strconv" "strconv"
"strings" "strings"
"testing" "testing"
"github.com/google/go-cmp/cmp"
"github.com/peterbourgon/ff/v3/ffcli" "github.com/peterbourgon/ff/v3/ffcli"
"tailscale.com/ipn" "tailscale.com/ipn"
"tailscale.com/ipn/ipnstate" "tailscale.com/ipn/ipnstate"
"tailscale.com/types/logger"
) )
func TestServeDevConfigMutations(t *testing.T) { func TestServeDevConfigMutations(t *testing.T) {
// Stateful mutations, starting from an empty config. // step is a stateful mutation within a group
type step struct { type step struct {
command []string // serve args; nil means no command to run (only reset) command []string // serve args; nil means no command to run (only reset)
reset bool // if true, reset all ServeConfig state
want *ipn.ServeConfig // non-nil means we want a save of this value want *ipn.ServeConfig // non-nil means we want a save of this value
wantErr func(error) (badErrMsg string) // nil means no error is wanted wantErr func(error) (badErrMsg string) // nil means no error is wanted
line int // line number of addStep call, for error messages before func(t *testing.T)
debugBreak func()
}
var steps []step
add := func(s step) {
_, _, s.line, _ = runtime.Caller(1)
steps = append(steps, s)
} }
// using port number // group is a group of steps that share the same
add(step{reset: true}) // config mutation, but always starts from an empty config
add(step{ type group struct {
name string
steps []step
}
// creaet a temporary directory for path-based destinations
td := t.TempDir()
writeFile := func(suffix, contents string) {
if err := os.WriteFile(filepath.Join(td, suffix), []byte(contents), 0600); err != nil {
t.Fatal(err)
}
}
writeFile("foo", "this is foo")
err := os.MkdirAll(filepath.Join(td, "subdir"), 0700)
if err != nil {
t.Fatal(err)
}
writeFile("subdir/file-a", "this is subdir")
groups := [...]group{
{
name: "using_port_number",
steps: []step{{
command: cmd("funnel --bg 3000"), command: cmd("funnel --bg 3000"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
@ -51,11 +65,11 @@ func TestServeDevConfigMutations(t *testing.T) {
}, },
AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:443": true}, AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:443": true},
}, },
}) }},
},
// funnel background {
add(step{reset: true}) name: "funnel_background",
add(step{ steps: []step{{
command: cmd("funnel --bg localhost:3000"), command: cmd("funnel --bg localhost:3000"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
@ -66,11 +80,11 @@ func TestServeDevConfigMutations(t *testing.T) {
}, },
AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:443": true}, AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:443": true},
}, },
}) }},
},
// serve background {
add(step{reset: true}) name: "serve_background",
add(step{ steps: []step{{
command: cmd("serve --bg localhost:3000"), command: cmd("serve --bg localhost:3000"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
@ -80,12 +94,12 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) }},
},
// --set-path runs in background {
add(step{reset: true}) name: "set_path_bg",
add(step{ steps: []step{{
command: cmd("serve --bg --set-path=/ localhost:3000"), command: cmd("serve --set-path=/ --bg localhost:3000"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{ Web: map[ipn.HostPort]*ipn.WebServerConfig{
@ -94,11 +108,11 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) }},
},
// using http listener {
add(step{reset: true}) name: "http_listener",
add(step{ steps: []step{{
command: cmd("serve --bg --http=80 localhost:3000"), command: cmd("serve --bg --http=80 localhost:3000"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}}, TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}},
@ -108,11 +122,11 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) }},
},
// using https listener with a valid port {
add(step{reset: true}) name: "https_listener_valid_port",
add(step{ steps: []step{{
command: cmd("serve --bg --https=8443 localhost:3000"), command: cmd("serve --bg --https=8443 localhost:3000"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{8443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{8443: {HTTPS: true}},
@ -122,11 +136,12 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) }},
},
// https {
add(step{reset: true}) name: "multiple_http_with_off",
add(step{ // allow omitting port (default to 80) steps: []step{
{
command: cmd("serve --http=80 --bg http://localhost:3000"), command: cmd("serve --http=80 --bg http://localhost:3000"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}}, TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}},
@ -136,9 +151,9 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
add(step{ // support non Funnel port { // support non Funnel port
command: cmd("serve --http=9999 --bg --set-path=/abc http://localhost:3001"), command: cmd("serve --bg --http=9999 --set-path=/abc http://localhost:3001"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}, 9999: {HTTP: true}}, TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}, 9999: {HTTP: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{ Web: map[ipn.HostPort]*ipn.WebServerConfig{
@ -150,9 +165,9 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
add(step{ { // turn off one handler
command: cmd("serve --http=9999 --bg --set-path=/abc off"), command: cmd("serve --bg --http=9999 --set-path=/abc off"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}}, TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{ Web: map[ipn.HostPort]*ipn.WebServerConfig{
@ -161,9 +176,9 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
add(step{ { // add another handler
command: cmd("serve --http=8080 --bg --set-path=/abc http://127.0.0.1:3001"), command: cmd("serve --bg --http=8080 --set-path=/abc http://127.0.0.1:3001"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}, 8080: {HTTP: true}}, TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}, 8080: {HTTP: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{ Web: map[ipn.HostPort]*ipn.WebServerConfig{
@ -175,28 +190,42 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
},
// // https },
add(step{reset: true}) {
add(step{ name: "invalid_port_too_low",
steps: []step{{
command: cmd("serve --https=443 --bg http://localhost:0"), // invalid port, too low command: cmd("serve --https=443 --bg http://localhost:0"), // invalid port, too low
wantErr: anyErr(), wantErr: anyErr(),
}) }},
add(step{ },
{
name: "invalid_port_too_high",
steps: []step{{
command: cmd("serve --https=443 --bg http://localhost:65536"), // invalid port, too high command: cmd("serve --https=443 --bg http://localhost:65536"), // invalid port, too high
wantErr: anyErr(), wantErr: anyErr(),
}) }},
add(step{ },
{
name: "invalid_host",
steps: []step{{
command: cmd("serve --https=443 --bg http://somehost:3000"), // invalid host command: cmd("serve --https=443 --bg http://somehost:3000"), // invalid host
wantErr: anyErr(), wantErr: anyErr(),
}) }},
add(step{ },
{
name: "invalid_scheme",
steps: []step{{
command: cmd("serve --https=443 --bg httpz://127.0.0.1"), // invalid scheme command: cmd("serve --https=443 --bg httpz://127.0.0.1"), // invalid scheme
wantErr: anyErr(), wantErr: anyErr(),
}) }},
add(step{ // allow omitting port (default to 443) },
command: cmd("serve --https=443 --bg http://localhost:3000"), {
name: "turn_off_https",
steps: []step{
{
command: cmd("serve --bg --https=443 http://localhost:3000"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{ Web: map[ipn.HostPort]*ipn.WebServerConfig{
@ -205,9 +234,9 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
add(step{ // support non Funnel port {
command: cmd("serve --https=9999 --bg --set-path=/abc http://localhost:3001"), command: cmd("serve --bg --https=9999 --set-path=/abc http://localhost:3001"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 9999: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 9999: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{ Web: map[ipn.HostPort]*ipn.WebServerConfig{
@ -219,9 +248,9 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
add(step{ {
command: cmd("serve --https=9999 --bg --set-path=/abc off"), command: cmd("serve --bg --https=9999 --set-path=/abc off"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{ Web: map[ipn.HostPort]*ipn.WebServerConfig{
@ -230,9 +259,9 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
add(step{ {
command: cmd("serve --https=8443 --bg --set-path=/abc http://127.0.0.1:3001"), command: cmd("serve --bg --https=8443 --set-path=/abc http://127.0.0.1:3001"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 8443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 8443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{ Web: map[ipn.HostPort]*ipn.WebServerConfig{
@ -244,61 +273,35 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
add(step{ },
command: cmd("serve --https=10000 --bg text:hi"), },
{
name: "https_text_bg",
steps: []step{{
command: cmd("serve --bg --https=10000 text:hi"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{ TCP: map[uint16]*ipn.TCPPortHandler{10000: {HTTPS: true}},
443: {HTTPS: true}, 8443: {HTTPS: true}, 10000: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{ Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
"foo.test.ts.net:8443": {Handlers: map[string]*ipn.HTTPHandler{
"/abc": {Proxy: "http://127.0.0.1:3001"},
}},
"foo.test.ts.net:10000": {Handlers: map[string]*ipn.HTTPHandler{ "foo.test.ts.net:10000": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Text: "hi"}, "/": {Text: "hi"},
}}, }},
}, },
}, },
}) }},
add(step{ },
command: cmd("serve --https=443 --bg --set-path=/foo off"), {
name: "handler_not_found",
steps: []step{{
command: cmd("serve --https=443 --set-path=/foo off"),
want: nil, // nothing to save want: nil, // nothing to save
wantErr: anyErr(), wantErr: anyErr(),
}) // handler doesn't exist, so we get an error
add(step{
command: cmd("serve --https=10000 off"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 8443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/": {Proxy: "http://127.0.0.1:3000"},
}},
"foo.test.ts.net:8443": {Handlers: map[string]*ipn.HTTPHandler{
"/abc": {Proxy: "http://127.0.0.1:3001"},
}}, }},
}, },
}, {
}) name: "clean_mount", // "bar" becomes "/bar"
add(step{ steps: []step{{
command: cmd("serve --https=443 off"), command: cmd("serve --bg --https=443 --set-path=bar https://127.0.0.1:8443"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{8443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:8443": {Handlers: map[string]*ipn.HTTPHandler{
"/abc": {Proxy: "http://127.0.0.1:3001"},
}},
},
},
})
add(step{
command: cmd("serve --https=8443 --bg --set-path=/abc off"),
want: &ipn.ServeConfig{},
})
add(step{ // clean mount: "bar" becomes "/bar"
command: cmd("serve --https=443 --bg --set-path=bar https://127.0.0.1:8443"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{ Web: map[ipn.HostPort]*ipn.WebServerConfig{
@ -307,17 +310,32 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) }},
// add(step{ },
// command: cmd("serve --https=443 --set-path=bar https://127.0.0.1:8443"), {
// want: nil, // nothing to save name: "serve_reset",
// }) steps: []step{
add(step{ // try resetting using reset command {
command: cmd("serve --bg --https=443 --set-path=bar https://127.0.0.1:8443"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
"/bar": {Proxy: "https://127.0.0.1:8443"},
}},
},
},
},
{
command: cmd("serve reset"), command: cmd("serve reset"),
want: &ipn.ServeConfig{}, want: &ipn.ServeConfig{},
}) },
add(step{ },
command: cmd("serve --https=443 --bg https+insecure://127.0.0.1:3001"), },
{
name: "https_insecure",
steps: []step{{
command: cmd("serve --bg --https=443 https+insecure://127.0.0.1:3001"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{ Web: map[ipn.HostPort]*ipn.WebServerConfig{
@ -326,10 +344,13 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) }},
add(step{reset: true}) },
add(step{ {
command: cmd("serve --https=443 --bg --set-path=/foo localhost:3000"), name: "two_ports_same_dest",
steps: []step{
{
command: cmd("serve --bg --https=443 --set-path=/foo localhost:3000"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{ Web: map[ipn.HostPort]*ipn.WebServerConfig{
@ -338,9 +359,9 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
add(step{ // test a second handler on the same port {
command: cmd("serve --https=8443 --bg --set-path=/foo localhost:3000"), command: cmd("serve --bg --https=8443 --set-path=/foo localhost:3000"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 8443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 8443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{ Web: map[ipn.HostPort]*ipn.WebServerConfig{
@ -352,10 +373,13 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
add(step{reset: true}) },
add(step{ // support path in proxy },
command: cmd("serve --https=443 --bg http://127.0.0.1:3000/foo/bar"), {
name: "path_in_dest",
steps: []step{{
command: cmd("serve --bg --https=443 http://127.0.0.1:3000/foo/bar"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{ Web: map[ipn.HostPort]*ipn.WebServerConfig{
@ -364,23 +388,32 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) }},
},
// // tcp {
add(step{reset: true}) name: "unknown_host_tcp",
add(step{ // !somehost, must be localhost or 127.0.0.1 steps: []step{{
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://somehost:5432"), command: cmd("serve --tls-terminated-tcp=443 --bg tcp://somehost:5432"),
wantErr: exactErrMsg(errHelp), wantErr: exactErrMsg(errHelp),
}) }},
add(step{ // bad target port, too low },
{
name: "tcp_port_too_low",
steps: []step{{
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://somehost:0"), command: cmd("serve --tls-terminated-tcp=443 --bg tcp://somehost:0"),
wantErr: exactErrMsg(errHelp), wantErr: exactErrMsg(errHelp),
}) }},
add(step{ // bad target port, too high },
{
name: "tcp_port_too_high",
steps: []step{{
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://somehost:65536"), command: cmd("serve --tls-terminated-tcp=443 --bg tcp://somehost:65536"),
wantErr: exactErrMsg(errHelp), wantErr: exactErrMsg(errHelp),
}) }},
add(step{ // support shorthand },
{
name: "tcp_shorthand",
steps: []step{{
command: cmd("serve --tls-terminated-tcp=443 --bg 5432"), command: cmd("serve --tls-terminated-tcp=443 --bg 5432"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{ TCP: map[uint16]*ipn.TCPPortHandler{
@ -390,9 +423,12 @@ func TestServeDevConfigMutations(t *testing.T) {
}, },
}, },
}, },
}) }},
add(step{reset: true}) },
add(step{ {
name: "tls_terminated_tcp",
steps: []step{
{
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://localhost:5432"), command: cmd("serve --tls-terminated-tcp=443 --bg tcp://localhost:5432"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{ TCP: map[uint16]*ipn.TCPPortHandler{
@ -402,8 +438,8 @@ func TestServeDevConfigMutations(t *testing.T) {
}, },
}, },
}, },
}) },
add(step{ {
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://127.0.0.1:8443"), command: cmd("serve --tls-terminated-tcp=443 --bg tcp://127.0.0.1:8443"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{ TCP: map[uint16]*ipn.TCPPortHandler{
@ -413,31 +449,13 @@ func TestServeDevConfigMutations(t *testing.T) {
}, },
}, },
}, },
})
add(step{
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://localhost:8444"),
want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{
443: {
TCPForward: "127.0.0.1:8444",
TerminateTLS: "foo.test.ts.net",
}, },
}, },
}, },
}) {
add(step{ name: "tcp_off",
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://127.0.0.1:8445"), steps: []step{
want: &ipn.ServeConfig{ {
TCP: map[uint16]*ipn.TCPPortHandler{
443: {
TCPForward: "127.0.0.1:8445",
TerminateTLS: "foo.test.ts.net",
},
},
},
})
add(step{reset: true})
add(step{
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://localhost:123"), command: cmd("serve --tls-terminated-tcp=443 --bg tcp://localhost:123"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{ TCP: map[uint16]*ipn.TCPPortHandler{
@ -447,19 +465,20 @@ func TestServeDevConfigMutations(t *testing.T) {
}, },
}, },
}, },
}) },
add(step{ // handler doesn't exist, so we get an error { // handler doesn't exist
command: cmd("serve --tls-terminated-tcp=8443 off"), command: cmd("serve --tls-terminated-tcp=8443 off"),
wantErr: anyErr(), wantErr: anyErr(),
}) },
add(step{ {
command: cmd("serve --tls-terminated-tcp=443 off"), command: cmd("serve --tls-terminated-tcp=443 off"),
want: &ipn.ServeConfig{}, want: &ipn.ServeConfig{},
}) },
},
// // text },
add(step{reset: true}) {
add(step{ name: "text",
steps: []step{{
command: cmd("serve --https=443 --bg text:hello"), command: cmd("serve --https=443 --bg text:hello"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
@ -469,19 +488,12 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) }},
},
// path {
td := t.TempDir() name: "path",
writeFile := func(suffix, contents string) { steps: []step{
if err := os.WriteFile(filepath.Join(td, suffix), []byte(contents), 0600); err != nil { {
t.Fatal(err)
}
}
add(step{reset: true})
writeFile("foo", "this is foo")
add(step{
command: cmd("serve --https=443 --bg " + filepath.Join(td, "foo")), command: cmd("serve --https=443 --bg " + filepath.Join(td, "foo")),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
@ -491,11 +503,9 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
os.MkdirAll(filepath.Join(td, "subdir"), 0700) {
writeFile("subdir/file-a", "this is A") command: cmd("serve --bg --https=443 --set-path=/some/where " + filepath.Join(td, "subdir/file-a")),
add(step{
command: cmd("serve --https=443 --bg --set-path=/some/where " + filepath.Join(td, "subdir/file-a")),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{ Web: map[ipn.HostPort]*ipn.WebServerConfig{
@ -505,14 +515,21 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
add(step{ // bad path },
command: cmd("serve --https=443 --bg bad/path"), },
{
name: "bad_path",
steps: []step{{
command: cmd("serve --bg --https=443 bad/path"),
wantErr: exactErrMsg(errHelp), wantErr: exactErrMsg(errHelp),
}) }},
add(step{reset: true}) },
add(step{ {
command: cmd("serve --https=443 --bg " + filepath.Join(td, "subdir")), name: "path_off",
steps: []step{
{
command: cmd("serve --bg --https=443 " + filepath.Join(td, "subdir")),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{ Web: map[ipn.HostPort]*ipn.WebServerConfig{
@ -521,15 +538,17 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
add(step{ {
command: cmd("serve --https=443 off"), command: cmd("serve --bg --https=443 off"),
want: &ipn.ServeConfig{}, want: &ipn.ServeConfig{},
}) },
},
// // combos },
add(step{reset: true}) {
add(step{ name: "combos",
steps: []step{
{
command: cmd("serve --bg localhost:3000"), command: cmd("serve --bg localhost:3000"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
@ -539,8 +558,8 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
add(step{ // enable funnel for primary port { // enable funnel for primary port
command: cmd("funnel --bg localhost:3000"), command: cmd("funnel --bg localhost:3000"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:443": true}, AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:443": true},
@ -551,9 +570,9 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
add(step{ // serving on secondary port doesn't change funnel on primary port { // serving on secondary port doesn't change funnel on primary port
command: cmd("serve --https=8443 --bg --set-path=/bar localhost:3001"), command: cmd("serve --bg --https=8443 --set-path=/bar localhost:3001"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:443": true}, AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:443": true},
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 8443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 8443: {HTTPS: true}},
@ -566,9 +585,9 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
add(step{ // turn funnel on for secondary port { // turn funnel on for secondary port
command: cmd("funnel --https=8443 --bg --set-path=/bar localhost:3001"), command: cmd("funnel --bg --https=8443 --set-path=/bar localhost:3001"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:443": true, "foo.test.ts.net:8443": true}, AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:443": true, "foo.test.ts.net:8443": true},
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 8443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 8443: {HTTPS: true}},
@ -581,8 +600,8 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
add(step{ // turn funnel off for primary port 443 { // turn funnel off for primary port 443
command: cmd("serve --bg localhost:3000"), command: cmd("serve --bg localhost:3000"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:8443": true}, AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:8443": true},
@ -596,9 +615,9 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
add(step{ // remove secondary port { // remove secondary port
command: cmd("serve --https=8443 --set-path=/bar off"), command: cmd("serve --bg --https=8443 --set-path=/bar off"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{ Web: map[ipn.HostPort]*ipn.WebServerConfig{
@ -607,8 +626,8 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
add(step{ // start a tcp forwarder on 8443 { // start a tcp forwarder on 8443
command: cmd("serve --bg --tcp=8443 tcp://localhost:5432"), command: cmd("serve --bg --tcp=8443 tcp://localhost:5432"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 8443: {TCPForward: "127.0.0.1:5432"}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}, 8443: {TCPForward: "127.0.0.1:5432"}},
@ -618,22 +637,24 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
add(step{ // remove primary port http handler { // remove primary port http handler
command: cmd("serve off"), command: cmd("serve off"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{8443: {TCPForward: "127.0.0.1:5432"}}, TCP: map[uint16]*ipn.TCPPortHandler{8443: {TCPForward: "127.0.0.1:5432"}},
}, },
}) },
add(step{ // remove tcp forwarder { // remove tcp forwarder
command: cmd("serve --tls-terminated-tcp=8443 off"), command: cmd("serve --tls-terminated-tcp=8443 off"),
want: &ipn.ServeConfig{}, want: &ipn.ServeConfig{},
}) },
},
// tricky steps },
add(step{reset: true}) {
add(step{ // a directory with a trailing slash mount point name: "tricky_steps",
command: cmd("serve --https=443 --bg --set-path=/dir " + filepath.Join(td, "subdir")), steps: []step{
{ // a directory with a trailing slash mount point
command: cmd("serve --bg --https=443 --set-path=/dir " + filepath.Join(td, "subdir")),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{ Web: map[ipn.HostPort]*ipn.WebServerConfig{
@ -642,9 +663,9 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
add(step{ // this should overwrite the previous one { // this should overwrite the previous one
command: cmd("serve --https=443 --bg --set-path=/dir " + filepath.Join(td, "foo")), command: cmd("serve --bg --https=443 --set-path=/dir " + filepath.Join(td, "foo")),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{ Web: map[ipn.HostPort]*ipn.WebServerConfig{
@ -653,10 +674,13 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
add(step{reset: true}) // reset and do the opposite { // reset and do opposite
add(step{ // a file without a trailing slash mount point command: cmd("serve reset"),
command: cmd("serve --https=443 --bg --set-path=/dir " + filepath.Join(td, "foo")), want: &ipn.ServeConfig{},
},
{ // a file without a trailing slash mount point
command: cmd("serve --bg --https=443 --set-path=/dir " + filepath.Join(td, "foo")),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{ Web: map[ipn.HostPort]*ipn.WebServerConfig{
@ -665,9 +689,9 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
add(step{ // this should overwrite the previous one { // this should overwrite the previous one
command: cmd("serve --https=443 --bg --set-path=/dir " + filepath.Join(td, "subdir")), command: cmd("serve --bg --https=443 --set-path=/dir " + filepath.Join(td, "subdir")),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
Web: map[ipn.HostPort]*ipn.WebServerConfig{ Web: map[ipn.HostPort]*ipn.WebServerConfig{
@ -676,11 +700,13 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
},
// // error states },
add(step{reset: true}) {
add(step{ // tcp forward 5432 on serve port 443 name: "cannot_override_tcp_with_http",
steps: []step{
{ // tcp forward 5432 on serve port 443
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://localhost:5432"), command: cmd("serve --tls-terminated-tcp=443 --bg tcp://localhost:5432"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{ TCP: map[uint16]*ipn.TCPPortHandler{
@ -690,13 +716,17 @@ func TestServeDevConfigMutations(t *testing.T) {
}, },
}, },
}, },
}) },
add(step{ // try to start a web handler on the same port {
command: cmd("serve --https=443 --bg localhost:3000"), command: cmd("serve --https=443 --bg localhost:3000"),
wantErr: anyErr(), wantErr: anyErr(),
}) },
add(step{reset: true}) },
add(step{ // start a web handler on port 443 },
{
name: "cannot_override_http_with_tcp",
steps: []step{
{
command: cmd("serve --https=443 --bg localhost:3000"), command: cmd("serve --https=443 --bg localhost:3000"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
@ -706,19 +736,17 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
add(step{ // try to start a tcp forwarder on the same serve port { // try to start a tcp forwarder on the same serve port
command: cmd("serve --tls-terminated-tcp=443 --bg tcp://localhost:5432"), command: cmd("serve --tls-terminated-tcp=443 --bg tcp://localhost:5432"),
wantErr: anyErr(), wantErr: anyErr(),
}) },
},
add(step{ },
command: cmd("serve reset"), {
want: &ipn.ServeConfig{}, name: "turn_off_multiple_handlers",
}) steps: []step{
{
// start two handlers and turn them off in one command
add(step{
command: cmd("serve --https=4545 --set-path=/foo --bg localhost:3000"), command: cmd("serve --https=4545 --set-path=/foo --bg localhost:3000"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{4545: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{4545: {HTTPS: true}},
@ -728,8 +756,8 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
add(step{ {
command: cmd("serve --https=4545 --set-path=/bar --bg localhost:3000"), command: cmd("serve --https=4545 --set-path=/bar --bg localhost:3000"),
want: &ipn.ServeConfig{ want: &ipn.ServeConfig{
TCP: map[uint16]*ipn.TCPPortHandler{4545: {HTTPS: true}}, TCP: map[uint16]*ipn.TCPPortHandler{4545: {HTTPS: true}},
@ -740,33 +768,27 @@ func TestServeDevConfigMutations(t *testing.T) {
}}, }},
}, },
}, },
}) },
add(step{ {
command: cmd("serve --https=4545 --bg --yes localhost:3000 off"), command: cmd("serve --https=4545 --bg --yes localhost:3000 off"),
want: &ipn.ServeConfig{}, want: &ipn.ServeConfig{},
}) },
},
},
}
for _, group := range groups {
t.Run(group.name, func(t *testing.T) {
lc := &fakeLocalServeClient{} lc := &fakeLocalServeClient{}
// And now run the steps above. for i, st := range group.steps {
for i, st := range steps { var stderr bytes.Buffer
if st.debugBreak != nil {
st.debugBreak()
}
if st.reset {
t.Logf("Executing step #%d, line %v: [reset]", i, st.line)
lc.config = nil
}
if st.command == nil {
continue
}
t.Logf("Executing step #%d, line %v: %q ... ", i, st.line, st.command)
var stdout bytes.Buffer var stdout bytes.Buffer
var flagOut bytes.Buffer var flagOut bytes.Buffer
e := &serveEnv{ e := &serveEnv{
lc: lc, lc: lc,
testFlagOut: &flagOut, testFlagOut: &flagOut,
testStdout: &stdout, testStdout: &stdout,
testStderr: &stderr,
} }
lastCount := lc.setCount lastCount := lc.setCount
var cmd *ffcli.Command var cmd *ffcli.Command
@ -785,30 +807,30 @@ func TestServeDevConfigMutations(t *testing.T) {
} }
if err != nil { if err != nil {
if st.wantErr == nil { if st.wantErr == nil {
t.Fatalf("step #%d, line %v: unexpected error: %v", i, st.line, err) t.Fatalf("step #%d: unexpected error: %v", i, err)
} }
if bad := st.wantErr(err); bad != "" { if bad := st.wantErr(err); bad != "" {
t.Fatalf("step #%d, line %v: unexpected error: %v", i, st.line, bad) t.Fatalf("step #%d: unexpected error: %v", i, bad)
} }
continue continue
} }
if st.wantErr != nil { if st.wantErr != nil {
t.Fatalf("step #%d, line %v: got success (saved=%v), but wanted an error", i, st.line, lc.config != nil) t.Fatalf("step #%d: got success (saved=%v), but wanted an error", i, lc.config != nil)
} }
var got *ipn.ServeConfig = nil var got *ipn.ServeConfig = nil
if lc.setCount > lastCount { if lc.setCount > lastCount {
got = lc.config got = lc.config
} }
if !reflect.DeepEqual(got, st.want) { if !reflect.DeepEqual(got, st.want) {
t.Fatalf("[%d] %v: bad state. got:\n%v\n\nwant:\n%v\n", gotbts, _ := json.MarshalIndent(got, "", "\t")
i, st.command, logger.AsJSON(got), logger.AsJSON(st.want)) wantbts, _ := json.MarshalIndent(st.want, "", "\t")
// NOTE: asJSON will omit empty fields, which might make t.Fatalf("step: %d, cmd: %v, diff:\n%s", i, st.command, cmp.Diff(string(gotbts), string(wantbts)))
// result in bad state got/want diffs being the same, even
// though the actual state is different. Use below to debug:
// t.Fatalf("[%d] %v: bad state. got:\n%+v\n\nwant:\n%+v\n",
// i, st.command, got, st.want)
} }
} }
})
}
} }
func TestValidateConfig(t *testing.T) { func TestValidateConfig(t *testing.T) {