| // Copyright 2020 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 main_test |
| |
| import ( |
| "fmt" |
| "os" |
| "os/exec" |
| "path/filepath" |
| "runtime" |
| "strings" |
| "sync" |
| "testing" |
| ) |
| |
| const comment = "This is a txtar archive.\n" |
| |
| const testdata = `This is a txtar archive. |
| -- one.txt -- |
| one |
| -- dir/two.txt -- |
| two |
| -- $SPECIAL_LOCATION/three.txt -- |
| three |
| ` |
| |
| var filelist = ` |
| one.txt |
| dir/two.txt |
| $SPECIAL_LOCATION/three.txt |
| `[1:] |
| |
| func TestMain(m *testing.M) { |
| code := m.Run() |
| txtarBin.once.Do(func() {}) |
| if txtarBin.name != "" { |
| os.Remove(txtarBin.name) |
| } |
| os.Exit(code) |
| } |
| |
| func TestRoundTrip(t *testing.T) { |
| os.Setenv("SPECIAL_LOCATION", "special") |
| defer os.Unsetenv("SPECIAL_LOCATION") |
| |
| // Expand the testdata archive into a temporary directory. |
| parentDir, err := os.MkdirTemp("", "txtar") |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer os.RemoveAll(parentDir) |
| dir := filepath.Join(parentDir, "dir") |
| if err := os.Mkdir(dir, 0755); err != nil { |
| t.Fatal(err) |
| } |
| |
| if out, err := txtar(t, dir, testdata, "--list"); err != nil { |
| t.Fatal(err) |
| } else if out != filelist { |
| t.Fatalf("txtar --list: stdout:\n%s\nwant:\n%s", out, filelist) |
| } |
| if entries, err := os.ReadDir(dir); err != nil { |
| t.Fatal(err) |
| } else if len(entries) > 0 { |
| t.Fatalf("txtar --list: did not expect any extracted files") |
| } |
| |
| if out, err := txtar(t, dir, testdata, "--extract"); err != nil { |
| t.Fatal(err) |
| } else if out != comment { |
| t.Fatalf("txtar --extract: stdout:\n%s\nwant:\n%s", out, comment) |
| } |
| |
| // Now, re-archive its contents explicitly and ensure that the result matches |
| // the original. |
| args := []string{"one.txt", "dir", "$SPECIAL_LOCATION"} |
| if out, err := txtar(t, dir, comment, args...); err != nil { |
| t.Fatal(err) |
| } else if out != testdata { |
| t.Fatalf("txtar %s: archive:\n%s\n\nwant:\n%s", strings.Join(args, " "), out, testdata) |
| } |
| } |
| |
| func TestUnsafePaths(t *testing.T) { |
| // Set up temporary directories for test archives. |
| parentDir, err := os.MkdirTemp("", "txtar") |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer os.RemoveAll(parentDir) |
| dir := filepath.Join(parentDir, "dir") |
| if err := os.Mkdir(dir, 0755); err != nil { |
| t.Fatal(err) |
| } |
| |
| // Test --unsafe option for both absolute and relative paths |
| testcases := []struct{ name, path string }{ |
| {"Absolute", filepath.Join(parentDir, "dirSpecial")}, |
| {"Relative", "../special"}, |
| } |
| |
| for _, tc := range testcases { |
| t.Run(tc.name, func(t *testing.T) { |
| // Set SPECIAL_LOCATION outside the current directory |
| t.Setenv("SPECIAL_LOCATION", tc.path) |
| |
| // Expand the testdata archive into a temporary directory. |
| |
| // Should fail without the --unsafe flag |
| if _, err := txtar(t, dir, testdata, "--extract"); err == nil { |
| t.Fatalf("txtar --extract: extracts to unsafe paths") |
| } |
| |
| // Should allow paths outside the current dir with the --unsafe flags |
| out, err := txtar(t, dir, testdata, "--extract", "--unsafe") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if out != comment { |
| t.Fatalf("txtar --extract --unsafe: stdout:\n%s\nwant:\n%s", out, comment) |
| } |
| |
| // Now, re-archive its contents explicitly and ensure that the result matches |
| // the original. |
| args := []string{"one.txt", "dir", "$SPECIAL_LOCATION"} |
| out, err = txtar(t, dir, comment, args...) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if out != testdata { |
| t.Fatalf("txtar %s: archive:\n%s\n\nwant:\n%s", strings.Join(args, " "), out, testdata) |
| } |
| }) |
| } |
| } |
| |
| // txtar runs the txtar command in the given directory with the given input and |
| // arguments. |
| func txtar(t *testing.T, dir, input string, args ...string) (string, error) { |
| t.Helper() |
| cmd := exec.Command(txtarName(t), args...) |
| cmd.Dir = dir |
| cmd.Env = append(os.Environ(), "PWD="+dir) |
| cmd.Stdin = strings.NewReader(input) |
| stderr := new(strings.Builder) |
| cmd.Stderr = stderr |
| out, err := cmd.Output() |
| if err != nil { |
| return "", fmt.Errorf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, stderr) |
| } |
| if stderr.String() != "" { |
| t.Logf("OK: %s\n%s", strings.Join(cmd.Args, " "), stderr) |
| } |
| return string(out), nil |
| } |
| |
| var txtarBin struct { |
| once sync.Once |
| name string |
| err error |
| } |
| |
| // txtarName returns the name of the txtar executable, building it if needed. |
| func txtarName(t *testing.T) string { |
| t.Helper() |
| if _, err := exec.LookPath("go"); err != nil { |
| t.Skipf("cannot build txtar binary: %v", err) |
| } |
| |
| txtarBin.once.Do(func() { |
| exe, err := os.CreateTemp("", "txtar-*.exe") |
| if err != nil { |
| txtarBin.err = err |
| return |
| } |
| exe.Close() |
| txtarBin.name = exe.Name() |
| |
| cmd := exec.Command("go", "build", "-o", txtarBin.name, ".") |
| out, err := cmd.CombinedOutput() |
| if err != nil { |
| txtarBin.err = fmt.Errorf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, out) |
| } |
| }) |
| |
| if txtarBin.err != nil { |
| if runtime.GOOS == "android" { |
| t.Skipf("skipping test after failing to build txtar binary: go_android_exec may have failed to copy needed dependencies (see https://golang.org/issue/37088)") |
| } |
| t.Fatal(txtarBin.err) |
| } |
| return txtarBin.name |
| } |