...

Source file src/github.com/onsi/gomega/gmeasure/cache.go

Documentation: github.com/onsi/gomega/gmeasure

     1  package gmeasure
     2  
     3  import (
     4  	"crypto/md5"
     5  	"encoding/json"
     6  	"fmt"
     7  	"os"
     8  	"path/filepath"
     9  
    10  	"github.com/onsi/gomega/internal/gutil"
    11  )
    12  
    13  const CACHE_EXT = ".gmeasure-cache"
    14  
    15  /*
    16  ExperimentCache provides a director-and-file based cache of experiments
    17  */
    18  type ExperimentCache struct {
    19  	Path string
    20  }
    21  
    22  /*
    23  NewExperimentCache creates and initializes a new cache.  Path must point to a directory (if path does not exist, NewExperimentCache will create a directory at path).
    24  
    25  Cached Experiments are stored as separate files in the cache directory - the filename is a hash of the Experiment name.  Each file contains two JSON-encoded objects - a CachedExperimentHeader that includes the experiment's name and cache version number, and then the Experiment itself.
    26  */
    27  func NewExperimentCache(path string) (ExperimentCache, error) {
    28  	stat, err := os.Stat(path)
    29  	if os.IsNotExist(err) {
    30  		err := os.MkdirAll(path, 0777)
    31  		if err != nil {
    32  			return ExperimentCache{}, err
    33  		}
    34  	} else if !stat.IsDir() {
    35  		return ExperimentCache{}, fmt.Errorf("%s is not a directory", path)
    36  	}
    37  
    38  	return ExperimentCache{
    39  		Path: path,
    40  	}, nil
    41  }
    42  
    43  /*
    44  CachedExperimentHeader captures the name of the Cached Experiment and its Version
    45  */
    46  type CachedExperimentHeader struct {
    47  	Name    string
    48  	Version int
    49  }
    50  
    51  func (cache ExperimentCache) hashOf(name string) string {
    52  	return fmt.Sprintf("%x", md5.Sum([]byte(name)))
    53  }
    54  
    55  func (cache ExperimentCache) readHeader(filename string) (CachedExperimentHeader, error) {
    56  	out := CachedExperimentHeader{}
    57  	f, err := os.Open(filepath.Join(cache.Path, filename))
    58  	if err != nil {
    59  		return out, err
    60  	}
    61  	defer f.Close()
    62  	err = json.NewDecoder(f).Decode(&out)
    63  	return out, err
    64  }
    65  
    66  /*
    67  List returns a list of all Cached Experiments found in the cache.
    68  */
    69  func (cache ExperimentCache) List() ([]CachedExperimentHeader, error) {
    70  	var out []CachedExperimentHeader
    71  	names, err := gutil.ReadDir(cache.Path)
    72  	if err != nil {
    73  		return out, err
    74  	}
    75  	for _, name := range names {
    76  		if filepath.Ext(name) != CACHE_EXT {
    77  			continue
    78  		}
    79  		header, err := cache.readHeader(name)
    80  		if err != nil {
    81  			return out, err
    82  		}
    83  		out = append(out, header)
    84  	}
    85  	return out, nil
    86  }
    87  
    88  /*
    89  Clear empties out the cache - this will delete any and all detected cache files in the cache directory.  Use with caution!
    90  */
    91  func (cache ExperimentCache) Clear() error {
    92  	names, err := gutil.ReadDir(cache.Path)
    93  	if err != nil {
    94  		return err
    95  	}
    96  	for _, name := range names {
    97  		if filepath.Ext(name) != CACHE_EXT {
    98  			continue
    99  		}
   100  		err := os.Remove(filepath.Join(cache.Path, name))
   101  		if err != nil {
   102  			return err
   103  		}
   104  	}
   105  	return nil
   106  }
   107  
   108  /*
   109  Load fetches an experiment from the cache.  Lookup occurs by name.  Load requires that the version numer in the cache is equal to or greater than the passed-in version.
   110  
   111  If an experiment with corresponding name and version >= the passed-in version is found, it is unmarshaled and returned.
   112  
   113  If no experiment is found, or the cached version is smaller than the passed-in version, Load will return nil.
   114  
   115  When paired with Ginkgo you can cache experiments and prevent potentially expensive recomputation with this pattern:
   116  
   117  	const EXPERIMENT_VERSION = 1 //bump this to bust the cache and recompute _all_ experiments
   118  
   119      Describe("some experiments", func() {
   120      	var cache gmeasure.ExperimentCache
   121      	var experiment *gmeasure.Experiment
   122  
   123      	BeforeEach(func() {
   124      		cache = gmeasure.NewExperimentCache("./gmeasure-cache")
   125      		name := CurrentSpecReport().LeafNodeText
   126      		experiment = cache.Load(name, EXPERIMENT_VERSION)
   127      		if experiment != nil {
   128      			AddReportEntry(experiment)
   129      			Skip("cached")
   130      		}
   131      		experiment = gmeasure.NewExperiment(name)
   132  			AddReportEntry(experiment)
   133      	})
   134  
   135      	It("foo runtime", func() {
   136      		experiment.SampleDuration("runtime", func() {
   137      			//do stuff
   138      		}, gmeasure.SamplingConfig{N:100})
   139      	})
   140  
   141      	It("bar runtime", func() {
   142      		experiment.SampleDuration("runtime", func() {
   143      			//do stuff
   144      		}, gmeasure.SamplingConfig{N:100})
   145      	})
   146  
   147      	AfterEach(func() {
   148      		if !CurrentSpecReport().State.Is(types.SpecStateSkipped) {
   149  	    		cache.Save(experiment.Name, EXPERIMENT_VERSION, experiment)
   150  	    	}
   151      	})
   152      })
   153  */
   154  func (cache ExperimentCache) Load(name string, version int) *Experiment {
   155  	path := filepath.Join(cache.Path, cache.hashOf(name)+CACHE_EXT)
   156  	f, err := os.Open(path)
   157  	if err != nil {
   158  		return nil
   159  	}
   160  	defer f.Close()
   161  	dec := json.NewDecoder(f)
   162  	header := CachedExperimentHeader{}
   163  	dec.Decode(&header)
   164  	if header.Version < version {
   165  		return nil
   166  	}
   167  	out := NewExperiment("")
   168  	err = dec.Decode(out)
   169  	if err != nil {
   170  		return nil
   171  	}
   172  	return out
   173  }
   174  
   175  /*
   176  Save stores the passed-in experiment to the cache with the passed-in name and version.
   177  */
   178  func (cache ExperimentCache) Save(name string, version int, experiment *Experiment) error {
   179  	path := filepath.Join(cache.Path, cache.hashOf(name)+CACHE_EXT)
   180  	f, err := os.Create(path)
   181  	if err != nil {
   182  		return err
   183  	}
   184  	defer f.Close()
   185  	enc := json.NewEncoder(f)
   186  	err = enc.Encode(CachedExperimentHeader{
   187  		Name:    name,
   188  		Version: version,
   189  	})
   190  	if err != nil {
   191  		return err
   192  	}
   193  	return enc.Encode(experiment)
   194  }
   195  
   196  /*
   197  Delete removes the experiment with the passed-in name from the cache
   198  */
   199  func (cache ExperimentCache) Delete(name string) error {
   200  	path := filepath.Join(cache.Path, cache.hashOf(name)+CACHE_EXT)
   201  	return os.Remove(path)
   202  }
   203  

View as plain text