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  }