helper/resource: Allow multiple providers in a single TestCase

Due to incorrect use of a loop iterator variable inside a closure, all of
the given providers were ending up with the same factory function.
Now we copy the factory function to a local within the loop first so that
each iteration has its own variable.

This is the second round of similar bugs in this function, so we'll also
add a test case for it to reduce the risk of future regressions given that
most real callers don't exercise this with multiple providers in practice.
This commit is contained in:
Martin Atkins 2019-01-07 15:29:51 -08:00
parent d0e6a4c69a
commit cdad78d69b
2 changed files with 78 additions and 4 deletions

View File

@ -635,9 +635,9 @@ func testProviderConfig(c TestCase) string {
// Any errors are stored so that they can be returned by the factory in // Any errors are stored so that they can be returned by the factory in
// terraform to match non-test behavior. // terraform to match non-test behavior.
func testProviderResolver(c TestCase) (providers.Resolver, error) { func testProviderResolver(c TestCase) (providers.Resolver, error) {
ctxProviders := c.ProviderFactories ctxProviders := make(map[string]terraform.ResourceProviderFactory)
if ctxProviders == nil { for k, pf := range c.ProviderFactories {
ctxProviders = make(map[string]terraform.ResourceProviderFactory) ctxProviders[k] = pf
} }
// add any fixed providers // add any fixed providers
@ -650,8 +650,9 @@ func testProviderResolver(c TestCase) (providers.Resolver, error) {
newProviders := make(map[string]providers.Factory) newProviders := make(map[string]providers.Factory)
for k, pf := range ctxProviders { for k, pf := range ctxProviders {
factory := pf // must copy to ensure each closure sees its own value
newProviders[k] = func() (providers.Interface, error) { newProviders[k] = func() (providers.Interface, error) {
p, err := pf() p, err := factory()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -14,6 +14,8 @@ import (
"testing" "testing"
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/plugin/discovery"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
@ -1033,6 +1035,77 @@ func TestTest_Taint(t *testing.T) {
} }
} }
func TestTestProviderResolver(t *testing.T) {
stubProvider := func(name string) terraform.ResourceProvider {
return &schema.Provider{
Schema: map[string]*schema.Schema{
name: &schema.Schema{
Type: schema.TypeString,
Required: true,
},
},
}
}
c := TestCase{
ProviderFactories: map[string]terraform.ResourceProviderFactory{
"foo": terraform.ResourceProviderFactoryFixed(stubProvider("foo")),
"bar": terraform.ResourceProviderFactoryFixed(stubProvider("bar")),
},
Providers: map[string]terraform.ResourceProvider{
"baz": stubProvider("baz"),
"bop": stubProvider("bop"),
},
}
resolver, err := testProviderResolver(c)
if err != nil {
t.Fatal(err)
}
reqd := discovery.PluginRequirements{
"foo": &discovery.PluginConstraints{},
"bar": &discovery.PluginConstraints{},
"baz": &discovery.PluginConstraints{},
"bop": &discovery.PluginConstraints{},
}
factories, errs := resolver.ResolveProviders(reqd)
if len(errs) != 0 {
for _, err := range errs {
t.Error(err)
}
t.Fatal("unexpected errors")
}
for name := range reqd {
t.Run(name, func(t *testing.T) {
pf, ok := factories[name]
if !ok {
t.Fatalf("no factory for %q", name)
}
p, err := pf()
if err != nil {
t.Fatal(err)
}
resp := p.GetSchema()
_, ok = resp.Provider.Block.Attributes[name]
if !ok {
var has string
for k := range resp.Provider.Block.Attributes {
has = k
break
}
if has != "" {
t.Errorf("provider %q does not have the expected schema attribute %q (but has %q)", name, name, has)
} else {
t.Errorf("provider %q does not have the expected schema attribute %q", name, name)
}
}
})
}
}
const testConfigStr = ` const testConfigStr = `
resource "test_instance" "foo" {} resource "test_instance" "foo" {}
` `