Files
mgmt/lang/lexparse_test.go
James Shubin c8e9a100a6 lang: Support lexing and parsing a list of files with offsets
This adds a LexParseWithOffsets method that also takes a list of offsets
to be used if our input stream is composed of multiple io.Readers
combined together.

At the moment the offsets are based on line count instead of file size.
I think the latter would be preferable, but it seems it's much more
difficult to implement as it probably requires support in the lexer and
parser. That improved solution would probably be faster, and more
correct in case someone passed in a file without a trailing newline.
2018-12-20 21:21:30 -05:00

1581 lines
28 KiB
Go

// Mgmt
// Copyright (C) 2013-2018+ 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 (
"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"
)
func TestLexParse0(t *testing.T) {
type test struct { // an individual test
name string
code string
fail bool
exp interfaces.Stmt
}
values := []test{}
{
values = append(values, test{
"nil",
``,
false,
nil,
})
}
{
values = append(values, test{
name: "simple assignment",
code: `$rewsna = -42`,
fail: false,
exp: &StmtProg{
Prog: []interfaces.Stmt{
&StmtBind{
Ident: "rewsna",
Value: &ExprInt{
V: -42,
},
},
},
},
})
}
{
values = append(values, test{
name: "one res",
code: `noop "n1" {}`,
fail: false,
//exp: ???, // FIXME: add the expected AST
})
}
{
values = append(values, test{
name: "res with keyword",
code: `false "n1" {}`, // false is a special keyword
fail: true,
})
}
{
values = append(values, test{
name: "bad escaping",
code: `
test "t1" {
str => "he\ llo", # incorrect escaping
}
`,
fail: true,
})
}
{
values = append(values, test{
name: "int overflow",
code: `
test "t1" {
int => 888888888888888888888888, # overflows
}
`,
fail: true,
})
}
{
values = append(values, test{
name: "overflow after lexer",
code: `
test "t1" {
uint8 => 128, # does not overflow at lexer stage
}
`,
fail: false,
//exp: ???, // FIXME: add the expected AST
})
}
{
values = append(values, test{
name: "one res with param",
code: `
test "t1" {
int16 => 01134, # some comment
}
`,
fail: false,
//exp: ???, // FIXME: add the expected AST
})
}
{
values = append(values, 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
values = append(values, test{
name: "two lists",
code: `
$somelist = [42, 0, -13,]
$somelonglist = [
"hello",
"and",
"how",
"are",
"you?",
]
`,
fail: false,
//exp: ???, // FIXME: add the expected AST
})
}
{
values = append(values, test{
name: "one map",
code: `
$somemap = {
"foo" => "foo1",
"bar" => "bar1",
}
`,
fail: false,
//exp: ???, // FIXME: add the expected AST
})
}
{
values = append(values, test{
name: "another map",
code: `
$somemap = {
"foo" => -13,
"bar" => 42,
}
`,
fail: false,
//exp: ???, // FIXME: add the expected AST
})
}
// TODO: alternate possible syntax ?
//{
// values = append(values, test{ // ?
// code: `
// $somestruct = struct{
// foo: "foo1";
// bar: 42 # no trailing semicolon at the moment
// }
// `,
// fail: false,
// //exp: ???, // FIXME: add the expected AST
// })
//}
{
values = append(values, test{
name: "one struct",
code: `
$somestruct = struct{
foo => "foo1",
bar => 42,
}
`,
fail: false,
//exp: ???, // FIXME: add the expected AST
})
}
{
values = append(values, 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
{
values = append(values, test{
name: "some lists",
code: `
$intlist []int = [42, -0, 13,]
$intlistnested [][]int = [[42,], [], [100, -0,], [-13,],]
`,
fail: false,
//exp: ???, // FIXME: add the expected AST
})
}
{
values = append(values, 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
})
}
{
values = append(values, 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
})
}
{
values = append(values, 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.
//{
// values = append(values, test{
// name: "overflowing float",
// code: `
// test "t1" {
// float32 => -457643875645764387564578645457864525457643875645764387564578645457864525.457643875645764387564578645457864525387899898753459879587574928798759863965, # overflow
// }
// `,
// fail: true,
// })
//}
{
values = append(values, 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{},
},
},
},
}
values = append(values, 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",
},
},
},
},
},
}
values = append(values, 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{},
},
},
},
}
values = append(values, 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",
},
},
},
},
},
}
values = append(values, test{
name: "func call dotted 2",
code: `
$x1 = pkg.foo1(true, "hello")
`,
fail: false,
exp: exp,
})
}
{
values = append(values, test{
name: "func call dotted invalid 1",
code: `
$x1 = .pkg.foo1(true, "hello")
`,
fail: true,
})
}
{
values = append(values, test{
name: "func call dotted invalid 2",
code: `
$x1 = pkg.foo1.(true, "hello")
`,
fail: true,
})
}
{
values = append(values, test{
name: "func call dotted invalid 3",
code: `
$x1 = .pkg.foo1.(true, "hello")
`,
fail: true,
})
}
{
values = append(values, 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",
},
},
},
}
values = append(values, 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",
},
},
},
}
values = append(values, test{
name: "dotted var 2",
code: `
$x1 = $pkg.foo1.bar
`,
fail: false,
exp: exp,
})
}
{
values = append(values, test{
name: "invalid dotted var 1",
code: `
$x1 = $.pkg.foo1.bar
`,
fail: true,
})
}
{
values = append(values, test{
name: "invalid dotted var 2",
code: `
$x1 = $pkg.foo1.bar.
`,
fail: true,
})
}
{
values = append(values, test{
name: "invalid dotted var 3",
code: `
$x1 = $.pkg.foo1.bar.
`,
fail: true,
})
}
{
values = append(values, 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,
},
},
},
},
},
},
},
}
values = append(values, 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,
},
},
},
},
},
},
},
}
values = append(values, 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,
},
},
},
},
},
},
},
},
},
}
values = append(values, 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,
},
},
},
},
},
},
},
}
values = append(values, 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,
},
},
},
},
},
},
},
},
},
}
values = append(values, 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,
},
},
},
},
},
},
},
}
values = append(values, 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,
},
},
},
},
},
},
},
},
},
}
values = append(values, 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,
},
},
},
},
},
},
},
}
values = append(values, 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,
},
},
},
},
},
},
},
}
values = append(values, 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",
},
},
},
},
}
values = append(values, 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,
})
}
{
values = append(values, test{
name: "parser set type incompatibility str",
code: `
$x int = "hello" # type should be str to work
test "t1" {
str => $x,
}
`,
fail: true,
})
}
{
values = append(values, 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",
},
},
}
values = append(values, 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",
},
},
}
values = append(values, 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",
},
},
}
values = append(values, 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,
})
}
{
values = append(values, test{
name: "simple dotted invalid class 1",
code: `
# a dotted identifier only occurs via an imported class
class foo.c1 {
}
`,
fail: true,
})
}
{
values = append(values, test{
name: "simple dotted invalid class 2",
code: `
# a dotted identifier only occurs via an imported class
class foo.bar.c1 {
}
`,
fail: true,
})
}
{
values = append(values, test{
name: "simple dotted invalid include 1",
code: `
class .foo.c1 {
}
`,
fail: true,
})
}
{
values = append(values, test{
name: "simple dotted invalid include 2",
code: `
class foo.c1. {
}
`,
fail: true,
})
}
{
values = append(values, test{
name: "simple dotted invalid include 3",
code: `
class .foo.c1. {
}
`,
fail: true,
})
}
{
values = append(values, test{
name: "simple dotted invalid include 4",
code: `
class foo..c1 {
}
`,
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",
},
},
},
},
}
values = append(values, 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",
},
},
},
},
}
values = append(values, 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,
})
}
names := []string{}
for index, test := range values { // run all the tests
name, code, fail, exp := test.name, test.code, test.fail, test.exp
if name == "" {
name = "<sub test not named>"
}
if util.StrInList(name, names) {
t.Errorf("test #%d: duplicate sub test name of: %s", index, name)
continue
}
names = append(names, name)
//if index != 3 { // hack to run a subset (useful for debugging)
//if (index != 20 && index != 21) {
//if test.name != "nil" {
// continue
//}
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)
continue
}
if fail && err == nil {
t.Errorf("test #%d: lex/parse passed, expected fail", index)
continue
}
if !fail && ast == nil {
t.Errorf("test #%d: lex/parse was nil", index)
continue
}
if exp != nil {
if !reflect.DeepEqual(ast, exp) {
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))
continue
}
}
}
}
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
}
}