tstest/integration: add more debugging, logs to catch flaky test

Updates #11962

Change-Id: I1ab0db69bdf8d1d535aa2cef434c586311f0fe18
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
pull/12024/head
Brad Fitzpatrick 7 months ago committed by Brad Fitzpatrick
parent caa3d7594f
commit e26f76a1c4

@ -398,6 +398,9 @@ func (c *Direct) TryLogout(ctx context.Context) error {
} }
func (c *Direct) TryLogin(ctx context.Context, t *tailcfg.Oauth2Token, flags LoginFlags) (url string, err error) { func (c *Direct) TryLogin(ctx context.Context, t *tailcfg.Oauth2Token, flags LoginFlags) (url string, err error) {
if strings.Contains(c.serverURL, "controlplane.tailscale.com") && envknob.Bool("TS_PANIC_IF_HIT_MAIN_CONTROL") {
panic("[unexpected] controlclient: TryLogin called on " + c.serverURL)
}
c.logf("[v1] direct.TryLogin(token=%v, flags=%v)", t != nil, flags) c.logf("[v1] direct.TryLogin(token=%v, flags=%v)", t != nil, flags)
return c.doLoginOrRegen(ctx, loginOpt{Token: t, Flags: flags}) return c.doLoginOrRegen(ctx, loginOpt{Token: t, Flags: flags})
} }

@ -357,6 +357,32 @@ func TestTwoNodes(t *testing.T) {
n2SocksAddrCh := n2.socks5AddrChan() n2SocksAddrCh := n2.socks5AddrChan()
d2 := n2.StartDaemon() d2 := n2.StartDaemon()
// Drop some logs to disk on test failure.
//
// TODO(bradfitz): make all nodes for all tests do this? give each node a
// unique integer within the test? But for now only do this test because
// this is what we often saw flaking.
t.Cleanup(func() {
if !t.Failed() {
return
}
n1.mu.Lock()
n2.mu.Lock()
defer n1.mu.Unlock()
defer n2.mu.Unlock()
rxNoDates := regexp.MustCompile(`(?m)^\d{4}.\d{2}.\d{2}.\d{2}:\d{2}:\d{2}`)
cleanLog := func(n *testNode) []byte {
b := n.tailscaledParser.allBuf.Bytes()
b = rxNoDates.ReplaceAll(b, nil)
return b
}
t.Logf("writing tailscaled logs to n1.log and n2.log")
os.WriteFile("n1.log", cleanLog(n1), 0666)
os.WriteFile("n2.log", cleanLog(n2), 0666)
})
n1Socks := n1.AwaitSocksAddr(n1SocksAddrCh) n1Socks := n1.AwaitSocksAddr(n1SocksAddrCh)
n2Socks := n1.AwaitSocksAddr(n2SocksAddrCh) n2Socks := n1.AwaitSocksAddr(n2SocksAddrCh)
t.Logf("node1 SOCKS5 addr: %v", n1Socks) t.Logf("node1 SOCKS5 addr: %v", n1Socks)
@ -1047,7 +1073,8 @@ func newTestEnv(t testing.TB, opts ...testEnvOpt) *testEnv {
// Currently, the test is simplistic and user==node==machine. // Currently, the test is simplistic and user==node==machine.
// That may grow complexity later to test more. // That may grow complexity later to test more.
type testNode struct { type testNode struct {
env *testEnv env *testEnv
tailscaledParser *nodeOutputParser
dir string // temp dir for sock & state dir string // temp dir for sock & state
configFile string // or empty for none configFile string // or empty for none
@ -1178,19 +1205,25 @@ func (n *testNode) AwaitSocksAddr(ch <-chan string) string {
// per-line callbacks previously registered via // per-line callbacks previously registered via
// testNode.addLogLineHook. // testNode.addLogLineHook.
type nodeOutputParser struct { type nodeOutputParser struct {
buf bytes.Buffer allBuf bytes.Buffer
n *testNode pendLineBuf bytes.Buffer
n *testNode
} }
func (op *nodeOutputParser) Write(p []byte) (n int, err error) { func (op *nodeOutputParser) Write(p []byte) (n int, err error) {
n, err = op.buf.Write(p) tn := op.n
op.parseLines() tn.mu.Lock()
defer tn.mu.Unlock()
op.allBuf.Write(p)
n, err = op.pendLineBuf.Write(p)
op.parseLinesLocked()
return return
} }
func (op *nodeOutputParser) parseLines() { func (op *nodeOutputParser) parseLinesLocked() {
n := op.n n := op.n
buf := op.buf.Bytes() buf := op.pendLineBuf.Bytes()
for len(buf) > 0 { for len(buf) > 0 {
nl := bytes.IndexByte(buf, '\n') nl := bytes.IndexByte(buf, '\n')
if nl == -1 { if nl == -1 {
@ -1199,16 +1232,14 @@ func (op *nodeOutputParser) parseLines() {
line := buf[:nl+1] line := buf[:nl+1]
buf = buf[nl+1:] buf = buf[nl+1:]
n.mu.Lock()
for _, f := range n.onLogLine { for _, f := range n.onLogLine {
f(line) f(line)
} }
n.mu.Unlock()
} }
if len(buf) == 0 { if len(buf) == 0 {
op.buf.Reset() op.pendLineBuf.Reset()
} else { } else {
io.CopyN(io.Discard, &op.buf, int64(op.buf.Len()-len(buf))) io.CopyN(io.Discard, &op.pendLineBuf, int64(op.pendLineBuf.Len()-len(buf)))
} }
} }
@ -1263,11 +1294,13 @@ func (n *testNode) StartDaemonAsIPNGOOS(ipnGOOS string) *Daemon {
"TS_ASSUME_NETWORK_UP_FOR_TEST=1", // don't pause control client in airplane mode (no wifi, etc) "TS_ASSUME_NETWORK_UP_FOR_TEST=1", // don't pause control client in airplane mode (no wifi, etc)
"TS_PANIC_IF_HIT_MAIN_CONTROL=1", "TS_PANIC_IF_HIT_MAIN_CONTROL=1",
"TS_DISABLE_PORTMAPPER=1", // shouldn't be needed; test is all localhost "TS_DISABLE_PORTMAPPER=1", // shouldn't be needed; test is all localhost
"TS_DEBUG_LOG_RATE=all",
) )
if version.IsRace() { if version.IsRace() {
cmd.Env = append(cmd.Env, "GORACE=halt_on_error=1") cmd.Env = append(cmd.Env, "GORACE=halt_on_error=1")
} }
cmd.Stderr = &nodeOutputParser{n: n} n.tailscaledParser = &nodeOutputParser{n: n}
cmd.Stderr = n.tailscaledParser
if *verboseTailscaled { if *verboseTailscaled {
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = io.MultiWriter(cmd.Stderr, os.Stderr) cmd.Stderr = io.MultiWriter(cmd.Stderr, os.Stderr)

Loading…
Cancel
Save