| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package testutil |
| |
| import ( |
| "bytes" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| "regexp" |
| "runtime" |
| ) |
| |
| // FormatLogLine will prepend the file and line number of the caller |
| // at the specificied depth (as per runtime.Caller) to the supplied |
| // format and args and return a formatted string. It is useful when |
| // implementing functions that factor out error handling and reporting |
| // in tests. |
| func FormatLogLine(depth int, format string, args ...interface{}) string { |
| _, file, line, _ := runtime.Caller(depth) |
| nargs := []interface{}{filepath.Base(file), line} |
| nargs = append(nargs, args...) |
| return fmt.Sprintf("%s:%d: "+format, nargs...) |
| } |
| |
| // FileTreeOpts contains options controlling how FileTreeEqual is performed. |
| type FileTreeOpts struct { |
| // Debug is written to with additional information describing trees that are |
| // not equal. |
| Debug io.Writer |
| |
| // The pathname of regular files must match File{A,B} if they are provided, |
| // otherwise the file is filtered out. Similarly the pathname of directories |
| // must match Dir{A,B} if they are provided. {File,Dir}A matches pathnames |
| // found under aRoot, while {File,Dir}B matches under bRoot. |
| FileA, DirA *regexp.Regexp |
| FileB, DirB *regexp.Regexp |
| } |
| |
| func (opts FileTreeOpts) debug(format string, args ...interface{}) { |
| if opts.Debug != nil { |
| fmt.Fprintf(opts.Debug, format, args...) |
| } |
| } |
| |
| // FileTreeEqual returns true if the filesystem trees rooted at aRoot and bRoot |
| // are the same. Use opts to control how matching is performed. |
| func FileTreeEqual(aRoot, bRoot string, opts FileTreeOpts) (bool, error) { |
| type node struct { |
| path string |
| info os.FileInfo |
| } |
| // Walk trees to collect nodes. |
| var aNodes, bNodes []node |
| nodes, file, dir := &aNodes, opts.FileA, opts.DirA |
| collectFn := func(path string, info os.FileInfo, err error) error { |
| if err != nil { |
| return fmt.Errorf("walk failed: %v", err) |
| } |
| if info.IsDir() { |
| if dir != nil && !dir.MatchString(path) { |
| return nil |
| } |
| } else { |
| if file != nil && !file.MatchString(path) { |
| return nil |
| } |
| } |
| *nodes = append(*nodes, node{path, info}) |
| return nil |
| } |
| if err := filepath.Walk(aRoot, collectFn); err != nil { |
| return false, err |
| } |
| nodes, file, dir = &bNodes, opts.FileB, opts.DirB |
| if err := filepath.Walk(bRoot, collectFn); err != nil { |
| return false, err |
| } |
| // Compare nodes. |
| if len(aNodes) != len(bNodes) { |
| opts.debug("node count mismatch: %v, %v", aNodes, bNodes) |
| return false, nil |
| } |
| for ix := 0; ix < len(aNodes); ix++ { |
| aNode, bNode := aNodes[ix], bNodes[ix] |
| aRel, err := filepath.Rel(aRoot, aNode.path) |
| if err != nil { |
| return false, fmt.Errorf("Rel(%v, %v) failed: %v", aRoot, aNode.path, err) |
| } |
| bRel, err := filepath.Rel(bRoot, bNode.path) |
| if err != nil { |
| return false, fmt.Errorf("Rel(%v, %v) failed: %v", bRoot, bNode.path, err) |
| } |
| if aRel != bRel { |
| opts.debug("%v relative path doesn't match %v", aRel, bRel) |
| return false, nil |
| } |
| aDir, bDir := aNode.info.IsDir(), bNode.info.IsDir() |
| if aDir != bDir { |
| opts.debug("%v isdir:%v, but %v isdir:%v", aNode.path, aDir, bNode.path, bDir) |
| return false, nil |
| } |
| if aDir { |
| continue |
| } |
| aBytes, err := ioutil.ReadFile(aNode.path) |
| if err != nil { |
| return false, fmt.Errorf("%v read failed: %v", aNode.path, err) |
| } |
| bBytes, err := ioutil.ReadFile(bNode.path) |
| if err != nil { |
| return false, fmt.Errorf("%v read failed: %v", bNode.path, err) |
| } |
| if !bytes.Equal(aBytes, bBytes) { |
| opts.debug("%v bytes don't match", aRel) |
| return false, nil |
| } |
| } |
| return true, nil |
| } |