github.com/westcoastroms/westcoastroms-build@v0.0.0-20190928114312-2350e5a73030/build/blueprint/context_test.go (about)

     1  // Copyright 2014 Google Inc. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package blueprint
    16  
    17  import (
    18  	"bytes"
    19  	"errors"
    20  	"fmt"
    21  	"reflect"
    22  	"strings"
    23  	"sync"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/google/blueprint/parser"
    28  )
    29  
    30  type Walker interface {
    31  	Walk() bool
    32  }
    33  
    34  type fooModule struct {
    35  	SimpleName
    36  	properties struct {
    37  		Deps []string
    38  		Foo  string
    39  	}
    40  }
    41  
    42  func newFooModule() (Module, []interface{}) {
    43  	m := &fooModule{}
    44  	return m, []interface{}{&m.properties, &m.SimpleName.Properties}
    45  }
    46  
    47  func (f *fooModule) GenerateBuildActions(ModuleContext) {
    48  }
    49  
    50  func (f *fooModule) DynamicDependencies(ctx DynamicDependerModuleContext) []string {
    51  	return f.properties.Deps
    52  }
    53  
    54  func (f *fooModule) Foo() string {
    55  	return f.properties.Foo
    56  }
    57  
    58  func (f *fooModule) Walk() bool {
    59  	return true
    60  }
    61  
    62  type barModule struct {
    63  	SimpleName
    64  	properties struct {
    65  		Deps []string
    66  		Bar  bool
    67  	}
    68  }
    69  
    70  func newBarModule() (Module, []interface{}) {
    71  	m := &barModule{}
    72  	return m, []interface{}{&m.properties, &m.SimpleName.Properties}
    73  }
    74  
    75  func (b *barModule) DynamicDependencies(ctx DynamicDependerModuleContext) []string {
    76  	return b.properties.Deps
    77  }
    78  
    79  func (b *barModule) GenerateBuildActions(ModuleContext) {
    80  }
    81  
    82  func (b *barModule) Bar() bool {
    83  	return b.properties.Bar
    84  }
    85  
    86  func (b *barModule) Walk() bool {
    87  	return false
    88  }
    89  
    90  func TestContextParse(t *testing.T) {
    91  	ctx := NewContext()
    92  	ctx.RegisterModuleType("foo_module", newFooModule)
    93  	ctx.RegisterModuleType("bar_module", newBarModule)
    94  
    95  	r := bytes.NewBufferString(`
    96  		foo_module {
    97  	        name: "MyFooModule",
    98  			deps: ["MyBarModule"],
    99  		}
   100  
   101  		bar_module {
   102  	        name: "MyBarModule",
   103  		}
   104  	`)
   105  
   106  	_, _, errs := ctx.parseOne(".", "Blueprint", r, parser.NewScope(nil), nil)
   107  	if len(errs) > 0 {
   108  		t.Errorf("unexpected parse errors:")
   109  		for _, err := range errs {
   110  			t.Errorf("  %s", err)
   111  		}
   112  		t.FailNow()
   113  	}
   114  
   115  	_, errs = ctx.ResolveDependencies(nil)
   116  	if len(errs) > 0 {
   117  		t.Errorf("unexpected dep errors:")
   118  		for _, err := range errs {
   119  			t.Errorf("  %s", err)
   120  		}
   121  		t.FailNow()
   122  	}
   123  }
   124  
   125  // |---B===D       - represents a non-walkable edge
   126  // A               = represents a walkable edge
   127  // |===C---E===G
   128  //     |       |   A should not be visited because it's the root node.
   129  //     |===F===|   B, D and E should not be walked.
   130  func TestWalkDeps(t *testing.T) {
   131  	ctx := NewContext()
   132  	ctx.MockFileSystem(map[string][]byte{
   133  		"Blueprints": []byte(`
   134  			foo_module {
   135  			    name: "A",
   136  			    deps: ["B", "C"],
   137  			}
   138  			
   139  			bar_module {
   140  			    name: "B",
   141  			    deps: ["D"],
   142  			}
   143  			
   144  			foo_module {
   145  			    name: "C",
   146  			    deps: ["E", "F"],
   147  			}
   148  			
   149  			foo_module {
   150  			    name: "D",
   151  			}
   152  			
   153  			bar_module {
   154  			    name: "E",
   155  			    deps: ["G"],
   156  			}
   157  			
   158  			foo_module {
   159  			    name: "F",
   160  			    deps: ["G"],
   161  			}
   162  			
   163  			foo_module {
   164  			    name: "G",
   165  			}
   166  		`),
   167  	})
   168  
   169  	ctx.RegisterModuleType("foo_module", newFooModule)
   170  	ctx.RegisterModuleType("bar_module", newBarModule)
   171  	_, errs := ctx.ParseBlueprintsFiles("Blueprints")
   172  	if len(errs) > 0 {
   173  		t.Errorf("unexpected parse errors:")
   174  		for _, err := range errs {
   175  			t.Errorf("  %s", err)
   176  		}
   177  		t.FailNow()
   178  	}
   179  
   180  	_, errs = ctx.ResolveDependencies(nil)
   181  	if len(errs) > 0 {
   182  		t.Errorf("unexpected dep errors:")
   183  		for _, err := range errs {
   184  			t.Errorf("  %s", err)
   185  		}
   186  		t.FailNow()
   187  	}
   188  
   189  	var outputDown string
   190  	var outputUp string
   191  	topModule := ctx.modulesFromName("A", nil)[0]
   192  	ctx.walkDeps(topModule,
   193  		func(dep depInfo, parent *moduleInfo) bool {
   194  			if dep.module.logicModule.(Walker).Walk() {
   195  				outputDown += ctx.ModuleName(dep.module.logicModule)
   196  				return true
   197  			}
   198  			return false
   199  		},
   200  		func(dep depInfo, parent *moduleInfo) {
   201  			if dep.module.logicModule.(Walker).Walk() {
   202  				outputUp += ctx.ModuleName(dep.module.logicModule)
   203  			}
   204  		})
   205  	if outputDown != "CFG" {
   206  		t.Fatalf("unexpected walkDeps behaviour: %s\ndown should be: CFG", outputDown)
   207  	}
   208  	if outputUp != "GFC" {
   209  		t.Fatalf("unexpected walkDeps behaviour: %s\nup should be: GFC", outputUp)
   210  	}
   211  }
   212  
   213  func TestCreateModule(t *testing.T) {
   214  	ctx := newContext()
   215  	ctx.MockFileSystem(map[string][]byte{
   216  		"Blueprints": []byte(`
   217  			foo_module {
   218  			    name: "A",
   219  			    deps: ["B", "C"],
   220  			}
   221  		`),
   222  	})
   223  
   224  	ctx.RegisterTopDownMutator("create", createTestMutator)
   225  	ctx.RegisterBottomUpMutator("deps", blueprintDepsMutator)
   226  
   227  	ctx.RegisterModuleType("foo_module", newFooModule)
   228  	ctx.RegisterModuleType("bar_module", newBarModule)
   229  	_, errs := ctx.ParseBlueprintsFiles("Blueprints")
   230  	if len(errs) > 0 {
   231  		t.Errorf("unexpected parse errors:")
   232  		for _, err := range errs {
   233  			t.Errorf("  %s", err)
   234  		}
   235  		t.FailNow()
   236  	}
   237  
   238  	_, errs = ctx.ResolveDependencies(nil)
   239  	if len(errs) > 0 {
   240  		t.Errorf("unexpected dep errors:")
   241  		for _, err := range errs {
   242  			t.Errorf("  %s", err)
   243  		}
   244  		t.FailNow()
   245  	}
   246  
   247  	a := ctx.modulesFromName("A", nil)[0].logicModule.(*fooModule)
   248  	b := ctx.modulesFromName("B", nil)[0].logicModule.(*barModule)
   249  	c := ctx.modulesFromName("C", nil)[0].logicModule.(*barModule)
   250  	d := ctx.modulesFromName("D", nil)[0].logicModule.(*fooModule)
   251  
   252  	checkDeps := func(m Module, expected string) {
   253  		var deps []string
   254  		ctx.VisitDirectDeps(m, func(m Module) {
   255  			deps = append(deps, ctx.ModuleName(m))
   256  		})
   257  		got := strings.Join(deps, ",")
   258  		if got != expected {
   259  			t.Errorf("unexpected %q dependencies, got %q expected %q",
   260  				ctx.ModuleName(m), got, expected)
   261  		}
   262  	}
   263  
   264  	checkDeps(a, "B,C")
   265  	checkDeps(b, "D")
   266  	checkDeps(c, "D")
   267  	checkDeps(d, "")
   268  }
   269  
   270  func createTestMutator(ctx TopDownMutatorContext) {
   271  	type props struct {
   272  		Name string
   273  		Deps []string
   274  	}
   275  
   276  	ctx.CreateModule(newBarModule, &props{
   277  		Name: "B",
   278  		Deps: []string{"D"},
   279  	})
   280  
   281  	ctx.CreateModule(newBarModule, &props{
   282  		Name: "C",
   283  		Deps: []string{"D"},
   284  	})
   285  
   286  	ctx.CreateModule(newFooModule, &props{
   287  		Name: "D",
   288  	})
   289  }
   290  
   291  func TestWalkFileOrder(t *testing.T) {
   292  	// Run the test once to see how long it normally takes
   293  	start := time.Now()
   294  	doTestWalkFileOrder(t, time.Duration(0))
   295  	duration := time.Since(start)
   296  
   297  	// Run the test again, but put enough of a sleep into each visitor to detect ordering
   298  	// problems if they exist
   299  	doTestWalkFileOrder(t, duration)
   300  }
   301  
   302  // test that WalkBlueprintsFiles calls asyncVisitor in the right order
   303  func doTestWalkFileOrder(t *testing.T, sleepDuration time.Duration) {
   304  	// setup mock context
   305  	ctx := newContext()
   306  	mockFiles := map[string][]byte{
   307  		"Blueprints": []byte(`
   308  			sample_module {
   309  			    name: "a",
   310  			}
   311  		`),
   312  		"dir1/Blueprints": []byte(`
   313  			sample_module {
   314  			    name: "b",
   315  			}
   316  		`),
   317  		"dir1/dir2/Blueprints": []byte(`
   318  			sample_module {
   319  			    name: "c",
   320  			}
   321  		`),
   322  	}
   323  	ctx.MockFileSystem(mockFiles)
   324  
   325  	// prepare to monitor the visit order
   326  	visitOrder := []string{}
   327  	visitLock := sync.Mutex{}
   328  	correctVisitOrder := []string{"Blueprints", "dir1/Blueprints", "dir1/dir2/Blueprints"}
   329  
   330  	// sleep longer when processing the earlier files
   331  	chooseSleepDuration := func(fileName string) (duration time.Duration) {
   332  		duration = time.Duration(0)
   333  		for i := len(correctVisitOrder) - 1; i >= 0; i-- {
   334  			if fileName == correctVisitOrder[i] {
   335  				return duration
   336  			}
   337  			duration = duration + sleepDuration
   338  		}
   339  		panic("unrecognized file name " + fileName)
   340  	}
   341  
   342  	visitor := func(file *parser.File) {
   343  		time.Sleep(chooseSleepDuration(file.Name))
   344  		visitLock.Lock()
   345  		defer visitLock.Unlock()
   346  		visitOrder = append(visitOrder, file.Name)
   347  	}
   348  	keys := []string{"Blueprints", "dir1/Blueprints", "dir1/dir2/Blueprints"}
   349  
   350  	// visit the blueprints files
   351  	ctx.WalkBlueprintsFiles(".", keys, visitor)
   352  
   353  	// check the order
   354  	if !reflect.DeepEqual(visitOrder, correctVisitOrder) {
   355  		t.Errorf("Incorrect visit order; expected %v, got %v", correctVisitOrder, visitOrder)
   356  	}
   357  }
   358  
   359  // test that WalkBlueprintsFiles reports syntax errors
   360  func TestWalkingWithSyntaxError(t *testing.T) {
   361  	// setup mock context
   362  	ctx := newContext()
   363  	mockFiles := map[string][]byte{
   364  		"Blueprints": []byte(`
   365  			sample_module {
   366  			    name: "a" "b",
   367  			}
   368  		`),
   369  		"dir1/Blueprints": []byte(`
   370  			sample_module {
   371  			    name: "b",
   372  		`),
   373  		"dir1/dir2/Blueprints": []byte(`
   374  			sample_module {
   375  			    name: "c",
   376  			}
   377  		`),
   378  	}
   379  	ctx.MockFileSystem(mockFiles)
   380  
   381  	keys := []string{"Blueprints", "dir1/Blueprints", "dir1/dir2/Blueprints"}
   382  
   383  	// visit the blueprints files
   384  	_, errs := ctx.WalkBlueprintsFiles(".", keys, func(file *parser.File) {})
   385  
   386  	expectedErrs := []error{
   387  		errors.New(`Blueprints:3:18: expected "}", found String`),
   388  		errors.New(`dir1/Blueprints:4:3: expected "}", found EOF`),
   389  	}
   390  	if fmt.Sprintf("%s", expectedErrs) != fmt.Sprintf("%s", errs) {
   391  		t.Errorf("Incorrect errors; expected:\n%s\ngot:\n%s", expectedErrs, errs)
   392  	}
   393  
   394  }