blob: 2c75c5b1d75ead73eeec545638c3da7f3bf49cd2 [file] [log] [blame]
Bob Badourdc62de42022-10-12 20:10:17 -07001// Copyright 2022 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package testfs
16
17import (
18 "fmt"
19 "io"
20 "io/fs"
21 "strings"
22 "time"
23)
24
25// TestFS implements a test file system (fs.FS) simulated by a map from filename to []byte content.
26type TestFS map[string][]byte
27
28var _ fs.FS = (*TestFS)(nil)
29var _ fs.StatFS = (*TestFS)(nil)
30
31// Open implements fs.FS.Open() to open a file based on the filename.
32func (tfs *TestFS) Open(name string) (fs.File, error) {
33 if _, ok := (*tfs)[name]; !ok {
34 return nil, fmt.Errorf("unknown file %q", name)
35 }
36 return &TestFile{tfs, name, 0}, nil
37}
38
39// Stat implements fs.StatFS.Stat() to examine a file based on the filename.
40func (tfs *TestFS) Stat(name string) (fs.FileInfo, error) {
41 if content, ok := (*tfs)[name]; ok {
42 return &TestFileInfo{name, len(content), 0666}, nil
43 }
44 dirname := name
45 if !strings.HasSuffix(dirname, "/") {
46 dirname = dirname + "/"
47 }
48 for name := range (*tfs) {
49 if strings.HasPrefix(name, dirname) {
50 return &TestFileInfo{name, 8, fs.ModeDir | fs.ModePerm}, nil
51 }
52 }
53 return nil, fmt.Errorf("file not found: %q", name)
54}
55
56// TestFileInfo implements a file info (fs.FileInfo) based on TestFS above.
57type TestFileInfo struct {
58 name string
59 size int
60 mode fs.FileMode
61}
62
63var _ fs.FileInfo = (*TestFileInfo)(nil)
64
65// Name returns the name of the file
66func (fi *TestFileInfo) Name() string {
67 return fi.name
68}
69
70// Size returns the size of the file in bytes.
71func (fi *TestFileInfo) Size() int64 {
72 return int64(fi.size)
73}
74
75// Mode returns the fs.FileMode bits.
76func (fi *TestFileInfo) Mode() fs.FileMode {
77 return fi.mode
78}
79
80// ModTime fakes a modification time.
81func (fi *TestFileInfo) ModTime() time.Time {
82 return time.UnixMicro(0xb0bb)
83}
84
85// IsDir is a synonym for Mode().IsDir()
86func (fi *TestFileInfo) IsDir() bool {
87 return fi.mode.IsDir()
88}
89
90// Sys is unused and returns nil.
91func (fi *TestFileInfo) Sys() any {
92 return nil
93}
94
95// TestFile implements a test file (fs.File) based on TestFS above.
96type TestFile struct {
97 fs *TestFS
98 name string
99 posn int
100}
101
102var _ fs.File = (*TestFile)(nil)
103
104// Stat not implemented to obviate implementing fs.FileInfo.
105func (f *TestFile) Stat() (fs.FileInfo, error) {
106 return f.fs.Stat(f.name)
107}
108
109// Read copies bytes from the TestFS map.
110func (f *TestFile) Read(b []byte) (int, error) {
111 if f.posn < 0 {
112 return 0, fmt.Errorf("file not open: %q", f.name)
113 }
114 if f.posn >= len((*f.fs)[f.name]) {
115 return 0, io.EOF
116 }
117 n := copy(b, (*f.fs)[f.name][f.posn:])
118 f.posn += n
119 return n, nil
120}
121
122// Close marks the TestFile as no longer in use.
123func (f *TestFile) Close() error {
124 if f.posn < 0 {
125 return fmt.Errorf("file already closed: %q", f.name)
126 }
127 f.posn = -1
128 return nil
129}