mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-11 22:43:40 +00:00
runtime, testing: support Goexit, SkipNow, FailNow
Keep a pending Goexit separate from the active panic state so recovered deferred panics do not cancel the original Goexit unwind. Goexit should also ignore -panic=trap, since it is not a panic. Wire testing.FailNow and SkipNow through runtime.Goexit, and run tests and benchmarks in goroutines. This lets Goexit terminate the user function without skipping test bookkeeping. Add Goexit/recover coverage and enable crypto/ecdh in the Linux stdlib test list.
This commit is contained in:
@@ -413,6 +413,7 @@ TEST_PACKAGES_LINUX := \
|
|||||||
context \
|
context \
|
||||||
crypto/aes \
|
crypto/aes \
|
||||||
crypto/des \
|
crypto/des \
|
||||||
|
crypto/ecdh \
|
||||||
crypto/hmac \
|
crypto/hmac \
|
||||||
debug/dwarf \
|
debug/dwarf \
|
||||||
debug/plan9obj \
|
debug/plan9obj \
|
||||||
|
|||||||
+7
-3
@@ -116,8 +116,8 @@ func (b *builder) createLandingPad() {
|
|||||||
func (b *builder) createCheckpoint(ptr llvm.Value) llvm.Value {
|
func (b *builder) createCheckpoint(ptr llvm.Value) llvm.Value {
|
||||||
// Construct inline assembly equivalents of setjmp.
|
// Construct inline assembly equivalents of setjmp.
|
||||||
// The assembly works as follows:
|
// The assembly works as follows:
|
||||||
// * All registers (both callee-saved and caller saved) are clobbered
|
// * Registers are either clobbered or, on 386, saved for longjmp to
|
||||||
// after the inline assembly returns.
|
// restore if the ABI requires them to survive calls.
|
||||||
// * The assembly stores the address just past the end of the assembly
|
// * The assembly stores the address just past the end of the assembly
|
||||||
// into the jump buffer.
|
// into the jump buffer.
|
||||||
// * The return value (eax, rax, r0, etc) is set to zero in the inline
|
// * The return value (eax, rax, r0, etc) is set to zero in the inline
|
||||||
@@ -130,8 +130,12 @@ func (b *builder) createCheckpoint(ptr llvm.Value) llvm.Value {
|
|||||||
asmString = `
|
asmString = `
|
||||||
xorl %eax, %eax
|
xorl %eax, %eax
|
||||||
movl $$1f, 4(%ebx)
|
movl $$1f, 4(%ebx)
|
||||||
|
movl %ebx, 8(%ebx)
|
||||||
|
movl %esi, 12(%ebx)
|
||||||
|
movl %edi, 16(%ebx)
|
||||||
|
movl %ebp, 20(%ebx)
|
||||||
1:`
|
1:`
|
||||||
constraints = "={eax},{ebx},~{ebx},~{ecx},~{edx},~{esi},~{edi},~{ebp},~{xmm0},~{xmm1},~{xmm2},~{xmm3},~{xmm4},~{xmm5},~{xmm6},~{xmm7},~{fpsr},~{fpcr},~{flags},~{dirflag},~{memory}"
|
constraints = "={eax},{ebx},~{ecx},~{edx},~{xmm0},~{xmm1},~{xmm2},~{xmm3},~{xmm4},~{xmm5},~{xmm6},~{xmm7},~{fpsr},~{fpcr},~{flags},~{dirflag},~{memory}"
|
||||||
// This doesn't include the floating point stack because TinyGo uses
|
// This doesn't include the floating point stack because TinyGo uses
|
||||||
// newer floating point instructions.
|
// newer floating point instructions.
|
||||||
case "x86_64":
|
case "x86_64":
|
||||||
|
|||||||
+3
-3
@@ -3,7 +3,7 @@ source_filename = "defer.go"
|
|||||||
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
|
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
|
||||||
target triple = "thumbv7m-unknown-unknown-eabi"
|
target triple = "thumbv7m-unknown-unknown-eabi"
|
||||||
|
|
||||||
%runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i8, %runtime._interface, ptr }
|
%runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i8, %runtime._interface, ptr, i1 }
|
||||||
%runtime._interface = type { ptr, ptr }
|
%runtime._interface = type { ptr, ptr }
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
@@ -111,9 +111,9 @@ rundefers.end3: ; preds = %rundefers.loophead6
|
|||||||
; Function Attrs: nocallback nofree nosync nounwind willreturn
|
; Function Attrs: nocallback nofree nosync nounwind willreturn
|
||||||
declare ptr @llvm.stacksave.p0() #2
|
declare ptr @llvm.stacksave.p0() #2
|
||||||
|
|
||||||
declare void @runtime.setupDeferFrame(ptr dereferenceable_or_null(28), ptr, ptr) #1
|
declare void @runtime.setupDeferFrame(ptr dereferenceable_or_null(32), ptr, ptr) #1
|
||||||
|
|
||||||
declare void @runtime.destroyDeferFrame(ptr dereferenceable_or_null(28), ptr) #1
|
declare void @runtime.destroyDeferFrame(ptr dereferenceable_or_null(32), ptr) #1
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
define internal void @"main.deferSimple$1"(ptr %context) unnamed_addr #0 {
|
define internal void @"main.deferSimple$1"(ptr %context) unnamed_addr #0 {
|
||||||
|
|||||||
@@ -473,6 +473,12 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
|
|||||||
options.Directory = path
|
options.Directory = path
|
||||||
pkgName = "."
|
pkgName = "."
|
||||||
}
|
}
|
||||||
|
isWebAssembly := strings.HasPrefix(options.Target, "wasi") ||
|
||||||
|
strings.HasPrefix(options.Target, "wasm") ||
|
||||||
|
strings.HasPrefix(options.GOARCH, "wasm")
|
||||||
|
if name == "testing.go" && isWebAssembly {
|
||||||
|
expectedOutputPath = TESTDATA + "/testing-wasm.txt"
|
||||||
|
}
|
||||||
|
|
||||||
config, err := builder.NewConfig(&options)
|
config, err := builder.NewConfig(&options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -948,6 +954,32 @@ func checkOutputData(t *testing.T, expectedOutput, actual []byte) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGoexitCrash(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
options := optionsFromTarget("", sema)
|
||||||
|
config, err := builder.NewConfig(&options)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
output := &bytes.Buffer{}
|
||||||
|
_, err = buildAndRun("testdata/goexit.go", config, output, nil, nil, time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error {
|
||||||
|
cmd.Stdout = nil
|
||||||
|
cmd.Stderr = nil
|
||||||
|
data, err := cmd.CombinedOutput()
|
||||||
|
output.Write(data)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("program unexpectedly exited successfully")
|
||||||
|
}
|
||||||
|
const want = "panic: panic after Goexit"
|
||||||
|
if !strings.Contains(output.String(), want) {
|
||||||
|
t.Fatalf("output does not contain %q:\n%s", want, output.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTest(t *testing.T) {
|
func TestTest(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ const GOARCH = "386"
|
|||||||
// The bitness of the CPU (e.g. 8, 32, 64).
|
// The bitness of the CPU (e.g. 8, 32, 64).
|
||||||
const TargetBits = 32
|
const TargetBits = 32
|
||||||
|
|
||||||
const deferExtraRegs = 0
|
const deferExtraRegs = 4
|
||||||
|
|
||||||
const callInstSize = 5 // "call someFunction" is 5 bytes
|
const callInstSize = 5 // "call someFunction" is 5 bytes
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ tinygo_longjmp:
|
|||||||
// Note: the code we jump to assumes eax is set to a non-zero value if we
|
// Note: the code we jump to assumes eax is set to a non-zero value if we
|
||||||
// jump from here.
|
// jump from here.
|
||||||
movl 4(%esp), %eax
|
movl 4(%esp), %eax
|
||||||
|
movl 8(%eax), %ebx
|
||||||
|
movl 12(%eax), %esi
|
||||||
|
movl 16(%eax), %edi
|
||||||
|
movl 20(%eax), %ebp
|
||||||
movl 0(%eax), %esp // jumpSP
|
movl 0(%eax), %esp // jumpSP
|
||||||
movl 4(%eax), %eax // jumpPC (stash in volatile register)
|
movl 4(%eax), %eax // jumpPC (stash in volatile register)
|
||||||
jmpl *%eax
|
jmpl *%eax
|
||||||
|
|||||||
+11
-1
@@ -41,6 +41,7 @@ type deferFrame struct {
|
|||||||
Panicking panicState // not panicking, panicking, or in Goexit
|
Panicking panicState // not panicking, panicking, or in Goexit
|
||||||
PanicValue interface{} // panic value, might be nil for panic(nil) for example
|
PanicValue interface{} // panic value, might be nil for panic(nil) for example
|
||||||
DeferPtr unsafe.Pointer // head of the stack-allocated defer list
|
DeferPtr unsafe.Pointer // head of the stack-allocated defer list
|
||||||
|
Goexit bool // whether Goexit is still pending after a recovered deferred panic
|
||||||
}
|
}
|
||||||
|
|
||||||
type panicState uint8
|
type panicState uint8
|
||||||
@@ -57,7 +58,7 @@ func _panic(message interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func panicOrGoexit(message interface{}, panicking panicState) {
|
func panicOrGoexit(message interface{}, panicking panicState) {
|
||||||
if panicStrategy() == tinygo.PanicStrategyTrap {
|
if panicking != panicGoexit && panicStrategy() == tinygo.PanicStrategyTrap {
|
||||||
trap()
|
trap()
|
||||||
}
|
}
|
||||||
// Note: recover is not supported inside interrupts.
|
// Note: recover is not supported inside interrupts.
|
||||||
@@ -65,6 +66,9 @@ func panicOrGoexit(message interface{}, panicking panicState) {
|
|||||||
if supportsRecover() && !interrupt.In() {
|
if supportsRecover() && !interrupt.In() {
|
||||||
frame := (*deferFrame)(task.Current().DeferFrame)
|
frame := (*deferFrame)(task.Current().DeferFrame)
|
||||||
if frame != nil {
|
if frame != nil {
|
||||||
|
if panicking == panicGoexit {
|
||||||
|
frame.Goexit = true
|
||||||
|
}
|
||||||
frame.PanicValue = message
|
frame.PanicValue = message
|
||||||
frame.Panicking = panicking
|
frame.Panicking = panicking
|
||||||
tinygo_longjmp(frame)
|
tinygo_longjmp(frame)
|
||||||
@@ -138,6 +142,7 @@ func setupDeferFrame(frame *deferFrame, jumpSP unsafe.Pointer) {
|
|||||||
frame.JumpSP = jumpSP
|
frame.JumpSP = jumpSP
|
||||||
frame.Panicking = panicFalse
|
frame.Panicking = panicFalse
|
||||||
frame.DeferPtr = nil
|
frame.DeferPtr = nil
|
||||||
|
frame.Goexit = false
|
||||||
currentTask.DeferFrame = unsafe.Pointer(frame)
|
currentTask.DeferFrame = unsafe.Pointer(frame)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,6 +159,11 @@ func destroyDeferFrame(frame *deferFrame) {
|
|||||||
// Re-raise the panic now.
|
// Re-raise the panic now.
|
||||||
panicOrGoexit(frame.PanicValue, frame.Panicking)
|
panicOrGoexit(frame.PanicValue, frame.Panicking)
|
||||||
}
|
}
|
||||||
|
if frame.Goexit {
|
||||||
|
// A deferred function panicked during Goexit, and that panic was
|
||||||
|
// recovered. Continue the original Goexit instead of returning.
|
||||||
|
panicOrGoexit(nil, panicGoexit)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// _recover is the built-in recover() function. It tries to recover a currently
|
// _recover is the built-in recover() function. It tries to recover a currently
|
||||||
|
|||||||
@@ -179,7 +179,12 @@ func (b *B) run1() bool {
|
|||||||
ctx.maxLen = n + 8 // Add additional slack to avoid too many jumps in size.
|
ctx.maxLen = n + 8 // Add additional slack to avoid too many jumps in size.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b.runN(1)
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
b.runN(1)
|
||||||
|
}()
|
||||||
|
<-done
|
||||||
return !b.hasSub
|
return !b.hasSub
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,8 +200,12 @@ func (b *B) run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *B) doBench() BenchmarkResult {
|
func (b *B) doBench() BenchmarkResult {
|
||||||
// in upstream, this uses a goroutine
|
done := make(chan struct{})
|
||||||
b.launch()
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
b.launch()
|
||||||
|
}()
|
||||||
|
<-done
|
||||||
return b.result
|
return b.result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+15
-11
@@ -214,9 +214,8 @@ func (c *common) Failed() bool {
|
|||||||
// current goroutine).
|
// current goroutine).
|
||||||
func (c *common) FailNow() {
|
func (c *common) FailNow() {
|
||||||
c.Fail()
|
c.Fail()
|
||||||
|
|
||||||
c.finished = true
|
c.finished = true
|
||||||
c.Error("FailNow is incomplete, requires runtime.Goexit()")
|
runtime.Goexit()
|
||||||
}
|
}
|
||||||
|
|
||||||
// log generates the output.
|
// log generates the output.
|
||||||
@@ -288,7 +287,7 @@ func (c *common) Skipf(format string, args ...any) {
|
|||||||
func (c *common) SkipNow() {
|
func (c *common) SkipNow() {
|
||||||
c.skip()
|
c.skip()
|
||||||
c.finished = true
|
c.finished = true
|
||||||
c.Error("SkipNow is incomplete, requires runtime.Goexit()")
|
runtime.Goexit()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *common) skip() {
|
func (c *common) skip() {
|
||||||
@@ -480,19 +479,19 @@ type InternalTest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func tRunner(t *T, fn func(t *T)) {
|
func tRunner(t *T, fn func(t *T)) {
|
||||||
|
t.start = time.Now()
|
||||||
defer func() {
|
defer func() {
|
||||||
|
t.duration += time.Since(t.start) // TODO: capture cleanup time, too.
|
||||||
t.runCleanup()
|
t.runCleanup()
|
||||||
|
t.report() // Report after all subtests have finished.
|
||||||
|
if t.parent != nil && !t.hasSub {
|
||||||
|
t.setRan()
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Run the test.
|
// Run the test.
|
||||||
t.start = time.Now()
|
|
||||||
fn(t)
|
fn(t)
|
||||||
t.duration += time.Since(t.start) // TODO: capture cleanup time, too.
|
t.finished = true
|
||||||
|
|
||||||
t.report() // Report after all subtests have finished.
|
|
||||||
if t.parent != nil && !t.hasSub {
|
|
||||||
t.setRan()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run runs f as a subtest of t called name. It waits until the subtest is finished
|
// Run runs f as a subtest of t called name. It waits until the subtest is finished
|
||||||
@@ -524,7 +523,12 @@ func (t *T) Run(name string, f func(t *T)) bool {
|
|||||||
fmt.Fprintf(t.output, "=== RUN %s\n", sub.name)
|
fmt.Fprintf(t.output, "=== RUN %s\n", sub.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
tRunner(&sub, f)
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
tRunner(&sub, f)
|
||||||
|
}()
|
||||||
|
<-done
|
||||||
return !sub.failed
|
return !sub.failed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "runtime"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
defer func() {
|
||||||
|
panic("panic after Goexit")
|
||||||
|
}()
|
||||||
|
runtime.Goexit()
|
||||||
|
}
|
||||||
Vendored
+65
@@ -158,6 +158,71 @@ func runtimeGoexit() {
|
|||||||
runtime.Goexit()
|
runtime.Goexit()
|
||||||
}()
|
}()
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
runtimeGoexitRecoveredPanic()
|
||||||
|
println("unreachable after Goexit helper")
|
||||||
|
}()
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
runtimeGoexitNestedRecoveredPanic()
|
||||||
|
println("unreachable after nested Goexit helper")
|
||||||
|
}()
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
runtimeGoexitLoopDefers()
|
||||||
|
println("unreachable after loop Goexit helper")
|
||||||
|
}()
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func runtimeGoexitRecoveredPanic() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
printitf("Goexit recovered deferred panic:", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
defer func() {
|
||||||
|
panic("panic during Goexit")
|
||||||
|
}()
|
||||||
|
runtime.Goexit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func runtimeGoexitNestedRecoveredPanic() {
|
||||||
|
defer func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
printitf("Goexit nested recovered deferred panic:", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
panic("nested panic during Goexit")
|
||||||
|
}()
|
||||||
|
runtime.Goexit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func runtimeGoexitLoopDefers() {
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
defer func() {
|
||||||
|
println("Goexit loop defer")
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
printitf("Goexit loop recovered deferred panic:", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
defer func() {
|
||||||
|
panic("loop panic during Goexit")
|
||||||
|
}()
|
||||||
|
runtime.Goexit()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that a repanic inside a deferred function propagates correctly
|
// Test that a repanic inside a deferred function propagates correctly
|
||||||
|
|||||||
Vendored
+5
@@ -33,6 +33,11 @@ indirect recover returned: indirect panic
|
|||||||
|
|
||||||
# runtime.Goexit
|
# runtime.Goexit
|
||||||
Goexit deferred function, recover is nil: true
|
Goexit deferred function, recover is nil: true
|
||||||
|
Goexit recovered deferred panic: panic during Goexit
|
||||||
|
Goexit nested recovered deferred panic: nested panic during Goexit
|
||||||
|
Goexit loop recovered deferred panic: loop panic during Goexit
|
||||||
|
Goexit loop defer
|
||||||
|
Goexit loop defer
|
||||||
|
|
||||||
# repanic
|
# repanic
|
||||||
inner, repanicking
|
inner, repanicking
|
||||||
|
|||||||
Vendored
+21
@@ -0,0 +1,21 @@
|
|||||||
|
--- FAIL: TestBar (0.00s)
|
||||||
|
log Bar
|
||||||
|
log g
|
||||||
|
h
|
||||||
|
i
|
||||||
|
|
||||||
|
--- FAIL: TestBar/Bar2 (0.00s)
|
||||||
|
log Bar2
|
||||||
|
a
|
||||||
|
b
|
||||||
|
c
|
||||||
|
failed
|
||||||
|
after failed
|
||||||
|
log Bar end
|
||||||
|
--- FAIL: TestAllLowercase (0.00s)
|
||||||
|
--- FAIL: TestAllLowercase/BETA (0.00s)
|
||||||
|
expected lowercase name, got BETA
|
||||||
|
--- FAIL: TestAllLowercase/BELTA (0.00s)
|
||||||
|
expected lowercase name, got BELTA
|
||||||
|
FAIL
|
||||||
|
exitcode: 1
|
||||||
Vendored
+12
-1
@@ -6,6 +6,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"io"
|
"io"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -25,11 +26,21 @@ func TestBar(t *testing.T) {
|
|||||||
t.Log("after failed")
|
t.Log("after failed")
|
||||||
})
|
})
|
||||||
t.Run("Bar3", func(t *testing.T) {})
|
t.Run("Bar3", func(t *testing.T) {})
|
||||||
|
if runtime.GOARCH != "wasm" {
|
||||||
|
t.Run("Bar4", func(t *testing.T) {
|
||||||
|
t.Fatal("fatal")
|
||||||
|
t.Log("after fatal")
|
||||||
|
})
|
||||||
|
t.Run("Bar5", func(t *testing.T) {
|
||||||
|
t.SkipNow()
|
||||||
|
t.Error("after skip")
|
||||||
|
})
|
||||||
|
}
|
||||||
t.Log("log Bar end")
|
t.Log("log Bar end")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAllLowercase(t *testing.T) {
|
func TestAllLowercase(t *testing.T) {
|
||||||
names := []string {
|
names := []string{
|
||||||
"alpha",
|
"alpha",
|
||||||
"BETA",
|
"BETA",
|
||||||
"gamma",
|
"gamma",
|
||||||
|
|||||||
Vendored
+2
@@ -11,6 +11,8 @@
|
|||||||
c
|
c
|
||||||
failed
|
failed
|
||||||
after failed
|
after failed
|
||||||
|
--- FAIL: TestBar/Bar4 (0.00s)
|
||||||
|
fatal
|
||||||
log Bar end
|
log Bar end
|
||||||
--- FAIL: TestAllLowercase (0.00s)
|
--- FAIL: TestAllLowercase (0.00s)
|
||||||
--- FAIL: TestAllLowercase/BETA (0.00s)
|
--- FAIL: TestAllLowercase/BETA (0.00s)
|
||||||
|
|||||||
Reference in New Issue
Block a user