Skip to content

Commit

Permalink
test: Improve test coverage (#289)
Browse files Browse the repository at this point in the history
* test: Test `NewReader()`

* test: Test `fileReadCloser.Seek()`

* refactor: Change `io/fs` import

* refactor: Use afero for filesystem interactions

* test: Test `openReader()`
  • Loading branch information
bodgit authored Nov 24, 2024
1 parent 5201139 commit 1ee54c1
Show file tree
Hide file tree
Showing 9 changed files with 668 additions and 101 deletions.
5 changes: 4 additions & 1 deletion export_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
package sevenzip

var ErrMissingUnpackInfo = errMissingUnpackInfo
var (
ErrMissingUnpackInfo = errMissingUnpackInfo
ErrNegativeSize = errNegativeSize
)
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/klauspost/compress v1.17.11
github.com/pierrec/lz4/v4 v4.1.21
github.com/spf13/afero v1.11.0
github.com/stretchr/testify v1.9.0
github.com/ulikunitz/xz v0.5.12
go4.org v0.0.0-20200411211856-f5505b9728dd
Expand All @@ -19,5 +20,6 @@ require (
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,13 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
Expand Down
81 changes: 44 additions & 37 deletions reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import (
"fmt"
"hash/crc32"
"io"
"io/fs"
"os"
iofs "io/fs"
"path"
"path/filepath"
"sort"
Expand All @@ -21,6 +20,7 @@ import (
"github.com/bodgit/plumbing"
"github.com/bodgit/sevenzip/internal/pool"
"github.com/bodgit/sevenzip/internal/util"
"github.com/spf13/afero"
"go4.org/readerutil"
)

Expand Down Expand Up @@ -63,7 +63,7 @@ type Reader struct {

// A ReadCloser is a [Reader] that must be closed when no longer needed.
type ReadCloser struct {
f []*os.File
f []afero.File
Reader
}

Expand All @@ -83,7 +83,7 @@ type fileReader struct {
n int64
}

func (fr *fileReader) Stat() (fs.FileInfo, error) {
func (fr *fileReader) Stat() (iofs.FileInfo, error) {
return headerFileInfo{&fr.f.FileHeader}, nil
}

Expand Down Expand Up @@ -187,37 +187,31 @@ func (f *File) Open() (io.ReadCloser, error) {
}, nil
}

// OpenReaderWithPassword will open the 7-zip file specified by name using
// password as the basis of the decryption key and return a [*ReadCloser]. If
// name has a ".001" suffix it is assumed there are multiple volumes and each
// sequential volume will be opened.
//
//nolint:cyclop,funlen
func OpenReaderWithPassword(name, password string) (*ReadCloser, error) {
f, err := os.Open(filepath.Clean(name))
func openReader(fs afero.Fs, name string) (io.ReaderAt, int64, []afero.File, error) {
f, err := fs.Open(filepath.Clean(name))
if err != nil {
return nil, fmt.Errorf("sevenzip: error opening: %w", err)
return nil, 0, nil, fmt.Errorf("sevenzip: error opening: %w", err)
}

info, err := f.Stat()
if err != nil {
err = errors.Join(err, f.Close())

return nil, fmt.Errorf("sevenzip: error retrieving file info: %w", err)
return nil, 0, nil, fmt.Errorf("sevenzip: error retrieving file info: %w", err)
}

var reader io.ReaderAt = f

size := info.Size()
files := []*os.File{f}
files := []afero.File{f}

if ext := filepath.Ext(name); ext == ".001" {
sr := []readerutil.SizeReaderAt{io.NewSectionReader(f, 0, size)}

for i := 2; true; i++ {
f, err := os.Open(fmt.Sprintf("%s.%03d", strings.TrimSuffix(name, ext), i))
f, err := fs.Open(fmt.Sprintf("%s.%03d", strings.TrimSuffix(name, ext), i))
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
if errors.Is(err, iofs.ErrNotExist) {
break
}

Expand All @@ -228,7 +222,7 @@ func OpenReaderWithPassword(name, password string) (*ReadCloser, error) {
errs = append(errs, file.Close())
}

return nil, fmt.Errorf("sevenzip: error opening: %w", errors.Join(errs...))
return nil, 0, nil, fmt.Errorf("sevenzip: error opening: %w", errors.Join(errs...))
}

files = append(files, f)
Expand All @@ -242,7 +236,7 @@ func OpenReaderWithPassword(name, password string) (*ReadCloser, error) {
errs = append(errs, file.Close())
}

return nil, fmt.Errorf("sevenzip: error retrieving file info: %w", errors.Join(errs...))
return nil, 0, nil, fmt.Errorf("sevenzip: error retrieving file info: %w", errors.Join(errs...))
}

sr = append(sr, io.NewSectionReader(f, 0, info.Size()))
Expand All @@ -252,6 +246,19 @@ func OpenReaderWithPassword(name, password string) (*ReadCloser, error) {
reader, size = mr, mr.Size()
}

return reader, size, files, nil
}

// OpenReaderWithPassword will open the 7-zip file specified by name using
// password as the basis of the decryption key and return a [*ReadCloser]. If
// name has a ".001" suffix it is assumed there are multiple volumes and each
// sequential volume will be opened.
func OpenReaderWithPassword(name, password string) (*ReadCloser, error) {
reader, size, files, err := openReader(afero.NewOsFs(), name)
if err != nil {
return nil, err
}

r := new(ReadCloser)
r.p = password

Expand Down Expand Up @@ -582,8 +589,8 @@ type fileListEntry struct {
}

type fileInfoDirEntry interface {
fs.FileInfo
fs.DirEntry
iofs.FileInfo
iofs.DirEntry
}

func (e *fileListEntry) stat() (fileInfoDirEntry, error) {
Expand All @@ -604,11 +611,11 @@ func (e *fileListEntry) Name() string {
return elem
}

func (e *fileListEntry) Size() int64 { return 0 }
func (e *fileListEntry) Mode() fs.FileMode { return fs.ModeDir | 0o555 }
func (e *fileListEntry) Type() fs.FileMode { return fs.ModeDir }
func (e *fileListEntry) IsDir() bool { return true }
func (e *fileListEntry) Sys() interface{} { return nil }
func (e *fileListEntry) Size() int64 { return 0 }
func (e *fileListEntry) Mode() iofs.FileMode { return iofs.ModeDir | 0o555 }
func (e *fileListEntry) Type() iofs.FileMode { return iofs.ModeDir }
func (e *fileListEntry) IsDir() bool { return true }
func (e *fileListEntry) Sys() interface{} { return nil }

func (e *fileListEntry) ModTime() time.Time {
if e.file == nil {
Expand All @@ -618,7 +625,7 @@ func (e *fileListEntry) ModTime() time.Time {
return e.file.FileHeader.Modified.UTC()
}

func (e *fileListEntry) Info() (fs.FileInfo, error) { return e, nil }
func (e *fileListEntry) Info() (iofs.FileInfo, error) { return e, nil }

func toValidName(name string) string {
name = strings.ReplaceAll(name, `\`, `/`)
Expand Down Expand Up @@ -708,16 +715,16 @@ func fileEntryLess(x, y string) bool {
// Open opens the named file in the 7-zip archive, using the semantics of
// [fs.FS.Open]: paths are always slash separated, with no leading / or ../
// elements.
func (z *Reader) Open(name string) (fs.File, error) {
func (z *Reader) Open(name string) (iofs.File, error) {
z.initFileList()

if !fs.ValidPath(name) {
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrInvalid}
if !iofs.ValidPath(name) {
return nil, &iofs.PathError{Op: "open", Path: name, Err: iofs.ErrInvalid}
}

e := z.openLookup(name)
if e == nil {
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
return nil, &iofs.PathError{Op: "open", Path: name, Err: iofs.ErrNotExist}
}

if e.isDir {
Expand All @@ -729,7 +736,7 @@ func (z *Reader) Open(name string) (fs.File, error) {
return nil, err
}

return rc.(fs.File), nil //nolint:forcetypeassert
return rc.(iofs.File), nil //nolint:forcetypeassert
}

func split(name string) (dir, elem string) {
Expand Down Expand Up @@ -800,16 +807,16 @@ type openDir struct {
offset int
}

func (d *openDir) Close() error { return nil }
func (d *openDir) Stat() (fs.FileInfo, error) { return d.e.stat() }
func (d *openDir) Close() error { return nil }
func (d *openDir) Stat() (iofs.FileInfo, error) { return d.e.stat() }

var errIsDirectory = errors.New("is a directory")

func (d *openDir) Read([]byte) (int, error) {
return 0, &fs.PathError{Op: "read", Path: d.e.name, Err: errIsDirectory}
return 0, &iofs.PathError{Op: "read", Path: d.e.name, Err: errIsDirectory}
}

func (d *openDir) ReadDir(count int) ([]fs.DirEntry, error) {
func (d *openDir) ReadDir(count int) ([]iofs.DirEntry, error) {
n := len(d.files) - d.offset
if count > 0 && n > count {
n = count
Expand All @@ -823,7 +830,7 @@ func (d *openDir) ReadDir(count int) ([]fs.DirEntry, error) {
return nil, io.EOF
}

list := make([]fs.DirEntry, n)
list := make([]iofs.DirEntry, n)
for i := range list {
s, err := d.files[d.offset+i].stat()
if err != nil {
Expand Down
Loading

0 comments on commit 1ee54c1

Please sign in to comment.