// Copyright 2016-2022, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package lifecycletest

import (
	"regexp"
	"strings"
	"testing"

	"github.com/blang/semver"

	"github.com/stretchr/testify/assert"

	"github.com/pulumi/pulumi/pkg/v3/display"
	. "github.com/pulumi/pulumi/pkg/v3/engine" //nolint:revive
	"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
	"github.com/pulumi/pulumi/pkg/v3/resource/deploy/deploytest"
	"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
	"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
)

func TestPlannedUpdate(t *testing.T) {
	t.Parallel()

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
					preview bool,
				) (resource.ID, resource.PropertyMap, resource.Status, error) {
					return "created-id", news, resource.StatusOK, nil
				},
			}, nil
		}),
	}

	var ins resource.PropertyMap
	programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		_, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
			Inputs: ins,
		})
		assert.NoError(t, err)
		return nil
	})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{
			HostF:         hostF,
			UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
		},
	}

	project := p.GetProject()

	// Generate a plan.
	computed := interface{}(resource.Computed{Element: resource.NewStringProperty("")})
	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
		"baz": map[string]interface{}{
			"a": 42,
			"b": computed,
		},
		"qux": []interface{}{
			computed,
			24,
		},
		"zed": computed,
	})
	plan, err := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
	assert.NoError(t, err)

	// Attempt to run an update using the plan.
	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
		"qux": []interface{}{
			"alpha",
			24,
		},
	})
	p.Options.Plan = plan.Clone()
	validate := ExpectDiagMessage(t, regexp.QuoteMeta(
		"<{%reset%}>resource urn:pulumi:test::test::pkgA:m:typA::resA violates plan: "+
			"properties changed: +-baz[{map[a:{42} b:output<string>{}]}], +-foo[{bar}]<{%reset%}>\n"))
	snap, err := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, validate)
	assert.NoError(t, err)

	// Check the resource's state.
	if !assert.Len(t, snap.Resources, 1) {
		return
	}

	// Change the provider's planned operation to a same step.
	// Remove the provider from the plan.
	plan.ResourcePlans["urn:pulumi:test::test::pulumi:providers:pkgA::default"].Ops = []display.StepOp{deploy.OpSame}

	// Attempt to run an update using the plan.
	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
		"baz": map[string]interface{}{
			"a": 42,
			"b": "alpha",
		},
		"qux": []interface{}{
			"beta",
			24,
		},
		"zed": "grr",
	})
	p.Options.Plan = plan.Clone()
	snap, err = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil)
	assert.NoError(t, err)

	// Check the resource's state.
	if !assert.Len(t, snap.Resources, 2) {
		return
	}

	expected := resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
		"baz": map[string]interface{}{
			"a": 42,
			"b": "alpha",
		},
		"qux": []interface{}{
			"beta",
			24,
		},
		"zed": "grr",
	})
	assert.Equal(t, expected, snap.Resources[1].Outputs)
}

func TestUnplannedCreate(t *testing.T) {
	t.Parallel()

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
					preview bool,
				) (resource.ID, resource.PropertyMap, resource.Status, error) {
					return "created-id", news, resource.StatusOK, nil
				},
			}, nil
		}),
	}

	ins := resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
	})
	createResource := false
	programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		if createResource {
			_, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
				Inputs: ins,
			})
			assert.NoError(t, err)
		}
		return nil
	})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{
			HostF:         hostF,
			UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
		},
	}

	project := p.GetProject()

	// Create a plan to do nothing
	plan, err := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
	assert.NoError(t, err)

	// Now set the flag for the language runtime to create a resource, and run update with the plan
	createResource = true
	p.Options.Plan = plan.Clone()
	validate := ExpectDiagMessage(t, regexp.QuoteMeta(
		"<{%reset%}>create is not allowed by the plan: no steps were expected for this resource<{%reset%}>\n"))
	snap, err := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, validate)
	assert.NoError(t, err)

	// Check nothing was was created
	assert.NotNil(t, snap)
	if !assert.Len(t, snap.Resources, 0) {
		return
	}
}

func TestUnplannedDelete(t *testing.T) {
	t.Parallel()

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
					preview bool,
				) (resource.ID, resource.PropertyMap, resource.Status, error) {
					return resource.ID("created-id-" + urn.Name()), news, resource.StatusOK, nil
				},
				DeleteF: func(
					urn resource.URN,
					id resource.ID,
					oldInputs, oldOutputs resource.PropertyMap,
					timeout float64,
				) (resource.Status, error) {
					return resource.StatusOK, nil
				},
			}, nil
		}),
	}

	ins := resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
	})
	createAllResources := true
	programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		_, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
			Inputs: ins,
		})
		assert.NoError(t, err)

		if createAllResources {
			_, _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resB", true, deploytest.ResourceOptions{
				Inputs: ins,
			})
			assert.NoError(t, err)
		}

		return nil
	})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{
			HostF:         hostF,
			UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
		},
	}

	project := p.GetProject()

	// Create an initial snapshot that resA and resB exist
	snap, err := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
	assert.NoError(t, err)

	// Create a plan that resA and resB won't change
	plan, err := TestOp(Update).Plan(project, p.GetTarget(t, snap), p.Options, p.BackendClient, nil)
	assert.NoError(t, err)

	// Now set the flag for the language runtime to not create resB and run an update with
	// the no-op plan, this should block the delete
	createAllResources = false
	p.Options.Plan = plan.Clone()
	validate := ExpectDiagMessage(t, regexp.QuoteMeta(
		"<{%reset%}>delete is not allowed by the plan: this resource is constrained to same<{%reset%}>\n"))
	snap, err = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, validate)
	assert.NotNil(t, snap)
	assert.NoError(t, err)

	// Check both resources and the provider are still listed in the snapshot
	if !assert.Len(t, snap.Resources, 3) {
		return
	}
}

