github.com/oweisse/u-root@v0.0.0-20181109060735-d005ad25fef1/pkg/uroot/uroot_test.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 uroot
     6  
     7  import (
     8  	"fmt"
     9  	"io/ioutil"
    10  	"log"
    11  	"os"
    12  	"path/filepath"
    13  	"reflect"
    14  	"syscall"
    15  	"testing"
    16  
    17  	"github.com/u-root/u-root/pkg/cpio"
    18  	"github.com/u-root/u-root/pkg/golang"
    19  	"github.com/u-root/u-root/pkg/uroot/builder"
    20  )
    21  
    22  type inMemArchive struct {
    23  	*cpio.Archive
    24  }
    25  
    26  // Finish implements initramfs.Writer.Finish.
    27  func (inMemArchive) Finish() error { return nil }
    28  
    29  func TestResolvePackagePaths(t *testing.T) {
    30  	defaultEnv := golang.Default()
    31  	gopath1, err := filepath.Abs("test/gopath1")
    32  	if err != nil {
    33  		t.Fatalf("failure to set up test: %v", err)
    34  	}
    35  	gopath2, err := filepath.Abs("test/gopath2")
    36  	if err != nil {
    37  		t.Fatalf("failure to set up test: %v", err)
    38  	}
    39  	gopath1Env := defaultEnv
    40  	gopath1Env.GOPATH = gopath1
    41  	gopath2Env := defaultEnv
    42  	gopath2Env.GOPATH = gopath2
    43  	everythingEnv := defaultEnv
    44  	everythingEnv.GOPATH = gopath1 + ":" + gopath2
    45  	foopath, err := filepath.Abs("test/gopath1/src/foo")
    46  	if err != nil {
    47  		t.Fatalf("failure to set up test: %v", err)
    48  	}
    49  
    50  	// Why doesn't the log package export this as a default?
    51  	l := log.New(os.Stdout, "", log.LstdFlags)
    52  
    53  	for _, tc := range []struct {
    54  		env      golang.Environ
    55  		in       []string
    56  		expected []string
    57  		wantErr  bool
    58  	}{
    59  		// Nonexistent Package
    60  		{
    61  			env:      defaultEnv,
    62  			in:       []string{"fakepackagename"},
    63  			expected: nil,
    64  			wantErr:  true,
    65  		},
    66  		// Single go package import
    67  		{
    68  			env: defaultEnv,
    69  			in:  []string{"github.com/u-root/u-root/cmds/ls"},
    70  			// We expect the full URL format because that's the path in our default GOPATH
    71  			expected: []string{"github.com/u-root/u-root/cmds/ls"},
    72  			wantErr:  false,
    73  		},
    74  		// Single package directory relative to working dir
    75  		{
    76  			env:      defaultEnv,
    77  			in:       []string{"test/gopath1/src/foo"},
    78  			expected: []string{"github.com/u-root/u-root/pkg/uroot/test/gopath1/src/foo"},
    79  			wantErr:  false,
    80  		},
    81  		// Single package directory with absolute path
    82  		{
    83  			env:      defaultEnv,
    84  			in:       []string{foopath},
    85  			expected: []string{"github.com/u-root/u-root/pkg/uroot/test/gopath1/src/foo"},
    86  			wantErr:  false,
    87  		},
    88  		// Single package directory relative to GOPATH
    89  		{
    90  			env: gopath1Env,
    91  			in:  []string{"foo"},
    92  			expected: []string{
    93  				"foo",
    94  			},
    95  			wantErr: false,
    96  		},
    97  		// Package directory glob
    98  		{
    99  			env: defaultEnv,
   100  			in:  []string{"test/gopath2/src/mypkg*"},
   101  			expected: []string{
   102  				"github.com/u-root/u-root/pkg/uroot/test/gopath2/src/mypkga",
   103  				"github.com/u-root/u-root/pkg/uroot/test/gopath2/src/mypkgb",
   104  			},
   105  			wantErr: false,
   106  		},
   107  		// GOPATH glob
   108  		{
   109  			env: gopath2Env,
   110  			in:  []string{"mypkg*"},
   111  			expected: []string{
   112  				"mypkga",
   113  				"mypkgb",
   114  			},
   115  			wantErr: false,
   116  		},
   117  		// Single ambiguous package - exists in both GOROOT and GOPATH
   118  		{
   119  			env: gopath1Env,
   120  			in:  []string{"os"},
   121  			expected: []string{
   122  				"os",
   123  			},
   124  			wantErr: false,
   125  		},
   126  		// Packages from different gopaths
   127  		{
   128  			env: everythingEnv,
   129  			in:  []string{"foo", "mypkga"},
   130  			expected: []string{
   131  				"foo",
   132  				"mypkga",
   133  			},
   134  			wantErr: false,
   135  		},
   136  		// Same package specified twice
   137  		{
   138  			env: defaultEnv,
   139  			in:  []string{"test/gopath2/src/mypkga", "test/gopath2/src/mypkga"},
   140  			// TODO: This returns the package twice. Is this preferred?
   141  			expected: []string{
   142  				"github.com/u-root/u-root/pkg/uroot/test/gopath2/src/mypkga",
   143  				"github.com/u-root/u-root/pkg/uroot/test/gopath2/src/mypkga",
   144  			},
   145  			wantErr: false,
   146  		},
   147  	} {
   148  		t.Run(fmt.Sprintf("%q", tc.in), func(t *testing.T) {
   149  			out, err := ResolvePackagePaths(l, tc.env, tc.in)
   150  			if (err != nil) != tc.wantErr {
   151  				t.Fatalf("ResolvePackagePaths(%#v, %v) err != nil is %v, want %v\nerr is %v",
   152  					tc.env, tc.in, err != nil, tc.wantErr, err)
   153  			}
   154  			if !reflect.DeepEqual(out, tc.expected) {
   155  				t.Errorf("ResolvePackagePaths(%#v, %v) = %v; want %v",
   156  					tc.env, tc.in, out, tc.expected)
   157  			}
   158  		})
   159  	}
   160  }
   161  
   162  type archiveValidator interface {
   163  	validate(a *cpio.Archive) error
   164  }
   165  
   166  type hasRecord struct {
   167  	r cpio.Record
   168  }
   169  
   170  func (hr hasRecord) validate(a *cpio.Archive) error {
   171  	r, ok := a.Get(hr.r.Name)
   172  	if !ok {
   173  		return fmt.Errorf("archive does not contain %v", hr.r)
   174  	}
   175  	if !cpio.Equal(r, hr.r) {
   176  		return fmt.Errorf("archive does not contain %v; instead has %v", hr.r, r)
   177  	}
   178  	return nil
   179  }
   180  
   181  type hasFile struct {
   182  	path string
   183  }
   184  
   185  func (hf hasFile) validate(a *cpio.Archive) error {
   186  	if _, ok := a.Get(hf.path); ok {
   187  		return nil
   188  	}
   189  	return fmt.Errorf("archive does not contain %s, but should", hf.path)
   190  }
   191  
   192  type missingFile struct {
   193  	path string
   194  }
   195  
   196  func (mf missingFile) validate(a *cpio.Archive) error {
   197  	if _, ok := a.Get(mf.path); ok {
   198  		return fmt.Errorf("archive contains %s, but shouldn't", mf.path)
   199  	}
   200  	return nil
   201  }
   202  
   203  type isEmpty struct{}
   204  
   205  func (isEmpty) validate(a *cpio.Archive) error {
   206  	if empty := a.Empty(); !empty {
   207  		return fmt.Errorf("expected archive to be empty")
   208  	}
   209  	return nil
   210  }
   211  
   212  func TestCreateInitramfs(t *testing.T) {
   213  	dir, err := ioutil.TempDir("", "foo")
   214  	if err != nil {
   215  		t.Error(err)
   216  	}
   217  	defer os.RemoveAll(dir)
   218  	syscall.Umask(0)
   219  
   220  	tmp777 := filepath.Join(dir, "tmp777")
   221  	if err := os.MkdirAll(tmp777, 0777); err != nil {
   222  		t.Error(err)
   223  	}
   224  
   225  	// Why doesn't the log package export this as a default?
   226  	l := log.New(os.Stdout, "", log.LstdFlags)
   227  
   228  	for i, tt := range []struct {
   229  		name       string
   230  		opts       Opts
   231  		want       error
   232  		validators []archiveValidator
   233  	}{
   234  		{
   235  			name: "BB archive with ls and init",
   236  			opts: Opts{
   237  				Env:             golang.Default(),
   238  				TempDir:         dir,
   239  				ExtraFiles:      nil,
   240  				UseExistingInit: false,
   241  				InitCmd:         "init",
   242  				DefaultShell:    "ls",
   243  				Commands: []Commands{
   244  					{
   245  						Builder: builder.BusyBox,
   246  						Packages: []string{
   247  							"github.com/u-root/u-root/cmds/init",
   248  							"github.com/u-root/u-root/cmds/ls",
   249  						},
   250  					},
   251  				},
   252  			},
   253  			want: nil,
   254  			validators: []archiveValidator{
   255  				hasFile{path: "bbin/bb"},
   256  				hasRecord{cpio.Symlink("bbin/init", "bb")},
   257  				hasRecord{cpio.Symlink("bbin/ls", "bb")},
   258  				hasRecord{cpio.Symlink("bin/defaultsh", "/bbin/ls")},
   259  			},
   260  		},
   261  		{
   262  			name: "no temp dir",
   263  			opts: Opts{
   264  				Env:          golang.Default(),
   265  				InitCmd:      "init",
   266  				DefaultShell: "",
   267  			},
   268  			// TODO: Ew. Our error types suck.
   269  			want: fmt.Errorf("temp dir \"\" must exist: stat : no such file or directory"),
   270  			validators: []archiveValidator{
   271  				isEmpty{},
   272  			},
   273  		},
   274  		{
   275  			name: "no commands",
   276  			opts: Opts{
   277  				Env:     golang.Default(),
   278  				TempDir: dir,
   279  			},
   280  			want: nil,
   281  			validators: []archiveValidator{
   282  				missingFile{"bbin/bb"},
   283  			},
   284  		},
   285  		{
   286  			name: "init specified, but not in commands",
   287  			opts: Opts{
   288  				Env:          golang.Default(),
   289  				TempDir:      dir,
   290  				DefaultShell: "zoocar",
   291  				InitCmd:      "foobar",
   292  			},
   293  			want: fmt.Errorf("could not find init: command or path \"foobar\" not included in u-root build"),
   294  			validators: []archiveValidator{
   295  				isEmpty{},
   296  			},
   297  		},
   298  		{
   299  			name: "init symlinked to absolute path",
   300  			opts: Opts{
   301  				Env:     golang.Default(),
   302  				TempDir: dir,
   303  				InitCmd: "/bin/systemd",
   304  			},
   305  			want: nil,
   306  			validators: []archiveValidator{
   307  				hasRecord{cpio.Symlink("init", "/bin/systemd")},
   308  			},
   309  		},
   310  		{
   311  			name: "multi-mode archive",
   312  			opts: Opts{
   313  				Env:             golang.Default(),
   314  				TempDir:         dir,
   315  				ExtraFiles:      nil,
   316  				UseExistingInit: false,
   317  				InitCmd:         "init",
   318  				DefaultShell:    "ls",
   319  				Commands: []Commands{
   320  					{
   321  						Builder: builder.BusyBox,
   322  						Packages: []string{
   323  							"github.com/u-root/u-root/cmds/init",
   324  							"github.com/u-root/u-root/cmds/ls",
   325  						},
   326  					},
   327  					{
   328  						Builder: builder.Binary,
   329  						Packages: []string{
   330  							"github.com/u-root/u-root/cmds/cp",
   331  							"github.com/u-root/u-root/cmds/dd",
   332  						},
   333  					},
   334  					{
   335  						Builder: builder.Source,
   336  						Packages: []string{
   337  							"github.com/u-root/u-root/cmds/cat",
   338  							"github.com/u-root/u-root/cmds/chroot",
   339  							"github.com/u-root/u-root/cmds/installcommand",
   340  						},
   341  					},
   342  				},
   343  			},
   344  			want: nil,
   345  			validators: []archiveValidator{
   346  				hasRecord{cpio.Symlink("init", "/bbin/init")},
   347  
   348  				// bb mode.
   349  				hasFile{path: "bbin/bb"},
   350  				hasRecord{cpio.Symlink("bbin/init", "bb")},
   351  				hasRecord{cpio.Symlink("bbin/ls", "bb")},
   352  				hasRecord{cpio.Symlink("bin/defaultsh", "/bbin/ls")},
   353  
   354  				// binary mode.
   355  				hasFile{path: "bin/cp"},
   356  				hasFile{path: "bin/dd"},
   357  
   358  				// source mode.
   359  				hasRecord{cpio.Symlink("buildbin/cat", "/buildbin/installcommand")},
   360  				hasRecord{cpio.Symlink("buildbin/chroot", "/buildbin/installcommand")},
   361  				hasFile{path: "buildbin/installcommand"},
   362  				hasFile{path: "src/github.com/u-root/u-root/cmds/cat/cat.go"},
   363  				hasFile{path: "src/github.com/u-root/u-root/cmds/chroot/chroot.go"},
   364  				hasFile{path: "src/github.com/u-root/u-root/cmds/installcommand/installcommand.go"},
   365  			},
   366  		},
   367  	} {
   368  		t.Run(fmt.Sprintf("Test %d [%s]", i, tt.name), func(t *testing.T) {
   369  			archive := inMemArchive{cpio.InMemArchive()}
   370  			tt.opts.OutputFile = archive
   371  			// Compare error type or error string.
   372  			if err := CreateInitramfs(l, tt.opts); err != tt.want && (tt.want == nil || err.Error() != tt.want.Error()) {
   373  				t.Errorf("CreateInitramfs(%v) = %v, want %v", tt.opts, err, tt.want)
   374  			}
   375  
   376  			for _, v := range tt.validators {
   377  				if err := v.validate(archive.Archive); err != nil {
   378  					t.Errorf("validator failed: %v / archive:\n%s", err, archive)
   379  				}
   380  			}
   381  		})
   382  	}
   383  }