github.com/Aoi-hosizora/ahlib@v1.5.1-0.20230404072829-241b93cf91c7/xtesting/xtesting_helper.go (about) 1 package xtesting 2 3 import ( 4 "fmt" 5 "os" 6 "os/exec" 7 "path" 8 "path/filepath" 9 "runtime" 10 "sync/atomic" 11 "testing" 12 _ "unsafe" 13 ) 14 15 // ================ 16 // failTest related 17 // ================ 18 19 // failTest outputs the error message and fails the test. 20 func failTest(t testing.TB, skip int, failureMessage string, msgAndArgs ...interface{}) bool { 21 if skip < 0 { 22 skip = 0 23 } 24 extraSkip := int(atomic.LoadInt32(&_extraSkip)) 25 skip = skip + extraSkip 26 27 _, file, line, _ := runtime.Caller(skip + 1) 28 message := fmt.Sprintf("%s:%d %s", path.Base(file), line, failureMessage) 29 _, _ = fmt.Fprintf(os.Stderr, "%s%s\n", message, combineMsgAndArgs(msgAndArgs...)) // use fmt.Fprintf() rather than t.Log() or t.Errorf() 30 31 failNow := atomic.LoadInt32(&_useFailNow) == 1 32 if !failNow { 33 t.Fail() 34 } else { 35 t.FailNow() 36 } 37 return false 38 } 39 40 var ( 41 // _extraSkip is the extra skip. Note that this value cannot be less than zero, and it defaults to zero. 42 _extraSkip int32 = 0 43 44 // _useFailNow is a flag for using `FailNow` (if set to 1) rather than `Fail` (if set to 0), defaults to 0. 45 _useFailNow int32 = 0 46 ) 47 48 // SetExtraSkip sets extra skip for testing functions. Note that this will be used when printing the failed message, and it defaults to zero. 49 func SetExtraSkip(skip int32) { 50 if skip >= 0 { 51 atomic.StoreInt32(&_extraSkip, skip) 52 } 53 } 54 55 // UseFailNow makes testing functions use `FailNow` when tests failed, defaults to false, and it means to use `Fail` rather than `FailNow`. 56 func UseFailNow(failNow bool) { 57 if failNow { 58 atomic.StoreInt32(&_useFailNow, 1) 59 } else { 60 atomic.StoreInt32(&_useFailNow, 0) 61 } 62 } 63 64 // combineMsgAndArgs generates message from given arguments. 65 func combineMsgAndArgs(msgAndArgs ...interface{}) string { 66 if len(msgAndArgs) == 0 { 67 return "" 68 } 69 70 if len(msgAndArgs) == 1 { 71 msg := msgAndArgs[0] 72 if msgAsStr, ok := msg.(string); ok { 73 return msgAsStr 74 } 75 return fmt.Sprintf("%+v", msg) 76 } 77 78 return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...) 79 } 80 81 // ===================== 82 // mass helper functions 83 // ===================== 84 85 // Assert panics when condition is false. 86 func Assert(condition bool, format string, v ...interface{}) bool { 87 if !condition { 88 panic(fmt.Sprintf(format, v...)) 89 } 90 91 return true 92 } 93 94 var _testGoToolFlag atomic.Value 95 96 // GoCommand reports the path to the Go executable file, if the bin file is not available, GoCommand returns error. For more details, 97 // please read the source code of src/internal/testenv/testenv.go. 98 // 99 // Example: 100 // func TestXXX(t *testing.T) { 101 // gocmd, err := GoCommand() 102 // if err != nil { 103 // t.Fail() 104 // } 105 // tmpdir := t.TempDir() 106 // 107 // modFile := path.Join(tmpdir, "go.mod") 108 // if ioutil.WriteFile(modFile, []byte("module xxx\ngo 1.18"), 0666) != nil { 109 // t.Fail() 110 // } 111 // sourceFile := path.Join(tmpdir, "test.go") 112 // if ioutil.WriteFile(sourceFile, []byte("package main\nfunc main() { ... }"), 0666) != nil { 113 // t.Fail() 114 // } 115 // 116 // buildCmd := exec.Command(gocmd, "build", "-o", "test", sourceFile) 117 // buildCmd.Dir = tmpdir 118 // buildOut, err := buildCmd.CombinedOutput() 119 // // ... 120 // 121 // runCmd := exec.Command("test") 122 // buildCmd.Dir = tmpdir 123 // runOut, err := runCmd.CombinedOutput() 124 // // ... 125 // } 126 func GoCommand() (string, error) { 127 p := filepath.Join(runtime.GOROOT(), "bin", "go") 128 if _testGoToolFlag.Load() == true { 129 // enter only when testing GoCommand function 130 p += "_fake" 131 } 132 133 goBin, err := exec.LookPath(p) 134 if err != nil { 135 return "", fmt.Errorf("xtesting: go command is not found: %w", err) 136 } 137 return goBin, nil 138 }