Files
mgmt/lang/lexparse_test.go
James Shubin f53376cea1 lang: Add function values and lambdas
This adds a giant missing piece of the language: proper function values!
It is lovely to now understand why early programming language designers
didn't implement these, but a joy to now reap the benefits of them. In
adding these, many other changes had to be made to get them to "fit"
correctly. This improved the code and fixed a number of bugs.
Unfortunately this touched many areas of the code, and since I was
learning how to do all of this for the first time, I've squashed most of
my work into a single commit. Some more information:

* This adds over 70 new tests to verify the new functionality.

* Functions, global variables, and classes can all be implemented
natively in mcl and built into core packages.

* A new compiler step called "Ordering" was added. It is called by the
SetScope step, and determines statement ordering and shadowing
precedence formally. It helped remove at least one bug and provided the
additional analysis required to properly capture variables when
implementing function generators and closures.

* The type unification code was improved to handle the new cases.

* Light copying of Node's allowed our function graphs to be more optimal
and share common vertices and edges. For example, if two different
closures capture a variable $x, they'll both use the same copy when
running the function, since the compiler can prove if they're identical.

* Some areas still need improvements, but this is ready for mainstream
testing and use!
2019-07-17 00:27:09 -04:00

2546 lines
49 KiB
Go

// Mgmt
// Copyright (C) 2013-2019+ James Shubin and the project contributors
// Written by James Shubin <james@shubin.ca> and the project contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// +build !root
package lang
import (
"fmt"
"io"
"reflect"
"strings"
"testing"
"github.com/purpleidea/mgmt/lang/interfaces"
"github.com/purpleidea/mgmt/lang/types"
"github.com/purpleidea/mgmt/util"
"github.com/davecgh/go-spew/spew"
"github.com/kylelemons/godebug/pretty"
)
func TestLexParse0(t *testing.T) {
type test struct { // an individual test
name string
code string
fail bool
exp interfaces.Stmt
}
testCases := []test{}
{
testCases = append(testCases, test{
"nil",
``,
false,
nil,
})
}
{
testCases = append(testCases, test{
name: "simple assignment",
code: `$rewsna = -42`,
fail: false,
exp: &StmtProg{
Prog: []interfaces.Stmt{
&StmtBind{
Ident: "rewsna",
Value: &ExprInt{
V: -42,
},
},
},
},
})
}
{
testCases = append(testCases, test{
name: "one res",
code: `noop "n1" {}`,
fail: false,
//exp: ???, // FIXME: add the expected AST
})
}
{
testCases = append(testCases, test{
name: "res with keyword",
code: `false "n1" {}`, // false is a special keyword
fail: true,
})
}
{
testCases = append(testCases, test{
name: "bad escaping",
code: `
test "t1" {
str => "he\ llo", # incorrect escaping
}
`,
fail: true,
})
}
{
testCases = append(testCases, test{
name: "int overflow",
code: `
test "t1" {
int => 888888888888888888888888, # overflows
}
`,
fail: true,
})
}
{
testCases = append(testCases, test{
name: "overflow after lexer",
code: `
test "t1" {
uint8 => 128, # does not overflow at lexer stage
}
`,
fail: false,
//exp: ???, // FIXME: add the expected AST
})
}
{
testCases = append(testCases, test{
name: "one res with param",
code: `
test "t1" {
int16 => 01134, # some comment
}
`,
fail: false,
//exp: ???, // FIXME: add the expected AST
})
}
{
testCases = append(testCases, test{
name: "one res with elvis",
code: `
test "t1" {
int16 => true ?: 42, # elvis operator
int32 => 42,
stringptr => false ?: "", # missing is not ""
}
`,
fail: false,
//exp: ???, // FIXME: add the expected AST
})
}
{
// TODO: skip trailing comma requirement on one-liners
testCases = append(testCases, test{
name: "two lists",
code: `
$somelist = [42, 0, -13,]
$somelonglist = [
"hello",
"and",
"how",
"are",
"you?",
]
`,
fail: false,
//exp: ???, // FIXME: add the expected AST
})
}
{
testCases = append(testCases, test{
name: "one map",
code: `
$somemap = {
"foo" => "foo1",
"bar" => "bar1",
}
`,
fail: false,
//exp: ???, // FIXME: add the expected AST
})
}
{
testCases = append(testCases, test{
name: "another map",
code: `
$somemap = {
"foo" => -13,
"bar" => 42,
}
`,
fail: false,
//exp: ???, // FIXME: add the expected AST
})
}
// TODO: alternate possible syntax ?
//{
// testCases = append(testCases, test{ // ?
// code: `
// $somestruct = struct{
// foo: "foo1";
// bar: 42 # no trailing semicolon at the moment
// }
// `,
// fail: false,
// //exp: ???, // FIXME: add the expected AST
// })
//}
{
testCases = append(testCases, test{
name: "one struct",
code: `
$somestruct = struct{
foo => "foo1",
bar => 42,
}
`,
fail: false,
//exp: ???, // FIXME: add the expected AST
})
}
{
testCases = append(testCases, test{
name: "struct with nested struct",
code: `
$somestruct = struct{
foo => "foo1",
bar => struct{
a => true,
b => "hello",
},
baz => 42,
}
`,
fail: false,
//exp: ???, // FIXME: add the expected AST
})
}
// types
{
testCases = append(testCases, test{
name: "some lists",
code: `
$intlist []int = [42, -0, 13,]
$intlistnested [][]int = [[42,], [], [100, -0,], [-13,],]
`,
fail: false,
//exp: ???, // FIXME: add the expected AST
})
}
{
testCases = append(testCases, test{
name: "maps 1",
code: `
# make sure the "str:" part doesn't match a single ident
$strmap map{str: int} = {
"key1" => 42,
"key2" => -13,
}
`,
fail: false,
//exp: ???, // FIXME: add the expected AST
})
}
{
testCases = append(testCases, test{
name: "maps 2",
code: `
$mapstrintlist map{str: []int} = {
"key1" => [42, 44,],
"key2" => [],
"key3" => [-13,],
}
`,
fail: false,
//exp: ???, // FIXME: add the expected AST
})
}
{
testCases = append(testCases, test{
name: "maps and lists",
code: `
$strmap map{str: int} = {
"key1" => 42,
"key2" => -13,
}
$mapstrintlist map{str: []int} = {
"key1" => [42, 44,],
"key2" => [],
"key3" => [-13,],
}
`,
fail: false,
//exp: ???, // FIXME: add the expected AST
})
}
{
testCases = append(testCases, test{
name: "some structs",
code: `
$structx struct{a int; b bool; c str} = struct{
a => 42,
b => true,
c => "hello",
}
$structx2 struct{a int; b []bool; c str} = struct{
a => 42,
b => [true, false, false, true,],
c => "hello",
}
`,
fail: false,
//exp: ???, // FIXME: add the expected AST
})
}
{
testCases = append(testCases, test{
name: "res with floats",
code: `
test "t1" {
float32 => -25.38789, # some float
float64 => 53.393908945, # some float
}
`,
fail: false,
//exp: ???, // FIXME: add the expected AST
})
}
// FIXME: why doesn't this overflow, and thus fail?
// from the docs: If s is syntactically well-formed but is more than 1/2
// ULP away from the largest floating point number of the given size,
// ParseFloat returns f = ±Inf, err.Err = ErrRange.
//{
// testCases = append(testCases, test{
// name: "overflowing float",
// code: `
// test "t1" {
// float32 => -457643875645764387564578645457864525457643875645764387564578645457864525.457643875645764387564578645457864525387899898753459879587574928798759863965, # overflow
// }
// `,
// fail: true,
// })
//}
{
testCases = append(testCases, test{
name: "res and addition",
code: `
test "t1" {
float32 => -25.38789 + 32.6,
}
`,
fail: false,
//exp: ???, // FIXME: add the expected AST
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtBind{
Ident: "x1",
Value: &ExprCall{
Name: "foo1",
Args: []interfaces.Expr{},
},
},
},
}
testCases = append(testCases, test{
name: "func call 1",
code: `
$x1 = foo1()
`,
fail: false,
exp: exp,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtBind{
Ident: "x1",
Value: &ExprCall{
Name: "foo1",
Args: []interfaces.Expr{
&ExprInt{
V: 13,
},
&ExprStr{
V: "hello",
},
},
},
},
},
}
testCases = append(testCases, test{
name: "func call 2",
code: `
$x1 = foo1(13, "hello")
`,
fail: false,
exp: exp,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtBind{
Ident: "x1",
Value: &ExprCall{
Name: "pkg.foo1",
Args: []interfaces.Expr{},
},
},
},
}
testCases = append(testCases, test{
name: "func call dotted 1",
code: `
$x1 = pkg.foo1()
`,
fail: false,
exp: exp,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtBind{
Ident: "x1",
Value: &ExprCall{
Name: "pkg.foo1",
Args: []interfaces.Expr{
&ExprBool{
V: true,
},
&ExprStr{
V: "hello",
},
},
},
},
},
}
testCases = append(testCases, test{
name: "func call dotted 2",
code: `
$x1 = pkg.foo1(true, "hello")
`,
fail: false,
exp: exp,
})
}
{
testCases = append(testCases, test{
name: "func call dotted invalid 1",
code: `
$x1 = .pkg.foo1(true, "hello")
`,
fail: true,
})
}
{
testCases = append(testCases, test{
name: "func call dotted invalid 2",
code: `
$x1 = pkg.foo1.(true, "hello")
`,
fail: true,
})
}
{
testCases = append(testCases, test{
name: "func call dotted invalid 3",
code: `
$x1 = .pkg.foo1.(true, "hello")
`,
fail: true,
})
}
{
testCases = append(testCases, test{
name: "func call dotted invalid 4",
code: `
$x1 = pkg..foo1(true, "hello")
`,
fail: true,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtBind{
Ident: "x1",
Value: &ExprVar{
Name: "pkg.foo1",
},
},
},
}
testCases = append(testCases, test{
name: "dotted var 1",
code: `
$x1 = $pkg.foo1
`,
fail: false,
exp: exp,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtBind{
Ident: "x1",
Value: &ExprVar{
Name: "pkg.foo1.bar",
},
},
},
}
testCases = append(testCases, test{
name: "dotted var 2",
code: `
$x1 = $pkg.foo1.bar
`,
fail: false,
exp: exp,
})
}
{
testCases = append(testCases, test{
name: "invalid dotted var 1",
code: `
$x1 = $.pkg.foo1.bar
`,
fail: true,
})
}
{
testCases = append(testCases, test{
name: "invalid dotted var 2",
code: `
$x1 = $pkg.foo1.bar.
`,
fail: true,
})
}
{
testCases = append(testCases, test{
name: "invalid dotted var 3",
code: `
$x1 = $.pkg.foo1.bar.
`,
fail: true,
})
}
{
testCases = append(testCases, test{
name: "invalid dotted var 4",
code: `
$x1 = $pkg..foo1.bar
`,
fail: true,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtRes{
Kind: "test",
Name: &ExprStr{
V: "t1",
},
Contents: []StmtResContents{
&StmtResField{
Field: "int64ptr",
Value: &ExprCall{
Name: operatorFuncName,
Args: []interfaces.Expr{
&ExprStr{
V: "+",
},
&ExprInt{
V: 13,
},
&ExprInt{
V: 42,
},
},
},
},
},
},
},
}
testCases = append(testCases, test{
name: "addition",
code: `
test "t1" {
int64ptr => 13 + 42,
}
`,
fail: false,
exp: exp,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtRes{
Kind: "test",
Name: &ExprStr{
V: "t1",
},
Contents: []StmtResContents{
&StmtResField{
Field: "float32",
Value: &ExprCall{
Name: operatorFuncName,
Args: []interfaces.Expr{
&ExprStr{
V: "+",
},
&ExprCall{
Name: operatorFuncName,
Args: []interfaces.Expr{
&ExprStr{
V: "+",
},
&ExprFloat{
V: -25.38789,
},
&ExprFloat{
V: 32.6,
},
},
},
&ExprFloat{
V: 13.7,
},
},
},
},
},
},
},
}
testCases = append(testCases, test{
name: "multiple float addition",
code: `
test "t1" {
float32 => -25.38789 + 32.6 + 13.7,
}
`,
fail: false,
exp: exp,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtRes{
Kind: "test",
Name: &ExprStr{
V: "t1",
},
Contents: []StmtResContents{
&StmtResField{
Field: "int64ptr",
Value: &ExprCall{
Name: operatorFuncName,
Args: []interfaces.Expr{
&ExprStr{
V: "+",
},
&ExprInt{
V: 4,
},
&ExprCall{
Name: operatorFuncName,
Args: []interfaces.Expr{
&ExprStr{
V: "*",
},
&ExprInt{
V: 3,
},
&ExprInt{
V: 12,
},
},
},
},
},
},
},
},
},
}
testCases = append(testCases, test{
name: "order of operations lucky",
code: `
test "t1" {
int64ptr => 4 + 3 * 12, # 40, not 84
}
`,
fail: false,
exp: exp,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtRes{
Kind: "test",
Name: &ExprStr{
V: "t1",
},
Contents: []StmtResContents{
&StmtResField{
Field: "int64ptr",
Value: &ExprCall{
Name: operatorFuncName,
Args: []interfaces.Expr{
&ExprStr{
V: "+",
},
&ExprCall{
Name: operatorFuncName,
Args: []interfaces.Expr{
&ExprStr{
V: "*",
},
&ExprInt{
V: 3,
},
&ExprInt{
V: 12,
},
},
},
&ExprInt{
V: 4,
},
},
},
},
},
},
},
}
testCases = append(testCases, test{
name: "order of operations needs left precedence",
code: `
test "t1" {
int64ptr => 3 * 12 + 4, # 40, not 48
}
`,
fail: false,
exp: exp,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtRes{
Kind: "test",
Name: &ExprStr{
V: "t1",
},
Contents: []StmtResContents{
&StmtResField{
Field: "int64ptr",
Value: &ExprCall{
Name: operatorFuncName,
Args: []interfaces.Expr{
&ExprStr{
V: "*",
},
&ExprInt{
V: 3,
},
&ExprCall{
Name: operatorFuncName,
Args: []interfaces.Expr{
&ExprStr{
V: "+",
},
&ExprInt{
V: 12,
},
&ExprInt{
V: 4,
},
},
},
},
},
},
},
},
},
}
testCases = append(testCases, test{
name: "order of operations parens",
code: `
test "t1" {
int64ptr => 3 * (12 + 4), # 48, not 40
}
`,
fail: false,
exp: exp,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtRes{
Kind: "test",
Name: &ExprStr{
V: "t1",
},
Contents: []StmtResContents{
&StmtResField{
Field: "boolptr",
Value: &ExprCall{
Name: operatorFuncName,
Args: []interfaces.Expr{
&ExprStr{
V: ">",
},
&ExprCall{
Name: operatorFuncName,
Args: []interfaces.Expr{
&ExprStr{
V: "+",
},
&ExprInt{
V: 3,
},
&ExprInt{
V: 4,
},
},
},
&ExprInt{
V: 5,
},
},
},
},
},
},
},
}
testCases = append(testCases, test{
name: "order of operations bools",
code: `
test "t1" {
boolptr => 3 + 4 > 5, # should be true
}
`,
fail: false,
exp: exp,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtRes{
Kind: "test",
Name: &ExprStr{
V: "t1",
},
Contents: []StmtResContents{
&StmtResField{
Field: "boolptr",
Value: &ExprCall{
Name: operatorFuncName,
Args: []interfaces.Expr{
&ExprStr{
V: ">",
},
&ExprInt{
V: 3,
},
&ExprCall{
Name: operatorFuncName,
Args: []interfaces.Expr{
&ExprStr{
V: "+",
},
&ExprInt{
V: 4,
},
&ExprInt{
V: 5,
},
},
},
},
},
},
},
},
},
}
testCases = append(testCases, test{
name: "order of operations bools reversed",
code: `
test "t1" {
boolptr => 3 > 4 + 5, # should be false
}
`,
fail: false,
exp: exp,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtRes{
Kind: "test",
Name: &ExprStr{
V: "t1",
},
Contents: []StmtResContents{
&StmtResField{
Field: "boolptr",
Value: &ExprCall{
Name: operatorFuncName,
Args: []interfaces.Expr{
&ExprStr{
V: ">",
},
&ExprCall{
Name: operatorFuncName,
Args: []interfaces.Expr{
&ExprStr{
V: "!",
},
&ExprInt{
V: 3,
},
},
},
&ExprInt{
V: 4,
},
},
},
},
},
},
},
}
testCases = append(testCases, test{
name: "order of operations with not",
code: `
test "t1" {
boolptr => ! 3 > 4, # should parse, but not compile
}
`,
fail: false,
exp: exp,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtRes{
Kind: "test",
Name: &ExprStr{
V: "t1",
},
Contents: []StmtResContents{
&StmtResField{
Field: "boolptr",
Value: &ExprCall{
Name: operatorFuncName,
Args: []interfaces.Expr{
&ExprStr{
V: "&&",
},
&ExprCall{
Name: operatorFuncName,
Args: []interfaces.Expr{
&ExprStr{
V: "<",
},
&ExprInt{
V: 7,
},
&ExprInt{
V: 4,
},
},
},
&ExprBool{
V: true,
},
},
},
},
},
},
},
}
testCases = append(testCases, test{
name: "order of operations logical",
code: `
test "t1" {
boolptr => 7 < 4 && true, # should be false
}
`,
fail: false,
exp: exp,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtRes{
Kind: "test",
Name: &ExprStr{
V: "t1",
},
Contents: []StmtResContents{
&StmtResField{
Field: "int64ptr",
Value: &ExprInt{
V: 42,
},
},
},
},
&StmtRes{
Kind: "test",
Name: &ExprStr{
V: "t2",
},
Contents: []StmtResContents{
&StmtResField{
Field: "int64ptr",
Value: &ExprInt{
V: 13,
},
},
},
},
&StmtEdge{
EdgeHalfList: []*StmtEdgeHalf{
{
Kind: "test",
Name: &ExprStr{
V: "t1",
},
SendRecv: "foosend",
},
{
Kind: "test",
Name: &ExprStr{
V: "t2",
},
SendRecv: "barrecv",
},
},
},
},
}
testCases = append(testCases, test{
name: "edge stmt",
code: `
test "t1" {
int64ptr => 42,
}
test "t2" {
int64ptr => 13,
}
Test["t1"].foosend -> Test["t2"].barrecv # send/recv
`,
fail: false,
exp: exp,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtRes{
Kind: "test",
Name: &ExprStr{
V: "t1",
},
Contents: []StmtResContents{
&StmtResMeta{
Property: "noop",
MetaExpr: &ExprBool{
V: true,
},
},
&StmtResMeta{
Property: "delay",
MetaExpr: &ExprInt{
V: 42,
},
Condition: &ExprBool{
V: true,
},
},
},
},
&StmtRes{
Kind: "test",
Name: &ExprStr{
V: "t2",
},
Contents: []StmtResContents{
&StmtResMeta{
Property: "limit",
MetaExpr: &ExprFloat{
V: 0.45,
},
},
&StmtResMeta{
Property: "burst",
MetaExpr: &ExprInt{
V: 4,
},
},
},
},
&StmtRes{
Kind: "test",
Name: &ExprStr{
V: "t3",
},
Contents: []StmtResContents{
&StmtResMeta{
Property: "noop",
MetaExpr: &ExprBool{
V: true,
},
},
&StmtResMeta{
Property: "meta",
MetaExpr: &ExprStruct{
Fields: []*ExprStructField{
{Name: "poll", Value: &ExprInt{V: 5}},
{Name: "retry", Value: &ExprInt{V: 3}},
{
Name: "sema",
Value: &ExprList{
Elements: []interfaces.Expr{
&ExprStr{V: "foo:1"},
&ExprStr{V: "bar:3"},
},
},
},
},
},
},
},
}},
}
testCases = append(testCases, test{
name: "res meta stmt",
code: `
test "t1" {
Meta:noop => true,
Meta:delay => true ?: 42,
}
test "t2" {
Meta:limit => 0.45,
Meta:burst => 4,
}
test "t3" {
Meta:noop => true, # meta params can be combined
Meta => struct{
poll => 5,
retry => 3,
sema => ["foo:1", "bar:3",],
},
}
`,
fail: false,
exp: exp,
})
}
{
testCases = append(testCases, test{
name: "parser set type incompatibility str",
code: `
$x int = "hello" # type should be str to work
test "t1" {
str => $x,
}
`,
fail: true,
})
}
{
testCases = append(testCases, test{
name: "parser set type incompatibility int",
code: `
$x int = "hello" # value should be int to work
test "t1" {
int => $x,
}
`,
fail: true,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtClass{
Name: "c1",
Body: &StmtProg{
Prog: []interfaces.Stmt{
&StmtRes{
Kind: "test",
Name: &ExprStr{
V: "t1",
},
Contents: []StmtResContents{
&StmtResField{
Field: "stringptr",
Value: &ExprStr{
V: "hello",
},
},
},
},
},
},
},
&StmtInclude{
Name: "c1",
},
},
}
testCases = append(testCases, test{
name: "simple class 1",
code: `
class c1 {
test "t1" {
stringptr => "hello",
}
}
include c1
`,
fail: false,
exp: exp,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtClass{
Name: "c1",
Body: &StmtProg{
Prog: []interfaces.Stmt{
&StmtRes{
Kind: "test",
Name: &ExprStr{
V: "t1",
},
Contents: []StmtResContents{
&StmtResField{
Field: "stringptr",
Value: &ExprStr{
V: "hello",
},
},
},
},
},
},
},
&StmtInclude{
Name: "pkg.c1",
},
},
}
testCases = append(testCases, test{
name: "simple dotted class 1",
code: `
# a dotted identifier only occurs via an imported class
class c1 {
test "t1" {
stringptr => "hello",
}
}
# a dotted identifier is allowed here if it's imported
include pkg.c1
`,
fail: false,
exp: exp,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtClass{
Name: "c1",
Body: &StmtProg{
Prog: []interfaces.Stmt{
&StmtRes{
Kind: "test",
Name: &ExprStr{
V: "t1",
},
Contents: []StmtResContents{
&StmtResField{
Field: "stringptr",
Value: &ExprStr{
V: "hello",
},
},
},
},
},
},
},
&StmtInclude{
Name: "pkg.ns.c1",
},
},
}
testCases = append(testCases, test{
name: "simple dotted class 2",
code: `
# a dotted identifier only occurs via an imported class
class c1 {
test "t1" {
stringptr => "hello",
}
}
# a dotted identifier is allowed here if it's imported
include pkg.ns.c1
`,
fail: false,
exp: exp,
})
}
{
testCases = append(testCases, test{
name: "simple dotted invalid class 1",
code: `
# a dotted identifier only occurs via an imported class
class foo.c1 {
}
`,
fail: true,
})
}
{
testCases = append(testCases, test{
name: "simple dotted invalid class 2",
code: `
# a dotted identifier only occurs via an imported class
class foo.bar.c1 {
}
`,
fail: true,
})
}
{
testCases = append(testCases, test{
name: "simple dotted invalid include 1",
code: `
class .foo.c1 {
}
`,
fail: true,
})
}
{
testCases = append(testCases, test{
name: "simple dotted invalid include 2",
code: `
class foo.c1. {
}
`,
fail: true,
})
}
{
testCases = append(testCases, test{
name: "simple dotted invalid include 3",
code: `
class .foo.c1. {
}
`,
fail: true,
})
}
{
testCases = append(testCases, test{
name: "simple dotted invalid include 4",
code: `
class foo..c1 {
}
`,
fail: true,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtClass{
Name: "x",
Args: []*Arg{},
Body: &StmtProg{
Prog: []interfaces.Stmt{},
},
},
&StmtClass{
Name: "y1",
Args: []*Arg{},
Body: &StmtProg{
Prog: []interfaces.Stmt{},
},
},
&StmtInclude{
Name: "z",
Args: nil,
},
},
}
testCases = append(testCases, test{
name: "simple class with args 0",
code: `
class x() {
}
class y1() {
}
include z
`,
fail: false,
exp: exp,
})
}
{
testCases = append(testCases, test{
name: "simple class underscore failure",
code: `
class x_() {
}
`,
fail: true,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtClass{
Name: "c1",
Args: []*Arg{
{
Name: "a",
//Type: &types.Type{},
},
{
Name: "b",
//Type: &types.Type{},
},
},
Body: &StmtProg{
Prog: []interfaces.Stmt{
&StmtRes{
Kind: "test",
Name: &ExprVar{
Name: "a",
},
Contents: []StmtResContents{
&StmtResField{
Field: "stringptr",
Value: &ExprVar{
Name: "b",
},
},
},
},
},
},
},
&StmtInclude{
Name: "c1",
Args: []interfaces.Expr{
&ExprStr{
V: "t1",
},
&ExprStr{
V: "hello",
},
},
},
&StmtInclude{
Name: "c1",
Args: []interfaces.Expr{
&ExprStr{
V: "t2",
},
&ExprStr{
V: "world",
},
},
},
},
}
testCases = append(testCases, test{
name: "simple class with args 1",
code: `
class c1($a, $b) {
test $a {
stringptr => $b,
}
}
include c1("t1", "hello")
include c1("t2", "world")
`,
fail: false,
exp: exp,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtClass{
Name: "c1",
Args: []*Arg{
{
Name: "a",
Type: types.TypeStr,
},
{
Name: "b",
//Type: &types.Type{},
},
},
Body: &StmtProg{
Prog: []interfaces.Stmt{
&StmtRes{
Kind: "test",
Name: &ExprVar{
Name: "a",
},
Contents: []StmtResContents{
&StmtResField{
Field: "stringptr",
Value: &ExprVar{
Name: "b",
},
},
},
},
},
},
},
&StmtInclude{
Name: "c1",
Args: []interfaces.Expr{
&ExprStr{
V: "t1",
},
&ExprStr{
V: "hello",
},
},
},
&StmtInclude{
Name: "c1",
Args: []interfaces.Expr{
&ExprStr{
V: "t2",
},
&ExprStr{
V: "world",
},
},
},
},
}
testCases = append(testCases, test{
name: "simple class with typed args 1",
code: `
class c1($a str, $b) {
test $a {
stringptr => $b,
}
}
include c1("t1", "hello")
include c1("t2", "world")
`,
fail: false,
exp: exp,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtImport{
Name: "foo1",
Alias: "",
},
},
}
testCases = append(testCases, test{
name: "simple import 1",
code: `
import "foo1"
`,
fail: false,
exp: exp,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtImport{
Name: "foo1",
Alias: "bar",
},
},
}
testCases = append(testCases, test{
name: "simple import 2",
code: `
import "foo1" as bar
`,
fail: false,
exp: exp,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtImport{
Name: "foo1",
Alias: "",
},
&StmtImport{
Name: "foo2",
Alias: "bar",
},
&StmtImport{
Name: "foo3",
Alias: "",
},
},
}
testCases = append(testCases, test{
name: "simple import 3",
code: `
import "foo1"
import "foo2" as bar
import "foo3"
`,
fail: false,
exp: exp,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtImport{
Name: "foo1",
Alias: "*",
},
},
}
testCases = append(testCases, test{
name: "simple import 4",
code: `
import "foo1" as *
`,
fail: false,
exp: exp,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtClass{
Name: "c1",
Body: &StmtProg{
Prog: []interfaces.Stmt{
&StmtImport{
Name: "foo",
Alias: "bar",
},
&StmtImport{
Name: "baz",
Alias: "",
},
},
},
},
&StmtInclude{
Name: "c1",
},
},
}
testCases = append(testCases, test{
name: "simple import inside class 1",
code: `
class c1 {
import "foo" as bar
import "baz"
}
include c1
`,
fail: false,
exp: exp,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtFunc{
Name: "f1",
Func: &ExprFunc{
Args: []*Arg{},
Body: &ExprInt{
V: 42,
},
},
},
},
}
testCases = append(testCases, test{
name: "simple function stmt 1",
code: `
func f1() {
42
}
`,
fail: false,
exp: exp,
})
}
{
fn := &ExprFunc{
Args: []*Arg{},
Return: types.TypeInt,
Body: &ExprCall{
Name: operatorFuncName,
Args: []interfaces.Expr{
&ExprStr{
V: "+",
},
&ExprInt{
V: 13,
},
&ExprInt{
V: 42,
},
},
},
}
// sometimes, the type can get set by the parser when it's known
if err := fn.SetType(types.NewType("func() int")); err != nil {
t.Fatal("could not build type")
}
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtFunc{
Name: "f2",
Func: fn,
},
},
}
testCases = append(testCases, test{
name: "simple function stmt 2",
code: `
func f2() int {
13 + 42
}
`,
fail: false,
exp: exp,
})
}
{
fn := &ExprFunc{
Args: []*Arg{
{
Name: "a",
Type: types.TypeInt,
},
{
Name: "b",
//Type: &types.Type{},
},
},
Return: types.TypeInt,
Body: &ExprCall{
Name: operatorFuncName,
Args: []interfaces.Expr{
&ExprStr{
V: "+",
},
&ExprVar{
Name: "a",
},
&ExprVar{
Name: "b",
},
},
},
}
// we can't set the type here, because it's only partially known
//if err := fn.SetType(types.NewType("func() int")); err != nil {
// t.Fatal("could not build type")
//}
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtFunc{
Name: "f3",
Func: fn,
},
},
}
testCases = append(testCases, test{
name: "simple function stmt 3",
code: `
func f3($a int, $b) int {
$a + $b
}
`,
fail: false,
exp: exp,
})
}
{
fn := &ExprFunc{
Args: []*Arg{
{
Name: "x",
Type: types.TypeStr,
},
},
Return: types.TypeStr,
Body: &ExprCall{
Name: operatorFuncName,
Args: []interfaces.Expr{
&ExprStr{
V: "+",
},
&ExprStr{
V: "hello",
},
&ExprVar{
Name: "x",
},
},
},
}
if err := fn.SetType(types.NewType("func(x str) str")); err != nil {
t.Fatal("could not build type")
}
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtFunc{
Name: "f4",
Func: fn,
},
},
}
testCases = append(testCases, test{
name: "simple function stmt 4",
code: `
func f4($x str) str {
"hello" + $x
}
`,
fail: false,
exp: exp,
})
}
{
fn := &ExprFunc{
Args: []*Arg{},
Body: &ExprInt{
V: 42,
},
}
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtBind{
Ident: "fn",
Value: fn,
},
},
}
testCases = append(testCases, test{
name: "simple function expr 1",
code: `
# lambda
$fn = func() {
42
}
`,
fail: false,
exp: exp,
})
}
{
fn := &ExprFunc{
Args: []*Arg{
{
Name: "x",
Type: types.TypeStr,
},
},
Return: types.TypeStr,
Body: &ExprCall{
Name: operatorFuncName,
Args: []interfaces.Expr{
&ExprStr{
V: "+",
},
&ExprStr{
V: "hello",
},
&ExprVar{
Name: "x",
},
},
},
}
if err := fn.SetType(types.NewType("func(x str) str")); err != nil {
t.Fatal("could not build type")
}
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtBind{
Ident: "fn",
Value: fn,
},
},
}
testCases = append(testCases, test{
name: "simple function expr 2",
code: `
# lambda
$fn = func($x str) str {
"hello" + $x
}
`,
fail: false,
exp: exp,
})
}
{
fn := &ExprFunc{
Args: []*Arg{
{
Name: "x",
Type: types.TypeStr,
},
},
Return: types.TypeStr,
Body: &ExprCall{
Name: operatorFuncName,
Args: []interfaces.Expr{
&ExprStr{
V: "+",
},
&ExprStr{
V: "hello",
},
&ExprVar{
Name: "x",
},
},
},
}
if err := fn.SetType(types.NewType("func(x str) str")); err != nil {
t.Fatal("could not build type")
}
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtBind{
Ident: "fn",
Value: fn,
},
&StmtBind{
Ident: "foo",
Value: &ExprCall{
Name: "fn",
Args: []interfaces.Expr{
&ExprStr{
V: "world",
},
},
Var: true,
},
},
},
}
testCases = append(testCases, test{
name: "simple function expr 3",
code: `
# lambda
$fn = func($x str) str {
"hello" + $x
}
$foo = $fn("world") # helloworld
`,
fail: false,
exp: exp,
})
}
{
exp := &StmtProg{
Prog: []interfaces.Stmt{
&StmtFunc{
Name: "funcgen",
// This is the outer function...
Func: &ExprFunc{
Args: []*Arg{},
// This is the inner function...
Body: &ExprFunc{
Args: []*Arg{},
Body: &ExprStr{
V: "hello",
},
},
},
},
&StmtBind{
Ident: "fn",
Value: &ExprCall{
Name: "funcgen",
Args: []interfaces.Expr{},
Var: false,
},
},
&StmtBind{
Ident: "foo",
Value: &ExprCall{
Name: "fn",
Args: []interfaces.Expr{},
Var: true, // comes from a var
},
},
},
}
testCases = append(testCases, test{
name: "simple nested function 1",
code: `
func funcgen() { # returns a function expression
func() {
"hello"
}
}
$fn = funcgen()
$foo = $fn() # hello
`,
fail: false,
exp: exp,
})
}
if testing.Short() {
t.Logf("available tests:")
}
names := []string{}
for index, tc := range testCases { // run all the tests
if tc.name == "" {
t.Errorf("test #%d: not named", index)
continue
}
if util.StrInList(tc.name, names) {
t.Errorf("test #%d: duplicate sub test name of: %s", index, tc.name)
continue
}
names = append(names, tc.name)
//if index != 3 { // hack to run a subset (useful for debugging)
//if (index != 20 && index != 21) {
//if tc.name != "nil" {
// continue
//}
testName := fmt.Sprintf("test #%d (%s)", index, tc.name)
if testing.Short() { // make listing tests easier
t.Logf("%s", testName)
continue
}
t.Run(testName, func(t *testing.T) {
name, code, fail, exp := tc.name, tc.code, tc.fail, tc.exp
t.Logf("\n\ntest #%d (%s) ----------------\n\n", index, name)
str := strings.NewReader(code)
ast, err := LexParse(str)
if !fail && err != nil {
t.Errorf("test #%d: lex/parse failed with: %+v", index, err)
return
}
if fail && err == nil {
t.Errorf("test #%d: lex/parse passed, expected fail", index)
return
}
if !fail && ast == nil {
t.Errorf("test #%d: lex/parse was nil", index)
return
}
if exp != nil {
if !reflect.DeepEqual(ast, exp) {
// double check because DeepEqual is different since the func exists
// more details, for tricky cases:
diffable := &pretty.Config{
Diffable: true,
IncludeUnexported: false,
//PrintStringers: false, // always false!
//PrintTextMarshalers: false,
SkipZeroFields: true,
}
diff := diffable.Compare(exp, ast)
if diff != "" { // bonus
t.Errorf("test #%d: AST did not match expected", index)
// TODO: consider making our own recursive print function
t.Logf("test #%d: actual: \n\n%s\n", index, spew.Sdump(ast))
t.Logf("test #%d: expected: \n\n%s", index, spew.Sdump(exp))
t.Logf("test #%d: actual: \n\n%s\n", index, diffable.Sprint(ast))
t.Logf("test #%d: expected: \n\n%s", index, diffable.Sprint(exp))
t.Logf("test #%d: diff:\n%s", index, diff)
return
}
}
}
})
}
}
func TestLexParse1(t *testing.T) {
code := `
$a = 42
$b = true
$c = 13
$d = "hello"
$e = true
$f = 3.13
# some noop resource
noop "n0" {
foo => true,
bar => false # this should be a parser error (no comma)
}
# hello
# world
test "t1" {}
` // error
str := strings.NewReader(code)
_, err := LexParse(str)
if e, ok := err.(*LexParseErr); ok && e.Err != ErrParseExpectingComma {
t.Errorf("lex/parse failure, got: %+v", e)
} else if err == nil {
t.Errorf("lex/parse success, expected error")
} else {
if e.Row != 10 || e.Col != 9 {
t.Errorf("expected error at 10 x 9, got: %d x %d", e.Row, e.Col)
}
t.Logf("row x col: %d x %d", e.Row, e.Col)
t.Logf("message: %s", e.Str)
t.Logf("output: %+v", err)
}
}
func TestLexParse2(t *testing.T) {
code := `
$a == 13
test "t1" {
int8 => $a,
}
` // error, assignment is a single equals, not two
str := strings.NewReader(code)
_, err := LexParse(str)
if e, ok := err.(*LexParseErr); ok && e.Err != ErrParseAdditionalEquals {
t.Errorf("lex/parse failure, got: %+v", e)
} else if err == nil {
t.Errorf("lex/parse success, expected error")
} else {
// TODO: when this is accurate, pick values and enable this!
//if e.Row != 8 || e.Col != 2 {
// t.Errorf("expected error at 8 x 2, got: %d x %d", e.Row, e.Col)
//}
t.Logf("row x col: %d x %d", e.Row, e.Col)
t.Logf("message: %s", e.Str)
t.Logf("output: %+v", err)
}
}
func TestLexParseWithOffsets1(t *testing.T) {
code1 := `
# "file1"
$a = 42
$b = true
$c = 13
$d = "hello"
$e = true
$f = 3.13
`
code2 := `
# "file2"
# some noop resource
noop "n0" {
foo => true,
bar => false # this should be a parser error (no comma)
}
# hello
# world
test "t2" {}
`
code3 := `
# "file3"
# this is some more code
test "t3" {}
`
str1 := strings.NewReader(code1)
str2 := strings.NewReader(code2)
str3 := strings.NewReader(code3)
// TODO: this is currently in number of lines instead of bytes
o1 := uint64(len(strings.Split(code1, "\n")) - 1)
o2 := uint64(len(strings.Split(code2, "\n")) - 1)
//o1 := uint64(len(code1))
//o2 := uint64(len(code2))
t.Logf("o1: %+v", o1)
t.Logf("o2: %+v", o2)
t.Logf("o1+o2: %+v", o1+o2)
readers := io.MultiReader(str1, str2, str3)
offsets := map[uint64]string{
0: "file1",
o1: "file2",
o1 + o2: "file3", // offset is cumulative
}
_, err := LexParseWithOffsets(readers, offsets)
if e, ok := err.(*LexParseErr); ok && e.Err != ErrParseExpectingComma {
t.Errorf("lex/parse failure, got: %+v", e)
} else if err == nil {
t.Errorf("lex/parse success, expected error")
} else {
if e.Row != 5 || e.Col != 9 || e.Filename != "file2" {
t.Errorf("expected error in 'file2' @ 5 x 9, got: '%s' @ %d x %d", e.Filename, e.Row, e.Col)
}
t.Logf("file @ row x col: '%s' @ %d x %d", e.Filename, e.Row, e.Col)
t.Logf("message: %s", e.Str)
t.Logf("output: %+v", err) // this will be 1-indexed, instead of zero-indexed
}
}
func TestImportParsing0(t *testing.T) {
type test struct { // an individual test
name string
fail bool
alias string
isSystem bool
isLocal bool
isFile bool
path string
url string
}
testCases := []test{}
testCases = append(testCases, test{ // index: 0
name: "",
fail: true, // can't be empty
})
testCases = append(testCases, test{
name: "/",
fail: true, // can't be root
})
testCases = append(testCases, test{
name: "git://example.com/purpleidea/mgmt",
alias: "mgmt",
isLocal: false,
path: "example.com/purpleidea/mgmt/",
url: "git://example.com/purpleidea/mgmt",
})
testCases = append(testCases, test{
name: "git://example.com/purpleidea/mgmt/",
alias: "mgmt",
isLocal: false,
path: "example.com/purpleidea/mgmt/",
url: "git://example.com/purpleidea/mgmt/",
})
testCases = append(testCases, test{
name: "git://example.com/purpleidea/mgmt/foo/bar/",
alias: "bar",
isLocal: false,
path: "example.com/purpleidea/mgmt/foo/bar/",
// TODO: change this to be more clever about the clone URL
//url: "git://example.com/purpleidea/mgmt/",
// TODO: also consider changing `git` to `https` ?
url: "git://example.com/purpleidea/mgmt/foo/bar/",
})
testCases = append(testCases, test{
name: "git://example.com/purpleidea/mgmt-foo",
alias: "foo", // prefix is magic
isLocal: false,
path: "example.com/purpleidea/mgmt-foo/",
url: "git://example.com/purpleidea/mgmt-foo",
})
testCases = append(testCases, test{
name: "git://example.com/purpleidea/foo-bar",
alias: "foo_bar",
isLocal: false,
path: "example.com/purpleidea/foo-bar/",
url: "git://example.com/purpleidea/foo-bar",
})
testCases = append(testCases, test{
name: "git://example.com/purpleidea/FOO-bar",
alias: "foo_bar",
isLocal: false,
path: "example.com/purpleidea/FOO-bar/",
url: "git://example.com/purpleidea/FOO-bar",
})
testCases = append(testCases, test{
name: "git://example.com/purpleidea/foo-BAR",
alias: "foo_bar",
isLocal: false,
path: "example.com/purpleidea/foo-BAR/",
url: "git://example.com/purpleidea/foo-BAR",
})
testCases = append(testCases, test{
name: "git://example.com/purpleidea/foo-BAR-baz",
alias: "foo_bar_baz",
isLocal: false,
path: "example.com/purpleidea/foo-BAR-baz/",
url: "git://example.com/purpleidea/foo-BAR-baz",
})
testCases = append(testCases, test{
name: "git://example.com/purpleidea/Module-Name",
alias: "module_name",
isLocal: false,
path: "example.com/purpleidea/Module-Name/",
url: "git://example.com/purpleidea/Module-Name",
})
testCases = append(testCases, test{
name: "git://example.com/purpleidea/foo-",
fail: true, // trailing dash or underscore
})
testCases = append(testCases, test{
name: "git://example.com/purpleidea/foo_",
fail: true, // trailing dash or underscore
})
testCases = append(testCases, test{
name: "/var/lib/mgmt",
alias: "mgmt",
fail: true, // don't allow absolute paths
//isLocal: true,
//path: "/var/lib/mgmt",
})
testCases = append(testCases, test{
name: "/var/lib/mgmt/",
alias: "mgmt",
fail: true, // don't allow absolute paths
//isLocal: true,
//path: "/var/lib/mgmt/",
})
testCases = append(testCases, test{
name: "git://example.com/purpleidea/Module-Name?foo=bar&baz=42",
alias: "module_name",
isLocal: false,
path: "example.com/purpleidea/Module-Name/",
url: "git://example.com/purpleidea/Module-Name",
})
testCases = append(testCases, test{
name: "git://example.com/purpleidea/Module-Name/?foo=bar&baz=42",
alias: "module_name",
isLocal: false,
path: "example.com/purpleidea/Module-Name/",
url: "git://example.com/purpleidea/Module-Name/",
})
testCases = append(testCases, test{
name: "git://example.com/purpleidea/Module-Name/?sha1=25ad05cce36d55ce1c55fd7e70a3ab74e321b66e",
alias: "module_name",
isLocal: false,
path: "example.com/purpleidea/Module-Name/",
url: "git://example.com/purpleidea/Module-Name/",
// TODO: report the query string info as an additional param
})
testCases = append(testCases, test{
name: "git://example.com/purpleidea/Module-Name/subpath/foo",
alias: "foo",
isLocal: false,
path: "example.com/purpleidea/Module-Name/subpath/foo/",
url: "git://example.com/purpleidea/Module-Name/subpath/foo",
})
testCases = append(testCases, test{
name: "foo/",
alias: "foo",
isLocal: true,
path: "foo/",
})
testCases = append(testCases, test{
// import foo.mcl # import a file next to me
name: "foo.mcl",
alias: "foo",
isSystem: false,
isLocal: true,
isFile: true,
path: "foo.mcl",
})
testCases = append(testCases, test{
// import server/foo.mcl # import a file in a dir next to me
name: "server/foo.mcl",
alias: "foo",
isSystem: false,
isLocal: true,
isFile: true,
path: "server/foo.mcl",
})
testCases = append(testCases, test{
// import a deeper file (not necessarily a good idea)
name: "server/vars/blah.mcl",
alias: "blah",
isSystem: false,
isLocal: true,
isFile: true,
path: "server/vars/blah.mcl",
})
testCases = append(testCases, test{
name: "foo/bar",
alias: "bar",
isSystem: true, // system because not a dir (no trailing slash)
isLocal: true, // not really used, but this is what we return
})
testCases = append(testCases, test{
name: "foo/bar/baz",
alias: "baz",
isSystem: true, // system because not a dir (no trailing slash)
isLocal: true, // not really used, but this is what we return
})
testCases = append(testCases, test{
name: "fmt",
alias: "fmt",
isSystem: true,
isLocal: true, // not really used, but this is what we return
})
testCases = append(testCases, test{
name: "blah",
alias: "blah",
isSystem: true, // even modules that don't exist return true here
isLocal: true,
})
testCases = append(testCases, test{
name: "git:///home/james/code/mgmt-example1/",
alias: "example1",
isSystem: false,
isLocal: false,
// FIXME: do we want to have a special "local" imports dir?
path: "home/james/code/mgmt-example1/",
url: "git:///home/james/code/mgmt-example1/",
})
testCases = append(testCases, test{
name: "git:////home/james/code/mgmt-example1/",
fail: true, // don't allow double root slash
})
t.Logf("ModuleMagicPrefix: %s", ModuleMagicPrefix)
names := []string{}
for index, tc := range testCases { // run all the tests
if util.StrInList(tc.name, names) {
t.Errorf("test #%d: duplicate sub test name of: %s", index, tc.name)
continue
}
names = append(names, tc.name)
t.Run(fmt.Sprintf("test #%d (%s)", index, tc.name), func(t *testing.T) {
name, fail, alias, isSystem, isLocal, isFile, path, url := tc.name, tc.fail, tc.alias, tc.isSystem, tc.isLocal, tc.isFile, tc.path, tc.url
output, err := ParseImportName(name)
if !fail && err != nil {
t.Errorf("test #%d: FAIL", index)
t.Errorf("test #%d: ParseImportName failed with: %+v", index, err)
return
}
if fail && err == nil {
t.Errorf("test #%d: FAIL", index)
t.Errorf("test #%d: ParseImportName expected error, not nil", index)
t.Logf("test #%d: output: %+v", index, output)
return
}
if fail { // we failed as expected, don't continue...
return
}
if alias != output.Alias {
t.Errorf("test #%d: unexpected value for: `Alias`", index)
//t.Logf("test #%d: input: %s", index, name)
t.Logf("test #%d: output: %+v", index, output)
t.Logf("test #%d: alias: %s", index, alias)
return
}
if isSystem != output.IsSystem {
t.Errorf("test #%d: unexpected value for: `IsSystem`", index)
//t.Logf("test #%d: input: %s", index, name)
t.Logf("test #%d: output: %+v", index, output)
t.Logf("test #%d: isSystem: %t", index, isSystem)
return
}
if isLocal != output.IsLocal {
t.Errorf("test #%d: unexpected value for: `IsLocal`", index)
//t.Logf("test #%d: input: %s", index, name)
t.Logf("test #%d: output: %+v", index, output)
t.Logf("test #%d: isLocal: %t", index, isLocal)
return
}
if isFile != output.IsFile {
t.Errorf("test #%d: unexpected value for: `isFile`", index)
//t.Logf("test #%d: input: %s", index, name)
t.Logf("test #%d: output: %+v", index, output)
t.Logf("test #%d: isFile: %t", index, isFile)
return
}
if path != output.Path {
t.Errorf("test #%d: unexpected value for: `Path`", index)
//t.Logf("test #%d: input: %s", index, name)
t.Logf("test #%d: output: %+v", index, output)
t.Logf("test #%d: path: %s", index, path)
return
}
if url != output.URL {
t.Errorf("test #%d: unexpected value for: `URL`", index)
//t.Logf("test #%d: input: %s", index, name)
t.Logf("test #%d: output: %+v", index, output)
t.Logf("test #%d: url: %s", index, url)
return
}
// add some additional sanity checking:
if strings.HasPrefix(path, "/") {
t.Errorf("test #%d: the path value starts with a / (it should be relative)", index)
}
if !isSystem {
if !strings.HasSuffix(path, "/") && !strings.HasSuffix(path, interfaces.DotFileNameExtension) {
t.Errorf("test #%d: the path value should be a directory or a code file", index)
}
}
})
}
}