gobot.io/x/gobot/v2@v2.1.0/system/fs_mock.go (about)

     1  package system
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"path"
     8  	"regexp"
     9  	"strings"
    10  	"time"
    11  )
    12  
    13  var _ File = (*MockFile)(nil)
    14  var _ filesystem = (*MockFilesystem)(nil)
    15  
    16  // MockFilesystem represents a filesystem of mock files.
    17  type MockFilesystem struct {
    18  	Seq            int // Increases with each write or read.
    19  	Files          map[string]*MockFile
    20  	WithReadError  bool
    21  	WithWriteError bool
    22  	WithCloseError bool
    23  }
    24  
    25  // A MockFile represents a mock file that contains a single string.  Any write
    26  // overwrites, and any read returns from the start.
    27  type MockFile struct {
    28  	Contents string
    29  	Seq      int // When this file was last written or read.
    30  	Opened   bool
    31  	Closed   bool
    32  	fd       uintptr
    33  
    34  	fs *MockFilesystem
    35  }
    36  
    37  var (
    38  	errRead  = fmt.Errorf("read error")
    39  	errWrite = fmt.Errorf("write error")
    40  	errClose = fmt.Errorf("close error")
    41  )
    42  
    43  // Write writes string(b) to f.Contents
    44  func (f *MockFile) Write(b []byte) (n int, err error) {
    45  	if f.fs.WithWriteError {
    46  		return 0, errWrite
    47  	}
    48  	return f.WriteString(string(b))
    49  }
    50  
    51  // Seek seeks to a specific offset in a file
    52  func (f *MockFile) Seek(offset int64, whence int) (ret int64, err error) {
    53  	return offset, nil
    54  }
    55  
    56  // WriteString writes s to f.Contents
    57  func (f *MockFile) WriteString(s string) (ret int, err error) {
    58  	f.Contents = s
    59  	f.Seq = f.fs.next()
    60  	return len(s), nil
    61  }
    62  
    63  // Sync implements the File interface Sync function
    64  func (f *MockFile) Sync() (err error) {
    65  	return nil
    66  }
    67  
    68  // Read copies b bytes from f.Contents
    69  func (f *MockFile) Read(b []byte) (n int, err error) {
    70  	if f.fs.WithReadError {
    71  		return 0, errRead
    72  	}
    73  
    74  	count := len(b)
    75  	if len(f.Contents) < count {
    76  		count = len(f.Contents)
    77  	}
    78  	copy(b, []byte(f.Contents)[:count])
    79  	f.Seq = f.fs.next()
    80  
    81  	return count, nil
    82  }
    83  
    84  // ReadAt calls MockFile.Read
    85  func (f *MockFile) ReadAt(b []byte, off int64) (n int, err error) {
    86  	return f.Read(b)
    87  }
    88  
    89  // Fd returns a random uintprt based on the time of the MockFile creation
    90  func (f *MockFile) Fd() uintptr {
    91  	return f.fd
    92  }
    93  
    94  // Close implements the File interface Close function
    95  func (f *MockFile) Close() error {
    96  	if f != nil {
    97  		f.Opened = false
    98  		f.Closed = true
    99  		if f.fs != nil && f.fs.WithCloseError {
   100  			f.Closed = false
   101  			return errClose
   102  		}
   103  	}
   104  	return nil
   105  }
   106  
   107  // newMockFilesystem returns a new MockFilesystem given a list of file and folder paths
   108  func newMockFilesystem(items []string) *MockFilesystem {
   109  	m := &MockFilesystem{
   110  		Files: make(map[string]*MockFile),
   111  	}
   112  
   113  	for _, item := range items {
   114  		m.Add(item)
   115  	}
   116  
   117  	return m
   118  }
   119  
   120  // OpenFile opens file name from fs.Files, if the file does not exist it returns an os.PathError
   121  func (fs *MockFilesystem) openFile(name string, _ int, _ os.FileMode) (file File, err error) {
   122  	f, ok := fs.Files[name]
   123  	if ok {
   124  		f.Opened = true
   125  		f.Closed = false
   126  		return f, nil
   127  	}
   128  
   129  	return (*MockFile)(nil), &os.PathError{Err: fmt.Errorf("%s: no such file", name)}
   130  }
   131  
   132  // Stat returns a generic FileInfo for all files in fs.Files.
   133  // If the file does not exist it returns an os.PathError
   134  func (fs *MockFilesystem) stat(name string) (os.FileInfo, error) {
   135  	_, ok := fs.Files[name]
   136  	if ok {
   137  		// return file based mock FileInfo
   138  		tmpFile, err := ioutil.TempFile("", name)
   139  		if err != nil {
   140  			return nil, err
   141  		}
   142  		defer os.Remove(tmpFile.Name())
   143  
   144  		return os.Stat(tmpFile.Name())
   145  	}
   146  
   147  	dirName := name + "/"
   148  	for path := range fs.Files {
   149  		if strings.HasPrefix(path, dirName) {
   150  			// return dir based mock FileInfo, TempDir don't like "/" in between
   151  			tmpDir, err := ioutil.TempDir("", strings.ReplaceAll(name, "/", "_"))
   152  			if err != nil {
   153  				return nil, err
   154  			}
   155  			defer os.RemoveAll(tmpDir)
   156  
   157  			return os.Stat(tmpDir)
   158  		}
   159  	}
   160  
   161  	return nil, &os.PathError{Err: fmt.Errorf("%s: no such file", name)}
   162  }
   163  
   164  // Find returns all items (files or folders) below the given directory matching the given pattern.
   165  func (fs *MockFilesystem) find(baseDir string, pattern string) ([]string, error) {
   166  	reg, err := regexp.Compile(pattern)
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  
   171  	var found []string
   172  	for name := range fs.Files {
   173  		if !strings.HasPrefix(name, baseDir) {
   174  			continue
   175  		}
   176  		item := strings.TrimPrefix(name[len(baseDir):], "/")
   177  
   178  		firstItem := strings.Split(item, "/")[0]
   179  		if reg.MatchString(firstItem) {
   180  			found = append(found, path.Join(baseDir, firstItem))
   181  		}
   182  	}
   183  	return found, nil
   184  }
   185  
   186  // readFile returns the contents of the given file. If the file does not exist it returns an os.PathError
   187  func (fs *MockFilesystem) readFile(name string) ([]byte, error) {
   188  	if fs.WithReadError {
   189  		return nil, errRead
   190  	}
   191  
   192  	f, ok := fs.Files[name]
   193  	if !ok {
   194  		return nil, &os.PathError{Err: fmt.Errorf("%s: no such file", name)}
   195  	}
   196  	return []byte(f.Contents), nil
   197  }
   198  
   199  func (fs *MockFilesystem) next() int {
   200  	fs.Seq++
   201  	return fs.Seq
   202  }
   203  
   204  // Add adds a new file to fs.Files given a name, and returns the newly created file
   205  func (fs *MockFilesystem) Add(name string) *MockFile {
   206  	f := &MockFile{
   207  		Seq: -1,
   208  		fd:  uintptr(time.Now().UnixNano() & 0xffff),
   209  		fs:  fs,
   210  	}
   211  	fs.Files[name] = f
   212  	return f
   213  }