-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from xmidt-org/add-fs-interface
Add a simple fs interface for easy testing.
- Loading branch information
Showing
6 changed files
with
969 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// SPDX-FileCopyrightText: 2023 Comcast Cable Communications Management, LLC | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package fs | ||
|
||
import "io/fs" | ||
|
||
// Look here for interface options to include when we need more functionality: | ||
// https://github.com/hack-pad/hackpadfs/blob/main/fs.go | ||
|
||
// FS is the interface for a filesystem used by the program. | ||
type FS interface { | ||
fs.FS | ||
|
||
// Mkdir creates a directory with the specified permissions. Should match os.Mkdir(). | ||
Mkdir(path string, perm fs.FileMode) error | ||
|
||
// ReadFile reads the file and returns the contents. Should match os.ReadFile(). | ||
ReadFile(name string) ([]byte, error) | ||
|
||
// WriteFile writes the file with the specified permissions. Should match os.WriteFile(). | ||
WriteFile(name string, data []byte, perm fs.FileMode) error | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
// SPDX-FileCopyrightText: 2023 Comcast Cable Communications Management, LLC | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package mem | ||
|
||
import ( | ||
"errors" | ||
"io" | ||
iofs "io/fs" | ||
"time" | ||
) | ||
|
||
var ( | ||
ErrIsDir = errors.New("is a directory") | ||
) | ||
|
||
// File is an in-memory implementation of the iofs.File interface. | ||
type File struct { | ||
Bytes []byte | ||
Perm iofs.FileMode | ||
name string | ||
isDir bool | ||
offset int | ||
closed bool | ||
} | ||
|
||
var _ iofs.File = (*File)(nil) | ||
|
||
// Implement the iofs.File interface. | ||
|
||
func (f *File) Stat() (iofs.FileInfo, error) { | ||
return &fileInfo{ | ||
name: f.name, | ||
size: int64(len(f.Bytes)), | ||
mode: f.Perm, | ||
modTime: time.Time{}, | ||
isDir: false, | ||
}, nil | ||
} | ||
|
||
func (f *File) Read(b []byte) (int, error) { | ||
if f.isDir { | ||
return 0, &iofs.PathError{Op: "read", Path: f.name, Err: ErrIsDir} | ||
} | ||
|
||
if f.closed { | ||
return 0, io.ErrClosedPipe | ||
} | ||
|
||
if len(f.Bytes) <= f.offset { | ||
return 0, io.EOF | ||
} | ||
|
||
n := copy(b, f.Bytes[f.offset:]) | ||
f.offset += n | ||
return n, nil | ||
} | ||
|
||
func (f *File) Close() error { | ||
f.closed = true | ||
return nil | ||
} | ||
|
||
// fileInfo is an in-memory implementation of the iofs.FileInfo interface. | ||
type fileInfo struct { | ||
name string | ||
size int64 | ||
mode iofs.FileMode | ||
modTime time.Time | ||
isDir bool | ||
} | ||
|
||
var _ iofs.FileInfo = (*fileInfo)(nil) | ||
|
||
// Implement the iofs.FileInfo interface. | ||
|
||
func (fi *fileInfo) Name() string { | ||
return fi.name | ||
} | ||
|
||
func (fi *fileInfo) Size() int64 { | ||
return fi.size | ||
} | ||
|
||
func (fi *fileInfo) Mode() iofs.FileMode { | ||
return fi.mode | ||
} | ||
|
||
func (fi *fileInfo) ModTime() time.Time { | ||
return fi.modTime | ||
} | ||
|
||
func (fi *fileInfo) IsDir() bool { | ||
return fi.isDir | ||
} | ||
|
||
func (fi *fileInfo) Sys() interface{} { | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
// SPDX-FileCopyrightText: 2023 Comcast Cable Communications Management, LLC | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package mem | ||
|
||
import ( | ||
"io" | ||
iofs "io/fs" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestFile_Stat(t *testing.T) { | ||
tests := []struct { | ||
description string | ||
file File | ||
want iofs.FileInfo | ||
expectedErr error | ||
}{ | ||
{ | ||
description: "a simple file", | ||
file: File{ | ||
Bytes: []byte("hello"), | ||
Perm: iofs.FileMode(0644), | ||
name: "foo", | ||
}, | ||
want: &fileInfo{ | ||
name: "foo", | ||
size: 5, | ||
mode: iofs.FileMode(0644), | ||
isDir: false, | ||
}, | ||
}, | ||
} | ||
for _, tc := range tests { | ||
t.Run(tc.description, func(t *testing.T) { | ||
assert := assert.New(t) | ||
|
||
got, err := tc.file.Stat() | ||
|
||
assert.ErrorIs(err, tc.expectedErr) | ||
assert.Equal(tc.want.Name(), got.Name()) | ||
assert.Equal(tc.want.Size(), got.Size()) | ||
assert.Equal(tc.want.Mode(), got.Mode()) | ||
assert.Equal(tc.want.ModTime(), got.ModTime()) | ||
assert.Equal(tc.want.IsDir(), got.IsDir()) | ||
assert.Equal(tc.want.Sys(), got.Sys()) | ||
}) | ||
} | ||
} | ||
|
||
func TestFile_Read(t *testing.T) { | ||
tests := []struct { | ||
description string | ||
file File | ||
buf []byte | ||
want int | ||
before func(file *File) | ||
expect string | ||
expectedErr error | ||
}{ | ||
{ | ||
description: "a simple file read", | ||
file: File{ | ||
Bytes: []byte("hello"), | ||
Perm: iofs.FileMode(0644), | ||
name: "foo", | ||
}, | ||
buf: make([]byte, 5), | ||
want: 5, | ||
expect: "hello", | ||
}, { | ||
description: "a simple file read that is shorter than the buffer", | ||
file: File{ | ||
Bytes: []byte("hello"), | ||
Perm: iofs.FileMode(0644), | ||
name: "foo", | ||
}, | ||
buf: make([]byte, 1), | ||
want: 1, | ||
expect: "h", | ||
}, { | ||
description: "read an empty file", | ||
file: File{ | ||
Perm: iofs.FileMode(0644), | ||
name: "foo", | ||
}, | ||
buf: make([]byte, 5), | ||
want: 0, | ||
expectedErr: io.EOF, | ||
}, { | ||
description: "read a directory", | ||
file: File{ | ||
Perm: iofs.FileMode(0755), | ||
name: "foo", | ||
isDir: true, | ||
}, | ||
buf: make([]byte, 5), | ||
want: 0, | ||
expectedErr: ErrIsDir, | ||
}, { | ||
description: "read a closed file", | ||
file: File{ | ||
Perm: iofs.FileMode(0644), | ||
name: "foo", | ||
}, | ||
before: func(file *File) { | ||
file.Close() | ||
}, | ||
buf: make([]byte, 5), | ||
want: 0, | ||
expectedErr: io.ErrClosedPipe, | ||
}, { | ||
description: "read a partially read file", | ||
file: File{ | ||
Bytes: []byte("hello"), | ||
Perm: iofs.FileMode(0644), | ||
name: "foo", | ||
}, | ||
before: func(file *File) { | ||
buf := make([]byte, 1) | ||
_, _ = file.Read(buf) | ||
}, | ||
buf: make([]byte, 5), | ||
want: 4, | ||
expect: "ello", | ||
}, | ||
} | ||
for _, tc := range tests { | ||
t.Run(tc.description, func(t *testing.T) { | ||
assert := assert.New(t) | ||
|
||
file := tc.file | ||
if tc.before != nil { | ||
tc.before(&file) | ||
} | ||
|
||
got, err := file.Read(tc.buf) | ||
|
||
assert.ErrorIs(err, tc.expectedErr) | ||
assert.Equal(tc.want, got) | ||
assert.Equal(tc.expect, string(tc.buf[:tc.want])) | ||
}) | ||
} | ||
} |
Oops, something went wrong.