Allow tests to mock filesystem

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

Change-Id: Idd67c68f7cb43bc2117cc78336347db7e2f21991
diff --git a/context.go b/context.go
index b512106..0e9efad 100644
--- a/context.go
+++ b/context.go
@@ -19,7 +19,6 @@
 	"errors"
 	"fmt"
 	"io"
-	"os"
 	"path/filepath"
 	"reflect"
 	"runtime"
@@ -98,6 +97,8 @@
 
 	// 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 @@
 		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 @@
 	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 @@
 	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 @@
 		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 @@
 		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