Support checking syntax of generated Blueprint files

Adds a CheckBlueprintSyntax(...) method to check the syntax of a
Blueprint file.

Changes processModuleDef and newModule from being method on *Context to
being standalone functions. That ensures that CheckBlueprintSyntax(...)
does not need to take a context and so there is no chance that it can
change its state.
This commit is contained in:
Paul Duffin 2020-05-13 09:06:17 +01:00
parent 301749801e
commit 2a2c58ef46
3 changed files with 139 additions and 10 deletions

View file

@ -692,7 +692,7 @@ func (c *Context) ParseFileList(rootDir string, filePaths []string,
var scopedModuleFactories map[string]ModuleFactory
var addModule func(module *moduleInfo) []error
addModule = func(module *moduleInfo) ([]error) {
addModule = func(module *moduleInfo) []error {
// Run any load hooks immediately before it is sent to the moduleCh and is
// registered by name. This allows load hooks to set and/or modify any aspect
// of the module (including names) using information that is not available when
@ -716,7 +716,7 @@ func (c *Context) ParseFileList(rootDir string, filePaths []string,
for _, def := range file.Defs {
switch def := def.(type) {
case *parser.Module:
module, errs := c.processModuleDef(def, file.Name, scopedModuleFactories)
module, errs := processModuleDef(def, file.Name, c.moduleFactories, scopedModuleFactories, c.ignoreUnknownModuleTypes)
if len(errs) == 0 && module != nil {
errs = addModule(module)
}
@ -1349,7 +1349,7 @@ func (c *Context) prettyPrintGroupVariants(group *moduleGroup) string {
return strings.Join(variants, "\n ")
}
func (c *Context) newModule(factory ModuleFactory) *moduleInfo {
func newModule(factory ModuleFactory) *moduleInfo {
logicModule, properties := factory()
module := &moduleInfo{
@ -1362,15 +1362,15 @@ func (c *Context) newModule(factory ModuleFactory) *moduleInfo {
return module
}
func (c *Context) processModuleDef(moduleDef *parser.Module,
relBlueprintsFile string, scopedModuleFactories map[string]ModuleFactory) (*moduleInfo, []error) {
func processModuleDef(moduleDef *parser.Module,
relBlueprintsFile string, moduleFactories, scopedModuleFactories map[string]ModuleFactory, ignoreUnknownModuleTypes bool) (*moduleInfo, []error) {
factory, ok := c.moduleFactories[moduleDef.Type]
factory, ok := moduleFactories[moduleDef.Type]
if !ok && scopedModuleFactories != nil {
factory, ok = scopedModuleFactories[moduleDef.Type]
}
if !ok {
if c.ignoreUnknownModuleTypes {
if ignoreUnknownModuleTypes {
return nil, nil
}
@ -1382,7 +1382,7 @@ func (c *Context) processModuleDef(moduleDef *parser.Module,
}
}
module := c.newModule(factory)
module := newModule(factory)
module.typeName = moduleDef.Type
module.relBlueprintsFile = relBlueprintsFile

View file

@ -17,9 +17,11 @@ package blueprint
import (
"fmt"
"path/filepath"
"strings"
"sync"
"text/scanner"
"github.com/google/blueprint/parser"
"github.com/google/blueprint/pathtools"
"github.com/google/blueprint/proptools"
)
@ -988,7 +990,7 @@ func (mctx *mutatorContext) Rename(name string) {
}
func (mctx *mutatorContext) CreateModule(factory ModuleFactory, props ...interface{}) Module {
module := mctx.context.newModule(factory)
module := newModule(factory)
module.relBlueprintsFile = mctx.module.relBlueprintsFile
module.pos = mctx.module.pos
@ -1035,7 +1037,7 @@ type LoadHookContext interface {
}
func (l *loadHookContext) CreateModule(factory ModuleFactory, props ...interface{}) Module {
module := l.context.newModule(factory)
module := newModule(factory)
module.relBlueprintsFile = l.module.relBlueprintsFile
module.pos = l.module.pos
@ -1123,3 +1125,44 @@ func runAndRemoveLoadHooks(ctx *Context, config interface{}, module *moduleInfo,
return nil, nil
}
// Check the syntax of a generated blueprint file.
//
// This is intended to perform a quick sanity check for generated blueprint
// code to ensure that it is syntactically correct, where syntactically correct
// means:
// * No variable definitions.
// * Valid module types.
// * Valid property names.
// * Valid values for the property type.
//
// It does not perform any semantic checking of properties, existence of referenced
// files, or dependencies.
//
// At a low level it:
// * Parses the contents.
// * Invokes relevant factory to create Module instances.
// * Unpacks the properties into the Module.
// * Does not invoke load hooks or any mutators.
//
// The filename is only used for reporting errors.
func CheckBlueprintSyntax(moduleFactories map[string]ModuleFactory, filename string, contents string) []error {
scope := parser.NewScope(nil)
file, errs := parser.Parse(filename, strings.NewReader(contents), scope)
if len(errs) != 0 {
return errs
}
for _, def := range file.Defs {
switch def := def.(type) {
case *parser.Module:
_, moduleErrs := processModuleDef(def, filename, moduleFactories, nil, false)
errs = append(errs, moduleErrs...)
default:
panic(fmt.Errorf("unknown definition type: %T", def))
}
}
return errs
}

View file

@ -195,3 +195,89 @@ func TestAliases(t *testing.T) {
"\n 1:a, 2:a\n 1:a, 2:b\n 1:b, 2:a\n 1:b, 2:b")
})
}
func expectedErrors(t *testing.T, errs []error, expectedMessages ...string) {
t.Helper()
if len(errs) != len(expectedMessages) {
t.Errorf("expected %d error, found: %q", len(expectedMessages), errs)
} else {
for i, expected := range expectedMessages {
err := errs[i]
if err.Error() != expected {
t.Errorf("expected error %q found %q", expected, err)
}
}
}
}
func TestCheckBlueprintSyntax(t *testing.T) {
factories := map[string]ModuleFactory{
"test": newModuleCtxTestModule,
}
t.Run("valid", func(t *testing.T) {
errs := CheckBlueprintSyntax(factories, "path/Blueprint", `
test {
name: "test",
}
`)
expectedErrors(t, errs)
})
t.Run("syntax error", func(t *testing.T) {
errs := CheckBlueprintSyntax(factories, "path/Blueprint", `
test {
name: "test",
`)
expectedErrors(t, errs, `path/Blueprint:5:1: expected "}", found EOF`)
})
t.Run("unknown module type", func(t *testing.T) {
errs := CheckBlueprintSyntax(factories, "path/Blueprint", `
test2 {
name: "test",
}
`)
expectedErrors(t, errs, `path/Blueprint:2:1: unrecognized module type "test2"`)
})
t.Run("unknown property name", func(t *testing.T) {
errs := CheckBlueprintSyntax(factories, "path/Blueprint", `
test {
nam: "test",
}
`)
expectedErrors(t, errs, `path/Blueprint:3:5: unrecognized property "nam"`)
})
t.Run("invalid property type", func(t *testing.T) {
errs := CheckBlueprintSyntax(factories, "path/Blueprint", `
test {
name: false,
}
`)
expectedErrors(t, errs, `path/Blueprint:3:8: can't assign bool value to string property "name"`)
})
t.Run("multiple failures", func(t *testing.T) {
errs := CheckBlueprintSyntax(factories, "path/Blueprint", `
test {
name: false,
}
test2 {
name: false,
}
`)
expectedErrors(t, errs,
`path/Blueprint:3:8: can't assign bool value to string property "name"`,
`path/Blueprint:6:1: unrecognized module type "test2"`,
)
})
}