// Copyright 2016-2021, Pulumi Corporation. All rights reserved. package graph import ( "testing" "github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers" "github.com/pulumi/pulumi/sdk/v3/go/common/resource" "github.com/pulumi/pulumi/sdk/v3/go/common/tokens" "github.com/stretchr/testify/assert" ) func NewProviderResource(pkg, name, id string, deps ...resource.URN) *resource.State { t := providers.MakeProviderType(tokens.Package(pkg)) return &resource.State{ Type: t, URN: resource.NewURN("test", "test", "", t, name), ID: resource.ID(id), Inputs: resource.PropertyMap{}, Outputs: resource.PropertyMap{}, Dependencies: deps, } } func NewResource(name string, provider *resource.State, deps ...resource.URN) *resource.State { prov := "" if provider != nil { p, err := providers.NewReference(provider.URN, provider.ID) if err != nil { panic(err) } prov = p.String() } t := tokens.Type("test:test:test") return &resource.State{ Type: t, URN: resource.NewURN("test", "test", "", t, name), Inputs: resource.PropertyMap{}, Outputs: resource.PropertyMap{}, Dependencies: deps, Provider: prov, } } func TestBasicGraph(t *testing.T) { t.Parallel() pA := NewProviderResource("test", "pA", "0") a := NewResource("a", pA) b := NewResource("b", pA, a.URN) pB := NewProviderResource("test", "pB", "1", a.URN, b.URN) c := NewResource("c", pB, a.URN) d := NewResource("d", nil, b.URN) dg := NewDependencyGraph([]*resource.State{ pA, a, b, pB, c, d, }) assert.Equal(t, []*resource.State{ a, b, pB, c, d, }, dg.DependingOn(pA, nil, false)) assert.Equal(t, []*resource.State{ b, pB, c, d, }, dg.DependingOn(a, nil, false)) assert.Equal(t, []*resource.State{ pB, c, d, }, dg.DependingOn(b, nil, false)) assert.Equal(t, []*resource.State{ c, }, dg.DependingOn(pB, nil, false)) assert.Nil(t, dg.DependingOn(c, nil, false)) assert.Nil(t, dg.DependingOn(d, nil, false)) assert.Nil(t, dg.DependingOn(pA, map[resource.URN]bool{ a.URN: true, b.URN: true, }, false)) assert.Equal(t, []*resource.State{ a, pB, c, }, dg.DependingOn(pA, map[resource.URN]bool{ b.URN: true, }, false)) assert.Equal(t, []*resource.State{ b, pB, c, d, }, dg.DependingOn(pA, map[resource.URN]bool{ a.URN: true, }, false)) assert.Equal(t, []*resource.State{ c, }, dg.DependingOn(a, map[resource.URN]bool{ b.URN: true, pB.URN: true, }, false)) assert.Equal(t, []*resource.State{ pB, c, }, dg.DependingOn(a, map[resource.URN]bool{ b.URN: true, }, false)) assert.Equal(t, []*resource.State{ d, }, dg.DependingOn(b, map[resource.URN]bool{ pB.URN: true, }, false)) } // Tests that we don't add the same node to the DependingOn set twice. func TestGraphNoDuplicates(t *testing.T) { t.Parallel() a := NewResource("a", nil) b := NewResource("b", nil, a.URN) c := NewResource("c", nil, a.URN) d := NewResource("d", nil, b.URN, c.URN) dg := NewDependencyGraph([]*resource.State{ a, b, c, d, }) assert.Equal(t, []*resource.State{ b, c, d, }, dg.DependingOn(a, nil, false)) } func TestDependenciesOf(t *testing.T) { t.Parallel() pA := NewProviderResource("test", "pA", "0") a := NewResource("a", pA) b := NewResource("b", pA, a.URN) c := NewResource("c", pA) d := NewResource("d", pA) d.Parent = a.URN dg := NewDependencyGraph([]*resource.State{ pA, a, b, c, d, }) aDepends := dg.DependenciesOf(a) assert.True(t, aDepends.Contains(pA)) assert.False(t, aDepends.Contains(a)) assert.False(t, aDepends.Contains(b)) bDepends := dg.DependenciesOf(b) assert.True(t, bDepends.Contains(pA)) assert.True(t, bDepends.Contains(a)) assert.False(t, bDepends.Contains(b)) cDepends := dg.DependenciesOf(c) assert.True(t, cDepends.Contains(pA)) assert.False(t, cDepends.Contains(a)) assert.False(t, cDepends.Contains(b)) dDepends := dg.DependenciesOf(d) assert.True(t, dDepends.Contains(pA)) assert.True(t, dDepends.Contains(a)) // due to A being the parent of D assert.False(t, dDepends.Contains(b)) assert.False(t, dDepends.Contains(c)) } func TestDependenciesOfRemoteComponents(t *testing.T) { t.Parallel() aws := NewProviderResource("aws", "default", "0") xyz := NewProviderResource("xyz", "default", "0") first := NewResource("first", xyz) firstNested := NewResource("firstNested", xyz) firstNested.Parent = first.URN sg := NewResource("sg", aws) sg.Parent = firstNested.URN second := NewResource("second", xyz) rule := NewResource("rule", aws, first.URN) rule.Parent = second.URN dg := NewDependencyGraph([]*resource.State{ aws, xyz, first, firstNested, sg, second, rule, }) ruleDepends := dg.DependenciesOf(rule) assert.True(t, ruleDepends.Contains(first), "direct dependency") assert.True(t, ruleDepends.Contains(firstNested), "child of dependency") assert.True(t, ruleDepends.Contains(sg), "transitive child of dependency") assert.True(t, ruleDepends.Contains(second), "parent") assert.True(t, ruleDepends.Contains(aws), "provider") assert.False(t, ruleDepends.Contains(xyz), "unrelated") } func TestDependenciesOfRemoteComponentsNoCycle(t *testing.T) { t.Parallel() aws := NewProviderResource("aws", "default", "0") parent := NewResource("parent", aws) r := NewResource("r", aws, parent.URN) child := NewResource("child", aws, r.URN) child.Parent = parent.URN dg := NewDependencyGraph([]*resource.State{ aws, parent, r, child, }) childDependencies := dg.DependenciesOf(child) assert.True(t, childDependencies.Contains(aws)) assert.True(t, childDependencies.Contains(parent)) assert.True(t, childDependencies.Contains(r)) rDependencies := dg.DependenciesOf(r) assert.True(t, rDependencies.Contains(aws)) assert.True(t, rDependencies.Contains(parent)) assert.False(t, rDependencies.Contains(child)) } func TestTransitiveDependenciesOf(t *testing.T) { t.Parallel() aws := NewProviderResource("aws", "default", "0") parent := NewResource("parent", aws) greatUncle := NewResource("greatUncle", aws) uncle := NewResource("r", aws) uncle.Parent = greatUncle.URN child := NewResource("child", aws, uncle.URN) child.Parent = parent.URN baby := NewResource("baby", aws) baby.Parent = child.URN dg := NewDependencyGraph([]*resource.State{ aws, parent, greatUncle, uncle, child, baby, }) // <(relation)- as an alias for depends on via relation // baby <(Parent)- child <(Dependency)- uncle <(Parent)- greatUncle <(Provider)- aws set := dg.TransitiveDependenciesOf(baby) assert.True(t, set.Contains(aws), "everything should depend on the provider") assert.True(t, set.Contains(greatUncle), "child depends on greatUncle") } func TestOnlyDependsOn(t *testing.T) { t.Parallel() aws0 := NewProviderResource("aws", "default", "0") aws1 := NewProviderResource("aws", "default", "1") aws2 := NewProviderResource("aws", "default", "2") b0 := NewResource("b", aws0) b1 := NewResource("b", aws1) c0 := NewResource("c0", aws2, b0.URN) dg := NewDependencyGraph([]*resource.State{ aws0, aws1, aws2, b0, b1, c0, }) assert.Equal(t, []*resource.State{b1}, dg.OnlyDependsOn(aws1)) assert.Equal(t, []*resource.State{c0}, dg.OnlyDependsOn(aws2)) assert.Equal(t, []*resource.State(nil), dg.OnlyDependsOn(b0)) } func TestOnlyDependsOnMultipleProviders(t *testing.T) { t.Parallel() aws0 := NewProviderResource("aws", "default", "0") aws1 := NewProviderResource("aws", "default", "1") b0 := NewResource("b", aws0) c0 := NewProviderResource("aws", "non-default", "0", b0.URN) c1 := NewProviderResource("aws", "non-default", "1") d0 := NewResource("d", c0) d1 := NewResource("d", c1) dg := NewDependencyGraph([]*resource.State{ aws0, aws1, b0, c0, c1, d0, d1, }) assert.Equal(t, []*resource.State{b0, c0, d0}, dg.OnlyDependsOn(aws0)) assert.Equal(t, []*resource.State(nil), dg.OnlyDependsOn(aws1)) assert.Equal(t, []*resource.State{c0, d0}, dg.OnlyDependsOn(b0)) assert.Equal(t, []*resource.State{d1}, dg.OnlyDependsOn(c1)) }