func TestExpectedDelete(t *testing.T) {
	t.Parallel()

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
					preview bool,
				) (resource.ID, resource.PropertyMap, resource.Status, error) {
					return resource.ID("created-id-" + urn.Name()), news, resource.StatusOK, nil
				},
				DeleteF: func(
					urn resource.URN,
					id resource.ID,
					oldInputs, oldOutputs resource.PropertyMap,
					timeout float64,
				) (resource.Status, error) {
					return resource.StatusOK, nil
				},
			}, nil
		}),
	}

	ins := resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
	})
	createAllResources := true
	programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		_, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
			Inputs: ins,
		})
		assert.NoError(t, err)

		if createAllResources {
			_, _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resB", true, deploytest.ResourceOptions{
				Inputs: ins,
			})
			assert.NoError(t, err)
		}

		return nil
	})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{
			HostF:         hostF,
			UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
		},
	}

	project := p.GetProject()

	// Create an initial snapshot that resA and resB exist
	snap, err := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
	assert.NotNil(t, snap)
	assert.NoError(t, err)

	// Create a plan that resA is same and resB is deleted
	createAllResources = false
	plan, err := TestOp(Update).Plan(project, p.GetTarget(t, snap), p.Options, p.BackendClient, nil)
	assert.NotNil(t, plan)
	assert.NoError(t, err)

	// Now run but set the runtime to return resA and resB, given we expected resB to be deleted
	// this should be an error
	createAllResources = true
	p.Options.Plan = plan.Clone()
	validate := ExpectDiagMessage(t, regexp.QuoteMeta(
		"<{%reset%}>resource urn:pulumi:test::test::pkgA:m:typA::resB violates plan: "+
			"resource unexpectedly not deleted<{%reset%}>\n"))
	snap, err = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, validate)
	assert.NotNil(t, snap)
	assert.NoError(t, err)

	// Check both resources and the provider are still listed in the snapshot
	if !assert.Len(t, snap.Resources, 3) {
		return
	}
}

func TestExpectedCreate(t *testing.T) {
	t.Parallel()

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
					preview bool,
				) (resource.ID, resource.PropertyMap, resource.Status, error) {
					return resource.ID("created-id-" + urn.Name()), news, resource.StatusOK, nil
				},
			}, nil
		}),
	}

	ins := resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
	})
	createAllResources := false
	programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		_, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
			Inputs: ins,
		})
		assert.NoError(t, err)

		if createAllResources {
			_, _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resB", true, deploytest.ResourceOptions{
				Inputs: ins,
			})
			assert.NoError(t, err)
		}

		return nil
	})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{
			HostF:         hostF,
			UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
		},
	}

	project := p.GetProject()

	// Create an initial snapshot that resA exists
	snap, err := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
	assert.NotNil(t, snap)
	assert.NoError(t, err)

	// Create a plan that resA is same and resB is created
	createAllResources = true
	plan, err := TestOp(Update).Plan(project, p.GetTarget(t, snap), p.Options, p.BackendClient, nil)
	assert.NotNil(t, plan)
	assert.NoError(t, err)

	// Now run but set the runtime to return resA, given we expected resB to be created
	// this should be an error
	createAllResources = false
	p.Options.Plan = plan.Clone()
	validate := ExpectDiagMessage(t, regexp.QuoteMeta(
		"<{%reset%}>expected resource operations for "+
			"urn:pulumi:test::test::pkgA:m:typA::resB but none were seen<{%reset%}>\n"))
	snap, err = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, validate)
	assert.NotNil(t, snap)
	assert.NoError(t, err)

	// Check resA and the provider are still listed in the snapshot
	if !assert.Len(t, snap.Resources, 2) {
		return
	}
}

func TestPropertySetChange(t *testing.T) {
	t.Parallel()

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
					preview bool,
				) (resource.ID, resource.PropertyMap, resource.Status, error) {
					return resource.ID("created-id-" + urn.Name()), news, resource.StatusOK, nil
				},
			}, nil
		}),
	}

	ins := resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo":  "bar",
		"frob": "baz",
	})
	programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		_, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
			Inputs: ins,
		})
		assert.NoError(t, err)

		return nil
	})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{
			HostF:         hostF,
			UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
		},
	}

	project := p.GetProject()

	// Create an initial plan to create resA
	plan, err := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
	assert.NotNil(t, plan)
	assert.NoError(t, err)

	// Now change the runtime to not return property "frob", this should error
	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
	})
	p.Options.Plan = plan.Clone()
	validate := ExpectDiagMessage(t, regexp.QuoteMeta(
		"<{%reset%}>resource urn:pulumi:test::test::pkgA:m:typA::resA violates plan: "+
			"properties changed: +-frob[{baz}]<{%reset%}>\n"))
	snap, err := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, validate)
	assert.NotNil(t, snap)
	assert.NoError(t, err)
}

