Reintroduce a handful of binder tests

This change adds a few binder tests, and fixes package dependency
enumeration to be deterministic by using stable map enumeration.
This commit is contained in:
joeduffy 2017-01-26 16:07:21 -08:00
parent 289a0d405d
commit 9455483d27
10 changed files with 452 additions and 173 deletions

View file

@ -4,211 +4,92 @@ package binder
import (
"fmt"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
ast "github.com/marapongo/mu/pkg/astlegacy"
"github.com/marapongo/mu/pkg/compiler/core"
"github.com/marapongo/mu/pkg/compiler/errors"
"github.com/marapongo/mu/pkg/compiler/metadata"
"github.com/marapongo/mu/pkg/diag"
"github.com/marapongo/mu/pkg/errors"
"github.com/marapongo/mu/pkg/util/contract"
"github.com/marapongo/mu/pkg/util/testutil"
"github.com/marapongo/mu/pkg/workspace"
)
func TestBadMissingStackName(t *testing.T) {
sink := buildNoCodegen("testdata", "binder", "bad__missing_stack_name")
func testBind(paths ...string) *testutil.TestDiagSink {
// Create the test directory path.
pwd, _ := os.Getwd()
testdir := filepath.Join(append([]string{pwd}, paths...)...)
// Check that the compiler complained about a missing Stack name.
d := errors.ErrorMissingStackName
assert.Equal(t, 1, sink.Errors(), "expected a single error")
assert.Equal(t,
fmt.Sprintf("%v: %v%v: %v: %v\n",
diag.DefaultSinkErrorPrefix, diag.DefaultSinkIDPrefix, d.ID, "Mu.yaml", d.Message),
sink.ErrorMsgs()[0])
// Create a test sink, so we can capture and inspect outputs.
sink := testutil.NewTestDiagSink(testdir)
// Create the compiler machinery (context, reader, workspace).
ctx := core.NewContext(testdir, sink, &core.Options{Diag: sink})
reader := metadata.NewReader(ctx)
w, err := workspace.New(ctx)
contract.Assertf(err == nil, "Expected nil workspace error; got '%v'", err)
// Detect and read in the package.
pkgpath, err := w.DetectPackage()
contract.Assertf(err == nil, "Expected nil package detection error; got '%v'", err)
pkgdoc, err := diag.ReadDocument(pkgpath)
contract.Assertf(err == nil, "Expected nil package reader error; got '%v'", err)
pkg := reader.ReadPackage(pkgdoc)
// Now create a binder and bind away, returning the resulting sink.
if pkg != nil {
b := New(w, ctx, reader)
b.BindPackage(pkg)
}
return sink
}
func TestBadStackSemVer1(t *testing.T) {
sink := buildNoCodegen("testdata", "binder", "bad__stack_semver__1")
func TestBadDepSemVer(t *testing.T) {
sink := testBind("testdata", "bad__dep_semver")
// Check that the compiler complained about an illegal semantic version.
d := errors.ErrorIllegalStackVersion
assert.Equal(t, 1, sink.Errors(), "expected a single error")
d := errors.ErrorMalformedPackageURL
assert.Equal(t, 3, sink.Errors(), "expected an error for each bad semver")
assert.Equal(t,
fmt.Sprintf("%v: %v%v: %v: %v\n",
diag.DefaultSinkErrorPrefix, diag.DefaultSinkIDPrefix, d.ID, "Mu.yaml",
fmt.Sprintf(d.Message, "badbadbad", "No Major.Minor.Patch elements found")),
sink.ErrorMsgs()[0])
}
func TestBadStackSemVer2(t *testing.T) {
sink := buildNoCodegen("testdata", "binder", "bad__stack_semver__2")
// Check that the compiler complained about an illegal semantic version.
d := errors.ErrorIllegalStackVersion
assert.Equal(t, 1, sink.Errors(), "expected a single error")
assert.Equal(t,
fmt.Sprintf("%v: %v%v: %v: %v\n",
diag.DefaultSinkErrorPrefix, diag.DefaultSinkIDPrefix, d.ID, "Mu.yaml",
fmt.Sprintf(d.Message, ">1.0.0",
"Invalid character(s) found in major number \">1\"")),
sink.ErrorMsgs()[0])
}
func TestBadDepSemVer1(t *testing.T) {
sink := buildNoCodegen("testdata", "binder", "bad__dep_semver__1")
// Check that the compiler complained about an illegal semantic version.
d := errors.ErrorIllegalNameLikeSyntax
assert.Equal(t, 1, sink.Errors(), "expected a single error")
assert.Equal(t,
fmt.Sprintf("%v: %v%v: %v: %v\n",
diag.DefaultSinkErrorPrefix, diag.DefaultSinkIDPrefix, d.ID, "Mu.yaml",
fmt.Sprintf(d.Message, "dep1@badbadbad",
fmt.Sprintf(d.Message, "dep1#badbadbad",
"Illegal version spec: Could not get version from string: \"badbadbad\"")),
sink.ErrorMsgs()[0])
}
func TestBadDepSemVer2(t *testing.T) {
sink := buildNoCodegen("testdata", "binder", "bad__dep_semver__2")
// Check that the compiler complained about an illegal semantic version.
d := errors.ErrorIllegalNameLikeSyntax
assert.Equal(t, 1, sink.Errors(), "expected a single error")
assert.Equal(t,
fmt.Sprintf("%v: %v%v: %v: %v\n",
diag.DefaultSinkErrorPrefix, diag.DefaultSinkIDPrefix, d.ID, "Mu.yaml",
fmt.Sprintf(d.Message, "dep3@badbadbad",
fmt.Sprintf(d.Message, "hub.mu.com/dep2#badbadbad",
"Illegal version spec: Could not get version from string: \"badbadbad\"")),
sink.ErrorMsgs()[0])
}
func TestBadDepSemVer3(t *testing.T) {
sink := buildNoCodegen("testdata", "binder", "bad__dep_semver__3")
// Check that the compiler complained about an illegal semantic version.
d := errors.ErrorIllegalNameLikeSyntax
assert.Equal(t, 4, sink.Errors(), "expected an error for each bad semver")
assert.Equal(t,
fmt.Sprintf("%v: %v%v: %v: %v\n",
diag.DefaultSinkErrorPrefix, diag.DefaultSinkIDPrefix, d.ID, "Mu.yaml",
fmt.Sprintf(d.Message, "dep1@bad1",
"Illegal version spec: Could not parse Range \"bad1\": "+
"Could not parse comparator \"bad\" in \"bad1\"")),
sink.ErrorMsgs()[0])
assert.Equal(t,
fmt.Sprintf("%v: %v%v: %v: %v\n",
diag.DefaultSinkErrorPrefix, diag.DefaultSinkIDPrefix, d.ID, "Mu.yaml",
fmt.Sprintf(d.Message, "dep2@0.0",
"Illegal version spec: Could not parse Range \"0.0\": "+
"Could not parse version \"0.0\" in \"0.0\": No Major.Minor.Patch elements found")),
sink.ErrorMsgs()[1])
assert.Equal(t,
fmt.Sprintf("%v: %v%v: %v: %v\n",
diag.DefaultSinkErrorPrefix, diag.DefaultSinkIDPrefix, d.ID, "Mu.yaml",
fmt.Sprintf(d.Message, "dep3@bad3",
"Illegal version spec: Could not parse Range \"bad3\": "+
"Could not parse comparator \"bad\" in \"bad3\"")),
fmt.Sprintf(d.Message, "https://hub.mu.com/dep3/a/b/c/d#badbadbad",
"Illegal version spec: Could not get version from string: \"badbadbad\"")),
sink.ErrorMsgs()[2])
assert.Equal(t,
fmt.Sprintf("%v: %v%v: %v: %v\n",
diag.DefaultSinkErrorPrefix, diag.DefaultSinkIDPrefix, d.ID, "Mu.yaml",
fmt.Sprintf(d.Message, "dep4@0.6.bad.ness.1",
"Illegal version spec: Could not parse Range \"0.6.bad.ness.1\": "+
"Could not parse version \"0.6.bad.ness.1\" in \"0.6.bad.ness.1\": "+
"Invalid character(s) found in patch number \"bad.ness.1\"")),
sink.ErrorMsgs()[3])
}
func TestBadSymbolAlreadyExists(t *testing.T) {
sink := buildNoCodegen("testdata", "binder", "bad__symbol_already_exists")
// Check that the compiler complained about a duplicate symbol.
d := errors.ErrorSymbolAlreadyExists
assert.Equal(t, 1, sink.Errors(), "expected a single error")
assert.Equal(t,
fmt.Sprintf("%v: %v%v: %v: %v\n",
diag.DefaultSinkErrorPrefix, diag.DefaultSinkIDPrefix, d.ID, "Mu.yaml",
fmt.Sprintf(d.Message, "foo")),
sink.ErrorMsgs()[0])
}
func TestBadTypeNotFound1(t *testing.T) {
sink := buildNoCodegen("testdata", "binder", "bad__type_not_found__1")
func TestBadTypeNotFound(t *testing.T) {
sink := testBind("testdata", "bad__type_not_found")
// Check that the compiler complained about the type missisng.
d := errors.ErrorStackTypeNotFound
d := errors.ErrorTypeNotFound
assert.Equal(t, 1, sink.Errors(), "expected a single error")
assert.Equal(t,
fmt.Sprintf("%v: %v%v: %v: %v\n",
diag.DefaultSinkErrorPrefix, diag.DefaultSinkIDPrefix, d.ID, "Mu.yaml",
fmt.Sprintf(d.Message,
fmt.Sprintf("%v%vsomething/non/existent@%v",
ast.DefaultRefProto, ast.DefaultRefBase, ast.DefaultRefVersion))),
fmt.Sprintf("%v: %v%v: %v\n",
diag.DefaultSinkErrorPrefix, diag.DefaultSinkIDPrefix, d.ID,
fmt.Sprintf(d.Message, "missing/package:bad/module/Clazz", "symbol missing")),
sink.ErrorMsgs()[0])
}
func TestBadTypeNotFound2(t *testing.T) {
sink := buildNoCodegen("testdata", "binder", "bad__type_not_found__2")
func TestGoodPrimitiveTypes(t *testing.T) {
sink := testBind("testdata", "good__primitive_types")
// Check that the compiler complained about the type missisng.
d := errors.ErrorStackTypeNotFound
assert.Equal(t, 1, sink.Errors(), "expected a single error")
assert.Equal(t,
fmt.Sprintf("%v: %v%v: %v: %v\n",
diag.DefaultSinkErrorPrefix, diag.DefaultSinkIDPrefix, d.ID, "Mu.yaml",
fmt.Sprintf(d.Message,
fmt.Sprintf("%v%vsomething/non/existent@%v",
ast.DefaultRefProto, ast.DefaultRefBase, ast.DefaultRefVersion))),
sink.ErrorMsgs()[0])
}
func TestBadMissingProperties(t *testing.T) {
sink := buildNoCodegen("testdata", "binder", "bad__properties", "bad__missing")
d := errors.ErrorMissingRequiredProperty
reqs := []string{"req_bool", "req_number", "req_service", "req_string"}
assert.Equal(t, len(reqs), sink.Errors(), "expected an error per property")
for i, req := range reqs {
assert.Equal(t,
fmt.Sprintf("%v: %v%v: %v\n",
diag.DefaultSinkErrorPrefix, diag.DefaultSinkIDPrefix, d.ID,
fmt.Sprintf(d.Message, req)),
sink.ErrorMsgs()[i])
}
}
func TestBadUnrecognizedProperties(t *testing.T) {
sink := buildNoCodegen("testdata", "binder", "bad__properties", "bad__unrecognized")
d := errors.ErrorUnrecognizedProperty
unks := []string{"unk_bool", "unk_number", "unk_service", "unk_string"}
assert.Equal(t, len(unks), sink.Errors(), "expected an error per property")
for i, unk := range unks {
assert.Equal(t,
fmt.Sprintf("%v: %v%v: %v\n",
diag.DefaultSinkErrorPrefix, diag.DefaultSinkIDPrefix, d.ID,
fmt.Sprintf(d.Message, unk)),
sink.ErrorMsgs()[i])
}
}
func TestBadPropertyTypes(t *testing.T) {
sink := buildNoCodegen("testdata", "binder", "bad__properties", "bad__types")
d := errors.ErrorIncorrectType
exp := []string{"bool", "number", "service", "string"}
got := []string{"string", "string", "bool", "float64"}
assert.Equal(t, len(exp), sink.Errors(), "expected an error per property")
for i, ty := range exp {
assert.Equal(t,
fmt.Sprintf("%v: %v%v: %v\n",
diag.DefaultSinkErrorPrefix, diag.DefaultSinkIDPrefix, d.ID,
fmt.Sprintf(d.Message, ty, got[i])),
sink.ErrorMsgs()[i])
}
}
func TestGoodPredefTypes(t *testing.T) {
sink := buildNoCodegen("testdata", "binder", "good__predef_types")
// Check that no errors are found when using predefined stack types.
assert.Equal(t, 0, sink.Errors(), "expected no errors when binding to predef types")
// Check that no errors are found when using primitive types.
assert.Equal(t, 0, sink.Errors(), "expected no errors when binding to primitive types")
}

View file

@ -52,11 +52,12 @@ func (b *binder) resolvePackageDeps(pkg *symbols.Package) {
contract.Require(pkg != nil, "pkg")
if pkg.Node.Dependencies != nil {
for _, depurl := range *pkg.Node.Dependencies {
for _, dep := range pack.StableDependencies(*pkg.Node.Dependencies) {
depurl := (*pkg.Node.Dependencies)[dep]
// The dependency is a URL. Transform it into a name used for symbol resolution.
dep, err := depurl.Parse()
if err != nil {
b.Diag().Errorf(errors.ErrorMalformedPackageURL, depurl, err)
b.Diag().Errorf(errors.ErrorMalformedPackageURL.At(pkg.Node), depurl, err)
} else {
glog.V(3).Infof("Resolving package '%v' dependency name=%v, url=%v", pkg.Name(), dep.Name, dep.URL())
if depsym := b.resolveDep(dep); depsym != nil {

View file

@ -0,0 +1,2 @@
# This exists solely to stop the Mu compiler from going beyond this directory in its search for Mufiles, etc.

View file

@ -0,0 +1,6 @@
name: "binder/bad__dep_semver"
dependencies:
dep1: dep1#badbadbad
dep2: hub.mu.com/dep2#badbadbad
dep3: https://hub.mu.com/dep3/a/b/c/d#badbadbad

View file

@ -0,0 +1,18 @@
name: binder/bad_type_not_found
modules:
index:
kind: Module
name:
kind: Identifier
ident: index
default: true
members:
x:
kind: ModuleProperty
name:
kind: Identifier
ident: x
type:
kind: TypeToken
tok: missing/package:bad/module/Clazz

View file

@ -0,0 +1,4 @@
{
"name": "binder/good__primitive_types"
}

View file

@ -0,0 +1,330 @@
{
"name": "binder/good__primitive_types",
"modules": {
"index": {
"kind": "Module",
"name": {
"kind": "Identifier",
"ident": "index"
},
"imports": [],
"members": {
"b": {
"kind": "ModuleProperty",
"name": {
"kind": "Identifier",
"ident": "b",
"loc": {
"file": "index.ts",
"start": {
"line": 1,
"column": 11
},
"end": {
"line": 1,
"column": 12
}
}
},
"access": "public",
"type": {
"kind": "TypeToken",
"tok": "bool",
"loc": {
"file": "index.ts",
"start": {
"line": 1,
"column": 11
},
"end": {
"line": 1,
"column": 28
}
}
}
},
"n": {
"kind": "ModuleProperty",
"name": {
"kind": "Identifier",
"ident": "n",
"loc": {
"file": "index.ts",
"start": {
"line": 2,
"column": 11
},
"end": {
"line": 2,
"column": 12
}
}
},
"access": "public",
"type": {
"kind": "TypeToken",
"tok": "number",
"loc": {
"file": "index.ts",
"start": {
"line": 2,
"column": 11
},
"end": {
"line": 2,
"column": 25
}
}
}
},
"s": {
"kind": "ModuleProperty",
"name": {
"kind": "Identifier",
"ident": "s",
"loc": {
"file": "index.ts",
"start": {
"line": 3,
"column": 11
},
"end": {
"line": 3,
"column": 12
}
}
},
"access": "public",
"type": {
"kind": "TypeToken",
"tok": "string",
"loc": {
"file": "index.ts",
"start": {
"line": 3,
"column": 11
},
"end": {
"line": 3,
"column": 34
}
}
}
},
".init": {
"kind": "ModuleMethod",
"name": {
"kind": "Identifier",
"ident": ".init"
},
"access": "public",
"body": {
"kind": "Block",
"statements": [
{
"kind": "ExpressionStatement",
"expression": {
"kind": "BinaryOperatorExpression",
"left": {
"kind": "LoadLocationExpression",
"name": {
"kind": "Token",
"tok": "b",
"loc": {
"file": "index.ts",
"start": {
"line": 1,
"column": 11
},
"end": {
"line": 1,
"column": 12
}
}
}
},
"operator": "=",
"right": {
"kind": "BoolLiteral",
"raw": "true",
"value": true,
"loc": {
"file": "index.ts",
"start": {
"line": 1,
"column": 24
},
"end": {
"line": 1,
"column": 28
}
}
},
"loc": {
"file": "index.ts",
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 29
}
}
},
"loc": {
"file": "index.ts",
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 29
}
}
},
{
"kind": "ExpressionStatement",
"expression": {
"kind": "BinaryOperatorExpression",
"left": {
"kind": "LoadLocationExpression",
"name": {
"kind": "Token",
"tok": "n",
"loc": {
"file": "index.ts",
"start": {
"line": 2,
"column": 11
},
"end": {
"line": 2,
"column": 12
}
}
}
},
"operator": "=",
"right": {
"kind": "NumberLiteral",
"raw": "42",
"value": 42,
"loc": {
"file": "index.ts",
"start": {
"line": 2,
"column": 23
},
"end": {
"line": 2,
"column": 25
}
}
},
"loc": {
"file": "index.ts",
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 2,
"column": 26
}
}
},
"loc": {
"file": "index.ts",
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 2,
"column": 26
}
}
},
{
"kind": "ExpressionStatement",
"expression": {
"kind": "BinaryOperatorExpression",
"left": {
"kind": "LoadLocationExpression",
"name": {
"kind": "Token",
"tok": "s",
"loc": {
"file": "index.ts",
"start": {
"line": 3,
"column": 11
},
"end": {
"line": 3,
"column": 12
}
}
}
},
"operator": "=",
"right": {
"kind": "StringLiteral",
"raw": "hello, mu",
"value": "hello, mu",
"loc": {
"file": "index.ts",
"start": {
"line": 3,
"column": 23
},
"end": {
"line": 3,
"column": 34
}
}
},
"loc": {
"file": "index.ts",
"start": {
"line": 3,
"column": 0
},
"end": {
"line": 3,
"column": 35
}
}
},
"loc": {
"file": "index.ts",
"start": {
"line": 3,
"column": 0
},
"end": {
"line": 3,
"column": 35
}
}
}
]
}
}
},
"loc": {
"file": "index.ts",
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 5,
"column": 0
}
}
}
}
}

View file

@ -0,0 +1,4 @@
export let b: boolean = true;
export let n: number = 42;
export let s: string = "hello, mu";

View file

@ -44,6 +44,7 @@ func (r *reader) ReadPackage(doc *diag.Document) *pack.Package {
defer glog.V(2).Infof("Reading MuPackage '%v' completed w/ %v warnings and %v errors",
doc.File, r.Diag().Warnings(), r.Diag().Errors())
}
contract.Assert(len(doc.Body) != 0)
// We support many file formats. Detect the file extension and deserialize the contents.
m, has := encoding.Marshalers[doc.Ext()]

32
pkg/pack/stable.go Normal file
View file

@ -0,0 +1,32 @@
// Copyright 2016 Marapongo, Inc. All rights reserved.
package pack
import (
"sort"
"github.com/marapongo/mu/pkg/tokens"
)
func StableDependencies(deps Dependencies) []tokens.PackageName {
sorted := make(packageNames, 0, len(deps))
for dep := range deps {
sorted = append(sorted, dep)
}
sort.Sort(sorted)
return sorted
}
type packageNames []tokens.PackageName
func (s packageNames) Len() int {
return len(s)
}
func (s packageNames) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s packageNames) Less(i, j int) bool {
return s[i] < s[j]
}