diff options
Diffstat (limited to 'fstest/mockobject/mockobject.go')
| -rw-r--r-- | fstest/mockobject/mockobject.go | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/fstest/mockobject/mockobject.go b/fstest/mockobject/mockobject.go new file mode 100644 index 0000000..89f0fab --- /dev/null +++ b/fstest/mockobject/mockobject.go @@ -0,0 +1,235 @@ +// Package mockobject provides a mock object which can be created from a string +package mockobject + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "time" + + "github.com/rclone/rclone/fs" + "github.com/rclone/rclone/fs/hash" +) + +var errNotImpl = errors.New("not implemented") + +// Object is a mock fs.Object useful for testing +type Object string + +// New returns mock fs.Object useful for testing +func New(name string) Object { + return Object(name) +} + +// String returns a description of the Object +func (o Object) String() string { + return string(o) +} + +// Fs returns read only access to the Fs that this object is part of +func (o Object) Fs() fs.Info { + return nil +} + +// Remote returns the remote path +func (o Object) Remote() string { + return string(o) +} + +// Hash returns the selected checksum of the file +// If no checksum is available it returns "" +func (o Object) Hash(ctx context.Context, t hash.Type) (string, error) { + return "", errNotImpl +} + +// ModTime returns the modification date of the file +// It should return a best guess if one isn't available +func (o Object) ModTime(ctx context.Context) (t time.Time) { + return t +} + +// Size returns the size of the file +func (o Object) Size() int64 { return 0 } + +// Storable says whether this object can be stored +func (o Object) Storable() bool { + return true +} + +// SetModTime sets the metadata on the object to set the modification date +func (o Object) SetModTime(ctx context.Context, t time.Time) error { + return errNotImpl +} + +// Open opens the file for read. Call Close() on the returned io.ReadCloser +func (o Object) Open(ctx context.Context, options ...fs.OpenOption) (io.ReadCloser, error) { + return nil, errNotImpl +} + +// Update in to the object with the modTime given of the given size +func (o Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) error { + return errNotImpl +} + +// Remove this object +func (o Object) Remove(ctx context.Context) error { + return errNotImpl +} + +// SeekMode specifies the optional Seek interface for the ReadCloser returned by Open +type SeekMode int + +const ( + // SeekModeNone specifies no seek interface + SeekModeNone SeekMode = iota + // SeekModeRegular specifies the regular io.Seek interface + SeekModeRegular + // SeekModeRange specifies the fs.RangeSeek interface + SeekModeRange +) + +// SeekModes contains all valid SeekMode's +var SeekModes = []SeekMode{SeekModeNone, SeekModeRegular, SeekModeRange} + +// ContentMockObject mocks an fs.Object and has content, mod time +type ContentMockObject struct { + Object + content []byte + seekMode SeekMode + f fs.Fs + unknownSize bool + modTime time.Time +} + +// WithContent returns an fs.Object with the given content. +func (o Object) WithContent(content []byte, mode SeekMode) *ContentMockObject { + return &ContentMockObject{ + Object: o, + content: content, + seekMode: mode, + } +} + +// SetFs sets the return value of the Fs() call +func (o *ContentMockObject) SetFs(f fs.Fs) { + o.f = f +} + +// SetUnknownSize makes the mock object return -1 for size if true +func (o *ContentMockObject) SetUnknownSize(unknownSize bool) { + o.unknownSize = unknownSize +} + +// Fs returns read only access to the Fs that this object is part of +// +// This is nil unless SetFs has been called +func (o *ContentMockObject) Fs() fs.Info { + return o.f +} + +// Open opens the file for read. Call Close() on the returned io.ReadCloser +func (o *ContentMockObject) Open(ctx context.Context, options ...fs.OpenOption) (io.ReadCloser, error) { + size := int64(len(o.content)) + var offset, limit int64 = 0, -1 + for _, option := range options { + switch x := option.(type) { + case *fs.SeekOption: + offset = x.Offset + case *fs.RangeOption: + offset, limit = x.Decode(size) + default: + if option.Mandatory() { + return nil, fmt.Errorf("unsupported mandatory option: %v", option) + } + } + } + if limit == -1 || offset+limit > size { + limit = size - offset + } + + var r *bytes.Reader + if o.seekMode == SeekModeNone { + r = bytes.NewReader(o.content[offset : offset+limit]) + } else { + r = bytes.NewReader(o.content) + _, err := r.Seek(offset, io.SeekStart) + if err != nil { + return nil, err + } + } + switch o.seekMode { + case SeekModeNone: + return &readCloser{r}, nil + case SeekModeRegular: + return &readSeekCloser{r}, nil + case SeekModeRange: + return &readRangeSeekCloser{r}, nil + default: + return nil, errors.New(o.seekMode.String()) + } +} + +// Size returns the size of the file +func (o *ContentMockObject) Size() int64 { + if o.unknownSize { + return -1 + } + return int64(len(o.content)) +} + +// Hash returns the selected checksum of the file +// If no checksum is available it returns "" +func (o *ContentMockObject) Hash(ctx context.Context, t hash.Type) (string, error) { + hasher, err := hash.NewMultiHasherTypes(hash.NewHashSet(t)) + if err != nil { + return "", err + } + _, err = hasher.Write(o.content) + if err != nil { + return "", err + } + return hasher.Sums()[t], nil +} + +// ModTime returns the modification date of the file +// It should return a best guess if one isn't available +func (o *ContentMockObject) ModTime(ctx context.Context) time.Time { + return o.modTime +} + +// SetModTime sets the metadata on the object to set the modification date +func (o *ContentMockObject) SetModTime(ctx context.Context, t time.Time) error { + o.modTime = t + return nil +} + +type readCloser struct{ io.Reader } + +func (r *readCloser) Close() error { return nil } + +type readSeekCloser struct{ io.ReadSeeker } + +func (r *readSeekCloser) Close() error { return nil } + +type readRangeSeekCloser struct{ io.ReadSeeker } + +func (r *readRangeSeekCloser) RangeSeek(offset int64, whence int, length int64) (int64, error) { + return r.ReadSeeker.Seek(offset, whence) +} + +func (r *readRangeSeekCloser) Close() error { return nil } + +func (m SeekMode) String() string { + switch m { + case SeekModeNone: + return "SeekModeNone" + case SeekModeRegular: + return "SeekModeRegular" + case SeekModeRange: + return "SeekModeRange" + default: + return fmt.Sprintf("SeekModeInvalid(%d)", m) + } +} |
