diff --git a/testharness/tester.go b/testharness/tester.go index 9e728d3207..de40c20f86 100644 --- a/testharness/tester.go +++ b/testharness/tester.go @@ -1,6 +1,7 @@ package testharness import ( + "github.com/hashicorp/hcl2/hcl" "github.com/hashicorp/terraform/tfdiags" lua "github.com/yuin/gopher-lua" ) @@ -83,6 +84,7 @@ func (t *describe) test(subject *Subject, cs CheckStream) { return } +Contexts: for _, childContext := range childContexts { L := childContext.lstate var diags tfdiags.Diagnostics @@ -126,15 +128,38 @@ func (t *describe) test(subject *Subject, cs CheckStream) { continue } - if testersB.Skip { - // testersB.Skip is set if there's a call to require() in - // the body and the given condition didn't hold. - cs.Write(CheckItem{ - Result: Skipped, - Caption: childContext.Name(), - Diags: diags, - }) - continue + for _, requirement := range testersB.Requirements { + switch requirement.Result() { + case Skipped: + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Requirement created without assertion", + Detail: "An assertion method must be called on the result of each \"require\" call.", + Subject: requirement.defRange.ToHCL().Ptr(), + }) + cs.Write(CheckItem{ + Result: Error, + Caption: childContext.Name(), + Diags: diags, + }) + continue Contexts + case Error: + diags = diags.Append(requirement.diags) + cs.Write(CheckItem{ + Result: Error, + Caption: childContext.Name(), + Diags: diags, + }) + continue Contexts + case Failure: + // TODO: Do something with the detail message from the requirement, if any. + cs.Write(CheckItem{ + Result: Skipped, + Caption: childContext.Name(), + Diags: diags, + }) + continue Contexts + } } for _, tester := range testersB.Testers { diff --git a/testharness/testers_builder.go b/testharness/testers_builder.go index 5df8180cc6..e06da5d849 100644 --- a/testharness/testers_builder.go +++ b/testharness/testers_builder.go @@ -16,13 +16,15 @@ type testersBuilder struct { Context *Context Testers Testers Diags *Diagnostics - Skip bool + + Requirements []*expect } func (b *testersBuilder) luaTesterDecls(L *lua.LState) map[lua.LString]lua.LValue { return map[lua.LString]lua.LValue{ "describe": L.NewFunction(b.luaDescribeFunc), "it": L.NewFunction(b.luaItFunc), + "require": L.NewFunction(b.luaRequireFunc), } } @@ -124,6 +126,38 @@ func (b *testersBuilder) luaItFunc(L *lua.LState) int { return 0 } +func (b *testersBuilder) luaRequireFunc(L *lua.LState) int { + defRangeHCL := callingRange(L, 1) + var defRange tfdiags.SourceRange + if defRangeHCL != nil { + defRange = tfdiags.SourceRangeFromHCL(*defRangeHCL) + } + + if L.GetTop() != 1 { + b.Diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid \"require\" call", + Detail: "A \"require\" call must have one argument: the given value to make an assertion about.", + Subject: defRangeHCL, + }) + + // We'll still return a requirement object just so the subsequent + // method call doesn't _also_ fail here. It'll never actually + // be evaluated (since it's not in the requirements list) so doesn't + // really matter what value we set it to. + stubRequirement := newExpect(b.Context.lstate, lua.LNil, defRange) + L.Push(stubRequirement.LuaObject()) + return 1 + } + + given := L.CheckAny(1) + + requirement := newExpect(b.Context.lstate, given, defRange) + b.Requirements = append(b.Requirements, requirement) + L.Push(requirement.LuaObject()) + return 1 +} + func (b *testersBuilder) luaContextSetters(L *lua.LState) map[lua.LString]lua.LValue { return map[lua.LString]lua.LValue{ "resource": b.luaResourceObj(L),