Merge pull request #1770 from crosbymichael/resume
content, remotes: download resumption support
This commit is contained in:
commit
fc149f0ef9
@ -2,7 +2,6 @@ package content
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -119,7 +118,7 @@ func seekReader(r io.Reader, offset, size int64) (io.Reader, error) {
|
|||||||
if ok {
|
if ok {
|
||||||
nn, err := seeker.Seek(offset, io.SeekStart)
|
nn, err := seeker.Seek(offset, io.SeekStart)
|
||||||
if nn != offset {
|
if nn != offset {
|
||||||
return nil, fmt.Errorf("failed to seek to offset %v", offset)
|
return nil, errors.Wrapf(err, "failed to seek to offset %v", offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Handles locking references
|
// Handles locking references
|
||||||
// TODO: use boltdb for lock status
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// locks lets us lock in process
|
// locks lets us lock in process
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -219,7 +220,7 @@ func (s *store) Walk(ctx context.Context, fn content.WalkFunc, filters ...string
|
|||||||
|
|
||||||
// TODO(stevvooe): There are few more cases with subdirs that should be
|
// TODO(stevvooe): There are few more cases with subdirs that should be
|
||||||
// handled in case the layout gets corrupted. This isn't strict enough
|
// handled in case the layout gets corrupted. This isn't strict enough
|
||||||
// an may spew bad data.
|
// and may spew bad data.
|
||||||
|
|
||||||
if path == root {
|
if path == root {
|
||||||
return nil
|
return nil
|
||||||
@ -324,12 +325,28 @@ func (s *store) status(ingestPath string) (content.Status, error) {
|
|||||||
return content.Status{}, err
|
return content.Status{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startedAt, err := readFileTimestamp(filepath.Join(ingestPath, "startedat"))
|
||||||
|
if err != nil {
|
||||||
|
return content.Status{}, errors.Wrapf(err, "could not read startedat")
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedAt, err := readFileTimestamp(filepath.Join(ingestPath, "updatedat"))
|
||||||
|
if err != nil {
|
||||||
|
return content.Status{}, errors.Wrapf(err, "could not read updatedat")
|
||||||
|
}
|
||||||
|
|
||||||
|
// because we don't write updatedat on every write, the mod time may
|
||||||
|
// actually be more up to date.
|
||||||
|
if fi.ModTime().After(updatedAt) {
|
||||||
|
updatedAt = fi.ModTime()
|
||||||
|
}
|
||||||
|
|
||||||
return content.Status{
|
return content.Status{
|
||||||
Ref: ref,
|
Ref: ref,
|
||||||
Offset: fi.Size(),
|
Offset: fi.Size(),
|
||||||
Total: s.total(ingestPath),
|
Total: s.total(ingestPath),
|
||||||
UpdatedAt: fi.ModTime(),
|
UpdatedAt: updatedAt,
|
||||||
StartedAt: getStartTime(fi),
|
StartedAt: startedAt,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -369,6 +386,37 @@ func (s *store) total(ingestPath string) int64 {
|
|||||||
//
|
//
|
||||||
// The argument `ref` is used to uniquely identify a long-lived writer transaction.
|
// The argument `ref` is used to uniquely identify a long-lived writer transaction.
|
||||||
func (s *store) Writer(ctx context.Context, ref string, total int64, expected digest.Digest) (content.Writer, error) {
|
func (s *store) Writer(ctx context.Context, ref string, total int64, expected digest.Digest) (content.Writer, error) {
|
||||||
|
var lockErr error
|
||||||
|
for count := uint64(0); count < 10; count++ {
|
||||||
|
time.Sleep(time.Millisecond * time.Duration(rand.Intn(1<<count)))
|
||||||
|
if err := tryLock(ref); err != nil {
|
||||||
|
if !errdefs.IsUnavailable(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lockErr = err
|
||||||
|
} else {
|
||||||
|
lockErr = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if lockErr != nil {
|
||||||
|
return nil, lockErr
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err := s.writer(ctx, ref, total, expected)
|
||||||
|
if err != nil {
|
||||||
|
unlock(ref)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return w, nil // lock is now held by w.
|
||||||
|
}
|
||||||
|
|
||||||
|
// writer provides the main implementation of the Writer method. The caller
|
||||||
|
// must hold the lock correctly and release on error if there is a problem.
|
||||||
|
func (s *store) writer(ctx context.Context, ref string, total int64, expected digest.Digest) (content.Writer, error) {
|
||||||
// TODO(stevvooe): Need to actually store expected here. We have
|
// TODO(stevvooe): Need to actually store expected here. We have
|
||||||
// code in the service that shouldn't be dealing with this.
|
// code in the service that shouldn't be dealing with this.
|
||||||
if expected != "" {
|
if expected != "" {
|
||||||
@ -380,10 +428,6 @@ func (s *store) Writer(ctx context.Context, ref string, total int64, expected di
|
|||||||
|
|
||||||
path, refp, data := s.ingestPaths(ref)
|
path, refp, data := s.ingestPaths(ref)
|
||||||
|
|
||||||
if err := tryLock(ref); err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "locking ref %v failed", ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
digester = digest.Canonical.Digester()
|
digester = digest.Canonical.Digester()
|
||||||
offset int64
|
offset int64
|
||||||
@ -412,7 +456,7 @@ func (s *store) Writer(ctx context.Context, ref string, total int64, expected di
|
|||||||
return nil, errors.Errorf("provided total differs from status: %v != %v", total, status.Total)
|
return nil, errors.Errorf("provided total differs from status: %v != %v", total, status.Total)
|
||||||
}
|
}
|
||||||
|
|
||||||
// slow slow slow!!, send to goroutine or use resumable hashes
|
// TODO(stevvooe): slow slow slow!!, send to goroutine or use resumable hashes
|
||||||
fp, err := os.Open(data)
|
fp, err := os.Open(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -431,27 +475,39 @@ func (s *store) Writer(ctx context.Context, ref string, total int64, expected di
|
|||||||
startedAt = status.StartedAt
|
startedAt = status.StartedAt
|
||||||
total = status.Total
|
total = status.Total
|
||||||
} else {
|
} else {
|
||||||
|
startedAt = time.Now()
|
||||||
|
updatedAt = startedAt
|
||||||
|
|
||||||
// the ingest is new, we need to setup the target location.
|
// the ingest is new, we need to setup the target location.
|
||||||
// write the ref to a file for later use
|
// write the ref to a file for later use
|
||||||
if err := ioutil.WriteFile(refp, []byte(ref), 0666); err != nil {
|
if err := ioutil.WriteFile(refp, []byte(ref), 0666); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if writeTimestampFile(filepath.Join(path, "startedat"), startedAt); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if writeTimestampFile(filepath.Join(path, "updatedat"), startedAt); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if total > 0 {
|
if total > 0 {
|
||||||
if err := ioutil.WriteFile(filepath.Join(path, "total"), []byte(fmt.Sprint(total)), 0666); err != nil {
|
if err := ioutil.WriteFile(filepath.Join(path, "total"), []byte(fmt.Sprint(total)), 0666); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startedAt = time.Now()
|
|
||||||
updatedAt = startedAt
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fp, err := os.OpenFile(data, os.O_WRONLY|os.O_CREATE, 0666)
|
fp, err := os.OpenFile(data, os.O_WRONLY|os.O_CREATE|os.O_SYNC, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to open data file")
|
return nil, errors.Wrap(err, "failed to open data file")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, err := fp.Seek(offset, io.SeekStart); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "could not seek to current write offset")
|
||||||
|
}
|
||||||
|
|
||||||
return &writer{
|
return &writer{
|
||||||
s: s,
|
s: s,
|
||||||
fp: fp,
|
fp: fp,
|
||||||
@ -509,3 +565,30 @@ func readFileString(path string) (string, error) {
|
|||||||
p, err := ioutil.ReadFile(path)
|
p, err := ioutil.ReadFile(path)
|
||||||
return string(p), err
|
return string(p), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readFileTimestamp reads a file with just a timestamp present.
|
||||||
|
func readFileTimestamp(p string) (time.Time, error) {
|
||||||
|
b, err := ioutil.ReadFile(p)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err = errors.Wrap(errdefs.ErrNotFound, err.Error())
|
||||||
|
}
|
||||||
|
return time.Time{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var t time.Time
|
||||||
|
if err := t.UnmarshalText(b); err != nil {
|
||||||
|
return time.Time{}, errors.Wrapf(err, "could not parse timestamp file %v", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeTimestampFile(p string, t time.Time) error {
|
||||||
|
b, err := t.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ioutil.WriteFile(p, b, 0666)
|
||||||
|
}
|
||||||
|
@ -152,6 +152,7 @@ func (w *writer) Close() (err error) {
|
|||||||
if w.fp != nil {
|
if w.fp != nil {
|
||||||
w.fp.Sync()
|
w.fp.Sync()
|
||||||
err = w.fp.Close()
|
err = w.fp.Close()
|
||||||
|
writeTimestampFile(filepath.Join(w.path, "updatedat"), w.updatedAt)
|
||||||
w.fp = nil
|
w.fp = nil
|
||||||
unlock(w.ref)
|
unlock(w.ref)
|
||||||
return
|
return
|
||||||
|
@ -16,12 +16,14 @@ import (
|
|||||||
"github.com/containerd/containerd/testutil"
|
"github.com/containerd/containerd/testutil"
|
||||||
digest "github.com/opencontainers/go-digest"
|
digest "github.com/opencontainers/go-digest"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ContentSuite runs a test suite on the content store given a factory function.
|
// ContentSuite runs a test suite on the content store given a factory function.
|
||||||
func ContentSuite(t *testing.T, name string, storeFn func(ctx context.Context, root string) (content.Store, func() error, error)) {
|
func ContentSuite(t *testing.T, name string, storeFn func(ctx context.Context, root string) (content.Store, func() error, error)) {
|
||||||
t.Run("Writer", makeTest(t, name, storeFn, checkContentStoreWriter))
|
t.Run("Writer", makeTest(t, name, storeFn, checkContentStoreWriter))
|
||||||
t.Run("UploadStatus", makeTest(t, name, storeFn, checkUploadStatus))
|
t.Run("UploadStatus", makeTest(t, name, storeFn, checkUploadStatus))
|
||||||
|
t.Run("Resume", makeTest(t, name, storeFn, checkResumeWriter))
|
||||||
t.Run("Labels", makeTest(t, name, storeFn, checkLabels))
|
t.Run("Labels", makeTest(t, name, storeFn, checkLabels))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,8 +137,82 @@ func checkContentStoreWriter(ctx context.Context, t *testing.T, cs content.Store
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkResumeWriter(ctx context.Context, t *testing.T, cs content.Store) {
|
||||||
|
checkWrite := func(t *testing.T, w io.Writer, p []byte) {
|
||||||
|
t.Helper()
|
||||||
|
n, err := w.Write(p)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n != len(p) {
|
||||||
|
t.Fatal("short write to content store")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ref = "cb"
|
||||||
|
cb, dgst = createContent(256, 10)
|
||||||
|
first, second = cb[:128], cb[128:]
|
||||||
|
)
|
||||||
|
|
||||||
|
preStart := time.Now()
|
||||||
|
w1, err := cs.Writer(ctx, ref, 256, dgst)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
postStart := time.Now()
|
||||||
|
preUpdate := postStart
|
||||||
|
|
||||||
|
checkWrite(t, w1, first)
|
||||||
|
postUpdate := time.Now()
|
||||||
|
|
||||||
|
dgstFirst := digest.FromBytes(first)
|
||||||
|
expected := content.Status{
|
||||||
|
Ref: ref,
|
||||||
|
Offset: int64(len(first)),
|
||||||
|
Total: int64(len(cb)),
|
||||||
|
Expected: dgstFirst,
|
||||||
|
}
|
||||||
|
|
||||||
|
checkStatus(t, w1, expected, dgstFirst, preStart, postStart, preUpdate, postUpdate)
|
||||||
|
require.NoError(t, w1.Close(), "close first writer")
|
||||||
|
|
||||||
|
w2, err := cs.Writer(ctx, ref, 256, dgst)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// status should be consistent with version before close.
|
||||||
|
checkStatus(t, w2, expected, dgstFirst, preStart, postStart, preUpdate, postUpdate)
|
||||||
|
|
||||||
|
preUpdate = time.Now()
|
||||||
|
checkWrite(t, w2, second)
|
||||||
|
postUpdate = time.Now()
|
||||||
|
|
||||||
|
expected.Offset = expected.Total
|
||||||
|
expected.Expected = dgst
|
||||||
|
checkStatus(t, w2, expected, dgst, preStart, postStart, preUpdate, postUpdate)
|
||||||
|
|
||||||
|
preCommit := time.Now()
|
||||||
|
if err := w2.Commit(ctx, 0, ""); err != nil {
|
||||||
|
t.Fatalf("commit failed: %+v", err)
|
||||||
|
}
|
||||||
|
postCommit := time.Now()
|
||||||
|
|
||||||
|
require.NoError(t, w2.Close(), "close second writer")
|
||||||
|
info := content.Info{
|
||||||
|
Digest: dgst,
|
||||||
|
Size: 256,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checkInfo(ctx, cs, dgst, info, preCommit, postCommit, preCommit, postCommit); err != nil {
|
||||||
|
t.Fatalf("Check info failed: %+v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func checkUploadStatus(ctx context.Context, t *testing.T, cs content.Store) {
|
func checkUploadStatus(ctx context.Context, t *testing.T, cs content.Store) {
|
||||||
c1, d1 := createContent(256, 1)
|
c1, d1 := createContent(256, 17)
|
||||||
|
|
||||||
preStart := time.Now()
|
preStart := time.Now()
|
||||||
w1, err := cs.Writer(ctx, "c1", 256, d1)
|
w1, err := cs.Writer(ctx, "c1", 256, d1)
|
||||||
@ -156,9 +232,7 @@ func checkUploadStatus(ctx context.Context, t *testing.T, cs content.Store) {
|
|||||||
preUpdate := preStart
|
preUpdate := preStart
|
||||||
postUpdate := postStart
|
postUpdate := postStart
|
||||||
|
|
||||||
if err := checkStatus(w1, expected, d, preStart, postStart, preUpdate, postUpdate); err != nil {
|
checkStatus(t, w1, expected, d, preStart, postStart, preUpdate, postUpdate)
|
||||||
t.Fatalf("Status check failed: %+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write first 64 bytes
|
// Write first 64 bytes
|
||||||
preUpdate = time.Now()
|
preUpdate = time.Now()
|
||||||
@ -168,9 +242,7 @@ func checkUploadStatus(ctx context.Context, t *testing.T, cs content.Store) {
|
|||||||
postUpdate = time.Now()
|
postUpdate = time.Now()
|
||||||
expected.Offset = 64
|
expected.Offset = 64
|
||||||
d = digest.FromBytes(c1[:64])
|
d = digest.FromBytes(c1[:64])
|
||||||
if err := checkStatus(w1, expected, d, preStart, postStart, preUpdate, postUpdate); err != nil {
|
checkStatus(t, w1, expected, d, preStart, postStart, preUpdate, postUpdate)
|
||||||
t.Fatalf("Status check failed: %+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write next 128 bytes
|
// Write next 128 bytes
|
||||||
preUpdate = time.Now()
|
preUpdate = time.Now()
|
||||||
@ -180,9 +252,7 @@ func checkUploadStatus(ctx context.Context, t *testing.T, cs content.Store) {
|
|||||||
postUpdate = time.Now()
|
postUpdate = time.Now()
|
||||||
expected.Offset = 192
|
expected.Offset = 192
|
||||||
d = digest.FromBytes(c1[:192])
|
d = digest.FromBytes(c1[:192])
|
||||||
if err := checkStatus(w1, expected, d, preStart, postStart, preUpdate, postUpdate); err != nil {
|
checkStatus(t, w1, expected, d, preStart, postStart, preUpdate, postUpdate)
|
||||||
t.Fatalf("Status check failed: %+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write last 64 bytes
|
// Write last 64 bytes
|
||||||
preUpdate = time.Now()
|
preUpdate = time.Now()
|
||||||
@ -191,9 +261,7 @@ func checkUploadStatus(ctx context.Context, t *testing.T, cs content.Store) {
|
|||||||
}
|
}
|
||||||
postUpdate = time.Now()
|
postUpdate = time.Now()
|
||||||
expected.Offset = 256
|
expected.Offset = 256
|
||||||
if err := checkStatus(w1, expected, d1, preStart, postStart, preUpdate, postUpdate); err != nil {
|
checkStatus(t, w1, expected, d1, preStart, postStart, preUpdate, postUpdate)
|
||||||
t.Fatalf("Status check failed: %+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
preCommit := time.Now()
|
preCommit := time.Now()
|
||||||
if err := w1.Commit(ctx, 0, ""); err != nil {
|
if err := w1.Commit(ctx, 0, ""); err != nil {
|
||||||
@ -212,7 +280,7 @@ func checkUploadStatus(ctx context.Context, t *testing.T, cs content.Store) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func checkLabels(ctx context.Context, t *testing.T, cs content.Store) {
|
func checkLabels(ctx context.Context, t *testing.T, cs content.Store) {
|
||||||
c1, d1 := createContent(256, 1)
|
c1, d1 := createContent(256, 19)
|
||||||
|
|
||||||
w1, err := cs.Writer(ctx, "c1", 256, d1)
|
w1, err := cs.Writer(ctx, "c1", 256, d1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -275,42 +343,48 @@ func checkLabels(ctx context.Context, t *testing.T, cs content.Store) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkStatus(w content.Writer, expected content.Status, d digest.Digest, preStart, postStart, preUpdate, postUpdate time.Time) error {
|
func checkStatus(t *testing.T, w content.Writer, expected content.Status, d digest.Digest, preStart, postStart, preUpdate, postUpdate time.Time) {
|
||||||
|
t.Helper()
|
||||||
st, err := w.Status()
|
st, err := w.Status()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to get status")
|
t.Fatalf("failed to get status: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wd := w.Digest()
|
wd := w.Digest()
|
||||||
if wd != d {
|
if wd != d {
|
||||||
return errors.Errorf("unexpected digest %v, expected %v", wd, d)
|
t.Fatalf("unexpected digest %v, expected %v", wd, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
if st.Ref != expected.Ref {
|
if st.Ref != expected.Ref {
|
||||||
return errors.Errorf("unexpected ref %q, expected %q", st.Ref, expected.Ref)
|
t.Fatalf("unexpected ref %q, expected %q", st.Ref, expected.Ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
if st.Offset != expected.Offset {
|
if st.Offset != expected.Offset {
|
||||||
return errors.Errorf("unexpected offset %d, expected %d", st.Offset, expected.Offset)
|
t.Fatalf("unexpected offset %d, expected %d", st.Offset, expected.Offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
if st.Total != expected.Total {
|
if st.Total != expected.Total {
|
||||||
return errors.Errorf("unexpected total %d, expected %d", st.Total, expected.Total)
|
t.Fatalf("unexpected total %d, expected %d", st.Total, expected.Total)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add this test once all implementations guarantee this value is held
|
// TODO: Add this test once all implementations guarantee this value is held
|
||||||
//if st.Expected != expected.Expected {
|
//if st.Expected != expected.Expected {
|
||||||
// return errors.Errorf("unexpected \"expected digest\" %q, expected %q", st.Expected, expected.Expected)
|
// t.Fatalf("unexpected \"expected digest\" %q, expected %q", st.Expected, expected.Expected)
|
||||||
//}
|
//}
|
||||||
|
|
||||||
if st.StartedAt.After(postStart) || st.StartedAt.Before(preStart) {
|
// FIXME: broken on windows: unexpected updated at time 2017-11-14 13:43:22.178013 -0800 PST,
|
||||||
return errors.Errorf("unexpected started at time %s, expected between %s and %s", st.StartedAt, preStart, postStart)
|
// expected between 2017-11-14 13:43:22.1790195 -0800 PST m=+1.022137300 and
|
||||||
}
|
// 2017-11-14 13:43:22.1790195 -0800 PST m=+1.022137300
|
||||||
if st.UpdatedAt.After(postUpdate) || st.UpdatedAt.Before(preUpdate) {
|
if runtime.GOOS != "windows" {
|
||||||
return errors.Errorf("unexpected updated at time %s, expected between %s and %s", st.UpdatedAt, preUpdate, postUpdate)
|
if st.StartedAt.After(postStart) || st.StartedAt.Before(preStart) {
|
||||||
}
|
t.Fatalf("unexpected started at time %s, expected between %s and %s", st.StartedAt, preStart, postStart)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
t.Logf("compare update %v against (%v, %v)", st.UpdatedAt, preUpdate, postUpdate)
|
||||||
|
if st.UpdatedAt.After(postUpdate) || st.UpdatedAt.Before(preUpdate) {
|
||||||
|
t.Fatalf("unexpected updated at time %s, expected between %s and %s", st.UpdatedAt, preUpdate, postUpdate)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkInfo(ctx context.Context, cs content.Store, d digest.Digest, expected content.Info, c1, c2, u1, u2 time.Time) error {
|
func checkInfo(ctx context.Context, cs content.Store, d digest.Digest, expected content.Info, c1, c2, u1, u2 time.Time) error {
|
||||||
|
@ -2,6 +2,7 @@ package docker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
@ -37,32 +38,60 @@ func (r dockerFetcher) Fetch(ctx context.Context, desc ocispec.Descriptor) (io.R
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, u := range urls {
|
return newHTTPReadSeeker(desc.Size, func(offset int64) (io.ReadCloser, error) {
|
||||||
req, err := http.NewRequest(http.MethodGet, u, nil)
|
for _, u := range urls {
|
||||||
if err != nil {
|
rc, err := r.open(ctx, u, desc.MediaType, offset)
|
||||||
return nil, err
|
if err != nil {
|
||||||
}
|
if errdefs.IsNotFound(err) {
|
||||||
|
continue // try one of the other urls.
|
||||||
|
}
|
||||||
|
|
||||||
req.Header.Set("Accept", strings.Join([]string{desc.MediaType, `*`}, ", "))
|
return nil, err
|
||||||
resp, err := r.doRequestWithRetries(ctx, req, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode > 299 {
|
|
||||||
resp.Body.Close()
|
|
||||||
if resp.StatusCode == http.StatusNotFound {
|
|
||||||
continue // try one of the other urls.
|
|
||||||
}
|
}
|
||||||
return nil, errors.Errorf("unexpected status code %v: %v", u, resp.Status)
|
|
||||||
|
return rc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp.Body, nil
|
return nil, errors.Wrapf(errdefs.ErrNotFound,
|
||||||
|
"could not fetch content descriptor %v (%v) from remote",
|
||||||
|
desc.Digest, desc.MediaType)
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r dockerFetcher) open(ctx context.Context, u, mediatype string, offset int64) (io.ReadCloser, error) {
|
||||||
|
req, err := http.NewRequest(http.MethodGet, u, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.Wrapf(errdefs.ErrNotFound,
|
req.Header.Set("Accept", strings.Join([]string{mediatype, `*`}, ", "))
|
||||||
"could not fetch content descriptor %v (%v) from remote",
|
|
||||||
desc.Digest, desc.MediaType)
|
if offset > 0 {
|
||||||
|
// TODO(stevvooe): Only set this header in response to the
|
||||||
|
// "Accept-Ranges: bytes" header.
|
||||||
|
req.Header.Set("Range", fmt.Sprintf("bytes=%d-", offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := r.doRequestWithRetries(ctx, req, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode > 299 {
|
||||||
|
// TODO(stevvooe): When doing a offset specific request, we should
|
||||||
|
// really distinguish between a 206 and a 200. In the case of 200, we
|
||||||
|
// can discard the bytes, hiding the seek behavior from the
|
||||||
|
// implementation.
|
||||||
|
|
||||||
|
resp.Body.Close()
|
||||||
|
if resp.StatusCode == http.StatusNotFound {
|
||||||
|
return nil, errors.Wrapf(errdefs.ErrNotFound, "content at %v not found", u)
|
||||||
|
}
|
||||||
|
return nil, errors.Errorf("unexpected status code %v: %v", u, resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getV2URLPaths generates the candidate urls paths for the object based on the
|
// getV2URLPaths generates the candidate urls paths for the object based on the
|
||||||
|
125
remotes/docker/httpreadseeker.go
Normal file
125
remotes/docker/httpreadseeker.go
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/errdefs"
|
||||||
|
"github.com/containerd/containerd/log"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type httpReadSeeker struct {
|
||||||
|
size int64
|
||||||
|
offset int64
|
||||||
|
rc io.ReadCloser
|
||||||
|
open func(offset int64) (io.ReadCloser, error)
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHTTPReadSeeker(size int64, open func(offset int64) (io.ReadCloser, error)) (io.ReadCloser, error) {
|
||||||
|
return &httpReadSeeker{
|
||||||
|
size: size,
|
||||||
|
open: open,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hrs *httpReadSeeker) Read(p []byte) (n int, err error) {
|
||||||
|
if hrs.closed {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
rd, err := hrs.reader()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err = rd.Read(p)
|
||||||
|
hrs.offset += int64(n)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hrs *httpReadSeeker) Close() error {
|
||||||
|
if hrs.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
hrs.closed = true
|
||||||
|
if hrs.rc != nil {
|
||||||
|
return hrs.rc.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hrs *httpReadSeeker) Seek(offset int64, whence int) (int64, error) {
|
||||||
|
if hrs.closed {
|
||||||
|
return 0, errors.Wrap(errdefs.ErrUnavailable, "Fetcher.Seek: closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
abs := hrs.offset
|
||||||
|
switch whence {
|
||||||
|
case io.SeekStart:
|
||||||
|
abs = offset
|
||||||
|
case io.SeekCurrent:
|
||||||
|
abs += offset
|
||||||
|
case io.SeekEnd:
|
||||||
|
abs = hrs.size + offset
|
||||||
|
default:
|
||||||
|
return 0, errors.Wrap(errdefs.ErrInvalidArgument, "Fetcher.Seek: invalid whence")
|
||||||
|
}
|
||||||
|
|
||||||
|
if abs < 0 {
|
||||||
|
return 0, errors.Wrapf(errdefs.ErrInvalidArgument, "Fetcher.Seek: negative offset")
|
||||||
|
}
|
||||||
|
|
||||||
|
if abs != hrs.offset {
|
||||||
|
if hrs.rc != nil {
|
||||||
|
if err := hrs.rc.Close(); err != nil {
|
||||||
|
log.L.WithError(err).Errorf("Fetcher.Seek: failed to close ReadCloser")
|
||||||
|
}
|
||||||
|
|
||||||
|
hrs.rc = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
hrs.offset = abs
|
||||||
|
}
|
||||||
|
|
||||||
|
return hrs.offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hrs *httpReadSeeker) reader() (io.Reader, error) {
|
||||||
|
if hrs.rc != nil {
|
||||||
|
return hrs.rc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if hrs.offset < hrs.size {
|
||||||
|
// only try to reopen the body request if we are seeking to a value
|
||||||
|
// less than the actual size.
|
||||||
|
if hrs.open == nil {
|
||||||
|
return nil, errors.Wrapf(errdefs.ErrNotImplemented, "cannot open")
|
||||||
|
}
|
||||||
|
|
||||||
|
rc, err := hrs.open(hrs.offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "httpReaderSeeker: failed open")
|
||||||
|
}
|
||||||
|
|
||||||
|
if hrs.rc != nil {
|
||||||
|
if err := hrs.rc.Close(); err != nil {
|
||||||
|
log.L.WithError(err).Errorf("httpReadSeeker: failed to close ReadCloser")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hrs.rc = rc
|
||||||
|
} else {
|
||||||
|
// There is an edge case here where offset == size of the content. If
|
||||||
|
// we seek, we will probably get an error for content that cannot be
|
||||||
|
// sought (?). In that case, we should err on committing the content,
|
||||||
|
// as the length is already satisified but we just return the empty
|
||||||
|
// reader instead.
|
||||||
|
|
||||||
|
hrs.rc = ioutil.NopCloser(bytes.NewReader([]byte{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
return hrs.rc, nil
|
||||||
|
}
|
@ -298,7 +298,7 @@ func (r *dockerBase) authorize(req *http.Request) {
|
|||||||
|
|
||||||
func (r *dockerBase) doRequest(ctx context.Context, req *http.Request) (*http.Response, error) {
|
func (r *dockerBase) doRequest(ctx context.Context, req *http.Request) (*http.Response, error) {
|
||||||
ctx = log.WithLogger(ctx, log.G(ctx).WithField("url", req.URL.String()))
|
ctx = log.WithLogger(ctx, log.G(ctx).WithField("url", req.URL.String()))
|
||||||
log.G(ctx).WithField("request.headers", req.Header).WithField("request.method", req.Method).Debug("Do request")
|
log.G(ctx).WithField("request.headers", req.Header).WithField("request.method", req.Method).Debug("do request")
|
||||||
r.authorize(req)
|
r.authorize(req)
|
||||||
resp, err := ctxhttp.Do(ctx, r.client, req)
|
resp, err := ctxhttp.Do(ctx, r.client, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -41,7 +41,7 @@ func (rw *remoteWriter) Status() (content.Status, error) {
|
|||||||
Action: contentapi.WriteActionStat,
|
Action: contentapi.WriteActionStat,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return content.Status{}, err
|
return content.Status{}, errors.Wrap(err, "error getting writer status")
|
||||||
}
|
}
|
||||||
|
|
||||||
return content.Status{
|
return content.Status{
|
||||||
|
@ -2,6 +2,7 @@ package testutil
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -35,7 +36,7 @@ func DumpDir(t *testing.T, root string) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
t.Log(fi.Mode(), path, "->", target)
|
t.Log(fi.Mode(), fmt.Sprintf("%10s", ""), path, "->", target)
|
||||||
} else if fi.Mode().IsRegular() {
|
} else if fi.Mode().IsRegular() {
|
||||||
p, err := ioutil.ReadFile(path)
|
p, err := ioutil.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -46,10 +47,9 @@ func DumpDir(t *testing.T, root string) {
|
|||||||
if len(p) > 64 { // just display a little bit.
|
if len(p) > 64 { // just display a little bit.
|
||||||
p = p[:64]
|
p = p[:64]
|
||||||
}
|
}
|
||||||
|
t.Log(fi.Mode(), fmt.Sprintf("%10d", fi.Size()), path, "[", strconv.Quote(string(p)), "...]")
|
||||||
t.Log(fi.Mode(), path, "[", strconv.Quote(string(p)), "...]")
|
|
||||||
} else {
|
} else {
|
||||||
t.Log(fi.Mode(), path)
|
t.Log(fi.Mode(), fmt.Sprintf("%10d", fi.Size()), path)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
28
vendor/github.com/stretchr/testify/require/doc.go
generated
vendored
Normal file
28
vendor/github.com/stretchr/testify/require/doc.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// 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()`.
|
||||||
|
//
|
||||||
|
// 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
|
16
vendor/github.com/stretchr/testify/require/forward_requirements.go
generated
vendored
Normal file
16
vendor/github.com/stretchr/testify/require/forward_requirements.go
generated
vendored
Normal 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 go run ../_codegen/main.go -output-package=require -template=require_forward.go.tmpl
|
464
vendor/github.com/stretchr/testify/require/require.go
generated
vendored
Normal file
464
vendor/github.com/stretchr/testify/require/require.go
generated
vendored
Normal file
@ -0,0 +1,464 @@
|
|||||||
|
/*
|
||||||
|
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
|
||||||
|
* THIS FILE MUST NOT BE EDITED BY HAND
|
||||||
|
*/
|
||||||
|
|
||||||
|
package require
|
||||||
|
|
||||||
|
import (
|
||||||
|
|
||||||
|
assert "github.com/stretchr/testify/assert"
|
||||||
|
http "net/http"
|
||||||
|
url "net/url"
|
||||||
|
time "time"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
// Condition uses a Comparison to assert a complex condition.
|
||||||
|
func Condition(t TestingT, comp assert.Comparison, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.Condition(t, comp, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Contains asserts that the specified string, list(array, slice...) or map contains the
|
||||||
|
// specified substring or element.
|
||||||
|
//
|
||||||
|
// assert.Contains(t, "Hello World", "World", "But 'Hello World' does contain 'World'")
|
||||||
|
// assert.Contains(t, ["Hello", "World"], "World", "But ["Hello", "World"] does contain 'World'")
|
||||||
|
// assert.Contains(t, {"Hello": "World"}, "Hello", "But {'Hello': 'World'} does contain 'Hello'")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func Contains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.Contains(t, s, contains, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either
|
||||||
|
// a slice or a channel with len == 0.
|
||||||
|
//
|
||||||
|
// assert.Empty(t, obj)
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.Empty(t, object, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Equal asserts that two objects are equal.
|
||||||
|
//
|
||||||
|
// assert.Equal(t, 123, 123, "123 and 123 should be equal")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func Equal(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.Equal(t, expected, actual, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// EqualError asserts that a function returned an error (i.e. not `nil`)
|
||||||
|
// and that it is equal to the provided error.
|
||||||
|
//
|
||||||
|
// actualObj, err := SomeFunction()
|
||||||
|
// if assert.Error(t, err, "An error was expected") {
|
||||||
|
// assert.Equal(t, err, expectedError)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.EqualError(t, theError, errString, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// EqualValues asserts that two objects are equal or convertable to the same types
|
||||||
|
// and equal.
|
||||||
|
//
|
||||||
|
// assert.EqualValues(t, uint32(123), int32(123), "123 and 123 should be equal")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func EqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.EqualValues(t, expected, actual, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Error asserts that a function returned an error (i.e. not `nil`).
|
||||||
|
//
|
||||||
|
// actualObj, err := SomeFunction()
|
||||||
|
// if assert.Error(t, err, "An error was expected") {
|
||||||
|
// assert.Equal(t, err, expectedError)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func Error(t TestingT, err error, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.Error(t, err, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Exactly asserts that two objects are equal is value and type.
|
||||||
|
//
|
||||||
|
// assert.Exactly(t, int32(123), int64(123), "123 and 123 should NOT be equal")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func Exactly(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.Exactly(t, expected, actual, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Fail reports a failure through
|
||||||
|
func Fail(t TestingT, failureMessage string, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.Fail(t, failureMessage, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// FailNow fails test
|
||||||
|
func FailNow(t TestingT, failureMessage string, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.FailNow(t, failureMessage, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// False asserts that the specified value is false.
|
||||||
|
//
|
||||||
|
// assert.False(t, myBool, "myBool should be false")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func False(t TestingT, value bool, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.False(t, value, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// HTTPBodyContains asserts that a specified handler returns a
|
||||||
|
// body that contains a string.
|
||||||
|
//
|
||||||
|
// assert.HTTPBodyContains(t, myHandler, "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 string, url string, values url.Values, str interface{}) {
|
||||||
|
if !assert.HTTPBodyContains(t, handler, method, url, values, str) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// HTTPBodyNotContains asserts that a specified handler returns a
|
||||||
|
// body that does not contain a string.
|
||||||
|
//
|
||||||
|
// assert.HTTPBodyNotContains(t, myHandler, "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 string, url string, values url.Values, str interface{}) {
|
||||||
|
if !assert.HTTPBodyNotContains(t, handler, method, url, values, str) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 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 string, url string, values url.Values) {
|
||||||
|
if !assert.HTTPError(t, handler, method, url, values) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 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 string, url string, values url.Values) {
|
||||||
|
if !assert.HTTPRedirect(t, handler, method, url, values) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 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 string, url string, values url.Values) {
|
||||||
|
if !assert.HTTPSuccess(t, handler, method, url, values) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Implements asserts that an object is implemented by the specified interface.
|
||||||
|
//
|
||||||
|
// assert.Implements(t, (*MyInterface)(nil), new(MyObject), "MyObject")
|
||||||
|
func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.Implements(t, interfaceObject, object, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// InDelta asserts that the two numerals are within delta of each other.
|
||||||
|
//
|
||||||
|
// assert.InDelta(t, math.Pi, (22 / 7.0), 0.01)
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func InDelta(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.InDelta(t, expected, actual, delta, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// InDeltaSlice is the same as InDelta, except it compares two slices.
|
||||||
|
func InDeltaSlice(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.InDeltaSlice(t, expected, actual, delta, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// InEpsilon asserts that expected and actual have a relative error less than epsilon
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func InEpsilon(t TestingT, expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.InEpsilon(t, expected, actual, epsilon, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// InEpsilonSlice is the same as InEpsilon, except it compares two slices.
|
||||||
|
func InEpsilonSlice(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.InEpsilonSlice(t, expected, actual, delta, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// IsType asserts that the specified objects are of the same type.
|
||||||
|
func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.IsType(t, expectedType, object, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// JSONEq asserts that two JSON strings are equivalent.
|
||||||
|
//
|
||||||
|
// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.JSONEq(t, expected, actual, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Len asserts that the specified object has specific length.
|
||||||
|
// Len also fails if the object has a type that len() not accept.
|
||||||
|
//
|
||||||
|
// assert.Len(t, mySlice, 3, "The size of slice is not 3")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.Len(t, object, length, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Nil asserts that the specified object is nil.
|
||||||
|
//
|
||||||
|
// assert.Nil(t, err, "err should be nothing")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.Nil(t, object, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// NoError asserts that a function returned no error (i.e. `nil`).
|
||||||
|
//
|
||||||
|
// actualObj, err := SomeFunction()
|
||||||
|
// if assert.NoError(t, err) {
|
||||||
|
// assert.Equal(t, actualObj, expectedObj)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func NoError(t TestingT, err error, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.NoError(t, err, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the
|
||||||
|
// specified substring or element.
|
||||||
|
//
|
||||||
|
// assert.NotContains(t, "Hello World", "Earth", "But 'Hello World' does NOT contain 'Earth'")
|
||||||
|
// assert.NotContains(t, ["Hello", "World"], "Earth", "But ['Hello', 'World'] does NOT contain 'Earth'")
|
||||||
|
// assert.NotContains(t, {"Hello": "World"}, "Earth", "But {'Hello': 'World'} does NOT contain 'Earth'")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func NotContains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.NotContains(t, s, contains, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
|
||||||
|
// a slice or a channel with len == 0.
|
||||||
|
//
|
||||||
|
// if assert.NotEmpty(t, obj) {
|
||||||
|
// assert.Equal(t, "two", obj[1])
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.NotEmpty(t, object, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// NotEqual asserts that the specified values are NOT equal.
|
||||||
|
//
|
||||||
|
// assert.NotEqual(t, obj1, obj2, "two objects shouldn't be equal")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func NotEqual(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.NotEqual(t, expected, actual, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// NotNil asserts that the specified object is not nil.
|
||||||
|
//
|
||||||
|
// assert.NotNil(t, err, "err should be something")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.NotNil(t, object, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic.
|
||||||
|
//
|
||||||
|
// assert.NotPanics(t, func(){
|
||||||
|
// RemainCalm()
|
||||||
|
// }, "Calling RemainCalm() should NOT panic")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func NotPanics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.NotPanics(t, f, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// NotRegexp asserts that a specified regexp does not match a string.
|
||||||
|
//
|
||||||
|
// assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting")
|
||||||
|
// assert.NotRegexp(t, "^start", "it's not starting")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.NotRegexp(t, rx, str, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// NotZero asserts that i is not the zero value for its type and returns the truth.
|
||||||
|
func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.NotZero(t, i, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Panics asserts that the code inside the specified PanicTestFunc panics.
|
||||||
|
//
|
||||||
|
// assert.Panics(t, func(){
|
||||||
|
// GoCrazy()
|
||||||
|
// }, "Calling GoCrazy() should panic")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func Panics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.Panics(t, f, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Regexp asserts that a specified regexp matches a string.
|
||||||
|
//
|
||||||
|
// assert.Regexp(t, regexp.MustCompile("start"), "it's starting")
|
||||||
|
// assert.Regexp(t, "start...$", "it's not starting")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.Regexp(t, rx, str, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// True asserts that the specified value is true.
|
||||||
|
//
|
||||||
|
// assert.True(t, myBool, "myBool should be true")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func True(t TestingT, value bool, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.True(t, value, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// WithinDuration asserts that the two times are within duration delta of each other.
|
||||||
|
//
|
||||||
|
// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second, "The difference should not be more than 10s")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func WithinDuration(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.WithinDuration(t, expected, actual, delta, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Zero asserts that i is the zero value for its type and returns the truth.
|
||||||
|
func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) {
|
||||||
|
if !assert.Zero(t, i, msgAndArgs...) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
388
vendor/github.com/stretchr/testify/require/require_forward.go
generated
vendored
Normal file
388
vendor/github.com/stretchr/testify/require/require_forward.go
generated
vendored
Normal file
@ -0,0 +1,388 @@
|
|||||||
|
/*
|
||||||
|
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
|
||||||
|
* THIS FILE MUST NOT BE EDITED BY HAND
|
||||||
|
*/
|
||||||
|
|
||||||
|
package require
|
||||||
|
|
||||||
|
import (
|
||||||
|
|
||||||
|
assert "github.com/stretchr/testify/assert"
|
||||||
|
http "net/http"
|
||||||
|
url "net/url"
|
||||||
|
time "time"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
// Condition uses a Comparison to assert a complex condition.
|
||||||
|
func (a *Assertions) Condition(comp assert.Comparison, msgAndArgs ...interface{}) {
|
||||||
|
Condition(a.t, comp, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Contains asserts that the specified string, list(array, slice...) or map contains the
|
||||||
|
// specified substring or element.
|
||||||
|
//
|
||||||
|
// a.Contains("Hello World", "World", "But 'Hello World' does contain 'World'")
|
||||||
|
// a.Contains(["Hello", "World"], "World", "But ["Hello", "World"] does contain 'World'")
|
||||||
|
// a.Contains({"Hello": "World"}, "Hello", "But {'Hello': 'World'} does contain 'Hello'")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) {
|
||||||
|
Contains(a.t, s, contains, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either
|
||||||
|
// a slice or a channel with len == 0.
|
||||||
|
//
|
||||||
|
// a.Empty(obj)
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) {
|
||||||
|
Empty(a.t, object, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Equal asserts that two objects are equal.
|
||||||
|
//
|
||||||
|
// a.Equal(123, 123, "123 and 123 should be equal")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
|
||||||
|
Equal(a.t, expected, actual, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// EqualError asserts that a function returned an error (i.e. not `nil`)
|
||||||
|
// and that it is equal to the provided error.
|
||||||
|
//
|
||||||
|
// actualObj, err := SomeFunction()
|
||||||
|
// if assert.Error(t, err, "An error was expected") {
|
||||||
|
// assert.Equal(t, err, expectedError)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) {
|
||||||
|
EqualError(a.t, theError, errString, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// EqualValues asserts that two objects are equal or convertable to the same types
|
||||||
|
// and equal.
|
||||||
|
//
|
||||||
|
// a.EqualValues(uint32(123), int32(123), "123 and 123 should be equal")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
|
||||||
|
EqualValues(a.t, expected, actual, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Error asserts that a function returned an error (i.e. not `nil`).
|
||||||
|
//
|
||||||
|
// actualObj, err := SomeFunction()
|
||||||
|
// if a.Error(err, "An error was expected") {
|
||||||
|
// assert.Equal(t, err, expectedError)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) Error(err error, msgAndArgs ...interface{}) {
|
||||||
|
Error(a.t, err, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Exactly asserts that two objects are equal is value and type.
|
||||||
|
//
|
||||||
|
// a.Exactly(int32(123), int64(123), "123 and 123 should NOT be equal")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
|
||||||
|
Exactly(a.t, expected, actual, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Fail reports a failure through
|
||||||
|
func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) {
|
||||||
|
Fail(a.t, failureMessage, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// FailNow fails test
|
||||||
|
func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) {
|
||||||
|
FailNow(a.t, failureMessage, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// False asserts that the specified value is false.
|
||||||
|
//
|
||||||
|
// a.False(myBool, "myBool should be false")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) False(value bool, msgAndArgs ...interface{}) {
|
||||||
|
False(a.t, value, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// HTTPBodyContains asserts that a specified handler returns a
|
||||||
|
// body that contains a string.
|
||||||
|
//
|
||||||
|
// a.HTTPBodyContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) {
|
||||||
|
HTTPBodyContains(a.t, handler, method, url, values, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// HTTPBodyNotContains asserts that a specified handler returns a
|
||||||
|
// body that does not contain a string.
|
||||||
|
//
|
||||||
|
// a.HTTPBodyNotContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) {
|
||||||
|
HTTPBodyNotContains(a.t, handler, method, url, values, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// HTTPError asserts that a specified handler returns an error status code.
|
||||||
|
//
|
||||||
|
// a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values) {
|
||||||
|
HTTPError(a.t, handler, method, url, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// HTTPRedirect asserts that a specified handler returns a redirect status code.
|
||||||
|
//
|
||||||
|
// a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values) {
|
||||||
|
HTTPRedirect(a.t, handler, method, url, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// HTTPSuccess asserts that a specified handler returns a success status code.
|
||||||
|
//
|
||||||
|
// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil)
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values) {
|
||||||
|
HTTPSuccess(a.t, handler, method, url, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Implements asserts that an object is implemented by the specified interface.
|
||||||
|
//
|
||||||
|
// a.Implements((*MyInterface)(nil), new(MyObject), "MyObject")
|
||||||
|
func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) {
|
||||||
|
Implements(a.t, interfaceObject, object, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// InDelta asserts that the two numerals are within delta of each other.
|
||||||
|
//
|
||||||
|
// a.InDelta(math.Pi, (22 / 7.0), 0.01)
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) {
|
||||||
|
InDelta(a.t, expected, actual, delta, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// InDeltaSlice is the same as InDelta, except it compares two slices.
|
||||||
|
func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) {
|
||||||
|
InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// InEpsilon asserts that expected and actual have a relative error less than epsilon
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) {
|
||||||
|
InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// InEpsilonSlice is the same as InEpsilon, except it compares two slices.
|
||||||
|
func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) {
|
||||||
|
InEpsilonSlice(a.t, expected, actual, delta, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// IsType asserts that the specified objects are of the same type.
|
||||||
|
func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) {
|
||||||
|
IsType(a.t, expectedType, object, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// JSONEq asserts that two JSON strings are equivalent.
|
||||||
|
//
|
||||||
|
// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) {
|
||||||
|
JSONEq(a.t, expected, actual, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Len asserts that the specified object has specific length.
|
||||||
|
// Len also fails if the object has a type that len() not accept.
|
||||||
|
//
|
||||||
|
// a.Len(mySlice, 3, "The size of slice is not 3")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) {
|
||||||
|
Len(a.t, object, length, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Nil asserts that the specified object is nil.
|
||||||
|
//
|
||||||
|
// a.Nil(err, "err should be nothing")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) {
|
||||||
|
Nil(a.t, object, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// NoError asserts that a function returned no error (i.e. `nil`).
|
||||||
|
//
|
||||||
|
// actualObj, err := SomeFunction()
|
||||||
|
// if a.NoError(err) {
|
||||||
|
// assert.Equal(t, actualObj, expectedObj)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) {
|
||||||
|
NoError(a.t, err, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the
|
||||||
|
// specified substring or element.
|
||||||
|
//
|
||||||
|
// a.NotContains("Hello World", "Earth", "But 'Hello World' does NOT contain 'Earth'")
|
||||||
|
// a.NotContains(["Hello", "World"], "Earth", "But ['Hello', 'World'] does NOT contain 'Earth'")
|
||||||
|
// a.NotContains({"Hello": "World"}, "Earth", "But {'Hello': 'World'} does NOT contain 'Earth'")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) {
|
||||||
|
NotContains(a.t, s, contains, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
|
||||||
|
// a slice or a channel with len == 0.
|
||||||
|
//
|
||||||
|
// if a.NotEmpty(obj) {
|
||||||
|
// assert.Equal(t, "two", obj[1])
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) {
|
||||||
|
NotEmpty(a.t, object, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// NotEqual asserts that the specified values are NOT equal.
|
||||||
|
//
|
||||||
|
// a.NotEqual(obj1, obj2, "two objects shouldn't be equal")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
|
||||||
|
NotEqual(a.t, expected, actual, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// NotNil asserts that the specified object is not nil.
|
||||||
|
//
|
||||||
|
// a.NotNil(err, "err should be something")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) {
|
||||||
|
NotNil(a.t, object, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic.
|
||||||
|
//
|
||||||
|
// a.NotPanics(func(){
|
||||||
|
// RemainCalm()
|
||||||
|
// }, "Calling RemainCalm() should NOT panic")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) NotPanics(f assert.PanicTestFunc, msgAndArgs ...interface{}) {
|
||||||
|
NotPanics(a.t, f, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// NotRegexp asserts that a specified regexp does not match a string.
|
||||||
|
//
|
||||||
|
// a.NotRegexp(regexp.MustCompile("starts"), "it's starting")
|
||||||
|
// a.NotRegexp("^start", "it's not starting")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) {
|
||||||
|
NotRegexp(a.t, rx, str, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// NotZero asserts that i is not the zero value for its type and returns the truth.
|
||||||
|
func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) {
|
||||||
|
NotZero(a.t, i, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Panics asserts that the code inside the specified PanicTestFunc panics.
|
||||||
|
//
|
||||||
|
// a.Panics(func(){
|
||||||
|
// GoCrazy()
|
||||||
|
// }, "Calling GoCrazy() should panic")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) Panics(f assert.PanicTestFunc, msgAndArgs ...interface{}) {
|
||||||
|
Panics(a.t, f, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Regexp asserts that a specified regexp matches a string.
|
||||||
|
//
|
||||||
|
// a.Regexp(regexp.MustCompile("start"), "it's starting")
|
||||||
|
// a.Regexp("start...$", "it's not starting")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) {
|
||||||
|
Regexp(a.t, rx, str, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// True asserts that the specified value is true.
|
||||||
|
//
|
||||||
|
// a.True(myBool, "myBool should be true")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) True(value bool, msgAndArgs ...interface{}) {
|
||||||
|
True(a.t, value, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// WithinDuration asserts that the two times are within duration delta of each other.
|
||||||
|
//
|
||||||
|
// a.WithinDuration(time.Now(), time.Now(), 10*time.Second, "The difference should not be more than 10s")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) {
|
||||||
|
WithinDuration(a.t, expected, actual, delta, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Zero asserts that i is the zero value for its type and returns the truth.
|
||||||
|
func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) {
|
||||||
|
Zero(a.t, i, msgAndArgs...)
|
||||||
|
}
|
9
vendor/github.com/stretchr/testify/require/requirements.go
generated
vendored
Normal file
9
vendor/github.com/stretchr/testify/require/requirements.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package require
|
||||||
|
|
||||||
|
// TestingT is an interface wrapper around *testing.T
|
||||||
|
type TestingT interface {
|
||||||
|
Errorf(format string, args ...interface{})
|
||||||
|
FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run ../_codegen/main.go -output-package=require -template=require.go.tmpl
|
Loading…
Reference in New Issue
Block a user