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  }