diff --git a/README.md b/README.md index 570de5d5..b716dd75 100644 --- a/README.md +++ b/README.md @@ -261,7 +261,7 @@ All Go syntax (including `cgo`) is already supported. Here are some examples: ### Defer -LLGo `defer` does not support usage in loops. This is not a bug but a feature, because we think that using `defer` in a loop is a very unrecommended practice. +LLGo now supports `defer` within loops, matching Go's semantics of executing defers in LIFO order for every iteration. The usual caveat from Go still applies: be mindful of loop-heavy defer usage because it allocates per iteration. ### Garbage Collection (GC) diff --git a/_demo/go/export/libexport.h.want b/_demo/go/export/libexport.h.want index d652c758..75fe8570 100644 --- a/_demo/go/export/libexport.h.want +++ b/_demo/go/export/libexport.h.want @@ -300,6 +300,9 @@ github_com_goplus_llgo_runtime_internal_clite_pthread_sync_init(void) GO_SYMBOL_ void github_com_goplus_llgo_runtime_internal_clite_signal_init(void) GO_SYMBOL_RENAME("github.com/goplus/llgo/runtime/internal/clite/signal.init") +void +github_com_goplus_llgo_runtime_internal_clite_tls_init(void) GO_SYMBOL_RENAME("github.com/goplus/llgo/runtime/internal/clite/tls.init") + void github_com_goplus_llgo_runtime_internal_runtime_goarch_init(void) GO_SYMBOL_RENAME("github.com/goplus/llgo/runtime/internal/runtime/goarch.init") diff --git a/cl/_testgo/defercomplex/in.go b/cl/_testgo/defercomplex/in.go new file mode 100644 index 00000000..63413926 --- /dev/null +++ b/cl/_testgo/defercomplex/in.go @@ -0,0 +1,51 @@ +package main + +func main() { + for _, label := range complexOrder() { + println(label) + } +} + +func complexOrder() (res []string) { + record := func(label string) { res = append(res, label) } + + defer record(label1("cleanup-final", 0)) + defer record(label1("cleanup-before-loop", 0)) + + for i := 0; i < 2; i++ { + defer record(label1("exit-outer", i)) + for j := 0; j < 2; j++ { + if j == 0 { + defer record(label2("branch-even", i, j)) + } else { + defer record(label2("branch-odd", i, j)) + } + for k := 0; k < 2; k++ { + nested := label3("nested", i, j, k) + defer record(nested) + if k == 1 { + defer record(label3("nested-tail", i, j, k)) + } + } + } + } + + defer record(label1("post-loop", 0)) + return +} + +func label1(prefix string, a int) string { + return prefix + "-" + digit(a) +} + +func label2(prefix string, a, b int) string { + return prefix + "-" + digit(a) + "-" + digit(b) +} + +func label3(prefix string, a, b, c int) string { + return prefix + "-" + digit(a) + "-" + digit(b) + "-" + digit(c) +} + +func digit(n int) string { + return string(rune('0' + n)) +} diff --git a/cl/_testgo/defercomplex/out.ll b/cl/_testgo/defercomplex/out.ll new file mode 100644 index 00000000..1c8a0e79 --- /dev/null +++ b/cl/_testgo/defercomplex/out.ll @@ -0,0 +1 @@ +; \ No newline at end of file diff --git a/cl/_testgo/deferloop/in.go b/cl/_testgo/deferloop/in.go new file mode 100644 index 00000000..b26aa970 --- /dev/null +++ b/cl/_testgo/deferloop/in.go @@ -0,0 +1,7 @@ +package main + +func main() { + for i := 0; i < 3; i++ { + defer println("loop", i) + } +} diff --git a/cl/_testgo/deferloop/out.ll b/cl/_testgo/deferloop/out.ll new file mode 100644 index 00000000..1c8a0e79 --- /dev/null +++ b/cl/_testgo/deferloop/out.ll @@ -0,0 +1 @@ +; \ No newline at end of file diff --git a/cl/compile.go b/cl/compile.go index 9521ff14..082b34b5 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -98,18 +98,19 @@ type pkgInfo struct { type none = struct{} type context struct { - prog llssa.Program - pkg llssa.Package - fn llssa.Function - fset *token.FileSet - goProg *ssa.Program - goTyps *types.Package - goPkg *ssa.Package - pyMod string - skips map[string]none - loaded map[*types.Package]*pkgInfo // loaded packages - bvals map[ssa.Value]llssa.Expr // block values - vargs map[*ssa.Alloc][]llssa.Expr // varargs + prog llssa.Program + pkg llssa.Package + fn llssa.Function + fset *token.FileSet + goProg *ssa.Program + goTyps *types.Package + goPkg *ssa.Package + pyMod string + skips map[string]none + loaded map[*types.Package]*pkgInfo // loaded packages + bvals map[ssa.Value]llssa.Expr // block values + vargs map[*ssa.Alloc][]llssa.Expr // varargs + paramDIVars map[*types.Var]llssa.DIVar patches Patches blkInfos []blocks.Info @@ -263,6 +264,8 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun if f.Recover != nil { // set recover block fn.SetRecover(fn.Block(f.Recover.Index)) } + dbgEnabled := enableDbg && (f == nil || f.Origin() == nil) + dbgSymsEnabled := enableDbgSyms && (f == nil || f.Origin() == nil) p.inits = append(p.inits, func() { p.fn = fn p.state = state // restore pkgState when compiling funcBody @@ -270,6 +273,11 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun p.fn = nil }() p.phis = nil + if dbgSymsEnabled { + p.paramDIVars = make(map[*types.Var]llssa.DIVar) + } else { + p.paramDIVars = nil + } if debugGoSSA { f.WriteTo(os.Stderr) } @@ -277,7 +285,7 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun log.Println("==> FuncBody", name) } b := fn.NewBuilder() - if enableDbg { + if dbgEnabled { pos := p.goProg.Fset.Position(f.Pos()) bodyPos := p.getFuncBodyPos(f) b.DebugFunction(fn, pos, bodyPos) @@ -371,6 +379,9 @@ func (p *context) debugParams(b llssa.Builder, f *ssa.Function) { ty := param.Type() argNo := i + 1 div := b.DIVarParam(p.fn, pos, param.Name(), p.type_(ty, llssa.InGo), argNo) + if p.paramDIVars != nil { + p.paramDIVars[variable] = div + } b.DIParam(variable, v, div, p.fn, pos, p.fn.Block(0)) } } @@ -388,7 +399,7 @@ func (p *context) compileBlock(b llssa.Builder, block *ssa.BasicBlock, n int, do b.Printf("call " + fn.Name() + "\n\x00") } // place here to avoid wrong current-block - if enableDbgSyms && block.Index == 0 { + if enableDbgSyms && block.Parent().Origin() == nil && block.Index == 0 { p.debugParams(b, block.Parent()) } if doModInit { @@ -783,7 +794,7 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) { p.compileInstrOrValue(b, iv, false) return } - if enableDbg { + if enableDbg && instr.Parent().Origin() == nil { scope := p.getDebugLocScope(instr.Parent(), instr.Pos()) if scope != nil { diScope := b.DIScope(p.fn, scope) @@ -846,7 +857,7 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) { x := p.compileValue(b, v.X) b.Send(ch, x) case *ssa.DebugRef: - if enableDbgSyms { + if enableDbgSyms && v.Parent().Origin() == nil { p.debugRef(b, v) } default: @@ -855,14 +866,13 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) { } func (p *context) getLocalVariable(b llssa.Builder, fn *ssa.Function, v *types.Var) llssa.DIVar { - pos := p.fset.Position(v.Pos()) - t := p.type_(v.Type(), llssa.InGo) - for i, param := range fn.Params { - if param.Object().(*types.Var) == v { - argNo := i + 1 - return b.DIVarParam(p.fn, pos, v.Name(), t, argNo) + if p.paramDIVars != nil { + if div, ok := p.paramDIVars[v]; ok { + return div } } + pos := p.fset.Position(v.Pos()) + t := p.type_(v.Type(), llssa.InGo) scope := b.DIScope(p.fn, v.Parent()) return b.DIVarAuto(scope, pos, v.Name(), t) } diff --git a/doc/defer-tls-gc.md b/doc/defer-tls-gc.md new file mode 100644 index 00000000..459451da --- /dev/null +++ b/doc/defer-tls-gc.md @@ -0,0 +1,32 @@ +# Defer Loop GC Integration + +## Background + +`defer` chains are stored in a per-thread TLS slot so that unwind paths can locate the active `*runtime.Defer`. With the default allocator (`AllocU`) backed by Boehm GC (bdwgc), those TLS-resident pointers were invisible to the collector. In stress scenarios—e.g. `TestDeferLoopStress` with 1,000,000 defers—the collector reclaimed the defer nodes, leaving dangling pointers and causing crashes inside the deferred closures. + +Prior experiments (`test-defer-dont-free` branch) confirmed the crash disappeared when allocations bypassed GC (plain `malloc` without `free`), pointing to a root-registration gap rather than logical corruption. + +## Solution Overview + +1. **GC-aware TLS slot helper** *(from PR [#1347](https://github.com/goplus/llgo/pull/1347))* + - Added `runtime/internal/clite/tls`, which exposes `tls.Alloc` to create per-thread storage that is automatically registered as a Boehm GC root. + - `SetThreadDefer` delegates to this helper so every thread reuses the same GC-safe slot without bespoke plumbing. + - The package handles TLS key creation, root registration/removal, and invokes an optional destructor when a thread exits. + +2. **SSA codegen synchronization** + - `ssa/eh.go` now calls `runtime.SetThreadDefer` whenever it updates the TLS pointer (on first allocation and when restoring the previous link during unwind). + - Defer argument nodes and the `runtime.Defer` struct itself are allocated with `aggregateAllocU`, ensuring new memory comes from GC-managed heaps, and nodes are released via `runtime.FreeDeferNode`. + +3. **Non-GC builds** + - The `tls` helper falls back to a malloc-backed TLS slot without GC registration, while `FreeDeferNode` continues to release nodes via `c.Free` when building with `-tags nogc`. + +## Testing + +Run the stress and regression suites to validate the integration: + +```sh +./llgo.sh test ./test -run TestDeferLoopStress +./llgo.sh test ./test +``` + +The updated `TestDeferLoopStress` now asserts 1,000,000 loop defers execute without failure, catching regressions in GC root tracking. diff --git a/internal/crosscompile/compile/libc/libc_test.go b/internal/crosscompile/compile/libc/libc_test.go index b866f1dd..e46e2ccc 100644 --- a/internal/crosscompile/compile/libc/libc_test.go +++ b/internal/crosscompile/compile/libc/libc_test.go @@ -1,3 +1,5 @@ +//go:build !llgo + package libc import ( diff --git a/runtime/_overlay/go/parser/resolver.go b/runtime/_overlay/go/parser/resolver.go deleted file mode 100644 index cba6ee65..00000000 --- a/runtime/_overlay/go/parser/resolver.go +++ /dev/null @@ -1,622 +0,0 @@ -// Copyright 2021 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. - -package parser - -import ( - "fmt" - "go/ast" - "go/token" - "strings" -) - -const debugResolve = false - -// resolveFile walks the given file to resolve identifiers within the file -// scope, updating ast.Ident.Obj fields with declaration information. -// -// If declErr is non-nil, it is used to report declaration errors during -// resolution. tok is used to format position in error messages. -func resolveFile(file *ast.File, handle *token.File, declErr func(token.Pos, string)) { - pkgScope := ast.NewScope(nil) - r := &resolver{ - handle: handle, - declErr: declErr, - topScope: pkgScope, - pkgScope: pkgScope, - depth: 1, - } - - for _, decl := range file.Decls { - ast.Walk(r, decl) - } - - r.closeScope() - assert(r.topScope == nil, "unbalanced scopes") - assert(r.labelScope == nil, "unbalanced label scopes") - - // resolve global identifiers within the same file - i := 0 - for _, ident := range r.unresolved { - // i <= index for current ident - assert(ident.Obj == unresolved, "object already resolved") - ident.Obj = r.pkgScope.Lookup(ident.Name) // also removes unresolved sentinel - if ident.Obj == nil { - r.unresolved[i] = ident - i++ - } else if debugResolve { - pos := ident.Obj.Decl.(interface{ Pos() token.Pos }).Pos() - r.trace("resolved %s@%v to package object %v", ident.Name, ident.Pos(), pos) - } - } - file.Scope = r.pkgScope - file.Unresolved = r.unresolved[0:i] -} - -const maxScopeDepth int = 1e3 - -type resolver struct { - handle *token.File - declErr func(token.Pos, string) - - // Ordinary identifier scopes - pkgScope *ast.Scope // pkgScope.Outer == nil - topScope *ast.Scope // top-most scope; may be pkgScope - unresolved []*ast.Ident // unresolved identifiers - depth int // scope depth - - // Label scopes - // (maintained by open/close LabelScope) - labelScope *ast.Scope // label scope for current function - targetStack [][]*ast.Ident // stack of unresolved labels -} - -func (r *resolver) trace(format string, args ...any) { - fmt.Println(strings.Repeat(". ", r.depth) + r.sprintf(format, args...)) -} - -func (r *resolver) sprintf(format string, args ...any) string { - for i, arg := range args { - switch arg := arg.(type) { - case token.Pos: - args[i] = r.handle.Position(arg) - } - } - return fmt.Sprintf(format, args...) -} - -func (r *resolver) openScope(pos token.Pos) { - r.depth++ - if r.depth > maxScopeDepth { - panic(bailout{pos: pos, msg: "exceeded max scope depth during object resolution"}) - } - if debugResolve { - r.trace("opening scope @%v", pos) - } - r.topScope = ast.NewScope(r.topScope) -} - -func (r *resolver) closeScope() { - r.depth-- - if debugResolve { - r.trace("closing scope") - } - r.topScope = r.topScope.Outer -} - -func (r *resolver) openLabelScope() { - r.labelScope = ast.NewScope(r.labelScope) - r.targetStack = append(r.targetStack, nil) -} - -func (r *resolver) closeLabelScope() { - // resolve labels - n := len(r.targetStack) - 1 - scope := r.labelScope - for _, ident := range r.targetStack[n] { - ident.Obj = scope.Lookup(ident.Name) - if ident.Obj == nil && r.declErr != nil { - r.declErr(ident.Pos(), fmt.Sprintf("label %s undefined", ident.Name)) - } - } - // pop label scope - r.targetStack = r.targetStack[0:n] - r.labelScope = r.labelScope.Outer -} - -func (r *resolver) declare(decl, data any, scope *ast.Scope, kind ast.ObjKind, idents ...*ast.Ident) { - for _, ident := range idents { - if ident.Obj != nil { - panic(fmt.Sprintf("%v: identifier %s already declared or resolved", ident.Pos(), ident.Name)) - } - obj := ast.NewObj(kind, ident.Name) - // remember the corresponding declaration for redeclaration - // errors and global variable resolution/typechecking phase - obj.Decl = decl - obj.Data = data - // Identifiers (for receiver type parameters) are written to the scope, but - // never set as the resolved object. See go.dev/issue/50956. - if _, ok := decl.(*ast.Ident); !ok { - ident.Obj = obj - } - if ident.Name != "_" { - if debugResolve { - r.trace("declaring %s@%v", ident.Name, ident.Pos()) - } - if alt := scope.Insert(obj); alt != nil && r.declErr != nil { - prevDecl := "" - if pos := alt.Pos(); pos.IsValid() { - prevDecl = r.sprintf("\n\tprevious declaration at %v", pos) - } - r.declErr(ident.Pos(), fmt.Sprintf("%s redeclared in this block%s", ident.Name, prevDecl)) - } - } - } -} - -func (r *resolver) shortVarDecl(decl *ast.AssignStmt) { - // Go spec: A short variable declaration may redeclare variables - // provided they were originally declared in the same block with - // the same type, and at least one of the non-blank variables is new. - n := 0 // number of new variables - for _, x := range decl.Lhs { - if ident, isIdent := x.(*ast.Ident); isIdent { - assert(ident.Obj == nil, "identifier already declared or resolved") - obj := ast.NewObj(ast.Var, ident.Name) - // remember corresponding assignment for other tools - obj.Decl = decl - ident.Obj = obj - if ident.Name != "_" { - if debugResolve { - r.trace("declaring %s@%v", ident.Name, ident.Pos()) - } - if alt := r.topScope.Insert(obj); alt != nil { - ident.Obj = alt // redeclaration - } else { - n++ // new declaration - } - } - } - } - if n == 0 && r.declErr != nil { - r.declErr(decl.Lhs[0].Pos(), "no new variables on left side of :=") - } -} - -// The unresolved object is a sentinel to mark identifiers that have been added -// to the list of unresolved identifiers. The sentinel is only used for verifying -// internal consistency. -var unresolved = new(ast.Object) - -// If x is an identifier, resolve attempts to resolve x by looking up -// the object it denotes. If no object is found and collectUnresolved is -// set, x is marked as unresolved and collected in the list of unresolved -// identifiers. -func (r *resolver) resolve(ident *ast.Ident, collectUnresolved bool) { - if ident.Obj != nil { - panic(r.sprintf("%v: identifier %s already declared or resolved", ident.Pos(), ident.Name)) - } - // '_' should never refer to existing declarations, because it has special - // handling in the spec. - if ident.Name == "_" { - return - } - for s := r.topScope; s != nil; s = s.Outer { - if obj := s.Lookup(ident.Name); obj != nil { - if debugResolve { - r.trace("resolved %v:%s to %v", ident.Pos(), ident.Name, obj) - } - assert(obj.Name != "", "obj with no name") - // Identifiers (for receiver type parameters) are written to the scope, - // but never set as the resolved object. See go.dev/issue/50956. - if _, ok := obj.Decl.(*ast.Ident); !ok { - ident.Obj = obj - } - return - } - } - // all local scopes are known, so any unresolved identifier - // must be found either in the file scope, package scope - // (perhaps in another file), or universe scope --- collect - // them so that they can be resolved later - if collectUnresolved { - ident.Obj = unresolved - r.unresolved = append(r.unresolved, ident) - } -} - -func (r *resolver) walkExprs(list []ast.Expr) { - for _, node := range list { - ast.Walk(r, node) - } -} - -func Unparen(e ast.Expr) ast.Expr { - for { - paren, ok := e.(*ast.ParenExpr) - if !ok { - return e - } - e = paren.X - } -} - -func (r *resolver) walkLHS(list []ast.Expr) { - for _, expr := range list { - expr := Unparen(expr) - if _, ok := expr.(*ast.Ident); !ok && expr != nil { - ast.Walk(r, expr) - } - } -} - -func (r *resolver) walkStmts(list []ast.Stmt) { - for _, stmt := range list { - ast.Walk(r, stmt) - } -} - -func (r *resolver) Visit(node ast.Node) ast.Visitor { - if debugResolve && node != nil { - r.trace("node %T@%v", node, node.Pos()) - } - - switch n := node.(type) { - - // Expressions. - case *ast.Ident: - r.resolve(n, true) - - case *ast.FuncLit: - r.openScope(n.Pos()) - defer r.closeScope() - r.walkFuncType(n.Type) - r.walkBody(n.Body) - - case *ast.SelectorExpr: - ast.Walk(r, n.X) - // Note: don't try to resolve n.Sel, as we don't support qualified - // resolution. - - case *ast.StructType: - r.openScope(n.Pos()) - defer r.closeScope() - r.walkFieldList(n.Fields, ast.Var) - - case *ast.FuncType: - r.openScope(n.Pos()) - defer r.closeScope() - r.walkFuncType(n) - - case *ast.CompositeLit: - if n.Type != nil { - ast.Walk(r, n.Type) - } - for _, e := range n.Elts { - if kv, _ := e.(*ast.KeyValueExpr); kv != nil { - // See go.dev/issue/45160: try to resolve composite lit keys, but don't - // collect them as unresolved if resolution failed. This replicates - // existing behavior when resolving during parsing. - if ident, _ := kv.Key.(*ast.Ident); ident != nil { - r.resolve(ident, false) - } else { - ast.Walk(r, kv.Key) - } - ast.Walk(r, kv.Value) - } else { - ast.Walk(r, e) - } - } - - case *ast.InterfaceType: - r.openScope(n.Pos()) - defer r.closeScope() - r.walkFieldList(n.Methods, ast.Fun) - - // Statements - case *ast.LabeledStmt: - r.declare(n, nil, r.labelScope, ast.Lbl, n.Label) - ast.Walk(r, n.Stmt) - - case *ast.AssignStmt: - r.walkExprs(n.Rhs) - if n.Tok == token.DEFINE { - r.shortVarDecl(n) - } else { - r.walkExprs(n.Lhs) - } - - case *ast.BranchStmt: - // add to list of unresolved targets - if n.Tok != token.FALLTHROUGH && n.Label != nil { - depth := len(r.targetStack) - 1 - r.targetStack[depth] = append(r.targetStack[depth], n.Label) - } - - case *ast.BlockStmt: - r.openScope(n.Pos()) - defer r.closeScope() - r.walkStmts(n.List) - - case *ast.IfStmt: - r.openScope(n.Pos()) - defer r.closeScope() - if n.Init != nil { - ast.Walk(r, n.Init) - } - ast.Walk(r, n.Cond) - ast.Walk(r, n.Body) - if n.Else != nil { - ast.Walk(r, n.Else) - } - - case *ast.CaseClause: - r.walkExprs(n.List) - r.openScope(n.Pos()) - defer r.closeScope() - r.walkStmts(n.Body) - - case *ast.SwitchStmt: - r.openScope(n.Pos()) - defer r.closeScope() - if n.Init != nil { - ast.Walk(r, n.Init) - } - if n.Tag != nil { - // The scope below reproduces some unnecessary behavior of the parser, - // opening an extra scope in case this is a type switch. It's not needed - // for expression switches. - // TODO: remove this once we've matched the parser resolution exactly. - if n.Init != nil { - r.openScope(n.Tag.Pos()) - defer r.closeScope() - } - ast.Walk(r, n.Tag) - } - if n.Body != nil { - r.walkStmts(n.Body.List) - } - - case *ast.TypeSwitchStmt: - if n.Init != nil { - r.openScope(n.Pos()) - defer r.closeScope() - ast.Walk(r, n.Init) - } - r.openScope(n.Assign.Pos()) - defer r.closeScope() - ast.Walk(r, n.Assign) - // s.Body consists only of case clauses, so does not get its own - // scope. - if n.Body != nil { - r.walkStmts(n.Body.List) - } - - case *ast.CommClause: - r.openScope(n.Pos()) - defer r.closeScope() - if n.Comm != nil { - ast.Walk(r, n.Comm) - } - r.walkStmts(n.Body) - - case *ast.SelectStmt: - // as for switch statements, select statement bodies don't get their own - // scope. - if n.Body != nil { - r.walkStmts(n.Body.List) - } - - case *ast.ForStmt: - r.openScope(n.Pos()) - defer r.closeScope() - if n.Init != nil { - ast.Walk(r, n.Init) - } - if n.Cond != nil { - ast.Walk(r, n.Cond) - } - if n.Post != nil { - ast.Walk(r, n.Post) - } - ast.Walk(r, n.Body) - - case *ast.RangeStmt: - r.openScope(n.Pos()) - defer r.closeScope() - ast.Walk(r, n.X) - var lhs []ast.Expr - if n.Key != nil { - lhs = append(lhs, n.Key) - } - if n.Value != nil { - lhs = append(lhs, n.Value) - } - if len(lhs) > 0 { - if n.Tok == token.DEFINE { - // Note: we can't exactly match the behavior of object resolution - // during the parsing pass here, as it uses the position of the RANGE - // token for the RHS OpPos. That information is not contained within - // the AST. - as := &ast.AssignStmt{ - Lhs: lhs, - Tok: token.DEFINE, - TokPos: n.TokPos, - Rhs: []ast.Expr{&ast.UnaryExpr{Op: token.RANGE, X: n.X}}, - } - // TODO(rFindley): this walkLHS reproduced the parser resolution, but - // is it necessary? By comparison, for a normal AssignStmt we don't - // walk the LHS in case there is an invalid identifier list. - r.walkLHS(lhs) - r.shortVarDecl(as) - } else { - r.walkExprs(lhs) - } - } - ast.Walk(r, n.Body) - - // Declarations - case *ast.GenDecl: - switch n.Tok { - case token.CONST, token.VAR: - for i, spec := range n.Specs { - spec := spec.(*ast.ValueSpec) - kind := ast.Con - if n.Tok == token.VAR { - kind = ast.Var - } - r.walkExprs(spec.Values) - if spec.Type != nil { - ast.Walk(r, spec.Type) - } - r.declare(spec, i, r.topScope, kind, spec.Names...) - } - case token.TYPE: - for _, spec := range n.Specs { - spec := spec.(*ast.TypeSpec) - // Go spec: The scope of a type identifier declared inside a function begins - // at the identifier in the TypeSpec and ends at the end of the innermost - // containing block. - r.declare(spec, nil, r.topScope, ast.Typ, spec.Name) - if spec.TypeParams != nil { - r.openScope(spec.Pos()) - r.walkTParams(spec.TypeParams) - r.closeScope() - } - ast.Walk(r, spec.Type) - } - } - - case *ast.FuncDecl: - // Open the function scope. - r.openScope(n.Pos()) - defer r.closeScope() - - r.walkRecv(n.Recv) - - // Type parameters are walked normally: they can reference each other, and - // can be referenced by normal parameters. - if n.Type.TypeParams != nil { - r.walkTParams(n.Type.TypeParams) - // TODO(rFindley): need to address receiver type parameters. - } - - // Resolve and declare parameters in a specific order to get duplicate - // declaration errors in the correct location. - r.resolveList(n.Type.Params) - r.resolveList(n.Type.Results) - r.declareList(n.Recv, ast.Var) - r.declareList(n.Type.Params, ast.Var) - r.declareList(n.Type.Results, ast.Var) - - r.walkBody(n.Body) - if n.Recv == nil && n.Name.Name != "init" { - r.declare(n, nil, r.pkgScope, ast.Fun, n.Name) - } - - default: - return r - } - - return nil -} - -func (r *resolver) walkFuncType(typ *ast.FuncType) { - // typ.TypeParams must be walked separately for FuncDecls. - r.resolveList(typ.Params) - r.resolveList(typ.Results) - r.declareList(typ.Params, ast.Var) - r.declareList(typ.Results, ast.Var) -} - -func (r *resolver) resolveList(list *ast.FieldList) { - if list == nil { - return - } - for _, f := range list.List { - if f.Type != nil { - ast.Walk(r, f.Type) - } - } -} - -func (r *resolver) declareList(list *ast.FieldList, kind ast.ObjKind) { - if list == nil { - return - } - for _, f := range list.List { - r.declare(f, nil, r.topScope, kind, f.Names...) - } -} - -func (r *resolver) walkRecv(recv *ast.FieldList) { - // If our receiver has receiver type parameters, we must declare them before - // trying to resolve the rest of the receiver, and avoid re-resolving the - // type parameter identifiers. - if recv == nil || len(recv.List) == 0 { - return // nothing to do - } - typ := recv.List[0].Type - if ptr, ok := typ.(*ast.StarExpr); ok { - typ = ptr.X - } - - var declareExprs []ast.Expr // exprs to declare - var resolveExprs []ast.Expr // exprs to resolve - switch typ := typ.(type) { - case *ast.IndexExpr: - declareExprs = []ast.Expr{typ.Index} - resolveExprs = append(resolveExprs, typ.X) - case *ast.IndexListExpr: - declareExprs = typ.Indices - resolveExprs = append(resolveExprs, typ.X) - default: - resolveExprs = append(resolveExprs, typ) - } - for _, expr := range declareExprs { - if id, _ := expr.(*ast.Ident); id != nil { - r.declare(expr, nil, r.topScope, ast.Typ, id) - } else { - // The receiver type parameter expression is invalid, but try to resolve - // it anyway for consistency. - resolveExprs = append(resolveExprs, expr) - } - } - for _, expr := range resolveExprs { - if expr != nil { - ast.Walk(r, expr) - } - } - // The receiver is invalid, but try to resolve it anyway for consistency. - for _, f := range recv.List[1:] { - if f.Type != nil { - ast.Walk(r, f.Type) - } - } -} - -func (r *resolver) walkFieldList(list *ast.FieldList, kind ast.ObjKind) { - if list == nil { - return - } - r.resolveList(list) - r.declareList(list, kind) -} - -// walkTParams is like walkFieldList, but declares type parameters eagerly so -// that they may be resolved in the constraint expressions held in the field -// Type. -func (r *resolver) walkTParams(list *ast.FieldList) { - r.declareList(list, ast.Typ) - r.resolveList(list) -} - -func (r *resolver) walkBody(body *ast.BlockStmt) { - if body == nil { - return - } - r.openLabelScope() - defer r.closeLabelScope() - r.walkStmts(body.List) -} diff --git a/runtime/_overlay/testing/testing.go b/runtime/_overlay/testing/testing.go deleted file mode 100644 index 7603f836..00000000 --- a/runtime/_overlay/testing/testing.go +++ /dev/null @@ -1 +0,0 @@ -package testing diff --git a/runtime/_overlay/testing/testing_go123.go b/runtime/_overlay/testing/testing_go123.go deleted file mode 100644 index 6d9eefc2..00000000 --- a/runtime/_overlay/testing/testing_go123.go +++ /dev/null @@ -1,2425 +0,0 @@ -//go:build !go1.24 -// +build !go1.24 - -// Copyright 2009 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. - -// Package testing provides support for automated testing of Go packages. -// It is intended to be used in concert with the "go test" command, which automates -// execution of any function of the form -// -// func TestXxx(*testing.T) -// -// where Xxx does not start with a lowercase letter. The function name -// serves to identify the test routine. -// -// Within these functions, use the Error, Fail or related methods to signal failure. -// -// To write a new test suite, create a file that -// contains the TestXxx functions as described here, -// and give that file a name ending in "_test.go". -// The file will be excluded from regular -// package builds but will be included when the "go test" command is run. -// -// The test file can be in the same package as the one being tested, -// or in a corresponding package with the suffix "_test". -// -// If the test file is in the same package, it may refer to unexported -// identifiers within the package, as in this example: -// -// package abs -// -// import "testing" -// -// func TestAbs(t *testing.T) { -// got := Abs(-1) -// if got != 1 { -// t.Errorf("Abs(-1) = %d; want 1", got) -// } -// } -// -// If the file is in a separate "_test" package, the package being tested -// must be imported explicitly and only its exported identifiers may be used. -// This is known as "black box" testing. -// -// package abs_test -// -// import ( -// "testing" -// -// "path_to_pkg/abs" -// ) -// -// func TestAbs(t *testing.T) { -// got := abs.Abs(-1) -// if got != 1 { -// t.Errorf("Abs(-1) = %d; want 1", got) -// } -// } -// -// For more detail, run "go help test" and "go help testflag". -// -// # Benchmarks -// -// Functions of the form -// -// func BenchmarkXxx(*testing.B) -// -// are considered benchmarks, and are executed by the "go test" command when -// its -bench flag is provided. Benchmarks are run sequentially. -// -// For a description of the testing flags, see -// https://golang.org/cmd/go/#hdr-Testing_flags. -// -// A sample benchmark function looks like this: -// -// func BenchmarkRandInt(b *testing.B) { -// for range b.N { -// rand.Int() -// } -// } -// -// The benchmark function must run the target code b.N times. -// It is called multiple times with b.N adjusted until the -// benchmark function lasts long enough to be timed reliably. -// The output -// -// BenchmarkRandInt-8 68453040 17.8 ns/op -// -// means that the loop ran 68453040 times at a speed of 17.8 ns per loop. -// -// If a benchmark needs some expensive setup before running, the timer -// may be reset: -// -// func BenchmarkBigLen(b *testing.B) { -// big := NewBig() -// b.ResetTimer() -// for range b.N { -// big.Len() -// } -// } -// -// If a benchmark needs to test performance in a parallel setting, it may use -// the RunParallel helper function; such benchmarks are intended to be used with -// the go test -cpu flag: -// -// func BenchmarkTemplateParallel(b *testing.B) { -// templ := template.Must(template.New("test").Parse("Hello, {{.}}!")) -// b.RunParallel(func(pb *testing.PB) { -// var buf bytes.Buffer -// for pb.Next() { -// buf.Reset() -// templ.Execute(&buf, "World") -// } -// }) -// } -// -// A detailed specification of the benchmark results format is given -// in https://golang.org/design/14313-benchmark-format. -// -// There are standard tools for working with benchmark results at -// https://golang.org/x/perf/cmd. -// In particular, https://golang.org/x/perf/cmd/benchstat performs -// statistically robust A/B comparisons. -// -// # Examples -// -// The package also runs and verifies example code. Example functions may -// include a concluding line comment that begins with "Output:" and is compared with -// the standard output of the function when the tests are run. (The comparison -// ignores leading and trailing space.) These are examples of an example: -// -// func ExampleHello() { -// fmt.Println("hello") -// // Output: hello -// } -// -// func ExampleSalutations() { -// fmt.Println("hello, and") -// fmt.Println("goodbye") -// // Output: -// // hello, and -// // goodbye -// } -// -// The comment prefix "Unordered output:" is like "Output:", but matches any -// line order: -// -// func ExamplePerm() { -// for _, value := range Perm(5) { -// fmt.Println(value) -// } -// // Unordered output: 4 -// // 2 -// // 1 -// // 3 -// // 0 -// } -// -// Example functions without output comments are compiled but not executed. -// -// The naming convention to declare examples for the package, a function F, a type T and -// method M on type T are: -// -// func Example() { ... } -// func ExampleF() { ... } -// func ExampleT() { ... } -// func ExampleT_M() { ... } -// -// Multiple example functions for a package/type/function/method may be provided by -// appending a distinct suffix to the name. The suffix must start with a -// lower-case letter. -// -// func Example_suffix() { ... } -// func ExampleF_suffix() { ... } -// func ExampleT_suffix() { ... } -// func ExampleT_M_suffix() { ... } -// -// The entire test file is presented as the example when it contains a single -// example function, at least one other function, type, variable, or constant -// declaration, and no test or benchmark functions. -// -// # Fuzzing -// -// 'go test' and the testing package support fuzzing, a testing technique where -// a function is called with randomly generated inputs to find bugs not -// anticipated by unit tests. -// -// Functions of the form -// -// func FuzzXxx(*testing.F) -// -// are considered fuzz tests. -// -// For example: -// -// func FuzzHex(f *testing.F) { -// for _, seed := range [][]byte{{}, {0}, {9}, {0xa}, {0xf}, {1, 2, 3, 4}} { -// f.Add(seed) -// } -// f.Fuzz(func(t *testing.T, in []byte) { -// enc := hex.EncodeToString(in) -// out, err := hex.DecodeString(enc) -// if err != nil { -// t.Fatalf("%v: decode: %v", in, err) -// } -// if !bytes.Equal(in, out) { -// t.Fatalf("%v: not equal after round trip: %v", in, out) -// } -// }) -// } -// -// A fuzz test maintains a seed corpus, or a set of inputs which are run by -// default, and can seed input generation. Seed inputs may be registered by -// calling (*F).Add or by storing files in the directory testdata/fuzz/ -// (where is the name of the fuzz test) within the package containing -// the fuzz test. Seed inputs are optional, but the fuzzing engine may find -// bugs more efficiently when provided with a set of small seed inputs with good -// code coverage. These seed inputs can also serve as regression tests for bugs -// identified through fuzzing. -// -// The function passed to (*F).Fuzz within the fuzz test is considered the fuzz -// target. A fuzz target must accept a *T parameter, followed by one or more -// parameters for random inputs. The types of arguments passed to (*F).Add must -// be identical to the types of these parameters. The fuzz target may signal -// that it's found a problem the same way tests do: by calling T.Fail (or any -// method that calls it like T.Error or T.Fatal) or by panicking. -// -// When fuzzing is enabled (by setting the -fuzz flag to a regular expression -// that matches a specific fuzz test), the fuzz target is called with arguments -// generated by repeatedly making random changes to the seed inputs. On -// supported platforms, 'go test' compiles the test executable with fuzzing -// coverage instrumentation. The fuzzing engine uses that instrumentation to -// find and cache inputs that expand coverage, increasing the likelihood of -// finding bugs. If the fuzz target fails for a given input, the fuzzing engine -// writes the inputs that caused the failure to a file in the directory -// testdata/fuzz/ within the package directory. This file later serves as -// a seed input. If the file can't be written at that location (for example, -// because the directory is read-only), the fuzzing engine writes the file to -// the fuzz cache directory within the build cache instead. -// -// When fuzzing is disabled, the fuzz target is called with the seed inputs -// registered with F.Add and seed inputs from testdata/fuzz/. In this -// mode, the fuzz test acts much like a regular test, with subtests started -// with F.Fuzz instead of T.Run. -// -// See https://go.dev/doc/fuzz for documentation about fuzzing. -// -// # Skipping -// -// Tests or benchmarks may be skipped at run time with a call to -// the Skip method of *T or *B: -// -// func TestTimeConsuming(t *testing.T) { -// if testing.Short() { -// t.Skip("skipping test in short mode.") -// } -// ... -// } -// -// The Skip method of *T can be used in a fuzz target if the input is invalid, -// but should not be considered a failing input. For example: -// -// func FuzzJSONMarshaling(f *testing.F) { -// f.Fuzz(func(t *testing.T, b []byte) { -// var v interface{} -// if err := json.Unmarshal(b, &v); err != nil { -// t.Skip() -// } -// if _, err := json.Marshal(v); err != nil { -// t.Errorf("Marshal: %v", err) -// } -// }) -// } -// -// # Subtests and Sub-benchmarks -// -// The Run methods of T and B allow defining subtests and sub-benchmarks, -// without having to define separate functions for each. This enables uses -// like table-driven benchmarks and creating hierarchical tests. -// It also provides a way to share common setup and tear-down code: -// -// func TestFoo(t *testing.T) { -// // -// t.Run("A=1", func(t *testing.T) { ... }) -// t.Run("A=2", func(t *testing.T) { ... }) -// t.Run("B=1", func(t *testing.T) { ... }) -// // -// } -// -// Each subtest and sub-benchmark has a unique name: the combination of the name -// of the top-level test and the sequence of names passed to Run, separated by -// slashes, with an optional trailing sequence number for disambiguation. -// -// The argument to the -run, -bench, and -fuzz command-line flags is an unanchored regular -// expression that matches the test's name. For tests with multiple slash-separated -// elements, such as subtests, the argument is itself slash-separated, with -// expressions matching each name element in turn. Because it is unanchored, an -// empty expression matches any string. -// For example, using "matching" to mean "whose name contains": -// -// go test -run '' # Run all tests. -// go test -run Foo # Run top-level tests matching "Foo", such as "TestFooBar". -// go test -run Foo/A= # For top-level tests matching "Foo", run subtests matching "A=". -// go test -run /A=1 # For all top-level tests, run subtests matching "A=1". -// go test -fuzz FuzzFoo # Fuzz the target matching "FuzzFoo" -// -// The -run argument can also be used to run a specific value in the seed -// corpus, for debugging. For example: -// -// go test -run=FuzzFoo/9ddb952d9814 -// -// The -fuzz and -run flags can both be set, in order to fuzz a target but -// skip the execution of all other tests. -// -// Subtests can also be used to control parallelism. A parent test will only -// complete once all of its subtests complete. In this example, all tests are -// run in parallel with each other, and only with each other, regardless of -// other top-level tests that may be defined: -// -// func TestGroupedParallel(t *testing.T) { -// for _, tc := range tests { -// tc := tc // capture range variable -// t.Run(tc.Name, func(t *testing.T) { -// t.Parallel() -// ... -// }) -// } -// } -// -// Run does not return until parallel subtests have completed, providing a way -// to clean up after a group of parallel tests: -// -// func TestTeardownParallel(t *testing.T) { -// // This Run will not return until the parallel tests finish. -// t.Run("group", func(t *testing.T) { -// t.Run("Test1", parallelTest1) -// t.Run("Test2", parallelTest2) -// t.Run("Test3", parallelTest3) -// }) -// // -// } -// -// # Main -// -// It is sometimes necessary for a test or benchmark program to do extra setup or teardown -// before or after it executes. It is also sometimes necessary to control -// which code runs on the main thread. To support these and other cases, -// if a test file contains a function: -// -// func TestMain(m *testing.M) -// -// then the generated test will call TestMain(m) instead of running the tests or benchmarks -// directly. TestMain runs in the main goroutine and can do whatever setup -// and teardown is necessary around a call to m.Run. m.Run will return an exit -// code that may be passed to os.Exit. If TestMain returns, the test wrapper -// will pass the result of m.Run to os.Exit itself. -// -// When TestMain is called, flag.Parse has not been run. If TestMain depends on -// command-line flags, including those of the testing package, it should call -// flag.Parse explicitly. Command line flags are always parsed by the time test -// or benchmark functions run. -// -// A simple implementation of TestMain is: -// -// func TestMain(m *testing.M) { -// // call flag.Parse() here if TestMain uses flags -// m.Run() -// } -// -// TestMain is a low-level primitive and should not be necessary for casual -// testing needs, where ordinary test functions suffice. -package testing - -import ( - "bytes" - "errors" - "flag" - "fmt" - "internal/goexperiment" - "internal/race" - "io" - "math/rand" - "os" - "reflect" - "runtime" - "runtime/debug" - "runtime/trace" - "slices" - "strconv" - "strings" - "sync" - "sync/atomic" - "time" - "unicode" - "unicode/utf8" -) - -var initRan bool - -// Init registers testing flags. These flags are automatically registered by -// the "go test" command before running test functions, so Init is only needed -// when calling functions such as Benchmark without using "go test". -// -// Init is not safe to call concurrently. It has no effect if it was already called. -func Init() { - if initRan { - return - } - initRan = true - // The short flag requests that tests run more quickly, but its functionality - // is provided by test writers themselves. The testing package is just its - // home. The all.bash installation script sets it to make installation more - // efficient, but by default the flag is off so a plain "go test" will do a - // full test of the package. - short = flag.Bool("test.short", false, "run smaller test suite to save time") - - // The failfast flag requests that test execution stop after the first test failure. - failFast = flag.Bool("test.failfast", false, "do not start new tests after the first test failure") - - // The directory in which to create profile files and the like. When run from - // "go test", the binary always runs in the source directory for the package; - // this flag lets "go test" tell the binary to write the files in the directory where - // the "go test" command is run. - outputDir = flag.String("test.outputdir", "", "write profiles to `dir`") - // Report as tests are run; default is silent for success. - flag.Var(&chatty, "test.v", "verbose: print additional output") - count = flag.Uint("test.count", 1, "run tests and benchmarks `n` times") - coverProfile = flag.String("test.coverprofile", "", "write a coverage profile to `file`") - gocoverdir = flag.String("test.gocoverdir", "", "write coverage intermediate files to this directory") - matchList = flag.String("test.list", "", "list tests, examples, and benchmarks matching `regexp` then exit") - match = flag.String("test.run", "", "run only tests and examples matching `regexp`") - skip = flag.String("test.skip", "", "do not list or run tests matching `regexp`") - memProfile = flag.String("test.memprofile", "", "write an allocation profile to `file`") - memProfileRate = flag.Int("test.memprofilerate", 0, "set memory allocation profiling `rate` (see runtime.MemProfileRate)") - cpuProfile = flag.String("test.cpuprofile", "", "write a cpu profile to `file`") - blockProfile = flag.String("test.blockprofile", "", "write a goroutine blocking profile to `file`") - blockProfileRate = flag.Int("test.blockprofilerate", 1, "set blocking profile `rate` (see runtime.SetBlockProfileRate)") - mutexProfile = flag.String("test.mutexprofile", "", "write a mutex contention profile to the named file after execution") - mutexProfileFraction = flag.Int("test.mutexprofilefraction", 1, "if >= 0, calls runtime.SetMutexProfileFraction()") - panicOnExit0 = flag.Bool("test.paniconexit0", false, "panic on call to os.Exit(0)") - traceFile = flag.String("test.trace", "", "write an execution trace to `file`") - timeout = flag.Duration("test.timeout", 0, "panic test binary after duration `d` (default 0, timeout disabled)") - cpuListStr = flag.String("test.cpu", "", "comma-separated `list` of cpu counts to run each test with") - parallel = flag.Int("test.parallel", runtime.GOMAXPROCS(0), "run at most `n` tests in parallel") - testlog = flag.String("test.testlogfile", "", "write test action log to `file` (for use only by cmd/go)") - shuffle = flag.String("test.shuffle", "off", "randomize the execution order of tests and benchmarks") - fullPath = flag.Bool("test.fullpath", false, "show full file names in error messages") - - initBenchmarkFlags() - initFuzzFlags() -} - -var ( - // Flags, registered during Init. - short *bool - failFast *bool - outputDir *string - chatty chattyFlag - count *uint - coverProfile *string - gocoverdir *string - matchList *string - match *string - skip *string - memProfile *string - memProfileRate *int - cpuProfile *string - blockProfile *string - blockProfileRate *int - mutexProfile *string - mutexProfileFraction *int - panicOnExit0 *bool - traceFile *string - timeout *time.Duration - cpuListStr *string - parallel *int - shuffle *string - testlog *string - fullPath *bool - - haveExamples bool // are there examples? - - cpuList []int - testlogFile *os.File - - numFailed atomic.Uint32 // number of test failures - - running sync.Map // map[string]time.Time of running, unpaused tests -) - -type chattyFlag struct { - on bool // -v is set in some form - json bool // -v=test2json is set, to make output better for test2json -} - -func (*chattyFlag) IsBoolFlag() bool { return true } - -func (f *chattyFlag) Set(arg string) error { - switch arg { - default: - return fmt.Errorf("invalid flag -test.v=%s", arg) - case "true", "test2json": - f.on = true - f.json = arg == "test2json" - case "false": - f.on = false - f.json = false - } - return nil -} - -func (f *chattyFlag) String() string { - if f.json { - return "test2json" - } - if f.on { - return "true" - } - return "false" -} - -func (f *chattyFlag) Get() any { - if f.json { - return "test2json" - } - return f.on -} - -const marker = byte(0x16) // ^V for framing - -func (f *chattyFlag) prefix() string { - if f.json { - return string(marker) - } - return "" -} - -type chattyPrinter struct { - w io.Writer - lastNameMu sync.Mutex // guards lastName - lastName string // last printed test name in chatty mode - json bool // -v=json output mode -} - -func newChattyPrinter(w io.Writer) *chattyPrinter { - return &chattyPrinter{w: w, json: chatty.json} -} - -// prefix is like chatty.prefix but using p.json instead of chatty.json. -// Using p.json allows tests to check the json behavior without modifying -// the global variable. For convenience, we allow p == nil and treat -// that as not in json mode (because it's not chatty at all). -func (p *chattyPrinter) prefix() string { - if p != nil && p.json { - return string(marker) - } - return "" -} - -// Updatef prints a message about the status of the named test to w. -// -// The formatted message must include the test name itself. -func (p *chattyPrinter) Updatef(testName, format string, args ...any) { - p.lastNameMu.Lock() - defer p.lastNameMu.Unlock() - - // Since the message already implies an association with a specific new test, - // we don't need to check what the old test name was or log an extra NAME line - // for it. (We're updating it anyway, and the current message already includes - // the test name.) - p.lastName = testName - fmt.Fprintf(p.w, p.prefix()+format, args...) -} - -// Printf prints a message, generated by the named test, that does not -// necessarily mention that tests's name itself. -func (p *chattyPrinter) Printf(testName, format string, args ...any) { - p.lastNameMu.Lock() - defer p.lastNameMu.Unlock() - - if p.lastName == "" { - p.lastName = testName - } else if p.lastName != testName { - fmt.Fprintf(p.w, "%s=== NAME %s\n", p.prefix(), testName) - p.lastName = testName - } - - fmt.Fprintf(p.w, format, args...) -} - -// The maximum number of stack frames to go through when skipping helper functions for -// the purpose of decorating log messages. -const maxStackLen = 50 - -// common holds the elements common between T and B and -// captures common methods such as Errorf. -type common struct { - mu sync.RWMutex // guards this group of fields - output []byte // Output generated by test or benchmark. - w io.Writer // For flushToParent. - ran bool // Test or benchmark (or one of its subtests) was executed. - failed bool // Test or benchmark has failed. - skipped bool // Test or benchmark has been skipped. - done bool // Test is finished and all subtests have completed. - helperPCs map[uintptr]struct{} // functions to be skipped when writing file/line info - helperNames map[string]struct{} // helperPCs converted to function names - cleanups []func() // optional functions to be called at the end of the test - cleanupName string // Name of the cleanup function. - cleanupPc []uintptr // The stack trace at the point where Cleanup was called. - finished bool // Test function has completed. - inFuzzFn bool // Whether the fuzz target, if this is one, is running. - - chatty *chattyPrinter // A copy of chattyPrinter, if the chatty flag is set. - bench bool // Whether the current test is a benchmark. - hasSub atomic.Bool // whether there are sub-benchmarks. - cleanupStarted atomic.Bool // Registered cleanup callbacks have started to execute - runner string // Function name of tRunner running the test. - isParallel bool // Whether the test is parallel. - - parent *common - level int // Nesting depth of test or benchmark. - creator []uintptr // If level > 0, the stack trace at the point where the parent called t.Run. - name string // Name of test or benchmark. - start highPrecisionTime // Time test or benchmark started - duration time.Duration - barrier chan bool // To signal parallel subtests they may start. Nil when T.Parallel is not present (B) or not usable (when fuzzing). - signal chan bool // To signal a test is done. - sub []*T // Queue of subtests to be run in parallel. - - lastRaceErrors atomic.Int64 // Max value of race.Errors seen during the test or its subtests. - raceErrorLogged atomic.Bool - - tempDirMu sync.Mutex - tempDir string - tempDirErr error - tempDirSeq int32 -} - -// Short reports whether the -test.short flag is set. -func Short() bool { - if short == nil { - panic("testing: Short called before Init") - } - // Catch code that calls this from TestMain without first calling flag.Parse. - if !flag.Parsed() { - panic("testing: Short called before Parse") - } - - return *short -} - -// testBinary is set by cmd/go to "1" if this is a binary built by "go test". -// The value is set to "1" by a -X option to cmd/link. We assume that -// because this is possible, the compiler will not optimize testBinary -// into a constant on the basis that it is an unexported package-scope -// variable that is never changed. If the compiler ever starts implementing -// such an optimization, we will need some technique to mark this variable -// as "changed by a cmd/link -X option". -var testBinary = "0" - -// Testing reports whether the current code is being run in a test. -// This will report true in programs created by "go test", -// false in programs created by "go build". -func Testing() bool { - return testBinary == "1" -} - -// CoverMode reports what the test coverage mode is set to. The -// values are "set", "count", or "atomic". The return value will be -// empty if test coverage is not enabled. -func CoverMode() string { - if goexperiment.CoverageRedesign { - return cover2.mode - } - return cover.Mode -} - -// Verbose reports whether the -test.v flag is set. -func Verbose() bool { - // Same as in Short. - if !flag.Parsed() { - panic("testing: Verbose called before Parse") - } - return chatty.on -} - -func (c *common) checkFuzzFn(name string) { - if c.inFuzzFn { - panic(fmt.Sprintf("testing: f.%s was called inside the fuzz target, use t.%s instead", name, name)) - } -} - -// frameSkip searches, starting after skip frames, for the first caller frame -// in a function not marked as a helper and returns that frame. -// The search stops if it finds a tRunner function that -// was the entry point into the test and the test is not a subtest. -// This function must be called with c.mu held. -func (c *common) frameSkip(skip int) runtime.Frame { - // If the search continues into the parent test, we'll have to hold - // its mu temporarily. If we then return, we need to unlock it. - shouldUnlock := false - defer func() { - if shouldUnlock { - c.mu.Unlock() - } - }() - var pc [maxStackLen]uintptr - // Skip two extra frames to account for this function - // and runtime.Callers itself. - n := runtime.Callers(skip+2, pc[:]) - if n == 0 { - panic("testing: zero callers found") - } - frames := runtime.CallersFrames(pc[:n]) - var firstFrame, prevFrame, frame runtime.Frame - for more := true; more; prevFrame = frame { - frame, more = frames.Next() - if frame.Function == "runtime.gopanic" { - continue - } - if frame.Function == c.cleanupName { - frames = runtime.CallersFrames(c.cleanupPc) - continue - } - if firstFrame.PC == 0 { - firstFrame = frame - } - if frame.Function == c.runner { - // We've gone up all the way to the tRunner calling - // the test function (so the user must have - // called tb.Helper from inside that test function). - // If this is a top-level test, only skip up to the test function itself. - // If we're in a subtest, continue searching in the parent test, - // starting from the point of the call to Run which created this subtest. - if c.level > 1 { - frames = runtime.CallersFrames(c.creator) - parent := c.parent - // We're no longer looking at the current c after this point, - // so we should unlock its mu, unless it's the original receiver, - // in which case our caller doesn't expect us to do that. - if shouldUnlock { - c.mu.Unlock() - } - c = parent - // Remember to unlock c.mu when we no longer need it, either - // because we went up another nesting level, or because we - // returned. - shouldUnlock = true - c.mu.Lock() - continue - } - return prevFrame - } - // If more helper PCs have been added since we last did the conversion - if c.helperNames == nil { - c.helperNames = make(map[string]struct{}) - for pc := range c.helperPCs { - c.helperNames[pcToName(pc)] = struct{}{} - } - } - if _, ok := c.helperNames[frame.Function]; !ok { - // Found a frame that wasn't inside a helper function. - return frame - } - } - return firstFrame -} - -// decorate prefixes the string with the file and line of the call site -// and inserts the final newline if needed and indentation spaces for formatting. -// This function must be called with c.mu held. -func (c *common) decorate(s string, skip int) string { - frame := c.frameSkip(skip) - file := frame.File - line := frame.Line - if file != "" { - if *fullPath { - // If relative path, truncate file name at last file name separator. - } else if index := strings.LastIndexAny(file, `/\`); index >= 0 { - file = file[index+1:] - } - } else { - file = "???" - } - if line == 0 { - line = 1 - } - buf := new(strings.Builder) - // Every line is indented at least 4 spaces. - buf.WriteString(" ") - fmt.Fprintf(buf, "%s:%d: ", file, line) - lines := strings.Split(s, "\n") - if l := len(lines); l > 1 && lines[l-1] == "" { - lines = lines[:l-1] - } - for i, line := range lines { - if i > 0 { - // Second and subsequent lines are indented an additional 4 spaces. - buf.WriteString("\n ") - } - buf.WriteString(line) - } - buf.WriteByte('\n') - return buf.String() -} - -// flushToParent writes c.output to the parent after first writing the header -// with the given format and arguments. -func (c *common) flushToParent(testName, format string, args ...any) { - p := c.parent - p.mu.Lock() - defer p.mu.Unlock() - - c.mu.Lock() - defer c.mu.Unlock() - - if len(c.output) > 0 { - // Add the current c.output to the print, - // and then arrange for the print to replace c.output. - // (This displays the logged output after the --- FAIL line.) - format += "%s" - args = append(args[:len(args):len(args)], c.output) - c.output = c.output[:0] - } - - if c.chatty != nil && (p.w == c.chatty.w || c.chatty.json) { - // We're flushing to the actual output, so track that this output is - // associated with a specific test (and, specifically, that the next output - // is *not* associated with that test). - // - // Moreover, if c.output is non-empty it is important that this write be - // atomic with respect to the output of other tests, so that we don't end up - // with confusing '=== NAME' lines in the middle of our '--- PASS' block. - // Neither humans nor cmd/test2json can parse those easily. - // (See https://go.dev/issue/40771.) - // - // If test2json is used, we never flush to parent tests, - // so that the json stream shows subtests as they finish. - // (See https://go.dev/issue/29811.) - c.chatty.Updatef(testName, format, args...) - } else { - // We're flushing to the output buffer of the parent test, which will - // itself follow a test-name header when it is finally flushed to stdout. - fmt.Fprintf(p.w, c.chatty.prefix()+format, args...) - } -} - -type indenter struct { - c *common -} - -func (w indenter) Write(b []byte) (n int, err error) { - n = len(b) - for len(b) > 0 { - end := bytes.IndexByte(b, '\n') - if end == -1 { - end = len(b) - } else { - end++ - } - // An indent of 4 spaces will neatly align the dashes with the status - // indicator of the parent. - line := b[:end] - if line[0] == marker { - w.c.output = append(w.c.output, marker) - line = line[1:] - } - const indent = " " - w.c.output = append(w.c.output, indent...) - w.c.output = append(w.c.output, line...) - b = b[end:] - } - return -} - -// fmtDuration returns a string representing d in the form "87.00s". -func fmtDuration(d time.Duration) string { - return fmt.Sprintf("%.2fs", d.Seconds()) -} - -// TB is the interface common to T, B, and F. -type TB interface { - Cleanup(func()) - Error(args ...any) - Errorf(format string, args ...any) - Fail() - FailNow() - Failed() bool - Fatal(args ...any) - Fatalf(format string, args ...any) - Helper() - Log(args ...any) - Logf(format string, args ...any) - Name() string - Setenv(key, value string) - Skip(args ...any) - SkipNow() - Skipf(format string, args ...any) - Skipped() bool - TempDir() string - - // A private method to prevent users implementing the - // interface and so future additions to it will not - // violate Go 1 compatibility. - private() -} - -var _ TB = (*T)(nil) -var _ TB = (*B)(nil) - -// T is a type passed to Test functions to manage test state and support formatted test logs. -// -// A test ends when its Test function returns or calls any of the methods -// FailNow, Fatal, Fatalf, SkipNow, Skip, or Skipf. Those methods, as well as -// the Parallel method, must be called only from the goroutine running the -// Test function. -// -// The other reporting methods, such as the variations of Log and Error, -// may be called simultaneously from multiple goroutines. -type T struct { - common - isEnvSet bool - context *testContext // For running tests and subtests. -} - -func (c *common) private() {} - -// Name returns the name of the running (sub-) test or benchmark. -// -// The name will include the name of the test along with the names of -// any nested sub-tests. If two sibling sub-tests have the same name, -// Name will append a suffix to guarantee the returned name is unique. -func (c *common) Name() string { - return c.name -} - -func (c *common) setRan() { - if c.parent != nil { - c.parent.setRan() - } - c.mu.Lock() - defer c.mu.Unlock() - c.ran = true -} - -// Fail marks the function as having failed but continues execution. -func (c *common) Fail() { - if c.parent != nil { - c.parent.Fail() - } - c.mu.Lock() - defer c.mu.Unlock() - // c.done needs to be locked to synchronize checks to c.done in parent tests. - if c.done { - panic("Fail in goroutine after " + c.name + " has completed") - } - c.failed = true -} - -// Failed reports whether the function has failed. -func (c *common) Failed() bool { - c.mu.RLock() - defer c.mu.RUnlock() - - if !c.done && int64(race.Errors()) > c.lastRaceErrors.Load() { - c.mu.RUnlock() - c.checkRaces() - c.mu.RLock() - } - - return c.failed -} - -// FailNow marks the function as having failed and stops its execution -// by calling runtime.Goexit (which then runs all deferred calls in the -// current goroutine). -// Execution will continue at the next test or benchmark. -// FailNow must be called from the goroutine running the -// test or benchmark function, not from other goroutines -// created during the test. Calling FailNow does not stop -// those other goroutines. -func (c *common) FailNow() { - c.checkFuzzFn("FailNow") - c.Fail() - - // Calling runtime.Goexit will exit the goroutine, which - // will run the deferred functions in this goroutine, - // which will eventually run the deferred lines in tRunner, - // which will signal to the test loop that this test is done. - // - // A previous version of this code said: - // - // c.duration = ... - // c.signal <- c.self - // runtime.Goexit() - // - // This previous version duplicated code (those lines are in - // tRunner no matter what), but worse the goroutine teardown - // implicit in runtime.Goexit was not guaranteed to complete - // before the test exited. If a test deferred an important cleanup - // function (like removing temporary files), there was no guarantee - // it would run on a test failure. Because we send on c.signal during - // a top-of-stack deferred function now, we know that the send - // only happens after any other stacked defers have completed. - c.mu.Lock() - c.finished = true - c.mu.Unlock() - runtime.Goexit() -} - -// log generates the output. It's always at the same stack depth. -func (c *common) log(s string) { - c.logDepth(s, 3) // logDepth + log + public function -} - -// logDepth generates the output at an arbitrary stack depth. -func (c *common) logDepth(s string, depth int) { - c.mu.Lock() - defer c.mu.Unlock() - if c.done { - // This test has already finished. Try and log this message - // with our parent. If we don't have a parent, panic. - for parent := c.parent; parent != nil; parent = parent.parent { - r := func() bool { - parent.mu.Lock() - defer parent.mu.Unlock() - if !parent.done { - parent.output = append(parent.output, parent.decorate(s, depth+1)...) - return true - } - return false - }() - if r { - return - } - } - panic("Log in goroutine after " + c.name + " has completed: " + s) - } else { - if c.chatty != nil { - if c.bench { - // Benchmarks don't print === CONT, so we should skip the test - // printer and just print straight to stdout. - fmt.Print(c.decorate(s, depth+1)) - } else { - c.chatty.Printf(c.name, "%s", c.decorate(s, depth+1)) - } - - return - } - c.output = append(c.output, c.decorate(s, depth+1)...) - } -} - -// Log formats its arguments using default formatting, analogous to Println, -// and records the text in the error log. For tests, the text will be printed only if -// the test fails or the -test.v flag is set. For benchmarks, the text is always -// printed to avoid having performance depend on the value of the -test.v flag. -func (c *common) Log(args ...any) { - c.checkFuzzFn("Log") - c.log(fmt.Sprintln(args...)) -} - -// Logf formats its arguments according to the format, analogous to Printf, and -// records the text in the error log. A final newline is added if not provided. For -// tests, the text will be printed only if the test fails or the -test.v flag is -// set. For benchmarks, the text is always printed to avoid having performance -// depend on the value of the -test.v flag. -func (c *common) Logf(format string, args ...any) { - c.checkFuzzFn("Logf") - c.log(fmt.Sprintf(format, args...)) -} - -// Error is equivalent to Log followed by Fail. -func (c *common) Error(args ...any) { - c.checkFuzzFn("Error") - c.log(fmt.Sprintln(args...)) - c.Fail() -} - -// Errorf is equivalent to Logf followed by Fail. -func (c *common) Errorf(format string, args ...any) { - c.checkFuzzFn("Errorf") - c.log(fmt.Sprintf(format, args...)) - c.Fail() -} - -// Fatal is equivalent to Log followed by FailNow. -func (c *common) Fatal(args ...any) { - c.checkFuzzFn("Fatal") - c.log(fmt.Sprintln(args...)) - c.FailNow() -} - -// Fatalf is equivalent to Logf followed by FailNow. -func (c *common) Fatalf(format string, args ...any) { - c.checkFuzzFn("Fatalf") - c.log(fmt.Sprintf(format, args...)) - c.FailNow() -} - -// Skip is equivalent to Log followed by SkipNow. -func (c *common) Skip(args ...any) { - c.checkFuzzFn("Skip") - c.log(fmt.Sprintln(args...)) - c.SkipNow() -} - -// Skipf is equivalent to Logf followed by SkipNow. -func (c *common) Skipf(format string, args ...any) { - c.checkFuzzFn("Skipf") - c.log(fmt.Sprintf(format, args...)) - c.SkipNow() -} - -// SkipNow marks the test as having been skipped and stops its execution -// by calling [runtime.Goexit]. -// If a test fails (see Error, Errorf, Fail) and is then skipped, -// it is still considered to have failed. -// Execution will continue at the next test or benchmark. See also FailNow. -// SkipNow must be called from the goroutine running the test, not from -// other goroutines created during the test. Calling SkipNow does not stop -// those other goroutines. -func (c *common) SkipNow() { - c.checkFuzzFn("SkipNow") - c.mu.Lock() - c.skipped = true - c.finished = true - c.mu.Unlock() - runtime.Goexit() -} - -// Skipped reports whether the test was skipped. -func (c *common) Skipped() bool { - c.mu.RLock() - defer c.mu.RUnlock() - return c.skipped -} - -// Helper marks the calling function as a test helper function. -// When printing file and line information, that function will be skipped. -// Helper may be called simultaneously from multiple goroutines. -func (c *common) Helper() { - c.mu.Lock() - defer c.mu.Unlock() - if c.helperPCs == nil { - c.helperPCs = make(map[uintptr]struct{}) - } - // repeating code from callerName here to save walking a stack frame - var pc [1]uintptr - n := runtime.Callers(2, pc[:]) // skip runtime.Callers + Helper - if n == 0 { - panic("testing: zero callers found") - } - if _, found := c.helperPCs[pc[0]]; !found { - c.helperPCs[pc[0]] = struct{}{} - c.helperNames = nil // map will be recreated next time it is needed - } -} - -// Cleanup registers a function to be called when the test (or subtest) and all its -// subtests complete. Cleanup functions will be called in last added, -// first called order. -func (c *common) Cleanup(f func()) { - c.checkFuzzFn("Cleanup") - var pc [maxStackLen]uintptr - // Skip two extra frames to account for this function and runtime.Callers itself. - n := runtime.Callers(2, pc[:]) - cleanupPc := pc[:n] - - fn := func() { - defer func() { - c.mu.Lock() - defer c.mu.Unlock() - c.cleanupName = "" - c.cleanupPc = nil - }() - - name := callerName(0) - c.mu.Lock() - c.cleanupName = name - c.cleanupPc = cleanupPc - c.mu.Unlock() - - f() - } - - c.mu.Lock() - defer c.mu.Unlock() - c.cleanups = append(c.cleanups, fn) -} - -// TempDir returns a temporary directory for the test to use. -// The directory is automatically removed when the test and -// all its subtests complete. -// Each subsequent call to t.TempDir returns a unique directory; -// if the directory creation fails, TempDir terminates the test by calling Fatal. -func (c *common) TempDir() string { - c.checkFuzzFn("TempDir") - // Use a single parent directory for all the temporary directories - // created by a test, each numbered sequentially. - c.tempDirMu.Lock() - var nonExistent bool - if c.tempDir == "" { // Usually the case with js/wasm - nonExistent = true - } else { - _, err := os.Stat(c.tempDir) - nonExistent = os.IsNotExist(err) - if err != nil && !nonExistent { - c.Fatalf("TempDir: %v", err) - } - } - - if nonExistent { - c.Helper() - - // Drop unusual characters (such as path separators or - // characters interacting with globs) from the directory name to - // avoid surprising os.MkdirTemp behavior. - mapper := func(r rune) rune { - if r < utf8.RuneSelf { - const allowed = "!#$%&()+,-.=@^_{}~ " - if '0' <= r && r <= '9' || - 'a' <= r && r <= 'z' || - 'A' <= r && r <= 'Z' { - return r - } - if strings.ContainsRune(allowed, r) { - return r - } - } else if unicode.IsLetter(r) || unicode.IsNumber(r) { - return r - } - return -1 - } - pattern := strings.Map(mapper, c.Name()) - c.tempDir, c.tempDirErr = os.MkdirTemp("", pattern) - if c.tempDirErr == nil { - c.Cleanup(func() { - if err := removeAll(c.tempDir); err != nil { - c.Errorf("TempDir RemoveAll cleanup: %v", err) - } - }) - } - } - - if c.tempDirErr == nil { - c.tempDirSeq++ - } - seq := c.tempDirSeq - c.tempDirMu.Unlock() - - if c.tempDirErr != nil { - c.Fatalf("TempDir: %v", c.tempDirErr) - } - - dir := fmt.Sprintf("%s%c%03d", c.tempDir, os.PathSeparator, seq) - if err := os.Mkdir(dir, 0777); err != nil { - c.Fatalf("TempDir: %v", err) - } - return dir -} - -// removeAll is like os.RemoveAll, but retries Windows "Access is denied." -// errors up to an arbitrary timeout. -// -// Those errors have been known to occur spuriously on at least the -// windows-amd64-2012 builder (https://go.dev/issue/50051), and can only occur -// legitimately if the test leaves behind a temp file that either is still open -// or the test otherwise lacks permission to delete. In the case of legitimate -// failures, a failing test may take a bit longer to fail, but once the test is -// fixed the extra latency will go away. -func removeAll(path string) error { - const arbitraryTimeout = 2 * time.Second - var ( - start time.Time - nextSleep = 1 * time.Millisecond - ) - for { - err := os.RemoveAll(path) - if !isWindowsRetryable(err) { - return err - } - if start.IsZero() { - start = time.Now() - } else if d := time.Since(start) + nextSleep; d >= arbitraryTimeout { - return err - } - time.Sleep(nextSleep) - nextSleep += time.Duration(rand.Int63n(int64(nextSleep))) - } -} - -// Setenv calls os.Setenv(key, value) and uses Cleanup to -// restore the environment variable to its original value -// after the test. -// -// Because Setenv affects the whole process, it cannot be used -// in parallel tests or tests with parallel ancestors. -func (c *common) Setenv(key, value string) { - c.checkFuzzFn("Setenv") - prevValue, ok := os.LookupEnv(key) - - if err := os.Setenv(key, value); err != nil { - c.Fatalf("cannot set environment variable: %v", err) - } - - if ok { - c.Cleanup(func() { - os.Setenv(key, prevValue) - }) - } else { - c.Cleanup(func() { - os.Unsetenv(key) - }) - } -} - -// panicHandling controls the panic handling used by runCleanup. -type panicHandling int - -const ( - normalPanic panicHandling = iota - recoverAndReturnPanic -) - -// runCleanup is called at the end of the test. -// If ph is recoverAndReturnPanic, it will catch panics, and return the -// recovered value if any. -func (c *common) runCleanup(ph panicHandling) (panicVal any) { - c.cleanupStarted.Store(true) - defer c.cleanupStarted.Store(false) - - if ph == recoverAndReturnPanic { - defer func() { - panicVal = recover() - }() - } - - // Make sure that if a cleanup function panics, - // we still run the remaining cleanup functions. - defer func() { - c.mu.Lock() - recur := len(c.cleanups) > 0 - c.mu.Unlock() - if recur { - c.runCleanup(normalPanic) - } - }() - - for { - var cleanup func() - c.mu.Lock() - if len(c.cleanups) > 0 { - last := len(c.cleanups) - 1 - cleanup = c.cleanups[last] - c.cleanups = c.cleanups[:last] - } - c.mu.Unlock() - if cleanup == nil { - return nil - } - cleanup() - } -} - -// resetRaces updates c.parent's count of data race errors (or the global count, -// if c has no parent), and updates c.lastRaceErrors to match. -// -// Any races that occurred prior to this call to resetRaces will -// not be attributed to c. -func (c *common) resetRaces() { - if c.parent == nil { - c.lastRaceErrors.Store(int64(race.Errors())) - } else { - c.lastRaceErrors.Store(c.parent.checkRaces()) - } -} - -// checkRaces checks whether the global count of data race errors has increased -// since c's count was last reset. -// -// If so, it marks c as having failed due to those races (logging an error for -// the first such race), and updates the race counts for the parents of c so -// that if they are currently suspended (such as in a call to T.Run) they will -// not log separate errors for the race(s). -// -// Note that multiple tests may be marked as failed due to the same race if they -// are executing in parallel. -func (c *common) checkRaces() (raceErrors int64) { - raceErrors = int64(race.Errors()) - for { - last := c.lastRaceErrors.Load() - if raceErrors <= last { - // All races have already been reported. - return raceErrors - } - if c.lastRaceErrors.CompareAndSwap(last, raceErrors) { - break - } - } - - if c.raceErrorLogged.CompareAndSwap(false, true) { - // This is the first race we've encountered for this test. - // Mark the test as failed, and log the reason why only once. - // (Note that the race detector itself will still write a goroutine - // dump for any further races it detects.) - c.Errorf("race detected during execution of test") - } - - // Update the parent(s) of this test so that they don't re-report the race. - parent := c.parent - for parent != nil { - for { - last := parent.lastRaceErrors.Load() - if raceErrors <= last { - // This race was already reported by another (likely parallel) subtest. - return raceErrors - } - if parent.lastRaceErrors.CompareAndSwap(last, raceErrors) { - break - } - } - parent = parent.parent - } - - return raceErrors -} - -// callerName gives the function name (qualified with a package path) -// for the caller after skip frames (where 0 means the current function). -func callerName(skip int) string { - var pc [1]uintptr - n := runtime.Callers(skip+2, pc[:]) // skip + runtime.Callers + callerName - if n == 0 { - panic("testing: zero callers found") - } - return pcToName(pc[0]) -} - -func pcToName(pc uintptr) string { - pcs := []uintptr{pc} - frames := runtime.CallersFrames(pcs) - frame, _ := frames.Next() - return frame.Function -} - -// Parallel signals that this test is to be run in parallel with (and only with) -// other parallel tests. When a test is run multiple times due to use of -// -test.count or -test.cpu, multiple instances of a single test never run in -// parallel with each other. -func (t *T) Parallel() { - if t.isParallel { - panic("testing: t.Parallel called multiple times") - } - if t.isEnvSet { - panic("testing: t.Parallel called after t.Setenv; cannot set environment variables in parallel tests") - } - t.isParallel = true - if t.parent.barrier == nil { - // T.Parallel has no effect when fuzzing. - // Multiple processes may run in parallel, but only one input can run at a - // time per process so we can attribute crashes to specific inputs. - return - } - - // We don't want to include the time we spend waiting for serial tests - // in the test duration. Record the elapsed time thus far and reset the - // timer afterwards. - t.duration += highPrecisionTimeSince(t.start) - - // Add to the list of tests to be released by the parent. - t.parent.sub = append(t.parent.sub, t) - - // Report any races during execution of this test up to this point. - // - // We will assume that any races that occur between here and the point where - // we unblock are not caused by this subtest. That assumption usually holds, - // although it can be wrong if the test spawns a goroutine that races in the - // background while the rest of the test is blocked on the call to Parallel. - // If that happens, we will misattribute the background race to some other - // test, or to no test at all — but that false-negative is so unlikely that it - // is not worth adding race-report noise for the common case where the test is - // completely suspended during the call to Parallel. - t.checkRaces() - - if t.chatty != nil { - t.chatty.Updatef(t.name, "=== PAUSE %s\n", t.name) - } - running.Delete(t.name) - - t.signal <- true // Release calling test. - <-t.parent.barrier // Wait for the parent test to complete. - t.context.waitParallel() - - if t.chatty != nil { - t.chatty.Updatef(t.name, "=== CONT %s\n", t.name) - } - running.Store(t.name, highPrecisionTimeNow()) - t.start = highPrecisionTimeNow() - - // Reset the local race counter to ignore any races that happened while this - // goroutine was blocked, such as in the parent test or in other parallel - // subtests. - // - // (Note that we don't call parent.checkRaces here: - // if other parallel subtests have already introduced races, we want to - // let them report those races instead of attributing them to the parent.) - t.lastRaceErrors.Store(int64(race.Errors())) -} - -// Setenv calls os.Setenv(key, value) and uses Cleanup to -// restore the environment variable to its original value -// after the test. -// -// Because Setenv affects the whole process, it cannot be used -// in parallel tests or tests with parallel ancestors. -func (t *T) Setenv(key, value string) { - // Non-parallel subtests that have parallel ancestors may still - // run in parallel with other tests: they are only non-parallel - // with respect to the other subtests of the same parent. - // Since SetEnv affects the whole process, we need to disallow it - // if the current test or any parent is parallel. - isParallel := false - for c := &t.common; c != nil; c = c.parent { - if c.isParallel { - isParallel = true - break - } - } - if isParallel { - panic("testing: t.Setenv called after t.Parallel; cannot set environment variables in parallel tests") - } - - t.isEnvSet = true - - t.common.Setenv(key, value) -} - -// InternalTest is an internal type but exported because it is cross-package; -// it is part of the implementation of the "go test" command. -type InternalTest struct { - Name string - F func(*T) -} - -var errNilPanicOrGoexit = errors.New("test executed panic(nil) or runtime.Goexit") - -func tRunner(t *T, fn func(t *T)) { - t.runner = callerName(0) - - // When this goroutine is done, either because fn(t) - // returned normally or because a test failure triggered - // a call to runtime.Goexit, record the duration and send - // a signal saying that the test is done. - defer func() { - t.checkRaces() - - // TODO(#61034): This is the wrong place for this check. - if t.Failed() { - numFailed.Add(1) - } - - // Check if the test panicked or Goexited inappropriately. - // - // If this happens in a normal test, print output but continue panicking. - // tRunner is called in its own goroutine, so this terminates the process. - // - // If this happens while fuzzing, recover from the panic and treat it like a - // normal failure. It's important that the process keeps running in order to - // find short inputs that cause panics. - err := recover() - signal := true - - t.mu.RLock() - finished := t.finished - t.mu.RUnlock() - if !finished && err == nil { - err = errNilPanicOrGoexit - for p := t.parent; p != nil; p = p.parent { - p.mu.RLock() - finished = p.finished - p.mu.RUnlock() - if finished { - if !t.isParallel { - t.Errorf("%v: subtest may have called FailNow on a parent test", err) - err = nil - } - signal = false - break - } - } - } - - if err != nil && t.context.isFuzzing { - prefix := "panic: " - if err == errNilPanicOrGoexit { - prefix = "" - } - t.Errorf("%s%s\n%s\n", prefix, err, string(debug.Stack())) - t.mu.Lock() - t.finished = true - t.mu.Unlock() - err = nil - } - - // Use a deferred call to ensure that we report that the test is - // complete even if a cleanup function calls t.FailNow. See issue 41355. - didPanic := false - defer func() { - // Only report that the test is complete if it doesn't panic, - // as otherwise the test binary can exit before the panic is - // reported to the user. See issue 41479. - if didPanic { - return - } - if err != nil { - panic(err) - } - running.Delete(t.name) - t.signal <- signal - }() - - doPanic := func(err any) { - t.Fail() - if r := t.runCleanup(recoverAndReturnPanic); r != nil { - t.Logf("cleanup panicked with %v", r) - } - // Flush the output log up to the root before dying. - for root := &t.common; root.parent != nil; root = root.parent { - root.mu.Lock() - root.duration += highPrecisionTimeSince(root.start) - d := root.duration - root.mu.Unlock() - root.flushToParent(root.name, "--- FAIL: %s (%s)\n", root.name, fmtDuration(d)) - if r := root.parent.runCleanup(recoverAndReturnPanic); r != nil { - fmt.Fprintf(root.parent.w, "cleanup panicked with %v", r) - } - } - didPanic = true - panic(err) - } - if err != nil { - doPanic(err) - } - - t.duration += highPrecisionTimeSince(t.start) - - if len(t.sub) > 0 { - // Run parallel subtests. - - // Decrease the running count for this test and mark it as no longer running. - t.context.release() - running.Delete(t.name) - - // Release the parallel subtests. - close(t.barrier) - // Wait for subtests to complete. - for _, sub := range t.sub { - <-sub.signal - } - - // Run any cleanup callbacks, marking the test as running - // in case the cleanup hangs. - cleanupStart := highPrecisionTimeNow() - running.Store(t.name, cleanupStart) - err := t.runCleanup(recoverAndReturnPanic) - t.duration += highPrecisionTimeSince(cleanupStart) - if err != nil { - doPanic(err) - } - t.checkRaces() - if !t.isParallel { - // Reacquire the count for sequential tests. See comment in Run. - t.context.waitParallel() - } - } else if t.isParallel { - // Only release the count for this test if it was run as a parallel - // test. See comment in Run method. - t.context.release() - } - t.report() // Report after all subtests have finished. - - // Do not lock t.done to allow race detector to detect race in case - // the user does not appropriately synchronize a goroutine. - t.done = true - if t.parent != nil && !t.hasSub.Load() { - t.setRan() - } - }() - defer func() { - if len(t.sub) == 0 { - t.runCleanup(normalPanic) - } - }() - - t.start = highPrecisionTimeNow() - t.resetRaces() - fn(t) - - // code beyond here will not be executed when FailNow is invoked - t.mu.Lock() - t.finished = true - t.mu.Unlock() -} - -// Run runs f as a subtest of t called name. It runs f in a separate goroutine -// and blocks until f returns or calls t.Parallel to become a parallel test. -// Run reports whether f succeeded (or at least did not fail before calling t.Parallel). -// -// Run may be called simultaneously from multiple goroutines, but all such calls -// must return before the outer test function for t returns. -func (t *T) Run(name string, f func(t *T)) bool { - if t.cleanupStarted.Load() { - panic("testing: t.Run called during t.Cleanup") - } - - t.hasSub.Store(true) - testName, ok, _ := t.context.match.fullName(&t.common, name) - if !ok || shouldFailFast() { - return true - } - // Record the stack trace at the point of this call so that if the subtest - // function - which runs in a separate stack - is marked as a helper, we can - // continue walking the stack into the parent test. - var pc [maxStackLen]uintptr - n := runtime.Callers(2, pc[:]) - t = &T{ - common: common{ - barrier: make(chan bool), - signal: make(chan bool, 1), - name: testName, - parent: &t.common, - level: t.level + 1, - creator: pc[:n], - chatty: t.chatty, - }, - context: t.context, - } - t.w = indenter{&t.common} - - if t.chatty != nil { - t.chatty.Updatef(t.name, "=== RUN %s\n", t.name) - } - running.Store(t.name, highPrecisionTimeNow()) - - // Instead of reducing the running count of this test before calling the - // tRunner and increasing it afterwards, we rely on tRunner keeping the - // count correct. This ensures that a sequence of sequential tests runs - // without being preempted, even when their parent is a parallel test. This - // may especially reduce surprises if *parallel == 1. - go tRunner(t, f) - - // The parent goroutine will block until the subtest either finishes or calls - // Parallel, but in general we don't know whether the parent goroutine is the - // top-level test function or some other goroutine it has spawned. - // To avoid confusing false-negatives, we leave the parent in the running map - // even though in the typical case it is blocked. - - if !<-t.signal { - // At this point, it is likely that FailNow was called on one of the - // parent tests by one of the subtests. Continue aborting up the chain. - runtime.Goexit() - } - - if t.chatty != nil && t.chatty.json { - t.chatty.Updatef(t.parent.name, "=== NAME %s\n", t.parent.name) - } - return !t.failed -} - -// Deadline reports the time at which the test binary will have -// exceeded the timeout specified by the -timeout flag. -// -// The ok result is false if the -timeout flag indicates “no timeout” (0). -func (t *T) Deadline() (deadline time.Time, ok bool) { - deadline = t.context.deadline - return deadline, !deadline.IsZero() -} - -// testContext holds all fields that are common to all tests. This includes -// synchronization primitives to run at most *parallel tests. -type testContext struct { - match *matcher - deadline time.Time - - // isFuzzing is true in the context used when generating random inputs - // for fuzz targets. isFuzzing is false when running normal tests and - // when running fuzz tests as unit tests (without -fuzz or when -fuzz - // does not match). - isFuzzing bool - - mu sync.Mutex - - // Channel used to signal tests that are ready to be run in parallel. - startParallel chan bool - - // running is the number of tests currently running in parallel. - // This does not include tests that are waiting for subtests to complete. - running int - - // numWaiting is the number tests waiting to be run in parallel. - numWaiting int - - // maxParallel is a copy of the parallel flag. - maxParallel int -} - -func newTestContext(maxParallel int, m *matcher) *testContext { - return &testContext{ - match: m, - startParallel: make(chan bool), - maxParallel: maxParallel, - running: 1, // Set the count to 1 for the main (sequential) test. - } -} - -func (c *testContext) waitParallel() { - c.mu.Lock() - if c.running < c.maxParallel { - c.running++ - c.mu.Unlock() - return - } - c.numWaiting++ - c.mu.Unlock() - <-c.startParallel -} - -func (c *testContext) release() { - c.mu.Lock() - if c.numWaiting == 0 { - c.running-- - c.mu.Unlock() - return - } - c.numWaiting-- - c.mu.Unlock() - c.startParallel <- true // Pick a waiting test to be run. -} - -// No one should be using func Main anymore. -// See the doc comment on func Main and use MainStart instead. -var errMain = errors.New("testing: unexpected use of func Main") - -type matchStringOnly func(pat, str string) (bool, error) - -func (f matchStringOnly) MatchString(pat, str string) (bool, error) { return f(pat, str) } -func (f matchStringOnly) StartCPUProfile(w io.Writer) error { return errMain } -func (f matchStringOnly) StopCPUProfile() {} -func (f matchStringOnly) WriteProfileTo(string, io.Writer, int) error { return errMain } -func (f matchStringOnly) ImportPath() string { return "" } -func (f matchStringOnly) StartTestLog(io.Writer) {} -func (f matchStringOnly) StopTestLog() error { return errMain } -func (f matchStringOnly) SetPanicOnExit0(bool) {} -func (f matchStringOnly) CoordinateFuzzing(time.Duration, int64, time.Duration, int64, int, []corpusEntry, []reflect.Type, string, string) error { - return errMain -} -func (f matchStringOnly) RunFuzzWorker(func(corpusEntry) error) error { return errMain } -func (f matchStringOnly) ReadCorpus(string, []reflect.Type) ([]corpusEntry, error) { - return nil, errMain -} -func (f matchStringOnly) CheckCorpus([]any, []reflect.Type) error { return nil } -func (f matchStringOnly) ResetCoverage() {} -func (f matchStringOnly) SnapshotCoverage() {} - -func (f matchStringOnly) InitRuntimeCoverage() (mode string, tearDown func(string, string) (string, error), snapcov func() float64) { - return -} - -// Main is an internal function, part of the implementation of the "go test" command. -// It was exported because it is cross-package and predates "internal" packages. -// It is no longer used by "go test" but preserved, as much as possible, for other -// systems that simulate "go test" using Main, but Main sometimes cannot be updated as -// new functionality is added to the testing package. -// Systems simulating "go test" should be updated to use MainStart. -func Main(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample) { - os.Exit(MainStart(matchStringOnly(matchString), tests, benchmarks, nil, examples).Run()) -} - -// M is a type passed to a TestMain function to run the actual tests. -type M struct { - deps testDeps - tests []InternalTest - benchmarks []InternalBenchmark - fuzzTargets []InternalFuzzTarget - examples []InternalExample - - timer *time.Timer - afterOnce sync.Once - - numRun int - - // value to pass to os.Exit, the outer test func main - // harness calls os.Exit with this code. See #34129. - exitCode int -} - -// testDeps is an internal interface of functionality that is -// passed into this package by a test's generated main package. -// The canonical implementation of this interface is -// testing/internal/testdeps's TestDeps. -type testDeps interface { - ImportPath() string - MatchString(pat, str string) (bool, error) - SetPanicOnExit0(bool) - StartCPUProfile(io.Writer) error - StopCPUProfile() - StartTestLog(io.Writer) - StopTestLog() error - WriteProfileTo(string, io.Writer, int) error - CoordinateFuzzing(time.Duration, int64, time.Duration, int64, int, []corpusEntry, []reflect.Type, string, string) error - RunFuzzWorker(func(corpusEntry) error) error - ReadCorpus(string, []reflect.Type) ([]corpusEntry, error) - CheckCorpus([]any, []reflect.Type) error - ResetCoverage() - SnapshotCoverage() - InitRuntimeCoverage() (mode string, tearDown func(coverprofile string, gocoverdir string) (string, error), snapcov func() float64) -} - -// MainStart is meant for use by tests generated by 'go test'. -// It is not meant to be called directly and is not subject to the Go 1 compatibility document. -// It may change signature from release to release. -func MainStart(deps testDeps, tests []InternalTest, benchmarks []InternalBenchmark, fuzzTargets []InternalFuzzTarget, examples []InternalExample) *M { - registerCover2(deps.InitRuntimeCoverage()) - Init() - return &M{ - deps: deps, - tests: tests, - benchmarks: benchmarks, - fuzzTargets: fuzzTargets, - examples: examples, - } -} - -var testingTesting bool -var realStderr *os.File - -// Run runs the tests. It returns an exit code to pass to os.Exit. -func (m *M) Run() (code int) { - defer func() { - code = m.exitCode - }() - - // Count the number of calls to m.Run. - // We only ever expected 1, but we didn't enforce that, - // and now there are tests in the wild that call m.Run multiple times. - // Sigh. go.dev/issue/23129. - m.numRun++ - - // TestMain may have already called flag.Parse. - if !flag.Parsed() { - flag.Parse() - } - - if chatty.json { - // With -v=json, stdout and stderr are pointing to the same pipe, - // which is leading into test2json. In general, operating systems - // do a good job of ensuring that writes to the same pipe through - // different file descriptors are delivered whole, so that writing - // AAA to stdout and BBB to stderr simultaneously produces - // AAABBB or BBBAAA on the pipe, not something like AABBBA. - // However, the exception to this is when the pipe fills: in that - // case, Go's use of non-blocking I/O means that writing AAA - // or BBB might be split across multiple system calls, making it - // entirely possible to get output like AABBBA. The same problem - // happens inside the operating system kernel if we switch to - // blocking I/O on the pipe. This interleaved output can do things - // like print unrelated messages in the middle of a TestFoo line, - // which confuses test2json. Setting os.Stderr = os.Stdout will make - // them share a single pfd, which will hold a lock for each program - // write, preventing any interleaving. - // - // It might be nice to set Stderr = Stdout always, or perhaps if - // we can tell they are the same file, but for now -v=json is - // a very clear signal. Making the two files the same may cause - // surprises if programs close os.Stdout but expect to be able - // to continue to write to os.Stderr, but it's hard to see why a - // test would think it could take over global state that way. - // - // This fix only helps programs where the output is coming directly - // from Go code. It does not help programs in which a subprocess is - // writing to stderr or stdout at the same time that a Go test is writing output. - // It also does not help when the output is coming from the runtime, - // such as when using the print/println functions, since that code writes - // directly to fd 2 without any locking. - // We keep realStderr around to prevent fd 2 from being closed. - // - // See go.dev/issue/33419. - realStderr = os.Stderr - os.Stderr = os.Stdout - } - - if *parallel < 1 { - fmt.Fprintln(os.Stderr, "testing: -parallel can only be given a positive integer") - flag.Usage() - m.exitCode = 2 - return - } - if *matchFuzz != "" && *fuzzCacheDir == "" { - fmt.Fprintln(os.Stderr, "testing: -test.fuzzcachedir must be set if -test.fuzz is set") - flag.Usage() - m.exitCode = 2 - return - } - - if *matchList != "" { - listTests(m.deps.MatchString, m.tests, m.benchmarks, m.fuzzTargets, m.examples) - m.exitCode = 0 - return - } - - if *shuffle != "off" { - var n int64 - var err error - if *shuffle == "on" { - n = time.Now().UnixNano() - } else { - n, err = strconv.ParseInt(*shuffle, 10, 64) - if err != nil { - fmt.Fprintln(os.Stderr, `testing: -shuffle should be "off", "on", or a valid integer:`, err) - m.exitCode = 2 - return - } - } - fmt.Println("-test.shuffle", n) - rng := rand.New(rand.NewSource(n)) - rng.Shuffle(len(m.tests), func(i, j int) { m.tests[i], m.tests[j] = m.tests[j], m.tests[i] }) - rng.Shuffle(len(m.benchmarks), func(i, j int) { m.benchmarks[i], m.benchmarks[j] = m.benchmarks[j], m.benchmarks[i] }) - } - - parseCpuList() - - m.before() - defer m.after() - - // Run tests, examples, and benchmarks unless this is a fuzz worker process. - // Workers start after this is done by their parent process, and they should - // not repeat this work. - if !*isFuzzWorker { - deadline := m.startAlarm() - haveExamples = len(m.examples) > 0 - testRan, testOk := runTests(m.deps.MatchString, m.tests, deadline) - fuzzTargetsRan, fuzzTargetsOk := runFuzzTests(m.deps, m.fuzzTargets, deadline) - exampleRan, exampleOk := runExamples(m.deps.MatchString, m.examples) - m.stopAlarm() - if !testRan && !exampleRan && !fuzzTargetsRan && *matchBenchmarks == "" && *matchFuzz == "" { - fmt.Fprintln(os.Stderr, "testing: warning: no tests to run") - if testingTesting && *match != "^$" { - // If this happens during testing of package testing it could be that - // package testing's own logic for when to run a test is broken, - // in which case every test will run nothing and succeed, - // with no obvious way to detect this problem (since no tests are running). - // So make 'no tests to run' a hard failure when testing package testing itself. - fmt.Print(chatty.prefix(), "FAIL: package testing must run tests\n") - testOk = false - } - } - anyFailed := !testOk || !exampleOk || !fuzzTargetsOk || !runBenchmarks(m.deps.ImportPath(), m.deps.MatchString, m.benchmarks) - if !anyFailed && race.Errors() > 0 { - fmt.Print(chatty.prefix(), "testing: race detected outside of test execution\n") - anyFailed = true - } - if anyFailed { - fmt.Print(chatty.prefix(), "FAIL\n") - m.exitCode = 1 - return - } - } - - fuzzingOk := runFuzzing(m.deps, m.fuzzTargets) - if !fuzzingOk { - fmt.Print(chatty.prefix(), "FAIL\n") - if *isFuzzWorker { - m.exitCode = fuzzWorkerExitCode - } else { - m.exitCode = 1 - } - return - } - - m.exitCode = 0 - if !*isFuzzWorker { - fmt.Print(chatty.prefix(), "PASS\n") - } - return -} - -func (t *T) report() { - if t.parent == nil { - return - } - dstr := fmtDuration(t.duration) - format := "--- %s: %s (%s)\n" - if t.Failed() { - t.flushToParent(t.name, format, "FAIL", t.name, dstr) - } else if t.chatty != nil { - if t.Skipped() { - t.flushToParent(t.name, format, "SKIP", t.name, dstr) - } else { - t.flushToParent(t.name, format, "PASS", t.name, dstr) - } - } -} - -func listTests(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, fuzzTargets []InternalFuzzTarget, examples []InternalExample) { - if _, err := matchString(*matchList, "non-empty"); err != nil { - fmt.Fprintf(os.Stderr, "testing: invalid regexp in -test.list (%q): %s\n", *matchList, err) - os.Exit(1) - } - - for _, test := range tests { - if ok, _ := matchString(*matchList, test.Name); ok { - fmt.Println(test.Name) - } - } - for _, bench := range benchmarks { - if ok, _ := matchString(*matchList, bench.Name); ok { - fmt.Println(bench.Name) - } - } - for _, fuzzTarget := range fuzzTargets { - if ok, _ := matchString(*matchList, fuzzTarget.Name); ok { - fmt.Println(fuzzTarget.Name) - } - } - for _, example := range examples { - if ok, _ := matchString(*matchList, example.Name); ok { - fmt.Println(example.Name) - } - } -} - -// RunTests is an internal function but exported because it is cross-package; -// it is part of the implementation of the "go test" command. -func RunTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ok bool) { - var deadline time.Time - if *timeout > 0 { - deadline = time.Now().Add(*timeout) - } - ran, ok := runTests(matchString, tests, deadline) - if !ran && !haveExamples { - fmt.Fprintln(os.Stderr, "testing: warning: no tests to run") - } - return ok -} - -func runTests(matchString func(pat, str string) (bool, error), tests []InternalTest, deadline time.Time) (ran, ok bool) { - ok = true - for _, procs := range cpuList { - runtime.GOMAXPROCS(procs) - for i := uint(0); i < *count; i++ { - if shouldFailFast() { - break - } - if i > 0 && !ran { - // There were no tests to run on the first - // iteration. This won't change, so no reason - // to keep trying. - break - } - ctx := newTestContext(*parallel, newMatcher(matchString, *match, "-test.run", *skip)) - ctx.deadline = deadline - t := &T{ - common: common{ - signal: make(chan bool, 1), - barrier: make(chan bool), - w: os.Stdout, - }, - context: ctx, - } - if Verbose() { - t.chatty = newChattyPrinter(t.w) - } - tRunner(t, func(t *T) { - for _, test := range tests { - t.Run(test.Name, test.F) - } - }) - select { - case <-t.signal: - default: - panic("internal error: tRunner exited without sending on t.signal") - } - ok = ok && !t.Failed() - ran = ran || t.ran - } - } - return ran, ok -} - -// before runs before all testing. -func (m *M) before() { - if *memProfileRate > 0 { - runtime.MemProfileRate = *memProfileRate - } - if *cpuProfile != "" { - f, err := os.Create(toOutputDir(*cpuProfile)) - if err != nil { - fmt.Fprintf(os.Stderr, "testing: %s\n", err) - return - } - if err := m.deps.StartCPUProfile(f); err != nil { - fmt.Fprintf(os.Stderr, "testing: can't start cpu profile: %s\n", err) - f.Close() - return - } - // Could save f so after can call f.Close; not worth the effort. - } - if *traceFile != "" { - f, err := os.Create(toOutputDir(*traceFile)) - if err != nil { - fmt.Fprintf(os.Stderr, "testing: %s\n", err) - return - } - if err := trace.Start(f); err != nil { - fmt.Fprintf(os.Stderr, "testing: can't start tracing: %s\n", err) - f.Close() - return - } - // Could save f so after can call f.Close; not worth the effort. - } - if *blockProfile != "" && *blockProfileRate >= 0 { - runtime.SetBlockProfileRate(*blockProfileRate) - } - if *mutexProfile != "" && *mutexProfileFraction >= 0 { - runtime.SetMutexProfileFraction(*mutexProfileFraction) - } - if *coverProfile != "" && CoverMode() == "" { - fmt.Fprintf(os.Stderr, "testing: cannot use -test.coverprofile because test binary was not built with coverage enabled\n") - os.Exit(2) - } - if *gocoverdir != "" && CoverMode() == "" { - fmt.Fprintf(os.Stderr, "testing: cannot use -test.gocoverdir because test binary was not built with coverage enabled\n") - os.Exit(2) - } - if *testlog != "" { - // Note: Not using toOutputDir. - // This file is for use by cmd/go, not users. - var f *os.File - var err error - if m.numRun == 1 { - f, err = os.Create(*testlog) - } else { - f, err = os.OpenFile(*testlog, os.O_WRONLY, 0) - if err == nil { - f.Seek(0, io.SeekEnd) - } - } - if err != nil { - fmt.Fprintf(os.Stderr, "testing: %s\n", err) - os.Exit(2) - } - m.deps.StartTestLog(f) - testlogFile = f - } - if *panicOnExit0 { - m.deps.SetPanicOnExit0(true) - } -} - -// after runs after all testing. -func (m *M) after() { - m.afterOnce.Do(func() { - m.writeProfiles() - }) - - // Restore PanicOnExit0 after every run, because we set it to true before - // every run. Otherwise, if m.Run is called multiple times the behavior of - // os.Exit(0) will not be restored after the second run. - if *panicOnExit0 { - m.deps.SetPanicOnExit0(false) - } -} - -func (m *M) writeProfiles() { - if *testlog != "" { - if err := m.deps.StopTestLog(); err != nil { - fmt.Fprintf(os.Stderr, "testing: can't write %s: %s\n", *testlog, err) - os.Exit(2) - } - if err := testlogFile.Close(); err != nil { - fmt.Fprintf(os.Stderr, "testing: can't write %s: %s\n", *testlog, err) - os.Exit(2) - } - } - if *cpuProfile != "" { - m.deps.StopCPUProfile() // flushes profile to disk - } - if *traceFile != "" { - trace.Stop() // flushes trace to disk - } - if *memProfile != "" { - f, err := os.Create(toOutputDir(*memProfile)) - if err != nil { - fmt.Fprintf(os.Stderr, "testing: %s\n", err) - os.Exit(2) - } - runtime.GC() // materialize all statistics - if err = m.deps.WriteProfileTo("allocs", f, 0); err != nil { - fmt.Fprintf(os.Stderr, "testing: can't write %s: %s\n", *memProfile, err) - os.Exit(2) - } - f.Close() - } - if *blockProfile != "" && *blockProfileRate >= 0 { - f, err := os.Create(toOutputDir(*blockProfile)) - if err != nil { - fmt.Fprintf(os.Stderr, "testing: %s\n", err) - os.Exit(2) - } - if err = m.deps.WriteProfileTo("block", f, 0); err != nil { - fmt.Fprintf(os.Stderr, "testing: can't write %s: %s\n", *blockProfile, err) - os.Exit(2) - } - f.Close() - } - if *mutexProfile != "" && *mutexProfileFraction >= 0 { - f, err := os.Create(toOutputDir(*mutexProfile)) - if err != nil { - fmt.Fprintf(os.Stderr, "testing: %s\n", err) - os.Exit(2) - } - if err = m.deps.WriteProfileTo("mutex", f, 0); err != nil { - fmt.Fprintf(os.Stderr, "testing: can't write %s: %s\n", *mutexProfile, err) - os.Exit(2) - } - f.Close() - } - if CoverMode() != "" { - coverReport() - } -} - -// toOutputDir returns the file name relocated, if required, to outputDir. -// Simple implementation to avoid pulling in path/filepath. -func toOutputDir(path string) string { - if *outputDir == "" || path == "" { - return path - } - // On Windows, it's clumsy, but we can be almost always correct - // by just looking for a drive letter and a colon. - // Absolute paths always have a drive letter (ignoring UNC). - // Problem: if path == "C:A" and outputdir == "C:\Go" it's unclear - // what to do, but even then path/filepath doesn't help. - // TODO: Worth doing better? Probably not, because we're here only - // under the management of go test. - if runtime.GOOS == "windows" && len(path) >= 2 { - letter, colon := path[0], path[1] - if ('a' <= letter && letter <= 'z' || 'A' <= letter && letter <= 'Z') && colon == ':' { - // If path starts with a drive letter we're stuck with it regardless. - return path - } - } - if os.IsPathSeparator(path[0]) { - return path - } - return fmt.Sprintf("%s%c%s", *outputDir, os.PathSeparator, path) -} - -// startAlarm starts an alarm if requested. -func (m *M) startAlarm() time.Time { - if *timeout <= 0 { - return time.Time{} - } - - deadline := time.Now().Add(*timeout) - m.timer = time.AfterFunc(*timeout, func() { - m.after() - debug.SetTraceback("all") - extra := "" - - if list := runningList(); len(list) > 0 { - var b strings.Builder - b.WriteString("\nrunning tests:") - for _, name := range list { - b.WriteString("\n\t") - b.WriteString(name) - } - extra = b.String() - } - panic(fmt.Sprintf("test timed out after %v%s", *timeout, extra)) - }) - return deadline -} - -// runningList returns the list of running tests. -func runningList() []string { - var list []string - running.Range(func(k, v any) bool { - list = append(list, fmt.Sprintf("%s (%v)", k.(string), highPrecisionTimeSince(v.(highPrecisionTime)).Round(time.Second))) - return true - }) - slices.Sort(list) - return list -} - -// stopAlarm turns off the alarm. -func (m *M) stopAlarm() { - if *timeout > 0 { - m.timer.Stop() - } -} - -func parseCpuList() { - for _, val := range strings.Split(*cpuListStr, ",") { - val = strings.TrimSpace(val) - if val == "" { - continue - } - cpu, err := strconv.Atoi(val) - if err != nil || cpu <= 0 { - fmt.Fprintf(os.Stderr, "testing: invalid value %q for -test.cpu\n", val) - os.Exit(1) - } - cpuList = append(cpuList, cpu) - } - if cpuList == nil { - cpuList = append(cpuList, runtime.GOMAXPROCS(-1)) - } -} - -func shouldFailFast() bool { - return *failFast && numFailed.Load() > 0 -} diff --git a/runtime/_overlay/testing/testing_go124.go b/runtime/_overlay/testing/testing_go124.go deleted file mode 100644 index bce7174f..00000000 --- a/runtime/_overlay/testing/testing_go124.go +++ /dev/null @@ -1,2536 +0,0 @@ -//go:build go1.24 -// +build go1.24 - -// Copyright 2009 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. - -// Package testing provides support for automated testing of Go packages. -// It is intended to be used in concert with the "go test" command, which automates -// execution of any function of the form -// -// func TestXxx(*testing.T) -// -// where Xxx does not start with a lowercase letter. The function name -// serves to identify the test routine. -// -// Within these functions, use the Error, Fail or related methods to signal failure. -// -// To write a new test suite, create a file that -// contains the TestXxx functions as described here, -// and give that file a name ending in "_test.go". -// The file will be excluded from regular -// package builds but will be included when the "go test" command is run. -// -// The test file can be in the same package as the one being tested, -// or in a corresponding package with the suffix "_test". -// -// If the test file is in the same package, it may refer to unexported -// identifiers within the package, as in this example: -// -// package abs -// -// import "testing" -// -// func TestAbs(t *testing.T) { -// got := Abs(-1) -// if got != 1 { -// t.Errorf("Abs(-1) = %d; want 1", got) -// } -// } -// -// If the file is in a separate "_test" package, the package being tested -// must be imported explicitly and only its exported identifiers may be used. -// This is known as "black box" testing. -// -// package abs_test -// -// import ( -// "testing" -// -// "path_to_pkg/abs" -// ) -// -// func TestAbs(t *testing.T) { -// got := abs.Abs(-1) -// if got != 1 { -// t.Errorf("Abs(-1) = %d; want 1", got) -// } -// } -// -// For more detail, run "go help test" and "go help testflag". -// -// # Benchmarks -// -// Functions of the form -// -// func BenchmarkXxx(*testing.B) -// -// are considered benchmarks, and are executed by the "go test" command when -// its -bench flag is provided. Benchmarks are run sequentially. -// -// For a description of the testing flags, see -// https://golang.org/cmd/go/#hdr-Testing_flags. -// -// A sample benchmark function looks like this: -// -// func BenchmarkRandInt(b *testing.B) { -// for b.Loop() { -// rand.Int() -// } -// } -// -// The output -// -// BenchmarkRandInt-8 68453040 17.8 ns/op -// -// means that the body of the loop ran 68453040 times at a speed of 17.8 ns per loop. -// -// Only the body of the loop is timed, so benchmarks may do expensive -// setup before calling b.Loop, which will not be counted toward the -// benchmark measurement: -// -// func BenchmarkBigLen(b *testing.B) { -// big := NewBig() -// for b.Loop() { -// big.Len() -// } -// } -// -// If a benchmark needs to test performance in a parallel setting, it may use -// the RunParallel helper function; such benchmarks are intended to be used with -// the go test -cpu flag: -// -// func BenchmarkTemplateParallel(b *testing.B) { -// templ := template.Must(template.New("test").Parse("Hello, {{.}}!")) -// b.RunParallel(func(pb *testing.PB) { -// var buf bytes.Buffer -// for pb.Next() { -// buf.Reset() -// templ.Execute(&buf, "World") -// } -// }) -// } -// -// A detailed specification of the benchmark results format is given -// in https://golang.org/design/14313-benchmark-format. -// -// There are standard tools for working with benchmark results at -// https://golang.org/x/perf/cmd. -// In particular, https://golang.org/x/perf/cmd/benchstat performs -// statistically robust A/B comparisons. -// -// # b.N-style benchmarks -// -// Prior to the introduction of [B.Loop], benchmarks were written in a -// different style using B.N. For example: -// -// func BenchmarkRandInt(b *testing.B) { -// for range b.N { -// rand.Int() -// } -// } -// -// In this style of benchmark, the benchmark function must run -// the target code b.N times. The benchmark function is called -// multiple times with b.N adjusted until the benchmark function -// lasts long enough to be timed reliably. This also means any setup -// done before the loop may be run several times. -// -// If a benchmark needs some expensive setup before running, the timer -// should be explicitly reset: -// -// func BenchmarkBigLen(b *testing.B) { -// big := NewBig() -// b.ResetTimer() -// for range b.N { -// big.Len() -// } -// } -// -// New benchmarks should prefer using [B.Loop], which is more robust -// and more efficient. -// -// # Examples -// -// The package also runs and verifies example code. Example functions may -// include a concluding line comment that begins with "Output:" and is compared with -// the standard output of the function when the tests are run. (The comparison -// ignores leading and trailing space.) These are examples of an example: -// -// func ExampleHello() { -// fmt.Println("hello") -// // Output: hello -// } -// -// func ExampleSalutations() { -// fmt.Println("hello, and") -// fmt.Println("goodbye") -// // Output: -// // hello, and -// // goodbye -// } -// -// The comment prefix "Unordered output:" is like "Output:", but matches any -// line order: -// -// func ExamplePerm() { -// for _, value := range Perm(5) { -// fmt.Println(value) -// } -// // Unordered output: 4 -// // 2 -// // 1 -// // 3 -// // 0 -// } -// -// Example functions without output comments are compiled but not executed. -// -// The naming convention to declare examples for the package, a function F, a type T and -// method M on type T are: -// -// func Example() { ... } -// func ExampleF() { ... } -// func ExampleT() { ... } -// func ExampleT_M() { ... } -// -// Multiple example functions for a package/type/function/method may be provided by -// appending a distinct suffix to the name. The suffix must start with a -// lower-case letter. -// -// func Example_suffix() { ... } -// func ExampleF_suffix() { ... } -// func ExampleT_suffix() { ... } -// func ExampleT_M_suffix() { ... } -// -// The entire test file is presented as the example when it contains a single -// example function, at least one other function, type, variable, or constant -// declaration, and no test or benchmark functions. -// -// # Fuzzing -// -// 'go test' and the testing package support fuzzing, a testing technique where -// a function is called with randomly generated inputs to find bugs not -// anticipated by unit tests. -// -// Functions of the form -// -// func FuzzXxx(*testing.F) -// -// are considered fuzz tests. -// -// For example: -// -// func FuzzHex(f *testing.F) { -// for _, seed := range [][]byte{{}, {0}, {9}, {0xa}, {0xf}, {1, 2, 3, 4}} { -// f.Add(seed) -// } -// f.Fuzz(func(t *testing.T, in []byte) { -// enc := hex.EncodeToString(in) -// out, err := hex.DecodeString(enc) -// if err != nil { -// t.Fatalf("%v: decode: %v", in, err) -// } -// if !bytes.Equal(in, out) { -// t.Fatalf("%v: not equal after round trip: %v", in, out) -// } -// }) -// } -// -// A fuzz test maintains a seed corpus, or a set of inputs which are run by -// default, and can seed input generation. Seed inputs may be registered by -// calling (*F).Add or by storing files in the directory testdata/fuzz/ -// (where is the name of the fuzz test) within the package containing -// the fuzz test. Seed inputs are optional, but the fuzzing engine may find -// bugs more efficiently when provided with a set of small seed inputs with good -// code coverage. These seed inputs can also serve as regression tests for bugs -// identified through fuzzing. -// -// The function passed to (*F).Fuzz within the fuzz test is considered the fuzz -// target. A fuzz target must accept a *T parameter, followed by one or more -// parameters for random inputs. The types of arguments passed to (*F).Add must -// be identical to the types of these parameters. The fuzz target may signal -// that it's found a problem the same way tests do: by calling T.Fail (or any -// method that calls it like T.Error or T.Fatal) or by panicking. -// -// When fuzzing is enabled (by setting the -fuzz flag to a regular expression -// that matches a specific fuzz test), the fuzz target is called with arguments -// generated by repeatedly making random changes to the seed inputs. On -// supported platforms, 'go test' compiles the test executable with fuzzing -// coverage instrumentation. The fuzzing engine uses that instrumentation to -// find and cache inputs that expand coverage, increasing the likelihood of -// finding bugs. If the fuzz target fails for a given input, the fuzzing engine -// writes the inputs that caused the failure to a file in the directory -// testdata/fuzz/ within the package directory. This file later serves as -// a seed input. If the file can't be written at that location (for example, -// because the directory is read-only), the fuzzing engine writes the file to -// the fuzz cache directory within the build cache instead. -// -// When fuzzing is disabled, the fuzz target is called with the seed inputs -// registered with F.Add and seed inputs from testdata/fuzz/. In this -// mode, the fuzz test acts much like a regular test, with subtests started -// with F.Fuzz instead of T.Run. -// -// See https://go.dev/doc/fuzz for documentation about fuzzing. -// -// # Skipping -// -// Tests or benchmarks may be skipped at run time with a call to -// the Skip method of *T or *B: -// -// func TestTimeConsuming(t *testing.T) { -// if testing.Short() { -// t.Skip("skipping test in short mode.") -// } -// ... -// } -// -// The Skip method of *T can be used in a fuzz target if the input is invalid, -// but should not be considered a failing input. For example: -// -// func FuzzJSONMarshaling(f *testing.F) { -// f.Fuzz(func(t *testing.T, b []byte) { -// var v interface{} -// if err := json.Unmarshal(b, &v); err != nil { -// t.Skip() -// } -// if _, err := json.Marshal(v); err != nil { -// t.Errorf("Marshal: %v", err) -// } -// }) -// } -// -// # Subtests and Sub-benchmarks -// -// The Run methods of T and B allow defining subtests and sub-benchmarks, -// without having to define separate functions for each. This enables uses -// like table-driven benchmarks and creating hierarchical tests. -// It also provides a way to share common setup and tear-down code: -// -// func TestFoo(t *testing.T) { -// // -// t.Run("A=1", func(t *testing.T) { ... }) -// t.Run("A=2", func(t *testing.T) { ... }) -// t.Run("B=1", func(t *testing.T) { ... }) -// // -// } -// -// Each subtest and sub-benchmark has a unique name: the combination of the name -// of the top-level test and the sequence of names passed to Run, separated by -// slashes, with an optional trailing sequence number for disambiguation. -// -// The argument to the -run, -bench, and -fuzz command-line flags is an unanchored regular -// expression that matches the test's name. For tests with multiple slash-separated -// elements, such as subtests, the argument is itself slash-separated, with -// expressions matching each name element in turn. Because it is unanchored, an -// empty expression matches any string. -// For example, using "matching" to mean "whose name contains": -// -// go test -run '' # Run all tests. -// go test -run Foo # Run top-level tests matching "Foo", such as "TestFooBar". -// go test -run Foo/A= # For top-level tests matching "Foo", run subtests matching "A=". -// go test -run /A=1 # For all top-level tests, run subtests matching "A=1". -// go test -fuzz FuzzFoo # Fuzz the target matching "FuzzFoo" -// -// The -run argument can also be used to run a specific value in the seed -// corpus, for debugging. For example: -// -// go test -run=FuzzFoo/9ddb952d9814 -// -// The -fuzz and -run flags can both be set, in order to fuzz a target but -// skip the execution of all other tests. -// -// Subtests can also be used to control parallelism. A parent test will only -// complete once all of its subtests complete. In this example, all tests are -// run in parallel with each other, and only with each other, regardless of -// other top-level tests that may be defined: -// -// func TestGroupedParallel(t *testing.T) { -// for _, tc := range tests { -// tc := tc // capture range variable -// t.Run(tc.Name, func(t *testing.T) { -// t.Parallel() -// ... -// }) -// } -// } -// -// Run does not return until parallel subtests have completed, providing a way -// to clean up after a group of parallel tests: -// -// func TestTeardownParallel(t *testing.T) { -// // This Run will not return until the parallel tests finish. -// t.Run("group", func(t *testing.T) { -// t.Run("Test1", parallelTest1) -// t.Run("Test2", parallelTest2) -// t.Run("Test3", parallelTest3) -// }) -// // -// } -// -// # Main -// -// It is sometimes necessary for a test or benchmark program to do extra setup or teardown -// before or after it executes. It is also sometimes necessary to control -// which code runs on the main thread. To support these and other cases, -// if a test file contains a function: -// -// func TestMain(m *testing.M) -// -// then the generated test will call TestMain(m) instead of running the tests or benchmarks -// directly. TestMain runs in the main goroutine and can do whatever setup -// and teardown is necessary around a call to m.Run. m.Run will return an exit -// code that may be passed to os.Exit. If TestMain returns, the test wrapper -// will pass the result of m.Run to os.Exit itself. -// -// When TestMain is called, flag.Parse has not been run. If TestMain depends on -// command-line flags, including those of the testing package, it should call -// flag.Parse explicitly. Command line flags are always parsed by the time test -// or benchmark functions run. -// -// A simple implementation of TestMain is: -// -// func TestMain(m *testing.M) { -// // call flag.Parse() here if TestMain uses flags -// m.Run() -// } -// -// TestMain is a low-level primitive and should not be necessary for casual -// testing needs, where ordinary test functions suffice. -package testing - -import ( - "bytes" - "context" - "errors" - "flag" - "fmt" - "internal/goexperiment" - "internal/race" - "io" - "math/rand" - "os" - "path/filepath" - "reflect" - "runtime" - "runtime/debug" - "runtime/trace" - "slices" - "strconv" - "strings" - "sync" - "sync/atomic" - "time" - "unicode" - "unicode/utf8" -) - -var initRan bool - -// Init registers testing flags. These flags are automatically registered by -// the "go test" command before running test functions, so Init is only needed -// when calling functions such as Benchmark without using "go test". -// -// Init is not safe to call concurrently. It has no effect if it was already called. -func Init() { - if initRan { - return - } - initRan = true - // The short flag requests that tests run more quickly, but its functionality - // is provided by test writers themselves. The testing package is just its - // home. The all.bash installation script sets it to make installation more - // efficient, but by default the flag is off so a plain "go test" will do a - // full test of the package. - short = flag.Bool("test.short", false, "run smaller test suite to save time") - - // The failfast flag requests that test execution stop after the first test failure. - failFast = flag.Bool("test.failfast", false, "do not start new tests after the first test failure") - - // The directory in which to create profile files and the like. When run from - // "go test", the binary always runs in the source directory for the package; - // this flag lets "go test" tell the binary to write the files in the directory where - // the "go test" command is run. - outputDir = flag.String("test.outputdir", "", "write profiles to `dir`") - // Report as tests are run; default is silent for success. - flag.Var(&chatty, "test.v", "verbose: print additional output") - count = flag.Uint("test.count", 1, "run tests and benchmarks `n` times") - coverProfile = flag.String("test.coverprofile", "", "write a coverage profile to `file`") - gocoverdir = flag.String("test.gocoverdir", "", "write coverage intermediate files to this directory") - matchList = flag.String("test.list", "", "list tests, examples, and benchmarks matching `regexp` then exit") - match = flag.String("test.run", "", "run only tests and examples matching `regexp`") - skip = flag.String("test.skip", "", "do not list or run tests matching `regexp`") - memProfile = flag.String("test.memprofile", "", "write an allocation profile to `file`") - memProfileRate = flag.Int("test.memprofilerate", 0, "set memory allocation profiling `rate` (see runtime.MemProfileRate)") - cpuProfile = flag.String("test.cpuprofile", "", "write a cpu profile to `file`") - blockProfile = flag.String("test.blockprofile", "", "write a goroutine blocking profile to `file`") - blockProfileRate = flag.Int("test.blockprofilerate", 1, "set blocking profile `rate` (see runtime.SetBlockProfileRate)") - mutexProfile = flag.String("test.mutexprofile", "", "write a mutex contention profile to the named file after execution") - mutexProfileFraction = flag.Int("test.mutexprofilefraction", 1, "if >= 0, calls runtime.SetMutexProfileFraction()") - panicOnExit0 = flag.Bool("test.paniconexit0", false, "panic on call to os.Exit(0)") - traceFile = flag.String("test.trace", "", "write an execution trace to `file`") - timeout = flag.Duration("test.timeout", 0, "panic test binary after duration `d` (default 0, timeout disabled)") - cpuListStr = flag.String("test.cpu", "", "comma-separated `list` of cpu counts to run each test with") - parallel = flag.Int("test.parallel", runtime.GOMAXPROCS(0), "run at most `n` tests in parallel") - testlog = flag.String("test.testlogfile", "", "write test action log to `file` (for use only by cmd/go)") - shuffle = flag.String("test.shuffle", "off", "randomize the execution order of tests and benchmarks") - fullPath = flag.Bool("test.fullpath", false, "show full file names in error messages") - - initBenchmarkFlags() - initFuzzFlags() -} - -var ( - // Flags, registered during Init. - short *bool - failFast *bool - outputDir *string - chatty chattyFlag - count *uint - coverProfile *string - gocoverdir *string - matchList *string - match *string - skip *string - memProfile *string - memProfileRate *int - cpuProfile *string - blockProfile *string - blockProfileRate *int - mutexProfile *string - mutexProfileFraction *int - panicOnExit0 *bool - traceFile *string - timeout *time.Duration - cpuListStr *string - parallel *int - shuffle *string - testlog *string - fullPath *bool - - haveExamples bool // are there examples? - - cpuList []int - testlogFile *os.File - - numFailed atomic.Uint32 // number of test failures - - running sync.Map // map[string]time.Time of running, unpaused tests -) - -type chattyFlag struct { - on bool // -v is set in some form - json bool // -v=test2json is set, to make output better for test2json -} - -func (*chattyFlag) IsBoolFlag() bool { return true } - -func (f *chattyFlag) Set(arg string) error { - switch arg { - default: - return fmt.Errorf("invalid flag -test.v=%s", arg) - case "true", "test2json": - f.on = true - f.json = arg == "test2json" - case "false": - f.on = false - f.json = false - } - return nil -} - -func (f *chattyFlag) String() string { - if f.json { - return "test2json" - } - if f.on { - return "true" - } - return "false" -} - -func (f *chattyFlag) Get() any { - if f.json { - return "test2json" - } - return f.on -} - -const marker = byte(0x16) // ^V for framing - -func (f *chattyFlag) prefix() string { - if f.json { - return string(marker) - } - return "" -} - -type chattyPrinter struct { - w io.Writer - lastNameMu sync.Mutex // guards lastName - lastName string // last printed test name in chatty mode - json bool // -v=json output mode -} - -func newChattyPrinter(w io.Writer) *chattyPrinter { - return &chattyPrinter{w: w, json: chatty.json} -} - -// prefix is like chatty.prefix but using p.json instead of chatty.json. -// Using p.json allows tests to check the json behavior without modifying -// the global variable. For convenience, we allow p == nil and treat -// that as not in json mode (because it's not chatty at all). -func (p *chattyPrinter) prefix() string { - if p != nil && p.json { - return string(marker) - } - return "" -} - -// Updatef prints a message about the status of the named test to w. -// -// The formatted message must include the test name itself. -func (p *chattyPrinter) Updatef(testName, format string, args ...any) { - p.lastNameMu.Lock() - defer p.lastNameMu.Unlock() - - // Since the message already implies an association with a specific new test, - // we don't need to check what the old test name was or log an extra NAME line - // for it. (We're updating it anyway, and the current message already includes - // the test name.) - p.lastName = testName - fmt.Fprintf(p.w, p.prefix()+format, args...) -} - -// Printf prints a message, generated by the named test, that does not -// necessarily mention that tests's name itself. -func (p *chattyPrinter) Printf(testName, format string, args ...any) { - p.lastNameMu.Lock() - defer p.lastNameMu.Unlock() - - if p.lastName == "" { - p.lastName = testName - } else if p.lastName != testName { - fmt.Fprintf(p.w, "%s=== NAME %s\n", p.prefix(), testName) - p.lastName = testName - } - - fmt.Fprintf(p.w, format, args...) -} - -// The maximum number of stack frames to go through when skipping helper functions for -// the purpose of decorating log messages. -const maxStackLen = 50 - -// common holds the elements common between T and B and -// captures common methods such as Errorf. -type common struct { - mu sync.RWMutex // guards this group of fields - output []byte // Output generated by test or benchmark. - w io.Writer // For flushToParent. - ran bool // Test or benchmark (or one of its subtests) was executed. - failed bool // Test or benchmark has failed. - skipped bool // Test or benchmark has been skipped. - done bool // Test is finished and all subtests have completed. - helperPCs map[uintptr]struct{} // functions to be skipped when writing file/line info - helperNames map[string]struct{} // helperPCs converted to function names - cleanups []func() // optional functions to be called at the end of the test - cleanupName string // Name of the cleanup function. - cleanupPc []uintptr // The stack trace at the point where Cleanup was called. - finished bool // Test function has completed. - inFuzzFn bool // Whether the fuzz target, if this is one, is running. - - chatty *chattyPrinter // A copy of chattyPrinter, if the chatty flag is set. - bench bool // Whether the current test is a benchmark. - hasSub atomic.Bool // whether there are sub-benchmarks. - cleanupStarted atomic.Bool // Registered cleanup callbacks have started to execute - runner string // Function name of tRunner running the test. - isParallel bool // Whether the test is parallel. - - parent *common - level int // Nesting depth of test or benchmark. - creator []uintptr // If level > 0, the stack trace at the point where the parent called t.Run. - name string // Name of test or benchmark. - start highPrecisionTime // Time test or benchmark started - duration time.Duration - barrier chan bool // To signal parallel subtests they may start. Nil when T.Parallel is not present (B) or not usable (when fuzzing). - signal chan bool // To signal a test is done. - sub []*T // Queue of subtests to be run in parallel. - - lastRaceErrors atomic.Int64 // Max value of race.Errors seen during the test or its subtests. - raceErrorLogged atomic.Bool - - tempDirMu sync.Mutex - tempDir string - tempDirErr error - tempDirSeq int32 - - ctx context.Context - cancelCtx context.CancelFunc -} - -// Short reports whether the -test.short flag is set. -func Short() bool { - if short == nil { - panic("testing: Short called before Init") - } - // Catch code that calls this from TestMain without first calling flag.Parse. - if !flag.Parsed() { - panic("testing: Short called before Parse") - } - - return *short -} - -// testBinary is set by cmd/go to "1" if this is a binary built by "go test". -// The value is set to "1" by a -X option to cmd/link. We assume that -// because this is possible, the compiler will not optimize testBinary -// into a constant on the basis that it is an unexported package-scope -// variable that is never changed. If the compiler ever starts implementing -// such an optimization, we will need some technique to mark this variable -// as "changed by a cmd/link -X option". -var testBinary = "0" - -// Testing reports whether the current code is being run in a test. -// This will report true in programs created by "go test", -// false in programs created by "go build". -func Testing() bool { - return testBinary == "1" -} - -// CoverMode reports what the test coverage mode is set to. The -// values are "set", "count", or "atomic". The return value will be -// empty if test coverage is not enabled. -func CoverMode() string { - if goexperiment.CoverageRedesign { - return cover2.mode - } - return cover.Mode -} - -// Verbose reports whether the -test.v flag is set. -func Verbose() bool { - // Same as in Short. - if !flag.Parsed() { - panic("testing: Verbose called before Parse") - } - return chatty.on -} - -func (c *common) checkFuzzFn(name string) { - if c.inFuzzFn { - panic(fmt.Sprintf("testing: f.%s was called inside the fuzz target, use t.%s instead", name, name)) - } -} - -// frameSkip searches, starting after skip frames, for the first caller frame -// in a function not marked as a helper and returns that frame. -// The search stops if it finds a tRunner function that -// was the entry point into the test and the test is not a subtest. -// This function must be called with c.mu held. -func (c *common) frameSkip(skip int) runtime.Frame { - // If the search continues into the parent test, we'll have to hold - // its mu temporarily. If we then return, we need to unlock it. - shouldUnlock := false - defer func() { - if shouldUnlock { - c.mu.Unlock() - } - }() - var pc [maxStackLen]uintptr - // Skip two extra frames to account for this function - // and runtime.Callers itself. - n := runtime.Callers(skip+2, pc[:]) - if n == 0 { - panic("testing: zero callers found") - } - frames := runtime.CallersFrames(pc[:n]) - var firstFrame, prevFrame, frame runtime.Frame - for more := true; more; prevFrame = frame { - frame, more = frames.Next() - if frame.Function == "runtime.gopanic" { - continue - } - if frame.Function == c.cleanupName { - frames = runtime.CallersFrames(c.cleanupPc) - continue - } - if firstFrame.PC == 0 { - firstFrame = frame - } - if frame.Function == c.runner { - // We've gone up all the way to the tRunner calling - // the test function (so the user must have - // called tb.Helper from inside that test function). - // If this is a top-level test, only skip up to the test function itself. - // If we're in a subtest, continue searching in the parent test, - // starting from the point of the call to Run which created this subtest. - if c.level > 1 { - frames = runtime.CallersFrames(c.creator) - parent := c.parent - // We're no longer looking at the current c after this point, - // so we should unlock its mu, unless it's the original receiver, - // in which case our caller doesn't expect us to do that. - if shouldUnlock { - c.mu.Unlock() - } - c = parent - // Remember to unlock c.mu when we no longer need it, either - // because we went up another nesting level, or because we - // returned. - shouldUnlock = true - c.mu.Lock() - continue - } - return prevFrame - } - // If more helper PCs have been added since we last did the conversion - if c.helperNames == nil { - c.helperNames = make(map[string]struct{}) - for pc := range c.helperPCs { - c.helperNames[pcToName(pc)] = struct{}{} - } - } - if _, ok := c.helperNames[frame.Function]; !ok { - // Found a frame that wasn't inside a helper function. - return frame - } - } - return firstFrame -} - -// decorate prefixes the string with the file and line of the call site -// and inserts the final newline if needed and indentation spaces for formatting. -// This function must be called with c.mu held. -func (c *common) decorate(s string, skip int) string { - frame := c.frameSkip(skip) - file := frame.File - line := frame.Line - if file != "" { - if *fullPath { - // If relative path, truncate file name at last file name separator. - } else if index := strings.LastIndexAny(file, `/\`); index >= 0 { - file = file[index+1:] - } - } else { - file = "???" - } - if line == 0 { - line = 1 - } - buf := new(strings.Builder) - // Every line is indented at least 4 spaces. - buf.WriteString(" ") - fmt.Fprintf(buf, "%s:%d: ", file, line) - lines := strings.Split(s, "\n") - if l := len(lines); l > 1 && lines[l-1] == "" { - lines = lines[:l-1] - } - for i, line := range lines { - if i > 0 { - // Second and subsequent lines are indented an additional 4 spaces. - buf.WriteString("\n ") - } - buf.WriteString(line) - } - buf.WriteByte('\n') - return buf.String() -} - -// flushToParent writes c.output to the parent after first writing the header -// with the given format and arguments. -func (c *common) flushToParent(testName, format string, args ...any) { - p := c.parent - p.mu.Lock() - defer p.mu.Unlock() - - c.mu.Lock() - defer c.mu.Unlock() - - if len(c.output) > 0 { - // Add the current c.output to the print, - // and then arrange for the print to replace c.output. - // (This displays the logged output after the --- FAIL line.) - format += "%s" - args = append(args[:len(args):len(args)], c.output) - c.output = c.output[:0] - } - - if c.chatty != nil && (p.w == c.chatty.w || c.chatty.json) { - // We're flushing to the actual output, so track that this output is - // associated with a specific test (and, specifically, that the next output - // is *not* associated with that test). - // - // Moreover, if c.output is non-empty it is important that this write be - // atomic with respect to the output of other tests, so that we don't end up - // with confusing '=== NAME' lines in the middle of our '--- PASS' block. - // Neither humans nor cmd/test2json can parse those easily. - // (See https://go.dev/issue/40771.) - // - // If test2json is used, we never flush to parent tests, - // so that the json stream shows subtests as they finish. - // (See https://go.dev/issue/29811.) - c.chatty.Updatef(testName, format, args...) - } else { - // We're flushing to the output buffer of the parent test, which will - // itself follow a test-name header when it is finally flushed to stdout. - fmt.Fprintf(p.w, c.chatty.prefix()+format, args...) - } -} - -type indenter struct { - c *common -} - -func (w indenter) Write(b []byte) (n int, err error) { - n = len(b) - for len(b) > 0 { - end := bytes.IndexByte(b, '\n') - if end == -1 { - end = len(b) - } else { - end++ - } - // An indent of 4 spaces will neatly align the dashes with the status - // indicator of the parent. - line := b[:end] - if line[0] == marker { - w.c.output = append(w.c.output, marker) - line = line[1:] - } - const indent = " " - w.c.output = append(w.c.output, indent...) - w.c.output = append(w.c.output, line...) - b = b[end:] - } - return -} - -// fmtDuration returns a string representing d in the form "87.00s". -func fmtDuration(d time.Duration) string { - return fmt.Sprintf("%.2fs", d.Seconds()) -} - -// TB is the interface common to T, B, and F. -type TB interface { - Cleanup(func()) - Error(args ...any) - Errorf(format string, args ...any) - Fail() - FailNow() - Failed() bool - Fatal(args ...any) - Fatalf(format string, args ...any) - Helper() - Log(args ...any) - Logf(format string, args ...any) - Name() string - Setenv(key, value string) - Chdir(dir string) - Skip(args ...any) - SkipNow() - Skipf(format string, args ...any) - Skipped() bool - TempDir() string - Context() context.Context - - // A private method to prevent users implementing the - // interface and so future additions to it will not - // violate Go 1 compatibility. - private() -} - -var _ TB = (*T)(nil) -var _ TB = (*B)(nil) - -// T is a type passed to Test functions to manage test state and support formatted test logs. -// -// A test ends when its Test function returns or calls any of the methods -// FailNow, Fatal, Fatalf, SkipNow, Skip, or Skipf. Those methods, as well as -// the Parallel method, must be called only from the goroutine running the -// Test function. -// -// The other reporting methods, such as the variations of Log and Error, -// may be called simultaneously from multiple goroutines. -type T struct { - common - denyParallel bool - tstate *testState // For running tests and subtests. -} - -func (c *common) private() {} - -// Name returns the name of the running (sub-) test or benchmark. -// -// The name will include the name of the test along with the names of -// any nested sub-tests. If two sibling sub-tests have the same name, -// Name will append a suffix to guarantee the returned name is unique. -func (c *common) Name() string { - return c.name -} - -func (c *common) setRan() { - if c.parent != nil { - c.parent.setRan() - } - c.mu.Lock() - defer c.mu.Unlock() - c.ran = true -} - -// Fail marks the function as having failed but continues execution. -func (c *common) Fail() { - if c.parent != nil { - c.parent.Fail() - } - c.mu.Lock() - defer c.mu.Unlock() - // c.done needs to be locked to synchronize checks to c.done in parent tests. - if c.done { - panic("Fail in goroutine after " + c.name + " has completed") - } - c.failed = true -} - -// Failed reports whether the function has failed. -func (c *common) Failed() bool { - c.mu.RLock() - defer c.mu.RUnlock() - - if !c.done && int64(race.Errors()) > c.lastRaceErrors.Load() { - c.mu.RUnlock() - c.checkRaces() - c.mu.RLock() - } - - return c.failed -} - -// FailNow marks the function as having failed and stops its execution -// by calling runtime.Goexit (which then runs all deferred calls in the -// current goroutine). -// Execution will continue at the next test or benchmark. -// FailNow must be called from the goroutine running the -// test or benchmark function, not from other goroutines -// created during the test. Calling FailNow does not stop -// those other goroutines. -func (c *common) FailNow() { - c.checkFuzzFn("FailNow") - c.Fail() - - // Calling runtime.Goexit will exit the goroutine, which - // will run the deferred functions in this goroutine, - // which will eventually run the deferred lines in tRunner, - // which will signal to the test loop that this test is done. - // - // A previous version of this code said: - // - // c.duration = ... - // c.signal <- c.self - // runtime.Goexit() - // - // This previous version duplicated code (those lines are in - // tRunner no matter what), but worse the goroutine teardown - // implicit in runtime.Goexit was not guaranteed to complete - // before the test exited. If a test deferred an important cleanup - // function (like removing temporary files), there was no guarantee - // it would run on a test failure. Because we send on c.signal during - // a top-of-stack deferred function now, we know that the send - // only happens after any other stacked defers have completed. - c.mu.Lock() - c.finished = true - c.mu.Unlock() - runtime.Goexit() -} - -// log generates the output. It's always at the same stack depth. -func (c *common) log(s string) { - c.logDepth(s, 3) // logDepth + log + public function -} - -// logDepth generates the output at an arbitrary stack depth. -func (c *common) logDepth(s string, depth int) { - c.mu.Lock() - defer c.mu.Unlock() - if c.done { - // This test has already finished. Try and log this message - // with our parent. If we don't have a parent, panic. - for parent := c.parent; parent != nil; parent = parent.parent { - r := func() bool { - parent.mu.Lock() - defer parent.mu.Unlock() - if !parent.done { - parent.output = append(parent.output, parent.decorate(s, depth+1)...) - return true - } - return false - }() - if r { - return - } - } - panic("Log in goroutine after " + c.name + " has completed: " + s) - } else { - if c.chatty != nil { - if c.bench { - // Benchmarks don't print === CONT, so we should skip the test - // printer and just print straight to stdout. - fmt.Print(c.decorate(s, depth+1)) - } else { - c.chatty.Printf(c.name, "%s", c.decorate(s, depth+1)) - } - - return - } - c.output = append(c.output, c.decorate(s, depth+1)...) - } -} - -// Log formats its arguments using default formatting, analogous to Println, -// and records the text in the error log. For tests, the text will be printed only if -// the test fails or the -test.v flag is set. For benchmarks, the text is always -// printed to avoid having performance depend on the value of the -test.v flag. -func (c *common) Log(args ...any) { - c.checkFuzzFn("Log") - c.log(fmt.Sprintln(args...)) -} - -// Logf formats its arguments according to the format, analogous to Printf, and -// records the text in the error log. A final newline is added if not provided. For -// tests, the text will be printed only if the test fails or the -test.v flag is -// set. For benchmarks, the text is always printed to avoid having performance -// depend on the value of the -test.v flag. -func (c *common) Logf(format string, args ...any) { - c.checkFuzzFn("Logf") - c.log(fmt.Sprintf(format, args...)) -} - -// Error is equivalent to Log followed by Fail. -func (c *common) Error(args ...any) { - c.checkFuzzFn("Error") - c.log(fmt.Sprintln(args...)) - c.Fail() -} - -// Errorf is equivalent to Logf followed by Fail. -func (c *common) Errorf(format string, args ...any) { - c.checkFuzzFn("Errorf") - c.log(fmt.Sprintf(format, args...)) - c.Fail() -} - -// Fatal is equivalent to Log followed by FailNow. -func (c *common) Fatal(args ...any) { - c.checkFuzzFn("Fatal") - c.log(fmt.Sprintln(args...)) - c.FailNow() -} - -// Fatalf is equivalent to Logf followed by FailNow. -func (c *common) Fatalf(format string, args ...any) { - c.checkFuzzFn("Fatalf") - c.log(fmt.Sprintf(format, args...)) - c.FailNow() -} - -// Skip is equivalent to Log followed by SkipNow. -func (c *common) Skip(args ...any) { - c.checkFuzzFn("Skip") - c.log(fmt.Sprintln(args...)) - c.SkipNow() -} - -// Skipf is equivalent to Logf followed by SkipNow. -func (c *common) Skipf(format string, args ...any) { - c.checkFuzzFn("Skipf") - c.log(fmt.Sprintf(format, args...)) - c.SkipNow() -} - -// SkipNow marks the test as having been skipped and stops its execution -// by calling [runtime.Goexit]. -// If a test fails (see Error, Errorf, Fail) and is then skipped, -// it is still considered to have failed. -// Execution will continue at the next test or benchmark. See also FailNow. -// SkipNow must be called from the goroutine running the test, not from -// other goroutines created during the test. Calling SkipNow does not stop -// those other goroutines. -func (c *common) SkipNow() { - c.checkFuzzFn("SkipNow") - c.mu.Lock() - c.skipped = true - c.finished = true - c.mu.Unlock() - runtime.Goexit() -} - -// Skipped reports whether the test was skipped. -func (c *common) Skipped() bool { - c.mu.RLock() - defer c.mu.RUnlock() - return c.skipped -} - -// Helper marks the calling function as a test helper function. -// When printing file and line information, that function will be skipped. -// Helper may be called simultaneously from multiple goroutines. -func (c *common) Helper() { - c.mu.Lock() - defer c.mu.Unlock() - if c.helperPCs == nil { - c.helperPCs = make(map[uintptr]struct{}) - } - // repeating code from callerName here to save walking a stack frame - var pc [1]uintptr - n := runtime.Callers(2, pc[:]) // skip runtime.Callers + Helper - if n == 0 { - panic("testing: zero callers found") - } - if _, found := c.helperPCs[pc[0]]; !found { - c.helperPCs[pc[0]] = struct{}{} - c.helperNames = nil // map will be recreated next time it is needed - } -} - -// Cleanup registers a function to be called when the test (or subtest) and all its -// subtests complete. Cleanup functions will be called in last added, -// first called order. -func (c *common) Cleanup(f func()) { - c.checkFuzzFn("Cleanup") - var pc [maxStackLen]uintptr - // Skip two extra frames to account for this function and runtime.Callers itself. - n := runtime.Callers(2, pc[:]) - cleanupPc := pc[:n] - - fn := func() { - defer func() { - c.mu.Lock() - defer c.mu.Unlock() - c.cleanupName = "" - c.cleanupPc = nil - }() - - name := callerName(0) - c.mu.Lock() - c.cleanupName = name - c.cleanupPc = cleanupPc - c.mu.Unlock() - - f() - } - - c.mu.Lock() - defer c.mu.Unlock() - c.cleanups = append(c.cleanups, fn) -} - -// TempDir returns a temporary directory for the test to use. -// The directory is automatically removed when the test and -// all its subtests complete. -// Each subsequent call to t.TempDir returns a unique directory; -// if the directory creation fails, TempDir terminates the test by calling Fatal. -func (c *common) TempDir() string { - c.checkFuzzFn("TempDir") - // Use a single parent directory for all the temporary directories - // created by a test, each numbered sequentially. - c.tempDirMu.Lock() - var nonExistent bool - if c.tempDir == "" { // Usually the case with js/wasm - nonExistent = true - } else { - _, err := os.Stat(c.tempDir) - nonExistent = os.IsNotExist(err) - if err != nil && !nonExistent { - c.Fatalf("TempDir: %v", err) - } - } - - if nonExistent { - c.Helper() - - // Drop unusual characters (such as path separators or - // characters interacting with globs) from the directory name to - // avoid surprising os.MkdirTemp behavior. - mapper := func(r rune) rune { - if r < utf8.RuneSelf { - const allowed = "!#$%&()+,-.=@^_{}~ " - if '0' <= r && r <= '9' || - 'a' <= r && r <= 'z' || - 'A' <= r && r <= 'Z' { - return r - } - if strings.ContainsRune(allowed, r) { - return r - } - } else if unicode.IsLetter(r) || unicode.IsNumber(r) { - return r - } - return -1 - } - pattern := strings.Map(mapper, c.Name()) - c.tempDir, c.tempDirErr = os.MkdirTemp("", pattern) - if c.tempDirErr == nil { - c.Cleanup(func() { - if err := removeAll(c.tempDir); err != nil { - c.Errorf("TempDir RemoveAll cleanup: %v", err) - } - }) - } - } - - if c.tempDirErr == nil { - c.tempDirSeq++ - } - seq := c.tempDirSeq - c.tempDirMu.Unlock() - - if c.tempDirErr != nil { - c.Fatalf("TempDir: %v", c.tempDirErr) - } - - dir := fmt.Sprintf("%s%c%03d", c.tempDir, os.PathSeparator, seq) - if err := os.Mkdir(dir, 0777); err != nil { - c.Fatalf("TempDir: %v", err) - } - return dir -} - -// removeAll is like os.RemoveAll, but retries Windows "Access is denied." -// errors up to an arbitrary timeout. -// -// Those errors have been known to occur spuriously on at least the -// windows-amd64-2012 builder (https://go.dev/issue/50051), and can only occur -// legitimately if the test leaves behind a temp file that either is still open -// or the test otherwise lacks permission to delete. In the case of legitimate -// failures, a failing test may take a bit longer to fail, but once the test is -// fixed the extra latency will go away. -func removeAll(path string) error { - const arbitraryTimeout = 2 * time.Second - var ( - start time.Time - nextSleep = 1 * time.Millisecond - ) - for { - err := os.RemoveAll(path) - if !isWindowsRetryable(err) { - return err - } - if start.IsZero() { - start = time.Now() - } else if d := time.Since(start) + nextSleep; d >= arbitraryTimeout { - return err - } - time.Sleep(nextSleep) - nextSleep += time.Duration(rand.Int63n(int64(nextSleep))) - } -} - -// Setenv calls os.Setenv(key, value) and uses Cleanup to -// restore the environment variable to its original value -// after the test. -// -// Because Setenv affects the whole process, it cannot be used -// in parallel tests or tests with parallel ancestors. -func (c *common) Setenv(key, value string) { - c.checkFuzzFn("Setenv") - prevValue, ok := os.LookupEnv(key) - - if err := os.Setenv(key, value); err != nil { - c.Fatalf("cannot set environment variable: %v", err) - } - - if ok { - c.Cleanup(func() { - os.Setenv(key, prevValue) - }) - } else { - c.Cleanup(func() { - os.Unsetenv(key) - }) - } -} - -// Chdir calls os.Chdir(dir) and uses Cleanup to restore the current -// working directory to its original value after the test. On Unix, it -// also sets PWD environment variable for the duration of the test. -// -// Because Chdir affects the whole process, it cannot be used -// in parallel tests or tests with parallel ancestors. -func (c *common) Chdir(dir string) { - c.checkFuzzFn("Chdir") - oldwd, err := os.Open(".") - if err != nil { - c.Fatal(err) - } - if err := os.Chdir(dir); err != nil { - c.Fatal(err) - } - // On POSIX platforms, PWD represents “an absolute pathname of the - // current working directory.” Since we are changing the working - // directory, we should also set or update PWD to reflect that. - switch runtime.GOOS { - case "windows", "plan9": - // Windows and Plan 9 do not use the PWD variable. - default: - if !filepath.IsAbs(dir) { - dir, err = os.Getwd() - if err != nil { - c.Fatal(err) - } - } - c.Setenv("PWD", dir) - } - c.Cleanup(func() { - err := oldwd.Chdir() - oldwd.Close() - if err != nil { - // It's not safe to continue with tests if we can't - // get back to the original working directory. Since - // we are holding a dirfd, this is highly unlikely. - panic("testing.Chdir: " + err.Error()) - } - }) -} - -// Context returns a context that is canceled just before -// Cleanup-registered functions are called. -// -// Cleanup functions can wait for any resources -// that shut down on Context.Done before the test or benchmark completes. -func (c *common) Context() context.Context { - c.checkFuzzFn("Context") - return c.ctx -} - -// panicHandling controls the panic handling used by runCleanup. -type panicHandling int - -const ( - normalPanic panicHandling = iota - recoverAndReturnPanic -) - -// runCleanup is called at the end of the test. -// If ph is recoverAndReturnPanic, it will catch panics, and return the -// recovered value if any. -func (c *common) runCleanup(ph panicHandling) (panicVal any) { - c.cleanupStarted.Store(true) - defer c.cleanupStarted.Store(false) - - if ph == recoverAndReturnPanic { - defer func() { - panicVal = recover() - }() - } - - // Make sure that if a cleanup function panics, - // we still run the remaining cleanup functions. - defer func() { - c.mu.Lock() - recur := len(c.cleanups) > 0 - c.mu.Unlock() - if recur { - c.runCleanup(normalPanic) - } - }() - - if c.cancelCtx != nil { - c.cancelCtx() - } - - for { - var cleanup func() - c.mu.Lock() - if len(c.cleanups) > 0 { - last := len(c.cleanups) - 1 - cleanup = c.cleanups[last] - c.cleanups = c.cleanups[:last] - } - c.mu.Unlock() - if cleanup == nil { - return nil - } - cleanup() - } -} - -// resetRaces updates c.parent's count of data race errors (or the global count, -// if c has no parent), and updates c.lastRaceErrors to match. -// -// Any races that occurred prior to this call to resetRaces will -// not be attributed to c. -func (c *common) resetRaces() { - if c.parent == nil { - c.lastRaceErrors.Store(int64(race.Errors())) - } else { - c.lastRaceErrors.Store(c.parent.checkRaces()) - } -} - -// checkRaces checks whether the global count of data race errors has increased -// since c's count was last reset. -// -// If so, it marks c as having failed due to those races (logging an error for -// the first such race), and updates the race counts for the parents of c so -// that if they are currently suspended (such as in a call to T.Run) they will -// not log separate errors for the race(s). -// -// Note that multiple tests may be marked as failed due to the same race if they -// are executing in parallel. -func (c *common) checkRaces() (raceErrors int64) { - raceErrors = int64(race.Errors()) - for { - last := c.lastRaceErrors.Load() - if raceErrors <= last { - // All races have already been reported. - return raceErrors - } - if c.lastRaceErrors.CompareAndSwap(last, raceErrors) { - break - } - } - - if c.raceErrorLogged.CompareAndSwap(false, true) { - // This is the first race we've encountered for this test. - // Mark the test as failed, and log the reason why only once. - // (Note that the race detector itself will still write a goroutine - // dump for any further races it detects.) - c.Errorf("race detected during execution of test") - } - - // Update the parent(s) of this test so that they don't re-report the race. - parent := c.parent - for parent != nil { - for { - last := parent.lastRaceErrors.Load() - if raceErrors <= last { - // This race was already reported by another (likely parallel) subtest. - return raceErrors - } - if parent.lastRaceErrors.CompareAndSwap(last, raceErrors) { - break - } - } - parent = parent.parent - } - - return raceErrors -} - -// callerName gives the function name (qualified with a package path) -// for the caller after skip frames (where 0 means the current function). -func callerName(skip int) string { - var pc [1]uintptr - n := runtime.Callers(skip+2, pc[:]) // skip + runtime.Callers + callerName - if n == 0 { - panic("testing: zero callers found") - } - return pcToName(pc[0]) -} - -func pcToName(pc uintptr) string { - pcs := []uintptr{pc} - frames := runtime.CallersFrames(pcs) - frame, _ := frames.Next() - return frame.Function -} - -const parallelConflict = `testing: test using t.Setenv or t.Chdir can not use t.Parallel` - -// Parallel signals that this test is to be run in parallel with (and only with) -// other parallel tests. When a test is run multiple times due to use of -// -test.count or -test.cpu, multiple instances of a single test never run in -// parallel with each other. -func (t *T) Parallel() { - if t.isParallel { - panic("testing: t.Parallel called multiple times") - } - if t.denyParallel { - panic(parallelConflict) - } - t.isParallel = true - if t.parent.barrier == nil { - // T.Parallel has no effect when fuzzing. - // Multiple processes may run in parallel, but only one input can run at a - // time per process so we can attribute crashes to specific inputs. - return - } - - // We don't want to include the time we spend waiting for serial tests - // in the test duration. Record the elapsed time thus far and reset the - // timer afterwards. - t.duration += highPrecisionTimeSince(t.start) - - // Add to the list of tests to be released by the parent. - t.parent.sub = append(t.parent.sub, t) - - // Report any races during execution of this test up to this point. - // - // We will assume that any races that occur between here and the point where - // we unblock are not caused by this subtest. That assumption usually holds, - // although it can be wrong if the test spawns a goroutine that races in the - // background while the rest of the test is blocked on the call to Parallel. - // If that happens, we will misattribute the background race to some other - // test, or to no test at all — but that false-negative is so unlikely that it - // is not worth adding race-report noise for the common case where the test is - // completely suspended during the call to Parallel. - t.checkRaces() - - if t.chatty != nil { - t.chatty.Updatef(t.name, "=== PAUSE %s\n", t.name) - } - running.Delete(t.name) - - t.signal <- true // Release calling test. - <-t.parent.barrier // Wait for the parent test to complete. - t.tstate.waitParallel() - - if t.chatty != nil { - t.chatty.Updatef(t.name, "=== CONT %s\n", t.name) - } - running.Store(t.name, highPrecisionTimeNow()) - t.start = highPrecisionTimeNow() - - // Reset the local race counter to ignore any races that happened while this - // goroutine was blocked, such as in the parent test or in other parallel - // subtests. - // - // (Note that we don't call parent.checkRaces here: - // if other parallel subtests have already introduced races, we want to - // let them report those races instead of attributing them to the parent.) - t.lastRaceErrors.Store(int64(race.Errors())) -} - -func (t *T) checkParallel() { - // Non-parallel subtests that have parallel ancestors may still - // run in parallel with other tests: they are only non-parallel - // with respect to the other subtests of the same parent. - // Since calls like SetEnv or Chdir affects the whole process, we need - // to deny those if the current test or any parent is parallel. - for c := &t.common; c != nil; c = c.parent { - if c.isParallel { - panic(parallelConflict) - } - } - - t.denyParallel = true -} - -// Setenv calls os.Setenv(key, value) and uses Cleanup to -// restore the environment variable to its original value -// after the test. -// -// Because Setenv affects the whole process, it cannot be used -// in parallel tests or tests with parallel ancestors. -func (t *T) Setenv(key, value string) { - t.checkParallel() - t.common.Setenv(key, value) -} - -// Chdir calls os.Chdir(dir) and uses Cleanup to restore the current -// working directory to its original value after the test. On Unix, it -// also sets PWD environment variable for the duration of the test. -// -// Because Chdir affects the whole process, it cannot be used -// in parallel tests or tests with parallel ancestors. -func (t *T) Chdir(dir string) { - t.checkParallel() - t.common.Chdir(dir) -} - -// InternalTest is an internal type but exported because it is cross-package; -// it is part of the implementation of the "go test" command. -type InternalTest struct { - Name string - F func(*T) -} - -var errNilPanicOrGoexit = errors.New("test executed panic(nil) or runtime.Goexit") - -func tRunner(t *T, fn func(t *T)) { - t.runner = callerName(0) - - // When this goroutine is done, either because fn(t) - // returned normally or because a test failure triggered - // a call to runtime.Goexit, record the duration and send - // a signal saying that the test is done. - defer func() { - t.checkRaces() - - // TODO(#61034): This is the wrong place for this check. - if t.Failed() { - numFailed.Add(1) - } - - // Check if the test panicked or Goexited inappropriately. - // - // If this happens in a normal test, print output but continue panicking. - // tRunner is called in its own goroutine, so this terminates the process. - // - // If this happens while fuzzing, recover from the panic and treat it like a - // normal failure. It's important that the process keeps running in order to - // find short inputs that cause panics. - err := recover() - signal := true - - t.mu.RLock() - finished := t.finished - t.mu.RUnlock() - if !finished && err == nil { - err = errNilPanicOrGoexit - for p := t.parent; p != nil; p = p.parent { - p.mu.RLock() - finished = p.finished - p.mu.RUnlock() - if finished { - if !t.isParallel { - t.Errorf("%v: subtest may have called FailNow on a parent test", err) - err = nil - } - signal = false - break - } - } - } - - if err != nil && t.tstate.isFuzzing { - prefix := "panic: " - if err == errNilPanicOrGoexit { - prefix = "" - } - t.Errorf("%s%s\n%s\n", prefix, err, string(debug.Stack())) - t.mu.Lock() - t.finished = true - t.mu.Unlock() - err = nil - } - - // Use a deferred call to ensure that we report that the test is - // complete even if a cleanup function calls t.FailNow. See issue 41355. - didPanic := false - defer func() { - // Only report that the test is complete if it doesn't panic, - // as otherwise the test binary can exit before the panic is - // reported to the user. See issue 41479. - if didPanic { - return - } - if err != nil { - panic(err) - } - running.Delete(t.name) - t.signal <- signal - }() - - doPanic := func(err any) { - t.Fail() - if r := t.runCleanup(recoverAndReturnPanic); r != nil { - t.Logf("cleanup panicked with %v", r) - } - // Flush the output log up to the root before dying. - for root := &t.common; root.parent != nil; root = root.parent { - root.mu.Lock() - root.duration += highPrecisionTimeSince(root.start) - d := root.duration - root.mu.Unlock() - root.flushToParent(root.name, "--- FAIL: %s (%s)\n", root.name, fmtDuration(d)) - if r := root.parent.runCleanup(recoverAndReturnPanic); r != nil { - fmt.Fprintf(root.parent.w, "cleanup panicked with %v", r) - } - } - didPanic = true - panic(err) - } - if err != nil { - doPanic(err) - } - - t.duration += highPrecisionTimeSince(t.start) - - if len(t.sub) > 0 { - // Run parallel subtests. - - // Decrease the running count for this test and mark it as no longer running. - t.tstate.release() - running.Delete(t.name) - - // Release the parallel subtests. - close(t.barrier) - // Wait for subtests to complete. - for _, sub := range t.sub { - <-sub.signal - } - - // Run any cleanup callbacks, marking the test as running - // in case the cleanup hangs. - cleanupStart := highPrecisionTimeNow() - running.Store(t.name, cleanupStart) - err := t.runCleanup(recoverAndReturnPanic) - t.duration += highPrecisionTimeSince(cleanupStart) - if err != nil { - doPanic(err) - } - t.checkRaces() - if !t.isParallel { - // Reacquire the count for sequential tests. See comment in Run. - t.tstate.waitParallel() - } - } else if t.isParallel { - // Only release the count for this test if it was run as a parallel - // test. See comment in Run method. - t.tstate.release() - } - t.report() // Report after all subtests have finished. - - // Do not lock t.done to allow race detector to detect race in case - // the user does not appropriately synchronize a goroutine. - t.done = true - if t.parent != nil && !t.hasSub.Load() { - t.setRan() - } - }() - defer func() { - if len(t.sub) == 0 { - t.runCleanup(normalPanic) - } - }() - - t.start = highPrecisionTimeNow() - t.resetRaces() - fn(t) - - // code beyond here will not be executed when FailNow is invoked - t.mu.Lock() - t.finished = true - t.mu.Unlock() -} - -// Run runs f as a subtest of t called name. It runs f in a separate goroutine -// and blocks until f returns or calls t.Parallel to become a parallel test. -// Run reports whether f succeeded (or at least did not fail before calling t.Parallel). -// -// Run may be called simultaneously from multiple goroutines, but all such calls -// must return before the outer test function for t returns. -func (t *T) Run(name string, f func(t *T)) bool { - if t.cleanupStarted.Load() { - panic("testing: t.Run called during t.Cleanup") - } - - t.hasSub.Store(true) - testName, ok, _ := t.tstate.match.fullName(&t.common, name) - if !ok || shouldFailFast() { - return true - } - // Record the stack trace at the point of this call so that if the subtest - // function - which runs in a separate stack - is marked as a helper, we can - // continue walking the stack into the parent test. - var pc [maxStackLen]uintptr - n := runtime.Callers(2, pc[:]) - - // There's no reason to inherit this context from parent. The user's code can't observe - // the difference between the background context and the one from the parent test. - ctx, cancelCtx := context.WithCancel(context.Background()) - t = &T{ - common: common{ - barrier: make(chan bool), - signal: make(chan bool, 1), - name: testName, - parent: &t.common, - level: t.level + 1, - creator: pc[:n], - chatty: t.chatty, - ctx: ctx, - cancelCtx: cancelCtx, - }, - tstate: t.tstate, - } - t.w = indenter{&t.common} - - if t.chatty != nil { - t.chatty.Updatef(t.name, "=== RUN %s\n", t.name) - } - running.Store(t.name, highPrecisionTimeNow()) - - // Instead of reducing the running count of this test before calling the - // tRunner and increasing it afterwards, we rely on tRunner keeping the - // count correct. This ensures that a sequence of sequential tests runs - // without being preempted, even when their parent is a parallel test. This - // may especially reduce surprises if *parallel == 1. - go tRunner(t, f) - - // The parent goroutine will block until the subtest either finishes or calls - // Parallel, but in general we don't know whether the parent goroutine is the - // top-level test function or some other goroutine it has spawned. - // To avoid confusing false-negatives, we leave the parent in the running map - // even though in the typical case it is blocked. - - if !<-t.signal { - // At this point, it is likely that FailNow was called on one of the - // parent tests by one of the subtests. Continue aborting up the chain. - runtime.Goexit() - } - - if t.chatty != nil && t.chatty.json { - t.chatty.Updatef(t.parent.name, "=== NAME %s\n", t.parent.name) - } - return !t.failed -} - -// Deadline reports the time at which the test binary will have -// exceeded the timeout specified by the -timeout flag. -// -// The ok result is false if the -timeout flag indicates “no timeout” (0). -func (t *T) Deadline() (deadline time.Time, ok bool) { - deadline = t.tstate.deadline - return deadline, !deadline.IsZero() -} - -// testState holds all fields that are common to all tests. This includes -// synchronization primitives to run at most *parallel tests. -type testState struct { - match *matcher - deadline time.Time - - // isFuzzing is true in the state used when generating random inputs - // for fuzz targets. isFuzzing is false when running normal tests and - // when running fuzz tests as unit tests (without -fuzz or when -fuzz - // does not match). - isFuzzing bool - - mu sync.Mutex - - // Channel used to signal tests that are ready to be run in parallel. - startParallel chan bool - - // running is the number of tests currently running in parallel. - // This does not include tests that are waiting for subtests to complete. - running int - - // numWaiting is the number tests waiting to be run in parallel. - numWaiting int - - // maxParallel is a copy of the parallel flag. - maxParallel int -} - -func newTestState(maxParallel int, m *matcher) *testState { - return &testState{ - match: m, - startParallel: make(chan bool), - maxParallel: maxParallel, - running: 1, // Set the count to 1 for the main (sequential) test. - } -} - -func (s *testState) waitParallel() { - s.mu.Lock() - if s.running < s.maxParallel { - s.running++ - s.mu.Unlock() - return - } - s.numWaiting++ - s.mu.Unlock() - <-s.startParallel -} - -func (s *testState) release() { - s.mu.Lock() - if s.numWaiting == 0 { - s.running-- - s.mu.Unlock() - return - } - s.numWaiting-- - s.mu.Unlock() - s.startParallel <- true // Pick a waiting test to be run. -} - -// No one should be using func Main anymore. -// See the doc comment on func Main and use MainStart instead. -var errMain = errors.New("testing: unexpected use of func Main") - -type matchStringOnly func(pat, str string) (bool, error) - -func (f matchStringOnly) MatchString(pat, str string) (bool, error) { return f(pat, str) } -func (f matchStringOnly) StartCPUProfile(w io.Writer) error { return errMain } -func (f matchStringOnly) StopCPUProfile() {} -func (f matchStringOnly) WriteProfileTo(string, io.Writer, int) error { return errMain } -func (f matchStringOnly) ImportPath() string { return "" } -func (f matchStringOnly) StartTestLog(io.Writer) {} -func (f matchStringOnly) StopTestLog() error { return errMain } -func (f matchStringOnly) SetPanicOnExit0(bool) {} -func (f matchStringOnly) CoordinateFuzzing(time.Duration, int64, time.Duration, int64, int, []corpusEntry, []reflect.Type, string, string) error { - return errMain -} -func (f matchStringOnly) RunFuzzWorker(func(corpusEntry) error) error { return errMain } -func (f matchStringOnly) ReadCorpus(string, []reflect.Type) ([]corpusEntry, error) { - return nil, errMain -} -func (f matchStringOnly) CheckCorpus([]any, []reflect.Type) error { return nil } -func (f matchStringOnly) ResetCoverage() {} -func (f matchStringOnly) SnapshotCoverage() {} - -func (f matchStringOnly) InitRuntimeCoverage() (mode string, tearDown func(string, string) (string, error), snapcov func() float64) { - return -} - -// Main is an internal function, part of the implementation of the "go test" command. -// It was exported because it is cross-package and predates "internal" packages. -// It is no longer used by "go test" but preserved, as much as possible, for other -// systems that simulate "go test" using Main, but Main sometimes cannot be updated as -// new functionality is added to the testing package. -// Systems simulating "go test" should be updated to use MainStart. -func Main(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample) { - os.Exit(MainStart(matchStringOnly(matchString), tests, benchmarks, nil, examples).Run()) -} - -// M is a type passed to a TestMain function to run the actual tests. -type M struct { - deps testDeps - tests []InternalTest - benchmarks []InternalBenchmark - fuzzTargets []InternalFuzzTarget - examples []InternalExample - - timer *time.Timer - afterOnce sync.Once - - numRun int - - // value to pass to os.Exit, the outer test func main - // harness calls os.Exit with this code. See #34129. - exitCode int -} - -// testDeps is an internal interface of functionality that is -// passed into this package by a test's generated main package. -// The canonical implementation of this interface is -// testing/internal/testdeps's TestDeps. -type testDeps interface { - ImportPath() string - MatchString(pat, str string) (bool, error) - SetPanicOnExit0(bool) - StartCPUProfile(io.Writer) error - StopCPUProfile() - StartTestLog(io.Writer) - StopTestLog() error - WriteProfileTo(string, io.Writer, int) error - CoordinateFuzzing(time.Duration, int64, time.Duration, int64, int, []corpusEntry, []reflect.Type, string, string) error - RunFuzzWorker(func(corpusEntry) error) error - ReadCorpus(string, []reflect.Type) ([]corpusEntry, error) - CheckCorpus([]any, []reflect.Type) error - ResetCoverage() - SnapshotCoverage() - InitRuntimeCoverage() (mode string, tearDown func(coverprofile string, gocoverdir string) (string, error), snapcov func() float64) -} - -// MainStart is meant for use by tests generated by 'go test'. -// It is not meant to be called directly and is not subject to the Go 1 compatibility document. -// It may change signature from release to release. -func MainStart(deps testDeps, tests []InternalTest, benchmarks []InternalBenchmark, fuzzTargets []InternalFuzzTarget, examples []InternalExample) *M { - registerCover2(deps.InitRuntimeCoverage()) - Init() - return &M{ - deps: deps, - tests: tests, - benchmarks: benchmarks, - fuzzTargets: fuzzTargets, - examples: examples, - } -} - -var testingTesting bool -var realStderr *os.File - -// Run runs the tests. It returns an exit code to pass to os.Exit. -func (m *M) Run() (code int) { - defer func() { - code = m.exitCode - }() - - // Count the number of calls to m.Run. - // We only ever expected 1, but we didn't enforce that, - // and now there are tests in the wild that call m.Run multiple times. - // Sigh. go.dev/issue/23129. - m.numRun++ - - // TestMain may have already called flag.Parse. - if !flag.Parsed() { - flag.Parse() - } - - if chatty.json { - // With -v=json, stdout and stderr are pointing to the same pipe, - // which is leading into test2json. In general, operating systems - // do a good job of ensuring that writes to the same pipe through - // different file descriptors are delivered whole, so that writing - // AAA to stdout and BBB to stderr simultaneously produces - // AAABBB or BBBAAA on the pipe, not something like AABBBA. - // However, the exception to this is when the pipe fills: in that - // case, Go's use of non-blocking I/O means that writing AAA - // or BBB might be split across multiple system calls, making it - // entirely possible to get output like AABBBA. The same problem - // happens inside the operating system kernel if we switch to - // blocking I/O on the pipe. This interleaved output can do things - // like print unrelated messages in the middle of a TestFoo line, - // which confuses test2json. Setting os.Stderr = os.Stdout will make - // them share a single pfd, which will hold a lock for each program - // write, preventing any interleaving. - // - // It might be nice to set Stderr = Stdout always, or perhaps if - // we can tell they are the same file, but for now -v=json is - // a very clear signal. Making the two files the same may cause - // surprises if programs close os.Stdout but expect to be able - // to continue to write to os.Stderr, but it's hard to see why a - // test would think it could take over global state that way. - // - // This fix only helps programs where the output is coming directly - // from Go code. It does not help programs in which a subprocess is - // writing to stderr or stdout at the same time that a Go test is writing output. - // It also does not help when the output is coming from the runtime, - // such as when using the print/println functions, since that code writes - // directly to fd 2 without any locking. - // We keep realStderr around to prevent fd 2 from being closed. - // - // See go.dev/issue/33419. - realStderr = os.Stderr - os.Stderr = os.Stdout - } - - if *parallel < 1 { - fmt.Fprintln(os.Stderr, "testing: -parallel can only be given a positive integer") - flag.Usage() - m.exitCode = 2 - return - } - if *matchFuzz != "" && *fuzzCacheDir == "" { - fmt.Fprintln(os.Stderr, "testing: -test.fuzzcachedir must be set if -test.fuzz is set") - flag.Usage() - m.exitCode = 2 - return - } - - if *matchList != "" { - listTests(m.deps.MatchString, m.tests, m.benchmarks, m.fuzzTargets, m.examples) - m.exitCode = 0 - return - } - - if *shuffle != "off" { - var n int64 - var err error - if *shuffle == "on" { - n = time.Now().UnixNano() - } else { - n, err = strconv.ParseInt(*shuffle, 10, 64) - if err != nil { - fmt.Fprintln(os.Stderr, `testing: -shuffle should be "off", "on", or a valid integer:`, err) - m.exitCode = 2 - return - } - } - fmt.Println("-test.shuffle", n) - rng := rand.New(rand.NewSource(n)) - rng.Shuffle(len(m.tests), func(i, j int) { m.tests[i], m.tests[j] = m.tests[j], m.tests[i] }) - rng.Shuffle(len(m.benchmarks), func(i, j int) { m.benchmarks[i], m.benchmarks[j] = m.benchmarks[j], m.benchmarks[i] }) - } - - parseCpuList() - - m.before() - defer m.after() - - // Run tests, examples, and benchmarks unless this is a fuzz worker process. - // Workers start after this is done by their parent process, and they should - // not repeat this work. - if !*isFuzzWorker { - deadline := m.startAlarm() - haveExamples = len(m.examples) > 0 - testRan, testOk := runTests(m.deps.MatchString, m.tests, deadline) - fuzzTargetsRan, fuzzTargetsOk := runFuzzTests(m.deps, m.fuzzTargets, deadline) - exampleRan, exampleOk := runExamples(m.deps.MatchString, m.examples) - m.stopAlarm() - if !testRan && !exampleRan && !fuzzTargetsRan && *matchBenchmarks == "" && *matchFuzz == "" { - fmt.Fprintln(os.Stderr, "testing: warning: no tests to run") - if testingTesting && *match != "^$" { - // If this happens during testing of package testing it could be that - // package testing's own logic for when to run a test is broken, - // in which case every test will run nothing and succeed, - // with no obvious way to detect this problem (since no tests are running). - // So make 'no tests to run' a hard failure when testing package testing itself. - fmt.Print(chatty.prefix(), "FAIL: package testing must run tests\n") - testOk = false - } - } - anyFailed := !testOk || !exampleOk || !fuzzTargetsOk || !runBenchmarks(m.deps.ImportPath(), m.deps.MatchString, m.benchmarks) - if !anyFailed && race.Errors() > 0 { - fmt.Print(chatty.prefix(), "testing: race detected outside of test execution\n") - anyFailed = true - } - if anyFailed { - fmt.Print(chatty.prefix(), "FAIL\n") - m.exitCode = 1 - return - } - } - - fuzzingOk := runFuzzing(m.deps, m.fuzzTargets) - if !fuzzingOk { - fmt.Print(chatty.prefix(), "FAIL\n") - if *isFuzzWorker { - m.exitCode = fuzzWorkerExitCode - } else { - m.exitCode = 1 - } - return - } - - m.exitCode = 0 - if !*isFuzzWorker { - fmt.Print(chatty.prefix(), "PASS\n") - } - return -} - -func (t *T) report() { - if t.parent == nil { - return - } - dstr := fmtDuration(t.duration) - format := "--- %s: %s (%s)\n" - if t.Failed() { - t.flushToParent(t.name, format, "FAIL", t.name, dstr) - } else if t.chatty != nil { - if t.Skipped() { - t.flushToParent(t.name, format, "SKIP", t.name, dstr) - } else { - t.flushToParent(t.name, format, "PASS", t.name, dstr) - } - } -} - -func listTests(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, fuzzTargets []InternalFuzzTarget, examples []InternalExample) { - if _, err := matchString(*matchList, "non-empty"); err != nil { - fmt.Fprintf(os.Stderr, "testing: invalid regexp in -test.list (%q): %s\n", *matchList, err) - os.Exit(1) - } - - for _, test := range tests { - if ok, _ := matchString(*matchList, test.Name); ok { - fmt.Println(test.Name) - } - } - for _, bench := range benchmarks { - if ok, _ := matchString(*matchList, bench.Name); ok { - fmt.Println(bench.Name) - } - } - for _, fuzzTarget := range fuzzTargets { - if ok, _ := matchString(*matchList, fuzzTarget.Name); ok { - fmt.Println(fuzzTarget.Name) - } - } - for _, example := range examples { - if ok, _ := matchString(*matchList, example.Name); ok { - fmt.Println(example.Name) - } - } -} - -// RunTests is an internal function but exported because it is cross-package; -// it is part of the implementation of the "go test" command. -func RunTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ok bool) { - var deadline time.Time - if *timeout > 0 { - deadline = time.Now().Add(*timeout) - } - ran, ok := runTests(matchString, tests, deadline) - if !ran && !haveExamples { - fmt.Fprintln(os.Stderr, "testing: warning: no tests to run") - } - return ok -} - -func runTests(matchString func(pat, str string) (bool, error), tests []InternalTest, deadline time.Time) (ran, ok bool) { - ok = true - for _, procs := range cpuList { - runtime.GOMAXPROCS(procs) - for i := uint(0); i < *count; i++ { - if shouldFailFast() { - break - } - if i > 0 && !ran { - // There were no tests to run on the first - // iteration. This won't change, so no reason - // to keep trying. - break - } - ctx, cancelCtx := context.WithCancel(context.Background()) - tstate := newTestState(*parallel, newMatcher(matchString, *match, "-test.run", *skip)) - tstate.deadline = deadline - t := &T{ - common: common{ - signal: make(chan bool, 1), - barrier: make(chan bool), - w: os.Stdout, - ctx: ctx, - cancelCtx: cancelCtx, - }, - tstate: tstate, - } - if Verbose() { - t.chatty = newChattyPrinter(t.w) - } - tRunner(t, func(t *T) { - for _, test := range tests { - t.Run(test.Name, test.F) - } - }) - select { - case <-t.signal: - default: - panic("internal error: tRunner exited without sending on t.signal") - } - ok = ok && !t.Failed() - ran = ran || t.ran - } - } - return ran, ok -} - -// before runs before all testing. -func (m *M) before() { - if *memProfileRate > 0 { - runtime.MemProfileRate = *memProfileRate - } - if *cpuProfile != "" { - f, err := os.Create(toOutputDir(*cpuProfile)) - if err != nil { - fmt.Fprintf(os.Stderr, "testing: %s\n", err) - return - } - if err := m.deps.StartCPUProfile(f); err != nil { - fmt.Fprintf(os.Stderr, "testing: can't start cpu profile: %s\n", err) - f.Close() - return - } - // Could save f so after can call f.Close; not worth the effort. - } - if *traceFile != "" { - f, err := os.Create(toOutputDir(*traceFile)) - if err != nil { - fmt.Fprintf(os.Stderr, "testing: %s\n", err) - return - } - if err := trace.Start(f); err != nil { - fmt.Fprintf(os.Stderr, "testing: can't start tracing: %s\n", err) - f.Close() - return - } - // Could save f so after can call f.Close; not worth the effort. - } - if *blockProfile != "" && *blockProfileRate >= 0 { - runtime.SetBlockProfileRate(*blockProfileRate) - } - if *mutexProfile != "" && *mutexProfileFraction >= 0 { - runtime.SetMutexProfileFraction(*mutexProfileFraction) - } - if *coverProfile != "" && CoverMode() == "" { - fmt.Fprintf(os.Stderr, "testing: cannot use -test.coverprofile because test binary was not built with coverage enabled\n") - os.Exit(2) - } - if *gocoverdir != "" && CoverMode() == "" { - fmt.Fprintf(os.Stderr, "testing: cannot use -test.gocoverdir because test binary was not built with coverage enabled\n") - os.Exit(2) - } - if *testlog != "" { - // Note: Not using toOutputDir. - // This file is for use by cmd/go, not users. - var f *os.File - var err error - if m.numRun == 1 { - f, err = os.Create(*testlog) - } else { - f, err = os.OpenFile(*testlog, os.O_WRONLY, 0) - if err == nil { - f.Seek(0, io.SeekEnd) - } - } - if err != nil { - fmt.Fprintf(os.Stderr, "testing: %s\n", err) - os.Exit(2) - } - m.deps.StartTestLog(f) - testlogFile = f - } - if *panicOnExit0 { - m.deps.SetPanicOnExit0(true) - } -} - -// after runs after all testing. -func (m *M) after() { - m.afterOnce.Do(func() { - m.writeProfiles() - }) - - // Restore PanicOnExit0 after every run, because we set it to true before - // every run. Otherwise, if m.Run is called multiple times the behavior of - // os.Exit(0) will not be restored after the second run. - if *panicOnExit0 { - m.deps.SetPanicOnExit0(false) - } -} - -func (m *M) writeProfiles() { - if *testlog != "" { - if err := m.deps.StopTestLog(); err != nil { - fmt.Fprintf(os.Stderr, "testing: can't write %s: %s\n", *testlog, err) - os.Exit(2) - } - if err := testlogFile.Close(); err != nil { - fmt.Fprintf(os.Stderr, "testing: can't write %s: %s\n", *testlog, err) - os.Exit(2) - } - } - if *cpuProfile != "" { - m.deps.StopCPUProfile() // flushes profile to disk - } - if *traceFile != "" { - trace.Stop() // flushes trace to disk - } - if *memProfile != "" { - f, err := os.Create(toOutputDir(*memProfile)) - if err != nil { - fmt.Fprintf(os.Stderr, "testing: %s\n", err) - os.Exit(2) - } - runtime.GC() // materialize all statistics - if err = m.deps.WriteProfileTo("allocs", f, 0); err != nil { - fmt.Fprintf(os.Stderr, "testing: can't write %s: %s\n", *memProfile, err) - os.Exit(2) - } - f.Close() - } - if *blockProfile != "" && *blockProfileRate >= 0 { - f, err := os.Create(toOutputDir(*blockProfile)) - if err != nil { - fmt.Fprintf(os.Stderr, "testing: %s\n", err) - os.Exit(2) - } - if err = m.deps.WriteProfileTo("block", f, 0); err != nil { - fmt.Fprintf(os.Stderr, "testing: can't write %s: %s\n", *blockProfile, err) - os.Exit(2) - } - f.Close() - } - if *mutexProfile != "" && *mutexProfileFraction >= 0 { - f, err := os.Create(toOutputDir(*mutexProfile)) - if err != nil { - fmt.Fprintf(os.Stderr, "testing: %s\n", err) - os.Exit(2) - } - if err = m.deps.WriteProfileTo("mutex", f, 0); err != nil { - fmt.Fprintf(os.Stderr, "testing: can't write %s: %s\n", *mutexProfile, err) - os.Exit(2) - } - f.Close() - } - if CoverMode() != "" { - coverReport() - } -} - -// toOutputDir returns the file name relocated, if required, to outputDir. -// Simple implementation to avoid pulling in path/filepath. -func toOutputDir(path string) string { - if *outputDir == "" || path == "" { - return path - } - // On Windows, it's clumsy, but we can be almost always correct - // by just looking for a drive letter and a colon. - // Absolute paths always have a drive letter (ignoring UNC). - // Problem: if path == "C:A" and outputdir == "C:\Go" it's unclear - // what to do, but even then path/filepath doesn't help. - // TODO: Worth doing better? Probably not, because we're here only - // under the management of go test. - if runtime.GOOS == "windows" && len(path) >= 2 { - letter, colon := path[0], path[1] - if ('a' <= letter && letter <= 'z' || 'A' <= letter && letter <= 'Z') && colon == ':' { - // If path starts with a drive letter we're stuck with it regardless. - return path - } - } - if os.IsPathSeparator(path[0]) { - return path - } - return fmt.Sprintf("%s%c%s", *outputDir, os.PathSeparator, path) -} - -// startAlarm starts an alarm if requested. -func (m *M) startAlarm() time.Time { - if *timeout <= 0 { - return time.Time{} - } - - deadline := time.Now().Add(*timeout) - m.timer = time.AfterFunc(*timeout, func() { - m.after() - debug.SetTraceback("all") - extra := "" - - if list := runningList(); len(list) > 0 { - var b strings.Builder - b.WriteString("\nrunning tests:") - for _, name := range list { - b.WriteString("\n\t") - b.WriteString(name) - } - extra = b.String() - } - panic(fmt.Sprintf("test timed out after %v%s", *timeout, extra)) - }) - return deadline -} - -// runningList returns the list of running tests. -func runningList() []string { - var list []string - running.Range(func(k, v any) bool { - list = append(list, fmt.Sprintf("%s (%v)", k.(string), highPrecisionTimeSince(v.(highPrecisionTime)).Round(time.Second))) - return true - }) - slices.Sort(list) - return list -} - -// stopAlarm turns off the alarm. -func (m *M) stopAlarm() { - if *timeout > 0 { - m.timer.Stop() - } -} - -func parseCpuList() { - for _, val := range strings.Split(*cpuListStr, ",") { - val = strings.TrimSpace(val) - if val == "" { - continue - } - cpu, err := strconv.Atoi(val) - if err != nil || cpu <= 0 { - fmt.Fprintf(os.Stderr, "testing: invalid value %q for -test.cpu\n", val) - os.Exit(1) - } - cpuList = append(cpuList, cpu) - } - if cpuList == nil { - cpuList = append(cpuList, runtime.GOMAXPROCS(-1)) - } -} - -func shouldFailFast() bool { - return *failFast && numFailed.Load() > 0 -} diff --git a/runtime/internal/clite/tls/tls_stub.go b/runtime/internal/clite/tls/tls_stub.go new file mode 100644 index 00000000..f209bb5a --- /dev/null +++ b/runtime/internal/clite/tls/tls_stub.go @@ -0,0 +1,34 @@ +//go:build !llgo + +/* + * Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tls + +// Handle is a no-op TLS handle used when building without the llgo tag. +type Handle[T any] struct{} + +// Alloc returns a stub TLS handle that ignores all operations. +func Alloc[T any](func(*T)) Handle[T] { return Handle[T]{} } + +// Get always returns the zero value. +func (Handle[T]) Get() (zero T) { return zero } + +// Set is a no-op. +func (Handle[T]) Set(T) {} + +// Clear is a no-op. +func (Handle[T]) Clear() {} diff --git a/runtime/internal/runtime/defer_tls.go b/runtime/internal/runtime/defer_tls.go new file mode 100644 index 00000000..f56728cb --- /dev/null +++ b/runtime/internal/runtime/defer_tls.go @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package runtime + +import "github.com/goplus/llgo/runtime/internal/clite/tls" + +var deferTLS = tls.Alloc[*Defer](func(head **Defer) { + if head != nil { + *head = nil + } +}) + +// SetThreadDefer associates the current thread with the given defer chain. +func SetThreadDefer(head *Defer) { + deferTLS.Set(head) +} + +// GetThreadDefer returns the current thread's defer chain head. +func GetThreadDefer() *Defer { + return deferTLS.Get() +} + +// ClearThreadDefer resets the current thread's defer chain to nil. +func ClearThreadDefer() { + deferTLS.Clear() +} diff --git a/runtime/internal/runtime/z_defer_gc.go b/runtime/internal/runtime/z_defer_gc.go new file mode 100644 index 00000000..fdfd008b --- /dev/null +++ b/runtime/internal/runtime/z_defer_gc.go @@ -0,0 +1,33 @@ +//go:build !nogc + +/* + * Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package runtime + +import ( + "unsafe" + + c "github.com/goplus/llgo/runtime/internal/clite" + "github.com/goplus/llgo/runtime/internal/clite/bdwgc" +) + +// FreeDeferNode releases a defer argument node allocated from the Boehm heap. +func FreeDeferNode(ptr unsafe.Pointer) { + if ptr != nil { + bdwgc.Free(c.Pointer(ptr)) + } +} diff --git a/runtime/internal/runtime/z_defer_nogc.go b/runtime/internal/runtime/z_defer_nogc.go new file mode 100644 index 00000000..336462c8 --- /dev/null +++ b/runtime/internal/runtime/z_defer_nogc.go @@ -0,0 +1,32 @@ +//go:build nogc + +/* + * Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package runtime + +import ( + "unsafe" + + c "github.com/goplus/llgo/runtime/internal/clite" +) + +// FreeDeferNode releases the defer node when GC integration is disabled. +func FreeDeferNode(ptr unsafe.Pointer) { + if ptr != nil { + c.Free(ptr) + } +} diff --git a/runtime/overlay.go b/runtime/overlay.go index 5649223b..d69defd3 100644 --- a/runtime/overlay.go +++ b/runtime/overlay.go @@ -7,27 +7,11 @@ import ( //go:embed _overlay/runtime/runtime.go var fakeRuntime string -//go:embed _overlay/go/parser/resolver.go -var go_parser_resolver string - -//go:embed _overlay/testing/testing.go -var testing_testing string - -//go:embed _overlay/testing/testing_go123.go -var testing_testing_go123 string - -//go:embed _overlay/testing/testing_go124.go -var testing_testing_go124 string - //go:embed _overlay/net/textproto/textproto.go var net_textproto string var OverlayFiles = map[string]string{ "math/exp_amd64.go": "package math;", - "go/parser/resolver.go": go_parser_resolver, - "testing/testing.go": testing_testing, - "testing/testing_go123.go": testing_testing_go123, - "testing/testing_go124.go": testing_testing_go124, "net/textproto/textproto.go": net_textproto, "runtime/runtime.go": fakeRuntime, } diff --git a/ssa/decl.go b/ssa/decl.go index b877addb..bb298715 100644 --- a/ssa/decl.go +++ b/ssa/decl.go @@ -294,7 +294,7 @@ func (p Function) NewBuilder() Builder { // TODO(xsw): Finalize may cause panic, so comment it. // b.Finalize() return &aBuilder{b, nil, p, p.Pkg, prog, - make(map[Expr]dbgExpr), make(map[*types.Scope]DIScope)} + make(map[*types.Scope]DIScope)} } // HasBody reports whether the function has a body. diff --git a/ssa/di.go b/ssa/di.go index 5778cd4f..5b9acc3c 100644 --- a/ssa/di.go +++ b/ssa/di.go @@ -662,14 +662,8 @@ func (b diBuilder) createExpression(ops []uint64) DIExpression { // Copy to alloca'd memory to get declareable address. func (b Builder) constructDebugAddr(v Expr) (dbgPtr Expr, dbgVal Expr, exists bool) { - if v, ok := b.dbgVars[v]; ok { - return v.ptr, v.val, true - } t := v.Type.RawType().Underlying() dbgPtr, dbgVal = b.doConstructDebugAddr(v, t) - dbgExpr := dbgExpr{dbgPtr, dbgVal} - b.dbgVars[v] = dbgExpr - b.dbgVars[dbgVal] = dbgExpr return dbgPtr, dbgVal, false } @@ -874,11 +868,7 @@ func (b Builder) DebugFunction(f Function, pos token.Position, bodyPos token.Pos } func (b Builder) Param(idx int) Expr { - p := b.Func.Param(idx) - if v, ok := b.dbgVars[p]; ok { - return v.val - } - return p + return b.Func.Param(idx) } // ----------------------------------------------------------------------------- diff --git a/ssa/eh.go b/ssa/eh.go index bc866539..ffb48eaa 100644 --- a/ssa/eh.go +++ b/ssa/eh.go @@ -149,10 +149,6 @@ func (b Builder) Longjmp(jb, retval Expr) { // ----------------------------------------------------------------------------- -const ( - deferKey = "__llgo_defer" -) - func (p Function) deferInitBuilder() (b Builder, next BasicBlock) { b = p.NewBuilder() next = b.setBlockMoveLast(p.blks[0]) @@ -162,7 +158,6 @@ func (p Function) deferInitBuilder() (b Builder, next BasicBlock) { type aDefer struct { nextBit int // next defer bit - key Expr // pthread TLS key data Expr // pointer to runtime.Defer bitsPtr Expr // pointer to defer bits rethPtr Expr // next block of Rethrow @@ -174,30 +169,6 @@ type aDefer struct { stmts []func(bits Expr) } -func (p Package) keyInit(name string) { - keyVar := p.VarOf(name) - if keyVar == nil { - return - } - prog := p.Prog - keyVar.InitNil() - keyVar.impl.SetLinkage(llvm.LinkOnceAnyLinkage) - - b := p.afterBuilder() - eq := b.BinOp(token.EQL, b.Load(keyVar.Expr), prog.IntVal(0, prog.CInt())) - b.IfThen(eq, func() { - b.pthreadKeyCreate(keyVar.Expr, prog.Nil(prog.VoidPtr())) - }) -} - -func (p Package) newKey(name string) Global { - return p.NewVarEx(name, p.Prog.CIntPtr()) -} - -func (b Builder) deferKey() Expr { - return b.Load(b.Pkg.newKey(deferKey).Expr) -} - const ( // 0: addr sigjmpbuf // 1: bits uintptr @@ -230,17 +201,19 @@ func (b Builder) getDefer(kind DoAction) *aDefer { blks := self.MakeBlocks(2) procBlk, rethrowBlk := blks[0], blks[1] - key := b.deferKey() zero := prog.Val(uintptr(0)) - link := Expr{b.pthreadGetspecific(key).impl, prog.DeferPtr()} + link := b.Call(b.Pkg.rtFunc("GetThreadDefer")) jb := b.AllocaSigjmpBuf() - ptr := b.aggregateAlloca(prog.Defer(), jb.impl, zero.impl, link.impl, procBlk.Addr().impl) + ptr := b.aggregateAllocU(prog.Defer(), jb.impl, zero.impl, link.impl, procBlk.Addr().impl) deferData := Expr{ptr, prog.DeferPtr()} - b.pthreadSetspecific(key, deferData) + b.Call(b.Pkg.rtFunc("SetThreadDefer"), deferData) bitsPtr := b.FieldAddr(deferData, deferBits) rethPtr := b.FieldAddr(deferData, deferRethrow) rundPtr := b.FieldAddr(deferData, deferRunDefers) argsPtr := b.FieldAddr(deferData, deferArgs) + // Initialize the args list so later guards (e.g. DeferAlways/DeferInLoop) + // can safely detect an empty chain without a prior push. + b.Store(argsPtr, prog.Nil(prog.VoidPtr())) czero := prog.IntVal(0, prog.CInt()) retval := b.Sigsetjmp(jb, czero) @@ -253,7 +226,6 @@ func (b Builder) getDefer(kind DoAction) *aDefer { b.If(b.BinOp(token.EQL, retval, czero), next, panicBlk) self.defer_ = &aDefer{ - key: key, data: deferData, bitsPtr: bitsPtr, rethPtr: rethPtr, @@ -278,8 +250,7 @@ func (b Builder) getDefer(kind DoAction) *aDefer { // DeferData returns the defer data (*runtime.Defer). func (b Builder) DeferData() Expr { - key := b.deferKey() - return Expr{b.pthreadGetspecific(key).impl, b.Prog.DeferPtr()} + return b.Call(b.Pkg.rtFunc("GetThreadDefer")) } // Defer emits a defer instruction. @@ -294,14 +265,17 @@ func (b Builder) Defer(kind DoAction, fn Expr, args ...Expr) { case DeferInCond: prog = b.Prog next := self.nextBit + if uintptr(next) >= unsafe.Sizeof(uintptr(0))*8 { + panic("too many conditional defers") + } self.nextBit++ bits := b.Load(self.bitsPtr) nextbit = prog.Val(uintptr(1 << next)) b.Store(self.bitsPtr, b.BinOp(token.OR, bits, nextbit)) case DeferAlways: // nothing to do - default: - panic("todo: DeferInLoop is not supported - " + b.Func.Name()) + case DeferInLoop: + // Loop defers rely on a dedicated drain loop inserted below. } typ := b.saveDeferArgs(self, fn, args) self.stmts = append(self.stmts, func(bits Expr) { @@ -314,6 +288,29 @@ func (b Builder) Defer(kind DoAction, fn Expr, args ...Expr) { }) case DeferAlways: b.callDefer(self, typ, fn, args) + case DeferInLoop: + prog := b.Prog + condBlk := b.Func.MakeBlock() + bodyBlk := b.Func.MakeBlock() + exitBlk := b.Func.MakeBlock() + // Control flow: + // condBlk: check argsPtr for non-nil to see if there's work to drain. + // bodyBlk: execute a single defer node, then jump back to condBlk. + // exitBlk: reached when the list is empty (argsPtr == nil). + // This mirrors runtime's linked-list unwinding semantics for loop defers. + + // jump to condition check before executing + b.Jump(condBlk) + b.SetBlockEx(condBlk, AtEnd, true) + list := b.Load(self.argsPtr) + has := b.BinOp(token.NEQ, list, prog.Nil(prog.VoidPtr())) + b.If(has, bodyBlk, exitBlk) + + b.SetBlockEx(bodyBlk, AtEnd, true) + b.callDefer(self, typ, fn, args) + b.Jump(condBlk) + + b.SetBlockEx(exitBlk, AtEnd, true) } }) } @@ -354,7 +351,7 @@ func (b Builder) saveDeferArgs(self *aDefer, fn Expr, args []Expr) Type { flds[i+offset] = arg.impl } typ := prog.Struct(typs...) - ptr := Expr{b.aggregateMalloc(typ, flds...), prog.VoidPtr()} + ptr := Expr{b.aggregateAllocU(typ, flds...), prog.VoidPtr()} b.Store(self.argsPtr, ptr) return typ } @@ -365,19 +362,28 @@ func (b Builder) callDefer(self *aDefer, typ Type, fn Expr, args []Expr) { return } prog := b.Prog - ptr := b.Load(self.argsPtr) - data := b.Load(Expr{ptr.impl, prog.Pointer(typ)}) - offset := 1 - b.Store(self.argsPtr, Expr{b.getField(data, 0).impl, prog.VoidPtr()}) - if fn.kind == vkClosure { - fn = b.getField(data, 1) - offset++ - } - for i := 0; i < len(args); i++ { - args[i] = b.getField(data, i+offset) - } - b.Call(fn, args...) - b.free(ptr) + zero := prog.Nil(prog.VoidPtr()) + list := b.Load(self.argsPtr) + has := b.BinOp(token.NEQ, list, zero) + // The guard is required because callDefer is reused by endDefer() after the + // list has been drained. Without this check we would dereference a nil + // pointer when no loop defers were recorded. + b.IfThen(has, func() { + ptr := b.Load(self.argsPtr) + data := b.Load(Expr{ptr.impl, prog.Pointer(typ)}) + offset := 1 + b.Store(self.argsPtr, Expr{b.getField(data, 0).impl, prog.VoidPtr()}) + callFn := fn + if callFn.kind == vkClosure { + callFn = b.getField(data, 1) + offset++ + } + for i := 0; i < len(args); i++ { + args[i] = b.getField(data, i+offset) + } + b.Call(callFn, args...) + b.Call(b.Pkg.rtFunc("FreeDeferNode"), ptr) + }) } // RunDefers emits instructions to run deferred instructions. @@ -431,7 +437,7 @@ func (p Function) endDefer(b Builder) { } } link := b.getField(b.Load(self.data), deferLink) - b.pthreadSetspecific(self.key, link) + b.Call(b.Pkg.rtFunc("SetThreadDefer"), link) b.IndirectJump(b.Load(rundPtr), nexts) b.SetBlockEx(panicBlk, AtEnd, false) // panicBlk: exec runDefers and rethrow diff --git a/ssa/eh_loop_test.go b/ssa/eh_loop_test.go new file mode 100644 index 00000000..8125ce67 --- /dev/null +++ b/ssa/eh_loop_test.go @@ -0,0 +1,53 @@ +//go:build !llgo + +/* + * Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ssa_test + +import ( + "strings" + "testing" + + "github.com/goplus/llgo/ssa" + "github.com/goplus/llgo/ssa/ssatest" +) + +func TestDeferInLoopIR(t *testing.T) { + prog := ssatest.NewProgram(t, nil) + pkg := prog.NewPackage("foo", "foo") + + callee := pkg.NewFunc("callee", ssa.NoArgsNoRet, ssa.InGo) + cb := callee.MakeBody(1) + cb.Return() + cb.EndBuild() + + fn := pkg.NewFunc("main", ssa.NoArgsNoRet, ssa.InGo) + b := fn.MakeBody(1) + fn.SetRecover(fn.MakeBlock()) + + // Ensure entry block has a terminator like real codegen + b.Return() + b.SetBlockEx(fn.Block(0), ssa.BeforeLast, true) + + b.Defer(ssa.DeferInLoop, callee.Expr) + b.EndBuild() + + ir := pkg.Module().String() + if !strings.Contains(ir, "icmp ne ptr") { + t.Fatalf("expected loop defer condition in IR, got:\n%s", ir) + } +} diff --git a/ssa/package.go b/ssa/package.go index 7a3daccf..68494ebd 100644 --- a/ssa/package.go +++ b/ssa/package.go @@ -798,7 +798,6 @@ func (p Package) afterBuilder() Builder { // AfterInit is called after the package is initialized (init all packages that depends on). func (p Package) AfterInit(b Builder, ret BasicBlock) { - p.keyInit(deferKey) doAfterb := p.afterb != nil doPyLoadModSyms := p.pyHasModSyms() if doAfterb || doPyLoadModSyms { diff --git a/ssa/ssa_test.go b/ssa/ssa_test.go index 1757101a..bd66374f 100644 --- a/ssa/ssa_test.go +++ b/ssa/ssa_test.go @@ -60,6 +60,42 @@ func TestUnsafeString(t *testing.T) { b.Return() } +func TestTooManyConditionalDefers(t *testing.T) { + wd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + os.Chdir("../../runtime") + defer os.Chdir(wd) + + prog := NewProgram(nil) + prog.SetRuntime(func() *types.Package { + fset := token.NewFileSet() + imp := packages.NewImporter(fset) + pkg, _ := imp.Import(PkgRuntime) + return pkg + }) + + pkg := prog.NewPackage("foo", "foo") + target := pkg.NewFunc("f", NoArgsNoRet, InGo) + fn := pkg.NewFunc("main", NoArgsNoRet, InGo) + fn.SetRecover(fn.MakeBlock()) + b := fn.MakeBody(1) + + defer func() { + if r := recover(); r == nil { + t.Fatal("expected panic: too many conditional defers") + } else if r != "too many conditional defers" { + t.Fatalf("unexpected panic: %v", r) + } + }() + + b.Return() + for i := 0; i < 65; i++ { + b.Defer(DeferInCond, target.Expr) + } +} + func TestPointerSize(t *testing.T) { expected := unsafe.Sizeof(uintptr(0)) if size := NewProgram(nil).PointerSize(); size != int(expected) { diff --git a/ssa/stmt_builder.go b/ssa/stmt_builder.go index 10128ce6..bd7d93e2 100644 --- a/ssa/stmt_builder.go +++ b/ssa/stmt_builder.go @@ -57,11 +57,6 @@ func (p BasicBlock) Addr() Expr { // ----------------------------------------------------------------------------- -type dbgExpr struct { - ptr Expr - val Expr -} - type aBuilder struct { impl llvm.Builder blk BasicBlock @@ -69,7 +64,6 @@ type aBuilder struct { Pkg Package Prog Program - dbgVars map[Expr]dbgExpr // save copied address and values for debug info diScopeCache map[*types.Scope]DIScope // avoid duplicated DILexicalBlock(s) } diff --git a/test/defer_test.go b/test/defer_test.go new file mode 100644 index 00000000..ada51747 --- /dev/null +++ b/test/defer_test.go @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package test + +import ( + "reflect" + "strconv" + "testing" +) + +// runLoopDefers exercises a defer statement inside a loop and relies on +// defers executing after the loop but before the function returns. +func runLoopDefers() (result []int) { + for i := 0; i < 3; i++ { + v := i + defer func() { + result = append(result, v) + }() + } + return +} + +func runLoopDeferCount(n int) (count int) { + for i := 0; i < n; i++ { + defer func() { + count++ + }() + } + return +} + +func runDeferRecover() (recovered any, ran bool) { + defer func() { + recovered = recover() + ran = true + }() + panic("defer recover sentinel") +} + +func runNestedLoopDeferOrder() (order []string) { + outerNestedLoop(&order) + return +} + +func outerNestedLoop(order *[]string) { + for i := 0; i < 3; i++ { + v := i + label := "outer:" + strconv.Itoa(v) + defer func(label string) { + *order = append(*order, label) + }(label) + } + middleNestedLoop(order) +} + +func middleNestedLoop(order *[]string) { + for i := 0; i < 2; i++ { + v := i + label := "middle:" + strconv.Itoa(v) + defer func(label string) { + *order = append(*order, label) + }(label) + } + innerNestedLoop(order) +} + +func innerNestedLoop(order *[]string) { + for i := 0; i < 4; i++ { + v := i + label := "inner:" + strconv.Itoa(v) + defer func(label string) { + *order = append(*order, label) + }(label) + } +} + +func TestDeferInLoopOrder(t *testing.T) { + got := runLoopDefers() + want := []int{2, 1, 0} + if !reflect.DeepEqual(got, want) { + t.Fatalf("unexpected defer order: got %v, want %v", got, want) + } +} + +func TestDeferLoopStress(t *testing.T) { + const n = 1_000_000 + if got := runLoopDeferCount(n); got != n { + t.Fatalf("unexpected count: got %d, want %d", got, n) + } +} + +func TestDeferRecoverHandlesPanic(t *testing.T) { + got, ran := runDeferRecover() + want := "defer recover sentinel" + if !ran { + t.Fatalf("recover defer not executed") + } + str, ok := got.(string) + if !ok { + t.Fatalf("recover returned %T, want string", got) + } + if str != want { + t.Fatalf("unexpected recover value: got %q, want %q", str, want) + } +} + +func TestNestedDeferLoops(t *testing.T) { + got := runNestedLoopDeferOrder() + want := []string{ + "inner:3", "inner:2", "inner:1", "inner:0", + "middle:1", "middle:0", + "outer:2", "outer:1", "outer:0", + } + if !reflect.DeepEqual(got, want) { + t.Fatalf("unexpected nested order: got %v, want %v", got, want) + } +} + +func runNestedConditionalDeferWithRecover() (order []string, recovered any) { + defer func() { recovered = recover() }() + nestedCondOuter(&order) + return +} + +func nestedCondOuter(order *[]string) { + for i := 0; i < 3; i++ { + v := i + label := "outer:" + strconv.Itoa(v) + if v%2 == 0 { + defer func(label string) { + *order = append(*order, label) + }(label) + } + nestedCondMiddle(order, v) + } +} + +func nestedCondMiddle(order *[]string, v int) { + for j := 0; j < 3; j++ { + u := j + label := "middle:" + strconv.Itoa(u) + if u < 2 { + defer func(label string) { + *order = append(*order, label) + }(label) + } + nestedCondInner(order) + } + if v == 1 { + panic("nested-conditional-boom") + } +} + +func nestedCondInner(order *[]string) { + for k := 0; k < 2; k++ { + w := k + label := "inner:" + strconv.Itoa(w) + defer func(label string) { + *order = append(*order, label) + }(label) + } +} + +func TestNestedConditionalDeferWithRecover(t *testing.T) { + gotOrder, gotRecovered := runNestedConditionalDeferWithRecover() + wantRecovered := "nested-conditional-boom" + if s, ok := gotRecovered.(string); !ok || s != wantRecovered { + t.Fatalf("unexpected recover value: got %v, want %q", gotRecovered, wantRecovered) + } + wantOrder := []string{ + "inner:1", "inner:0", + "inner:1", "inner:0", + "inner:1", "inner:0", + "middle:1", "middle:0", + "inner:1", "inner:0", + "inner:1", "inner:0", + "inner:1", "inner:0", + "middle:1", "middle:0", + "outer:0", + } + if !reflect.DeepEqual(gotOrder, wantOrder) { + t.Fatalf("unexpected nested conditional order: got %v, want %v", gotOrder, wantOrder) + } +} + +func callWithRecover(fn func()) (recovered any) { + defer func() { recovered = recover() }() + fn() + return +} + +func loopBranchEven(order *[]string, i int) { + label := "even:" + strconv.Itoa(i) + defer func() { *order = append(*order, label) }() +} + +func loopBranchOddNoRecover(order *[]string, i int) { + label := "odd-wrap:" + strconv.Itoa(i) + defer func() { *order = append(*order, label) }() + panic("odd-no-recover") +} + +func loopBranchOddLocalRecover(order *[]string, i int) { + label := "odd-local:" + strconv.Itoa(i) + defer func() { *order = append(*order, label) }() + defer func() { _ = recover() }() + panic("odd-local-recover") +} + +func runLoopBranchRecoverMixed(n int) (order []string, recoveredVals []any) { + for i := 0; i < n; i++ { + if i%2 == 0 { + loopBranchEven(&order, i) + } else if i%4 == 3 { + rec := callWithRecover(func() { loopBranchOddNoRecover(&order, i) }) + recoveredVals = append(recoveredVals, rec) + } else { + loopBranchOddLocalRecover(&order, i) + } + } + return +} + +func TestLoopBranchRecoverMixed(t *testing.T) { + order, recovered := runLoopBranchRecoverMixed(6) + wantOrder := []string{ + "even:0", + "odd-local:1", + "even:2", + "odd-wrap:3", + "even:4", + "odd-local:5", + } + if !reflect.DeepEqual(order, wantOrder) { + t.Fatalf("unexpected loop/branch order: got %v, want %v", order, wantOrder) + } + if len(recovered) != 1 { + t.Fatalf("unexpected recovered count: got %d, want %d", len(recovered), 1) + } + if s, ok := recovered[0].(string); !ok || s != "odd-no-recover" { + t.Fatalf("unexpected recovered value: got %v, want %q", recovered[0], "odd-no-recover") + } +} + +func deepInner(order *[]string) { + for i := 0; i < 2; i++ { + idx := i + label := "inner:" + strconv.Itoa(idx) + defer func(label string) { + *order = append(*order, label) + }(label) + if idx == 0 { + continue + } + panic("deep-boom") + } +} + +func deepMiddle(order *[]string) { + for i := 0; i < 2; i++ { + idx := i + label := "middle:" + strconv.Itoa(idx) + defer func(label string) { + *order = append(*order, label) + }(label) + if idx == 0 { + continue + } + defer func() { + if rec := recover(); rec != nil { + panic(rec) + } + }() + deepInner(order) + } +} + +func deepOuter(order *[]string) (recovered any) { + for i := 0; i < 2; i++ { + idx := i + label := "outer:" + strconv.Itoa(idx) + defer func(label string) { + *order = append(*order, label) + }(label) + if idx == 0 { + continue + } + defer func() { + if rec := recover(); rec != nil { + recovered = rec + } + }() + deepMiddle(order) + } + return +} + +func TestPanicCrossTwoFunctionsRecover(t *testing.T) { + var order []string + recovered := deepOuter(&order) + if s, ok := recovered.(string); !ok || s != "deep-boom" { + t.Fatalf("unexpected recovered value: got %v, want %q", recovered, "deep-boom") + } + wantOrder := []string{ + "inner:1", "inner:0", + "middle:1", "middle:0", + "outer:1", "outer:0", + } + if !reflect.DeepEqual(order, wantOrder) { + t.Fatalf("unexpected cross-function defer order: got %v, want %v", order, wantOrder) + } +}