From a4bbf16f8380243a7233d73176bc8989dee5457d Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 27 Oct 2017 07:49:50 -0700 Subject: [PATCH] testharness: "resource" context constructor The result of calling this can be passed into the first argument of "describe" to create a description with a resource as context. In child blocks it can also be used to access the attributes of a resource selected by a parent block. --- testharness/context_setters.go | 2 +- testharness/loader.go | 4 +- testharness/loader_test.go | 15 +++++- .../test-fixtures/basic-specs/b.tfspec | 4 ++ testharness/testers_builder.go | 53 +++++++++++++++++++ 5 files changed, 75 insertions(+), 3 deletions(-) diff --git a/testharness/context_setters.go b/testharness/context_setters.go index ff8b26ac9c..9f3fbc5e78 100644 --- a/testharness/context_setters.go +++ b/testharness/context_setters.go @@ -42,7 +42,7 @@ type resourceContextSetter struct { DefRange tfdiags.SourceRange } -func (s resourceContextSetter) Context(parent *Context, subject *Subject) (*Context, tfdiags.Diagnostics) { +func (s *resourceContextSetter) Context(parent *Context, subject *Subject) (*Context, tfdiags.Diagnostics) { // TODO: Set the resource object too return parent.WithNameSuffix(s.Addr.String()), nil } diff --git a/testharness/loader.go b/testharness/loader.go index a4a1761327..c7f39a4ad7 100644 --- a/testharness/loader.go +++ b/testharness/loader.go @@ -140,10 +140,12 @@ func loadSpec(r io.Reader, filename string, L *lua.LState) (*Spec, tfdiags.Diagn Diags: builderDiags, } testersB := testersBuilder{ - Diags: builderDiags, + Context: RootContext, + Diags: builderDiags, } topEnv.RawSet(lua.LString("scenario"), L.NewFunction(scenariosB.luaScenarioFunc)) topEnv.RawSet(lua.LString("describe"), L.NewFunction(testersB.luaDescribeFunc)) + topEnv.RawSet(lua.LString("resource"), testersB.luaResourceObj(L)) L.Push(fn) err = L.PCall(0, 0, nil) diff --git a/testharness/loader_test.go b/testharness/loader_test.go index 87ef6befc1..5c42f77524 100644 --- a/testharness/loader_test.go +++ b/testharness/loader_test.go @@ -61,7 +61,7 @@ func TestLoadSpecDir(t *testing.T) { } { - if got, want := len(spec.testers), 1; got != want { + if got, want := len(spec.testers), 2; got != want { t.Fatalf("wrong number of testers %d; want %d", got, want) } @@ -77,5 +77,18 @@ func TestLoadSpecDir(t *testing.T) { } else { t.Errorf("spec.testers[0] is %T; want *describe", spec.testers[0]) } + + resourceTester := spec.testers[1] + if resourceTester, isResource := resourceTester.(*describe); isResource { + if resourceCtxSet, isResource := resourceTester.Described.(*resourceContextSetter); isResource { + if got, want := resourceCtxSet.Addr.String(), "test.foo"; got != want { + t.Errorf("spec.testers[1] has wrong resource address %q; want %q", got, want) + } + } else { + t.Errorf("spec.testers[1].Described is %T; want resourceContextSetter", resourceTester.Described) + } + } else { + t.Errorf("spec.testers[1] is %T; want *describe", spec.testers[0]) + } } } diff --git a/testharness/test-fixtures/basic-specs/b.tfspec b/testharness/test-fixtures/basic-specs/b.tfspec index 68b87ca14d..e6c14c07a6 100644 --- a/testharness/test-fixtures/basic-specs/b.tfspec +++ b/testharness/test-fixtures/basic-specs/b.tfspec @@ -6,3 +6,7 @@ end) describe("foo", function () end) + +describe(resource("test.foo"), function () + +end) diff --git a/testharness/testers_builder.go b/testharness/testers_builder.go index 37c9ef1dc2..32360344e2 100644 --- a/testharness/testers_builder.go +++ b/testharness/testers_builder.go @@ -1,14 +1,19 @@ package testharness import ( + "fmt" + "github.com/hashicorp/hcl2/hcl" + "github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/tfdiags" lua "github.com/yuin/gopher-lua" + "github.com/zclconf/gopherlua-cty/luacty" ) // testersBuilder provides functions for constructing a sequence of testers // from within a Lua test spec. type testersBuilder struct { + Context *Context Testers Testers Diags *Diagnostics } @@ -71,3 +76,51 @@ func (b *testersBuilder) luaDescribeFunc(L *lua.LState) int { return 0 } + +func (b *testersBuilder) luaResourceObj(L *lua.LState) lua.LValue { + ret := L.NewUserData() + meta := L.NewTable() + + conv := luacty.NewConverter(L) + + if b.Context.HasResource() { + meta.RawSet(lua.LString("__index"), conv.WrapCtyValue(b.Context.Resource())) + meta.RawSet(lua.LString("__call"), L.NewFunction(func(L *lua.LState) int { + L.Error(lua.LString("a resource is already being described by a parent block"), 2) + return 0 + })) + } else { + meta.RawSet(lua.LString("__index"), L.NewFunction(func(L *lua.LState) int { + L.Error(lua.LString("no resource is being described by the current block"), 2) + return 0 + })) + meta.RawSet(lua.LString("__call"), L.NewFunction(func(L *lua.LState) int { + addrS := L.CheckString(2) + addr, err := terraform.ParseResourceAddress(addrS) + if err != nil { + L.Error(lua.LString(fmt.Sprintf("invalid resource address: %s", err)), 2) + return 0 + } + if addr.Name == "" { + L.Error(lua.LString("invalid resource address: must refer to specific resource"), 2) + return 0 + } + + ctxSet := &resourceContextSetter{ + Addr: addr, + } + if rng := callingRange(L, 1); rng != nil { + ctxSet.DefRange = tfdiags.SourceRangeFromHCL(*rng) + } + + ret := L.NewUserData() + ret.Value = ctxSet + L.Push(ret) + return 1 + })) + } + + ret.Metatable = meta + + return ret +}