github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/engine/lifecycletest/target_test.go (about)

     1  package lifecycletest
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/blang/semver"
     8  	combinations "github.com/mxschmitt/golang-combinations"
     9  	"github.com/stretchr/testify/assert"
    10  
    11  	. "github.com/pulumi/pulumi/pkg/v3/engine"
    12  	"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
    13  	"github.com/pulumi/pulumi/pkg/v3/resource/deploy/deploytest"
    14  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
    15  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
    16  	"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
    17  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/result"
    18  	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
    19  )
    20  
    21  func TestDestroyTarget(t *testing.T) {
    22  	t.Parallel()
    23  
    24  	// Try refreshing a stack with combinations of the above resources as target to destroy.
    25  	subsets := combinations.All(complexTestDependencyGraphNames)
    26  
    27  	//nolint:paralleltest // false positive because range var isn't used directly in t.Run(name) arg
    28  	for _, subset := range subsets {
    29  		subset := subset
    30  		// limit to up to 3 resources to destroy.  This keeps the test running time under
    31  		// control as it only generates a few hundred combinations instead of several thousand.
    32  		if len(subset) <= 3 {
    33  			t.Run(fmt.Sprintf("%v", subset), func(t *testing.T) {
    34  				t.Parallel()
    35  
    36  				destroySpecificTargets(t, subset, true, /*targetDependents*/
    37  					func(urns []resource.URN, deleted map[resource.URN]bool) {})
    38  			})
    39  		}
    40  	}
    41  
    42  	t.Run("destroy root", func(t *testing.T) {
    43  		t.Parallel()
    44  
    45  		destroySpecificTargets(
    46  			t, []string{"A"}, true, /*targetDependents*/
    47  			func(urns []resource.URN, deleted map[resource.URN]bool) {
    48  				// when deleting 'A' we expect A, B, C, D, E, F, G, H, I, J, K, and L to be deleted
    49  				names := complexTestDependencyGraphNames
    50  				assert.Equal(t, map[resource.URN]bool{
    51  					pickURN(t, urns, names, "A"): true,
    52  					pickURN(t, urns, names, "B"): true,
    53  					pickURN(t, urns, names, "C"): true,
    54  					pickURN(t, urns, names, "D"): true,
    55  					pickURN(t, urns, names, "E"): true,
    56  					pickURN(t, urns, names, "F"): true,
    57  					pickURN(t, urns, names, "G"): true,
    58  					pickURN(t, urns, names, "H"): true,
    59  					pickURN(t, urns, names, "I"): true,
    60  					pickURN(t, urns, names, "J"): true,
    61  					pickURN(t, urns, names, "K"): true,
    62  					pickURN(t, urns, names, "L"): true,
    63  				}, deleted)
    64  			})
    65  	})
    66  
    67  	destroySpecificTargets(
    68  		t, []string{"A"}, false, /*targetDependents*/
    69  		func(urns []resource.URN, deleted map[resource.URN]bool) {})
    70  }
    71  
    72  func destroySpecificTargets(
    73  	t *testing.T, targets []string, targetDependents bool,
    74  	validate func(urns []resource.URN, deleted map[resource.URN]bool)) {
    75  
    76  	//             A
    77  	//    _________|_________
    78  	//    B        C        D
    79  	//          ___|___  ___|___
    80  	//          E  F  G  H  I  J
    81  	//             |__|
    82  	//             K  L
    83  
    84  	p := &TestPlan{}
    85  
    86  	urns, old, program := generateComplexTestDependencyGraph(t, p)
    87  
    88  	loaders := []*deploytest.ProviderLoader{
    89  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
    90  			return &deploytest.Provider{
    91  				DiffConfigF: func(urn resource.URN, olds, news resource.PropertyMap,
    92  					ignoreChanges []string) (plugin.DiffResult, error) {
    93  					if !olds["A"].DeepEquals(news["A"]) {
    94  						return plugin.DiffResult{
    95  							ReplaceKeys:         []resource.PropertyKey{"A"},
    96  							DeleteBeforeReplace: true,
    97  						}, nil
    98  					}
    99  					return plugin.DiffResult{}, nil
   100  				},
   101  				DiffF: func(urn resource.URN, id resource.ID,
   102  					olds, news resource.PropertyMap, ignoreChanges []string) (plugin.DiffResult, error) {
   103  
   104  					if !olds["A"].DeepEquals(news["A"]) {
   105  						return plugin.DiffResult{ReplaceKeys: []resource.PropertyKey{"A"}}, nil
   106  					}
   107  					return plugin.DiffResult{}, nil
   108  				},
   109  			}, nil
   110  		}),
   111  	}
   112  
   113  	p.Options.Host = deploytest.NewPluginHost(nil, nil, program, loaders...)
   114  	p.Options.TargetDependents = targetDependents
   115  
   116  	destroyTargets := []resource.URN{}
   117  	for _, target := range targets {
   118  		destroyTargets = append(destroyTargets, pickURN(t, urns, complexTestDependencyGraphNames, target))
   119  	}
   120  
   121  	p.Options.DestroyTargets = deploy.NewUrnTargetsFromUrns(destroyTargets)
   122  	t.Logf("Destroying targets: %v", destroyTargets)
   123  
   124  	// If we're not forcing the targets to be destroyed, then expect to get a failure here as
   125  	// we'll have downstream resources to delete that weren't specified explicitly.
   126  	p.Steps = []TestStep{{
   127  		Op:            Destroy,
   128  		ExpectFailure: !targetDependents,
   129  		Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
   130  			evts []Event, res result.Result) result.Result {
   131  
   132  			assert.Nil(t, res)
   133  			assert.True(t, len(entries) > 0)
   134  
   135  			deleted := make(map[resource.URN]bool)
   136  			for _, entry := range entries {
   137  				assert.Equal(t, deploy.OpDelete, entry.Step.Op())
   138  				deleted[entry.Step.URN()] = true
   139  			}
   140  
   141  			for _, target := range p.Options.DestroyTargets.Literals() {
   142  				assert.Contains(t, deleted, target)
   143  			}
   144  
   145  			validate(urns, deleted)
   146  			return res
   147  		},
   148  	}}
   149  
   150  	p.Run(t, old)
   151  }
   152  
   153  func TestUpdateTarget(t *testing.T) {
   154  	t.Parallel()
   155  
   156  	// Try refreshing a stack with combinations of the above resources as target to destroy.
   157  	subsets := combinations.All(complexTestDependencyGraphNames)
   158  
   159  	//nolint:paralleltest // false positive because range var isn't used directly in t.Run(name) arg
   160  	for _, subset := range subsets {
   161  		subset := subset
   162  		// limit to up to 3 resources to destroy.  This keeps the test running time under
   163  		// control as it only generates a few hundred combinations instead of several thousand.
   164  		if len(subset) <= 3 {
   165  			t.Run(fmt.Sprintf("update %v", subset), func(t *testing.T) {
   166  				t.Parallel()
   167  
   168  				updateSpecificTargets(t, subset, nil, false /*targetDependents*/, -1)
   169  			})
   170  		}
   171  	}
   172  
   173  	updateSpecificTargets(t, []string{"A"}, nil, false /*targetDependents*/, -1)
   174  
   175  	// Also update a target that doesn't exist to make sure we don't crash or otherwise go off the rails.
   176  	updateInvalidTarget(t)
   177  
   178  	// We want to check that targetDependents is respected
   179  	updateSpecificTargets(t, []string{"C"}, nil, true /*targetDependents*/, -1)
   180  
   181  	updateSpecificTargets(t, nil, []string{"**C**"}, false, 1)
   182  	updateSpecificTargets(t, nil, []string{"**providers:pkgA**"}, false, 3)
   183  }
   184  
   185  func updateSpecificTargets(t *testing.T, targets, globTargets []string, targetDependents bool, expectedUpdates int) {
   186  	//             A
   187  	//    _________|_________
   188  	//    B        C        D
   189  	//          ___|___  ___|___
   190  	//          E  F  G  H  I  J
   191  	//             |__|
   192  	//             K  L
   193  
   194  	p := &TestPlan{}
   195  
   196  	urns, old, program := generateComplexTestDependencyGraph(t, p)
   197  
   198  	loaders := []*deploytest.ProviderLoader{
   199  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
   200  			return &deploytest.Provider{
   201  				DiffF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap,
   202  					ignoreChanges []string) (plugin.DiffResult, error) {
   203  
   204  					// all resources will change.
   205  					return plugin.DiffResult{
   206  						Changes: plugin.DiffSome,
   207  					}, nil
   208  				},
   209  
   210  				UpdateF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap, timeout float64,
   211  					ignoreChanges []string, preview bool) (resource.PropertyMap, resource.Status, error) {
   212  
   213  					outputs := olds.Copy()
   214  
   215  					outputs["output_prop"] = resource.NewPropertyValue(42)
   216  					return outputs, resource.StatusOK, nil
   217  				},
   218  			}, nil
   219  		}),
   220  	}
   221  
   222  	p.Options.Host = deploytest.NewPluginHost(nil, nil, program, loaders...)
   223  	p.Options.TargetDependents = targetDependents
   224  
   225  	updateTargets := globTargets
   226  	for _, target := range targets {
   227  		updateTargets = append(updateTargets,
   228  			string(pickURN(t, urns, complexTestDependencyGraphNames, target)))
   229  	}
   230  
   231  	p.Options.UpdateTargets = deploy.NewUrnTargets(updateTargets)
   232  	t.Logf("Updating targets: %v", updateTargets)
   233  
   234  	p.Steps = []TestStep{{
   235  		Op:            Update,
   236  		ExpectFailure: false,
   237  		Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
   238  			evts []Event, res result.Result) result.Result {
   239  
   240  			assert.Nil(t, res)
   241  			assert.True(t, len(entries) > 0)
   242  
   243  			updated := make(map[resource.URN]bool)
   244  			sames := make(map[resource.URN]bool)
   245  			for _, entry := range entries {
   246  				if entry.Step.Op() == deploy.OpUpdate {
   247  					updated[entry.Step.URN()] = true
   248  				} else if entry.Step.Op() == deploy.OpSame {
   249  					sames[entry.Step.URN()] = true
   250  				} else {
   251  					assert.FailNowf(t, "", "Got a step that wasn't a same/update: %v", entry.Step.Op())
   252  				}
   253  			}
   254  
   255  			for _, target := range p.Options.UpdateTargets.Literals() {
   256  				assert.Contains(t, updated, target)
   257  			}
   258  
   259  			if !targetDependents {
   260  				// We should only perform updates on the entries we have targeted.
   261  				for _, target := range p.Options.UpdateTargets.Literals() {
   262  					assert.Contains(t, targets, target.Name().String())
   263  				}
   264  			} else {
   265  				// We expect to find at least one other resource updates.
   266  
   267  				// NOTE: The test is limited to only passing a subset valid behavior. By specifying
   268  				// a URN with no dependents, no other urns will be updated and the test will fail
   269  				// (incorrectly).
   270  				found := false
   271  				updateList := []string{}
   272  				for target := range updated {
   273  					updateList = append(updateList, target.Name().String())
   274  					if !contains(targets, target.Name().String()) {
   275  						found = true
   276  					}
   277  				}
   278  				assert.True(t, found, "Updates: %v", updateList)
   279  			}
   280  
   281  			for _, target := range p.Options.UpdateTargets.Literals() {
   282  				assert.NotContains(t, sames, target)
   283  			}
   284  			if expectedUpdates > -1 {
   285  				assert.Equal(t, expectedUpdates, len(updated), "Updates = %#v", updated)
   286  			}
   287  			return res
   288  		},
   289  	}}
   290  	p.Run(t, old)
   291  }
   292  
   293  func contains(list []string, entry string) bool {
   294  	for _, e := range list {
   295  		if e == entry {
   296  			return true
   297  		}
   298  	}
   299  	return false
   300  }
   301  
   302  func updateInvalidTarget(t *testing.T) {
   303  	p := &TestPlan{}
   304  
   305  	_, old, program := generateComplexTestDependencyGraph(t, p)
   306  
   307  	loaders := []*deploytest.ProviderLoader{
   308  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
   309  			return &deploytest.Provider{
   310  				DiffF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap,
   311  					ignoreChanges []string) (plugin.DiffResult, error) {
   312  
   313  					// all resources will change.
   314  					return plugin.DiffResult{
   315  						Changes: plugin.DiffSome,
   316  					}, nil
   317  				},
   318  
   319  				UpdateF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap, timeout float64,
   320  					ignoreChanges []string, preview bool) (resource.PropertyMap, resource.Status, error) {
   321  
   322  					outputs := olds.Copy()
   323  
   324  					outputs["output_prop"] = resource.NewPropertyValue(42)
   325  					return outputs, resource.StatusOK, nil
   326  				},
   327  			}, nil
   328  		}),
   329  	}
   330  
   331  	p.Options.Host = deploytest.NewPluginHost(nil, nil, program, loaders...)
   332  
   333  	p.Options.UpdateTargets = deploy.NewUrnTargetsFromUrns([]resource.URN{"foo"})
   334  	t.Logf("Updating invalid targets: %v", p.Options.UpdateTargets)
   335  
   336  	p.Steps = []TestStep{{
   337  		Op:            Update,
   338  		ExpectFailure: true,
   339  	}}
   340  
   341  	p.Run(t, old)
   342  }
   343  
   344  func TestCreateDuringTargetedUpdate_CreateMentionedAsTarget(t *testing.T) {
   345  	t.Parallel()
   346  
   347  	loaders := []*deploytest.ProviderLoader{
   348  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
   349  			return &deploytest.Provider{}, nil
   350  		}),
   351  	}
   352  
   353  	program1 := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
   354  		_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
   355  		assert.NoError(t, err)
   356  		return nil
   357  	})
   358  	host1 := deploytest.NewPluginHost(nil, nil, program1, loaders...)
   359  
   360  	p := &TestPlan{
   361  		Options: UpdateOptions{Host: host1},
   362  	}
   363  
   364  	p.Steps = []TestStep{{Op: Update}}
   365  	snap1 := p.Run(t, nil)
   366  
   367  	// Now, create a resource resB.  This shouldn't be a problem since resB isn't referenced by anything.
   368  	program2 := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
   369  		_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
   370  		assert.NoError(t, err)
   371  
   372  		_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resB", true)
   373  		assert.NoError(t, err)
   374  
   375  		return nil
   376  	})
   377  	host2 := deploytest.NewPluginHost(nil, nil, program2, loaders...)
   378  
   379  	resA := p.NewURN("pkgA:m:typA", "resA", "")
   380  	resB := p.NewURN("pkgA:m:typA", "resB", "")
   381  	p.Options.Host = host2
   382  	p.Options.UpdateTargets = deploy.NewUrnTargetsFromUrns([]resource.URN{resA, resB})
   383  	p.Steps = []TestStep{{
   384  		Op:            Update,
   385  		ExpectFailure: false,
   386  		Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
   387  			evts []Event, res result.Result) result.Result {
   388  
   389  			assert.Nil(t, res)
   390  			assert.True(t, len(entries) > 0)
   391  
   392  			for _, entry := range entries {
   393  				if entry.Step.URN() == resA {
   394  					assert.Equal(t, deploy.OpSame, entry.Step.Op())
   395  				} else if entry.Step.URN() == resB {
   396  					assert.Equal(t, deploy.OpCreate, entry.Step.Op())
   397  				}
   398  			}
   399  
   400  			return res
   401  		},
   402  	}}
   403  	p.Run(t, snap1)
   404  }
   405  
   406  func TestCreateDuringTargetedUpdate_UntargetedCreateNotReferenced(t *testing.T) {
   407  	t.Parallel()
   408  
   409  	loaders := []*deploytest.ProviderLoader{
   410  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
   411  			return &deploytest.Provider{}, nil
   412  		}),
   413  	}
   414  
   415  	program1 := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
   416  		_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
   417  		assert.NoError(t, err)
   418  		return nil
   419  	})
   420  	host1 := deploytest.NewPluginHost(nil, nil, program1, loaders...)
   421  
   422  	p := &TestPlan{
   423  		Options: UpdateOptions{Host: host1},
   424  	}
   425  
   426  	p.Steps = []TestStep{{Op: Update}}
   427  	snap1 := p.Run(t, nil)
   428  
   429  	// Now, create a resource resB.  This shouldn't be a problem since resB isn't referenced by anything.
   430  	program2 := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
   431  		_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
   432  		assert.NoError(t, err)
   433  
   434  		_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resB", true)
   435  		assert.NoError(t, err)
   436  
   437  		return nil
   438  	})
   439  	host2 := deploytest.NewPluginHost(nil, nil, program2, loaders...)
   440  
   441  	resA := p.NewURN("pkgA:m:typA", "resA", "")
   442  
   443  	p.Options.Host = host2
   444  	p.Options.UpdateTargets = deploy.NewUrnTargetsFromUrns([]resource.URN{resA})
   445  	p.Steps = []TestStep{{
   446  		Op:            Update,
   447  		ExpectFailure: false,
   448  		Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
   449  			evts []Event, res result.Result) result.Result {
   450  
   451  			assert.Nil(t, res)
   452  			assert.True(t, len(entries) > 0)
   453  
   454  			for _, entry := range entries {
   455  				// everything should be a same op here.
   456  				assert.Equal(t, deploy.OpSame, entry.Step.Op())
   457  			}
   458  
   459  			return res
   460  		},
   461  	}}
   462  	p.Run(t, snap1)
   463  }
   464  
   465  func TestCreateDuringTargetedUpdate_UntargetedCreateReferencedByTarget(t *testing.T) {
   466  	t.Parallel()
   467  
   468  	loaders := []*deploytest.ProviderLoader{
   469  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
   470  			return &deploytest.Provider{}, nil
   471  		}),
   472  	}
   473  
   474  	program1 := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
   475  		_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
   476  		assert.NoError(t, err)
   477  		return nil
   478  	})
   479  	host1 := deploytest.NewPluginHost(nil, nil, program1, loaders...)
   480  
   481  	p := &TestPlan{
   482  		Options: UpdateOptions{Host: host1},
   483  	}
   484  
   485  	p.Steps = []TestStep{{Op: Update}}
   486  	p.Run(t, nil)
   487  
   488  	resA := p.NewURN("pkgA:m:typA", "resA", "")
   489  	resB := p.NewURN("pkgA:m:typA", "resB", "")
   490  
   491  	// Now, create a resource resB.  But reference it from A. This will cause a dependency we can't
   492  	// satisfy.
   493  	program2 := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
   494  		_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resB", true)
   495  		assert.NoError(t, err)
   496  
   497  		_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true,
   498  			deploytest.ResourceOptions{
   499  				Dependencies: []resource.URN{resB},
   500  			})
   501  		assert.NoError(t, err)
   502  
   503  		return nil
   504  	})
   505  	host2 := deploytest.NewPluginHost(nil, nil, program2, loaders...)
   506  
   507  	p.Options.Host = host2
   508  	p.Options.UpdateTargets = deploy.NewUrnTargetsFromUrns([]resource.URN{resA})
   509  	p.Steps = []TestStep{{
   510  		Op:            Update,
   511  		ExpectFailure: true,
   512  	}}
   513  	p.Run(t, nil)
   514  }
   515  
   516  func TestCreateDuringTargetedUpdate_UntargetedCreateReferencedByUntargetedCreate(t *testing.T) {
   517  	t.Parallel()
   518  
   519  	loaders := []*deploytest.ProviderLoader{
   520  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
   521  			return &deploytest.Provider{}, nil
   522  		}),
   523  	}
   524  
   525  	program1 := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
   526  		_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
   527  		assert.NoError(t, err)
   528  		return nil
   529  	})
   530  	host1 := deploytest.NewPluginHost(nil, nil, program1, loaders...)
   531  
   532  	p := &TestPlan{
   533  		Options: UpdateOptions{Host: host1},
   534  	}
   535  
   536  	p.Steps = []TestStep{{Op: Update}}
   537  	snap1 := p.Run(t, nil)
   538  
   539  	resA := p.NewURN("pkgA:m:typA", "resA", "")
   540  	resB := p.NewURN("pkgA:m:typA", "resB", "")
   541  
   542  	// Now, create a resource resB.  But reference it from A. This will cause a dependency we can't
   543  	// satisfy.
   544  	program2 := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
   545  		_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resB", true)
   546  		assert.NoError(t, err)
   547  
   548  		_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resC", true,
   549  			deploytest.ResourceOptions{
   550  				Dependencies: []resource.URN{resB},
   551  			})
   552  		assert.NoError(t, err)
   553  
   554  		_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true)
   555  		assert.NoError(t, err)
   556  
   557  		return nil
   558  	})
   559  	host2 := deploytest.NewPluginHost(nil, nil, program2, loaders...)
   560  
   561  	p.Options.Host = host2
   562  	p.Options.UpdateTargets = deploy.NewUrnTargetsFromUrns([]resource.URN{resA})
   563  	p.Steps = []TestStep{{
   564  		Op:            Update,
   565  		ExpectFailure: false,
   566  		Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
   567  			evts []Event, res result.Result) result.Result {
   568  
   569  			assert.Nil(t, res)
   570  			assert.True(t, len(entries) > 0)
   571  
   572  			for _, entry := range entries {
   573  				assert.Equal(t, deploy.OpSame, entry.Step.Op())
   574  			}
   575  
   576  			return res
   577  		},
   578  	}}
   579  	p.Run(t, snap1)
   580  }
   581  
   582  func TestReplaceSpecificTargets(t *testing.T) {
   583  	t.Parallel()
   584  
   585  	//             A
   586  	//    _________|_________
   587  	//    B        C        D
   588  	//          ___|___  ___|___
   589  	//          E  F  G  H  I  J
   590  	//             |__|
   591  	//             K  L
   592  
   593  	p := &TestPlan{}
   594  
   595  	urns, old, program := generateComplexTestDependencyGraph(t, p)
   596  
   597  	loaders := []*deploytest.ProviderLoader{
   598  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
   599  			return &deploytest.Provider{
   600  				DiffF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap,
   601  					ignoreChanges []string) (plugin.DiffResult, error) {
   602  
   603  					// No resources will change.
   604  					return plugin.DiffResult{Changes: plugin.DiffNone}, nil
   605  				},
   606  
   607  				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
   608  					preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
   609  
   610  					return "created-id", news, resource.StatusOK, nil
   611  				},
   612  			}, nil
   613  		}),
   614  	}
   615  
   616  	p.Options.Host = deploytest.NewPluginHost(nil, nil, program, loaders...)
   617  
   618  	getURN := func(name string) resource.URN {
   619  		return pickURN(t, urns, complexTestDependencyGraphNames, name)
   620  	}
   621  
   622  	p.Options.ReplaceTargets = deploy.NewUrnTargetsFromUrns([]resource.URN{
   623  		getURN("F"),
   624  		getURN("B"),
   625  		getURN("G"),
   626  	})
   627  
   628  	p.Steps = []TestStep{{
   629  		Op:            Update,
   630  		ExpectFailure: false,
   631  		Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
   632  			evts []Event, res result.Result) result.Result {
   633  
   634  			assert.Nil(t, res)
   635  			assert.True(t, len(entries) > 0)
   636  
   637  			replaced := make(map[resource.URN]bool)
   638  			sames := make(map[resource.URN]bool)
   639  			for _, entry := range entries {
   640  				if entry.Step.Op() == deploy.OpReplace {
   641  					replaced[entry.Step.URN()] = true
   642  				} else if entry.Step.Op() == deploy.OpSame {
   643  					sames[entry.Step.URN()] = true
   644  				}
   645  			}
   646  
   647  			for _, target := range p.Options.ReplaceTargets.Literals() {
   648  				assert.Contains(t, replaced, target)
   649  			}
   650  
   651  			for _, target := range p.Options.ReplaceTargets.Literals() {
   652  				assert.NotContains(t, sames, target)
   653  			}
   654  
   655  			return res
   656  		},
   657  	}}
   658  
   659  	p.Run(t, old)
   660  }
   661  
   662  var componentBasedTestDependencyGraphNames = []string{"A", "B", "C", "D", "E", "F", "G", "H",
   663  	"I", "J", "K", "L", "M", "N"}
   664  
   665  func generateParentedTestDependencyGraph(t *testing.T, p *TestPlan) (
   666  	// Parent-child graph
   667  	//      A               B
   668  	//    __|__         ____|____
   669  	//    D   I         E       F
   670  	//  __|__         __|__   __|__
   671  	//  G   H         J   K   L   M
   672  	//
   673  	// A has children D, I
   674  	// D has children G, H
   675  	// B has children E, F
   676  	// E has children J, K
   677  	// F has children L, M
   678  	//
   679  	// Dependency graph
   680  	//  G        H
   681  	//  |      __|__
   682  	//  I      K   N
   683  	//
   684  	// I depends on G
   685  	// K depends on H
   686  	// N depends on H
   687  
   688  	[]resource.URN, *deploy.Snapshot, plugin.LanguageRuntime) {
   689  	resTypeComponent := tokens.Type("pkgA:index:Component")
   690  	resTypeResource := tokens.Type("pkgA:index:Resource")
   691  
   692  	names := componentBasedTestDependencyGraphNames
   693  
   694  	urnA := p.NewURN(resTypeComponent, names[0], "")
   695  	urnB := p.NewURN(resTypeComponent, names[1], "")
   696  	urnC := p.NewURN(resTypeResource, names[2], "")
   697  	urnD := p.NewURN(resTypeComponent, names[3], urnA)
   698  	urnE := p.NewURN(resTypeComponent, names[4], urnB)
   699  	urnF := p.NewURN(resTypeComponent, names[5], urnB)
   700  	urnG := p.NewURN(resTypeResource, names[6], urnD)
   701  	urnH := p.NewURN(resTypeResource, names[7], urnD)
   702  	urnI := p.NewURN(resTypeResource, names[8], urnA)
   703  	urnJ := p.NewURN(resTypeResource, names[9], urnE)
   704  	urnK := p.NewURN(resTypeResource, names[10], urnE)
   705  	urnL := p.NewURN(resTypeResource, names[11], urnF)
   706  	urnM := p.NewURN(resTypeResource, names[12], urnF)
   707  	urnN := p.NewURN(resTypeResource, names[13], "")
   708  
   709  	urns := []resource.URN{urnA, urnB, urnC, urnD, urnE, urnF, urnG, urnH, urnI, urnJ, urnK, urnL, urnM, urnN}
   710  
   711  	newResource := func(urn, parent resource.URN, id resource.ID,
   712  		dependencies []resource.URN, propertyDeps propertyDependencies) *resource.State {
   713  		return newResource(urn, parent, id, "", dependencies, propertyDeps,
   714  			nil, urn.Type() != resTypeComponent)
   715  	}
   716  
   717  	old := &deploy.Snapshot{
   718  		Resources: []*resource.State{
   719  			newResource(urnA, "", "0", nil, nil),
   720  			newResource(urnB, "", "1", nil, nil),
   721  			newResource(urnC, "", "2", nil, nil),
   722  			newResource(urnD, urnA, "3", nil, nil),
   723  			newResource(urnE, urnB, "4", nil, nil),
   724  			newResource(urnF, urnB, "5", nil, nil),
   725  			newResource(urnG, urnD, "6", nil, nil),
   726  			newResource(urnH, urnD, "7", nil, nil),
   727  			newResource(urnI, urnA, "8", []resource.URN{urnG},
   728  				propertyDependencies{"A": []resource.URN{urnG}}),
   729  			newResource(urnJ, urnE, "9", nil, nil),
   730  			newResource(urnK, urnE, "10", []resource.URN{urnH},
   731  				propertyDependencies{"A": []resource.URN{urnH}}),
   732  			newResource(urnL, urnF, "11", nil, nil),
   733  			newResource(urnM, urnF, "12", nil, nil),
   734  			newResource(urnN, "", "13", []resource.URN{urnH},
   735  				propertyDependencies{"A": []resource.URN{urnH}}),
   736  		},
   737  	}
   738  
   739  	program := deploytest.NewLanguageRuntime(
   740  		func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
   741  			register := func(urn, parent resource.URN) resource.ID {
   742  				_, id, _, err := monitor.RegisterResource(
   743  					urn.Type(),
   744  					string(urn.Name()),
   745  					urn.Type() != resTypeComponent,
   746  					deploytest.ResourceOptions{
   747  						Inputs: nil,
   748  						Parent: parent,
   749  					})
   750  				assert.NoError(t, err)
   751  				return id
   752  			}
   753  
   754  			register(urnA, "")
   755  			register(urnB, "")
   756  			register(urnC, "")
   757  			register(urnD, urnA)
   758  			register(urnE, urnB)
   759  			register(urnF, urnB)
   760  			register(urnG, urnD)
   761  			register(urnH, urnD)
   762  			register(urnI, urnA)
   763  			register(urnJ, urnE)
   764  			register(urnK, urnE)
   765  			register(urnL, urnF)
   766  			register(urnM, urnF)
   767  			register(urnN, "")
   768  
   769  			return nil
   770  		})
   771  
   772  	return urns, old, program
   773  }
   774  
   775  func TestDestroyTargetWithChildren(t *testing.T) {
   776  	t.Parallel()
   777  
   778  	// when deleting 'A' with targetDependents specified we expect A, D, G, H, I, K and N to be deleted.
   779  	destroySpecificTargetsWithChildren(
   780  		t, []string{"A"}, true, /*targetDependents*/
   781  		func(urns []resource.URN, deleted map[resource.URN]bool) {
   782  			names := componentBasedTestDependencyGraphNames
   783  			assert.Equal(t, map[resource.URN]bool{
   784  				pickURN(t, urns, names, "A"): true,
   785  				pickURN(t, urns, names, "D"): true,
   786  				pickURN(t, urns, names, "G"): true,
   787  				pickURN(t, urns, names, "H"): true,
   788  				pickURN(t, urns, names, "I"): true,
   789  				pickURN(t, urns, names, "K"): true,
   790  				pickURN(t, urns, names, "N"): true,
   791  			}, deleted)
   792  		})
   793  
   794  	// when deleting 'A' with targetDependents not specified, we expect an error.
   795  	destroySpecificTargetsWithChildren(
   796  		t, []string{"A"}, false, /*targetDependents*/
   797  		func(urns []resource.URN, deleted map[resource.URN]bool) {})
   798  
   799  	// when deleting 'B' we expect B, E, F, J, K, L, M to be deleted.
   800  	destroySpecificTargetsWithChildren(
   801  		t, []string{"B"}, false, /*targetDependents*/
   802  		func(urns []resource.URN, deleted map[resource.URN]bool) {
   803  			names := componentBasedTestDependencyGraphNames
   804  			assert.Equal(t, map[resource.URN]bool{
   805  				pickURN(t, urns, names, "B"): true,
   806  				pickURN(t, urns, names, "E"): true,
   807  				pickURN(t, urns, names, "F"): true,
   808  				pickURN(t, urns, names, "J"): true,
   809  				pickURN(t, urns, names, "K"): true,
   810  				pickURN(t, urns, names, "L"): true,
   811  				pickURN(t, urns, names, "M"): true,
   812  			}, deleted)
   813  		})
   814  }
   815  
   816  func destroySpecificTargetsWithChildren(
   817  	t *testing.T, targets []string, targetDependents bool,
   818  	validate func(urns []resource.URN, deleted map[resource.URN]bool)) {
   819  
   820  	p := &TestPlan{}
   821  
   822  	urns, old, program := generateParentedTestDependencyGraph(t, p)
   823  
   824  	loaders := []*deploytest.ProviderLoader{
   825  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
   826  			return &deploytest.Provider{
   827  				DiffConfigF: func(urn resource.URN, olds, news resource.PropertyMap,
   828  					ignoreChanges []string) (plugin.DiffResult, error) {
   829  					if !olds["A"].DeepEquals(news["A"]) {
   830  						return plugin.DiffResult{
   831  							ReplaceKeys:         []resource.PropertyKey{"A"},
   832  							DeleteBeforeReplace: true,
   833  						}, nil
   834  					}
   835  					return plugin.DiffResult{}, nil
   836  				},
   837  				DiffF: func(urn resource.URN, id resource.ID,
   838  					olds, news resource.PropertyMap, ignoreChanges []string) (plugin.DiffResult, error) {
   839  
   840  					if !olds["A"].DeepEquals(news["A"]) {
   841  						return plugin.DiffResult{ReplaceKeys: []resource.PropertyKey{"A"}}, nil
   842  					}
   843  					return plugin.DiffResult{}, nil
   844  				},
   845  			}, nil
   846  		}),
   847  	}
   848  
   849  	p.Options.Host = deploytest.NewPluginHost(nil, nil, program, loaders...)
   850  	p.Options.TargetDependents = targetDependents
   851  
   852  	destroyTargets := []resource.URN{}
   853  	for _, target := range targets {
   854  		destroyTargets = append(destroyTargets, pickURN(t, urns, componentBasedTestDependencyGraphNames, target))
   855  	}
   856  
   857  	p.Options.DestroyTargets = deploy.NewUrnTargetsFromUrns(destroyTargets)
   858  	t.Logf("Destroying targets: %v", destroyTargets)
   859  
   860  	// If we're not forcing the targets to be destroyed, then expect to get a failure here as
   861  	// we'll have downstream resources to delete that weren't specified explicitly.
   862  	p.Steps = []TestStep{{
   863  		Op:            Destroy,
   864  		ExpectFailure: !targetDependents,
   865  		Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
   866  			evts []Event, res result.Result) result.Result {
   867  
   868  			assert.Nil(t, res)
   869  			assert.True(t, len(entries) > 0)
   870  
   871  			deleted := make(map[resource.URN]bool)
   872  			for _, entry := range entries {
   873  				assert.Equal(t, deploy.OpDelete, entry.Step.Op())
   874  				deleted[entry.Step.URN()] = true
   875  			}
   876  
   877  			for _, target := range p.Options.DestroyTargets.Literals() {
   878  				assert.Contains(t, deleted, target)
   879  			}
   880  
   881  			validate(urns, deleted)
   882  			return res
   883  		},
   884  	}}
   885  
   886  	p.Run(t, old)
   887  }
   888  
   889  func newResource(urn, parent resource.URN, id resource.ID, provider string, dependencies []resource.URN,
   890  	propertyDeps propertyDependencies, outputs resource.PropertyMap, custom bool) *resource.State {
   891  
   892  	inputs := resource.PropertyMap{}
   893  	for k := range propertyDeps {
   894  		inputs[k] = resource.NewStringProperty("foo")
   895  	}
   896  
   897  	return &resource.State{
   898  		Type:                 urn.Type(),
   899  		URN:                  urn,
   900  		Custom:               custom,
   901  		Delete:               false,
   902  		ID:                   id,
   903  		Inputs:               inputs,
   904  		Outputs:              outputs,
   905  		Dependencies:         dependencies,
   906  		PropertyDependencies: propertyDeps,
   907  		Provider:             provider,
   908  		Parent:               parent,
   909  	}
   910  }