github.com/xyproto/u-root@v6.0.1-0.20200302025726-5528e0c77a3c+incompatible/pkg/vmtest/gotest.go (about)

     1  // Copyright 2018 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 vmtest
     6  
     7  import (
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"os"
    12  	"os/exec"
    13  	"path"
    14  	"path/filepath"
    15  	"testing"
    16  
    17  	"github.com/u-root/u-root/pkg/golang"
    18  	"github.com/u-root/u-root/pkg/uio"
    19  	"github.com/u-root/u-root/pkg/uroot"
    20  	"github.com/u-root/u-root/pkg/vmtest/internal/json2test"
    21  )
    22  
    23  // GolangTest compiles the unit tests found in pkgs and runs them in a QEMU VM.
    24  func GolangTest(t *testing.T, pkgs []string, o *Options) {
    25  	SkipWithoutQEMU(t)
    26  	// TODO: support arm
    27  	if TestArch() != "amd64" {
    28  		t.Skipf("test not supported on %s", TestArch())
    29  	}
    30  
    31  	if o == nil {
    32  		o = &Options{}
    33  	}
    34  
    35  	// Create a temporary directory.
    36  	if len(o.TmpDir) == 0 {
    37  		tmpDir, err := ioutil.TempDir("", "uroot-integration")
    38  		if err != nil {
    39  			t.Fatal(err)
    40  		}
    41  		o.TmpDir = tmpDir
    42  	}
    43  
    44  	// Set up u-root build options.
    45  	env := golang.Default()
    46  	env.CgoEnabled = false
    47  	env.GOARCH = TestArch()
    48  	o.BuildOpts.Env = env
    49  
    50  	// Statically build tests and add them to the temporary directory.
    51  	var tests []string
    52  	os.Setenv("CGO_ENABLED", "0")
    53  	testDir := filepath.Join(o.TmpDir, "tests")
    54  	for _, pkg := range pkgs {
    55  		pkgDir := filepath.Join(testDir, pkg)
    56  		if err := os.MkdirAll(pkgDir, 0755); err != nil {
    57  			t.Fatal(err)
    58  		}
    59  
    60  		testFile := filepath.Join(pkgDir, fmt.Sprintf("%s.test", path.Base(pkg)))
    61  
    62  		cmd := exec.Command("go", "test",
    63  			"-gcflags=all=-l",
    64  			"-ldflags", "-s -w",
    65  			"-c", pkg,
    66  			"-o", testFile,
    67  		)
    68  		if stderr, err := cmd.CombinedOutput(); err != nil {
    69  			t.Fatalf("could not build %s: %v\n%s", pkg, err, string(stderr))
    70  		}
    71  
    72  		// When a package does not contain any tests, the test
    73  		// executable is not generated, so it is not included in the
    74  		// `tests` list.
    75  		if _, err := os.Stat(testFile); !os.IsNotExist(err) {
    76  			tests = append(tests, pkg)
    77  
    78  			p, err := o.BuildOpts.Env.Package(pkg)
    79  			if err != nil {
    80  				t.Fatal(err)
    81  			}
    82  			// Optimistically copy any files in the pkg's
    83  			// directory, in case e.g. a testdata dir is there.
    84  			if err := copyRelativeFiles(p.Dir, filepath.Join(testDir, pkg)); err != nil {
    85  				t.Fatal(err)
    86  			}
    87  		}
    88  	}
    89  
    90  	// Create the CPIO and start QEMU.
    91  	o.BuildOpts.AddBusyBoxCommands("github.com/u-root/u-root/cmds/core/*")
    92  	o.BuildOpts.AddCommands(uroot.BinaryCmds("cmd/test2json")...)
    93  
    94  	// Specify the custom gotest uinit.
    95  	o.Uinit = "github.com/u-root/u-root/integration/testcmd/gotest/uinit"
    96  
    97  	tc := json2test.NewTestCollector()
    98  	serial := []io.Writer{
    99  		// Collect JSON test events in tc.
   100  		json2test.EventParser(tc),
   101  		// Write non-JSON output to log.
   102  		JSONLessTestLineWriter(t, "serial"),
   103  	}
   104  	if o.QEMUOpts.SerialOutput != nil {
   105  		serial = append(serial, o.QEMUOpts.SerialOutput)
   106  	}
   107  	o.QEMUOpts.SerialOutput = uio.MultiWriteCloser(serial...)
   108  
   109  	q, cleanup := QEMUTest(t, o)
   110  	defer cleanup()
   111  
   112  	if err := q.Expect("GoTest Done"); err != nil {
   113  		t.Errorf("Waiting for GoTest Done: %v", err)
   114  	}
   115  
   116  	// TODO: check that tc.Tests == tests
   117  	for pkg, test := range tc.Tests {
   118  		switch test.State {
   119  		case json2test.StateFail:
   120  			t.Errorf("Test %v failed:\n%v", pkg, test.FullOutput)
   121  		case json2test.StateSkip:
   122  			t.Logf("Test %v skipped", pkg)
   123  		case json2test.StatePass:
   124  			// Nothing.
   125  		default:
   126  			t.Errorf("Test %v left in state %v:\n%v", pkg, test.State, test.FullOutput)
   127  		}
   128  	}
   129  }
   130  
   131  func copyRelativeFiles(src string, dst string) error {
   132  	return filepath.Walk(src, func(path string, fi os.FileInfo, err error) error {
   133  		if err != nil {
   134  			return err
   135  		}
   136  
   137  		rel, err := filepath.Rel(src, path)
   138  		if err != nil {
   139  			return err
   140  		}
   141  
   142  		if fi.Mode().IsDir() {
   143  			return os.MkdirAll(filepath.Join(dst, rel), fi.Mode().Perm())
   144  		} else if fi.Mode().IsRegular() {
   145  			srcf, err := os.Open(path)
   146  			if err != nil {
   147  				return err
   148  			}
   149  			defer srcf.Close()
   150  			dstf, err := os.Create(filepath.Join(dst, rel))
   151  			if err != nil {
   152  				return err
   153  			}
   154  			defer dstf.Close()
   155  			_, err = io.Copy(dstf, srcf)
   156  			return err
   157  		}
   158  		return nil
   159  	})
   160  }