func TestExpectedUnneededCreate(t *testing.T) {
	t.Parallel()

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
					preview bool,
				) (resource.ID, resource.PropertyMap, resource.Status, error) {
					return resource.ID("created-id-" + urn.Name()), news, resource.StatusOK, nil
				},
			}, nil
		}),
	}

	ins := resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
	})
	programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		_, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
			Inputs: ins,
		})
		assert.NoError(t, err)

		return nil
	})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{
			HostF:         hostF,
			UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
		},
	}

	project := p.GetProject()

	// Create a plan that resA needs creating
	plan, err := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
	assert.NotNil(t, plan)
	assert.NoError(t, err)

	// Create an a snapshot that resA exists
	snap, err := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
	assert.NotNil(t, snap)
	assert.NoError(t, err)

	// Now run again with the plan set but the snapshot that resA already exists
	p.Options.Plan = plan.Clone()
	snap, err = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil)
	assert.NotNil(t, snap)
	assert.NoError(t, err)

	// Check resA and the provider are still listed in the snapshot
	if !assert.Len(t, snap.Resources, 2) {
		return
	}
}

func TestExpectedUnneededDelete(t *testing.T) {
	t.Parallel()

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
					preview bool,
				) (resource.ID, resource.PropertyMap, resource.Status, error) {
					return resource.ID("created-id-" + urn.Name()), news, resource.StatusOK, nil
				},
				DeleteF: func(
					urn resource.URN,
					id resource.ID,
					oldInputs, oldOutputs resource.PropertyMap,
					timeout float64,
				) (resource.Status, error) {
					return resource.StatusOK, nil
				},
			}, nil
		}),
	}

	ins := resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
	})
	createResource := true
	programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		if createResource {
			_, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
				Inputs: ins,
			})
			assert.NoError(t, err)
		}

		return nil
	})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{
			HostF:         hostF,
			UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
		},
	}

	project := p.GetProject()

	// Create an initial snapshot that resA exists
	snap, err := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
	assert.NoError(t, err)

	// Create a plan that resA is deleted
	createResource = false
	plan, err := TestOp(Update).Plan(project, p.GetTarget(t, snap), p.Options, p.BackendClient, nil)
	assert.NoError(t, err)

	// Now run to delete resA
	snap, err = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil)
	assert.NotNil(t, snap)
	assert.NoError(t, err)

	// Now run again with the plan set but the snapshot that resA is already deleted
	p.Options.Plan = plan.Clone()
	snap, err = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil)
	assert.NotNil(t, snap)
	assert.NoError(t, err)

	// Check the resources are still gone
	if !assert.Len(t, snap.Resources, 0) {
		return
	}
}

func TestResoucesWithSames(t *testing.T) {
	t.Parallel()

	// This test checks that if between generating a constraint and running the update that if new resources have been
	// added to the stack that the update doesn't change those resources in any way that they don't cause constraint
	// errors.

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
					preview bool,
				) (resource.ID, resource.PropertyMap, resource.Status, error) {
					return "created-id", news, resource.StatusOK, nil
				},
			}, nil
		}),
	}

	var ins resource.PropertyMap
	createA := false
	createB := false
	programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		if createA {
			_, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
				Inputs: ins,
			})
			assert.NoError(t, err)
		}

		if createB {
			_, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resB", true, deploytest.ResourceOptions{
				Inputs: resource.NewPropertyMapFromMap(map[string]interface{}{
					"X": "Y",
				}),
			})
			assert.NoError(t, err)
		}
		return nil
	})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{
			HostF:         hostF,
			UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
		},
	}

	project := p.GetProject()

	// Generate a plan to create A
	createA = true
	createB = false
	computed := interface{}(resource.Computed{Element: resource.NewStringProperty("")})
	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
		"zed": computed,
	})
	plan, err := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
	assert.NoError(t, err)

	// Run an update that creates B
	createA = false
	createB = true
	snap, err := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
	assert.NoError(t, err)

	// Check the resource's state.
	if !assert.Len(t, snap.Resources, 2) {
		return
	}

	expected := resource.NewPropertyMapFromMap(map[string]interface{}{
		"X": "Y",
	})
	assert.Equal(t, expected, snap.Resources[1].Outputs)

	// Attempt to run an update with the plan on the stack that creates A and sames B
	createA = true
	createB = true
	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
		"zed": 24,
	})
	p.Options.Plan = plan.Clone()
	snap, err = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil)
	assert.NoError(t, err)

	// Check the resource's state.
	if !assert.Len(t, snap.Resources, 3) {
		return
	}

	expected = resource.NewPropertyMapFromMap(map[string]interface{}{
		"X": "Y",
	})
	assert.Equal(t, expected, snap.Resources[2].Outputs)

	expected = resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
		"zed": 24,
	})
	assert.Equal(t, expected, snap.Resources[1].Outputs)
}

