github.com/zaolin/u-root@v0.0.0-20200428085104-64aaafd46c6d/pkg/testutil/testutil.go (about) 1 // Copyright 2017 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package testutil 6 7 import ( 8 "fmt" 9 "io/ioutil" 10 "log" 11 "os" 12 "os/exec" 13 "path/filepath" 14 "strings" 15 "syscall" 16 "testing" 17 "time" 18 19 "github.com/u-root/u-root/pkg/golang" 20 ) 21 22 // CheckError is a helper function for tests 23 // It is common to check if an err is expected in the form of errStr, then 24 // there should be an actual error reported. This is an if and only if condition 25 // that needs to be verified. 26 func CheckError(err error, errStr string) error { 27 if err != nil && errStr == "" { 28 return fmt.Errorf("no error expected, got: \n%v", err) 29 } else if err == nil && errStr != "" { 30 return fmt.Errorf("error \n%v\nexpected, got nil error", errStr) 31 } else if err != nil && err.Error() != errStr { 32 return fmt.Errorf("error \n%v\nexpected, got: \n%v", errStr, err) 33 } 34 return nil 35 } 36 37 // NowLog returns the current time formatted like the standard log package's 38 // timestamp. 39 func NowLog() string { 40 return time.Now().Format("2006/01/02 15:04:05") 41 } 42 43 var binary string 44 45 // Command returns an exec.Cmd appropriate for testing the u-root command. 46 // 47 // Command decides which executable to call based on environment variables: 48 // - EXECPATH="executable args" overrides any other test subject. 49 // - UROOT_TEST_BUILD=1 will force compiling the u-root command in question. 50 func Command(t testing.TB, args ...string) *exec.Cmd { 51 // If EXECPATH is set, just use that. 52 execPath := os.Getenv("EXECPATH") 53 if len(execPath) > 0 { 54 exe := strings.Split(os.Getenv("EXECPATH"), " ") 55 return exec.Command(exe[0], append(exe[1:], args...)...) 56 } 57 58 // Should be cached by Run if os.Executable is going to fail. 59 if len(binary) > 0 { 60 t.Logf("binary: %v", binary) 61 return exec.Command(binary, args...) 62 } 63 64 execPath, err := os.Executable() 65 if err != nil { 66 // Run should have prevented this case by caching something in 67 // `binary`. 68 t.Fatal("You must call testutil.Run() in your TestMain.") 69 } 70 71 c := exec.Command(execPath, args...) 72 c.Env = append(c.Env, append(os.Environ(), "UROOT_CALL_MAIN=1")...) 73 return c 74 } 75 76 // IsExitCode takes err and checks whether it represents the given process exit 77 // code. 78 // 79 // IsExitCode assumes that `err` is the return value of a successful call to 80 // exec.Cmd.Run/Output/CombinedOutput and hence an *exec.ExitError. 81 func IsExitCode(err error, exitCode int) error { 82 if err == nil { 83 if exitCode != 0 { 84 return fmt.Errorf("got code 0, want %d", exitCode) 85 } 86 return nil 87 } 88 89 exitErr, ok := err.(*exec.ExitError) 90 if !ok { 91 return fmt.Errorf("encountered error other than ExitError: %#v", err) 92 } 93 ws, ok := exitErr.Sys().(syscall.WaitStatus) 94 if !ok { 95 return fmt.Errorf("sys() is not a syscall WaitStatus: %v", err) 96 } 97 if es := ws.ExitStatus(); es != exitCode { 98 return fmt.Errorf("got exit status %d, want %d", es, exitCode) 99 } 100 return nil 101 } 102 103 func run(m *testing.M, mainFn func()) int { 104 // UROOT_CALL_MAIN=1 /proc/self/exe should be the same as just running 105 // the command we are testing. 106 if len(os.Getenv("UROOT_CALL_MAIN")) > 0 { 107 mainFn() 108 return 0 109 } 110 111 // Normally, /proc/self/exe (and equivalents) are used to test u-root 112 // commands. 113 // 114 // Such a symlink isn't available on Plan 9, OS X, or FreeBSD. On these 115 // systems, we compile the u-root command in question on the fly 116 // instead. 117 // 118 // Here, we decide whether to compile or not and cache the executable. 119 // Do this here, so that when m.Run() returns, we can remove the 120 // executable using the functor returned. 121 _, err := os.Executable() 122 if err != nil || len(os.Getenv("UROOT_TEST_BUILD")) > 0 { 123 // We can't find ourselves? Probably FreeBSD or something. Try to go 124 // build the command. 125 // 126 // This is NOT build-system-independent, and hence the fallback. 127 tmpDir, err := ioutil.TempDir("", "uroot-build") 128 if err != nil { 129 log.Fatal(err) 130 } 131 defer os.RemoveAll(tmpDir) 132 133 wd, err := os.Getwd() 134 if err != nil { 135 log.Fatal(err) 136 } 137 138 execPath := filepath.Join(tmpDir, "binary") 139 // Build the stuff. 140 if err := golang.Default().BuildDir(wd, execPath, golang.BuildOpts{}); err != nil { 141 log.Fatal(err) 142 } 143 144 // Cache dat. 145 binary = execPath 146 } 147 148 return m.Run() 149 } 150 151 // Run sets up necessary commands to be compiled, if necessary, and calls 152 // m.Run. 153 func Run(m *testing.M, mainFn func()) { 154 os.Exit(run(m, mainFn)) 155 } 156 157 // SkipIfNotRoot skips the calling test if uid != 0. 158 func SkipIfNotRoot(t *testing.T) { 159 if os.Getuid() != 0 { 160 t.Skipf("Skipping test since we are not root") 161 } 162 }