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