func TestPlannedPreviews(t *testing.T) {
	t.Parallel()

	// This checks that plans work in previews, this is very similar to TestPlannedUpdate except we only do previews

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
					preview bool,
				) (resource.ID, resource.PropertyMap, resource.Status, error) {
					return "created-id", news, resource.StatusOK, nil
				},
			}, nil
		}),
	}

	var ins resource.PropertyMap
	programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		_, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
			Inputs: ins,
		})
		assert.NoError(t, err)
		return nil
	})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{
			HostF:         hostF,
			UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
		},
	}

	project := p.GetProject()

	// Generate a plan.
	computed := interface{}(resource.Computed{Element: resource.NewStringProperty("")})
	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
		"baz": map[string]interface{}{
			"a": 42,
			"b": computed,
		},
		"qux": []interface{}{
			computed,
			24,
		},
		"zed": computed,
	})
	plan, err := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
	assert.NoError(t, err)

	// Attempt to run a new preview using the plan, given we've changed the property set this should fail
	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
		"qux": []interface{}{
			"alpha",
			24,
		},
	})
	p.Options.Plan = plan.Clone()
	validate := ExpectDiagMessage(t, regexp.QuoteMeta(
		"<{%reset%}>resource urn:pulumi:test::test::pkgA:m:typA::resA violates plan: properties changed: "+
			"+-baz[{map[a:{42} b:output<string>{}]}], +-foo[{bar}]<{%reset%}>\n"))
	_, err = TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, validate)
	assert.NoError(t, err)

	// Attempt to run an preview using the plan, such that the property set is now valid
	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
		"baz": map[string]interface{}{
			"a": 42,
			"b": computed,
		},
		"qux": []interface{}{
			"beta",
			24,
		},
		"zed": "grr",
	})
	p.Options.Plan = plan.Clone()
	_, err = TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
	assert.NoError(t, err)
}

func TestPlannedUpdateChangedStack(t *testing.T) {
	t.Parallel()

	// This tests the case that we run a planned update against a stack that has changed between preview and update

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
					preview bool,
				) (resource.ID, resource.PropertyMap, resource.Status, error) {
					return "created-id", news, resource.StatusOK, nil
				},
			}, nil
		}),
	}

	var ins resource.PropertyMap
	programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		_, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
			Inputs: ins,
		})
		assert.NoError(t, err)
		return nil
	})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{
			HostF:         hostF,
			UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
		},
	}

	project := p.GetProject()

	// Set initial data for foo and zed
	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
		"zed": 24,
	})
	snap, err := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
	assert.NoError(t, err)

	// Generate a plan that we want to change foo
	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "baz",
		"zed": 24,
	})
	plan, err := TestOp(Update).Plan(project, p.GetTarget(t, snap), p.Options, p.BackendClient, nil)
	assert.NoError(t, err)

	// Change zed in the stack
	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
		"zed": 26,
	})
	snap, err = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil)
	assert.NoError(t, err)

	// Attempt to run an update using the plan but where we haven't updated our program for the change of zed
	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "baz",
		"zed": 24,
	})
	p.Options.Plan = plan.Clone()
	validate := ExpectDiagMessage(t, regexp.QuoteMeta(
		"<{%reset%}>resource urn:pulumi:test::test::pkgA:m:typA::resA violates plan: "+
			"properties changed: =~zed[{24}]<{%reset%}>\n"))
	snap, err = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, validate)
	assert.NoError(t, err)

	// Check the resource's state we shouldn't of changed anything because the update failed
	if !assert.Len(t, snap.Resources, 2) {
		return
	}

	expected := resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
		"zed": 26,
	})
	assert.Equal(t, expected, snap.Resources[1].Outputs)
}

func TestPlannedOutputChanges(t *testing.T) {
	t.Parallel()

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
					preview bool,
				) (resource.ID, resource.PropertyMap, resource.Status, error) {
					return resource.ID("created-id-" + urn.Name()), news, resource.StatusOK, nil
				},
			}, nil
		}),
	}

	outs := resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo":  "bar",
		"frob": "baz",
	})
	programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		urn, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{})
		assert.NoError(t, err)

		err = monitor.RegisterResourceOutputs(urn, outs)
		assert.NoError(t, err)

		return nil
	})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{
			HostF:         hostF,
			UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
		},
	}

	project := p.GetProject()

	// Create an initial plan to create resA and the outputs
	plan, err := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
	assert.NotNil(t, plan)
	assert.NoError(t, err)

	// Now change the runtime to not return property "frob", this should error
	outs = resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
	})
	p.Options.Plan = plan.Clone()
	validate := ExpectDiagMessage(t, regexp.QuoteMeta(
		"<{%reset%}>resource violates plan: properties changed: +-frob[{baz}]<{%reset%}>\n"))
	snap, err := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, validate)
	assert.NotNil(t, snap)
	assert.NoError(t, err)
}

func TestPlannedInputOutputDifferences(t *testing.T) {
	t.Parallel()

	// This tests that plans are working on the program inputs, not the provider outputs

	createOutputs := resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo":  "bar",
		"frob": "baz",
		"baz":  24,
	})
	updateOutputs := resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo":  "bar",
		"frob": "newBazzer",
		"baz":  24,
	})

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
					preview bool,
				) (resource.ID, resource.PropertyMap, resource.Status, error) {
					return resource.ID("created-id-" + urn.Name()), createOutputs, resource.StatusOK, nil
				},
				UpdateF: func(urn resource.URN, id resource.ID,
					oldInputs, oldOutputs, newInputs resource.PropertyMap,
					timeout float64, ignoreChanges []string, preview bool,
				) (resource.PropertyMap, resource.Status, error) {
					return updateOutputs, resource.StatusOK, nil
				},
			}, nil
		}),
	}

	inputs := resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo":  "bar",
		"frob": "baz",
	})
	programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		_, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
			Inputs: inputs,
		})
		assert.NoError(t, err)

		return nil
	})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{
			HostF:         hostF,
			UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
		},
	}

	project := p.GetProject()

	// Create an initial plan to create resA
	plan, err := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
	assert.NotNil(t, plan)
	assert.NoError(t, err)

	// Check we can create resA even though its outputs are different to the planned inputs
	p.Options.Plan = plan.Clone()
	snap, err := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
	assert.NotNil(t, snap)
	assert.NoError(t, err)

	// Make a plan to change resA
	inputs = resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo":  "bar",
		"frob": "newBazzer",
	})
	p.Options.Plan = nil
	plan, err = TestOp(Update).Plan(project, p.GetTarget(t, snap), p.Options, p.BackendClient, nil)
	assert.NotNil(t, plan)
	assert.NoError(t, err)

	// Test the plan fails if we don't pass newBazzer
	inputs = resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo":  "bar",
		"frob": "differentBazzer",
	})
	p.Options.Plan = plan.Clone()
	validate := ExpectDiagMessage(t, regexp.QuoteMeta(
		"<{%reset%}>resource urn:pulumi:test::test::pkgA:m:typA::resA violates plan: "+
			"properties changed: ~~frob[{newBazzer}!={differentBazzer}]<{%reset%}>\n"))
	snap, err = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, validate)
	assert.NotNil(t, snap)
	assert.NoError(t, err)

	// Check the plan succeeds if we do pass newBazzer
	inputs = resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo":  "bar",
		"frob": "newBazzer",
	})
	p.Options.Plan = plan.Clone()
	snap, err = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil)
	assert.NotNil(t, snap)
	assert.NoError(t, err)
}

