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