130 lines
3.1 KiB
Go
130 lines
3.1 KiB
Go
|
// Copyright 2022 Google LLC
|
||
|
//
|
||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
// you may not use this file except in compliance with the License.
|
||
|
// You may obtain a copy of the License at
|
||
|
//
|
||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
//
|
||
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
// See the License for the specific language governing permissions and
|
||
|
// limitations under the License.
|
||
|
|
||
|
package testfs
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"io/fs"
|
||
|
"strings"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
// TestFS implements a test file system (fs.FS) simulated by a map from filename to []byte content.
|
||
|
type TestFS map[string][]byte
|
||
|
|
||
|
var _ fs.FS = (*TestFS)(nil)
|
||
|
var _ fs.StatFS = (*TestFS)(nil)
|
||
|
|
||
|
// Open implements fs.FS.Open() to open a file based on the filename.
|
||
|
func (tfs *TestFS) Open(name string) (fs.File, error) {
|
||
|
if _, ok := (*tfs)[name]; !ok {
|
||
|
return nil, fmt.Errorf("unknown file %q", name)
|
||
|
}
|
||
|
return &TestFile{tfs, name, 0}, nil
|
||
|
}
|
||
|
|
||
|
// Stat implements fs.StatFS.Stat() to examine a file based on the filename.
|
||
|
func (tfs *TestFS) Stat(name string) (fs.FileInfo, error) {
|
||
|
if content, ok := (*tfs)[name]; ok {
|
||
|
return &TestFileInfo{name, len(content), 0666}, nil
|
||
|
}
|
||
|
dirname := name
|
||
|
if !strings.HasSuffix(dirname, "/") {
|
||
|
dirname = dirname + "/"
|
||
|
}
|
||
|
for name := range (*tfs) {
|
||
|
if strings.HasPrefix(name, dirname) {
|
||
|
return &TestFileInfo{name, 8, fs.ModeDir | fs.ModePerm}, nil
|
||
|
}
|
||
|
}
|
||
|
return nil, fmt.Errorf("file not found: %q", name)
|
||
|
}
|
||
|
|
||
|
// TestFileInfo implements a file info (fs.FileInfo) based on TestFS above.
|
||
|
type TestFileInfo struct {
|
||
|
name string
|
||
|
size int
|
||
|
mode fs.FileMode
|
||
|
}
|
||
|
|
||
|
var _ fs.FileInfo = (*TestFileInfo)(nil)
|
||
|
|
||
|
// Name returns the name of the file
|
||
|
func (fi *TestFileInfo) Name() string {
|
||
|
return fi.name
|
||
|
}
|
||
|
|
||
|
// Size returns the size of the file in bytes.
|
||
|
func (fi *TestFileInfo) Size() int64 {
|
||
|
return int64(fi.size)
|
||
|
}
|
||
|
|
||
|
// Mode returns the fs.FileMode bits.
|
||
|
func (fi *TestFileInfo) Mode() fs.FileMode {
|
||
|
return fi.mode
|
||
|
}
|
||
|
|
||
|
// ModTime fakes a modification time.
|
||
|
func (fi *TestFileInfo) ModTime() time.Time {
|
||
|
return time.UnixMicro(0xb0bb)
|
||
|
}
|
||
|
|
||
|
// IsDir is a synonym for Mode().IsDir()
|
||
|
func (fi *TestFileInfo) IsDir() bool {
|
||
|
return fi.mode.IsDir()
|
||
|
}
|
||
|
|
||
|
// Sys is unused and returns nil.
|
||
|
func (fi *TestFileInfo) Sys() any {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// TestFile implements a test file (fs.File) based on TestFS above.
|
||
|
type TestFile struct {
|
||
|
fs *TestFS
|
||
|
name string
|
||
|
posn int
|
||
|
}
|
||
|
|
||
|
var _ fs.File = (*TestFile)(nil)
|
||
|
|
||
|
// Stat not implemented to obviate implementing fs.FileInfo.
|
||
|
func (f *TestFile) Stat() (fs.FileInfo, error) {
|
||
|
return f.fs.Stat(f.name)
|
||
|
}
|
||
|
|
||
|
// Read copies bytes from the TestFS map.
|
||
|
func (f *TestFile) Read(b []byte) (int, error) {
|
||
|
if f.posn < 0 {
|
||
|
return 0, fmt.Errorf("file not open: %q", f.name)
|
||
|
}
|
||
|
if f.posn >= len((*f.fs)[f.name]) {
|
||
|
return 0, io.EOF
|
||
|
}
|
||
|
n := copy(b, (*f.fs)[f.name][f.posn:])
|
||
|
f.posn += n
|
||
|
return n, nil
|
||
|
}
|
||
|
|
||
|
// Close marks the TestFile as no longer in use.
|
||
|
func (f *TestFile) Close() error {
|
||
|
if f.posn < 0 {
|
||
|
return fmt.Errorf("file already closed: %q", f.name)
|
||
|
}
|
||
|
f.posn = -1
|
||
|
return nil
|
||
|
}
|