func TestAliasWithPlans(t *testing.T) {
	t.Parallel()

	// This tests that if a resource has an alias the plan for it is still used

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
					preview bool,
				) (resource.ID, resource.PropertyMap, resource.Status, error) {
					return resource.ID("created-id-" + urn.Name()), news, resource.StatusOK, nil
				},
			}, nil
		}),
	}

	resourceName := "resA"
	var aliases []resource.URN
	ins := resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo":  "bar",
		"frob": "baz",
	})
	programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		_, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", resourceName, true, deploytest.ResourceOptions{
			Inputs:    ins,
			AliasURNs: aliases,
		})
		assert.NoError(t, err)

		return nil
	})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{
			HostF:         hostF,
			UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
		},
	}

	project := p.GetProject()

	// Create an initial ResA
	snap, err := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
	assert.NotNil(t, snap)
	assert.NoError(t, err)

	// Update the name and alias and make a plan for resA
	resourceName = "newResA"
	aliases = make([]resource.URN, 1)
	aliases[0] = resource.URN("urn:pulumi:test::test::pkgA:m:typA::resA")
	plan, err := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
	assert.NotNil(t, plan)
	assert.NoError(t, err)

	// Now try and run with the plan
	p.Options.Plan = plan.Clone()
	snap, err = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil)
	assert.NotNil(t, snap)
	assert.NoError(t, err)
}

func TestComputedCanBeDropped(t *testing.T) {
	t.Parallel()

	// This tests that values that show as <computed> in the plan can be dropped in the update (because they may of
	// resolved to undefined). We're testing both RegisterResource and RegisterResourceOutputs here.

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
					preview bool,
				) (resource.ID, resource.PropertyMap, resource.Status, error) {
					return resource.ID("created-id-" + urn.Name()), news, resource.StatusOK, nil
				},
			}, nil
		}),
	}

	var resourceInputs resource.PropertyMap
	programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		urn, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{})
		assert.NoError(t, err)

		_, _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resB", true, deploytest.ResourceOptions{
			Inputs: resourceInputs,
		})
		assert.NoError(t, err)

		// We're using the same property set on purpose, this is not a test bug
		err = monitor.RegisterResourceOutputs(urn, resourceInputs)
		assert.NoError(t, err)

		return nil
	})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{
			HostF:         hostF,
			UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
		},
	}

	project := p.GetProject()

	// The three property sets we'll use in this test
	computed := interface{}(resource.Computed{Element: resource.NewStringProperty("")})
	computedPropertySet := resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
		"baz": map[string]interface{}{
			"a": 42,
			"b": computed,
		},
		"qux": []interface{}{
			computed,
			24,
		},
		"zed": computed,
	})
	fullPropertySet := resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
		"baz": map[string]interface{}{
			"a": 42,
			"b": "alpha",
		},
		"qux": []interface{}{
			"beta",
			24,
		},
		"zed": "grr",
	})
	partialPropertySet := resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
		"baz": map[string]interface{}{
			"a": 42,
		},
		"qux": []interface{}{
			nil, // computed values that resolve to undef don't get dropped from arrays, they just become null
			24,
		},
	})

	// Generate a plan.
	resourceInputs = computedPropertySet
	plan, err := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
	assert.NoError(t, err)

	// Attempt to run an update using the plan with all computed values removed
	resourceInputs = partialPropertySet
	p.Options.Plan = plan.Clone()
	snap, err := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
	assert.NoError(t, err)

	// Check the resource's state.
	if !assert.Len(t, snap.Resources, 3) {
		return
	}

	assert.Equal(t, partialPropertySet, snap.Resources[1].Outputs)
	assert.Equal(t, partialPropertySet, snap.Resources[2].Outputs)

	// Now run an update to set the values of the computed properties...
	resourceInputs = fullPropertySet
	p.Options.Plan = nil
	snap, err = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil)
	assert.NoError(t, err)

	// Check the resource's state.
	if !assert.Len(t, snap.Resources, 3) {
		return
	}

	assert.Equal(t, fullPropertySet, snap.Resources[1].Outputs)
	assert.Equal(t, fullPropertySet, snap.Resources[2].Outputs)

	// ...and then build a new plan where they're computed updates (vs above where its computed creates)
	resourceInputs = computedPropertySet
	plan, err = TestOp(Update).Plan(project, p.GetTarget(t, snap), p.Options, p.BackendClient, nil)
	assert.NoError(t, err)

	// Now run the an update with the plan and check the update is allowed to remove these properties
	resourceInputs = partialPropertySet
	p.Options.Plan = plan.Clone()
	snap, err = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil)
	assert.NoError(t, err)

	// Check the resource's state.
	if !assert.Len(t, snap.Resources, 3) {
		return
	}

	assert.Equal(t, partialPropertySet, snap.Resources[1].Outputs)
	assert.Equal(t, partialPropertySet, snap.Resources[2].Outputs)
}

