diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54ea336d3..f87a43238 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -149,7 +149,7 @@ jobs: matrix: image: - opensuse/leap:latest - - centos:latest + - almalinux:latest - debian:latest - ubuntu:latest - fedora:latest diff --git a/CHANGELOG.md b/CHANGELOG.md index 2087ab3ca..dea4320c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,20 @@ and this project adheres to [Semantic Versioning](http://semver.org/). had no real impact on umoci but for safety we implemented the now-recommended media-type embedding and verification. CVE-2021-41190 +### Added ### +- `umoci unpack` now supports handling layers compressed with zstd. This is + something that was added in image-spec v1.2 (which we do not yet support + fully) but at least this will allow users to operate on zstd-compressed + images, which are slowly becoming more common. +- `umoci repack` and `umoci insert` now support creating zstd-compressed + layers. The default behaviour (called `auto`) is to try to match the last + layer's compression algorithm, with a fallback to `gzip` if none of the layer + algorithms were supported. + * Users can specify their preferred compression algorithm using the new + `--compress` flag. You can also disable compression entirely using + `--compress=none` but `--compress=auto` will never automatically choose + `none` compression. + ### Changes ### - In this release, the primary development branch was renamed to `main`. - The runtime-spec version of the `config.json` version we generate is no diff --git a/cmd/umoci/insert.go b/cmd/umoci/insert.go index 66f2731da..5eeb618cf 100644 --- a/cmd/umoci/insert.go +++ b/cmd/umoci/insert.go @@ -29,12 +29,13 @@ import ( "github.com/opencontainers/umoci/mutate" "github.com/opencontainers/umoci/oci/cas/dir" "github.com/opencontainers/umoci/oci/casext" + "github.com/opencontainers/umoci/oci/casext/blobcompress" igen "github.com/opencontainers/umoci/oci/config/generate" "github.com/opencontainers/umoci/oci/layer" "github.com/urfave/cli" ) -var insertCommand = uxRemap(uxHistory(uxTag(cli.Command{ +var insertCommand = uxCompress(uxRemap(uxHistory(uxTag(cli.Command{ Name: "insert", Usage: "insert content into an OCI image", ArgsUsage: `--image [:] [--opaque] @@ -107,7 +108,7 @@ Some examples: ctx.App.Metadata["--target-path"] = targetPath return nil }, -}))) +})))) func insert(ctx *cli.Context) error { imagePath := ctx.App.Metadata["--image-path"].(string) @@ -115,6 +116,11 @@ func insert(ctx *cli.Context) error { sourcePath := ctx.App.Metadata["--source-path"].(string) targetPath := ctx.App.Metadata["--target-path"].(string) + var compressAlgo blobcompress.Algorithm + if algo, ok := ctx.App.Metadata["--compress"].(blobcompress.Algorithm); ok { + compressAlgo = algo + } + // By default we clobber the old tag. tagName := fromName if val, ok := ctx.App.Metadata["--tag"]; ok { @@ -188,9 +194,7 @@ func insert(ctx *cli.Context) error { } } - // TODO: We should add a flag to allow for a new layer to be made - // non-distributable. - if _, err := mutator.Add(context.Background(), ispec.MediaTypeImageLayer, reader, history, mutate.GzipCompressor, nil); err != nil { + if _, err := mutator.Add(context.Background(), ispec.MediaTypeImageLayer, reader, history, compressAlgo, nil); err != nil { return fmt.Errorf("add diff layer: %w", err) } diff --git a/cmd/umoci/raw-add-layer.go b/cmd/umoci/raw-add-layer.go index 1183c9131..76cf4fcb0 100644 --- a/cmd/umoci/raw-add-layer.go +++ b/cmd/umoci/raw-add-layer.go @@ -30,11 +30,12 @@ import ( "github.com/opencontainers/umoci/mutate" "github.com/opencontainers/umoci/oci/cas/dir" "github.com/opencontainers/umoci/oci/casext" + "github.com/opencontainers/umoci/oci/casext/blobcompress" igen "github.com/opencontainers/umoci/oci/config/generate" "github.com/urfave/cli" ) -var rawAddLayerCommand = uxHistory(uxTag(cli.Command{ +var rawAddLayerCommand = uxCompress(uxHistory(uxTag(cli.Command{ Name: "add-layer", Usage: "add a layer archive verbatim to an image", ArgsUsage: `--image [:] @@ -65,13 +66,18 @@ only supports uncompressed archives.`, ctx.App.Metadata["newlayer"] = ctx.Args().First() return nil }, -})) +}))) func rawAddLayer(ctx *cli.Context) error { imagePath := ctx.App.Metadata["--image-path"].(string) fromName := ctx.App.Metadata["--image-tag"].(string) newLayerPath := ctx.App.Metadata["newlayer"].(string) + var compressAlgo blobcompress.Algorithm + if algo, ok := ctx.App.Metadata["--compress"].(blobcompress.Algorithm); ok { + compressAlgo = algo + } + // Overide the from tag by default, otherwise use the one specified. tagName := fromName if overrideTagName, ok := ctx.App.Metadata["--tag"]; ok { @@ -154,9 +160,7 @@ func rawAddLayer(ctx *cli.Context) error { } } - // TODO: We should add a flag to allow for a new layer to be made - // non-distributable. - if _, err := mutator.Add(context.Background(), ispec.MediaTypeImageLayer, newLayer, history, mutate.GzipCompressor, nil); err != nil { + if _, err := mutator.Add(context.Background(), ispec.MediaTypeImageLayer, newLayer, history, compressAlgo, nil); err != nil { return fmt.Errorf("add diff layer: %w", err) } diff --git a/cmd/umoci/repack.go b/cmd/umoci/repack.go index 1c5b33fb7..3c0119770 100644 --- a/cmd/umoci/repack.go +++ b/cmd/umoci/repack.go @@ -29,12 +29,13 @@ import ( "github.com/opencontainers/umoci/mutate" "github.com/opencontainers/umoci/oci/cas/dir" "github.com/opencontainers/umoci/oci/casext" + "github.com/opencontainers/umoci/oci/casext/blobcompress" igen "github.com/opencontainers/umoci/oci/config/generate" "github.com/opencontainers/umoci/pkg/mtreefilter" "github.com/urfave/cli" ) -var repackCommand = uxHistory(cli.Command{ +var repackCommand = uxCompress(uxHistory(cli.Command{ Name: "repack", Usage: "repacks an OCI runtime bundle into a reference", ArgsUsage: `--image [:] @@ -88,13 +89,18 @@ manifest and configuration information uses the new diff atop the old manifest.` ctx.App.Metadata["bundle"] = ctx.Args().First() return nil }, -}) +})) func repack(ctx *cli.Context) error { imagePath := ctx.App.Metadata["--image-path"].(string) tagName := ctx.App.Metadata["--image-tag"].(string) bundlePath := ctx.App.Metadata["bundle"].(string) + var compressAlgo blobcompress.Algorithm + if algo, ok := ctx.App.Metadata["--compress"].(blobcompress.Algorithm); ok { + compressAlgo = algo + } + // Read the metadata first. meta, err := umoci.ReadBundleMeta(bundlePath) if err != nil { @@ -176,5 +182,5 @@ func repack(ctx *cli.Context) error { mtreefilter.MaskFilter(maskedPaths), } - return umoci.Repack(engineExt, tagName, bundlePath, meta, history, filters, ctx.Bool("refresh-bundle"), mutator) + return umoci.Repack(engineExt, tagName, bundlePath, meta, history, filters, ctx.Bool("refresh-bundle"), mutator, compressAlgo) } diff --git a/cmd/umoci/utils_ux.go b/cmd/umoci/utils_ux.go index df555db29..8e4da39d6 100644 --- a/cmd/umoci/utils_ux.go +++ b/cmd/umoci/utils_ux.go @@ -23,6 +23,7 @@ import ( "strings" "github.com/opencontainers/umoci/oci/casext" + "github.com/opencontainers/umoci/oci/casext/blobcompress" "github.com/urfave/cli" ) @@ -88,6 +89,45 @@ func uxHistory(cmd cli.Command) cli.Command { return cmd } +// uxCompress adds the --compress flag to the given cli.Command as well as +// adding relevant validation logic to the .Before of the command. The value +// will be stored in ctx.Metadata["--compress"] as a string (or nil if --tag +// was not specified). +func uxCompress(cmd cli.Command) cli.Command { + cmd.Flags = append(cmd.Flags, cli.StringFlag{ + Name: "compress", + Usage: "compression algorithm for newly created layer blobs", + Value: "auto", + }) + + oldBefore := cmd.Before + cmd.Before = func(ctx *cli.Context) error { + // Verify compression algorithm value. + var layerCompressor blobcompress.Algorithm + if ctx.IsSet("compress") { + compressType := ctx.String("compress") + if compressType == "none" { + compressType = "" + } + if compressType != "auto" { + layerCompressor = blobcompress.GetAlgorithm(compressType) + if layerCompressor == nil { + return fmt.Errorf("invalid --compress: unknown layer compression type %q", ctx.String("compress")) + } + } + } + ctx.App.Metadata["--compress"] = layerCompressor + + // Include any old befores set. + if oldBefore != nil { + return oldBefore(ctx) + } + return nil + } + + return cmd +} + // uxTag adds a --tag flag to the given cli.Command as well as adding relevant // validation logic to the .Before of the command. The value will be stored in // ctx.Metadata["--tag"] as a string (or nil if --tag was not specified). diff --git a/doc/man/umoci-insert.1.md b/doc/man/umoci-insert.1.md index 3446d1366..442aaafa9 100644 --- a/doc/man/umoci-insert.1.md +++ b/doc/man/umoci-insert.1.md @@ -8,6 +8,7 @@ umoci insert - insert content into an OCI image **umoci insert** **--image**=*image*[:*tag*] [**--tag**=*new-tag*] +[**--compress**=*compression-type*] [**--opaque**] [**--rootless**] [**--uid-map**=*value*] @@ -60,6 +61,21 @@ The global options are defined in **umoci**(1). Tag name for the modified image, if unspecified then the original tag provided to **--image** will be clobbered. +**--compress**=*compression-type* + Specify the compression type to use when creating a new layer. Supported + compression types are *none*, *gzip*, and *zstd*. **umoci-unpack**(1) + transparently supports all compression methods you can specify with + **--compress**. + + The special value *auto* will cause **umoci**(1) to auto-select the most + appropriate compression algorithm based on what previous layers are + compressed with (it will try to use the most recent layer's compression + algorithm which it supports). Note that *auto* will never select *none* + compression automatically, as not compressing **tar**(1) archives is really + not advisable. + + If no *compression-type* is provided, it defaults to *auto*. + **--opaque** (Assuming *target* is a directory.) Add an opaque whiteout entry for *target* so that any child path of *target* in previous layers is masked by the new diff --git a/doc/man/umoci-raw-add-layer.1.md b/doc/man/umoci-raw-add-layer.1.md index 922988d6d..ecf910fcc 100644 --- a/doc/man/umoci-raw-add-layer.1.md +++ b/doc/man/umoci-raw-add-layer.1.md @@ -8,6 +8,7 @@ umoci raw add-layer - add a layer archive verbatim to an image **umoci raw add-layer** **--image**=*image* [**--tag**=*tag*] +[**--compress**=*compression-type*] [**--no-history**] [**--history.comment**=*comment*] [**--history.created_by**=*created_by*] @@ -39,6 +40,21 @@ The global options are defined in **umoci**(1). tag in the image. If *tag* is not provided it defaults to the *tag* specified in **--image** (overwriting it). +**--compress**=*compression-type* + Specify the compression type to use when creating a new layer. Supported + compression types are *none*, *gzip*, and *zstd*. **umoci-unpack**(1) + transparently supports all compression methods you can specify with + **--compress**. + + The special value *auto* will cause **umoci**(1) to auto-select the most + appropriate compression algorithm based on what previous layers are + compressed with (it will try to use the most recent layer's compression + algorithm which it supports). Note that *auto* will never select *none* + compression automatically, as not compressing **tar**(1) archives is really + not advisable. + + If no *compression-type* is provided, it defaults to *auto*. + **--no-history** Causes no history entry to be added for this operation. **This is not recommended for use with umoci-raw-add-layer(1), since it results in the diff --git a/doc/man/umoci-repack.1.md b/doc/man/umoci-repack.1.md index ea5dac142..5d3ab3c51 100644 --- a/doc/man/umoci-repack.1.md +++ b/doc/man/umoci-repack.1.md @@ -7,6 +7,7 @@ umoci repack - Repacks an OCI runtime bundle into an image tag # SYNOPSIS **umoci repack** **--image**=*image*[:*tag*] +[**--compress**=*compression-type*] [**--no-history**] [**--history.comment**=*comment*] [**--history.created_by**=*created_by*] @@ -45,6 +46,21 @@ The global options are defined in **umoci**(1). the same name as *tag* it will be overwritten. If *tag* is not provided it defaults to "latest". +**--compress**=*compression-type* + Specify the compression type to use when creating a new layer. Supported + compression types are *none*, *gzip*, and *zstd*. **umoci-unpack**(1) + transparently supports all compression methods you can specify with + **--compress**. + + The special value *auto* will cause **umoci**(1) to auto-select the most + appropriate compression algorithm based on what previous layers are + compressed with (it will try to use the most recent layer's compression + algorithm which it supports). Note that *auto* will never select *none* + compression automatically, as not compressing **tar**(1) archives is really + not advisable. + + If no *compression-type* is provided, it defaults to *auto*. + **--no-history** Causes no history entry to be added for this operation. **This is not recommended for use with umoci-repack(1), since it results in the history not diff --git a/mutate/compress.go b/mutate/compress.go index 4039fd617..f1161cebf 100644 --- a/mutate/compress.go +++ b/mutate/compress.go @@ -1,19 +1,34 @@ +/* + * umoci: Umoci Modifies Open Containers' Images + * Copyright (C) 2016-2024 SUSE LLC + * + * 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 mutate import ( - "fmt" "io" - "io/ioutil" - "runtime" - "github.com/apex/log" - zstd "github.com/klauspost/compress/zstd" - gzip "github.com/klauspost/pgzip" - "github.com/opencontainers/umoci/pkg/system" + "github.com/opencontainers/umoci/oci/casext/blobcompress" ) // Compressor is an interface which users can use to implement different // compression types. +// +// Deprecated: The compression algorithm logic has been moved to [blobcompress] +// to unify the media-type compression handling for both compression and +// decompression. Please switch to [blobcompress.Algorithm]. type Compressor interface { // Compress sets up the streaming compressor for this compression type. Compress(io.Reader) (io.ReadCloser, error) @@ -22,119 +37,16 @@ type Compressor interface { // indicate what compression type is used, e.g. "gzip", or "" for no // compression. MediaTypeSuffix() string - - // BytesRead returns the number of bytes read from the uncompressed input - // stream at the current time, no guarantee of completion. - BytesRead() int64 -} - -type noopCompressor struct{} - -func (nc noopCompressor) Compress(r io.Reader) (io.ReadCloser, error) { - return ioutil.NopCloser(r), nil -} - -func (nc noopCompressor) MediaTypeSuffix() string { - return "" -} - -func (nc noopCompressor) BytesRead() int64 { - return -1 -} - -// NoopCompressor provides no compression. -var NoopCompressor Compressor = noopCompressor{} - -// GzipCompressor provides gzip compression. -var GzipCompressor Compressor = &gzipCompressor{} - -type gzipCompressor struct { - bytesRead int64 -} - -func (gz *gzipCompressor) Compress(reader io.Reader) (io.ReadCloser, error) { - pipeReader, pipeWriter := io.Pipe() - - gzw := gzip.NewWriter(pipeWriter) - if err := gzw.SetConcurrency(256<<10, 2*runtime.NumCPU()); err != nil { - return nil, fmt.Errorf("set concurrency level to %v blocks: %w", 2*runtime.NumCPU(), err) - } - go func() { - bytesRead, err := system.Copy(gzw, reader) - if err != nil { - log.Warnf("gzip compress: could not compress layer: %v", err) - // #nosec G104 - _ = pipeWriter.CloseWithError(fmt.Errorf("compressing layer: %w", err)) - return - } - gz.bytesRead = bytesRead - if err := gzw.Close(); err != nil { - log.Warnf("gzip compress: could not close gzip writer: %v", err) - // #nosec G104 - _ = pipeWriter.CloseWithError(fmt.Errorf("close gzip writer: %w", err)) - return - } - if err := pipeWriter.Close(); err != nil { - log.Warnf("gzip compress: could not close pipe: %v", err) - // We don't CloseWithError because we cannot override the Close. - return - } - }() - - return pipeReader, nil -} - -func (gz gzipCompressor) MediaTypeSuffix() string { - return "gzip" -} - -func (gz gzipCompressor) BytesRead() int64 { - return gz.bytesRead -} - -// ZstdCompressor provides zstd compression. -var ZstdCompressor Compressor = &zstdCompressor{} - -type zstdCompressor struct { - bytesRead int64 } -func (zs *zstdCompressor) Compress(reader io.Reader) (io.ReadCloser, error) { - - pipeReader, pipeWriter := io.Pipe() - zw, err := zstd.NewWriter(pipeWriter) - if err != nil { - return nil, err - } - go func() { - bytesRead, err := system.Copy(zw, reader) - if err != nil { - log.Warnf("zstd compress: could not compress layer: %v", err) - // #nosec G104 - _ = pipeWriter.CloseWithError(fmt.Errorf("compressing layer: %w", err)) - return - } - zs.bytesRead = bytesRead - if err := zw.Close(); err != nil { - log.Warnf("zstd compress: could not close gzip writer: %v", err) - // #nosec G104 - _ = pipeWriter.CloseWithError(fmt.Errorf("close zstd writer: %w", err)) - return - } - if err := pipeWriter.Close(); err != nil { - log.Warnf("zstd compress: could not close pipe: %v", err) - // We don't CloseWithError because we cannot override the Close. - return - } - }() - - return pipeReader, nil -} - -func (zs zstdCompressor) MediaTypeSuffix() string { - return "zstd" -} - -func (zs zstdCompressor) BytesRead() int64 { - return zs.bytesRead -} +// Deprecated: The compression algorithm logic has been moved to [blobcompress] +// to unify the media-type compression handling for both compression and +// decompression. Please switch to blobcompress. +var ( + // NoopCompressor provides no compression. + NoopCompressor Compressor = blobcompress.Noop + // GzipCompressor provides gzip compression. + GzipCompressor Compressor = blobcompress.Gzip + // ZstdCompressor provides zstd compression. + ZstdCompressor Compressor = blobcompress.Zstd +) diff --git a/mutate/mutate.go b/mutate/mutate.go index a975110e0..026763da4 100644 --- a/mutate/mutate.go +++ b/mutate/mutate.go @@ -29,11 +29,15 @@ import ( "reflect" "time" + "github.com/opencontainers/umoci/oci/cas" + "github.com/opencontainers/umoci/oci/casext" + "github.com/opencontainers/umoci/oci/casext/blobcompress" + "github.com/opencontainers/umoci/oci/casext/mediatype" + "github.com/opencontainers/umoci/pkg/iohelpers" + "github.com/apex/log" "github.com/opencontainers/go-digest" ispec "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/opencontainers/umoci/oci/cas" - "github.com/opencontainers/umoci/oci/casext" ) // UmociUncompressedBlobSizeAnnotation is an umoci-specific annotation to @@ -254,32 +258,28 @@ func (m *Mutator) appendToConfig(history *ispec.History, layerDiffID digest.Dige } } -// add adds the given layer to the CAS, and mutates the configuration to -// include the diffID. The returned string is the digest of the *compressed* -// layer (which is compressed by us). -func (m *Mutator) add(ctx context.Context, reader io.Reader, history *ispec.History, compressor Compressor) (digest.Digest, int64, error) { +// PickDefaultCompressAlgorithm returns the best option for the compression +// algorithm for new layers. The main preference is to use re-use whatever the +// most recent layer's compression algorithm is (for those we support). As a +// final fallback, we use blobcompress.Default. +func (m *Mutator) PickDefaultCompressAlgorithm(ctx context.Context) (Compressor, error) { if err := m.cache(ctx); err != nil { - return "", -1, fmt.Errorf("getting cache failed: %w", err) - } - - diffidDigester := cas.BlobAlgorithm.Digester() - hashReader := io.TeeReader(reader, diffidDigester.Hash()) - - compressed, err := compressor.Compress(hashReader) - if err != nil { - return "", -1, fmt.Errorf("couldn't create compression for blob: %w", err) + return nil, fmt.Errorf("getting cache failed: %w", err) } - defer compressed.Close() - - layerDigest, layerSize, err := m.engine.PutBlob(ctx, compressed) - if err != nil { - return "", -1, fmt.Errorf("put layer blob: %w", err) + layers := m.manifest.Layers + for i := len(layers) - 1; i >= 0; i-- { + _, compressType := mediatype.SplitMediaTypeSuffix(layers[i].MediaType) + // Don't generate an uncompressed layer even if the previous one is -- + // there is no reason to automatically generate uncompressed blobs. + if compressType != "" { + candidate := blobcompress.GetAlgorithm(compressType) + if candidate != nil { + return candidate, nil + } + } } - - // Add DiffID to configuration. - layerDiffID := diffidDigester.Digest() - m.appendToConfig(history, layerDiffID) - return layerDigest, layerSize, nil + // No supported, non-plain algorithm found. Just use the default. + return blobcompress.Default, nil } // Add adds a layer to the image, by reading the layer changeset blob from the @@ -293,28 +293,50 @@ func (m *Mutator) Add(ctx context.Context, mediaType string, r io.Reader, histor return desc, fmt.Errorf("getting cache failed: %w", err) } - digest, size, err := m.add(ctx, r, history, compressor) + countReader := iohelpers.CountReader(r) + + diffidDigester := cas.BlobAlgorithm.Digester() + hashReader := io.TeeReader(countReader, diffidDigester.Hash()) + + if compressor == nil { + bestCompressor, err := m.PickDefaultCompressAlgorithm(ctx) + if err != nil { + return desc, fmt.Errorf("find best default layer compression algorithm: %w", err) + } + compressor = bestCompressor + } + + compressed, err := compressor.Compress(hashReader) + if err != nil { + return desc, fmt.Errorf("couldn't create compression for blob: %w", err) + } + defer compressed.Close() + + layerDigest, layerSize, err := m.engine.PutBlob(ctx, compressed) if err != nil { - return desc, fmt.Errorf("add layer: %w", err) + return desc, fmt.Errorf("put layer blob: %w", err) } - compressedMediaType := mediaType - if compressor.MediaTypeSuffix() != "" { - compressedMediaType = compressedMediaType + "+" + compressor.MediaTypeSuffix() + // Add DiffID to configuration. + m.appendToConfig(history, diffidDigester.Digest()) + + // Build the descriptor. + if suffix := compressor.MediaTypeSuffix(); suffix != "" { + mediaType += "+" + suffix } if annotations == nil { annotations = make(map[string]string) } - if compressor.BytesRead() >= 0 { - annotations[UmociUncompressedBlobSizeAnnotation] = fmt.Sprintf("%d", compressor.BytesRead()) + if plainSize := countReader.BytesRead(); plainSize != layerSize { + annotations[UmociUncompressedBlobSizeAnnotation] = fmt.Sprintf("%d", plainSize) } // Append to layers. desc = ispec.Descriptor{ - MediaType: compressedMediaType, - Digest: digest, - Size: size, + MediaType: mediaType, + Digest: layerDigest, + Size: layerSize, Annotations: annotations, } m.manifest.Layers = append(m.manifest.Layers, desc) diff --git a/mutate/mutate_test.go b/mutate/mutate_test.go index 1d46a2a7c..ff9f475f0 100644 --- a/mutate/mutate_test.go +++ b/mutate/mutate_test.go @@ -35,6 +35,9 @@ import ( "github.com/opencontainers/umoci/oci/cas" casdir "github.com/opencontainers/umoci/oci/cas/dir" "github.com/opencontainers/umoci/oci/casext" + "github.com/opencontainers/umoci/oci/casext/blobcompress" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // These come from just running the code. @@ -220,7 +223,7 @@ func TestMutateAdd(t *testing.T) { annotations := map[string]string{"hello": "world"} newLayerDesc, err := mutator.Add(context.Background(), ispec.MediaTypeImageLayer, buffer, &ispec.History{ Comment: "new layer", - }, GzipCompressor, annotations) + }, blobcompress.Gzip, annotations) if err != nil { t.Fatalf("unexpected error adding layer: %+v", err) } @@ -304,6 +307,114 @@ func TestMutateAdd(t *testing.T) { } } +func testMutateAddCompression(t *testing.T, mutator *Mutator, mediaType string, addCompressAlgo, expectedCompressAlgo blobcompress.Algorithm) { + // This test doesn't care about whether the layer is real. + fakeLayerData := `fake tar archive` + fakeLayerTar := bytes.NewBufferString(fakeLayerData) + + newLayerDescriptor, err := mutator.Add( + context.Background(), + mediaType, + fakeLayerTar, + &ispec.History{Comment: "fake layer"}, + addCompressAlgo, + nil, + ) + require.NoError(t, err) + + expectedMediaType := mediaType + if suffix := expectedCompressAlgo.MediaTypeSuffix(); suffix != "" { + expectedMediaType += "+" + suffix + } + + usedCompressName := "auto" + if addCompressAlgo != nil { + if suffix := addCompressAlgo.MediaTypeSuffix(); suffix != "" { + usedCompressName = suffix + } else { + usedCompressName = "plain" + } + } + + // The media-type should be what we expected. + assert.Equalf(t, newLayerDescriptor.MediaType, expectedMediaType, "unexpected media type of new layer with compression algo %q", usedCompressName) + + // Double-check that the blob actually used the expected compression + // algorithm. + layerRdr, err := mutator.engine.GetVerifiedBlob(context.Background(), newLayerDescriptor) + require.NoError(t, err) + defer func() { + require.NoError(t, layerRdr.Close()) + }() + + plainLayerRdr, err := expectedCompressAlgo.Decompress(layerRdr) + require.NoError(t, err) + defer func() { + require.NoError(t, plainLayerRdr.Close()) + }() + + plainLayerData, err := ioutil.ReadAll(plainLayerRdr) + require.NoError(t, err) + + assert.Equal(t, fakeLayerData, string(plainLayerData), "layer data should match after round-trip") +} + +func TestMutateAddCompression(t *testing.T) { + dir := t.TempDir() + + engine, fromDescriptor := setup(t, dir) + defer engine.Close() + + mutator, err := New(engine, casext.DescriptorPath{Walk: []ispec.Descriptor{fromDescriptor}}) + if err != nil { + t.Fatal(err) + } + + // Test that explicitly setting the compression does what you expect: + for _, test := range []struct { + name string + useAlgo, expectedAlgo blobcompress.Algorithm + }{ + // The default with no previous layers should be gzip. + {"DefaultGzip", nil, blobcompress.Gzip}, + // Explicitly setting the algorithms. + {"Noop", blobcompress.Noop, blobcompress.Noop}, + {"Gzip", blobcompress.Gzip, blobcompress.Gzip}, + {"Zstd", blobcompress.Zstd, blobcompress.Zstd}, + } { + test := test // copy iterator + t.Run(test.name, func(t *testing.T) { + testMutateAddCompression(t, mutator, "vendor/TESTING-umoci-fake-layer", test.useAlgo, test.expectedAlgo) + }) + } + + // Check that the auto-selection of compression works properly. + t.Run("Auto", func(t *testing.T) { + for i, test := range []struct { + name string + useAlgo, expectedAlgo blobcompress.Algorithm + }{ + // Basic inheritance for zstd. + {"ExplicitZstd", blobcompress.Zstd, blobcompress.Zstd}, + {"AutoZstd", nil, blobcompress.Zstd}, + // Inheritance skips noop. + {"ExplicitNoop", blobcompress.Noop, blobcompress.Noop}, + {"AutoZstd-SkipNoop", nil, blobcompress.Zstd}, + // Basic inheritance for gzip. + {"ExplicitGzip", blobcompress.Gzip, blobcompress.Gzip}, + {"AutoGzip", nil, blobcompress.Gzip}, + // Inheritance skips noop. + {"ExplicitNoop", blobcompress.Noop, blobcompress.Noop}, + {"AutoGzip-SkipNoop", nil, blobcompress.Gzip}, + } { + test := test // copy iterator + t.Run(fmt.Sprintf("Step%d-%s", i, test.name), func(t *testing.T) { + testMutateAddCompression(t, mutator, "vendor/TESTING-umoci-fake-layer", test.useAlgo, test.expectedAlgo) + }) + } + }) +} + func TestMutateAddExisting(t *testing.T) { dir, err := ioutil.TempDir("", "umoci-TestMutateAddExisting") if err != nil { @@ -325,7 +436,7 @@ func TestMutateAddExisting(t *testing.T) { // Add a new layer. _, err = mutator.Add(context.Background(), ispec.MediaTypeImageLayer, buffer, &ispec.History{ Comment: "new layer", - }, GzipCompressor, nil) + }, blobcompress.Gzip, nil) if err != nil { t.Fatalf("unexpected error adding layer: %+v", err) } diff --git a/oci/casext/blobcompress/algo.go b/oci/casext/blobcompress/algo.go new file mode 100644 index 000000000..94649c3ad --- /dev/null +++ b/oci/casext/blobcompress/algo.go @@ -0,0 +1,86 @@ +/* + * umoci: Umoci Modifies Open Containers' Images + * Copyright (C) 2016-2025 SUSE LLC + * + * 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 blobcompress provides a somewhat implementation-agnostic mechanism for +// blobcompression and deblobcompression of an [io.Reader]. This is mainly intended to +// be used for blobcompressing tar layer blobs. +package blobcompress + +import ( + "fmt" + "io" + "sync" +) + +// Default is the default algorithm used within umoci if unspecified by a user. +var Default = Gzip + +// Algorithm is a generic representation of a compression algorithm that can be +// used by umoci for compressing and decompressing layer blobs. If you create a +// custom algorithm, you should make sure you register it with +// [RegisterAlgorithm] so that layers using the algorithm can be decompressed. +type Algorithm interface { + // MediaTypeSuffix returns the name of the blobcompression algorithm. This name + // will be used when generating and parsing MIME type +-suffixes. + MediaTypeSuffix() string + + // Compress sets up the streaming blobcompressor for this blobcompression type. + Compress(plain io.Reader) (blobcompressed io.ReadCloser, _ error) + + // Deblobcompress sets up the streaming deblobcompressor for this blobcompression type. + Decompress(blobcompressed io.Reader) (plain io.ReadCloser, _ error) +} + +var ( + algorithmsLock sync.RWMutex + algorithms = map[string]Algorithm{} +) + +// RegisterAlgorithm adds the provided [Algorithm] to the set of algorithms +// that umoci can automatically handle when extracting images. Returns an error +// if another [Algorithm] with the same MediaTypeSuffix has already been +// registered. +func RegisterAlgorithm(algo Algorithm) error { + name := algo.MediaTypeSuffix() + + algorithmsLock.Lock() + defer algorithmsLock.Unlock() + + if _, ok := algorithms[name]; ok { + return fmt.Errorf("blob blobcompression algorithm %s already registered", name) + } + algorithms[name] = algo + return nil +} + +// MustRegisterAlgorithm is like [RegisterAlgorithm] but it panics if +// [RegisterAlgorithm] returns an error. Intended for use in init functions. +func MustRegisterAlgorithm(algo Algorithm) { + if err := RegisterAlgorithm(algo); err != nil { + panic(err) + } +} + +// GetAlgorithm looks for a registered [Algorithm] with the given +// MediaTypeSuffix (which doubles as its name). Return nil if no such algorithm +// has been registered. +func GetAlgorithm(name string) Algorithm { + algorithmsLock.RLock() + defer algorithmsLock.RUnlock() + + return algorithms[name] +} diff --git a/oci/casext/blobcompress/algo_test.go b/oci/casext/blobcompress/algo_test.go new file mode 100644 index 000000000..68389cb16 --- /dev/null +++ b/oci/casext/blobcompress/algo_test.go @@ -0,0 +1,105 @@ +/* + * umoci: Umoci Modifies Open Containers' Images + * Copyright (C) 2016-2025 SUSE LLC + * + * 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 blobcompress + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "testing" + + "github.com/stretchr/testify/assert" +) + +func testAlgo(t *testing.T, algo Algorithm, expectedName string, expectedDiff bool) { + assert := assert.New(t) + + const data = "meshuggah rocks!!!" + + plainBuf := bytes.NewBufferString(data) + + r, err := algo.Compress(plainBuf) + assert.NoError(err) + assert.Equal(algo.MediaTypeSuffix(), expectedName) + + compressed, err := ioutil.ReadAll(r) + assert.NoError(err) + if expectedDiff { + assert.NotEqual(string(compressed), data) + } else { + assert.Equal(string(compressed), data) + } + + compressedBuf := bytes.NewBuffer(compressed) + + r, err = algo.Decompress(compressedBuf) + assert.NoError(err) + + content, err := ioutil.ReadAll(r) + assert.NoError(err) + + assert.Equal(string(content), data) +} + +type fakeAlgo struct{ name string } + +var _ Algorithm = fakeAlgo{"foo"} + +func (f fakeAlgo) MediaTypeSuffix() string { return f.name } +func (f fakeAlgo) Compress(r io.Reader) (io.ReadCloser, error) { return nil, fmt.Errorf("err") } +func (f fakeAlgo) Decompress(r io.Reader) (io.ReadCloser, error) { return nil, fmt.Errorf("err") } + +func TestRegister(t *testing.T) { + assert := assert.New(t) + + var fakeAlgo Algorithm = fakeAlgo{"fake-algo1"} + fakeAlgoName := fakeAlgo.MediaTypeSuffix() + + err := RegisterAlgorithm(fakeAlgo) + assert.NoError(err) + + gotAlgo := GetAlgorithm(fakeAlgoName) + assert.NotNil(gotAlgo) + assert.Equal(gotAlgo, fakeAlgo) +} + +func TestRegisterFail(t *testing.T) { + assert := assert.New(t) + + var fakeAlgo Algorithm = fakeAlgo{"fake-algo2"} + fakeAlgoName := fakeAlgo.MediaTypeSuffix() + + err1 := RegisterAlgorithm(fakeAlgo) + assert.NoError(err1) + + // Registering the same algorithm again should fail. + err2 := RegisterAlgorithm(fakeAlgo) + assert.Error(err2) + + gotAlgo := GetAlgorithm(fakeAlgoName) + assert.NotNil(gotAlgo) + assert.Equal(gotAlgo, fakeAlgo) +} + +func TestGetFail(t *testing.T) { + assert := assert.New(t) + + algo := GetAlgorithm("doesnotexist") + assert.Nil(algo, "GetAlgorithm for non-existent compression algorithm should return nil") +} diff --git a/oci/casext/blobcompress/gzip.go b/oci/casext/blobcompress/gzip.go new file mode 100644 index 000000000..23cd292e6 --- /dev/null +++ b/oci/casext/blobcompress/gzip.go @@ -0,0 +1,78 @@ +/* + * umoci: Umoci Modifies Open Containers' Images + * Copyright (C) 2016-2025 SUSE LLC + * + * 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 blobcompress + +import ( + "fmt" + "io" + "runtime" + + "github.com/opencontainers/umoci/oci/casext/mediatype" + "github.com/opencontainers/umoci/pkg/system" + + "github.com/apex/log" + gzip "github.com/klauspost/pgzip" +) + +// Gzip provides concurrent gzip blobcompression and deblobcompression. +var Gzip Algorithm = gzipAlgo{} + +type gzipAlgo struct{} + +func (gz gzipAlgo) MediaTypeSuffix() string { + return mediatype.GzipSuffix +} + +func (gz gzipAlgo) Compress(reader io.Reader) (io.ReadCloser, error) { + pipeReader, pipeWriter := io.Pipe() + + gzw := gzip.NewWriter(pipeWriter) + if err := gzw.SetConcurrency(256<<10, 2*runtime.NumCPU()); err != nil { + return nil, fmt.Errorf("set concurrency level to %v blocks: %w", 2*runtime.NumCPU(), err) + } + go func() { + _, err := system.Copy(gzw, reader) + if err != nil { + log.Warnf("gzip blobcompress: could not blobcompress layer: %v", err) + // #nosec G104 + _ = pipeWriter.CloseWithError(fmt.Errorf("blobcompressing layer: %w", err)) + return + } + if err := gzw.Close(); err != nil { + log.Warnf("gzip blobcompress: could not close gzip writer: %v", err) + // #nosec G104 + _ = pipeWriter.CloseWithError(fmt.Errorf("close gzip writer: %w", err)) + return + } + if err := pipeWriter.Close(); err != nil { + log.Warnf("gzip blobcompress: could not close pipe: %v", err) + // We don't CloseWithError because we cannot override the Close. + return + } + }() + + return pipeReader, nil +} + +func (gz gzipAlgo) Decompress(reader io.Reader) (io.ReadCloser, error) { + return gzip.NewReader(reader) +} + +func init() { + MustRegisterAlgorithm(Gzip) +} diff --git a/oci/casext/blobcompress/gzip_test.go b/oci/casext/blobcompress/gzip_test.go new file mode 100644 index 000000000..3d3e6fcfc --- /dev/null +++ b/oci/casext/blobcompress/gzip_test.go @@ -0,0 +1,26 @@ +/* + * umoci: Umoci Modifies Open Containers' Images + * Copyright (C) 2016-2025 SUSE LLC + * + * 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 blobcompress + +import ( + "testing" +) + +func TestGzip(t *testing.T) { + testAlgo(t, Gzip, "gzip", true) +} diff --git a/oci/casext/blobcompress/noop.go b/oci/casext/blobcompress/noop.go new file mode 100644 index 000000000..8350f9e84 --- /dev/null +++ b/oci/casext/blobcompress/noop.go @@ -0,0 +1,43 @@ +/* + * umoci: Umoci Modifies Open Containers' Images + * Copyright (C) 2016-2025 SUSE LLC + * + * 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 blobcompress + +import ( + "io" +) + +// Noop does no compression. +var Noop Algorithm = noopAlgo{} + +type noopAlgo struct{} + +func (n noopAlgo) MediaTypeSuffix() string { + return "" +} + +func (n noopAlgo) Compress(reader io.Reader) (io.ReadCloser, error) { + return io.NopCloser(reader), nil +} + +func (n noopAlgo) Decompress(reader io.Reader) (io.ReadCloser, error) { + return io.NopCloser(reader), nil +} + +func init() { + MustRegisterAlgorithm(Noop) +} diff --git a/oci/casext/blobcompress/noop_test.go b/oci/casext/blobcompress/noop_test.go new file mode 100644 index 000000000..b53f18bfa --- /dev/null +++ b/oci/casext/blobcompress/noop_test.go @@ -0,0 +1,26 @@ +/* + * umoci: Umoci Modifies Open Containers' Images + * Copyright (C) 2016-2025 SUSE LLC + * + * 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 blobcompress + +import ( + "testing" +) + +func TestNoop(t *testing.T) { + testAlgo(t, Noop, "", false) +} diff --git a/oci/casext/blobcompress/zstd.go b/oci/casext/blobcompress/zstd.go new file mode 100644 index 000000000..6318752cb --- /dev/null +++ b/oci/casext/blobcompress/zstd.go @@ -0,0 +1,80 @@ +/* + * umoci: Umoci Modifies Open Containers' Images + * Copyright (C) 2016-2025 SUSE LLC + * + * 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 blobcompress + +import ( + "fmt" + "io" + + "github.com/opencontainers/umoci/oci/casext/mediatype" + "github.com/opencontainers/umoci/pkg/system" + + "github.com/apex/log" + zstd "github.com/klauspost/compress/zstd" +) + +// Zstd provides zstd blobcompression and deblobcompression. +var Zstd Algorithm = zstdAlgo{} + +type zstdAlgo struct{} + +func (zs zstdAlgo) MediaTypeSuffix() string { + return mediatype.ZstdSuffix +} + +func (zs zstdAlgo) Compress(reader io.Reader) (io.ReadCloser, error) { + pipeReader, pipeWriter := io.Pipe() + zw, err := zstd.NewWriter(pipeWriter) + if err != nil { + return nil, err + } + go func() { + _, err := system.Copy(zw, reader) + if err != nil { + log.Warnf("zstd blobcompress: could not blobcompress layer: %v", err) + // #nosec G104 + _ = pipeWriter.CloseWithError(fmt.Errorf("blobcompressing layer: %w", err)) + return + } + if err := zw.Close(); err != nil { + log.Warnf("zstd blobcompress: could not close gzip writer: %v", err) + // #nosec G104 + _ = pipeWriter.CloseWithError(fmt.Errorf("close zstd writer: %w", err)) + return + } + if err := pipeWriter.Close(); err != nil { + log.Warnf("zstd blobcompress: could not close pipe: %v", err) + // We don't CloseWithError because we cannot override the Close. + return + } + }() + + return pipeReader, nil +} + +func (zs zstdAlgo) Decompress(reader io.Reader) (io.ReadCloser, error) { + plain, err := zstd.NewReader(reader) + if err != nil { + return nil, err + } + return plain.IOReadCloser(), nil +} + +func init() { + MustRegisterAlgorithm(Zstd) +} diff --git a/oci/casext/blobcompress/zstd_test.go b/oci/casext/blobcompress/zstd_test.go new file mode 100644 index 000000000..fd57d5426 --- /dev/null +++ b/oci/casext/blobcompress/zstd_test.go @@ -0,0 +1,26 @@ +/* + * umoci: Umoci Modifies Open Containers' Images + * Copyright (C) 2016-2025 SUSE LLC + * + * 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 blobcompress + +import ( + "testing" +) + +func TestZstd(t *testing.T) { + testAlgo(t, Zstd, "zstd", true) +} diff --git a/oci/casext/mediatype/compress.go b/oci/casext/mediatype/compress.go new file mode 100644 index 000000000..897479a31 --- /dev/null +++ b/oci/casext/mediatype/compress.go @@ -0,0 +1,46 @@ +/* + * umoci: Umoci Modifies Open Containers' Images + * Copyright (C) 2016-2025 SUSE LLC + * + * 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 mediatype + +import ( + "strings" +) + +const ( + // GzipSuffix is the standard media-type suffix for gzip compressed blobs. + GzipSuffix = "gzip" + // ZstdSuffix is the standard media-type suffix for zstd compressed blobs. + ZstdSuffix = "zstd" +) + +// SplitMediaTypeSuffix takes an OCI-style MIME media-type and splits it into a +// base type and a "+" suffix. For layer blobs, this is usually the compression +// algorithm ("+gzip", "+zstd"). For JSON blobs this is usually "+json". +// +// The returned media-type suffix does not contain a "+", and media-types not +// containing a "+" are treated as having an empty suffix. +// +// While this usage is technically sanctioned by by RFC 6838, this method +// should only be used for OCI media-types, where this behaviour is +// well-defined. +func SplitMediaTypeSuffix(mediaType string) (baseType, suffix string) { + if suffixStart := strings.Index(mediaType, "+"); suffixStart >= 0 { + return mediaType[:suffixStart], mediaType[suffixStart+1:] + } + return mediaType, "" +} diff --git a/oci/layer/unpack.go b/oci/layer/unpack.go index 3ebc4214c..f6d89f910 100644 --- a/oci/layer/unpack.go +++ b/oci/layer/unpack.go @@ -33,12 +33,13 @@ import ( _ "crypto/sha256" "github.com/apex/log" - gzip "github.com/klauspost/pgzip" "github.com/opencontainers/go-digest" ispec "github.com/opencontainers/image-spec/specs-go/v1" rspec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/umoci/oci/cas" "github.com/opencontainers/umoci/oci/casext" + "github.com/opencontainers/umoci/oci/casext/blobcompress" + "github.com/opencontainers/umoci/oci/casext/mediatype" iconv "github.com/opencontainers/umoci/oci/config/convert" "github.com/opencontainers/umoci/pkg/fseval" "github.com/opencontainers/umoci/pkg/idtools" @@ -78,15 +79,25 @@ func UnpackLayer(root string, layer io.Reader, opt *UnpackOptions) error { // generated. const RootfsName = "rootfs" -// isLayerType returns if the given MediaType is the media type of an image -// layer blob. This includes both distributable and non-distributable images. func isLayerType(mediaType string) bool { - return mediaType == ispec.MediaTypeImageLayer || mediaType == ispec.MediaTypeImageLayerNonDistributable || - mediaType == ispec.MediaTypeImageLayerGzip || mediaType == ispec.MediaTypeImageLayerNonDistributableGzip + layerMediaType, _ := mediatype.SplitMediaTypeSuffix(mediaType) + switch layerMediaType { + case ispec.MediaTypeImageLayerNonDistributable: + log.Infof("image contains layers using the deprecated 'non-distributable' media type %q", layerMediaType) + fallthrough + case ispec.MediaTypeImageLayer: + return true + } + return false } -func needsGunzip(mediaType string) bool { - return mediaType == ispec.MediaTypeImageLayerGzip || mediaType == ispec.MediaTypeImageLayerNonDistributableGzip +func getLayerCompressAlgorithm(mediaType string) (string, blobcompress.Algorithm, error) { + _, compressType := mediatype.SplitMediaTypeSuffix(mediaType) + algorithm := blobcompress.GetAlgorithm(compressType) + if algorithm == nil { + return "", nil, fmt.Errorf("unsupported layer media type %q: compression method %q unsupported", mediaType, compressType) + } + return compressType, algorithm, nil } // UnpackManifest extracts all of the layers in the given manifest, as well as @@ -242,7 +253,7 @@ func UnpackRootfs(ctx context.Context, engine cas.Engine, rootfsPath string, man } defer layerBlob.Close() if !isLayerType(layerBlob.Descriptor.MediaType) { - return fmt.Errorf("unpack rootfs: layer %s: blob is not correct mediatype: %s", layerBlob.Descriptor.Digest, layerBlob.Descriptor.MediaType) + return fmt.Errorf("unpack rootfs: layer %s: layer data is an unsupported mediatype: %s", layerBlob.Descriptor.Digest, layerBlob.Descriptor.MediaType) } layerData, ok := layerBlob.Data.(io.ReadCloser) if !ok { @@ -251,13 +262,17 @@ func UnpackRootfs(ctx context.Context, engine cas.Engine, rootfsPath string, man } layerRaw := layerData - if needsGunzip(layerBlob.Descriptor.MediaType) { - // We have to extract a gzip'd version of the above layer. Also note - // that we have to check the DiffID we're extracting (which is the - // sha256 sum of the *uncompressed* layer). - layerRaw, err = gzip.NewReader(layerData) + + // Pick the decompression algorithm based on the media-type. + if compressType, compressAlgo, err := getLayerCompressAlgorithm(layerBlob.Descriptor.MediaType); err != nil { + return fmt.Errorf("unpack rootfs: layer %s: could not decompress layer: %w", layerBlob.Descriptor.Digest, err) + } else if compressAlgo != nil { + // We have to extract a compressed version of the above layer. Also + // note that we have to check the DiffID we're extracting (which is + // the sha256 sum of the *uncompressed* layer). + layerRaw, err = compressAlgo.Decompress(layerData) if err != nil { - return fmt.Errorf("create gzip reader: %w", err) + return fmt.Errorf("create %s reader: %w", compressType, err) } } diff --git a/pkg/iohelpers/count_reader.go b/pkg/iohelpers/count_reader.go new file mode 100644 index 000000000..9dd623d7a --- /dev/null +++ b/pkg/iohelpers/count_reader.go @@ -0,0 +1,48 @@ +/* + * umoci: Umoci Modifies Open Containers' Images + * Copyright (C) 2016-2025 SUSE LLC + * + * 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 iohelpers + +import ( + "io" +) + +// CountingReader is an [io.Reader] wrapper that counts how many bytes were read +// from the underlying [io.Reader]. +type CountingReader struct { + R io.Reader // underlying reader + N int64 // number of bytes read +} + +// CountReader returns a new *CountingReader that wraps the given [io.Reader]. +func CountReader(rdr io.Reader) *CountingReader { + return &CountingReader{R: rdr, N: 0} +} + +func (c *CountingReader) Read(p []byte) (n int, err error) { + n, err = c.R.Read(p) + c.N += int64(n) + return +} + +// BytesRead returns the number of bytes read so far from the reader. This is +// just shorthand for c.N. +func (c CountingReader) BytesRead() int64 { + return c.N +} + +// TODO: What about WriteTo? diff --git a/repack.go b/repack.go index 7b48da4f3..d3e8e7eb3 100644 --- a/repack.go +++ b/repack.go @@ -36,7 +36,9 @@ import ( // Repack repacks a bundle into an image adding a new layer for the changed // data in the bundle. -func Repack(engineExt casext.Engine, tagName string, bundlePath string, meta Meta, history *ispec.History, filters []mtreefilter.FilterFunc, refreshBundle bool, mutator *mutate.Mutator) error { +// +// If layerCompressor is nil, the compression algorithm is auto-selected. +func Repack(engineExt casext.Engine, tagName string, bundlePath string, meta Meta, history *ispec.History, filters []mtreefilter.FilterFunc, refreshBundle bool, mutator *mutate.Mutator, layerCompressor mutate.Compressor) error { mtreeName := strings.Replace(meta.From.Descriptor().Digest.String(), ":", "_", 1) mtreePath := filepath.Join(bundlePath, mtreeName+".mtree") fullRootfsPath := filepath.Join(bundlePath, layer.RootfsName) @@ -112,9 +114,7 @@ func Repack(engineExt casext.Engine, tagName string, bundlePath string, meta Met } defer reader.Close() - // TODO: We should add a flag to allow for a new layer to be made - // non-distributable. - if _, err := mutator.Add(context.Background(), ispec.MediaTypeImageLayer, reader, history, mutate.GzipCompressor, nil); err != nil { + if _, err := mutator.Add(context.Background(), ispec.MediaTypeImageLayer, reader, history, layerCompressor, nil); err != nil { return fmt.Errorf("add diff layer: %w", err) } } diff --git a/test/insert.bats b/test/insert.bats index 38206524f..76ec41790 100644 --- a/test/insert.bats +++ b/test/insert.bats @@ -374,3 +374,160 @@ function teardown() { image-verify "${IMAGE}" } + +OCI_MEDIATYPE_LAYER="application/vnd.oci.image.layer.v1.tar" + +@test "umoci insert --compress=gzip" { + # Some things to insert. + INSERTDIR="$(setup_tmpdir)" + mkdir -p "${INSERTDIR}/etc" + touch "${INSERTDIR}/etc/foo" + + # Add layer to the image. + umoci insert --image "${IMAGE}:${TAG}" --compress=gzip "${INSERTDIR}/etc" /etc + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + umoci stat --image "${IMAGE}:${TAG}" --json + [ "$status" -eq 0 ] + stat_json="$output" + + # Make sure that the last layer had the expected compression based on the + # mediatype. + expected_mediatype="${OCI_MEDIATYPE_LAYER}+gzip" + layer_mediatype="$(jq -SMr '.history[-1].layer.mediaType' <<<"$stat_json")" + [[ "$layer_mediatype" == "$expected_mediatype" ]] + + # Make sure that the actual blob seems to be a gzip + layer_hash="$(jq -SMr '.history[-1].layer.digest' <<<"$stat_json" | tr : /)" + sane_run file -i "$IMAGE/blobs/$layer_hash" + [ "$status" -eq 0 ] + [[ "$output" == *"application/x-gzip"* ]] +} + +@test "umoci insert --compress=zstd" { + # Some things to insert. + INSERTDIR="$(setup_tmpdir)" + mkdir -p "${INSERTDIR}/etc" + touch "${INSERTDIR}/etc/foo" + + # Add layer to the image. + umoci insert --image "${IMAGE}:${TAG}" --compress=zstd "${INSERTDIR}/etc" /etc + [ "$status" -eq 0 ] + #image-verify "${IMAGE}" # image-tools cannot handle zstd + + umoci stat --image "${IMAGE}:${TAG}" --json + [ "$status" -eq 0 ] + stat_json="$output" + + # Make sure that the last layer had the expected compression based on the + # mediatype. + expected_mediatype="${OCI_MEDIATYPE_LAYER}+zstd" + layer_mediatype="$(jq -SMr '.history[-1].layer.mediaType' <<<"$stat_json")" + [[ "$layer_mediatype" == "$expected_mediatype" ]] + + # Make sure that the actual blob seems to be a gzip + layer_hash="$(jq -SMr '.history[-1].layer.digest' <<<"$stat_json" | tr : /)" + sane_run file -i "$IMAGE/blobs/$layer_hash" + [ "$status" -eq 0 ] + [[ "$output" == *"application/x-zstd"* ]] +} + +@test "umoci insert --compress=none" { + # Some things to insert. + INSERTDIR="$(setup_tmpdir)" + mkdir -p "${INSERTDIR}/etc" + touch "${INSERTDIR}/etc/foo" + + # Add layer to the image. + umoci insert --image "${IMAGE}:${TAG}" --compress=none "${INSERTDIR}/etc" /etc + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + umoci stat --image "${IMAGE}:${TAG}" --json + [ "$status" -eq 0 ] + stat_json="$output" + + # Make sure that the last layer had the expected compression based on the + # mediatype. + expected_mediatype="${OCI_MEDIATYPE_LAYER}" + layer_mediatype="$(jq -SMr '.history[-1].layer.mediaType' <<<"$stat_json")" + [[ "$layer_mediatype" == "$expected_mediatype" ]] + + # Make sure that the actual blob seems to be a gzip + layer_hash="$(jq -SMr '.history[-1].layer.digest' <<<"$stat_json" | tr : /)" + sane_run file -i "$IMAGE/blobs/$layer_hash" + [ "$status" -eq 0 ] + [[ "$output" == *"application/x-tar"* ]] # x-tar means no compression +} + +@test "umoci insert --compress=auto" { + # Some things to insert. + INSERTDIR="$(setup_tmpdir)" + mkdir -p "${INSERTDIR}/etc" + touch "${INSERTDIR}/etc/foo" + + # Add zstd layer to the image. + umoci insert --image "${IMAGE}:${TAG}" --compress=zstd "${INSERTDIR}/etc" /etc + [ "$status" -eq 0 ] + #image-verify "${IMAGE}" # image-tools cannot handle zstd + + umoci stat --image "${IMAGE}:${TAG}" --json + [ "$status" -eq 0 ] + stat_json="$output" + + # Make sure that the last layer had the expected compression based on the + # mediatype. + expected_mediatype="${OCI_MEDIATYPE_LAYER}+zstd" + layer_mediatype="$(jq -SMr '.history[-1].layer.mediaType' <<<"$stat_json")" + [[ "$layer_mediatype" == "$expected_mediatype" ]] + + # Make sure that the actual blob seems to be a gzip + layer_hash="$(jq -SMr '.history[-1].layer.digest' <<<"$stat_json" | tr : /)" + sane_run file -i "$IMAGE/blobs/$layer_hash" + [ "$status" -eq 0 ] + [[ "$output" == *"application/x-zstd"* ]] + + # Add another zstd layer to the image, by making use of the auto selection. + umoci insert --image "${IMAGE}:${TAG}" --compress=auto "${INSERTDIR}/etc" /etc + [ "$status" -eq 0 ] + #image-verify "${IMAGE}" # image-tools cannot handle zstd + + umoci stat --image "${IMAGE}:${TAG}" --json + [ "$status" -eq 0 ] + stat_json="$output" + + # Make sure that the last layer had the expected compression based on the + # mediatype. + expected_mediatype="${OCI_MEDIATYPE_LAYER}+zstd" + layer_mediatype="$(jq -SMr '.history[-1].layer.mediaType' <<<"$stat_json")" + [[ "$layer_mediatype" == "$expected_mediatype" ]] + + # Make sure that the actual blob seems to be a gzip + layer_hash="$(jq -SMr '.history[-1].layer.digest' <<<"$stat_json" | tr : /)" + sane_run file -i "$IMAGE/blobs/$layer_hash" + [ "$status" -eq 0 ] + [[ "$output" == *"application/x-zstd"* ]] + + # Add yet another zstd layer to the image, to show that --compress=auto is + # the default. + umoci insert --image "${IMAGE}:${TAG}" "${INSERTDIR}/etc" /etc + [ "$status" -eq 0 ] + #image-verify "${IMAGE}" # image-tools cannot handle zstd + + umoci stat --image "${IMAGE}:${TAG}" --json + [ "$status" -eq 0 ] + stat_json="$output" + + # Make sure that the last layer had the expected compression based on the + # mediatype. + expected_mediatype="${OCI_MEDIATYPE_LAYER}+zstd" + layer_mediatype="$(jq -SMr '.history[-1].layer.mediaType' <<<"$stat_json")" + [[ "$layer_mediatype" == "$expected_mediatype" ]] + + # Make sure that the actual blob seems to be a gzip + layer_hash="$(jq -SMr '.history[-1].layer.digest' <<<"$stat_json" | tr : /)" + sane_run file -i "$IMAGE/blobs/$layer_hash" + [ "$status" -eq 0 ] + [[ "$output" == *"application/x-zstd"* ]] +} diff --git a/test/raw-add-layer.bats b/test/raw-add-layer.bats index 558f666db..55706cbc2 100644 --- a/test/raw-add-layer.bats +++ b/test/raw-add-layer.bats @@ -89,6 +89,171 @@ function teardown() { image-verify "${IMAGE}" } +OCI_MEDIATYPE_LAYER="application/vnd.oci.image.layer.v1.tar" + +@test "umoci raw add-layer --compress=gzip" { + LAYER="$(setup_tmpdir)" + echo "layer" > "$LAYER/file" + sane_run tar cvfC "$UMOCI_TMPDIR/layer.tar" "$LAYER" . + + # Add layer to the image. + umoci new --image "${IMAGE}:${TAG}" + [ "$status" -eq 0 ] + #image-verify "${IMAGE}" + umoci raw add-layer --image "${IMAGE}:${TAG}" --compress=gzip "$UMOCI_TMPDIR/layer.tar" + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + umoci stat --image "${IMAGE}:${TAG}" --json + [ "$status" -eq 0 ] + stat_json="$output" + + # Make sure that the last layer had the expected compression based on the + # mediatype. + expected_mediatype="${OCI_MEDIATYPE_LAYER}+gzip" + layer_mediatype="$(jq -SMr '.history[-1].layer.mediaType' <<<"$stat_json")" + [[ "$layer_mediatype" == "$expected_mediatype" ]] + + # Make sure that the actual blob seems to be a gzip + layer_hash="$(jq -SMr '.history[-1].layer.digest' <<<"$stat_json" | tr : /)" + sane_run file -i "$IMAGE/blobs/$layer_hash" + [ "$status" -eq 0 ] + [[ "$output" == *"application/x-gzip"* ]] +} + +@test "umoci raw add-layer --compress=zstd" { + LAYER="$(setup_tmpdir)" + echo "layer" > "$LAYER/file" + sane_run tar cvfC "$UMOCI_TMPDIR/layer.tar" "$LAYER" . + + # Add layer to the image. + umoci new --image "${IMAGE}:${TAG}" + [ "$status" -eq 0 ] + #image-verify "${IMAGE}" + umoci raw add-layer --image "${IMAGE}:${TAG}" --compress=zstd "$UMOCI_TMPDIR/layer.tar" + [ "$status" -eq 0 ] + #image-verify "${IMAGE}" # image-tools cannot handle zstd + + umoci stat --image "${IMAGE}:${TAG}" --json + [ "$status" -eq 0 ] + stat_json="$output" + + # Make sure that the last layer had the expected compression based on the + # mediatype. + expected_mediatype="${OCI_MEDIATYPE_LAYER}+zstd" + layer_mediatype="$(jq -SMr '.history[-1].layer.mediaType' <<<"$stat_json")" + [[ "$layer_mediatype" == "$expected_mediatype" ]] + + # Make sure that the actual blob seems to be a gzip + layer_hash="$(jq -SMr '.history[-1].layer.digest' <<<"$stat_json" | tr : /)" + sane_run file -i "$IMAGE/blobs/$layer_hash" + [ "$status" -eq 0 ] + [[ "$output" == *"application/x-zstd"* ]] +} + +@test "umoci raw add-layer --compress=none" { + LAYER="$(setup_tmpdir)" + echo "layer" > "$LAYER/file" + sane_run tar cvfC "$UMOCI_TMPDIR/layer.tar" "$LAYER" . + + # Add layer to the image. + umoci new --image "${IMAGE}:${TAG}" + [ "$status" -eq 0 ] + #image-verify "${IMAGE}" + umoci raw add-layer --image "${IMAGE}:${TAG}" --compress=none "$UMOCI_TMPDIR/layer.tar" + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + umoci stat --image "${IMAGE}:${TAG}" --json + [ "$status" -eq 0 ] + stat_json="$output" + + # Make sure that the last layer had the expected compression based on the + # mediatype. + expected_mediatype="${OCI_MEDIATYPE_LAYER}" + layer_mediatype="$(jq -SMr '.history[-1].layer.mediaType' <<<"$stat_json")" + [[ "$layer_mediatype" == "$expected_mediatype" ]] + + # Make sure that the actual blob seems to be a gzip + layer_hash="$(jq -SMr '.history[-1].layer.digest' <<<"$stat_json" | tr : /)" + sane_run file -i "$IMAGE/blobs/$layer_hash" + [ "$status" -eq 0 ] + [[ "$output" == *"application/x-tar"* ]] # x-tar means no compression +} + +@test "umoci raw add-layer --compress=auto" { + LAYER="$(setup_tmpdir)" + echo "layer" > "$LAYER/file" + sane_run tar cvfC "$UMOCI_TMPDIR/layer.tar" "$LAYER" . + + # Add zstd layer to the image. + umoci new --image "${IMAGE}:${TAG}" + [ "$status" -eq 0 ] + #image-verify "${IMAGE}" + umoci raw add-layer --image "${IMAGE}:${TAG}" --compress=zstd "$UMOCI_TMPDIR/layer.tar" + [ "$status" -eq 0 ] + #image-verify "${IMAGE}" # image-tools cannot handle zstd + + umoci stat --image "${IMAGE}:${TAG}" --json + [ "$status" -eq 0 ] + stat_json="$output" + + # Make sure that the last layer had the expected compression based on the + # mediatype. + expected_mediatype="${OCI_MEDIATYPE_LAYER}+zstd" + layer_mediatype="$(jq -SMr '.history[-1].layer.mediaType' <<<"$stat_json")" + [[ "$layer_mediatype" == "$expected_mediatype" ]] + + # Make sure that the actual blob seems to be a gzip + layer_hash="$(jq -SMr '.history[-1].layer.digest' <<<"$stat_json" | tr : /)" + sane_run file -i "$IMAGE/blobs/$layer_hash" + [ "$status" -eq 0 ] + [[ "$output" == *"application/x-zstd"* ]] + + # Add another zstd layer to the image, by making use of the auto selection. + umoci raw add-layer --image "${IMAGE}:${TAG}" --compress=auto "$UMOCI_TMPDIR/layer.tar" + [ "$status" -eq 0 ] + #image-verify "${IMAGE}" # image-tools cannot handle zstd + + umoci stat --image "${IMAGE}:${TAG}" --json + [ "$status" -eq 0 ] + stat_json="$output" + + # Make sure that the last layer had the expected compression based on the + # mediatype. + expected_mediatype="${OCI_MEDIATYPE_LAYER}+zstd" + layer_mediatype="$(jq -SMr '.history[-1].layer.mediaType' <<<"$stat_json")" + [[ "$layer_mediatype" == "$expected_mediatype" ]] + + # Make sure that the actual blob seems to be a gzip + layer_hash="$(jq -SMr '.history[-1].layer.digest' <<<"$stat_json" | tr : /)" + sane_run file -i "$IMAGE/blobs/$layer_hash" + [ "$status" -eq 0 ] + [[ "$output" == *"application/x-zstd"* ]] + + # Add yet another zstd layer to the image, to show that --compress=auto is + # the default. + umoci raw add-layer --image "${IMAGE}:${TAG}" "$UMOCI_TMPDIR/layer.tar" + [ "$status" -eq 0 ] + #image-verify "${IMAGE}" # image-tools cannot handle zstd + + umoci stat --image "${IMAGE}:${TAG}" --json + [ "$status" -eq 0 ] + stat_json="$output" + + # Make sure that the last layer had the expected compression based on the + # mediatype. + expected_mediatype="${OCI_MEDIATYPE_LAYER}+zstd" + layer_mediatype="$(jq -SMr '.history[-1].layer.mediaType' <<<"$stat_json")" + [[ "$layer_mediatype" == "$expected_mediatype" ]] + + # Make sure that the actual blob seems to be a gzip + layer_hash="$(jq -SMr '.history[-1].layer.digest' <<<"$stat_json" | tr : /)" + sane_run file -i "$IMAGE/blobs/$layer_hash" + [ "$status" -eq 0 ] + [[ "$output" == *"application/x-zstd"* ]] +} + @test "umoci raw add-layer [invalid arguments]" { LAYERFILE="$UMOCI_TMPDIR/file" touch "$LAYERFILE"{,-extra} diff --git a/test/repack.bats b/test/repack.bats index 6e4eb1e7a..0bf716a0e 100644 --- a/test/repack.bats +++ b/test/repack.bats @@ -876,3 +876,176 @@ function teardown() { layers1=$(cat "${IMAGE}/oci/blobs/sha256/$manifest1" | jq -r .layers) [ "$layers0" == "$layers1" ] } + +OCI_MEDIATYPE_LAYER="application/vnd.oci.image.layer.v1.tar" + +@test "umoci repack --compress=gzip" { + # Unpack the image. + new_bundle_rootfs + umoci unpack --image "${IMAGE}:${TAG}" "$BUNDLE" + [ "$status" -eq 0 ] + bundle-verify "$BUNDLE" + + # Make sure we make a new tar layer. + touch "$ROOTFS/new-file" + # Add layer to the image. + umoci repack --image "${IMAGE}:${TAG}" --compress=gzip "$BUNDLE" + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + umoci stat --image "${IMAGE}:${TAG}" --json + [ "$status" -eq 0 ] + stat_json="$output" + + # Make sure that the last layer had the expected compression based on the + # mediatype. + expected_mediatype="${OCI_MEDIATYPE_LAYER}+gzip" + layer_mediatype="$(jq -SMr '.history[-1].layer.mediaType' <<<"$stat_json")" + [[ "$layer_mediatype" == "$expected_mediatype" ]] + + # Make sure that the actual blob seems to be a gzip + layer_hash="$(jq -SMr '.history[-1].layer.digest' <<<"$stat_json" | tr : /)" + sane_run file -i "$IMAGE/blobs/$layer_hash" + [ "$status" -eq 0 ] + [[ "$output" == *"application/x-gzip"* ]] +} + +@test "umoci repack --compress=zstd" { + # Unpack the image. + new_bundle_rootfs + umoci unpack --image "${IMAGE}:${TAG}" "$BUNDLE" + [ "$status" -eq 0 ] + bundle-verify "$BUNDLE" + + # Make sure we make a new tar layer. + touch "$ROOTFS/new-file" + # Add layer to the image. + umoci repack --image "${IMAGE}:${TAG}" --compress=zstd "$BUNDLE" + [ "$status" -eq 0 ] + #image-verify "${IMAGE}" # image-tools cannot handle zstd + + umoci stat --image "${IMAGE}:${TAG}" --json + [ "$status" -eq 0 ] + stat_json="$output" + + # Make sure that the last layer had the expected compression based on the + # mediatype. + expected_mediatype="${OCI_MEDIATYPE_LAYER}+zstd" + layer_mediatype="$(jq -SMr '.history[-1].layer.mediaType' <<<"$stat_json")" + [[ "$layer_mediatype" == "$expected_mediatype" ]] + + # Make sure that the actual blob seems to be a gzip + layer_hash="$(jq -SMr '.history[-1].layer.digest' <<<"$stat_json" | tr : /)" + sane_run file -i "$IMAGE/blobs/$layer_hash" + [ "$status" -eq 0 ] + [[ "$output" == *"application/x-zstd"* ]] +} + +@test "umoci repack --compress=none" { + # Unpack the image. + new_bundle_rootfs + umoci unpack --image "${IMAGE}:${TAG}" "$BUNDLE" + [ "$status" -eq 0 ] + bundle-verify "$BUNDLE" + + # Make sure we make a new tar layer. + touch "$ROOTFS/new-file" + # Add layer to the image. + umoci repack --image "${IMAGE}:${TAG}" --compress=none "$BUNDLE" + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + umoci stat --image "${IMAGE}:${TAG}" --json + [ "$status" -eq 0 ] + stat_json="$output" + + # Make sure that the last layer had the expected compression based on the + # mediatype. + expected_mediatype="${OCI_MEDIATYPE_LAYER}" + layer_mediatype="$(jq -SMr '.history[-1].layer.mediaType' <<<"$stat_json")" + [[ "$layer_mediatype" == "$expected_mediatype" ]] + + # Make sure that the actual blob seems to be a gzip + layer_hash="$(jq -SMr '.history[-1].layer.digest' <<<"$stat_json" | tr : /)" + sane_run file -i "$IMAGE/blobs/$layer_hash" + [ "$status" -eq 0 ] + [[ "$output" == *"application/x-tar"* ]] # x-tar means no compression +} + +@test "umoci repack --refresh-bundle --compress=auto" { + # Unpack the image. + new_bundle_rootfs + umoci unpack --image "${IMAGE}:${TAG}" "$BUNDLE" + [ "$status" -eq 0 ] + bundle-verify "$BUNDLE" + + # Make sure we make a new tar layer. + touch "$ROOTFS/new-file1" + # Add zstd layer to the image. + umoci repack --image "${IMAGE}:${TAG}" --refresh-bundle --compress=zstd "$BUNDLE" + [ "$status" -eq 0 ] + #image-verify "${IMAGE}" # image-tools cannot handle zstd + + umoci stat --image "${IMAGE}:${TAG}" --json + [ "$status" -eq 0 ] + stat_json="$output" + + # Make sure that the last layer had the expected compression based on the + # mediatype. + expected_mediatype="${OCI_MEDIATYPE_LAYER}+zstd" + layer_mediatype="$(jq -SMr '.history[-1].layer.mediaType' <<<"$stat_json")" + [[ "$layer_mediatype" == "$expected_mediatype" ]] + + # Make sure that the actual blob seems to be a gzip + layer_hash="$(jq -SMr '.history[-1].layer.digest' <<<"$stat_json" | tr : /)" + sane_run file -i "$IMAGE/blobs/$layer_hash" + [ "$status" -eq 0 ] + [[ "$output" == *"application/x-zstd"* ]] + + # Make sure we make a new tar layer. + touch "$ROOTFS/new-file2" + # Add another zstd layer to the image, by making use of the auto selection. + umoci repack --image "${IMAGE}:${TAG}" --refresh-bundle --compress=auto "$BUNDLE" + [ "$status" -eq 0 ] + #image-verify "${IMAGE}" # image-tools cannot handle zstd + + umoci stat --image "${IMAGE}:${TAG}" --json + [ "$status" -eq 0 ] + stat_json="$output" + + # Make sure that the last layer had the expected compression based on the + # mediatype. + expected_mediatype="${OCI_MEDIATYPE_LAYER}+zstd" + layer_mediatype="$(jq -SMr '.history[-1].layer.mediaType' <<<"$stat_json")" + [[ "$layer_mediatype" == "$expected_mediatype" ]] + + # Make sure that the actual blob seems to be a gzip + layer_hash="$(jq -SMr '.history[-1].layer.digest' <<<"$stat_json" | tr : /)" + sane_run file -i "$IMAGE/blobs/$layer_hash" + [ "$status" -eq 0 ] + [[ "$output" == *"application/x-zstd"* ]] + + # Make sure we make a new tar layer. + touch "$ROOTFS/new-file3" + # Add yet another zstd layer to the image, to show that --compress=auto is + # the default. + umoci repack --image "${IMAGE}:${TAG}" --refresh-bundle "$BUNDLE" + [ "$status" -eq 0 ] + #image-verify "${IMAGE}" # image-tools cannot handle zstd + + umoci stat --image "${IMAGE}:${TAG}" --json + [ "$status" -eq 0 ] + stat_json="$output" + + # Make sure that the last layer had the expected compression based on the + # mediatype. + expected_mediatype="${OCI_MEDIATYPE_LAYER}+zstd" + layer_mediatype="$(jq -SMr '.history[-1].layer.mediaType' <<<"$stat_json")" + [[ "$layer_mediatype" == "$expected_mediatype" ]] + + # Make sure that the actual blob seems to be a gzip + layer_hash="$(jq -SMr '.history[-1].layer.digest' <<<"$stat_json" | tr : /)" + sane_run file -i "$IMAGE/blobs/$layer_hash" + [ "$status" -eq 0 ] + [[ "$output" == *"application/x-zstd"* ]] +} diff --git a/test/unpack.bats b/test/unpack.bats index 02a225251..311cb24d8 100644 --- a/test/unpack.bats +++ b/test/unpack.bats @@ -354,3 +354,63 @@ function teardown() { [ "$(readlink "$ROOTFS/loop3")" = "link2/loop4" ] [ "$(readlink "$ROOTFS/dir/loop4")" = "../loop1" ] } + +@test "umoci unpack [mixed compression]" { + # Unpack the image. + new_bundle_rootfs + umoci unpack --image "${IMAGE}:${TAG}" "$BUNDLE" + [ "$status" -eq 0 ] + bundle-verify "$BUNDLE" + + # Create a few layers with different compression algorithms. + + # zstd layer + touch "$ROOTFS/zstd1" + umoci repack --image "${IMAGE}:${TAG}" --refresh-bundle --compress=zstd "$BUNDLE" + [ "$status" -eq 0 ] + #image-verify "${IMAGE}" # image-tools cannot handle zstd + + # gzip layer + touch "$ROOTFS/gzip1" + umoci repack --image "${IMAGE}:${TAG}" --refresh-bundle --compress=gzip "$BUNDLE" + [ "$status" -eq 0 ] + #image-verify "${IMAGE}" # image-tools cannot handle zstd + + # plain layer + touch "$ROOTFS/plain1" + umoci repack --image "${IMAGE}:${TAG}" --refresh-bundle --compress=none "$BUNDLE" + [ "$status" -eq 0 ] + #image-verify "${IMAGE}" # image-tools cannot handle zstd + + # zstd layer + touch "$ROOTFS/zstd2" + umoci repack --image "${IMAGE}:${TAG}" --refresh-bundle --compress=zstd "$BUNDLE" + [ "$status" -eq 0 ] + #image-verify "${IMAGE}" # image-tools cannot handle zstd + + # plain layer + touch "$ROOTFS/plain2" + umoci repack --image "${IMAGE}:${TAG}" --refresh-bundle --compress=none "$BUNDLE" + [ "$status" -eq 0 ] + #image-verify "${IMAGE}" # image-tools cannot handle zstd + + # zstd layer (auto) + touch "$ROOTFS/zstd3" + umoci repack --image "${IMAGE}:${TAG}" --refresh-bundle "$BUNDLE" + [ "$status" -eq 0 ] + #image-verify "${IMAGE}" # image-tools cannot handle zstd + + # Re-extract the latest image and make sure all of the files were correctly + # extracted. + new_bundle_rootfs + umoci unpack --image "${IMAGE}:${TAG}" "$BUNDLE" + [ "$status" -eq 0 ] + bundle-verify "$BUNDLE" + + [ -f "$ROOTFS/zstd1" ] + [ -f "$ROOTFS/gzip1" ] + [ -f "$ROOTFS/plain1" ] + [ -f "$ROOTFS/zstd2" ] + [ -f "$ROOTFS/plain2" ] + [ -f "$ROOTFS/zstd3" ] +} diff --git a/vendor/github.com/stretchr/testify/require/doc.go b/vendor/github.com/stretchr/testify/require/doc.go new file mode 100644 index 000000000..968434724 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/doc.go @@ -0,0 +1,29 @@ +// 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 diff --git a/vendor/github.com/stretchr/testify/require/forward_requirements.go b/vendor/github.com/stretchr/testify/require/forward_requirements.go new file mode 100644 index 000000000..1dcb2338c --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/forward_requirements.go @@ -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" diff --git a/vendor/github.com/stretchr/testify/require/require.go b/vendor/github.com/stretchr/testify/require/require.go new file mode 100644 index 000000000..506a82f80 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/require.go @@ -0,0 +1,2060 @@ +// Code generated with github.com/stretchr/testify/_codegen; DO NOT EDIT. + +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 h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Condition(t, comp, msgAndArgs...) { + return + } + t.FailNow() +} + +// Conditionf uses a Comparison to assert a complex condition. +func Conditionf(t TestingT, comp assert.Comparison, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Conditionf(t, comp, msg, args...) { + return + } + 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") +// assert.Contains(t, ["Hello", "World"], "World") +// assert.Contains(t, {"Hello": "World"}, "Hello") +func Contains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Contains(t, s, contains, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Containsf(t, s, contains, msg, args...) { + return + } + t.FailNow() +} + +// DirExists 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 DirExists(t TestingT, path string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.DirExists(t, path, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.DirExistsf(t, path, msg, args...) { + return + } + t.FailNow() +} + +// ElementsMatch 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.ElementsMatch(t, [1, 3, 2, 3], [1, 3, 3, 2]) +func ElementsMatch(t TestingT, listA interface{}, listB interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ElementsMatch(t, listA, listB, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ElementsMatchf(t, listA, listB, msg, args...) { + return + } + 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) +func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Empty(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// assert.Emptyf(t, obj, "error message %s", "formatted") +func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Emptyf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// Equal asserts that two objects are equal. +// +// assert.Equal(t, 123, 123) +// +// 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 Equal(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Equal(t, expected, actual, msgAndArgs...) { + return + } + 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() +// assert.EqualError(t, err, expectedErrorString) +func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualError(t, theError, errString, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualErrorf(t, theError, errString, msg, args...) { + return + } + t.FailNow() +} + +// EqualExportedValues 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.EqualExportedValues(t, S{1, 2}, S{1, 3}) => true +// assert.EqualExportedValues(t, S{1, 2}, S{2, 3}) => false +func EqualExportedValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualExportedValues(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualExportedValuesf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// EqualValues asserts that two objects are equal or convertible to the same types +// and equal. +// +// assert.EqualValues(t, uint32(123), int32(123)) +func EqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualValues(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// EqualValuesf asserts that two objects are equal or convertible to the same types +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualValuesf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Equalf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Error asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if assert.Error(t, err) { +// assert.Equal(t, expectedError, err) +// } +func Error(t TestingT, err error, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Error(t, err, msgAndArgs...) { + return + } + t.FailNow() +} + +// ErrorAs 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 ErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ErrorAs(t, err, target, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ErrorAsf(t, err, target, msg, args...) { + return + } + t.FailNow() +} + +// ErrorContains asserts that a function returned an error (i.e. not `nil`) +// and that the error contains the specified substring. +// +// actualObj, err := SomeFunction() +// assert.ErrorContains(t, err, expectedErrorSubString) +func ErrorContains(t TestingT, theError error, contains string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ErrorContains(t, theError, contains, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ErrorContainsf(t, theError, contains, msg, args...) { + return + } + t.FailNow() +} + +// ErrorIs asserts that at least one of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func ErrorIs(t TestingT, err error, target error, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ErrorIs(t, err, target, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ErrorIsf(t, err, target, msg, args...) { + return + } + t.FailNow() +} + +// Errorf asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if assert.Errorf(t, err, "error message %s", "formatted") { +// assert.Equal(t, expectedErrorf, err) +// } +func Errorf(t TestingT, err error, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Errorf(t, err, msg, args...) { + return + } + t.FailNow() +} + +// Eventually asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// assert.Eventually(t, func() bool { return true; }, time.Second, 10*time.Millisecond) +func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Eventually(t, condition, waitFor, tick, msgAndArgs...) { + return + } + t.FailNow() +} + +// EventuallyWithT 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.EventuallyWithT(t, func(c *assert.CollectT) { +// // add assertions as needed; any assertion failure will fail the current tick +// assert.True(c, externalValue, "expected 'externalValue' to be true") +// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +func EventuallyWithT(t TestingT, condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EventuallyWithT(t, condition, waitFor, tick, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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") +// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +func EventuallyWithTf(t TestingT, condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EventuallyWithTf(t, condition, waitFor, tick, msg, args...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Eventuallyf(t, condition, waitFor, tick, msg, args...) { + return + } + t.FailNow() +} + +// Exactly asserts that two objects are equal in value and type. +// +// assert.Exactly(t, int32(123), int64(123)) +func Exactly(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Exactly(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Exactlyf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Fail reports a failure through +func Fail(t TestingT, failureMessage string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Fail(t, failureMessage, msgAndArgs...) { + return + } + t.FailNow() +} + +// FailNow fails test +func FailNow(t TestingT, failureMessage string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.FailNow(t, failureMessage, msgAndArgs...) { + return + } + t.FailNow() +} + +// FailNowf fails test +func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.FailNowf(t, failureMessage, msg, args...) { + return + } + t.FailNow() +} + +// Failf reports a failure through +func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Failf(t, failureMessage, msg, args...) { + return + } + t.FailNow() +} + +// False asserts that the specified value is false. +// +// assert.False(t, myBool) +func False(t TestingT, value bool, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.False(t, value, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Falsef(t, value, msg, args...) { + return + } + t.FailNow() +} + +// FileExists 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 FileExists(t TestingT, path string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.FileExists(t, path, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.FileExistsf(t, path, msg, args...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Greater(t, e1, e2, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.GreaterOrEqual(t, e1, e2, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.GreaterOrEqualf(t, e1, e2, msg, args...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Greaterf(t, e1, e2, msg, args...) { + return + } + t.FailNow() +} + +// 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 string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPBodyContains(t, handler, method, url, values, str, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPBodyContainsf(t, handler, method, url, values, str, msg, args...) { + return + } + t.FailNow() +} + +// 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 string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPBodyNotContains(t, handler, method, url, values, str, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPBodyNotContainsf(t, handler, method, url, values, str, msg, args...) { + return + } + 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, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPError(t, handler, method, url, values, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPErrorf(t, handler, method, url, values, msg, args...) { + return + } + 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, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPRedirect(t, handler, method, url, values, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPRedirectf(t, handler, method, url, values, msg, args...) { + return + } + t.FailNow() +} + +// 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 string, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPStatusCode(t, handler, method, url, values, statuscode, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPStatusCodef(t, handler, method, url, values, statuscode, msg, args...) { + return + } + 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, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPSuccess(t, handler, method, url, values, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPSuccessf(t, handler, method, url, values, msg, args...) { + return + } + t.FailNow() +} + +// Implements asserts that an object is implemented by the specified interface. +// +// assert.Implements(t, (*MyInterface)(nil), new(MyObject)) +func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Implements(t, interfaceObject, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Implementsf(t, interfaceObject, object, msg, args...) { + return + } + t.FailNow() +} + +// InDelta asserts that the two numerals are within delta of each other. +// +// assert.InDelta(t, math.Pi, 22/7.0, 0.01) +func InDelta(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDelta(t, expected, actual, delta, msgAndArgs...) { + return + } + t.FailNow() +} + +// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func InDeltaMapValues(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDeltaMapValues(t, expected, actual, delta, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDeltaMapValuesf(t, expected, actual, delta, msg, args...) { + return + } + 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 h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDeltaSlice(t, expected, actual, delta, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDeltaSlicef(t, expected, actual, delta, msg, args...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDeltaf(t, expected, actual, delta, msg, args...) { + return + } + t.FailNow() +} + +// InEpsilon asserts that expected and actual have a relative error less than epsilon +func InEpsilon(t TestingT, expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InEpsilon(t, expected, actual, epsilon, msgAndArgs...) { + return + } + t.FailNow() +} + +// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices. +func InEpsilonSlice(t TestingT, expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InEpsilonSlice(t, expected, actual, epsilon, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InEpsilonSlicef(t, expected, actual, epsilon, msg, args...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InEpsilonf(t, expected, actual, epsilon, msg, args...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsDecreasing(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsDecreasingf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsIncreasing(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsIncreasingf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsNonDecreasing(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsNonDecreasingf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsNonIncreasing(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsNonIncreasingf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// IsType asserts that the specified objects are of the same type. +func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsType(t, expectedType, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// IsTypef asserts that the specified objects are of the same type. +func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsTypef(t, expectedType, object, msg, args...) { + return + } + t.FailNow() +} + +// JSONEq asserts that two JSON strings are equivalent. +// +// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.JSONEq(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.JSONEqf(t, expected, actual, msg, args...) { + return + } + 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) +func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Len(t, object, length, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Lenf(t, object, length, msg, args...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Less(t, e1, e2, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.LessOrEqual(t, e1, e2, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.LessOrEqualf(t, e1, e2, msg, args...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Lessf(t, e1, e2, msg, args...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Negative(t, e, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Negativef(t, e, msg, args...) { + return + } + t.FailNow() +} + +// Never asserts that the given condition doesn't satisfy in waitFor time, +// periodically checking the target function each tick. +// +// assert.Never(t, func() bool { return false; }, time.Second, 10*time.Millisecond) +func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Never(t, condition, waitFor, tick, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Neverf(t, condition, waitFor, tick, msg, args...) { + return + } + t.FailNow() +} + +// Nil asserts that the specified object is nil. +// +// assert.Nil(t, err) +func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Nil(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Nilf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// NoDirExists checks whether a directory does not exist in the given path. +// It fails if the path points to an existing _directory_ only. +func NoDirExists(t TestingT, path string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoDirExists(t, path, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoDirExistsf(t, path, msg, args...) { + return + } + t.FailNow() +} + +// NoError asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if assert.NoError(t, err) { +// assert.Equal(t, expectedObj, actualObj) +// } +func NoError(t TestingT, err error, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoError(t, err, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoErrorf(t, err, msg, args...) { + return + } + t.FailNow() +} + +// NoFileExists checks whether a file does not exist in a given path. It fails +// if the path points to an existing _file_ only. +func NoFileExists(t TestingT, path string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoFileExists(t, path, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoFileExistsf(t, path, msg, args...) { + return + } + 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") +// assert.NotContains(t, ["Hello", "World"], "Earth") +// assert.NotContains(t, {"Hello": "World"}, "Earth") +func NotContains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotContains(t, s, contains, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotContainsf(t, s, contains, msg, args...) { + return + } + 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]) +// } +func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEmpty(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotEmptyf 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.NotEmptyf(t, obj, "error message %s", "formatted") { +// assert.Equal(t, "two", obj[1]) +// } +func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEmptyf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// NotEqual asserts that the specified values are NOT equal. +// +// assert.NotEqual(t, obj1, obj2) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func NotEqual(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEqual(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotEqualValues asserts that two objects are not equal even when converted to the same type +// +// assert.NotEqualValues(t, obj1, obj2) +func NotEqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEqualValues(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEqualValuesf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEqualf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// NotErrorIs asserts that at none of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func NotErrorIs(t TestingT, err error, target error, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotErrorIs(t, err, target, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotErrorIsf asserts that at 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotErrorIsf(t, err, target, msg, args...) { + return + } + t.FailNow() +} + +// NotImplements asserts that an object does not implement the specified interface. +// +// assert.NotImplements(t, (*MyInterface)(nil), new(MyObject)) +func NotImplements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotImplements(t, interfaceObject, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotImplementsf(t, interfaceObject, object, msg, args...) { + return + } + t.FailNow() +} + +// NotNil asserts that the specified object is not nil. +// +// assert.NotNil(t, err) +func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotNil(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotNilf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// assert.NotPanics(t, func(){ RemainCalm() }) +func NotPanics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotPanics(t, f, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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 assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotPanicsf(t, f, msg, args...) { + return + } + 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") +func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotRegexp(t, rx, str, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotRegexpf(t, rx, str, msg, args...) { + return + } + t.FailNow() +} + +// NotSame asserts that two pointers do not reference the same object. +// +// assert.NotSame(t, ptr1, ptr2) +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func NotSame(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotSame(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotSamef(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// NotSubset asserts that the specified list(array, slice...) or map does NOT +// contain all elements given in the specified subset list(array, slice...) or +// map. +// +// assert.NotSubset(t, [1, 3, 4], [1, 2]) +// assert.NotSubset(t, {"x": 1, "y": 2}, {"z": 3}) +func NotSubset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotSubset(t, list, subset, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotSubsetf asserts that the specified list(array, slice...) or map does NOT +// contain all elements given in the specified subset list(array, slice...) or +// map. +// +// 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") +func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotSubsetf(t, list, subset, msg, args...) { + return + } + t.FailNow() +} + +// NotZero asserts that i is not the zero value for its type. +func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotZero(t, i, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotZerof asserts that i is not the zero value for its type. +func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotZerof(t, i, msg, args...) { + return + } + t.FailNow() +} + +// Panics asserts that the code inside the specified PanicTestFunc panics. +// +// assert.Panics(t, func(){ GoCrazy() }) +func Panics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Panics(t, f, msgAndArgs...) { + return + } + t.FailNow() +} + +// PanicsWithError asserts that the code inside the specified PanicTestFunc +// panics, and that the recovered panic value is an error that satisfies the +// EqualError comparison. +// +// assert.PanicsWithError(t, "crazy error", func(){ GoCrazy() }) +func PanicsWithError(t TestingT, errString string, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.PanicsWithError(t, errString, f, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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 assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.PanicsWithErrorf(t, errString, f, msg, args...) { + return + } + t.FailNow() +} + +// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// assert.PanicsWithValue(t, "crazy error", func(){ GoCrazy() }) +func PanicsWithValue(t TestingT, expected interface{}, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.PanicsWithValue(t, expected, f, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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 assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.PanicsWithValuef(t, expected, f, msg, args...) { + return + } + t.FailNow() +} + +// Panicsf asserts that the code inside the specified PanicTestFunc panics. +// +// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted") +func Panicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Panicsf(t, f, msg, args...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Positive(t, e, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Positivef(t, e, msg, args...) { + return + } + 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") +func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Regexp(t, rx, str, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Regexpf(t, rx, str, msg, args...) { + return + } + t.FailNow() +} + +// Same asserts that two pointers reference the same object. +// +// assert.Same(t, ptr1, ptr2) +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func Same(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Same(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Samef(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Subset asserts that the specified list(array, slice...) or map contains all +// elements given in the specified subset list(array, slice...) or map. +// +// assert.Subset(t, [1, 2, 3], [1, 2]) +// assert.Subset(t, {"x": 1, "y": 2}, {"x": 1}) +func Subset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Subset(t, list, subset, msgAndArgs...) { + return + } + t.FailNow() +} + +// Subsetf asserts that the specified list(array, slice...) or map contains all +// elements given in the specified subset list(array, slice...) or map. +// +// 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") +func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Subsetf(t, list, subset, msg, args...) { + return + } + t.FailNow() +} + +// True asserts that the specified value is true. +// +// assert.True(t, myBool) +func True(t TestingT, value bool, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.True(t, value, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Truef(t, value, msg, args...) { + return + } + 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) +func WithinDuration(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.WithinDuration(t, expected, actual, delta, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.WithinDurationf(t, expected, actual, delta, msg, args...) { + return + } + t.FailNow() +} + +// WithinRange asserts that a time is within a time range (inclusive). +// +// assert.WithinRange(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second)) +func WithinRange(t TestingT, actual time.Time, start time.Time, end time.Time, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.WithinRange(t, actual, start, end, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.WithinRangef(t, actual, start, end, msg, args...) { + return + } + t.FailNow() +} + +// YAMLEq asserts that two YAML strings are equivalent. +func YAMLEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.YAMLEq(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// YAMLEqf asserts that two YAML strings are equivalent. +func YAMLEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.YAMLEqf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Zero asserts that i is the zero value for its type. +func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Zero(t, i, msgAndArgs...) { + return + } + t.FailNow() +} + +// Zerof asserts that i is the zero value for its type. +func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Zerof(t, i, msg, args...) { + return + } + t.FailNow() +} diff --git a/vendor/github.com/stretchr/testify/require/require.go.tmpl b/vendor/github.com/stretchr/testify/require/require.go.tmpl new file mode 100644 index 000000000..55e42ddeb --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/require.go.tmpl @@ -0,0 +1,6 @@ +{{.Comment}} +func {{.DocInfo.Name}}(t TestingT, {{.Params}}) { + if h, ok := t.(tHelper); ok { h.Helper() } + if assert.{{.DocInfo.Name}}(t, {{.ForwardedParams}}) { return } + t.FailNow() +} diff --git a/vendor/github.com/stretchr/testify/require/require_forward.go b/vendor/github.com/stretchr/testify/require/require_forward.go new file mode 100644 index 000000000..eee8310a5 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/require_forward.go @@ -0,0 +1,1622 @@ +// Code generated with github.com/stretchr/testify/_codegen; DO NOT EDIT. + +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{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Condition(a.t, comp, msgAndArgs...) +} + +// Conditionf uses a Comparison to assert a complex condition. +func (a *Assertions) Conditionf(comp assert.Comparison, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Conditionf(a.t, comp, msg, args...) +} + +// Contains asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// a.Contains("Hello World", "World") +// a.Contains(["Hello", "World"], "World") +// a.Contains({"Hello": "World"}, "Hello") +func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Contains(a.t, s, contains, msgAndArgs...) +} + +// Containsf asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// a.Containsf("Hello World", "World", "error message %s", "formatted") +// a.Containsf(["Hello", "World"], "World", "error message %s", "formatted") +// a.Containsf({"Hello": "World"}, "Hello", "error message %s", "formatted") +func (a *Assertions) Containsf(s interface{}, contains interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Containsf(a.t, s, contains, msg, args...) +} + +// DirExists 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 (a *Assertions) DirExists(path string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + DirExists(a.t, path, msgAndArgs...) +} + +// 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 (a *Assertions) DirExistsf(path string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + DirExistsf(a.t, path, msg, args...) +} + +// ElementsMatch 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. +// +// a.ElementsMatch([1, 3, 2, 3], [1, 3, 3, 2]) +func (a *Assertions) ElementsMatch(listA interface{}, listB interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ElementsMatch(a.t, listA, listB, msgAndArgs...) +} + +// 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. +// +// a.ElementsMatchf([1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") +func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ElementsMatchf(a.t, listA, listB, msg, args...) +} + +// 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) +func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Empty(a.t, object, msgAndArgs...) +} + +// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// a.Emptyf(obj, "error message %s", "formatted") +func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Emptyf(a.t, object, msg, args...) +} + +// Equal asserts that two objects are equal. +// +// a.Equal(123, 123) +// +// 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 (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + 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() +// a.EqualError(err, expectedErrorString) +func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualError(a.t, theError, errString, msgAndArgs...) +} + +// EqualErrorf asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// a.EqualErrorf(err, expectedErrorString, "error message %s", "formatted") +func (a *Assertions) EqualErrorf(theError error, errString string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualErrorf(a.t, theError, errString, msg, args...) +} + +// EqualExportedValues 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 +// } +// a.EqualExportedValues(S{1, 2}, S{1, 3}) => true +// a.EqualExportedValues(S{1, 2}, S{2, 3}) => false +func (a *Assertions) EqualExportedValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualExportedValues(a.t, expected, actual, msgAndArgs...) +} + +// 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 +// } +// a.EqualExportedValuesf(S{1, 2}, S{1, 3}, "error message %s", "formatted") => true +// a.EqualExportedValuesf(S{1, 2}, S{2, 3}, "error message %s", "formatted") => false +func (a *Assertions) EqualExportedValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualExportedValuesf(a.t, expected, actual, msg, args...) +} + +// EqualValues asserts that two objects are equal or convertible to the same types +// and equal. +// +// a.EqualValues(uint32(123), int32(123)) +func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualValues(a.t, expected, actual, msgAndArgs...) +} + +// EqualValuesf asserts that two objects are equal or convertible to the same types +// and equal. +// +// a.EqualValuesf(uint32(123), int32(123), "error message %s", "formatted") +func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualValuesf(a.t, expected, actual, msg, args...) +} + +// Equalf asserts that two objects are equal. +// +// a.Equalf(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 (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Equalf(a.t, expected, actual, msg, args...) +} + +// Error asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if a.Error(err) { +// assert.Equal(t, expectedError, err) +// } +func (a *Assertions) Error(err error, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Error(a.t, err, msgAndArgs...) +} + +// ErrorAs 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 (a *Assertions) ErrorAs(err error, target interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ErrorAs(a.t, err, target, msgAndArgs...) +} + +// 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 (a *Assertions) ErrorAsf(err error, target interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ErrorAsf(a.t, err, target, msg, args...) +} + +// ErrorContains asserts that a function returned an error (i.e. not `nil`) +// and that the error contains the specified substring. +// +// actualObj, err := SomeFunction() +// a.ErrorContains(err, expectedErrorSubString) +func (a *Assertions) ErrorContains(theError error, contains string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ErrorContains(a.t, theError, contains, msgAndArgs...) +} + +// ErrorContainsf asserts that a function returned an error (i.e. not `nil`) +// and that the error contains the specified substring. +// +// actualObj, err := SomeFunction() +// a.ErrorContainsf(err, expectedErrorSubString, "error message %s", "formatted") +func (a *Assertions) ErrorContainsf(theError error, contains string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ErrorContainsf(a.t, theError, contains, msg, args...) +} + +// ErrorIs asserts that at least one of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func (a *Assertions) ErrorIs(err error, target error, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ErrorIs(a.t, err, target, msgAndArgs...) +} + +// ErrorIsf asserts that at least one of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func (a *Assertions) ErrorIsf(err error, target error, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ErrorIsf(a.t, err, target, msg, args...) +} + +// Errorf asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if a.Errorf(err, "error message %s", "formatted") { +// assert.Equal(t, expectedErrorf, err) +// } +func (a *Assertions) Errorf(err error, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Errorf(a.t, err, msg, args...) +} + +// Eventually asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// a.Eventually(func() bool { return true; }, time.Second, 10*time.Millisecond) +func (a *Assertions) Eventually(condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Eventually(a.t, condition, waitFor, tick, msgAndArgs...) +} + +// EventuallyWithT 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 +// }() +// a.EventuallyWithT(func(c *assert.CollectT) { +// // add assertions as needed; any assertion failure will fail the current tick +// assert.True(c, externalValue, "expected 'externalValue' to be true") +// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +func (a *Assertions) EventuallyWithT(condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EventuallyWithT(a.t, condition, waitFor, tick, msgAndArgs...) +} + +// 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 +// }() +// a.EventuallyWithTf(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") +// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +func (a *Assertions) EventuallyWithTf(condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EventuallyWithTf(a.t, condition, waitFor, tick, msg, args...) +} + +// Eventuallyf asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// a.Eventuallyf(func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func (a *Assertions) Eventuallyf(condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Eventuallyf(a.t, condition, waitFor, tick, msg, args...) +} + +// Exactly asserts that two objects are equal in value and type. +// +// a.Exactly(int32(123), int64(123)) +func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Exactly(a.t, expected, actual, msgAndArgs...) +} + +// Exactlyf asserts that two objects are equal in value and type. +// +// a.Exactlyf(int32(123), int64(123), "error message %s", "formatted") +func (a *Assertions) Exactlyf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Exactlyf(a.t, expected, actual, msg, args...) +} + +// Fail reports a failure through +func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Fail(a.t, failureMessage, msgAndArgs...) +} + +// FailNow fails test +func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FailNow(a.t, failureMessage, msgAndArgs...) +} + +// FailNowf fails test +func (a *Assertions) FailNowf(failureMessage string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FailNowf(a.t, failureMessage, msg, args...) +} + +// Failf reports a failure through +func (a *Assertions) Failf(failureMessage string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Failf(a.t, failureMessage, msg, args...) +} + +// False asserts that the specified value is false. +// +// a.False(myBool) +func (a *Assertions) False(value bool, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + False(a.t, value, msgAndArgs...) +} + +// Falsef asserts that the specified value is false. +// +// a.Falsef(myBool, "error message %s", "formatted") +func (a *Assertions) Falsef(value bool, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Falsef(a.t, value, msg, args...) +} + +// FileExists 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 (a *Assertions) FileExists(path string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FileExists(a.t, path, msgAndArgs...) +} + +// 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 (a *Assertions) FileExistsf(path string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FileExistsf(a.t, path, msg, args...) +} + +// Greater asserts that the first element is greater than the second +// +// a.Greater(2, 1) +// a.Greater(float64(2), float64(1)) +// a.Greater("b", "a") +func (a *Assertions) Greater(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Greater(a.t, e1, e2, msgAndArgs...) +} + +// GreaterOrEqual asserts that the first element is greater than or equal to the second +// +// a.GreaterOrEqual(2, 1) +// a.GreaterOrEqual(2, 2) +// a.GreaterOrEqual("b", "a") +// a.GreaterOrEqual("b", "b") +func (a *Assertions) GreaterOrEqual(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + GreaterOrEqual(a.t, e1, e2, msgAndArgs...) +} + +// GreaterOrEqualf asserts that the first element is greater than or equal to the second +// +// a.GreaterOrEqualf(2, 1, "error message %s", "formatted") +// a.GreaterOrEqualf(2, 2, "error message %s", "formatted") +// a.GreaterOrEqualf("b", "a", "error message %s", "formatted") +// a.GreaterOrEqualf("b", "b", "error message %s", "formatted") +func (a *Assertions) GreaterOrEqualf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + GreaterOrEqualf(a.t, e1, e2, msg, args...) +} + +// Greaterf asserts that the first element is greater than the second +// +// a.Greaterf(2, 1, "error message %s", "formatted") +// a.Greaterf(float64(2), float64(1), "error message %s", "formatted") +// a.Greaterf("b", "a", "error message %s", "formatted") +func (a *Assertions) Greaterf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Greaterf(a.t, e1, e2, msg, args...) +} + +// HTTPBodyContains asserts that a specified handler returns a +// body that contains a string. +// +// a.HTTPBodyContains(myHandler, "GET", "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{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyContains(a.t, handler, method, url, values, str, msgAndArgs...) +} + +// HTTPBodyContainsf asserts that a specified handler returns a +// body that contains a string. +// +// a.HTTPBodyContainsf(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 (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyContainsf(a.t, handler, method, url, values, str, msg, args...) +} + +// HTTPBodyNotContains asserts that a specified handler returns a +// body that does not contain a string. +// +// a.HTTPBodyNotContains(myHandler, "GET", "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{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyNotContains(a.t, handler, method, url, values, str, msgAndArgs...) +} + +// HTTPBodyNotContainsf asserts that a specified handler returns a +// body that does not contain a string. +// +// a.HTTPBodyNotContainsf(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 (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyNotContainsf(a.t, handler, method, url, values, str, msg, args...) +} + +// 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, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPError(a.t, handler, method, url, values, msgAndArgs...) +} + +// HTTPErrorf asserts that a specified handler returns an error status code. +// +// a.HTTPErrorf(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPErrorf(a.t, handler, method, url, values, msg, args...) +} + +// 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, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPRedirect(a.t, handler, method, url, values, msgAndArgs...) +} + +// HTTPRedirectf asserts that a specified handler returns a redirect status code. +// +// a.HTTPRedirectf(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPRedirectf(a.t, handler, method, url, values, msg, args...) +} + +// HTTPStatusCode asserts that a specified handler returns a specified status code. +// +// a.HTTPStatusCode(myHandler, "GET", "/notImplemented", nil, 501) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPStatusCode(handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPStatusCode(a.t, handler, method, url, values, statuscode, msgAndArgs...) +} + +// HTTPStatusCodef asserts that a specified handler returns a specified status code. +// +// a.HTTPStatusCodef(myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPStatusCodef(handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPStatusCodef(a.t, handler, method, url, values, statuscode, msg, args...) +} + +// 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, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPSuccess(a.t, handler, method, url, values, msgAndArgs...) +} + +// HTTPSuccessf asserts that a specified handler returns a success status code. +// +// a.HTTPSuccessf(myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPSuccessf(a.t, handler, method, url, values, msg, args...) +} + +// Implements asserts that an object is implemented by the specified interface. +// +// a.Implements((*MyInterface)(nil), new(MyObject)) +func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Implements(a.t, interfaceObject, object, msgAndArgs...) +} + +// Implementsf asserts that an object is implemented by the specified interface. +// +// a.Implementsf((*MyInterface)(nil), new(MyObject), "error message %s", "formatted") +func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Implementsf(a.t, interfaceObject, object, msg, args...) +} + +// InDelta asserts that the two numerals are within delta of each other. +// +// a.InDelta(math.Pi, 22/7.0, 0.01) +func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDelta(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func (a *Assertions) InDeltaMapValues(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaMapValues(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func (a *Assertions) InDeltaMapValuesf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaMapValuesf(a.t, expected, actual, delta, msg, args...) +} + +// InDeltaSlice is the same as InDelta, except it compares two slices. +func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaSlicef is the same as InDelta, except it compares two slices. +func (a *Assertions) InDeltaSlicef(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaSlicef(a.t, expected, actual, delta, msg, args...) +} + +// InDeltaf asserts that the two numerals are within delta of each other. +// +// a.InDeltaf(math.Pi, 22/7.0, 0.01, "error message %s", "formatted") +func (a *Assertions) InDeltaf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaf(a.t, expected, actual, delta, msg, args...) +} + +// InEpsilon asserts that expected and actual have a relative error less than epsilon +func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...) +} + +// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices. +func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilonSlice(a.t, expected, actual, epsilon, msgAndArgs...) +} + +// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices. +func (a *Assertions) InEpsilonSlicef(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilonSlicef(a.t, expected, actual, epsilon, msg, args...) +} + +// InEpsilonf asserts that expected and actual have a relative error less than epsilon +func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilonf(a.t, expected, actual, epsilon, msg, args...) +} + +// IsDecreasing asserts that the collection is decreasing +// +// a.IsDecreasing([]int{2, 1, 0}) +// a.IsDecreasing([]float{2, 1}) +// a.IsDecreasing([]string{"b", "a"}) +func (a *Assertions) IsDecreasing(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsDecreasing(a.t, object, msgAndArgs...) +} + +// IsDecreasingf asserts that the collection is decreasing +// +// a.IsDecreasingf([]int{2, 1, 0}, "error message %s", "formatted") +// a.IsDecreasingf([]float{2, 1}, "error message %s", "formatted") +// a.IsDecreasingf([]string{"b", "a"}, "error message %s", "formatted") +func (a *Assertions) IsDecreasingf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsDecreasingf(a.t, object, msg, args...) +} + +// IsIncreasing asserts that the collection is increasing +// +// a.IsIncreasing([]int{1, 2, 3}) +// a.IsIncreasing([]float{1, 2}) +// a.IsIncreasing([]string{"a", "b"}) +func (a *Assertions) IsIncreasing(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsIncreasing(a.t, object, msgAndArgs...) +} + +// IsIncreasingf asserts that the collection is increasing +// +// a.IsIncreasingf([]int{1, 2, 3}, "error message %s", "formatted") +// a.IsIncreasingf([]float{1, 2}, "error message %s", "formatted") +// a.IsIncreasingf([]string{"a", "b"}, "error message %s", "formatted") +func (a *Assertions) IsIncreasingf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsIncreasingf(a.t, object, msg, args...) +} + +// IsNonDecreasing asserts that the collection is not decreasing +// +// a.IsNonDecreasing([]int{1, 1, 2}) +// a.IsNonDecreasing([]float{1, 2}) +// a.IsNonDecreasing([]string{"a", "b"}) +func (a *Assertions) IsNonDecreasing(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsNonDecreasing(a.t, object, msgAndArgs...) +} + +// IsNonDecreasingf asserts that the collection is not decreasing +// +// a.IsNonDecreasingf([]int{1, 1, 2}, "error message %s", "formatted") +// a.IsNonDecreasingf([]float{1, 2}, "error message %s", "formatted") +// a.IsNonDecreasingf([]string{"a", "b"}, "error message %s", "formatted") +func (a *Assertions) IsNonDecreasingf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsNonDecreasingf(a.t, object, msg, args...) +} + +// IsNonIncreasing asserts that the collection is not increasing +// +// a.IsNonIncreasing([]int{2, 1, 1}) +// a.IsNonIncreasing([]float{2, 1}) +// a.IsNonIncreasing([]string{"b", "a"}) +func (a *Assertions) IsNonIncreasing(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsNonIncreasing(a.t, object, msgAndArgs...) +} + +// IsNonIncreasingf asserts that the collection is not increasing +// +// a.IsNonIncreasingf([]int{2, 1, 1}, "error message %s", "formatted") +// a.IsNonIncreasingf([]float{2, 1}, "error message %s", "formatted") +// a.IsNonIncreasingf([]string{"b", "a"}, "error message %s", "formatted") +func (a *Assertions) IsNonIncreasingf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsNonIncreasingf(a.t, object, msg, args...) +} + +// IsType asserts that the specified objects are of the same type. +func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsType(a.t, expectedType, object, msgAndArgs...) +} + +// IsTypef asserts that the specified objects are of the same type. +func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsTypef(a.t, expectedType, object, msg, args...) +} + +// JSONEq asserts that two JSON strings are equivalent. +// +// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + JSONEq(a.t, expected, actual, msgAndArgs...) +} + +// JSONEqf asserts that two JSON strings are equivalent. +// +// a.JSONEqf(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") +func (a *Assertions) JSONEqf(expected string, actual string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + JSONEqf(a.t, expected, actual, msg, args...) +} + +// 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) +func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Len(a.t, object, length, msgAndArgs...) +} + +// Lenf asserts that the specified object has specific length. +// Lenf also fails if the object has a type that len() not accept. +// +// a.Lenf(mySlice, 3, "error message %s", "formatted") +func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Lenf(a.t, object, length, msg, args...) +} + +// Less asserts that the first element is less than the second +// +// a.Less(1, 2) +// a.Less(float64(1), float64(2)) +// a.Less("a", "b") +func (a *Assertions) Less(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Less(a.t, e1, e2, msgAndArgs...) +} + +// LessOrEqual asserts that the first element is less than or equal to the second +// +// a.LessOrEqual(1, 2) +// a.LessOrEqual(2, 2) +// a.LessOrEqual("a", "b") +// a.LessOrEqual("b", "b") +func (a *Assertions) LessOrEqual(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + LessOrEqual(a.t, e1, e2, msgAndArgs...) +} + +// LessOrEqualf asserts that the first element is less than or equal to the second +// +// a.LessOrEqualf(1, 2, "error message %s", "formatted") +// a.LessOrEqualf(2, 2, "error message %s", "formatted") +// a.LessOrEqualf("a", "b", "error message %s", "formatted") +// a.LessOrEqualf("b", "b", "error message %s", "formatted") +func (a *Assertions) LessOrEqualf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + LessOrEqualf(a.t, e1, e2, msg, args...) +} + +// Lessf asserts that the first element is less than the second +// +// a.Lessf(1, 2, "error message %s", "formatted") +// a.Lessf(float64(1), float64(2), "error message %s", "formatted") +// a.Lessf("a", "b", "error message %s", "formatted") +func (a *Assertions) Lessf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Lessf(a.t, e1, e2, msg, args...) +} + +// Negative asserts that the specified element is negative +// +// a.Negative(-1) +// a.Negative(-1.23) +func (a *Assertions) Negative(e interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Negative(a.t, e, msgAndArgs...) +} + +// Negativef asserts that the specified element is negative +// +// a.Negativef(-1, "error message %s", "formatted") +// a.Negativef(-1.23, "error message %s", "formatted") +func (a *Assertions) Negativef(e interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Negativef(a.t, e, msg, args...) +} + +// Never asserts that the given condition doesn't satisfy in waitFor time, +// periodically checking the target function each tick. +// +// a.Never(func() bool { return false; }, time.Second, 10*time.Millisecond) +func (a *Assertions) Never(condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Never(a.t, condition, waitFor, tick, msgAndArgs...) +} + +// Neverf asserts that the given condition doesn't satisfy in waitFor time, +// periodically checking the target function each tick. +// +// a.Neverf(func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func (a *Assertions) Neverf(condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Neverf(a.t, condition, waitFor, tick, msg, args...) +} + +// Nil asserts that the specified object is nil. +// +// a.Nil(err) +func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Nil(a.t, object, msgAndArgs...) +} + +// Nilf asserts that the specified object is nil. +// +// a.Nilf(err, "error message %s", "formatted") +func (a *Assertions) Nilf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Nilf(a.t, object, msg, args...) +} + +// NoDirExists checks whether a directory does not exist in the given path. +// It fails if the path points to an existing _directory_ only. +func (a *Assertions) NoDirExists(path string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoDirExists(a.t, path, msgAndArgs...) +} + +// NoDirExistsf checks whether a directory does not exist in the given path. +// It fails if the path points to an existing _directory_ only. +func (a *Assertions) NoDirExistsf(path string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoDirExistsf(a.t, path, msg, args...) +} + +// NoError asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if a.NoError(err) { +// assert.Equal(t, expectedObj, actualObj) +// } +func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoError(a.t, err, msgAndArgs...) +} + +// NoErrorf asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if a.NoErrorf(err, "error message %s", "formatted") { +// assert.Equal(t, expectedObj, actualObj) +// } +func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoErrorf(a.t, err, msg, args...) +} + +// NoFileExists checks whether a file does not exist in a given path. It fails +// if the path points to an existing _file_ only. +func (a *Assertions) NoFileExists(path string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoFileExists(a.t, path, msgAndArgs...) +} + +// NoFileExistsf checks whether a file does not exist in a given path. It fails +// if the path points to an existing _file_ only. +func (a *Assertions) NoFileExistsf(path string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoFileExistsf(a.t, path, msg, args...) +} + +// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// a.NotContains("Hello World", "Earth") +// a.NotContains(["Hello", "World"], "Earth") +// a.NotContains({"Hello": "World"}, "Earth") +func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotContains(a.t, s, contains, msgAndArgs...) +} + +// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// a.NotContainsf("Hello World", "Earth", "error message %s", "formatted") +// a.NotContainsf(["Hello", "World"], "Earth", "error message %s", "formatted") +// a.NotContainsf({"Hello": "World"}, "Earth", "error message %s", "formatted") +func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotContainsf(a.t, s, contains, msg, args...) +} + +// 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]) +// } +func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEmpty(a.t, object, msgAndArgs...) +} + +// NotEmptyf 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.NotEmptyf(obj, "error message %s", "formatted") { +// assert.Equal(t, "two", obj[1]) +// } +func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEmptyf(a.t, object, msg, args...) +} + +// NotEqual asserts that the specified values are NOT equal. +// +// a.NotEqual(obj1, obj2) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEqual(a.t, expected, actual, msgAndArgs...) +} + +// NotEqualValues asserts that two objects are not equal even when converted to the same type +// +// a.NotEqualValues(obj1, obj2) +func (a *Assertions) NotEqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEqualValues(a.t, expected, actual, msgAndArgs...) +} + +// NotEqualValuesf asserts that two objects are not equal even when converted to the same type +// +// a.NotEqualValuesf(obj1, obj2, "error message %s", "formatted") +func (a *Assertions) NotEqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEqualValuesf(a.t, expected, actual, msg, args...) +} + +// NotEqualf asserts that the specified values are NOT equal. +// +// a.NotEqualf(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 (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEqualf(a.t, expected, actual, msg, args...) +} + +// NotErrorIs asserts that at none of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func (a *Assertions) NotErrorIs(err error, target error, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotErrorIs(a.t, err, target, msgAndArgs...) +} + +// NotErrorIsf asserts that at none of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func (a *Assertions) NotErrorIsf(err error, target error, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotErrorIsf(a.t, err, target, msg, args...) +} + +// NotImplements asserts that an object does not implement the specified interface. +// +// a.NotImplements((*MyInterface)(nil), new(MyObject)) +func (a *Assertions) NotImplements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotImplements(a.t, interfaceObject, object, msgAndArgs...) +} + +// NotImplementsf asserts that an object does not implement the specified interface. +// +// a.NotImplementsf((*MyInterface)(nil), new(MyObject), "error message %s", "formatted") +func (a *Assertions) NotImplementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotImplementsf(a.t, interfaceObject, object, msg, args...) +} + +// NotNil asserts that the specified object is not nil. +// +// a.NotNil(err) +func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotNil(a.t, object, msgAndArgs...) +} + +// NotNilf asserts that the specified object is not nil. +// +// a.NotNilf(err, "error message %s", "formatted") +func (a *Assertions) NotNilf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotNilf(a.t, object, msg, args...) +} + +// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// a.NotPanics(func(){ RemainCalm() }) +func (a *Assertions) NotPanics(f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotPanics(a.t, f, msgAndArgs...) +} + +// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// a.NotPanicsf(func(){ RemainCalm() }, "error message %s", "formatted") +func (a *Assertions) NotPanicsf(f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotPanicsf(a.t, f, msg, args...) +} + +// 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") +func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotRegexp(a.t, rx, str, msgAndArgs...) +} + +// NotRegexpf asserts that a specified regexp does not match a string. +// +// a.NotRegexpf(regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted") +// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted") +func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotRegexpf(a.t, rx, str, msg, args...) +} + +// NotSame asserts that two pointers do not reference the same object. +// +// a.NotSame(ptr1, ptr2) +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func (a *Assertions) NotSame(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotSame(a.t, expected, actual, msgAndArgs...) +} + +// NotSamef asserts that two pointers do not reference the same object. +// +// a.NotSamef(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 (a *Assertions) NotSamef(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotSamef(a.t, expected, actual, msg, args...) +} + +// NotSubset asserts that the specified list(array, slice...) or map does NOT +// contain all elements given in the specified subset list(array, slice...) or +// map. +// +// a.NotSubset([1, 3, 4], [1, 2]) +// a.NotSubset({"x": 1, "y": 2}, {"z": 3}) +func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotSubset(a.t, list, subset, msgAndArgs...) +} + +// NotSubsetf asserts that the specified list(array, slice...) or map does NOT +// contain all elements given in the specified subset list(array, slice...) or +// map. +// +// a.NotSubsetf([1, 3, 4], [1, 2], "error message %s", "formatted") +// a.NotSubsetf({"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted") +func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotSubsetf(a.t, list, subset, msg, args...) +} + +// NotZero asserts that i is not the zero value for its type. +func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotZero(a.t, i, msgAndArgs...) +} + +// NotZerof asserts that i is not the zero value for its type. +func (a *Assertions) NotZerof(i interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotZerof(a.t, i, msg, args...) +} + +// Panics asserts that the code inside the specified PanicTestFunc panics. +// +// a.Panics(func(){ GoCrazy() }) +func (a *Assertions) Panics(f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Panics(a.t, f, msgAndArgs...) +} + +// PanicsWithError asserts that the code inside the specified PanicTestFunc +// panics, and that the recovered panic value is an error that satisfies the +// EqualError comparison. +// +// a.PanicsWithError("crazy error", func(){ GoCrazy() }) +func (a *Assertions) PanicsWithError(errString string, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + PanicsWithError(a.t, errString, f, msgAndArgs...) +} + +// PanicsWithErrorf asserts that the code inside the specified PanicTestFunc +// panics, and that the recovered panic value is an error that satisfies the +// EqualError comparison. +// +// a.PanicsWithErrorf("crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func (a *Assertions) PanicsWithErrorf(errString string, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + PanicsWithErrorf(a.t, errString, f, msg, args...) +} + +// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// a.PanicsWithValue("crazy error", func(){ GoCrazy() }) +func (a *Assertions) PanicsWithValue(expected interface{}, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + PanicsWithValue(a.t, expected, f, msgAndArgs...) +} + +// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// a.PanicsWithValuef("crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func (a *Assertions) PanicsWithValuef(expected interface{}, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + PanicsWithValuef(a.t, expected, f, msg, args...) +} + +// Panicsf asserts that the code inside the specified PanicTestFunc panics. +// +// a.Panicsf(func(){ GoCrazy() }, "error message %s", "formatted") +func (a *Assertions) Panicsf(f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Panicsf(a.t, f, msg, args...) +} + +// Positive asserts that the specified element is positive +// +// a.Positive(1) +// a.Positive(1.23) +func (a *Assertions) Positive(e interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Positive(a.t, e, msgAndArgs...) +} + +// Positivef asserts that the specified element is positive +// +// a.Positivef(1, "error message %s", "formatted") +// a.Positivef(1.23, "error message %s", "formatted") +func (a *Assertions) Positivef(e interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Positivef(a.t, e, msg, args...) +} + +// Regexp asserts that a specified regexp matches a string. +// +// a.Regexp(regexp.MustCompile("start"), "it's starting") +// a.Regexp("start...$", "it's not starting") +func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Regexp(a.t, rx, str, msgAndArgs...) +} + +// Regexpf asserts that a specified regexp matches a string. +// +// a.Regexpf(regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") +// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted") +func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Regexpf(a.t, rx, str, msg, args...) +} + +// Same asserts that two pointers reference the same object. +// +// a.Same(ptr1, ptr2) +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func (a *Assertions) Same(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Same(a.t, expected, actual, msgAndArgs...) +} + +// Samef asserts that two pointers reference the same object. +// +// a.Samef(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 (a *Assertions) Samef(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Samef(a.t, expected, actual, msg, args...) +} + +// Subset asserts that the specified list(array, slice...) or map contains all +// elements given in the specified subset list(array, slice...) or map. +// +// a.Subset([1, 2, 3], [1, 2]) +// a.Subset({"x": 1, "y": 2}, {"x": 1}) +func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Subset(a.t, list, subset, msgAndArgs...) +} + +// Subsetf asserts that the specified list(array, slice...) or map contains all +// elements given in the specified subset list(array, slice...) or map. +// +// a.Subsetf([1, 2, 3], [1, 2], "error message %s", "formatted") +// a.Subsetf({"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted") +func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Subsetf(a.t, list, subset, msg, args...) +} + +// True asserts that the specified value is true. +// +// a.True(myBool) +func (a *Assertions) True(value bool, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + True(a.t, value, msgAndArgs...) +} + +// Truef asserts that the specified value is true. +// +// a.Truef(myBool, "error message %s", "formatted") +func (a *Assertions) Truef(value bool, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Truef(a.t, value, msg, args...) +} + +// WithinDuration asserts that the two times are within duration delta of each other. +// +// a.WithinDuration(time.Now(), time.Now(), 10*time.Second) +func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + WithinDuration(a.t, expected, actual, delta, msgAndArgs...) +} + +// WithinDurationf asserts that the two times are within duration delta of each other. +// +// a.WithinDurationf(time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") +func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + WithinDurationf(a.t, expected, actual, delta, msg, args...) +} + +// WithinRange asserts that a time is within a time range (inclusive). +// +// a.WithinRange(time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second)) +func (a *Assertions) WithinRange(actual time.Time, start time.Time, end time.Time, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + WithinRange(a.t, actual, start, end, msgAndArgs...) +} + +// WithinRangef asserts that a time is within a time range (inclusive). +// +// a.WithinRangef(time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted") +func (a *Assertions) WithinRangef(actual time.Time, start time.Time, end time.Time, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + WithinRangef(a.t, actual, start, end, msg, args...) +} + +// YAMLEq asserts that two YAML strings are equivalent. +func (a *Assertions) YAMLEq(expected string, actual string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + YAMLEq(a.t, expected, actual, msgAndArgs...) +} + +// YAMLEqf asserts that two YAML strings are equivalent. +func (a *Assertions) YAMLEqf(expected string, actual string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + YAMLEqf(a.t, expected, actual, msg, args...) +} + +// Zero asserts that i is the zero value for its type. +func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Zero(a.t, i, msgAndArgs...) +} + +// Zerof asserts that i is the zero value for its type. +func (a *Assertions) Zerof(i interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Zerof(a.t, i, msg, args...) +} diff --git a/vendor/github.com/stretchr/testify/require/require_forward.go.tmpl b/vendor/github.com/stretchr/testify/require/require_forward.go.tmpl new file mode 100644 index 000000000..54124df1d --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/require_forward.go.tmpl @@ -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}}) +} diff --git a/vendor/github.com/stretchr/testify/require/requirements.go b/vendor/github.com/stretchr/testify/require/requirements.go new file mode 100644 index 000000000..91772dfeb --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/requirements.go @@ -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" diff --git a/vendor/modules.txt b/vendor/modules.txt index 6b567d060..5919dd719 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -78,6 +78,7 @@ github.com/sirupsen/logrus # github.com/stretchr/testify v1.9.0 ## explicit; go 1.17 github.com/stretchr/testify/assert +github.com/stretchr/testify/require # github.com/urfave/cli v1.22.12 ## explicit; go 1.11 github.com/urfave/cli