1 // This file is part of the ffldb package rather than the ffldb_test package as it is part of the whitebox testing.
2 package ffldb
3 4 import (
5 "errors"
6 "io"
7 "sync"
8 )
9 10 // Errors used for the mock file.
11 var (
12 // errMockFileClosed is used to indicate a mock file is closed.
13 errMockFileClosed = errors.New("file closed")
14 // errInvalidOffset is used to indicate an offset that is out of range for the file was provided.
15 errInvalidOffset = errors.New("invalid offset")
16 // errSyncFail is used to indicate simulated sync failure.
17 errSyncFail = errors.New("simulated sync failure")
18 )
19 20 // mockFile implements the filer interface and used in order to force failures the database code related to reading and
21 // writing from the flat block files. A maxSize of -1 is unlimited.
22 type mockFile struct {
23 sync.RWMutex
24 maxSize int64
25 data []byte
26 forceSyncErr bool
27 closed bool
28 }
29 30 // Close closes the mock file without releasing any data associated with it. This allows it to be "reopened" without
31 // losing the data. This is part of the jebote implementation.
32 func (f *mockFile) Close() (e error) {
33 f.Lock()
34 defer f.Unlock()
35 if f.closed {
36 return errMockFileClosed
37 }
38 f.closed = true
39 return nil
40 }
41 42 // ReadAt reads len(b) bytes from the mock file starting at byte offset off. It returns the number of bytes read and the
43 // error, if any. ReadAt always returns a non-nil error when n < len(b). At end of file, that error is io.EOF. This is
44 // part of the filer implementation.
45 func (f *mockFile) ReadAt(b []byte, off int64) (int, error) {
46 f.RLock()
47 defer f.RUnlock()
48 if f.closed {
49 return 0, errMockFileClosed
50 }
51 maxSize := int64(len(f.data))
52 if f.maxSize > -1 && maxSize > f.maxSize {
53 maxSize = f.maxSize
54 }
55 if off < 0 || off > maxSize {
56 return 0, errInvalidOffset
57 }
58 // Limit to the max size field, if set.
59 numToRead := int64(len(b))
60 endOffset := off + numToRead
61 if endOffset > maxSize {
62 numToRead = maxSize - off
63 }
64 copy(b, f.data[off:off+numToRead])
65 if numToRead < int64(len(b)) {
66 return int(numToRead), io.EOF
67 }
68 return int(numToRead), nil
69 }
70 71 // Truncate changes the size of the mock file. This is part of the filer implementation.
72 func (f *mockFile) Truncate(size int64) (e error) {
73 f.Lock()
74 defer f.Unlock()
75 if f.closed {
76 return errMockFileClosed
77 }
78 maxSize := int64(len(f.data))
79 if f.maxSize > -1 && maxSize > f.maxSize {
80 maxSize = f.maxSize
81 }
82 if size > maxSize {
83 return errInvalidOffset
84 }
85 f.data = f.data[:size]
86 return nil
87 }
88 89 // Write writes len(b) bytes to the mock file. It returns the number of bytes written and an error, if any. Write
90 // returns a non-nil error any time n != len(b).
91 //
92 // This is part of the filer implementation.
93 func (f *mockFile) WriteAt(b []byte, off int64) (int, error) {
94 f.Lock()
95 defer f.Unlock()
96 if f.closed {
97 return 0, errMockFileClosed
98 }
99 maxSize := f.maxSize
100 if maxSize < 0 {
101 maxSize = 100 * 1024 // 100KiB
102 }
103 if off < 0 || off > maxSize {
104 return 0, errInvalidOffset
105 }
106 // Limit to the max size field, if set, and grow the slice if needed.
107 numToWrite := int64(len(b))
108 if off+numToWrite > maxSize {
109 numToWrite = maxSize - off
110 }
111 if off+numToWrite > int64(len(f.data)) {
112 newData := make([]byte, off+numToWrite)
113 copy(newData, f.data)
114 f.data = newData
115 }
116 copy(f.data[off:], b[:numToWrite])
117 if numToWrite < int64(len(b)) {
118 return int(numToWrite), io.EOF
119 }
120 return int(numToWrite), nil
121 }
122 123 // Sync doesn't do anything for mock files. However, it will return an error if the mock file's forceSyncErr flag is
124 // set.
125 //
126 // This is part of the filer implementation.
127 func (f *mockFile) Sync() (e error) {
128 if f.forceSyncErr {
129 return errSyncFail
130 }
131 return nil
132 }
133 134 // Ensure the mockFile type implements the filer interface.
135 var _ filer = (*mockFile)(nil)
136