func TestPlannedUpdateWithNondeterministicCheck(t *testing.T) {
	t.Parallel()

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
					preview bool,
				) (resource.ID, resource.PropertyMap, resource.Status, error) {
					return resource.ID("created-id-" + urn.Name()), news, resource.StatusOK, nil
				},
				CheckF: func(urn resource.URN,
					olds, news resource.PropertyMap, _ []byte,
				) (resource.PropertyMap, []plugin.CheckFailure, error) {
					// If we have name use it, else use olds name, else make one up
					if _, has := news["name"]; has {
						return news, nil, nil
					}
					if _, has := olds["name"]; has {
						result := news.Copy()
						result["name"] = olds["name"]
						return result, nil, nil
					}

					name, err := resource.NewUniqueHex(urn.Name(), 8, 512)
					assert.NoError(t, err)

					result := news.Copy()
					result["name"] = resource.NewStringProperty(name)
					return result, nil, nil
				},
			}, nil
		}),
	}

	var ins resource.PropertyMap
	programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		_, _, outs, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
			Inputs: ins,
		})
		assert.NoError(t, err)

		_, _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resB", true, deploytest.ResourceOptions{
			Inputs: resource.NewPropertyMapFromMap(map[string]interface{}{
				"other": outs["name"].StringValue(),
			}),
		})
		assert.NoError(t, err)

		return nil
	})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{
			HostF:         hostF,
			UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
		},
	}

	project := p.GetProject()

	// Generate a plan.
	computed := interface{}(resource.Computed{Element: resource.NewStringProperty("")})
	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
		"zed": computed,
	})
	plan, err := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
	assert.NoError(t, err)

	// Attempt to run an update using the plan.
	// This should fail because of the non-determinism
	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
		"zed": "baz",
	})
	p.Options.Plan = plan.Clone()

	validate := ExpectDiagMessage(t,
		"<{%reset%}>resource urn:pulumi:test::test::pkgA:m:typA::resA violates plan: "+
			"properties changed: \\+\\+name\\[{res[\\d\\w]{9}}!={res[\\d\\w]{9}}\\]<{%reset%}>\\n")
	snap, err := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, validate)
	assert.NoError(t, err)

	// Check the resource's state.
	if !assert.Len(t, snap.Resources, 1) {
		return
	}
}

func TestPlannedUpdateWithCheckFailure(t *testing.T) {
	// Regression test for https://github.com/pulumi/pulumi/issues/9247

	t.Parallel()

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
					preview bool,
				) (resource.ID, resource.PropertyMap, resource.Status, error) {
					return "created-id", news, resource.StatusOK, nil
				},
				CheckF: func(urn resource.URN, olds, news resource.PropertyMap,
					randomSeed []byte,
				) (resource.PropertyMap, []plugin.CheckFailure, error) {
					if news["foo"].StringValue() == "bad" {
						return nil, []plugin.CheckFailure{
							{Property: resource.PropertyKey("foo"), Reason: "Bad foo"},
						}, nil
					}
					return news, nil, nil
				},
			}, nil
		}),
	}

	var ins resource.PropertyMap
	programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		_, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
			Inputs: ins,
		})
		assert.NoError(t, err)
		return nil
	})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{
			HostF:         hostF,
			UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
		},
	}

	project := p.GetProject()

	// Generate a plan with bad inputs
	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bad",
	})
	validate := ExpectDiagMessage(t, regexp.QuoteMeta(
		"<{%reset%}>pkgA:m:typA resource 'resA': property foo value {bad} has a problem: Bad foo<{%reset%}>\n"))
	plan, err := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, validate)
	assert.Nil(t, plan)
	assert.NoError(t, err)

	// Generate a plan with good inputs
	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "good",
	})
	plan, err = TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
	assert.NotNil(t, plan)
	assert.Contains(t, plan.ResourcePlans, resource.URN("urn:pulumi:test::test::pkgA:m:typA::resA"))
	assert.NoError(t, err)

	// Try and run against the plan with inputs that will fail Check
	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bad",
	})
	p.Options.Plan = plan.Clone()
	validate = ExpectDiagMessage(t, regexp.QuoteMeta(
		"<{%reset%}>pkgA:m:typA resource 'resA': property foo value {bad} has a problem: Bad foo<{%reset%}>\n"))
	snap, err := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, validate)
	assert.NoError(t, err)
	assert.NotNil(t, snap)

	// Check the resource's state.
	if !assert.Len(t, snap.Resources, 1) {
		return
	}
}

