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  }