diff --git a/go.mod b/go.mod index 28e3f8f61d..f221ec7a11 100644 --- a/go.mod +++ b/go.mod @@ -43,7 +43,7 @@ require ( github.com/olekukonko/tablewriter v0.0.5 github.com/onsi/ginkgo/v2 v2.27.2 github.com/onsi/gomega v1.38.2 - github.com/otiai10/copy v1.7.0 + github.com/otiai10/copy v1.14.1 github.com/pkg/errors v0.9.1 github.com/pkg/sftp v1.13.10 github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94 @@ -164,6 +164,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opencontainers/runtime-spec v1.2.1 // indirect + github.com/otiai10/mint v1.6.3 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect diff --git a/go.sum b/go.sum index ec4bbb81f2..67f9fbf845 100644 --- a/go.sum +++ b/go.sum @@ -451,13 +451,10 @@ github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/ github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8= github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE= -github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U= -github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= -github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= -github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/otiai10/mint v1.3.3 h1:7JgpsBaN0uMkyju4tbYHu0mnM55hNKVYLsXmwr15NQI= -github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= +github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I= +github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs= +github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= github.com/package-url/packageurl-go v0.1.1-0.20220428063043-89078438f170 h1:DiLBVp4DAcZlBVBEtJpNWZpZVq0AEeCY7Hqk8URVs4o= github.com/package-url/packageurl-go v0.1.1-0.20220428063043-89078438f170/go.mod h1:uQd4a7Rh3ZsVg5j0lNyAfyxIeGde9yrlhjF78GzeW0c= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= diff --git a/vendor/github.com/otiai10/copy/.gitignore b/vendor/github.com/otiai10/copy/.gitignore index d65ce17257..a793485586 100644 --- a/vendor/github.com/otiai10/copy/.gitignore +++ b/vendor/github.com/otiai10/copy/.gitignore @@ -1,5 +1,9 @@ test/data.copy +test/owned-by-root coverage.txt vendor .vagrant .idea/ + +# Test Specific +test/data/case16/large.file diff --git a/vendor/github.com/otiai10/copy/README.md b/vendor/github.com/otiai10/copy/README.md index 2305fbfd55..9f3dce74da 100644 --- a/vendor/github.com/otiai10/copy/README.md +++ b/vendor/github.com/otiai10/copy/README.md @@ -10,13 +10,25 @@ [![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/otiai10/copy?sort=semver)](https://pkg.go.dev/github.com/otiai10/copy) [![Docker Test](https://github.com/otiai10/copy/actions/workflows/docker-test.yml/badge.svg)](https://github.com/otiai10/copy/actions/workflows/docker-test.yml) [![Vagrant Test](https://github.com/otiai10/copy/actions/workflows/vagrant-test.yml/badge.svg)](https://github.com/otiai10/copy/actions/workflows/vagrant-test.yml) +[![GopherJS](https://github.com/otiai10/copy/actions/workflows/gopherjs.yml/badge.svg)](https://github.com/otiai10/copy/actions/workflows/gopherjs.yml) +[![Go WASM](https://github.com/otiai10/copy/actions/workflows/wasm.yml/badge.svg)](https://github.com/otiai10/copy/actions/workflows/wasm.yml) `copy` copies directories recursively. # Example Usage ```go -err := Copy("your/directory", "your/directory.copy") +package main + +import ( + "fmt" + cp "github.com/otiai10/copy" +) + +func main() { + err := cp.Copy("your/src", "your/dest") + fmt.Println(err) // nil +} ``` # Advanced Usage @@ -31,12 +43,28 @@ type Options struct { // OnDirExists can specify what to do when there is a directory already existing in destination. OnDirExists func(src, dest string) DirExistsAction - // Skip can specify which files should be skipped - Skip func(src string) (bool, error) + // OnError can let users decide how to handle errors (e.g., you can suppress specific error). + OnError func(src, dest, string, err error) error - // AddPermission to every entry, - // NO MORE THAN 0777 - AddPermission os.FileMode + // Skip can specify which files should be skipped + Skip func(srcinfo os.FileInfo, src, dest string) (bool, error) + + // RenameDestination can rename destination. + // If not set, nil, it does nothing. + RenameDestination func(src, dest string) (string, error) + + // PermissionControl can control permission of + // every entry. + // When you want to add permission 0222, do like + // + // PermissionControl = AddPermission(0222) + // + // or if you even don't want to touch permission, + // + // PermissionControl = DoNothing + // + // By default, PermissionControl = PreservePermission + PermissionControl PermissionControlFunc // Sync file after copy. // Useful in case when file must be on the disk @@ -55,13 +83,35 @@ type Options struct { // If zero, the internal default buffer of 32KB is used. // See https://golang.org/pkg/io/#CopyBuffer for more information. CopyBufferSize uint + + // If you want to add some limitation on reading src file, + // you can wrap the src and provide new reader, + // such as `RateLimitReader` in the test case. + WrapReader func(src io.Reader) io.Reader + + // If given, copy.Copy refers to this fs.FS instead of the OS filesystem. + // e.g., You can use embed.FS to copy files from embedded filesystem. + FS fs.FS + + // NumOfWorkers represents the number of workers used for + // concurrent copying contents of directories. + // If 0 or 1, it does not use goroutine for copying directories. + // Please refer to https://pkg.go.dev/golang.org/x/sync/semaphore for more details. + NumOfWorkers int64 + + // PreferConcurrent is a function to determine whether or not + // to use goroutine for copying contents of directories. + // If PreferConcurrent is nil, which is default, it does concurrent + // copying for all directories. + // If NumOfWorkers is 0 or 1, this function will be ignored. + PreferConcurrent func(srcdir, destdir string) (bool, error) } ``` ```go // For example... opt := Options{ - Skip: func(src string) (bool, error) { + Skip: func(info os.FileInfo, src, dest string) (bool, error) { return strings.HasSuffix(src, ".git"), nil }, } @@ -74,4 +124,4 @@ err := Copy("your/directory", "your/directory.copy", opt) ## License -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fotiai10%2Fcopy.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fotiai10%2Fcopy?ref=badge_large) \ No newline at end of file +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fotiai10%2Fcopy.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fotiai10%2Fcopy?ref=badge_large) diff --git a/vendor/github.com/otiai10/copy/copy.go b/vendor/github.com/otiai10/copy/copy.go index ae04036b83..f9787cd9cf 100644 --- a/vendor/github.com/otiai10/copy/copy.go +++ b/vendor/github.com/otiai10/copy/copy.go @@ -1,18 +1,15 @@ package copy import ( + "context" "io" - "io/ioutil" + "io/fs" "os" "path/filepath" "time" -) -const ( - // tmpPermissionForDirectory makes the destination directory writable, - // so that stuff can be copied recursively even if any original directory is NOT writable. - // See https://github.com/otiai10/copy/pull/9 for more information. - tmpPermissionForDirectory = os.FileMode(0755) + "golang.org/x/sync/errgroup" + "golang.org/x/sync/semaphore" ) type timespec struct { @@ -22,17 +19,39 @@ type timespec struct { } // Copy copies src to dest, doesn't matter if src is a directory or a file. -func Copy(src, dest string, opt ...Options) error { +func Copy(src, dest string, opts ...Options) error { + opt := assureOptions(src, dest, opts...) + if opt.NumOfWorkers > 1 { + opt.intent.sem = semaphore.NewWeighted(opt.NumOfWorkers) + opt.intent.ctx = context.Background() + } + if opt.FS != nil { + info, err := fs.Stat(opt.FS, src) + if err != nil { + return onError(src, dest, err, opt) + } + return switchboard(src, dest, info, opt) + } info, err := os.Lstat(src) if err != nil { - return err + return onError(src, dest, err, opt) } - return switchboard(src, dest, info, assure(src, dest, opt...)) + return switchboard(src, dest, info, opt) } // switchboard switches proper copy functions regarding file type, etc... // If there would be anything else here, add a case to this switchboard. func switchboard(src, dest string, info os.FileInfo, opt Options) (err error) { + if info.Mode()&os.ModeDevice != 0 && !opt.Specials { + return onError(src, dest, err, opt) + } + + if opt.RenameDestination != nil { + if dest, err = opt.RenameDestination(src, dest); err != nil { + return onError(src, dest, err, opt) + } + } + switch { case info.Mode()&os.ModeSymlink != 0: err = onsymlink(src, dest, opt) @@ -44,19 +63,21 @@ func switchboard(src, dest string, info os.FileInfo, opt Options) (err error) { err = fcopy(src, dest, info, opt) } - return err + return onError(src, dest, err, opt) } // copyNextOrSkip decide if this src should be copied or not. // Because this "copy" could be called recursively, // "info" MUST be given here, NOT nil. func copyNextOrSkip(src, dest string, info os.FileInfo, opt Options) error { - skip, err := opt.Skip(src) - if err != nil { - return err - } - if skip { - return nil + if opt.Skip != nil { + skip, err := opt.Skip(info, src, dest) + if err != nil { + return err + } + if skip { + return nil + } } return switchboard(src, dest, info, opt) } @@ -66,6 +87,20 @@ func copyNextOrSkip(src, dest string, info os.FileInfo, opt Options) error { // and file permission. func fcopy(src, dest string, info os.FileInfo, opt Options) (err error) { + var readcloser io.ReadCloser + if opt.FS != nil { + readcloser, err = opt.FS.Open(src) + } else { + readcloser, err = os.Open(src) + } + if err != nil { + if os.IsNotExist(err) { + return nil + } + return + } + defer fclose(readcloser, &err) + if err = os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil { return } @@ -76,19 +111,20 @@ func fcopy(src, dest string, info os.FileInfo, opt Options) (err error) { } defer fclose(f, &err) - if err = os.Chmod(f.Name(), info.Mode()|opt.AddPermission); err != nil { - return - } - - s, err := os.Open(src) + chmodfunc, err := opt.PermissionControl(info, dest) if err != nil { - return + return err } - defer fclose(s, &err) + chmodfunc(&err) var buf []byte = nil var w io.Writer = f - // var r io.Reader = s + var r io.Reader = readcloser + + if opt.WrapReader != nil { + r = opt.WrapReader(r) + } + if opt.CopyBufferSize != 0 { buf = make([]byte, opt.CopyBufferSize) // Disable using `ReadFrom` by io.CopyBuffer. @@ -96,7 +132,8 @@ func fcopy(src, dest string, info os.FileInfo, opt Options) (err error) { w = struct{ io.Writer }{f} // r = struct{ io.Reader }{s} } - if _, err = io.CopyBuffer(w, s, buf); err != nil { + + if _, err = io.CopyBuffer(w, r, buf); err != nil { return err } @@ -122,41 +159,53 @@ func fcopy(src, dest string, info os.FileInfo, opt Options) (err error) { // with scanning contents inside the directory // and pass everything to "copy" recursively. func dcopy(srcdir, destdir string, info os.FileInfo, opt Options) (err error) { - - _, err = os.Stat(destdir) - if err == nil && opt.OnDirExists != nil && destdir != opt.intent.dest { - switch opt.OnDirExists(srcdir, destdir) { - case Replace: - if err := os.RemoveAll(destdir); err != nil { - return err - } - case Untouchable: - return nil - } // case "Merge" is default behaviour. Go through. - } else if err != nil && !os.IsNotExist(err) { - return err // Unwelcome error type...! + if skip, err := onDirExists(opt, srcdir, destdir); err != nil { + return err + } else if skip { + return nil } - originalMode := info.Mode() - // Make dest dir with 0755 so that everything writable. - if err = os.MkdirAll(destdir, tmpPermissionForDirectory); err != nil { - return + chmodfunc, err := opt.PermissionControl(info, destdir) + if err != nil { + return err } - // Recover dir mode with original one. - defer chmod(destdir, originalMode|opt.AddPermission, &err) + defer chmodfunc(&err) - contents, err := ioutil.ReadDir(srcdir) - if err != nil { - return + var entries []fs.DirEntry + if opt.FS != nil { + entries, err = fs.ReadDir(opt.FS, srcdir) + if err != nil { + return err + } + } else { + entries, err = os.ReadDir(srcdir) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } } - for _, content := range contents { - cs, cd := filepath.Join(srcdir, content.Name()), filepath.Join(destdir, content.Name()) + contents := make([]fs.FileInfo, 0, len(entries)) + for _, e := range entries { + info, err := e.Info() + if err != nil { + return err + } + contents = append(contents, info) + } - if err = copyNextOrSkip(cs, cd, content, opt); err != nil { - // If any error, exit immediately - return + if yes, err := shouldCopyDirectoryConcurrent(opt, srcdir, destdir); err != nil { + return err + } else if yes { + if err := dcopyConcurrent(srcdir, destdir, contents, opt); err != nil { + return err + } + } else { + if err := dcopySequential(srcdir, destdir, contents, opt); err != nil { + return err } } @@ -175,15 +224,78 @@ func dcopy(srcdir, destdir string, info os.FileInfo, opt Options) (err error) { return } +func dcopySequential(srcdir, destdir string, contents []os.FileInfo, opt Options) error { + for _, content := range contents { + cs, cd := filepath.Join(srcdir, content.Name()), filepath.Join(destdir, content.Name()) + + if err := copyNextOrSkip(cs, cd, content, opt); err != nil { + // If any error, exit immediately + return err + } + } + return nil +} + +// Copy this directory concurrently regarding semaphore of opt.intent +func dcopyConcurrent(srcdir, destdir string, contents []os.FileInfo, opt Options) error { + group, ctx := errgroup.WithContext(opt.intent.ctx) + getRoutine := func(cs, cd string, content os.FileInfo) func() error { + return func() error { + if content.IsDir() { + return copyNextOrSkip(cs, cd, content, opt) + } + if err := opt.intent.sem.Acquire(ctx, 1); err != nil { + return err + } + err := copyNextOrSkip(cs, cd, content, opt) + opt.intent.sem.Release(1) + return err + } + } + for _, content := range contents { + csd := filepath.Join(srcdir, content.Name()) + cdd := filepath.Join(destdir, content.Name()) + group.Go(getRoutine(csd, cdd, content)) + } + return group.Wait() +} + +func onDirExists(opt Options, srcdir, destdir string) (bool, error) { + _, err := os.Stat(destdir) + if err == nil && opt.OnDirExists != nil && destdir != opt.intent.dest { + switch opt.OnDirExists(srcdir, destdir) { + case Replace: + if err := os.RemoveAll(destdir); err != nil { + return false, err + } + case Untouchable: + return true, nil + } // case "Merge" is default behaviour. Go through. + } else if err != nil && !os.IsNotExist(err) { + return true, err // Unwelcome error type...! + } + return false, nil +} + func onsymlink(src, dest string, opt Options) error { switch opt.OnSymlink(src) { case Shallow: - return lcopy(src, dest) + if err := lcopy(src, dest); err != nil { + return err + } + if opt.PreserveTimes { + return preserveLtimes(src, dest) + } + return nil case Deep: orig, err := os.Readlink(src) if err != nil { return err } + if !filepath.IsAbs(orig) { + // orig is a relative link: need to add src dir to orig + orig = filepath.Join(filepath.Dir(src), orig) + } info, err := os.Lstat(orig) if err != nil { return err @@ -199,45 +311,42 @@ func onsymlink(src, dest string, opt Options) error { // lcopy is for a symlink, // with just creating a new symlink by replicating src symlink. func lcopy(src, dest string) error { - src, err := os.Readlink(src) + orig, err := os.Readlink(src) + // @See https://github.com/otiai10/copy/issues/111 + // TODO: This might be controlled by Options in the future. if err != nil { + if os.IsNotExist(err) { // Copy symlink even if not existing + return os.Symlink(src, dest) + } return err } - return os.Symlink(src, dest) + + // @See https://github.com/otiai10/copy/issues/132 + // TODO: Control by SymlinkExistsAction + if _, err := os.Lstat(dest); err == nil { + if err := os.Remove(dest); err != nil { + return err + } + } + + return os.Symlink(orig, dest) } // fclose ANYHOW closes file, -// with asiging error raised during Close, +// with assigning error raised during Close, // BUT respecting the error already reported. -func fclose(f *os.File, reported *error) { +func fclose(f io.Closer, reported *error) { if err := f.Close(); *reported == nil { *reported = err } } -// chmod ANYHOW changes file mode, -// with asiging error raised during Chmod, -// BUT respecting the error already reported. -func chmod(dir string, mode os.FileMode, reported *error) { - if err := os.Chmod(dir, mode); *reported == nil { - *reported = err +// onError lets caller to handle errors +// occurred when copying a file. +func onError(src, dest string, err error, opt Options) error { + if opt.OnError == nil { + return err } -} -// assure Options struct, should be called only once. -// All optional values MUST NOT BE nil/zero after assured. -func assure(src, dest string, opts ...Options) Options { - defopt := getDefaultOptions(src, dest) - if len(opts) == 0 { - return defopt - } - if opts[0].OnSymlink == nil { - opts[0].OnSymlink = defopt.OnSymlink - } - if opts[0].Skip == nil { - opts[0].Skip = defopt.Skip - } - opts[0].intent.src = defopt.intent.src - opts[0].intent.dest = defopt.intent.dest - return opts[0] + return opt.OnError(src, dest, err) } diff --git a/vendor/github.com/otiai10/copy/copy_namedpipes.go b/vendor/github.com/otiai10/copy/copy_namedpipes.go index 48784e7640..657fb38125 100644 --- a/vendor/github.com/otiai10/copy/copy_namedpipes.go +++ b/vendor/github.com/otiai10/copy/copy_namedpipes.go @@ -1,4 +1,4 @@ -// +build !windows,!plan9,!netbsd,!aix,!illumos,!solaris +//go:build !windows && !plan9 && !netbsd && !aix && !illumos && !solaris && !js package copy diff --git a/vendor/github.com/otiai10/copy/copy_namedpipes_x.go b/vendor/github.com/otiai10/copy/copy_namedpipes_x.go index 2f6c74740a..da3d6f7967 100644 --- a/vendor/github.com/otiai10/copy/copy_namedpipes_x.go +++ b/vendor/github.com/otiai10/copy/copy_namedpipes_x.go @@ -1,4 +1,4 @@ -// +build windows plan9 netbsd aix illumos solaris +//go:build windows || plan9 || netbsd || aix || illumos || solaris || js package copy diff --git a/vendor/github.com/otiai10/copy/fileinfo.go b/vendor/github.com/otiai10/copy/fileinfo.go deleted file mode 100644 index 0b32abeb40..0000000000 --- a/vendor/github.com/otiai10/copy/fileinfo.go +++ /dev/null @@ -1,12 +0,0 @@ -package copy - -// This is a cloned definition of os.FileInfo (go1.15) or fs.FileInfo (go1.16~) -// A FileInfo describes a file and is returned by Stat. -type fileInfo interface { - // Name() string // base name of the file - // Size() int64 // length in bytes for regular files; system-dependent for others - // Mode() FileMode // file mode bits - // ModTime() time.Time // modification time - // IsDir() bool // abbreviation for Mode().IsDir() - Sys() interface{} // underlying data source (can return nil) -} diff --git a/vendor/github.com/otiai10/copy/options.go b/vendor/github.com/otiai10/copy/options.go index f598fc10de..c1db48c8cf 100644 --- a/vendor/github.com/otiai10/copy/options.go +++ b/vendor/github.com/otiai10/copy/options.go @@ -1,6 +1,13 @@ package copy -import "os" +import ( + "context" + "io" + "io/fs" + "os" + + "golang.org/x/sync/semaphore" +) // Options specifies optional actions on copying. type Options struct { @@ -11,13 +18,32 @@ type Options struct { // OnDirExists can specify what to do when there is a directory already existing in destination. OnDirExists func(src, dest string) DirExistsAction + // OnErr lets called decide whether or not to continue on particular copy error. + OnError func(src, dest string, err error) error + // Skip can specify which files should be skipped - Skip func(src string) (bool, error) + Skip func(srcinfo os.FileInfo, src, dest string) (bool, error) + + // RenameDestination can specify the destination file or dir name if needed to rename. + RenameDestination func(src, dest string) (string, error) + + // Specials includes special files to be copied. default false. + Specials bool // AddPermission to every entities, // NO MORE THAN 0777 + // @OBSOLETE + // Use `PermissionControl = AddPermission(perm)` instead AddPermission os.FileMode + // PermissionControl can preserve or even add permission to + // every entries, for example + // + // opt.PermissionControl = AddPermission(0222) + // + // See permission_control.go for more detail. + PermissionControl PermissionControlFunc + // Sync file after copy. // Useful in case when file must be on the disk // (in case crash happens, for example), @@ -36,10 +62,37 @@ type Options struct { // See https://golang.org/pkg/io/#CopyBuffer for more information. CopyBufferSize uint - intent struct { - src string - dest string - } + // If you want to add some limitation on reading src file, + // you can wrap the src and provide new reader, + // such as `RateLimitReader` in the test case. + WrapReader func(src io.Reader) io.Reader + + // If given, copy.Copy refers to this fs.FS instead of the OS filesystem. + // e.g., You can use embed.FS to copy files from embedded filesystem. + FS fs.FS + + // NumOfWorkers represents the number of workers used for + // concurrent copying contents of directories. + // If 0 or 1, it does not use goroutine for copying directories. + // Please refer to https://pkg.go.dev/golang.org/x/sync/semaphore for more details. + NumOfWorkers int64 + + // PreferConcurrent is a function to determine whether or not + // to use goroutine for copying contents of directories. + // If PreferConcurrent is nil, which is default, it does concurrent + // copying for all directories. + // If NumOfWorkers is 0 or 1, this function will be ignored. + PreferConcurrent func(srcdir, destdir string) (bool, error) + + // Internal use only + intent intent +} + +type intent struct { + src string + dest string + sem *semaphore.Weighted + ctx context.Context } // SymlinkAction represents what to do on symlink. @@ -73,17 +126,49 @@ func getDefaultOptions(src, dest string) Options { OnSymlink: func(string) SymlinkAction { return Shallow // Do shallow copy }, - OnDirExists: nil, // Default behavior is "Merge". - Skip: func(string) (bool, error) { - return false, nil // Don't skip - }, - AddPermission: 0, // Add nothing - Sync: false, // Do not sync - PreserveTimes: false, // Do not preserve the modification time - CopyBufferSize: 0, // Do not specify, use default bufsize (32*1024) - intent: struct { - src string - dest string - }{src, dest}, + OnDirExists: nil, // Default behavior is "Merge". + OnError: nil, // Default is "accept error" + Skip: nil, // Do not skip anything + AddPermission: 0, // Add nothing + PermissionControl: PerservePermission, // Just preserve permission + Sync: false, // Do not sync + Specials: false, // Do not copy special files + PreserveTimes: false, // Do not preserve the modification time + CopyBufferSize: 0, // Do not specify, use default bufsize (32*1024) + WrapReader: nil, // Do not wrap src files, use them as they are. + intent: intent{src, dest, nil, nil}, + } +} + +// assureOptions struct, should be called only once. +// All optional values MUST NOT BE nil/zero after assured. +func assureOptions(src, dest string, opts ...Options) Options { + defopt := getDefaultOptions(src, dest) + if len(opts) == 0 { + return defopt + } + if opts[0].OnSymlink == nil { + opts[0].OnSymlink = defopt.OnSymlink + } + if opts[0].Skip == nil { + opts[0].Skip = defopt.Skip + } + if opts[0].AddPermission > 0 { + opts[0].PermissionControl = AddPermission(opts[0].AddPermission) + } else if opts[0].PermissionControl == nil { + opts[0].PermissionControl = PerservePermission + } + opts[0].intent.src = defopt.intent.src + opts[0].intent.dest = defopt.intent.dest + return opts[0] +} + +func shouldCopyDirectoryConcurrent(opt Options, srcdir, destdir string) (bool, error) { + if opt.NumOfWorkers <= 1 { + return false, nil + } + if opt.PreferConcurrent == nil { + return true, nil } + return opt.PreferConcurrent(srcdir, destdir) } diff --git a/vendor/github.com/otiai10/copy/permission_control.go b/vendor/github.com/otiai10/copy/permission_control.go new file mode 100644 index 0000000000..901a84514e --- /dev/null +++ b/vendor/github.com/otiai10/copy/permission_control.go @@ -0,0 +1,49 @@ +package copy + +import ( + "io/fs" + "os" +) + +const ( + // tmpPermissionForDirectory makes the destination directory writable, + // so that stuff can be copied recursively even if any original directory is NOT writable. + // See https://github.com/otiai10/copy/pull/9 for more information. + tmpPermissionForDirectory = os.FileMode(0755) +) + +type PermissionControlFunc func(srcinfo fs.FileInfo, dest string) (chmodfunc func(*error), err error) + +var ( + AddPermission = func(perm os.FileMode) PermissionControlFunc { + return func(srcinfo fs.FileInfo, dest string) (func(*error), error) { + orig := srcinfo.Mode() + if srcinfo.IsDir() { + if err := os.MkdirAll(dest, tmpPermissionForDirectory); err != nil { + return func(*error) {}, err + } + } + return func(err *error) { + chmod(dest, orig|perm, err) + }, nil + } + } + PerservePermission PermissionControlFunc = AddPermission(0) + DoNothing PermissionControlFunc = func(srcinfo fs.FileInfo, dest string) (func(*error), error) { + if srcinfo.IsDir() { + if err := os.MkdirAll(dest, srcinfo.Mode()); err != nil { + return func(*error) {}, err + } + } + return func(*error) {}, nil + } +) + +// chmod ANYHOW changes file mode, +// with assigning error raised during Chmod, +// BUT respecting the error already reported. +func chmod(dir string, mode os.FileMode, reported *error) { + if err := os.Chmod(dir, mode); *reported == nil { + *reported = err + } +} diff --git a/vendor/github.com/otiai10/copy/preserve_ltimes.go b/vendor/github.com/otiai10/copy/preserve_ltimes.go new file mode 100644 index 0000000000..6b6787b2af --- /dev/null +++ b/vendor/github.com/otiai10/copy/preserve_ltimes.go @@ -0,0 +1,19 @@ +//go:build !windows && !plan9 && !js + +package copy + +import ( + "golang.org/x/sys/unix" +) + +func preserveLtimes(src, dest string) error { + info := new(unix.Stat_t) + if err := unix.Lstat(src, info); err != nil { + return err + } + + return unix.Lutimes(dest, []unix.Timeval{ + unix.NsecToTimeval(info.Atim.Nano()), + unix.NsecToTimeval(info.Mtim.Nano()), + }) +} diff --git a/vendor/github.com/otiai10/copy/preserve_ltimes_x.go b/vendor/github.com/otiai10/copy/preserve_ltimes_x.go new file mode 100644 index 0000000000..5ef234d563 --- /dev/null +++ b/vendor/github.com/otiai10/copy/preserve_ltimes_x.go @@ -0,0 +1,7 @@ +//go:build windows || js || plan9 + +package copy + +func preserveLtimes(src, dest string) error { + return nil // Unsupported +} diff --git a/vendor/github.com/otiai10/copy/preserve_owner.go b/vendor/github.com/otiai10/copy/preserve_owner.go index 0401d9f27c..bd129644f9 100644 --- a/vendor/github.com/otiai10/copy/preserve_owner.go +++ b/vendor/github.com/otiai10/copy/preserve_owner.go @@ -1,13 +1,14 @@ -//+build !windows +//go:build !windows && !plan9 package copy import ( + "io/fs" "os" "syscall" ) -func preserveOwner(src, dest string, info fileInfo) (err error) { +func preserveOwner(src, dest string, info fs.FileInfo) (err error) { if info == nil { if info, err = os.Stat(src); err != nil { return err diff --git a/vendor/github.com/otiai10/copy/preserve_owner_windows.go b/vendor/github.com/otiai10/copy/preserve_owner_windows.go deleted file mode 100644 index 4ebb5cf1dd..0000000000 --- a/vendor/github.com/otiai10/copy/preserve_owner_windows.go +++ /dev/null @@ -1,7 +0,0 @@ -//+build windows - -package copy - -func preserveOwner(src, dest string, info fileInfo) (err error) { - return nil -} diff --git a/vendor/github.com/otiai10/copy/preserve_owner_x.go b/vendor/github.com/otiai10/copy/preserve_owner_x.go new file mode 100644 index 0000000000..1e8f1251da --- /dev/null +++ b/vendor/github.com/otiai10/copy/preserve_owner_x.go @@ -0,0 +1,9 @@ +//go:build windows || plan9 + +package copy + +import "io/fs" + +func preserveOwner(src, dest string, info fs.FileInfo) (err error) { + return nil +} diff --git a/vendor/github.com/otiai10/copy/stat_times.go b/vendor/github.com/otiai10/copy/stat_times.go index e72de944da..49ea67c27e 100644 --- a/vendor/github.com/otiai10/copy/stat_times.go +++ b/vendor/github.com/otiai10/copy/stat_times.go @@ -1,4 +1,4 @@ -// +build !windows,!darwin,!freebsd,!plan9,!netbsd +//go:build !windows && !darwin && !freebsd && !plan9 && !netbsd && !js // TODO: add more runtimes diff --git a/vendor/github.com/otiai10/copy/stat_times_darwin.go b/vendor/github.com/otiai10/copy/stat_times_darwin.go index ce7a7fbc7f..935ce1d798 100644 --- a/vendor/github.com/otiai10/copy/stat_times_darwin.go +++ b/vendor/github.com/otiai10/copy/stat_times_darwin.go @@ -1,4 +1,4 @@ -// +build darwin +//go:build darwin package copy diff --git a/vendor/github.com/otiai10/copy/stat_times_freebsd.go b/vendor/github.com/otiai10/copy/stat_times_freebsd.go index 115f1388b4..1deb1cc4eb 100644 --- a/vendor/github.com/otiai10/copy/stat_times_freebsd.go +++ b/vendor/github.com/otiai10/copy/stat_times_freebsd.go @@ -1,4 +1,4 @@ -// +build freebsd +//go:build freebsd package copy diff --git a/vendor/github.com/otiai10/copy/stat_times_js.go b/vendor/github.com/otiai10/copy/stat_times_js.go new file mode 100644 index 0000000000..a4b1e288f4 --- /dev/null +++ b/vendor/github.com/otiai10/copy/stat_times_js.go @@ -0,0 +1,19 @@ +//go:build js + +package copy + +import ( + "os" + "syscall" + "time" +) + +func getTimeSpec(info os.FileInfo) timespec { + stat := info.Sys().(*syscall.Stat_t) + times := timespec{ + Mtime: info.ModTime(), + Atime: time.Unix(int64(stat.Atime), int64(stat.AtimeNsec)), + Ctime: time.Unix(int64(stat.Ctime), int64(stat.CtimeNsec)), + } + return times +} diff --git a/vendor/github.com/otiai10/copy/stat_times_windows.go b/vendor/github.com/otiai10/copy/stat_times_windows.go index 113a2ece58..babfe7d9d9 100644 --- a/vendor/github.com/otiai10/copy/stat_times_windows.go +++ b/vendor/github.com/otiai10/copy/stat_times_windows.go @@ -1,4 +1,4 @@ -// +build windows +//go:build windows package copy diff --git a/vendor/github.com/otiai10/copy/stat_times_x.go b/vendor/github.com/otiai10/copy/stat_times_x.go index 7b2d1d44f6..53da32e2cc 100644 --- a/vendor/github.com/otiai10/copy/stat_times_x.go +++ b/vendor/github.com/otiai10/copy/stat_times_x.go @@ -1,4 +1,4 @@ -// +build plan9 netbsd +//go:build plan9 || netbsd package copy diff --git a/vendor/github.com/otiai10/copy/symlink_test_x.go b/vendor/github.com/otiai10/copy/symlink_test_x.go new file mode 100644 index 0000000000..1f6bb1f46c --- /dev/null +++ b/vendor/github.com/otiai10/copy/symlink_test_x.go @@ -0,0 +1,45 @@ +//go:build windows || plan9 || netbsd || aix || illumos || solaris || js + +package copy + +import ( + "os" + "testing" + + . "github.com/otiai10/mint" +) + +func TestOptions_OnSymlink(t *testing.T) { + opt := Options{OnSymlink: func(string) SymlinkAction { return Deep }} + err := Copy("test/data/case03", "test/data.copy/case03.deep", opt) + Expect(t, err).ToBe(nil) + info, err := os.Lstat("test/data.copy/case03.deep/case01") + Expect(t, err).ToBe(nil) + Expect(t, info.Mode()&os.ModeSymlink).ToBe(os.FileMode(0)) + + opt = Options{OnSymlink: func(string) SymlinkAction { return Shallow }} + err = Copy("test/data/case03", "test/data.copy/case03.shallow", opt) + Expect(t, err).ToBe(nil) + info, err = os.Lstat("test/data.copy/case03.shallow/case01") + Expect(t, err).ToBe(nil) + Expect(t, info.Mode()&os.ModeSymlink).Not().ToBe(os.FileMode(0)) + + opt = Options{OnSymlink: func(string) SymlinkAction { return Skip }} + err = Copy("test/data/case03", "test/data.copy/case03.skip", opt) + Expect(t, err).ToBe(nil) + _, err = os.Stat("test/data.copy/case03.skip/case01") + Expect(t, os.IsNotExist(err)).ToBe(true) + + err = Copy("test/data/case03", "test/data.copy/case03.default") + Expect(t, err).ToBe(nil) + info, err = os.Lstat("test/data.copy/case03.default/case01") + Expect(t, err).ToBe(nil) + Expect(t, info.Mode()&os.ModeSymlink).Not().ToBe(os.FileMode(0)) + + opt = Options{OnSymlink: nil} + err = Copy("test/data/case03", "test/data.copy/case03.not-specified", opt) + Expect(t, err).ToBe(nil) + info, err = os.Lstat("test/data.copy/case03.not-specified/case01") + Expect(t, err).ToBe(nil) + Expect(t, info.Mode()&os.ModeSymlink).Not().ToBe(os.FileMode(0)) +} diff --git a/vendor/github.com/otiai10/copy/test_setup.go b/vendor/github.com/otiai10/copy/test_setup.go deleted file mode 100644 index a30edb7b70..0000000000 --- a/vendor/github.com/otiai10/copy/test_setup.go +++ /dev/null @@ -1,17 +0,0 @@ -// +build !windows,!plan9,!netbsd,!aix,!illumos,!solaris - -package copy - -import ( - "os" - "syscall" - "testing" -) - -func setup(m *testing.M) { - os.MkdirAll("test/data.copy", os.ModePerm) - os.Symlink("test/data/case01", "test/data/case03/case01") - os.Chmod("test/data/case07/dir_0555", 0555) - os.Chmod("test/data/case07/file_0444", 0444) - syscall.Mkfifo("test/data/case11/foo/bar", 0555) -} diff --git a/vendor/github.com/otiai10/copy/test_setup_x.go b/vendor/github.com/otiai10/copy/test_setup_x.go deleted file mode 100644 index e4b44c10e0..0000000000 --- a/vendor/github.com/otiai10/copy/test_setup_x.go +++ /dev/null @@ -1,15 +0,0 @@ -// +build windows plan9 netbsd aix illumos solaris - -package copy - -import ( - "os" - "testing" -) - -func setup(m *testing.M) { - os.MkdirAll("test/data.copy", os.ModePerm) - os.Symlink("test/data/case01", "test/data/case03/case01") - os.Chmod("test/data/case07/dir_0555", 0555) - os.Chmod("test/data/case07/file_0444", 0444) -} diff --git a/vendor/github.com/otiai10/mint/.gitignore b/vendor/github.com/otiai10/mint/.gitignore new file mode 100644 index 0000000000..6ae51791ef --- /dev/null +++ b/vendor/github.com/otiai10/mint/.gitignore @@ -0,0 +1,2 @@ +coverage.txt +vendor diff --git a/vendor/github.com/otiai10/mint/LICENSE b/vendor/github.com/otiai10/mint/LICENSE new file mode 100644 index 0000000000..a5bad7fc46 --- /dev/null +++ b/vendor/github.com/otiai10/mint/LICENSE @@ -0,0 +1,7 @@ +Copyright 2017 otiai10 (Hiromu OCHIAI) + +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/otiai10/mint/README.md b/vendor/github.com/otiai10/mint/README.md new file mode 100644 index 0000000000..06caae0399 --- /dev/null +++ b/vendor/github.com/otiai10/mint/README.md @@ -0,0 +1,62 @@ +# mint + +[![Go](https://github.com/otiai10/mint/actions/workflows/go.yml/badge.svg)](https://github.com/otiai10/mint/actions/workflows/go.yml) +[![codecov](https://codecov.io/gh/otiai10/mint/branch/master/graph/badge.svg)](https://codecov.io/gh/otiai10/mint) +[![Go Report Card](https://goreportcard.com/badge/github.com/otiai10/mint)](https://goreportcard.com/report/github.com/otiai10/mint) +[![GoDoc](https://godoc.org/github.com/otiai10/mint?status.png)](https://godoc.org/github.com/otiai10/mint) +[![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/otiai10/mint?sort=semver)](https://pkg.go.dev/github.com/otiai10/mint) +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fotiai10%2Fmint.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fotiai10%2Fmint?ref=badge_shield) + +The very minimum assertion for Go. + +```go +package your_test + +import ( + "testing" + "pkg/your" + . "github.com/otiai10/mint" +) + +func TestFoo(t *testing.T) { + + foo := your.Foo() + Expect(t, foo).ToBe(1234) + Expect(t, foo).TypeOf("int") + Expect(t, foo).Not().ToBe(nil) + Expect(t, func() { yourFunc() }).Exit(1) + + // If assertion failed, exit 1 with message. + Expect(t, foo).ToBe("foobarbuz") + + // You can run assertions without os.Exit + res := Expect(t, foo).Dry().ToBe("bar") + // res.OK() == false + + // You can omit repeated `t`. + m := mint.Blend(t) + m.Expect(foo).ToBe(1234) +} +``` + +# features + +- Simple syntax +- Loosely coupled +- Plain implementation + +# tests +``` +go test ./... +``` + +# use cases + +Projects bellow use `mint` + +- [github.com/otiai10/gosseract](https://github.com/otiai10/gosseract/blob/master/all_test.go) +- [github.com/otiai10/marmoset](https://github.com/otiai10/marmoset/blob/master/all_test.go#L168-L190) + + +## License +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fotiai10%2Fmint.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fotiai10%2Fmint?ref=badge_large) \ No newline at end of file diff --git a/vendor/github.com/otiai10/mint/because.go b/vendor/github.com/otiai10/mint/because.go new file mode 100644 index 0000000000..6d496cee75 --- /dev/null +++ b/vendor/github.com/otiai10/mint/because.go @@ -0,0 +1,15 @@ +package mint + +import "testing" + +// Because is context printer. +func Because(t *testing.T, context string, wrapper func(*testing.T)) { + Log(" Because ", context, "\n") + wrapper(t) +} + +// When is an alternative of `Because` +func When(t *testing.T, context string, wrapper func(*testing.T)) { + Log(" When ", context, "\n") + wrapper(t) +} diff --git a/vendor/github.com/otiai10/mint/comparer.go b/vendor/github.com/otiai10/mint/comparer.go new file mode 100644 index 0000000000..d543eb6d52 --- /dev/null +++ b/vendor/github.com/otiai10/mint/comparer.go @@ -0,0 +1,53 @@ +package mint + +import ( + "fmt" + "reflect" +) + +func getComparer(a, b interface{}, deeply bool) Comparer { + if deeply { + return deepComparer{} + } + switch reflect.ValueOf(a).Kind() { + case reflect.Slice: + return sliceComparer{} + case reflect.Map: + return mapComparer{} + } + if b == nil { + return nilComparer{} + } + return defaultComparer{} +} + +type Comparer interface { + Compare(a, b interface{}) bool +} + +type defaultComparer struct{} + +func (c defaultComparer) Compare(a, b interface{}) bool { + return a == b +} + +type deepComparer struct{} + +func (c deepComparer) Compare(a, b interface{}) bool { + return reflect.DeepEqual(a, b) +} + +type mapComparer struct { + deepComparer +} + +type sliceComparer struct { + deepComparer +} + +type nilComparer struct { +} + +func (c nilComparer) Compare(a, _ interface{}) bool { + return fmt.Sprintf("%v", a) == fmt.Sprintf("%v", nil) +} diff --git a/vendor/github.com/otiai10/mint/exit.go b/vendor/github.com/otiai10/mint/exit.go new file mode 100644 index 0000000000..fc64ac9636 --- /dev/null +++ b/vendor/github.com/otiai10/mint/exit.go @@ -0,0 +1,41 @@ +//go:build !freebsd +// +build !freebsd + +package mint + +// On "freebsd/FreeBSD-10.4-STABLE" OS image, +// Go installed by `pkg install` might NOT have `syscall.Mprotect` +// causing such error: "bou.ke/monkey/replace_unix.go:13:10: undefined: syscall.Mprotect". +// See https://www.freebsd.org/cgi/man.cgi?sektion=2&query=mprotect +// TODO: Fix the image for https://github.com/otiai10/gosseract/blob/master/test/runtimes/freebsd.Vagrantfile#L4 +/* + * "bou.ke/monkey" + */ // FIXME: Now I remove this library because of LICENSE problem +// See https://github.com/otiai10/copy/issues/12 as well + +// Exit ... +func (testee *Testee) Exit(expectedCode int) MintResult { + + panic("`mint.Testee.Exit` method is temporarily deprecated.") + + /* + fun, ok := testee.actual.(func()) + if !ok { + panic("mint error: Exit only can be called for func type value") + } + + var actualCode int + patch := monkey.Patch(os.Exit, func(code int) { + actualCode = code + }) + fun() + patch.Unpatch() + + testee.actual = actualCode + if judge(actualCode, expectedCode, testee.not, testee.deeply) { + return testee.result + } + testee.expected = expectedCode + return testee.failed(failExitCode) + */ +} diff --git a/vendor/github.com/otiai10/mint/exit_freebsd.go b/vendor/github.com/otiai10/mint/exit_freebsd.go new file mode 100644 index 0000000000..d5eed6cf07 --- /dev/null +++ b/vendor/github.com/otiai10/mint/exit_freebsd.go @@ -0,0 +1,10 @@ +//go:build freebsd +// +build freebsd + +package mint + +// Exit ... +func (testee *Testee) Exit(expectedCode int) MintResult { + panic("Exit method can NOT be used on FreeBSD, for now.") + return MintResult{ok: false} +} diff --git a/vendor/github.com/otiai10/mint/log.go b/vendor/github.com/otiai10/mint/log.go new file mode 100644 index 0000000000..6aa8f8dcfb --- /dev/null +++ b/vendor/github.com/otiai10/mint/log.go @@ -0,0 +1,15 @@ +package mint + +import ( + "fmt" + "os" +) + +// Log only output if -v flag is given. +// This is because the standard "t.Testing.Log" method decorates +// its caller: runtime.Caller(3) automatically. +func Log(args ...interface{}) { + if isVerbose(os.Args) { + fmt.Print(args...) + } +} diff --git a/vendor/github.com/otiai10/mint/mint.go b/vendor/github.com/otiai10/mint/mint.go new file mode 100644 index 0000000000..a37e3c1d7b --- /dev/null +++ b/vendor/github.com/otiai10/mint/mint.go @@ -0,0 +1,86 @@ +package mint + +import ( + "os" + "testing" +) + +// Mint (mint.Mint) is wrapper for *testing.T +// blending testing type to omit repeated `t`. +type Mint struct { + t *testing.T +} + +var ( + failToBe = 0 + failType = 1 + failIn = 2 + failToMatch = 3 + failExitCode = 4 + scolds = map[int]string{ + failToBe: "%s:%d\n\tExpected %sto be\t`%+v`\n\tBut actual\t`%+v`", + failType: "%s:%d\n\tExpected %stype\t`%+v`\n\tBut actual\t`%T`", + failIn: "%s:%d\n\tExpected %sis in\t`%v`\n\tbut it's not", + failToMatch: "%s:%d\n\tExpected %v to match\t`%s`\n\tBut actual\t`%+v`", + failExitCode: "%s:%d\n\tExpected %sto exit with code `%d`\n\tBut actual\t`%d`", + } +) +var ( + redB = "\033[1;31m" + reset = "\033[0m" + colorize = map[string]func(string) string{ + "red": func(v string) string { + return redB + v + reset + }, + } +) + +// Blend provides (blended) *mint.Mint. +// You can save writing "t" repeatedly. +func Blend(t *testing.T) *Mint { + return &Mint{ + t, + } +} + +// Expect provides "*Testee". +// The blended mint is merely a proxy to instantiate testee. +func (m *Mint) Expect(actual interface{}) *Testee { + return expect(m.t, actual) +} + +// Expect provides "*mint.Testee". +// It has assertion methods such as "ToBe". +func Expect(t *testing.T, actual interface{}) *Testee { + return expect(t, actual) +} + +func expect(t *testing.T, actual interface{}) *Testee { + return &Testee{t: t, actual: actual, verbose: isVerbose(os.Args), result: MintResult{ok: true}} +} + +// Require provides "*mint.Testee", +// which stops execution of goroutine when the assertion failed. +func Require(t *testing.T, actual interface{}) *Testee { + return require(t, actual) +} + +func require(t *testing.T, actual interface{}) *Testee { + return &Testee{t: t, actual: actual, verbose: isVerbose(os.Args), required: true, result: MintResult{ok: true}} +} + +func isVerbose(flags []string) bool { + for _, f := range flags { + if f == "-test.v=true" { + return true + } + } + return false +} +func judge(a, b interface{}, not, deeply bool) bool { + comparer := getComparer(a, b, deeply) + if not { + return !comparer.Compare(a, b) + } + return comparer.Compare(a, b) +} diff --git a/vendor/github.com/otiai10/mint/mocks.go b/vendor/github.com/otiai10/mint/mocks.go new file mode 100644 index 0000000000..87feab4940 --- /dev/null +++ b/vendor/github.com/otiai10/mint/mocks.go @@ -0,0 +1,30 @@ +package mint + +import ( + "bytes" + "io/ioutil" + "net/http" +) + +type HTTPClientMock struct { + HTTPError error + ResponseStatusCode int + ResponseBody string +} + +func (hcm *HTTPClientMock) Handle() (res *http.Response, err error, ok bool) { + if hcm.HTTPError != nil { + err = hcm.HTTPError + ok = true + } + res = new(http.Response) + if hcm.ResponseBody != "" { + res.Body = ioutil.NopCloser(bytes.NewBufferString(hcm.ResponseBody)) + ok = true + } + if hcm.ResponseStatusCode != 0 { + res.StatusCode = hcm.ResponseStatusCode + ok = true + } + return res, err, ok +} diff --git a/vendor/github.com/otiai10/mint/mquery/README.md b/vendor/github.com/otiai10/mint/mquery/README.md new file mode 100644 index 0000000000..4992930de4 --- /dev/null +++ b/vendor/github.com/otiai10/mint/mquery/README.md @@ -0,0 +1,31 @@ +mquery +=== + +```go +import mquery + +var m = map[string]interface{}{ + "foo": "bar", + "hoge": map[string]interface{}{ + "name": "otiai10", + }, + "fuga": map[int]map[string]interface{}{ + 0: {"greet": "Hello"}, + 1: {"greet": "こんにちは"}, + }, + "langs": []string{"Go", "JavaScript", "English"}, + "baz": nil, + "required": false, +} + +func main() { + fmt.Println( + Query(m, "foo"), // "bar" + Query(m, "hoge.name"), // "otiai10" + Query(m, "fuga.0.greet"), // "Hello" + Query(m, "langs.2"), // "English" + Query(m, "required"), // false + Query(m, "baz.biz"), // nil + ) +} +``` \ No newline at end of file diff --git a/vendor/github.com/otiai10/mint/mquery/mquery.go b/vendor/github.com/otiai10/mint/mquery/mquery.go new file mode 100644 index 0000000000..2a7ddbac39 --- /dev/null +++ b/vendor/github.com/otiai10/mint/mquery/mquery.go @@ -0,0 +1,72 @@ +package mquery + +import ( + "fmt" + "reflect" + "strconv" + "strings" +) + +func Query(m interface{}, q string) interface{} { + return query(m, strings.Split(q, ".")) +} + +func query(m interface{}, qs []string) interface{} { + t := reflect.TypeOf(m) + switch t.Kind() { + case reflect.Map: + return queryMap(m, t, qs) + case reflect.Slice: + return querySlice(m, t, qs) + default: + return m + } +} + +func queryMap(m interface{}, t reflect.Type, qs []string) interface{} { + if len(qs) == 0 { + return m + } + val := reflect.ValueOf(m) + if val.IsZero() { + return nil + } + switch t.Key().Kind() { + case reflect.String: + val := reflect.ValueOf(m).MapIndex(reflect.ValueOf(qs[0])) + if !val.IsValid() { + return nil + } + return query(val.Interface(), qs[1:]) + case reflect.Int: + i, err := strconv.Atoi(qs[0]) + if err != nil { + return fmt.Errorf("cannot access map with keyword: %s: %v", qs[0], err) + } + val := reflect.ValueOf(m).MapIndex(reflect.ValueOf(i)) + if !val.IsValid() { + return nil + } + return query(val.Interface(), qs[1:]) + } + return nil +} + +func querySlice(m interface{}, t reflect.Type, qs []string) interface{} { + if len(qs) == 0 { + return m + } + v := reflect.ValueOf(m) + if v.Len() == 0 { + return nil + } + i, err := strconv.Atoi(qs[0]) + if err != nil { + return fmt.Errorf("cannot access slice with keyword: %s: %v", qs[0], err) + } + if v.Len() <= i { + return nil + } + next := v.Index(i).Interface() + return query(next, qs[1:]) +} diff --git a/vendor/github.com/otiai10/mint/result.go b/vendor/github.com/otiai10/mint/result.go new file mode 100644 index 0000000000..2ce38c0564 --- /dev/null +++ b/vendor/github.com/otiai10/mint/result.go @@ -0,0 +1,23 @@ +package mint + +// MintResult provide the results of assertion +// for `Dry` option. +type MintResult struct { + ok bool + message string +} + +// OK returns whether result is ok or not. +func (r MintResult) OK() bool { + return r.ok +} + +// NG is the opposite alias for OK(). +func (r MintResult) NG() bool { + return !r.ok +} + +// Message returns failure message. +func (r MintResult) Message() string { + return r.message +} diff --git a/vendor/github.com/otiai10/mint/testee.go b/vendor/github.com/otiai10/mint/testee.go new file mode 100644 index 0000000000..90537fc3b2 --- /dev/null +++ b/vendor/github.com/otiai10/mint/testee.go @@ -0,0 +1,145 @@ +package mint + +import ( + "fmt" + "path/filepath" + "reflect" + "regexp" + "runtime" + "testing" + + "github.com/otiai10/mint/mquery" +) + +// Testee is holder of interfaces which user want to assert +// and also has its result. +type Testee struct { + t *testing.T + actual interface{} + expected interface{} + dry bool + not bool + deeply bool + result MintResult + required bool + verbose bool + + // origin string // Only used when querying +} + +// Query queries the actual value with given query string. +func (testee *Testee) Query(query string) *Testee { + // testee.origin = fmt.Sprintf("%T", testee.actual) + testee.actual = mquery.Query(testee.actual, query) + return testee +} + +// ToBe can assert the testee to equal the parameter of this func. +// OS will exit with code 1, when the assertion fail. +// If you don't want to exit, see "Dry()". +func (testee *Testee) ToBe(expected interface{}) MintResult { + if judge(testee.actual, expected, testee.not, testee.deeply) { + return testee.result + } + testee.expected = expected + return testee.failed(failToBe) +} + +// Match can assert the testee to match with specified regular expression. +// It uses `regexp.MustCompile`, it's due to caller to make sure it's valid regexp. +// OS will exit with code 1, when the assertion fail. +// If you don't want to exit, see "Dry()". +func (testee *Testee) Match(expression string) MintResult { + exp := regexp.MustCompile(expression) + matched := exp.MatchString(fmt.Sprintf("%v", testee.actual)) + if judge(matched, true, testee.not, testee.deeply) { + return testee.result + } + testee.expected = expression + return testee.failed(failToMatch) +} + +// In can assert the testee is in given array. +func (testee *Testee) In(expecteds ...interface{}) MintResult { + for _, expected := range expecteds { + if judge(testee.actual, expected, testee.not, testee.deeply) { + return testee.result + } + } + testee.expected = expecteds + return testee.failed(failIn) +} + +// TypeOf can assert the type of testee to equal the parameter of this func. +// OS will exit with code 1, when the assertion fail. +// If you don't want to exit, see "Dry()". +func (testee *Testee) TypeOf(typeName string) MintResult { + if judge(reflect.TypeOf(testee.actual).String(), typeName, testee.not, testee.deeply) { + return testee.result + } + testee.expected = typeName + return testee.failed(failType) +} + +// Not makes following assertion conversed. +func (testee *Testee) Not() *Testee { + testee.not = true + return testee +} + +// Dry makes the testee NOT to call "Fail()". +// Use this if you want to fail test in a purpose. +func (testee *Testee) Dry() *Testee { + testee.dry = true + return testee +} + +// Deeply makes following assertions use `reflect.DeepEqual`. +// You had better use this to compare reference type objects. +func (testee *Testee) Deeply() *Testee { + testee.deeply = true + return testee +} + +func (testee *Testee) failed(failure int) MintResult { + message := testee.toText(failure) + testee.result.ok = false + testee.result.message = message + if !testee.dry { + fmt.Println(colorize["red"](message)) + if testee.required { + testee.t.FailNow() + } else { + testee.t.Fail() + } + } + return testee.result +} + +func (testee *Testee) toText(fail int) string { + not := "" + if testee.not { + not = "NOT " + } + _, file, line, _ := runtime.Caller(3) + // if testee.origin != "" { + // testee.origin = fmt.Sprintf("(queried from %s)", testee.origin) + // } + return fmt.Sprintf( + scolds[fail], + filepath.Base(file), line, + not, + testee.expected, + testee.actual, + ) +} + +// Log only output if -v flag is given. +// This is because the standard "t.Testing.Log" method decorates +// its caller: runtime.Caller(3) automatically. +func (testee *Testee) Log(args ...interface{}) { + if !testee.verbose { + return + } + fmt.Print(args...) +} diff --git a/vendor/golang.org/x/sync/semaphore/semaphore.go b/vendor/golang.org/x/sync/semaphore/semaphore.go new file mode 100644 index 0000000000..b618162aab --- /dev/null +++ b/vendor/golang.org/x/sync/semaphore/semaphore.go @@ -0,0 +1,160 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package semaphore provides a weighted semaphore implementation. +package semaphore // import "golang.org/x/sync/semaphore" + +import ( + "container/list" + "context" + "sync" +) + +type waiter struct { + n int64 + ready chan<- struct{} // Closed when semaphore acquired. +} + +// NewWeighted creates a new weighted semaphore with the given +// maximum combined weight for concurrent access. +func NewWeighted(n int64) *Weighted { + w := &Weighted{size: n} + return w +} + +// Weighted provides a way to bound concurrent access to a resource. +// The callers can request access with a given weight. +type Weighted struct { + size int64 + cur int64 + mu sync.Mutex + waiters list.List +} + +// Acquire acquires the semaphore with a weight of n, blocking until resources +// are available or ctx is done. On success, returns nil. On failure, returns +// ctx.Err() and leaves the semaphore unchanged. +func (s *Weighted) Acquire(ctx context.Context, n int64) error { + done := ctx.Done() + + s.mu.Lock() + select { + case <-done: + // ctx becoming done has "happened before" acquiring the semaphore, + // whether it became done before the call began or while we were + // waiting for the mutex. We prefer to fail even if we could acquire + // the mutex without blocking. + s.mu.Unlock() + return ctx.Err() + default: + } + if s.size-s.cur >= n && s.waiters.Len() == 0 { + // Since we hold s.mu and haven't synchronized since checking done, if + // ctx becomes done before we return here, it becoming done must have + // "happened concurrently" with this call - it cannot "happen before" + // we return in this branch. So, we're ok to always acquire here. + s.cur += n + s.mu.Unlock() + return nil + } + + if n > s.size { + // Don't make other Acquire calls block on one that's doomed to fail. + s.mu.Unlock() + <-done + return ctx.Err() + } + + ready := make(chan struct{}) + w := waiter{n: n, ready: ready} + elem := s.waiters.PushBack(w) + s.mu.Unlock() + + select { + case <-done: + s.mu.Lock() + select { + case <-ready: + // Acquired the semaphore after we were canceled. + // Pretend we didn't and put the tokens back. + s.cur -= n + s.notifyWaiters() + default: + isFront := s.waiters.Front() == elem + s.waiters.Remove(elem) + // If we're at the front and there're extra tokens left, notify other waiters. + if isFront && s.size > s.cur { + s.notifyWaiters() + } + } + s.mu.Unlock() + return ctx.Err() + + case <-ready: + // Acquired the semaphore. Check that ctx isn't already done. + // We check the done channel instead of calling ctx.Err because we + // already have the channel, and ctx.Err is O(n) with the nesting + // depth of ctx. + select { + case <-done: + s.Release(n) + return ctx.Err() + default: + } + return nil + } +} + +// TryAcquire acquires the semaphore with a weight of n without blocking. +// On success, returns true. On failure, returns false and leaves the semaphore unchanged. +func (s *Weighted) TryAcquire(n int64) bool { + s.mu.Lock() + success := s.size-s.cur >= n && s.waiters.Len() == 0 + if success { + s.cur += n + } + s.mu.Unlock() + return success +} + +// Release releases the semaphore with a weight of n. +func (s *Weighted) Release(n int64) { + s.mu.Lock() + s.cur -= n + if s.cur < 0 { + s.mu.Unlock() + panic("semaphore: released more than held") + } + s.notifyWaiters() + s.mu.Unlock() +} + +func (s *Weighted) notifyWaiters() { + for { + next := s.waiters.Front() + if next == nil { + break // No more waiters blocked. + } + + w := next.Value.(waiter) + if s.size-s.cur < w.n { + // Not enough tokens for the next waiter. We could keep going (to try to + // find a waiter with a smaller request), but under load that could cause + // starvation for large requests; instead, we leave all remaining waiters + // blocked. + // + // Consider a semaphore used as a read-write lock, with N tokens, N + // readers, and one writer. Each reader can Acquire(1) to obtain a read + // lock. The writer can Acquire(N) to obtain a write lock, excluding all + // of the readers. If we allow the readers to jump ahead in the queue, + // the writer will starve — there is always one token available for every + // reader. + break + } + + s.cur += w.n + s.waiters.Remove(next) + close(w.ready) + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 0520089151..ca7aa0e2f2 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -621,9 +621,13 @@ github.com/opencontainers/image-spec/specs-go/v1 # github.com/opencontainers/runtime-spec v1.2.1 ## explicit github.com/opencontainers/runtime-spec/specs-go -# github.com/otiai10/copy v1.7.0 -## explicit; go 1.14 +# github.com/otiai10/copy v1.14.1 +## explicit; go 1.18 github.com/otiai10/copy +# github.com/otiai10/mint v1.6.3 +## explicit; go 1.18 +github.com/otiai10/mint +github.com/otiai10/mint/mquery # github.com/peterbourgon/diskv v2.0.1+incompatible ## explicit github.com/peterbourgon/diskv @@ -838,6 +842,7 @@ golang.org/x/oauth2/internal # golang.org/x/sync v0.18.0 ## explicit; go 1.24.0 golang.org/x/sync/errgroup +golang.org/x/sync/semaphore # golang.org/x/sys v0.38.0 ## explicit; go 1.24.0 golang.org/x/sys/cpu