blob: f698f90ee4ab26f53f5026a8cc22d096a9047d27 [file] [log] [blame]
// 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
}