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:
parent
fd1ef9ec7a
commit
d7b0f60802
6 changed files with 168 additions and 59 deletions
|
@ -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",
|
||||
|
|
|
@ -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 | $
|
||||
|
|
41
context.go
41
context.go
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
73
fs.go
Normal 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
|
||||
}
|
Loading…
Reference in a new issue