golang.org/x/exp@v0.0.0-20240506185415-9bf2ced13842/cmd/txtar/txtar_test.go (about) 1 // Copyright 2020 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 5 package main_test 6 7 import ( 8 "fmt" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "runtime" 13 "strings" 14 "sync" 15 "testing" 16 ) 17 18 const comment = "This is a txtar archive.\n" 19 20 const testdata = `This is a txtar archive. 21 -- one.txt -- 22 one 23 -- dir/two.txt -- 24 two 25 -- $SPECIAL_LOCATION/three.txt -- 26 three 27 ` 28 29 var filelist = ` 30 one.txt 31 dir/two.txt 32 $SPECIAL_LOCATION/three.txt 33 `[1:] 34 35 func TestMain(m *testing.M) { 36 code := m.Run() 37 txtarBin.once.Do(func() {}) 38 if txtarBin.name != "" { 39 os.Remove(txtarBin.name) 40 } 41 os.Exit(code) 42 } 43 44 func TestRoundTrip(t *testing.T) { 45 os.Setenv("SPECIAL_LOCATION", "special") 46 defer os.Unsetenv("SPECIAL_LOCATION") 47 48 // Expand the testdata archive into a temporary directory. 49 parentDir, err := os.MkdirTemp("", "txtar") 50 if err != nil { 51 t.Fatal(err) 52 } 53 defer os.RemoveAll(parentDir) 54 dir := filepath.Join(parentDir, "dir") 55 if err := os.Mkdir(dir, 0755); err != nil { 56 t.Fatal(err) 57 } 58 59 if out, err := txtar(t, dir, testdata, "--list"); err != nil { 60 t.Fatal(err) 61 } else if out != filelist { 62 t.Fatalf("txtar --list: stdout:\n%s\nwant:\n%s", out, filelist) 63 } 64 if entries, err := os.ReadDir(dir); err != nil { 65 t.Fatal(err) 66 } else if len(entries) > 0 { 67 t.Fatalf("txtar --list: did not expect any extracted files") 68 } 69 70 if out, err := txtar(t, dir, testdata, "--extract"); err != nil { 71 t.Fatal(err) 72 } else if out != comment { 73 t.Fatalf("txtar --extract: stdout:\n%s\nwant:\n%s", out, comment) 74 } 75 76 // Now, re-archive its contents explicitly and ensure that the result matches 77 // the original. 78 args := []string{"one.txt", "dir", "$SPECIAL_LOCATION"} 79 if out, err := txtar(t, dir, comment, args...); err != nil { 80 t.Fatal(err) 81 } else if out != testdata { 82 t.Fatalf("txtar %s: archive:\n%s\n\nwant:\n%s", strings.Join(args, " "), out, testdata) 83 } 84 } 85 86 func TestUnsafePaths(t *testing.T) { 87 // Set up temporary directories for test archives. 88 parentDir, err := os.MkdirTemp("", "txtar") 89 if err != nil { 90 t.Fatal(err) 91 } 92 defer os.RemoveAll(parentDir) 93 dir := filepath.Join(parentDir, "dir") 94 if err := os.Mkdir(dir, 0755); err != nil { 95 t.Fatal(err) 96 } 97 98 // Test --unsafe option for both absolute and relative paths 99 testcases := []struct{ name, path string }{ 100 {"Absolute", filepath.Join(parentDir, "dirSpecial")}, 101 {"Relative", "../special"}, 102 } 103 104 for _, tc := range testcases { 105 t.Run(tc.name, func(t *testing.T) { 106 // Set SPECIAL_LOCATION outside the current directory 107 t.Setenv("SPECIAL_LOCATION", tc.path) 108 109 // Expand the testdata archive into a temporary directory. 110 111 // Should fail without the --unsafe flag 112 if _, err := txtar(t, dir, testdata, "--extract"); err == nil { 113 t.Fatalf("txtar --extract: extracts to unsafe paths") 114 } 115 116 // Should allow paths outside the current dir with the --unsafe flags 117 out, err := txtar(t, dir, testdata, "--extract", "--unsafe") 118 if err != nil { 119 t.Fatal(err) 120 } 121 if out != comment { 122 t.Fatalf("txtar --extract --unsafe: stdout:\n%s\nwant:\n%s", out, comment) 123 } 124 125 // Now, re-archive its contents explicitly and ensure that the result matches 126 // the original. 127 args := []string{"one.txt", "dir", "$SPECIAL_LOCATION"} 128 out, err = txtar(t, dir, comment, args...) 129 if err != nil { 130 t.Fatal(err) 131 } 132 if out != testdata { 133 t.Fatalf("txtar %s: archive:\n%s\n\nwant:\n%s", strings.Join(args, " "), out, testdata) 134 } 135 }) 136 } 137 } 138 139 // txtar runs the txtar command in the given directory with the given input and 140 // arguments. 141 func txtar(t *testing.T, dir, input string, args ...string) (string, error) { 142 t.Helper() 143 cmd := exec.Command(txtarName(t), args...) 144 cmd.Dir = dir 145 cmd.Env = append(os.Environ(), "PWD="+dir) 146 cmd.Stdin = strings.NewReader(input) 147 stderr := new(strings.Builder) 148 cmd.Stderr = stderr 149 out, err := cmd.Output() 150 if err != nil { 151 return "", fmt.Errorf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, stderr) 152 } 153 if stderr.String() != "" { 154 t.Logf("OK: %s\n%s", strings.Join(cmd.Args, " "), stderr) 155 } 156 return string(out), nil 157 } 158 159 var txtarBin struct { 160 once sync.Once 161 name string 162 err error 163 } 164 165 // txtarName returns the name of the txtar executable, building it if needed. 166 func txtarName(t *testing.T) string { 167 t.Helper() 168 if _, err := exec.LookPath("go"); err != nil { 169 t.Skipf("cannot build txtar binary: %v", err) 170 } 171 172 txtarBin.once.Do(func() { 173 exe, err := os.CreateTemp("", "txtar-*.exe") 174 if err != nil { 175 txtarBin.err = err 176 return 177 } 178 exe.Close() 179 txtarBin.name = exe.Name() 180 181 cmd := exec.Command("go", "build", "-o", txtarBin.name, ".") 182 out, err := cmd.CombinedOutput() 183 if err != nil { 184 txtarBin.err = fmt.Errorf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, out) 185 } 186 }) 187 188 if txtarBin.err != nil { 189 if runtime.GOOS == "android" { 190 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)") 191 } 192 t.Fatal(txtarBin.err) 193 } 194 return txtarBin.name 195 }