Allow tests to mock filesystem

Tests can call Context.MockFileSystem to pass in a map of filenames to
contents.

Change-Id: Idd67c68f7cb43bc2117cc78336347db7e2f21991
This commit is contained in:
Colin Cross 2016-06-02 15:30:20 -07:00
parent fd1ef9ec7a
commit d7b0f60802
6 changed files with 168 additions and 59 deletions

View file

@ -8,6 +8,7 @@ bootstrap_go_package(
pkgPath = "github.com/google/blueprint",
srcs = [
"context.go",
"fs.go",
"live_tracker.go",
"mangle.go",
"module_ctx.go",

View file

@ -60,8 +60,8 @@ rule g.bootstrap.link
build $
${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg/github.com/google/blueprint.a $
: g.bootstrap.compile ${g.bootstrap.srcDir}/context.go $
${g.bootstrap.srcDir}/live_tracker.go ${g.bootstrap.srcDir}/mangle.go $
${g.bootstrap.srcDir}/module_ctx.go $
${g.bootstrap.srcDir}/fs.go ${g.bootstrap.srcDir}/live_tracker.go $
${g.bootstrap.srcDir}/mangle.go ${g.bootstrap.srcDir}/module_ctx.go $
${g.bootstrap.srcDir}/ninja_defs.go $
${g.bootstrap.srcDir}/ninja_strings.go $
${g.bootstrap.srcDir}/ninja_writer.go $
@ -81,7 +81,7 @@ default $
# Variant:
# Type: bootstrap_go_package
# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
# Defined: Blueprints:81:1
# Defined: Blueprints:83:1
build $
${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap/pkg/github.com/google/blueprint/bootstrap.a $
@ -108,7 +108,7 @@ default $
# Variant:
# Type: bootstrap_go_package
# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
# Defined: Blueprints:100:1
# Defined: Blueprints:102:1
build $
${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg/github.com/google/blueprint/bootstrap/bpdoc.a $
@ -128,7 +128,7 @@ default $
# Variant:
# Type: bootstrap_go_package
# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
# Defined: Blueprints:47:1
# Defined: Blueprints:49:1
build $
${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $
@ -143,7 +143,7 @@ default $
# Variant:
# Type: bootstrap_go_package
# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
# Defined: Blueprints:31:1
# Defined: Blueprints:33:1
build $
${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg/github.com/google/blueprint/parser.a $
@ -161,7 +161,7 @@ default $
# Variant:
# Type: bootstrap_go_package
# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
# Defined: Blueprints:53:1
# Defined: Blueprints:55:1
build $
${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a $
@ -176,7 +176,7 @@ default $
# Variant:
# Type: bootstrap_go_package
# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
# Defined: Blueprints:65:1
# Defined: Blueprints:67:1
build $
${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a $
@ -194,7 +194,7 @@ default $
# Variant:
# Type: bootstrap_core_go_binary
# Factory: github.com/google/blueprint/bootstrap.newGoBinaryModuleFactory.func1
# Defined: Blueprints:143:1
# Defined: Blueprints:145:1
build ${g.bootstrap.buildDir}/.bootstrap/choosestage/obj/choosestage.a: $
g.bootstrap.compile ${g.bootstrap.srcDir}/choosestage/choosestage.go | $
@ -217,7 +217,7 @@ default ${g.bootstrap.BinDir}/choosestage
# Variant:
# Type: bootstrap_core_go_binary
# Factory: github.com/google/blueprint/bootstrap.newGoBinaryModuleFactory.func1
# Defined: Blueprints:133:1
# Defined: Blueprints:135:1
build ${g.bootstrap.buildDir}/.bootstrap/gotestmain/obj/gotestmain.a: $
g.bootstrap.compile ${g.bootstrap.srcDir}/gotestmain/gotestmain.go | $
@ -240,7 +240,7 @@ default ${g.bootstrap.BinDir}/gotestmain
# Variant:
# Type: bootstrap_core_go_binary
# Factory: github.com/google/blueprint/bootstrap.newGoBinaryModuleFactory.func1
# Defined: Blueprints:138:1
# Defined: Blueprints:140:1
build ${g.bootstrap.buildDir}/.bootstrap/gotestrunner/obj/gotestrunner.a: $
g.bootstrap.compile ${g.bootstrap.srcDir}/gotestrunner/gotestrunner.go $
@ -263,7 +263,7 @@ default ${g.bootstrap.BinDir}/gotestrunner
# Variant:
# Type: bootstrap_core_go_binary
# Factory: github.com/google/blueprint/bootstrap.newGoBinaryModuleFactory.func1
# Defined: Blueprints:112:1
# Defined: Blueprints:114:1
build ${g.bootstrap.buildDir}/.bootstrap/minibp/obj/minibp.a: $
g.bootstrap.compile ${g.bootstrap.srcDir}/bootstrap/minibp/main.go | $

View file

@ -19,7 +19,6 @@ import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"reflect"
"runtime"
@ -98,6 +97,8 @@ type Context struct {
// set lazily by sortedModuleNames
cachedSortedModuleNames []string
fs fileSystem
}
// An Error describes a problem that was encountered that is related to a
@ -246,6 +247,7 @@ func NewContext() *Context {
moduleGroups: make(map[string]*moduleGroup),
moduleInfo: make(map[Module]*moduleInfo),
moduleNinjaNames: make(map[string]*moduleGroup),
fs: fs,
}
ctx.RegisterBottomUpMutator("blueprint_deps", blueprintDepsMutator)
@ -711,6 +713,14 @@ loop:
return
}
// MockFileSystem causes the Context to replace all reads with accesses to the provided map of
// filenames to contents stored as a byte slice.
func (c *Context) MockFileSystem(files map[string][]byte) {
c.fs = &mockFS{
files: files,
}
}
// parseBlueprintFile parses a single Blueprints file, returning any errors through
// errsCh, any defined modules through modulesCh, any sub-Blueprints files through
// blueprintsCh, and any dependencies on Blueprints files or directories through
@ -719,7 +729,7 @@ func (c *Context) parseBlueprintsFile(filename string, scope *parser.Scope, root
errsCh chan<- []error, fileCh chan<- *parser.File, blueprintsCh chan<- stringAndScope,
depsCh chan<- string) {
f, err := os.Open(filename)
f, err := c.fs.Open(filename)
if err != nil {
errsCh <- []error{err}
return
@ -772,15 +782,15 @@ func (c *Context) findBuildBlueprints(dir string, build []string,
deps = append(deps, matchedDirs...)
for _, foundBlueprints := range matches {
fileInfo, err := os.Stat(foundBlueprints)
if os.IsNotExist(err) {
exists, dir, err := c.fs.Exists(foundBlueprints)
if err != nil {
errs = append(errs, err)
} else if !exists {
errs = append(errs, &Error{
Err: fmt.Errorf("%q not found", foundBlueprints),
})
continue
}
if fileInfo.IsDir() {
} else if dir {
errs = append(errs, &Error{
Err: fmt.Errorf("%q is a directory", foundBlueprints),
})
@ -819,29 +829,34 @@ func (c *Context) findSubdirBlueprints(dir string, subdirs []string, subdirsPos
deps = append(deps, matchedDirs...)
for _, foundSubdir := range matches {
fileInfo, subdirStatErr := os.Stat(foundSubdir)
exists, dir, subdirStatErr := c.fs.Exists(foundSubdir)
if subdirStatErr != nil {
errs = append(errs, subdirStatErr)
continue
}
// Skip files
if !fileInfo.IsDir() {
if !dir {
continue
}
var subBlueprints string
if subBlueprintsName != "" {
subBlueprints = filepath.Join(foundSubdir, subBlueprintsName)
_, err = os.Stat(subBlueprints)
exists, _, err = c.fs.Exists(subBlueprints)
}
if os.IsNotExist(err) || subBlueprints == "" {
if err == nil && (!exists || subBlueprints == "") {
subBlueprints = filepath.Join(foundSubdir, "Blueprints")
_, err = os.Stat(subBlueprints)
exists, _, err = c.fs.Exists(subBlueprints)
}
if os.IsNotExist(err) {
if err != nil {
errs = append(errs, err)
continue
}
if !exists {
// There is no Blueprints file in this subdirectory. We
// need to add the directory to the list of dependencies
// so that if someone adds a Blueprints file in the

View file

@ -109,10 +109,62 @@ func TestContextParse(t *testing.T) {
// |===F===| B, D and E should not be walked.
func TestWalkDeps(t *testing.T) {
ctx := NewContext()
ctx.MockFileSystem(map[string][]byte{
"Blueprints": []byte(`
foo_module {
name: "A",
deps: ["B", "C"],
}
bar_module {
name: "B",
deps: ["D"],
}
foo_module {
name: "C",
deps: ["E", "F"],
}
foo_module {
name: "D",
}
bar_module {
name: "E",
deps: ["G"],
}
foo_module {
name: "F",
deps: ["G"],
}
foo_module {
name: "G",
}
`),
})
ctx.RegisterModuleType("foo_module", newFooModule)
ctx.RegisterModuleType("bar_module", newBarModule)
ctx.ParseBlueprintsFiles("context_test_Blueprints")
ctx.ResolveDependencies(nil)
_, errs := ctx.ParseBlueprintsFiles("Blueprints")
if len(errs) > 0 {
t.Errorf("unexpected parse errors:")
for _, err := range errs {
t.Errorf(" %s", err)
}
t.FailNow()
}
errs = ctx.ResolveDependencies(nil)
if len(errs) > 0 {
t.Errorf("unexpected dep errors:")
for _, err := range errs {
t.Errorf(" %s", err)
}
t.FailNow()
}
var output string
topModule := ctx.moduleGroups["A"].modules[0]

View file

@ -1,32 +0,0 @@
foo_module {
name: "A",
deps: ["B", "C"],
}
bar_module {
name: "B",
deps: ["D"],
}
foo_module {
name: "C",
deps: ["E", "F"],
}
foo_module {
name: "D",
}
bar_module {
name: "E",
deps: ["G"],
}
foo_module {
name: "F",
deps: ["G"],
}
foo_module {
name: "G",
}

73
fs.go Normal file
View file

@ -0,0 +1,73 @@
// Copyright 2016 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package blueprint
import (
"bytes"
"io"
"io/ioutil"
"os"
)
// Based on Andrew Gerrand's "10 things you (probably) dont' know about Go"
var fs fileSystem = osFS{}
type fileSystem interface {
Open(name string) (io.ReadCloser, error)
Exists(name string) (bool, bool, error)
}
// osFS implements fileSystem using the local disk.
type osFS struct{}
func (osFS) Open(name string) (io.ReadCloser, error) { return os.Open(name) }
func (osFS) Exists(name string) (bool, bool, error) {
stat, err := os.Stat(name)
if err == nil {
return true, stat.IsDir(), nil
} else if os.IsNotExist(err) {
return false, false, nil
} else {
return false, false, err
}
}
type mockFS struct {
files map[string][]byte
}
func (m mockFS) Open(name string) (io.ReadCloser, error) {
if f, ok := m.files[name]; ok {
return struct {
io.Closer
*bytes.Reader
}{
ioutil.NopCloser(nil),
bytes.NewReader(f),
}, nil
}
return nil, &os.PathError{
Op: "open",
Path: name,
Err: os.ErrNotExist,
}
}
func (m mockFS) Exists(name string) (bool, bool, error) {
_, ok := m.files[name]
return ok, false, nil
}