1
0
Fork 0
mirror of https://github.com/vbatts/go-mtree.git synced 2025-10-03 20:21:01 +00:00
This commit is contained in:
Aleksa Sarai 2025-09-28 13:22:28 +00:00 committed by GitHub
commit 07221a4c6f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
66 changed files with 23354 additions and 1472 deletions

View file

@ -21,12 +21,21 @@ jobs:
with: with:
go-version: ${{ matrix.go }} go-version: ${{ matrix.go }}
- name: Go Modules
run: |
go mod tidy
git diff --exit-code
go mod vendor
git add -N vendor/
git diff --exit-code
- name: Build - name: Build
run: make build run: make build
- name: Validation - name: Validation
run: make validation run: make validation
- name: Build.Arches - name: Build.Arches
run: make build.arches run: make build.arches

View file

@ -6,23 +6,22 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
// simple walk of current directory, and immediately check it. // simple walk of current directory, and immediately check it.
// may not be parallelizable. // may not be parallelizable.
func TestCheck(t *testing.T) { func TestCheck(t *testing.T) {
dh, err := Walk(".", nil, append(DefaultKeywords, []Keyword{"sha1", "xattr"}...), nil) dh, err := Walk(".", nil, append(DefaultKeywords, []Keyword{"sha1", "xattr"}...), nil)
if err != nil { require.NoError(t, err, "walk .")
t.Fatal(err)
}
res, err := Check(".", dh, nil, nil) res, err := Check(".", dh, nil, nil)
if err != nil { require.NoError(t, err, "check .")
t.Fatal(err)
}
if len(res) > 0 { if !assert.Empty(t, res, "check after no changes should have no diff") {
t.Errorf("%#v", res) pprintInodeDeltas(t, res)
} }
} }
@ -30,57 +29,49 @@ func TestCheck(t *testing.T) {
// only check again for size and sha1, and ignore time, and ensure it passes // only check again for size and sha1, and ignore time, and ensure it passes
func TestCheckKeywords(t *testing.T) { func TestCheckKeywords(t *testing.T) {
content := []byte("I know half of you half as well as I ought to") content := []byte("I know half of you half as well as I ought to")
dir, err := os.MkdirTemp("", "test-check-keywords") dir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir) // clean up
tmpfn := filepath.Join(dir, "tmpfile") tmpfn := filepath.Join(dir, "tmpfile")
if err := os.WriteFile(tmpfn, content, 0666); err != nil { require.NoError(t, os.WriteFile(tmpfn, content, 0666))
t.Fatal(err)
}
// Walk this tempdir // Walk this tempdir
dh, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil) dh, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
if err != nil { require.NoErrorf(t, err, "walk %s", dir)
t.Fatal(err)
}
// Check for sanity. This ought to pass. // Check for sanity. This ought to pass.
res, err := Check(dir, dh, nil, nil) res, err := Check(dir, dh, nil, nil)
if err != nil { require.NoErrorf(t, err, "check %s", dir)
t.Fatal(err) if !assert.Empty(t, res, "check after no changes should have no diff") {
} pprintInodeDeltas(t, res)
if len(res) > 0 {
t.Errorf("%#v", res)
} }
// Touch a file, so the mtime changes. // Touch a file, so the mtime changes.
newtime := time.Date(2006, time.February, 1, 3, 4, 5, 0, time.UTC) newtime := time.Date(2006, time.February, 1, 3, 4, 5, 0, time.UTC)
if err := os.Chtimes(tmpfn, newtime, newtime); err != nil { require.NoError(t, os.Chtimes(tmpfn, newtime, newtime))
t.Fatal(err)
}
// Check again. This ought to fail. // Check again. This ought to fail.
res, err = Check(dir, dh, nil, nil) res, err = Check(dir, dh, nil, nil)
if err != nil { require.NoErrorf(t, err, "check %s", dir)
t.Fatal(err)
} if assert.NotEmpty(t, res, "should get a delta after mtime change") {
if len(res) != 1 { assert.Len(t, res, 1, "should only be one changed file entry")
t.Fatal("expected to get 1 delta on changed mtimes, but did not") diff := res[0]
} assert.Equal(t, Modified, diff.Type(), "expected to get modified entry")
if res[0].Type() != Modified { kds := diff.Diff()
t.Errorf("expected to get modified delta on changed mtimes, but did not") if assert.Len(t, kds, 1, "should have one key different after mtime change") {
kd := kds[0]
assert.Equal(t, Modified, kd.Type(), "after mtime change key delta type should be modified")
assert.Equal(t, Keyword("time"), kd.Name(), "after mtime change key delta should be 'time'")
assert.NotNil(t, kd.Old(), "after mtime change key delta Old")
assert.NotNil(t, kd.New(), "after mtime change key delta New")
}
} }
// Check again, but only sha1 and mode. This ought to pass. // Check again, but only sha1 and mode. This ought to pass.
res, err = Check(dir, dh, []Keyword{"sha1", "mode"}, nil) res, err = Check(dir, dh, []Keyword{"sha1", "mode"}, nil)
if err != nil { require.NoErrorf(t, err, "check .", err)
t.Fatal(err) if !assert.Empty(t, res, "check (~time) should have no diff") {
} pprintInodeDeltas(t, res)
if len(res) > 0 {
t.Errorf("%#v", res)
} }
} }
@ -102,28 +93,22 @@ func ExampleCheck() {
// Tests default action for evaluating a symlink, which is just to compare the // Tests default action for evaluating a symlink, which is just to compare the
// link itself, not to follow it // link itself, not to follow it
func TestDefaultBrokenLink(t *testing.T) { func TestDefaultBrokenLink(t *testing.T) {
dh, err := Walk("./testdata/dirwithbrokenlink", nil, append(DefaultKeywords, "sha1"), nil) dir := "./testdata/dirwithbrokenlink"
if err != nil {
t.Fatal(err) dh, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
} require.NoErrorf(t, err, "walk %s", dir)
res, err := Check("./testdata/dirwithbrokenlink", dh, nil, nil)
if err != nil { res, err := Check(dir, dh, nil, nil)
t.Fatal(err) require.NoErrorf(t, err, "check %s", dir)
}
if len(res) > 0 { if !assert.Empty(t, res, "check after no changes should have no diff") {
for _, delta := range res { pprintInodeDeltas(t, res)
t.Error(delta)
}
} }
} }
// https://github.com/vbatts/go-mtree/issues/8 // https://github.com/vbatts/go-mtree/issues/8
func TestTimeComparison(t *testing.T) { func TestTimeComparison(t *testing.T) {
dir, err := os.MkdirTemp("", "test-time.") dir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
// This is the format of time from FreeBSD // This is the format of time from FreeBSD
spec := ` spec := `
@ -134,41 +119,26 @@ func TestTimeComparison(t *testing.T) {
` `
fh, err := os.Create(filepath.Join(dir, "file")) fh, err := os.Create(filepath.Join(dir, "file"))
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
// This is what mode we're checking for. Round integer of epoch seconds // This is what mode we're checking for. Round integer of epoch seconds
epoch := time.Unix(5, 0) epoch := time.Unix(5, 0)
if err := os.Chtimes(fh.Name(), epoch, epoch); err != nil { require.NoError(t, os.Chtimes(fh.Name(), epoch, epoch))
t.Fatal(err) require.NoError(t, os.Chtimes(dir, epoch, epoch))
} require.NoError(t, fh.Close())
if err := os.Chtimes(dir, epoch, epoch); err != nil {
t.Fatal(err)
}
if err := fh.Close(); err != nil {
t.Error(err)
}
dh, err := ParseSpec(bytes.NewBufferString(spec)) dh, err := ParseSpec(bytes.NewBufferString(spec))
if err != nil { require.NoError(t, err, "parse specfile")
t.Fatal(err)
}
res, err := Check(dir, dh, nil, nil) res, err := Check(dir, dh, nil, nil)
if err != nil { require.NoErrorf(t, err, "check %s against spec", dir)
t.Error(err) if !assert.Empty(t, res) {
} pprintInodeDeltas(t, res)
if len(res) > 0 {
t.Fatal(res)
} }
} }
func TestTarTime(t *testing.T) { func TestTarTime(t *testing.T) {
dir, err := os.MkdirTemp("", "test-tar-time.") dir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
// This is the format of time from FreeBSD // This is the format of time from FreeBSD
spec := ` spec := `
@ -179,50 +149,35 @@ func TestTarTime(t *testing.T) {
` `
fh, err := os.Create(filepath.Join(dir, "file")) fh, err := os.Create(filepath.Join(dir, "file"))
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
// This is what mode we're checking for. Round integer of epoch seconds // This is what mode we're checking for. Round integer of epoch seconds
epoch := time.Unix(5, 0) epoch := time.Unix(5, 0)
if err := os.Chtimes(fh.Name(), epoch, epoch); err != nil { require.NoError(t, os.Chtimes(fh.Name(), epoch, epoch))
t.Fatal(err) require.NoError(t, os.Chtimes(dir, epoch, epoch))
} require.NoError(t, fh.Close())
if err := os.Chtimes(dir, epoch, epoch); err != nil {
t.Fatal(err)
}
if err := fh.Close(); err != nil {
t.Error(err)
}
dh, err := ParseSpec(bytes.NewBufferString(spec)) dh, err := ParseSpec(bytes.NewBufferString(spec))
if err != nil { require.NoError(t, err, "parse specfile")
t.Fatal(err)
}
keywords := dh.UsedKeywords() keywords := dh.UsedKeywords()
assert.ElementsMatch(t, keywords, []Keyword{"type", "time"}, "UsedKeywords")
// make sure "time" keyword works // make sure "time" keyword works
_, err = Check(dir, dh, keywords, nil) res1, err := Check(dir, dh, keywords, nil)
if err != nil { require.NoErrorf(t, err, "check %s (UsedKeywords)", dir)
t.Error(err) assert.NotEmpty(t, res1, "check should have errors when time mismatched")
}
// make sure tar_time wins // make sure tar_time wins
res, err := Check(dir, dh, append(keywords, "tar_time"), nil) res2, err := Check(dir, dh, append(keywords, "tar_time"), nil)
if err != nil { require.NoErrorf(t, err, "check %s (UsedKeywords + tar_time)", dir)
t.Error(err) if !assert.Empty(t, res2, "tar_time should check against truncated timestamp") {
} pprintInodeDeltas(t, res2)
if len(res) > 0 {
t.Fatal(res)
} }
} }
func TestIgnoreComments(t *testing.T) { func TestIgnoreComments(t *testing.T) {
dir, err := os.MkdirTemp("", "test-comments.") dir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
// This is the format of time from FreeBSD // This is the format of time from FreeBSD
spec := ` spec := `
@ -233,33 +188,20 @@ func TestIgnoreComments(t *testing.T) {
` `
fh, err := os.Create(filepath.Join(dir, "file1")) fh, err := os.Create(filepath.Join(dir, "file1"))
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
// This is what mode we're checking for. Round integer of epoch seconds // This is what mode we're checking for. Round integer of epoch seconds
epoch := time.Unix(5, 0) epoch := time.Unix(5, 0)
if err := os.Chtimes(fh.Name(), epoch, epoch); err != nil { require.NoError(t, os.Chtimes(fh.Name(), epoch, epoch))
t.Fatal(err) require.NoError(t, os.Chtimes(dir, epoch, epoch))
} require.NoError(t, fh.Close())
if err := os.Chtimes(dir, epoch, epoch); err != nil {
t.Fatal(err)
}
if err := fh.Close(); err != nil {
t.Error(err)
}
dh, err := ParseSpec(bytes.NewBufferString(spec)) dh, err := ParseSpec(bytes.NewBufferString(spec))
if err != nil { require.NoError(t, err, "parse specfile")
t.Fatal(err)
}
res, err := Check(dir, dh, nil, nil) res, err := Check(dir, dh, nil, nil)
if err != nil { require.NoErrorf(t, err, "check %s", dir)
t.Error(err) if !assert.Empty(t, res) {
} pprintInodeDeltas(t, res)
if len(res) > 0 {
t.Fatal(res)
} }
// now change the spec to a comment that looks like an actual Entry but has // now change the spec to a comment that looks like an actual Entry but has
@ -272,51 +214,32 @@ func TestIgnoreComments(t *testing.T) {
.. ..
` `
dh, err = ParseSpec(bytes.NewBufferString(spec)) dh, err = ParseSpec(bytes.NewBufferString(spec))
if err != nil { require.NoError(t, err, "parse specfile")
t.Error(err)
}
res, err = Check(dir, dh, nil, nil) res, err = Check(dir, dh, nil, nil)
if err != nil { require.NoErrorf(t, err, "check %s", dir)
t.Error(err) if !assert.Empty(t, res) {
} pprintInodeDeltas(t, res)
if len(res) > 0 {
t.Fatal(res)
} }
} }
func TestCheckNeedsEncoding(t *testing.T) { func TestCheckNeedsEncoding(t *testing.T) {
dir, err := os.MkdirTemp("", "test-needs-encoding") dir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
fh, err := os.Create(filepath.Join(dir, "file[ ")) fh, err := os.Create(filepath.Join(dir, "file[ "))
if err != nil { require.NoError(t, err)
t.Fatal(err) require.NoError(t, fh.Close())
}
if err := fh.Close(); err != nil {
t.Error(err)
}
fh, err = os.Create(filepath.Join(dir, " , should work")) fh, err = os.Create(filepath.Join(dir, " , should work"))
if err != nil { require.NoError(t, err)
t.Fatal(err) require.NoError(t, fh.Close())
}
if err := fh.Close(); err != nil {
t.Error(err)
}
dh, err := Walk(dir, nil, DefaultKeywords, nil) dh, err := Walk(dir, nil, DefaultKeywords, nil)
if err != nil { require.NoErrorf(t, err, "walk %s", dir)
t.Fatal(err)
}
res, err := Check(dir, dh, nil, nil) res, err := Check(dir, dh, nil, nil)
if err != nil { require.NoErrorf(t, err, "check %s", dir)
t.Fatal(err) if !assert.Empty(t, res) {
} pprintInodeDeltas(t, res)
if len(res) > 0 {
t.Fatal(res)
} }
} }

View file

@ -3,6 +3,9 @@ package mtree
import ( import (
"os" "os"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
var ( var (
@ -14,18 +17,11 @@ var (
// testing that the cksum function matches that of cksum(1) utility (silly POSIX crc32) // testing that the cksum function matches that of cksum(1) utility (silly POSIX crc32)
func TestCksum(t *testing.T) { func TestCksum(t *testing.T) {
fh, err := os.Open(checkFile) fh, err := os.Open(checkFile)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
defer fh.Close() defer fh.Close()
sum, i, err := cksum(fh) sum, i, err := cksum(fh)
if err != nil { require.NoError(t, err)
t.Fatal(err) assert.Equal(t, checkSize, i, "checksum size mismatch")
} assert.Equal(t, checkSum, sum, "checksum mismatch")
if i != checkSize {
t.Errorf("%q: expected size %d, got %d", checkFile, checkSize, i)
}
if sum != checkSum {
t.Errorf("%q: expected sum %d, got %d", checkFile, checkSum, sum)
}
} }

View file

@ -9,172 +9,120 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func pprintInodeDeltas(t *testing.T, deltas []InodeDelta) {
for idx, delta := range deltas {
var str string
if buf, err := json.MarshalIndent(delta, "", " "); err == nil {
str = string(buf)
} else {
str = delta.String()
}
t.Logf("diff[%d] = %s", idx, str)
}
}
// simple walk of current directory, and immediately check it. // simple walk of current directory, and immediately check it.
// may not be parallelizable. // may not be parallelizable.
func TestCompare(t *testing.T) { func TestCompare(t *testing.T) {
old, err := Walk(".", nil, append(DefaultKeywords, "sha1"), nil) old, err := Walk(".", nil, append(DefaultKeywords, "sha1"), nil)
if err != nil { require.NoError(t, err, "walk .")
t.Fatal(err)
}
new, err := Walk(".", nil, append(DefaultKeywords, "sha1"), nil) new, err := Walk(".", nil, append(DefaultKeywords, "sha1"), nil)
if err != nil { require.NoError(t, err, "walk .")
t.Fatal(err)
}
diffs, err := Compare(old, new, nil) res, err := Compare(old, new, nil)
if err != nil { require.NoError(t, err, "compare")
t.Fatal(err)
}
if len(diffs) > 0 { if !assert.Empty(t, res, "compare after no changes should have no diff") {
t.Errorf("%#v", diffs) pprintInodeDeltas(t, res)
} }
} }
//gocyclo:ignore //gocyclo:ignore
func TestCompareModified(t *testing.T) { func TestCompareModified(t *testing.T) {
dir, err := os.MkdirTemp("", "test-compare-modified") dir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
// Create a bunch of objects. // Create a bunch of objects.
tmpfile := filepath.Join(dir, "tmpfile") tmpfile := filepath.Join(dir, "tmpfile")
if err := os.WriteFile(tmpfile, []byte("some content here"), 0666); err != nil { require.NoError(t, os.WriteFile(tmpfile, []byte("some content here"), 0666))
t.Fatal(err)
}
tmpdir := filepath.Join(dir, "testdir") tmpdir := filepath.Join(dir, "testdir")
if err := os.Mkdir(tmpdir, 0755); err != nil { require.NoError(t, os.Mkdir(tmpdir, 0755))
t.Fatal(err)
}
tmpsubfile := filepath.Join(tmpdir, "anotherfile") tmpsubfile := filepath.Join(tmpdir, "anotherfile")
if err := os.WriteFile(tmpsubfile, []byte("some different content"), 0666); err != nil { require.NoError(t, os.WriteFile(tmpsubfile, []byte("some different content"), 0666))
t.Fatal(err)
}
// Walk the current state. // Walk the current state.
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil) old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
if err != nil { require.NoErrorf(t, err, "walk %s", dir)
t.Fatal(err)
}
// Overwrite the content in one of the files. // Overwrite the content in one of the files.
if err := os.WriteFile(tmpsubfile, []byte("modified content"), 0666); err != nil { require.NoError(t, os.WriteFile(tmpsubfile, []byte("modified content"), 0666))
t.Fatal(err)
}
// Walk the new state. // Walk the new state.
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil) new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
if err != nil { require.NoErrorf(t, err, "walk %s", dir)
t.Fatal(err)
}
// Compare. // Compare.
diffs, err := Compare(old, new, nil) diffs, err := Compare(old, new, nil)
if err != nil { require.NoError(t, err, "compare")
t.Fatal(err)
}
// 1 object // 1 object
if len(diffs) != 1 { if !assert.Len(t, diffs, 1, "unexpected diff count") {
t.Errorf("expected the diff length to be 1, got %d", len(diffs)) pprintInodeDeltas(t, diffs)
for i, diff := range diffs {
t.Logf("diff[%d] = %#v", i, diff)
}
} }
// These cannot fail. // These cannot fail.
tmpsubfile, _ = filepath.Rel(dir, tmpsubfile) tmpsubfile, _ = filepath.Rel(dir, tmpsubfile)
for _, diff := range diffs { for _, diff := range diffs {
switch diff.Path() { if assert.Equal(t, tmpsubfile, diff.Path()) {
case tmpsubfile: assert.Equalf(t, Modified, diff.Type(), "unexpected diff type for %s", diff.Path())
if diff.Type() != Modified { assert.NotNil(t, diff.Diff(), "Diff for modified diff")
t.Errorf("unexpected diff type for %s: %s", diff.Path(), diff.Type()) assert.NotNil(t, diff.Old(), "Old for modified diff")
} assert.NotNil(t, diff.New(), "New for modified diff")
if diff.Diff() == nil {
t.Errorf("expect to not get nil for .Diff()")
}
old := diff.Old()
new := diff.New()
if old == nil || new == nil {
t.Errorf("expected to get (!nil, !nil) for (.Old, .New), got (%#v, %#v)", old, new)
}
default:
t.Errorf("unexpected diff found: %#v", diff)
} }
} }
} }
//gocyclo:ignore //gocyclo:ignore
func TestCompareMissing(t *testing.T) { func TestCompareMissing(t *testing.T) {
dir, err := os.MkdirTemp("", "test-compare-missing") dir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
// Create a bunch of objects. // Create a bunch of objects.
tmpfile := filepath.Join(dir, "tmpfile") tmpfile := filepath.Join(dir, "tmpfile")
if err := os.WriteFile(tmpfile, []byte("some content here"), 0666); err != nil { require.NoError(t, os.WriteFile(tmpfile, []byte("some content here"), 0666))
t.Fatal(err)
}
tmpdir := filepath.Join(dir, "testdir") tmpdir := filepath.Join(dir, "testdir")
if err := os.Mkdir(tmpdir, 0755); err != nil { require.NoError(t, os.Mkdir(tmpdir, 0755))
t.Fatal(err)
}
tmpsubfile := filepath.Join(tmpdir, "anotherfile") tmpsubfile := filepath.Join(tmpdir, "anotherfile")
if err := os.WriteFile(tmpsubfile, []byte("some different content"), 0666); err != nil { require.NoError(t, os.WriteFile(tmpsubfile, []byte("some different content"), 0666))
t.Fatal(err)
}
// Walk the current state. // Walk the current state.
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil) old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
if err != nil { require.NoErrorf(t, err, "walk %s", dir)
t.Fatal(err)
}
// Delete the objects. // Delete the objects.
if err := os.RemoveAll(tmpfile); err != nil { require.NoError(t, os.RemoveAll(tmpfile))
t.Fatal(err) require.NoError(t, os.RemoveAll(tmpsubfile))
} require.NoError(t, os.RemoveAll(tmpdir))
if err := os.RemoveAll(tmpsubfile); err != nil {
t.Fatal(err)
}
if err := os.RemoveAll(tmpdir); err != nil {
t.Fatal(err)
}
// Walk the new state. // Walk the new state.
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil) new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
if err != nil { require.NoErrorf(t, err, "walk %s", dir)
t.Fatal(err)
}
// Compare. // Compare.
diffs, err := Compare(old, new, nil) diffs, err := Compare(old, new, nil)
if err != nil { require.NoError(t, err, "compare")
t.Fatal(err)
}
// 3 objects + the changes to '.' // 3 objects + the changes to '.'
if len(diffs) != 4 { if !assert.Len(t, diffs, 4, "unexpected diff count") {
t.Errorf("expected the diff length to be 4, got %d", len(diffs)) pprintInodeDeltas(t, diffs)
for i, diff := range diffs {
t.Logf("diff[%d] = %#v", i, diff)
}
} }
// These cannot fail. // These cannot fail.
@ -187,19 +135,10 @@ func TestCompareMissing(t *testing.T) {
case ".": case ".":
// ignore these changes // ignore these changes
case tmpfile, tmpdir, tmpsubfile: case tmpfile, tmpdir, tmpsubfile:
if diff.Type() != Missing { assert.Equalf(t, Missing, diff.Type(), "unexpected diff type for %s", diff.Path())
t.Errorf("unexpected diff type for %s: %s", diff.Path(), diff.Type()) assert.Nil(t, diff.Diff(), "Diff for missing diff")
} assert.NotNil(t, diff.Old(), "Old for missing diff")
assert.Nil(t, diff.New(), "New for missing diff")
if diff.Diff() != nil {
t.Errorf("expect to get nil for .Diff(), got %#v", diff.Diff())
}
old := diff.Old()
new := diff.New()
if old == nil || new != nil {
t.Errorf("expected to get (!nil, nil) for (.Old, .New), got (%#v, %#v)", old, new)
}
default: default:
t.Errorf("unexpected diff found: %#v", diff) t.Errorf("unexpected diff found: %#v", diff)
} }
@ -208,52 +147,33 @@ func TestCompareMissing(t *testing.T) {
//gocyclo:ignore //gocyclo:ignore
func TestCompareExtra(t *testing.T) { func TestCompareExtra(t *testing.T) {
dir, err := os.MkdirTemp("", "test-compare-extra") dir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
// Walk the current state. // Walk the current state.
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil) old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
if err != nil { require.NoErrorf(t, err, "walk %s", dir)
t.Fatal(err)
}
// Create a bunch of objects. // Create a bunch of objects.
tmpfile := filepath.Join(dir, "tmpfile") tmpfile := filepath.Join(dir, "tmpfile")
if err := os.WriteFile(tmpfile, []byte("some content here"), 0666); err != nil { require.NoError(t, os.WriteFile(tmpfile, []byte("some content here"), 0666))
t.Fatal(err)
}
tmpdir := filepath.Join(dir, "testdir") tmpdir := filepath.Join(dir, "testdir")
if err := os.Mkdir(tmpdir, 0755); err != nil { require.NoError(t, os.Mkdir(tmpdir, 0755))
t.Fatal(err)
}
tmpsubfile := filepath.Join(tmpdir, "anotherfile") tmpsubfile := filepath.Join(tmpdir, "anotherfile")
if err := os.WriteFile(tmpsubfile, []byte("some different content"), 0666); err != nil { require.NoError(t, os.WriteFile(tmpsubfile, []byte("some different content"), 0666))
t.Fatal(err)
}
// Walk the new state. // Walk the new state.
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil) new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
if err != nil { require.NoErrorf(t, err, "walk %s", dir)
t.Fatal(err)
}
// Compare. // Compare.
diffs, err := Compare(old, new, nil) diffs, err := Compare(old, new, nil)
if err != nil { require.NoError(t, err, "compare")
t.Fatal(err)
}
// 3 objects + the changes to '.' // 3 objects + the changes to '.'
if len(diffs) != 4 { if !assert.Len(t, diffs, 4, "unexpected diff count") {
t.Errorf("expected the diff length to be 4, got %d", len(diffs)) pprintInodeDeltas(t, diffs)
for i, diff := range diffs {
t.Logf("diff[%d] = %#v", i, diff)
}
} }
// These cannot fail. // These cannot fail.
@ -266,103 +186,63 @@ func TestCompareExtra(t *testing.T) {
case ".": case ".":
// ignore these changes // ignore these changes
case tmpfile, tmpdir, tmpsubfile: case tmpfile, tmpdir, tmpsubfile:
if diff.Type() != Extra { assert.Equalf(t, Extra, diff.Type(), "unexpected diff type for %s", diff.Path())
t.Errorf("unexpected diff type for %s: %s", diff.Path(), diff.Type()) assert.Nil(t, diff.Diff(), "Diff for extra diff")
} assert.Nil(t, diff.Old(), "Old for extra diff")
assert.NotNil(t, diff.New(), "New for extra diff")
if diff.Diff() != nil {
t.Errorf("expect to get nil for .Diff(), got %#v", diff.Diff())
}
old := diff.Old()
new := diff.New()
if old != nil || new == nil {
t.Errorf("expected to get (!nil, nil) for (.Old, .New), got (%#v, %#v)", old, new)
}
default: default:
t.Errorf("unexpected diff found: %#v", diff) t.Errorf("unexpected diff found: %#v", diff)
} }
} }
} }
func TestCompareKeys(t *testing.T) { func TestCompareKeySubset(t *testing.T) {
dir, err := os.MkdirTemp("", "test-compare-keys") dir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
// Create a bunch of objects. // Create a bunch of objects.
tmpfile := filepath.Join(dir, "tmpfile") tmpfile := filepath.Join(dir, "tmpfile")
if err := os.WriteFile(tmpfile, []byte("some content here"), 0666); err != nil { require.NoError(t, os.WriteFile(tmpfile, []byte("some content here"), 0666))
t.Fatal(err)
}
tmpdir := filepath.Join(dir, "testdir") tmpdir := filepath.Join(dir, "testdir")
if err := os.Mkdir(tmpdir, 0755); err != nil { require.NoError(t, os.Mkdir(tmpdir, 0755))
t.Fatal(err)
}
tmpsubfile := filepath.Join(tmpdir, "anotherfile") tmpsubfile := filepath.Join(tmpdir, "anotherfile")
if err := os.WriteFile(tmpsubfile, []byte("aaa"), 0666); err != nil { require.NoError(t, os.WriteFile(tmpsubfile, []byte("aaa"), 0666))
t.Fatal(err)
}
// Walk the current state. // Walk the current state.
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil) old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
if err != nil { require.NoErrorf(t, err, "walk %s", dir)
t.Fatal(err)
}
// Overwrite the content in one of the files, but without changing the size. // Overwrite the content in one of the files, but without changing the size.
if err := os.WriteFile(tmpsubfile, []byte("bbb"), 0666); err != nil { require.NoError(t, os.WriteFile(tmpsubfile, []byte("bbb"), 0666))
t.Fatal(err)
}
// Walk the new state. // Walk the new state.
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil) new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
if err != nil { require.NoErrorf(t, err, "walk %s", dir)
t.Fatal(err)
}
// Compare. // Compare.
diffs, err := Compare(old, new, []Keyword{"size"}) diffs, err := Compare(old, new, []Keyword{"size"})
if err != nil { require.NoError(t, err, "compare")
t.Fatal(err)
}
// 0 objects // 0 objects
if len(diffs) != 0 { if !assert.Empty(t, diffs, "size-only compare should not return any entries") {
t.Errorf("expected the diff length to be 0, got %d", len(diffs)) pprintInodeDeltas(t, diffs)
for i, diff := range diffs {
t.Logf("diff[%d] = %#v", i, diff)
}
} }
} }
//gocyclo:ignore //gocyclo:ignore
func TestTarCompare(t *testing.T) { func TestTarCompare(t *testing.T) {
dir, err := os.MkdirTemp("", "test-compare-tar") dir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
// Create a bunch of objects. // Create a bunch of objects.
tmpfile := filepath.Join(dir, "tmpfile") tmpfile := filepath.Join(dir, "tmpfile")
if err := os.WriteFile(tmpfile, []byte("some content"), 0644); err != nil { require.NoError(t, os.WriteFile(tmpfile, []byte("some content"), 0644))
t.Fatal(err)
}
tmpdir := filepath.Join(dir, "testdir") tmpdir := filepath.Join(dir, "testdir")
if err := os.Mkdir(tmpdir, 0755); err != nil { require.NoError(t, os.Mkdir(tmpdir, 0755))
t.Fatal(err)
}
tmpsubfile := filepath.Join(tmpdir, "anotherfile") tmpsubfile := filepath.Join(tmpdir, "anotherfile")
if err := os.WriteFile(tmpsubfile, []byte("aaa"), 0644); err != nil { require.NoError(t, os.WriteFile(tmpsubfile, []byte("aaa"), 0644))
t.Fatal(err)
}
// Create a tar-like archive. // Create a tar-like archive.
compareFiles := []fakeFile{ compareFiles := []fakeFile{
@ -377,43 +257,33 @@ func TestTarCompare(t *testing.T) {
// Change the time to something known with nanosec != 0. // Change the time to something known with nanosec != 0.
chtime := time.Unix(file.Sec, 987654321) chtime := time.Unix(file.Sec, 987654321)
if err := os.Chtimes(path, chtime, chtime); err != nil { require.NoError(t, os.Chtimes(path, chtime, chtime))
t.Fatal(err)
}
} }
// Walk the current state. // Walk the current state.
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil) old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
if err != nil { require.NoErrorf(t, err, "walk %s", dir)
t.Fatal(err)
}
ts, err := makeTarStream(compareFiles) ts, err := makeTarStream(compareFiles)
if err != nil { require.NoError(t, err, "make tar stream")
t.Fatal(err)
}
str := NewTarStreamer(bytes.NewBuffer(ts), nil, append(DefaultTarKeywords, "sha1")) str := NewTarStreamer(bytes.NewBuffer(ts), nil, append(DefaultTarKeywords, "sha1"))
if _, err = io.Copy(io.Discard, str); err != nil && err != io.EOF {
t.Fatal(err) n, err := io.Copy(io.Discard, str)
} require.NoError(t, err, "read full tar stream")
if err = str.Close(); err != nil { require.Greater(t, n, int64(0), "tar stream should be non-empty")
t.Fatal(err) require.NoError(t, str.Close(), "close tar stream")
}
new, err := str.Hierarchy() new, err := str.Hierarchy()
if err != nil { require.NoError(t, err, "TarStreamer Hierarchy")
t.Fatal(err) require.NotNil(t, new, "TarStreamer Hierarchy")
}
// Compare. // Compare.
diffs, err := Compare(old, new, append(DefaultTarKeywords, "sha1")) diffs, err := Compare(old, new, append(DefaultTarKeywords, "sha1"))
if err != nil { require.NoError(t, err, "compare")
t.Fatal(err)
}
// 0 objects // 0 objects, but there are bugs in tar generation.
if len(diffs) != 0 { if len(diffs) > 0 {
actualFailure := false actualFailure := false
for i, delta := range diffs { for i, delta := range diffs {
// XXX: Tar generation is slightly broken, so we need to ignore some bugs. // XXX: Tar generation is slightly broken, so we need to ignore some bugs.
@ -449,7 +319,7 @@ func TestTarCompare(t *testing.T) {
if err == nil { if err == nil {
t.Logf("FAILURE: diff[%d] = %s", i, string(buf)) t.Logf("FAILURE: diff[%d] = %s", i, string(buf))
} else { } else {
t.Logf("FAILURE: diff[%d] = %#v", i, delta) t.Logf("FAILURE: diff[%d] = %s", i, delta)
} }
} }

View file

@ -1,11 +1,13 @@
package mtree package mtree
import ( import (
"encoding/json"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
var mockTime = time.Unix(1337888823, 0) var mockTime = time.Unix(1337888823, 0)
@ -72,95 +74,53 @@ func (fs *MockFsEval) KeywordFunc(fn KeywordFunc) KeywordFunc {
//gocyclo:ignore //gocyclo:ignore
func TestCheckFsEval(t *testing.T) { func TestCheckFsEval(t *testing.T) {
dir, err := os.MkdirTemp("", "test-check-fs-eval") dir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir) // clean up
content := []byte("If you hide your ignorance, no one will hit you and you'll never learn.") content := []byte("If you hide your ignorance, no one will hit you and you'll never learn.")
tmpfn := filepath.Join(dir, "tmpfile") tmpfn := filepath.Join(dir, "tmpfile")
if err := os.WriteFile(tmpfn, content, 0451); err != nil { require.NoError(t, os.WriteFile(tmpfn, content, 0451))
t.Fatal(err)
}
// Walk this tempdir // Walk this tempdir
mock := &MockFsEval{} mock := &MockFsEval{}
dh, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), mock) dh, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), mock)
if err != nil { require.NoErrorf(t, err, "walk %s (mock FsEval)", dir)
t.Fatal(err)
}
// Make sure that mock functions have been called. // Make sure that mock functions have been called.
if mock.open == 0 { assert.NotZero(t, mock.open, "mock.Open not called")
t.Errorf("mock.Open not called") assert.NotZero(t, mock.lstat, "mock.Lstat not called")
} assert.NotZero(t, mock.readdir, "mock.Readdir not called")
if mock.lstat == 0 { assert.NotZero(t, mock.keywordFunc, "mock.KeywordFunc not called")
t.Errorf("mock.Lstat not called")
}
if mock.readdir == 0 {
t.Errorf("mock.Readdir not called")
}
if mock.keywordFunc == 0 {
t.Errorf("mock.KeywordFunc not called")
}
// Check for sanity. This ought to pass. // Check for sanity. This ought to pass.
mock = &MockFsEval{} mock = &MockFsEval{}
res, err := Check(dir, dh, nil, mock) res, err := Check(dir, dh, nil, mock)
if err != nil { require.NoErrorf(t, err, "check %s (mock FsEval)", dir)
t.Fatal(err) if !assert.Empty(t, res) {
} pprintInodeDeltas(t, res)
if len(res) > 0 {
t.Errorf("%#v", res)
} }
// Make sure that mock functions have been called. // Make sure that mock functions have been called.
if mock.open == 0 { assert.NotZero(t, mock.open, "mock.Open not called")
t.Errorf("mock.Open not called") assert.NotZero(t, mock.lstat, "mock.Lstat not called")
} assert.NotZero(t, mock.readdir, "mock.Readdir not called")
if mock.lstat == 0 { assert.NotZero(t, mock.keywordFunc, "mock.KeywordFunc not called")
t.Errorf("mock.Lstat not called")
}
if mock.readdir == 0 {
t.Errorf("mock.Readdir not called")
}
if mock.keywordFunc == 0 {
t.Errorf("mock.KeywordFunc not called")
}
// This should FAIL. // This should FAIL.
res, err = Check(dir, dh, nil, nil) res, err = Check(dir, dh, nil, nil)
if err != nil { require.NoErrorf(t, err, "walk %s", dir)
t.Fatal(err) if !assert.NotEmpty(t, res) {
} pprintInodeDeltas(t, res)
if len(res) == 0 {
t.Errorf("expected Check to fail")
} }
// Modify the metadata so you can get the right output. // Modify the metadata so you can get the right output.
if err := os.Chmod(tmpfn, 0777); err != nil { require.NoError(t, os.Chmod(tmpfn, 0777))
t.Fatal(err) require.NoError(t, os.Chtimes(tmpfn, mockTime, mockTime))
} require.NoError(t, os.Chmod(dir, 0777))
if err := os.Chtimes(tmpfn, mockTime, mockTime); err != nil { require.NoError(t, os.Chtimes(dir, mockTime, mockTime))
t.Fatal(err)
}
if err := os.Chmod(dir, 0777); err != nil {
t.Fatal(err)
}
if err := os.Chtimes(dir, mockTime, mockTime); err != nil {
t.Fatal(err)
}
// It should now succeed. // It should now succeed.
res, err = Check(dir, dh, nil, nil) res, err = Check(dir, dh, nil, nil)
if err != nil { require.NoErrorf(t, err, "check %s", dir)
t.Fatal(err) if !assert.Empty(t, res) {
} pprintInodeDeltas(t, res)
if len(res) > 0 {
buf, err := json.MarshalIndent(res, "", " ")
if err != nil {
t.Errorf("%#v", res)
} else {
t.Errorf("%s", buf)
}
} }
} }

3
go.mod
View file

@ -8,6 +8,7 @@ require (
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/fatih/color v1.18.0 github.com/fatih/color v1.18.0
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.11.1
github.com/urfave/cli/v2 v2.27.7 github.com/urfave/cli/v2 v2.27.7
golang.org/x/crypto v0.41.0 golang.org/x/crypto v0.41.0
golang.org/x/sys v0.36.0 golang.org/x/sys v0.36.0
@ -17,6 +18,8 @@ require (
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 // indirect github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

77
go.sum
View file

@ -1,4 +1,3 @@
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -6,12 +5,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@ -21,86 +16,20 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU=
github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAzt5X7s6266i6cSVkkFPS0TuXWbIg= github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAzt5X7s6266i6cSVkkFPS0TuXWbIg=
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View file

@ -3,13 +3,20 @@ package mtree
import ( import (
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
var checklist = []struct { func TestUsedKeywords(t *testing.T) {
blob string for _, test := range []struct {
set []Keyword name string
}{ blob string
{blob: ` set []Keyword
}{
{
name: "NonHomogenous",
blob: `
# machine: bananaboat # machine: bananaboat
# tree: .git # tree: .git
# date: Wed Nov 16 14:54:17 2016 # date: Wed Nov 16 14:54:17 2016
@ -19,8 +26,12 @@ var checklist = []struct {
. size=4096 type=dir mode=0755 nlink=8 time=1479326055.423853146 . size=4096 type=dir mode=0755 nlink=8 time=1479326055.423853146
.COMMIT_EDITMSG.un~ size=1006 mode=0644 time=1479325423.450468662 sha1digest=dead0face .COMMIT_EDITMSG.un~ size=1006 mode=0644 time=1479325423.450468662 sha1digest=dead0face
.TAG_EDITMSG.un~ size=1069 mode=0600 time=1471362316.801317529 sha256digest=dead0face .TAG_EDITMSG.un~ size=1069 mode=0600 time=1471362316.801317529 sha256digest=dead0face
`, set: []Keyword{"size", "mode", "time", "sha256digest"}}, `,
{blob: ` set: []Keyword{"type", "nlink", "mode", "uid", "gid", "size", "time", "sha1digest", "sha256digest"},
},
{
name: "xattrs",
blob: `
# user: cyphar # user: cyphar
# machine: ryuk # machine: ryuk
# tree: xattr # tree: xattr
@ -32,20 +43,15 @@ var checklist = []struct {
. size=8 type=dir mode=0755 time=1506666472.255992830 . size=8 type=dir mode=0755 time=1506666472.255992830
file size=0 mode=0644 time=1506666472.255992830 xattr.user.something=dGVzdA== file size=0 mode=0644 time=1506666472.255992830 xattr.user.something=dGVzdA==
.. ..
`, set: []Keyword{"size", "type", "uid", "gid", "mode", "nlink", "time", "xattr"}}, `,
} set: []Keyword{"size", "type", "uid", "gid", "mode", "nlink", "time", "xattr"},
},
func TestUsedKeywords(t *testing.T) { } {
for i, item := range checklist { t.Run(test.name, func(t *testing.T) {
dh, err := ParseSpec(strings.NewReader(item.blob)) dh, err := ParseSpec(strings.NewReader(test.blob))
if err != nil { require.NoError(t, err, "parse spec")
t.Error(err) used := dh.UsedKeywords()
} assert.ElementsMatch(t, used, test.set, "UsedKeywords should contain all keywords used")
used := dh.UsedKeywords() })
for _, k := range item.set {
if !InKeywordSlice(k, used) {
t.Errorf("%d: expected to find %q in %q", i, k, used)
}
}
} }
} }

View file

@ -8,6 +8,9 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/vbatts/go-mtree/xattr" "github.com/vbatts/go-mtree/xattr"
) )
@ -21,70 +24,48 @@ func TestXattr(t *testing.T) {
testDir = "." testDir = "."
} }
dir, err := os.MkdirTemp(testDir, "test.xattrs.") dir, err := os.MkdirTemp(testDir, "test.xattrs.")
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
defer os.RemoveAll(dir) defer os.RemoveAll(dir)
fh, err := os.Create(filepath.Join(dir, "file"))
if err != nil {
t.Fatal(err)
}
_, err = fh.WriteString("howdy")
if err != nil {
t.Errorf("failed to write string: %s", err)
}
err = fh.Sync()
if err != nil {
t.Errorf("failed to sync file: %s", err)
}
if _, err := fh.Seek(0, 0); err != nil {
t.Fatal(err)
}
if err := os.Symlink("./no/such/path", filepath.Join(dir, "symlink")); err != nil { fh, err := os.Create(filepath.Join(dir, "file"))
t.Fatal(err) require.NoError(t, err)
}
_, err = fh.WriteString("howdy")
require.NoError(t, err)
err = fh.Sync()
require.NoError(t, err)
_, err = fh.Seek(0, 0)
require.NoError(t, err)
require.NoError(t, os.Symlink("./no/such/path", filepath.Join(dir, "symlink")))
if err := xattr.Set(dir, "user.test", []byte("directory")); err != nil { if err := xattr.Set(dir, "user.test", []byte("directory")); err != nil {
t.Skipf("skipping: %q does not support xattrs", dir) t.Skipf("skipping: %q does not support xattrs", dir)
} }
if err := xattr.Set(filepath.Join(dir, "file"), "user.test", []byte("regular file")); err != nil { require.NoError(t, xattr.Set(filepath.Join(dir, "file"), "user.test", []byte("regular file")))
t.Fatal(err)
}
dirstat, err := os.Lstat(dir) dirstat, err := os.Lstat(dir)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
// Check the directory // Check the directory
kvs, err := xattrKeywordFunc(dir, dirstat, nil) kvs, err := xattrKeywordFunc(dir, dirstat, nil)
if err != nil { require.NoError(t, err, "xattr keyword fn")
t.Error(err) assert.NotEmpty(t, kvs, "expected to get a keyval from xattr keyword fn")
}
if len(kvs) == 0 {
t.Errorf("expected a keyval; got none")
}
filestat, err := fh.Stat() filestat, err := fh.Stat()
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
// Check the regular file // Check the regular file
kvs, err = xattrKeywordFunc(filepath.Join(dir, "file"), filestat, fh) kvs, err = xattrKeywordFunc(filepath.Join(dir, "file"), filestat, fh)
if err != nil { require.NoError(t, err, "xattr keyword fn")
t.Error(err) assert.NotEmpty(t, kvs, "expected to get a keyval from xattr keyword fn")
}
if len(kvs) == 0 {
t.Errorf("expected a keyval; got none")
}
linkstat, err := os.Lstat(filepath.Join(dir, "symlink")) linkstat, err := os.Lstat(filepath.Join(dir, "symlink"))
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
// Check a broken symlink // Check a broken symlink
_, err = xattrKeywordFunc(filepath.Join(dir, "symlink"), linkstat, nil) _, err = xattrKeywordFunc(filepath.Join(dir, "symlink"), linkstat, nil)
if err != nil { require.NoError(t, err, "xattr keyword fn broken symlink")
t.Error(err)
}
} }

View file

@ -5,45 +5,36 @@ import (
"os" "os"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestKeyValRoundtrip(t *testing.T) { func TestKeyValRoundtrip(t *testing.T) {
kv := KeyVal("xattr.security.selinux=dW5jb25maW5lZF91Om9iamVjdF9yOnVzZXJfaG9tZV90OnMwAA==") kv := KeyVal("xattr.security.selinux=dW5jb25maW5lZF91Om9iamVjdF9yOnVzZXJfaG9tZV90OnMwAA==")
expected := "xattr.security.selinux" expected := "xattr.security.selinux"
got := string(kv.Keyword()) got := string(kv.Keyword())
if got != expected { assert.Equalf(t, expected, got, "%q keyword", kv)
t.Errorf("expected %q; got %q", expected, got)
}
expected = "xattr" expected = "xattr"
got = string(kv.Keyword().Prefix()) got = string(kv.Keyword().Prefix())
if got != expected { assert.Equalf(t, expected, got, "%q keyword prefix", kv)
t.Errorf("expected %q; got %q", expected, got)
}
expected = "security.selinux" expected = "security.selinux"
got = kv.Keyword().Suffix() got = kv.Keyword().Suffix()
if got != expected { assert.Equalf(t, expected, got, "%q keyword suffix", kv)
t.Errorf("expected %q; got %q", expected, got)
}
expected = "dW5jb25maW5lZF91Om9iamVjdF9yOnVzZXJfaG9tZV90OnMwAA==" expected = "dW5jb25maW5lZF91Om9iamVjdF9yOnVzZXJfaG9tZV90OnMwAA=="
got = kv.Value() got = kv.Value()
if got != expected { assert.Equalf(t, expected, got, "%q value", kv)
t.Errorf("expected %q; got %q", expected, got)
}
expected = "xattr.security.selinux=farts" expected = "xattr.security.selinux=farts"
got = string(kv.NewValue("farts")) got = string(kv.NewValue("farts"))
if got != expected { assert.Equal(t, expected, got, "NewValue", kv)
t.Errorf("expected %q; got %q", expected, got)
}
kv1 := KeyVal(got) kv1 := KeyVal(expected)
kv2 := kv.NewValue("farts") kv2 := kv.NewValue("farts")
if !kv2.Equal(kv1) { assert.Equal(t, kv1, kv2, "NewValue should be equivalent to explicit value")
t.Errorf("expected equality of %q and %q", kv1, kv2)
}
} }
@ -93,20 +84,17 @@ func TestKeywordsTimeNano(t *testing.T) {
{144123582122, 1}, {144123582122, 1},
{857125628319, 0}, {857125628319, 0},
} { } {
mtime := time.Unix(test.sec, test.nsec) t.Run(fmt.Sprintf("%d.%9.9d", test.sec, test.nsec), func(t *testing.T) {
expected := KeyVal(fmt.Sprintf("time=%d.%9.9d", test.sec, test.nsec)) mtime := time.Unix(test.sec, test.nsec)
got, err := timeKeywordFunc("", fakeFileInfo{ expected := KeyVal(fmt.Sprintf("time=%d.%9.9d", test.sec, test.nsec))
mtime: mtime, got, err := timeKeywordFunc("", fakeFileInfo{
}, nil) mtime: mtime,
if err != nil { }, nil)
t.Errorf("unexpected error while parsing '%q': %q", mtime, err) require.NoErrorf(t, err, "time keyword fn")
} if assert.Len(t, got, 1, "time keyword fn") {
if len(got) != 1 { assert.Equal(t, []KeyVal{expected}, got, "should get matching keyword")
t.Errorf("expected 1 KeyVal, but got %d", len(got)) }
} })
if expected != got[0] {
t.Errorf("keyword didn't match, expected '%s' got '%s'", expected, got[0])
}
} }
} }
@ -123,25 +111,22 @@ func TestKeywordsTimeTar(t *testing.T) {
{144123582122, 1}, {144123582122, 1},
{857125628319, 0}, {857125628319, 0},
} { } {
mtime := time.Unix(test.sec, test.nsec) t.Run(fmt.Sprintf("%d.%9.9d", test.sec, test.nsec), func(t *testing.T) {
expected := KeyVal(fmt.Sprintf("tar_time=%d.%9.9d", test.sec, 0)) mtime := time.Unix(test.sec, test.nsec)
got, err := tartimeKeywordFunc("", fakeFileInfo{ expected := KeyVal(fmt.Sprintf("tar_time=%d.%9.9d", test.sec, 0))
mtime: mtime, got, err := tartimeKeywordFunc("", fakeFileInfo{
}, nil) mtime: mtime,
if err != nil { }, nil)
t.Errorf("unexpected error while parsing '%q': %q", mtime, err) require.NoErrorf(t, err, "tar_time keyword fn")
} if assert.Len(t, got, 1, "tar_time keyword fn") {
if len(got) != 1 { assert.Equal(t, []KeyVal{expected}, got, "should get matching keyword")
t.Errorf("expected 1 KeyVal, but got %d", len(got)) }
} })
if expected != got[0] {
t.Errorf("keyword didn't match, expected '%s' got '%s'", expected, got[0])
}
} }
} }
func TestKeywordSynonym(t *testing.T) { func TestKeywordSynonym(t *testing.T) {
checklist := []struct { for _, test := range []struct {
give string give string
expect Keyword expect Keyword
}{ }{
@ -161,12 +146,10 @@ func TestKeywordSynonym(t *testing.T) {
{give: "sha512digest", expect: "sha512digest"}, {give: "sha512digest", expect: "sha512digest"},
{give: "xattr", expect: "xattr"}, {give: "xattr", expect: "xattr"},
{give: "xattrs", expect: "xattr"}, {give: "xattrs", expect: "xattr"},
} } {
t.Run(test.give, func(t *testing.T) {
for i, check := range checklist { got := KeywordSynonym(test.give)
got := KeywordSynonym(check.give) assert.Equal(t, test.expect, got)
if got != check.expect { })
t.Errorf("%d: expected %q; got %q", i, check.expect, got)
}
} }
} }

View file

@ -1,88 +1,68 @@
package mtree package mtree
import ( import (
"bytes"
"io" "io"
"os" "os"
"testing" "testing"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
var ( func TestParser(t *testing.T) {
testFiles = []struct { for _, test := range []struct {
Name string name string
Counts map[EntryType]int counts map[EntryType]int
Len int64 size int64
}{ }{
{ {
Name: "testdata/source.mtree", name: "testdata/source.mtree",
Counts: map[EntryType]int{ counts: map[EntryType]int{
FullType: 0, //FullType: 0,
RelativeType: 45, RelativeType: 45,
CommentType: 37, CommentType: 37,
SpecialType: 7, SpecialType: 7,
DotDotType: 17, DotDotType: 17,
BlankType: 34, BlankType: 34,
}, },
Len: int64(7887), size: 7887,
}, },
{ {
Name: "testdata/source.casync-mtree", name: "testdata/source.casync-mtree",
Counts: map[EntryType]int{ counts: map[EntryType]int{
FullType: 744, FullType: 744,
RelativeType: 56, RelativeType: 56,
CommentType: 37,
SpecialType: 7,
DotDotType: 17,
BlankType: 34,
}, },
Len: int64(168439), size: 168439,
}, },
} } {
) t.Run(test.name, func(t *testing.T) {
fh, err := os.Open(test.name)
func TestParser(t *testing.T) { require.NoError(t, err)
for i, tf := range testFiles {
_ = i
func() {
fh, err := os.Open(tf.Name)
if err != nil {
t.Error(err)
return
}
defer fh.Close() defer fh.Close()
dh, err := ParseSpec(fh) var readSpecBuf bytes.Buffer
if err != nil { rdr := io.TeeReader(fh, &readSpecBuf)
t.Error(err)
}
if i == 1 { dh, err := ParseSpec(rdr)
spew.Dump(dh) require.NoErrorf(t, err, "parse spec %s", test.name)
//buf, err := xml.MarshalIndent(dh, "", " ")
//if err == nil { defer func() {
//t.Error(string(buf)) if t.Failed() {
//} t.Log(spew.Sdump(dh))
} }
}()
gotNums := countTypes(dh) gotNums := countTypes(dh)
for typ, num := range tf.Counts { assert.Equal(t, test.counts, gotNums, "count of entry types mismatch")
if gNum, ok := gotNums[typ]; ok {
if num != gNum {
t.Errorf("for type %s: expected %d, got %d", typ, num, gNum)
}
}
}
i, err := dh.WriteTo(io.Discard) n, err := dh.WriteTo(io.Discard)
if err != nil { require.NoError(t, err, "write directory hierarchy representation")
t.Error(err) assert.Equal(t, test.size, n, "output mtree spec should match input size")
} // TODO: Verify that the output is equal to the input.
if i != tf.Len { })
t.Errorf("expected to write %d, but wrote %d", tf.Len, i)
}
}()
} }
} }

View file

@ -11,7 +11,7 @@ Because 80s BSD code is not very nice to read.
``` ```
govis: unicode aware vis(3) encoding implementation govis: unicode aware vis(3) encoding implementation
Copyright (C) 2017 SUSE LLC. Copyright (C) 2017-2025 SUSE LLC.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -1,6 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
/* /*
* govis: unicode aware vis(3) encoding implementation * govis: unicode aware vis(3) encoding implementation
* Copyright (C) 2017 SUSE LLC. * Copyright (C) 2017-2025 SUSE LLC.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,6 +18,13 @@
package govis package govis
import (
"errors"
"fmt"
"strconv"
"strings"
)
// VisFlag manipulates how the characters are encoded/decoded // VisFlag manipulates how the characters are encoded/decoded
type VisFlag uint type VisFlag uint
@ -24,16 +32,73 @@ type VisFlag uint
// mtree only uses one set of flags, implementing them all is necessary in // mtree only uses one set of flags, implementing them all is necessary in
// order to have compatibility with BSD's vis() and unvis() commands. // order to have compatibility with BSD's vis() and unvis() commands.
const ( const (
VisOctal VisFlag = (1 << iota) // VIS_OCTAL: Use octal \ddd format. VisOctal VisFlag = (1 << iota) // VIS_OCTAL: Use octal \ddd format.
VisCStyle // VIS_CSTYLE: Use \[nrft0..] where appropriate. VisCStyle // VIS_CSTYLE: Use \[nrft0..] where appropriate.
VisSpace // VIS_SP: Also encode space. VisSpace // VIS_SP: Also encode space.
VisTab // VIS_TAB: Also encode tab. VisTab // VIS_TAB: Also encode tab.
VisNewline // VIS_NL: Also encode newline. VisNewline // VIS_NL: Also encode newline.
VisSafe // VIS_SAFE: Encode unsafe characters. VisSafe // VIS_SAFE: Encode unsafe characters.
VisNoSlash // VIS_NOSLASH: Inhibit printing '\'. VisNoSlash // VIS_NOSLASH: Inhibit printing '\'.
VisHTTPStyle // VIS_HTTPSTYLE: HTTP-style escape %xx. VisHTTPStyle // VIS_HTTPSTYLE: HTTP-style escape %xx.
VisGlob // VIS_GLOB: Encode glob(3) magics. VisGlob // VIS_GLOB: Encode glob(3) magics.
visMask VisFlag = (1 << iota) - 1 // Mask of all flags. VisDoubleQuote // VIS_DQ: Encode double-quotes (").
visMask VisFlag = (1 << iota) - 1 // Mask of all flags.
VisWhite VisFlag = (VisSpace | VisTab | VisNewline) VisWhite VisFlag = (VisSpace | VisTab | VisNewline)
) )
// errUnknownVisFlagsError is a special value that lets you use [errors.Is]
// with [unknownVisFlagsError]. Don't actually return this value, use
// [unknownVisFlagsError] instead!
var errUnknownVisFlagsError = errors.New("unknown or unsupported vis flags")
// unknownVisFlagsError represents an error caused by unknown [VisFlag]s being
// passed to [Vis] or [Unvis].
type unknownVisFlagsError struct {
flags VisFlag
}
func (err unknownVisFlagsError) Is(target error) bool {
return target == errUnknownVisFlagsError
}
func (err unknownVisFlagsError) Error() string {
return fmt.Sprintf("%s contains unknown or unsupported flags %s", err.flags, err.flags&^visMask)
}
// String pretty-prints VisFlag.
func (vflags VisFlag) String() string {
flagNames := []struct {
name string
bits VisFlag
}{
{"VisOctal", VisOctal},
{"VisCStyle", VisCStyle},
{"VisSpace", VisSpace},
{"VisTab", VisTab},
{"VisNewline", VisNewline},
{"VisSafe", VisSafe},
{"VisNoSlash", VisNoSlash},
{"VisHTTPStyle", VisHTTPStyle},
{"VisGlob", VisGlob},
}
var (
flagSet = make([]string, 0, len(flagNames))
seenBits VisFlag
)
for _, flag := range flagNames {
if vflags&flag.bits == flag.bits {
seenBits |= flag.bits
flagSet = append(flagSet, flag.name)
}
}
// If there were any remaining flags specified we don't know the name of,
// just add them in an 0x... format.
if remaining := vflags &^ seenBits; remaining != 0 {
flagSet = append(flagSet, "0x"+strconv.FormatUint(uint64(remaining), 16))
}
if len(flagSet) == 0 {
return "0"
}
return strings.Join(flagSet, "|")
}

View file

@ -1,6 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
/* /*
* govis: unicode aware vis(3) encoding implementation * govis: unicode aware vis(3) encoding implementation
* Copyright (C) 2017 SUSE LLC. * Copyright (C) 2017-2025 SUSE LLC.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,9 +19,11 @@
package govis package govis
import ( import (
"bytes"
"crypto/rand" "crypto/rand"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
const DefaultVisFlags = VisWhite | VisOctal | VisGlob const DefaultVisFlags = VisWhite | VisOctal | VisGlob
@ -31,31 +34,29 @@ func TestRandomVisUnvis(t *testing.T) {
for i := 0; i < N; i++ { for i := 0; i < N; i++ {
testBytes := make([]byte, 256) testBytes := make([]byte, 256)
if n, err := rand.Read(testBytes); n != cap(testBytes) || err != nil {
t.Fatalf("could not read enough bytes: err=%v n=%d", err, n) n, err := rand.Read(testBytes)
} require.NoErrorf(t, err, "read %d random bytes", len(testBytes))
require.Equal(t, len(testBytes), n, "read unexpected number of bytes")
test := string(testBytes) test := string(testBytes)
for flag := VisFlag(0); flag <= visMask; flag++ { for flag := VisFlag(0); flag <= visMask; flag++ {
// VisNoSlash is frankly just a dumb flag, and it is impossible for us // VisNoSlash is frankly just a dumb flag, and it is impossible
// to actually preserve things in a round-trip. // for us to actually preserve things in a round-trip.
if flag&VisNoSlash == VisNoSlash { if flag&VisNoSlash == VisNoSlash {
continue continue
} }
enc, err := Vis(test, flag) enc, err := Vis(test, flag)
if err != nil { require.NoErrorf(t, err, "vis(%q, %s)", test, flag)
t.Errorf("unexpected error doing vis(%q, %b): %s", test, flag, err)
continue
}
dec, err := Unvis(enc, flag) dec, err := Unvis(enc, flag)
if err != nil { require.NoErrorf(t, err, "unvis(%q, %s)", enc, flag)
t.Errorf("unexpected error doing unvis(%q, %b): %s", enc, flag, err)
if !assert.Equalf(t, test, dec, "unvis(vis(%q, %b) = %q, %b) round-trip", test, flag, enc, flag) {
continue continue
} }
if dec != test {
t.Errorf("roundtrip failed: unvis(vis(%q, %b) = %q, %b) = %q", test, flag, enc, flag, dec)
}
} }
} }
} }
@ -66,93 +67,88 @@ func TestRandomVisVisUnvisUnvis(t *testing.T) {
for i := 0; i < N; i++ { for i := 0; i < N; i++ {
testBytes := make([]byte, 256) testBytes := make([]byte, 256)
if n, err := rand.Read(testBytes); n != cap(testBytes) || err != nil {
t.Fatalf("could not read enough bytes: err=%v n=%d", err, n) n, err := rand.Read(testBytes)
} require.NoErrorf(t, err, "read %d random bytes", len(testBytes))
require.Equal(t, len(testBytes), n, "read unexpected number of bytes")
test := string(testBytes) test := string(testBytes)
for flag := VisFlag(0); flag <= visMask; flag++ { for flag := VisFlag(0); flag <= visMask; flag++ {
// VisNoSlash is frankly just a dumb flag, and it is impossible for us // VisNoSlash is frankly just a dumb flag, and it is impossible
// to actually preserve things in a round-trip. // for us to actually preserve things in a round-trip.
if flag&VisNoSlash == VisNoSlash { if flag&VisNoSlash == VisNoSlash {
continue continue
} }
enc, err := Vis(test, flag) enc, err := Vis(test, flag)
if err != nil { require.NoErrorf(t, err, "vis(%q, %s)", test, flag)
t.Errorf("unexpected error doing vis(%q, %b): %s", test, flag, err)
continue
}
enc2, err := Vis(enc, flag) enc2, err := Vis(enc, flag)
if err != nil { require.NoErrorf(t, err, "vis(%q, %s)", enc, flag)
t.Errorf("unexpected error doing vis(%q, %b): %s", enc, flag, err)
dec2, err := Unvis(enc2, flag)
require.NoErrorf(t, err, "unvis(%q, %s)", enc2, flag)
dec, err := Unvis(dec2, flag)
require.NoErrorf(t, err, "unvis(%q, %s)", dec2, flag)
if !assert.Equalf(t, test, dec, "unvis(unvis(vis(vis(%q) = %q) = %q) = %q, %b) round-trip", test, enc, enc2, dec2, flag) {
continue continue
} }
dec, err := Unvis(enc2, flag)
if err != nil {
t.Errorf("unexpected error doing unvis(%q, %b): %s", enc2, flag, err)
continue
}
dec2, err := Unvis(dec, flag)
if err != nil {
t.Errorf("unexpected error doing unvis(%q, %b): %s", dec, flag, err)
continue
}
if dec2 != test {
t.Errorf("roundtrip failed: unvis(unvis(vis(vis(%q) = %q) = %q) = %q, %b) = %q", test, enc, enc2, dec, flag, dec2)
}
} }
} }
} }
func TestVisUnvis(t *testing.T) { func TestVisUnvis(t *testing.T) {
for flag := VisFlag(0); flag <= visMask; flag++ { // Round-trip testing.
// VisNoSlash is frankly just a dumb flag, and it is impossible for us for _, test := range []struct {
// to actually preserve things in a round-trip. name string
if flag&VisNoSlash == VisNoSlash { input string
continue }{
} {"Empty", ""},
{"Plain", "hello world"},
{"Backslash", "THIS\\IS_A_TEST1234"},
{"Punctuation", "this.is.a.normal_string"},
{"Unicode1", "AC_Ra\u00edz_Certic\u00e1mara_S.A..pem"},
{"Unicode2", "NetLock_Arany_=Class_Gold=_F\u0151tan\u00fas\u00edtv\u00e1ny.pem"},
{"Unicode3", "T\u00dcB\u0130TAK_UEKAE_K\u00f6k_Sertifika_Hizmet_Sa\u011flay\u0131c\u0131s\u0131_-_S\u00fcr\u00fcm_3.pem"},
{"ExtraPunctuation", "hello world [ this string needs=enco ding! ]"},
{"Whitespace", "even \n more encoding necessary\a\a "},
{"WeirdChars", "\024 <-- some more weird characters --> \u4f60\u597d\uff0c\u4e16\u754c"},
{"DoubleEncoding", "\\xff\\n double encoding is also great fun \\x"},
{"Unicode1-Encoded", "AC_Ra\\M-C\\M--z_Certic\\M-C\\M-!mara_S.A..pem"},
{"Rand1", "z^i3i$\u00d3\u008anqgh5/t\u00e5<86>\u00b2kzla\\e^lv\u00df\u0093nv\u00df\u00aea|3}\u00d8\u0088\u00d6\u0084"},
{"Rand2", `z^i3i$\M-C\M^S\M-B\M^Jnqgh5/t\M-C\M-%<86>\M-B\M-2kzla\\e^lv\M-C\M^_\M-B\M^Snv\M-C\M^_\M-B\M-.a|3}\M-C\M^X\M-B\M^H\M-C\M^V\M-B\M^D`},
{"Rand3", "@?e1xs+.R_Kjo]7s8pgRP:*nXCE4{!c"},
{"Rand4", "62_\u00c6\u00c62\u00ae\u00b7m\u00db\u00c3r^\u00bfp\u00c6u'q\u00fbc2\u00f0u\u00b8\u00dd\u00e8v\u00ff\u00b0\u00dc\u00c2\u00f53\u00db-k\u00f2sd4\\p\u00da\u00a6\u00d3\u00eea<\u00e6s{\u00a0p\u00f0\u00ffj\u00e0\u00e8\u00b8\u00b8\u00bc\u00fcb"},
{"Rand4-Encoded", `62_\M-C\M^F\M-C\M^F2\M-B\M-.\M-B\M-7m\M-C\M^[\M-C\M^Cr^\M-B\M-?p\M-C\M^Fu'q\M-C\M-;c2\M-C\M-0u\M-B\M-8\M-C\M^]\M-C\M-(v\M-C\M-?\M-B\M-0\M-C\M^\\M-C\M^B\M-C\M-53\M-C\M^[-k\M-C\M-2sd4\\p\M-C\M^Z\M-B\M-&\M-C\M^S\M-C\M-.a<\M-C\M-&s{\M-B\240p\M-C\M-0\M-C\M-?j\M-C\240\M-C\M-(\M-B\M-8\M-B\M-8\M-B\M-<\M-C\M-<b`},
{"Rand5", "\u9003\"9v1)T798|o;fly jnKX\u0489Be="},
{"Rand5-Encoded", `\M-i\M^@\M^C"9v1)T798|o;fly jnKX\M-R\M^IBe=`},
{"Rand6", "'3Ze\u050e|\u02del\u069du-Rpct4+Z5b={@_{b"},
{"Rand6-Encoded", `'3Ze\M-T\M^N|\M-K\M^^l\M-Z\M^]u-Rpct4+Z5b={@_{b`},
{"Rand7", "1\u00c6\u00abTcz+Vda?)k1%\\\"P;`po`h"},
{"Rand7-Encoded", `1%C3%86%C2%ABTcz+Vda%3F)k1%25%5C%22P%3B%60po%60h`},
} {
t.Run(test.name, func(t *testing.T) {
for flag := VisFlag(0); flag <= visMask; flag++ {
// VisNoSlash is frankly just a dumb flag, and it is impossible for us
// to actually preserve things in a round-trip.
if flag&VisNoSlash == VisNoSlash {
continue
}
// Round-trip testing. enc, err := Vis(test.input, flag)
for _, test := range []string{ require.NoErrorf(t, err, "vis(%q, %s)", test.input, flag)
"",
"hello world", dec, err := Unvis(enc, flag)
"THIS\\IS_A_TEST1234", require.NoErrorf(t, err, "unvis(%q, %s)", enc, flag)
"this.is.a.normal_string",
"AC_Ra\u00edz_Certic\u00e1mara_S.A..pem", if !assert.Equalf(t, test.input, dec, "unvis(vis(%q, %b) = %q, %b) round-trip", test, flag, enc, flag) {
"NetLock_Arany_=Class_Gold=_F\u0151tan\u00fas\u00edtv\u00e1ny.pem", continue
"T\u00dcB\u0130TAK_UEKAE_K\u00f6k_Sertifika_Hizmet_Sa\u011flay\u0131c\u0131s\u0131_-_S\u00fcr\u00fcm_3.pem", }
"hello world [ this string needs=enco ding! ]",
"even \n more encoding necessary\a\a ",
"\024 <-- some more weird characters --> \u4f60\u597d\uff0c\u4e16\u754c",
"\\xff\\n double encoding is also great fun \\x",
"AC_Ra\\M-C\\M--z_Certic\\M-C\\M-!mara_S.A..pem",
"z^i3i$\u00d3\u008anqgh5/t\u00e5<86>\u00b2kzla\\e^lv\u00df\u0093nv\u00df\u00aea|3}\u00d8\u0088\u00d6\u0084",
`z^i3i$\M-C\M^S\M-B\M^Jnqgh5/t\M-C\M-%<86>\M-B\M-2kzla\\e^lv\M-C\M^_\M-B\M^Snv\M-C\M^_\M-B\M-.a|3}\M-C\M^X\M-B\M^H\M-C\M^V\M-B\M^D`,
"@?e1xs+.R_Kjo]7s8pgRP:*nXCE4{!c",
"62_\u00c6\u00c62\u00ae\u00b7m\u00db\u00c3r^\u00bfp\u00c6u'q\u00fbc2\u00f0u\u00b8\u00dd\u00e8v\u00ff\u00b0\u00dc\u00c2\u00f53\u00db-k\u00f2sd4\\p\u00da\u00a6\u00d3\u00eea<\u00e6s{\u00a0p\u00f0\u00ffj\u00e0\u00e8\u00b8\u00b8\u00bc\u00fcb",
`62_\M-C\M^F\M-C\M^F2\M-B\M-.\M-B\M-7m\M-C\M^[\M-C\M^Cr^\M-B\M-?p\M-C\M^Fu'q\M-C\M-;c2\M-C\M-0u\M-B\M-8\M-C\M^]\M-C\M-(v\M-C\M-?\M-B\M-0\M-C\M^\\M-C\M^B\M-C\M-53\M-C\M^[-k\M-C\M-2sd4\\p\M-C\M^Z\M-B\M-&\M-C\M^S\M-C\M-.a<\M-C\M-&s{\M-B\240p\M-C\M-0\M-C\M-?j\M-C\240\M-C\M-(\M-B\M-8\M-B\M-8\M-B\M-<\M-C\M-<b`,
"\u9003\"9v1)T798|o;fly jnKX\u0489Be=",
`\M-i\M^@\M^C"9v1)T798|o;fly jnKX\M-R\M^IBe=`,
"'3Ze\u050e|\u02del\u069du-Rpct4+Z5b={@_{b",
`'3Ze\M-T\M^N|\M-K\M^^l\M-Z\M^]u-Rpct4+Z5b={@_{b`,
"1\u00c6\u00abTcz+Vda?)k1%\\\"P;`po`h",
`1%C3%86%C2%ABTcz+Vda%3F)k1%25%5C%22P%3B%60po%60h`,
} {
enc, err := Vis(test, flag)
if err != nil {
t.Errorf("unexpected error doing vis(%q, %b): %s", test, flag, err)
continue
} }
dec, err := Unvis(enc, flag) })
if err != nil {
t.Errorf("unexpected error doing unvis(%q, %b): %s", enc, flag, err)
continue
}
if dec != test {
t.Errorf("roundtrip failed: unvis(vis(%q, %b) = %q, %b) = %q", test, flag, enc, flag, dec)
}
}
} }
} }
@ -162,33 +158,78 @@ func TestByteStrings(t *testing.T) {
// identical but bit-stream non-identical strings (causing much confusion // identical but bit-stream non-identical strings (causing much confusion
// when trying to access such files). // when trying to access such files).
for _, test := range [][]byte{ for _, test := range []struct {
[]byte("This is a man in business suit levitating: \U0001f574"), name string
{0x7f, 0x17, 0x01, 0x33}, input []byte
}{
{"Unicode", []byte("This is a man in business suit levitating: \U0001f574")},
{"ASCII", []byte{0x7f, 0x17, 0x01, 0x33}},
// TODO: Test arbitrary byte streams like the one below. Currently this // TODO: Test arbitrary byte streams like the one below. Currently this
// fails because Vis() is messing around with it (converting it // fails because Vis() is messing around with it (converting it
// to a rune and spacing it out). // to a rune and spacing it out).
//{'\xef', '\xae', 'h', '\077', 'k'}, //{'\xef', '\xae', 'h', '\077', 'k'},
} { } {
testString := string(test) t.Run(test.name, func(t *testing.T) {
enc, err := Vis(testString, DefaultVisFlags) testString := string(test.input)
if err != nil {
t.Errorf("unexpected error doing vis(%q): %s", test, err)
continue
}
dec, err := Unvis(enc, DefaultVisFlags)
if err != nil {
t.Errorf("unexpected error doing unvis(%q): %s", enc, err)
continue
}
decBytes := []byte(dec)
if dec != testString { enc, err := Vis(testString, DefaultVisFlags)
t.Errorf("roundtrip failed [string comparison]: unvis(vis(%q) = %q) = %q", test, enc, dec) require.NoErrorf(t, err, "vis(%q)", testString)
}
if !bytes.Equal(decBytes, test) { dec, err := Unvis(enc, DefaultVisFlags)
t.Errorf("roundtrip failed [byte comparison]: unvis(vis(%q) = %q) = %q", test, enc, dec) require.NoErrorf(t, err, "unvis(%q)", enc)
}
assert.Equalf(t, testString, dec, "unvis(vis(%q) = %q) round-trip", testString, enc)
assert.Equalf(t, test.input, []byte(dec), "unvis(vis(%q) = %q) round-trip", testString, enc)
})
} }
}
func TestVisFlagsString(t *testing.T) {
for _, test := range []struct {
name string
flags VisFlag
expected string
}{
{"Empty", VisFlag(0), "0"},
// Single valid flags.
{"Single-VisOctal", VisOctal, "VisOctal"},
{"Single-VisCStyle", VisCStyle, "VisCStyle"},
{"Single-VisSpace", VisSpace, "VisSpace"},
{"Single-VisTab", VisTab, "VisTab"},
{"Single-VisNewline", VisNewline, "VisNewline"},
{"Single-VisSafe", VisSafe, "VisSafe"},
{"Single-VisNoSlash", VisNoSlash, "VisNoSlash"},
{"Single-VisHTTPStyle", VisHTTPStyle, "VisHTTPStyle"},
{"Single-VisGlob", VisGlob, "VisGlob"},
// Invalid flag value.
{"Unknown", VisFlag(0xff000), "0xff000"},
// Multiple flag values.
{"VisWhite", VisWhite, "VisSpace|VisTab|VisNewline"},
{"DefaultVisFlags", DefaultVisFlags, "VisOctal|VisSpace|VisTab|VisNewline|VisGlob"},
{"Multiple-Valid", VisOctal | VisCStyle | VisNoSlash | VisHTTPStyle, "VisOctal|VisCStyle|VisNoSlash|VisHTTPStyle"},
{"Multiple-Mixed", VisOctal | VisSafe | 0xbeef0000, "VisOctal|VisSafe|0xbeef0000"},
} {
t.Run(test.name, func(t *testing.T) {
got := test.flags.String()
assert.Equal(t, test.expected, got, "VisFlag(%+x).String")
})
}
}
func TestUnknownVisFlagsError(t *testing.T) {
t.Run("Vis", func(t *testing.T) {
enc, err := Vis("dummy text", visMask+1)
require.Error(t, err, "Vis with invalid flags should fail")
assert.ErrorIs(t, err, errUnknownVisFlagsError, "Vis with invalid flags")
assert.ErrorContains(t, err, "contains unknown or unsupported flags", "Vis with invalid flags")
assert.Equal(t, "", enc, "error Vis should return empty string")
})
t.Run("Unvis", func(t *testing.T) {
dec, err := Unvis("dummy text", visMask+1)
require.Error(t, err, "Unvis with invalid flags should fail")
assert.ErrorIs(t, err, errUnknownVisFlagsError, "Unvis with invalid flags")
assert.ErrorContains(t, err, "contains unknown or unsupported flags", "Vis with invalid flags")
assert.Equal(t, "", dec, "error Unvis should return empty string")
})
} }

View file

@ -1,6 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
/* /*
* govis: unicode aware vis(3) encoding implementation * govis: unicode aware vis(3) encoding implementation
* Copyright (C) 2017 SUSE LLC. * Copyright (C) 2017-2025 SUSE LLC.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,41 +19,75 @@
package govis package govis
import ( import (
"errors"
"fmt" "fmt"
"strconv" "strconv"
"strings"
"unicode" "unicode"
) )
var (
errEndOfString = errors.New("unexpectedly reached end of string")
errUnknownEscapeChar = errors.New("unknown escape character")
errOutsideLatin1 = errors.New("outside latin-1 encoding")
errParseDigit = errors.New("could not parse digit")
)
// unvisParser stores the current state of the token parser. // unvisParser stores the current state of the token parser.
type unvisParser struct { type unvisParser struct {
output *strings.Builder
tokens []rune tokens []rune
idx int idx int
flag VisFlag flags VisFlag
} }
// Next moves the index to the next character. // Input resets the parser with a new input string.
func (p *unvisParser) Next() { func (p *unvisParser) Input(input string) {
p.output = new(strings.Builder)
p.output.Grow(len(input)) // the output will be at most input-sized
p.tokens = []rune(input)
p.idx = 0
}
// Output returns the internal [strings.Builder].
func (p *unvisParser) Output() *strings.Builder {
return p.output
}
// Step moves the index to the next character.
func (p *unvisParser) Step() {
p.idx++ p.idx++
} }
// Peek gets the current token. // Peek gets the current token.
func (p *unvisParser) Peek() (rune, error) { func (p *unvisParser) Peek() (rune, error) {
if p.idx >= len(p.tokens) { if p.idx >= len(p.tokens) {
return unicode.ReplacementChar, fmt.Errorf("tried to read past end of token list") return unicode.ReplacementChar, errEndOfString
} }
return p.tokens[p.idx], nil return p.tokens[p.idx], nil
} }
// Next moves the index to the next character and returns said character.
func (p *unvisParser) Next() (rune, error) {
ch, err := p.Peek()
if err == nil {
p.Step()
}
return ch, err
}
// End returns whether all of the tokens have been consumed. // End returns whether all of the tokens have been consumed.
func (p *unvisParser) End() bool { func (p *unvisParser) End() bool {
return p.idx >= len(p.tokens) return p.idx >= len(p.tokens)
} }
func newParser(input string, flag VisFlag) *unvisParser { func newParser(flags VisFlag) *unvisParser {
return &unvisParser{ return &unvisParser{
tokens: []rune(input), output: nil,
tokens: nil,
idx: 0, idx: 0,
flag: flag, flags: flags,
} }
} }
@ -60,8 +95,8 @@ func newParser(input string, flag VisFlag) *unvisParser {
// codes, this is IMO much easier to read than the ugly 80s coroutine code used // codes, this is IMO much easier to read than the ugly 80s coroutine code used
// by the original unvis(3) parser. Here's the EBNF for an unvis sequence: // by the original unvis(3) parser. Here's the EBNF for an unvis sequence:
// //
// <input> ::= (<rune>)* // <input> ::= (<element>)*
// <rune> ::= ("\" <escape-sequence>) | ("%" <escape-hex>) | <plain-rune> // <element> ::= ("\" <escape-sequence>) | ("%" <escape-hex>) | <plain-rune>
// <plain-rune> ::= any rune // <plain-rune> ::= any rune
// <escape-sequence> ::= ("x" <escape-hex>) | ("M" <escape-meta>) | ("^" <escape-ctrl) | <escape-cstyle> | <escape-octal> // <escape-sequence> ::= ("x" <escape-hex>) | ("M" <escape-meta>) | ("^" <escape-ctrl) | <escape-cstyle> | <escape-octal>
// <escape-meta> ::= ("-" <escape-meta1>) | ("^" <escape-ctrl>) // <escape-meta> ::= ("-" <escape-meta1>) | ("^" <escape-ctrl>)
@ -71,71 +106,57 @@ func newParser(input string, flag VisFlag) *unvisParser {
// <escape-hex> ::= [0-9a-f] [0-9a-f] // <escape-hex> ::= [0-9a-f] [0-9a-f]
// <escape-octal> ::= [0-7] ([0-7] ([0-7])?)? // <escape-octal> ::= [0-7] ([0-7] ([0-7])?)?
func unvisPlainRune(p *unvisParser) ([]byte, error) { func (p *unvisParser) plainRune() error {
ch, err := p.Peek() ch, err := p.Next()
if err != nil { if err != nil {
return nil, fmt.Errorf("plain rune: %c", ch) return fmt.Errorf("plain rune: %w", err)
} }
p.Next() _, err = p.output.WriteRune(ch)
return err
// XXX: Maybe we should not be converting to runes and then back to strings
// here. Are we sure that the byte-for-byte representation is the
// same? If the bytes change, then using these strings for paths will
// break...
str := string(ch)
return []byte(str), nil
} }
func unvisEscapeCStyle(p *unvisParser) ([]byte, error) { func (p *unvisParser) escapeCStyle() error {
ch, err := p.Peek() ch, err := p.Next()
if err != nil { if err != nil {
return nil, fmt.Errorf("escape hex: %s", err) return fmt.Errorf("escape cstyle: %w", err)
} }
output := ""
switch ch { switch ch {
case 'n': case 'n':
output = "\n" return p.output.WriteByte('\n')
case 'r': case 'r':
output = "\r" return p.output.WriteByte('\r')
case 'b': case 'b':
output = "\b" return p.output.WriteByte('\b')
case 'a': case 'a':
output = "\x07" return p.output.WriteByte('\x07')
case 'v': case 'v':
output = "\v" return p.output.WriteByte('\v')
case 't': case 't':
output = "\t" return p.output.WriteByte('\t')
case 'f': case 'f':
output = "\f" return p.output.WriteByte('\f')
case 's': case 's':
output = " " return p.output.WriteByte(' ')
case 'E': case 'E':
output = "\x1b" return p.output.WriteByte('\x1b')
case '\n': case '\n', '$':
// Hidden newline. // Hidden newline or marker.
case '$': return nil
// Hidden marker.
default:
// XXX: We should probably allow falling through and return "\" here...
return nil, fmt.Errorf("escape cstyle: unknown escape character: %q", ch)
} }
// XXX: We should probably allow falling through and return "\" here...
p.Next() return fmt.Errorf("escape cstyle: %w %q", errUnknownEscapeChar, ch)
return []byte(output), nil
} }
func unvisEscapeDigits(p *unvisParser, base int, force bool) ([]byte, error) { func (p *unvisParser) escapeDigits(base int, force bool) error {
var code int var code int
for i := int(0xFF); i > 0; i /= base { for i := int(0xFF); i > 0; i /= base {
ch, err := p.Peek() ch, err := p.Peek()
if err != nil { if err != nil {
if !force && i != 0xFF { if !force && i != 0xFF {
break break
} }
return nil, fmt.Errorf("escape base %d: %s", base, err) return fmt.Errorf("escape base %d: %w", base, err)
} }
digit, err := strconv.ParseInt(string(ch), base, 8) digit, err := strconv.ParseInt(string(ch), base, 8)
@ -143,152 +164,134 @@ func unvisEscapeDigits(p *unvisParser, base int, force bool) ([]byte, error) {
if !force && i != 0xFF { if !force && i != 0xFF {
break break
} }
return nil, fmt.Errorf("escape base %d: could not parse digit: %s", base, err) return fmt.Errorf("escape base %d: %w %q: %w", base, errParseDigit, ch, err)
} }
code = (code * base) + int(digit) code = (code * base) + int(digit)
p.Next() p.Step() // only consume token if we use it (length is variable)
} }
if code > unicode.MaxLatin1 { if code > unicode.MaxLatin1 {
return nil, fmt.Errorf("escape base %d: code %q outside latin-1 encoding", base, code) return fmt.Errorf("escape base %d: code %+.2x %w", base, code, errOutsideLatin1)
} }
return p.output.WriteByte(byte(code))
char := byte(code & 0xFF)
return []byte{char}, nil
} }
func unvisEscapeCtrl(p *unvisParser, mask byte) ([]byte, error) { func (p *unvisParser) escapeCtrl(mask byte) error {
ch, err := p.Peek() ch, err := p.Next()
if err != nil { if err != nil {
return nil, fmt.Errorf("escape ctrl: %s", err) return fmt.Errorf("escape ctrl: %w", err)
} }
if ch > unicode.MaxLatin1 { if ch > unicode.MaxLatin1 {
return nil, fmt.Errorf("escape ctrl: code %q outside latin-1 encoding", ch) return fmt.Errorf("escape ctrl: code %q %w", ch, errOutsideLatin1)
} }
char := byte(ch) & 0x1f char := byte(ch) & 0x1f
if ch == '?' { if ch == '?' {
char = 0x7f char = 0x7f
} }
return p.output.WriteByte(mask | char)
p.Next()
return []byte{mask | char}, nil
} }
func unvisEscapeMeta(p *unvisParser) ([]byte, error) { func (p *unvisParser) escapeMeta() error {
ch, err := p.Peek() ch, err := p.Next()
if err != nil { if err != nil {
return nil, fmt.Errorf("escape meta: %s", err) return fmt.Errorf("escape meta: %w", err)
} }
mask := byte(0x80) mask := byte(0x80)
switch ch { switch ch {
case '^': case '^':
// The same as "\^..." except we apply a mask. // The same as "\^..." except we apply a mask.
p.Next() return p.escapeCtrl(mask)
return unvisEscapeCtrl(p, mask)
case '-': case '-':
p.Next() ch, err := p.Next()
ch, err := p.Peek()
if err != nil { if err != nil {
return nil, fmt.Errorf("escape meta1: %s", err) return fmt.Errorf("escape meta1: %w", err)
} }
if ch > unicode.MaxLatin1 { if ch > unicode.MaxLatin1 {
return nil, fmt.Errorf("escape meta1: code %q outside latin-1 encoding", ch) return fmt.Errorf("escape meta1: code %q %w", ch, errOutsideLatin1)
} }
// Add mask to character. // Add mask to character.
p.Next() return p.output.WriteByte(mask | byte(ch))
return []byte{mask | byte(ch)}, nil
} }
return nil, fmt.Errorf("escape meta: unknown escape char: %s", err) return fmt.Errorf("escape meta: %w %q", errUnknownEscapeChar, ch)
} }
func unvisEscapeSequence(p *unvisParser) ([]byte, error) { func (p *unvisParser) escapeSequence() error {
ch, err := p.Peek() ch, err := p.Peek()
if err != nil { if err != nil {
return nil, fmt.Errorf("escape sequence: %s", err) return fmt.Errorf("escape sequence: %w", err)
} }
switch ch { switch ch {
case '\\': case '\\', '"':
p.Next() p.Step()
return []byte("\\"), nil return p.output.WriteByte(byte(ch))
case '0', '1', '2', '3', '4', '5', '6', '7': case '0', '1', '2', '3', '4', '5', '6', '7':
return unvisEscapeDigits(p, 8, false) return p.escapeDigits(8, false)
case 'x': case 'x':
p.Next() p.Step()
return unvisEscapeDigits(p, 16, true) return p.escapeDigits(16, true)
case '^': case '^':
p.Next() p.Step()
return unvisEscapeCtrl(p, 0x00) return p.escapeCtrl(0x00)
case 'M': case 'M':
p.Next() p.Step()
return unvisEscapeMeta(p) return p.escapeMeta()
default: default:
return unvisEscapeCStyle(p) return p.escapeCStyle()
} }
} }
func unvisRune(p *unvisParser) ([]byte, error) { func (p *unvisParser) element() error {
ch, err := p.Peek() ch, err := p.Peek()
if err != nil { if err != nil {
return nil, fmt.Errorf("rune: %s", err) return err
} }
switch ch { switch ch {
case '\\': case '\\':
p.Next() p.Step()
return unvisEscapeSequence(p) return p.escapeSequence()
case '%': case '%':
// % HEX HEX only applies to HTTPStyle encodings. // % HEX HEX only applies to HTTPStyle encodings.
if p.flag&VisHTTPStyle == VisHTTPStyle { if p.flags&VisHTTPStyle == VisHTTPStyle {
p.Next() p.Step()
return unvisEscapeDigits(p, 16, true) return p.escapeDigits(16, true)
} }
fallthrough
default:
return unvisPlainRune(p)
} }
return p.plainRune()
} }
func unvis(p *unvisParser) (string, error) { func (p *unvisParser) unvis(input string) (string, error) {
var output []byte p.Input(input)
for !p.End() { for !p.End() {
ch, err := unvisRune(p) if err := p.element(); err != nil {
if err != nil { return "", err
return "", fmt.Errorf("input: %s", err)
} }
output = append(output, ch...)
} }
return string(output), nil return p.Output().String(), nil
} }
// Unvis takes a string formatted with the given Vis flags (though only the // Unvis takes a string formatted with the given Vis flags (though only the
// VisHTTPStyle flag is checked) and output the un-encoded version of the // VisHTTPStyle flag is checked) and output the un-encoded version of the
// encoded string. An error is returned if any escape sequences in the input // encoded string. An error is returned if any escape sequences in the input
// string were invalid. // string were invalid.
func Unvis(input string, flag VisFlag) (string, error) { func Unvis(input string, flags VisFlag) (string, error) {
// TODO: Check all of the VisFlag bits. if unknown := flags &^ visMask; unknown != 0 {
p := newParser(input, flag) return "", unknownVisFlagsError{flags: flags}
output, err := unvis(p)
if err != nil {
return "", fmt.Errorf("unvis: %s", err)
} }
if !p.End() { p := newParser(flags)
return "", fmt.Errorf("unvis: trailing characters at end of input") output, err := p.unvis(input)
if err != nil {
return "", fmt.Errorf("unvis '%s': %w", input, err)
} }
return output, nil return output, nil
} }

View file

@ -1,6 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
/* /*
* govis: unicode aware vis(3) encoding implementation * govis: unicode aware vis(3) encoding implementation
* Copyright (C) 2017 SUSE LLC. * Copyright (C) 2017-2025 SUSE LLC.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,20 +19,43 @@
package govis package govis
import ( import (
"crypto/rand"
"strconv"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestUnvisError(t *testing.T) { func TestUnvisError(t *testing.T) {
for _, test := range []string{ for _, test := range []struct {
// Octal escape codes allow you to specify invalid byte values. input string
"\\777", err error
"\\420\\322\\455", }{
"\\652\\233", // Octal escape codes allow you to specify invalid ASCII values.
{"\\777", errOutsideLatin1},
{"\\420\\322\\455", errOutsideLatin1},
{"\\652\\233", errOutsideLatin1},
// Escapes that end abruptly.
{"\\", errEndOfString},
{"\\J", errUnknownEscapeChar},
{"a bad slash: \\", errEndOfString},
{"testing -- \\x", errEndOfString},
{"\\xG0 test", strconv.ErrSyntax},
{" abc \\Mx", errUnknownEscapeChar},
{"\\Mx", errUnknownEscapeChar},
{"\\M-", errEndOfString},
{"\\M-\u5000", errOutsideLatin1},
{"\\M^", errEndOfString},
{"\\^", errEndOfString},
{"\\^\u5000", errOutsideLatin1},
{"\\M", errEndOfString},
} { } {
got, err := Unvis(test, DefaultVisFlags) t.Run(test.input, func(t *testing.T) {
if err == nil { _, err := Unvis(test.input, DefaultVisFlags)
t.Errorf("expected unvis(%q) to give an error, got %q", test, got) require.Errorf(t, err, "invalid escape string should give an error")
} assert.ErrorIs(t, err, test.err, "unexpected error from invalid escape string")
})
} }
} }
@ -41,25 +65,23 @@ func TestUnvisCStyleEscape(t *testing.T) {
expected string expected string
}{ }{
{"", ""}, {"", ""},
{"\\n\\v\\t\\s", "\n\v\t "}, {`\n\v\t\s`, "\n\v\t "},
{"\\\\n\\tt", "\\n\tt"}, {`\\n\tt`, "\\n\tt"},
{"\\b", "\b"}, {`\b`, "\b"},
{"\\r\\b\\n", "\r\b\n"}, {`\r\b\n`, "\r\b\n"},
{"\\a\\a\\b", "\x07\x07\b"}, {`\a\a\b`, "\x07\x07\b"},
{"\\f\\s\\E", "\f \x1b"}, {`\f\s\E`, "\f \x1b"},
{`\"foo\"\\"bar`, `"foo"\"bar`},
// Hidden markers. They actually aren't generated by vis(3) but for // Hidden markers. They actually aren't generated by vis(3) but for
// some reason, they're supported... // some reason, they're supported...
{"test\\\ning", "testing"}, {"test\\\ning", "testing"},
{"test\\$\\$ing", "testing"}, {"test\\$\\$ing", "testing"},
} { } {
got, err := Unvis(test.input, DefaultVisFlags) t.Run(test.input, func(t *testing.T) {
if err != nil { got, err := Unvis(test.input, DefaultVisFlags)
t.Errorf("unexpected error doing unvis(%q): %q", test.input, err) require.NoErrorf(t, err, "unvis(%q)", test.input)
continue assert.Equal(t, test.expected, got, "unvis(%q)", test.input)
} })
if got != test.expected {
t.Errorf("expected unvis(%q) = %q, got %q", test.input, test.expected, got)
}
} }
} }
@ -76,14 +98,11 @@ func TestUnvisMetaEscape(t *testing.T) {
// TODO: Add some more of these tests, but I need to have some // TODO: Add some more of these tests, but I need to have some
// secondary source to verify these outputs properly. // secondary source to verify these outputs properly.
} { } {
got, err := Unvis(test.input, DefaultVisFlags) t.Run(test.input, func(t *testing.T) {
if err != nil { got, err := Unvis(test.input, DefaultVisFlags)
t.Errorf("unexpected error doing unvis(%q): %q", test.input, err) require.NoErrorf(t, err, "unvis(%q)", test.input)
continue assert.Equal(t, test.expected, got, "unvis(%q)", test.input)
} })
if got != test.expected {
t.Errorf("expected unvis(%q) = %q, got %q", test.input, test.expected, got)
}
} }
} }
@ -106,14 +125,11 @@ func TestUnvisOctalEscape(t *testing.T) {
// Some invalid characters... // Some invalid characters...
{"\\377\\2\\225\\264", "\xff\x02\x95\xb4"}, {"\\377\\2\\225\\264", "\xff\x02\x95\xb4"},
} { } {
got, err := Unvis(test.input, DefaultVisFlags) t.Run(test.input, func(t *testing.T) {
if err != nil { got, err := Unvis(test.input, DefaultVisFlags)
t.Errorf("unexpected error doing unvis(%q): %q", test.input, err) require.NoErrorf(t, err, "unvis(%q)", test.input)
continue assert.Equal(t, test.expected, got, "unvis(%q)", test.input)
} })
if got != test.expected {
t.Errorf("expected unvis(%q) = %q, got %q", test.input, test.expected, got)
}
} }
} }
@ -134,14 +150,11 @@ func TestUnvisHexEscape(t *testing.T) {
// Some invalid characters... // Some invalid characters...
{"\\xff\\x02\\x95\\xb4", "\xff\x02\x95\xb4"}, {"\\xff\\x02\\x95\\xb4", "\xff\x02\x95\xb4"},
} { } {
got, err := Unvis(test.input, DefaultVisFlags) t.Run(test.input, func(t *testing.T) {
if err != nil { got, err := Unvis(test.input, DefaultVisFlags)
t.Errorf("unexpected error doing unvis(%q): %q", test.input, err) require.NoErrorf(t, err, "unvis(%q)", test.input)
continue assert.Equal(t, test.expected, got, "unvis(%q)", test.input)
} })
if got != test.expected {
t.Errorf("expected unvis(%q) = %q, got %q", test.input, test.expected, got)
}
} }
} }
@ -154,13 +167,63 @@ func TestUnvisUnicode(t *testing.T) {
"NetLock_Arany_=Class_Gold=_Főtanúsítvány.pem", "NetLock_Arany_=Class_Gold=_Főtanúsítvány.pem",
"TÜBİTAK_UEKAE_Kök_Sertifika_Hizmet_Sağlayıcısı_-_Sürüm_3.pem", "TÜBİTAK_UEKAE_Kök_Sertifika_Hizmet_Sağlayıcısı_-_Sürüm_3.pem",
} { } {
got, err := Unvis(test, DefaultVisFlags) t.Run(test, func(t *testing.T) {
if err != nil { enc, err := Unvis(test, DefaultVisFlags)
t.Errorf("unexpected error doing unvis(%q): %s", test, err) require.NoErrorf(t, err, "unvis(%q)", test)
continue assert.Equalf(t, test, enc, "decoding of %q should be the same as original", test)
} })
if got != test {
t.Errorf("expected %q to be unchanged, got %q", test, got)
}
} }
} }
func BenchmarkUnvis(b *testing.B) {
doBench := func(b *testing.B, text string) {
encoded, err := Vis(text, DefaultVisFlags)
require.NoErrorf(b, err, "vis(%q)", text)
decoded, err := Unvis(encoded, DefaultVisFlags)
require.NoErrorf(b, err, "unvis(vis(%q) = %q)", text, encoded)
require.Equalf(b, text, decoded, "unvis(vis(%q) = %q)", text, encoded)
for b.Loop() {
_, _ = Unvis(encoded, DefaultVisFlags)
}
}
b.Run("NoChange", func(b *testing.B) {
text := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
doBench(b, text)
})
b.Run("Binary", func(b *testing.B) {
var data [32]byte
n, err := rand.Read(data[:])
require.NoError(b, err, "rand.Read")
require.Equal(b, len(data), n, "rand.Read len return")
text := string(data[:])
doBench(b, text)
})
// The rest of these test strings come from a set of test strings collated
// in <https://www.w3.org/2001/06/utf-8-test/quickbrown.html>.
b.Run("ASCII", func(b *testing.B) {
text := "The quick brown fox jumps over the lazy dog."
doBench(b, text)
})
b.Run("German", func(b *testing.B) {
text := "Falsches Üben von Xylophonmusik quält jeden größeren Zwerg"
doBench(b, text)
})
b.Run("Russian", func(b *testing.B) {
text := "В чащах юга жил бы цитрус? Да, но фальшивый экземпляр!"
doBench(b, text)
})
b.Run("Japanese", func(b *testing.B) {
text := "いろはにほへとちりぬるをイロハニホヘトチリヌルヲ"
doBench(b, text)
})
}

View file

@ -1,6 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
/* /*
* govis: unicode aware vis(3) encoding implementation * govis: unicode aware vis(3) encoding implementation
* Copyright (C) 2017 SUSE LLC. * Copyright (C) 2017-2025 SUSE LLC.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -19,27 +20,29 @@ package govis
import ( import (
"fmt" "fmt"
"strings"
"unicode" "unicode"
) )
func isunsafe(ch rune) bool { var maxAscii byte = unicode.MaxASCII // 0x7f
func isunsafe(ch byte) bool {
return ch == '\b' || ch == '\007' || ch == '\r' return ch == '\b' || ch == '\007' || ch == '\r'
} }
func isglob(ch rune) bool { func isglob(ch byte) bool {
return ch == '*' || ch == '?' || ch == '[' || ch == '#' return ch == '*' || ch == '?' || ch == '[' || ch == '#'
} }
// ishttp is defined by RFC 1808. // ishttp is defined by RFC 1808.
func ishttp(ch rune) bool { func ishttp(ch byte) bool {
// RFC1808 does not really consider characters outside of ASCII, so just to // RFC1808 does not really consider characters outside of ASCII, so just to
// be safe always treat characters outside the ASCII character set as "not // be safe always treat characters outside the ASCII character set as "not
// HTTP". // HTTP".
if ch > unicode.MaxASCII { if ch > maxAscii {
return false return false
} }
return unicode.IsDigit(rune(ch)) || unicode.IsLetter(rune(ch)) ||
return unicode.IsDigit(ch) || unicode.IsLetter(ch) ||
// Safe characters. // Safe characters.
ch == '$' || ch == '-' || ch == '_' || ch == '.' || ch == '+' || ch == '$' || ch == '-' || ch == '_' || ch == '.' || ch == '+' ||
// Extra characters. // Extra characters.
@ -47,8 +50,13 @@ func ishttp(ch rune) bool {
ch == ')' || ch == ',' ch == ')' || ch == ','
} }
func isgraph(ch rune) bool { func isgraph(ch byte) bool {
return unicode.IsGraphic(ch) && !unicode.IsSpace(ch) && ch <= unicode.MaxASCII return ch <= maxAscii &&
unicode.IsGraphic(rune(ch)) && !unicode.IsSpace(rune(ch))
}
func isctrl(ch byte) bool {
return unicode.IsControl(rune(ch))
} }
// vis converts a single *byte* into its encoding. While Go supports the // vis converts a single *byte* into its encoding. While Go supports the
@ -58,71 +66,76 @@ func isgraph(ch rune) bool {
// the plus side this is actually a benefit on the encoding side (it will // the plus side this is actually a benefit on the encoding side (it will
// always work with the simple unvis(3) implementation). It also means that we // always work with the simple unvis(3) implementation). It also means that we
// don't have to worry about different multi-byte encodings. // don't have to worry about different multi-byte encodings.
func vis(b byte, flag VisFlag) (string, error) { func vis(output *strings.Builder, ch byte, flag VisFlag) {
// Treat the single-byte character as a rune.
ch := rune(b)
// XXX: This is quite a horrible thing to support. // XXX: This is quite a horrible thing to support.
if flag&VisHTTPStyle == VisHTTPStyle { if flag&VisHTTPStyle == VisHTTPStyle && !ishttp(ch) {
if !ishttp(ch) { _, _ = fmt.Fprintf(output, "%%%.2X", ch)
return "%" + fmt.Sprintf("%.2X", ch), nil return
}
} }
// Figure out if the character doesn't need to be encoded. Effectively, we // Figure out if the character doesn't need to be encoded. Effectively, we
// encode most "normal" (graphical) characters as themselves unless we have // encode most "normal" (graphical) characters as themselves unless we have
// been specifically asked not to. Note though that we *ALWAYS* encode // been specifically asked not to.
// everything outside ASCII. switch {
// TODO: Switch this to much more logical code. case ch > maxAscii:
// We must *always* encode stuff characters not in ASCII.
if ch > unicode.MaxASCII { case flag&VisGlob == VisGlob && isglob(ch):
/* ... */ // Glob characters are graphical but can be forced to be encoded.
} else if flag&VisGlob == VisGlob && isglob(ch) { case flag&VisNoSlash == 0 && ch == '\\',
/* ... */ flag&VisDoubleQuote == VisDoubleQuote && ch == '"':
} else if isgraph(ch) || // Prefix \ if applicable.
(flag&VisSpace != VisSpace && ch == ' ') || _ = output.WriteByte('\\')
(flag&VisTab != VisTab && ch == '\t') || fallthrough
(flag&VisNewline != VisNewline && ch == '\n') || case isgraph(ch),
(flag&VisSafe != 0 && isunsafe(ch)) { flag&VisSpace != VisSpace && ch == ' ',
flag&VisTab != VisTab && ch == '\t',
encoded := string(ch) flag&VisNewline != VisNewline && ch == '\n',
if ch == '\\' && flag&VisNoSlash == 0 { flag&VisSafe != 0 && isunsafe(ch):
encoded += "\\" _ = output.WriteByte(ch)
} return
return encoded, nil
} }
// Try to use C-style escapes first. // Try to use C-style escapes first.
if flag&VisCStyle == VisCStyle { if flag&VisCStyle == VisCStyle {
switch ch { switch ch {
case ' ': case ' ':
return "\\s", nil _, _ = output.WriteString("\\s")
return
case '\n': case '\n':
return "\\n", nil _, _ = output.WriteString("\\n")
return
case '\r': case '\r':
return "\\r", nil _, _ = output.WriteString("\\r")
return
case '\b': case '\b':
return "\\b", nil _, _ = output.WriteString("\\b")
return
case '\a': case '\a':
return "\\a", nil _, _ = output.WriteString("\\a")
return
case '\v': case '\v':
return "\\v", nil _, _ = output.WriteString("\\v")
return
case '\t': case '\t':
return "\\t", nil _, _ = output.WriteString("\\t")
return
case '\f': case '\f':
return "\\f", nil _, _ = output.WriteString("\\f")
return
case '\x00': case '\x00':
// Output octal just to be safe. // Output octal just to be safe.
return "\\000", nil _, _ = output.WriteString("\\000")
return
} }
} }
// For graphical characters we generate octal output (and also if it's // For graphical characters we generate octal output (and also if it's
// being forced by the caller's flags). Also spaces should always be // being forced by the caller's flags). Also spaces should always be
// encoded as octal. // encoded as octal (note that ' '|0x80 == '\xa0' is a non-breaking space).
if flag&VisOctal == VisOctal || isgraph(ch) || ch&0x7f == ' ' { if flag&VisOctal == VisOctal || isgraph(ch) || ch&0x7f == ' ' {
// Always output three-character octal just to be safe. // Always output three-character octal just to be safe.
return fmt.Sprintf("\\%.3o", ch), nil _, _ = fmt.Fprintf(output, "\\%.3o", ch)
return
} }
// Now we have to output meta or ctrl escapes. As far as I can tell, this // Now we have to output meta or ctrl escapes. As far as I can tell, this
@ -130,48 +143,40 @@ func vis(b byte, flag VisFlag) (string, error) {
// copied from the original vis(3) implementation. Hopefully nobody // copied from the original vis(3) implementation. Hopefully nobody
// actually relies on this (octal and hex are better). // actually relies on this (octal and hex are better).
encoded := ""
if flag&VisNoSlash == 0 { if flag&VisNoSlash == 0 {
encoded += "\\" _ = output.WriteByte('\\')
} }
// Meta characters have 0x80 set, but are otherwise identical to control // Meta characters have 0x80 set, but are otherwise identical to control
// characters. // characters.
if b&0x80 != 0 { if ch&0x80 != 0 {
b &= 0x7f ch &= 0x7f
encoded += "M" _ = output.WriteByte('M')
} }
if isctrl(ch) {
if unicode.IsControl(rune(b)) { _ = output.WriteByte('^')
encoded += "^" if ch == 0x7f {
if b == 0x7f { _ = output.WriteByte('?')
encoded += "?"
} else { } else {
encoded += fmt.Sprintf("%c", b+'@') _ = output.WriteByte(ch + '@')
} }
} else { } else {
encoded += fmt.Sprintf("-%c", b) _ = output.WriteByte('-')
_ = output.WriteByte(ch)
} }
return encoded, nil
} }
// Vis encodes the provided string to a BSD-compatible encoding using BSD's // Vis encodes the provided string to a BSD-compatible encoding using BSD's
// vis() flags. However, it will correctly handle multi-byte encoding (which is // vis() flags. However, it will correctly handle multi-byte encoding (which is
// not done properly by BSD's vis implementation). // not done properly by BSD's vis implementation).
func Vis(src string, flag VisFlag) (string, error) { func Vis(src string, flags VisFlag) (string, error) {
if flag&visMask != flag { if unknown := flags &^ visMask; unknown != 0 {
return "", fmt.Errorf("vis: flag %q contains unknown or unsupported flags", flag) return "", unknownVisFlagsError{flags: flags}
} }
var output strings.Builder
output := "" output.Grow(len(src)) // vis() will always take up at least len(src) bytes
for _, ch := range []byte(src) { for _, ch := range []byte(src) {
encodedCh, err := vis(ch, flag) vis(&output, ch, flags)
if err != nil {
return "", err
}
output += encodedCh
} }
return output.String(), nil
return output, nil
} }

View file

@ -1,6 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
/* /*
* govis: unicode aware vis(3) encoding implementation * govis: unicode aware vis(3) encoding implementation
* Copyright (C) 2017 SUSE LLC. * Copyright (C) 2017-2025 SUSE LLC.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,34 +19,38 @@
package govis package govis
import ( import (
"crypto/rand"
"fmt"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestVisUnchanged(t *testing.T) { func TestVisUnchanged(t *testing.T) {
for _, test := range []struct { for _, test := range []struct {
name string
input string input string
flag VisFlag flag VisFlag
}{ }{
{"", DefaultVisFlags}, {"Empty", "", DefaultVisFlags},
{"helloworld", DefaultVisFlags}, {"Plain1", "helloworld", DefaultVisFlags},
{"THIS_IS_A_TEST1234", DefaultVisFlags}, {"Plain2", "THIS_IS_A_TEST1234", DefaultVisFlags},
{"SomeEncodingsAreCool", DefaultVisFlags}, {"Plain3", "SomeEncodingsAreCool", DefaultVisFlags},
{"spaces are totally safe", DefaultVisFlags &^ VisSpace}, {"Spaces", "spaces are totally safe", DefaultVisFlags &^ VisSpace},
{"tabs\tare\talso\tsafe!!", DefaultVisFlags &^ VisTab}, {"Tabs", "tabs\tare\talso\tsafe!!", DefaultVisFlags &^ VisTab},
{"just\a\atrustme\r\b\b!!", DefaultVisFlags | VisSafe}, {"BasicCtrlChars", "just\a\atrustme\r\b\b!!", DefaultVisFlags | VisSafe},
} { } {
enc, err := Vis(test.input, test.flag) t.Run(test.name, func(t *testing.T) {
if err != nil { enc, err := Vis(test.input, test.flag)
t.Errorf("unexpected error with %q: %s", test, err) require.NoErrorf(t, err, "vis(%q, %s)", test.input, test.flag)
} assert.Equalf(t, test.input, enc, "encoding of vis(%q, %s) should be unchanged", test.input, test.flag)
if enc != test.input { })
t.Errorf("expected encoding of %q (flag=%q) to be unchanged, got %q", test.input, test.flag, enc)
}
} }
} }
func TestVisFlags(t *testing.T) { func TestVisFlags(t *testing.T) {
for _, test := range []struct { for idx, test := range []struct {
input string input string
output string output string
flag VisFlag flag VisFlag
@ -99,14 +104,17 @@ func TestVisFlags(t *testing.T) {
{"62_\u00c6\u00c62\u00ae\u00b7m\u00db\u00c3r^\u00bfp\u00c6u'q\u00fbc2\u00f0u\u00b8\u00dd\u00e8v\u00ff\u00b0\u00dc\u00c2\u00f53\u00db-k\u00f2sd4\\p\u00da\u00a6\u00d3\u00eea<\u00e6s{\u00a0p\u00f0\u00ffj\u00e0\u00e8\u00b8\u00b8\u00bc\u00fcb", `62_\303\206\303\2062\302\256\302\267m\303\233\303\203r^\302\277p\303\206u'q\303\273c2\303\260u\302\270\303\235\303\250v\303\277\302\260\303\234\303\202\303\2653\303\233-k\303\262sd4\\p\303\232\302\246\303\223\303\256a<\303\246s{\302\240p\303\260\303\277j\303\240\303\250\302\270\302\270\302\274\303\274b`, VisGlob | VisOctal}, {"62_\u00c6\u00c62\u00ae\u00b7m\u00db\u00c3r^\u00bfp\u00c6u'q\u00fbc2\u00f0u\u00b8\u00dd\u00e8v\u00ff\u00b0\u00dc\u00c2\u00f53\u00db-k\u00f2sd4\\p\u00da\u00a6\u00d3\u00eea<\u00e6s{\u00a0p\u00f0\u00ffj\u00e0\u00e8\u00b8\u00b8\u00bc\u00fcb", `62_\303\206\303\2062\302\256\302\267m\303\233\303\203r^\302\277p\303\206u'q\303\273c2\303\260u\302\270\303\235\303\250v\303\277\302\260\303\234\303\202\303\2653\303\233-k\303\262sd4\\p\303\232\302\246\303\223\303\256a<\303\246s{\302\240p\303\260\303\277j\303\240\303\250\302\270\302\270\302\274\303\274b`, VisGlob | VisOctal},
{"'3Ze\u050e|\u02del\u069du-Rpct4+Z5b={@_{b", `'3Ze\M-T\M^N|\M-K\M^^l\M-Z\M^]u-Rpct4+Z5b={@_{b`, VisGlob}, {"'3Ze\u050e|\u02del\u069du-Rpct4+Z5b={@_{b", `'3Ze\M-T\M^N|\M-K\M^^l\M-Z\M^]u-Rpct4+Z5b={@_{b`, VisGlob},
{"'3Ze\u050e|\u02del\u069du-Rpct4+Z5b={@_{b", `'3Ze\324\216|\313\236l\332\235u-Rpct4+Z5b={@_{b`, VisGlob | VisOctal}, {"'3Ze\u050e|\u02del\u069du-Rpct4+Z5b={@_{b", `'3Ze\324\216|\313\236l\332\235u-Rpct4+Z5b={@_{b`, VisGlob | VisOctal},
// VisDoubleQuote
{`Foo="Bar's"`, `Foo="Bar's"`, 0},
{`Foo="Bar's"`, `Foo=\"Bar's\"`, VisDoubleQuote},
{`"“Unicode” Quotes"`, `\"\M-b\M^@\M^\Unicode\M-b\M^@\M^] \M-b\M^@\M^XQuote\M-b\M^@\M^Ys\M-b\M^@\M^Y\"`, VisDoubleQuote},
{`"“Unicode” Quotes"`, `\"\342\200\234Unicode\342\200\235 \342\200\230Quote\342\200\231s\342\200\231\"`, VisDoubleQuote | VisOctal},
} { } {
enc, err := Vis(test.input, test.flag) t.Run(fmt.Sprintf("Test%.2d", idx), func(t *testing.T) {
if err != nil { enc, err := Vis(test.input, test.flag)
t.Errorf("unexpected error with %q: %s", test, err) require.NoErrorf(t, err, "vis(%q, %s)", test.input, test.flag)
} assert.Equalf(t, test.output, enc, "vis(%q, %s)", test.input, test.flag)
if enc != test.output { })
t.Errorf("expected vis(%q, flag=%b) = %q, got %q", test.input, test.flag, test.output, enc)
}
} }
} }
@ -116,12 +124,59 @@ func TestVisChanged(t *testing.T) {
"THIS\\IS_A_TEST1234", "THIS\\IS_A_TEST1234",
"AC_Ra\u00edz_Certic\u00e1mara_S.A..pem", "AC_Ra\u00edz_Certic\u00e1mara_S.A..pem",
} { } {
enc, err := Vis(test, DefaultVisFlags) t.Run(test, func(t *testing.T) {
if err != nil { enc, err := Vis(test, DefaultVisFlags)
t.Errorf("unexpected error with %q: %s", test, err) require.NoErrorf(t, err, "vis(%q)", test)
} assert.NotEqualf(t, test, enc, "encoding of %q should be different to original", test)
if enc == test { })
t.Errorf("expected encoding of %q to be changed", test)
}
} }
} }
func BenchmarkVis(b *testing.B) {
doBench := func(b *testing.B, text string) {
_, err := Vis(text, DefaultVisFlags)
require.NoErrorf(b, err, "vis(%q)", text)
for b.Loop() {
_, _ = Vis(text, DefaultVisFlags)
}
}
b.Run("NoChange", func(b *testing.B) {
text := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
doBench(b, text)
})
b.Run("Binary", func(b *testing.B) {
var data [32]byte
n, err := rand.Read(data[:])
require.NoError(b, err, "rand.Read")
require.Equal(b, len(data), n, "rand.Read len return")
text := string(data[:])
doBench(b, text)
})
// The rest of these test strings come from a set of test strings collated
// in <https://www.w3.org/2001/06/utf-8-test/quickbrown.html>.
b.Run("ASCII", func(b *testing.B) {
text := "The quick brown fox jumps over the lazy dog."
doBench(b, text)
})
b.Run("German", func(b *testing.B) {
text := "Falsches Üben von Xylophonmusik quält jeden größeren Zwerg"
doBench(b, text)
})
b.Run("Russian", func(b *testing.B) {
text := "В чащах юга жил бы цитрус? Да, но фальшивый экземпляр!"
doBench(b, text)
})
b.Run("Japanese", func(b *testing.B) {
text := "いろはにほへとちりぬるをイロハニホヘトチリヌルヲ"
doBench(b, text)
})
}

View file

@ -6,9 +6,13 @@ import (
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"slices"
"syscall" "syscall"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func ExampleStreamer() { func ExampleStreamer() {
@ -56,27 +60,20 @@ func TestTar(t *testing.T) {
log.Println(fh.Stat()) log.Println(fh.Stat())
fh.Close() */ fh.Close() */
fh, err := os.Open("./testdata/test.tar") fh, err := os.Open("./testdata/test.tar")
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
str := NewTarStreamer(fh, nil, append(DefaultKeywords, "sha1"))
if _, err := io.Copy(io.Discard, str); err != nil && err != io.EOF {
t.Fatal(err)
}
if err := str.Close(); err != nil {
t.Fatal(err)
}
defer fh.Close() defer fh.Close()
// get DirectoryHierarcy struct from walking the tar archive str := NewTarStreamer(fh, nil, append(DefaultKeywords, "sha1"))
n, err := io.Copy(io.Discard, str)
require.NoError(t, err, "read full tar stream")
require.NotZero(t, n, "tar stream should be non-empty")
require.NoError(t, str.Close(), "close tar stream")
// get DirectoryHierarchy struct from walking the tar archive
tdh, err := str.Hierarchy() tdh, err := str.Hierarchy()
if err != nil { require.NoError(t, err, "TarStreamer Hierarchy")
t.Fatal(err) require.NotNil(t, tdh, "TarStreamer Hierarchy")
}
if tdh == nil {
t.Fatal("expected a DirectoryHierarchy struct, but got nil")
}
testDir, present := os.LookupEnv("MTREE_TESTDIR") testDir, present := os.LookupEnv("MTREE_TESTDIR")
if present == false { if present == false {
@ -84,42 +81,26 @@ func TestTar(t *testing.T) {
} }
testPath := filepath.Join(testDir, "test.mtree") testPath := filepath.Join(testDir, "test.mtree")
fh, err = os.Create(testPath) fh, err = os.Create(testPath)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
defer os.Remove(testPath) defer os.Remove(testPath)
// put output of tar walk into test.mtree // put output of tar walk into test.mtree
_, err = tdh.WriteTo(fh) _, err = tdh.WriteTo(fh)
if err != nil { require.NoError(t, err)
t.Fatal(err) require.NoError(t, fh.Close())
}
fh.Close()
// now simulate gomtree -T testdata/test.tar -f testdata/test.mtree // now simulate gomtree -T testdata/test.tar -f testdata/test.mtree
fh, err = os.Open(testPath) fh, err = os.Open(testPath)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
defer fh.Close() defer fh.Close()
dh, err := ParseSpec(fh) dh, err := ParseSpec(fh)
if err != nil { require.NoErrorf(t, err, "parse spec %s", fh.Name())
t.Fatal(err)
}
res, err := Compare(tdh, dh, append(DefaultKeywords, "sha1")) res, err := Compare(tdh, dh, append(DefaultKeywords, "sha1"))
if err != nil { require.NoError(t, err, "compare tar DirectoryHierarchy")
t.Fatal(err) if !assert.Empty(t, res) {
} pprintInodeDeltas(t, res)
// print any failures, and then call t.Fatal once all failures/extra/missing
// are outputted
if len(res) > 0 {
for _, delta := range res {
t.Error(delta)
}
t.Fatal("unexpected errors")
} }
} }
@ -132,64 +113,44 @@ func TestTar(t *testing.T) {
//gocyclo:ignore //gocyclo:ignore
func TestArchiveCreation(t *testing.T) { func TestArchiveCreation(t *testing.T) {
fh, err := os.Open("./testdata/collection.tar") fh, err := os.Open("./testdata/collection.tar")
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
str := NewTarStreamer(fh, nil, []Keyword{"sha1"})
if _, err := io.Copy(io.Discard, str); err != nil && err != io.EOF {
t.Fatal(err)
}
if err := str.Close(); err != nil {
t.Fatal(err)
}
defer fh.Close() defer fh.Close()
// get DirectoryHierarcy struct from walking the tar archive str := NewTarStreamer(fh, nil, []Keyword{"sha1"})
n, err := io.Copy(io.Discard, str)
require.NoError(t, err, "read full tar stream")
require.NotZero(t, n, "tar stream should be non-empty")
require.NoError(t, str.Close(), "close tar stream")
// get DirectoryHierarchy struct from walking the tar archive
tdh, err := str.Hierarchy() tdh, err := str.Hierarchy()
if err != nil { require.NoError(t, err, "TarStreamer Hierarchy")
t.Fatal(err) require.NotNil(t, tdh, "TarStreamer Hierarchy")
}
// Test the tar manifest against the actual directory // Test the tar manifest against the actual directory
res, err := Check("./testdata/collection", tdh, []Keyword{"sha1"}, nil) dir := "./testdata/collection"
if err != nil { res, err := Check(dir, tdh, []Keyword{"sha1"}, nil)
t.Fatal(err) require.NoErrorf(t, err, "check %s", dir)
} if !assert.Empty(t, res, "check against tar DirectoryHierarchy") {
pprintInodeDeltas(t, res)
if len(res) > 0 {
for _, delta := range res {
t.Error(delta)
}
t.Fatal("unexpected errors")
} }
// Test the tar manifest against itself // Test the tar manifest against itself
res, err = Compare(tdh, tdh, []Keyword{"sha1"}) res, err = Compare(tdh, tdh, []Keyword{"sha1"})
if err != nil { require.NoErrorf(t, err, "compare tar against itself")
t.Fatal(err) if !assert.Empty(t, res, "compare tar against itself") {
} pprintInodeDeltas(t, res)
if len(res) > 0 {
for _, delta := range res {
t.Error(delta)
}
t.Fatal("unexpected errors")
} }
// Validate the directory manifest against the archive // Validate the directory manifest against the archive
dh, err := Walk("./testdata/collection", nil, []Keyword{"sha1"}, nil) dh, err := Walk(dir, nil, []Keyword{"sha1"}, nil)
if err != nil { require.NoErrorf(t, err, "walk %s", dir)
t.Fatal(err)
}
res, err = Compare(tdh, dh, []Keyword{"sha1"}) res, err = Compare(tdh, dh, []Keyword{"sha1"})
if err != nil { require.NoErrorf(t, err, "compare tar against %s walk", dir)
t.Fatal(err) if !assert.Emptyf(t, res, "compare tar against %s walk", dir) {
} pprintInodeDeltas(t, res)
if len(res) > 0 {
for _, delta := range res {
t.Error(delta)
}
t.Fatal("unexpected errors")
} }
} }
@ -202,131 +163,104 @@ func TestArchiveCreation(t *testing.T) {
//gocyclo:ignore //gocyclo:ignore
func TestTreeTraversal(t *testing.T) { func TestTreeTraversal(t *testing.T) {
fh, err := os.Open("./testdata/traversal.tar") fh, err := os.Open("./testdata/traversal.tar")
if err != nil { require.NoError(t, err)
t.Fatal(err) defer fh.Close()
}
str := NewTarStreamer(fh, nil, DefaultTarKeywords) str := NewTarStreamer(fh, nil, DefaultTarKeywords)
if _, err = io.Copy(io.Discard, str); err != nil && err != io.EOF { n, err := io.Copy(io.Discard, str)
t.Fatal(err) require.NoError(t, err, "read full tar stream")
} require.NotZero(t, n, "tar stream should be non-empty")
if err = str.Close(); err != nil { require.NoError(t, str.Close(), "close tar stream")
t.Fatal(err)
}
fh.Close()
tdh, err := str.Hierarchy() tdh, err := str.Hierarchy()
require.NoError(t, err, "TarStreamer Hierarchy")
if err != nil { require.NotNil(t, tdh, "TarStreamer Hierarchy")
t.Fatal(err)
}
res, err := Compare(tdh, tdh, []Keyword{"sha1"}) res, err := Compare(tdh, tdh, []Keyword{"sha1"})
if err != nil { require.NoErrorf(t, err, "compare tar against itself")
t.Fatal(err) if !assert.Empty(t, res, "compare tar against itself") {
} pprintInodeDeltas(t, res)
if len(res) > 0 {
for _, delta := range res {
t.Error(delta)
}
t.Fatal("unexpected errors")
} }
// top-level "." directory will contain contents of traversal.tar
res, err = Check("./testdata/.", tdh, []Keyword{"sha1"}, nil) res, err = Check("./testdata/.", tdh, []Keyword{"sha1"}, nil)
if err != nil { require.NoError(t, err, "check testdata dir")
t.Fatal(err)
} // The top-level "." directory will contain contents of some extra files.
if len(res) > 0 { // This test was originally written with the pre-Compare Check code in mind
var failed bool // (i.e., only file *modifications* were counted as errors).
for _, delta := range res { res = slices.DeleteFunc(res, func(delta InodeDelta) bool {
// We only care about missing or modified files. skip := delta.Type() == Extra
// The original test was written using the old check code. if skip {
if delta.Type() != Extra { t.Logf("ignoring extra entry for %q", delta.Path())
failed = true
t.Error(delta)
}
}
if failed {
t.Fatal("unexpected errors")
} }
return skip
})
if !assert.Emptyf(t, res, "compare %s against testdata walk", fh.Name()) {
pprintInodeDeltas(t, res)
} }
// Now test an archive that requires placeholder directories, i.e, there are // Now test an archive that requires placeholder directories, i.e, there
// no headers in the archive that are associated with the actual directory name // are no headers in the archive that are associated with the actual
// directory name.
fh, err = os.Open("./testdata/singlefile.tar") fh, err = os.Open("./testdata/singlefile.tar")
if err != nil { require.NoError(t, err)
t.Fatal(err) defer fh.Close()
}
str = NewTarStreamer(fh, nil, DefaultTarKeywords)
if _, err = io.Copy(io.Discard, str); err != nil && err != io.EOF {
t.Fatal(err)
}
if err = str.Close(); err != nil {
t.Fatal(err)
}
tdh, err = str.Hierarchy()
if err != nil {
t.Fatal(err)
}
// Implied top-level "." directory will contain the contents of singlefile.tar str = NewTarStreamer(fh, nil, DefaultTarKeywords)
res, err = Check("./testdata/.", tdh, []Keyword{"sha1"}, nil)
if err != nil { n, err = io.Copy(io.Discard, str)
t.Fatal(err) require.NoError(t, err, "read full tar stream")
} require.NotZero(t, n, "tar stream should be non-empty")
if len(res) > 0 { require.NoError(t, str.Close(), "close tar stream")
var failed bool
for _, delta := range res { tdh, err = str.Hierarchy()
// We only care about missing or modified files. require.NoError(t, err, "TarStreamer Hierarchy")
// The original test was written using the old check code. require.NotNil(t, tdh, "TarStreamer Hierarchy")
if delta.Type() != Extra {
failed = true // The top-level "." directory will contain contents of some extra files.
t.Error(delta) // This test was originally written with the pre-Compare Check code in mind
} // (i.e., only file *modifications* were counted as errors).
} res = slices.DeleteFunc(res, func(delta InodeDelta) bool {
if failed { skip := delta.Type() == Extra
t.Fatal("unexpected errors") if skip {
t.Logf("ignoring extra entry for %q", delta.Path())
} }
return skip
})
if !assert.Emptyf(t, res, "compare %s against testdata walk", fh.Name()) {
pprintInodeDeltas(t, res)
} }
} }
func TestHardlinks(t *testing.T) { func TestHardlinks(t *testing.T) {
fh, err := os.Open("./testdata/hardlinks.tar") fh, err := os.Open("./testdata/hardlinks.tar")
if err != nil { require.NoError(t, err)
t.Fatal(err) defer fh.Close()
}
str := NewTarStreamer(fh, nil, append(DefaultTarKeywords, "nlink")) str := NewTarStreamer(fh, nil, append(DefaultTarKeywords, "nlink"))
if _, err = io.Copy(io.Discard, str); err != nil && err != io.EOF { n, err := io.Copy(io.Discard, str)
t.Fatal(err) require.NoError(t, err, "read full tar stream")
} require.NotZero(t, n, "tar stream should be non-empty")
if err = str.Close(); err != nil { require.NoError(t, str.Close(), "close tar stream")
t.Fatal(err)
}
fh.Close()
tdh, err := str.Hierarchy() tdh, err := str.Hierarchy()
require.NoError(t, err, "TarStreamer Hierarchy")
require.NotNil(t, tdh, "TarStreamer Hierarchy")
if err != nil {
t.Fatal(err)
}
foundnlink := false foundnlink := false
for _, e := range tdh.Entries { for _, e := range tdh.Entries {
if e.Type == RelativeType { if e.Type == RelativeType {
for _, kv := range e.Keywords { for _, kv := range e.Keywords {
if KeyVal(kv).Keyword() == "nlink" { if KeyVal(kv).Keyword() == "nlink" {
foundnlink = true foundnlink = true
if KeyVal(kv).Value() != "3" { assert.Equalf(t, "3", KeyVal(kv).Value(), "expected 3 hardlinks for %s", e.Name)
t.Errorf("expected to have 3 hardlinks for %s", e.Name)
}
} }
} }
} }
} }
if !foundnlink { require.True(t, foundnlink, "there should be an nlink entry")
t.Errorf("nlink expected to be evaluated")
}
} }
type fakeFile struct { type fakeFile struct {
@ -375,23 +309,20 @@ func makeTarStream(ff []fakeFile) ([]byte, error) {
func TestArchiveExcludeNonDirectory(t *testing.T) { func TestArchiveExcludeNonDirectory(t *testing.T) {
fh, err := os.Open("./testdata/collection.tar") fh, err := os.Open("./testdata/collection.tar")
if err != nil { require.NoError(t, err)
t.Fatal(err) defer fh.Close()
}
str := NewTarStreamer(fh, []ExcludeFunc{ExcludeNonDirectories}, []Keyword{"type"}) str := NewTarStreamer(fh, []ExcludeFunc{ExcludeNonDirectories}, []Keyword{"type"})
if _, err := io.Copy(io.Discard, str); err != nil && err != io.EOF { n, err := io.Copy(io.Discard, str)
t.Fatal(err) require.NoError(t, err, "read full tar stream")
} require.NotZero(t, n, "tar stream should be non-empty")
if err := str.Close(); err != nil { require.NoError(t, str.Close(), "close tar stream")
t.Fatal(err)
}
fh.Close()
// get DirectoryHierarcy struct from walking the tar archive
tdh, err := str.Hierarchy() tdh, err := str.Hierarchy()
if err != nil { require.NoError(t, err, "TarStreamer Hierarchy")
t.Fatal(err) require.NotNil(t, tdh, "TarStreamer Hierarchy")
}
for i := range tdh.Entries { for i := range tdh.Entries {
for _, keyval := range tdh.Entries[i].AllKeys() { for _, keyval := range tdh.Entries[i].AllKeys() {
if tdh.Entries[i].Type == FullType || tdh.Entries[i].Type == RelativeType { if tdh.Entries[i].Type == FullType || tdh.Entries[i].Type == RelativeType {

View file

@ -1,11 +1,13 @@
package mtree package mtree
import ( import (
"encoding/json"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/vbatts/go-mtree/xattr" "github.com/vbatts/go-mtree/xattr"
) )
@ -19,73 +21,47 @@ func TestXattrUpdate(t *testing.T) {
// a bit dirty to create/destroy a directory in cwd, but often /tmp is // a bit dirty to create/destroy a directory in cwd, but often /tmp is
// mounted tmpfs and doesn't support xattrs // mounted tmpfs and doesn't support xattrs
dir, err := os.MkdirTemp(".", "test.xattr.restore.") dir, err := os.MkdirTemp(".", "test.xattr.restore.")
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
defer os.RemoveAll(dir) // clean up defer os.RemoveAll(dir) // clean up
tmpfn := filepath.Join(dir, "tmpfile") tmpfn := filepath.Join(dir, "tmpfile")
if err := os.WriteFile(tmpfn, content, 0666); err != nil { require.NoError(t, os.WriteFile(tmpfn, content, 0666))
t.Fatal(err)
}
if err := xattr.Set(dir, "user.test", []byte("directory")); err != nil { if err := xattr.Set(dir, "user.test", []byte("directory")); err != nil {
t.Skipf("skipping: %q does not support xattrs", dir) t.Skipf("skipping: %q does not support xattrs", dir)
} }
if err := xattr.Set(tmpfn, "user.test", []byte("regular file")); err != nil { require.NoError(t, xattr.Set(tmpfn, "user.test", []byte("regular file")))
t.Fatal(err)
}
// Walk this tempdir // Walk this tempdir
dh, err := Walk(dir, nil, append(DefaultKeywords, []Keyword{"xattr", "sha1"}...), nil) dh, err := Walk(dir, nil, append(DefaultKeywords, []Keyword{"xattr", "sha1"}...), nil)
if err != nil { require.NoErrorf(t, err, "walk %s", dir)
t.Fatal(err)
}
// Now check that we're sane // Now check that we're sane
res, err := Check(dir, dh, nil, nil) res, err := Check(dir, dh, nil, nil)
if err != nil { require.NoErrorf(t, err, "check %s", dir)
t.Fatal(err) if !assert.Empty(t, res) {
} pprintInodeDeltas(t, res)
if len(res) != 0 {
t.Errorf("expecting no failures, but got %q", res)
} }
if err := xattr.Set(tmpfn, "user.test", []byte("let it fly")); err != nil { require.NoError(t, xattr.Set(tmpfn, "user.test", []byte("let it fly")))
t.Fatal(err)
}
// Now check that we fail the check // Now check that we fail the check
res, err = Check(dir, dh, nil, nil) res, err = Check(dir, dh, nil, nil)
if err != nil { require.NoErrorf(t, err, "check %s", dir)
t.Fatal(err) assert.NotEmpty(t, res, "should see xattr deltas from check")
}
if len(res) == 0 {
t.Error("expected failures (like xattrs), but got none")
}
// restore the xattrs to original // restore the xattrs to original
res, err = Update(dir, dh, append(DefaultUpdateKeywords, "xattr"), nil) res, err = Update(dir, dh, append(DefaultUpdateKeywords, "xattr"), nil)
if err != nil { require.NoErrorf(t, err, "update %s", dir)
t.Error(err) if !assert.Empty(t, res, "update implied check") {
} pprintInodeDeltas(t, res)
if len(res) != 0 {
t.Errorf("expecting no failures, but got %q", res)
} }
// Now check that we're sane again // Now check that we're sane again
res, err = Check(dir, dh, nil, nil) res, err = Check(dir, dh, nil, nil)
if err != nil { require.NoErrorf(t, err, "update %s", dir)
t.Fatal(err) if !assert.Empty(t, res, "post-update check") {
} pprintInodeDeltas(t, res)
if len(res) != 0 {
// pretty this shit up
buf, err := json.MarshalIndent(res, "", " ")
if err != nil {
t.Errorf("expecting no failures, but got %q", res)
} else {
t.Errorf("expecting no failures, but got %s", string(buf))
}
} }
// TODO make a test for xattr here. Likely in the user space for privileges. Even still this may be prone to error for some tmpfs don't act right with xattrs. :-\ // TODO make a test for xattr here. Likely in the user space for privileges. Even still this may be prone to error for some tmpfs don't act right with xattrs. :-\

View file

@ -5,7 +5,6 @@ package mtree
import ( import (
"container/heap" "container/heap"
"encoding/json"
"os" "os"
"os/user" "os/user"
"path/filepath" "path/filepath"
@ -14,6 +13,8 @@ import (
"time" "time"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func init() { func init() {
@ -23,93 +24,53 @@ func init() {
//gocyclo:ignore //gocyclo:ignore
func TestUpdate(t *testing.T) { func TestUpdate(t *testing.T) {
content := []byte("I know half of you half as well as I ought to") content := []byte("I know half of you half as well as I ought to")
dir, err := os.MkdirTemp("", "test-check-keywords") dir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir) // clean up
tmpfn := filepath.Join(dir, "tmpfile") tmpfn := filepath.Join(dir, "tmpfile")
if err := os.WriteFile(tmpfn, content, 0666); err != nil { require.NoError(t, os.WriteFile(tmpfn, content, 0666))
t.Fatal(err)
}
// Walk this tempdir // Walk this tempdir
dh, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil) dh, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
if err != nil { require.NoErrorf(t, err, "walk %s", dir)
t.Fatal(err)
}
// Touch a file, so the mtime changes. // Touch a file, so the mtime changes.
now := time.Now() now := time.Now()
if err := os.Chtimes(tmpfn, now, now); err != nil { require.NoError(t, os.Chtimes(tmpfn, now, now))
t.Fatal(err) require.NoError(t, os.Chmod(tmpfn, os.FileMode(0600)))
}
if err := os.Chmod(tmpfn, os.FileMode(0600)); err != nil {
t.Fatal(err)
}
// Changing user is a little tough, but the group can be changed by a limited user to any group that the user is a member of. So just choose one that is not the current main group. // Changing user is a little tough, but the group can be changed by a limited user to any group that the user is a member of. So just choose one that is not the current main group.
u, err := user.Current() u, err := user.Current()
if err != nil { require.NoError(t, err, "get current user")
t.Fatal(err)
}
ugroups, err := u.GroupIds() ugroups, err := u.GroupIds()
if err != nil { require.NoError(t, err, "get current user groups")
t.Fatal(err)
}
for _, ugroup := range ugroups { for _, ugroup := range ugroups {
if ugroup == u.Gid { if ugroup == u.Gid {
continue continue
} }
gid, err := strconv.Atoi(ugroup) gid, err := strconv.Atoi(ugroup)
if err != nil { require.NoErrorf(t, err, "parse group %q", ugroup)
t.Fatal(ugroup) require.NoError(t, os.Lchown(tmpfn, -1, gid))
}
if err := os.Lchown(tmpfn, -1, gid); err != nil {
t.Fatal(err)
}
} }
// Check for sanity. This ought to have failures // Check for sanity. This ought to have failures
res, err := Check(dir, dh, nil, nil) res, err := Check(dir, dh, nil, nil)
if err != nil { require.NoErrorf(t, err, "check %s", dir)
t.Fatal(err) assert.NotEmpty(t, res, "should see mtime/chown/chtimes deltas from check")
}
if len(res) == 0 {
t.Error("expected failures (like mtimes), but got none")
}
//dh.WriteTo(os.Stdout) //dh.WriteTo(os.Stdout)
res, err = Update(dir, dh, DefaultUpdateKeywords, nil) res, err = Update(dir, dh, DefaultUpdateKeywords, nil)
if err != nil { require.NoErrorf(t, err, "update %d", err)
t.Error(err) if !assert.Empty(t, res, "update implied check") {
} pprintInodeDeltas(t, res)
if len(res) > 0 {
// pretty this shit up
buf, err := json.MarshalIndent(res, "", " ")
if err != nil {
t.Errorf("%#v", res)
}
t.Error(string(buf))
} }
// Now check that we're sane again // Now check that we're sane again
res, err = Check(dir, dh, nil, nil) res, err = Check(dir, dh, nil, nil)
if err != nil { require.NoErrorf(t, err, "update %s", dir)
t.Fatal(err) if !assert.Empty(t, res, "post-update check") {
pprintInodeDeltas(t, res)
} }
// should have no failures now
if len(res) > 0 {
// pretty this shit up
buf, err := json.MarshalIndent(res, "", " ")
if err != nil {
t.Errorf("%#v", res)
} else {
t.Error(string(buf))
}
}
} }
func TestPathUpdateHeap(t *testing.T) { func TestPathUpdateHeap(t *testing.T) {
@ -127,11 +88,7 @@ func TestPathUpdateHeap(t *testing.T) {
var p string var p string
for h.Len() > 0 { for h.Len() > 0 {
p = heap.Pop(h).(pathUpdate).Path p = heap.Pop(h).(pathUpdate).Path
if len(p) > longest { assert.LessOrEqual(t, len(p), longest, "expected next path %q to be shorter", p)
t.Errorf("expected next path to be shorter, but it was not %q is longer than %d", p, longest)
}
}
if p != "." {
t.Errorf("expected \".\" to be the last, but got %q", p)
} }
assert.Equal(t, ".", p, ". should be the last path")
} }

27
vendor/github.com/pmezard/go-difflib/LICENSE generated vendored Normal file
View file

@ -0,0 +1,27 @@
Copyright (c) 2013, Patrick Mezard
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.
The names of its contributors may not 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.

772
vendor/github.com/pmezard/go-difflib/difflib/difflib.go generated vendored Normal file
View file

@ -0,0 +1,772 @@
// Package difflib is a partial port of Python difflib module.
//
// It provides tools to compare sequences of strings and generate textual diffs.
//
// The following class and functions have been ported:
//
// - SequenceMatcher
//
// - unified_diff
//
// - context_diff
//
// Getting unified diffs was the main goal of the port. Keep in mind this code
// is mostly suitable to output text differences in a human friendly way, there
// are no guarantees generated diffs are consumable by patch(1).
package difflib
import (
"bufio"
"bytes"
"fmt"
"io"
"strings"
)
func min(a, b int) int {
if a < b {
return a
}
return b
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func calculateRatio(matches, length int) float64 {
if length > 0 {
return 2.0 * float64(matches) / float64(length)
}
return 1.0
}
type Match struct {
A int
B int
Size int
}
type OpCode struct {
Tag byte
I1 int
I2 int
J1 int
J2 int
}
// SequenceMatcher compares sequence of strings. The basic
// algorithm predates, and is a little fancier than, an algorithm
// published in the late 1980's by Ratcliff and Obershelp under the
// hyperbolic name "gestalt pattern matching". The basic idea is to find
// the longest contiguous matching subsequence that contains no "junk"
// elements (R-O doesn't address junk). The same idea is then applied
// recursively to the pieces of the sequences to the left and to the right
// of the matching subsequence. This does not yield minimal edit
// sequences, but does tend to yield matches that "look right" to people.
//
// SequenceMatcher tries to compute a "human-friendly diff" between two
// sequences. Unlike e.g. UNIX(tm) diff, the fundamental notion is the
// longest *contiguous* & junk-free matching subsequence. That's what
// catches peoples' eyes. The Windows(tm) windiff has another interesting
// notion, pairing up elements that appear uniquely in each sequence.
// That, and the method here, appear to yield more intuitive difference
// reports than does diff. This method appears to be the least vulnerable
// to synching up on blocks of "junk lines", though (like blank lines in
// ordinary text files, or maybe "<P>" lines in HTML files). That may be
// because this is the only method of the 3 that has a *concept* of
// "junk" <wink>.
//
// Timing: Basic R-O is cubic time worst case and quadratic time expected
// case. SequenceMatcher is quadratic time for the worst case and has
// expected-case behavior dependent in a complicated way on how many
// elements the sequences have in common; best case time is linear.
type SequenceMatcher struct {
a []string
b []string
b2j map[string][]int
IsJunk func(string) bool
autoJunk bool
bJunk map[string]struct{}
matchingBlocks []Match
fullBCount map[string]int
bPopular map[string]struct{}
opCodes []OpCode
}
func NewMatcher(a, b []string) *SequenceMatcher {
m := SequenceMatcher{autoJunk: true}
m.SetSeqs(a, b)
return &m
}
func NewMatcherWithJunk(a, b []string, autoJunk bool,
isJunk func(string) bool) *SequenceMatcher {
m := SequenceMatcher{IsJunk: isJunk, autoJunk: autoJunk}
m.SetSeqs(a, b)
return &m
}
// Set two sequences to be compared.
func (m *SequenceMatcher) SetSeqs(a, b []string) {
m.SetSeq1(a)
m.SetSeq2(b)
}
// Set the first sequence to be compared. The second sequence to be compared is
// not changed.
//
// SequenceMatcher computes and caches detailed information about the second
// sequence, so if you want to compare one sequence S against many sequences,
// use .SetSeq2(s) once and call .SetSeq1(x) repeatedly for each of the other
// sequences.
//
// See also SetSeqs() and SetSeq2().
func (m *SequenceMatcher) SetSeq1(a []string) {
if &a == &m.a {
return
}
m.a = a
m.matchingBlocks = nil
m.opCodes = nil
}
// Set the second sequence to be compared. The first sequence to be compared is
// not changed.
func (m *SequenceMatcher) SetSeq2(b []string) {
if &b == &m.b {
return
}
m.b = b
m.matchingBlocks = nil
m.opCodes = nil
m.fullBCount = nil
m.chainB()
}
func (m *SequenceMatcher) chainB() {
// Populate line -> index mapping
b2j := map[string][]int{}
for i, s := range m.b {
indices := b2j[s]
indices = append(indices, i)
b2j[s] = indices
}
// Purge junk elements
m.bJunk = map[string]struct{}{}
if m.IsJunk != nil {
junk := m.bJunk
for s, _ := range b2j {
if m.IsJunk(s) {
junk[s] = struct{}{}
}
}
for s, _ := range junk {
delete(b2j, s)
}
}
// Purge remaining popular elements
popular := map[string]struct{}{}
n := len(m.b)
if m.autoJunk && n >= 200 {
ntest := n/100 + 1
for s, indices := range b2j {
if len(indices) > ntest {
popular[s] = struct{}{}
}
}
for s, _ := range popular {
delete(b2j, s)
}
}
m.bPopular = popular
m.b2j = b2j
}
func (m *SequenceMatcher) isBJunk(s string) bool {
_, ok := m.bJunk[s]
return ok
}
// Find longest matching block in a[alo:ahi] and b[blo:bhi].
//
// If IsJunk is not defined:
//
// Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where
// alo <= i <= i+k <= ahi
// blo <= j <= j+k <= bhi
// and for all (i',j',k') meeting those conditions,
// k >= k'
// i <= i'
// and if i == i', j <= j'
//
// In other words, of all maximal matching blocks, return one that
// starts earliest in a, and of all those maximal matching blocks that
// start earliest in a, return the one that starts earliest in b.
//
// If IsJunk is defined, first the longest matching block is
// determined as above, but with the additional restriction that no
// junk element appears in the block. Then that block is extended as
// far as possible by matching (only) junk elements on both sides. So
// the resulting block never matches on junk except as identical junk
// happens to be adjacent to an "interesting" match.
//
// If no blocks match, return (alo, blo, 0).
func (m *SequenceMatcher) findLongestMatch(alo, ahi, blo, bhi int) Match {
// CAUTION: stripping common prefix or suffix would be incorrect.
// E.g.,
// ab
// acab
// Longest matching block is "ab", but if common prefix is
// stripped, it's "a" (tied with "b"). UNIX(tm) diff does so
// strip, so ends up claiming that ab is changed to acab by
// inserting "ca" in the middle. That's minimal but unintuitive:
// "it's obvious" that someone inserted "ac" at the front.
// Windiff ends up at the same place as diff, but by pairing up
// the unique 'b's and then matching the first two 'a's.
besti, bestj, bestsize := alo, blo, 0
// find longest junk-free match
// during an iteration of the loop, j2len[j] = length of longest
// junk-free match ending with a[i-1] and b[j]
j2len := map[int]int{}
for i := alo; i != ahi; i++ {
// look at all instances of a[i] in b; note that because
// b2j has no junk keys, the loop is skipped if a[i] is junk
newj2len := map[int]int{}
for _, j := range m.b2j[m.a[i]] {
// a[i] matches b[j]
if j < blo {
continue
}
if j >= bhi {
break
}
k := j2len[j-1] + 1
newj2len[j] = k
if k > bestsize {
besti, bestj, bestsize = i-k+1, j-k+1, k
}
}
j2len = newj2len
}
// Extend the best by non-junk elements on each end. In particular,
// "popular" non-junk elements aren't in b2j, which greatly speeds
// the inner loop above, but also means "the best" match so far
// doesn't contain any junk *or* popular non-junk elements.
for besti > alo && bestj > blo && !m.isBJunk(m.b[bestj-1]) &&
m.a[besti-1] == m.b[bestj-1] {
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
}
for besti+bestsize < ahi && bestj+bestsize < bhi &&
!m.isBJunk(m.b[bestj+bestsize]) &&
m.a[besti+bestsize] == m.b[bestj+bestsize] {
bestsize += 1
}
// Now that we have a wholly interesting match (albeit possibly
// empty!), we may as well suck up the matching junk on each
// side of it too. Can't think of a good reason not to, and it
// saves post-processing the (possibly considerable) expense of
// figuring out what to do with it. In the case of an empty
// interesting match, this is clearly the right thing to do,
// because no other kind of match is possible in the regions.
for besti > alo && bestj > blo && m.isBJunk(m.b[bestj-1]) &&
m.a[besti-1] == m.b[bestj-1] {
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
}
for besti+bestsize < ahi && bestj+bestsize < bhi &&
m.isBJunk(m.b[bestj+bestsize]) &&
m.a[besti+bestsize] == m.b[bestj+bestsize] {
bestsize += 1
}
return Match{A: besti, B: bestj, Size: bestsize}
}
// Return list of triples describing matching subsequences.
//
// Each triple is of the form (i, j, n), and means that
// a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in
// i and in j. It's also guaranteed that if (i, j, n) and (i', j', n') are
// adjacent triples in the list, and the second is not the last triple in the
// list, then i+n != i' or j+n != j'. IOW, adjacent triples never describe
// adjacent equal blocks.
//
// The last triple is a dummy, (len(a), len(b), 0), and is the only
// triple with n==0.
func (m *SequenceMatcher) GetMatchingBlocks() []Match {
if m.matchingBlocks != nil {
return m.matchingBlocks
}
var matchBlocks func(alo, ahi, blo, bhi int, matched []Match) []Match
matchBlocks = func(alo, ahi, blo, bhi int, matched []Match) []Match {
match := m.findLongestMatch(alo, ahi, blo, bhi)
i, j, k := match.A, match.B, match.Size
if match.Size > 0 {
if alo < i && blo < j {
matched = matchBlocks(alo, i, blo, j, matched)
}
matched = append(matched, match)
if i+k < ahi && j+k < bhi {
matched = matchBlocks(i+k, ahi, j+k, bhi, matched)
}
}
return matched
}
matched := matchBlocks(0, len(m.a), 0, len(m.b), nil)
// It's possible that we have adjacent equal blocks in the
// matching_blocks list now.
nonAdjacent := []Match{}
i1, j1, k1 := 0, 0, 0
for _, b := range matched {
// Is this block adjacent to i1, j1, k1?
i2, j2, k2 := b.A, b.B, b.Size
if i1+k1 == i2 && j1+k1 == j2 {
// Yes, so collapse them -- this just increases the length of
// the first block by the length of the second, and the first
// block so lengthened remains the block to compare against.
k1 += k2
} else {
// Not adjacent. Remember the first block (k1==0 means it's
// the dummy we started with), and make the second block the
// new block to compare against.
if k1 > 0 {
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
}
i1, j1, k1 = i2, j2, k2
}
}
if k1 > 0 {
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
}
nonAdjacent = append(nonAdjacent, Match{len(m.a), len(m.b), 0})
m.matchingBlocks = nonAdjacent
return m.matchingBlocks
}
// Return list of 5-tuples describing how to turn a into b.
//
// Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple
// has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the
// tuple preceding it, and likewise for j1 == the previous j2.
//
// The tags are characters, with these meanings:
//
// 'r' (replace): a[i1:i2] should be replaced by b[j1:j2]
//
// 'd' (delete): a[i1:i2] should be deleted, j1==j2 in this case.
//
// 'i' (insert): b[j1:j2] should be inserted at a[i1:i1], i1==i2 in this case.
//
// 'e' (equal): a[i1:i2] == b[j1:j2]
func (m *SequenceMatcher) GetOpCodes() []OpCode {
if m.opCodes != nil {
return m.opCodes
}
i, j := 0, 0
matching := m.GetMatchingBlocks()
opCodes := make([]OpCode, 0, len(matching))
for _, m := range matching {
// invariant: we've pumped out correct diffs to change
// a[:i] into b[:j], and the next matching block is
// a[ai:ai+size] == b[bj:bj+size]. So we need to pump
// out a diff to change a[i:ai] into b[j:bj], pump out
// the matching block, and move (i,j) beyond the match
ai, bj, size := m.A, m.B, m.Size
tag := byte(0)
if i < ai && j < bj {
tag = 'r'
} else if i < ai {
tag = 'd'
} else if j < bj {
tag = 'i'
}
if tag > 0 {
opCodes = append(opCodes, OpCode{tag, i, ai, j, bj})
}
i, j = ai+size, bj+size
// the list of matching blocks is terminated by a
// sentinel with size 0
if size > 0 {
opCodes = append(opCodes, OpCode{'e', ai, i, bj, j})
}
}
m.opCodes = opCodes
return m.opCodes
}
// Isolate change clusters by eliminating ranges with no changes.
//
// Return a generator of groups with up to n lines of context.
// Each group is in the same format as returned by GetOpCodes().
func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode {
if n < 0 {
n = 3
}
codes := m.GetOpCodes()
if len(codes) == 0 {
codes = []OpCode{OpCode{'e', 0, 1, 0, 1}}
}
// Fixup leading and trailing groups if they show no changes.
if codes[0].Tag == 'e' {
c := codes[0]
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
codes[0] = OpCode{c.Tag, max(i1, i2-n), i2, max(j1, j2-n), j2}
}
if codes[len(codes)-1].Tag == 'e' {
c := codes[len(codes)-1]
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
codes[len(codes)-1] = OpCode{c.Tag, i1, min(i2, i1+n), j1, min(j2, j1+n)}
}
nn := n + n
groups := [][]OpCode{}
group := []OpCode{}
for _, c := range codes {
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
// End the current group and start a new one whenever
// there is a large range with no changes.
if c.Tag == 'e' && i2-i1 > nn {
group = append(group, OpCode{c.Tag, i1, min(i2, i1+n),
j1, min(j2, j1+n)})
groups = append(groups, group)
group = []OpCode{}
i1, j1 = max(i1, i2-n), max(j1, j2-n)
}
group = append(group, OpCode{c.Tag, i1, i2, j1, j2})
}
if len(group) > 0 && !(len(group) == 1 && group[0].Tag == 'e') {
groups = append(groups, group)
}
return groups
}
// Return a measure of the sequences' similarity (float in [0,1]).
//
// Where T is the total number of elements in both sequences, and
// M is the number of matches, this is 2.0*M / T.
// Note that this is 1 if the sequences are identical, and 0 if
// they have nothing in common.
//
// .Ratio() is expensive to compute if you haven't already computed
// .GetMatchingBlocks() or .GetOpCodes(), in which case you may
// want to try .QuickRatio() or .RealQuickRation() first to get an
// upper bound.
func (m *SequenceMatcher) Ratio() float64 {
matches := 0
for _, m := range m.GetMatchingBlocks() {
matches += m.Size
}
return calculateRatio(matches, len(m.a)+len(m.b))
}
// Return an upper bound on ratio() relatively quickly.
//
// This isn't defined beyond that it is an upper bound on .Ratio(), and
// is faster to compute.
func (m *SequenceMatcher) QuickRatio() float64 {
// viewing a and b as multisets, set matches to the cardinality
// of their intersection; this counts the number of matches
// without regard to order, so is clearly an upper bound
if m.fullBCount == nil {
m.fullBCount = map[string]int{}
for _, s := range m.b {
m.fullBCount[s] = m.fullBCount[s] + 1
}
}
// avail[x] is the number of times x appears in 'b' less the
// number of times we've seen it in 'a' so far ... kinda
avail := map[string]int{}
matches := 0
for _, s := range m.a {
n, ok := avail[s]
if !ok {
n = m.fullBCount[s]
}
avail[s] = n - 1
if n > 0 {
matches += 1
}
}
return calculateRatio(matches, len(m.a)+len(m.b))
}
// Return an upper bound on ratio() very quickly.
//
// This isn't defined beyond that it is an upper bound on .Ratio(), and
// is faster to compute than either .Ratio() or .QuickRatio().
func (m *SequenceMatcher) RealQuickRatio() float64 {
la, lb := len(m.a), len(m.b)
return calculateRatio(min(la, lb), la+lb)
}
// Convert range to the "ed" format
func formatRangeUnified(start, stop int) string {
// Per the diff spec at http://www.unix.org/single_unix_specification/
beginning := start + 1 // lines start numbering with one
length := stop - start
if length == 1 {
return fmt.Sprintf("%d", beginning)
}
if length == 0 {
beginning -= 1 // empty ranges begin at line just before the range
}
return fmt.Sprintf("%d,%d", beginning, length)
}
// Unified diff parameters
type UnifiedDiff struct {
A []string // First sequence lines
FromFile string // First file name
FromDate string // First file time
B []string // Second sequence lines
ToFile string // Second file name
ToDate string // Second file time
Eol string // Headers end of line, defaults to LF
Context int // Number of context lines
}
// Compare two sequences of lines; generate the delta as a unified diff.
//
// Unified diffs are a compact way of showing line changes and a few
// lines of context. The number of context lines is set by 'n' which
// defaults to three.
//
// By default, the diff control lines (those with ---, +++, or @@) are
// created with a trailing newline. This is helpful so that inputs
// created from file.readlines() result in diffs that are suitable for
// file.writelines() since both the inputs and outputs have trailing
// newlines.
//
// For inputs that do not have trailing newlines, set the lineterm
// argument to "" so that the output will be uniformly newline free.
//
// The unidiff format normally has a header for filenames and modification
// times. Any or all of these may be specified using strings for
// 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.
// The modification times are normally expressed in the ISO 8601 format.
func WriteUnifiedDiff(writer io.Writer, diff UnifiedDiff) error {
buf := bufio.NewWriter(writer)
defer buf.Flush()
wf := func(format string, args ...interface{}) error {
_, err := buf.WriteString(fmt.Sprintf(format, args...))
return err
}
ws := func(s string) error {
_, err := buf.WriteString(s)
return err
}
if len(diff.Eol) == 0 {
diff.Eol = "\n"
}
started := false
m := NewMatcher(diff.A, diff.B)
for _, g := range m.GetGroupedOpCodes(diff.Context) {
if !started {
started = true
fromDate := ""
if len(diff.FromDate) > 0 {
fromDate = "\t" + diff.FromDate
}
toDate := ""
if len(diff.ToDate) > 0 {
toDate = "\t" + diff.ToDate
}
if diff.FromFile != "" || diff.ToFile != "" {
err := wf("--- %s%s%s", diff.FromFile, fromDate, diff.Eol)
if err != nil {
return err
}
err = wf("+++ %s%s%s", diff.ToFile, toDate, diff.Eol)
if err != nil {
return err
}
}
}
first, last := g[0], g[len(g)-1]
range1 := formatRangeUnified(first.I1, last.I2)
range2 := formatRangeUnified(first.J1, last.J2)
if err := wf("@@ -%s +%s @@%s", range1, range2, diff.Eol); err != nil {
return err
}
for _, c := range g {
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
if c.Tag == 'e' {
for _, line := range diff.A[i1:i2] {
if err := ws(" " + line); err != nil {
return err
}
}
continue
}
if c.Tag == 'r' || c.Tag == 'd' {
for _, line := range diff.A[i1:i2] {
if err := ws("-" + line); err != nil {
return err
}
}
}
if c.Tag == 'r' || c.Tag == 'i' {
for _, line := range diff.B[j1:j2] {
if err := ws("+" + line); err != nil {
return err
}
}
}
}
}
return nil
}
// Like WriteUnifiedDiff but returns the diff a string.
func GetUnifiedDiffString(diff UnifiedDiff) (string, error) {
w := &bytes.Buffer{}
err := WriteUnifiedDiff(w, diff)
return string(w.Bytes()), err
}
// Convert range to the "ed" format.
func formatRangeContext(start, stop int) string {
// Per the diff spec at http://www.unix.org/single_unix_specification/
beginning := start + 1 // lines start numbering with one
length := stop - start
if length == 0 {
beginning -= 1 // empty ranges begin at line just before the range
}
if length <= 1 {
return fmt.Sprintf("%d", beginning)
}
return fmt.Sprintf("%d,%d", beginning, beginning+length-1)
}
type ContextDiff UnifiedDiff
// Compare two sequences of lines; generate the delta as a context diff.
//
// Context diffs are a compact way of showing line changes and a few
// lines of context. The number of context lines is set by diff.Context
// which defaults to three.
//
// By default, the diff control lines (those with *** or ---) are
// created with a trailing newline.
//
// For inputs that do not have trailing newlines, set the diff.Eol
// argument to "" so that the output will be uniformly newline free.
//
// The context diff format normally has a header for filenames and
// modification times. Any or all of these may be specified using
// strings for diff.FromFile, diff.ToFile, diff.FromDate, diff.ToDate.
// The modification times are normally expressed in the ISO 8601 format.
// If not specified, the strings default to blanks.
func WriteContextDiff(writer io.Writer, diff ContextDiff) error {
buf := bufio.NewWriter(writer)
defer buf.Flush()
var diffErr error
wf := func(format string, args ...interface{}) {
_, err := buf.WriteString(fmt.Sprintf(format, args...))
if diffErr == nil && err != nil {
diffErr = err
}
}
ws := func(s string) {
_, err := buf.WriteString(s)
if diffErr == nil && err != nil {
diffErr = err
}
}
if len(diff.Eol) == 0 {
diff.Eol = "\n"
}
prefix := map[byte]string{
'i': "+ ",
'd': "- ",
'r': "! ",
'e': " ",
}
started := false
m := NewMatcher(diff.A, diff.B)
for _, g := range m.GetGroupedOpCodes(diff.Context) {
if !started {
started = true
fromDate := ""
if len(diff.FromDate) > 0 {
fromDate = "\t" + diff.FromDate
}
toDate := ""
if len(diff.ToDate) > 0 {
toDate = "\t" + diff.ToDate
}
if diff.FromFile != "" || diff.ToFile != "" {
wf("*** %s%s%s", diff.FromFile, fromDate, diff.Eol)
wf("--- %s%s%s", diff.ToFile, toDate, diff.Eol)
}
}
first, last := g[0], g[len(g)-1]
ws("***************" + diff.Eol)
range1 := formatRangeContext(first.I1, last.I2)
wf("*** %s ****%s", range1, diff.Eol)
for _, c := range g {
if c.Tag == 'r' || c.Tag == 'd' {
for _, cc := range g {
if cc.Tag == 'i' {
continue
}
for _, line := range diff.A[cc.I1:cc.I2] {
ws(prefix[cc.Tag] + line)
}
}
break
}
}
range2 := formatRangeContext(first.J1, last.J2)
wf("--- %s ----%s", range2, diff.Eol)
for _, c := range g {
if c.Tag == 'r' || c.Tag == 'i' {
for _, cc := range g {
if cc.Tag == 'd' {
continue
}
for _, line := range diff.B[cc.J1:cc.J2] {
ws(prefix[cc.Tag] + line)
}
}
break
}
}
}
return diffErr
}
// Like WriteContextDiff but returns the diff a string.
func GetContextDiffString(diff ContextDiff) (string, error) {
w := &bytes.Buffer{}
err := WriteContextDiff(w, diff)
return string(w.Bytes()), err
}
// Split a string on "\n" while preserving them. The output can be used
// as input for UnifiedDiff and ContextDiff structures.
func SplitLines(s string) []string {
lines := strings.SplitAfter(s, "\n")
lines[len(lines)-1] += "\n"
return lines
}

21
vendor/github.com/stretchr/testify/LICENSE generated vendored Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,495 @@
package assert
import (
"bytes"
"fmt"
"reflect"
"time"
)
// Deprecated: CompareType has only ever been for internal use and has accidentally been published since v1.6.0. Do not use it.
type CompareType = compareResult
type compareResult int
const (
compareLess compareResult = iota - 1
compareEqual
compareGreater
)
var (
intType = reflect.TypeOf(int(1))
int8Type = reflect.TypeOf(int8(1))
int16Type = reflect.TypeOf(int16(1))
int32Type = reflect.TypeOf(int32(1))
int64Type = reflect.TypeOf(int64(1))
uintType = reflect.TypeOf(uint(1))
uint8Type = reflect.TypeOf(uint8(1))
uint16Type = reflect.TypeOf(uint16(1))
uint32Type = reflect.TypeOf(uint32(1))
uint64Type = reflect.TypeOf(uint64(1))
uintptrType = reflect.TypeOf(uintptr(1))
float32Type = reflect.TypeOf(float32(1))
float64Type = reflect.TypeOf(float64(1))
stringType = reflect.TypeOf("")
timeType = reflect.TypeOf(time.Time{})
bytesType = reflect.TypeOf([]byte{})
)
func compare(obj1, obj2 interface{}, kind reflect.Kind) (compareResult, bool) {
obj1Value := reflect.ValueOf(obj1)
obj2Value := reflect.ValueOf(obj2)
// throughout this switch we try and avoid calling .Convert() if possible,
// as this has a pretty big performance impact
switch kind {
case reflect.Int:
{
intobj1, ok := obj1.(int)
if !ok {
intobj1 = obj1Value.Convert(intType).Interface().(int)
}
intobj2, ok := obj2.(int)
if !ok {
intobj2 = obj2Value.Convert(intType).Interface().(int)
}
if intobj1 > intobj2 {
return compareGreater, true
}
if intobj1 == intobj2 {
return compareEqual, true
}
if intobj1 < intobj2 {
return compareLess, true
}
}
case reflect.Int8:
{
int8obj1, ok := obj1.(int8)
if !ok {
int8obj1 = obj1Value.Convert(int8Type).Interface().(int8)
}
int8obj2, ok := obj2.(int8)
if !ok {
int8obj2 = obj2Value.Convert(int8Type).Interface().(int8)
}
if int8obj1 > int8obj2 {
return compareGreater, true
}
if int8obj1 == int8obj2 {
return compareEqual, true
}
if int8obj1 < int8obj2 {
return compareLess, true
}
}
case reflect.Int16:
{
int16obj1, ok := obj1.(int16)
if !ok {
int16obj1 = obj1Value.Convert(int16Type).Interface().(int16)
}
int16obj2, ok := obj2.(int16)
if !ok {
int16obj2 = obj2Value.Convert(int16Type).Interface().(int16)
}
if int16obj1 > int16obj2 {
return compareGreater, true
}
if int16obj1 == int16obj2 {
return compareEqual, true
}
if int16obj1 < int16obj2 {
return compareLess, true
}
}
case reflect.Int32:
{
int32obj1, ok := obj1.(int32)
if !ok {
int32obj1 = obj1Value.Convert(int32Type).Interface().(int32)
}
int32obj2, ok := obj2.(int32)
if !ok {
int32obj2 = obj2Value.Convert(int32Type).Interface().(int32)
}
if int32obj1 > int32obj2 {
return compareGreater, true
}
if int32obj1 == int32obj2 {
return compareEqual, true
}
if int32obj1 < int32obj2 {
return compareLess, true
}
}
case reflect.Int64:
{
int64obj1, ok := obj1.(int64)
if !ok {
int64obj1 = obj1Value.Convert(int64Type).Interface().(int64)
}
int64obj2, ok := obj2.(int64)
if !ok {
int64obj2 = obj2Value.Convert(int64Type).Interface().(int64)
}
if int64obj1 > int64obj2 {
return compareGreater, true
}
if int64obj1 == int64obj2 {
return compareEqual, true
}
if int64obj1 < int64obj2 {
return compareLess, true
}
}
case reflect.Uint:
{
uintobj1, ok := obj1.(uint)
if !ok {
uintobj1 = obj1Value.Convert(uintType).Interface().(uint)
}
uintobj2, ok := obj2.(uint)
if !ok {
uintobj2 = obj2Value.Convert(uintType).Interface().(uint)
}
if uintobj1 > uintobj2 {
return compareGreater, true
}
if uintobj1 == uintobj2 {
return compareEqual, true
}
if uintobj1 < uintobj2 {
return compareLess, true
}
}
case reflect.Uint8:
{
uint8obj1, ok := obj1.(uint8)
if !ok {
uint8obj1 = obj1Value.Convert(uint8Type).Interface().(uint8)
}
uint8obj2, ok := obj2.(uint8)
if !ok {
uint8obj2 = obj2Value.Convert(uint8Type).Interface().(uint8)
}
if uint8obj1 > uint8obj2 {
return compareGreater, true
}
if uint8obj1 == uint8obj2 {
return compareEqual, true
}
if uint8obj1 < uint8obj2 {
return compareLess, true
}
}
case reflect.Uint16:
{
uint16obj1, ok := obj1.(uint16)
if !ok {
uint16obj1 = obj1Value.Convert(uint16Type).Interface().(uint16)
}
uint16obj2, ok := obj2.(uint16)
if !ok {
uint16obj2 = obj2Value.Convert(uint16Type).Interface().(uint16)
}
if uint16obj1 > uint16obj2 {
return compareGreater, true
}
if uint16obj1 == uint16obj2 {
return compareEqual, true
}
if uint16obj1 < uint16obj2 {
return compareLess, true
}
}
case reflect.Uint32:
{
uint32obj1, ok := obj1.(uint32)
if !ok {
uint32obj1 = obj1Value.Convert(uint32Type).Interface().(uint32)
}
uint32obj2, ok := obj2.(uint32)
if !ok {
uint32obj2 = obj2Value.Convert(uint32Type).Interface().(uint32)
}
if uint32obj1 > uint32obj2 {
return compareGreater, true
}
if uint32obj1 == uint32obj2 {
return compareEqual, true
}
if uint32obj1 < uint32obj2 {
return compareLess, true
}
}
case reflect.Uint64:
{
uint64obj1, ok := obj1.(uint64)
if !ok {
uint64obj1 = obj1Value.Convert(uint64Type).Interface().(uint64)
}
uint64obj2, ok := obj2.(uint64)
if !ok {
uint64obj2 = obj2Value.Convert(uint64Type).Interface().(uint64)
}
if uint64obj1 > uint64obj2 {
return compareGreater, true
}
if uint64obj1 == uint64obj2 {
return compareEqual, true
}
if uint64obj1 < uint64obj2 {
return compareLess, true
}
}
case reflect.Float32:
{
float32obj1, ok := obj1.(float32)
if !ok {
float32obj1 = obj1Value.Convert(float32Type).Interface().(float32)
}
float32obj2, ok := obj2.(float32)
if !ok {
float32obj2 = obj2Value.Convert(float32Type).Interface().(float32)
}
if float32obj1 > float32obj2 {
return compareGreater, true
}
if float32obj1 == float32obj2 {
return compareEqual, true
}
if float32obj1 < float32obj2 {
return compareLess, true
}
}
case reflect.Float64:
{
float64obj1, ok := obj1.(float64)
if !ok {
float64obj1 = obj1Value.Convert(float64Type).Interface().(float64)
}
float64obj2, ok := obj2.(float64)
if !ok {
float64obj2 = obj2Value.Convert(float64Type).Interface().(float64)
}
if float64obj1 > float64obj2 {
return compareGreater, true
}
if float64obj1 == float64obj2 {
return compareEqual, true
}
if float64obj1 < float64obj2 {
return compareLess, true
}
}
case reflect.String:
{
stringobj1, ok := obj1.(string)
if !ok {
stringobj1 = obj1Value.Convert(stringType).Interface().(string)
}
stringobj2, ok := obj2.(string)
if !ok {
stringobj2 = obj2Value.Convert(stringType).Interface().(string)
}
if stringobj1 > stringobj2 {
return compareGreater, true
}
if stringobj1 == stringobj2 {
return compareEqual, true
}
if stringobj1 < stringobj2 {
return compareLess, true
}
}
// Check for known struct types we can check for compare results.
case reflect.Struct:
{
// All structs enter here. We're not interested in most types.
if !obj1Value.CanConvert(timeType) {
break
}
// time.Time can be compared!
timeObj1, ok := obj1.(time.Time)
if !ok {
timeObj1 = obj1Value.Convert(timeType).Interface().(time.Time)
}
timeObj2, ok := obj2.(time.Time)
if !ok {
timeObj2 = obj2Value.Convert(timeType).Interface().(time.Time)
}
if timeObj1.Before(timeObj2) {
return compareLess, true
}
if timeObj1.Equal(timeObj2) {
return compareEqual, true
}
return compareGreater, true
}
case reflect.Slice:
{
// We only care about the []byte type.
if !obj1Value.CanConvert(bytesType) {
break
}
// []byte can be compared!
bytesObj1, ok := obj1.([]byte)
if !ok {
bytesObj1 = obj1Value.Convert(bytesType).Interface().([]byte)
}
bytesObj2, ok := obj2.([]byte)
if !ok {
bytesObj2 = obj2Value.Convert(bytesType).Interface().([]byte)
}
return compareResult(bytes.Compare(bytesObj1, bytesObj2)), true
}
case reflect.Uintptr:
{
uintptrObj1, ok := obj1.(uintptr)
if !ok {
uintptrObj1 = obj1Value.Convert(uintptrType).Interface().(uintptr)
}
uintptrObj2, ok := obj2.(uintptr)
if !ok {
uintptrObj2 = obj2Value.Convert(uintptrType).Interface().(uintptr)
}
if uintptrObj1 > uintptrObj2 {
return compareGreater, true
}
if uintptrObj1 == uintptrObj2 {
return compareEqual, true
}
if uintptrObj1 < uintptrObj2 {
return compareLess, true
}
}
}
return compareEqual, false
}
// Greater asserts that the first element is greater than the second
//
// assert.Greater(t, 2, 1)
// assert.Greater(t, float64(2), float64(1))
// assert.Greater(t, "b", "a")
func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
failMessage := fmt.Sprintf("\"%v\" is not greater than \"%v\"", e1, e2)
return compareTwoValues(t, e1, e2, []compareResult{compareGreater}, failMessage, msgAndArgs...)
}
// GreaterOrEqual asserts that the first element is greater than or equal to the second
//
// assert.GreaterOrEqual(t, 2, 1)
// assert.GreaterOrEqual(t, 2, 2)
// assert.GreaterOrEqual(t, "b", "a")
// assert.GreaterOrEqual(t, "b", "b")
func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
failMessage := fmt.Sprintf("\"%v\" is not greater than or equal to \"%v\"", e1, e2)
return compareTwoValues(t, e1, e2, []compareResult{compareGreater, compareEqual}, failMessage, msgAndArgs...)
}
// Less asserts that the first element is less than the second
//
// assert.Less(t, 1, 2)
// assert.Less(t, float64(1), float64(2))
// assert.Less(t, "a", "b")
func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
failMessage := fmt.Sprintf("\"%v\" is not less than \"%v\"", e1, e2)
return compareTwoValues(t, e1, e2, []compareResult{compareLess}, failMessage, msgAndArgs...)
}
// LessOrEqual asserts that the first element is less than or equal to the second
//
// assert.LessOrEqual(t, 1, 2)
// assert.LessOrEqual(t, 2, 2)
// assert.LessOrEqual(t, "a", "b")
// assert.LessOrEqual(t, "b", "b")
func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
failMessage := fmt.Sprintf("\"%v\" is not less than or equal to \"%v\"", e1, e2)
return compareTwoValues(t, e1, e2, []compareResult{compareLess, compareEqual}, failMessage, msgAndArgs...)
}
// Positive asserts that the specified element is positive
//
// assert.Positive(t, 1)
// assert.Positive(t, 1.23)
func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
zero := reflect.Zero(reflect.TypeOf(e))
failMessage := fmt.Sprintf("\"%v\" is not positive", e)
return compareTwoValues(t, e, zero.Interface(), []compareResult{compareGreater}, failMessage, msgAndArgs...)
}
// Negative asserts that the specified element is negative
//
// assert.Negative(t, -1)
// assert.Negative(t, -1.23)
func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
zero := reflect.Zero(reflect.TypeOf(e))
failMessage := fmt.Sprintf("\"%v\" is not negative", e)
return compareTwoValues(t, e, zero.Interface(), []compareResult{compareLess}, failMessage, msgAndArgs...)
}
func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []compareResult, failMessage string, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
e1Kind := reflect.ValueOf(e1).Kind()
e2Kind := reflect.ValueOf(e2).Kind()
if e1Kind != e2Kind {
return Fail(t, "Elements should be the same type", msgAndArgs...)
}
compareResult, isComparable := compare(e1, e2, e1Kind)
if !isComparable {
return Fail(t, fmt.Sprintf(`Can not compare type "%T"`, e1), msgAndArgs...)
}
if !containsValue(allowedComparesResults, compareResult) {
return Fail(t, failMessage, msgAndArgs...)
}
return true
}
func containsValue(values []compareResult, value compareResult) bool {
for _, v := range values {
if v == value {
return true
}
}
return false
}

View file

@ -0,0 +1,866 @@
// Code generated with github.com/stretchr/testify/_codegen; DO NOT EDIT.
package assert
import (
http "net/http"
url "net/url"
time "time"
)
// Conditionf uses a Comparison to assert a complex condition.
func Conditionf(t TestingT, comp Comparison, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Condition(t, comp, append([]interface{}{msg}, args...)...)
}
// Containsf asserts that the specified string, list(array, slice...) or map contains the
// specified substring or element.
//
// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted")
// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted")
// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted")
func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Contains(t, s, contains, append([]interface{}{msg}, args...)...)
}
// DirExistsf checks whether a directory exists in the given path. It also fails
// if the path is a file rather a directory or there is an error checking whether it exists.
func DirExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return DirExists(t, path, append([]interface{}{msg}, args...)...)
}
// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified
// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements,
// the number of appearances of each of them in both lists should match.
//
// assert.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted")
func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return ElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...)
}
// Emptyf asserts that the given value is "empty".
//
// [Zero values] are "empty".
//
// Arrays are "empty" if every element is the zero value of the type (stricter than "empty").
//
// Slices, maps and channels with zero length are "empty".
//
// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty".
//
// assert.Emptyf(t, obj, "error message %s", "formatted")
//
// [Zero values]: https://go.dev/ref/spec#The_zero_value
func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Empty(t, object, append([]interface{}{msg}, args...)...)
}
// Equalf asserts that two objects are equal.
//
// assert.Equalf(t, 123, 123, "error message %s", "formatted")
//
// Pointer variable equality is determined based on the equality of the
// referenced values (as opposed to the memory addresses). Function equality
// cannot be determined and will always fail.
func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Equal(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// EqualErrorf asserts that a function returned an error (i.e. not `nil`)
// and that it is equal to the provided error.
//
// actualObj, err := SomeFunction()
// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted")
func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return EqualError(t, theError, errString, append([]interface{}{msg}, args...)...)
}
// EqualExportedValuesf asserts that the types of two objects are equal and their public
// fields are also equal. This is useful for comparing structs that have private fields
// that could potentially differ.
//
// type S struct {
// Exported int
// notExported int
// }
// assert.EqualExportedValuesf(t, S{1, 2}, S{1, 3}, "error message %s", "formatted") => true
// assert.EqualExportedValuesf(t, S{1, 2}, S{2, 3}, "error message %s", "formatted") => false
func EqualExportedValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return EqualExportedValues(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// EqualValuesf asserts that two objects are equal or convertible to the larger
// type and equal.
//
// assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted")
func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return EqualValues(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// Errorf asserts that a function returned an error (i.e. not `nil`).
//
// actualObj, err := SomeFunction()
// assert.Errorf(t, err, "error message %s", "formatted")
func Errorf(t TestingT, err error, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Error(t, err, append([]interface{}{msg}, args...)...)
}
// ErrorAsf asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value.
// This is a wrapper for errors.As.
func ErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return ErrorAs(t, err, target, append([]interface{}{msg}, args...)...)
}
// ErrorContainsf asserts that a function returned an error (i.e. not `nil`)
// and that the error contains the specified substring.
//
// actualObj, err := SomeFunction()
// assert.ErrorContainsf(t, err, expectedErrorSubString, "error message %s", "formatted")
func ErrorContainsf(t TestingT, theError error, contains string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return ErrorContains(t, theError, contains, append([]interface{}{msg}, args...)...)
}
// ErrorIsf asserts that at least one of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return ErrorIs(t, err, target, append([]interface{}{msg}, args...)...)
}
// Eventuallyf asserts that given condition will be met in waitFor time,
// periodically checking target function each tick.
//
// assert.Eventuallyf(t, func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted")
func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Eventually(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...)
}
// EventuallyWithTf asserts that given condition will be met in waitFor time,
// periodically checking target function each tick. In contrast to Eventually,
// it supplies a CollectT to the condition function, so that the condition
// function can use the CollectT to call other assertions.
// The condition is considered "met" if no errors are raised in a tick.
// The supplied CollectT collects all errors from one tick (if there are any).
// If the condition is not met before waitFor, the collected errors of
// the last tick are copied to t.
//
// externalValue := false
// go func() {
// time.Sleep(8*time.Second)
// externalValue = true
// }()
// assert.EventuallyWithTf(t, func(c *assert.CollectT, "error message %s", "formatted") {
// // add assertions as needed; any assertion failure will fail the current tick
// assert.True(c, externalValue, "expected 'externalValue' to be true")
// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false")
func EventuallyWithTf(t TestingT, condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return EventuallyWithT(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...)
}
// Exactlyf asserts that two objects are equal in value and type.
//
// assert.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted")
func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Exactly(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// Failf reports a failure through
func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Fail(t, failureMessage, append([]interface{}{msg}, args...)...)
}
// FailNowf fails test
func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return FailNow(t, failureMessage, append([]interface{}{msg}, args...)...)
}
// Falsef asserts that the specified value is false.
//
// assert.Falsef(t, myBool, "error message %s", "formatted")
func Falsef(t TestingT, value bool, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return False(t, value, append([]interface{}{msg}, args...)...)
}
// FileExistsf checks whether a file exists in the given path. It also fails if
// the path points to a directory or there is an error when trying to check the file.
func FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return FileExists(t, path, append([]interface{}{msg}, args...)...)
}
// Greaterf asserts that the first element is greater than the second
//
// assert.Greaterf(t, 2, 1, "error message %s", "formatted")
// assert.Greaterf(t, float64(2), float64(1), "error message %s", "formatted")
// assert.Greaterf(t, "b", "a", "error message %s", "formatted")
func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Greater(t, e1, e2, append([]interface{}{msg}, args...)...)
}
// GreaterOrEqualf asserts that the first element is greater than or equal to the second
//
// assert.GreaterOrEqualf(t, 2, 1, "error message %s", "formatted")
// assert.GreaterOrEqualf(t, 2, 2, "error message %s", "formatted")
// assert.GreaterOrEqualf(t, "b", "a", "error message %s", "formatted")
// assert.GreaterOrEqualf(t, "b", "b", "error message %s", "formatted")
func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return GreaterOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...)
}
// HTTPBodyContainsf asserts that a specified handler returns a
// body that contains a string.
//
// assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return HTTPBodyContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...)
}
// HTTPBodyNotContainsf asserts that a specified handler returns a
// body that does not contain a string.
//
// assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return HTTPBodyNotContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...)
}
// HTTPErrorf asserts that a specified handler returns an error status code.
//
// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return HTTPError(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
}
// HTTPRedirectf asserts that a specified handler returns a redirect status code.
//
// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return HTTPRedirect(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
}
// HTTPStatusCodef asserts that a specified handler returns a specified status code.
//
// assert.HTTPStatusCodef(t, myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPStatusCodef(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return HTTPStatusCode(t, handler, method, url, values, statuscode, append([]interface{}{msg}, args...)...)
}
// HTTPSuccessf asserts that a specified handler returns a success status code.
//
// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return HTTPSuccess(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
}
// Implementsf asserts that an object is implemented by the specified interface.
//
// assert.Implementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted")
func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Implements(t, interfaceObject, object, append([]interface{}{msg}, args...)...)
}
// InDeltaf asserts that the two numerals are within delta of each other.
//
// assert.InDeltaf(t, math.Pi, 22/7.0, 0.01, "error message %s", "formatted")
func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return InDelta(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
}
// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys.
func InDeltaMapValuesf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return InDeltaMapValues(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
}
// InDeltaSlicef is the same as InDelta, except it compares two slices.
func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return InDeltaSlice(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
}
// InEpsilonf asserts that expected and actual have a relative error less than epsilon
func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return InEpsilon(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
}
// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices.
func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return InEpsilonSlice(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
}
// IsDecreasingf asserts that the collection is decreasing
//
// assert.IsDecreasingf(t, []int{2, 1, 0}, "error message %s", "formatted")
// assert.IsDecreasingf(t, []float{2, 1}, "error message %s", "formatted")
// assert.IsDecreasingf(t, []string{"b", "a"}, "error message %s", "formatted")
func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return IsDecreasing(t, object, append([]interface{}{msg}, args...)...)
}
// IsIncreasingf asserts that the collection is increasing
//
// assert.IsIncreasingf(t, []int{1, 2, 3}, "error message %s", "formatted")
// assert.IsIncreasingf(t, []float{1, 2}, "error message %s", "formatted")
// assert.IsIncreasingf(t, []string{"a", "b"}, "error message %s", "formatted")
func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return IsIncreasing(t, object, append([]interface{}{msg}, args...)...)
}
// IsNonDecreasingf asserts that the collection is not decreasing
//
// assert.IsNonDecreasingf(t, []int{1, 1, 2}, "error message %s", "formatted")
// assert.IsNonDecreasingf(t, []float{1, 2}, "error message %s", "formatted")
// assert.IsNonDecreasingf(t, []string{"a", "b"}, "error message %s", "formatted")
func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return IsNonDecreasing(t, object, append([]interface{}{msg}, args...)...)
}
// IsNonIncreasingf asserts that the collection is not increasing
//
// assert.IsNonIncreasingf(t, []int{2, 1, 1}, "error message %s", "formatted")
// assert.IsNonIncreasingf(t, []float{2, 1}, "error message %s", "formatted")
// assert.IsNonIncreasingf(t, []string{"b", "a"}, "error message %s", "formatted")
func IsNonIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return IsNonIncreasing(t, object, append([]interface{}{msg}, args...)...)
}
// IsNotTypef asserts that the specified objects are not of the same type.
//
// assert.IsNotTypef(t, &NotMyStruct{}, &MyStruct{}, "error message %s", "formatted")
func IsNotTypef(t TestingT, theType interface{}, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return IsNotType(t, theType, object, append([]interface{}{msg}, args...)...)
}
// IsTypef asserts that the specified objects are of the same type.
//
// assert.IsTypef(t, &MyStruct{}, &MyStruct{}, "error message %s", "formatted")
func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return IsType(t, expectedType, object, append([]interface{}{msg}, args...)...)
}
// JSONEqf asserts that two JSON strings are equivalent.
//
// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted")
func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return JSONEq(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// Lenf asserts that the specified object has specific length.
// Lenf also fails if the object has a type that len() not accept.
//
// assert.Lenf(t, mySlice, 3, "error message %s", "formatted")
func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Len(t, object, length, append([]interface{}{msg}, args...)...)
}
// Lessf asserts that the first element is less than the second
//
// assert.Lessf(t, 1, 2, "error message %s", "formatted")
// assert.Lessf(t, float64(1), float64(2), "error message %s", "formatted")
// assert.Lessf(t, "a", "b", "error message %s", "formatted")
func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Less(t, e1, e2, append([]interface{}{msg}, args...)...)
}
// LessOrEqualf asserts that the first element is less than or equal to the second
//
// assert.LessOrEqualf(t, 1, 2, "error message %s", "formatted")
// assert.LessOrEqualf(t, 2, 2, "error message %s", "formatted")
// assert.LessOrEqualf(t, "a", "b", "error message %s", "formatted")
// assert.LessOrEqualf(t, "b", "b", "error message %s", "formatted")
func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return LessOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...)
}
// Negativef asserts that the specified element is negative
//
// assert.Negativef(t, -1, "error message %s", "formatted")
// assert.Negativef(t, -1.23, "error message %s", "formatted")
func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Negative(t, e, append([]interface{}{msg}, args...)...)
}
// Neverf asserts that the given condition doesn't satisfy in waitFor time,
// periodically checking the target function each tick.
//
// assert.Neverf(t, func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted")
func Neverf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Never(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...)
}
// Nilf asserts that the specified object is nil.
//
// assert.Nilf(t, err, "error message %s", "formatted")
func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Nil(t, object, append([]interface{}{msg}, args...)...)
}
// NoDirExistsf checks whether a directory does not exist in the given path.
// It fails if the path points to an existing _directory_ only.
func NoDirExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NoDirExists(t, path, append([]interface{}{msg}, args...)...)
}
// NoErrorf asserts that a function returned no error (i.e. `nil`).
//
// actualObj, err := SomeFunction()
// if assert.NoErrorf(t, err, "error message %s", "formatted") {
// assert.Equal(t, expectedObj, actualObj)
// }
func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NoError(t, err, append([]interface{}{msg}, args...)...)
}
// NoFileExistsf checks whether a file does not exist in a given path. It fails
// if the path points to an existing _file_ only.
func NoFileExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NoFileExists(t, path, append([]interface{}{msg}, args...)...)
}
// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the
// specified substring or element.
//
// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted")
// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted")
// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted")
func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotContains(t, s, contains, append([]interface{}{msg}, args...)...)
}
// NotElementsMatchf asserts that the specified listA(array, slice...) is NOT equal to specified
// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements,
// the number of appearances of each of them in both lists should not match.
// This is an inverse of ElementsMatch.
//
// assert.NotElementsMatchf(t, [1, 1, 2, 3], [1, 1, 2, 3], "error message %s", "formatted") -> false
//
// assert.NotElementsMatchf(t, [1, 1, 2, 3], [1, 2, 3], "error message %s", "formatted") -> true
//
// assert.NotElementsMatchf(t, [1, 2, 3], [1, 2, 4], "error message %s", "formatted") -> true
func NotElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...)
}
// NotEmptyf asserts that the specified object is NOT [Empty].
//
// if assert.NotEmptyf(t, obj, "error message %s", "formatted") {
// assert.Equal(t, "two", obj[1])
// }
func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotEmpty(t, object, append([]interface{}{msg}, args...)...)
}
// NotEqualf asserts that the specified values are NOT equal.
//
// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted")
//
// Pointer variable equality is determined based on the equality of the
// referenced values (as opposed to the memory addresses).
func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotEqual(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// NotEqualValuesf asserts that two objects are not equal even when converted to the same type
//
// assert.NotEqualValuesf(t, obj1, obj2, "error message %s", "formatted")
func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotEqualValues(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// NotErrorAsf asserts that none of the errors in err's chain matches target,
// but if so, sets target to that error value.
func NotErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotErrorAs(t, err, target, append([]interface{}{msg}, args...)...)
}
// NotErrorIsf asserts that none of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotErrorIs(t, err, target, append([]interface{}{msg}, args...)...)
}
// NotImplementsf asserts that an object does not implement the specified interface.
//
// assert.NotImplementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted")
func NotImplementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotImplements(t, interfaceObject, object, append([]interface{}{msg}, args...)...)
}
// NotNilf asserts that the specified object is not nil.
//
// assert.NotNilf(t, err, "error message %s", "formatted")
func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotNil(t, object, append([]interface{}{msg}, args...)...)
}
// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic.
//
// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted")
func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotPanics(t, f, append([]interface{}{msg}, args...)...)
}
// NotRegexpf asserts that a specified regexp does not match a string.
//
// assert.NotRegexpf(t, regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted")
// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted")
func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotRegexp(t, rx, str, append([]interface{}{msg}, args...)...)
}
// NotSamef asserts that two pointers do not reference the same object.
//
// assert.NotSamef(t, ptr1, ptr2, "error message %s", "formatted")
//
// Both arguments must be pointer variables. Pointer variable sameness is
// determined based on the equality of both type and value.
func NotSamef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotSame(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// NotSubsetf asserts that the list (array, slice, or map) does NOT contain all
// elements given in the subset (array, slice, or map).
// Map elements are key-value pairs unless compared with an array or slice where
// only the map key is evaluated.
//
// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "error message %s", "formatted")
// assert.NotSubsetf(t, {"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted")
// assert.NotSubsetf(t, [1, 3, 4], {1: "one", 2: "two"}, "error message %s", "formatted")
// assert.NotSubsetf(t, {"x": 1, "y": 2}, ["z"], "error message %s", "formatted")
func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotSubset(t, list, subset, append([]interface{}{msg}, args...)...)
}
// NotZerof asserts that i is not the zero value for its type.
func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotZero(t, i, append([]interface{}{msg}, args...)...)
}
// Panicsf asserts that the code inside the specified PanicTestFunc panics.
//
// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted")
func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Panics(t, f, append([]interface{}{msg}, args...)...)
}
// PanicsWithErrorf asserts that the code inside the specified PanicTestFunc
// panics, and that the recovered panic value is an error that satisfies the
// EqualError comparison.
//
// assert.PanicsWithErrorf(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
func PanicsWithErrorf(t TestingT, errString string, f PanicTestFunc, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return PanicsWithError(t, errString, f, append([]interface{}{msg}, args...)...)
}
// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that
// the recovered panic value equals the expected panic value.
//
// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return PanicsWithValue(t, expected, f, append([]interface{}{msg}, args...)...)
}
// Positivef asserts that the specified element is positive
//
// assert.Positivef(t, 1, "error message %s", "formatted")
// assert.Positivef(t, 1.23, "error message %s", "formatted")
func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Positive(t, e, append([]interface{}{msg}, args...)...)
}
// Regexpf asserts that a specified regexp matches a string.
//
// assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted")
// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted")
func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Regexp(t, rx, str, append([]interface{}{msg}, args...)...)
}
// Samef asserts that two pointers reference the same object.
//
// assert.Samef(t, ptr1, ptr2, "error message %s", "formatted")
//
// Both arguments must be pointer variables. Pointer variable sameness is
// determined based on the equality of both type and value.
func Samef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Same(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// Subsetf asserts that the list (array, slice, or map) contains all elements
// given in the subset (array, slice, or map).
// Map elements are key-value pairs unless compared with an array or slice where
// only the map key is evaluated.
//
// assert.Subsetf(t, [1, 2, 3], [1, 2], "error message %s", "formatted")
// assert.Subsetf(t, {"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted")
// assert.Subsetf(t, [1, 2, 3], {1: "one", 2: "two"}, "error message %s", "formatted")
// assert.Subsetf(t, {"x": 1, "y": 2}, ["x"], "error message %s", "formatted")
func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Subset(t, list, subset, append([]interface{}{msg}, args...)...)
}
// Truef asserts that the specified value is true.
//
// assert.Truef(t, myBool, "error message %s", "formatted")
func Truef(t TestingT, value bool, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return True(t, value, append([]interface{}{msg}, args...)...)
}
// WithinDurationf asserts that the two times are within duration delta of each other.
//
// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted")
func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return WithinDuration(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
}
// WithinRangef asserts that a time is within a time range (inclusive).
//
// assert.WithinRangef(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted")
func WithinRangef(t TestingT, actual time.Time, start time.Time, end time.Time, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return WithinRange(t, actual, start, end, append([]interface{}{msg}, args...)...)
}
// YAMLEqf asserts that two YAML strings are equivalent.
func YAMLEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return YAMLEq(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// Zerof asserts that i is the zero value for its type.
func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Zero(t, i, append([]interface{}{msg}, args...)...)
}

View file

@ -0,0 +1,5 @@
{{.CommentFormat}}
func {{.DocInfo.Name}}f(t TestingT, {{.ParamsFormat}}) bool {
if h, ok := t.(tHelper); ok { h.Helper() }
return {{.DocInfo.Name}}(t, {{.ForwardedParamsFormat}})
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,5 @@
{{.CommentWithoutT "a"}}
func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) bool {
if h, ok := a.t.(tHelper); ok { h.Helper() }
return {{.DocInfo.Name}}(a.t, {{.ForwardedParams}})
}

View file

@ -0,0 +1,81 @@
package assert
import (
"fmt"
"reflect"
)
// isOrdered checks that collection contains orderable elements.
func isOrdered(t TestingT, object interface{}, allowedComparesResults []compareResult, failMessage string, msgAndArgs ...interface{}) bool {
objKind := reflect.TypeOf(object).Kind()
if objKind != reflect.Slice && objKind != reflect.Array {
return false
}
objValue := reflect.ValueOf(object)
objLen := objValue.Len()
if objLen <= 1 {
return true
}
value := objValue.Index(0)
valueInterface := value.Interface()
firstValueKind := value.Kind()
for i := 1; i < objLen; i++ {
prevValue := value
prevValueInterface := valueInterface
value = objValue.Index(i)
valueInterface = value.Interface()
compareResult, isComparable := compare(prevValueInterface, valueInterface, firstValueKind)
if !isComparable {
return Fail(t, fmt.Sprintf(`Can not compare type "%T" and "%T"`, value, prevValue), msgAndArgs...)
}
if !containsValue(allowedComparesResults, compareResult) {
return Fail(t, fmt.Sprintf(failMessage, prevValue, value), msgAndArgs...)
}
}
return true
}
// IsIncreasing asserts that the collection is increasing
//
// assert.IsIncreasing(t, []int{1, 2, 3})
// assert.IsIncreasing(t, []float{1, 2})
// assert.IsIncreasing(t, []string{"a", "b"})
func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
return isOrdered(t, object, []compareResult{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...)
}
// IsNonIncreasing asserts that the collection is not increasing
//
// assert.IsNonIncreasing(t, []int{2, 1, 1})
// assert.IsNonIncreasing(t, []float{2, 1})
// assert.IsNonIncreasing(t, []string{"b", "a"})
func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
return isOrdered(t, object, []compareResult{compareEqual, compareGreater}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...)
}
// IsDecreasing asserts that the collection is decreasing
//
// assert.IsDecreasing(t, []int{2, 1, 0})
// assert.IsDecreasing(t, []float{2, 1})
// assert.IsDecreasing(t, []string{"b", "a"})
func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
return isOrdered(t, object, []compareResult{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...)
}
// IsNonDecreasing asserts that the collection is not decreasing
//
// assert.IsNonDecreasing(t, []int{1, 1, 2})
// assert.IsNonDecreasing(t, []float{1, 2})
// assert.IsNonDecreasing(t, []string{"a", "b"})
func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
return isOrdered(t, object, []compareResult{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...)
}

2295
vendor/github.com/stretchr/testify/assert/assertions.go generated vendored Normal file

File diff suppressed because it is too large Load diff

50
vendor/github.com/stretchr/testify/assert/doc.go generated vendored Normal file
View file

@ -0,0 +1,50 @@
// Package assert provides a set of comprehensive testing tools for use with the normal Go testing system.
//
// # Note
//
// All functions in this package return a bool value indicating whether the assertion has passed.
//
// # Example Usage
//
// The following is a complete example using assert in a standard test function:
//
// import (
// "testing"
// "github.com/stretchr/testify/assert"
// )
//
// func TestSomething(t *testing.T) {
//
// var a string = "Hello"
// var b string = "Hello"
//
// assert.Equal(t, a, b, "The two words should be the same.")
//
// }
//
// if you assert many times, use the format below:
//
// import (
// "testing"
// "github.com/stretchr/testify/assert"
// )
//
// func TestSomething(t *testing.T) {
// assert := assert.New(t)
//
// var a string = "Hello"
// var b string = "Hello"
//
// assert.Equal(a, b, "The two words should be the same.")
// }
//
// # Assertions
//
// Assertions allow you to easily write test code, and are global funcs in the `assert` package.
// All assertion functions take, as the first argument, the `*testing.T` object provided by the
// testing framework. This allows the assertion funcs to write the failings and other details to
// the correct place.
//
// Every assertion function also takes an optional string message as the final argument,
// allowing custom error messages to be appended to the message the assertion method outputs.
package assert

10
vendor/github.com/stretchr/testify/assert/errors.go generated vendored Normal file
View file

@ -0,0 +1,10 @@
package assert
import (
"errors"
)
// AnError is an error instance useful for testing. If the code does not care
// about error specifics, and only needs to return the error for example, this
// error should be used to make the test code more readable.
var AnError = errors.New("assert.AnError general error for testing")

View file

@ -0,0 +1,16 @@
package assert
// Assertions provides assertion methods around the
// TestingT interface.
type Assertions struct {
t TestingT
}
// New makes a new Assertions object for the specified TestingT.
func New(t TestingT) *Assertions {
return &Assertions{
t: t,
}
}
//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=assert -template=assertion_forward.go.tmpl -include-format-funcs"

View file

@ -0,0 +1,165 @@
package assert
import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"strings"
)
// httpCode is a helper that returns HTTP code of the response. It returns -1 and
// an error if building a new request fails.
func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (int, error) {
w := httptest.NewRecorder()
req, err := http.NewRequest(method, url, http.NoBody)
if err != nil {
return -1, err
}
req.URL.RawQuery = values.Encode()
handler(w, req)
return w.Code, nil
}
// HTTPSuccess asserts that a specified handler returns a success status code.
//
// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil)
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
code, err := httpCode(handler, method, url, values)
if err != nil {
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err), msgAndArgs...)
}
isSuccessCode := code >= http.StatusOK && code <= http.StatusPartialContent
if !isSuccessCode {
Fail(t, fmt.Sprintf("Expected HTTP success status code for %q but received %d", url+"?"+values.Encode(), code), msgAndArgs...)
}
return isSuccessCode
}
// HTTPRedirect asserts that a specified handler returns a redirect status code.
//
// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
code, err := httpCode(handler, method, url, values)
if err != nil {
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err), msgAndArgs...)
}
isRedirectCode := code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect
if !isRedirectCode {
Fail(t, fmt.Sprintf("Expected HTTP redirect status code for %q but received %d", url+"?"+values.Encode(), code), msgAndArgs...)
}
return isRedirectCode
}
// HTTPError asserts that a specified handler returns an error status code.
//
// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
code, err := httpCode(handler, method, url, values)
if err != nil {
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err), msgAndArgs...)
}
isErrorCode := code >= http.StatusBadRequest
if !isErrorCode {
Fail(t, fmt.Sprintf("Expected HTTP error status code for %q but received %d", url+"?"+values.Encode(), code), msgAndArgs...)
}
return isErrorCode
}
// HTTPStatusCode asserts that a specified handler returns a specified status code.
//
// assert.HTTPStatusCode(t, myHandler, "GET", "/notImplemented", nil, 501)
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPStatusCode(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
code, err := httpCode(handler, method, url, values)
if err != nil {
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err), msgAndArgs...)
}
successful := code == statuscode
if !successful {
Fail(t, fmt.Sprintf("Expected HTTP status code %d for %q but received %d", statuscode, url+"?"+values.Encode(), code), msgAndArgs...)
}
return successful
}
// HTTPBody is a helper that returns HTTP body of the response. It returns
// empty string if building a new request fails.
func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) string {
w := httptest.NewRecorder()
if len(values) > 0 {
url += "?" + values.Encode()
}
req, err := http.NewRequest(method, url, http.NoBody)
if err != nil {
return ""
}
handler(w, req)
return w.Body.String()
}
// HTTPBodyContains asserts that a specified handler returns a
// body that contains a string.
//
// assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
body := HTTPBody(handler, method, url, values)
contains := strings.Contains(body, fmt.Sprint(str))
if !contains {
Fail(t, fmt.Sprintf("Expected response body for %q to contain %q but found %q", url+"?"+values.Encode(), str, body), msgAndArgs...)
}
return contains
}
// HTTPBodyNotContains asserts that a specified handler returns a
// body that does not contain a string.
//
// assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
body := HTTPBody(handler, method, url, values)
contains := strings.Contains(body, fmt.Sprint(str))
if contains {
Fail(t, fmt.Sprintf("Expected response body for %q to NOT contain %q but found %q", url+"?"+values.Encode(), str, body), msgAndArgs...)
}
return !contains
}

View file

@ -0,0 +1,24 @@
//go:build testify_yaml_custom && !testify_yaml_fail && !testify_yaml_default
// Package yaml is an implementation of YAML functions that calls a pluggable implementation.
//
// This implementation is selected with the testify_yaml_custom build tag.
//
// go test -tags testify_yaml_custom
//
// This implementation can be used at build time to replace the default implementation
// to avoid linking with [gopkg.in/yaml.v3].
//
// In your test package:
//
// import assertYaml "github.com/stretchr/testify/assert/yaml"
//
// func init() {
// assertYaml.Unmarshal = func (in []byte, out interface{}) error {
// // ...
// return nil
// }
// }
package yaml
var Unmarshal func(in []byte, out interface{}) error

View file

@ -0,0 +1,36 @@
//go:build !testify_yaml_fail && !testify_yaml_custom
// Package yaml is just an indirection to handle YAML deserialization.
//
// This package is just an indirection that allows the builder to override the
// indirection with an alternative implementation of this package that uses
// another implementation of YAML deserialization. This allows to not either not
// use YAML deserialization at all, or to use another implementation than
// [gopkg.in/yaml.v3] (for example for license compatibility reasons, see [PR #1120]).
//
// Alternative implementations are selected using build tags:
//
// - testify_yaml_fail: [Unmarshal] always fails with an error
// - testify_yaml_custom: [Unmarshal] is a variable. Caller must initialize it
// before calling any of [github.com/stretchr/testify/assert.YAMLEq] or
// [github.com/stretchr/testify/assert.YAMLEqf].
//
// Usage:
//
// go test -tags testify_yaml_fail
//
// You can check with "go list" which implementation is linked:
//
// go list -f '{{.Imports}}' github.com/stretchr/testify/assert/yaml
// go list -tags testify_yaml_fail -f '{{.Imports}}' github.com/stretchr/testify/assert/yaml
// go list -tags testify_yaml_custom -f '{{.Imports}}' github.com/stretchr/testify/assert/yaml
//
// [PR #1120]: https://github.com/stretchr/testify/pull/1120
package yaml
import goyaml "gopkg.in/yaml.v3"
// Unmarshal is just a wrapper of [gopkg.in/yaml.v3.Unmarshal].
func Unmarshal(in []byte, out interface{}) error {
return goyaml.Unmarshal(in, out)
}

View file

@ -0,0 +1,17 @@
//go:build testify_yaml_fail && !testify_yaml_custom && !testify_yaml_default
// Package yaml is an implementation of YAML functions that always fail.
//
// This implementation can be used at build time to replace the default implementation
// to avoid linking with [gopkg.in/yaml.v3]:
//
// go test -tags testify_yaml_fail
package yaml
import "errors"
var errNotImplemented = errors.New("YAML functions are not available (see https://pkg.go.dev/github.com/stretchr/testify/assert/yaml)")
func Unmarshal([]byte, interface{}) error {
return errNotImplemented
}

31
vendor/github.com/stretchr/testify/require/doc.go generated vendored Normal file
View file

@ -0,0 +1,31 @@
// Package require implements the same assertions as the `assert` package but
// stops test execution when a test fails.
//
// # Example Usage
//
// The following is a complete example using require in a standard test function:
//
// import (
// "testing"
// "github.com/stretchr/testify/require"
// )
//
// func TestSomething(t *testing.T) {
//
// var a string = "Hello"
// var b string = "Hello"
//
// require.Equal(t, a, b, "The two words should be the same.")
//
// }
//
// # Assertions
//
// The `require` package have same global functions as in the `assert` package,
// but instead of returning a boolean result they call `t.FailNow()`.
// A consequence of this is that it must be called from the goroutine running
// the test function, not from other goroutines created during the test.
//
// Every assertion function also takes an optional string message as the final argument,
// allowing custom error messages to be appended to the message the assertion method outputs.
package require

View file

@ -0,0 +1,16 @@
package require
// Assertions provides assertion methods around the
// TestingT interface.
type Assertions struct {
t TestingT
}
// New makes a new Assertions object for the specified TestingT.
func New(t TestingT) *Assertions {
return &Assertions{
t: t,
}
}
//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=require -template=require_forward.go.tmpl -include-format-funcs"

2180
vendor/github.com/stretchr/testify/require/require.go generated vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,6 @@
{{ replace .Comment "assert." "require."}}
func {{.DocInfo.Name}}(t TestingT, {{.Params}}) {
if h, ok := t.(tHelper); ok { h.Helper() }
if assert.{{.DocInfo.Name}}(t, {{.ForwardedParams}}) { return }
t.FailNow()
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,5 @@
{{.CommentWithoutT "a"}}
func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) {
if h, ok := a.t.(tHelper); ok { h.Helper() }
{{.DocInfo.Name}}(a.t, {{.ForwardedParams}})
}

View file

@ -0,0 +1,29 @@
package require
// TestingT is an interface wrapper around *testing.T
type TestingT interface {
Errorf(format string, args ...interface{})
FailNow()
}
type tHelper = interface {
Helper()
}
// ComparisonAssertionFunc is a common function prototype when comparing two values. Can be useful
// for table driven tests.
type ComparisonAssertionFunc func(TestingT, interface{}, interface{}, ...interface{})
// ValueAssertionFunc is a common function prototype when validating a single value. Can be useful
// for table driven tests.
type ValueAssertionFunc func(TestingT, interface{}, ...interface{})
// BoolAssertionFunc is a common function prototype when validating a bool value. Can be useful
// for table driven tests.
type BoolAssertionFunc func(TestingT, bool, ...interface{})
// ErrorAssertionFunc is a common function prototype when validating an error value. Can be useful
// for table driven tests.
type ErrorAssertionFunc func(TestingT, error, ...interface{})
//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=require -template=require.go.tmpl -include-format-funcs"

36
vendor/golang.org/x/sys/unix/auxv.go generated vendored Normal file
View file

@ -0,0 +1,36 @@
// Copyright 2025 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 go1.21 && (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos)
package unix
import (
"syscall"
"unsafe"
)
//go:linkname runtime_getAuxv runtime.getAuxv
func runtime_getAuxv() []uintptr
// Auxv returns the ELF auxiliary vector as a sequence of key/value pairs.
// The returned slice is always a fresh copy, owned by the caller.
// It returns an error on non-ELF platforms, or if the auxiliary vector cannot be accessed,
// which happens in some locked-down environments and build modes.
func Auxv() ([][2]uintptr, error) {
vec := runtime_getAuxv()
vecLen := len(vec)
if vecLen == 0 {
return nil, syscall.ENOENT
}
if vecLen%2 != 0 {
return nil, syscall.EINVAL
}
result := make([]uintptr, vecLen)
copy(result, vec)
return unsafe.Slice((*[2]uintptr)(unsafe.Pointer(&result[0])), vecLen/2), nil
}

13
vendor/golang.org/x/sys/unix/auxv_unsupported.go generated vendored Normal file
View file

@ -0,0 +1,13 @@
// Copyright 2025 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 !go1.21 && (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos)
package unix
import "syscall"
func Auxv() ([][2]uintptr, error) {
return nil, syscall.ENOTSUP
}

50
vendor/gopkg.in/yaml.v3/LICENSE generated vendored Normal file
View file

@ -0,0 +1,50 @@
This project is covered by two different licenses: MIT and Apache.
#### MIT License ####
The following files were ported to Go from C files of libyaml, and thus
are still covered by their original MIT license, with the additional
copyright staring in 2011 when the project was ported over:
apic.go emitterc.go parserc.go readerc.go scannerc.go
writerc.go yamlh.go yamlprivateh.go
Copyright (c) 2006-2010 Kirill Simonov
Copyright (c) 2006-2011 Kirill Simonov
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
### Apache License ###
All the remaining project files are covered by the Apache license:
Copyright (c) 2011-2019 Canonical Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

13
vendor/gopkg.in/yaml.v3/NOTICE generated vendored Normal file
View file

@ -0,0 +1,13 @@
Copyright 2011-2016 Canonical Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

150
vendor/gopkg.in/yaml.v3/README.md generated vendored Normal file
View file

@ -0,0 +1,150 @@
# YAML support for the Go language
Introduction
------------
The yaml package enables Go programs to comfortably encode and decode YAML
values. It was developed within [Canonical](https://www.canonical.com) as
part of the [juju](https://juju.ubuntu.com) project, and is based on a
pure Go port of the well-known [libyaml](http://pyyaml.org/wiki/LibYAML)
C library to parse and generate YAML data quickly and reliably.
Compatibility
-------------
The yaml package supports most of YAML 1.2, but preserves some behavior
from 1.1 for backwards compatibility.
Specifically, as of v3 of the yaml package:
- YAML 1.1 bools (_yes/no, on/off_) are supported as long as they are being
decoded into a typed bool value. Otherwise they behave as a string. Booleans
in YAML 1.2 are _true/false_ only.
- Octals encode and decode as _0777_ per YAML 1.1, rather than _0o777_
as specified in YAML 1.2, because most parsers still use the old format.
Octals in the _0o777_ format are supported though, so new files work.
- Does not support base-60 floats. These are gone from YAML 1.2, and were
actually never supported by this package as it's clearly a poor choice.
and offers backwards
compatibility with YAML 1.1 in some cases.
1.2, including support for
anchors, tags, map merging, etc. Multi-document unmarshalling is not yet
implemented, and base-60 floats from YAML 1.1 are purposefully not
supported since they're a poor design and are gone in YAML 1.2.
Installation and usage
----------------------
The import path for the package is *gopkg.in/yaml.v3*.
To install it, run:
go get gopkg.in/yaml.v3
API documentation
-----------------
If opened in a browser, the import path itself leads to the API documentation:
- [https://gopkg.in/yaml.v3](https://gopkg.in/yaml.v3)
API stability
-------------
The package API for yaml v3 will remain stable as described in [gopkg.in](https://gopkg.in).
License
-------
The yaml package is licensed under the MIT and Apache License 2.0 licenses.
Please see the LICENSE file for details.
Example
-------
```Go
package main
import (
"fmt"
"log"
"gopkg.in/yaml.v3"
)
var data = `
a: Easy!
b:
c: 2
d: [3, 4]
`
// Note: struct fields must be public in order for unmarshal to
// correctly populate the data.
type T struct {
A string
B struct {
RenamedC int `yaml:"c"`
D []int `yaml:",flow"`
}
}
func main() {
t := T{}
err := yaml.Unmarshal([]byte(data), &t)
if err != nil {
log.Fatalf("error: %v", err)
}
fmt.Printf("--- t:\n%v\n\n", t)
d, err := yaml.Marshal(&t)
if err != nil {
log.Fatalf("error: %v", err)
}
fmt.Printf("--- t dump:\n%s\n\n", string(d))
m := make(map[interface{}]interface{})
err = yaml.Unmarshal([]byte(data), &m)
if err != nil {
log.Fatalf("error: %v", err)
}
fmt.Printf("--- m:\n%v\n\n", m)
d, err = yaml.Marshal(&m)
if err != nil {
log.Fatalf("error: %v", err)
}
fmt.Printf("--- m dump:\n%s\n\n", string(d))
}
```
This example will generate the following output:
```
--- t:
{Easy! {2 [3 4]}}
--- t dump:
a: Easy!
b:
c: 2
d: [3, 4]
--- m:
map[a:Easy! b:map[c:2 d:[3 4]]]
--- m dump:
a: Easy!
b:
c: 2
d:
- 3
- 4
```

747
vendor/gopkg.in/yaml.v3/apic.go generated vendored Normal file
View file

@ -0,0 +1,747 @@
//
// Copyright (c) 2011-2019 Canonical Ltd
// Copyright (c) 2006-2010 Kirill Simonov
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package yaml
import (
"io"
)
func yaml_insert_token(parser *yaml_parser_t, pos int, token *yaml_token_t) {
//fmt.Println("yaml_insert_token", "pos:", pos, "typ:", token.typ, "head:", parser.tokens_head, "len:", len(parser.tokens))
// Check if we can move the queue at the beginning of the buffer.
if parser.tokens_head > 0 && len(parser.tokens) == cap(parser.tokens) {
if parser.tokens_head != len(parser.tokens) {
copy(parser.tokens, parser.tokens[parser.tokens_head:])
}
parser.tokens = parser.tokens[:len(parser.tokens)-parser.tokens_head]
parser.tokens_head = 0
}
parser.tokens = append(parser.tokens, *token)
if pos < 0 {
return
}
copy(parser.tokens[parser.tokens_head+pos+1:], parser.tokens[parser.tokens_head+pos:])
parser.tokens[parser.tokens_head+pos] = *token
}
// Create a new parser object.
func yaml_parser_initialize(parser *yaml_parser_t) bool {
*parser = yaml_parser_t{
raw_buffer: make([]byte, 0, input_raw_buffer_size),
buffer: make([]byte, 0, input_buffer_size),
}
return true
}
// Destroy a parser object.
func yaml_parser_delete(parser *yaml_parser_t) {
*parser = yaml_parser_t{}
}
// String read handler.
func yaml_string_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) {
if parser.input_pos == len(parser.input) {
return 0, io.EOF
}
n = copy(buffer, parser.input[parser.input_pos:])
parser.input_pos += n
return n, nil
}
// Reader read handler.
func yaml_reader_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) {
return parser.input_reader.Read(buffer)
}
// Set a string input.
func yaml_parser_set_input_string(parser *yaml_parser_t, input []byte) {
if parser.read_handler != nil {
panic("must set the input source only once")
}
parser.read_handler = yaml_string_read_handler
parser.input = input
parser.input_pos = 0
}
// Set a file input.
func yaml_parser_set_input_reader(parser *yaml_parser_t, r io.Reader) {
if parser.read_handler != nil {
panic("must set the input source only once")
}
parser.read_handler = yaml_reader_read_handler
parser.input_reader = r
}
// Set the source encoding.
func yaml_parser_set_encoding(parser *yaml_parser_t, encoding yaml_encoding_t) {
if parser.encoding != yaml_ANY_ENCODING {
panic("must set the encoding only once")
}
parser.encoding = encoding
}
// Create a new emitter object.
func yaml_emitter_initialize(emitter *yaml_emitter_t) {
*emitter = yaml_emitter_t{
buffer: make([]byte, output_buffer_size),
raw_buffer: make([]byte, 0, output_raw_buffer_size),
states: make([]yaml_emitter_state_t, 0, initial_stack_size),
events: make([]yaml_event_t, 0, initial_queue_size),
best_width: -1,
}
}
// Destroy an emitter object.
func yaml_emitter_delete(emitter *yaml_emitter_t) {
*emitter = yaml_emitter_t{}
}
// String write handler.
func yaml_string_write_handler(emitter *yaml_emitter_t, buffer []byte) error {
*emitter.output_buffer = append(*emitter.output_buffer, buffer...)
return nil
}
// yaml_writer_write_handler uses emitter.output_writer to write the
// emitted text.
func yaml_writer_write_handler(emitter *yaml_emitter_t, buffer []byte) error {
_, err := emitter.output_writer.Write(buffer)
return err
}
// Set a string output.
func yaml_emitter_set_output_string(emitter *yaml_emitter_t, output_buffer *[]byte) {
if emitter.write_handler != nil {
panic("must set the output target only once")
}
emitter.write_handler = yaml_string_write_handler
emitter.output_buffer = output_buffer
}
// Set a file output.
func yaml_emitter_set_output_writer(emitter *yaml_emitter_t, w io.Writer) {
if emitter.write_handler != nil {
panic("must set the output target only once")
}
emitter.write_handler = yaml_writer_write_handler
emitter.output_writer = w
}
// Set the output encoding.
func yaml_emitter_set_encoding(emitter *yaml_emitter_t, encoding yaml_encoding_t) {
if emitter.encoding != yaml_ANY_ENCODING {
panic("must set the output encoding only once")
}
emitter.encoding = encoding
}
// Set the canonical output style.
func yaml_emitter_set_canonical(emitter *yaml_emitter_t, canonical bool) {
emitter.canonical = canonical
}
// Set the indentation increment.
func yaml_emitter_set_indent(emitter *yaml_emitter_t, indent int) {
if indent < 2 || indent > 9 {
indent = 2
}
emitter.best_indent = indent
}
// Set the preferred line width.
func yaml_emitter_set_width(emitter *yaml_emitter_t, width int) {
if width < 0 {
width = -1
}
emitter.best_width = width
}
// Set if unescaped non-ASCII characters are allowed.
func yaml_emitter_set_unicode(emitter *yaml_emitter_t, unicode bool) {
emitter.unicode = unicode
}
// Set the preferred line break character.
func yaml_emitter_set_break(emitter *yaml_emitter_t, line_break yaml_break_t) {
emitter.line_break = line_break
}
///*
// * Destroy a token object.
// */
//
//YAML_DECLARE(void)
//yaml_token_delete(yaml_token_t *token)
//{
// assert(token); // Non-NULL token object expected.
//
// switch (token.type)
// {
// case YAML_TAG_DIRECTIVE_TOKEN:
// yaml_free(token.data.tag_directive.handle);
// yaml_free(token.data.tag_directive.prefix);
// break;
//
// case YAML_ALIAS_TOKEN:
// yaml_free(token.data.alias.value);
// break;
//
// case YAML_ANCHOR_TOKEN:
// yaml_free(token.data.anchor.value);
// break;
//
// case YAML_TAG_TOKEN:
// yaml_free(token.data.tag.handle);
// yaml_free(token.data.tag.suffix);
// break;
//
// case YAML_SCALAR_TOKEN:
// yaml_free(token.data.scalar.value);
// break;
//
// default:
// break;
// }
//
// memset(token, 0, sizeof(yaml_token_t));
//}
//
///*
// * Check if a string is a valid UTF-8 sequence.
// *
// * Check 'reader.c' for more details on UTF-8 encoding.
// */
//
//static int
//yaml_check_utf8(yaml_char_t *start, size_t length)
//{
// yaml_char_t *end = start+length;
// yaml_char_t *pointer = start;
//
// while (pointer < end) {
// unsigned char octet;
// unsigned int width;
// unsigned int value;
// size_t k;
//
// octet = pointer[0];
// width = (octet & 0x80) == 0x00 ? 1 :
// (octet & 0xE0) == 0xC0 ? 2 :
// (octet & 0xF0) == 0xE0 ? 3 :
// (octet & 0xF8) == 0xF0 ? 4 : 0;
// value = (octet & 0x80) == 0x00 ? octet & 0x7F :
// (octet & 0xE0) == 0xC0 ? octet & 0x1F :
// (octet & 0xF0) == 0xE0 ? octet & 0x0F :
// (octet & 0xF8) == 0xF0 ? octet & 0x07 : 0;
// if (!width) return 0;
// if (pointer+width > end) return 0;
// for (k = 1; k < width; k ++) {
// octet = pointer[k];
// if ((octet & 0xC0) != 0x80) return 0;
// value = (value << 6) + (octet & 0x3F);
// }
// if (!((width == 1) ||
// (width == 2 && value >= 0x80) ||
// (width == 3 && value >= 0x800) ||
// (width == 4 && value >= 0x10000))) return 0;
//
// pointer += width;
// }
//
// return 1;
//}
//
// Create STREAM-START.
func yaml_stream_start_event_initialize(event *yaml_event_t, encoding yaml_encoding_t) {
*event = yaml_event_t{
typ: yaml_STREAM_START_EVENT,
encoding: encoding,
}
}
// Create STREAM-END.
func yaml_stream_end_event_initialize(event *yaml_event_t) {
*event = yaml_event_t{
typ: yaml_STREAM_END_EVENT,
}
}
// Create DOCUMENT-START.
func yaml_document_start_event_initialize(
event *yaml_event_t,
version_directive *yaml_version_directive_t,
tag_directives []yaml_tag_directive_t,
implicit bool,
) {
*event = yaml_event_t{
typ: yaml_DOCUMENT_START_EVENT,
version_directive: version_directive,
tag_directives: tag_directives,
implicit: implicit,
}
}
// Create DOCUMENT-END.
func yaml_document_end_event_initialize(event *yaml_event_t, implicit bool) {
*event = yaml_event_t{
typ: yaml_DOCUMENT_END_EVENT,
implicit: implicit,
}
}
// Create ALIAS.
func yaml_alias_event_initialize(event *yaml_event_t, anchor []byte) bool {
*event = yaml_event_t{
typ: yaml_ALIAS_EVENT,
anchor: anchor,
}
return true
}
// Create SCALAR.
func yaml_scalar_event_initialize(event *yaml_event_t, anchor, tag, value []byte, plain_implicit, quoted_implicit bool, style yaml_scalar_style_t) bool {
*event = yaml_event_t{
typ: yaml_SCALAR_EVENT,
anchor: anchor,
tag: tag,
value: value,
implicit: plain_implicit,
quoted_implicit: quoted_implicit,
style: yaml_style_t(style),
}
return true
}
// Create SEQUENCE-START.
func yaml_sequence_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_sequence_style_t) bool {
*event = yaml_event_t{
typ: yaml_SEQUENCE_START_EVENT,
anchor: anchor,
tag: tag,
implicit: implicit,
style: yaml_style_t(style),
}
return true
}
// Create SEQUENCE-END.
func yaml_sequence_end_event_initialize(event *yaml_event_t) bool {
*event = yaml_event_t{
typ: yaml_SEQUENCE_END_EVENT,
}
return true
}
// Create MAPPING-START.
func yaml_mapping_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_mapping_style_t) {
*event = yaml_event_t{
typ: yaml_MAPPING_START_EVENT,
anchor: anchor,
tag: tag,
implicit: implicit,
style: yaml_style_t(style),
}
}
// Create MAPPING-END.
func yaml_mapping_end_event_initialize(event *yaml_event_t) {
*event = yaml_event_t{
typ: yaml_MAPPING_END_EVENT,
}
}
// Destroy an event object.
func yaml_event_delete(event *yaml_event_t) {
*event = yaml_event_t{}
}
///*
// * Create a document object.
// */
//
//YAML_DECLARE(int)
//yaml_document_initialize(document *yaml_document_t,
// version_directive *yaml_version_directive_t,
// tag_directives_start *yaml_tag_directive_t,
// tag_directives_end *yaml_tag_directive_t,
// start_implicit int, end_implicit int)
//{
// struct {
// error yaml_error_type_t
// } context
// struct {
// start *yaml_node_t
// end *yaml_node_t
// top *yaml_node_t
// } nodes = { NULL, NULL, NULL }
// version_directive_copy *yaml_version_directive_t = NULL
// struct {
// start *yaml_tag_directive_t
// end *yaml_tag_directive_t
// top *yaml_tag_directive_t
// } tag_directives_copy = { NULL, NULL, NULL }
// value yaml_tag_directive_t = { NULL, NULL }
// mark yaml_mark_t = { 0, 0, 0 }
//
// assert(document) // Non-NULL document object is expected.
// assert((tag_directives_start && tag_directives_end) ||
// (tag_directives_start == tag_directives_end))
// // Valid tag directives are expected.
//
// if (!STACK_INIT(&context, nodes, INITIAL_STACK_SIZE)) goto error
//
// if (version_directive) {
// version_directive_copy = yaml_malloc(sizeof(yaml_version_directive_t))
// if (!version_directive_copy) goto error
// version_directive_copy.major = version_directive.major
// version_directive_copy.minor = version_directive.minor
// }
//
// if (tag_directives_start != tag_directives_end) {
// tag_directive *yaml_tag_directive_t
// if (!STACK_INIT(&context, tag_directives_copy, INITIAL_STACK_SIZE))
// goto error
// for (tag_directive = tag_directives_start
// tag_directive != tag_directives_end; tag_directive ++) {
// assert(tag_directive.handle)
// assert(tag_directive.prefix)
// if (!yaml_check_utf8(tag_directive.handle,
// strlen((char *)tag_directive.handle)))
// goto error
// if (!yaml_check_utf8(tag_directive.prefix,
// strlen((char *)tag_directive.prefix)))
// goto error
// value.handle = yaml_strdup(tag_directive.handle)
// value.prefix = yaml_strdup(tag_directive.prefix)
// if (!value.handle || !value.prefix) goto error
// if (!PUSH(&context, tag_directives_copy, value))
// goto error
// value.handle = NULL
// value.prefix = NULL
// }
// }
//
// DOCUMENT_INIT(*document, nodes.start, nodes.end, version_directive_copy,
// tag_directives_copy.start, tag_directives_copy.top,
// start_implicit, end_implicit, mark, mark)
//
// return 1
//
//error:
// STACK_DEL(&context, nodes)
// yaml_free(version_directive_copy)
// while (!STACK_EMPTY(&context, tag_directives_copy)) {
// value yaml_tag_directive_t = POP(&context, tag_directives_copy)
// yaml_free(value.handle)
// yaml_free(value.prefix)
// }
// STACK_DEL(&context, tag_directives_copy)
// yaml_free(value.handle)
// yaml_free(value.prefix)
//
// return 0
//}
//
///*
// * Destroy a document object.
// */
//
//YAML_DECLARE(void)
//yaml_document_delete(document *yaml_document_t)
//{
// struct {
// error yaml_error_type_t
// } context
// tag_directive *yaml_tag_directive_t
//
// context.error = YAML_NO_ERROR // Eliminate a compiler warning.
//
// assert(document) // Non-NULL document object is expected.
//
// while (!STACK_EMPTY(&context, document.nodes)) {
// node yaml_node_t = POP(&context, document.nodes)
// yaml_free(node.tag)
// switch (node.type) {
// case YAML_SCALAR_NODE:
// yaml_free(node.data.scalar.value)
// break
// case YAML_SEQUENCE_NODE:
// STACK_DEL(&context, node.data.sequence.items)
// break
// case YAML_MAPPING_NODE:
// STACK_DEL(&context, node.data.mapping.pairs)
// break
// default:
// assert(0) // Should not happen.
// }
// }
// STACK_DEL(&context, document.nodes)
//
// yaml_free(document.version_directive)
// for (tag_directive = document.tag_directives.start
// tag_directive != document.tag_directives.end
// tag_directive++) {
// yaml_free(tag_directive.handle)
// yaml_free(tag_directive.prefix)
// }
// yaml_free(document.tag_directives.start)
//
// memset(document, 0, sizeof(yaml_document_t))
//}
//
///**
// * Get a document node.
// */
//
//YAML_DECLARE(yaml_node_t *)
//yaml_document_get_node(document *yaml_document_t, index int)
//{
// assert(document) // Non-NULL document object is expected.
//
// if (index > 0 && document.nodes.start + index <= document.nodes.top) {
// return document.nodes.start + index - 1
// }
// return NULL
//}
//
///**
// * Get the root object.
// */
//
//YAML_DECLARE(yaml_node_t *)
//yaml_document_get_root_node(document *yaml_document_t)
//{
// assert(document) // Non-NULL document object is expected.
//
// if (document.nodes.top != document.nodes.start) {
// return document.nodes.start
// }
// return NULL
//}
//
///*
// * Add a scalar node to a document.
// */
//
//YAML_DECLARE(int)
//yaml_document_add_scalar(document *yaml_document_t,
// tag *yaml_char_t, value *yaml_char_t, length int,
// style yaml_scalar_style_t)
//{
// struct {
// error yaml_error_type_t
// } context
// mark yaml_mark_t = { 0, 0, 0 }
// tag_copy *yaml_char_t = NULL
// value_copy *yaml_char_t = NULL
// node yaml_node_t
//
// assert(document) // Non-NULL document object is expected.
// assert(value) // Non-NULL value is expected.
//
// if (!tag) {
// tag = (yaml_char_t *)YAML_DEFAULT_SCALAR_TAG
// }
//
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
// tag_copy = yaml_strdup(tag)
// if (!tag_copy) goto error
//
// if (length < 0) {
// length = strlen((char *)value)
// }
//
// if (!yaml_check_utf8(value, length)) goto error
// value_copy = yaml_malloc(length+1)
// if (!value_copy) goto error
// memcpy(value_copy, value, length)
// value_copy[length] = '\0'
//
// SCALAR_NODE_INIT(node, tag_copy, value_copy, length, style, mark, mark)
// if (!PUSH(&context, document.nodes, node)) goto error
//
// return document.nodes.top - document.nodes.start
//
//error:
// yaml_free(tag_copy)
// yaml_free(value_copy)
//
// return 0
//}
//
///*
// * Add a sequence node to a document.
// */
//
//YAML_DECLARE(int)
//yaml_document_add_sequence(document *yaml_document_t,
// tag *yaml_char_t, style yaml_sequence_style_t)
//{
// struct {
// error yaml_error_type_t
// } context
// mark yaml_mark_t = { 0, 0, 0 }
// tag_copy *yaml_char_t = NULL
// struct {
// start *yaml_node_item_t
// end *yaml_node_item_t
// top *yaml_node_item_t
// } items = { NULL, NULL, NULL }
// node yaml_node_t
//
// assert(document) // Non-NULL document object is expected.
//
// if (!tag) {
// tag = (yaml_char_t *)YAML_DEFAULT_SEQUENCE_TAG
// }
//
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
// tag_copy = yaml_strdup(tag)
// if (!tag_copy) goto error
//
// if (!STACK_INIT(&context, items, INITIAL_STACK_SIZE)) goto error
//
// SEQUENCE_NODE_INIT(node, tag_copy, items.start, items.end,
// style, mark, mark)
// if (!PUSH(&context, document.nodes, node)) goto error
//
// return document.nodes.top - document.nodes.start
//
//error:
// STACK_DEL(&context, items)
// yaml_free(tag_copy)
//
// return 0
//}
//
///*
// * Add a mapping node to a document.
// */
//
//YAML_DECLARE(int)
//yaml_document_add_mapping(document *yaml_document_t,
// tag *yaml_char_t, style yaml_mapping_style_t)
//{
// struct {
// error yaml_error_type_t
// } context
// mark yaml_mark_t = { 0, 0, 0 }
// tag_copy *yaml_char_t = NULL
// struct {
// start *yaml_node_pair_t
// end *yaml_node_pair_t
// top *yaml_node_pair_t
// } pairs = { NULL, NULL, NULL }
// node yaml_node_t
//
// assert(document) // Non-NULL document object is expected.
//
// if (!tag) {
// tag = (yaml_char_t *)YAML_DEFAULT_MAPPING_TAG
// }
//
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
// tag_copy = yaml_strdup(tag)
// if (!tag_copy) goto error
//
// if (!STACK_INIT(&context, pairs, INITIAL_STACK_SIZE)) goto error
//
// MAPPING_NODE_INIT(node, tag_copy, pairs.start, pairs.end,
// style, mark, mark)
// if (!PUSH(&context, document.nodes, node)) goto error
//
// return document.nodes.top - document.nodes.start
//
//error:
// STACK_DEL(&context, pairs)
// yaml_free(tag_copy)
//
// return 0
//}
//
///*
// * Append an item to a sequence node.
// */
//
//YAML_DECLARE(int)
//yaml_document_append_sequence_item(document *yaml_document_t,
// sequence int, item int)
//{
// struct {
// error yaml_error_type_t
// } context
//
// assert(document) // Non-NULL document is required.
// assert(sequence > 0
// && document.nodes.start + sequence <= document.nodes.top)
// // Valid sequence id is required.
// assert(document.nodes.start[sequence-1].type == YAML_SEQUENCE_NODE)
// // A sequence node is required.
// assert(item > 0 && document.nodes.start + item <= document.nodes.top)
// // Valid item id is required.
//
// if (!PUSH(&context,
// document.nodes.start[sequence-1].data.sequence.items, item))
// return 0
//
// return 1
//}
//
///*
// * Append a pair of a key and a value to a mapping node.
// */
//
//YAML_DECLARE(int)
//yaml_document_append_mapping_pair(document *yaml_document_t,
// mapping int, key int, value int)
//{
// struct {
// error yaml_error_type_t
// } context
//
// pair yaml_node_pair_t
//
// assert(document) // Non-NULL document is required.
// assert(mapping > 0
// && document.nodes.start + mapping <= document.nodes.top)
// // Valid mapping id is required.
// assert(document.nodes.start[mapping-1].type == YAML_MAPPING_NODE)
// // A mapping node is required.
// assert(key > 0 && document.nodes.start + key <= document.nodes.top)
// // Valid key id is required.
// assert(value > 0 && document.nodes.start + value <= document.nodes.top)
// // Valid value id is required.
//
// pair.key = key
// pair.value = value
//
// if (!PUSH(&context,
// document.nodes.start[mapping-1].data.mapping.pairs, pair))
// return 0
//
// return 1
//}
//
//

1000
vendor/gopkg.in/yaml.v3/decode.go generated vendored Normal file

File diff suppressed because it is too large Load diff

2020
vendor/gopkg.in/yaml.v3/emitterc.go generated vendored Normal file

File diff suppressed because it is too large Load diff

577
vendor/gopkg.in/yaml.v3/encode.go generated vendored Normal file
View file

@ -0,0 +1,577 @@
//
// Copyright (c) 2011-2019 Canonical Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package yaml
import (
"encoding"
"fmt"
"io"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"time"
"unicode/utf8"
)
type encoder struct {
emitter yaml_emitter_t
event yaml_event_t
out []byte
flow bool
indent int
doneInit bool
}
func newEncoder() *encoder {
e := &encoder{}
yaml_emitter_initialize(&e.emitter)
yaml_emitter_set_output_string(&e.emitter, &e.out)
yaml_emitter_set_unicode(&e.emitter, true)
return e
}
func newEncoderWithWriter(w io.Writer) *encoder {
e := &encoder{}
yaml_emitter_initialize(&e.emitter)
yaml_emitter_set_output_writer(&e.emitter, w)
yaml_emitter_set_unicode(&e.emitter, true)
return e
}
func (e *encoder) init() {
if e.doneInit {
return
}
if e.indent == 0 {
e.indent = 4
}
e.emitter.best_indent = e.indent
yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING)
e.emit()
e.doneInit = true
}
func (e *encoder) finish() {
e.emitter.open_ended = false
yaml_stream_end_event_initialize(&e.event)
e.emit()
}
func (e *encoder) destroy() {
yaml_emitter_delete(&e.emitter)
}
func (e *encoder) emit() {
// This will internally delete the e.event value.
e.must(yaml_emitter_emit(&e.emitter, &e.event))
}
func (e *encoder) must(ok bool) {
if !ok {
msg := e.emitter.problem
if msg == "" {
msg = "unknown problem generating YAML content"
}
failf("%s", msg)
}
}
func (e *encoder) marshalDoc(tag string, in reflect.Value) {
e.init()
var node *Node
if in.IsValid() {
node, _ = in.Interface().(*Node)
}
if node != nil && node.Kind == DocumentNode {
e.nodev(in)
} else {
yaml_document_start_event_initialize(&e.event, nil, nil, true)
e.emit()
e.marshal(tag, in)
yaml_document_end_event_initialize(&e.event, true)
e.emit()
}
}
func (e *encoder) marshal(tag string, in reflect.Value) {
tag = shortTag(tag)
if !in.IsValid() || in.Kind() == reflect.Ptr && in.IsNil() {
e.nilv()
return
}
iface := in.Interface()
switch value := iface.(type) {
case *Node:
e.nodev(in)
return
case Node:
if !in.CanAddr() {
var n = reflect.New(in.Type()).Elem()
n.Set(in)
in = n
}
e.nodev(in.Addr())
return
case time.Time:
e.timev(tag, in)
return
case *time.Time:
e.timev(tag, in.Elem())
return
case time.Duration:
e.stringv(tag, reflect.ValueOf(value.String()))
return
case Marshaler:
v, err := value.MarshalYAML()
if err != nil {
fail(err)
}
if v == nil {
e.nilv()
return
}
e.marshal(tag, reflect.ValueOf(v))
return
case encoding.TextMarshaler:
text, err := value.MarshalText()
if err != nil {
fail(err)
}
in = reflect.ValueOf(string(text))
case nil:
e.nilv()
return
}
switch in.Kind() {
case reflect.Interface:
e.marshal(tag, in.Elem())
case reflect.Map:
e.mapv(tag, in)
case reflect.Ptr:
e.marshal(tag, in.Elem())
case reflect.Struct:
e.structv(tag, in)
case reflect.Slice, reflect.Array:
e.slicev(tag, in)
case reflect.String:
e.stringv(tag, in)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
e.intv(tag, in)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
e.uintv(tag, in)
case reflect.Float32, reflect.Float64:
e.floatv(tag, in)
case reflect.Bool:
e.boolv(tag, in)
default:
panic("cannot marshal type: " + in.Type().String())
}
}
func (e *encoder) mapv(tag string, in reflect.Value) {
e.mappingv(tag, func() {
keys := keyList(in.MapKeys())
sort.Sort(keys)
for _, k := range keys {
e.marshal("", k)
e.marshal("", in.MapIndex(k))
}
})
}
func (e *encoder) fieldByIndex(v reflect.Value, index []int) (field reflect.Value) {
for _, num := range index {
for {
if v.Kind() == reflect.Ptr {
if v.IsNil() {
return reflect.Value{}
}
v = v.Elem()
continue
}
break
}
v = v.Field(num)
}
return v
}
func (e *encoder) structv(tag string, in reflect.Value) {
sinfo, err := getStructInfo(in.Type())
if err != nil {
panic(err)
}
e.mappingv(tag, func() {
for _, info := range sinfo.FieldsList {
var value reflect.Value
if info.Inline == nil {
value = in.Field(info.Num)
} else {
value = e.fieldByIndex(in, info.Inline)
if !value.IsValid() {
continue
}
}
if info.OmitEmpty && isZero(value) {
continue
}
e.marshal("", reflect.ValueOf(info.Key))
e.flow = info.Flow
e.marshal("", value)
}
if sinfo.InlineMap >= 0 {
m := in.Field(sinfo.InlineMap)
if m.Len() > 0 {
e.flow = false
keys := keyList(m.MapKeys())
sort.Sort(keys)
for _, k := range keys {
if _, found := sinfo.FieldsMap[k.String()]; found {
panic(fmt.Sprintf("cannot have key %q in inlined map: conflicts with struct field", k.String()))
}
e.marshal("", k)
e.flow = false
e.marshal("", m.MapIndex(k))
}
}
}
})
}
func (e *encoder) mappingv(tag string, f func()) {
implicit := tag == ""
style := yaml_BLOCK_MAPPING_STYLE
if e.flow {
e.flow = false
style = yaml_FLOW_MAPPING_STYLE
}
yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)
e.emit()
f()
yaml_mapping_end_event_initialize(&e.event)
e.emit()
}
func (e *encoder) slicev(tag string, in reflect.Value) {
implicit := tag == ""
style := yaml_BLOCK_SEQUENCE_STYLE
if e.flow {
e.flow = false
style = yaml_FLOW_SEQUENCE_STYLE
}
e.must(yaml_sequence_start_event_initialize(&e.event, nil, []byte(tag), implicit, style))
e.emit()
n := in.Len()
for i := 0; i < n; i++ {
e.marshal("", in.Index(i))
}
e.must(yaml_sequence_end_event_initialize(&e.event))
e.emit()
}
// isBase60 returns whether s is in base 60 notation as defined in YAML 1.1.
//
// The base 60 float notation in YAML 1.1 is a terrible idea and is unsupported
// in YAML 1.2 and by this package, but these should be marshalled quoted for
// the time being for compatibility with other parsers.
func isBase60Float(s string) (result bool) {
// Fast path.
if s == "" {
return false
}
c := s[0]
if !(c == '+' || c == '-' || c >= '0' && c <= '9') || strings.IndexByte(s, ':') < 0 {
return false
}
// Do the full match.
return base60float.MatchString(s)
}
// From http://yaml.org/type/float.html, except the regular expression there
// is bogus. In practice parsers do not enforce the "\.[0-9_]*" suffix.
var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`)
// isOldBool returns whether s is bool notation as defined in YAML 1.1.
//
// We continue to force strings that YAML 1.1 would interpret as booleans to be
// rendered as quotes strings so that the marshalled output valid for YAML 1.1
// parsing.
func isOldBool(s string) (result bool) {
switch s {
case "y", "Y", "yes", "Yes", "YES", "on", "On", "ON",
"n", "N", "no", "No", "NO", "off", "Off", "OFF":
return true
default:
return false
}
}
func (e *encoder) stringv(tag string, in reflect.Value) {
var style yaml_scalar_style_t
s := in.String()
canUsePlain := true
switch {
case !utf8.ValidString(s):
if tag == binaryTag {
failf("explicitly tagged !!binary data must be base64-encoded")
}
if tag != "" {
failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag))
}
// It can't be encoded directly as YAML so use a binary tag
// and encode it as base64.
tag = binaryTag
s = encodeBase64(s)
case tag == "":
// Check to see if it would resolve to a specific
// tag when encoded unquoted. If it doesn't,
// there's no need to quote it.
rtag, _ := resolve("", s)
canUsePlain = rtag == strTag && !(isBase60Float(s) || isOldBool(s))
}
// Note: it's possible for user code to emit invalid YAML
// if they explicitly specify a tag and a string containing
// text that's incompatible with that tag.
switch {
case strings.Contains(s, "\n"):
if e.flow {
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
} else {
style = yaml_LITERAL_SCALAR_STYLE
}
case canUsePlain:
style = yaml_PLAIN_SCALAR_STYLE
default:
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
}
e.emitScalar(s, "", tag, style, nil, nil, nil, nil)
}
func (e *encoder) boolv(tag string, in reflect.Value) {
var s string
if in.Bool() {
s = "true"
} else {
s = "false"
}
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
}
func (e *encoder) intv(tag string, in reflect.Value) {
s := strconv.FormatInt(in.Int(), 10)
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
}
func (e *encoder) uintv(tag string, in reflect.Value) {
s := strconv.FormatUint(in.Uint(), 10)
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
}
func (e *encoder) timev(tag string, in reflect.Value) {
t := in.Interface().(time.Time)
s := t.Format(time.RFC3339Nano)
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
}
func (e *encoder) floatv(tag string, in reflect.Value) {
// Issue #352: When formatting, use the precision of the underlying value
precision := 64
if in.Kind() == reflect.Float32 {
precision = 32
}
s := strconv.FormatFloat(in.Float(), 'g', -1, precision)
switch s {
case "+Inf":
s = ".inf"
case "-Inf":
s = "-.inf"
case "NaN":
s = ".nan"
}
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
}
func (e *encoder) nilv() {
e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
}
func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t, head, line, foot, tail []byte) {
// TODO Kill this function. Replace all initialize calls by their underlining Go literals.
implicit := tag == ""
if !implicit {
tag = longTag(tag)
}
e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style))
e.event.head_comment = head
e.event.line_comment = line
e.event.foot_comment = foot
e.event.tail_comment = tail
e.emit()
}
func (e *encoder) nodev(in reflect.Value) {
e.node(in.Interface().(*Node), "")
}
func (e *encoder) node(node *Node, tail string) {
// Zero nodes behave as nil.
if node.Kind == 0 && node.IsZero() {
e.nilv()
return
}
// If the tag was not explicitly requested, and dropping it won't change the
// implicit tag of the value, don't include it in the presentation.
var tag = node.Tag
var stag = shortTag(tag)
var forceQuoting bool
if tag != "" && node.Style&TaggedStyle == 0 {
if node.Kind == ScalarNode {
if stag == strTag && node.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0 {
tag = ""
} else {
rtag, _ := resolve("", node.Value)
if rtag == stag {
tag = ""
} else if stag == strTag {
tag = ""
forceQuoting = true
}
}
} else {
var rtag string
switch node.Kind {
case MappingNode:
rtag = mapTag
case SequenceNode:
rtag = seqTag
}
if rtag == stag {
tag = ""
}
}
}
switch node.Kind {
case DocumentNode:
yaml_document_start_event_initialize(&e.event, nil, nil, true)
e.event.head_comment = []byte(node.HeadComment)
e.emit()
for _, node := range node.Content {
e.node(node, "")
}
yaml_document_end_event_initialize(&e.event, true)
e.event.foot_comment = []byte(node.FootComment)
e.emit()
case SequenceNode:
style := yaml_BLOCK_SEQUENCE_STYLE
if node.Style&FlowStyle != 0 {
style = yaml_FLOW_SEQUENCE_STYLE
}
e.must(yaml_sequence_start_event_initialize(&e.event, []byte(node.Anchor), []byte(longTag(tag)), tag == "", style))
e.event.head_comment = []byte(node.HeadComment)
e.emit()
for _, node := range node.Content {
e.node(node, "")
}
e.must(yaml_sequence_end_event_initialize(&e.event))
e.event.line_comment = []byte(node.LineComment)
e.event.foot_comment = []byte(node.FootComment)
e.emit()
case MappingNode:
style := yaml_BLOCK_MAPPING_STYLE
if node.Style&FlowStyle != 0 {
style = yaml_FLOW_MAPPING_STYLE
}
yaml_mapping_start_event_initialize(&e.event, []byte(node.Anchor), []byte(longTag(tag)), tag == "", style)
e.event.tail_comment = []byte(tail)
e.event.head_comment = []byte(node.HeadComment)
e.emit()
// The tail logic below moves the foot comment of prior keys to the following key,
// since the value for each key may be a nested structure and the foot needs to be
// processed only the entirety of the value is streamed. The last tail is processed
// with the mapping end event.
var tail string
for i := 0; i+1 < len(node.Content); i += 2 {
k := node.Content[i]
foot := k.FootComment
if foot != "" {
kopy := *k
kopy.FootComment = ""
k = &kopy
}
e.node(k, tail)
tail = foot
v := node.Content[i+1]
e.node(v, "")
}
yaml_mapping_end_event_initialize(&e.event)
e.event.tail_comment = []byte(tail)
e.event.line_comment = []byte(node.LineComment)
e.event.foot_comment = []byte(node.FootComment)
e.emit()
case AliasNode:
yaml_alias_event_initialize(&e.event, []byte(node.Value))
e.event.head_comment = []byte(node.HeadComment)
e.event.line_comment = []byte(node.LineComment)
e.event.foot_comment = []byte(node.FootComment)
e.emit()
case ScalarNode:
value := node.Value
if !utf8.ValidString(value) {
if stag == binaryTag {
failf("explicitly tagged !!binary data must be base64-encoded")
}
if stag != "" {
failf("cannot marshal invalid UTF-8 data as %s", stag)
}
// It can't be encoded directly as YAML so use a binary tag
// and encode it as base64.
tag = binaryTag
value = encodeBase64(value)
}
style := yaml_PLAIN_SCALAR_STYLE
switch {
case node.Style&DoubleQuotedStyle != 0:
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
case node.Style&SingleQuotedStyle != 0:
style = yaml_SINGLE_QUOTED_SCALAR_STYLE
case node.Style&LiteralStyle != 0:
style = yaml_LITERAL_SCALAR_STYLE
case node.Style&FoldedStyle != 0:
style = yaml_FOLDED_SCALAR_STYLE
case strings.Contains(value, "\n"):
style = yaml_LITERAL_SCALAR_STYLE
case forceQuoting:
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
}
e.emitScalar(value, node.Anchor, tag, style, []byte(node.HeadComment), []byte(node.LineComment), []byte(node.FootComment), []byte(tail))
default:
failf("cannot encode node with unknown kind %d", node.Kind)
}
}

1258
vendor/gopkg.in/yaml.v3/parserc.go generated vendored Normal file

File diff suppressed because it is too large Load diff

434
vendor/gopkg.in/yaml.v3/readerc.go generated vendored Normal file
View file

@ -0,0 +1,434 @@
//
// Copyright (c) 2011-2019 Canonical Ltd
// Copyright (c) 2006-2010 Kirill Simonov
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package yaml
import (
"io"
)
// Set the reader error and return 0.
func yaml_parser_set_reader_error(parser *yaml_parser_t, problem string, offset int, value int) bool {
parser.error = yaml_READER_ERROR
parser.problem = problem
parser.problem_offset = offset
parser.problem_value = value
return false
}
// Byte order marks.
const (
bom_UTF8 = "\xef\xbb\xbf"
bom_UTF16LE = "\xff\xfe"
bom_UTF16BE = "\xfe\xff"
)
// Determine the input stream encoding by checking the BOM symbol. If no BOM is
// found, the UTF-8 encoding is assumed. Return 1 on success, 0 on failure.
func yaml_parser_determine_encoding(parser *yaml_parser_t) bool {
// Ensure that we had enough bytes in the raw buffer.
for !parser.eof && len(parser.raw_buffer)-parser.raw_buffer_pos < 3 {
if !yaml_parser_update_raw_buffer(parser) {
return false
}
}
// Determine the encoding.
buf := parser.raw_buffer
pos := parser.raw_buffer_pos
avail := len(buf) - pos
if avail >= 2 && buf[pos] == bom_UTF16LE[0] && buf[pos+1] == bom_UTF16LE[1] {
parser.encoding = yaml_UTF16LE_ENCODING
parser.raw_buffer_pos += 2
parser.offset += 2
} else if avail >= 2 && buf[pos] == bom_UTF16BE[0] && buf[pos+1] == bom_UTF16BE[1] {
parser.encoding = yaml_UTF16BE_ENCODING
parser.raw_buffer_pos += 2
parser.offset += 2
} else if avail >= 3 && buf[pos] == bom_UTF8[0] && buf[pos+1] == bom_UTF8[1] && buf[pos+2] == bom_UTF8[2] {
parser.encoding = yaml_UTF8_ENCODING
parser.raw_buffer_pos += 3
parser.offset += 3
} else {
parser.encoding = yaml_UTF8_ENCODING
}
return true
}
// Update the raw buffer.
func yaml_parser_update_raw_buffer(parser *yaml_parser_t) bool {
size_read := 0
// Return if the raw buffer is full.
if parser.raw_buffer_pos == 0 && len(parser.raw_buffer) == cap(parser.raw_buffer) {
return true
}
// Return on EOF.
if parser.eof {
return true
}
// Move the remaining bytes in the raw buffer to the beginning.
if parser.raw_buffer_pos > 0 && parser.raw_buffer_pos < len(parser.raw_buffer) {
copy(parser.raw_buffer, parser.raw_buffer[parser.raw_buffer_pos:])
}
parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)-parser.raw_buffer_pos]
parser.raw_buffer_pos = 0
// Call the read handler to fill the buffer.
size_read, err := parser.read_handler(parser, parser.raw_buffer[len(parser.raw_buffer):cap(parser.raw_buffer)])
parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)+size_read]
if err == io.EOF {
parser.eof = true
} else if err != nil {
return yaml_parser_set_reader_error(parser, "input error: "+err.Error(), parser.offset, -1)
}
return true
}
// Ensure that the buffer contains at least `length` characters.
// Return true on success, false on failure.
//
// The length is supposed to be significantly less that the buffer size.
func yaml_parser_update_buffer(parser *yaml_parser_t, length int) bool {
if parser.read_handler == nil {
panic("read handler must be set")
}
// [Go] This function was changed to guarantee the requested length size at EOF.
// The fact we need to do this is pretty awful, but the description above implies
// for that to be the case, and there are tests
// If the EOF flag is set and the raw buffer is empty, do nothing.
if parser.eof && parser.raw_buffer_pos == len(parser.raw_buffer) {
// [Go] ACTUALLY! Read the documentation of this function above.
// This is just broken. To return true, we need to have the
// given length in the buffer. Not doing that means every single
// check that calls this function to make sure the buffer has a
// given length is Go) panicking; or C) accessing invalid memory.
//return true
}
// Return if the buffer contains enough characters.
if parser.unread >= length {
return true
}
// Determine the input encoding if it is not known yet.
if parser.encoding == yaml_ANY_ENCODING {
if !yaml_parser_determine_encoding(parser) {
return false
}
}
// Move the unread characters to the beginning of the buffer.
buffer_len := len(parser.buffer)
if parser.buffer_pos > 0 && parser.buffer_pos < buffer_len {
copy(parser.buffer, parser.buffer[parser.buffer_pos:])
buffer_len -= parser.buffer_pos
parser.buffer_pos = 0
} else if parser.buffer_pos == buffer_len {
buffer_len = 0
parser.buffer_pos = 0
}
// Open the whole buffer for writing, and cut it before returning.
parser.buffer = parser.buffer[:cap(parser.buffer)]
// Fill the buffer until it has enough characters.
first := true
for parser.unread < length {
// Fill the raw buffer if necessary.
if !first || parser.raw_buffer_pos == len(parser.raw_buffer) {
if !yaml_parser_update_raw_buffer(parser) {
parser.buffer = parser.buffer[:buffer_len]
return false
}
}
first = false
// Decode the raw buffer.
inner:
for parser.raw_buffer_pos != len(parser.raw_buffer) {
var value rune
var width int
raw_unread := len(parser.raw_buffer) - parser.raw_buffer_pos
// Decode the next character.
switch parser.encoding {
case yaml_UTF8_ENCODING:
// Decode a UTF-8 character. Check RFC 3629
// (http://www.ietf.org/rfc/rfc3629.txt) for more details.
//
// The following table (taken from the RFC) is used for
// decoding.
//
// Char. number range | UTF-8 octet sequence
// (hexadecimal) | (binary)
// --------------------+------------------------------------
// 0000 0000-0000 007F | 0xxxxxxx
// 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
// 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
// 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
//
// Additionally, the characters in the range 0xD800-0xDFFF
// are prohibited as they are reserved for use with UTF-16
// surrogate pairs.
// Determine the length of the UTF-8 sequence.
octet := parser.raw_buffer[parser.raw_buffer_pos]
switch {
case octet&0x80 == 0x00:
width = 1
case octet&0xE0 == 0xC0:
width = 2
case octet&0xF0 == 0xE0:
width = 3
case octet&0xF8 == 0xF0:
width = 4
default:
// The leading octet is invalid.
return yaml_parser_set_reader_error(parser,
"invalid leading UTF-8 octet",
parser.offset, int(octet))
}
// Check if the raw buffer contains an incomplete character.
if width > raw_unread {
if parser.eof {
return yaml_parser_set_reader_error(parser,
"incomplete UTF-8 octet sequence",
parser.offset, -1)
}
break inner
}
// Decode the leading octet.
switch {
case octet&0x80 == 0x00:
value = rune(octet & 0x7F)
case octet&0xE0 == 0xC0:
value = rune(octet & 0x1F)
case octet&0xF0 == 0xE0:
value = rune(octet & 0x0F)
case octet&0xF8 == 0xF0:
value = rune(octet & 0x07)
default:
value = 0
}
// Check and decode the trailing octets.
for k := 1; k < width; k++ {
octet = parser.raw_buffer[parser.raw_buffer_pos+k]
// Check if the octet is valid.
if (octet & 0xC0) != 0x80 {
return yaml_parser_set_reader_error(parser,
"invalid trailing UTF-8 octet",
parser.offset+k, int(octet))
}
// Decode the octet.
value = (value << 6) + rune(octet&0x3F)
}
// Check the length of the sequence against the value.
switch {
case width == 1:
case width == 2 && value >= 0x80:
case width == 3 && value >= 0x800:
case width == 4 && value >= 0x10000:
default:
return yaml_parser_set_reader_error(parser,
"invalid length of a UTF-8 sequence",
parser.offset, -1)
}
// Check the range of the value.
if value >= 0xD800 && value <= 0xDFFF || value > 0x10FFFF {
return yaml_parser_set_reader_error(parser,
"invalid Unicode character",
parser.offset, int(value))
}
case yaml_UTF16LE_ENCODING, yaml_UTF16BE_ENCODING:
var low, high int
if parser.encoding == yaml_UTF16LE_ENCODING {
low, high = 0, 1
} else {
low, high = 1, 0
}
// The UTF-16 encoding is not as simple as one might
// naively think. Check RFC 2781
// (http://www.ietf.org/rfc/rfc2781.txt).
//
// Normally, two subsequent bytes describe a Unicode
// character. However a special technique (called a
// surrogate pair) is used for specifying character
// values larger than 0xFFFF.
//
// A surrogate pair consists of two pseudo-characters:
// high surrogate area (0xD800-0xDBFF)
// low surrogate area (0xDC00-0xDFFF)
//
// The following formulas are used for decoding
// and encoding characters using surrogate pairs:
//
// U = U' + 0x10000 (0x01 00 00 <= U <= 0x10 FF FF)
// U' = yyyyyyyyyyxxxxxxxxxx (0 <= U' <= 0x0F FF FF)
// W1 = 110110yyyyyyyyyy
// W2 = 110111xxxxxxxxxx
//
// where U is the character value, W1 is the high surrogate
// area, W2 is the low surrogate area.
// Check for incomplete UTF-16 character.
if raw_unread < 2 {
if parser.eof {
return yaml_parser_set_reader_error(parser,
"incomplete UTF-16 character",
parser.offset, -1)
}
break inner
}
// Get the character.
value = rune(parser.raw_buffer[parser.raw_buffer_pos+low]) +
(rune(parser.raw_buffer[parser.raw_buffer_pos+high]) << 8)
// Check for unexpected low surrogate area.
if value&0xFC00 == 0xDC00 {
return yaml_parser_set_reader_error(parser,
"unexpected low surrogate area",
parser.offset, int(value))
}
// Check for a high surrogate area.
if value&0xFC00 == 0xD800 {
width = 4
// Check for incomplete surrogate pair.
if raw_unread < 4 {
if parser.eof {
return yaml_parser_set_reader_error(parser,
"incomplete UTF-16 surrogate pair",
parser.offset, -1)
}
break inner
}
// Get the next character.
value2 := rune(parser.raw_buffer[parser.raw_buffer_pos+low+2]) +
(rune(parser.raw_buffer[parser.raw_buffer_pos+high+2]) << 8)
// Check for a low surrogate area.
if value2&0xFC00 != 0xDC00 {
return yaml_parser_set_reader_error(parser,
"expected low surrogate area",
parser.offset+2, int(value2))
}
// Generate the value of the surrogate pair.
value = 0x10000 + ((value & 0x3FF) << 10) + (value2 & 0x3FF)
} else {
width = 2
}
default:
panic("impossible")
}
// Check if the character is in the allowed range:
// #x9 | #xA | #xD | [#x20-#x7E] (8 bit)
// | #x85 | [#xA0-#xD7FF] | [#xE000-#xFFFD] (16 bit)
// | [#x10000-#x10FFFF] (32 bit)
switch {
case value == 0x09:
case value == 0x0A:
case value == 0x0D:
case value >= 0x20 && value <= 0x7E:
case value == 0x85:
case value >= 0xA0 && value <= 0xD7FF:
case value >= 0xE000 && value <= 0xFFFD:
case value >= 0x10000 && value <= 0x10FFFF:
default:
return yaml_parser_set_reader_error(parser,
"control characters are not allowed",
parser.offset, int(value))
}
// Move the raw pointers.
parser.raw_buffer_pos += width
parser.offset += width
// Finally put the character into the buffer.
if value <= 0x7F {
// 0000 0000-0000 007F . 0xxxxxxx
parser.buffer[buffer_len+0] = byte(value)
buffer_len += 1
} else if value <= 0x7FF {
// 0000 0080-0000 07FF . 110xxxxx 10xxxxxx
parser.buffer[buffer_len+0] = byte(0xC0 + (value >> 6))
parser.buffer[buffer_len+1] = byte(0x80 + (value & 0x3F))
buffer_len += 2
} else if value <= 0xFFFF {
// 0000 0800-0000 FFFF . 1110xxxx 10xxxxxx 10xxxxxx
parser.buffer[buffer_len+0] = byte(0xE0 + (value >> 12))
parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 6) & 0x3F))
parser.buffer[buffer_len+2] = byte(0x80 + (value & 0x3F))
buffer_len += 3
} else {
// 0001 0000-0010 FFFF . 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
parser.buffer[buffer_len+0] = byte(0xF0 + (value >> 18))
parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 12) & 0x3F))
parser.buffer[buffer_len+2] = byte(0x80 + ((value >> 6) & 0x3F))
parser.buffer[buffer_len+3] = byte(0x80 + (value & 0x3F))
buffer_len += 4
}
parser.unread++
}
// On EOF, put NUL into the buffer and return.
if parser.eof {
parser.buffer[buffer_len] = 0
buffer_len++
parser.unread++
break
}
}
// [Go] Read the documentation of this function above. To return true,
// we need to have the given length in the buffer. Not doing that means
// every single check that calls this function to make sure the buffer
// has a given length is Go) panicking; or C) accessing invalid memory.
// This happens here due to the EOF above breaking early.
for buffer_len < length {
parser.buffer[buffer_len] = 0
buffer_len++
}
parser.buffer = parser.buffer[:buffer_len]
return true
}

326
vendor/gopkg.in/yaml.v3/resolve.go generated vendored Normal file
View file

@ -0,0 +1,326 @@
//
// Copyright (c) 2011-2019 Canonical Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package yaml
import (
"encoding/base64"
"math"
"regexp"
"strconv"
"strings"
"time"
)
type resolveMapItem struct {
value interface{}
tag string
}
var resolveTable = make([]byte, 256)
var resolveMap = make(map[string]resolveMapItem)
func init() {
t := resolveTable
t[int('+')] = 'S' // Sign
t[int('-')] = 'S'
for _, c := range "0123456789" {
t[int(c)] = 'D' // Digit
}
for _, c := range "yYnNtTfFoO~" {
t[int(c)] = 'M' // In map
}
t[int('.')] = '.' // Float (potentially in map)
var resolveMapList = []struct {
v interface{}
tag string
l []string
}{
{true, boolTag, []string{"true", "True", "TRUE"}},
{false, boolTag, []string{"false", "False", "FALSE"}},
{nil, nullTag, []string{"", "~", "null", "Null", "NULL"}},
{math.NaN(), floatTag, []string{".nan", ".NaN", ".NAN"}},
{math.Inf(+1), floatTag, []string{".inf", ".Inf", ".INF"}},
{math.Inf(+1), floatTag, []string{"+.inf", "+.Inf", "+.INF"}},
{math.Inf(-1), floatTag, []string{"-.inf", "-.Inf", "-.INF"}},
{"<<", mergeTag, []string{"<<"}},
}
m := resolveMap
for _, item := range resolveMapList {
for _, s := range item.l {
m[s] = resolveMapItem{item.v, item.tag}
}
}
}
const (
nullTag = "!!null"
boolTag = "!!bool"
strTag = "!!str"
intTag = "!!int"
floatTag = "!!float"
timestampTag = "!!timestamp"
seqTag = "!!seq"
mapTag = "!!map"
binaryTag = "!!binary"
mergeTag = "!!merge"
)
var longTags = make(map[string]string)
var shortTags = make(map[string]string)
func init() {
for _, stag := range []string{nullTag, boolTag, strTag, intTag, floatTag, timestampTag, seqTag, mapTag, binaryTag, mergeTag} {
ltag := longTag(stag)
longTags[stag] = ltag
shortTags[ltag] = stag
}
}
const longTagPrefix = "tag:yaml.org,2002:"
func shortTag(tag string) string {
if strings.HasPrefix(tag, longTagPrefix) {
if stag, ok := shortTags[tag]; ok {
return stag
}
return "!!" + tag[len(longTagPrefix):]
}
return tag
}
func longTag(tag string) string {
if strings.HasPrefix(tag, "!!") {
if ltag, ok := longTags[tag]; ok {
return ltag
}
return longTagPrefix + tag[2:]
}
return tag
}
func resolvableTag(tag string) bool {
switch tag {
case "", strTag, boolTag, intTag, floatTag, nullTag, timestampTag:
return true
}
return false
}
var yamlStyleFloat = regexp.MustCompile(`^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$`)
func resolve(tag string, in string) (rtag string, out interface{}) {
tag = shortTag(tag)
if !resolvableTag(tag) {
return tag, in
}
defer func() {
switch tag {
case "", rtag, strTag, binaryTag:
return
case floatTag:
if rtag == intTag {
switch v := out.(type) {
case int64:
rtag = floatTag
out = float64(v)
return
case int:
rtag = floatTag
out = float64(v)
return
}
}
}
failf("cannot decode %s `%s` as a %s", shortTag(rtag), in, shortTag(tag))
}()
// Any data is accepted as a !!str or !!binary.
// Otherwise, the prefix is enough of a hint about what it might be.
hint := byte('N')
if in != "" {
hint = resolveTable[in[0]]
}
if hint != 0 && tag != strTag && tag != binaryTag {
// Handle things we can lookup in a map.
if item, ok := resolveMap[in]; ok {
return item.tag, item.value
}
// Base 60 floats are a bad idea, were dropped in YAML 1.2, and
// are purposefully unsupported here. They're still quoted on
// the way out for compatibility with other parser, though.
switch hint {
case 'M':
// We've already checked the map above.
case '.':
// Not in the map, so maybe a normal float.
floatv, err := strconv.ParseFloat(in, 64)
if err == nil {
return floatTag, floatv
}
case 'D', 'S':
// Int, float, or timestamp.
// Only try values as a timestamp if the value is unquoted or there's an explicit
// !!timestamp tag.
if tag == "" || tag == timestampTag {
t, ok := parseTimestamp(in)
if ok {
return timestampTag, t
}
}
plain := strings.Replace(in, "_", "", -1)
intv, err := strconv.ParseInt(plain, 0, 64)
if err == nil {
if intv == int64(int(intv)) {
return intTag, int(intv)
} else {
return intTag, intv
}
}
uintv, err := strconv.ParseUint(plain, 0, 64)
if err == nil {
return intTag, uintv
}
if yamlStyleFloat.MatchString(plain) {
floatv, err := strconv.ParseFloat(plain, 64)
if err == nil {
return floatTag, floatv
}
}
if strings.HasPrefix(plain, "0b") {
intv, err := strconv.ParseInt(plain[2:], 2, 64)
if err == nil {
if intv == int64(int(intv)) {
return intTag, int(intv)
} else {
return intTag, intv
}
}
uintv, err := strconv.ParseUint(plain[2:], 2, 64)
if err == nil {
return intTag, uintv
}
} else if strings.HasPrefix(plain, "-0b") {
intv, err := strconv.ParseInt("-"+plain[3:], 2, 64)
if err == nil {
if true || intv == int64(int(intv)) {
return intTag, int(intv)
} else {
return intTag, intv
}
}
}
// Octals as introduced in version 1.2 of the spec.
// Octals from the 1.1 spec, spelled as 0777, are still
// decoded by default in v3 as well for compatibility.
// May be dropped in v4 depending on how usage evolves.
if strings.HasPrefix(plain, "0o") {
intv, err := strconv.ParseInt(plain[2:], 8, 64)
if err == nil {
if intv == int64(int(intv)) {
return intTag, int(intv)
} else {
return intTag, intv
}
}
uintv, err := strconv.ParseUint(plain[2:], 8, 64)
if err == nil {
return intTag, uintv
}
} else if strings.HasPrefix(plain, "-0o") {
intv, err := strconv.ParseInt("-"+plain[3:], 8, 64)
if err == nil {
if true || intv == int64(int(intv)) {
return intTag, int(intv)
} else {
return intTag, intv
}
}
}
default:
panic("internal error: missing handler for resolver table: " + string(rune(hint)) + " (with " + in + ")")
}
}
return strTag, in
}
// encodeBase64 encodes s as base64 that is broken up into multiple lines
// as appropriate for the resulting length.
func encodeBase64(s string) string {
const lineLen = 70
encLen := base64.StdEncoding.EncodedLen(len(s))
lines := encLen/lineLen + 1
buf := make([]byte, encLen*2+lines)
in := buf[0:encLen]
out := buf[encLen:]
base64.StdEncoding.Encode(in, []byte(s))
k := 0
for i := 0; i < len(in); i += lineLen {
j := i + lineLen
if j > len(in) {
j = len(in)
}
k += copy(out[k:], in[i:j])
if lines > 1 {
out[k] = '\n'
k++
}
}
return string(out[:k])
}
// This is a subset of the formats allowed by the regular expression
// defined at http://yaml.org/type/timestamp.html.
var allowedTimestampFormats = []string{
"2006-1-2T15:4:5.999999999Z07:00", // RCF3339Nano with short date fields.
"2006-1-2t15:4:5.999999999Z07:00", // RFC3339Nano with short date fields and lower-case "t".
"2006-1-2 15:4:5.999999999", // space separated with no time zone
"2006-1-2", // date only
// Notable exception: time.Parse cannot handle: "2001-12-14 21:59:43.10 -5"
// from the set of examples.
}
// parseTimestamp parses s as a timestamp string and
// returns the timestamp and reports whether it succeeded.
// Timestamp formats are defined at http://yaml.org/type/timestamp.html
func parseTimestamp(s string) (time.Time, bool) {
// TODO write code to check all the formats supported by
// http://yaml.org/type/timestamp.html instead of using time.Parse.
// Quick check: all date formats start with YYYY-.
i := 0
for ; i < len(s); i++ {
if c := s[i]; c < '0' || c > '9' {
break
}
}
if i != 4 || i == len(s) || s[i] != '-' {
return time.Time{}, false
}
for _, format := range allowedTimestampFormats {
if t, err := time.Parse(format, s); err == nil {
return t, true
}
}
return time.Time{}, false
}

3038
vendor/gopkg.in/yaml.v3/scannerc.go generated vendored Normal file

File diff suppressed because it is too large Load diff

134
vendor/gopkg.in/yaml.v3/sorter.go generated vendored Normal file
View file

@ -0,0 +1,134 @@
//
// Copyright (c) 2011-2019 Canonical Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package yaml
import (
"reflect"
"unicode"
)
type keyList []reflect.Value
func (l keyList) Len() int { return len(l) }
func (l keyList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
func (l keyList) Less(i, j int) bool {
a := l[i]
b := l[j]
ak := a.Kind()
bk := b.Kind()
for (ak == reflect.Interface || ak == reflect.Ptr) && !a.IsNil() {
a = a.Elem()
ak = a.Kind()
}
for (bk == reflect.Interface || bk == reflect.Ptr) && !b.IsNil() {
b = b.Elem()
bk = b.Kind()
}
af, aok := keyFloat(a)
bf, bok := keyFloat(b)
if aok && bok {
if af != bf {
return af < bf
}
if ak != bk {
return ak < bk
}
return numLess(a, b)
}
if ak != reflect.String || bk != reflect.String {
return ak < bk
}
ar, br := []rune(a.String()), []rune(b.String())
digits := false
for i := 0; i < len(ar) && i < len(br); i++ {
if ar[i] == br[i] {
digits = unicode.IsDigit(ar[i])
continue
}
al := unicode.IsLetter(ar[i])
bl := unicode.IsLetter(br[i])
if al && bl {
return ar[i] < br[i]
}
if al || bl {
if digits {
return al
} else {
return bl
}
}
var ai, bi int
var an, bn int64
if ar[i] == '0' || br[i] == '0' {
for j := i - 1; j >= 0 && unicode.IsDigit(ar[j]); j-- {
if ar[j] != '0' {
an = 1
bn = 1
break
}
}
}
for ai = i; ai < len(ar) && unicode.IsDigit(ar[ai]); ai++ {
an = an*10 + int64(ar[ai]-'0')
}
for bi = i; bi < len(br) && unicode.IsDigit(br[bi]); bi++ {
bn = bn*10 + int64(br[bi]-'0')
}
if an != bn {
return an < bn
}
if ai != bi {
return ai < bi
}
return ar[i] < br[i]
}
return len(ar) < len(br)
}
// keyFloat returns a float value for v if it is a number/bool
// and whether it is a number/bool or not.
func keyFloat(v reflect.Value) (f float64, ok bool) {
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return float64(v.Int()), true
case reflect.Float32, reflect.Float64:
return v.Float(), true
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return float64(v.Uint()), true
case reflect.Bool:
if v.Bool() {
return 1, true
}
return 0, true
}
return 0, false
}
// numLess returns whether a < b.
// a and b must necessarily have the same kind.
func numLess(a, b reflect.Value) bool {
switch a.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return a.Int() < b.Int()
case reflect.Float32, reflect.Float64:
return a.Float() < b.Float()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return a.Uint() < b.Uint()
case reflect.Bool:
return !a.Bool() && b.Bool()
}
panic("not a number")
}

48
vendor/gopkg.in/yaml.v3/writerc.go generated vendored Normal file
View file

@ -0,0 +1,48 @@
//
// Copyright (c) 2011-2019 Canonical Ltd
// Copyright (c) 2006-2010 Kirill Simonov
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package yaml
// Set the writer error and return false.
func yaml_emitter_set_writer_error(emitter *yaml_emitter_t, problem string) bool {
emitter.error = yaml_WRITER_ERROR
emitter.problem = problem
return false
}
// Flush the output buffer.
func yaml_emitter_flush(emitter *yaml_emitter_t) bool {
if emitter.write_handler == nil {
panic("write handler not set")
}
// Check if the buffer is empty.
if emitter.buffer_pos == 0 {
return true
}
if err := emitter.write_handler(emitter, emitter.buffer[:emitter.buffer_pos]); err != nil {
return yaml_emitter_set_writer_error(emitter, "write error: "+err.Error())
}
emitter.buffer_pos = 0
return true
}

698
vendor/gopkg.in/yaml.v3/yaml.go generated vendored Normal file
View file

@ -0,0 +1,698 @@
//
// Copyright (c) 2011-2019 Canonical Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package yaml implements YAML support for the Go language.
//
// Source code and other details for the project are available at GitHub:
//
// https://github.com/go-yaml/yaml
//
package yaml
import (
"errors"
"fmt"
"io"
"reflect"
"strings"
"sync"
"unicode/utf8"
)
// The Unmarshaler interface may be implemented by types to customize their
// behavior when being unmarshaled from a YAML document.
type Unmarshaler interface {
UnmarshalYAML(value *Node) error
}
type obsoleteUnmarshaler interface {
UnmarshalYAML(unmarshal func(interface{}) error) error
}
// The Marshaler interface may be implemented by types to customize their
// behavior when being marshaled into a YAML document. The returned value
// is marshaled in place of the original value implementing Marshaler.
//
// If an error is returned by MarshalYAML, the marshaling procedure stops
// and returns with the provided error.
type Marshaler interface {
MarshalYAML() (interface{}, error)
}
// Unmarshal decodes the first document found within the in byte slice
// and assigns decoded values into the out value.
//
// Maps and pointers (to a struct, string, int, etc) are accepted as out
// values. If an internal pointer within a struct is not initialized,
// the yaml package will initialize it if necessary for unmarshalling
// the provided data. The out parameter must not be nil.
//
// The type of the decoded values should be compatible with the respective
// values in out. If one or more values cannot be decoded due to a type
// mismatches, decoding continues partially until the end of the YAML
// content, and a *yaml.TypeError is returned with details for all
// missed values.
//
// Struct fields are only unmarshalled if they are exported (have an
// upper case first letter), and are unmarshalled using the field name
// lowercased as the default key. Custom keys may be defined via the
// "yaml" name in the field tag: the content preceding the first comma
// is used as the key, and the following comma-separated options are
// used to tweak the marshalling process (see Marshal).
// Conflicting names result in a runtime error.
//
// For example:
//
// type T struct {
// F int `yaml:"a,omitempty"`
// B int
// }
// var t T
// yaml.Unmarshal([]byte("a: 1\nb: 2"), &t)
//
// See the documentation of Marshal for the format of tags and a list of
// supported tag options.
//
func Unmarshal(in []byte, out interface{}) (err error) {
return unmarshal(in, out, false)
}
// A Decoder reads and decodes YAML values from an input stream.
type Decoder struct {
parser *parser
knownFields bool
}
// NewDecoder returns a new decoder that reads from r.
//
// The decoder introduces its own buffering and may read
// data from r beyond the YAML values requested.
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{
parser: newParserFromReader(r),
}
}
// KnownFields ensures that the keys in decoded mappings to
// exist as fields in the struct being decoded into.
func (dec *Decoder) KnownFields(enable bool) {
dec.knownFields = enable
}
// Decode reads the next YAML-encoded value from its input
// and stores it in the value pointed to by v.
//
// See the documentation for Unmarshal for details about the
// conversion of YAML into a Go value.
func (dec *Decoder) Decode(v interface{}) (err error) {
d := newDecoder()
d.knownFields = dec.knownFields
defer handleErr(&err)
node := dec.parser.parse()
if node == nil {
return io.EOF
}
out := reflect.ValueOf(v)
if out.Kind() == reflect.Ptr && !out.IsNil() {
out = out.Elem()
}
d.unmarshal(node, out)
if len(d.terrors) > 0 {
return &TypeError{d.terrors}
}
return nil
}
// Decode decodes the node and stores its data into the value pointed to by v.
//
// See the documentation for Unmarshal for details about the
// conversion of YAML into a Go value.
func (n *Node) Decode(v interface{}) (err error) {
d := newDecoder()
defer handleErr(&err)
out := reflect.ValueOf(v)
if out.Kind() == reflect.Ptr && !out.IsNil() {
out = out.Elem()
}
d.unmarshal(n, out)
if len(d.terrors) > 0 {
return &TypeError{d.terrors}
}
return nil
}
func unmarshal(in []byte, out interface{}, strict bool) (err error) {
defer handleErr(&err)
d := newDecoder()
p := newParser(in)
defer p.destroy()
node := p.parse()
if node != nil {
v := reflect.ValueOf(out)
if v.Kind() == reflect.Ptr && !v.IsNil() {
v = v.Elem()
}
d.unmarshal(node, v)
}
if len(d.terrors) > 0 {
return &TypeError{d.terrors}
}
return nil
}
// Marshal serializes the value provided into a YAML document. The structure
// of the generated document will reflect the structure of the value itself.
// Maps and pointers (to struct, string, int, etc) are accepted as the in value.
//
// Struct fields are only marshalled if they are exported (have an upper case
// first letter), and are marshalled using the field name lowercased as the
// default key. Custom keys may be defined via the "yaml" name in the field
// tag: the content preceding the first comma is used as the key, and the
// following comma-separated options are used to tweak the marshalling process.
// Conflicting names result in a runtime error.
//
// The field tag format accepted is:
//
// `(...) yaml:"[<key>][,<flag1>[,<flag2>]]" (...)`
//
// The following flags are currently supported:
//
// omitempty Only include the field if it's not set to the zero
// value for the type or to empty slices or maps.
// Zero valued structs will be omitted if all their public
// fields are zero, unless they implement an IsZero
// method (see the IsZeroer interface type), in which
// case the field will be excluded if IsZero returns true.
//
// flow Marshal using a flow style (useful for structs,
// sequences and maps).
//
// inline Inline the field, which must be a struct or a map,
// causing all of its fields or keys to be processed as if
// they were part of the outer struct. For maps, keys must
// not conflict with the yaml keys of other struct fields.
//
// In addition, if the key is "-", the field is ignored.
//
// For example:
//
// type T struct {
// F int `yaml:"a,omitempty"`
// B int
// }
// yaml.Marshal(&T{B: 2}) // Returns "b: 2\n"
// yaml.Marshal(&T{F: 1}} // Returns "a: 1\nb: 0\n"
//
func Marshal(in interface{}) (out []byte, err error) {
defer handleErr(&err)
e := newEncoder()
defer e.destroy()
e.marshalDoc("", reflect.ValueOf(in))
e.finish()
out = e.out
return
}
// An Encoder writes YAML values to an output stream.
type Encoder struct {
encoder *encoder
}
// NewEncoder returns a new encoder that writes to w.
// The Encoder should be closed after use to flush all data
// to w.
func NewEncoder(w io.Writer) *Encoder {
return &Encoder{
encoder: newEncoderWithWriter(w),
}
}
// Encode writes the YAML encoding of v to the stream.
// If multiple items are encoded to the stream, the
// second and subsequent document will be preceded
// with a "---" document separator, but the first will not.
//
// See the documentation for Marshal for details about the conversion of Go
// values to YAML.
func (e *Encoder) Encode(v interface{}) (err error) {
defer handleErr(&err)
e.encoder.marshalDoc("", reflect.ValueOf(v))
return nil
}
// Encode encodes value v and stores its representation in n.
//
// See the documentation for Marshal for details about the
// conversion of Go values into YAML.
func (n *Node) Encode(v interface{}) (err error) {
defer handleErr(&err)
e := newEncoder()
defer e.destroy()
e.marshalDoc("", reflect.ValueOf(v))
e.finish()
p := newParser(e.out)
p.textless = true
defer p.destroy()
doc := p.parse()
*n = *doc.Content[0]
return nil
}
// SetIndent changes the used indentation used when encoding.
func (e *Encoder) SetIndent(spaces int) {
if spaces < 0 {
panic("yaml: cannot indent to a negative number of spaces")
}
e.encoder.indent = spaces
}
// Close closes the encoder by writing any remaining data.
// It does not write a stream terminating string "...".
func (e *Encoder) Close() (err error) {
defer handleErr(&err)
e.encoder.finish()
return nil
}
func handleErr(err *error) {
if v := recover(); v != nil {
if e, ok := v.(yamlError); ok {
*err = e.err
} else {
panic(v)
}
}
}
type yamlError struct {
err error
}
func fail(err error) {
panic(yamlError{err})
}
func failf(format string, args ...interface{}) {
panic(yamlError{fmt.Errorf("yaml: "+format, args...)})
}
// A TypeError is returned by Unmarshal when one or more fields in
// the YAML document cannot be properly decoded into the requested
// types. When this error is returned, the value is still
// unmarshaled partially.
type TypeError struct {
Errors []string
}
func (e *TypeError) Error() string {
return fmt.Sprintf("yaml: unmarshal errors:\n %s", strings.Join(e.Errors, "\n "))
}
type Kind uint32
const (
DocumentNode Kind = 1 << iota
SequenceNode
MappingNode
ScalarNode
AliasNode
)
type Style uint32
const (
TaggedStyle Style = 1 << iota
DoubleQuotedStyle
SingleQuotedStyle
LiteralStyle
FoldedStyle
FlowStyle
)
// Node represents an element in the YAML document hierarchy. While documents
// are typically encoded and decoded into higher level types, such as structs
// and maps, Node is an intermediate representation that allows detailed
// control over the content being decoded or encoded.
//
// It's worth noting that although Node offers access into details such as
// line numbers, colums, and comments, the content when re-encoded will not
// have its original textual representation preserved. An effort is made to
// render the data plesantly, and to preserve comments near the data they
// describe, though.
//
// Values that make use of the Node type interact with the yaml package in the
// same way any other type would do, by encoding and decoding yaml data
// directly or indirectly into them.
//
// For example:
//
// var person struct {
// Name string
// Address yaml.Node
// }
// err := yaml.Unmarshal(data, &person)
//
// Or by itself:
//
// var person Node
// err := yaml.Unmarshal(data, &person)
//
type Node struct {
// Kind defines whether the node is a document, a mapping, a sequence,
// a scalar value, or an alias to another node. The specific data type of
// scalar nodes may be obtained via the ShortTag and LongTag methods.
Kind Kind
// Style allows customizing the apperance of the node in the tree.
Style Style
// Tag holds the YAML tag defining the data type for the value.
// When decoding, this field will always be set to the resolved tag,
// even when it wasn't explicitly provided in the YAML content.
// When encoding, if this field is unset the value type will be
// implied from the node properties, and if it is set, it will only
// be serialized into the representation if TaggedStyle is used or
// the implicit tag diverges from the provided one.
Tag string
// Value holds the unescaped and unquoted represenation of the value.
Value string
// Anchor holds the anchor name for this node, which allows aliases to point to it.
Anchor string
// Alias holds the node that this alias points to. Only valid when Kind is AliasNode.
Alias *Node
// Content holds contained nodes for documents, mappings, and sequences.
Content []*Node
// HeadComment holds any comments in the lines preceding the node and
// not separated by an empty line.
HeadComment string
// LineComment holds any comments at the end of the line where the node is in.
LineComment string
// FootComment holds any comments following the node and before empty lines.
FootComment string
// Line and Column hold the node position in the decoded YAML text.
// These fields are not respected when encoding the node.
Line int
Column int
}
// IsZero returns whether the node has all of its fields unset.
func (n *Node) IsZero() bool {
return n.Kind == 0 && n.Style == 0 && n.Tag == "" && n.Value == "" && n.Anchor == "" && n.Alias == nil && n.Content == nil &&
n.HeadComment == "" && n.LineComment == "" && n.FootComment == "" && n.Line == 0 && n.Column == 0
}
// LongTag returns the long form of the tag that indicates the data type for
// the node. If the Tag field isn't explicitly defined, one will be computed
// based on the node properties.
func (n *Node) LongTag() string {
return longTag(n.ShortTag())
}
// ShortTag returns the short form of the YAML tag that indicates data type for
// the node. If the Tag field isn't explicitly defined, one will be computed
// based on the node properties.
func (n *Node) ShortTag() string {
if n.indicatedString() {
return strTag
}
if n.Tag == "" || n.Tag == "!" {
switch n.Kind {
case MappingNode:
return mapTag
case SequenceNode:
return seqTag
case AliasNode:
if n.Alias != nil {
return n.Alias.ShortTag()
}
case ScalarNode:
tag, _ := resolve("", n.Value)
return tag
case 0:
// Special case to make the zero value convenient.
if n.IsZero() {
return nullTag
}
}
return ""
}
return shortTag(n.Tag)
}
func (n *Node) indicatedString() bool {
return n.Kind == ScalarNode &&
(shortTag(n.Tag) == strTag ||
(n.Tag == "" || n.Tag == "!") && n.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0)
}
// SetString is a convenience function that sets the node to a string value
// and defines its style in a pleasant way depending on its content.
func (n *Node) SetString(s string) {
n.Kind = ScalarNode
if utf8.ValidString(s) {
n.Value = s
n.Tag = strTag
} else {
n.Value = encodeBase64(s)
n.Tag = binaryTag
}
if strings.Contains(n.Value, "\n") {
n.Style = LiteralStyle
}
}
// --------------------------------------------------------------------------
// Maintain a mapping of keys to structure field indexes
// The code in this section was copied from mgo/bson.
// structInfo holds details for the serialization of fields of
// a given struct.
type structInfo struct {
FieldsMap map[string]fieldInfo
FieldsList []fieldInfo
// InlineMap is the number of the field in the struct that
// contains an ,inline map, or -1 if there's none.
InlineMap int
// InlineUnmarshalers holds indexes to inlined fields that
// contain unmarshaler values.
InlineUnmarshalers [][]int
}
type fieldInfo struct {
Key string
Num int
OmitEmpty bool
Flow bool
// Id holds the unique field identifier, so we can cheaply
// check for field duplicates without maintaining an extra map.
Id int
// Inline holds the field index if the field is part of an inlined struct.
Inline []int
}
var structMap = make(map[reflect.Type]*structInfo)
var fieldMapMutex sync.RWMutex
var unmarshalerType reflect.Type
func init() {
var v Unmarshaler
unmarshalerType = reflect.ValueOf(&v).Elem().Type()
}
func getStructInfo(st reflect.Type) (*structInfo, error) {
fieldMapMutex.RLock()
sinfo, found := structMap[st]
fieldMapMutex.RUnlock()
if found {
return sinfo, nil
}
n := st.NumField()
fieldsMap := make(map[string]fieldInfo)
fieldsList := make([]fieldInfo, 0, n)
inlineMap := -1
inlineUnmarshalers := [][]int(nil)
for i := 0; i != n; i++ {
field := st.Field(i)
if field.PkgPath != "" && !field.Anonymous {
continue // Private field
}
info := fieldInfo{Num: i}
tag := field.Tag.Get("yaml")
if tag == "" && strings.Index(string(field.Tag), ":") < 0 {
tag = string(field.Tag)
}
if tag == "-" {
continue
}
inline := false
fields := strings.Split(tag, ",")
if len(fields) > 1 {
for _, flag := range fields[1:] {
switch flag {
case "omitempty":
info.OmitEmpty = true
case "flow":
info.Flow = true
case "inline":
inline = true
default:
return nil, errors.New(fmt.Sprintf("unsupported flag %q in tag %q of type %s", flag, tag, st))
}
}
tag = fields[0]
}
if inline {
switch field.Type.Kind() {
case reflect.Map:
if inlineMap >= 0 {
return nil, errors.New("multiple ,inline maps in struct " + st.String())
}
if field.Type.Key() != reflect.TypeOf("") {
return nil, errors.New("option ,inline needs a map with string keys in struct " + st.String())
}
inlineMap = info.Num
case reflect.Struct, reflect.Ptr:
ftype := field.Type
for ftype.Kind() == reflect.Ptr {
ftype = ftype.Elem()
}
if ftype.Kind() != reflect.Struct {
return nil, errors.New("option ,inline may only be used on a struct or map field")
}
if reflect.PtrTo(ftype).Implements(unmarshalerType) {
inlineUnmarshalers = append(inlineUnmarshalers, []int{i})
} else {
sinfo, err := getStructInfo(ftype)
if err != nil {
return nil, err
}
for _, index := range sinfo.InlineUnmarshalers {
inlineUnmarshalers = append(inlineUnmarshalers, append([]int{i}, index...))
}
for _, finfo := range sinfo.FieldsList {
if _, found := fieldsMap[finfo.Key]; found {
msg := "duplicated key '" + finfo.Key + "' in struct " + st.String()
return nil, errors.New(msg)
}
if finfo.Inline == nil {
finfo.Inline = []int{i, finfo.Num}
} else {
finfo.Inline = append([]int{i}, finfo.Inline...)
}
finfo.Id = len(fieldsList)
fieldsMap[finfo.Key] = finfo
fieldsList = append(fieldsList, finfo)
}
}
default:
return nil, errors.New("option ,inline may only be used on a struct or map field")
}
continue
}
if tag != "" {
info.Key = tag
} else {
info.Key = strings.ToLower(field.Name)
}
if _, found = fieldsMap[info.Key]; found {
msg := "duplicated key '" + info.Key + "' in struct " + st.String()
return nil, errors.New(msg)
}
info.Id = len(fieldsList)
fieldsList = append(fieldsList, info)
fieldsMap[info.Key] = info
}
sinfo = &structInfo{
FieldsMap: fieldsMap,
FieldsList: fieldsList,
InlineMap: inlineMap,
InlineUnmarshalers: inlineUnmarshalers,
}
fieldMapMutex.Lock()
structMap[st] = sinfo
fieldMapMutex.Unlock()
return sinfo, nil
}
// IsZeroer is used to check whether an object is zero to
// determine whether it should be omitted when marshaling
// with the omitempty flag. One notable implementation
// is time.Time.
type IsZeroer interface {
IsZero() bool
}
func isZero(v reflect.Value) bool {
kind := v.Kind()
if z, ok := v.Interface().(IsZeroer); ok {
if (kind == reflect.Ptr || kind == reflect.Interface) && v.IsNil() {
return true
}
return z.IsZero()
}
switch kind {
case reflect.String:
return len(v.String()) == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
case reflect.Slice:
return v.Len() == 0
case reflect.Map:
return v.Len() == 0
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Struct:
vt := v.Type()
for i := v.NumField() - 1; i >= 0; i-- {
if vt.Field(i).PkgPath != "" {
continue // Private field
}
if !isZero(v.Field(i)) {
return false
}
}
return true
}
return false
}

807
vendor/gopkg.in/yaml.v3/yamlh.go generated vendored Normal file
View file

@ -0,0 +1,807 @@
//
// Copyright (c) 2011-2019 Canonical Ltd
// Copyright (c) 2006-2010 Kirill Simonov
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package yaml
import (
"fmt"
"io"
)
// The version directive data.
type yaml_version_directive_t struct {
major int8 // The major version number.
minor int8 // The minor version number.
}
// The tag directive data.
type yaml_tag_directive_t struct {
handle []byte // The tag handle.
prefix []byte // The tag prefix.
}
type yaml_encoding_t int
// The stream encoding.
const (
// Let the parser choose the encoding.
yaml_ANY_ENCODING yaml_encoding_t = iota
yaml_UTF8_ENCODING // The default UTF-8 encoding.
yaml_UTF16LE_ENCODING // The UTF-16-LE encoding with BOM.
yaml_UTF16BE_ENCODING // The UTF-16-BE encoding with BOM.
)
type yaml_break_t int
// Line break types.
const (
// Let the parser choose the break type.
yaml_ANY_BREAK yaml_break_t = iota
yaml_CR_BREAK // Use CR for line breaks (Mac style).
yaml_LN_BREAK // Use LN for line breaks (Unix style).
yaml_CRLN_BREAK // Use CR LN for line breaks (DOS style).
)
type yaml_error_type_t int
// Many bad things could happen with the parser and emitter.
const (
// No error is produced.
yaml_NO_ERROR yaml_error_type_t = iota
yaml_MEMORY_ERROR // Cannot allocate or reallocate a block of memory.
yaml_READER_ERROR // Cannot read or decode the input stream.
yaml_SCANNER_ERROR // Cannot scan the input stream.
yaml_PARSER_ERROR // Cannot parse the input stream.
yaml_COMPOSER_ERROR // Cannot compose a YAML document.
yaml_WRITER_ERROR // Cannot write to the output stream.
yaml_EMITTER_ERROR // Cannot emit a YAML stream.
)
// The pointer position.
type yaml_mark_t struct {
index int // The position index.
line int // The position line.
column int // The position column.
}
// Node Styles
type yaml_style_t int8
type yaml_scalar_style_t yaml_style_t
// Scalar styles.
const (
// Let the emitter choose the style.
yaml_ANY_SCALAR_STYLE yaml_scalar_style_t = 0
yaml_PLAIN_SCALAR_STYLE yaml_scalar_style_t = 1 << iota // The plain scalar style.
yaml_SINGLE_QUOTED_SCALAR_STYLE // The single-quoted scalar style.
yaml_DOUBLE_QUOTED_SCALAR_STYLE // The double-quoted scalar style.
yaml_LITERAL_SCALAR_STYLE // The literal scalar style.
yaml_FOLDED_SCALAR_STYLE // The folded scalar style.
)
type yaml_sequence_style_t yaml_style_t
// Sequence styles.
const (
// Let the emitter choose the style.
yaml_ANY_SEQUENCE_STYLE yaml_sequence_style_t = iota
yaml_BLOCK_SEQUENCE_STYLE // The block sequence style.
yaml_FLOW_SEQUENCE_STYLE // The flow sequence style.
)
type yaml_mapping_style_t yaml_style_t
// Mapping styles.
const (
// Let the emitter choose the style.
yaml_ANY_MAPPING_STYLE yaml_mapping_style_t = iota
yaml_BLOCK_MAPPING_STYLE // The block mapping style.
yaml_FLOW_MAPPING_STYLE // The flow mapping style.
)
// Tokens
type yaml_token_type_t int
// Token types.
const (
// An empty token.
yaml_NO_TOKEN yaml_token_type_t = iota
yaml_STREAM_START_TOKEN // A STREAM-START token.
yaml_STREAM_END_TOKEN // A STREAM-END token.
yaml_VERSION_DIRECTIVE_TOKEN // A VERSION-DIRECTIVE token.
yaml_TAG_DIRECTIVE_TOKEN // A TAG-DIRECTIVE token.
yaml_DOCUMENT_START_TOKEN // A DOCUMENT-START token.
yaml_DOCUMENT_END_TOKEN // A DOCUMENT-END token.
yaml_BLOCK_SEQUENCE_START_TOKEN // A BLOCK-SEQUENCE-START token.
yaml_BLOCK_MAPPING_START_TOKEN // A BLOCK-SEQUENCE-END token.
yaml_BLOCK_END_TOKEN // A BLOCK-END token.
yaml_FLOW_SEQUENCE_START_TOKEN // A FLOW-SEQUENCE-START token.
yaml_FLOW_SEQUENCE_END_TOKEN // A FLOW-SEQUENCE-END token.
yaml_FLOW_MAPPING_START_TOKEN // A FLOW-MAPPING-START token.
yaml_FLOW_MAPPING_END_TOKEN // A FLOW-MAPPING-END token.
yaml_BLOCK_ENTRY_TOKEN // A BLOCK-ENTRY token.
yaml_FLOW_ENTRY_TOKEN // A FLOW-ENTRY token.
yaml_KEY_TOKEN // A KEY token.
yaml_VALUE_TOKEN // A VALUE token.
yaml_ALIAS_TOKEN // An ALIAS token.
yaml_ANCHOR_TOKEN // An ANCHOR token.
yaml_TAG_TOKEN // A TAG token.
yaml_SCALAR_TOKEN // A SCALAR token.
)
func (tt yaml_token_type_t) String() string {
switch tt {
case yaml_NO_TOKEN:
return "yaml_NO_TOKEN"
case yaml_STREAM_START_TOKEN:
return "yaml_STREAM_START_TOKEN"
case yaml_STREAM_END_TOKEN:
return "yaml_STREAM_END_TOKEN"
case yaml_VERSION_DIRECTIVE_TOKEN:
return "yaml_VERSION_DIRECTIVE_TOKEN"
case yaml_TAG_DIRECTIVE_TOKEN:
return "yaml_TAG_DIRECTIVE_TOKEN"
case yaml_DOCUMENT_START_TOKEN:
return "yaml_DOCUMENT_START_TOKEN"
case yaml_DOCUMENT_END_TOKEN:
return "yaml_DOCUMENT_END_TOKEN"
case yaml_BLOCK_SEQUENCE_START_TOKEN:
return "yaml_BLOCK_SEQUENCE_START_TOKEN"
case yaml_BLOCK_MAPPING_START_TOKEN:
return "yaml_BLOCK_MAPPING_START_TOKEN"
case yaml_BLOCK_END_TOKEN:
return "yaml_BLOCK_END_TOKEN"
case yaml_FLOW_SEQUENCE_START_TOKEN:
return "yaml_FLOW_SEQUENCE_START_TOKEN"
case yaml_FLOW_SEQUENCE_END_TOKEN:
return "yaml_FLOW_SEQUENCE_END_TOKEN"
case yaml_FLOW_MAPPING_START_TOKEN:
return "yaml_FLOW_MAPPING_START_TOKEN"
case yaml_FLOW_MAPPING_END_TOKEN:
return "yaml_FLOW_MAPPING_END_TOKEN"
case yaml_BLOCK_ENTRY_TOKEN:
return "yaml_BLOCK_ENTRY_TOKEN"
case yaml_FLOW_ENTRY_TOKEN:
return "yaml_FLOW_ENTRY_TOKEN"
case yaml_KEY_TOKEN:
return "yaml_KEY_TOKEN"
case yaml_VALUE_TOKEN:
return "yaml_VALUE_TOKEN"
case yaml_ALIAS_TOKEN:
return "yaml_ALIAS_TOKEN"
case yaml_ANCHOR_TOKEN:
return "yaml_ANCHOR_TOKEN"
case yaml_TAG_TOKEN:
return "yaml_TAG_TOKEN"
case yaml_SCALAR_TOKEN:
return "yaml_SCALAR_TOKEN"
}
return "<unknown token>"
}
// The token structure.
type yaml_token_t struct {
// The token type.
typ yaml_token_type_t
// The start/end of the token.
start_mark, end_mark yaml_mark_t
// The stream encoding (for yaml_STREAM_START_TOKEN).
encoding yaml_encoding_t
// The alias/anchor/scalar value or tag/tag directive handle
// (for yaml_ALIAS_TOKEN, yaml_ANCHOR_TOKEN, yaml_SCALAR_TOKEN, yaml_TAG_TOKEN, yaml_TAG_DIRECTIVE_TOKEN).
value []byte
// The tag suffix (for yaml_TAG_TOKEN).
suffix []byte
// The tag directive prefix (for yaml_TAG_DIRECTIVE_TOKEN).
prefix []byte
// The scalar style (for yaml_SCALAR_TOKEN).
style yaml_scalar_style_t
// The version directive major/minor (for yaml_VERSION_DIRECTIVE_TOKEN).
major, minor int8
}
// Events
type yaml_event_type_t int8
// Event types.
const (
// An empty event.
yaml_NO_EVENT yaml_event_type_t = iota
yaml_STREAM_START_EVENT // A STREAM-START event.
yaml_STREAM_END_EVENT // A STREAM-END event.
yaml_DOCUMENT_START_EVENT // A DOCUMENT-START event.
yaml_DOCUMENT_END_EVENT // A DOCUMENT-END event.
yaml_ALIAS_EVENT // An ALIAS event.
yaml_SCALAR_EVENT // A SCALAR event.
yaml_SEQUENCE_START_EVENT // A SEQUENCE-START event.
yaml_SEQUENCE_END_EVENT // A SEQUENCE-END event.
yaml_MAPPING_START_EVENT // A MAPPING-START event.
yaml_MAPPING_END_EVENT // A MAPPING-END event.
yaml_TAIL_COMMENT_EVENT
)
var eventStrings = []string{
yaml_NO_EVENT: "none",
yaml_STREAM_START_EVENT: "stream start",
yaml_STREAM_END_EVENT: "stream end",
yaml_DOCUMENT_START_EVENT: "document start",
yaml_DOCUMENT_END_EVENT: "document end",
yaml_ALIAS_EVENT: "alias",
yaml_SCALAR_EVENT: "scalar",
yaml_SEQUENCE_START_EVENT: "sequence start",
yaml_SEQUENCE_END_EVENT: "sequence end",
yaml_MAPPING_START_EVENT: "mapping start",
yaml_MAPPING_END_EVENT: "mapping end",
yaml_TAIL_COMMENT_EVENT: "tail comment",
}
func (e yaml_event_type_t) String() string {
if e < 0 || int(e) >= len(eventStrings) {
return fmt.Sprintf("unknown event %d", e)
}
return eventStrings[e]
}
// The event structure.
type yaml_event_t struct {
// The event type.
typ yaml_event_type_t
// The start and end of the event.
start_mark, end_mark yaml_mark_t
// The document encoding (for yaml_STREAM_START_EVENT).
encoding yaml_encoding_t
// The version directive (for yaml_DOCUMENT_START_EVENT).
version_directive *yaml_version_directive_t
// The list of tag directives (for yaml_DOCUMENT_START_EVENT).
tag_directives []yaml_tag_directive_t
// The comments
head_comment []byte
line_comment []byte
foot_comment []byte
tail_comment []byte
// The anchor (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_ALIAS_EVENT).
anchor []byte
// The tag (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT).
tag []byte
// The scalar value (for yaml_SCALAR_EVENT).
value []byte
// Is the document start/end indicator implicit, or the tag optional?
// (for yaml_DOCUMENT_START_EVENT, yaml_DOCUMENT_END_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_SCALAR_EVENT).
implicit bool
// Is the tag optional for any non-plain style? (for yaml_SCALAR_EVENT).
quoted_implicit bool
// The style (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT).
style yaml_style_t
}
func (e *yaml_event_t) scalar_style() yaml_scalar_style_t { return yaml_scalar_style_t(e.style) }
func (e *yaml_event_t) sequence_style() yaml_sequence_style_t { return yaml_sequence_style_t(e.style) }
func (e *yaml_event_t) mapping_style() yaml_mapping_style_t { return yaml_mapping_style_t(e.style) }
// Nodes
const (
yaml_NULL_TAG = "tag:yaml.org,2002:null" // The tag !!null with the only possible value: null.
yaml_BOOL_TAG = "tag:yaml.org,2002:bool" // The tag !!bool with the values: true and false.
yaml_STR_TAG = "tag:yaml.org,2002:str" // The tag !!str for string values.
yaml_INT_TAG = "tag:yaml.org,2002:int" // The tag !!int for integer values.
yaml_FLOAT_TAG = "tag:yaml.org,2002:float" // The tag !!float for float values.
yaml_TIMESTAMP_TAG = "tag:yaml.org,2002:timestamp" // The tag !!timestamp for date and time values.
yaml_SEQ_TAG = "tag:yaml.org,2002:seq" // The tag !!seq is used to denote sequences.
yaml_MAP_TAG = "tag:yaml.org,2002:map" // The tag !!map is used to denote mapping.
// Not in original libyaml.
yaml_BINARY_TAG = "tag:yaml.org,2002:binary"
yaml_MERGE_TAG = "tag:yaml.org,2002:merge"
yaml_DEFAULT_SCALAR_TAG = yaml_STR_TAG // The default scalar tag is !!str.
yaml_DEFAULT_SEQUENCE_TAG = yaml_SEQ_TAG // The default sequence tag is !!seq.
yaml_DEFAULT_MAPPING_TAG = yaml_MAP_TAG // The default mapping tag is !!map.
)
type yaml_node_type_t int
// Node types.
const (
// An empty node.
yaml_NO_NODE yaml_node_type_t = iota
yaml_SCALAR_NODE // A scalar node.
yaml_SEQUENCE_NODE // A sequence node.
yaml_MAPPING_NODE // A mapping node.
)
// An element of a sequence node.
type yaml_node_item_t int
// An element of a mapping node.
type yaml_node_pair_t struct {
key int // The key of the element.
value int // The value of the element.
}
// The node structure.
type yaml_node_t struct {
typ yaml_node_type_t // The node type.
tag []byte // The node tag.
// The node data.
// The scalar parameters (for yaml_SCALAR_NODE).
scalar struct {
value []byte // The scalar value.
length int // The length of the scalar value.
style yaml_scalar_style_t // The scalar style.
}
// The sequence parameters (for YAML_SEQUENCE_NODE).
sequence struct {
items_data []yaml_node_item_t // The stack of sequence items.
style yaml_sequence_style_t // The sequence style.
}
// The mapping parameters (for yaml_MAPPING_NODE).
mapping struct {
pairs_data []yaml_node_pair_t // The stack of mapping pairs (key, value).
pairs_start *yaml_node_pair_t // The beginning of the stack.
pairs_end *yaml_node_pair_t // The end of the stack.
pairs_top *yaml_node_pair_t // The top of the stack.
style yaml_mapping_style_t // The mapping style.
}
start_mark yaml_mark_t // The beginning of the node.
end_mark yaml_mark_t // The end of the node.
}
// The document structure.
type yaml_document_t struct {
// The document nodes.
nodes []yaml_node_t
// The version directive.
version_directive *yaml_version_directive_t
// The list of tag directives.
tag_directives_data []yaml_tag_directive_t
tag_directives_start int // The beginning of the tag directives list.
tag_directives_end int // The end of the tag directives list.
start_implicit int // Is the document start indicator implicit?
end_implicit int // Is the document end indicator implicit?
// The start/end of the document.
start_mark, end_mark yaml_mark_t
}
// The prototype of a read handler.
//
// The read handler is called when the parser needs to read more bytes from the
// source. The handler should write not more than size bytes to the buffer.
// The number of written bytes should be set to the size_read variable.
//
// [in,out] data A pointer to an application data specified by
// yaml_parser_set_input().
// [out] buffer The buffer to write the data from the source.
// [in] size The size of the buffer.
// [out] size_read The actual number of bytes read from the source.
//
// On success, the handler should return 1. If the handler failed,
// the returned value should be 0. On EOF, the handler should set the
// size_read to 0 and return 1.
type yaml_read_handler_t func(parser *yaml_parser_t, buffer []byte) (n int, err error)
// This structure holds information about a potential simple key.
type yaml_simple_key_t struct {
possible bool // Is a simple key possible?
required bool // Is a simple key required?
token_number int // The number of the token.
mark yaml_mark_t // The position mark.
}
// The states of the parser.
type yaml_parser_state_t int
const (
yaml_PARSE_STREAM_START_STATE yaml_parser_state_t = iota
yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE // Expect the beginning of an implicit document.
yaml_PARSE_DOCUMENT_START_STATE // Expect DOCUMENT-START.
yaml_PARSE_DOCUMENT_CONTENT_STATE // Expect the content of a document.
yaml_PARSE_DOCUMENT_END_STATE // Expect DOCUMENT-END.
yaml_PARSE_BLOCK_NODE_STATE // Expect a block node.
yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE // Expect a block node or indentless sequence.
yaml_PARSE_FLOW_NODE_STATE // Expect a flow node.
yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a block sequence.
yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE // Expect an entry of a block sequence.
yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE // Expect an entry of an indentless sequence.
yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping.
yaml_PARSE_BLOCK_MAPPING_KEY_STATE // Expect a block mapping key.
yaml_PARSE_BLOCK_MAPPING_VALUE_STATE // Expect a block mapping value.
yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a flow sequence.
yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE // Expect an entry of a flow sequence.
yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE // Expect a key of an ordered mapping.
yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE // Expect a value of an ordered mapping.
yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE // Expect the and of an ordered mapping entry.
yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping.
yaml_PARSE_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping.
yaml_PARSE_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping.
yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE // Expect an empty value of a flow mapping.
yaml_PARSE_END_STATE // Expect nothing.
)
func (ps yaml_parser_state_t) String() string {
switch ps {
case yaml_PARSE_STREAM_START_STATE:
return "yaml_PARSE_STREAM_START_STATE"
case yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE:
return "yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE"
case yaml_PARSE_DOCUMENT_START_STATE:
return "yaml_PARSE_DOCUMENT_START_STATE"
case yaml_PARSE_DOCUMENT_CONTENT_STATE:
return "yaml_PARSE_DOCUMENT_CONTENT_STATE"
case yaml_PARSE_DOCUMENT_END_STATE:
return "yaml_PARSE_DOCUMENT_END_STATE"
case yaml_PARSE_BLOCK_NODE_STATE:
return "yaml_PARSE_BLOCK_NODE_STATE"
case yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE:
return "yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE"
case yaml_PARSE_FLOW_NODE_STATE:
return "yaml_PARSE_FLOW_NODE_STATE"
case yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE:
return "yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE"
case yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE:
return "yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE"
case yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE:
return "yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE"
case yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE:
return "yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE"
case yaml_PARSE_BLOCK_MAPPING_KEY_STATE:
return "yaml_PARSE_BLOCK_MAPPING_KEY_STATE"
case yaml_PARSE_BLOCK_MAPPING_VALUE_STATE:
return "yaml_PARSE_BLOCK_MAPPING_VALUE_STATE"
case yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE:
return "yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE"
case yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE:
return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE"
case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE:
return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE"
case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE:
return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE"
case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE:
return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE"
case yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE:
return "yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE"
case yaml_PARSE_FLOW_MAPPING_KEY_STATE:
return "yaml_PARSE_FLOW_MAPPING_KEY_STATE"
case yaml_PARSE_FLOW_MAPPING_VALUE_STATE:
return "yaml_PARSE_FLOW_MAPPING_VALUE_STATE"
case yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE:
return "yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE"
case yaml_PARSE_END_STATE:
return "yaml_PARSE_END_STATE"
}
return "<unknown parser state>"
}
// This structure holds aliases data.
type yaml_alias_data_t struct {
anchor []byte // The anchor.
index int // The node id.
mark yaml_mark_t // The anchor mark.
}
// The parser structure.
//
// All members are internal. Manage the structure using the
// yaml_parser_ family of functions.
type yaml_parser_t struct {
// Error handling
error yaml_error_type_t // Error type.
problem string // Error description.
// The byte about which the problem occurred.
problem_offset int
problem_value int
problem_mark yaml_mark_t
// The error context.
context string
context_mark yaml_mark_t
// Reader stuff
read_handler yaml_read_handler_t // Read handler.
input_reader io.Reader // File input data.
input []byte // String input data.
input_pos int
eof bool // EOF flag
buffer []byte // The working buffer.
buffer_pos int // The current position of the buffer.
unread int // The number of unread characters in the buffer.
newlines int // The number of line breaks since last non-break/non-blank character
raw_buffer []byte // The raw buffer.
raw_buffer_pos int // The current position of the buffer.
encoding yaml_encoding_t // The input encoding.
offset int // The offset of the current position (in bytes).
mark yaml_mark_t // The mark of the current position.
// Comments
head_comment []byte // The current head comments
line_comment []byte // The current line comments
foot_comment []byte // The current foot comments
tail_comment []byte // Foot comment that happens at the end of a block.
stem_comment []byte // Comment in item preceding a nested structure (list inside list item, etc)
comments []yaml_comment_t // The folded comments for all parsed tokens
comments_head int
// Scanner stuff
stream_start_produced bool // Have we started to scan the input stream?
stream_end_produced bool // Have we reached the end of the input stream?
flow_level int // The number of unclosed '[' and '{' indicators.
tokens []yaml_token_t // The tokens queue.
tokens_head int // The head of the tokens queue.
tokens_parsed int // The number of tokens fetched from the queue.
token_available bool // Does the tokens queue contain a token ready for dequeueing.
indent int // The current indentation level.
indents []int // The indentation levels stack.
simple_key_allowed bool // May a simple key occur at the current position?
simple_keys []yaml_simple_key_t // The stack of simple keys.
simple_keys_by_tok map[int]int // possible simple_key indexes indexed by token_number
// Parser stuff
state yaml_parser_state_t // The current parser state.
states []yaml_parser_state_t // The parser states stack.
marks []yaml_mark_t // The stack of marks.
tag_directives []yaml_tag_directive_t // The list of TAG directives.
// Dumper stuff
aliases []yaml_alias_data_t // The alias data.
document *yaml_document_t // The currently parsed document.
}
type yaml_comment_t struct {
scan_mark yaml_mark_t // Position where scanning for comments started
token_mark yaml_mark_t // Position after which tokens will be associated with this comment
start_mark yaml_mark_t // Position of '#' comment mark
end_mark yaml_mark_t // Position where comment terminated
head []byte
line []byte
foot []byte
}
// Emitter Definitions
// The prototype of a write handler.
//
// The write handler is called when the emitter needs to flush the accumulated
// characters to the output. The handler should write @a size bytes of the
// @a buffer to the output.
//
// @param[in,out] data A pointer to an application data specified by
// yaml_emitter_set_output().
// @param[in] buffer The buffer with bytes to be written.
// @param[in] size The size of the buffer.
//
// @returns On success, the handler should return @c 1. If the handler failed,
// the returned value should be @c 0.
//
type yaml_write_handler_t func(emitter *yaml_emitter_t, buffer []byte) error
type yaml_emitter_state_t int
// The emitter states.
const (
// Expect STREAM-START.
yaml_EMIT_STREAM_START_STATE yaml_emitter_state_t = iota
yaml_EMIT_FIRST_DOCUMENT_START_STATE // Expect the first DOCUMENT-START or STREAM-END.
yaml_EMIT_DOCUMENT_START_STATE // Expect DOCUMENT-START or STREAM-END.
yaml_EMIT_DOCUMENT_CONTENT_STATE // Expect the content of a document.
yaml_EMIT_DOCUMENT_END_STATE // Expect DOCUMENT-END.
yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a flow sequence.
yaml_EMIT_FLOW_SEQUENCE_TRAIL_ITEM_STATE // Expect the next item of a flow sequence, with the comma already written out
yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE // Expect an item of a flow sequence.
yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping.
yaml_EMIT_FLOW_MAPPING_TRAIL_KEY_STATE // Expect the next key of a flow mapping, with the comma already written out
yaml_EMIT_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping.
yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a flow mapping.
yaml_EMIT_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping.
yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a block sequence.
yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE // Expect an item of a block sequence.
yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping.
yaml_EMIT_BLOCK_MAPPING_KEY_STATE // Expect the key of a block mapping.
yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a block mapping.
yaml_EMIT_BLOCK_MAPPING_VALUE_STATE // Expect a value of a block mapping.
yaml_EMIT_END_STATE // Expect nothing.
)
// The emitter structure.
//
// All members are internal. Manage the structure using the @c yaml_emitter_
// family of functions.
type yaml_emitter_t struct {
// Error handling
error yaml_error_type_t // Error type.
problem string // Error description.
// Writer stuff
write_handler yaml_write_handler_t // Write handler.
output_buffer *[]byte // String output data.
output_writer io.Writer // File output data.
buffer []byte // The working buffer.
buffer_pos int // The current position of the buffer.
raw_buffer []byte // The raw buffer.
raw_buffer_pos int // The current position of the buffer.
encoding yaml_encoding_t // The stream encoding.
// Emitter stuff
canonical bool // If the output is in the canonical style?
best_indent int // The number of indentation spaces.
best_width int // The preferred width of the output lines.
unicode bool // Allow unescaped non-ASCII characters?
line_break yaml_break_t // The preferred line break.
state yaml_emitter_state_t // The current emitter state.
states []yaml_emitter_state_t // The stack of states.
events []yaml_event_t // The event queue.
events_head int // The head of the event queue.
indents []int // The stack of indentation levels.
tag_directives []yaml_tag_directive_t // The list of tag directives.
indent int // The current indentation level.
flow_level int // The current flow level.
root_context bool // Is it the document root context?
sequence_context bool // Is it a sequence context?
mapping_context bool // Is it a mapping context?
simple_key_context bool // Is it a simple mapping key context?
line int // The current line.
column int // The current column.
whitespace bool // If the last character was a whitespace?
indention bool // If the last character was an indentation character (' ', '-', '?', ':')?
open_ended bool // If an explicit document end is required?
space_above bool // Is there's an empty line above?
foot_indent int // The indent used to write the foot comment above, or -1 if none.
// Anchor analysis.
anchor_data struct {
anchor []byte // The anchor value.
alias bool // Is it an alias?
}
// Tag analysis.
tag_data struct {
handle []byte // The tag handle.
suffix []byte // The tag suffix.
}
// Scalar analysis.
scalar_data struct {
value []byte // The scalar value.
multiline bool // Does the scalar contain line breaks?
flow_plain_allowed bool // Can the scalar be expessed in the flow plain style?
block_plain_allowed bool // Can the scalar be expressed in the block plain style?
single_quoted_allowed bool // Can the scalar be expressed in the single quoted style?
block_allowed bool // Can the scalar be expressed in the literal or folded styles?
style yaml_scalar_style_t // The output style.
}
// Comments
head_comment []byte
line_comment []byte
foot_comment []byte
tail_comment []byte
key_line_comment []byte
// Dumper stuff
opened bool // If the stream was already opened?
closed bool // If the stream was already closed?
// The information associated with the document nodes.
anchors *struct {
references int // The number of references.
anchor int // The anchor id.
serialized bool // If the node has been emitted?
}
last_anchor_id int // The last assigned anchor id.
document *yaml_document_t // The currently emitted document.
}

198
vendor/gopkg.in/yaml.v3/yamlprivateh.go generated vendored Normal file
View file

@ -0,0 +1,198 @@
//
// Copyright (c) 2011-2019 Canonical Ltd
// Copyright (c) 2006-2010 Kirill Simonov
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package yaml
const (
// The size of the input raw buffer.
input_raw_buffer_size = 512
// The size of the input buffer.
// It should be possible to decode the whole raw buffer.
input_buffer_size = input_raw_buffer_size * 3
// The size of the output buffer.
output_buffer_size = 128
// The size of the output raw buffer.
// It should be possible to encode the whole output buffer.
output_raw_buffer_size = (output_buffer_size*2 + 2)
// The size of other stacks and queues.
initial_stack_size = 16
initial_queue_size = 16
initial_string_size = 16
)
// Check if the character at the specified position is an alphabetical
// character, a digit, '_', or '-'.
func is_alpha(b []byte, i int) bool {
return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'Z' || b[i] >= 'a' && b[i] <= 'z' || b[i] == '_' || b[i] == '-'
}
// Check if the character at the specified position is a digit.
func is_digit(b []byte, i int) bool {
return b[i] >= '0' && b[i] <= '9'
}
// Get the value of a digit.
func as_digit(b []byte, i int) int {
return int(b[i]) - '0'
}
// Check if the character at the specified position is a hex-digit.
func is_hex(b []byte, i int) bool {
return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'F' || b[i] >= 'a' && b[i] <= 'f'
}
// Get the value of a hex-digit.
func as_hex(b []byte, i int) int {
bi := b[i]
if bi >= 'A' && bi <= 'F' {
return int(bi) - 'A' + 10
}
if bi >= 'a' && bi <= 'f' {
return int(bi) - 'a' + 10
}
return int(bi) - '0'
}
// Check if the character is ASCII.
func is_ascii(b []byte, i int) bool {
return b[i] <= 0x7F
}
// Check if the character at the start of the buffer can be printed unescaped.
func is_printable(b []byte, i int) bool {
return ((b[i] == 0x0A) || // . == #x0A
(b[i] >= 0x20 && b[i] <= 0x7E) || // #x20 <= . <= #x7E
(b[i] == 0xC2 && b[i+1] >= 0xA0) || // #0xA0 <= . <= #xD7FF
(b[i] > 0xC2 && b[i] < 0xED) ||
(b[i] == 0xED && b[i+1] < 0xA0) ||
(b[i] == 0xEE) ||
(b[i] == 0xEF && // #xE000 <= . <= #xFFFD
!(b[i+1] == 0xBB && b[i+2] == 0xBF) && // && . != #xFEFF
!(b[i+1] == 0xBF && (b[i+2] == 0xBE || b[i+2] == 0xBF))))
}
// Check if the character at the specified position is NUL.
func is_z(b []byte, i int) bool {
return b[i] == 0x00
}
// Check if the beginning of the buffer is a BOM.
func is_bom(b []byte, i int) bool {
return b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF
}
// Check if the character at the specified position is space.
func is_space(b []byte, i int) bool {
return b[i] == ' '
}
// Check if the character at the specified position is tab.
func is_tab(b []byte, i int) bool {
return b[i] == '\t'
}
// Check if the character at the specified position is blank (space or tab).
func is_blank(b []byte, i int) bool {
//return is_space(b, i) || is_tab(b, i)
return b[i] == ' ' || b[i] == '\t'
}
// Check if the character at the specified position is a line break.
func is_break(b []byte, i int) bool {
return (b[i] == '\r' || // CR (#xD)
b[i] == '\n' || // LF (#xA)
b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85)
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028)
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9) // PS (#x2029)
}
func is_crlf(b []byte, i int) bool {
return b[i] == '\r' && b[i+1] == '\n'
}
// Check if the character is a line break or NUL.
func is_breakz(b []byte, i int) bool {
//return is_break(b, i) || is_z(b, i)
return (
// is_break:
b[i] == '\r' || // CR (#xD)
b[i] == '\n' || // LF (#xA)
b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85)
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028)
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029)
// is_z:
b[i] == 0)
}
// Check if the character is a line break, space, or NUL.
func is_spacez(b []byte, i int) bool {
//return is_space(b, i) || is_breakz(b, i)
return (
// is_space:
b[i] == ' ' ||
// is_breakz:
b[i] == '\r' || // CR (#xD)
b[i] == '\n' || // LF (#xA)
b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85)
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028)
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029)
b[i] == 0)
}
// Check if the character is a line break, space, tab, or NUL.
func is_blankz(b []byte, i int) bool {
//return is_blank(b, i) || is_breakz(b, i)
return (
// is_blank:
b[i] == ' ' || b[i] == '\t' ||
// is_breakz:
b[i] == '\r' || // CR (#xD)
b[i] == '\n' || // LF (#xA)
b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85)
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028)
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029)
b[i] == 0)
}
// Determine the width of the character.
func width(b byte) int {
// Don't replace these by a switch without first
// confirming that it is being inlined.
if b&0x80 == 0x00 {
return 1
}
if b&0xE0 == 0xC0 {
return 2
}
if b&0xF0 == 0xE0 {
return 3
}
if b&0xF8 == 0xF0 {
return 4
}
return 0
}

11
vendor/modules.txt vendored
View file

@ -13,12 +13,20 @@ github.com/mattn/go-colorable
# github.com/mattn/go-isatty v0.0.20 # github.com/mattn/go-isatty v0.0.20
## explicit; go 1.15 ## explicit; go 1.15
github.com/mattn/go-isatty github.com/mattn/go-isatty
# github.com/pmezard/go-difflib v1.0.0
## explicit
github.com/pmezard/go-difflib/difflib
# github.com/russross/blackfriday/v2 v2.1.0 # github.com/russross/blackfriday/v2 v2.1.0
## explicit ## explicit
github.com/russross/blackfriday/v2 github.com/russross/blackfriday/v2
# github.com/sirupsen/logrus v1.9.3 # github.com/sirupsen/logrus v1.9.3
## explicit; go 1.13 ## explicit; go 1.13
github.com/sirupsen/logrus github.com/sirupsen/logrus
# github.com/stretchr/testify v1.11.1
## explicit; go 1.17
github.com/stretchr/testify/assert
github.com/stretchr/testify/assert/yaml
github.com/stretchr/testify/require
# github.com/urfave/cli/v2 v2.27.7 # github.com/urfave/cli/v2 v2.27.7
## explicit; go 1.18 ## explicit; go 1.18
github.com/urfave/cli/v2 github.com/urfave/cli/v2
@ -32,3 +40,6 @@ golang.org/x/crypto/ripemd160
## explicit; go 1.24.0 ## explicit; go 1.24.0
golang.org/x/sys/unix golang.org/x/sys/unix
golang.org/x/sys/windows golang.org/x/sys/windows
# gopkg.in/yaml.v3 v3.0.1
## explicit
gopkg.in/yaml.v3

View file

@ -3,43 +3,38 @@ package mtree
import ( import (
"os" "os"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestWalk(t *testing.T) { func TestWalk(t *testing.T) {
dh, err := Walk(".", nil, append(DefaultKeywords, "sha1"), nil) walkDh, err := Walk(".", nil, append(DefaultKeywords, "sha1"), nil)
if err != nil { require.NoError(t, err, "walk .")
t.Fatal(err) walkEntries := countTypes(walkDh)
}
numEntries := countTypes(dh)
fh, err := os.CreateTemp("", "walk.") fh, err := os.CreateTemp(t.TempDir(), "walk.")
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
defer os.Remove(fh.Name())
defer fh.Close() defer fh.Close()
if _, err = dh.WriteTo(fh); err != nil { n, err := walkDh.WriteTo(fh)
t.Fatal(err) require.NoError(t, err, "write directory hierarchy representation")
} assert.NotZero(t, n, "output mtree spec should be non-empty")
if _, err := fh.Seek(0, 0); err != nil {
t.Fatal(err) _, err = fh.Seek(0, 0)
} require.NoError(t, err)
if dh, err = ParseSpec(fh); err != nil {
t.Fatal(err) specDh, err := ParseSpec(fh)
} require.NoErrorf(t, err, "parse spec %s", fh.Name())
for k, v := range countTypes(dh) {
if numEntries[k] != v { specEntries := countTypes(specDh)
t.Errorf("for type %s: expected %d, got %d", k, numEntries[k], v)
} assert.Equal(t, walkEntries, specEntries, "round-trip specfile should have the same set of entries")
}
} }
func TestWalkDirectory(t *testing.T) { func TestWalkDirectory(t *testing.T) {
dh, err := Walk(".", []ExcludeFunc{ExcludeNonDirectories}, []Keyword{"type"}, nil) dh, err := Walk(".", []ExcludeFunc{ExcludeNonDirectories}, []Keyword{"type"}, nil)
if err != nil { require.NoError(t, err, "walk . (ExcludeNonDirectories)")
t.Fatal(err)
}
for i := range dh.Entries { for i := range dh.Entries {
for _, keyval := range dh.Entries[i].AllKeys() { for _, keyval := range dh.Entries[i].AllKeys() {

View file

@ -4,9 +4,11 @@
package xattr package xattr
import ( import (
"bytes"
"os" "os"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestXattr(t *testing.T) { func TestXattr(t *testing.T) {
@ -15,30 +17,22 @@ func TestXattr(t *testing.T) {
testDir = "." testDir = "."
} }
fh, err := os.CreateTemp(testDir, "xattr.") fh, err := os.CreateTemp(testDir, "xattr.")
if err != nil { require.NoError(t, err)
t.Fatal(err)
} path := fh.Name()
defer os.Remove(fh.Name()) defer os.Remove(path)
if err := fh.Close(); err != nil {
t.Fatal(err) require.NoError(t, fh.Close())
}
expected := []byte("1234") expected := []byte("1234")
if err := Set(fh.Name(), "user.testing", expected); err != nil { err = Set(path, "user.testing", expected)
t.Fatal(fh.Name(), err) require.NoErrorf(t, err, "set user.testing xattr %s", path)
}
l, err := List(fh.Name()) l, err := List(path)
if err != nil { require.NoErrorf(t, err, "list xattr %s", path)
t.Error(fh.Name(), err) assert.NotEmptyf(t, l, "expected at least one xattr in list for %s", path)
}
if !(len(l) > 0) { got, err := Get(path, "user.testing")
t.Errorf("%q: expected a list of at least 1; got %d", fh.Name(), len(l)) require.NoErrorf(t, err, "get user.testing xattr %s", path)
} assert.Equalf(t, expected, got, "user.testing xattr %s", path)
got, err := Get(fh.Name(), "user.testing")
if err != nil {
t.Fatal(fh.Name(), err)
}
if !bytes.Equal(got, expected) {
t.Errorf("%q: expected %q; got %q", fh.Name(), expected, got)
}
} }