1/*
2   Copyright The containerd Authors.
3
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7
8       http://www.apache.org/licenses/LICENSE-2.0
9
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15*/
16
17package testing
18
19import (
20	"os"
21	"sync"
22
23	containerdmount "github.com/containerd/containerd/mount"
24
25	osInterface "github.com/containerd/containerd/pkg/os"
26)
27
28// CalledDetail is the struct contains called function name and arguments.
29type CalledDetail struct {
30	// Name of the function called.
31	Name string
32	// Arguments of the function called.
33	Arguments []interface{}
34}
35
36// FakeOS mocks out certain OS calls to avoid perturbing the filesystem
37// If a member of the form `*Fn` is set, that function will be called in place
38// of the real call.
39type FakeOS struct {
40	sync.Mutex
41	MkdirAllFn             func(string, os.FileMode) error
42	RemoveAllFn            func(string) error
43	StatFn                 func(string) (os.FileInfo, error)
44	ResolveSymbolicLinkFn  func(string) (string, error)
45	FollowSymlinkInScopeFn func(string, string) (string, error)
46	CopyFileFn             func(string, string, os.FileMode) error
47	WriteFileFn            func(string, []byte, os.FileMode) error
48	MountFn                func(source string, target string, fstype string, flags uintptr, data string) error
49	UnmountFn              func(target string) error
50	LookupMountFn          func(path string) (containerdmount.Info, error)
51	HostnameFn             func() (string, error)
52	calls                  []CalledDetail
53	errors                 map[string]error
54}
55
56var _ osInterface.OS = &FakeOS{}
57
58// getError get error for call
59func (f *FakeOS) getError(op string) error {
60	f.Lock()
61	defer f.Unlock()
62	err, ok := f.errors[op]
63	if ok {
64		delete(f.errors, op)
65		return err
66	}
67	return nil
68}
69
70// InjectError inject error for call
71func (f *FakeOS) InjectError(fn string, err error) {
72	f.Lock()
73	defer f.Unlock()
74	f.errors[fn] = err
75}
76
77// InjectErrors inject errors for calls
78func (f *FakeOS) InjectErrors(errs map[string]error) {
79	f.Lock()
80	defer f.Unlock()
81	for fn, err := range errs {
82		f.errors[fn] = err
83	}
84}
85
86// ClearErrors clear errors for call
87func (f *FakeOS) ClearErrors() {
88	f.Lock()
89	defer f.Unlock()
90	f.errors = make(map[string]error)
91}
92
93func (f *FakeOS) appendCalls(name string, args ...interface{}) {
94	f.Lock()
95	defer f.Unlock()
96	f.calls = append(f.calls, CalledDetail{Name: name, Arguments: args})
97}
98
99// GetCalls get detail of calls.
100func (f *FakeOS) GetCalls() []CalledDetail {
101	f.Lock()
102	defer f.Unlock()
103	return append([]CalledDetail{}, f.calls...)
104}
105
106// NewFakeOS creates a FakeOS.
107func NewFakeOS() *FakeOS {
108	return &FakeOS{
109		errors: make(map[string]error),
110	}
111}
112
113// MkdirAll is a fake call that invokes MkdirAllFn or just returns nil.
114func (f *FakeOS) MkdirAll(path string, perm os.FileMode) error {
115	f.appendCalls("MkdirAll", path, perm)
116	if err := f.getError("MkdirAll"); err != nil {
117		return err
118	}
119
120	if f.MkdirAllFn != nil {
121		return f.MkdirAllFn(path, perm)
122	}
123	return nil
124}
125
126// RemoveAll is a fake call that invokes RemoveAllFn or just returns nil.
127func (f *FakeOS) RemoveAll(path string) error {
128	f.appendCalls("RemoveAll", path)
129	if err := f.getError("RemoveAll"); err != nil {
130		return err
131	}
132
133	if f.RemoveAllFn != nil {
134		return f.RemoveAllFn(path)
135	}
136	return nil
137}
138
139// Stat is a fake call that invokes StatFn or just return nil.
140func (f *FakeOS) Stat(name string) (os.FileInfo, error) {
141	f.appendCalls("Stat", name)
142	if err := f.getError("Stat"); err != nil {
143		return nil, err
144	}
145
146	if f.StatFn != nil {
147		return f.StatFn(name)
148	}
149	return nil, nil
150}
151
152// ResolveSymbolicLink is a fake call that invokes ResolveSymbolicLinkFn or returns its input
153func (f *FakeOS) ResolveSymbolicLink(path string) (string, error) {
154	f.appendCalls("ResolveSymbolicLink", path)
155	if err := f.getError("ResolveSymbolicLink"); err != nil {
156		return "", err
157	}
158
159	if f.ResolveSymbolicLinkFn != nil {
160		return f.ResolveSymbolicLinkFn(path)
161	}
162	return path, nil
163}
164
165// FollowSymlinkInScope is a fake call that invokes FollowSymlinkInScope or returns its input
166func (f *FakeOS) FollowSymlinkInScope(path, scope string) (string, error) {
167	f.appendCalls("FollowSymlinkInScope", path, scope)
168	if err := f.getError("FollowSymlinkInScope"); err != nil {
169		return "", err
170	}
171
172	if f.FollowSymlinkInScopeFn != nil {
173		return f.FollowSymlinkInScopeFn(path, scope)
174	}
175	return path, nil
176}
177
178// CopyFile is a fake call that invokes CopyFileFn or just return nil.
179func (f *FakeOS) CopyFile(src, dest string, perm os.FileMode) error {
180	f.appendCalls("CopyFile", src, dest, perm)
181	if err := f.getError("CopyFile"); err != nil {
182		return err
183	}
184
185	if f.CopyFileFn != nil {
186		return f.CopyFileFn(src, dest, perm)
187	}
188	return nil
189}
190
191// WriteFile is a fake call that invokes WriteFileFn or just return nil.
192func (f *FakeOS) WriteFile(filename string, data []byte, perm os.FileMode) error {
193	f.appendCalls("WriteFile", filename, data, perm)
194	if err := f.getError("WriteFile"); err != nil {
195		return err
196	}
197
198	if f.WriteFileFn != nil {
199		return f.WriteFileFn(filename, data, perm)
200	}
201	return nil
202}
203
204// Mount is a fake call that invokes MountFn or just return nil.
205func (f *FakeOS) Mount(source string, target string, fstype string, flags uintptr, data string) error {
206	f.appendCalls("Mount", source, target, fstype, flags, data)
207	if err := f.getError("Mount"); err != nil {
208		return err
209	}
210
211	if f.MountFn != nil {
212		return f.MountFn(source, target, fstype, flags, data)
213	}
214	return nil
215}
216
217// Unmount is a fake call that invokes UnmountFn or just return nil.
218func (f *FakeOS) Unmount(target string) error {
219	f.appendCalls("Unmount", target)
220	if err := f.getError("Unmount"); err != nil {
221		return err
222	}
223
224	if f.UnmountFn != nil {
225		return f.UnmountFn(target)
226	}
227	return nil
228}
229
230// LookupMount is a fake call that invokes LookupMountFn or just return nil.
231func (f *FakeOS) LookupMount(path string) (containerdmount.Info, error) {
232	f.appendCalls("LookupMount", path)
233	if err := f.getError("LookupMount"); err != nil {
234		return containerdmount.Info{}, err
235	}
236
237	if f.LookupMountFn != nil {
238		return f.LookupMountFn(path)
239	}
240	return containerdmount.Info{}, nil
241}
242
243// Hostname is a fake call that invokes HostnameFn or just return nil.
244func (f *FakeOS) Hostname() (string, error) {
245	f.appendCalls("Hostname")
246	if err := f.getError("Hostname"); err != nil {
247		return "", err
248	}
249
250	if f.HostnameFn != nil {
251		return f.HostnameFn()
252	}
253	return "", nil
254}
255