func TestPluginsAreDownloaded(t *testing.T) {
	t.Parallel()

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{}, nil
		}),
	}

	semver10 := semver.MustParse("1.0.0")

	programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		_, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{})
		assert.NoError(t, err)
		return nil
	}, workspace.PluginSpec{Name: "pkgA"}, workspace.PluginSpec{Name: "pkgB", Version: &semver10})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{
			HostF:         hostF,
			UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
		},
	}

	project := p.GetProject()

	plan, err := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
	assert.NotNil(t, plan)
	assert.Contains(t, plan.ResourcePlans, resource.URN("urn:pulumi:test::test::pkgA:m:typA::resA"))
	assert.NoError(t, err)
}

func TestProviderDeterministicPreview(t *testing.T) {
	t.Parallel()

	var generatedName resource.PropertyValue

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				CheckF: func(
					urn resource.URN,
					olds, news resource.PropertyMap,
					randomSeed []byte,
				) (resource.PropertyMap, []plugin.CheckFailure, error) {
					// make a deterministic autoname
					if _, has := news["name"]; !has {
						if name, has := olds["name"]; has {
							news["name"] = name
						} else {
							name, err := resource.NewUniqueName(randomSeed, urn.Name(), -1, -1, nil)
							assert.NoError(t, err)
							generatedName = resource.NewStringProperty(name)
							news["name"] = generatedName
						}
					}

					return news, nil, nil
				},
				DiffF: func(
					urn resource.URN,
					id resource.ID,
					oldInputs, oldOutputs, newInputs resource.PropertyMap,
					ignoreChanges []string,
				) (plugin.DiffResult, error) {
					if !oldOutputs["foo"].DeepEquals(newInputs["foo"]) {
						// If foo changes do a replace, we use this to check we get a new name
						return plugin.DiffResult{
							Changes:     plugin.DiffSome,
							ReplaceKeys: []resource.PropertyKey{"foo"},
						}, nil
					}
					return plugin.DiffResult{}, nil
				},
				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
					preview bool,
				) (resource.ID, resource.PropertyMap, resource.Status, error) {
					return "created-id", news, resource.StatusOK, nil
				},
			}, nil
		}, deploytest.WithoutGrpc),
	}

	ins := resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
	})

	programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		_, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
			Inputs: ins,
		})
		assert.NoError(t, err)
		return nil
	})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{
			HostF:         hostF,
			UpdateOptions: UpdateOptions{GeneratePlan: true, Experimental: true},
		},
	}

	project := p.GetProject()

	// Run a preview, this should want to create resA with a given name
	plan, err := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
	assert.NoError(t, err)
	assert.True(t, generatedName.IsString())
	assert.NotEqual(t, "", generatedName.StringValue())
	expectedName := generatedName

	// Run an update, we should get the same name as we saw in preview
	p.Options.Plan = plan
	snap, err := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
	assert.NoError(t, err)
	assert.NotNil(t, snap)
	assert.Len(t, snap.Resources, 2)
	assert.Equal(t, expectedName, snap.Resources[1].Inputs["name"])
	assert.Equal(t, expectedName, snap.Resources[1].Outputs["name"])

	// Run a new update which will cause a replace and check we get a new name
	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "baz",
	})
	p.Options.Plan = nil
	snap, err = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil)
	assert.NoError(t, err)
	assert.NotNil(t, snap)
	assert.Len(t, snap.Resources, 2)
	assert.NotEqual(t, expectedName, snap.Resources[1].Inputs["name"])
	assert.NotEqual(t, expectedName, snap.Resources[1].Outputs["name"])
}

func TestPlannedUpdateWithDependentDelete(t *testing.T) {
	t.Parallel()

	var diffResult *plugin.DiffResult

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{
				CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
					preview bool,
				) (resource.ID, resource.PropertyMap, resource.Status, error) {
					return resource.ID("created-id-" + urn.Name()), news, resource.StatusOK, nil
				},
				CheckF: func(urn resource.URN,
					olds, news resource.PropertyMap, _ []byte,
				) (resource.PropertyMap, []plugin.CheckFailure, error) {
					return news, nil, nil
				},
				DiffF: func(urn resource.URN,
					id resource.ID, oldInputs, oldOutputs, newInputs resource.PropertyMap, ignoreChanges []string,
				) (plugin.DiffResult, error) {
					if strings.Contains(string(urn), "resA") || strings.Contains(string(urn), "resB") {
						assert.NotNil(t, diffResult, "Diff was called but diffResult wasn't set")
						return *diffResult, nil
					}
					return plugin.DiffResult{}, nil
				},
			}, nil
		}),
	}

	var ins resource.PropertyMap
	programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		resA, _, outs, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
			Inputs: ins,
		})
		assert.NoError(t, err)

		_, _, _, _, err = monitor.RegisterResource("pkgA:m:typB", "resB", true, deploytest.ResourceOptions{
			Inputs:       outs,
			Dependencies: []resource.URN{resA},
		})
		assert.NoError(t, err)

		return nil
	})
	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{
		Options: TestUpdateOptions{HostF: hostF, UpdateOptions: UpdateOptions{GeneratePlan: true}},
	}

	project := p.GetProject()

	// Create an initial ResA and resB
	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "bar",
		"zed": "baz",
	})
	snap, err := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
	assert.NotNil(t, snap)
	assert.NoError(t, err)

	// Update the input and mark it as a replace, check that both A and B are marked as replacements
	ins = resource.NewPropertyMapFromMap(map[string]interface{}{
		"foo": "frob",
		"zed": "baz",
	})
	diffResult = &plugin.DiffResult{
		Changes:     plugin.DiffSome,
		ReplaceKeys: []resource.PropertyKey{"foo"},
		StableKeys:  []resource.PropertyKey{"zed"},
		DetailedDiff: map[string]plugin.PropertyDiff{
			"foo": {
				Kind:      plugin.DiffUpdateReplace,
				InputDiff: true,
			},
		},
		DeleteBeforeReplace: true,
	}
	plan, err := TestOp(Update).Plan(project, p.GetTarget(t, snap), p.Options, p.BackendClient, nil)
	assert.NotNil(t, plan)
	assert.NoError(t, err)

	assert.Equal(t, 3, len(plan.ResourcePlans["urn:pulumi:test::test::pkgA:m:typA::resA"].Ops))
	assert.Equal(t, 3, len(plan.ResourcePlans["urn:pulumi:test::test::pkgA:m:typB::resB"].Ops))

	// Now try and run with the plan
	p.Options.Plan = plan.Clone()
	snap, err = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil)
	assert.NotNil(t, snap)
	assert.NoError(t, err)
}

