1// Copyright 2016 The Go 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
5package work
6
7import (
8	"bytes"
9	"fmt"
10	"io/fs"
11	"os"
12	"path/filepath"
13	"reflect"
14	"runtime"
15	"strings"
16	"testing"
17
18	"cmd/go/internal/base"
19	"cmd/go/internal/cfg"
20	"cmd/go/internal/load"
21)
22
23func TestRemoveDevNull(t *testing.T) {
24	fi, err := os.Lstat(os.DevNull)
25	if err != nil {
26		t.Skip(err)
27	}
28	if fi.Mode().IsRegular() {
29		t.Errorf("Lstat(%s).Mode().IsRegular() = true; expected false", os.DevNull)
30	}
31	mayberemovefile(os.DevNull)
32	_, err = os.Lstat(os.DevNull)
33	if err != nil {
34		t.Errorf("mayberemovefile(%s) did remove it; oops", os.DevNull)
35	}
36}
37
38func TestSplitPkgConfigOutput(t *testing.T) {
39	for _, test := range []struct {
40		in   []byte
41		want []string
42	}{
43		{[]byte(`-r:foo -L/usr/white\ space/lib -lfoo\ bar -lbar\ baz`), []string{"-r:foo", "-L/usr/white space/lib", "-lfoo bar", "-lbar baz"}},
44		{[]byte(`-lextra\ fun\ arg\\`), []string{`-lextra fun arg\`}},
45		{[]byte("\textra     whitespace\r\n"), []string{"extra", "whitespace"}},
46		{[]byte("     \r\n      "), nil},
47		{[]byte(`"-r:foo" "-L/usr/white space/lib" "-lfoo bar" "-lbar baz"`), []string{"-r:foo", "-L/usr/white space/lib", "-lfoo bar", "-lbar baz"}},
48		{[]byte(`"-lextra fun arg\\"`), []string{`-lextra fun arg\`}},
49		{[]byte(`"     \r\n\      "`), []string{`     \r\n\      `}},
50		{[]byte(`""`), nil},
51		{[]byte(``), nil},
52		{[]byte(`"\\"`), []string{`\`}},
53		{[]byte(`"\x"`), []string{`\x`}},
54		{[]byte(`"\\x"`), []string{`\x`}},
55		{[]byte(`'\\'`), []string{`\`}},
56		{[]byte(`'\x'`), []string{`\x`}},
57		{[]byte(`"\\x"`), []string{`\x`}},
58		{[]byte(`-fPIC -I/test/include/foo -DQUOTED='"/test/share/doc"'`), []string{"-fPIC", "-I/test/include/foo", `-DQUOTED="/test/share/doc"`}},
59		{[]byte(`-fPIC -I/test/include/foo -DQUOTED="/test/share/doc"`), []string{"-fPIC", "-I/test/include/foo", "-DQUOTED=/test/share/doc"}},
60		{[]byte(`-fPIC -I/test/include/foo -DQUOTED=\"/test/share/doc\"`), []string{"-fPIC", "-I/test/include/foo", `-DQUOTED="/test/share/doc"`}},
61		{[]byte(`-fPIC -I/test/include/foo -DQUOTED='/test/share/doc'`), []string{"-fPIC", "-I/test/include/foo", "-DQUOTED=/test/share/doc"}},
62		{[]byte(`-DQUOTED='/te\st/share/d\oc'`), []string{`-DQUOTED=/te\st/share/d\oc`}},
63		{[]byte(`-Dhello=10 -Dworld=+32 -DDEFINED_FROM_PKG_CONFIG=hello\ world`), []string{"-Dhello=10", "-Dworld=+32", "-DDEFINED_FROM_PKG_CONFIG=hello world"}},
64		{[]byte(`"broken\"" \\\a "a"`), []string{"broken\"", "\\a", "a"}},
65	} {
66		got, err := splitPkgConfigOutput(test.in)
67		if err != nil {
68			t.Errorf("splitPkgConfigOutput on %v failed with error %v", test.in, err)
69			continue
70		}
71		if !reflect.DeepEqual(got, test.want) {
72			t.Errorf("splitPkgConfigOutput(%v) = %v; want %v", test.in, got, test.want)
73		}
74	}
75
76	for _, test := range []struct {
77		in   []byte
78		want []string
79	}{
80		// broken quotation
81		{[]byte(`"     \r\n      `), nil},
82		{[]byte(`"-r:foo" "-L/usr/white space/lib "-lfoo bar" "-lbar baz"`), nil},
83		{[]byte(`"-lextra fun arg\\`), nil},
84		// broken char escaping
85		{[]byte(`broken flag\`), nil},
86		{[]byte(`extra broken flag \`), nil},
87		{[]byte(`\`), nil},
88		{[]byte(`"broken\"" "extra" \`), nil},
89	} {
90		got, err := splitPkgConfigOutput(test.in)
91		if err == nil {
92			t.Errorf("splitPkgConfigOutput(%v) = %v; haven't failed with error as expected.", test.in, got)
93		}
94		if !reflect.DeepEqual(got, test.want) {
95			t.Errorf("splitPkgConfigOutput(%v) = %v; want %v", test.in, got, test.want)
96		}
97	}
98
99}
100
101func TestSharedLibName(t *testing.T) {
102	// TODO(avdva) - make these values platform-specific
103	prefix := "lib"
104	suffix := ".so"
105	testData := []struct {
106		args      []string
107		pkgs      []*load.Package
108		expected  string
109		expectErr bool
110		rootedAt  string
111	}{
112		{
113			args:     []string{"std"},
114			pkgs:     []*load.Package{},
115			expected: "std",
116		},
117		{
118			args:     []string{"std", "cmd"},
119			pkgs:     []*load.Package{},
120			expected: "std,cmd",
121		},
122		{
123			args:     []string{},
124			pkgs:     []*load.Package{pkgImportPath("gopkg.in/somelib")},
125			expected: "gopkg.in-somelib",
126		},
127		{
128			args:     []string{"./..."},
129			pkgs:     []*load.Package{pkgImportPath("somelib")},
130			expected: "somelib",
131			rootedAt: "somelib",
132		},
133		{
134			args:     []string{"../somelib", "../somelib"},
135			pkgs:     []*load.Package{pkgImportPath("somelib")},
136			expected: "somelib",
137		},
138		{
139			args:     []string{"../lib1", "../lib2"},
140			pkgs:     []*load.Package{pkgImportPath("gopkg.in/lib1"), pkgImportPath("gopkg.in/lib2")},
141			expected: "gopkg.in-lib1,gopkg.in-lib2",
142		},
143		{
144			args: []string{"./..."},
145			pkgs: []*load.Package{
146				pkgImportPath("gopkg.in/dir/lib1"),
147				pkgImportPath("gopkg.in/lib2"),
148				pkgImportPath("gopkg.in/lib3"),
149			},
150			expected: "gopkg.in",
151			rootedAt: "gopkg.in",
152		},
153		{
154			args:      []string{"std", "../lib2"},
155			pkgs:      []*load.Package{},
156			expectErr: true,
157		},
158		{
159			args:      []string{"all", "./"},
160			pkgs:      []*load.Package{},
161			expectErr: true,
162		},
163		{
164			args:      []string{"cmd", "fmt"},
165			pkgs:      []*load.Package{},
166			expectErr: true,
167		},
168	}
169	for _, data := range testData {
170		func() {
171			if data.rootedAt != "" {
172				tmpGopath, err := os.MkdirTemp("", "gopath")
173				if err != nil {
174					t.Fatal(err)
175				}
176				oldGopath := cfg.BuildContext.GOPATH
177				defer func() {
178					cfg.BuildContext.GOPATH = oldGopath
179					os.Chdir(base.Cwd)
180					err := os.RemoveAll(tmpGopath)
181					if err != nil {
182						t.Error(err)
183					}
184				}()
185				root := filepath.Join(tmpGopath, "src", data.rootedAt)
186				err = os.MkdirAll(root, 0755)
187				if err != nil {
188					t.Fatal(err)
189				}
190				cfg.BuildContext.GOPATH = tmpGopath
191				os.Chdir(root)
192			}
193			computed, err := libname(data.args, data.pkgs)
194			if err != nil {
195				if !data.expectErr {
196					t.Errorf("libname returned an error %q, expected a name", err.Error())
197				}
198			} else if data.expectErr {
199				t.Errorf("libname returned %q, expected an error", computed)
200			} else {
201				expected := prefix + data.expected + suffix
202				if expected != computed {
203					t.Errorf("libname returned %q, expected %q", computed, expected)
204				}
205			}
206		}()
207	}
208}
209
210func pkgImportPath(pkgpath string) *load.Package {
211	return &load.Package{
212		PackagePublic: load.PackagePublic{
213			ImportPath: pkgpath,
214		},
215	}
216}
217
218// When installing packages, the installed package directory should
219// respect the SetGID bit and group name of the destination
220// directory.
221// See https://golang.org/issue/18878.
222func TestRespectSetgidDir(t *testing.T) {
223	switch runtime.GOOS {
224	case "ios":
225		t.Skip("can't set SetGID bit with chmod on iOS")
226	case "windows", "plan9":
227		t.Skip("chown/chmod setgid are not supported on Windows or Plan 9")
228	}
229
230	var b Builder
231
232	// Check that `cp` is called instead of `mv` by looking at the output
233	// of `(*Builder).ShowCmd` afterwards as a sanity check.
234	cfg.BuildX = true
235	var cmdBuf bytes.Buffer
236	b.Print = func(a ...interface{}) (int, error) {
237		return cmdBuf.WriteString(fmt.Sprint(a...))
238	}
239
240	setgiddir, err := os.MkdirTemp("", "SetGroupID")
241	if err != nil {
242		t.Fatal(err)
243	}
244	defer os.RemoveAll(setgiddir)
245
246	// BSD mkdir(2) inherits the parent directory group, and other platforms
247	// can inherit the parent directory group via setgid. The test setup (chmod
248	// setgid) will fail if the process does not have the group permission to
249	// the new temporary directory.
250	err = os.Chown(setgiddir, os.Getuid(), os.Getgid())
251	if err != nil {
252		t.Fatal(err)
253	}
254
255	// Change setgiddir's permissions to include the SetGID bit.
256	if err := os.Chmod(setgiddir, 0755|fs.ModeSetgid); err != nil {
257		t.Fatal(err)
258	}
259
260	pkgfile, err := os.CreateTemp("", "pkgfile")
261	if err != nil {
262		t.Fatalf("os.CreateTemp(\"\", \"pkgfile\"): %v", err)
263	}
264	defer os.Remove(pkgfile.Name())
265	defer pkgfile.Close()
266
267	dirGIDFile := filepath.Join(setgiddir, "setgid")
268	if err := b.moveOrCopyFile(dirGIDFile, pkgfile.Name(), 0666, true); err != nil {
269		t.Fatalf("moveOrCopyFile: %v", err)
270	}
271
272	got := strings.TrimSpace(cmdBuf.String())
273	want := b.fmtcmd("", "cp %s %s", pkgfile.Name(), dirGIDFile)
274	if got != want {
275		t.Fatalf("moveOrCopyFile(%q, %q): want %q, got %q", dirGIDFile, pkgfile.Name(), want, got)
276	}
277}
278