github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/uroot_test.go (about) 1 // Copyright 2015-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 main 6 7 import ( 8 "bufio" 9 "bytes" 10 "crypto/sha256" 11 "fmt" 12 "io" 13 "io/ioutil" 14 "os" 15 "os/exec" 16 "path/filepath" 17 "strings" 18 "testing" 19 20 "github.com/u-root/u-root/pkg/cpio" 21 "github.com/u-root/u-root/pkg/golang" 22 "github.com/u-root/u-root/pkg/testutil" 23 "github.com/u-root/u-root/pkg/uio" 24 itest "github.com/u-root/u-root/pkg/uroot/initramfs/test" 25 ) 26 27 var twocmds = []string{ 28 "github.com/u-root/u-root/cmds/core/ls", 29 "github.com/u-root/u-root/cmds/core/init", 30 } 31 32 var srcmds = []string{ 33 "github.com/u-root/u-root/cmds/core/ls", 34 "github.com/u-root/u-root/cmds/core/init", 35 "github.com/u-root/u-root/cmds/core/installcommand", 36 } 37 38 type buildSourceValidator struct { 39 gopath string 40 goroot string 41 env []string 42 } 43 44 func (b buildSourceValidator) Validate(a *cpio.Archive) error { 45 dir, err := ioutil.TempDir("", "u-root-source-") 46 if err != nil { 47 return err 48 } 49 defer os.RemoveAll(dir) 50 51 if err := os.Mkdir(filepath.Join(dir, "tmp"), 0755); err != nil { 52 return err 53 } 54 55 // Unpack into dir. 56 err = cpio.ForEachRecord(a.Reader(), func(r cpio.Record) error { 57 return cpio.CreateFileInRoot(r, dir, false) 58 }) 59 if err != nil { 60 return err 61 } 62 63 goroot := filepath.Join(dir, b.goroot) 64 gopath := filepath.Join(dir, b.gopath) 65 // go build ./src/... 66 c := exec.Command(filepath.Join(goroot, "bin/go"), "build", filepath.Join(gopath, "src/...")) 67 c.Env = append(b.env, 68 fmt.Sprintf("GOPATH=%s", gopath), 69 fmt.Sprintf("GOCACHE=%s", filepath.Join(dir, "tmp")), 70 fmt.Sprintf("GOROOT=%s", goroot), 71 "CGO_ENABLED=0") 72 out, err := c.CombinedOutput() 73 if err != nil { 74 return fmt.Errorf("could not build go source %v; output\n%s", err, out) 75 } 76 return nil 77 } 78 79 func xTestDCE(t *testing.T) { 80 delFiles := false 81 f, _ := buildIt( 82 t, 83 []string{ 84 "-build=bb", "-no-strip", 85 "./cmds/*/*", 86 "-cmds/core/installcommand", "-cmds/exp/builtin", "-cmds/exp/run", 87 "pkg/uroot/test/foo", 88 }, 89 nil, 90 nil) 91 defer func() { 92 if delFiles { 93 os.RemoveAll(f.Name()) 94 } 95 }() 96 st, _ := f.Stat() 97 t.Logf("Built %s, size %d", f.Name(), st.Size()) 98 cmd := golang.Default().GoCmd("tool", "nm", f.Name()) 99 nmOutput, err := cmd.CombinedOutput() 100 if err != nil { 101 t.Fatalf("failed to run nm: %s %s", err, nmOutput) 102 } 103 symScanner := bufio.NewScanner(bytes.NewBuffer(nmOutput)) 104 syms := map[string]bool{} 105 for symScanner.Scan() { 106 line := symScanner.Text() 107 parts := strings.Split(line, " ") 108 if len(parts) == 0 { 109 continue 110 } 111 sym := parts[len(parts)-1] 112 syms[sym] = true 113 t.Logf("%s", sym) 114 } 115 } 116 117 type noDeadCode struct { 118 Path string 119 } 120 121 func (v noDeadCode) Validate(a *cpio.Archive) error { 122 // 1. Extract BB binary into a temporary file. 123 delFiles := true 124 bbRecord, ok := a.Get(v.Path) 125 if !ok { 126 return fmt.Errorf("archive does not contain %s, but should", v.Path) 127 } 128 tf, err := ioutil.TempFile("", "u-root-temp-bb-") 129 if err != nil { 130 return err 131 } 132 bbData, _ := uio.ReadAll(bbRecord) 133 tf.Write(bbData) 134 tf.Close() 135 defer func() { 136 if delFiles { 137 os.RemoveAll(tf.Name()) 138 } 139 }() 140 // 2. Run "go nm" on it and build symbol table. 141 cmd := golang.Default().GoCmd("tool", "nm", tf.Name()) 142 nmOutput, err := cmd.CombinedOutput() 143 if err != nil { 144 return fmt.Errorf("failed to run nm: %s %s", err, nmOutput) 145 } 146 symScanner := bufio.NewScanner(bytes.NewBuffer(nmOutput)) 147 syms := map[string]bool{} 148 for symScanner.Scan() { 149 line := symScanner.Text() 150 parts := strings.Split(line, " ") 151 if len(parts) == 0 { 152 continue 153 } 154 sym := parts[len(parts)-1] 155 syms[sym] = true 156 } 157 // 3. Check for presence and absence of particular symbols. 158 if !syms["github.com/u-root/u-root/pkg/uroot/test/bar.Bar.UsedInterfaceMethod"] { 159 // Sanity check of the test itself: this method must be in the binary. 160 return fmt.Errorf("expected symbol not found, something is wrong with the build") 161 } 162 if syms["github.com/u-root/u-root/pkg/uroot/test/bar.Bar.UnusedNonInterfaceMethod"] { 163 // Sanity check of the test itself: this method must be in the binary. 164 delFiles = false 165 return fmt.Errorf( 166 "Unused non-interface method has not been eliminated, dead code elimination is not working properly.\n"+ 167 "The most likely reason is use of reflect.Value.Method or .MethodByName somewhere "+ 168 "(could be a command or vendor dependency, apologies for not being more precise here).\n"+ 169 "See https://golang.org/src/cmd/link/internal/ld/deadcode.go for explanation.\n"+ 170 "%s contains the resulting binary.\n", tf.Name()) 171 } 172 return nil 173 } 174 175 func TestUrootCmdline(t *testing.T) { 176 samplef, err := ioutil.TempFile("", "u-root-test-") 177 if err != nil { 178 t.Fatal(err) 179 } 180 samplef.Close() 181 defer os.RemoveAll(samplef.Name()) 182 sampledir, err := ioutil.TempDir("", "u-root-test-dir-") 183 if err != nil { 184 t.Fatal(err) 185 } 186 if err = ioutil.WriteFile(filepath.Join(sampledir, "foo"), nil, 0644); err != nil { 187 t.Fatal(err) 188 } 189 if err = ioutil.WriteFile(filepath.Join(sampledir, "bar"), nil, 0644); err != nil { 190 t.Fatal(err) 191 } 192 defer os.RemoveAll(sampledir) 193 194 for _, tt := range []struct { 195 name string 196 env []string 197 args []string 198 err error 199 validators []itest.ArchiveValidator 200 }{ 201 { 202 name: "include one extra file", 203 args: []string{"-nocmd", "-files=/bin/bash"}, 204 err: nil, 205 validators: []itest.ArchiveValidator{ 206 itest.HasFile{"bin/bash"}, 207 }, 208 }, 209 { 210 name: "uinitcmd", 211 args: []string{"-build=bb", "-uinitcmd=echo foobar fuzz", "-defaultsh=", "./cmds/core/init", "./cmds/core/echo"}, 212 err: nil, 213 validators: []itest.ArchiveValidator{ 214 itest.HasRecord{cpio.Symlink("bin/uinit", "../bbin/echo")}, 215 itest.HasContent{ 216 Path: "etc/uinit.flags", 217 Content: "\"foobar\"\n\"fuzz\"", 218 }, 219 }, 220 }, 221 { 222 name: "fix usage of an absolute path", 223 args: []string{"-nocmd", fmt.Sprintf("-files=%s:/bin", sampledir)}, 224 err: nil, 225 validators: []itest.ArchiveValidator{ 226 itest.HasFile{"/bin/foo"}, 227 itest.HasFile{"/bin/bar"}, 228 }, 229 }, 230 { 231 name: "include multiple extra files", 232 args: []string{"-nocmd", "-files=/bin/bash", "-files=/bin/ls", fmt.Sprintf("-files=%s", samplef.Name())}, 233 validators: []itest.ArchiveValidator{ 234 itest.HasFile{"bin/bash"}, 235 itest.HasFile{"bin/ls"}, 236 itest.HasFile{samplef.Name()}, 237 }, 238 }, 239 { 240 name: "include one extra file with rename", 241 args: []string{"-nocmd", "-files=/bin/bash:bin/bush"}, 242 validators: []itest.ArchiveValidator{ 243 itest.HasFile{"bin/bush"}, 244 }, 245 }, 246 { 247 name: "make sure dead code gets eliminated", 248 args: []string{ 249 // Build the world + test symbols, unstripped. 250 "-build=bb", "-no-strip", "world", "pkg/uroot/test/foo", 251 // These are known to disable DCE and need to be exluded. 252 // The reason is https://github.com/golang/go/issues/36021 and is fixed in Go 1.15, 253 // so these can be removed once we no longer support Go < 1.15. 254 "-cmds/core/installcommand", "-cmds/exp/builtin", "-cmds/exp/run", 255 }, 256 err: nil, 257 validators: []itest.ArchiveValidator{ 258 noDeadCode{Path: "bbin/bb"}, 259 }, 260 }, 261 { 262 name: "hosted source mode", 263 args: append([]string{"-build=source", "-base=/dev/null", "-defaultsh=", "-initcmd="}, srcmds...), 264 }, 265 { 266 name: "hosted bb mode", 267 args: append([]string{"-build=bb", "-base=/dev/null", "-defaultsh=", "-initcmd="}, twocmds...), 268 }, 269 { 270 name: "AMD64 bb build", 271 env: []string{"GOARCH=amd64"}, 272 args: []string{"-build=bb", "all"}, 273 }, 274 { 275 name: "AMD64 source build", 276 env: []string{"GOARCH=amd64"}, 277 args: []string{"-build=source", "all"}, 278 validators: []itest.ArchiveValidator{ 279 buildSourceValidator{ 280 goroot: "/go", 281 gopath: ".", 282 env: []string{"GOARCH=amd64"}, 283 }, 284 }, 285 }, 286 { 287 name: "MIPS bb build", 288 env: []string{"GOARCH=mips"}, 289 args: []string{"-build=bb", "all"}, 290 }, 291 { 292 name: "MIPSLE bb build", 293 env: []string{"GOARCH=mipsle"}, 294 args: []string{"-build=bb", "all"}, 295 }, 296 { 297 name: "MIPS64 bb build", 298 env: []string{"GOARCH=mips64"}, 299 args: []string{"-build=bb", "all"}, 300 }, 301 { 302 name: "MIPS64LE bb build", 303 env: []string{"GOARCH=mips64le"}, 304 args: []string{"-build=bb", "all"}, 305 }, 306 { 307 name: "ARM7 bb build", 308 env: []string{"GOARCH=arm", "GOARM=7"}, 309 args: []string{"-build=bb", "all"}, 310 }, 311 { 312 name: "ARM64 bb build", 313 env: []string{"GOARCH=arm64"}, 314 args: []string{"-build=bb", "all"}, 315 }, 316 { 317 name: "386 (32 bit) bb build", 318 env: []string{"GOARCH=386"}, 319 args: []string{"-build=bb", "all"}, 320 }, 321 { 322 name: "Power 64bit bb build", 323 env: []string{"GOARCH=ppc64le"}, 324 args: []string{"-build=bb", "all"}, 325 }, 326 } { 327 t.Run(tt.name, func(t *testing.T) { 328 delFiles := true 329 f, sum1 := buildIt(t, tt.args, tt.env, tt.err) 330 defer func() { 331 if delFiles { 332 os.RemoveAll(f.Name()) 333 } 334 }() 335 336 a, err := itest.ReadArchive(f.Name()) 337 if err != nil { 338 t.Fatal(err) 339 } 340 341 for _, v := range tt.validators { 342 if err := v.Validate(a); err != nil { 343 t.Errorf("validator failed: %v / archive:\n%s", err, a) 344 } 345 } 346 347 f2, sum2 := buildIt(t, tt.args, tt.env, tt.err) 348 defer func() { 349 if delFiles { 350 os.RemoveAll(f2.Name()) 351 } 352 }() 353 if !bytes.Equal(sum1, sum2) { 354 delFiles = false 355 t.Errorf("not reproducible, hashes don't match") 356 t.Errorf("env: %v args: %v", tt.env, tt.args) 357 t.Errorf("file1: %v file2: %v", f.Name(), f2.Name()) 358 } 359 }) 360 } 361 } 362 363 func buildIt(t *testing.T, args, env []string, want error) (*os.File, []byte) { 364 f, err := ioutil.TempFile("", "u-root-") 365 if err != nil { 366 t.Fatal(err) 367 } 368 369 arg := append([]string{"-o", f.Name()}, args...) 370 c := testutil.Command(t, arg...) 371 t.Logf("Commandline: %v", arg) 372 c.Env = append(c.Env, env...) 373 if out, err := c.CombinedOutput(); err != want { 374 t.Fatalf("Error: %v\nOutput:\n%s", err, out) 375 } else if err != nil { 376 h1 := sha256.New() 377 if _, err := io.Copy(h1, f); err != nil { 378 t.Fatal() 379 } 380 return f, h1.Sum(nil) 381 } 382 return f, nil 383 } 384 385 func TestMain(m *testing.M) { 386 testutil.Run(m, main) 387 }