// TestResourcesTargeted checks that a plan created with targets specified captures only those targets and
// default providers. It checks that trying to construct a new resource that was not targeted in the plan
// fails and that the update with the same --targets specified is compatible with the plan (roundtripped).
func TestResoucesTargeted(t *testing.T) {
	t.Parallel()

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{}, nil
		}),
	}

	programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		_, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
			Inputs: resource.PropertyMap{
				"foo": resource.NewStringProperty("bar"),
			},
		})
		assert.NoError(t, err)

		_, _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resB", true, deploytest.ResourceOptions{
			Inputs: resource.PropertyMap{
				"foo": resource.NewStringProperty("bar"),
			},
		})
		assert.NoError(t, err)

		return nil
	})

	hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	p := &TestPlan{}

	project := p.GetProject()

	// Create the update plan with only targeted resources.
	plan, err := TestOp(Update).Plan(project, p.GetTarget(t, nil), TestUpdateOptions{
		HostF: hostF,
		UpdateOptions: UpdateOptions{
			Experimental: true,
			GeneratePlan: true,
			Targets: deploy.NewUrnTargets([]string{
				"urn:pulumi:test::test::pkgA:m:typA::resB",
			}),
		},
	}, p.BackendClient, nil)
	assert.NoError(t, err)
	assert.NotNil(t, plan)

	// Check that running an update with everything targeted fails due to our plan being constrained
	// to the resource.
	_, err = TestOp(Update).Run(project, p.GetTarget(t, nil), TestUpdateOptions{
		HostF: hostF,
		UpdateOptions: UpdateOptions{
			// Clone the plan as the plan will be mutated by the engine and useless in future runs.
			Plan:         plan.Clone(),
			Experimental: true,
		},
	}, false, p.BackendClient, nil)
	assert.Error(t, err)

	// Check that running an update with the same Targets as the Plan succeeds.
	_, err = TestOp(Update).Run(project, p.GetTarget(t, nil), TestUpdateOptions{
		HostF: hostF,
		UpdateOptions: UpdateOptions{
			// Clone the plan as the plan will be mutated by the engine and useless in future runs.
			Plan:         plan.Clone(),
			Experimental: true,
			Targets: deploy.NewUrnTargets([]string{
				"urn:pulumi:test::test::pkgA:m:typA::resB",
			}),
		},
	}, false, p.BackendClient, nil)
	assert.NoError(t, err)
}

// This test checks that registering resource outputs does not fail for the root stack resource when it
// is not specified by the update targets.
func TestStackOutputsWithTargetedPlan(t *testing.T) {
	t.Parallel()

	p := &TestPlan{}

	loaders := []*deploytest.ProviderLoader{
		deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
			return &deploytest.Provider{}, nil
		}),
	}

	programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
		stackURN, _, _, _, err := monitor.RegisterResource("pulumi:pulumi:Stack", "test-test", false)
		assert.NoError(t, err)

		_, _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true)

		assert.NoError(t, err)

		err = monitor.RegisterResourceOutputs(stackURN, resource.PropertyMap{
			"foo": resource.NewStringProperty("bar"),
		})

		assert.NoError(t, err)

		return nil
	})

	p.Options.HostF = deploytest.NewPluginHostF(nil, nil, programF, loaders...)

	project := p.GetProject()

	// Create the update plan without targeting the root stack.
	plan, err := TestOp(Update).Plan(project, p.GetTarget(t, nil), TestUpdateOptions{
		HostF: p.Options.HostF,
		UpdateOptions: UpdateOptions{
			Experimental: true,
			GeneratePlan: true,
			Targets: deploy.NewUrnTargetsFromUrns([]resource.URN{
				resource.URN("urn:pulumi:test::test::pkgA:m:typA::resA"),
			}),
		},
	}, p.BackendClient, nil)
	assert.NoError(t, err)
	assert.NotNil(t, plan)

	// Check that update succeeds despite the root stack not being targeted.
	_, err = TestOp(Update).Run(project, p.GetTarget(t, nil), TestUpdateOptions{
		HostF: p.Options.HostF,
		UpdateOptions: UpdateOptions{
			GeneratePlan: true,
			Experimental: true,
			Targets: deploy.NewUrnTargetsFromUrns([]resource.URN{
				resource.URN("urn:pulumi:test::test::pkgA:m:typA::resA"),
			}),
		},
	}, false, p.BackendClient, nil)
	assert.NoError(t, err)
}