github.com/freiheit-com/kuberpult@v1.24.2-0.20240328135542-315d5630abe6/pkg/testfs/testfs.go (about) 1 /*This file is part of kuberpult. 2 3 Kuberpult is free software: you can redistribute it and/or modify 4 it under the terms of the Expat(MIT) License as published by 5 the Free Software Foundation. 6 7 Kuberpult is distributed in the hope that it will be useful, 8 but WITHOUT ANY WARRANTY; without even the implied warranty of 9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 MIT License for more details. 11 12 You should have received a copy of the MIT License 13 along with kuberpult. If not, see <https://directory.fsf.org/wiki/License:Expat>. 14 15 Copyright 2023 freiheit.com*/ 16 17 package testfs 18 19 import ( 20 "fmt" 21 "os" 22 "sync" 23 24 "github.com/go-git/go-billy/v5" 25 ) 26 27 type Operation int 28 29 const ( 30 NONE Operation = iota 31 CREATE 32 OPEN 33 OPENFILE 34 STAT 35 RENAME 36 REMOVE 37 READDIR 38 SYMLINK 39 READLINK 40 MKDIRALL 41 ) 42 43 func (o Operation) String() string { 44 switch o { 45 case CREATE: 46 return "Create" 47 case OPEN: 48 return "Open" 49 case OPENFILE: 50 return "OpenFile" 51 case STAT: 52 return "Stat" 53 case RENAME: 54 return "Rename" 55 case REMOVE: 56 return "Remove" 57 case READDIR: 58 return "ReadDir" 59 case SYMLINK: 60 return "Symlink" 61 case READLINK: 62 return "ReadLink" 63 case MKDIRALL: 64 return "MkdirAll" 65 } 66 return fmt.Sprintf("unknown(%d)", o) 67 } 68 69 type FileOperation struct { 70 Operation Operation 71 Filename string 72 } 73 74 type value struct { 75 errored bool 76 } 77 78 // UsageCollector tracks which file operations have been used in a test suite and reports which were not 79 // tested with an injected error. 80 type UsageCollector struct { 81 mx sync.Mutex 82 usage map[FileOperation]value 83 } 84 85 func (u *UsageCollector) used(op Operation, filename string) { 86 u.mx.Lock() 87 defer u.mx.Unlock() 88 if u.usage == nil { 89 u.usage = map[FileOperation]value{} 90 } 91 _, ok := u.usage[FileOperation{op, filename}] 92 if !ok { 93 u.usage[FileOperation{op, filename}] = value{ 94 errored: false, 95 } 96 } 97 } 98 99 func (u *UsageCollector) errored(op Operation, filename string) { 100 u.mx.Lock() 101 defer u.mx.Unlock() 102 if u.usage == nil { 103 u.usage = map[FileOperation]value{} 104 } 105 u.usage[FileOperation{op, filename}] = value{errored: true} 106 } 107 108 func (u *UsageCollector) UntestedOps() []FileOperation { 109 u.mx.Lock() 110 defer u.mx.Unlock() 111 result := []FileOperation{} 112 for k, v := range u.usage { 113 if !v.errored { 114 result = append(result, k) 115 } 116 } 117 return result 118 } 119 120 type errorInjector struct { 121 operation Operation 122 filename string 123 err error 124 used bool 125 collector *UsageCollector 126 } 127 128 func (e *errorInjector) inject(op Operation, filename string) error { 129 if e.used { 130 return nil 131 } else if e.operation == op && e.filename == filename { 132 e.used = true 133 e.collector.errored(op, filename) 134 return e.err 135 } 136 e.collector.used(op, filename) 137 return nil 138 } 139 140 func (uc *UsageCollector) WithError(fs billy.Filesystem, op Operation, filename string, err error) *Filesystem { 141 return &Filesystem{ 142 Inner: fs, 143 errorInjector: errorInjector{ 144 used: false, 145 operation: op, 146 filename: filename, 147 err: err, 148 collector: uc, 149 }, 150 } 151 } 152 153 // A special filesystem that allows injecting errors at arbitrary operations. 154 type Filesystem struct { 155 Inner billy.Filesystem 156 errorInjector errorInjector 157 } 158 159 func (f *Filesystem) Create(filename string) (billy.File, error) { 160 err := f.errorInjector.inject(CREATE, filename) 161 if err != nil { 162 return nil, err 163 } 164 return f.Inner.Create(filename) 165 } 166 167 func (f *Filesystem) Open(filename string) (billy.File, error) { 168 err := f.errorInjector.inject(OPEN, filename) 169 if err != nil { 170 return nil, err 171 } 172 return f.Inner.Open(filename) 173 } 174 175 func (f *Filesystem) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) { 176 err := f.errorInjector.inject(OPENFILE, filename) 177 if err != nil { 178 return nil, err 179 } 180 return f.Inner.OpenFile(filename, flag, perm) 181 } 182 183 func (f *Filesystem) Stat(filename string) (os.FileInfo, error) { 184 err := f.errorInjector.inject(STAT, filename) 185 if err != nil { 186 return nil, err 187 } 188 return f.Inner.Stat(filename) 189 } 190 191 func (f *Filesystem) Rename(oldpath, newpath string) error { 192 err := f.errorInjector.inject(RENAME, oldpath) 193 if err != nil { 194 return err 195 } 196 return f.Inner.Rename(oldpath, newpath) 197 } 198 199 func (f *Filesystem) Remove(filename string) error { 200 err := f.errorInjector.inject(REMOVE, filename) 201 if err != nil { 202 return err 203 } 204 return f.Inner.Remove(filename) 205 } 206 207 func (f *Filesystem) Join(elem ...string) string { 208 return f.Inner.Join(elem...) 209 } 210 211 func (f *Filesystem) TempFile(dir, prefix string) (billy.File, error) { 212 return f.Inner.TempFile(dir, prefix) 213 } 214 215 func (f *Filesystem) Lstat(filename string) (os.FileInfo, error) { 216 return f.Inner.Lstat(filename) 217 } 218 219 func (f *Filesystem) Symlink(target, link string) error { 220 err := f.errorInjector.inject(SYMLINK, link) 221 if err != nil { 222 return err 223 } 224 return f.Inner.Symlink(target, link) 225 } 226 227 func (f *Filesystem) Readlink(link string) (string, error) { 228 err := f.errorInjector.inject(READLINK, link) 229 if err != nil { 230 return "", err 231 } 232 return f.Inner.Readlink(link) 233 } 234 235 func (f *Filesystem) ReadDir(path string) ([]os.FileInfo, error) { 236 err := f.errorInjector.inject(READDIR, path) 237 if err != nil { 238 return nil, err 239 } 240 return f.Inner.ReadDir(path) 241 } 242 243 func (f *Filesystem) MkdirAll(filename string, perm os.FileMode) error { 244 err := f.errorInjector.inject(MKDIRALL, filename) 245 if err != nil { 246 return err 247 } 248 return f.Inner.MkdirAll(filename, perm) 249 } 250 251 func (f *Filesystem) Chroot(path string) (billy.Filesystem, error) { 252 panic("Chroot not implemented") 253 } 254 255 func (f *Filesystem) Root() string { 256 panic("Root not implemented") 257 }