// Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package work import ( "bytes" "fmt" "io/fs" "os" "path/filepath" "reflect" "runtime" "strings" "testing" "cmd/go/internal/base" "cmd/go/internal/cfg" "cmd/go/internal/load" ) func TestRemoveDevNull(t *testing.T) { fi, err := os.Lstat(os.DevNull) if err != nil { t.Skip(err) } if fi.Mode().IsRegular() { t.Errorf("Lstat(%s).Mode().IsRegular() = true; expected false", os.DevNull) } mayberemovefile(os.DevNull) _, err = os.Lstat(os.DevNull) if err != nil { t.Errorf("mayberemovefile(%s) did remove it; oops", os.DevNull) } } func TestSplitPkgConfigOutput(t *testing.T) { for _, test := range []struct { in []byte want []string }{ {[]byte(`-r:foo -L/usr/white\ space/lib -lfoo\ bar -lbar\ baz`), []string{"-r:foo", "-L/usr/white space/lib", "-lfoo bar", "-lbar baz"}}, {[]byte(`-lextra\ fun\ arg\\`), []string{`-lextra fun arg\`}}, {[]byte("\textra whitespace\r\n"), []string{"extra", "whitespace"}}, {[]byte(" \r\n "), nil}, {[]byte(`"-r:foo" "-L/usr/white space/lib" "-lfoo bar" "-lbar baz"`), []string{"-r:foo", "-L/usr/white space/lib", "-lfoo bar", "-lbar baz"}}, {[]byte(`"-lextra fun arg\\"`), []string{`-lextra fun arg\`}}, {[]byte(`" \r\n\ "`), []string{` \r\n\ `}}, {[]byte(`""`), nil}, {[]byte(``), nil}, {[]byte(`"\\"`), []string{`\`}}, {[]byte(`"\x"`), []string{`\x`}}, {[]byte(`"\\x"`), []string{`\x`}}, {[]byte(`'\\'`), []string{`\`}}, {[]byte(`'\x'`), []string{`\x`}}, {[]byte(`"\\x"`), []string{`\x`}}, {[]byte(`-fPIC -I/test/include/foo -DQUOTED='"/test/share/doc"'`), []string{"-fPIC", "-I/test/include/foo", `-DQUOTED="/test/share/doc"`}}, {[]byte(`-fPIC -I/test/include/foo -DQUOTED="/test/share/doc"`), []string{"-fPIC", "-I/test/include/foo", "-DQUOTED=/test/share/doc"}}, {[]byte(`-fPIC -I/test/include/foo -DQUOTED=\"/test/share/doc\"`), []string{"-fPIC", "-I/test/include/foo", `-DQUOTED="/test/share/doc"`}}, {[]byte(`-fPIC -I/test/include/foo -DQUOTED='/test/share/doc'`), []string{"-fPIC", "-I/test/include/foo", "-DQUOTED=/test/share/doc"}}, {[]byte(`-DQUOTED='/te\st/share/d\oc'`), []string{`-DQUOTED=/te\st/share/d\oc`}}, {[]byte(`-Dhello=10 -Dworld=+32 -DDEFINED_FROM_PKG_CONFIG=hello\ world`), []string{"-Dhello=10", "-Dworld=+32", "-DDEFINED_FROM_PKG_CONFIG=hello world"}}, {[]byte(`"broken\"" \\\a "a"`), []string{"broken\"", "\\a", "a"}}, } { got, err := splitPkgConfigOutput(test.in) if err != nil { t.Errorf("splitPkgConfigOutput on %v failed with error %v", test.in, err) continue } if !reflect.DeepEqual(got, test.want) { t.Errorf("splitPkgConfigOutput(%v) = %v; want %v", test.in, got, test.want) } } for _, test := range []struct { in []byte want []string }{ // broken quotation {[]byte(`" \r\n `), nil}, {[]byte(`"-r:foo" "-L/usr/white space/lib "-lfoo bar" "-lbar baz"`), nil}, {[]byte(`"-lextra fun arg\\`), nil}, // broken char escaping {[]byte(`broken flag\`), nil}, {[]byte(`extra broken flag \`), nil}, {[]byte(`\`), nil}, {[]byte(`"broken\"" "extra" \`), nil}, } { got, err := splitPkgConfigOutput(test.in) if err == nil { t.Errorf("splitPkgConfigOutput(%v) = %v; haven't failed with error as expected.", test.in, got) } if !reflect.DeepEqual(got, test.want) { t.Errorf("splitPkgConfigOutput(%v) = %v; want %v", test.in, got, test.want) } } } func TestSharedLibName(t *testing.T) { // TODO(avdva) - make these values platform-specific prefix := "lib" suffix := ".so" testData := []struct { args []string pkgs []*load.Package expected string expectErr bool rootedAt string }{ { args: []string{"std"}, pkgs: []*load.Package{}, expected: "std", }, { args: []string{"std", "cmd"}, pkgs: []*load.Package{}, expected: "std,cmd", }, { args: []string{}, pkgs: []*load.Package{pkgImportPath("gopkg.in/somelib")}, expected: "gopkg.in-somelib", }, { args: []string{"./..."}, pkgs: []*load.Package{pkgImportPath("somelib")}, expected: "somelib", rootedAt: "somelib", }, { args: []string{"../somelib", "../somelib"}, pkgs: []*load.Package{pkgImportPath("somelib")}, expected: "somelib", }, { args: []string{"../lib1", "../lib2"}, pkgs: []*load.Package{pkgImportPath("gopkg.in/lib1"), pkgImportPath("gopkg.in/lib2")}, expected: "gopkg.in-lib1,gopkg.in-lib2", }, { args: []string{"./..."}, pkgs: []*load.Package{ pkgImportPath("gopkg.in/dir/lib1"), pkgImportPath("gopkg.in/lib2"), pkgImportPath("gopkg.in/lib3"), }, expected: "gopkg.in", rootedAt: "gopkg.in", }, { args: []string{"std", "../lib2"}, pkgs: []*load.Package{}, expectErr: true, }, { args: []string{"all", "./"}, pkgs: []*load.Package{}, expectErr: true, }, { args: []string{"cmd", "fmt"}, pkgs: []*load.Package{}, expectErr: true, }, } for _, data := range testData { func() { if data.rootedAt != "" { tmpGopath, err := os.MkdirTemp("", "gopath") if err != nil { t.Fatal(err) } cwd := base.Cwd() oldGopath := cfg.BuildContext.GOPATH defer func() { cfg.BuildContext.GOPATH = oldGopath os.Chdir(cwd) err := os.RemoveAll(tmpGopath) if err != nil { t.Error(err) } }() root := filepath.Join(tmpGopath, "src", data.rootedAt) err = os.MkdirAll(root, 0755) if err != nil { t.Fatal(err) } cfg.BuildContext.GOPATH = tmpGopath os.Chdir(root) } computed, err := libname(data.args, data.pkgs) if err != nil { if !data.expectErr { t.Errorf("libname returned an error %q, expected a name", err.Error()) } } else if data.expectErr { t.Errorf("libname returned %q, expected an error", computed) } else { expected := prefix + data.expected + suffix if expected != computed { t.Errorf("libname returned %q, expected %q", computed, expected) } } }() } } func pkgImportPath(pkgpath string) *load.Package { return &load.Package{ PackagePublic: load.PackagePublic{ ImportPath: pkgpath, }, } } // When installing packages, the installed package directory should // respect the SetGID bit and group name of the destination // directory. // See https://golang.org/issue/18878. func TestRespectSetgidDir(t *testing.T) { switch runtime.GOOS { case "ios": t.Skip("can't set SetGID bit with chmod on iOS") case "windows", "plan9": t.Skip("chown/chmod setgid are not supported on Windows or Plan 9") } var b Builder // Check that `cp` is called instead of `mv` by looking at the output // of `(*Builder).ShowCmd` afterwards as a sanity check. cfg.BuildX = true var cmdBuf bytes.Buffer b.Print = func(a ...any) (int, error) { return cmdBuf.WriteString(fmt.Sprint(a...)) } setgiddir, err := os.MkdirTemp("", "SetGroupID") if err != nil { t.Fatal(err) } defer os.RemoveAll(setgiddir) // BSD mkdir(2) inherits the parent directory group, and other platforms // can inherit the parent directory group via setgid. The test setup (chmod // setgid) will fail if the process does not have the group permission to // the new temporary directory. err = os.Chown(setgiddir, os.Getuid(), os.Getgid()) if err != nil { t.Fatal(err) } // Change setgiddir's permissions to include the SetGID bit. if err := os.Chmod(setgiddir, 0755|fs.ModeSetgid); err != nil { t.Fatal(err) } pkgfile, err := os.CreateTemp("", "pkgfile") if err != nil { t.Fatalf("os.CreateTemp(\"\", \"pkgfile\"): %v", err) } defer os.Remove(pkgfile.Name()) defer pkgfile.Close() dirGIDFile := filepath.Join(setgiddir, "setgid") if err := b.moveOrCopyFile(dirGIDFile, pkgfile.Name(), 0666, true); err != nil { t.Fatalf("moveOrCopyFile: %v", err) } got := strings.TrimSpace(cmdBuf.String()) want := b.fmtcmd("", "cp %s %s", pkgfile.Name(), dirGIDFile) if got != want { t.Fatalf("moveOrCopyFile(%q, %q): want %q, got %q", dirGIDFile, pkgfile.Name(), want, got) } }