From db160a0249395268ed09f76db637245c3572c5a5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 22 Jul 2014 14:21:47 -0700 Subject: [PATCH 1/8] config: add LiteralInterpolation --- config/interpolate.go | 21 +++++++++++++++++++++ config/interpolate_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/config/interpolate.go b/config/interpolate.go index 59f14a83b..935ff2e8f 100644 --- a/config/interpolate.go +++ b/config/interpolate.go @@ -43,6 +43,14 @@ type FunctionInterpolation struct { key string } +// LiteralInterpolation implements Interpolation for literals. Ex: +// ${"foo"} will equal "foo". +type LiteralInterpolation struct { + Literal string + + key string +} + // VariableInterpolation implements Interpolation for simple variable // interpolation. Ex: "${var.foo}" or "${aws_instance.foo.bar}" type VariableInterpolation struct { @@ -174,6 +182,19 @@ func (i *FunctionInterpolation) Variables() map[string]InterpolatedVariable { return result } +func (i *LiteralInterpolation) FullString() string { + return i.key +} + +func (i *LiteralInterpolation) Interpolate( + map[string]string) (string, error) { + return i.Literal, nil +} + +func (i *LiteralInterpolation) Variables() map[string]InterpolatedVariable { + return nil +} + func (i *VariableInterpolation) FullString() string { return i.key } diff --git a/config/interpolate_test.go b/config/interpolate_test.go index 3b01c6cd7..20d72f18a 100644 --- a/config/interpolate_test.go +++ b/config/interpolate_test.go @@ -199,6 +199,33 @@ func TestFunctionInterpolation(t *testing.T) { } } +func TestLiteralInterpolation_impl(t *testing.T) { + var _ Interpolation = new(LiteralInterpolation) +} + +func TestLiteralInterpolation(t *testing.T) { + i := &LiteralInterpolation{ + Literal: "bar", + key: "foo", + } + if i.FullString() != "foo" { + t.Fatalf("err: %#v", i) + } + + if i.Variables() != nil { + t.Fatalf("bad: %#v", i.Variables()) + } + + actual, err := i.Interpolate(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + if actual != "bar" { + t.Fatalf("bad: %#v", actual) + } +} + func TestResourceVariable_impl(t *testing.T) { var _ InterpolatedVariable = new(ResourceVariable) } From 1dcefba5c439daf12d9ba9a1a98b9d7997dee5ad Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 22 Jul 2014 15:23:01 -0700 Subject: [PATCH 2/8] config: parser --- .gitignore | 2 + config/expr.y | 84 +++++++++++++++++++++++++++ config/expr_lex.go | 130 ++++++++++++++++++++++++++++++++++++++++++ config/expr_test.go | 9 +++ config/interpolate.go | 4 +- 5 files changed, 227 insertions(+), 2 deletions(-) create mode 100644 config/expr.y create mode 100644 config/expr_lex.go create mode 100644 config/expr_test.go diff --git a/.gitignore b/.gitignore index 03a5621e4..e7669ef01 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ example.tf terraform.tfplan terraform.tfstate bin/ +config/y.go +config/y.output vendor/ diff --git a/config/expr.y b/config/expr.y new file mode 100644 index 000000000..58e5fac67 --- /dev/null +++ b/config/expr.y @@ -0,0 +1,84 @@ +// This is the yacc input for creating the parser for interpolation +// expressions in Go. + +// To build it: +// +// go tool yacc -p "expr" expr.y (produces y.go) +// + +%{ +package config + +import ( + "fmt" +) + +%} + +%union { + expr Interpolation + str string + variable InterpolatedVariable + args []Interpolation +} + +%type args +%type expr +%type string +%type variable + +%token STRING IDENTIFIER +%token COMMA LEFTPAREN RIGHTPAREN + +%% + +top: + expr + { + fmt.Printf("%#v", $1) + } + +expr: + string + { + $$ = &LiteralInterpolation{Literal: $1} + } +| variable + { + $$ = &VariableInterpolation{Variable: $1} + } +| IDENTIFIER LEFTPAREN args RIGHTPAREN + { + $$ = &FunctionInterpolation{Func: $1, Args: $3} + } + +args: + { + $$ = nil + } +| expr COMMA expr + { + $$ = append($$, $1, $3) + } +| expr + { + $$ = append($$, $1) + } + +string: + STRING + { + $$ = $1 + } + +variable: + IDENTIFIER + { + var err error + $$, err = NewInterpolatedVariable($1) + if err != nil { + panic(err) + } + } + +%% diff --git a/config/expr_lex.go b/config/expr_lex.go new file mode 100644 index 000000000..5e8404431 --- /dev/null +++ b/config/expr_lex.go @@ -0,0 +1,130 @@ +package config + +import ( + "bytes" + "log" + "unicode" + "unicode/utf8" +) + +// The parser expects the lexer to return 0 on EOF. +const lexEOF = 0 + +// The parser uses the type Lex as a lexer. It must provide +// the methods Lex(*SymType) int and Error(string). +type exprLex struct { + input string + pos int + width int +} + +// The parser calls this method to get each new token. +func (x *exprLex) Lex(yylval *exprSymType) int { + for { + c := x.next() + if c == lexEOF { + return lexEOF + } + + // Ignore all whitespace + if unicode.IsSpace(c) { + continue + } + + switch c { + case '"': + return x.lexString(yylval) + case ',': + return COMMA + case '(': + return LEFTPAREN + case ')': + return RIGHTPAREN + default: + x.backup() + return x.lexId(yylval) + } + } +} + +func (x *exprLex) lexId(yylval *exprSymType) int { + var b bytes.Buffer + for { + c := x.next() + if c == lexEOF { + break + } + + // If this isn't a character we want in an ID, return out. + // One day we should make this a regexp. + if c != '_' && + c != '.' && + c != '*' && + !unicode.IsLetter(c) && + !unicode.IsNumber(c) { + x.backup() + break + } + + if _, err := b.WriteRune(c); err != nil { + log.Printf("ERR: %s", err) + return lexEOF + } + } + + yylval.str = b.String() + return IDENTIFIER +} + +func (x *exprLex) lexString(yylval *exprSymType) int { + var b bytes.Buffer + for { + c := x.next() + if c == lexEOF { + break + } + + // String end + if c == '"' { + break + } + + if _, err := b.WriteRune(c); err != nil { + log.Printf("ERR: %s", err) + return lexEOF + } + } + + yylval.str = b.String() + return STRING +} + +// Return the next rune for the lexer. +func (x *exprLex) next() rune { + if int(x.pos) >= len(x.input) { + x.width = 0 + return lexEOF + } + + r, w := utf8.DecodeRuneInString(x.input[x.pos:]) + x.width = w + x.pos += x.width + return r +} + +// peek returns but does not consume the next rune in the input +func (x *exprLex) peek() rune { + r := x.next() + x.backup() + return r +} + +// backup steps back one rune. Can only be called once per next. +func (x *exprLex) backup() { + x.pos -= x.width +} + +// The parser calls this method on a parse error. +func (x *exprLex) Error(s string) { + log.Printf("parse error: %s", s) +} diff --git a/config/expr_test.go b/config/expr_test.go new file mode 100644 index 000000000..68a6b5fd4 --- /dev/null +++ b/config/expr_test.go @@ -0,0 +1,9 @@ +package config + +import ( + "testing" +) + +func TestExprParse(t *testing.T) { + exprParse(&exprLex{input: `lookup(var.foo)`}) +} diff --git a/config/interpolate.go b/config/interpolate.go index 935ff2e8f..64cb90df0 100644 --- a/config/interpolate.go +++ b/config/interpolate.go @@ -37,8 +37,8 @@ type InterpolatedVariable interface { // FunctionInterpolation is an Interpolation that executes a function // with some variable number of arguments to generate a value. type FunctionInterpolation struct { - Func InterpolationFunc - Args []InterpolatedVariable + Func string + Args []Interpolation key string } From bffdcbaede01ae5143ef95fef17683cae1dc6ed4 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 22 Jul 2014 15:23:05 -0700 Subject: [PATCH 3/8] Makefile: yacc config before make --- Makefile | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 40de98ffe..ad2da4cf6 100644 --- a/Makefile +++ b/Makefile @@ -16,27 +16,31 @@ export CGO_CFLAGS CGO_LDFLAGS PATH default: test -dev: libucl +dev: config/y.go libucl @sh -c "$(CURDIR)/scripts/build.sh" libucl: vendor/libucl/$(LIBUCL_NAME) -test: libucl +test: config/y.go libucl TF_ACC= go test $(TEST) $(TESTARGS) -timeout=10s -testacc: libucl +testacc: config/y.go libucl @if [ "$(TEST)" = "./..." ]; then \ echo "ERROR: Set TEST to a specific package"; \ exit 1; \ fi TF_ACC=1 go test $(TEST) -v $(TESTARGS) -testrace: libucl +testrace: config/y.go libucl TF_ACC= go test -race $(TEST) $(TESTARGS) updatedeps: go get -u -v ./... +config/y.go: config/expr.y + cd config/ && \ + go tool yacc -p "expr" expr.y + vendor/libucl/libucl.a: vendor/libucl cd vendor/libucl && \ cmake cmake/ && \ From 4f57437144edee0b08516f3e6b6dd94c88a532ba Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 22 Jul 2014 15:59:53 -0700 Subject: [PATCH 4/8] config: parser fixes and application --- config/expr.y | 174 +++++++++++++++++--------------- config/expr_lex.go | 1 + config/expr_parse.go | 34 +++++++ config/interpolate.go | 99 ++---------------- config/interpolate_test.go | 22 ++-- config/interpolate_walk.go | 2 +- config/interpolate_walk_test.go | 12 +-- 7 files changed, 149 insertions(+), 195 deletions(-) create mode 100644 config/expr_parse.go diff --git a/config/expr.y b/config/expr.y index 58e5fac67..727e0ef59 100644 --- a/config/expr.y +++ b/config/expr.y @@ -1,84 +1,90 @@ -// This is the yacc input for creating the parser for interpolation -// expressions in Go. - -// To build it: -// -// go tool yacc -p "expr" expr.y (produces y.go) -// - -%{ -package config - -import ( - "fmt" -) - -%} - -%union { - expr Interpolation - str string - variable InterpolatedVariable - args []Interpolation -} - -%type args -%type expr -%type string -%type variable - -%token STRING IDENTIFIER -%token COMMA LEFTPAREN RIGHTPAREN - -%% - -top: - expr - { - fmt.Printf("%#v", $1) - } - -expr: - string - { - $$ = &LiteralInterpolation{Literal: $1} - } -| variable - { - $$ = &VariableInterpolation{Variable: $1} - } -| IDENTIFIER LEFTPAREN args RIGHTPAREN - { - $$ = &FunctionInterpolation{Func: $1, Args: $3} - } - -args: - { - $$ = nil - } -| expr COMMA expr - { - $$ = append($$, $1, $3) - } -| expr - { - $$ = append($$, $1) - } - -string: - STRING - { - $$ = $1 - } - -variable: - IDENTIFIER - { - var err error - $$, err = NewInterpolatedVariable($1) - if err != nil { - panic(err) - } - } - -%% +// This is the yacc input for creating the parser for interpolation +// expressions in Go. + +// To build it: +// +// go tool yacc -p "expr" expr.y (produces y.go) +// + +%{ +package config + +import ( + "fmt" +) + +%} + +%union { + expr Interpolation + str string + variable InterpolatedVariable + args []Interpolation +} + +%type args +%type expr +%type string +%type variable + +%token STRING IDENTIFIER +%token COMMA LEFTPAREN RIGHTPAREN + +%% + +top: + expr + { + exprResult = $1 + } + +expr: + string + { + $$ = &LiteralInterpolation{Literal: $1} + } +| variable + { + $$ = &VariableInterpolation{Variable: $1} + } +| IDENTIFIER LEFTPAREN args RIGHTPAREN + { + f, ok := Funcs[$1] + if !ok { + exprErrors = append(exprErrors, fmt.Errorf( + "Unknown function: %s", $1)) + } + + $$ = &FunctionInterpolation{Func: f, Args: $3} + } + +args: + { + $$ = nil + } +| expr COMMA expr + { + $$ = append($$, $1, $3) + } +| expr + { + $$ = append($$, $1) + } + +string: + STRING + { + $$ = $1 + } + +variable: + IDENTIFIER + { + var err error + $$, err = NewInterpolatedVariable($1) + if err != nil { + panic(err) + } + } + +%% diff --git a/config/expr_lex.go b/config/expr_lex.go index 5e8404431..b54a14af8 100644 --- a/config/expr_lex.go +++ b/config/expr_lex.go @@ -58,6 +58,7 @@ func (x *exprLex) lexId(yylval *exprSymType) int { // If this isn't a character we want in an ID, return out. // One day we should make this a regexp. if c != '_' && + c != '-' && c != '.' && c != '*' && !unicode.IsLetter(c) && diff --git a/config/expr_parse.go b/config/expr_parse.go new file mode 100644 index 000000000..0ffb0ba14 --- /dev/null +++ b/config/expr_parse.go @@ -0,0 +1,34 @@ +package config + +import ( + "sync" + + "github.com/hashicorp/terraform/helper/multierror" +) + +// exprErrors are the errors built up from parsing. These should not +// be accessed directly. +var exprErrors []error +var exprLock sync.Mutex +var exprResult Interpolation + +// ExprParse parses the given expression and returns an executable +// Interpolation. +func ExprParse(v string) (Interpolation, error) { + exprLock.Lock() + defer exprLock.Unlock() + exprErrors = nil + exprResult = nil + + // Parse + exprParse(&exprLex{input: v}) + + // Build up the errors + var err error + if len(exprErrors) > 0 { + err = &multierror.Error{Errors: exprErrors} + exprResult = nil + } + + return exprResult, err +} diff --git a/config/interpolate.go b/config/interpolate.go index 64cb90df0..5250627b4 100644 --- a/config/interpolate.go +++ b/config/interpolate.go @@ -17,7 +17,6 @@ var funcRegexp *regexp.Regexp = regexp.MustCompile( // Interpolations might be simple variable references, or it might be // function calls, or even nested function calls. type Interpolation interface { - FullString() string Interpolate(map[string]string) (string, error) Variables() map[string]InterpolatedVariable } @@ -37,26 +36,20 @@ type InterpolatedVariable interface { // FunctionInterpolation is an Interpolation that executes a function // with some variable number of arguments to generate a value. type FunctionInterpolation struct { - Func string + Func InterpolationFunc Args []Interpolation - - key string } // LiteralInterpolation implements Interpolation for literals. Ex: // ${"foo"} will equal "foo". type LiteralInterpolation struct { Literal string - - key string } // VariableInterpolation implements Interpolation for simple variable // interpolation. Ex: "${var.foo}" or "${aws_instance.foo.bar}" type VariableInterpolation struct { Variable InterpolatedVariable - - key string } // A ResourceVariable is a variable that is referencing the field @@ -82,61 +75,6 @@ type UserVariable struct { key string } -// NewInterpolation takes some string and returns the valid -// Interpolation associated with it, or error if a valid -// interpolation could not be found or the interpolation itself -// is invalid. -func NewInterpolation(v string) (Interpolation, error) { - match := funcRegexp.FindStringSubmatch(v) - if match != nil { - fn, ok := Funcs[match[1]] - if !ok { - return nil, fmt.Errorf( - "%s: Unknown function '%s'", - v, match[1]) - } - - args := make([]InterpolatedVariable, 0, len(match)-2) - for i := 2; i < len(match); i++ { - // This can be empty if we have a single argument - // due to the format of the regexp. - if match[i] == "" { - continue - } - - v, err := NewInterpolatedVariable(match[i]) - if err != nil { - return nil, err - } - - args = append(args, v) - } - - return &FunctionInterpolation{ - Func: fn, - Args: args, - - key: v, - }, nil - } - - if idx := strings.Index(v, "."); idx >= 0 { - v, err := NewInterpolatedVariable(v) - if err != nil { - return nil, err - } - - return &VariableInterpolation{ - Variable: v, - key: v.FullKey(), - }, nil - } - - return nil, fmt.Errorf( - "Interpolation '%s' is not a valid interpolation. " + - "Please check your syntax and try again.") -} - func NewInterpolatedVariable(v string) (InterpolatedVariable, error) { if !strings.HasPrefix(v, "var.") { return NewResourceVariable(v) @@ -145,21 +83,13 @@ func NewInterpolatedVariable(v string) (InterpolatedVariable, error) { return NewUserVariable(v) } -func (i *FunctionInterpolation) FullString() string { - return i.key -} - func (i *FunctionInterpolation) Interpolate( vs map[string]string) (string, error) { args := make([]string, len(i.Args)) for idx, a := range i.Args { - k := a.FullKey() - v, ok := vs[k] - if !ok { - return "", fmt.Errorf( - "%s: variable argument value unknown: %s", - i.FullString(), - k) + v, err := a.Interpolate(vs) + if err != nil { + return "", err } args[idx] = v @@ -171,21 +101,14 @@ func (i *FunctionInterpolation) Interpolate( func (i *FunctionInterpolation) Variables() map[string]InterpolatedVariable { result := make(map[string]InterpolatedVariable) for _, a := range i.Args { - k := a.FullKey() - if _, ok := result[k]; ok { - continue + for k, v := range a.Variables() { + result[k] = v } - - result[k] = a } return result } -func (i *LiteralInterpolation) FullString() string { - return i.key -} - func (i *LiteralInterpolation) Interpolate( map[string]string) (string, error) { return i.Literal, nil @@ -195,24 +118,20 @@ func (i *LiteralInterpolation) Variables() map[string]InterpolatedVariable { return nil } -func (i *VariableInterpolation) FullString() string { - return i.key -} - func (i *VariableInterpolation) Interpolate( vs map[string]string) (string, error) { - v, ok := vs[i.key] + v, ok := vs[i.Variable.FullKey()] if !ok { return "", fmt.Errorf( "%s: value for variable not found", - i.key) + i.Variable.FullKey()) } return v, nil } func (i *VariableInterpolation) Variables() map[string]InterpolatedVariable { - return map[string]InterpolatedVariable{i.key: i.Variable} + return map[string]InterpolatedVariable{i.Variable.FullKey(): i.Variable} } func NewResourceVariable(key string) (*ResourceVariable, error) { diff --git a/config/interpolate_test.go b/config/interpolate_test.go index 20d72f18a..77188081e 100644 --- a/config/interpolate_test.go +++ b/config/interpolate_test.go @@ -6,6 +6,7 @@ import ( "testing" ) +/* func TestNewInterpolation(t *testing.T) { cases := []struct { Input string @@ -67,6 +68,7 @@ func TestNewInterpolation(t *testing.T) { } } } +*/ func TestNewInterpolatedVariable(t *testing.T) { cases := []struct { @@ -171,11 +173,10 @@ func TestFunctionInterpolation(t *testing.T) { i := &FunctionInterpolation{ Func: fn, - Args: []InterpolatedVariable{v1, v2}, - key: "foo", - } - if i.FullString() != "foo" { - t.Fatalf("err: %#v", i) + Args: []Interpolation{ + &VariableInterpolation{Variable: v1}, + &VariableInterpolation{Variable: v2}, + }, } expected := map[string]InterpolatedVariable{ @@ -206,10 +207,6 @@ func TestLiteralInterpolation_impl(t *testing.T) { func TestLiteralInterpolation(t *testing.T) { i := &LiteralInterpolation{ Literal: "bar", - key: "foo", - } - if i.FullString() != "foo" { - t.Fatalf("err: %#v", i) } if i.Variables() != nil { @@ -292,10 +289,7 @@ func TestVariableInterpolation(t *testing.T) { t.Fatalf("err: %s", err) } - i := &VariableInterpolation{Variable: uv, key: "var.foo"} - if i.FullString() != "var.foo" { - t.Fatalf("err: %#v", i) - } + i := &VariableInterpolation{Variable: uv} expected := map[string]InterpolatedVariable{"var.foo": uv} if !reflect.DeepEqual(i.Variables(), expected) { @@ -320,7 +314,7 @@ func TestVariableInterpolation_missing(t *testing.T) { t.Fatalf("err: %s", err) } - i := &VariableInterpolation{Variable: uv, key: "var.foo"} + i := &VariableInterpolation{Variable: uv} _, err = i.Interpolate(map[string]string{ "var.bar": "bar", }) diff --git a/config/interpolate_walk.go b/config/interpolate_walk.go index fd8a70833..83fa6c222 100644 --- a/config/interpolate_walk.go +++ b/config/interpolate_walk.go @@ -95,7 +95,7 @@ func (w *interpolationWalker) Primitive(v reflect.Value) error { // Interpolation found, instantiate it key := match[2] - i, err := NewInterpolation(key) + i, err := ExprParse(key) if err != nil { return err } diff --git a/config/interpolate_walk_test.go b/config/interpolate_walk_test.go index a731f8615..c27770e59 100644 --- a/config/interpolate_walk_test.go +++ b/config/interpolate_walk_test.go @@ -29,7 +29,6 @@ func TestInterpolationWalker_detect(t *testing.T) { Name: "foo", key: "var.foo", }, - key: "var.foo", }, }, }, @@ -41,13 +40,14 @@ func TestInterpolationWalker_detect(t *testing.T) { Result: []Interpolation{ &FunctionInterpolation{ Func: nil, - Args: []InterpolatedVariable{ - &UserVariable{ - Name: "foo", - key: "var.foo", + Args: []Interpolation{ + &VariableInterpolation{ + Variable: &UserVariable{ + Name: "foo", + key: "var.foo", + }, }, }, - key: "lookup(var.foo)", }, }, }, From de1c23617a2965088d12a6684fcb5a626c85f33c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 22 Jul 2014 16:02:09 -0700 Subject: [PATCH 5/8] config: don't panic on parse error of variables --- config/expr.y | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/expr.y b/config/expr.y index 727e0ef59..27903f714 100644 --- a/config/expr.y +++ b/config/expr.y @@ -83,7 +83,8 @@ variable: var err error $$, err = NewInterpolatedVariable($1) if err != nil { - panic(err) + exprErrors = append(exprErrors, fmt.Errorf( + "Error parsing variable '%s': %s", $1, err)) } } From 7dfd4f5a3c887d9444303d5183ffefafd187d4e6 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 22 Jul 2014 16:08:19 -0700 Subject: [PATCH 6/8] config: tests for the parser --- config/expr_parse_test.go | 70 ++++++++++++++++++++++++++++++++++++++ config/expr_test.go | 9 ----- config/interpolate_test.go | 64 ---------------------------------- 3 files changed, 70 insertions(+), 73 deletions(-) create mode 100644 config/expr_parse_test.go delete mode 100644 config/expr_test.go diff --git a/config/expr_parse_test.go b/config/expr_parse_test.go new file mode 100644 index 000000000..41697d8b6 --- /dev/null +++ b/config/expr_parse_test.go @@ -0,0 +1,70 @@ +package config + +import ( + "reflect" + "testing" +) + +func TestExprParse(t *testing.T) { + cases := []struct { + Input string + Result Interpolation + Error bool + }{ + { + "foo", + nil, + true, + }, + + { + "var.foo", + &VariableInterpolation{ + Variable: &UserVariable{ + Name: "foo", + key: "var.foo", + }, + }, + false, + }, + + { + "lookup(var.foo, var.bar)", + &FunctionInterpolation{ + Func: nil, // Funcs["lookup"] + Args: []Interpolation{ + &VariableInterpolation{ + Variable: &UserVariable{ + Name: "foo", + key: "var.foo", + }, + }, + &VariableInterpolation{ + Variable: &UserVariable{ + Name: "bar", + key: "var.bar", + }, + }, + }, + }, + false, + }, + } + + for i, tc := range cases { + actual, err := ExprParse(tc.Input) + if (err != nil) != tc.Error { + t.Fatalf("%d. Error: %s", i, err) + } + + // This is jank, but reflect.DeepEqual never has functions + // being the same. + if f, ok := actual.(*FunctionInterpolation); ok { + f.Func = nil + } + + if !reflect.DeepEqual(actual, tc.Result) { + t.Fatalf("%d bad: %#v", i, actual) + } + } +} diff --git a/config/expr_test.go b/config/expr_test.go deleted file mode 100644 index 68a6b5fd4..000000000 --- a/config/expr_test.go +++ /dev/null @@ -1,9 +0,0 @@ -package config - -import ( - "testing" -) - -func TestExprParse(t *testing.T) { - exprParse(&exprLex{input: `lookup(var.foo)`}) -} diff --git a/config/interpolate_test.go b/config/interpolate_test.go index 77188081e..92bb0f15c 100644 --- a/config/interpolate_test.go +++ b/config/interpolate_test.go @@ -6,70 +6,6 @@ import ( "testing" ) -/* -func TestNewInterpolation(t *testing.T) { - cases := []struct { - Input string - Result Interpolation - Error bool - }{ - { - "foo", - nil, - true, - }, - - { - "var.foo", - &VariableInterpolation{ - Variable: &UserVariable{ - Name: "foo", - key: "var.foo", - }, - key: "var.foo", - }, - false, - }, - - { - "lookup(var.foo, var.bar)", - &FunctionInterpolation{ - Func: nil, // Funcs["lookup"] - Args: []InterpolatedVariable{ - &UserVariable{ - Name: "foo", - key: "var.foo", - }, - &UserVariable{ - Name: "bar", - key: "var.bar", - }, - }, - key: "lookup(var.foo, var.bar)", - }, - false, - }, - } - - for i, tc := range cases { - actual, err := NewInterpolation(tc.Input) - if (err != nil) != tc.Error { - t.Fatalf("%d. Error: %s", i, err) - } - - // This is jank, but reflect.DeepEqual never has functions - // being the same. - if f, ok := actual.(*FunctionInterpolation); ok { - f.Func = nil - } - - if !reflect.DeepEqual(actual, tc.Result) { - t.Fatalf("%d bad: %#v", i, actual) - } - } -} -*/ - func TestNewInterpolatedVariable(t *testing.T) { cases := []struct { Input string From bff5c091647fae3b022b3aacccc20f3d056aa492 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 22 Jul 2014 16:16:46 -0700 Subject: [PATCH 7/8] config: test nested function calls --- config/expr_parse_test.go | 51 +++++++++++++++++++++++++++++++++++++-- config/interpolate.go | 8 ++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/config/expr_parse_test.go b/config/expr_parse_test.go index 41697d8b6..d11519102 100644 --- a/config/expr_parse_test.go +++ b/config/expr_parse_test.go @@ -49,6 +49,39 @@ func TestExprParse(t *testing.T) { }, false, }, + + { + "lookup(var.foo, lookup(var.baz, var.bar))", + &FunctionInterpolation{ + Func: nil, // Funcs["lookup"] + Args: []Interpolation{ + &VariableInterpolation{ + Variable: &UserVariable{ + Name: "foo", + key: "var.foo", + }, + }, + &FunctionInterpolation{ + Func: nil, // Funcs["lookup"] + Args: []Interpolation{ + &VariableInterpolation{ + Variable: &UserVariable{ + Name: "baz", + key: "var.baz", + }, + }, + &VariableInterpolation{ + Variable: &UserVariable{ + Name: "bar", + key: "var.bar", + }, + }, + }, + }, + }, + }, + false, + }, } for i, tc := range cases { @@ -59,8 +92,22 @@ func TestExprParse(t *testing.T) { // This is jank, but reflect.DeepEqual never has functions // being the same. - if f, ok := actual.(*FunctionInterpolation); ok { - f.Func = nil + f, ok := actual.(*FunctionInterpolation) + if ok { + fs := make([]*FunctionInterpolation, 1) + fs[0] = f + for len(fs) > 0 { + f := fs[0] + fs = fs[1:] + + f.Func = nil + for _, a := range f.Args { + f, ok := a.(*FunctionInterpolation) + if ok { + fs = append(fs, f) + } + } + } } if !reflect.DeepEqual(actual, tc.Result) { diff --git a/config/interpolate.go b/config/interpolate.go index 5250627b4..cffd58b17 100644 --- a/config/interpolate.go +++ b/config/interpolate.go @@ -98,6 +98,10 @@ func (i *FunctionInterpolation) Interpolate( return i.Func(vs, args...) } +func (i *FunctionInterpolation) GoString() string { + return fmt.Sprintf("*%#v", *i) +} + func (i *FunctionInterpolation) Variables() map[string]InterpolatedVariable { result := make(map[string]InterpolatedVariable) for _, a := range i.Args { @@ -130,6 +134,10 @@ func (i *VariableInterpolation) Interpolate( return v, nil } +func (i *VariableInterpolation) GoString() string { + return fmt.Sprintf("*%#v", *i) +} + func (i *VariableInterpolation) Variables() map[string]InterpolatedVariable { return map[string]InterpolatedVariable{i.Variable.FullKey(): i.Variable} } From 1e2de8f91cf4780fceb0a29298b693e81e65d82f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 22 Jul 2014 16:18:05 -0700 Subject: [PATCH 8/8] config: more tests --- config/expr_parse_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/config/expr_parse_test.go b/config/expr_parse_test.go index d11519102..da6ab0e43 100644 --- a/config/expr_parse_test.go +++ b/config/expr_parse_test.go @@ -17,6 +17,12 @@ func TestExprParse(t *testing.T) { true, }, + { + `"foo"`, + &LiteralInterpolation{Literal: "foo"}, + false, + }, + { "var.foo", &VariableInterpolation{