github.com/go-maxhub/gremlins@v1.0.1-0.20231227222204-b03a6a1e3e09/core/engine/workdir/workdir.go (about) 1 /* 2 * Copyright 2022 The Gremlins 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 17 package workdir 18 19 import ( 20 "io" 21 "io/fs" 22 "os" 23 "path/filepath" 24 "sync" 25 26 "github.com/go-maxhub/gremlins/core/log" 27 ) 28 29 // Dealer is the responsible for creating and returning the reference 30 // to a workdir to use during mutation testing instead of the actual 31 // source code. 32 // 33 // It has two methods: 34 // 35 // Get that returns a folder name that will be used by Gremlins as workdir. 36 // Clean that must be called to remove all the created folders. 37 type Dealer interface { 38 Get(idf string) (string, error) 39 Clean() 40 WorkDir() string 41 } 42 43 // CachedDealer is the implementation of the Dealer interface, responsible 44 // for creating a working directory of a source directory. It allows 45 // Gremlins not to work in the actual source directory messing up 46 // with the source code files. 47 type CachedDealer struct { 48 mutex *sync.RWMutex 49 cache map[string]string 50 workDir string 51 srcDir string 52 } 53 54 // NewCachedDealer instantiates a new Dealer that keeps a cache of the 55 // instantiated folders. Every time a new working directory is requested 56 // with the same identifier, the same folder reference is returned. 57 func NewCachedDealer(workDir, srcDir string) *CachedDealer { 58 dealer := &CachedDealer{ 59 mutex: &sync.RWMutex{}, 60 cache: make(map[string]string), 61 workDir: workDir, 62 srcDir: srcDir, 63 } 64 65 return dealer 66 } 67 68 // Get provides a working directory where all the files are full copies 69 // to the original files in the source directory. 70 func (cd *CachedDealer) Get(idf string) (string, error) { 71 dstDir, ok := cd.fromCache(idf) 72 if ok { 73 return dstDir, nil 74 } 75 76 dstDir, err := os.MkdirTemp(cd.workDir, "wd-*") 77 if err != nil { 78 return "", err 79 } 80 err = filepath.Walk(cd.srcDir, cd.copyTo(dstDir)) 81 if err != nil { 82 return "", err 83 } 84 85 cd.setCache(idf, dstDir) 86 87 return dstDir, nil 88 } 89 90 // WorkDir provides the root working directory. 91 func (cd *CachedDealer) WorkDir() string { 92 return cd.workDir 93 } 94 95 // Clean frees all the cached folders and removes all of them from disk. 96 func (cd *CachedDealer) Clean() { 97 for _, v := range cd.cache { 98 err := os.RemoveAll(v) 99 if err != nil { 100 log.Errorf("impossible to remove temporary folder %s: %s\n", v, err) 101 } 102 } 103 cd.cache = make(map[string]string) 104 } 105 106 func (cd *CachedDealer) fromCache(idf string) (string, bool) { 107 cd.mutex.RLock() 108 defer cd.mutex.RUnlock() 109 dstDir, ok := cd.cache[idf] 110 if ok { 111 return dstDir, true 112 } 113 114 return "", false 115 } 116 117 func (cd *CachedDealer) setCache(idf, folder string) { 118 cd.mutex.Lock() 119 defer cd.mutex.Unlock() 120 cd.cache[idf] = folder 121 } 122 123 func (cd *CachedDealer) copyTo(dstDir string) func(srcPath string, info fs.FileInfo, err error) error { 124 return func(srcPath string, info fs.FileInfo, err error) error { 125 if err != nil { 126 return err 127 } 128 relPath, err := filepath.Rel(cd.srcDir, srcPath) 129 if err != nil { 130 return err 131 } 132 if relPath == "." { 133 return nil 134 } 135 dstPath := filepath.Join(dstDir, relPath) 136 137 return copyPath(srcPath, dstPath, info) 138 } 139 } 140 141 func copyPath(srcPath, dstPath string, info fs.FileInfo) error { 142 switch mode := info.Mode(); { 143 case mode.IsDir(): 144 if err := os.Mkdir(dstPath, mode); err != nil && !os.IsExist(err) { 145 return err 146 } 147 case mode.IsRegular(): 148 if err := doCopy(srcPath, dstPath, mode); err != nil { 149 return err 150 } 151 } 152 153 return nil 154 } 155 156 func doCopy(srcPath, dstPath string, fileMode fs.FileMode) error { 157 s, err := os.Open(srcPath) 158 if err != nil { 159 return err 160 } 161 //nolint:nosnakecase 162 d, err := os.OpenFile(dstPath, os.O_CREATE|os.O_RDWR, fileMode) 163 if err != nil { 164 return err 165 } 166 167 if _, err = io.Copy(d, s); err != nil { 168 return err 169 } 170 171 return nil 172 }