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

     1  // Copyright 2016-2022, Pulumi Corporation.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // nolint: goconst
    16  package lifecycletest
    17  
    18  import (
    19  	"regexp"
    20  	"strings"
    21  	"testing"
    22  
    23  	"github.com/blang/semver"
    24  
    25  	"github.com/stretchr/testify/assert"
    26  
    27  	. "github.com/pulumi/pulumi/pkg/v3/engine"
    28  	"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
    29  	"github.com/pulumi/pulumi/pkg/v3/resource/deploy/deploytest"
    30  	"github.com/pulumi/pulumi/sdk/v3/go/common/display"
    31  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
    32  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
    33  	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
    34  )
    35  
    36  func TestPlannedUpdate(t *testing.T) {
    37  	t.Parallel()
    38  
    39  	loaders := []*deploytest.ProviderLoader{
    40  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
    41  			return &deploytest.Provider{
    42  				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
    43  					preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
    44  					return "created-id", news, resource.StatusOK, nil
    45  				},
    46  				UpdateF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap, timeout float64,
    47  					ignoreChanges []string, preview bool) (resource.PropertyMap, resource.Status, error) {
    48  					return news, resource.StatusOK, nil
    49  				},
    50  			}, nil
    51  		}),
    52  	}
    53  
    54  	var ins resource.PropertyMap
    55  	program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
    56  		_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
    57  			Inputs: ins,
    58  		})
    59  		assert.NoError(t, err)
    60  		return nil
    61  	})
    62  	host := deploytest.NewPluginHost(nil, nil, program, loaders...)
    63  
    64  	p := &TestPlan{
    65  		Options: UpdateOptions{Host: host, GeneratePlan: true, Experimental: true},
    66  	}
    67  
    68  	project := p.GetProject()
    69  
    70  	// Generate a plan.
    71  	computed := interface{}(resource.Computed{Element: resource.NewStringProperty("")})
    72  	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
    73  		"foo": "bar",
    74  		"baz": map[string]interface{}{
    75  			"a": 42,
    76  			"b": computed,
    77  		},
    78  		"qux": []interface{}{
    79  			computed,
    80  			24,
    81  		},
    82  		"zed": computed,
    83  	})
    84  	plan, res := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
    85  	assert.Nil(t, res)
    86  
    87  	// Attempt to run an update using the plan.
    88  	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
    89  		"qux": []interface{}{
    90  			"alpha",
    91  			24,
    92  		},
    93  	})
    94  	p.Options.Plan = plan.Clone()
    95  	validate := ExpectDiagMessage(t, regexp.QuoteMeta(
    96  		"<{%reset%}>resource urn:pulumi:test::test::pkgA:m:typA::resA violates plan: "+
    97  			"properties changed: +-baz[{map[a:{42} b:output<string>{}]}], +-foo[{bar}]<{%reset%}>\n"))
    98  	snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, validate)
    99  	assert.Nil(t, res)
   100  
   101  	// Check the resource's state.
   102  	if !assert.Len(t, snap.Resources, 1) {
   103  		return
   104  	}
   105  
   106  	// Change the provider's planned operation to a same step.
   107  	// Remove the provider from the plan.
   108  	plan.ResourcePlans["urn:pulumi:test::test::pulumi:providers:pkgA::default"].Ops = []display.StepOp{deploy.OpSame}
   109  
   110  	// Attempt to run an update using the plan.
   111  	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
   112  		"foo": "bar",
   113  		"baz": map[string]interface{}{
   114  			"a": 42,
   115  			"b": "alpha",
   116  		},
   117  		"qux": []interface{}{
   118  			"beta",
   119  			24,
   120  		},
   121  		"zed": "grr",
   122  	})
   123  	p.Options.Plan = plan.Clone()
   124  	snap, res = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil)
   125  	assert.Nil(t, res)
   126  
   127  	// Check the resource's state.
   128  	if !assert.Len(t, snap.Resources, 2) {
   129  		return
   130  	}
   131  
   132  	expected := resource.NewPropertyMapFromMap(map[string]interface{}{
   133  		"foo": "bar",
   134  		"baz": map[string]interface{}{
   135  			"a": 42,
   136  			"b": "alpha",
   137  		},
   138  		"qux": []interface{}{
   139  			"beta",
   140  			24,
   141  		},
   142  		"zed": "grr",
   143  	})
   144  	assert.Equal(t, expected, snap.Resources[1].Outputs)
   145  }
   146  
   147  func TestUnplannedCreate(t *testing.T) {
   148  	t.Parallel()
   149  
   150  	loaders := []*deploytest.ProviderLoader{
   151  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
   152  			return &deploytest.Provider{
   153  				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
   154  					preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
   155  					return "created-id", news, resource.StatusOK, nil
   156  				},
   157  			}, nil
   158  		}),
   159  	}
   160  
   161  	ins := resource.NewPropertyMapFromMap(map[string]interface{}{
   162  		"foo": "bar",
   163  	})
   164  	createResource := false
   165  	program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
   166  		if createResource {
   167  			_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
   168  				Inputs: ins,
   169  			})
   170  			assert.NoError(t, err)
   171  		}
   172  		return nil
   173  	})
   174  	host := deploytest.NewPluginHost(nil, nil, program, loaders...)
   175  
   176  	p := &TestPlan{
   177  		Options: UpdateOptions{Host: host, GeneratePlan: true, Experimental: true},
   178  	}
   179  
   180  	project := p.GetProject()
   181  
   182  	// Create a plan to do nothing
   183  	plan, res := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
   184  	assert.Nil(t, res)
   185  
   186  	// Now set the flag for the language runtime to create a resource, and run update with the plan
   187  	createResource = true
   188  	p.Options.Plan = plan.Clone()
   189  	validate := ExpectDiagMessage(t, regexp.QuoteMeta(
   190  		"<{%reset%}>create is not allowed by the plan: no steps were expected for this resource<{%reset%}>\n"))
   191  	snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, validate)
   192  	assert.Nil(t, res)
   193  
   194  	// Check nothing was was created
   195  	assert.NotNil(t, snap)
   196  	if !assert.Len(t, snap.Resources, 0) {
   197  		return
   198  	}
   199  }
   200  
   201  func TestUnplannedDelete(t *testing.T) {
   202  	t.Parallel()
   203  
   204  	loaders := []*deploytest.ProviderLoader{
   205  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
   206  			return &deploytest.Provider{
   207  				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
   208  					preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
   209  					return resource.ID("created-id-" + urn.Name()), news, resource.StatusOK, nil
   210  				},
   211  				DeleteF: func(
   212  					urn resource.URN,
   213  					id resource.ID,
   214  					olds resource.PropertyMap,
   215  					timeout float64) (resource.Status, error) {
   216  					return resource.StatusOK, nil
   217  				},
   218  			}, nil
   219  		}),
   220  	}
   221  
   222  	ins := resource.NewPropertyMapFromMap(map[string]interface{}{
   223  		"foo": "bar",
   224  	})
   225  	createAllResources := true
   226  	program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
   227  		_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
   228  			Inputs: ins,
   229  		})
   230  		assert.NoError(t, err)
   231  
   232  		if createAllResources {
   233  			_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resB", true, deploytest.ResourceOptions{
   234  				Inputs: ins,
   235  			})
   236  			assert.NoError(t, err)
   237  		}
   238  
   239  		return nil
   240  	})
   241  	host := deploytest.NewPluginHost(nil, nil, program, loaders...)
   242  
   243  	p := &TestPlan{
   244  		Options: UpdateOptions{Host: host, GeneratePlan: true, Experimental: true},
   245  	}
   246  
   247  	project := p.GetProject()
   248  
   249  	// Create an initial snapshot that resA and resB exist
   250  	snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
   251  	assert.Nil(t, res)
   252  
   253  	// Create a plan that resA and resB won't change
   254  	plan, res := TestOp(Update).Plan(project, p.GetTarget(t, snap), p.Options, p.BackendClient, nil)
   255  	assert.Nil(t, res)
   256  
   257  	// Now set the flag for the language runtime to not create resB and run an update with
   258  	// the no-op plan, this should block the delete
   259  	createAllResources = false
   260  	p.Options.Plan = plan.Clone()
   261  	validate := ExpectDiagMessage(t, regexp.QuoteMeta(
   262  		"<{%reset%}>delete is not allowed by the plan: this resource is constrained to same<{%reset%}>\n"))
   263  	snap, res = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, validate)
   264  	assert.NotNil(t, snap)
   265  	assert.Nil(t, res)
   266  
   267  	// Check both resources and the provider are still listed in the snapshot
   268  	if !assert.Len(t, snap.Resources, 3) {
   269  		return
   270  	}
   271  }
   272  
   273  func TestExpectedDelete(t *testing.T) {
   274  	t.Parallel()
   275  
   276  	loaders := []*deploytest.ProviderLoader{
   277  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
   278  			return &deploytest.Provider{
   279  				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
   280  					preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
   281  					return resource.ID("created-id-" + urn.Name()), news, resource.StatusOK, nil
   282  				},
   283  				DeleteF: func(
   284  					urn resource.URN,
   285  					id resource.ID,
   286  					olds resource.PropertyMap,
   287  					timeout float64) (resource.Status, error) {
   288  					return resource.StatusOK, nil
   289  				},
   290  			}, nil
   291  		}),
   292  	}
   293  
   294  	ins := resource.NewPropertyMapFromMap(map[string]interface{}{
   295  		"foo": "bar",
   296  	})
   297  	createAllResources := true
   298  	program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
   299  		_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
   300  			Inputs: ins,
   301  		})
   302  		assert.NoError(t, err)
   303  
   304  		if createAllResources {
   305  			_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resB", true, deploytest.ResourceOptions{
   306  				Inputs: ins,
   307  			})
   308  			assert.NoError(t, err)
   309  		}
   310  
   311  		return nil
   312  	})
   313  	host := deploytest.NewPluginHost(nil, nil, program, loaders...)
   314  
   315  	p := &TestPlan{
   316  		Options: UpdateOptions{Host: host, GeneratePlan: true, Experimental: true},
   317  	}
   318  
   319  	project := p.GetProject()
   320  
   321  	// Create an initial snapshot that resA and resB exist
   322  	snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
   323  	assert.NotNil(t, snap)
   324  	assert.Nil(t, res)
   325  
   326  	// Create a plan that resA is same and resB is deleted
   327  	createAllResources = false
   328  	plan, res := TestOp(Update).Plan(project, p.GetTarget(t, snap), p.Options, p.BackendClient, nil)
   329  	assert.NotNil(t, plan)
   330  	assert.Nil(t, res)
   331  
   332  	// Now run but set the runtime to return resA and resB, given we expected resB to be deleted
   333  	// this should be an error
   334  	createAllResources = true
   335  	p.Options.Plan = plan.Clone()
   336  	validate := ExpectDiagMessage(t, regexp.QuoteMeta(
   337  		"<{%reset%}>resource urn:pulumi:test::test::pkgA:m:typA::resB violates plan: "+
   338  			"resource unexpectedly not deleted<{%reset%}>\n"))
   339  	snap, res = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, validate)
   340  	assert.NotNil(t, snap)
   341  	assert.Nil(t, res)
   342  
   343  	// Check both resources and the provider are still listed in the snapshot
   344  	if !assert.Len(t, snap.Resources, 3) {
   345  		return
   346  	}
   347  }
   348  
   349  func TestExpectedCreate(t *testing.T) {
   350  	t.Parallel()
   351  
   352  	loaders := []*deploytest.ProviderLoader{
   353  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
   354  			return &deploytest.Provider{
   355  				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
   356  					preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
   357  					return resource.ID("created-id-" + urn.Name()), news, resource.StatusOK, nil
   358  				},
   359  			}, nil
   360  		}),
   361  	}
   362  
   363  	ins := resource.NewPropertyMapFromMap(map[string]interface{}{
   364  		"foo": "bar",
   365  	})
   366  	createAllResources := false
   367  	program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
   368  		_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
   369  			Inputs: ins,
   370  		})
   371  		assert.NoError(t, err)
   372  
   373  		if createAllResources {
   374  			_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resB", true, deploytest.ResourceOptions{
   375  				Inputs: ins,
   376  			})
   377  			assert.NoError(t, err)
   378  		}
   379  
   380  		return nil
   381  	})
   382  	host := deploytest.NewPluginHost(nil, nil, program, loaders...)
   383  
   384  	p := &TestPlan{
   385  		Options: UpdateOptions{Host: host, GeneratePlan: true, Experimental: true},
   386  	}
   387  
   388  	project := p.GetProject()
   389  
   390  	// Create an initial snapshot that resA exists
   391  	snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
   392  	assert.NotNil(t, snap)
   393  	assert.Nil(t, res)
   394  
   395  	// Create a plan that resA is same and resB is created
   396  	createAllResources = true
   397  	plan, res := TestOp(Update).Plan(project, p.GetTarget(t, snap), p.Options, p.BackendClient, nil)
   398  	assert.NotNil(t, plan)
   399  	assert.Nil(t, res)
   400  
   401  	// Now run but set the runtime to return resA, given we expected resB to be created
   402  	// this should be an error
   403  	createAllResources = false
   404  	p.Options.Plan = plan.Clone()
   405  	validate := ExpectDiagMessage(t, regexp.QuoteMeta(
   406  		"<{%reset%}>expected resource operations for "+
   407  			"urn:pulumi:test::test::pkgA:m:typA::resB but none were seen<{%reset%}>\n"))
   408  	snap, res = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, validate)
   409  	assert.NotNil(t, snap)
   410  	assert.Nil(t, res)
   411  
   412  	// Check resA and the provider are still listed in the snapshot
   413  	if !assert.Len(t, snap.Resources, 2) {
   414  		return
   415  	}
   416  }
   417  
   418  func TestPropertySetChange(t *testing.T) {
   419  	t.Parallel()
   420  
   421  	loaders := []*deploytest.ProviderLoader{
   422  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
   423  			return &deploytest.Provider{
   424  				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
   425  					preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
   426  					return resource.ID("created-id-" + urn.Name()), news, resource.StatusOK, nil
   427  				},
   428  			}, nil
   429  		}),
   430  	}
   431  
   432  	ins := resource.NewPropertyMapFromMap(map[string]interface{}{
   433  		"foo":  "bar",
   434  		"frob": "baz",
   435  	})
   436  	program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
   437  		_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
   438  			Inputs: ins,
   439  		})
   440  		assert.NoError(t, err)
   441  
   442  		return nil
   443  	})
   444  	host := deploytest.NewPluginHost(nil, nil, program, loaders...)
   445  
   446  	p := &TestPlan{
   447  		Options: UpdateOptions{Host: host, GeneratePlan: true, Experimental: true},
   448  	}
   449  
   450  	project := p.GetProject()
   451  
   452  	// Create an initial plan to create resA
   453  	plan, res := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
   454  	assert.NotNil(t, plan)
   455  	assert.Nil(t, res)
   456  
   457  	// Now change the runtime to not return property "frob", this should error
   458  	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
   459  		"foo": "bar",
   460  	})
   461  	p.Options.Plan = plan.Clone()
   462  	validate := ExpectDiagMessage(t, regexp.QuoteMeta(
   463  		"<{%reset%}>resource urn:pulumi:test::test::pkgA:m:typA::resA violates plan: "+
   464  			"properties changed: +-frob[{baz}]<{%reset%}>\n"))
   465  	snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, validate)
   466  	assert.NotNil(t, snap)
   467  	assert.Nil(t, res)
   468  }
   469  
   470  func TestExpectedUnneededCreate(t *testing.T) {
   471  	t.Parallel()
   472  
   473  	loaders := []*deploytest.ProviderLoader{
   474  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
   475  			return &deploytest.Provider{
   476  				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
   477  					preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
   478  					return resource.ID("created-id-" + urn.Name()), news, resource.StatusOK, nil
   479  				},
   480  			}, nil
   481  		}),
   482  	}
   483  
   484  	ins := resource.NewPropertyMapFromMap(map[string]interface{}{
   485  		"foo": "bar",
   486  	})
   487  	program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
   488  		_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
   489  			Inputs: ins,
   490  		})
   491  		assert.NoError(t, err)
   492  
   493  		return nil
   494  	})
   495  	host := deploytest.NewPluginHost(nil, nil, program, loaders...)
   496  
   497  	p := &TestPlan{
   498  		Options: UpdateOptions{Host: host, GeneratePlan: true, Experimental: true},
   499  	}
   500  
   501  	project := p.GetProject()
   502  
   503  	// Create a plan that resA needs creating
   504  	plan, res := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
   505  	assert.NotNil(t, plan)
   506  	assert.Nil(t, res)
   507  
   508  	// Create an a snapshot that resA exists
   509  	snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
   510  	assert.NotNil(t, snap)
   511  	assert.Nil(t, res)
   512  
   513  	// Now run again with the plan set but the snapshot that resA already exists
   514  	p.Options.Plan = plan.Clone()
   515  	snap, res = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil)
   516  	assert.NotNil(t, snap)
   517  	assert.Nil(t, res)
   518  
   519  	// Check resA and the provider are still listed in the snapshot
   520  	if !assert.Len(t, snap.Resources, 2) {
   521  		return
   522  	}
   523  }
   524  
   525  func TestExpectedUnneededDelete(t *testing.T) {
   526  	t.Parallel()
   527  
   528  	loaders := []*deploytest.ProviderLoader{
   529  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
   530  			return &deploytest.Provider{
   531  				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
   532  					preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
   533  					return resource.ID("created-id-" + urn.Name()), news, resource.StatusOK, nil
   534  				},
   535  				DeleteF: func(
   536  					urn resource.URN,
   537  					id resource.ID,
   538  					olds resource.PropertyMap,
   539  					timeout float64) (resource.Status, error) {
   540  					return resource.StatusOK, nil
   541  				},
   542  			}, nil
   543  		}),
   544  	}
   545  
   546  	ins := resource.NewPropertyMapFromMap(map[string]interface{}{
   547  		"foo": "bar",
   548  	})
   549  	createResource := true
   550  	program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
   551  		if createResource {
   552  			_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
   553  				Inputs: ins,
   554  			})
   555  			assert.NoError(t, err)
   556  		}
   557  
   558  		return nil
   559  	})
   560  	host := deploytest.NewPluginHost(nil, nil, program, loaders...)
   561  
   562  	p := &TestPlan{
   563  		Options: UpdateOptions{Host: host, GeneratePlan: true, Experimental: true},
   564  	}
   565  
   566  	project := p.GetProject()
   567  
   568  	// Create an initial snapshot that resA exists
   569  	snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
   570  	assert.Nil(t, res)
   571  
   572  	// Create a plan that resA is deleted
   573  	createResource = false
   574  	plan, res := TestOp(Update).Plan(project, p.GetTarget(t, snap), p.Options, p.BackendClient, nil)
   575  	assert.Nil(t, res)
   576  
   577  	// Now run to delete resA
   578  	snap, res = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil)
   579  	assert.NotNil(t, snap)
   580  	assert.Nil(t, res)
   581  
   582  	// Now run again with the plan set but the snapshot that resA is already deleted
   583  	p.Options.Plan = plan.Clone()
   584  	snap, res = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil)
   585  	assert.NotNil(t, snap)
   586  	assert.Nil(t, res)
   587  
   588  	// Check the resources are still gone
   589  	if !assert.Len(t, snap.Resources, 0) {
   590  		return
   591  	}
   592  }
   593  
   594  func TestResoucesWithSames(t *testing.T) {
   595  	t.Parallel()
   596  
   597  	// This test checks that if between generating a constraint and running the update that if new resources have been
   598  	// added to the stack that the update doesn't change those resources in any way that they don't cause constraint
   599  	// errors.
   600  
   601  	loaders := []*deploytest.ProviderLoader{
   602  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
   603  			return &deploytest.Provider{
   604  				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
   605  					preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
   606  					return "created-id", news, resource.StatusOK, nil
   607  				},
   608  				UpdateF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap, timeout float64,
   609  					ignoreChanges []string, preview bool) (resource.PropertyMap, resource.Status, error) {
   610  					return news, resource.StatusOK, nil
   611  				},
   612  			}, nil
   613  		}),
   614  	}
   615  
   616  	var ins resource.PropertyMap
   617  	createA := false
   618  	createB := false
   619  	program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
   620  		if createA {
   621  			_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
   622  				Inputs: ins,
   623  			})
   624  			assert.NoError(t, err)
   625  		}
   626  
   627  		if createB {
   628  			_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resB", true, deploytest.ResourceOptions{
   629  				Inputs: resource.NewPropertyMapFromMap(map[string]interface{}{
   630  					"X": "Y",
   631  				}),
   632  			})
   633  			assert.NoError(t, err)
   634  		}
   635  		return nil
   636  	})
   637  	host := deploytest.NewPluginHost(nil, nil, program, loaders...)
   638  
   639  	p := &TestPlan{
   640  		Options: UpdateOptions{Host: host, GeneratePlan: true, Experimental: true},
   641  	}
   642  
   643  	project := p.GetProject()
   644  
   645  	// Generate a plan to create A
   646  	createA = true
   647  	createB = false
   648  	computed := interface{}(resource.Computed{Element: resource.NewStringProperty("")})
   649  	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
   650  		"foo": "bar",
   651  		"zed": computed,
   652  	})
   653  	plan, res := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
   654  	assert.Nil(t, res)
   655  
   656  	// Run an update that creates B
   657  	createA = false
   658  	createB = true
   659  	snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
   660  	assert.Nil(t, res)
   661  
   662  	// Check the resource's state.
   663  	if !assert.Len(t, snap.Resources, 2) {
   664  		return
   665  	}
   666  
   667  	expected := resource.NewPropertyMapFromMap(map[string]interface{}{
   668  		"X": "Y",
   669  	})
   670  	assert.Equal(t, expected, snap.Resources[1].Outputs)
   671  
   672  	// Attempt to run an update with the plan on the stack that creates A and sames B
   673  	createA = true
   674  	createB = true
   675  	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
   676  		"foo": "bar",
   677  		"zed": 24,
   678  	})
   679  	p.Options.Plan = plan.Clone()
   680  	snap, res = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil)
   681  	assert.Nil(t, res)
   682  
   683  	// Check the resource's state.
   684  	if !assert.Len(t, snap.Resources, 3) {
   685  		return
   686  	}
   687  
   688  	expected = resource.NewPropertyMapFromMap(map[string]interface{}{
   689  		"X": "Y",
   690  	})
   691  	assert.Equal(t, expected, snap.Resources[2].Outputs)
   692  
   693  	expected = resource.NewPropertyMapFromMap(map[string]interface{}{
   694  		"foo": "bar",
   695  		"zed": 24,
   696  	})
   697  	assert.Equal(t, expected, snap.Resources[1].Outputs)
   698  }
   699  
   700  func TestPlannedPreviews(t *testing.T) {
   701  	t.Parallel()
   702  
   703  	// This checks that plans work in previews, this is very similar to TestPlannedUpdate except we only do previews
   704  
   705  	loaders := []*deploytest.ProviderLoader{
   706  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
   707  			return &deploytest.Provider{
   708  				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
   709  					preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
   710  					return "created-id", news, resource.StatusOK, nil
   711  				},
   712  				UpdateF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap, timeout float64,
   713  					ignoreChanges []string, preview bool) (resource.PropertyMap, resource.Status, error) {
   714  					return news, resource.StatusOK, nil
   715  				},
   716  			}, nil
   717  		}),
   718  	}
   719  
   720  	var ins resource.PropertyMap
   721  	program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
   722  		_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
   723  			Inputs: ins,
   724  		})
   725  		assert.NoError(t, err)
   726  		return nil
   727  	})
   728  	host := deploytest.NewPluginHost(nil, nil, program, loaders...)
   729  
   730  	p := &TestPlan{
   731  		Options: UpdateOptions{Host: host, GeneratePlan: true, Experimental: true},
   732  	}
   733  
   734  	project := p.GetProject()
   735  
   736  	// Generate a plan.
   737  	computed := interface{}(resource.Computed{Element: resource.NewStringProperty("")})
   738  	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
   739  		"foo": "bar",
   740  		"baz": map[string]interface{}{
   741  			"a": 42,
   742  			"b": computed,
   743  		},
   744  		"qux": []interface{}{
   745  			computed,
   746  			24,
   747  		},
   748  		"zed": computed,
   749  	})
   750  	plan, res := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
   751  	assert.Nil(t, res)
   752  
   753  	// Attempt to run a new preview using the plan, given we've changed the property set this should fail
   754  	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
   755  		"qux": []interface{}{
   756  			"alpha",
   757  			24,
   758  		},
   759  	})
   760  	p.Options.Plan = plan.Clone()
   761  	validate := ExpectDiagMessage(t, regexp.QuoteMeta(
   762  		"<{%reset%}>resource urn:pulumi:test::test::pkgA:m:typA::resA violates plan: properties changed: "+
   763  			"+-baz[{map[a:{42} b:output<string>{}]}], +-foo[{bar}]<{%reset%}>\n"))
   764  	_, res = TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, validate)
   765  	assert.Nil(t, res)
   766  
   767  	// Attempt to run an preview using the plan, such that the property set is now valid
   768  	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
   769  		"foo": "bar",
   770  		"baz": map[string]interface{}{
   771  			"a": 42,
   772  			"b": computed,
   773  		},
   774  		"qux": []interface{}{
   775  			"beta",
   776  			24,
   777  		},
   778  		"zed": "grr",
   779  	})
   780  	p.Options.Plan = plan.Clone()
   781  	_, res = TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
   782  	assert.Nil(t, res)
   783  }
   784  
   785  func TestPlannedUpdateChangedStack(t *testing.T) {
   786  	t.Parallel()
   787  
   788  	// This tests the case that we run a planned update against a stack that has changed between preview and update
   789  
   790  	loaders := []*deploytest.ProviderLoader{
   791  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
   792  			return &deploytest.Provider{
   793  				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
   794  					preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
   795  					return "created-id", news, resource.StatusOK, nil
   796  				},
   797  				UpdateF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap, timeout float64,
   798  					ignoreChanges []string, preview bool) (resource.PropertyMap, resource.Status, error) {
   799  					return news, resource.StatusOK, nil
   800  				},
   801  			}, nil
   802  		}),
   803  	}
   804  
   805  	var ins resource.PropertyMap
   806  	program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
   807  		_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
   808  			Inputs: ins,
   809  		})
   810  		assert.NoError(t, err)
   811  		return nil
   812  	})
   813  	host := deploytest.NewPluginHost(nil, nil, program, loaders...)
   814  
   815  	p := &TestPlan{
   816  		Options: UpdateOptions{Host: host, GeneratePlan: true, Experimental: true},
   817  	}
   818  
   819  	project := p.GetProject()
   820  
   821  	// Set initial data for foo and zed
   822  	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
   823  		"foo": "bar",
   824  		"zed": 24,
   825  	})
   826  	snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
   827  	assert.Nil(t, res)
   828  
   829  	// Generate a plan that we want to change foo
   830  	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
   831  		"foo": "baz",
   832  		"zed": 24,
   833  	})
   834  	plan, res := TestOp(Update).Plan(project, p.GetTarget(t, snap), p.Options, p.BackendClient, nil)
   835  	assert.Nil(t, res)
   836  
   837  	// Change zed in the stack
   838  	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
   839  		"foo": "bar",
   840  		"zed": 26,
   841  	})
   842  	snap, res = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil)
   843  	assert.Nil(t, res)
   844  
   845  	// Attempt to run an update using the plan but where we haven't updated our program for the change of zed
   846  	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
   847  		"foo": "baz",
   848  		"zed": 24,
   849  	})
   850  	p.Options.Plan = plan.Clone()
   851  	validate := ExpectDiagMessage(t, regexp.QuoteMeta(
   852  		"<{%reset%}>resource urn:pulumi:test::test::pkgA:m:typA::resA violates plan: "+
   853  			"properties changed: =~zed[{24}]<{%reset%}>\n"))
   854  	snap, res = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, validate)
   855  	assert.Nil(t, res)
   856  
   857  	// Check the resource's state we shouldn't of changed anything because the update failed
   858  	if !assert.Len(t, snap.Resources, 2) {
   859  		return
   860  	}
   861  
   862  	expected := resource.NewPropertyMapFromMap(map[string]interface{}{
   863  		"foo": "bar",
   864  		"zed": 26,
   865  	})
   866  	assert.Equal(t, expected, snap.Resources[1].Outputs)
   867  }
   868  
   869  func TestPlannedOutputChanges(t *testing.T) {
   870  	t.Parallel()
   871  
   872  	loaders := []*deploytest.ProviderLoader{
   873  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
   874  			return &deploytest.Provider{
   875  				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
   876  					preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
   877  					return resource.ID("created-id-" + urn.Name()), news, resource.StatusOK, nil
   878  				},
   879  			}, nil
   880  		}),
   881  	}
   882  
   883  	outs := resource.NewPropertyMapFromMap(map[string]interface{}{
   884  		"foo":  "bar",
   885  		"frob": "baz",
   886  	})
   887  	program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
   888  		urn, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{})
   889  		assert.NoError(t, err)
   890  
   891  		err = monitor.RegisterResourceOutputs(urn, outs)
   892  		assert.NoError(t, err)
   893  
   894  		return nil
   895  	})
   896  	host := deploytest.NewPluginHost(nil, nil, program, loaders...)
   897  
   898  	p := &TestPlan{
   899  		Options: UpdateOptions{Host: host, GeneratePlan: true, Experimental: true},
   900  	}
   901  
   902  	project := p.GetProject()
   903  
   904  	// Create an initial plan to create resA and the outputs
   905  	plan, res := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
   906  	assert.NotNil(t, plan)
   907  	assert.Nil(t, res)
   908  
   909  	// Now change the runtime to not return property "frob", this should error
   910  	outs = resource.NewPropertyMapFromMap(map[string]interface{}{
   911  		"foo": "bar",
   912  	})
   913  	p.Options.Plan = plan.Clone()
   914  	validate := ExpectDiagMessage(t, regexp.QuoteMeta(
   915  		"<{%reset%}>resource violates plan: properties changed: +-frob[{baz}]<{%reset%}>\n"))
   916  	snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, validate)
   917  	assert.NotNil(t, snap)
   918  	assert.Nil(t, res)
   919  }
   920  
   921  func TestPlannedInputOutputDifferences(t *testing.T) {
   922  	t.Parallel()
   923  
   924  	// This tests that plans are working on the program inputs, not the provider outputs
   925  
   926  	createOutputs := resource.NewPropertyMapFromMap(map[string]interface{}{
   927  		"foo":  "bar",
   928  		"frob": "baz",
   929  		"baz":  24,
   930  	})
   931  	updateOutputs := resource.NewPropertyMapFromMap(map[string]interface{}{
   932  		"foo":  "bar",
   933  		"frob": "newBazzer",
   934  		"baz":  24,
   935  	})
   936  
   937  	loaders := []*deploytest.ProviderLoader{
   938  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
   939  			return &deploytest.Provider{
   940  				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
   941  					preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
   942  					return resource.ID("created-id-" + urn.Name()), createOutputs, resource.StatusOK, nil
   943  				},
   944  				UpdateF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap, timeout float64,
   945  					ignoreChanges []string, preview bool) (resource.PropertyMap, resource.Status, error) {
   946  					return updateOutputs, resource.StatusOK, nil
   947  				},
   948  			}, nil
   949  		}),
   950  	}
   951  
   952  	inputs := resource.NewPropertyMapFromMap(map[string]interface{}{
   953  		"foo":  "bar",
   954  		"frob": "baz",
   955  	})
   956  	program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
   957  		_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
   958  			Inputs: inputs})
   959  		assert.NoError(t, err)
   960  
   961  		return nil
   962  	})
   963  	host := deploytest.NewPluginHost(nil, nil, program, loaders...)
   964  
   965  	p := &TestPlan{
   966  		Options: UpdateOptions{Host: host, GeneratePlan: true, Experimental: true},
   967  	}
   968  
   969  	project := p.GetProject()
   970  
   971  	// Create an initial plan to create resA
   972  	plan, res := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
   973  	assert.NotNil(t, plan)
   974  	assert.Nil(t, res)
   975  
   976  	// Check we can create resA even though its outputs are different to the planned inputs
   977  	p.Options.Plan = plan.Clone()
   978  	snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
   979  	assert.NotNil(t, snap)
   980  	assert.Nil(t, res)
   981  
   982  	// Make a plan to change resA
   983  	inputs = resource.NewPropertyMapFromMap(map[string]interface{}{
   984  		"foo":  "bar",
   985  		"frob": "newBazzer",
   986  	})
   987  	p.Options.Plan = nil
   988  	plan, res = TestOp(Update).Plan(project, p.GetTarget(t, snap), p.Options, p.BackendClient, nil)
   989  	assert.NotNil(t, plan)
   990  	assert.Nil(t, res)
   991  
   992  	// Test the plan fails if we don't pass newBazzer
   993  	inputs = resource.NewPropertyMapFromMap(map[string]interface{}{
   994  		"foo":  "bar",
   995  		"frob": "differentBazzer",
   996  	})
   997  	p.Options.Plan = plan.Clone()
   998  	validate := ExpectDiagMessage(t, regexp.QuoteMeta(
   999  		"<{%reset%}>resource urn:pulumi:test::test::pkgA:m:typA::resA violates plan: "+
  1000  			"properties changed: ~~frob[{newBazzer}!={differentBazzer}]<{%reset%}>\n"))
  1001  	snap, res = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, validate)
  1002  	assert.NotNil(t, snap)
  1003  	assert.Nil(t, res)
  1004  
  1005  	// Check the plan succeeds if we do pass newBazzer
  1006  	inputs = resource.NewPropertyMapFromMap(map[string]interface{}{
  1007  		"foo":  "bar",
  1008  		"frob": "newBazzer",
  1009  	})
  1010  	p.Options.Plan = plan.Clone()
  1011  	snap, res = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil)
  1012  	assert.NotNil(t, snap)
  1013  	assert.Nil(t, res)
  1014  }
  1015  
  1016  func TestAliasWithPlans(t *testing.T) {
  1017  	t.Parallel()
  1018  
  1019  	// This tests that if a resource has an alias the plan for it is still used
  1020  
  1021  	loaders := []*deploytest.ProviderLoader{
  1022  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
  1023  			return &deploytest.Provider{
  1024  				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
  1025  					preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
  1026  					return resource.ID("created-id-" + urn.Name()), news, resource.StatusOK, nil
  1027  				},
  1028  			}, nil
  1029  		}),
  1030  	}
  1031  
  1032  	resourceName := "resA"
  1033  	var aliases []resource.URN
  1034  	ins := resource.NewPropertyMapFromMap(map[string]interface{}{
  1035  		"foo":  "bar",
  1036  		"frob": "baz",
  1037  	})
  1038  	program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
  1039  		_, _, _, err := monitor.RegisterResource("pkgA:m:typA", resourceName, true, deploytest.ResourceOptions{
  1040  			Inputs:    ins,
  1041  			AliasURNs: aliases,
  1042  		})
  1043  		assert.NoError(t, err)
  1044  
  1045  		return nil
  1046  	})
  1047  	host := deploytest.NewPluginHost(nil, nil, program, loaders...)
  1048  
  1049  	p := &TestPlan{
  1050  		Options: UpdateOptions{Host: host, GeneratePlan: true, Experimental: true},
  1051  	}
  1052  
  1053  	project := p.GetProject()
  1054  
  1055  	// Create an initial ResA
  1056  	snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
  1057  	assert.NotNil(t, snap)
  1058  	assert.Nil(t, res)
  1059  
  1060  	// Update the name and alias and make a plan for resA
  1061  	resourceName = "newResA"
  1062  	aliases = make([]resource.URN, 1)
  1063  	aliases[0] = resource.URN("urn:pulumi:test::test::pkgA:m:typA::resA")
  1064  	plan, res := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
  1065  	assert.NotNil(t, plan)
  1066  	assert.Nil(t, res)
  1067  
  1068  	// Now try and run with the plan
  1069  	p.Options.Plan = plan.Clone()
  1070  	snap, res = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil)
  1071  	assert.NotNil(t, snap)
  1072  	assert.Nil(t, res)
  1073  }
  1074  
  1075  func TestComputedCanBeDropped(t *testing.T) {
  1076  	t.Parallel()
  1077  
  1078  	// This tests that values that show as <computed> in the plan can be dropped in the update (because they may of
  1079  	// resolved to undefined). We're testing both RegisterResource and RegisterResourceOutputs here.
  1080  
  1081  	loaders := []*deploytest.ProviderLoader{
  1082  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
  1083  			return &deploytest.Provider{
  1084  				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
  1085  					preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
  1086  					return resource.ID("created-id-" + urn.Name()), news, resource.StatusOK, nil
  1087  				},
  1088  				UpdateF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap, timeout float64,
  1089  					ignoreChanges []string, preview bool) (resource.PropertyMap, resource.Status, error) {
  1090  					return news, resource.StatusOK, nil
  1091  				},
  1092  			}, nil
  1093  		}),
  1094  	}
  1095  
  1096  	var resourceInputs resource.PropertyMap
  1097  	program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
  1098  		urn, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{})
  1099  		assert.NoError(t, err)
  1100  
  1101  		_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resB", true, deploytest.ResourceOptions{
  1102  			Inputs: resourceInputs,
  1103  		})
  1104  		assert.NoError(t, err)
  1105  
  1106  		// We're using the same property set on purpose, this is not a test bug
  1107  		err = monitor.RegisterResourceOutputs(urn, resourceInputs)
  1108  		assert.NoError(t, err)
  1109  
  1110  		return nil
  1111  	})
  1112  	host := deploytest.NewPluginHost(nil, nil, program, loaders...)
  1113  
  1114  	p := &TestPlan{
  1115  		Options: UpdateOptions{Host: host, GeneratePlan: true, Experimental: true},
  1116  	}
  1117  
  1118  	project := p.GetProject()
  1119  
  1120  	// The three property sets we'll use in this test
  1121  	computed := interface{}(resource.Computed{Element: resource.NewStringProperty("")})
  1122  	computedPropertySet := resource.NewPropertyMapFromMap(map[string]interface{}{
  1123  		"foo": "bar",
  1124  		"baz": map[string]interface{}{
  1125  			"a": 42,
  1126  			"b": computed,
  1127  		},
  1128  		"qux": []interface{}{
  1129  			computed,
  1130  			24,
  1131  		},
  1132  		"zed": computed,
  1133  	})
  1134  	fullPropertySet := resource.NewPropertyMapFromMap(map[string]interface{}{
  1135  		"foo": "bar",
  1136  		"baz": map[string]interface{}{
  1137  			"a": 42,
  1138  			"b": "alpha",
  1139  		},
  1140  		"qux": []interface{}{
  1141  			"beta",
  1142  			24,
  1143  		},
  1144  		"zed": "grr",
  1145  	})
  1146  	partialPropertySet := resource.NewPropertyMapFromMap(map[string]interface{}{
  1147  		"foo": "bar",
  1148  		"baz": map[string]interface{}{
  1149  			"a": 42,
  1150  		},
  1151  		"qux": []interface{}{
  1152  			nil, // computed values that resolve to undef don't get dropped from arrays, they just become null
  1153  			24,
  1154  		},
  1155  	})
  1156  
  1157  	// Generate a plan.
  1158  	resourceInputs = computedPropertySet
  1159  	plan, res := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
  1160  	assert.Nil(t, res)
  1161  
  1162  	// Attempt to run an update using the plan with all computed values removed
  1163  	resourceInputs = partialPropertySet
  1164  	p.Options.Plan = plan.Clone()
  1165  	snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
  1166  	assert.Nil(t, res)
  1167  
  1168  	// Check the resource's state.
  1169  	if !assert.Len(t, snap.Resources, 3) {
  1170  		return
  1171  	}
  1172  
  1173  	assert.Equal(t, partialPropertySet, snap.Resources[1].Outputs)
  1174  	assert.Equal(t, partialPropertySet, snap.Resources[2].Outputs)
  1175  
  1176  	// Now run an update to set the values of the computed properties...
  1177  	resourceInputs = fullPropertySet
  1178  	p.Options.Plan = nil
  1179  	snap, res = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil)
  1180  	assert.Nil(t, res)
  1181  
  1182  	// Check the resource's state.
  1183  	if !assert.Len(t, snap.Resources, 3) {
  1184  		return
  1185  	}
  1186  
  1187  	assert.Equal(t, fullPropertySet, snap.Resources[1].Outputs)
  1188  	assert.Equal(t, fullPropertySet, snap.Resources[2].Outputs)
  1189  
  1190  	// ...and then build a new plan where they're computed updates (vs above where its computed creates)
  1191  	resourceInputs = computedPropertySet
  1192  	plan, res = TestOp(Update).Plan(project, p.GetTarget(t, snap), p.Options, p.BackendClient, nil)
  1193  	assert.Nil(t, res)
  1194  
  1195  	// Now run the an update with the plan and check the update is allowed to remove these properties
  1196  	resourceInputs = partialPropertySet
  1197  	p.Options.Plan = plan.Clone()
  1198  	snap, res = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil)
  1199  	assert.Nil(t, res)
  1200  
  1201  	// Check the resource's state.
  1202  	if !assert.Len(t, snap.Resources, 3) {
  1203  		return
  1204  	}
  1205  
  1206  	assert.Equal(t, partialPropertySet, snap.Resources[1].Outputs)
  1207  	assert.Equal(t, partialPropertySet, snap.Resources[2].Outputs)
  1208  }
  1209  
  1210  func TestPlannedUpdateWithNondeterministicCheck(t *testing.T) {
  1211  	t.Parallel()
  1212  
  1213  	loaders := []*deploytest.ProviderLoader{
  1214  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
  1215  			return &deploytest.Provider{
  1216  				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
  1217  					preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
  1218  					return resource.ID("created-id-" + urn.Name()), news, resource.StatusOK, nil
  1219  				},
  1220  				UpdateF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap, timeout float64,
  1221  					ignoreChanges []string, preview bool) (resource.PropertyMap, resource.Status, error) {
  1222  					return news, resource.StatusOK, nil
  1223  				},
  1224  				CheckF: func(urn resource.URN,
  1225  					olds, news resource.PropertyMap, _ []byte) (resource.PropertyMap, []plugin.CheckFailure, error) {
  1226  
  1227  					// If we have name use it, else use olds name, else make one up
  1228  					if _, has := news["name"]; has {
  1229  						return news, nil, nil
  1230  					}
  1231  					if _, has := olds["name"]; has {
  1232  						result := news.Copy()
  1233  						result["name"] = olds["name"]
  1234  						return result, nil, nil
  1235  					}
  1236  
  1237  					name, err := resource.NewUniqueHex(urn.Name().String(), 8, 512)
  1238  					assert.Nil(t, err)
  1239  
  1240  					result := news.Copy()
  1241  					result["name"] = resource.NewStringProperty(name)
  1242  					return result, nil, nil
  1243  				},
  1244  			}, nil
  1245  		}),
  1246  	}
  1247  
  1248  	var ins resource.PropertyMap
  1249  	program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
  1250  		_, _, outs, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
  1251  			Inputs: ins,
  1252  		})
  1253  		assert.NoError(t, err)
  1254  
  1255  		_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resB", true, deploytest.ResourceOptions{
  1256  			Inputs: resource.NewPropertyMapFromMap(map[string]interface{}{
  1257  				"other": outs["name"].StringValue(),
  1258  			}),
  1259  		})
  1260  		assert.NoError(t, err)
  1261  
  1262  		return nil
  1263  	})
  1264  	host := deploytest.NewPluginHost(nil, nil, program, loaders...)
  1265  
  1266  	p := &TestPlan{
  1267  		Options: UpdateOptions{Host: host, GeneratePlan: true, Experimental: true},
  1268  	}
  1269  
  1270  	project := p.GetProject()
  1271  
  1272  	// Generate a plan.
  1273  	computed := interface{}(resource.Computed{Element: resource.NewStringProperty("")})
  1274  	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
  1275  		"foo": "bar",
  1276  		"zed": computed,
  1277  	})
  1278  	plan, res := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
  1279  	assert.Nil(t, res)
  1280  
  1281  	// Attempt to run an update using the plan.
  1282  	// This should fail because of the non-determinism
  1283  	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
  1284  		"foo": "bar",
  1285  		"zed": "baz",
  1286  	})
  1287  	p.Options.Plan = plan.Clone()
  1288  
  1289  	validate := ExpectDiagMessage(t,
  1290  		"<{%reset%}>resource urn:pulumi:test::test::pkgA:m:typA::resA violates plan: "+
  1291  			"properties changed: \\+\\+name\\[{res[\\d\\w]{9}}!={res[\\d\\w]{9}}\\]<{%reset%}>\\n")
  1292  	snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, validate)
  1293  	assert.Nil(t, res)
  1294  
  1295  	// Check the resource's state.
  1296  	if !assert.Len(t, snap.Resources, 1) {
  1297  		return
  1298  	}
  1299  }
  1300  
  1301  func TestPlannedUpdateWithCheckFailure(t *testing.T) {
  1302  	// Regression test for https://github.com/pulumi/pulumi/issues/9247
  1303  
  1304  	t.Parallel()
  1305  
  1306  	loaders := []*deploytest.ProviderLoader{
  1307  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
  1308  			return &deploytest.Provider{
  1309  				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
  1310  					preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
  1311  					return "created-id", news, resource.StatusOK, nil
  1312  				},
  1313  				UpdateF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap, timeout float64,
  1314  					ignoreChanges []string, preview bool) (resource.PropertyMap, resource.Status, error) {
  1315  					return news, resource.StatusOK, nil
  1316  				},
  1317  				CheckF: func(urn resource.URN, olds, news resource.PropertyMap,
  1318  					randomSeed []byte) (resource.PropertyMap, []plugin.CheckFailure, error) {
  1319  					if news["foo"].StringValue() == "bad" {
  1320  						return nil, []plugin.CheckFailure{
  1321  							{Property: resource.PropertyKey("foo"), Reason: "Bad foo"},
  1322  						}, nil
  1323  					}
  1324  					return news, nil, nil
  1325  				},
  1326  			}, nil
  1327  		}),
  1328  	}
  1329  
  1330  	var ins resource.PropertyMap
  1331  	program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
  1332  		_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
  1333  			Inputs: ins,
  1334  		})
  1335  		assert.NoError(t, err)
  1336  		return nil
  1337  	})
  1338  	host := deploytest.NewPluginHost(nil, nil, program, loaders...)
  1339  
  1340  	p := &TestPlan{
  1341  		Options: UpdateOptions{Host: host, GeneratePlan: true, Experimental: true},
  1342  	}
  1343  
  1344  	project := p.GetProject()
  1345  
  1346  	// Generate a plan with bad inputs
  1347  	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
  1348  		"foo": "bad",
  1349  	})
  1350  	validate := ExpectDiagMessage(t, regexp.QuoteMeta(
  1351  		"<{%reset%}>pkgA:m:typA resource 'resA': property foo value {bad} has a problem: Bad foo<{%reset%}>\n"))
  1352  	plan, res := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, validate)
  1353  	assert.Nil(t, plan)
  1354  	assert.Nil(t, res)
  1355  
  1356  	// Generate a plan with good inputs
  1357  	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
  1358  		"foo": "good",
  1359  	})
  1360  	plan, res = TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
  1361  	assert.NotNil(t, plan)
  1362  	assert.Contains(t, plan.ResourcePlans, resource.URN("urn:pulumi:test::test::pkgA:m:typA::resA"))
  1363  	assert.Nil(t, res)
  1364  
  1365  	// Try and run against the plan with inputs that will fail Check
  1366  	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
  1367  		"foo": "bad",
  1368  	})
  1369  	p.Options.Plan = plan.Clone()
  1370  	validate = ExpectDiagMessage(t, regexp.QuoteMeta(
  1371  		"<{%reset%}>pkgA:m:typA resource 'resA': property foo value {bad} has a problem: Bad foo<{%reset%}>\n"))
  1372  	snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, validate)
  1373  	assert.Nil(t, res)
  1374  	assert.NotNil(t, snap)
  1375  
  1376  	// Check the resource's state.
  1377  	if !assert.Len(t, snap.Resources, 1) {
  1378  		return
  1379  	}
  1380  }
  1381  
  1382  func TestPluginsAreDownloaded(t *testing.T) {
  1383  	t.Parallel()
  1384  
  1385  	loaders := []*deploytest.ProviderLoader{
  1386  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
  1387  			return &deploytest.Provider{}, nil
  1388  		}),
  1389  	}
  1390  
  1391  	semver10 := semver.MustParse("1.0.0")
  1392  
  1393  	program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
  1394  		_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{})
  1395  		assert.NoError(t, err)
  1396  		return nil
  1397  	}, workspace.PluginSpec{Name: "pkgA"}, workspace.PluginSpec{Name: "pkgB", Version: &semver10})
  1398  	host := deploytest.NewPluginHost(nil, nil, program, loaders...)
  1399  
  1400  	p := &TestPlan{
  1401  		Options: UpdateOptions{Host: host, GeneratePlan: true, Experimental: true},
  1402  	}
  1403  
  1404  	project := p.GetProject()
  1405  
  1406  	plan, res := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
  1407  	assert.NotNil(t, plan)
  1408  	assert.Contains(t, plan.ResourcePlans, resource.URN("urn:pulumi:test::test::pkgA:m:typA::resA"))
  1409  	assert.Nil(t, res)
  1410  }
  1411  
  1412  func TestProviderDeterministicPreview(t *testing.T) {
  1413  	t.Parallel()
  1414  
  1415  	var generatedName resource.PropertyValue
  1416  
  1417  	loaders := []*deploytest.ProviderLoader{
  1418  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
  1419  			return &deploytest.Provider{
  1420  				CheckF: func(
  1421  					urn resource.URN,
  1422  					olds, news resource.PropertyMap,
  1423  					randomSeed []byte) (resource.PropertyMap, []plugin.CheckFailure, error) {
  1424  					// make a deterministic autoname
  1425  					if _, has := news["name"]; !has {
  1426  						if name, has := olds["name"]; has {
  1427  							news["name"] = name
  1428  						} else {
  1429  							name, err := resource.NewUniqueName(randomSeed, urn.Name().String(), -1, -1, nil)
  1430  							assert.Nil(t, err)
  1431  							generatedName = resource.NewStringProperty(name)
  1432  							news["name"] = generatedName
  1433  						}
  1434  					}
  1435  
  1436  					return news, nil, nil
  1437  				},
  1438  				DiffF: func(
  1439  					urn resource.URN,
  1440  					id resource.ID,
  1441  					olds, news resource.PropertyMap,
  1442  					ignoreChanges []string) (plugin.DiffResult, error) {
  1443  					if !olds["foo"].DeepEquals(news["foo"]) {
  1444  						// If foo changes do a replace, we use this to check we get a new name
  1445  						return plugin.DiffResult{
  1446  							Changes:     plugin.DiffSome,
  1447  							ReplaceKeys: []resource.PropertyKey{"foo"},
  1448  						}, nil
  1449  					}
  1450  					return plugin.DiffResult{}, nil
  1451  				},
  1452  				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
  1453  					preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
  1454  					return "created-id", news, resource.StatusOK, nil
  1455  				},
  1456  				UpdateF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap, timeout float64,
  1457  					ignoreChanges []string, preview bool) (resource.PropertyMap, resource.Status, error) {
  1458  					return news, resource.StatusOK, nil
  1459  				},
  1460  			}, nil
  1461  		}, deploytest.WithoutGrpc),
  1462  	}
  1463  
  1464  	ins := resource.NewPropertyMapFromMap(map[string]interface{}{
  1465  		"foo": "bar",
  1466  	})
  1467  
  1468  	program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
  1469  		_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
  1470  			Inputs: ins,
  1471  		})
  1472  		assert.NoError(t, err)
  1473  		return nil
  1474  	})
  1475  	host := deploytest.NewPluginHost(nil, nil, program, loaders...)
  1476  
  1477  	p := &TestPlan{
  1478  		Options: UpdateOptions{Host: host, GeneratePlan: true, Experimental: true},
  1479  	}
  1480  
  1481  	project := p.GetProject()
  1482  
  1483  	// Run a preview, this should want to create resA with a given name
  1484  	plan, res := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
  1485  	assert.Nil(t, res)
  1486  	assert.True(t, generatedName.IsString())
  1487  	assert.NotEqual(t, "", generatedName.StringValue())
  1488  	expectedName := generatedName
  1489  
  1490  	// Run an update, we should get the same name as we saw in preview
  1491  	p.Options.Plan = plan
  1492  	snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
  1493  	assert.Nil(t, res)
  1494  	assert.NotNil(t, snap)
  1495  	assert.Len(t, snap.Resources, 2)
  1496  	assert.Equal(t, expectedName, snap.Resources[1].Inputs["name"])
  1497  	assert.Equal(t, expectedName, snap.Resources[1].Outputs["name"])
  1498  
  1499  	// Run a new update which will cause a replace and check we get a new name
  1500  	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
  1501  		"foo": "baz",
  1502  	})
  1503  	p.Options.Plan = nil
  1504  	snap, res = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil)
  1505  	assert.Nil(t, res)
  1506  	assert.NotNil(t, snap)
  1507  	assert.Len(t, snap.Resources, 2)
  1508  	assert.NotEqual(t, expectedName, snap.Resources[1].Inputs["name"])
  1509  	assert.NotEqual(t, expectedName, snap.Resources[1].Outputs["name"])
  1510  }
  1511  
  1512  func TestPlannedUpdateWithDependentDelete(t *testing.T) {
  1513  	t.Parallel()
  1514  
  1515  	var diffResult *plugin.DiffResult
  1516  
  1517  	loaders := []*deploytest.ProviderLoader{
  1518  		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
  1519  			return &deploytest.Provider{
  1520  				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
  1521  					preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
  1522  					return resource.ID("created-id-" + urn.Name()), news, resource.StatusOK, nil
  1523  				},
  1524  				UpdateF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap, timeout float64,
  1525  					ignoreChanges []string, preview bool) (resource.PropertyMap, resource.Status, error) {
  1526  					return news, resource.StatusOK, nil
  1527  				},
  1528  				CheckF: func(urn resource.URN,
  1529  					olds, news resource.PropertyMap, _ []byte) (resource.PropertyMap, []plugin.CheckFailure, error) {
  1530  					return news, nil, nil
  1531  				},
  1532  				DiffF: func(urn resource.URN,
  1533  					id resource.ID, olds, news resource.PropertyMap, ignoreChanges []string) (plugin.DiffResult, error) {
  1534  					if strings.Contains(string(urn), "resA") || strings.Contains(string(urn), "resB") {
  1535  						assert.NotNil(t, diffResult, "Diff was called but diffResult wasn't set")
  1536  						return *diffResult, nil
  1537  					}
  1538  					return plugin.DiffResult{}, nil
  1539  				},
  1540  			}, nil
  1541  		}),
  1542  	}
  1543  
  1544  	var ins resource.PropertyMap
  1545  	program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
  1546  		resA, _, outs, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
  1547  			Inputs: ins,
  1548  		})
  1549  		assert.NoError(t, err)
  1550  
  1551  		_, _, _, err = monitor.RegisterResource("pkgA:m:typB", "resB", true, deploytest.ResourceOptions{
  1552  			Inputs:       outs,
  1553  			Dependencies: []resource.URN{resA},
  1554  		})
  1555  		assert.NoError(t, err)
  1556  
  1557  		return nil
  1558  	})
  1559  	host := deploytest.NewPluginHost(nil, nil, program, loaders...)
  1560  
  1561  	p := &TestPlan{
  1562  		Options: UpdateOptions{Host: host, GeneratePlan: true},
  1563  	}
  1564  
  1565  	project := p.GetProject()
  1566  
  1567  	// Create an initial ResA and resB
  1568  	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
  1569  		"foo": "bar",
  1570  		"zed": "baz",
  1571  	})
  1572  	snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
  1573  	assert.NotNil(t, snap)
  1574  	assert.Nil(t, res)
  1575  
  1576  	// Update the input and mark it as a replace, check that both A and B are marked as replacements
  1577  	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
  1578  		"foo": "frob",
  1579  		"zed": "baz",
  1580  	})
  1581  	diffResult = &plugin.DiffResult{
  1582  		Changes:     plugin.DiffSome,
  1583  		ReplaceKeys: []resource.PropertyKey{"foo"},
  1584  		StableKeys:  []resource.PropertyKey{"zed"},
  1585  		DetailedDiff: map[string]plugin.PropertyDiff{
  1586  			"foo": {
  1587  				Kind:      plugin.DiffUpdateReplace,
  1588  				InputDiff: true,
  1589  			},
  1590  		},
  1591  		DeleteBeforeReplace: true,
  1592  	}
  1593  	plan, res := TestOp(Update).Plan(project, p.GetTarget(t, snap), p.Options, p.BackendClient, nil)
  1594  	assert.NotNil(t, plan)
  1595  	assert.Nil(t, res)
  1596  
  1597  	assert.Equal(t, 3, len(plan.ResourcePlans["urn:pulumi:test::test::pkgA:m:typA::resA"].Ops))
  1598  	assert.Equal(t, 3, len(plan.ResourcePlans["urn:pulumi:test::test::pkgA:m:typB::resB"].Ops))
  1599  
  1600  	// Now try and run with the plan
  1601  	p.Options.Plan = plan.Clone()
  1602  	snap, res = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil)
  1603  	assert.NotNil(t, snap)
  1604  	assert.Nil(t, res)
  1605  }