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