diff --git a/changelog/unreleased/add-virus-filter.md b/changelog/unreleased/add-virus-filter.md new file mode 100644 index 00000000000..5d6f6806db0 --- /dev/null +++ b/changelog/unreleased/add-virus-filter.md @@ -0,0 +1,5 @@ +Enhancement: Add virus filter to sessions command + +Allow filtering upload session by virus status (has-virus=true/false) + +https://github.com/owncloud/ocis/pull/9041 diff --git a/changelog/unreleased/assimilate-clean-into-sessions.md b/changelog/unreleased/assimilate-clean-into-sessions.md new file mode 100644 index 00000000000..4f8fd374598 --- /dev/null +++ b/changelog/unreleased/assimilate-clean-into-sessions.md @@ -0,0 +1,5 @@ +Enhancement: Assimilate `clean` into `sessions` command + +We deprecated `ocis storage-user uploads clean` and added the same logic to `ocis storage-users uploads session --clean` + +https://github.com/owncloud/ocis/pull/9828 diff --git a/go.mod b/go.mod index 0c6b4c52df8..29431212a49 100644 --- a/go.mod +++ b/go.mod @@ -81,6 +81,7 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.0 github.com/stretchr/testify v1.8.4 + github.com/test-go/testify v1.1.4 github.com/thejerf/suture/v4 v4.0.2 github.com/tidwall/gjson v1.17.1 github.com/tus/tusd v1.13.0 diff --git a/services/storage-users/README.md b/services/storage-users/README.md index bee130106f7..fd588ead6c4 100644 --- a/services/storage-users/README.md +++ b/services/storage-users/README.md @@ -51,58 +51,90 @@ ocis storage-users uploads ```plaintext COMMANDS: sessions Print a list of upload sessions - clean Clean up leftovers from expired uploads + clean Clean up leftovers from expired uploads (deprecated) list Print a list of all incomplete uploads (deprecated) ``` +#### Sessions command + +The `sessions` command is the entry point for listing, restarting and cleaning unfinished uploads. + +```bash +ocis storage-users uploads sessions +``` + +``` +NAME: + ocis storage-users uploads sessions - Print a list of upload sessions + +USAGE: + ocis storage-users uploads sessions [command options] + +OPTIONS: + --id value filter sessions by upload session id (default: unset) + --processing filter sessions by processing status (default: unset) + --expired filter sessions by expired status (default: unset) + --has-virus filter sessions by virus scan result (default: unset) + --json output as json (default: false) + --restart send restart event for all listed sessions (default: false) + --clean remove uploads (default: false) + --help, -h show help +``` + +This will always output a list of uploads that match the criteria. See Command Examples section. + +Some additional information on returned information: + - `Offset` is the amount of bytes the server has already received. If `Offset` == `Size` the server has reveived all bytes of the upload. + - `Processing` indicates if the uploaded file is currently going through postprocessing. + - `Scan Date` and `Scan Result` indicate the scanning status. If `Scan Date` is set and `Scan Result` is empty the file is not virus infected. + #### Command Examples Command to list ongoing upload sessions ```bash -ocis storage-users sessions --expired=false +ocis storage-users uploads sessions --expired=false --processing=false ``` ```plaintext Not expired sessions: -+--------------------------------------+--------------------------------------+---------+--------+------+--------------------------------------+--------------------------------------+---------------------------+------------+ -| Space | Upload Id | Name | Offset | Size | Executant | Owner | Expires | Processing | -+--------------------------------------+--------------------------------------+---------+--------+------+--------------------------------------+--------------------------------------+---------------------------+------------+ -| f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c | 5e387954-7313-4223-a904-bf996da6ec0b | foo.txt | 0 | 1234 | f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c | f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c | 2024-01-26T13:04:31+01:00 | false | -| f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c | f066244d-97b2-48e7-a30d-b40fcb60cec6 | bar.txt | 0 | 4321 | f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c | f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c | 2024-01-26T13:18:47+01:00 | false | -+--------------------------------------+--------------------------------------+---------+--------+------+--------------------------------------+--------------------------------------+---------------------------+------------+ ++--------------------------------------+--------------------------------------+---------+--------+------+--------------------------------------+--------------------------------------+---------------------------+------------+---------------------------+-----------------------+ +| Space | Upload Id | Name | Offset | Size | Executant | Owner | Expires | Processing | Scan Date | Scan Result | ++--------------------------------------+--------------------------------------+---------+--------+------+--------------------------------------+--------------------------------------+---------------------------+------------+---------------------------+-----------------------+ +| f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c | 5e387954-7313-4223-a904-bf996da6ec0b | foo.txt | 0 | 1234 | f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c | f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c | 2024-01-26T13:04:31+01:00 | false | 2024-04-24T11:24:14+02:00 | infected: virus A | +| f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c | f066244d-97b2-48e7-a30d-b40fcb60cec6 | bar.txt | 0 | 4321 | f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c | f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c | 2024-01-26T13:18:47+01:00 | false | 2024-04-24T14:38:29+02:00 | | ++--------------------------------------+--------------------------------------+---------+--------+------+--------------------------------------+--------------------------------------+---------------------------+------------+---------------------------+-----------------------+ ``` The sessions command can also output json ```bash -ocis storage-users sessions --expired=false --json +ocis storage-users uploads sessions --expired=false --processing=false --json ``` ```json -{"id":"5e387954-7313-4223-a904-bf996da6ec0b","space":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","filename":"foo.txt","offset":0,"size":1234,"executant":{"idp":"https://cloud.ocis.test","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c"},"spaceowner":{"opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c"},"expires":"2024-01-26T13:04:31+01:00","processing":false} -{"id":"f066244d-97b2-48e7-a30d-b40fcb60cec6","space":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","filename":"bar.txt","offset":0,"size":4321,"executant":{"idp":"https://cloud.ocis.test","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c"},"spaceowner":{"opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c"},"expires":"2024-01-26T13:18:47+01:00","processing":false} +{"id":"5e387954-7313-4223-a904-bf996da6ec0b","space":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","filename":"foo.txt","offset":0,"size":1234,"executant":{"idp":"https://cloud.ocis.test","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c"},"spaceowner":{"opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c"},"expires":"2024-01-26T13:04:31+01:00","processing":false, "scanDate": "2024-04-24T11:24:14+02:00", "scanResult": "infected: virus A"} +{"id":"f066244d-97b2-48e7-a30d-b40fcb60cec6","space":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","filename":"bar.txt","offset":0,"size":4321,"executant":{"idp":"https://cloud.ocis.test","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c"},"spaceowner":{"opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c"},"expires":"2024-01-26T13:18:47+01:00","processing":false, "scanDate": "2024-04-24T14:38:29+02:00", "scanResult": ""} ``` -Command to clear expired uploads +The sessions command can also clear and restart uploads. The output is the same as if run without `--clean` or `--restart` flag. +Note: It is recommended to run to command first without the `--clean` (`--processing`) flag to double check which uploads get cleaned (restarted). ```bash -ocis storage-users uploads clean -``` +# cleans all expired uploads regardless of processing and virus state. +ocis storage-users uploads sessions --expired=true --clean -```plaintext -Cleaned uploads: -- 455bd640-cd08-46e8-a5a0-9304908bd40a (Filename: file_example_PPT_1MB.ppt, Size: 1028608, Expires: 2022-08-17T12:35:34+02:00) +# restarts all uploads that are processing and are not virus infected +ocis storage-users uploads sessions --processing=false --has-virus=false --restart ``` -Deprecated list command to identify unfinished uploads +IMPOTANT: `list` and `clean` commands are deprecated. Do not use them. ```bash +# deprecated ocis storage-users uploads list -``` -```plaintext -Incomplete uploads: - - 455bd640-cd08-46e8-a5a0-9304908bd40a (file_example_PPT_1MB.ppt, Size: 1028608, Expires: 2022-08-17T12:35:34+02:00) +# deprecated +ocis storage-users uploads clean ``` ### Purge Expired Space Trash-Bins Items diff --git a/services/storage-users/pkg/command/uploads.go b/services/storage-users/pkg/command/uploads.go index 433773637c2..ca7555eed9c 100644 --- a/services/storage-users/pkg/command/uploads.go +++ b/services/storage-users/pkg/command/uploads.go @@ -25,6 +25,22 @@ import ( "github.com/owncloud/ocis/v2/services/storage-users/pkg/revaconfig" ) +// Session contains the information of an upload session +type Session struct { + ID string `json:"id"` + Space string `json:"space"` + Filename string `json:"filename"` + Offset int64 `json:"offset"` + Size int64 `json:"size"` + Executant userpb.UserId `json:"executant"` + SpaceOwner *userpb.UserId `json:"spaceowner,omitempty"` + Expires time.Time `json:"expires"` + Processing bool `json:"processing"` + ScanDate time.Time `json:"virus_scan_date"` + ScanResult string `json:"virus_scan_result"` +} + +// Uploads is the entry point for the uploads command func Uploads(cfg *config.Config) *cli.Command { return &cli.Command{ @@ -47,6 +63,8 @@ func ListUploads(cfg *config.Config) *cli.Command { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, Action: func(c *cli.Context) error { + fmt.Println("Warning: This command is deprecated, use 'uploads sessions' instead") + f, ok := registry.NewFuncs[cfg.Driver] if !ok { fmt.Fprintf(os.Stderr, "Unknown filesystem driver '%s'\n", cfg.Driver) @@ -101,6 +119,11 @@ func ListUploadSessions(cfg *config.Config) *cli.Command { DefaultText: "unset", Usage: "filter sessions by expired status", }, + &cli.BoolFlag{ + Name: "has-virus", + DefaultText: "unset", + Usage: "filter sessions by virus scan result", + }, &cli.BoolFlag{ Name: "json", Usage: "output as json", @@ -109,6 +132,10 @@ func ListUploadSessions(cfg *config.Config) *cli.Command { Name: "restart", Usage: "send restart event for all listed sessions", }, + &cli.BoolFlag{ + Name: "clean", + Usage: "remove uploads", + }, }, Before: func(c *cli.Context) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) @@ -141,138 +168,91 @@ func ListUploadSessions(cfg *config.Config) *cli.Command { } } - var b strings.Builder - filter := storage.UploadSessionFilter{} - if c.IsSet("processing") { - processingValue := c.Bool("processing") - filter.Processing = &processingValue - if !processingValue { - b.WriteString("Not ") - } - if b.Len() == 0 { - b.WriteString("Processing ") - } else { - b.WriteString("processing ") - } - } - if c.IsSet("expired") { - expiredValue := c.Bool("expired") - filter.Expired = &expiredValue - if !expiredValue { - if b.Len() == 0 { - b.WriteString("Not ") - } else { - b.WriteString(", not ") - } - } - if b.Len() == 0 { - b.WriteString("Expired ") - } else { - b.WriteString("expired ") - } - } - if b.Len() == 0 { - b.WriteString("Sessions") - } else { - b.WriteString("sessions") - } - if c.IsSet("id") { - idValue := c.String("id") - filter.ID = &idValue - b.WriteString(" with id '" + idValue + "'") - } - b.WriteString(":") + filter := buildFilter(c) uploads, err := managingFS.ListUploadSessions(c.Context, filter) if err != nil { return err } - var table *tw.Table - if c.Bool("json") { - for _, u := range uploads { - ref := u.Reference() - sr, sd := u.ScanData() - s := struct { - ID string `json:"id"` - Space string `json:"space"` - Filename string `json:"filename"` - Offset int64 `json:"offset"` - Size int64 `json:"size"` - Executant userpb.UserId `json:"executant"` - SpaceOwner *userpb.UserId `json:"spaceowner,omitempty"` - Expires time.Time `json:"expires"` - Processing bool `json:"processing"` - ScanDate time.Time `json:"virus_scan_date"` - ScanResult string `json:"virus_scan_result"` - }{ - Space: ref.GetResourceId().GetSpaceId(), - ID: u.ID(), - Filename: u.Filename(), - Offset: u.Offset(), - Size: u.Size(), - Executant: u.Executant(), - SpaceOwner: u.SpaceOwner(), - Expires: u.Expires(), - Processing: u.IsProcessing(), - ScanDate: sd, - ScanResult: sr, - } - j, err := json.Marshal(s) - if err != nil { - fmt.Println(err) - } - fmt.Println(string(j)) - - if c.Bool("restart") { - if err := events.Publish(context.Background(), stream, events.ResumePostprocessing{ - UploadID: u.ID(), - Timestamp: utils.TSNow(), - }); err != nil { - fmt.Fprintf(os.Stderr, "Failed to send restart event for upload session '%s'\n", u.ID()) - // if publishing fails there is no need to try publishing other events - they will fail too. - os.Exit(1) - } - } - } - } else { + var ( + table *tw.Table + raw []Session + ) - // Print what the user requested - fmt.Println(b.String()) + if !c.Bool("json") { + fmt.Println(buildInfo(filter)) - // start a table table = tw.NewWriter(os.Stdout) table.SetHeader([]string{"Space", "Upload Id", "Name", "Offset", "Size", "Executant", "Owner", "Expires", "Processing", "Scan Date", "Scan Result"}) table.SetAutoFormatHeaders(false) + } - for _, u := range uploads { - sr, sd := u.ScanData() + for _, u := range uploads { + ref := u.Reference() + sr, sd := u.ScanData() + + session := Session{ + Space: ref.GetResourceId().GetSpaceId(), + ID: u.ID(), + Filename: u.Filename(), + Offset: u.Offset(), + Size: u.Size(), + Executant: u.Executant(), + SpaceOwner: u.SpaceOwner(), + Expires: u.Expires(), + Processing: u.IsProcessing(), + ScanDate: sd, + ScanResult: sr, + } + + if c.Bool("json") { + raw = append(raw, session) + } else { table.Append([]string{ - u.Reference().ResourceId.GetSpaceId(), - u.ID(), - u.Filename(), - strconv.FormatInt(u.Offset(), 10), - strconv.FormatInt(u.Size(), 10), - u.Executant().OpaqueId, - u.SpaceOwner().GetOpaqueId(), - u.Expires().Format(time.RFC3339), - strconv.FormatBool(u.IsProcessing()), - sd.Format(time.RFC3339), - sr, + session.Space, + session.ID, + session.Filename, + strconv.FormatInt(session.Offset, 10), + strconv.FormatInt(session.Size, 10), + session.Executant.OpaqueId, + session.SpaceOwner.GetOpaqueId(), + session.Expires.Format(time.RFC3339), + strconv.FormatBool(session.Processing), + session.ScanDate.Format(time.RFC3339), + session.ScanResult, }) + } - if c.Bool("restart") { - if err := events.Publish(context.Background(), stream, events.ResumePostprocessing{ - UploadID: u.ID(), - Timestamp: utils.TSNow(), - }); err != nil { - fmt.Fprintf(os.Stderr, "Failed to send restart event for upload session '%s'\n", u.ID()) - // if publishing fails there is no need to try publishing other events - they will fail too. - os.Exit(1) - } + if c.Bool("restart") { + if err := events.Publish(context.Background(), stream, events.ResumePostprocessing{ + UploadID: u.ID(), + Timestamp: utils.TSNow(), + }); err != nil { + fmt.Fprintf(os.Stderr, "Failed to send restart event for upload session '%s'\n", u.ID()) + // if publishing fails there is no need to try publishing other events - they will fail too. + os.Exit(1) } } + + if c.Bool("clean") { + if err := u.Purge(c.Context); err != nil { + fmt.Fprintf(os.Stderr, "Failed to clean upload session '%s'\n", u.ID()) + } + } + + } + + if !c.Bool("json") { table.Render() + return nil + } + + j, err := json.Marshal(raw) + if err != nil { + fmt.Println(err) + return err } + fmt.Println(string(j)) return nil }, } @@ -287,6 +267,7 @@ func PurgeExpiredUploads(cfg *config.Config) *cli.Command { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, Action: func(c *cli.Context) error { + fmt.Println("Warning: This command is deprecated, use 'uploads sessions --clean' instead") f, ok := registry.NewFuncs[cfg.Driver] if !ok { fmt.Fprintf(os.Stderr, "Unknown filesystem driver '%s'\n", cfg.Driver) @@ -333,3 +314,90 @@ func PurgeExpiredUploads(cfg *config.Config) *cli.Command { }, } } + +func buildFilter(c *cli.Context) storage.UploadSessionFilter { + filter := storage.UploadSessionFilter{} + if c.IsSet("processing") { + processingValue := c.Bool("processing") + filter.Processing = &processingValue + } + if c.IsSet("expired") { + expiredValue := c.Bool("expired") + filter.Expired = &expiredValue + } + if c.IsSet("has-virus") { + infectedValue := c.Bool("has-virus") + filter.HasVirus = &infectedValue + } + if c.IsSet("id") { + idValue := c.String("id") + filter.ID = &idValue + } + return filter +} + +func buildInfo(filter storage.UploadSessionFilter) string { + var b strings.Builder + if filter.Processing != nil { + if !*filter.Processing { + b.WriteString("Not ") + } + if b.Len() == 0 { + b.WriteString("Processing") + } else { + b.WriteString("processing") + } + } + + if filter.Expired != nil { + if b.Len() != 0 { + b.WriteString(", ") + } + if !*filter.Expired { + if b.Len() == 0 { + b.WriteString("Not ") + } else { + b.WriteString("not ") + } + } + if b.Len() == 0 { + b.WriteString("Expired") + } else { + b.WriteString("expired") + } + } + + if filter.HasVirus != nil { + if b.Len() != 0 { + b.WriteString(", ") + } + if !*filter.HasVirus { + if b.Len() == 0 { + b.WriteString("Not ") + } else { + b.WriteString("not ") + } + } + if b.Len() == 0 { + b.WriteString("Virusinfected") + } else { + b.WriteString("virusinfected") + } + } + + if b.Len() == 0 { + b.WriteString("Session") + } else { + b.WriteString(" session") + } + + if filter.ID != nil { + b.WriteString(" with id '" + *filter.ID + "'") + } else { + // to make `session` plural + b.WriteString("s") + } + + b.WriteString(":") + return b.String() +} diff --git a/services/storage-users/pkg/command/uploads_test.go b/services/storage-users/pkg/command/uploads_test.go new file mode 100644 index 00000000000..4d638eaecab --- /dev/null +++ b/services/storage-users/pkg/command/uploads_test.go @@ -0,0 +1,85 @@ +package command + +import ( + "testing" + + "github.com/cs3org/reva/v2/pkg/storage" + "github.com/test-go/testify/require" +) + +func TestBuildInfo(t *testing.T) { + testCases := []struct { + alias string + filter storage.UploadSessionFilter + expectedInfo string + }{ + { + alias: "empty filter", + filter: storage.UploadSessionFilter{}, + expectedInfo: "Sessions:", + }, + { + alias: "processing", + filter: storage.UploadSessionFilter{Processing: boolPtr(true)}, + expectedInfo: "Processing sessions:", + }, + { + alias: "processing and not expired", + filter: storage.UploadSessionFilter{Processing: boolPtr(true), Expired: boolPtr(false)}, + expectedInfo: "Processing, not expired sessions:", + }, + { + alias: "processing and expired", + filter: storage.UploadSessionFilter{Processing: boolPtr(true), Expired: boolPtr(true)}, + expectedInfo: "Processing, expired sessions:", + }, + { + alias: "with id", + filter: storage.UploadSessionFilter{ID: strPtr("123")}, + expectedInfo: "Session with id '123':", + }, + { + alias: "processing, not expired and not virus infected", + filter: storage.UploadSessionFilter{Processing: boolPtr(true), Expired: boolPtr(false), HasVirus: boolPtr(false)}, + expectedInfo: "Processing, not expired, not virusinfected sessions:", + }, + { + alias: "not virusinfected", + filter: storage.UploadSessionFilter{HasVirus: boolPtr(false)}, + expectedInfo: "Not virusinfected sessions:", + }, + { + alias: "expired and virusinfected", + filter: storage.UploadSessionFilter{Expired: boolPtr(true), HasVirus: boolPtr(true)}, + expectedInfo: "Expired, virusinfected sessions:", + }, + { + alias: "expired and not virus infected", + filter: storage.UploadSessionFilter{Expired: boolPtr(true), HasVirus: boolPtr(false)}, + expectedInfo: "Expired, not virusinfected sessions:", + }, + { + alias: "processing, not expired, virus infected and with id (note: this makes no sense)", + filter: storage.UploadSessionFilter{Processing: boolPtr(true), Expired: boolPtr(false), HasVirus: boolPtr(true), ID: strPtr("123")}, + expectedInfo: "Processing, not expired, virusinfected session with id '123':", + }, + } + + for _, tc := range testCases { + alias := tc.alias + filter := tc.filter + expectedInfo := tc.expectedInfo + + t.Run(alias, func(t *testing.T) { + require.Equal(t, expectedInfo, buildInfo(filter)) + }) + } +} + +func boolPtr(b bool) *bool { + return &b +} + +func strPtr(s string) *string { + return &s +} diff --git a/vendor/github.com/test-go/testify/LICENSE b/vendor/github.com/test-go/testify/LICENSE new file mode 100644 index 00000000000..473b670a7c6 --- /dev/null +++ b/vendor/github.com/test-go/testify/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell + +Please consider promoting this project if you find it useful. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/test-go/testify/require/doc.go b/vendor/github.com/test-go/testify/require/doc.go new file mode 100644 index 00000000000..169de39221c --- /dev/null +++ b/vendor/github.com/test-go/testify/require/doc.go @@ -0,0 +1,28 @@ +// Package require implements the same assertions as the `assert` package but +// stops test execution when a test fails. +// +// Example Usage +// +// The following is a complete example using require in a standard test function: +// import ( +// "testing" +// "github.com/stretchr/testify/require" +// ) +// +// func TestSomething(t *testing.T) { +// +// var a string = "Hello" +// var b string = "Hello" +// +// require.Equal(t, a, b, "The two words should be the same.") +// +// } +// +// Assertions +// +// The `require` package have same global functions as in the `assert` package, +// but instead of returning a boolean result they call `t.FailNow()`. +// +// Every assertion function also takes an optional string message as the final argument, +// allowing custom error messages to be appended to the message the assertion method outputs. +package require diff --git a/vendor/github.com/test-go/testify/require/forward_requirements.go b/vendor/github.com/test-go/testify/require/forward_requirements.go new file mode 100644 index 00000000000..d3c2ab9bc7e --- /dev/null +++ b/vendor/github.com/test-go/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 go run ../_codegen/main.go -output-package=require -template=require_forward.go.tmpl diff --git a/vendor/github.com/test-go/testify/require/require.go b/vendor/github.com/test-go/testify/require/require.go new file mode 100644 index 00000000000..1bcfcb0d949 --- /dev/null +++ b/vendor/github.com/test-go/testify/require/require.go @@ -0,0 +1,464 @@ +/* +* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen +* THIS FILE MUST NOT BE EDITED BY HAND +*/ + +package require + +import ( + + assert "github.com/stretchr/testify/assert" + http "net/http" + url "net/url" + time "time" +) + + +// Condition uses a Comparison to assert a complex condition. +func Condition(t TestingT, comp assert.Comparison, msgAndArgs ...interface{}) { + if !assert.Condition(t, comp, msgAndArgs...) { + t.FailNow() + } +} + + +// Contains asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// assert.Contains(t, "Hello World", "World", "But 'Hello World' does contain 'World'") +// assert.Contains(t, ["Hello", "World"], "World", "But ["Hello", "World"] does contain 'World'") +// assert.Contains(t, {"Hello": "World"}, "Hello", "But {'Hello': 'World'} does contain 'Hello'") +// +// Returns whether the assertion was successful (true) or not (false). +func Contains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if !assert.Contains(t, s, contains, msgAndArgs...) { + t.FailNow() + } +} + + +// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// assert.Empty(t, obj) +// +// Returns whether the assertion was successful (true) or not (false). +func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if !assert.Empty(t, object, msgAndArgs...) { + t.FailNow() + } +} + + +// Equal asserts that two objects are equal. +// +// assert.Equal(t, 123, 123, "123 and 123 should be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func Equal(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if !assert.Equal(t, expected, actual, msgAndArgs...) { + t.FailNow() + } +} + + +// EqualError asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// if assert.Error(t, err, "An error was expected") { +// assert.Equal(t, err, expectedError) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) { + if !assert.EqualError(t, theError, errString, msgAndArgs...) { + t.FailNow() + } +} + + +// EqualValues asserts that two objects are equal or convertable to the same types +// and equal. +// +// assert.EqualValues(t, uint32(123), int32(123), "123 and 123 should be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func EqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if !assert.EqualValues(t, expected, actual, msgAndArgs...) { + t.FailNow() + } +} + + +// Error asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if assert.Error(t, err, "An error was expected") { +// assert.Equal(t, err, expectedError) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func Error(t TestingT, err error, msgAndArgs ...interface{}) { + if !assert.Error(t, err, msgAndArgs...) { + t.FailNow() + } +} + + +// Exactly asserts that two objects are equal is value and type. +// +// assert.Exactly(t, int32(123), int64(123), "123 and 123 should NOT be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func Exactly(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if !assert.Exactly(t, expected, actual, msgAndArgs...) { + t.FailNow() + } +} + + +// Fail reports a failure through +func Fail(t TestingT, failureMessage string, msgAndArgs ...interface{}) { + if !assert.Fail(t, failureMessage, msgAndArgs...) { + t.FailNow() + } +} + + +// FailNow fails test +func FailNow(t TestingT, failureMessage string, msgAndArgs ...interface{}) { + if !assert.FailNow(t, failureMessage, msgAndArgs...) { + t.FailNow() + } +} + + +// False asserts that the specified value is false. +// +// assert.False(t, myBool, "myBool should be false") +// +// Returns whether the assertion was successful (true) or not (false). +func False(t TestingT, value bool, msgAndArgs ...interface{}) { + if !assert.False(t, value, msgAndArgs...) { + t.FailNow() + } +} + + +// HTTPBodyContains asserts that a specified handler returns a +// body that contains a string. +// +// assert.HTTPBodyContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) { + if !assert.HTTPBodyContains(t, handler, method, url, values, str) { + t.FailNow() + } +} + + +// HTTPBodyNotContains asserts that a specified handler returns a +// body that does not contain a string. +// +// assert.HTTPBodyNotContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) { + if !assert.HTTPBodyNotContains(t, handler, method, url, values, str) { + t.FailNow() + } +} + + +// HTTPError asserts that a specified handler returns an error status code. +// +// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPError(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) { + if !assert.HTTPError(t, handler, method, url, values) { + t.FailNow() + } +} + + +// HTTPRedirect asserts that a specified handler returns a redirect status code. +// +// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPRedirect(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) { + if !assert.HTTPRedirect(t, handler, method, url, values) { + t.FailNow() + } +} + + +// HTTPSuccess asserts that a specified handler returns a success status code. +// +// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPSuccess(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) { + if !assert.HTTPSuccess(t, handler, method, url, values) { + t.FailNow() + } +} + + +// Implements asserts that an object is implemented by the specified interface. +// +// assert.Implements(t, (*MyInterface)(nil), new(MyObject), "MyObject") +func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { + if !assert.Implements(t, interfaceObject, object, msgAndArgs...) { + t.FailNow() + } +} + + +// InDelta asserts that the two numerals are within delta of each other. +// +// assert.InDelta(t, math.Pi, (22 / 7.0), 0.01) +// +// Returns whether the assertion was successful (true) or not (false). +func InDelta(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if !assert.InDelta(t, expected, actual, delta, msgAndArgs...) { + t.FailNow() + } +} + + +// InDeltaSlice is the same as InDelta, except it compares two slices. +func InDeltaSlice(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if !assert.InDeltaSlice(t, expected, actual, delta, msgAndArgs...) { + t.FailNow() + } +} + + +// InEpsilon asserts that expected and actual have a relative error less than epsilon +// +// Returns whether the assertion was successful (true) or not (false). +func InEpsilon(t TestingT, expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if !assert.InEpsilon(t, expected, actual, epsilon, msgAndArgs...) { + t.FailNow() + } +} + + +// InEpsilonSlice is the same as InEpsilon, except it compares two slices. +func InEpsilonSlice(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if !assert.InEpsilonSlice(t, expected, actual, delta, msgAndArgs...) { + t.FailNow() + } +} + + +// IsType asserts that the specified objects are of the same type. +func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) { + if !assert.IsType(t, expectedType, object, msgAndArgs...) { + t.FailNow() + } +} + + +// JSONEq asserts that two JSON strings are equivalent. +// +// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +// +// Returns whether the assertion was successful (true) or not (false). +func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) { + if !assert.JSONEq(t, expected, actual, msgAndArgs...) { + t.FailNow() + } +} + + +// Len asserts that the specified object has specific length. +// Len also fails if the object has a type that len() not accept. +// +// assert.Len(t, mySlice, 3, "The size of slice is not 3") +// +// Returns whether the assertion was successful (true) or not (false). +func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) { + if !assert.Len(t, object, length, msgAndArgs...) { + t.FailNow() + } +} + + +// Nil asserts that the specified object is nil. +// +// assert.Nil(t, err, "err should be nothing") +// +// Returns whether the assertion was successful (true) or not (false). +func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if !assert.Nil(t, object, msgAndArgs...) { + t.FailNow() + } +} + + +// NoError asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if assert.NoError(t, err) { +// assert.Equal(t, actualObj, expectedObj) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func NoError(t TestingT, err error, msgAndArgs ...interface{}) { + if !assert.NoError(t, err, msgAndArgs...) { + t.FailNow() + } +} + + +// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// assert.NotContains(t, "Hello World", "Earth", "But 'Hello World' does NOT contain 'Earth'") +// assert.NotContains(t, ["Hello", "World"], "Earth", "But ['Hello', 'World'] does NOT contain 'Earth'") +// assert.NotContains(t, {"Hello": "World"}, "Earth", "But {'Hello': 'World'} does NOT contain 'Earth'") +// +// Returns whether the assertion was successful (true) or not (false). +func NotContains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if !assert.NotContains(t, s, contains, msgAndArgs...) { + t.FailNow() + } +} + + +// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if assert.NotEmpty(t, obj) { +// assert.Equal(t, "two", obj[1]) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if !assert.NotEmpty(t, object, msgAndArgs...) { + t.FailNow() + } +} + + +// NotEqual asserts that the specified values are NOT equal. +// +// assert.NotEqual(t, obj1, obj2, "two objects shouldn't be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func NotEqual(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if !assert.NotEqual(t, expected, actual, msgAndArgs...) { + t.FailNow() + } +} + + +// NotNil asserts that the specified object is not nil. +// +// assert.NotNil(t, err, "err should be something") +// +// Returns whether the assertion was successful (true) or not (false). +func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if !assert.NotNil(t, object, msgAndArgs...) { + t.FailNow() + } +} + + +// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// assert.NotPanics(t, func(){ +// RemainCalm() +// }, "Calling RemainCalm() should NOT panic") +// +// Returns whether the assertion was successful (true) or not (false). +func NotPanics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if !assert.NotPanics(t, f, msgAndArgs...) { + t.FailNow() + } +} + + +// NotRegexp asserts that a specified regexp does not match a string. +// +// assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") +// assert.NotRegexp(t, "^start", "it's not starting") +// +// Returns whether the assertion was successful (true) or not (false). +func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if !assert.NotRegexp(t, rx, str, msgAndArgs...) { + t.FailNow() + } +} + + +// NotZero asserts that i is not the zero value for its type and returns the truth. +func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) { + if !assert.NotZero(t, i, msgAndArgs...) { + t.FailNow() + } +} + + +// Panics asserts that the code inside the specified PanicTestFunc panics. +// +// assert.Panics(t, func(){ +// GoCrazy() +// }, "Calling GoCrazy() should panic") +// +// Returns whether the assertion was successful (true) or not (false). +func Panics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if !assert.Panics(t, f, msgAndArgs...) { + t.FailNow() + } +} + + +// Regexp asserts that a specified regexp matches a string. +// +// assert.Regexp(t, regexp.MustCompile("start"), "it's starting") +// assert.Regexp(t, "start...$", "it's not starting") +// +// Returns whether the assertion was successful (true) or not (false). +func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if !assert.Regexp(t, rx, str, msgAndArgs...) { + t.FailNow() + } +} + + +// True asserts that the specified value is true. +// +// assert.True(t, myBool, "myBool should be true") +// +// Returns whether the assertion was successful (true) or not (false). +func True(t TestingT, value bool, msgAndArgs ...interface{}) { + if !assert.True(t, value, msgAndArgs...) { + t.FailNow() + } +} + + +// WithinDuration asserts that the two times are within duration delta of each other. +// +// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second, "The difference should not be more than 10s") +// +// Returns whether the assertion was successful (true) or not (false). +func WithinDuration(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) { + if !assert.WithinDuration(t, expected, actual, delta, msgAndArgs...) { + t.FailNow() + } +} + + +// Zero asserts that i is the zero value for its type and returns the truth. +func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) { + if !assert.Zero(t, i, msgAndArgs...) { + t.FailNow() + } +} diff --git a/vendor/github.com/test-go/testify/require/require.go.tmpl b/vendor/github.com/test-go/testify/require/require.go.tmpl new file mode 100644 index 00000000000..ab1b1e9fd57 --- /dev/null +++ b/vendor/github.com/test-go/testify/require/require.go.tmpl @@ -0,0 +1,6 @@ +{{.Comment}} +func {{.DocInfo.Name}}(t TestingT, {{.Params}}) { + if !assert.{{.DocInfo.Name}}(t, {{.ForwardedParams}}) { + t.FailNow() + } +} diff --git a/vendor/github.com/test-go/testify/require/require_forward.go b/vendor/github.com/test-go/testify/require/require_forward.go new file mode 100644 index 00000000000..58324f10551 --- /dev/null +++ b/vendor/github.com/test-go/testify/require/require_forward.go @@ -0,0 +1,388 @@ +/* +* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen +* THIS FILE MUST NOT BE EDITED BY HAND +*/ + +package require + +import ( + + assert "github.com/stretchr/testify/assert" + http "net/http" + url "net/url" + time "time" +) + + +// Condition uses a Comparison to assert a complex condition. +func (a *Assertions) Condition(comp assert.Comparison, msgAndArgs ...interface{}) { + Condition(a.t, comp, msgAndArgs...) +} + + +// Contains asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// a.Contains("Hello World", "World", "But 'Hello World' does contain 'World'") +// a.Contains(["Hello", "World"], "World", "But ["Hello", "World"] does contain 'World'") +// a.Contains({"Hello": "World"}, "Hello", "But {'Hello': 'World'} does contain 'Hello'") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) { + Contains(a.t, s, contains, msgAndArgs...) +} + + +// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// a.Empty(obj) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) { + Empty(a.t, object, msgAndArgs...) +} + + +// Equal asserts that two objects are equal. +// +// a.Equal(123, 123, "123 and 123 should be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + Equal(a.t, expected, actual, msgAndArgs...) +} + + +// EqualError asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// if assert.Error(t, err, "An error was expected") { +// assert.Equal(t, err, expectedError) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) { + EqualError(a.t, theError, errString, msgAndArgs...) +} + + +// EqualValues asserts that two objects are equal or convertable to the same types +// and equal. +// +// a.EqualValues(uint32(123), int32(123), "123 and 123 should be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + EqualValues(a.t, expected, actual, msgAndArgs...) +} + + +// Error asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if a.Error(err, "An error was expected") { +// assert.Equal(t, err, expectedError) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Error(err error, msgAndArgs ...interface{}) { + Error(a.t, err, msgAndArgs...) +} + + +// Exactly asserts that two objects are equal is value and type. +// +// a.Exactly(int32(123), int64(123), "123 and 123 should NOT be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + Exactly(a.t, expected, actual, msgAndArgs...) +} + + +// Fail reports a failure through +func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) { + Fail(a.t, failureMessage, msgAndArgs...) +} + + +// FailNow fails test +func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) { + FailNow(a.t, failureMessage, msgAndArgs...) +} + + +// False asserts that the specified value is false. +// +// a.False(myBool, "myBool should be false") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) False(value bool, msgAndArgs ...interface{}) { + False(a.t, value, msgAndArgs...) +} + + +// HTTPBodyContains asserts that a specified handler returns a +// body that contains a string. +// +// a.HTTPBodyContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) { + HTTPBodyContains(a.t, handler, method, url, values, str) +} + + +// HTTPBodyNotContains asserts that a specified handler returns a +// body that does not contain a string. +// +// a.HTTPBodyNotContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) { + HTTPBodyNotContains(a.t, handler, method, url, values, str) +} + + +// HTTPError asserts that a specified handler returns an error status code. +// +// a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values) { + HTTPError(a.t, handler, method, url, values) +} + + +// HTTPRedirect asserts that a specified handler returns a redirect status code. +// +// a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values) { + HTTPRedirect(a.t, handler, method, url, values) +} + + +// HTTPSuccess asserts that a specified handler returns a success status code. +// +// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values) { + HTTPSuccess(a.t, handler, method, url, values) +} + + +// Implements asserts that an object is implemented by the specified interface. +// +// a.Implements((*MyInterface)(nil), new(MyObject), "MyObject") +func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { + Implements(a.t, interfaceObject, object, msgAndArgs...) +} + + +// InDelta asserts that the two numerals are within delta of each other. +// +// a.InDelta(math.Pi, (22 / 7.0), 0.01) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + InDelta(a.t, expected, actual, delta, msgAndArgs...) +} + + +// InDeltaSlice is the same as InDelta, except it compares two slices. +func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...) +} + + +// InEpsilon asserts that expected and actual have a relative error less than epsilon +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...) +} + + +// InEpsilonSlice is the same as InEpsilon, except it compares two slices. +func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + InEpsilonSlice(a.t, expected, actual, delta, msgAndArgs...) +} + + +// IsType asserts that the specified objects are of the same type. +func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) { + IsType(a.t, expectedType, object, msgAndArgs...) +} + + +// JSONEq asserts that two JSON strings are equivalent. +// +// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) { + JSONEq(a.t, expected, actual, msgAndArgs...) +} + + +// Len asserts that the specified object has specific length. +// Len also fails if the object has a type that len() not accept. +// +// a.Len(mySlice, 3, "The size of slice is not 3") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) { + Len(a.t, object, length, msgAndArgs...) +} + + +// Nil asserts that the specified object is nil. +// +// a.Nil(err, "err should be nothing") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) { + Nil(a.t, object, msgAndArgs...) +} + + +// NoError asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if a.NoError(err) { +// assert.Equal(t, actualObj, expectedObj) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) { + NoError(a.t, err, msgAndArgs...) +} + + +// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// a.NotContains("Hello World", "Earth", "But 'Hello World' does NOT contain 'Earth'") +// a.NotContains(["Hello", "World"], "Earth", "But ['Hello', 'World'] does NOT contain 'Earth'") +// a.NotContains({"Hello": "World"}, "Earth", "But {'Hello': 'World'} does NOT contain 'Earth'") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) { + NotContains(a.t, s, contains, msgAndArgs...) +} + + +// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if a.NotEmpty(obj) { +// assert.Equal(t, "two", obj[1]) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) { + NotEmpty(a.t, object, msgAndArgs...) +} + + +// NotEqual asserts that the specified values are NOT equal. +// +// a.NotEqual(obj1, obj2, "two objects shouldn't be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + NotEqual(a.t, expected, actual, msgAndArgs...) +} + + +// NotNil asserts that the specified object is not nil. +// +// a.NotNil(err, "err should be something") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) { + NotNil(a.t, object, msgAndArgs...) +} + + +// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// a.NotPanics(func(){ +// RemainCalm() +// }, "Calling RemainCalm() should NOT panic") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NotPanics(f assert.PanicTestFunc, msgAndArgs ...interface{}) { + NotPanics(a.t, f, msgAndArgs...) +} + + +// NotRegexp asserts that a specified regexp does not match a string. +// +// a.NotRegexp(regexp.MustCompile("starts"), "it's starting") +// a.NotRegexp("^start", "it's not starting") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) { + NotRegexp(a.t, rx, str, msgAndArgs...) +} + + +// NotZero asserts that i is not the zero value for its type and returns the truth. +func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) { + NotZero(a.t, i, msgAndArgs...) +} + + +// Panics asserts that the code inside the specified PanicTestFunc panics. +// +// a.Panics(func(){ +// GoCrazy() +// }, "Calling GoCrazy() should panic") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Panics(f assert.PanicTestFunc, msgAndArgs ...interface{}) { + Panics(a.t, f, msgAndArgs...) +} + + +// Regexp asserts that a specified regexp matches a string. +// +// a.Regexp(regexp.MustCompile("start"), "it's starting") +// a.Regexp("start...$", "it's not starting") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) { + Regexp(a.t, rx, str, msgAndArgs...) +} + + +// True asserts that the specified value is true. +// +// a.True(myBool, "myBool should be true") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) True(value bool, msgAndArgs ...interface{}) { + True(a.t, value, msgAndArgs...) +} + + +// WithinDuration asserts that the two times are within duration delta of each other. +// +// a.WithinDuration(time.Now(), time.Now(), 10*time.Second, "The difference should not be more than 10s") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) { + WithinDuration(a.t, expected, actual, delta, msgAndArgs...) +} + + +// Zero asserts that i is the zero value for its type and returns the truth. +func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) { + Zero(a.t, i, msgAndArgs...) +} diff --git a/vendor/github.com/test-go/testify/require/require_forward.go.tmpl b/vendor/github.com/test-go/testify/require/require_forward.go.tmpl new file mode 100644 index 00000000000..b93569e0a97 --- /dev/null +++ b/vendor/github.com/test-go/testify/require/require_forward.go.tmpl @@ -0,0 +1,4 @@ +{{.CommentWithoutT "a"}} +func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) { + {{.DocInfo.Name}}(a.t, {{.ForwardedParams}}) +} diff --git a/vendor/github.com/test-go/testify/require/requirements.go b/vendor/github.com/test-go/testify/require/requirements.go new file mode 100644 index 00000000000..41147562d86 --- /dev/null +++ b/vendor/github.com/test-go/testify/require/requirements.go @@ -0,0 +1,9 @@ +package require + +// TestingT is an interface wrapper around *testing.T +type TestingT interface { + Errorf(format string, args ...interface{}) + FailNow() +} + +//go:generate go run ../_codegen/main.go -output-package=require -template=require.go.tmpl diff --git a/vendor/modules.txt b/vendor/modules.txt index 7eb268f8964..1bf3cab1aac 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1752,6 +1752,9 @@ github.com/studio-b12/gowebdav # github.com/tchap/go-patricia/v2 v2.3.1 ## explicit; go 1.16 github.com/tchap/go-patricia/v2/patricia +# github.com/test-go/testify v1.1.4 +## explicit +github.com/test-go/testify/require # github.com/thejerf/suture/v4 v4.0.2 ## explicit; go 1.9 github.com/thejerf/suture/v4