cmd/go/internal/txtar: new text archive format
This format is for storing tests and test modules.
Change-Id: I3d2e30653bcedc82f80211631307ce64c2a43265
Reviewed-on: https://go-review.googlesource.com/123359
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
diff --git a/vendor/cmd/go/internal/txtar/archive.go b/vendor/cmd/go/internal/txtar/archive.go
new file mode 100644
index 0000000..923997c
--- /dev/null
+++ b/vendor/cmd/go/internal/txtar/archive.go
@@ -0,0 +1,142 @@
+// Copyright 2018 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 txtar implements a trivial text-based file archive format.
+//
+// The goals for the format are:
+//
+// - be trivial enough to create and edit by hand.
+// - be able to store trees of text files describing go command test cases.
+// - diff nicely in git history and code reviews.
+//
+// Non-goals include being a completely general archive format,
+// storing binary data, storing file modes, storing special files like
+// symbolic links, and so on.
+//
+// Txtar format
+//
+// A txtar archive is zero or more comment lines and then a sequence of file entries.
+// Each file entry begins with a file marker line of the form "-- FILENAME --"
+// and is followed by zero or more file content lines making up the file data.
+// The comment or file content ends at the next file marker line.
+// The file marker line must begin with the three-byte sequence "-- "
+// and end with the three-byte sequence " --", but the enclosed
+// file name can be surrounded by additional white space,
+// all of which is stripped. The file name may itself contain spaces,
+// which are preserved.
+//
+// If the txtar file is missing a trailing newline on the final line,
+// parsers should consider a final newline to be present anyway.
+//
+// There are no possible syntax errors in a txtar archive.
+package txtar
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "strings"
+)
+
+// An Archive is a collection of files.
+type Archive struct {
+ Comment []byte
+ Files []File
+}
+
+// A File is a single file in an archive.
+type File struct {
+ Name string // name of file ("foo/bar.txt")
+ Data []byte // text content of file
+}
+
+// Format returns the serialized form of an Archive.
+// It is assumed that the Archive data structure is well-formed:
+// a.Comment and all a.File[i].Data contain no file marker lines,
+// and all a.File[i].Name are non-empty and well-formed
+// (no leading or trailing spaces, no newlines, and so on).
+func Format(a *Archive) []byte {
+ var buf bytes.Buffer
+ buf.Write(fixNL(a.Comment))
+ for _, f := range a.Files {
+ fmt.Fprintf(&buf, "-- %s --\n", f.Name)
+ buf.Write(fixNL(f.Data))
+ }
+ return buf.Bytes()
+}
+
+// ParseFile parses the named file as an archive.
+func ParseFile(file string) (*Archive, error) {
+ data, err := ioutil.ReadFile(file)
+ if err != nil {
+ return nil, err
+ }
+ return Parse(data), nil
+}
+
+// Parse parses the serialized form of an Archive.
+// The returned Archive holds slices of data.
+func Parse(data []byte) *Archive {
+ a := new(Archive)
+ var name string
+ a.Comment, name, data = findFileMarker(data)
+ for name != "" {
+ f := File{name, nil}
+ f.Data, name, data = findFileMarker(data)
+ a.Files = append(a.Files, f)
+ }
+ return a
+}
+
+var (
+ newlineMarker = []byte("\n-- ")
+ marker = []byte("-- ")
+ markerEnd = []byte(" --")
+)
+
+// findFileMarker finds the next file marker in data,
+// extracts the file name, and returns the data before the marker,
+// the file name, and the data after the marker.
+// If there is no next marker, findFileMarker returns before = fixNL(data), name = "", after = nil.
+func findFileMarker(data []byte) (before []byte, name string, after []byte) {
+ var i int
+ for {
+ if name, after = isMarker(data[i:]); name != "" {
+ return data[:i], name, after
+ }
+ j := bytes.Index(data[i:], newlineMarker)
+ if j < 0 {
+ return fixNL(data), "", nil
+ }
+ i += j + 1 // positioned at start of new possible marker
+ }
+}
+
+// isMarker checks whether data begins with a file marker line.
+// If so, it returns the name from the line and the data after the line.
+// Otherwise it returns name == "" with an unspecified after.
+func isMarker(data []byte) (name string, after []byte) {
+ if !bytes.HasPrefix(data, marker) {
+ return "", nil
+ }
+ if i := bytes.IndexByte(data, '\n'); i >= 0 {
+ data, after = data[:i], data[i+1:]
+ }
+ if !bytes.HasSuffix(data, markerEnd) {
+ return "", nil
+ }
+ return strings.TrimSpace(string(data[len(marker) : len(data)-len(markerEnd)])), after
+}
+
+// If data is empty or ends in \n, fixNL returns data.
+// Otherwise fixNL returns a new slice consisting of data with a final \n added.
+func fixNL(data []byte) []byte {
+ if len(data) == 0 || data[len(data)-1] == '\n' {
+ return data
+ }
+ d := make([]byte, len(data)+1)
+ copy(d, data)
+ d[len(data)] = '\n'
+ return d
+}
diff --git a/vendor/cmd/go/internal/txtar/archive_test.go b/vendor/cmd/go/internal/txtar/archive_test.go
new file mode 100644
index 0000000..3f734f6
--- /dev/null
+++ b/vendor/cmd/go/internal/txtar/archive_test.go
@@ -0,0 +1,67 @@
+// Copyright 2018 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 txtar
+
+import (
+ "bytes"
+ "fmt"
+ "reflect"
+ "testing"
+)
+
+var tests = []struct {
+ name string
+ text string
+ parsed *Archive
+}{
+ {
+ name: "basic",
+ text: `comment1
+comment2
+-- file1 --
+File 1 text.
+-- foo ---
+More file 1 text.
+-- file 2 --
+File 2 text.
+-- empty --
+-- noNL --
+hello world`,
+ parsed: &Archive{
+ Comment: []byte("comment1\ncomment2\n"),
+ Files: []File{
+ {"file1", []byte("File 1 text.\n-- foo ---\nMore file 1 text.\n")},
+ {"file 2", []byte("File 2 text.\n")},
+ {"empty", []byte{}},
+ {"noNL", []byte("hello world\n")},
+ },
+ },
+ },
+}
+
+func Test(t *testing.T) {
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ a := Parse([]byte(tt.text))
+ if !reflect.DeepEqual(a, tt.parsed) {
+ t.Fatalf("Parse: wrong output:\nhave:\n%s\nwant:\n%s", shortArchive(a), shortArchive(tt.parsed))
+ }
+ text := Format(a)
+ a = Parse(text)
+ if !reflect.DeepEqual(a, tt.parsed) {
+ t.Fatalf("Parse after Format: wrong output:\nhave:\n%s\nwant:\n%s", shortArchive(a), shortArchive(tt.parsed))
+ }
+ })
+ }
+}
+
+func shortArchive(a *Archive) string {
+ var buf bytes.Buffer
+ fmt.Fprintf(&buf, "comment: %q\n", a.Comment)
+ for _, f := range a.Files {
+ fmt.Fprintf(&buf, "file %q: %q\n", f.Name, f.Data)
+ }
+ return buf.String()
+}
diff --git a/vendor/cmd/go/testdata/addmod.go b/vendor/cmd/go/testdata/addmod.go
new file mode 100644
index 0000000..a3deba9
--- /dev/null
+++ b/vendor/cmd/go/testdata/addmod.go
@@ -0,0 +1,150 @@
+// Copyright 2018 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.
+
+// +build ignore
+
+// Addmod adds a module as a txtar archive to the testdata/mod directory.
+//
+// Usage:
+//
+// go run addmod.go path@version...
+//
+// It should only be used for very small modules - we do not want to check
+// very large files into testdata/mod.
+//
+// It is acceptable to edit the archive afterward to remove or shorten files.
+// See mod/README for more information.
+//
+package main
+
+import (
+ "bytes"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+
+ "../internal/txtar"
+)
+
+func usage() {
+ fmt.Fprintf(os.Stderr, "usage: go run addmod.go path@version...\n")
+ os.Exit(2)
+}
+
+var tmpdir string
+
+func fatalf(format string, args ...interface{}) {
+ os.RemoveAll(tmpdir)
+ log.Fatalf(format, args...)
+}
+
+const goCmd = "vgo"
+
+func main() {
+ flag.Usage = usage
+ flag.Parse()
+ if flag.NArg() == 0 {
+ usage()
+ }
+
+ log.SetPrefix("addmod: ")
+ log.SetFlags(0)
+
+ var err error
+ tmpdir, err = ioutil.TempDir("", "addmod-")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ run := func(command string, args ...string) string {
+ cmd := exec.Command(command, args...)
+ cmd.Dir = tmpdir
+ var stderr bytes.Buffer
+ cmd.Stderr = &stderr
+ out, err := cmd.Output()
+ if err != nil {
+ fatalf("%s %s: %v\n%s", command, strings.Join(args, " "), err, stderr.Bytes())
+ }
+ return string(out)
+ }
+
+ gopath := strings.TrimSpace(run("go", "env", "GOPATH"))
+ if gopath == "" {
+ fatalf("cannot find GOPATH")
+ }
+
+ exitCode := 0
+ for _, arg := range flag.Args() {
+ if err := ioutil.WriteFile(filepath.Join(tmpdir, "go.mod"), []byte("module m\n"), 0666); err != nil {
+ fatalf("%v", err)
+ }
+ run(goCmd, "get", "-d", arg)
+ path := arg
+ if i := strings.Index(path, "@"); i >= 0 {
+ path = path[:i]
+ }
+ out := run(goCmd, "list", "-m", "-f={{.Path}} {{.Version}} {{.Dir}}", path)
+ f := strings.Fields(out)
+ if len(f) != 3 {
+ log.Printf("go list -m %s: unexpected output %q", arg, out)
+ exitCode = 1
+ continue
+ }
+ path, vers, dir := f[0], f[1], f[2]
+ mod, err := ioutil.ReadFile(filepath.Join(gopath, "src/mod/cache/download", path, "@v", vers+".mod"))
+ if err != nil {
+ log.Printf("%s: %v", arg, err)
+ exitCode = 1
+ continue
+ }
+ info, err := ioutil.ReadFile(filepath.Join(gopath, "src/mod/cache/download", path, "@v", vers+".info"))
+ if err != nil {
+ log.Printf("%s: %v", arg, err)
+ exitCode = 1
+ continue
+ }
+
+ a := new(txtar.Archive)
+ a.Comment = []byte(fmt.Sprintf("module %s@%s\n\n", path, vers))
+ a.Files = []txtar.File{
+ {Name: ".mod", Data: mod},
+ {Name: ".info", Data: info},
+ }
+ dir = filepath.Clean(dir)
+ err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+ if !info.Mode().IsRegular() {
+ return nil
+ }
+ name := info.Name()
+ if name == "go.mod" || strings.HasSuffix(name, ".go") {
+ data, err := ioutil.ReadFile(path)
+ if err != nil {
+ return err
+ }
+ a.Files = append(a.Files, txtar.File{Name: strings.TrimPrefix(path, dir+string(filepath.Separator)), Data: data})
+ }
+ return nil
+ })
+ if err != nil {
+ log.Printf("%s: %v", arg, err)
+ exitCode = 1
+ continue
+ }
+
+ data := txtar.Format(a)
+ target := filepath.Join("mod", strings.Replace(path, "/", "_", -1)+"_"+vers+".txt")
+ if err := ioutil.WriteFile(target, data, 0666); err != nil {
+ log.Printf("%s: %v", arg, err)
+ exitCode = 1
+ continue
+ }
+ }
+ os.RemoveAll(tmpdir)
+ os.Exit(exitCode)
+}
diff --git a/vendor/cmd/go/testdata/savedir.go b/vendor/cmd/go/testdata/savedir.go
new file mode 100644
index 0000000..48a6318
--- /dev/null
+++ b/vendor/cmd/go/testdata/savedir.go
@@ -0,0 +1,79 @@
+// Copyright 2018 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.
+
+// +build ignore
+
+// Savedir archives a directory tree as a txtar archive printed to standard output.
+//
+// Usage:
+//
+// go run savedir.go /path/to/dir >saved.txt
+//
+// Typically the tree is later extracted during a test with tg.extract("testdata/saved.txt").
+//
+package main
+
+import (
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+ "unicode/utf8"
+
+ "../internal/txtar"
+)
+
+func usage() {
+ fmt.Fprintf(os.Stderr, "usage: go run savedir.go dir >saved.txt\n")
+ os.Exit(2)
+}
+
+const goCmd = "vgo"
+
+func main() {
+ flag.Usage = usage
+ flag.Parse()
+ if flag.NArg() != 1 {
+ usage()
+ }
+
+ log.SetPrefix("savedir: ")
+ log.SetFlags(0)
+
+ dir := flag.Arg(0)
+
+ a := new(txtar.Archive)
+ dir = filepath.Clean(dir)
+ filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+ if path == dir {
+ return nil
+ }
+ name := info.Name()
+ if strings.HasPrefix(name, ".") {
+ if info.IsDir() {
+ return filepath.SkipDir
+ }
+ return nil
+ }
+ if !info.Mode().IsRegular() {
+ return nil
+ }
+ data, err := ioutil.ReadFile(path)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if !utf8.Valid(data) {
+ log.Printf("%s: ignoring invalid UTF-8 data", path)
+ return nil
+ }
+ a.Files = append(a.Files, txtar.File{Name: strings.TrimPrefix(path, dir+string(filepath.Separator)), Data: data})
+ return nil
+ })
+
+ data := txtar.Format(a)
+ os.Stdout.Write(data)
+}