mirror of
https://github.com/openshift/installer.git
synced 2026-02-05 06:46:36 +01:00
vendor: add testscript
This commit is contained in:
8
go.mod
8
go.mod
@@ -24,12 +24,15 @@ require (
|
||||
github.com/apparentlymart/go-cidr v1.1.0
|
||||
github.com/awalterschulze/gographviz v0.0.0-20190522210029-fa59802746ab
|
||||
github.com/aws/aws-sdk-go v1.44.51
|
||||
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e
|
||||
github.com/clarketm/json v1.14.1
|
||||
github.com/containers/image v3.0.2+incompatible
|
||||
github.com/coreos/ignition/v2 v2.14.0
|
||||
github.com/coreos/stream-metadata-go v0.1.8
|
||||
github.com/diskfs/go-diskfs v1.2.1-0.20210727185522-a769efacd235
|
||||
github.com/form3tech-oss/jwt-go v3.2.3+incompatible
|
||||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
|
||||
github.com/go-openapi/errors v0.20.2
|
||||
github.com/go-openapi/strfmt v0.21.2
|
||||
github.com/go-playground/validator/v10 v10.2.0
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible
|
||||
@@ -66,10 +69,12 @@ require (
|
||||
github.com/ovirt/go-ovirt v0.0.0-20210809163552-d4276e35d3db
|
||||
github.com/pborman/uuid v1.2.0
|
||||
github.com/pelletier/go-toml v1.9.5
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pkg/sftp v1.10.1
|
||||
github.com/prometheus/client_golang v1.12.2
|
||||
github.com/prometheus/common v0.32.1
|
||||
github.com/rogpeppe/go-internal v1.9.0
|
||||
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/cobra v1.5.0
|
||||
@@ -140,14 +145,12 @@ require (
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
|
||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/coreos/go-semver v0.3.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
||||
github.com/coreos/vcontext v0.0.0-20211021162308-f1dbbca7bef4 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dimchansky/utfbom v1.1.0 // indirect
|
||||
github.com/diskfs/go-diskfs v1.2.1-0.20210727185522-a769efacd235 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
@@ -155,7 +158,6 @@ require (
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-logr/zapr v1.2.3 // indirect
|
||||
github.com/go-openapi/analysis v0.21.2 // indirect
|
||||
github.com/go-openapi/errors v0.20.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||
github.com/go-openapi/loads v0.21.1 // indirect
|
||||
|
||||
4
go.sum
4
go.sum
@@ -1217,6 +1217,8 @@ github.com/pierrec/lz4 v2.3.0+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi
|
||||
github.com/pin/tftp v2.1.0+incompatible/go.mod h1:xVpZOMCXTy+A5QMjEVN0Glwa1sUvaJhFXbr/aAxuxGY=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -1288,6 +1290,8 @@ github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYe
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
|
||||
|
||||
11
vendor/github.com/pkg/diff/LICENSE
generated
vendored
Normal file
11
vendor/github.com/pkg/diff/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
Copyright 2018 Joshua Bleecher Snyder
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
92
vendor/github.com/pkg/diff/ctxt/size.go
generated
vendored
Normal file
92
vendor/github.com/pkg/diff/ctxt/size.go
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
// Package ctxt provides routines to reduce the amount of context in an edit script.
|
||||
package ctxt
|
||||
|
||||
import (
|
||||
"github.com/pkg/diff/edit"
|
||||
)
|
||||
|
||||
// Size returns an edit script preserving only n common elements of context for changes.
|
||||
// The returned edit script may alias the input.
|
||||
// If n is negative, Size panics.
|
||||
func Size(e edit.Script, n int) edit.Script {
|
||||
if n < 0 {
|
||||
panic("ctxt.Size called with negative n")
|
||||
}
|
||||
|
||||
// Handle small scripts.
|
||||
switch len(e.Ranges) {
|
||||
case 0:
|
||||
return edit.Script{}
|
||||
case 1:
|
||||
if e.Ranges[0].IsEqual() {
|
||||
// Entirely identical contents.
|
||||
// Unclear what to do here. For now, just bail.
|
||||
// TODO: something else? what does command line diff do?
|
||||
return edit.Script{}
|
||||
}
|
||||
return edit.NewScript(e.Ranges[0])
|
||||
}
|
||||
|
||||
out := make([]edit.Range, 0, len(e.Ranges))
|
||||
for i, seg := range e.Ranges {
|
||||
if !seg.IsEqual() {
|
||||
out = append(out, seg)
|
||||
continue
|
||||
}
|
||||
if i == 0 {
|
||||
// Leading Range. Keep only the final n entries.
|
||||
if seg.Len() > n {
|
||||
seg = rangeLastN(seg, n)
|
||||
}
|
||||
out = append(out, seg)
|
||||
continue
|
||||
}
|
||||
if i == len(e.Ranges)-1 {
|
||||
// Trailing Range. Keep only the first n entries.
|
||||
if seg.Len() > n {
|
||||
seg = rangeFirstN(seg, n)
|
||||
}
|
||||
out = append(out, seg)
|
||||
continue
|
||||
}
|
||||
if seg.Len() <= n*2 {
|
||||
// Small middle Range. Keep unchanged.
|
||||
out = append(out, seg)
|
||||
continue
|
||||
}
|
||||
// Large middle Range. Break into two disjoint parts.
|
||||
out = append(out, rangeFirstN(seg, n), rangeLastN(seg, n))
|
||||
}
|
||||
|
||||
// TODO: Stock macOS diff also trims common blank lines
|
||||
// from the beginning/end of eq IndexRangess.
|
||||
// Perhaps we should do that here too.
|
||||
// Or perhaps that should be a separate, composable function?
|
||||
return edit.Script{Ranges: out}
|
||||
}
|
||||
|
||||
func rangeFirstN(seg edit.Range, n int) edit.Range {
|
||||
if !seg.IsEqual() {
|
||||
panic("rangeFirstN bad op")
|
||||
}
|
||||
if seg.Len() < n {
|
||||
panic("rangeFirstN bad Len")
|
||||
}
|
||||
return edit.Range{
|
||||
LowA: seg.LowA, HighA: seg.LowA + n,
|
||||
LowB: seg.LowB, HighB: seg.LowB + n,
|
||||
}
|
||||
}
|
||||
|
||||
func rangeLastN(seg edit.Range, n int) edit.Range {
|
||||
if !seg.IsEqual() {
|
||||
panic("rangeLastN bad op")
|
||||
}
|
||||
if seg.Len() < n {
|
||||
panic("rangeLastN bad Len")
|
||||
}
|
||||
return edit.Range{
|
||||
LowA: seg.HighA - n, HighA: seg.HighA,
|
||||
LowB: seg.HighB - n, HighB: seg.HighB,
|
||||
}
|
||||
}
|
||||
6
vendor/github.com/pkg/diff/ctxt/todo.go
generated
vendored
Normal file
6
vendor/github.com/pkg/diff/ctxt/todo.go
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
package ctxt
|
||||
|
||||
// TODO: the standard way to reduce context is to a fixed number of lines around changes.
|
||||
// But it would be better to be more flexible, to try to match human needs.
|
||||
// For example, if I deleted the first line of a function, I don't need three full lines of "before" context;
|
||||
// it should truncate at the function declaration.
|
||||
138
vendor/github.com/pkg/diff/diff.go
generated
vendored
Normal file
138
vendor/github.com/pkg/diff/diff.go
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
// Package diff contains high level routines that generate a textual diff.
|
||||
//
|
||||
// It is implemented in terms of the other packages in this module.
|
||||
// If you want fine-grained control,
|
||||
// want to inspect a diff programmatically,
|
||||
// want to provide a context for cancellation,
|
||||
// need to diff gigantic files that don't fit in memory,
|
||||
// or want to diff unusual things,
|
||||
// use the lower level packages.
|
||||
package diff
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/diff/ctxt"
|
||||
"github.com/pkg/diff/intern"
|
||||
"github.com/pkg/diff/myers"
|
||||
"github.com/pkg/diff/write"
|
||||
)
|
||||
|
||||
// lines returns the lines contained in text/filename.
|
||||
// text and filename are interpreted as described in the docs for Text.
|
||||
func lines(m intern.Strings, filename string, text interface{}) ([]*string, error) {
|
||||
var r io.Reader
|
||||
switch text := text.(type) {
|
||||
case nil:
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
r = f
|
||||
case string:
|
||||
r = strings.NewReader(text)
|
||||
case []byte:
|
||||
r = bytes.NewReader(text)
|
||||
case io.Reader:
|
||||
r = text
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected type %T, want string, []byte, io.Reader, or nil", text)
|
||||
}
|
||||
var x []*string
|
||||
scan := bufio.NewScanner(r)
|
||||
for scan.Scan() {
|
||||
x = append(x, m.FromBytes(scan.Bytes()))
|
||||
}
|
||||
return x, scan.Err()
|
||||
}
|
||||
|
||||
// addNames adds a Names write.Option using aName and bName,
|
||||
// taking care to put it at the end,
|
||||
// so as not to overwrite any competing option.
|
||||
func addNames(aName, bName string, options []write.Option) []write.Option {
|
||||
opts := make([]write.Option, len(options)+1)
|
||||
opts[0] = write.Names(aName, bName)
|
||||
copy(opts[1:], options)
|
||||
return opts
|
||||
}
|
||||
|
||||
// Text diffs a and b and writes the result to w.
|
||||
// It treats a and b as text, and splits their contents
|
||||
// into lines using bufio.ScanLines.
|
||||
// aFile and bFile are filenames to use in the output.
|
||||
//
|
||||
// a and b each may be nil or may have type string, []byte, or io.Reader.
|
||||
// If nil, the text is read from the filename.
|
||||
func Text(aFile, bFile string, a, b interface{}, w io.Writer, options ...write.Option) error {
|
||||
m := make(intern.Strings)
|
||||
aLines, err := lines(m, aFile, a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bLines, err := lines(m, bFile, b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ab := &diffStrings{a: aLines, b: bLines}
|
||||
s := myers.Diff(context.Background(), ab)
|
||||
s = ctxt.Size(s, 3)
|
||||
opts := addNames(aFile, bFile, options)
|
||||
err = write.Unified(s, w, ab, opts...)
|
||||
return err
|
||||
}
|
||||
|
||||
type diffStrings struct {
|
||||
a, b []*string
|
||||
}
|
||||
|
||||
func (ab *diffStrings) LenA() int { return len(ab.a) }
|
||||
func (ab *diffStrings) LenB() int { return len(ab.b) }
|
||||
func (ab *diffStrings) Equal(ai, bi int) bool { return ab.a[ai] == ab.b[bi] }
|
||||
func (ab *diffStrings) WriteATo(w io.Writer, i int) (int, error) { return io.WriteString(w, *ab.a[i]) }
|
||||
func (ab *diffStrings) WriteBTo(w io.Writer, i int) (int, error) { return io.WriteString(w, *ab.b[i]) }
|
||||
|
||||
// Slices diffs slices a and b and writes the result to w.
|
||||
// It uses fmt.Print to print the elements of a and b.
|
||||
// It uses reflect.DeepEqual to compare elements of a and b.
|
||||
// It uses aName and bName as the names of a and b in the output.
|
||||
func Slices(aName, bName string, a, b interface{}, w io.Writer, options ...write.Option) error {
|
||||
ab := &diffSlices{a: reflect.ValueOf(a), b: reflect.ValueOf(b)}
|
||||
if err := ab.validateTypes(); err != nil {
|
||||
return err
|
||||
}
|
||||
s := myers.Diff(context.Background(), ab)
|
||||
s = ctxt.Size(s, 3)
|
||||
opts := addNames(aName, bName, options)
|
||||
err := write.Unified(s, w, ab, opts...)
|
||||
return err
|
||||
}
|
||||
|
||||
type diffSlices struct {
|
||||
a, b reflect.Value
|
||||
}
|
||||
|
||||
func (ab *diffSlices) LenA() int { return ab.a.Len() }
|
||||
func (ab *diffSlices) LenB() int { return ab.b.Len() }
|
||||
func (ab *diffSlices) atA(i int) interface{} { return ab.a.Index(i).Interface() }
|
||||
func (ab *diffSlices) atB(i int) interface{} { return ab.b.Index(i).Interface() }
|
||||
func (ab *diffSlices) Equal(ai, bi int) bool { return reflect.DeepEqual(ab.atA(ai), ab.atB(bi)) }
|
||||
func (ab *diffSlices) WriteATo(w io.Writer, i int) (int, error) { return fmt.Fprint(w, ab.atA(i)) }
|
||||
func (ab *diffSlices) WriteBTo(w io.Writer, i int) (int, error) { return fmt.Fprint(w, ab.atB(i)) }
|
||||
|
||||
func (ab *diffSlices) validateTypes() error {
|
||||
if t := ab.a.Type(); t.Kind() != reflect.Slice {
|
||||
return fmt.Errorf("a has type %v, must be a slice", t)
|
||||
}
|
||||
if t := ab.b.Type(); t.Kind() != reflect.Slice {
|
||||
return fmt.Errorf("b has type %v, must be a slice", t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
118
vendor/github.com/pkg/diff/edit/edit.go
generated
vendored
Normal file
118
vendor/github.com/pkg/diff/edit/edit.go
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
// Package edit provides edit scripts.
|
||||
// Edit scripts are a core notion for diffs.
|
||||
// They represent a way to go from A to B by a sequence
|
||||
// of insertions, deletions, and equal elements.
|
||||
package edit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A Script is an edit script to alter A into B.
|
||||
type Script struct {
|
||||
Ranges []Range
|
||||
}
|
||||
|
||||
// NewScript returns a Script containing the ranges r.
|
||||
// It is only a convenience wrapper used to reduce line noise.
|
||||
func NewScript(r ...Range) Script {
|
||||
return Script{Ranges: r}
|
||||
}
|
||||
|
||||
// IsIdentity reports whether s is the identity edit script,
|
||||
// that is, whether A and B are identical.
|
||||
func (s *Script) IsIdentity() bool {
|
||||
for _, r := range s.Ranges {
|
||||
if !r.IsEqual() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Stat reports the total number of insertions and deletions in s.
|
||||
func (s *Script) Stat() (ins, del int) {
|
||||
for _, r := range s.Ranges {
|
||||
switch {
|
||||
case r.IsDelete():
|
||||
del += r.HighA - r.LowA
|
||||
case r.IsInsert():
|
||||
ins += r.HighB - r.LowB
|
||||
}
|
||||
}
|
||||
return ins, del
|
||||
}
|
||||
|
||||
// dump formats s for debugging.
|
||||
func (s *Script) dump() string {
|
||||
buf := new(strings.Builder)
|
||||
for _, r := range s.Ranges {
|
||||
fmt.Fprintln(buf, r)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// A Range is a pair of clopen index ranges.
|
||||
// It represents the elements A[LowA:HighA] and B[LowB:HighB].
|
||||
type Range struct {
|
||||
LowA, HighA int
|
||||
LowB, HighB int
|
||||
}
|
||||
|
||||
// IsInsert reports whether r represents an insertion in a Script.
|
||||
// If so, the inserted elements are B[LowB:HighB].
|
||||
func (r *Range) IsInsert() bool {
|
||||
return r.LowA == r.HighA
|
||||
}
|
||||
|
||||
// IsDelete reports whether r represents a deletion in a Script.
|
||||
// If so, the deleted elements are A[LowA:HighA].
|
||||
func (r *Range) IsDelete() bool {
|
||||
return r.LowB == r.HighB
|
||||
}
|
||||
|
||||
// IsEqual reports whether r represents a series of equal elements in a Script.
|
||||
// If so, the elements A[LowA:HighA] are equal to the elements B[LowB:HighB].
|
||||
func (r *Range) IsEqual() bool {
|
||||
return r.HighB-r.LowB == r.HighA-r.LowA
|
||||
}
|
||||
|
||||
// An Op is a edit operation in a Script.
|
||||
type Op int8
|
||||
|
||||
//go:generate stringer -type Op
|
||||
|
||||
const (
|
||||
Del Op = -1 // delete
|
||||
Eq Op = 0 // equal
|
||||
Ins Op = 1 // insert
|
||||
)
|
||||
|
||||
// Op reports what kind of operation r represents.
|
||||
// This can also be determined by calling r.IsInsert,
|
||||
// r.IsDelete, and r.IsEqual,
|
||||
// but this form is sometimes more convenient to use.
|
||||
func (r *Range) Op() Op {
|
||||
if r.IsInsert() {
|
||||
return Ins
|
||||
}
|
||||
if r.IsDelete() {
|
||||
return Del
|
||||
}
|
||||
if r.IsEqual() {
|
||||
return Eq
|
||||
}
|
||||
panic("malformed Range")
|
||||
}
|
||||
|
||||
// Len reports the number of elements in r.
|
||||
// In a deletion, it is the number of deleted elements.
|
||||
// In an insertion, it is the number of inserted elements.
|
||||
// For equal elements, it is the number of equal elements.
|
||||
func (r *Range) Len() int {
|
||||
if r.LowA == r.HighA {
|
||||
return r.HighB - r.LowB
|
||||
}
|
||||
return r.HighA - r.LowA
|
||||
}
|
||||
26
vendor/github.com/pkg/diff/edit/op_string.go
generated
vendored
Normal file
26
vendor/github.com/pkg/diff/edit/op_string.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
// Code generated by "stringer -type Op"; DO NOT EDIT.
|
||||
|
||||
package edit
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[Del - -1]
|
||||
_ = x[Eq-0]
|
||||
_ = x[Ins-1]
|
||||
}
|
||||
|
||||
const _Op_name = "DelEqIns"
|
||||
|
||||
var _Op_index = [...]uint8{0, 3, 5, 8}
|
||||
|
||||
func (i Op) String() string {
|
||||
i -= -1
|
||||
if i < 0 || i >= Op(len(_Op_index)-1) {
|
||||
return "Op(" + strconv.FormatInt(int64(i+-1), 10) + ")"
|
||||
}
|
||||
return _Op_name[_Op_index[i]:_Op_index[i+1]]
|
||||
}
|
||||
47
vendor/github.com/pkg/diff/fuzz.go
generated
vendored
Normal file
47
vendor/github.com/pkg/diff/fuzz.go
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
// +build gofuzz
|
||||
|
||||
package diff
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/pkg/diff/ctxt"
|
||||
"github.com/pkg/diff/myers"
|
||||
"github.com/pkg/diff/write"
|
||||
)
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
if len(data) < 2 {
|
||||
return -1
|
||||
}
|
||||
sz := int(data[0])
|
||||
data = data[1:]
|
||||
|
||||
nul := bytes.IndexByte(data, 0)
|
||||
if nul == -1 {
|
||||
nul = len(data) - 1
|
||||
}
|
||||
a := data[:nul]
|
||||
b := data[nul:]
|
||||
ab := &IndividualBytes{a: a, b: b}
|
||||
s := myers.Diff(context.Background(), ab)
|
||||
s = ctxt.Size(s, sz)
|
||||
err := write.Unified(s, ioutil.Discard, ab)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type IndividualBytes struct {
|
||||
a, b []byte
|
||||
}
|
||||
|
||||
func (ab *IndividualBytes) LenA() int { return len(ab.a) }
|
||||
func (ab *IndividualBytes) LenB() int { return len(ab.b) }
|
||||
func (ab *IndividualBytes) Equal(ai, bi int) bool { return ab.a[ai] == ab.b[bi] }
|
||||
func (ab *IndividualBytes) WriteATo(w io.Writer, i int) (int, error) { return w.Write([]byte{ab.a[i]}) }
|
||||
func (ab *IndividualBytes) WriteBTo(w io.Writer, i int) (int, error) { return w.Write([]byte{ab.b[i]}) }
|
||||
25
vendor/github.com/pkg/diff/intern/intern.go
generated
vendored
Normal file
25
vendor/github.com/pkg/diff/intern/intern.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
// Package intern provides string interning.
|
||||
//
|
||||
// Unlike much string interning, the routines in this package
|
||||
// return *string instead of string. This enables extremely
|
||||
// cheap (compare only a pointer) comparisons of any strings
|
||||
// interned by this package. Since diff algorithms involve
|
||||
// many string comparisons, this often ends up paying for the
|
||||
// cost of the interning. Also, in the typical case,
|
||||
// diffs involve lots of repeated lines (most of the file
|
||||
// contents are typically unchanged, so any give line
|
||||
// appears at least twice), so string interning saves memory.
|
||||
package intern
|
||||
|
||||
type Strings map[string]*string
|
||||
|
||||
func (m Strings) FromBytes(b []byte) *string {
|
||||
p, ok := m[string(b)]
|
||||
if ok {
|
||||
return p
|
||||
}
|
||||
s := string(b)
|
||||
p = &s
|
||||
m[s] = p
|
||||
return p
|
||||
}
|
||||
184
vendor/github.com/pkg/diff/myers/myers.go
generated
vendored
Normal file
184
vendor/github.com/pkg/diff/myers/myers.go
generated
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
// Package myers implements the Myers diff algorithm.
|
||||
package myers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/diff/edit"
|
||||
)
|
||||
|
||||
// A Pair is two things that can be diffed using the Myers diff algorithm.
|
||||
// A is the initial state; B is the final state.
|
||||
type Pair interface {
|
||||
// LenA returns the number of initial elements.
|
||||
LenA() int
|
||||
// LenB returns the number of final elements.
|
||||
LenB() int
|
||||
// Equal reports whether the aᵢ'th element of A is equal to the bᵢ'th element of B.
|
||||
Equal(ai, bi int) bool
|
||||
}
|
||||
|
||||
// Diff calculates an edit.Script for ab using the Myers diff algorithm.
|
||||
// This implementation uses the algorithm described in the first half
|
||||
// of Myers' paper, which requires quadratric space.
|
||||
// (An implementation of the linear space version is forthcoming.)
|
||||
//
|
||||
// Because diff calculation can be expensive, Myers supports cancellation via ctx.
|
||||
func Diff(ctx context.Context, ab Pair) edit.Script {
|
||||
aLen := ab.LenA()
|
||||
bLen := ab.LenB()
|
||||
if aLen == 0 {
|
||||
return edit.NewScript(edit.Range{HighB: bLen})
|
||||
}
|
||||
if bLen == 0 {
|
||||
return edit.NewScript(edit.Range{HighA: aLen})
|
||||
}
|
||||
|
||||
max := aLen + bLen
|
||||
if max < 0 {
|
||||
panic("overflow in myers.Diff")
|
||||
}
|
||||
// v has indices -max .. 0 .. max
|
||||
// access to elements of v have the form max + actual offset
|
||||
v := make([]int, 2*max+1)
|
||||
|
||||
var trace [][]int
|
||||
search:
|
||||
for d := 0; d < max; d++ {
|
||||
// Only check context every 16th iteration to reduce overhead.
|
||||
if ctx != nil && uint(d)%16 == 0 && ctx.Err() != nil {
|
||||
return edit.Script{}
|
||||
}
|
||||
|
||||
// append the middle (populated) elements of v to trace
|
||||
middle := v[max-d : max+d+1]
|
||||
vcopy := make([]int, len(middle))
|
||||
copy(vcopy, middle)
|
||||
trace = append(trace, vcopy)
|
||||
|
||||
for k := -d; k <= d; k += 2 {
|
||||
var x int
|
||||
if k == -d || (k != d && v[max+k-1] < v[max+k+1]) {
|
||||
x = v[max+k+1]
|
||||
} else {
|
||||
x = v[max+k-1] + 1
|
||||
}
|
||||
|
||||
y := x - k
|
||||
for x < aLen && y < bLen && ab.Equal(x, y) {
|
||||
x++
|
||||
y++
|
||||
}
|
||||
v[max+k] = x
|
||||
|
||||
if x == aLen && y == bLen {
|
||||
break search
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(trace) == max {
|
||||
// No commonality at all, delete everything and then insert everything.
|
||||
// This is handled as a special case to avoid complicating the logic below.
|
||||
return edit.NewScript(edit.Range{HighA: aLen}, edit.Range{HighB: bLen})
|
||||
}
|
||||
|
||||
// Create reversed edit script.
|
||||
x := aLen
|
||||
y := bLen
|
||||
var e edit.Script
|
||||
for d := len(trace) - 1; d >= 0; d-- {
|
||||
// v has indices -d .. 0 .. d
|
||||
// access to elements of v have the form d + actual offset
|
||||
v := trace[d]
|
||||
k := x - y
|
||||
var prevk int
|
||||
if k == -d || (k != d && v[d+k-1] < v[d+k+1]) {
|
||||
prevk = k + 1
|
||||
} else {
|
||||
prevk = k - 1
|
||||
}
|
||||
var prevx int
|
||||
if idx := d + prevk; 0 <= idx && idx < len(v) {
|
||||
prevx = v[idx]
|
||||
}
|
||||
prevy := prevx - prevk
|
||||
for x > prevx && y > prevy {
|
||||
appendToReversed(&e, edit.Range{LowA: x - 1, LowB: y - 1, HighA: x, HighB: y})
|
||||
x--
|
||||
y--
|
||||
}
|
||||
if d > 0 {
|
||||
appendToReversed(&e, edit.Range{LowA: prevx, LowB: prevy, HighA: x, HighB: y})
|
||||
}
|
||||
x, y = prevx, prevy
|
||||
}
|
||||
|
||||
// Reverse reversed edit script, to return to natural order.
|
||||
reverse(e)
|
||||
|
||||
// Sanity check
|
||||
for i := 1; i < len(e.Ranges); i++ {
|
||||
prevop := e.Ranges[i-1].Op()
|
||||
currop := e.Ranges[i].Op()
|
||||
if (prevop == currop) || (prevop == edit.Ins && currop != edit.Eq) || (currop == edit.Del && prevop != edit.Eq) {
|
||||
panic(fmt.Errorf("bad script: %v -> %v", prevop, currop))
|
||||
}
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
func reverse(e edit.Script) {
|
||||
for i := 0; i < len(e.Ranges)/2; i++ {
|
||||
j := len(e.Ranges) - i - 1
|
||||
e.Ranges[i], e.Ranges[j] = e.Ranges[j], e.Ranges[i]
|
||||
}
|
||||
}
|
||||
|
||||
func appendToReversed(e *edit.Script, seg edit.Range) {
|
||||
if len(e.Ranges) == 0 {
|
||||
e.Ranges = append(e.Ranges, seg)
|
||||
return
|
||||
}
|
||||
u, ok := combineRanges(seg, e.Ranges[len(e.Ranges)-1])
|
||||
if !ok {
|
||||
e.Ranges = append(e.Ranges, seg)
|
||||
return
|
||||
}
|
||||
e.Ranges[len(e.Ranges)-1] = u
|
||||
return
|
||||
}
|
||||
|
||||
// combineRanges combines s and t into a single edit.Range if possible
|
||||
// and reports whether it succeeded.
|
||||
func combineRanges(s, t edit.Range) (u edit.Range, ok bool) {
|
||||
if t.Len() == 0 {
|
||||
return s, true
|
||||
}
|
||||
if s.Len() == 0 {
|
||||
return t, true
|
||||
}
|
||||
if s.Op() != t.Op() {
|
||||
return edit.Range{LowA: -1, HighA: -1, LowB: -1, HighB: -1}, false
|
||||
}
|
||||
switch s.Op() {
|
||||
case edit.Ins:
|
||||
s.HighB = t.HighB
|
||||
case edit.Del:
|
||||
s.HighA = t.HighA
|
||||
case edit.Eq:
|
||||
s.HighA = t.HighA
|
||||
s.HighB = t.HighB
|
||||
default:
|
||||
panic("bad op")
|
||||
}
|
||||
return s, true
|
||||
}
|
||||
|
||||
func rangeString(r edit.Range) string {
|
||||
// This output is helpful when hacking on a Myers diff.
|
||||
// In other contexts it is usually more natural to group LowA, HighA and LowB, HighB.
|
||||
return fmt.Sprintf("(%d, %d) -- %s %d --> (%d, %d)", r.LowA, r.LowB, r.Op(), r.Len(), r.HighA, r.HighB)
|
||||
}
|
||||
29
vendor/github.com/pkg/diff/readme.md
generated
vendored
Normal file
29
vendor/github.com/pkg/diff/readme.md
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# diff [](http://godoc.org/github.com/pkg/diff)
|
||||
|
||||
Module github.com/pkg/diff can be used to create, modify, and print diffs.
|
||||
|
||||
The top level package, `diff`, contains convenience functions for the most common uses.
|
||||
|
||||
The subpackages provide very fine-grained control over every aspect:
|
||||
|
||||
* `myers` creates diffs using the Myers diff algorithm.
|
||||
* `edit` contains the core diff data types.
|
||||
* `ctxt` provides tools to reduce the amount of context in a diff.
|
||||
* `write` provides routines to write diffs in standard formats.
|
||||
|
||||
License: BSD 3-Clause.
|
||||
|
||||
### Contributing
|
||||
|
||||
Contributions are welcome. However, I am not always fast to respond.
|
||||
I apologize for any sadness or frustration that that causes.
|
||||
|
||||
Useful background reading about diffs:
|
||||
|
||||
* [Neil Fraser's website](https://neil.fraser.name/writing/diff)
|
||||
* [Myers diff paper](http://www.xmailserver.org/diff2.pdf)
|
||||
* [Guido Van Rossum's reverse engineering of the unified diff format](https://www.artima.com/weblogs/viewpost.jsp?thread=164293)
|
||||
* [The If Works](https://blog.jcoglan.com/) blog entries about diff algorithms and implementations
|
||||
|
||||
This module has not yet reached v1.0;
|
||||
the API is not yet settled (issue #18).
|
||||
43
vendor/github.com/pkg/diff/todo.go
generated
vendored
Normal file
43
vendor/github.com/pkg/diff/todo.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
package diff
|
||||
|
||||
// TODO: add a package for diffing gigantic files.
|
||||
// Instead of reading the entire thing into memory, we could
|
||||
// scan through the file once, storing the location of all newlines in each file.
|
||||
// Then Seek/ReadAt to read each line lazily as needed,
|
||||
// relying on the OS page cache for performance.
|
||||
// This will allow diffing giant files with low memory use,
|
||||
// albeit at a some time cost.
|
||||
// An alternative is to mmap the files,
|
||||
// although this is OS-specific and can be fiddly.
|
||||
|
||||
// TODO: add a package providing a StringIntern type, something like:
|
||||
//
|
||||
// type StringIntern struct {
|
||||
// s map[string]*string
|
||||
// }
|
||||
//
|
||||
// func (i *StringIntern) Bytes(b []byte) *string
|
||||
// func (i *StringIntern) String(s string) *string
|
||||
//
|
||||
// And document what it is and why to use it.
|
||||
// And consider adding helper functions to Strings and Bytes to use it.
|
||||
// The reason to use it is that a lot of the execution time in diffing
|
||||
// (which is an expensive operation) is taken up doing string comparisons.
|
||||
// If you have paid the O(n) cost to intern all strings involved in both A and B,
|
||||
// then string comparisons are reduced to cheap pointer comparisons.
|
||||
|
||||
// TODO: consider adding an "it just works" test helper that accepts two slices (via interface{}),
|
||||
// diffs them using Strings or Bytes or Slices (using reflect.DeepEqual) as appropriate,
|
||||
// and calls t.Errorf with a generated diff if they're not equal.
|
||||
|
||||
// TODO: add support for hunk/section/function headers.
|
||||
// This will probably take the form of a write option
|
||||
// providing access to the necessary data,
|
||||
// and a package that helps calculate the necessary data.
|
||||
// There are several ways to do that calculation...
|
||||
|
||||
// TODO: add copyright headers at top of all files
|
||||
|
||||
// TODO: hook up some CI
|
||||
|
||||
// TODO: add more badges? see github.com/pkg/errors for some likely candidates.
|
||||
39
vendor/github.com/pkg/diff/write/option.go
generated
vendored
Normal file
39
vendor/github.com/pkg/diff/write/option.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
// Package write provides routines for writing diffs.
|
||||
package write
|
||||
|
||||
// An Option modifies behavior when writing a diff.
|
||||
type Option interface {
|
||||
isOption()
|
||||
}
|
||||
|
||||
// Names provides the before/after names for writing a diff.
|
||||
// They are traditionally filenames.
|
||||
func Names(a, b string) Option {
|
||||
return names{a, b}
|
||||
}
|
||||
|
||||
type names struct {
|
||||
a, b string
|
||||
}
|
||||
|
||||
func (names) isOption() {}
|
||||
|
||||
// TerminalColor specifies that a diff intended
|
||||
// for a terminal should be written using colors.
|
||||
//
|
||||
// Do not use TerminalColor if TERM=dumb is set in the environment.
|
||||
func TerminalColor() Option {
|
||||
return colorOpt(true)
|
||||
}
|
||||
|
||||
type colorOpt bool
|
||||
|
||||
func (colorOpt) isOption() {}
|
||||
|
||||
const (
|
||||
ansiBold = "\u001b[1m"
|
||||
ansiFgRed = "\u001b[31m"
|
||||
ansiFgGreen = "\u001b[32m"
|
||||
ansiFgBlue = "\u001b[36m"
|
||||
ansiReset = "\u001b[0m"
|
||||
)
|
||||
7
vendor/github.com/pkg/diff/write/todo.go
generated
vendored
Normal file
7
vendor/github.com/pkg/diff/write/todo.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
package write
|
||||
|
||||
// TODO: add diff writing that uses < and > (don't know what that is called)
|
||||
// TODO: add side by side diffs
|
||||
// TODO: add html diffs (?)
|
||||
// TODO: add intraline highlighting?
|
||||
// TODO: a way to specify alternative colors, like a ColorScheme write option
|
||||
176
vendor/github.com/pkg/diff/write/unified.go
generated
vendored
Normal file
176
vendor/github.com/pkg/diff/write/unified.go
generated
vendored
Normal file
@@ -0,0 +1,176 @@
|
||||
package write
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/pkg/diff/edit"
|
||||
)
|
||||
|
||||
// A Pair supports writing a unified diff, element by element.
|
||||
// A is the initial state; B is the final state.
|
||||
type Pair interface {
|
||||
// WriteATo writes the element a[aᵢ] to w.
|
||||
WriteATo(w io.Writer, ai int) (int, error)
|
||||
// WriteBTo writes the element b[bᵢ] to w.
|
||||
WriteBTo(w io.Writer, bi int) (int, error)
|
||||
}
|
||||
|
||||
// Unified writes e to w using unified diff format.
|
||||
// ab writes the individual elements. Opts are optional write arguments.
|
||||
// Unified returns the number of bytes written and the first error (if any) encountered.
|
||||
// Before writing, edit scripts usually have their context reduced,
|
||||
// such as by a call to ctxt.Size.
|
||||
func Unified(e edit.Script, w io.Writer, ab Pair, opts ...Option) error {
|
||||
// read opts
|
||||
nameA := "a"
|
||||
nameB := "b"
|
||||
color := false
|
||||
for _, opt := range opts {
|
||||
switch opt := opt.(type) {
|
||||
case names:
|
||||
nameA = opt.a
|
||||
nameB = opt.b
|
||||
case colorOpt:
|
||||
color = true
|
||||
// TODO: add date/time/timezone WriteOpts
|
||||
default:
|
||||
panic(fmt.Sprintf("unrecognized WriteOpt type %T", opt))
|
||||
}
|
||||
}
|
||||
|
||||
bw := bufio.NewWriter(w)
|
||||
|
||||
needsColorReset := false
|
||||
|
||||
// per-file header
|
||||
if color {
|
||||
bw.WriteString(ansiBold)
|
||||
needsColorReset = true
|
||||
}
|
||||
fmt.Fprintf(bw, "--- %s\n", nameA)
|
||||
fmt.Fprintf(bw, "+++ %s\n", nameB)
|
||||
|
||||
for i := 0; i < len(e.Ranges); {
|
||||
// Peek into the future to learn the line ranges for this chunk of output.
|
||||
// A chunk of output ends when there's a discontiguity in the edit script.
|
||||
var ar, br lineRange
|
||||
var started [2]bool
|
||||
var j int
|
||||
for j = i; j < len(e.Ranges); j++ {
|
||||
curr := e.Ranges[j]
|
||||
if !curr.IsInsert() {
|
||||
if !started[0] {
|
||||
ar.first = curr.LowA
|
||||
started[0] = true
|
||||
}
|
||||
ar.last = curr.HighA
|
||||
}
|
||||
if !curr.IsDelete() {
|
||||
if !started[1] {
|
||||
br.first = curr.LowB
|
||||
started[1] = true
|
||||
}
|
||||
br.last = curr.HighB
|
||||
}
|
||||
if j+1 >= len(e.Ranges) {
|
||||
// end of script
|
||||
break
|
||||
}
|
||||
if next := e.Ranges[j+1]; curr.HighA != next.LowA || curr.HighB != next.LowB {
|
||||
// discontiguous edit script
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Print chunk header.
|
||||
// TODO: add per-chunk context, like what function we're in
|
||||
// But how do we get this? need to add PairWriter methods?
|
||||
// Maybe it should be stored in the EditScript,
|
||||
// and we can have EditScript methods to populate it somehow?
|
||||
if color {
|
||||
if needsColorReset {
|
||||
bw.WriteString(ansiReset)
|
||||
}
|
||||
bw.WriteString(ansiFgBlue)
|
||||
needsColorReset = true
|
||||
}
|
||||
fmt.Fprintf(bw, "@@ -%s +%s @@\n", ar, br)
|
||||
|
||||
// Print prefixed lines.
|
||||
for k := i; k <= j; k++ {
|
||||
seg := e.Ranges[k]
|
||||
switch seg.Op() {
|
||||
case edit.Eq:
|
||||
if needsColorReset {
|
||||
bw.WriteString(ansiReset)
|
||||
}
|
||||
for m := seg.LowA; m < seg.HighA; m++ {
|
||||
// " a[m]\n"
|
||||
bw.WriteByte(' ')
|
||||
ab.WriteATo(bw, m)
|
||||
bw.WriteByte('\n')
|
||||
}
|
||||
case edit.Del:
|
||||
if color {
|
||||
bw.WriteString(ansiFgRed)
|
||||
needsColorReset = true
|
||||
}
|
||||
for m := seg.LowA; m < seg.HighA; m++ {
|
||||
// "-a[m]\n"
|
||||
bw.WriteByte('-')
|
||||
ab.WriteATo(bw, m)
|
||||
bw.WriteByte('\n')
|
||||
}
|
||||
case edit.Ins:
|
||||
if color {
|
||||
bw.WriteString(ansiFgGreen)
|
||||
needsColorReset = true
|
||||
}
|
||||
for m := seg.LowB; m < seg.HighB; m++ {
|
||||
// "+b[m]\n"
|
||||
bw.WriteByte('+')
|
||||
ab.WriteBTo(bw, m)
|
||||
bw.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Advance to next chunk.
|
||||
i = j + 1
|
||||
|
||||
// TODO: break if error detected?
|
||||
}
|
||||
|
||||
// Always finish the output with no color, to prevent "leaking" the
|
||||
// color into any output that follows a diff.
|
||||
if needsColorReset {
|
||||
bw.WriteString(ansiReset)
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// If the last line of a file doesn't end in a newline character,
|
||||
// it is displayed with a newline character,
|
||||
// and the following line in the chunk has the literal text (starting in the first column):
|
||||
// '\ No newline at end of file'
|
||||
|
||||
return bw.Flush()
|
||||
}
|
||||
|
||||
type lineRange struct {
|
||||
first, last int
|
||||
}
|
||||
|
||||
func (r lineRange) String() string {
|
||||
len := r.last - r.first
|
||||
r.first++ // 1-based index, safe to modify r directly because it is a value
|
||||
if len <= 0 {
|
||||
r.first-- // for no obvious reason, empty ranges are "before" the range
|
||||
}
|
||||
return fmt.Sprintf("%d,%d", r.first, len)
|
||||
}
|
||||
|
||||
func (r lineRange) GoString() string {
|
||||
return fmt.Sprintf("(%d, %d)", r.first, r.last)
|
||||
}
|
||||
27
vendor/github.com/rogpeppe/go-internal/LICENSE
generated
vendored
Normal file
27
vendor/github.com/rogpeppe/go-internal/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2018 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
222
vendor/github.com/rogpeppe/go-internal/imports/build.go
generated
vendored
Normal file
222
vendor/github.com/rogpeppe/go-internal/imports/build.go
generated
vendored
Normal file
@@ -0,0 +1,222 @@
|
||||
// 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.
|
||||
|
||||
// Copied from Go distribution src/go/build/build.go, syslist.go
|
||||
|
||||
package imports
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var slashslash = []byte("//")
|
||||
|
||||
// ShouldBuild reports whether it is okay to use this file,
|
||||
// The rule is that in the file's leading run of // comments
|
||||
// and blank lines, which must be followed by a blank line
|
||||
// (to avoid including a Go package clause doc comment),
|
||||
// lines beginning with '// +build' are taken as build directives.
|
||||
//
|
||||
// The file is accepted only if each such line lists something
|
||||
// matching the file. For example:
|
||||
//
|
||||
// // +build windows linux
|
||||
//
|
||||
// marks the file as applicable only on Windows and Linux.
|
||||
//
|
||||
// If tags["*"] is true, then ShouldBuild will consider every
|
||||
// build tag except "ignore" to be both true and false for
|
||||
// the purpose of satisfying build tags, in order to estimate
|
||||
// (conservatively) whether a file could ever possibly be used
|
||||
// in any build.
|
||||
//
|
||||
func ShouldBuild(content []byte, tags map[string]bool) bool {
|
||||
// Pass 1. Identify leading run of // comments and blank lines,
|
||||
// which must be followed by a blank line.
|
||||
end := 0
|
||||
p := content
|
||||
for len(p) > 0 {
|
||||
line := p
|
||||
if i := bytes.IndexByte(line, '\n'); i >= 0 {
|
||||
line, p = line[:i], p[i+1:]
|
||||
} else {
|
||||
p = p[len(p):]
|
||||
}
|
||||
line = bytes.TrimSpace(line)
|
||||
if len(line) == 0 { // Blank line
|
||||
end = len(content) - len(p)
|
||||
continue
|
||||
}
|
||||
if !bytes.HasPrefix(line, slashslash) { // Not comment line
|
||||
break
|
||||
}
|
||||
}
|
||||
content = content[:end]
|
||||
|
||||
// Pass 2. Process each line in the run.
|
||||
p = content
|
||||
allok := true
|
||||
for len(p) > 0 {
|
||||
line := p
|
||||
if i := bytes.IndexByte(line, '\n'); i >= 0 {
|
||||
line, p = line[:i], p[i+1:]
|
||||
} else {
|
||||
p = p[len(p):]
|
||||
}
|
||||
line = bytes.TrimSpace(line)
|
||||
if !bytes.HasPrefix(line, slashslash) {
|
||||
continue
|
||||
}
|
||||
line = bytes.TrimSpace(line[len(slashslash):])
|
||||
if len(line) > 0 && line[0] == '+' {
|
||||
// Looks like a comment +line.
|
||||
f := strings.Fields(string(line))
|
||||
if f[0] == "+build" {
|
||||
ok := false
|
||||
for _, tok := range f[1:] {
|
||||
if matchTags(tok, tags) {
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
allok = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allok
|
||||
}
|
||||
|
||||
// matchTags reports whether the name is one of:
|
||||
//
|
||||
// tag (if tags[tag] is true)
|
||||
// !tag (if tags[tag] is false)
|
||||
// a comma-separated list of any of these
|
||||
//
|
||||
func matchTags(name string, tags map[string]bool) bool {
|
||||
if name == "" {
|
||||
return false
|
||||
}
|
||||
if i := strings.Index(name, ","); i >= 0 {
|
||||
// comma-separated list
|
||||
ok1 := matchTags(name[:i], tags)
|
||||
ok2 := matchTags(name[i+1:], tags)
|
||||
return ok1 && ok2
|
||||
}
|
||||
if strings.HasPrefix(name, "!!") { // bad syntax, reject always
|
||||
return false
|
||||
}
|
||||
if strings.HasPrefix(name, "!") { // negation
|
||||
return len(name) > 1 && matchTag(name[1:], tags, false)
|
||||
}
|
||||
return matchTag(name, tags, true)
|
||||
}
|
||||
|
||||
// matchTag reports whether the tag name is valid and satisfied by tags[name]==want.
|
||||
func matchTag(name string, tags map[string]bool, want bool) bool {
|
||||
// Tags must be letters, digits, underscores or dots.
|
||||
// Unlike in Go identifiers, all digits are fine (e.g., "386").
|
||||
for _, c := range name {
|
||||
if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if tags["*"] && name != "" && name != "ignore" {
|
||||
// Special case for gathering all possible imports:
|
||||
// if we put * in the tags map then all tags
|
||||
// except "ignore" are considered both present and not
|
||||
// (so we return true no matter how 'want' is set).
|
||||
return true
|
||||
}
|
||||
|
||||
have := tags[name]
|
||||
if name == "linux" {
|
||||
have = have || tags["android"]
|
||||
}
|
||||
return have == want
|
||||
}
|
||||
|
||||
// MatchFile returns false if the name contains a $GOOS or $GOARCH
|
||||
// suffix which does not match the current system.
|
||||
// The recognized name formats are:
|
||||
//
|
||||
// name_$(GOOS).*
|
||||
// name_$(GOARCH).*
|
||||
// name_$(GOOS)_$(GOARCH).*
|
||||
// name_$(GOOS)_test.*
|
||||
// name_$(GOARCH)_test.*
|
||||
// name_$(GOOS)_$(GOARCH)_test.*
|
||||
//
|
||||
// An exception: if GOOS=android, then files with GOOS=linux are also matched.
|
||||
//
|
||||
// If tags["*"] is true, then MatchFile will consider all possible
|
||||
// GOOS and GOARCH to be available and will consequently
|
||||
// always return true.
|
||||
func MatchFile(name string, tags map[string]bool) bool {
|
||||
if tags["*"] {
|
||||
return true
|
||||
}
|
||||
if dot := strings.Index(name, "."); dot != -1 {
|
||||
name = name[:dot]
|
||||
}
|
||||
|
||||
// Before Go 1.4, a file called "linux.go" would be equivalent to having a
|
||||
// build tag "linux" in that file. For Go 1.4 and beyond, we require this
|
||||
// auto-tagging to apply only to files with a non-empty prefix, so
|
||||
// "foo_linux.go" is tagged but "linux.go" is not. This allows new operating
|
||||
// systems, such as android, to arrive without breaking existing code with
|
||||
// innocuous source code in "android.go". The easiest fix: cut everything
|
||||
// in the name before the initial _.
|
||||
i := strings.Index(name, "_")
|
||||
if i < 0 {
|
||||
return true
|
||||
}
|
||||
name = name[i:] // ignore everything before first _
|
||||
|
||||
l := strings.Split(name, "_")
|
||||
if n := len(l); n > 0 && l[n-1] == "test" {
|
||||
l = l[:n-1]
|
||||
}
|
||||
n := len(l)
|
||||
if n >= 2 && KnownOS[l[n-2]] && KnownArch[l[n-1]] {
|
||||
return tags[l[n-2]] && tags[l[n-1]]
|
||||
}
|
||||
if n >= 1 && KnownOS[l[n-1]] {
|
||||
return tags[l[n-1]]
|
||||
}
|
||||
if n >= 1 && KnownArch[l[n-1]] {
|
||||
return tags[l[n-1]]
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var (
|
||||
KnownOS = make(map[string]bool)
|
||||
UnixOS = make(map[string]bool)
|
||||
KnownArch = make(map[string]bool)
|
||||
)
|
||||
|
||||
func init() {
|
||||
for _, v := range strings.Fields(goosList) {
|
||||
KnownOS[v] = true
|
||||
}
|
||||
for _, v := range strings.Fields(unixList) {
|
||||
UnixOS[v] = true
|
||||
}
|
||||
for _, v := range strings.Fields(goarchList) {
|
||||
KnownArch[v] = true
|
||||
}
|
||||
}
|
||||
|
||||
// These values come from Go's src/go/build/syslist.go and should be kept in
|
||||
// sync with that file.
|
||||
const (
|
||||
goosList = "aix android darwin dragonfly freebsd hurd illumos ios js linux nacl netbsd openbsd plan9 solaris windows zos "
|
||||
unixList = "aix android darwin dragonfly freebsd hurd illumos ios linux netbsd openbsd solaris "
|
||||
goarchList = "386 amd64 amd64p32 arm armbe arm64 arm64be loong64 mips mipsle mips64 mips64le mips64p32 mips64p32le ppc ppc64 ppc64le riscv riscv64 s390 s390x sparc sparc64 wasm "
|
||||
)
|
||||
249
vendor/github.com/rogpeppe/go-internal/imports/read.go
generated
vendored
Normal file
249
vendor/github.com/rogpeppe/go-internal/imports/read.go
generated
vendored
Normal file
@@ -0,0 +1,249 @@
|
||||
// Copyright 2012 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.
|
||||
|
||||
// Copied from Go distribution src/go/build/read.go.
|
||||
|
||||
package imports
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"io"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type importReader struct {
|
||||
b *bufio.Reader
|
||||
buf []byte
|
||||
peek byte
|
||||
err error
|
||||
eof bool
|
||||
nerr int
|
||||
}
|
||||
|
||||
func isIdent(c byte) bool {
|
||||
return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c >= utf8.RuneSelf
|
||||
}
|
||||
|
||||
var (
|
||||
errSyntax = errors.New("syntax error")
|
||||
errNUL = errors.New("unexpected NUL in input")
|
||||
)
|
||||
|
||||
// syntaxError records a syntax error, but only if an I/O error has not already been recorded.
|
||||
func (r *importReader) syntaxError() {
|
||||
if r.err == nil {
|
||||
r.err = errSyntax
|
||||
}
|
||||
}
|
||||
|
||||
// readByte reads the next byte from the input, saves it in buf, and returns it.
|
||||
// If an error occurs, readByte records the error in r.err and returns 0.
|
||||
func (r *importReader) readByte() byte {
|
||||
c, err := r.b.ReadByte()
|
||||
if err == nil {
|
||||
r.buf = append(r.buf, c)
|
||||
if c == 0 {
|
||||
err = errNUL
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
r.eof = true
|
||||
} else if r.err == nil {
|
||||
r.err = err
|
||||
}
|
||||
c = 0
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// peekByte returns the next byte from the input reader but does not advance beyond it.
|
||||
// If skipSpace is set, peekByte skips leading spaces and comments.
|
||||
func (r *importReader) peekByte(skipSpace bool) byte {
|
||||
if r.err != nil {
|
||||
if r.nerr++; r.nerr > 10000 {
|
||||
panic("go/build: import reader looping")
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Use r.peek as first input byte.
|
||||
// Don't just return r.peek here: it might have been left by peekByte(false)
|
||||
// and this might be peekByte(true).
|
||||
c := r.peek
|
||||
if c == 0 {
|
||||
c = r.readByte()
|
||||
}
|
||||
for r.err == nil && !r.eof {
|
||||
if skipSpace {
|
||||
// For the purposes of this reader, semicolons are never necessary to
|
||||
// understand the input and are treated as spaces.
|
||||
switch c {
|
||||
case ' ', '\f', '\t', '\r', '\n', ';':
|
||||
c = r.readByte()
|
||||
continue
|
||||
|
||||
case '/':
|
||||
c = r.readByte()
|
||||
if c == '/' {
|
||||
for c != '\n' && r.err == nil && !r.eof {
|
||||
c = r.readByte()
|
||||
}
|
||||
} else if c == '*' {
|
||||
var c1 byte
|
||||
for (c != '*' || c1 != '/') && r.err == nil {
|
||||
if r.eof {
|
||||
r.syntaxError()
|
||||
}
|
||||
c, c1 = c1, r.readByte()
|
||||
}
|
||||
} else {
|
||||
r.syntaxError()
|
||||
}
|
||||
c = r.readByte()
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
r.peek = c
|
||||
return r.peek
|
||||
}
|
||||
|
||||
// nextByte is like peekByte but advances beyond the returned byte.
|
||||
func (r *importReader) nextByte(skipSpace bool) byte {
|
||||
c := r.peekByte(skipSpace)
|
||||
r.peek = 0
|
||||
return c
|
||||
}
|
||||
|
||||
// readKeyword reads the given keyword from the input.
|
||||
// If the keyword is not present, readKeyword records a syntax error.
|
||||
func (r *importReader) readKeyword(kw string) {
|
||||
r.peekByte(true)
|
||||
for i := 0; i < len(kw); i++ {
|
||||
if r.nextByte(false) != kw[i] {
|
||||
r.syntaxError()
|
||||
return
|
||||
}
|
||||
}
|
||||
if isIdent(r.peekByte(false)) {
|
||||
r.syntaxError()
|
||||
}
|
||||
}
|
||||
|
||||
// readIdent reads an identifier from the input.
|
||||
// If an identifier is not present, readIdent records a syntax error.
|
||||
func (r *importReader) readIdent() {
|
||||
c := r.peekByte(true)
|
||||
if !isIdent(c) {
|
||||
r.syntaxError()
|
||||
return
|
||||
}
|
||||
for isIdent(r.peekByte(false)) {
|
||||
r.peek = 0
|
||||
}
|
||||
}
|
||||
|
||||
// readString reads a quoted string literal from the input.
|
||||
// If an identifier is not present, readString records a syntax error.
|
||||
func (r *importReader) readString(save *[]string) {
|
||||
switch r.nextByte(true) {
|
||||
case '`':
|
||||
start := len(r.buf) - 1
|
||||
for r.err == nil {
|
||||
if r.nextByte(false) == '`' {
|
||||
if save != nil {
|
||||
*save = append(*save, string(r.buf[start:]))
|
||||
}
|
||||
break
|
||||
}
|
||||
if r.eof {
|
||||
r.syntaxError()
|
||||
}
|
||||
}
|
||||
case '"':
|
||||
start := len(r.buf) - 1
|
||||
for r.err == nil {
|
||||
c := r.nextByte(false)
|
||||
if c == '"' {
|
||||
if save != nil {
|
||||
*save = append(*save, string(r.buf[start:]))
|
||||
}
|
||||
break
|
||||
}
|
||||
if r.eof || c == '\n' {
|
||||
r.syntaxError()
|
||||
}
|
||||
if c == '\\' {
|
||||
r.nextByte(false)
|
||||
}
|
||||
}
|
||||
default:
|
||||
r.syntaxError()
|
||||
}
|
||||
}
|
||||
|
||||
// readImport reads an import clause - optional identifier followed by quoted string -
|
||||
// from the input.
|
||||
func (r *importReader) readImport(imports *[]string) {
|
||||
c := r.peekByte(true)
|
||||
if c == '.' {
|
||||
r.peek = 0
|
||||
} else if isIdent(c) {
|
||||
r.readIdent()
|
||||
}
|
||||
r.readString(imports)
|
||||
}
|
||||
|
||||
// ReadComments is like ioutil.ReadAll, except that it only reads the leading
|
||||
// block of comments in the file.
|
||||
func ReadComments(f io.Reader) ([]byte, error) {
|
||||
r := &importReader{b: bufio.NewReader(f)}
|
||||
r.peekByte(true)
|
||||
if r.err == nil && !r.eof {
|
||||
// Didn't reach EOF, so must have found a non-space byte. Remove it.
|
||||
r.buf = r.buf[:len(r.buf)-1]
|
||||
}
|
||||
return r.buf, r.err
|
||||
}
|
||||
|
||||
// ReadImports is like ioutil.ReadAll, except that it expects a Go file as input
|
||||
// and stops reading the input once the imports have completed.
|
||||
func ReadImports(f io.Reader, reportSyntaxError bool, imports *[]string) ([]byte, error) {
|
||||
r := &importReader{b: bufio.NewReader(f)}
|
||||
|
||||
r.readKeyword("package")
|
||||
r.readIdent()
|
||||
for r.peekByte(true) == 'i' {
|
||||
r.readKeyword("import")
|
||||
if r.peekByte(true) == '(' {
|
||||
r.nextByte(false)
|
||||
for r.peekByte(true) != ')' && r.err == nil {
|
||||
r.readImport(imports)
|
||||
}
|
||||
r.nextByte(false)
|
||||
} else {
|
||||
r.readImport(imports)
|
||||
}
|
||||
}
|
||||
|
||||
// If we stopped successfully before EOF, we read a byte that told us we were done.
|
||||
// Return all but that last byte, which would cause a syntax error if we let it through.
|
||||
if r.err == nil && !r.eof {
|
||||
return r.buf[:len(r.buf)-1], nil
|
||||
}
|
||||
|
||||
// If we stopped for a syntax error, consume the whole file so that
|
||||
// we are sure we don't change the errors that go/parser returns.
|
||||
if r.err == errSyntax && !reportSyntaxError {
|
||||
r.err = nil
|
||||
for r.err == nil && !r.eof {
|
||||
r.readByte()
|
||||
}
|
||||
}
|
||||
|
||||
return r.buf, r.err
|
||||
}
|
||||
96
vendor/github.com/rogpeppe/go-internal/imports/scan.go
generated
vendored
Normal file
96
vendor/github.com/rogpeppe/go-internal/imports/scan.go
generated
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
// 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 imports
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ScanDir(dir string, tags map[string]bool) ([]string, []string, error) {
|
||||
infos, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var files []string
|
||||
for _, info := range infos {
|
||||
name := info.Name()
|
||||
if info.Mode().IsRegular() && !strings.HasPrefix(name, "_") && strings.HasSuffix(name, ".go") && MatchFile(name, tags) {
|
||||
files = append(files, filepath.Join(dir, name))
|
||||
}
|
||||
}
|
||||
return scanFiles(files, tags, false)
|
||||
}
|
||||
|
||||
func ScanFiles(files []string, tags map[string]bool) ([]string, []string, error) {
|
||||
return scanFiles(files, tags, true)
|
||||
}
|
||||
|
||||
func scanFiles(files []string, tags map[string]bool, explicitFiles bool) ([]string, []string, error) {
|
||||
imports := make(map[string]bool)
|
||||
testImports := make(map[string]bool)
|
||||
numFiles := 0
|
||||
Files:
|
||||
for _, name := range files {
|
||||
r, err := os.Open(name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var list []string
|
||||
data, err := ReadImports(r, false, &list)
|
||||
r.Close()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("reading %s: %v", name, err)
|
||||
}
|
||||
|
||||
// import "C" is implicit requirement of cgo tag.
|
||||
// When listing files on the command line (explicitFiles=true)
|
||||
// we do not apply build tag filtering but we still do apply
|
||||
// cgo filtering, so no explicitFiles check here.
|
||||
// Why? Because we always have, and it's not worth breaking
|
||||
// that behavior now.
|
||||
for _, path := range list {
|
||||
if path == `"C"` && !tags["cgo"] && !tags["*"] {
|
||||
continue Files
|
||||
}
|
||||
}
|
||||
|
||||
if !explicitFiles && !ShouldBuild(data, tags) {
|
||||
continue
|
||||
}
|
||||
numFiles++
|
||||
m := imports
|
||||
if strings.HasSuffix(name, "_test.go") {
|
||||
m = testImports
|
||||
}
|
||||
for _, p := range list {
|
||||
q, err := strconv.Unquote(p)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
m[q] = true
|
||||
}
|
||||
}
|
||||
if numFiles == 0 {
|
||||
return nil, nil, ErrNoGo
|
||||
}
|
||||
return keys(imports), keys(testImports), nil
|
||||
}
|
||||
|
||||
var ErrNoGo = fmt.Errorf("no Go source files")
|
||||
|
||||
func keys(m map[string]bool) []string {
|
||||
var list []string
|
||||
for k := range m {
|
||||
list = append(list, k)
|
||||
}
|
||||
sort.Strings(list)
|
||||
return list
|
||||
}
|
||||
8
vendor/github.com/rogpeppe/go-internal/internal/os/execpath/exec.go
generated
vendored
Normal file
8
vendor/github.com/rogpeppe/go-internal/internal/os/execpath/exec.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
package execpath
|
||||
|
||||
import "os/exec"
|
||||
|
||||
type Error = exec.Error
|
||||
|
||||
// ErrNotFound is the error resulting if a path search failed to find an executable file.
|
||||
var ErrNotFound = exec.ErrNotFound
|
||||
17
vendor/github.com/rogpeppe/go-internal/internal/os/execpath/lp_js.go
generated
vendored
Normal file
17
vendor/github.com/rogpeppe/go-internal/internal/os/execpath/lp_js.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// 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.
|
||||
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package execpath
|
||||
|
||||
// Look searches for an executable named file, using getenv to look up
|
||||
// environment variables. If getenv is nil, os.Getenv will be used. If file
|
||||
// contains a slash, it is tried directly and getenv will not be called. The
|
||||
// result may be an absolute path or a path relative to the current directory.
|
||||
func Look(file string, getenv func(string) string) (string, error) {
|
||||
// Wasm can not execute processes, so act as if there are no executables at all.
|
||||
return "", &Error{file, ErrNotFound}
|
||||
}
|
||||
54
vendor/github.com/rogpeppe/go-internal/internal/os/execpath/lp_plan9.go
generated
vendored
Normal file
54
vendor/github.com/rogpeppe/go-internal/internal/os/execpath/lp_plan9.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2011 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 execpath
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func findExecutable(file string) error {
|
||||
d, err := os.Stat(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if m := d.Mode(); !m.IsDir() && m&0111 != 0 {
|
||||
return nil
|
||||
}
|
||||
return os.ErrPermission
|
||||
}
|
||||
|
||||
// Look searches for an executable named file, using getenv to look up
|
||||
// environment variables. If getenv is nil, os.Getenv will be used. If file
|
||||
// contains a slash, it is tried directly and getenv will not be called. The
|
||||
// result may be an absolute path or a path relative to the current directory.
|
||||
func Look(file string, getenv func(string) string) (string, error) {
|
||||
if getenv == nil {
|
||||
getenv = os.Getenv
|
||||
}
|
||||
|
||||
// skip the path lookup for these prefixes
|
||||
skip := []string{"/", "#", "./", "../"}
|
||||
|
||||
for _, p := range skip {
|
||||
if strings.HasPrefix(file, p) {
|
||||
err := findExecutable(file)
|
||||
if err == nil {
|
||||
return file, nil
|
||||
}
|
||||
return "", &Error{file, err}
|
||||
}
|
||||
}
|
||||
|
||||
path := getenv("path")
|
||||
for _, dir := range filepath.SplitList(path) {
|
||||
path := filepath.Join(dir, file)
|
||||
if err := findExecutable(path); err == nil {
|
||||
return path, nil
|
||||
}
|
||||
}
|
||||
return "", &Error{file, ErrNotFound}
|
||||
}
|
||||
59
vendor/github.com/rogpeppe/go-internal/internal/os/execpath/lp_unix.go
generated
vendored
Normal file
59
vendor/github.com/rogpeppe/go-internal/internal/os/execpath/lp_unix.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright 2010 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.
|
||||
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || nacl || netbsd || openbsd || solaris
|
||||
// +build aix darwin dragonfly freebsd linux nacl netbsd openbsd solaris
|
||||
|
||||
package execpath
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func findExecutable(file string) error {
|
||||
d, err := os.Stat(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if m := d.Mode(); !m.IsDir() && m&0111 != 0 {
|
||||
return nil
|
||||
}
|
||||
return os.ErrPermission
|
||||
}
|
||||
|
||||
// Look searches for an executable named file, using getenv to look up
|
||||
// environment variables. If getenv is nil, os.Getenv will be used. If file
|
||||
// contains a slash, it is tried directly and getenv will not be called. The
|
||||
// result may be an absolute path or a path relative to the current directory.
|
||||
func Look(file string, getenv func(string) string) (string, error) {
|
||||
if getenv == nil {
|
||||
getenv = os.Getenv
|
||||
}
|
||||
|
||||
// NOTE(rsc): I wish we could use the Plan 9 behavior here
|
||||
// (only bypass the path if file begins with / or ./ or ../)
|
||||
// but that would not match all the Unix shells.
|
||||
|
||||
if strings.Contains(file, "/") {
|
||||
err := findExecutable(file)
|
||||
if err == nil {
|
||||
return file, nil
|
||||
}
|
||||
return "", &Error{file, err}
|
||||
}
|
||||
path := getenv("PATH")
|
||||
for _, dir := range filepath.SplitList(path) {
|
||||
if dir == "" {
|
||||
// Unix shell semantics: path element "" means "."
|
||||
dir = "."
|
||||
}
|
||||
path := filepath.Join(dir, file)
|
||||
if err := findExecutable(path); err == nil {
|
||||
return path, nil
|
||||
}
|
||||
}
|
||||
return "", &Error{file, ErrNotFound}
|
||||
}
|
||||
92
vendor/github.com/rogpeppe/go-internal/internal/os/execpath/lp_windows.go
generated
vendored
Normal file
92
vendor/github.com/rogpeppe/go-internal/internal/os/execpath/lp_windows.go
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
// Copyright 2010 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 execpath
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func chkStat(file string) error {
|
||||
d, err := os.Stat(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.IsDir() {
|
||||
return os.ErrPermission
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasExt(file string) bool {
|
||||
i := strings.LastIndex(file, ".")
|
||||
if i < 0 {
|
||||
return false
|
||||
}
|
||||
return strings.LastIndexAny(file, `:\/`) < i
|
||||
}
|
||||
|
||||
func findExecutable(file string, exts []string) (string, error) {
|
||||
if len(exts) == 0 {
|
||||
return file, chkStat(file)
|
||||
}
|
||||
if hasExt(file) {
|
||||
if chkStat(file) == nil {
|
||||
return file, nil
|
||||
}
|
||||
}
|
||||
for _, e := range exts {
|
||||
if f := file + e; chkStat(f) == nil {
|
||||
return f, nil
|
||||
}
|
||||
}
|
||||
return "", os.ErrNotExist
|
||||
}
|
||||
|
||||
// Look searches for an executable named file, using getenv to look up
|
||||
// environment variables. If getenv is nil, os.Getenv will be used. If file
|
||||
// contains a slash, it is tried directly and getenv will not be called. The
|
||||
// result may be an absolute path or a path relative to the current directory.
|
||||
// Look also uses PATHEXT environment variable to match
|
||||
// a suitable candidate.
|
||||
func Look(file string, getenv func(string) string) (string, error) {
|
||||
if getenv == nil {
|
||||
getenv = os.Getenv
|
||||
}
|
||||
var exts []string
|
||||
x := getenv(`PATHEXT`)
|
||||
if x != "" {
|
||||
for _, e := range strings.Split(strings.ToLower(x), `;`) {
|
||||
if e == "" {
|
||||
continue
|
||||
}
|
||||
if e[0] != '.' {
|
||||
e = "." + e
|
||||
}
|
||||
exts = append(exts, e)
|
||||
}
|
||||
} else {
|
||||
exts = []string{".com", ".exe", ".bat", ".cmd"}
|
||||
}
|
||||
|
||||
if strings.ContainsAny(file, `:\/`) {
|
||||
if f, err := findExecutable(file, exts); err == nil {
|
||||
return f, nil
|
||||
} else {
|
||||
return "", &Error{file, err}
|
||||
}
|
||||
}
|
||||
if f, err := findExecutable(filepath.Join(".", file), exts); err == nil {
|
||||
return f, nil
|
||||
}
|
||||
path := getenv("path")
|
||||
for _, dir := range filepath.SplitList(path) {
|
||||
if f, err := findExecutable(filepath.Join(dir, file), exts); err == nil {
|
||||
return f, nil
|
||||
}
|
||||
}
|
||||
return "", &Error{file, ErrNotFound}
|
||||
}
|
||||
149
vendor/github.com/rogpeppe/go-internal/par/work.go
generated
vendored
Normal file
149
vendor/github.com/rogpeppe/go-internal/par/work.go
generated
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
// 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 par implements parallel execution helpers.
|
||||
package par
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Work manages a set of work items to be executed in parallel, at most once each.
|
||||
// The items in the set must all be valid map keys.
|
||||
type Work struct {
|
||||
f func(interface{}) // function to run for each item
|
||||
running int // total number of runners
|
||||
|
||||
mu sync.Mutex
|
||||
added map[interface{}]bool // items added to set
|
||||
todo []interface{} // items yet to be run
|
||||
wait sync.Cond // wait when todo is empty
|
||||
waiting int // number of runners waiting for todo
|
||||
}
|
||||
|
||||
func (w *Work) init() {
|
||||
if w.added == nil {
|
||||
w.added = make(map[interface{}]bool)
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds item to the work set, if it hasn't already been added.
|
||||
func (w *Work) Add(item interface{}) {
|
||||
w.mu.Lock()
|
||||
w.init()
|
||||
if !w.added[item] {
|
||||
w.added[item] = true
|
||||
w.todo = append(w.todo, item)
|
||||
if w.waiting > 0 {
|
||||
w.wait.Signal()
|
||||
}
|
||||
}
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
// Do runs f in parallel on items from the work set,
|
||||
// with at most n invocations of f running at a time.
|
||||
// It returns when everything added to the work set has been processed.
|
||||
// At least one item should have been added to the work set
|
||||
// before calling Do (or else Do returns immediately),
|
||||
// but it is allowed for f(item) to add new items to the set.
|
||||
// Do should only be used once on a given Work.
|
||||
func (w *Work) Do(n int, f func(item interface{})) {
|
||||
if n < 1 {
|
||||
panic("par.Work.Do: n < 1")
|
||||
}
|
||||
if w.running >= 1 {
|
||||
panic("par.Work.Do: already called Do")
|
||||
}
|
||||
|
||||
w.running = n
|
||||
w.f = f
|
||||
w.wait.L = &w.mu
|
||||
|
||||
for i := 0; i < n-1; i++ {
|
||||
go w.runner()
|
||||
}
|
||||
w.runner()
|
||||
}
|
||||
|
||||
// runner executes work in w until both nothing is left to do
|
||||
// and all the runners are waiting for work.
|
||||
// (Then all the runners return.)
|
||||
func (w *Work) runner() {
|
||||
for {
|
||||
// Wait for something to do.
|
||||
w.mu.Lock()
|
||||
for len(w.todo) == 0 {
|
||||
w.waiting++
|
||||
if w.waiting == w.running {
|
||||
// All done.
|
||||
w.wait.Broadcast()
|
||||
w.mu.Unlock()
|
||||
return
|
||||
}
|
||||
w.wait.Wait()
|
||||
w.waiting--
|
||||
}
|
||||
|
||||
// Pick something to do at random,
|
||||
// to eliminate pathological contention
|
||||
// in case items added at about the same time
|
||||
// are most likely to contend.
|
||||
i := rand.Intn(len(w.todo))
|
||||
item := w.todo[i]
|
||||
w.todo[i] = w.todo[len(w.todo)-1]
|
||||
w.todo = w.todo[:len(w.todo)-1]
|
||||
w.mu.Unlock()
|
||||
|
||||
w.f(item)
|
||||
}
|
||||
}
|
||||
|
||||
// Cache runs an action once per key and caches the result.
|
||||
type Cache struct {
|
||||
m sync.Map
|
||||
}
|
||||
|
||||
type cacheEntry struct {
|
||||
done uint32
|
||||
mu sync.Mutex
|
||||
result interface{}
|
||||
}
|
||||
|
||||
// Do calls the function f if and only if Do is being called for the first time with this key.
|
||||
// No call to Do with a given key returns until the one call to f returns.
|
||||
// Do returns the value returned by the one call to f.
|
||||
func (c *Cache) Do(key interface{}, f func() interface{}) interface{} {
|
||||
entryIface, ok := c.m.Load(key)
|
||||
if !ok {
|
||||
entryIface, _ = c.m.LoadOrStore(key, new(cacheEntry))
|
||||
}
|
||||
e := entryIface.(*cacheEntry)
|
||||
if atomic.LoadUint32(&e.done) == 0 {
|
||||
e.mu.Lock()
|
||||
if atomic.LoadUint32(&e.done) == 0 {
|
||||
e.result = f()
|
||||
atomic.StoreUint32(&e.done, 1)
|
||||
}
|
||||
e.mu.Unlock()
|
||||
}
|
||||
return e.result
|
||||
}
|
||||
|
||||
// Get returns the cached result associated with key.
|
||||
// It returns nil if there is no such result.
|
||||
// If the result for key is being computed, Get does not wait for the computation to finish.
|
||||
func (c *Cache) Get(key interface{}) interface{} {
|
||||
entryIface, ok := c.m.Load(key)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
e := entryIface.(*cacheEntry)
|
||||
if atomic.LoadUint32(&e.done) == 0 {
|
||||
return nil
|
||||
}
|
||||
return e.result
|
||||
}
|
||||
248
vendor/github.com/rogpeppe/go-internal/testenv/testenv.go
generated
vendored
Normal file
248
vendor/github.com/rogpeppe/go-internal/testenv/testenv.go
generated
vendored
Normal file
@@ -0,0 +1,248 @@
|
||||
// Copyright 2015 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 testenv provides information about what functionality
|
||||
// is available in different testing environments run by the Go team.
|
||||
//
|
||||
// It is an internal package because these details are specific
|
||||
// to the Go team's test setup (on build.golang.org) and not
|
||||
// fundamental to tests in general.
|
||||
package testenv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Builder reports the name of the builder running this test
|
||||
// (for example, "linux-amd64" or "windows-386-gce").
|
||||
// If the test is not running on the build infrastructure,
|
||||
// Builder returns the empty string.
|
||||
func Builder() string {
|
||||
return os.Getenv("GO_BUILDER_NAME")
|
||||
}
|
||||
|
||||
// HasGoBuild reports whether the current system can build programs with ``go build''
|
||||
// and then run them with os.StartProcess or exec.Command.
|
||||
func HasGoBuild() bool {
|
||||
if os.Getenv("GO_GCFLAGS") != "" {
|
||||
// It's too much work to require every caller of the go command
|
||||
// to pass along "-gcflags="+os.Getenv("GO_GCFLAGS").
|
||||
// For now, if $GO_GCFLAGS is set, report that we simply can't
|
||||
// run go build.
|
||||
return false
|
||||
}
|
||||
switch runtime.GOOS {
|
||||
case "android", "nacl", "js":
|
||||
return false
|
||||
case "darwin":
|
||||
if strings.HasPrefix(runtime.GOARCH, "arm") {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// MustHaveGoBuild checks that the current system can build programs with ``go build''
|
||||
// and then run them with os.StartProcess or exec.Command.
|
||||
// If not, MustHaveGoBuild calls t.Skip with an explanation.
|
||||
func MustHaveGoBuild(t testing.TB) {
|
||||
if os.Getenv("GO_GCFLAGS") != "" {
|
||||
t.Skipf("skipping test: 'go build' not compatible with setting $GO_GCFLAGS")
|
||||
}
|
||||
if !HasGoBuild() {
|
||||
t.Skipf("skipping test: 'go build' not available on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
}
|
||||
|
||||
// HasGoRun reports whether the current system can run programs with ``go run.''
|
||||
func HasGoRun() bool {
|
||||
// For now, having go run and having go build are the same.
|
||||
return HasGoBuild()
|
||||
}
|
||||
|
||||
// MustHaveGoRun checks that the current system can run programs with ``go run.''
|
||||
// If not, MustHaveGoRun calls t.Skip with an explanation.
|
||||
func MustHaveGoRun(t testing.TB) {
|
||||
if !HasGoRun() {
|
||||
t.Skipf("skipping test: 'go run' not available on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
}
|
||||
|
||||
// GoToolPath reports the path to the Go tool.
|
||||
// It is a convenience wrapper around GoTool.
|
||||
// If the tool is unavailable GoToolPath calls t.Skip.
|
||||
// If the tool should be available and isn't, GoToolPath calls t.Fatal.
|
||||
func GoToolPath(t testing.TB) string {
|
||||
MustHaveGoBuild(t)
|
||||
path, err := GoTool()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// GoTool reports the path to the Go tool.
|
||||
func GoTool() (string, error) {
|
||||
if !HasGoBuild() {
|
||||
return "", errors.New("platform cannot run go tool")
|
||||
}
|
||||
var exeSuffix string
|
||||
if runtime.GOOS == "windows" {
|
||||
exeSuffix = ".exe"
|
||||
}
|
||||
path := filepath.Join(runtime.GOROOT(), "bin", "go"+exeSuffix)
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return path, nil
|
||||
}
|
||||
goBin, err := exec.LookPath("go" + exeSuffix)
|
||||
if err != nil {
|
||||
return "", errors.New("cannot find go tool: " + err.Error())
|
||||
}
|
||||
return goBin, nil
|
||||
}
|
||||
|
||||
// HasExec reports whether the current system can start new processes
|
||||
// using os.StartProcess or (more commonly) exec.Command.
|
||||
func HasExec() bool {
|
||||
switch runtime.GOOS {
|
||||
case "nacl", "js":
|
||||
return false
|
||||
case "darwin":
|
||||
if strings.HasPrefix(runtime.GOARCH, "arm") {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// HasSrc reports whether the entire source tree is available under GOROOT.
|
||||
func HasSrc() bool {
|
||||
switch runtime.GOOS {
|
||||
case "nacl":
|
||||
return false
|
||||
case "darwin":
|
||||
if strings.HasPrefix(runtime.GOARCH, "arm") {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// MustHaveExec checks that the current system can start new processes
|
||||
// using os.StartProcess or (more commonly) exec.Command.
|
||||
// If not, MustHaveExec calls t.Skip with an explanation.
|
||||
func MustHaveExec(t testing.TB) {
|
||||
if !HasExec() {
|
||||
t.Skipf("skipping test: cannot exec subprocess on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
}
|
||||
|
||||
// HasExternalNetwork reports whether the current system can use
|
||||
// external (non-localhost) networks.
|
||||
func HasExternalNetwork() bool {
|
||||
return !testing.Short() && runtime.GOOS != "nacl" && runtime.GOOS != "js"
|
||||
}
|
||||
|
||||
// MustHaveExternalNetwork checks that the current system can use
|
||||
// external (non-localhost) networks.
|
||||
// If not, MustHaveExternalNetwork calls t.Skip with an explanation.
|
||||
func MustHaveExternalNetwork(t testing.TB) {
|
||||
if runtime.GOOS == "nacl" || runtime.GOOS == "js" {
|
||||
t.Skipf("skipping test: no external network on %s", runtime.GOOS)
|
||||
}
|
||||
if testing.Short() {
|
||||
t.Skipf("skipping test: no external network in -short mode")
|
||||
}
|
||||
}
|
||||
|
||||
var haveCGO bool
|
||||
|
||||
// HasCGO reports whether the current system can use cgo.
|
||||
func HasCGO() bool {
|
||||
return haveCGO
|
||||
}
|
||||
|
||||
// MustHaveCGO calls t.Skip if cgo is not available.
|
||||
func MustHaveCGO(t testing.TB) {
|
||||
if !haveCGO {
|
||||
t.Skipf("skipping test: no cgo")
|
||||
}
|
||||
}
|
||||
|
||||
// HasSymlink reports whether the current system can use os.Symlink.
|
||||
func HasSymlink() bool {
|
||||
ok, _ := hasSymlink()
|
||||
return ok
|
||||
}
|
||||
|
||||
// MustHaveSymlink reports whether the current system can use os.Symlink.
|
||||
// If not, MustHaveSymlink calls t.Skip with an explanation.
|
||||
func MustHaveSymlink(t testing.TB) {
|
||||
ok, reason := hasSymlink()
|
||||
if !ok {
|
||||
t.Skipf("skipping test: cannot make symlinks on %s/%s%s", runtime.GOOS, runtime.GOARCH, reason)
|
||||
}
|
||||
}
|
||||
|
||||
// HasLink reports whether the current system can use os.Link.
|
||||
func HasLink() bool {
|
||||
// From Android release M (Marshmallow), hard linking files is blocked
|
||||
// and an attempt to call link() on a file will return EACCES.
|
||||
// - https://code.google.com/p/android-developer-preview/issues/detail?id=3150
|
||||
return runtime.GOOS != "plan9" && runtime.GOOS != "android"
|
||||
}
|
||||
|
||||
// MustHaveLink reports whether the current system can use os.Link.
|
||||
// If not, MustHaveLink calls t.Skip with an explanation.
|
||||
func MustHaveLink(t testing.TB) {
|
||||
if !HasLink() {
|
||||
t.Skipf("skipping test: hardlinks are not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
}
|
||||
|
||||
var flaky = flag.Bool("flaky", false, "run known-flaky tests too")
|
||||
|
||||
func SkipFlaky(t testing.TB, issue int) {
|
||||
t.Helper()
|
||||
if !*flaky {
|
||||
t.Skipf("skipping known flaky test without the -flaky flag; see golang.org/issue/%d", issue)
|
||||
}
|
||||
}
|
||||
|
||||
func SkipFlakyNet(t testing.TB) {
|
||||
t.Helper()
|
||||
if v, _ := strconv.ParseBool(os.Getenv("GO_BUILDER_FLAKY_NET")); v {
|
||||
t.Skip("skipping test on builder known to have frequent network failures")
|
||||
}
|
||||
}
|
||||
|
||||
// CleanCmdEnv will fill cmd.Env with the environment, excluding certain
|
||||
// variables that could modify the behavior of the Go tools such as
|
||||
// GODEBUG and GOTRACEBACK.
|
||||
func CleanCmdEnv(cmd *exec.Cmd) *exec.Cmd {
|
||||
if cmd.Env != nil {
|
||||
panic("environment already set")
|
||||
}
|
||||
for _, env := range os.Environ() {
|
||||
// Exclude GODEBUG from the environment to prevent its output
|
||||
// from breaking tests that are trying to parse other command output.
|
||||
if strings.HasPrefix(env, "GODEBUG=") {
|
||||
continue
|
||||
}
|
||||
// Exclude GOTRACEBACK for the same reason.
|
||||
if strings.HasPrefix(env, "GOTRACEBACK=") {
|
||||
continue
|
||||
}
|
||||
cmd.Env = append(cmd.Env, env)
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
12
vendor/github.com/rogpeppe/go-internal/testenv/testenv_cgo.go
generated
vendored
Normal file
12
vendor/github.com/rogpeppe/go-internal/testenv/testenv_cgo.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
//go:build cgo
|
||||
// +build cgo
|
||||
|
||||
package testenv
|
||||
|
||||
func init() {
|
||||
haveCGO = true
|
||||
}
|
||||
21
vendor/github.com/rogpeppe/go-internal/testenv/testenv_notwin.go
generated
vendored
Normal file
21
vendor/github.com/rogpeppe/go-internal/testenv/testenv_notwin.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright 2016 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.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package testenv
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func hasSymlink() (ok bool, reason string) {
|
||||
switch runtime.GOOS {
|
||||
case "android", "nacl", "plan9":
|
||||
return false, ""
|
||||
}
|
||||
|
||||
return true, ""
|
||||
}
|
||||
48
vendor/github.com/rogpeppe/go-internal/testenv/testenv_windows.go
generated
vendored
Normal file
48
vendor/github.com/rogpeppe/go-internal/testenv/testenv_windows.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright 2016 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 testenv
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var symlinkOnce sync.Once
|
||||
var winSymlinkErr error
|
||||
|
||||
func initWinHasSymlink() {
|
||||
tmpdir, err := ioutil.TempDir("", "symtest")
|
||||
if err != nil {
|
||||
panic("failed to create temp directory: " + err.Error())
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
err = os.Symlink("target", filepath.Join(tmpdir, "symlink"))
|
||||
if err != nil {
|
||||
err = err.(*os.LinkError).Err
|
||||
switch err {
|
||||
case syscall.EWINDOWS, syscall.ERROR_PRIVILEGE_NOT_HELD:
|
||||
winSymlinkErr = err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func hasSymlink() (ok bool, reason string) {
|
||||
symlinkOnce.Do(initWinHasSymlink)
|
||||
|
||||
switch winSymlinkErr {
|
||||
case nil:
|
||||
return true, ""
|
||||
case syscall.EWINDOWS:
|
||||
return false, ": symlinks are not supported on your version of Windows"
|
||||
case syscall.ERROR_PRIVILEGE_NOT_HELD:
|
||||
return false, ": you don't have enough privileges to create symlinks"
|
||||
}
|
||||
|
||||
return false, ""
|
||||
}
|
||||
653
vendor/github.com/rogpeppe/go-internal/testscript/cmd.go
generated
vendored
Normal file
653
vendor/github.com/rogpeppe/go-internal/testscript/cmd.go
generated
vendored
Normal file
@@ -0,0 +1,653 @@
|
||||
// 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 testscript
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/diff"
|
||||
"github.com/rogpeppe/go-internal/txtar"
|
||||
)
|
||||
|
||||
// scriptCmds are the script command implementations.
|
||||
// Keep list and the implementations below sorted by name.
|
||||
//
|
||||
// NOTE: If you make changes here, update doc.go.
|
||||
var scriptCmds = map[string]func(*TestScript, bool, []string){
|
||||
"cd": (*TestScript).cmdCd,
|
||||
"chmod": (*TestScript).cmdChmod,
|
||||
"cmp": (*TestScript).cmdCmp,
|
||||
"cmpenv": (*TestScript).cmdCmpenv,
|
||||
"cp": (*TestScript).cmdCp,
|
||||
"env": (*TestScript).cmdEnv,
|
||||
"exec": (*TestScript).cmdExec,
|
||||
"exists": (*TestScript).cmdExists,
|
||||
"grep": (*TestScript).cmdGrep,
|
||||
"mkdir": (*TestScript).cmdMkdir,
|
||||
"mv": (*TestScript).cmdMv,
|
||||
"rm": (*TestScript).cmdRm,
|
||||
"skip": (*TestScript).cmdSkip,
|
||||
"stderr": (*TestScript).cmdStderr,
|
||||
"stdin": (*TestScript).cmdStdin,
|
||||
"stdout": (*TestScript).cmdStdout,
|
||||
"stop": (*TestScript).cmdStop,
|
||||
"symlink": (*TestScript).cmdSymlink,
|
||||
"unix2dos": (*TestScript).cmdUNIX2DOS,
|
||||
"unquote": (*TestScript).cmdUnquote,
|
||||
"wait": (*TestScript).cmdWait,
|
||||
}
|
||||
|
||||
// cd changes to a different directory.
|
||||
func (ts *TestScript) cmdCd(neg bool, args []string) {
|
||||
if neg {
|
||||
ts.Fatalf("unsupported: ! cd")
|
||||
}
|
||||
if len(args) != 1 {
|
||||
ts.Fatalf("usage: cd dir")
|
||||
}
|
||||
|
||||
dir := args[0]
|
||||
if !filepath.IsAbs(dir) {
|
||||
dir = filepath.Join(ts.cd, dir)
|
||||
}
|
||||
info, err := os.Stat(dir)
|
||||
if os.IsNotExist(err) {
|
||||
ts.Fatalf("directory %s does not exist", dir)
|
||||
}
|
||||
ts.Check(err)
|
||||
if !info.IsDir() {
|
||||
ts.Fatalf("%s is not a directory", dir)
|
||||
}
|
||||
ts.cd = dir
|
||||
ts.Logf("%s\n", ts.cd)
|
||||
}
|
||||
|
||||
func (ts *TestScript) cmdChmod(neg bool, args []string) {
|
||||
if neg {
|
||||
ts.Fatalf("unsupported: ! chmod")
|
||||
}
|
||||
if len(args) != 2 {
|
||||
ts.Fatalf("usage: chmod perm paths...")
|
||||
}
|
||||
perm, err := strconv.ParseUint(args[0], 8, 32)
|
||||
if err != nil || perm&uint64(os.ModePerm) != perm {
|
||||
ts.Fatalf("invalid mode: %s", args[0])
|
||||
}
|
||||
for _, arg := range args[1:] {
|
||||
path := arg
|
||||
if !filepath.IsAbs(path) {
|
||||
path = filepath.Join(ts.cd, arg)
|
||||
}
|
||||
err := os.Chmod(path, os.FileMode(perm))
|
||||
ts.Check(err)
|
||||
}
|
||||
}
|
||||
|
||||
// cmp compares two files.
|
||||
func (ts *TestScript) cmdCmp(neg bool, args []string) {
|
||||
if len(args) != 2 {
|
||||
ts.Fatalf("usage: cmp file1 file2")
|
||||
}
|
||||
|
||||
ts.doCmdCmp(neg, args, false)
|
||||
}
|
||||
|
||||
// cmpenv compares two files with environment variable substitution.
|
||||
func (ts *TestScript) cmdCmpenv(neg bool, args []string) {
|
||||
if len(args) != 2 {
|
||||
ts.Fatalf("usage: cmpenv file1 file2")
|
||||
}
|
||||
ts.doCmdCmp(neg, args, true)
|
||||
}
|
||||
|
||||
func (ts *TestScript) doCmdCmp(neg bool, args []string, env bool) {
|
||||
name1, name2 := args[0], args[1]
|
||||
text1 := ts.ReadFile(name1)
|
||||
|
||||
absName2 := ts.MkAbs(name2)
|
||||
data, err := ioutil.ReadFile(absName2)
|
||||
ts.Check(err)
|
||||
text2 := string(data)
|
||||
if env {
|
||||
text2 = ts.expand(text2)
|
||||
}
|
||||
eq := text1 == text2
|
||||
if neg {
|
||||
if eq {
|
||||
ts.Fatalf("%s and %s do not differ", name1, name2)
|
||||
}
|
||||
return // they differ, as expected
|
||||
}
|
||||
if eq {
|
||||
return // they are equal, as expected
|
||||
}
|
||||
if ts.params.UpdateScripts && !env {
|
||||
if scriptFile, ok := ts.scriptFiles[absName2]; ok {
|
||||
ts.scriptUpdates[scriptFile] = text1
|
||||
return
|
||||
}
|
||||
// The file being compared against isn't in the txtar archive, so don't
|
||||
// update the script.
|
||||
}
|
||||
|
||||
// pkg/diff is quadratic at the moment.
|
||||
// If the product of the number of lines in the inputs is too large,
|
||||
// don't call pkg.Diff at all as it might take tons of memory or time.
|
||||
// We found one million to be reasonable for an average laptop.
|
||||
const maxLineDiff = 1_000_000
|
||||
if strings.Count(text1, "\n")*strings.Count(text2, "\n") > maxLineDiff {
|
||||
ts.Fatalf("large files %s and %s differ", name1, name2)
|
||||
return
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
if err := diff.Text(name1, name2, text1, text2, &sb); err != nil {
|
||||
ts.Check(err)
|
||||
}
|
||||
|
||||
ts.Logf("%s", sb.String())
|
||||
ts.Fatalf("%s and %s differ", name1, name2)
|
||||
}
|
||||
|
||||
// cp copies files, maybe eventually directories.
|
||||
func (ts *TestScript) cmdCp(neg bool, args []string) {
|
||||
if neg {
|
||||
ts.Fatalf("unsupported: ! cp")
|
||||
}
|
||||
if len(args) < 2 {
|
||||
ts.Fatalf("usage: cp src... dst")
|
||||
}
|
||||
|
||||
dst := ts.MkAbs(args[len(args)-1])
|
||||
info, err := os.Stat(dst)
|
||||
dstDir := err == nil && info.IsDir()
|
||||
if len(args) > 2 && !dstDir {
|
||||
ts.Fatalf("cp: destination %s is not a directory", dst)
|
||||
}
|
||||
|
||||
for _, arg := range args[:len(args)-1] {
|
||||
var (
|
||||
src string
|
||||
data []byte
|
||||
mode os.FileMode
|
||||
)
|
||||
switch arg {
|
||||
case "stdout":
|
||||
src = arg
|
||||
data = []byte(ts.stdout)
|
||||
mode = 0o666
|
||||
case "stderr":
|
||||
src = arg
|
||||
data = []byte(ts.stderr)
|
||||
mode = 0o666
|
||||
default:
|
||||
src = ts.MkAbs(arg)
|
||||
info, err := os.Stat(src)
|
||||
ts.Check(err)
|
||||
mode = info.Mode() & 0o777
|
||||
data, err = ioutil.ReadFile(src)
|
||||
ts.Check(err)
|
||||
}
|
||||
targ := dst
|
||||
if dstDir {
|
||||
targ = filepath.Join(dst, filepath.Base(src))
|
||||
}
|
||||
ts.Check(ioutil.WriteFile(targ, data, mode))
|
||||
}
|
||||
}
|
||||
|
||||
// env displays or adds to the environment.
|
||||
func (ts *TestScript) cmdEnv(neg bool, args []string) {
|
||||
if neg {
|
||||
ts.Fatalf("unsupported: ! env")
|
||||
}
|
||||
if len(args) == 0 {
|
||||
printed := make(map[string]bool) // env list can have duplicates; only print effective value (from envMap) once
|
||||
for _, kv := range ts.env {
|
||||
k := envvarname(kv[:strings.Index(kv, "=")])
|
||||
if !printed[k] {
|
||||
printed[k] = true
|
||||
ts.Logf("%s=%s\n", k, ts.envMap[k])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
for _, env := range args {
|
||||
i := strings.Index(env, "=")
|
||||
if i < 0 {
|
||||
// Display value instead of setting it.
|
||||
ts.Logf("%s=%s\n", env, ts.Getenv(env))
|
||||
continue
|
||||
}
|
||||
ts.Setenv(env[:i], env[i+1:])
|
||||
}
|
||||
}
|
||||
|
||||
var backgroundSpecifier = regexp.MustCompile(`^&([a-zA-Z_0-9]+&)?$`)
|
||||
|
||||
// exec runs the given command.
|
||||
func (ts *TestScript) cmdExec(neg bool, args []string) {
|
||||
if len(args) < 1 || (len(args) == 1 && args[0] == "&") {
|
||||
ts.Fatalf("usage: exec program [args...] [&]")
|
||||
}
|
||||
|
||||
var err error
|
||||
if len(args) > 0 && backgroundSpecifier.MatchString(args[len(args)-1]) {
|
||||
bgName := strings.TrimSuffix(strings.TrimPrefix(args[len(args)-1], "&"), "&")
|
||||
if ts.findBackground(bgName) != nil {
|
||||
ts.Fatalf("duplicate background process name %q", bgName)
|
||||
}
|
||||
var cmd *exec.Cmd
|
||||
cmd, err = ts.execBackground(args[0], args[1:len(args)-1]...)
|
||||
if err == nil {
|
||||
wait := make(chan struct{})
|
||||
go func() {
|
||||
ctxWait(ts.ctxt, cmd)
|
||||
close(wait)
|
||||
}()
|
||||
ts.background = append(ts.background, backgroundCmd{bgName, cmd, wait, neg})
|
||||
}
|
||||
ts.stdout, ts.stderr = "", ""
|
||||
} else {
|
||||
ts.stdout, ts.stderr, err = ts.exec(args[0], args[1:]...)
|
||||
if ts.stdout != "" {
|
||||
fmt.Fprintf(&ts.log, "[stdout]\n%s", ts.stdout)
|
||||
}
|
||||
if ts.stderr != "" {
|
||||
fmt.Fprintf(&ts.log, "[stderr]\n%s", ts.stderr)
|
||||
}
|
||||
if err == nil && neg {
|
||||
ts.Fatalf("unexpected command success")
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintf(&ts.log, "[%v]\n", err)
|
||||
if ts.ctxt.Err() != nil {
|
||||
ts.Fatalf("test timed out while running command")
|
||||
} else if !neg {
|
||||
ts.Fatalf("unexpected command failure")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// exists checks that the list of files exists.
|
||||
func (ts *TestScript) cmdExists(neg bool, args []string) {
|
||||
var readonly bool
|
||||
if len(args) > 0 && args[0] == "-readonly" {
|
||||
readonly = true
|
||||
args = args[1:]
|
||||
}
|
||||
if len(args) == 0 {
|
||||
ts.Fatalf("usage: exists [-readonly] file...")
|
||||
}
|
||||
|
||||
for _, file := range args {
|
||||
file = ts.MkAbs(file)
|
||||
info, err := os.Stat(file)
|
||||
if err == nil && neg {
|
||||
what := "file"
|
||||
if info.IsDir() {
|
||||
what = "directory"
|
||||
}
|
||||
ts.Fatalf("%s %s unexpectedly exists", what, file)
|
||||
}
|
||||
if err != nil && !neg {
|
||||
ts.Fatalf("%s does not exist", file)
|
||||
}
|
||||
if err == nil && !neg && readonly && info.Mode()&0o222 != 0 {
|
||||
ts.Fatalf("%s exists but is writable", file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mkdir creates directories.
|
||||
func (ts *TestScript) cmdMkdir(neg bool, args []string) {
|
||||
if neg {
|
||||
ts.Fatalf("unsupported: ! mkdir")
|
||||
}
|
||||
if len(args) < 1 {
|
||||
ts.Fatalf("usage: mkdir dir...")
|
||||
}
|
||||
for _, arg := range args {
|
||||
ts.Check(os.MkdirAll(ts.MkAbs(arg), 0o777))
|
||||
}
|
||||
}
|
||||
|
||||
func (ts *TestScript) cmdMv(neg bool, args []string) {
|
||||
if neg {
|
||||
ts.Fatalf("unsupported: ! mv")
|
||||
}
|
||||
if len(args) != 2 {
|
||||
ts.Fatalf("usage: mv old new")
|
||||
}
|
||||
ts.Check(os.Rename(ts.MkAbs(args[0]), ts.MkAbs(args[1])))
|
||||
}
|
||||
|
||||
// unquote unquotes files.
|
||||
func (ts *TestScript) cmdUnquote(neg bool, args []string) {
|
||||
if neg {
|
||||
ts.Fatalf("unsupported: ! unquote")
|
||||
}
|
||||
for _, arg := range args {
|
||||
file := ts.MkAbs(arg)
|
||||
data, err := ioutil.ReadFile(file)
|
||||
ts.Check(err)
|
||||
data, err = txtar.Unquote(data)
|
||||
ts.Check(err)
|
||||
err = ioutil.WriteFile(file, data, 0o666)
|
||||
ts.Check(err)
|
||||
}
|
||||
}
|
||||
|
||||
// rm removes files or directories.
|
||||
func (ts *TestScript) cmdRm(neg bool, args []string) {
|
||||
if neg {
|
||||
ts.Fatalf("unsupported: ! rm")
|
||||
}
|
||||
if len(args) < 1 {
|
||||
ts.Fatalf("usage: rm file...")
|
||||
}
|
||||
for _, arg := range args {
|
||||
file := ts.MkAbs(arg)
|
||||
removeAll(file) // does chmod and then attempts rm
|
||||
ts.Check(os.RemoveAll(file)) // report error
|
||||
}
|
||||
}
|
||||
|
||||
// skip marks the test skipped.
|
||||
func (ts *TestScript) cmdSkip(neg bool, args []string) {
|
||||
if len(args) > 1 {
|
||||
ts.Fatalf("usage: skip [msg]")
|
||||
}
|
||||
if neg {
|
||||
ts.Fatalf("unsupported: ! skip")
|
||||
}
|
||||
|
||||
// Before we mark the test as skipped, shut down any background processes and
|
||||
// make sure they have returned the correct status.
|
||||
for _, bg := range ts.background {
|
||||
interruptProcess(bg.cmd.Process)
|
||||
}
|
||||
ts.cmdWait(false, nil)
|
||||
|
||||
if len(args) == 1 {
|
||||
ts.t.Skip(args[0])
|
||||
}
|
||||
ts.t.Skip()
|
||||
}
|
||||
|
||||
func (ts *TestScript) cmdStdin(neg bool, args []string) {
|
||||
if neg {
|
||||
ts.Fatalf("unsupported: ! stdin")
|
||||
}
|
||||
if len(args) != 1 {
|
||||
ts.Fatalf("usage: stdin filename")
|
||||
}
|
||||
ts.stdin = ts.ReadFile(args[0])
|
||||
}
|
||||
|
||||
// stdout checks that the last go command standard output matches a regexp.
|
||||
func (ts *TestScript) cmdStdout(neg bool, args []string) {
|
||||
scriptMatch(ts, neg, args, ts.stdout, "stdout")
|
||||
}
|
||||
|
||||
// stderr checks that the last go command standard output matches a regexp.
|
||||
func (ts *TestScript) cmdStderr(neg bool, args []string) {
|
||||
scriptMatch(ts, neg, args, ts.stderr, "stderr")
|
||||
}
|
||||
|
||||
// grep checks that file content matches a regexp.
|
||||
// Like stdout/stderr and unlike Unix grep, it accepts Go regexp syntax.
|
||||
func (ts *TestScript) cmdGrep(neg bool, args []string) {
|
||||
scriptMatch(ts, neg, args, "", "grep")
|
||||
}
|
||||
|
||||
// stop stops execution of the test (marking it passed).
|
||||
func (ts *TestScript) cmdStop(neg bool, args []string) {
|
||||
if neg {
|
||||
ts.Fatalf("unsupported: ! stop")
|
||||
}
|
||||
if len(args) > 1 {
|
||||
ts.Fatalf("usage: stop [msg]")
|
||||
}
|
||||
if len(args) == 1 {
|
||||
ts.Logf("stop: %s\n", args[0])
|
||||
} else {
|
||||
ts.Logf("stop\n")
|
||||
}
|
||||
ts.stopped = true
|
||||
}
|
||||
|
||||
// symlink creates a symbolic link.
|
||||
func (ts *TestScript) cmdSymlink(neg bool, args []string) {
|
||||
if neg {
|
||||
ts.Fatalf("unsupported: ! symlink")
|
||||
}
|
||||
if len(args) != 3 || args[1] != "->" {
|
||||
ts.Fatalf("usage: symlink file -> target")
|
||||
}
|
||||
// Note that the link target args[2] is not interpreted with MkAbs:
|
||||
// it will be interpreted relative to the directory file is in.
|
||||
ts.Check(os.Symlink(args[2], ts.MkAbs(args[0])))
|
||||
}
|
||||
|
||||
// cmdUNIX2DOS converts files from UNIX line endings to DOS line endings.
|
||||
func (ts *TestScript) cmdUNIX2DOS(neg bool, args []string) {
|
||||
if neg {
|
||||
ts.Fatalf("unsupported: ! unix2dos")
|
||||
}
|
||||
if len(args) < 1 {
|
||||
ts.Fatalf("usage: unix2dos paths...")
|
||||
}
|
||||
for _, arg := range args {
|
||||
filename := ts.MkAbs(arg)
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
ts.Check(err)
|
||||
dosData, err := unix2DOS(data)
|
||||
ts.Check(err)
|
||||
if err := ioutil.WriteFile(filename, dosData, 0o666); err != nil {
|
||||
ts.Fatalf("%s: %v", filename, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tait waits for background commands to exit, setting stderr and stdout to their result.
|
||||
func (ts *TestScript) cmdWait(neg bool, args []string) {
|
||||
if len(args) > 1 {
|
||||
ts.Fatalf("usage: wait [name]")
|
||||
}
|
||||
if neg {
|
||||
ts.Fatalf("unsupported: ! wait")
|
||||
}
|
||||
if len(args) > 0 {
|
||||
ts.waitBackgroundOne(args[0])
|
||||
} else {
|
||||
ts.waitBackground(true)
|
||||
}
|
||||
}
|
||||
|
||||
func (ts *TestScript) waitBackgroundOne(bgName string) {
|
||||
bg := ts.findBackground(bgName)
|
||||
if bg == nil {
|
||||
ts.Fatalf("unknown background process %q", bgName)
|
||||
}
|
||||
<-bg.wait
|
||||
ts.stdout = bg.cmd.Stdout.(*strings.Builder).String()
|
||||
ts.stderr = bg.cmd.Stderr.(*strings.Builder).String()
|
||||
if ts.stdout != "" {
|
||||
fmt.Fprintf(&ts.log, "[stdout]\n%s", ts.stdout)
|
||||
}
|
||||
if ts.stderr != "" {
|
||||
fmt.Fprintf(&ts.log, "[stderr]\n%s", ts.stderr)
|
||||
}
|
||||
// Note: ignore bg.neg, which only takes effect on the non-specific
|
||||
// wait command.
|
||||
if bg.cmd.ProcessState.Success() {
|
||||
if bg.neg {
|
||||
ts.Fatalf("unexpected command success")
|
||||
}
|
||||
} else {
|
||||
if ts.ctxt.Err() != nil {
|
||||
ts.Fatalf("test timed out while running command")
|
||||
} else if !bg.neg {
|
||||
ts.Fatalf("unexpected command failure")
|
||||
}
|
||||
}
|
||||
// Remove this process from the list of running background processes.
|
||||
for i := range ts.background {
|
||||
if bg == &ts.background[i] {
|
||||
ts.background = append(ts.background[:i], ts.background[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ts *TestScript) findBackground(bgName string) *backgroundCmd {
|
||||
if bgName == "" {
|
||||
return nil
|
||||
}
|
||||
for i := range ts.background {
|
||||
bg := &ts.background[i]
|
||||
if bg.name == bgName {
|
||||
return bg
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ts *TestScript) waitBackground(checkStatus bool) {
|
||||
var stdouts, stderrs []string
|
||||
for _, bg := range ts.background {
|
||||
<-bg.wait
|
||||
|
||||
args := append([]string{filepath.Base(bg.cmd.Args[0])}, bg.cmd.Args[1:]...)
|
||||
fmt.Fprintf(&ts.log, "[background] %s: %v\n", strings.Join(args, " "), bg.cmd.ProcessState)
|
||||
|
||||
cmdStdout := bg.cmd.Stdout.(*strings.Builder).String()
|
||||
if cmdStdout != "" {
|
||||
fmt.Fprintf(&ts.log, "[stdout]\n%s", cmdStdout)
|
||||
stdouts = append(stdouts, cmdStdout)
|
||||
}
|
||||
|
||||
cmdStderr := bg.cmd.Stderr.(*strings.Builder).String()
|
||||
if cmdStderr != "" {
|
||||
fmt.Fprintf(&ts.log, "[stderr]\n%s", cmdStderr)
|
||||
stderrs = append(stderrs, cmdStderr)
|
||||
}
|
||||
|
||||
if !checkStatus {
|
||||
continue
|
||||
}
|
||||
if bg.cmd.ProcessState.Success() {
|
||||
if bg.neg {
|
||||
ts.Fatalf("unexpected command success")
|
||||
}
|
||||
} else {
|
||||
if ts.ctxt.Err() != nil {
|
||||
ts.Fatalf("test timed out while running command")
|
||||
} else if !bg.neg {
|
||||
ts.Fatalf("unexpected command failure")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ts.stdout = strings.Join(stdouts, "")
|
||||
ts.stderr = strings.Join(stderrs, "")
|
||||
ts.background = nil
|
||||
}
|
||||
|
||||
// scriptMatch implements both stdout and stderr.
|
||||
func scriptMatch(ts *TestScript, neg bool, args []string, text, name string) {
|
||||
n := 0
|
||||
if len(args) >= 1 && strings.HasPrefix(args[0], "-count=") {
|
||||
if neg {
|
||||
ts.Fatalf("cannot use -count= with negated match")
|
||||
}
|
||||
var err error
|
||||
n, err = strconv.Atoi(args[0][len("-count="):])
|
||||
if err != nil {
|
||||
ts.Fatalf("bad -count=: %v", err)
|
||||
}
|
||||
if n < 1 {
|
||||
ts.Fatalf("bad -count=: must be at least 1")
|
||||
}
|
||||
args = args[1:]
|
||||
}
|
||||
|
||||
extraUsage := ""
|
||||
want := 1
|
||||
if name == "grep" {
|
||||
extraUsage = " file"
|
||||
want = 2
|
||||
}
|
||||
if len(args) != want {
|
||||
ts.Fatalf("usage: %s [-count=N] 'pattern'%s", name, extraUsage)
|
||||
}
|
||||
|
||||
pattern := args[0]
|
||||
re, err := regexp.Compile(`(?m)` + pattern)
|
||||
ts.Check(err)
|
||||
|
||||
isGrep := name == "grep"
|
||||
if isGrep {
|
||||
name = args[1] // for error messages
|
||||
data, err := ioutil.ReadFile(ts.MkAbs(args[1]))
|
||||
ts.Check(err)
|
||||
text = string(data)
|
||||
}
|
||||
|
||||
if neg {
|
||||
if re.MatchString(text) {
|
||||
if isGrep {
|
||||
ts.Logf("[%s]\n%s\n", name, text)
|
||||
}
|
||||
ts.Fatalf("unexpected match for %#q found in %s: %s", pattern, name, re.FindString(text))
|
||||
}
|
||||
} else {
|
||||
if !re.MatchString(text) {
|
||||
if isGrep {
|
||||
ts.Logf("[%s]\n%s\n", name, text)
|
||||
}
|
||||
ts.Fatalf("no match for %#q found in %s", pattern, name)
|
||||
}
|
||||
if n > 0 {
|
||||
count := len(re.FindAllString(text, -1))
|
||||
if count != n {
|
||||
if isGrep {
|
||||
ts.Logf("[%s]\n%s\n", name, text)
|
||||
}
|
||||
ts.Fatalf("have %d matches for %#q, want %d", count, pattern, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unix2DOS returns data with UNIX line endings converted to DOS line endings.
|
||||
func unix2DOS(data []byte) ([]byte, error) {
|
||||
sb := &strings.Builder{}
|
||||
s := bufio.NewScanner(bytes.NewReader(data))
|
||||
for s.Scan() {
|
||||
if _, err := sb.Write(s.Bytes()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := sb.WriteString("\r\n"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := s.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(sb.String()), nil
|
||||
}
|
||||
280
vendor/github.com/rogpeppe/go-internal/testscript/cover.go
generated
vendored
Normal file
280
vendor/github.com/rogpeppe/go-internal/testscript/cover.go
generated
vendored
Normal file
@@ -0,0 +1,280 @@
|
||||
// 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 testscript
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// mergeCoverProfile merges the coverage information in f into
|
||||
// cover. It assumes that the coverage information in f is
|
||||
// always produced from the same binary for every call.
|
||||
func mergeCoverProfile(cover *testing.Cover, path string) error {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
scanner, err := newProfileScanner(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if scanner.Mode() != testing.CoverMode() {
|
||||
return errors.New("unexpected coverage mode in subcommand")
|
||||
}
|
||||
if cover.Mode == "" {
|
||||
cover.Mode = scanner.Mode()
|
||||
}
|
||||
isCount := cover.Mode == "count"
|
||||
if cover.Counters == nil {
|
||||
cover.Counters = make(map[string][]uint32)
|
||||
cover.Blocks = make(map[string][]testing.CoverBlock)
|
||||
}
|
||||
|
||||
// Note that we rely on the fact that the coverage is written
|
||||
// out file-by-file, with all blocks for a file in sequence.
|
||||
var (
|
||||
filename string
|
||||
blockId uint32
|
||||
counters []uint32
|
||||
blocks []testing.CoverBlock
|
||||
)
|
||||
flush := func() {
|
||||
if len(counters) > 0 {
|
||||
cover.Counters[filename] = counters
|
||||
cover.Blocks[filename] = blocks
|
||||
}
|
||||
}
|
||||
for scanner.Scan() {
|
||||
block := scanner.Block()
|
||||
if scanner.Filename() != filename {
|
||||
flush()
|
||||
filename = scanner.Filename()
|
||||
counters = cover.Counters[filename]
|
||||
blocks = cover.Blocks[filename]
|
||||
blockId = 0
|
||||
} else {
|
||||
blockId++
|
||||
}
|
||||
if int(blockId) >= len(counters) {
|
||||
counters = append(counters, block.Count)
|
||||
blocks = append(blocks, block.CoverBlock)
|
||||
continue
|
||||
}
|
||||
// TODO check that block.CoverBlock == blocks[blockId] ?
|
||||
if isCount {
|
||||
counters[blockId] += block.Count
|
||||
} else {
|
||||
counters[blockId] |= block.Count
|
||||
}
|
||||
}
|
||||
flush()
|
||||
if scanner.Err() != nil {
|
||||
return fmt.Errorf("error scanning profile: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func finalizeCoverProfile(dir string) error {
|
||||
// Merge all the coverage profiles written by test binary sub-processes,
|
||||
// such as those generated by executions of commands.
|
||||
var cover testing.Cover
|
||||
if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.Mode().IsRegular() {
|
||||
return nil
|
||||
}
|
||||
if err := mergeCoverProfile(&cover, path); err != nil {
|
||||
return fmt.Errorf("cannot merge coverage profile from %v: %v", path, err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
// The RemoveAll seems to fail very rarely, with messages like
|
||||
// "directory not empty". It's unclear why.
|
||||
// For now, if it happens again, try to print a bit more info.
|
||||
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err == nil && !info.IsDir() {
|
||||
fmt.Fprintln(os.Stderr, "non-directory found after RemoveAll:", path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// We need to include our own top-level coverage profile too.
|
||||
cprof := coverProfile()
|
||||
if err := mergeCoverProfile(&cover, cprof); err != nil {
|
||||
return fmt.Errorf("cannot merge coverage profile from %v: %v", cprof, err)
|
||||
}
|
||||
|
||||
// Finally, write the resulting merged profile.
|
||||
f, err := os.Create(cprof)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create cover profile: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
w := bufio.NewWriter(f)
|
||||
if err := writeCoverProfile1(w, cover); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeCoverProfile1(w io.Writer, cover testing.Cover) error {
|
||||
fmt.Fprintf(w, "mode: %s\n", cover.Mode)
|
||||
var active, total int64
|
||||
var count uint32
|
||||
for name, counts := range cover.Counters {
|
||||
blocks := cover.Blocks[name]
|
||||
for i := range counts {
|
||||
stmts := int64(blocks[i].Stmts)
|
||||
total += stmts
|
||||
count = atomic.LoadUint32(&counts[i]) // For -mode=atomic.
|
||||
if count > 0 {
|
||||
active += stmts
|
||||
}
|
||||
_, err := fmt.Fprintf(w, "%s:%d.%d,%d.%d %d %d\n", name,
|
||||
blocks[i].Line0, blocks[i].Col0,
|
||||
blocks[i].Line1, blocks[i].Col1,
|
||||
stmts,
|
||||
count,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if total == 0 {
|
||||
total = 1
|
||||
}
|
||||
fmt.Printf("total coverage: %.1f%% of statements%s\n", 100*float64(active)/float64(total), cover.CoveredPackages)
|
||||
return nil
|
||||
}
|
||||
|
||||
type profileScanner struct {
|
||||
mode string
|
||||
err error
|
||||
scanner *bufio.Scanner
|
||||
filename string
|
||||
block coverBlock
|
||||
}
|
||||
|
||||
type coverBlock struct {
|
||||
testing.CoverBlock
|
||||
Count uint32
|
||||
}
|
||||
|
||||
var profileLineRe = regexp.MustCompile(`^(.+):([0-9]+)\.([0-9]+),([0-9]+)\.([0-9]+) ([0-9]+) ([0-9]+)$`)
|
||||
|
||||
func toInt(s string) int {
|
||||
i, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func newProfileScanner(r io.Reader) (*profileScanner, error) {
|
||||
s := &profileScanner{
|
||||
scanner: bufio.NewScanner(r),
|
||||
}
|
||||
// First line is "mode: foo", where foo is "set", "count", or "atomic".
|
||||
// Rest of file is in the format
|
||||
// encoding/base64/base64.go:34.44,37.40 3 1
|
||||
// where the fields are: name.go:line.column,line.column numberOfStatements count
|
||||
if !s.scanner.Scan() {
|
||||
return nil, fmt.Errorf("no lines found in profile: %v", s.Err())
|
||||
}
|
||||
line := s.scanner.Text()
|
||||
mode := strings.TrimPrefix(line, "mode: ")
|
||||
if len(mode) == len(line) {
|
||||
return nil, fmt.Errorf("bad mode line %q", line)
|
||||
}
|
||||
s.mode = mode
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Mode returns the profile's coverage mode (one of "atomic", "count:
|
||||
// or "set").
|
||||
func (s *profileScanner) Mode() string {
|
||||
return s.mode
|
||||
}
|
||||
|
||||
// Err returns any error encountered when scanning a profile.
|
||||
func (s *profileScanner) Err() error {
|
||||
if s.err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return s.err
|
||||
}
|
||||
|
||||
// Block returns the most recently scanned profile block, or the zero
|
||||
// block if Scan has not been called or has returned false.
|
||||
func (s *profileScanner) Block() coverBlock {
|
||||
if s.err == nil {
|
||||
return s.block
|
||||
}
|
||||
return coverBlock{}
|
||||
}
|
||||
|
||||
// Filename returns the filename of the most recently scanned profile
|
||||
// block, or the empty string if Scan has not been called or has
|
||||
// returned false.
|
||||
func (s *profileScanner) Filename() string {
|
||||
if s.err == nil {
|
||||
return s.filename
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Scan scans the next line in a coverage profile and reports whether
|
||||
// a line was found.
|
||||
func (s *profileScanner) Scan() bool {
|
||||
if s.err != nil {
|
||||
return false
|
||||
}
|
||||
if !s.scanner.Scan() {
|
||||
s.err = io.EOF
|
||||
return false
|
||||
}
|
||||
m := profileLineRe.FindStringSubmatch(s.scanner.Text())
|
||||
if m == nil {
|
||||
s.err = fmt.Errorf("line %q doesn't match expected format %v", m, profileLineRe)
|
||||
return false
|
||||
}
|
||||
s.filename = m[1]
|
||||
s.block = coverBlock{
|
||||
CoverBlock: testing.CoverBlock{
|
||||
Line0: uint32(toInt(m[2])),
|
||||
Col0: uint16(toInt(m[3])),
|
||||
Line1: uint32(toInt(m[4])),
|
||||
Col1: uint16(toInt(m[5])),
|
||||
Stmts: uint16(toInt(m[6])),
|
||||
},
|
||||
Count: uint32(toInt(m[7])),
|
||||
}
|
||||
return true
|
||||
}
|
||||
345
vendor/github.com/rogpeppe/go-internal/testscript/doc.go
generated
vendored
Normal file
345
vendor/github.com/rogpeppe/go-internal/testscript/doc.go
generated
vendored
Normal file
@@ -0,0 +1,345 @@
|
||||
// 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 testscript provides support for defining filesystem-based tests by
|
||||
creating scripts in a directory.
|
||||
|
||||
To invoke the tests, call testscript.Run. For example:
|
||||
|
||||
func TestFoo(t *testing.T) {
|
||||
testscript.Run(t, testscript.Params{
|
||||
Dir: "testdata",
|
||||
})
|
||||
}
|
||||
|
||||
A testscript directory holds test scripts with extension txtar or txt run during 'go test'.
|
||||
Each script defines a subtest; the exact set of allowable commands in a
|
||||
script are defined by the parameters passed to the Run function.
|
||||
To run a specific script foo.txtar or foo.txt, run
|
||||
|
||||
go test cmd/go -run=TestName/^foo$
|
||||
|
||||
where TestName is the name of the test that Run is called from.
|
||||
|
||||
To define an executable command (or several) that can be run as part of the script,
|
||||
call RunMain with the functions that implement the command's functionality.
|
||||
The command functions will be called in a separate process, so are
|
||||
free to mutate global variables without polluting the top level test binary.
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(testscript.RunMain(m, map[string] func() int{
|
||||
"testscript": testscriptMain,
|
||||
}))
|
||||
}
|
||||
|
||||
In general script files should have short names: a few words, not whole sentences.
|
||||
The first word should be the general category of behavior being tested,
|
||||
often the name of a subcommand to be tested or a concept (vendor, pattern).
|
||||
|
||||
Each script is a text archive (go doc github.com/rogpeppe/go-internal/txtar).
|
||||
The script begins with an actual command script to run
|
||||
followed by the content of zero or more supporting files to
|
||||
create in the script's temporary file system before it starts executing.
|
||||
|
||||
As an example:
|
||||
|
||||
# hello world
|
||||
exec cat hello.text
|
||||
stdout 'hello world\n'
|
||||
! stderr .
|
||||
|
||||
-- hello.text --
|
||||
hello world
|
||||
|
||||
Each script runs in a fresh temporary work directory tree, available to scripts as $WORK.
|
||||
Scripts also have access to these other environment variables:
|
||||
|
||||
PATH=<actual PATH>
|
||||
HOME=/no-home
|
||||
TMPDIR=$WORK/.tmp
|
||||
devnull=<value of os.DevNull>
|
||||
/=<value of os.PathSeparator>
|
||||
:=<value of os.PathListSeparator>
|
||||
$=$
|
||||
|
||||
The environment variable $exe (lowercase) is an empty string on most
|
||||
systems, ".exe" on Windows.
|
||||
|
||||
The script's supporting files are unpacked relative to $WORK
|
||||
and then the script begins execution in that
|
||||
directory as well. Thus the example above runs in $WORK
|
||||
with $WORK/hello.txtar containing the listed contents.
|
||||
|
||||
The lines at the top of the script are a sequence of commands to be
|
||||
executed by a small script engine in the testscript package (not the system
|
||||
shell). The script stops and the overall test fails if any particular
|
||||
command fails.
|
||||
|
||||
Each line is parsed into a sequence of space-separated command words,
|
||||
with environment variable expansion and # marking an end-of-line comment.
|
||||
Adding single quotes around text keeps spaces in that text from being
|
||||
treated as word separators and also disables environment variable
|
||||
expansion. Inside a single-quoted block of text, a repeated single
|
||||
quote indicates a literal single quote, as in:
|
||||
|
||||
'Don''t communicate by sharing memory.'
|
||||
|
||||
A line beginning with # is a comment and conventionally explains what is
|
||||
being done or tested at the start of a new phase in the script.
|
||||
|
||||
A special form of environment variable syntax can be used to quote
|
||||
regexp metacharacters inside environment variables. The "@R" suffix
|
||||
is special, and indicates that the variable should be quoted.
|
||||
|
||||
${VAR@R}
|
||||
|
||||
The command prefix ! indicates that the command on the rest of the line
|
||||
(typically go or a matching predicate) must fail, not succeed. Only certain
|
||||
commands support this prefix. They are indicated below by [!] in the synopsis.
|
||||
|
||||
The command prefix [cond] indicates that the command on the rest of the line
|
||||
should only run when the condition is satisfied. The predefined conditions are:
|
||||
|
||||
- [short] for testing.Short()
|
||||
- [net] for whether the external network can be used
|
||||
- [link] for whether the OS has hard link support
|
||||
- [symlink] for whether the OS has symbolic link support
|
||||
- [exec:prog] for whether prog is available for execution (found by exec.LookPath)
|
||||
- [gc] for whether Go was built with gc
|
||||
- [gccgo] for whether Go was built with gccgo
|
||||
- [go1.x] for whether the Go version is 1.x or later
|
||||
- [unix] for whether the OS is Unix-like (that is, would match the 'unix' build
|
||||
constraint)
|
||||
|
||||
Any known values of GOOS and GOARCH can also be used as conditions. They will be
|
||||
satisfied if the target OS or architecture match the specified value. For example,
|
||||
the condition [darwin] is true if GOOS=darwin, and [amd64] is true if GOARCH=amd64.
|
||||
|
||||
A condition can be negated: [!short] means to run the rest of the line
|
||||
when testing.Short() is false.
|
||||
|
||||
Additional conditions can be added by passing a function to Params.Condition.
|
||||
|
||||
The predefined commands are:
|
||||
|
||||
- cd dir
|
||||
Change to the given directory for future commands.
|
||||
|
||||
- chmod perm path...
|
||||
Change the permissions of the files or directories named by the path arguments
|
||||
to the given octal mode (000 to 777).
|
||||
|
||||
- [!] cmp file1 file2
|
||||
Check that the named files have (or do not have) the same content.
|
||||
By convention, file1 is the actual data and file2 the expected data.
|
||||
File1 can be "stdout" or "stderr" to use the standard output or standard error
|
||||
from the most recent exec or wait command.
|
||||
(If the files have differing content and the command is not negated,
|
||||
the failure prints a diff.)
|
||||
|
||||
- [!] cmpenv file1 file2
|
||||
Like cmp, but environment variables in file2 are substituted before the
|
||||
comparison. For example, $GOOS is replaced by the target GOOS.
|
||||
|
||||
- cp src... dst
|
||||
Copy the listed files to the target file or existing directory.
|
||||
src can include "stdout" or "stderr" to use the standard output or standard error
|
||||
from the most recent exec or go command.
|
||||
|
||||
- env [key=value...]
|
||||
With no arguments, print the environment (useful for debugging).
|
||||
Otherwise add the listed key=value pairs to the environment.
|
||||
|
||||
- [!] exec program [args...] [&]
|
||||
Run the given executable program with the arguments.
|
||||
It must (or must not) succeed.
|
||||
Note that 'exec' does not terminate the script (unlike in Unix shells).
|
||||
|
||||
If the last token is '&', the program executes in the background. The standard
|
||||
output and standard error of the previous command is cleared, but the output
|
||||
of the background process is buffered — and checking of its exit status is
|
||||
delayed — until the next call to 'wait', 'skip', or 'stop' or the end of the
|
||||
test. At the end of the test, any remaining background processes are
|
||||
terminated using os.Interrupt (if supported) or os.Kill.
|
||||
|
||||
If the last token is '&word&` (where "word" is alphanumeric), the
|
||||
command runs in the background but has a name, and can be waited
|
||||
for specifically by passing the word to 'wait'.
|
||||
|
||||
Standard input can be provided using the stdin command; this will be
|
||||
cleared after exec has been called.
|
||||
|
||||
- [!] exists [-readonly] file...
|
||||
Each of the listed files or directories must (or must not) exist.
|
||||
If -readonly is given, the files or directories must be unwritable.
|
||||
|
||||
- [!] grep [-count=N] pattern file
|
||||
The file's content must (or must not) match the regular expression pattern.
|
||||
For positive matches, -count=N specifies an exact number of matches to require.
|
||||
|
||||
- mkdir path...
|
||||
Create the listed directories, if they do not already exists.
|
||||
|
||||
- mv path1 path2
|
||||
Rename path1 to path2. OS-specific restrictions may apply when path1 and path2
|
||||
are in different directories.
|
||||
|
||||
- rm file...
|
||||
Remove the listed files or directories.
|
||||
|
||||
- skip [message]
|
||||
Mark the test skipped, including the message if given.
|
||||
|
||||
- [!] stderr [-count=N] pattern
|
||||
Apply the grep command (see above) to the standard error
|
||||
from the most recent exec or wait command.
|
||||
|
||||
- stdin file
|
||||
Set the standard input for the next exec command to the contents of the given file.
|
||||
File can be "stdout" or "stderr" to use the standard output or standard error
|
||||
from the most recent exec or wait command.
|
||||
|
||||
- [!] stdout [-count=N] pattern
|
||||
Apply the grep command (see above) to the standard output
|
||||
from the most recent exec or wait command.
|
||||
|
||||
- stop [message]
|
||||
Stop the test early (marking it as passing), including the message if given.
|
||||
|
||||
- symlink file -> target
|
||||
Create file as a symlink to target. The -> (like in ls -l output) is required.
|
||||
|
||||
- unquote file...
|
||||
Rewrite each file by replacing any leading ">" characters from
|
||||
each line. This enables a file to contain substrings that look like
|
||||
txtar file markers.
|
||||
See also https://godoc.org/github.com/rogpeppe/go-internal/txtar#Unquote
|
||||
|
||||
- wait [command]
|
||||
Wait for all 'exec' and 'go' commands started in the background (with the '&'
|
||||
token) to exit, and display success or failure status for them.
|
||||
After a call to wait, the 'stderr' and 'stdout' commands will apply to the
|
||||
concatenation of the corresponding streams of the background commands,
|
||||
in the order in which those commands were started.
|
||||
|
||||
If an argument is specified, it waits for just that command.
|
||||
|
||||
When TestScript runs a script and the script fails, by default TestScript shows
|
||||
the execution of the most recent phase of the script (since the last # comment)
|
||||
and only shows the # comments for earlier phases. For example, here is a
|
||||
multi-phase script with a bug in it (TODO: make this example less go-command
|
||||
specific):
|
||||
|
||||
# GOPATH with p1 in d2, p2 in d2
|
||||
env GOPATH=$WORK/d1${:}$WORK/d2
|
||||
|
||||
# build & install p1
|
||||
env
|
||||
go install -i p1
|
||||
! stale p1
|
||||
! stale p2
|
||||
|
||||
# modify p2 - p1 should appear stale
|
||||
cp $WORK/p2x.go $WORK/d2/src/p2/p2.go
|
||||
stale p1 p2
|
||||
|
||||
# build & install p1 again
|
||||
go install -i p11
|
||||
! stale p1
|
||||
! stale p2
|
||||
|
||||
-- $WORK/d1/src/p1/p1.go --
|
||||
package p1
|
||||
import "p2"
|
||||
func F() { p2.F() }
|
||||
-- $WORK/d2/src/p2/p2.go --
|
||||
package p2
|
||||
func F() {}
|
||||
-- $WORK/p2x.go --
|
||||
package p2
|
||||
func F() {}
|
||||
func G() {}
|
||||
|
||||
The bug is that the final phase installs p11 instead of p1. The test failure looks like:
|
||||
|
||||
$ go test -run=Script
|
||||
--- FAIL: TestScript (3.75s)
|
||||
--- FAIL: TestScript/install_rebuild_gopath (0.16s)
|
||||
script_test.go:223:
|
||||
# GOPATH with p1 in d2, p2 in d2 (0.000s)
|
||||
# build & install p1 (0.087s)
|
||||
# modify p2 - p1 should appear stale (0.029s)
|
||||
# build & install p1 again (0.022s)
|
||||
> go install -i p11
|
||||
[stderr]
|
||||
can't load package: package p11: cannot find package "p11" in any of:
|
||||
/Users/rsc/go/src/p11 (from $GOROOT)
|
||||
$WORK/d1/src/p11 (from $GOPATH)
|
||||
$WORK/d2/src/p11
|
||||
[exit status 1]
|
||||
FAIL: unexpected go command failure
|
||||
|
||||
script_test.go:73: failed at testdata/script/install_rebuild_gopath.txt:15 in $WORK/gopath/src
|
||||
|
||||
FAIL
|
||||
exit status 1
|
||||
FAIL cmd/go 4.875s
|
||||
$
|
||||
|
||||
Note that the commands in earlier phases have been hidden, so that the relevant
|
||||
commands are more easily found, and the elapsed time for a completed phase
|
||||
is shown next to the phase heading. To see the entire execution, use "go test -v",
|
||||
which also adds an initial environment dump to the beginning of the log.
|
||||
|
||||
Note also that in reported output, the actual name of the per-script temporary directory
|
||||
has been consistently replaced with the literal string $WORK.
|
||||
|
||||
If Params.TestWork is true, it causes each test to log the name of its $WORK directory and other
|
||||
environment variable settings and also to leave that directory behind when it exits,
|
||||
for manual debugging of failing tests:
|
||||
|
||||
$ go test -run=Script -testwork
|
||||
--- FAIL: TestScript (3.75s)
|
||||
--- FAIL: TestScript/install_rebuild_gopath (0.16s)
|
||||
script_test.go:223:
|
||||
WORK=/tmp/cmd-go-test-745953508/script-install_rebuild_gopath
|
||||
GOARCH=
|
||||
GOCACHE=/Users/rsc/Library/Caches/go-build
|
||||
GOOS=
|
||||
GOPATH=$WORK/gopath
|
||||
GOROOT=/Users/rsc/go
|
||||
HOME=/no-home
|
||||
TMPDIR=$WORK/tmp
|
||||
exe=
|
||||
|
||||
# GOPATH with p1 in d2, p2 in d2 (0.000s)
|
||||
# build & install p1 (0.085s)
|
||||
# modify p2 - p1 should appear stale (0.030s)
|
||||
# build & install p1 again (0.019s)
|
||||
> go install -i p11
|
||||
[stderr]
|
||||
can't load package: package p11: cannot find package "p11" in any of:
|
||||
/Users/rsc/go/src/p11 (from $GOROOT)
|
||||
$WORK/d1/src/p11 (from $GOPATH)
|
||||
$WORK/d2/src/p11
|
||||
[exit status 1]
|
||||
FAIL: unexpected go command failure
|
||||
|
||||
script_test.go:73: failed at testdata/script/install_rebuild_gopath.txt:15 in $WORK/gopath/src
|
||||
|
||||
FAIL
|
||||
exit status 1
|
||||
FAIL cmd/go 4.875s
|
||||
$
|
||||
|
||||
$ WORK=/tmp/cmd-go-test-745953508/script-install_rebuild_gopath
|
||||
$ cd $WORK/d1/src/p1
|
||||
$ cat p1.go
|
||||
package p1
|
||||
import "p2"
|
||||
func F() { p2.F() }
|
||||
$
|
||||
*/
|
||||
package testscript
|
||||
8
vendor/github.com/rogpeppe/go-internal/testscript/envvarname.go
generated
vendored
Normal file
8
vendor/github.com/rogpeppe/go-internal/testscript/envvarname.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package testscript
|
||||
|
||||
func envvarname(k string) string {
|
||||
return k
|
||||
}
|
||||
7
vendor/github.com/rogpeppe/go-internal/testscript/envvarname_windows.go
generated
vendored
Normal file
7
vendor/github.com/rogpeppe/go-internal/testscript/envvarname_windows.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
package testscript
|
||||
|
||||
import "strings"
|
||||
|
||||
func envvarname(k string) string {
|
||||
return strings.ToLower(k)
|
||||
}
|
||||
304
vendor/github.com/rogpeppe/go-internal/testscript/exe.go
generated
vendored
Normal file
304
vendor/github.com/rogpeppe/go-internal/testscript/exe.go
generated
vendored
Normal file
@@ -0,0 +1,304 @@
|
||||
// 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 testscript
|
||||
|
||||
import (
|
||||
cryptorand "crypto/rand"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TestingM is implemented by *testing.M. It's defined as an interface
|
||||
// to allow testscript to co-exist with other testing frameworks
|
||||
// that might also wish to call M.Run.
|
||||
type TestingM interface {
|
||||
Run() int
|
||||
}
|
||||
|
||||
var ignoreMissedCoverage = false
|
||||
|
||||
// IgnoreMissedCoverage causes any missed coverage information
|
||||
// (for example when a function passed to RunMain
|
||||
// calls os.Exit, for example) to be ignored.
|
||||
// This function should be called before calling RunMain.
|
||||
func IgnoreMissedCoverage() {
|
||||
ignoreMissedCoverage = true
|
||||
}
|
||||
|
||||
// RunMain should be called within a TestMain function to allow
|
||||
// subcommands to be run in the testscript context.
|
||||
//
|
||||
// The commands map holds the set of command names, each
|
||||
// with an associated run function which should return the
|
||||
// code to pass to os.Exit. It's OK for a command function to
|
||||
// exit itself, but this may result in loss of coverage information.
|
||||
//
|
||||
// When Run is called, these commands are installed as regular commands in the shell
|
||||
// path, so can be invoked with "exec" or via any other command (for example a shell script).
|
||||
//
|
||||
// For backwards compatibility, the commands declared in the map can be run
|
||||
// without "exec" - that is, "foo" will behave like "exec foo".
|
||||
// This can be disabled with Params.RequireExplicitExec to keep consistency
|
||||
// across test scripts, and to keep separate process executions explicit.
|
||||
//
|
||||
// This function returns an exit code to pass to os.Exit, after calling m.Run.
|
||||
func RunMain(m TestingM, commands map[string]func() int) (exitCode int) {
|
||||
// Depending on os.Args[0], this is either the top-level execution of
|
||||
// the test binary by "go test", or the execution of one of the provided
|
||||
// commands via "foo" or "exec foo".
|
||||
|
||||
cmdName := filepath.Base(os.Args[0])
|
||||
if runtime.GOOS == "windows" {
|
||||
cmdName = strings.TrimSuffix(cmdName, ".exe")
|
||||
}
|
||||
mainf := commands[cmdName]
|
||||
if mainf == nil {
|
||||
// Unknown command; this is just the top-level execution of the
|
||||
// test binary by "go test".
|
||||
|
||||
// Set up all commands in a directory, added in $PATH.
|
||||
tmpdir, err := ioutil.TempDir("", "testscript-main")
|
||||
if err != nil {
|
||||
log.Printf("could not set up temporary directory: %v", err)
|
||||
return 2
|
||||
}
|
||||
defer func() {
|
||||
if err := os.RemoveAll(tmpdir); err != nil {
|
||||
log.Printf("cannot delete temporary directory: %v", err)
|
||||
exitCode = 2
|
||||
}
|
||||
}()
|
||||
bindir := filepath.Join(tmpdir, "bin")
|
||||
if err := os.MkdirAll(bindir, 0o777); err != nil {
|
||||
log.Printf("could not set up PATH binary directory: %v", err)
|
||||
return 2
|
||||
}
|
||||
os.Setenv("PATH", bindir+string(filepath.ListSeparator)+os.Getenv("PATH"))
|
||||
|
||||
flag.Parse()
|
||||
// If we are collecting a coverage profile, set up a shared
|
||||
// directory for all executed test binary sub-processes to write
|
||||
// their profiles to. Before finishing, we'll merge all of those
|
||||
// profiles into the main profile.
|
||||
if coverProfile() != "" {
|
||||
coverdir := filepath.Join(tmpdir, "cover")
|
||||
if err := os.MkdirAll(coverdir, 0o777); err != nil {
|
||||
log.Printf("could not set up cover directory: %v", err)
|
||||
return 2
|
||||
}
|
||||
os.Setenv("TESTSCRIPT_COVER_DIR", coverdir)
|
||||
defer func() {
|
||||
if err := finalizeCoverProfile(coverdir); err != nil {
|
||||
log.Printf("cannot merge cover profiles: %v", err)
|
||||
exitCode = 2
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// We're not in a subcommand.
|
||||
for name := range commands {
|
||||
name := name
|
||||
// Set up this command in the directory we added to $PATH.
|
||||
binfile := filepath.Join(bindir, name)
|
||||
if runtime.GOOS == "windows" {
|
||||
binfile += ".exe"
|
||||
}
|
||||
binpath, err := os.Executable()
|
||||
if err == nil {
|
||||
err = copyBinary(binpath, binfile)
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("could not set up %s in $PATH: %v", name, err)
|
||||
return 2
|
||||
}
|
||||
scriptCmds[name] = func(ts *TestScript, neg bool, args []string) {
|
||||
if ts.params.RequireExplicitExec {
|
||||
ts.Fatalf("use 'exec %s' rather than '%s' (because RequireExplicitExec is enabled)", name, name)
|
||||
}
|
||||
ts.cmdExec(neg, append([]string{name}, args...))
|
||||
}
|
||||
}
|
||||
return m.Run()
|
||||
}
|
||||
// The command being registered is being invoked, so run it, then exit.
|
||||
os.Args[0] = cmdName
|
||||
coverdir := os.Getenv("TESTSCRIPT_COVER_DIR")
|
||||
if coverdir == "" {
|
||||
// No coverage, act as normal.
|
||||
return mainf()
|
||||
}
|
||||
|
||||
// For a command "foo", write ${TESTSCRIPT_COVER_DIR}/foo-${RANDOM}.
|
||||
// Note that we do not use ioutil.TempFile as that creates the file.
|
||||
// In this case, we want to leave it to -test.coverprofile to create the
|
||||
// file, as otherwise we could end up with an empty file.
|
||||
// Later, when merging profiles, trying to merge an empty file would
|
||||
// result in a confusing error.
|
||||
rnd, err := nextRandom()
|
||||
if err != nil {
|
||||
log.Printf("could not obtain random number: %v", err)
|
||||
return 2
|
||||
}
|
||||
cprof := filepath.Join(coverdir, fmt.Sprintf("%s-%x", cmdName, rnd))
|
||||
return runCoverSubcommand(cprof, mainf)
|
||||
}
|
||||
|
||||
func nextRandom() ([]byte, error) {
|
||||
p := make([]byte, 6)
|
||||
_, err := cryptorand.Read(p)
|
||||
return p, err
|
||||
}
|
||||
|
||||
// copyBinary makes a copy of a binary to a new location. It is used as part of
|
||||
// setting up top-level commands in $PATH.
|
||||
//
|
||||
// It does not attempt to use symlinks for two reasons:
|
||||
//
|
||||
// First, some tools like cmd/go's -toolexec will be clever enough to realise
|
||||
// when they're given a symlink, and they will use the symlink target for
|
||||
// executing the program. This breaks testscript, as we depend on os.Args[0] to
|
||||
// know what command to run.
|
||||
//
|
||||
// Second, symlinks might not be available on some environments, so we have to
|
||||
// implement a "full copy" fallback anyway.
|
||||
//
|
||||
// However, we do try to use a hard link, since that will probably work on most
|
||||
// unix-like setups. Note that "go test" also places test binaries in the
|
||||
// system's temporary directory, like we do. We don't use hard links on Windows,
|
||||
// as that can lead to "access denied" errors when removing.
|
||||
func copyBinary(from, to string) error {
|
||||
if runtime.GOOS != "windows" {
|
||||
if err := os.Link(from, to); err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
writer, err := os.OpenFile(to, os.O_WRONLY|os.O_CREATE, 0o777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer writer.Close()
|
||||
|
||||
reader, err := os.Open(from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
_, err = io.Copy(writer, reader)
|
||||
return err
|
||||
}
|
||||
|
||||
// runCoverSubcommand runs the given function, then writes any generated
|
||||
// coverage information to the cprof file.
|
||||
// This is called inside a separately run executable.
|
||||
func runCoverSubcommand(cprof string, mainf func() int) (exitCode int) {
|
||||
// Change the error handling mode to PanicOnError
|
||||
// so that in the common case of calling flag.Parse in main we'll
|
||||
// be able to catch the panic instead of just exiting.
|
||||
flag.CommandLine.Init(flag.CommandLine.Name(), flag.PanicOnError)
|
||||
defer func() {
|
||||
panicErr := recover()
|
||||
if err, ok := panicErr.(error); ok {
|
||||
// The flag package will already have printed this error, assuming,
|
||||
// that is, that the error was created in the flag package.
|
||||
// TODO check the stack to be sure it was actually raised by the flag package.
|
||||
exitCode = 2
|
||||
if err == flag.ErrHelp {
|
||||
exitCode = 0
|
||||
}
|
||||
panicErr = nil
|
||||
}
|
||||
// Set os.Args so that flag.Parse will tell testing the correct
|
||||
// coverprofile setting. Unfortunately this isn't sufficient because
|
||||
// the testing oackage explicitly avoids calling flag.Parse again
|
||||
// if flag.Parsed returns true, so we the coverprofile value directly
|
||||
// too.
|
||||
os.Args = []string{os.Args[0], "-test.coverprofile=" + cprof}
|
||||
setCoverProfile(cprof)
|
||||
|
||||
// Suppress the chatty coverage and test report.
|
||||
devNull, err := os.Open(os.DevNull)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
os.Stdout = devNull
|
||||
os.Stderr = devNull
|
||||
|
||||
// Run MainStart (recursively, but it we should be ok) with no tests
|
||||
// so that it writes the coverage profile.
|
||||
m := mainStart()
|
||||
if code := m.Run(); code != 0 && exitCode == 0 {
|
||||
exitCode = code
|
||||
}
|
||||
if _, err := os.Stat(cprof); err != nil {
|
||||
log.Printf("failed to write coverage profile %q", cprof)
|
||||
}
|
||||
if panicErr != nil {
|
||||
// The error didn't originate from the flag package (we know that
|
||||
// flag.PanicOnError causes an error value that implements error),
|
||||
// so carry on panicking.
|
||||
panic(panicErr)
|
||||
}
|
||||
}()
|
||||
return mainf()
|
||||
}
|
||||
|
||||
func coverProfileFlag() flag.Getter {
|
||||
f := flag.CommandLine.Lookup("test.coverprofile")
|
||||
if f == nil {
|
||||
// We've imported testing so it definitely should be there.
|
||||
panic("cannot find test.coverprofile flag")
|
||||
}
|
||||
return f.Value.(flag.Getter)
|
||||
}
|
||||
|
||||
func coverProfile() string {
|
||||
return coverProfileFlag().Get().(string)
|
||||
}
|
||||
|
||||
func setCoverProfile(cprof string) {
|
||||
coverProfileFlag().Set(cprof)
|
||||
}
|
||||
|
||||
type nopTestDeps struct{}
|
||||
|
||||
func (nopTestDeps) MatchString(pat, str string) (result bool, err error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (nopTestDeps) StartCPUProfile(w io.Writer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nopTestDeps) StopCPUProfile() {}
|
||||
|
||||
func (nopTestDeps) WriteProfileTo(name string, w io.Writer, debug int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nopTestDeps) ImportPath() string {
|
||||
return ""
|
||||
}
|
||||
func (nopTestDeps) StartTestLog(w io.Writer) {}
|
||||
|
||||
func (nopTestDeps) StopTestLog() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Note: WriteHeapProfile is needed for Go 1.10 but not Go 1.11.
|
||||
func (nopTestDeps) WriteHeapProfile(io.Writer) error {
|
||||
// Not needed for Go 1.10.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Note: SetPanicOnExit0 was added in Go 1.16.
|
||||
func (nopTestDeps) SetPanicOnExit0(bool) {}
|
||||
51
vendor/github.com/rogpeppe/go-internal/testscript/exe_go118.go
generated
vendored
Normal file
51
vendor/github.com/rogpeppe/go-internal/testscript/exe_go118.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
|
||||
package testscript
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func mainStart() *testing.M {
|
||||
// Note: testing.MainStart acquired an extra argument in Go 1.18.
|
||||
return testing.MainStart(nopTestDeps{}, nil, nil, nil, nil)
|
||||
}
|
||||
|
||||
// Note: corpusEntry is an anonymous struct type used by some method stubs.
|
||||
type corpusEntry = struct {
|
||||
Parent string
|
||||
Path string
|
||||
Data []byte
|
||||
Values []interface{}
|
||||
Generation int
|
||||
IsSeed bool
|
||||
}
|
||||
|
||||
// Note: CoordinateFuzzing was added in Go 1.18.
|
||||
func (nopTestDeps) CoordinateFuzzing(time.Duration, int64, time.Duration, int64, int, []corpusEntry, []reflect.Type, string, string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Note: RunFuzzWorker was added in Go 1.18.
|
||||
func (nopTestDeps) RunFuzzWorker(func(corpusEntry) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Note: ReadCorpus was added in Go 1.18.
|
||||
func (nopTestDeps) ReadCorpus(string, []reflect.Type) ([]corpusEntry, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Note: CheckCorpus was added in Go 1.18.
|
||||
func (nopTestDeps) CheckCorpus([]interface{}, []reflect.Type) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Note: ResetCoverage was added in Go 1.18.
|
||||
func (nopTestDeps) ResetCoverage() {}
|
||||
|
||||
// Note: SnapshotCoverage was added in Go 1.18.
|
||||
func (nopTestDeps) SnapshotCoverage() {}
|
||||
12
vendor/github.com/rogpeppe/go-internal/testscript/exe_pre_go1.18.go
generated
vendored
Normal file
12
vendor/github.com/rogpeppe/go-internal/testscript/exe_pre_go1.18.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
//go:build !go1.18
|
||||
// +build !go1.18
|
||||
|
||||
package testscript
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func mainStart() *testing.M {
|
||||
return testing.MainStart(nopTestDeps{}, nil, nil, nil)
|
||||
}
|
||||
928
vendor/github.com/rogpeppe/go-internal/testscript/testscript.go
generated
vendored
Normal file
928
vendor/github.com/rogpeppe/go-internal/testscript/testscript.go
generated
vendored
Normal file
@@ -0,0 +1,928 @@
|
||||
// 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.
|
||||
|
||||
// Script-driven tests.
|
||||
// See testdata/script/README for an overview.
|
||||
|
||||
package testscript
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rogpeppe/go-internal/imports"
|
||||
"github.com/rogpeppe/go-internal/internal/os/execpath"
|
||||
"github.com/rogpeppe/go-internal/par"
|
||||
"github.com/rogpeppe/go-internal/testenv"
|
||||
"github.com/rogpeppe/go-internal/txtar"
|
||||
)
|
||||
|
||||
var goVersionRegex = regexp.MustCompile(`^go([1-9][0-9]*)\.([1-9][0-9]*)$`)
|
||||
|
||||
var execCache par.Cache
|
||||
|
||||
// If -testwork is specified, the test prints the name of the temp directory
|
||||
// and does not remove it when done, so that a programmer can
|
||||
// poke at the test file tree afterward.
|
||||
var testWork = flag.Bool("testwork", false, "")
|
||||
|
||||
// Env holds the environment to use at the start of a test script invocation.
|
||||
type Env struct {
|
||||
// WorkDir holds the path to the root directory of the
|
||||
// extracted files.
|
||||
WorkDir string
|
||||
// Vars holds the initial set environment variables that will be passed to the
|
||||
// testscript commands.
|
||||
Vars []string
|
||||
// Cd holds the initial current working directory.
|
||||
Cd string
|
||||
// Values holds a map of arbitrary values for use by custom
|
||||
// testscript commands. This enables Setup to pass arbitrary
|
||||
// values (not just strings) through to custom commands.
|
||||
Values map[interface{}]interface{}
|
||||
|
||||
ts *TestScript
|
||||
}
|
||||
|
||||
// Value returns a value from Env.Values, or nil if no
|
||||
// value was set by Setup.
|
||||
func (ts *TestScript) Value(key interface{}) interface{} {
|
||||
return ts.values[key]
|
||||
}
|
||||
|
||||
// Defer arranges for f to be called at the end
|
||||
// of the test. If Defer is called multiple times, the
|
||||
// defers are executed in reverse order (similar
|
||||
// to Go's defer statement)
|
||||
func (e *Env) Defer(f func()) {
|
||||
e.ts.Defer(f)
|
||||
}
|
||||
|
||||
// Getenv retrieves the value of the environment variable named by the key. It
|
||||
// returns the value, which will be empty if the variable is not present.
|
||||
func (e *Env) Getenv(key string) string {
|
||||
key = envvarname(key)
|
||||
for i := len(e.Vars) - 1; i >= 0; i-- {
|
||||
if pair := strings.SplitN(e.Vars[i], "=", 2); len(pair) == 2 && envvarname(pair[0]) == key {
|
||||
return pair[1]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Setenv sets the value of the environment variable named by the key. It
|
||||
// panics if key is invalid.
|
||||
func (e *Env) Setenv(key, value string) {
|
||||
if key == "" || strings.IndexByte(key, '=') != -1 {
|
||||
panic(fmt.Errorf("invalid environment variable key %q", key))
|
||||
}
|
||||
e.Vars = append(e.Vars, key+"="+value)
|
||||
}
|
||||
|
||||
// T returns the t argument passed to the current test by the T.Run method.
|
||||
// Note that if the tests were started by calling Run,
|
||||
// the returned value will implement testing.TB.
|
||||
// Note that, despite that, the underlying value will not be of type
|
||||
// *testing.T because *testing.T does not implement T.
|
||||
//
|
||||
// If Cleanup is called on the returned value, the function will run
|
||||
// after any functions passed to Env.Defer.
|
||||
func (e *Env) T() T {
|
||||
return e.ts.t
|
||||
}
|
||||
|
||||
// Params holds parameters for a call to Run.
|
||||
type Params struct {
|
||||
// Dir holds the name of the directory holding the scripts.
|
||||
// All files in the directory with a .txtar or .txt suffix will be
|
||||
// considered as test scripts. By default the current directory is used.
|
||||
// Dir is interpreted relative to the current test directory.
|
||||
Dir string
|
||||
|
||||
// Setup is called, if not nil, to complete any setup required
|
||||
// for a test. The WorkDir and Vars fields will have already
|
||||
// been initialized and all the files extracted into WorkDir,
|
||||
// and Cd will be the same as WorkDir.
|
||||
// The Setup function may modify Vars and Cd as it wishes.
|
||||
Setup func(*Env) error
|
||||
|
||||
// Condition is called, if not nil, to determine whether a particular
|
||||
// condition is true. It's called only for conditions not in the
|
||||
// standard set, and may be nil.
|
||||
Condition func(cond string) (bool, error)
|
||||
|
||||
// Cmds holds a map of commands available to the script.
|
||||
// It will only be consulted for commands not part of the standard set.
|
||||
Cmds map[string]func(ts *TestScript, neg bool, args []string)
|
||||
|
||||
// TestWork specifies that working directories should be
|
||||
// left intact for later inspection.
|
||||
TestWork bool
|
||||
|
||||
// WorkdirRoot specifies the directory within which scripts' work
|
||||
// directories will be created. Setting WorkdirRoot implies TestWork=true.
|
||||
// If empty, the work directories will be created inside
|
||||
// $GOTMPDIR/go-test-script*, where $GOTMPDIR defaults to os.TempDir().
|
||||
WorkdirRoot string
|
||||
|
||||
// IgnoreMissedCoverage specifies that if coverage information
|
||||
// is being generated (with the -test.coverprofile flag) and a subcommand
|
||||
// function passed to RunMain fails to generate coverage information
|
||||
// (for example because the function invoked os.Exit), then the
|
||||
// error will be ignored.
|
||||
IgnoreMissedCoverage bool
|
||||
|
||||
// UpdateScripts specifies that if a `cmp` command fails and its second
|
||||
// argument refers to a file inside the testscript file, the command will
|
||||
// succeed and the testscript file will be updated to reflect the actual
|
||||
// content (which could be stdout, stderr or a real file).
|
||||
//
|
||||
// The content will be quoted with txtar.Quote if needed;
|
||||
// a manual change will be needed if it is not unquoted in the
|
||||
// script.
|
||||
UpdateScripts bool
|
||||
|
||||
// RequireExplicitExec requires that commands passed to RunMain must be used
|
||||
// in test scripts via `exec cmd` and not simply `cmd`. This can help keep
|
||||
// consistency across test scripts as well as keep separate process
|
||||
// executions explicit.
|
||||
RequireExplicitExec bool
|
||||
}
|
||||
|
||||
// RunDir runs the tests in the given directory. All files in dir with a ".txt"
|
||||
// or ".txtar" extension are considered to be test files.
|
||||
func Run(t *testing.T, p Params) {
|
||||
RunT(tshim{t}, p)
|
||||
}
|
||||
|
||||
// T holds all the methods of the *testing.T type that
|
||||
// are used by testscript.
|
||||
type T interface {
|
||||
Skip(...interface{})
|
||||
Fatal(...interface{})
|
||||
Parallel()
|
||||
Log(...interface{})
|
||||
FailNow()
|
||||
Run(string, func(T))
|
||||
// Verbose is usually implemented by the testing package
|
||||
// directly rather than on the *testing.T type.
|
||||
Verbose() bool
|
||||
}
|
||||
|
||||
// TFailed holds optional extra methods implemented on T.
|
||||
// It's defined as a separate type for backward compatibility reasons.
|
||||
type TFailed interface {
|
||||
Failed() bool
|
||||
}
|
||||
|
||||
type tshim struct {
|
||||
*testing.T
|
||||
}
|
||||
|
||||
func (t tshim) Run(name string, f func(T)) {
|
||||
t.T.Run(name, func(t *testing.T) {
|
||||
f(tshim{t})
|
||||
})
|
||||
}
|
||||
|
||||
func (t tshim) Verbose() bool {
|
||||
return testing.Verbose()
|
||||
}
|
||||
|
||||
// RunT is like Run but uses an interface type instead of the concrete *testing.T
|
||||
// type to make it possible to use testscript functionality outside of go test.
|
||||
func RunT(t T, p Params) {
|
||||
entries, err := os.ReadDir(p.Dir)
|
||||
if os.IsNotExist(err) {
|
||||
// Continue so we give a helpful error on len(files)==0 below.
|
||||
} else if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var files []string
|
||||
for _, entry := range entries {
|
||||
name := entry.Name()
|
||||
if strings.HasSuffix(name, ".txtar") || strings.HasSuffix(name, ".txt") {
|
||||
files = append(files, filepath.Join(p.Dir, name))
|
||||
}
|
||||
}
|
||||
|
||||
if len(files) == 0 {
|
||||
t.Fatal(fmt.Sprintf("no txtar nor txt scripts found in dir %s", p.Dir))
|
||||
}
|
||||
testTempDir := p.WorkdirRoot
|
||||
if testTempDir == "" {
|
||||
testTempDir, err = ioutil.TempDir(os.Getenv("GOTMPDIR"), "go-test-script")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
p.TestWork = true
|
||||
}
|
||||
// The temp dir returned by ioutil.TempDir might be a sym linked dir (default
|
||||
// behaviour in macOS). That could mess up matching that includes $WORK if,
|
||||
// for example, an external program outputs resolved paths. Evaluating the
|
||||
// dir here will ensure consistency.
|
||||
testTempDir, err = filepath.EvalSymlinks(testTempDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
refCount := int32(len(files))
|
||||
for _, file := range files {
|
||||
file := file
|
||||
name := strings.TrimSuffix(filepath.Base(file), ".txt")
|
||||
name = strings.TrimSuffix(name, ".txtar")
|
||||
t.Run(name, func(t T) {
|
||||
t.Parallel()
|
||||
ts := &TestScript{
|
||||
t: t,
|
||||
testTempDir: testTempDir,
|
||||
name: name,
|
||||
file: file,
|
||||
params: p,
|
||||
ctxt: context.Background(),
|
||||
deferred: func() {},
|
||||
scriptFiles: make(map[string]string),
|
||||
scriptUpdates: make(map[string]string),
|
||||
}
|
||||
defer func() {
|
||||
if p.TestWork || *testWork {
|
||||
return
|
||||
}
|
||||
removeAll(ts.workdir)
|
||||
if atomic.AddInt32(&refCount, -1) == 0 {
|
||||
// This is the last subtest to finish. Remove the
|
||||
// parent directory too.
|
||||
os.Remove(testTempDir)
|
||||
}
|
||||
}()
|
||||
ts.run()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// A TestScript holds execution state for a single test script.
|
||||
type TestScript struct {
|
||||
params Params
|
||||
t T
|
||||
testTempDir string
|
||||
workdir string // temporary work dir ($WORK)
|
||||
log bytes.Buffer // test execution log (printed at end of test)
|
||||
mark int // offset of next log truncation
|
||||
cd string // current directory during test execution; initially $WORK/gopath/src
|
||||
name string // short name of test ("foo")
|
||||
file string // full file name ("testdata/script/foo.txt")
|
||||
lineno int // line number currently executing
|
||||
line string // line currently executing
|
||||
env []string // environment list (for os/exec)
|
||||
envMap map[string]string // environment mapping (matches env; on Windows keys are lowercase)
|
||||
values map[interface{}]interface{} // values for custom commands
|
||||
stdin string // standard input to next 'go' command; set by 'stdin' command.
|
||||
stdout string // standard output from last 'go' command; for 'stdout' command
|
||||
stderr string // standard error from last 'go' command; for 'stderr' command
|
||||
stopped bool // test wants to stop early
|
||||
start time.Time // time phase started
|
||||
background []backgroundCmd // backgrounded 'exec' and 'go' commands
|
||||
deferred func() // deferred cleanup actions.
|
||||
archive *txtar.Archive // the testscript being run.
|
||||
scriptFiles map[string]string // files stored in the txtar archive (absolute paths -> path in script)
|
||||
scriptUpdates map[string]string // updates to testscript files via UpdateScripts.
|
||||
|
||||
ctxt context.Context // per TestScript context
|
||||
}
|
||||
|
||||
type backgroundCmd struct {
|
||||
name string
|
||||
cmd *exec.Cmd
|
||||
wait <-chan struct{}
|
||||
neg bool // if true, cmd should fail
|
||||
}
|
||||
|
||||
// setup sets up the test execution temporary directory and environment.
|
||||
// It returns the comment section of the txtar archive.
|
||||
func (ts *TestScript) setup() string {
|
||||
ts.workdir = filepath.Join(ts.testTempDir, "script-"+ts.name)
|
||||
|
||||
// Establish a temporary directory in workdir, but use a prefix that ensures
|
||||
// this directory will not be walked when resolving the ./... pattern from
|
||||
// workdir. This is important because when resolving a ./... pattern, cmd/go
|
||||
// (which is used by go/packages) creates temporary build files and
|
||||
// directories. This can, and does, therefore interfere with the ./...
|
||||
// pattern when used from workdir and can lead to race conditions within
|
||||
// cmd/go as it walks directories to match the ./... pattern.
|
||||
tmpDir := filepath.Join(ts.workdir, ".tmp")
|
||||
|
||||
ts.Check(os.MkdirAll(tmpDir, 0o777))
|
||||
env := &Env{
|
||||
Vars: []string{
|
||||
"WORK=" + ts.workdir, // must be first for ts.abbrev
|
||||
"PATH=" + os.Getenv("PATH"),
|
||||
homeEnvName() + "=/no-home",
|
||||
tempEnvName() + "=" + tmpDir,
|
||||
"devnull=" + os.DevNull,
|
||||
"/=" + string(os.PathSeparator),
|
||||
":=" + string(os.PathListSeparator),
|
||||
"$=$",
|
||||
|
||||
// If we are collecting coverage profiles for merging into the main one,
|
||||
// ensure the environment variable is forwarded to sub-processes.
|
||||
"TESTSCRIPT_COVER_DIR=" + os.Getenv("TESTSCRIPT_COVER_DIR"),
|
||||
},
|
||||
WorkDir: ts.workdir,
|
||||
Values: make(map[interface{}]interface{}),
|
||||
Cd: ts.workdir,
|
||||
ts: ts,
|
||||
}
|
||||
// Must preserve SYSTEMROOT on Windows: https://github.com/golang/go/issues/25513 et al
|
||||
if runtime.GOOS == "windows" {
|
||||
env.Vars = append(env.Vars,
|
||||
"SYSTEMROOT="+os.Getenv("SYSTEMROOT"),
|
||||
"exe=.exe",
|
||||
)
|
||||
} else {
|
||||
env.Vars = append(env.Vars,
|
||||
"exe=",
|
||||
)
|
||||
}
|
||||
ts.cd = env.Cd
|
||||
// Unpack archive.
|
||||
a, err := txtar.ParseFile(ts.file)
|
||||
ts.Check(err)
|
||||
ts.archive = a
|
||||
for _, f := range a.Files {
|
||||
name := ts.MkAbs(ts.expand(f.Name))
|
||||
ts.scriptFiles[name] = f.Name
|
||||
ts.Check(os.MkdirAll(filepath.Dir(name), 0o777))
|
||||
ts.Check(ioutil.WriteFile(name, f.Data, 0o666))
|
||||
}
|
||||
// Run any user-defined setup.
|
||||
if ts.params.Setup != nil {
|
||||
ts.Check(ts.params.Setup(env))
|
||||
}
|
||||
ts.cd = env.Cd
|
||||
ts.env = env.Vars
|
||||
ts.values = env.Values
|
||||
|
||||
ts.envMap = make(map[string]string)
|
||||
for _, kv := range ts.env {
|
||||
if i := strings.Index(kv, "="); i >= 0 {
|
||||
ts.envMap[envvarname(kv[:i])] = kv[i+1:]
|
||||
}
|
||||
}
|
||||
return string(a.Comment)
|
||||
}
|
||||
|
||||
// run runs the test script.
|
||||
func (ts *TestScript) run() {
|
||||
// Truncate log at end of last phase marker,
|
||||
// discarding details of successful phase.
|
||||
rewind := func() {
|
||||
if !ts.t.Verbose() {
|
||||
ts.log.Truncate(ts.mark)
|
||||
}
|
||||
}
|
||||
|
||||
// Insert elapsed time for phase at end of phase marker
|
||||
markTime := func() {
|
||||
if ts.mark > 0 && !ts.start.IsZero() {
|
||||
afterMark := append([]byte{}, ts.log.Bytes()[ts.mark:]...)
|
||||
ts.log.Truncate(ts.mark - 1) // cut \n and afterMark
|
||||
fmt.Fprintf(&ts.log, " (%.3fs)\n", time.Since(ts.start).Seconds())
|
||||
ts.log.Write(afterMark)
|
||||
}
|
||||
ts.start = time.Time{}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// On a normal exit from the test loop, background processes are cleaned up
|
||||
// before we print PASS. If we return early (e.g., due to a test failure),
|
||||
// don't print anything about the processes that were still running.
|
||||
for _, bg := range ts.background {
|
||||
interruptProcess(bg.cmd.Process)
|
||||
}
|
||||
if ts.t.Verbose() || hasFailed(ts.t) {
|
||||
// In verbose mode or on test failure, we want to see what happened in the background
|
||||
// processes too.
|
||||
ts.waitBackground(false)
|
||||
} else {
|
||||
for _, bg := range ts.background {
|
||||
<-bg.wait
|
||||
}
|
||||
ts.background = nil
|
||||
}
|
||||
|
||||
markTime()
|
||||
// Flush testScript log to testing.T log.
|
||||
ts.t.Log(ts.abbrev(ts.log.String()))
|
||||
}()
|
||||
defer func() {
|
||||
ts.deferred()
|
||||
}()
|
||||
script := ts.setup()
|
||||
|
||||
// With -v or -testwork, start log with full environment.
|
||||
if *testWork || ts.t.Verbose() {
|
||||
// Display environment.
|
||||
ts.cmdEnv(false, nil)
|
||||
fmt.Fprintf(&ts.log, "\n")
|
||||
ts.mark = ts.log.Len()
|
||||
}
|
||||
defer ts.applyScriptUpdates()
|
||||
|
||||
// Run script.
|
||||
// See testdata/script/README for documentation of script form.
|
||||
Script:
|
||||
for script != "" {
|
||||
// Extract next line.
|
||||
ts.lineno++
|
||||
var line string
|
||||
if i := strings.Index(script, "\n"); i >= 0 {
|
||||
line, script = script[:i], script[i+1:]
|
||||
} else {
|
||||
line, script = script, ""
|
||||
}
|
||||
|
||||
// # is a comment indicating the start of new phase.
|
||||
if strings.HasPrefix(line, "#") {
|
||||
// If there was a previous phase, it succeeded,
|
||||
// so rewind the log to delete its details (unless -v is in use).
|
||||
// If nothing has happened at all since the mark,
|
||||
// rewinding is a no-op and adding elapsed time
|
||||
// for doing nothing is meaningless, so don't.
|
||||
if ts.log.Len() > ts.mark {
|
||||
rewind()
|
||||
markTime()
|
||||
}
|
||||
// Print phase heading and mark start of phase output.
|
||||
fmt.Fprintf(&ts.log, "%s\n", line)
|
||||
ts.mark = ts.log.Len()
|
||||
ts.start = time.Now()
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse input line. Ignore blanks entirely.
|
||||
args := ts.parse(line)
|
||||
if len(args) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Echo command to log.
|
||||
fmt.Fprintf(&ts.log, "> %s\n", line)
|
||||
|
||||
// Command prefix [cond] means only run this command if cond is satisfied.
|
||||
for strings.HasPrefix(args[0], "[") && strings.HasSuffix(args[0], "]") {
|
||||
cond := args[0]
|
||||
cond = cond[1 : len(cond)-1]
|
||||
cond = strings.TrimSpace(cond)
|
||||
args = args[1:]
|
||||
if len(args) == 0 {
|
||||
ts.Fatalf("missing command after condition")
|
||||
}
|
||||
want := true
|
||||
if strings.HasPrefix(cond, "!") {
|
||||
want = false
|
||||
cond = strings.TrimSpace(cond[1:])
|
||||
}
|
||||
ok, err := ts.condition(cond)
|
||||
if err != nil {
|
||||
ts.Fatalf("bad condition %q: %v", cond, err)
|
||||
}
|
||||
if ok != want {
|
||||
// Don't run rest of line.
|
||||
continue Script
|
||||
}
|
||||
}
|
||||
|
||||
// Command prefix ! means negate the expectations about this command:
|
||||
// go command should fail, match should not be found, etc.
|
||||
neg := false
|
||||
if args[0] == "!" {
|
||||
neg = true
|
||||
args = args[1:]
|
||||
if len(args) == 0 {
|
||||
ts.Fatalf("! on line by itself")
|
||||
}
|
||||
}
|
||||
|
||||
// Run command.
|
||||
cmd := scriptCmds[args[0]]
|
||||
if cmd == nil {
|
||||
cmd = ts.params.Cmds[args[0]]
|
||||
}
|
||||
if cmd == nil {
|
||||
ts.Fatalf("unknown command %q", args[0])
|
||||
}
|
||||
cmd(ts, neg, args[1:])
|
||||
|
||||
// Command can ask script to stop early.
|
||||
if ts.stopped {
|
||||
// Break instead of returning, so that we check the status of any
|
||||
// background processes and print PASS.
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, bg := range ts.background {
|
||||
interruptProcess(bg.cmd.Process)
|
||||
}
|
||||
ts.cmdWait(false, nil)
|
||||
|
||||
// Final phase ended.
|
||||
rewind()
|
||||
markTime()
|
||||
if !ts.stopped {
|
||||
fmt.Fprintf(&ts.log, "PASS\n")
|
||||
}
|
||||
}
|
||||
|
||||
func hasFailed(t T) bool {
|
||||
if t, ok := t.(TFailed); ok {
|
||||
return t.Failed()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (ts *TestScript) applyScriptUpdates() {
|
||||
if len(ts.scriptUpdates) == 0 {
|
||||
return
|
||||
}
|
||||
for name, content := range ts.scriptUpdates {
|
||||
found := false
|
||||
for i := range ts.archive.Files {
|
||||
f := &ts.archive.Files[i]
|
||||
if f.Name != name {
|
||||
continue
|
||||
}
|
||||
data := []byte(content)
|
||||
if txtar.NeedsQuote(data) {
|
||||
data1, err := txtar.Quote(data)
|
||||
if err != nil {
|
||||
ts.t.Fatal(fmt.Sprintf("cannot update script file %q: %v", f.Name, err))
|
||||
continue
|
||||
}
|
||||
data = data1
|
||||
}
|
||||
f.Data = data
|
||||
found = true
|
||||
}
|
||||
// Sanity check.
|
||||
if !found {
|
||||
panic("script update file not found")
|
||||
}
|
||||
}
|
||||
if err := ioutil.WriteFile(ts.file, txtar.Format(ts.archive), 0o666); err != nil {
|
||||
ts.t.Fatal("cannot update script: ", err)
|
||||
}
|
||||
ts.Logf("%s updated", ts.file)
|
||||
}
|
||||
|
||||
// condition reports whether the given condition is satisfied.
|
||||
func (ts *TestScript) condition(cond string) (bool, error) {
|
||||
switch {
|
||||
case cond == "short":
|
||||
return testing.Short(), nil
|
||||
case cond == "net":
|
||||
return testenv.HasExternalNetwork(), nil
|
||||
case cond == "link":
|
||||
return testenv.HasLink(), nil
|
||||
case cond == "symlink":
|
||||
return testenv.HasSymlink(), nil
|
||||
case imports.KnownOS[cond]:
|
||||
return cond == runtime.GOOS, nil
|
||||
case cond == "unix":
|
||||
return imports.UnixOS[runtime.GOOS], nil
|
||||
case imports.KnownArch[cond]:
|
||||
return cond == runtime.GOARCH, nil
|
||||
case strings.HasPrefix(cond, "exec:"):
|
||||
prog := cond[len("exec:"):]
|
||||
ok := execCache.Do(prog, func() interface{} {
|
||||
_, err := execpath.Look(prog, ts.Getenv)
|
||||
return err == nil
|
||||
}).(bool)
|
||||
return ok, nil
|
||||
case cond == "gc" || cond == "gccgo":
|
||||
// TODO this reflects the compiler that the current
|
||||
// binary was built with but not necessarily the compiler
|
||||
// that will be used.
|
||||
return cond == runtime.Compiler, nil
|
||||
case goVersionRegex.MatchString(cond):
|
||||
for _, v := range build.Default.ReleaseTags {
|
||||
if cond == v {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
case ts.params.Condition != nil:
|
||||
return ts.params.Condition(cond)
|
||||
default:
|
||||
ts.Fatalf("unknown condition %q", cond)
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers for command implementations.
|
||||
|
||||
// abbrev abbreviates the actual work directory in the string s to the literal string "$WORK".
|
||||
func (ts *TestScript) abbrev(s string) string {
|
||||
s = strings.Replace(s, ts.workdir, "$WORK", -1)
|
||||
if *testWork || ts.params.TestWork {
|
||||
// Expose actual $WORK value in environment dump on first line of work script,
|
||||
// so that the user can find out what directory -testwork left behind.
|
||||
s = "WORK=" + ts.workdir + "\n" + strings.TrimPrefix(s, "WORK=$WORK\n")
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Defer arranges for f to be called at the end
|
||||
// of the test. If Defer is called multiple times, the
|
||||
// defers are executed in reverse order (similar
|
||||
// to Go's defer statement)
|
||||
func (ts *TestScript) Defer(f func()) {
|
||||
old := ts.deferred
|
||||
ts.deferred = func() {
|
||||
defer old()
|
||||
f()
|
||||
}
|
||||
}
|
||||
|
||||
// Check calls ts.Fatalf if err != nil.
|
||||
func (ts *TestScript) Check(err error) {
|
||||
if err != nil {
|
||||
ts.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Logf appends the given formatted message to the test log transcript.
|
||||
func (ts *TestScript) Logf(format string, args ...interface{}) {
|
||||
format = strings.TrimSuffix(format, "\n")
|
||||
fmt.Fprintf(&ts.log, format, args...)
|
||||
ts.log.WriteByte('\n')
|
||||
}
|
||||
|
||||
// exec runs the given command line (an actual subprocess, not simulated)
|
||||
// in ts.cd with environment ts.env and then returns collected standard output and standard error.
|
||||
func (ts *TestScript) exec(command string, args ...string) (stdout, stderr string, err error) {
|
||||
cmd, err := ts.buildExecCmd(command, args...)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
cmd.Dir = ts.cd
|
||||
cmd.Env = append(ts.env, "PWD="+ts.cd)
|
||||
cmd.Stdin = strings.NewReader(ts.stdin)
|
||||
var stdoutBuf, stderrBuf strings.Builder
|
||||
cmd.Stdout = &stdoutBuf
|
||||
cmd.Stderr = &stderrBuf
|
||||
if err = cmd.Start(); err == nil {
|
||||
err = ctxWait(ts.ctxt, cmd)
|
||||
}
|
||||
ts.stdin = ""
|
||||
return stdoutBuf.String(), stderrBuf.String(), err
|
||||
}
|
||||
|
||||
// execBackground starts the given command line (an actual subprocess, not simulated)
|
||||
// in ts.cd with environment ts.env.
|
||||
func (ts *TestScript) execBackground(command string, args ...string) (*exec.Cmd, error) {
|
||||
cmd, err := ts.buildExecCmd(command, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd.Dir = ts.cd
|
||||
cmd.Env = append(ts.env, "PWD="+ts.cd)
|
||||
var stdoutBuf, stderrBuf strings.Builder
|
||||
cmd.Stdin = strings.NewReader(ts.stdin)
|
||||
cmd.Stdout = &stdoutBuf
|
||||
cmd.Stderr = &stderrBuf
|
||||
ts.stdin = ""
|
||||
return cmd, cmd.Start()
|
||||
}
|
||||
|
||||
func (ts *TestScript) buildExecCmd(command string, args ...string) (*exec.Cmd, error) {
|
||||
if filepath.Base(command) == command {
|
||||
if lp, err := execpath.Look(command, ts.Getenv); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
command = lp
|
||||
}
|
||||
}
|
||||
return exec.Command(command, args...), nil
|
||||
}
|
||||
|
||||
// BackgroundCmds returns a slice containing all the commands that have
|
||||
// been started in the background since the most recent wait command, or
|
||||
// the start of the script if wait has not been called.
|
||||
func (ts *TestScript) BackgroundCmds() []*exec.Cmd {
|
||||
cmds := make([]*exec.Cmd, len(ts.background))
|
||||
for i, b := range ts.background {
|
||||
cmds[i] = b.cmd
|
||||
}
|
||||
return cmds
|
||||
}
|
||||
|
||||
// ctxWait is like cmd.Wait, but terminates cmd with os.Interrupt if ctx becomes done.
|
||||
//
|
||||
// This differs from exec.CommandContext in that it prefers os.Interrupt over os.Kill.
|
||||
// (See https://golang.org/issue/21135.)
|
||||
func ctxWait(ctx context.Context, cmd *exec.Cmd) error {
|
||||
errc := make(chan error, 1)
|
||||
go func() { errc <- cmd.Wait() }()
|
||||
|
||||
select {
|
||||
case err := <-errc:
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
interruptProcess(cmd.Process)
|
||||
return <-errc
|
||||
}
|
||||
}
|
||||
|
||||
// interruptProcess sends os.Interrupt to p if supported, or os.Kill otherwise.
|
||||
func interruptProcess(p *os.Process) {
|
||||
if err := p.Signal(os.Interrupt); err != nil {
|
||||
// Per https://golang.org/pkg/os/#Signal, “Interrupt is not implemented on
|
||||
// Windows; using it with os.Process.Signal will return an error.”
|
||||
// Fall back to Kill instead.
|
||||
p.Kill()
|
||||
}
|
||||
}
|
||||
|
||||
// Exec runs the given command and saves its stdout and stderr so
|
||||
// they can be inspected by subsequent script commands.
|
||||
func (ts *TestScript) Exec(command string, args ...string) error {
|
||||
var err error
|
||||
ts.stdout, ts.stderr, err = ts.exec(command, args...)
|
||||
if ts.stdout != "" {
|
||||
ts.Logf("[stdout]\n%s", ts.stdout)
|
||||
}
|
||||
if ts.stderr != "" {
|
||||
ts.Logf("[stderr]\n%s", ts.stderr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// expand applies environment variable expansion to the string s.
|
||||
func (ts *TestScript) expand(s string) string {
|
||||
return os.Expand(s, func(key string) string {
|
||||
if key1 := strings.TrimSuffix(key, "@R"); len(key1) != len(key) {
|
||||
return regexp.QuoteMeta(ts.Getenv(key1))
|
||||
}
|
||||
return ts.Getenv(key)
|
||||
})
|
||||
}
|
||||
|
||||
// fatalf aborts the test with the given failure message.
|
||||
func (ts *TestScript) Fatalf(format string, args ...interface{}) {
|
||||
fmt.Fprintf(&ts.log, "FAIL: %s:%d: %s\n", ts.file, ts.lineno, fmt.Sprintf(format, args...))
|
||||
ts.t.FailNow()
|
||||
}
|
||||
|
||||
// MkAbs interprets file relative to the test script's current directory
|
||||
// and returns the corresponding absolute path.
|
||||
func (ts *TestScript) MkAbs(file string) string {
|
||||
if filepath.IsAbs(file) {
|
||||
return file
|
||||
}
|
||||
return filepath.Join(ts.cd, file)
|
||||
}
|
||||
|
||||
// ReadFile returns the contents of the file with the
|
||||
// given name, intepreted relative to the test script's
|
||||
// current directory. It interprets "stdout" and "stderr" to
|
||||
// mean the standard output or standard error from
|
||||
// the most recent exec or wait command respectively.
|
||||
//
|
||||
// If the file cannot be read, the script fails.
|
||||
func (ts *TestScript) ReadFile(file string) string {
|
||||
switch file {
|
||||
case "stdout":
|
||||
return ts.stdout
|
||||
case "stderr":
|
||||
return ts.stderr
|
||||
default:
|
||||
file = ts.MkAbs(file)
|
||||
data, err := ioutil.ReadFile(file)
|
||||
ts.Check(err)
|
||||
return string(data)
|
||||
}
|
||||
}
|
||||
|
||||
// Setenv sets the value of the environment variable named by the key.
|
||||
func (ts *TestScript) Setenv(key, value string) {
|
||||
ts.env = append(ts.env, key+"="+value)
|
||||
ts.envMap[envvarname(key)] = value
|
||||
}
|
||||
|
||||
// Getenv gets the value of the environment variable named by the key.
|
||||
func (ts *TestScript) Getenv(key string) string {
|
||||
return ts.envMap[envvarname(key)]
|
||||
}
|
||||
|
||||
// parse parses a single line as a list of space-separated arguments
|
||||
// subject to environment variable expansion (but not resplitting).
|
||||
// Single quotes around text disable splitting and expansion.
|
||||
// To embed a single quote, double it: 'Don”t communicate by sharing memory.'
|
||||
func (ts *TestScript) parse(line string) []string {
|
||||
ts.line = line
|
||||
|
||||
var (
|
||||
args []string
|
||||
arg string // text of current arg so far (need to add line[start:i])
|
||||
start = -1 // if >= 0, position where current arg text chunk starts
|
||||
quoted = false // currently processing quoted text
|
||||
)
|
||||
for i := 0; ; i++ {
|
||||
if !quoted && (i >= len(line) || line[i] == ' ' || line[i] == '\t' || line[i] == '\r' || line[i] == '#') {
|
||||
// Found arg-separating space.
|
||||
if start >= 0 {
|
||||
arg += ts.expand(line[start:i])
|
||||
args = append(args, arg)
|
||||
start = -1
|
||||
arg = ""
|
||||
}
|
||||
if i >= len(line) || line[i] == '#' {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
if i >= len(line) {
|
||||
ts.Fatalf("unterminated quoted argument")
|
||||
}
|
||||
if line[i] == '\'' {
|
||||
if !quoted {
|
||||
// starting a quoted chunk
|
||||
if start >= 0 {
|
||||
arg += ts.expand(line[start:i])
|
||||
}
|
||||
start = i + 1
|
||||
quoted = true
|
||||
continue
|
||||
}
|
||||
// 'foo''bar' means foo'bar, like in rc shell and Pascal.
|
||||
if i+1 < len(line) && line[i+1] == '\'' {
|
||||
arg += line[start:i]
|
||||
start = i + 1
|
||||
i++ // skip over second ' before next iteration
|
||||
continue
|
||||
}
|
||||
// ending a quoted chunk
|
||||
arg += line[start:i]
|
||||
start = i + 1
|
||||
quoted = false
|
||||
continue
|
||||
}
|
||||
// found character worth saving; make sure we're saving
|
||||
if start < 0 {
|
||||
start = i
|
||||
}
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func removeAll(dir string) error {
|
||||
// module cache has 0o444 directories;
|
||||
// make them writable in order to remove content.
|
||||
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return nil // ignore errors walking in file system
|
||||
}
|
||||
if info.IsDir() {
|
||||
os.Chmod(path, 0o777)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return os.RemoveAll(dir)
|
||||
}
|
||||
|
||||
func homeEnvName() string {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
return "USERPROFILE"
|
||||
case "plan9":
|
||||
return "home"
|
||||
default:
|
||||
return "HOME"
|
||||
}
|
||||
}
|
||||
|
||||
func tempEnvName() string {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
return "TMP"
|
||||
case "plan9":
|
||||
return "TMPDIR" // actually plan 9 doesn't have one at all but this is fine
|
||||
default:
|
||||
return "TMPDIR"
|
||||
}
|
||||
}
|
||||
234
vendor/github.com/rogpeppe/go-internal/txtar/archive.go
generated
vendored
Normal file
234
vendor/github.com/rogpeppe/go-internal/txtar/archive.go
generated
vendored
Normal file
@@ -0,0 +1,234 @@
|
||||
// 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 surrounding by additional white space,
|
||||
// all of which is stripped.
|
||||
//
|
||||
// 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"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// 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 is non-empty.
|
||||
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
|
||||
}
|
||||
|
||||
// NeedsQuote reports whether the given data needs to
|
||||
// be quoted before it's included as a txtar file.
|
||||
func NeedsQuote(data []byte) bool {
|
||||
_, _, after := findFileMarker(data)
|
||||
return after != nil
|
||||
}
|
||||
|
||||
// Quote quotes the data so that it can be safely stored in a txtar
|
||||
// file. This copes with files that contain lines that look like txtar
|
||||
// separators.
|
||||
//
|
||||
// The original data can be recovered with Unquote. It returns an error
|
||||
// if the data cannot be quoted (for example because it has no final
|
||||
// newline or it holds unprintable characters)
|
||||
func Quote(data []byte) ([]byte, error) {
|
||||
if len(data) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if data[len(data)-1] != '\n' {
|
||||
return nil, errors.New("data has no final newline")
|
||||
}
|
||||
if !utf8.Valid(data) {
|
||||
return nil, fmt.Errorf("data contains non-UTF-8 characters")
|
||||
}
|
||||
var nd []byte
|
||||
prev := byte('\n')
|
||||
for _, b := range data {
|
||||
if prev == '\n' {
|
||||
nd = append(nd, '>')
|
||||
}
|
||||
nd = append(nd, b)
|
||||
prev = b
|
||||
}
|
||||
return nd, nil
|
||||
}
|
||||
|
||||
// Unquote unquotes data as quoted by Quote.
|
||||
func Unquote(data []byte) ([]byte, error) {
|
||||
if len(data) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if data[0] != '>' || data[len(data)-1] != '\n' {
|
||||
return nil, errors.New("data does not appear to be quoted")
|
||||
}
|
||||
data = bytes.Replace(data, []byte("\n>"), []byte("\n"), -1)
|
||||
data = bytes.TrimPrefix(data, []byte(">"))
|
||||
return data, nil
|
||||
}
|
||||
|
||||
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 data[i-1] == '\r' {
|
||||
data = data[:len(data)-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
|
||||
}
|
||||
|
||||
// Write writes each File in an Archive to the given directory, returning any
|
||||
// errors encountered. An error is also returned in the event a file would be
|
||||
// written outside of dir.
|
||||
func Write(a *Archive, dir string) error {
|
||||
for _, f := range a.Files {
|
||||
fp := filepath.Clean(filepath.FromSlash(f.Name))
|
||||
if isAbs(fp) || strings.HasPrefix(fp, ".."+string(filepath.Separator)) {
|
||||
return fmt.Errorf("%q: outside parent directory", f.Name)
|
||||
}
|
||||
fp = filepath.Join(dir, fp)
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(fp), 0o777); err != nil {
|
||||
return err
|
||||
}
|
||||
// Avoid overwriting existing files by using O_EXCL.
|
||||
out, err := os.OpenFile(fp, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = out.Write(f.Data)
|
||||
cerr := out.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cerr != nil {
|
||||
return cerr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isAbs(p string) bool {
|
||||
// Note: under Windows, filepath.IsAbs(`\foo`) returns false,
|
||||
// so we need to check for that case specifically.
|
||||
return filepath.IsAbs(p) || strings.HasPrefix(p, string(filepath.Separator))
|
||||
}
|
||||
16
vendor/modules.txt
vendored
16
vendor/modules.txt
vendored
@@ -817,6 +817,14 @@ github.com/pierrec/lz4/internal/xxh32
|
||||
# github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
|
||||
## explicit; go 1.14
|
||||
github.com/pkg/browser
|
||||
# github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e
|
||||
## explicit; go 1.15
|
||||
github.com/pkg/diff
|
||||
github.com/pkg/diff/ctxt
|
||||
github.com/pkg/diff/edit
|
||||
github.com/pkg/diff/intern
|
||||
github.com/pkg/diff/myers
|
||||
github.com/pkg/diff/write
|
||||
# github.com/pkg/errors v0.9.1
|
||||
## explicit
|
||||
github.com/pkg/errors
|
||||
@@ -849,6 +857,14 @@ github.com/prometheus/common/model
|
||||
github.com/prometheus/procfs
|
||||
github.com/prometheus/procfs/internal/fs
|
||||
github.com/prometheus/procfs/internal/util
|
||||
# github.com/rogpeppe/go-internal v1.9.0
|
||||
## explicit; go 1.17
|
||||
github.com/rogpeppe/go-internal/imports
|
||||
github.com/rogpeppe/go-internal/internal/os/execpath
|
||||
github.com/rogpeppe/go-internal/par
|
||||
github.com/rogpeppe/go-internal/testenv
|
||||
github.com/rogpeppe/go-internal/testscript
|
||||
github.com/rogpeppe/go-internal/txtar
|
||||
# github.com/satori/go.uuid v1.2.0
|
||||
## explicit
|
||||
# github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371
|
||||
|
||||
Reference in New Issue
Block a user