// Copyright 2016-2017, Pulumi Corporation. All rights reserved. package ints import ( "bytes" "regexp" "strings" "testing" "unicode" "github.com/pmezard/go-difflib/difflib" "github.com/pulumi/pulumi/pkg/resource" "github.com/pulumi/pulumi/pkg/testing/integration" "github.com/stretchr/testify/assert" ) // TestDiffs tests many combinations of creates, updates, deletes, replacements, and checks the // output of the command against an expected baseline. func TestDiffs(t *testing.T) { var buf bytes.Buffer opts := integration.ProgramTestOptions{ Dir: "step1", Dependencies: []string{"pulumi"}, Quick: true, StackName: "diffstack", UpdateCommandlineFlags: []string{"--color=raw"}, ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) { checkpoint := stack.Checkpoint assert.NotNil(t, checkpoint.Latest) assert.Equal(t, 5, len(checkpoint.Latest.Resources)) stackRes := checkpoint.Latest.Resources[0] assert.Equal(t, resource.RootStackType, stackRes.URN.Type()) a := checkpoint.Latest.Resources[1] assert.Equal(t, "a", string(a.URN.Name())) b := checkpoint.Latest.Resources[2] assert.Equal(t, "b", string(b.URN.Name())) c := checkpoint.Latest.Resources[3] assert.Equal(t, "c", string(c.URN.Name())) d := checkpoint.Latest.Resources[4] assert.Equal(t, "d", string(d.URN.Name())) }, EditDirs: []integration.EditDir{ { Dir: "step2", Additive: true, Stdout: &buf, Verbose: true, ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) { checkpoint := stack.Checkpoint assert.NotNil(t, checkpoint.Latest) assert.Equal(t, 5, len(checkpoint.Latest.Resources)) stackRes := checkpoint.Latest.Resources[0] assert.Equal(t, resource.RootStackType, stackRes.URN.Type()) a := checkpoint.Latest.Resources[1] assert.Equal(t, "a", string(a.URN.Name())) b := checkpoint.Latest.Resources[2] assert.Equal(t, "b", string(b.URN.Name())) c := checkpoint.Latest.Resources[3] assert.Equal(t, "c", string(c.URN.Name())) e := checkpoint.Latest.Resources[4] assert.Equal(t, "e", string(e.URN.Name())) expected := `Performing changes: * pulumi:pulumi:Stack: (same) [urn=urn:pulumi:diffstack::steps::pulumi:pulumi:Stack::steps-diffstack] + pulumi-nodejs:dynamic:Resource: (create) [urn=urn:pulumi:diffstack::steps::pulumi-nodejs:dynamic:Resource::e] __provider: "exports.handler = __d1295c56b890ca4312c6b6aec1efc37f1270220f;\n\nfunction __d1295c56b890ca4312c6b6aec1efc37f1270220f() {\n return (function() {\n with({ provider: { diff: __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44, create: __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9, update: __599534012ff37f9801d962f9c6059b4bc0778921, delete: __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543 } }) {\n\nreturn (() => provider)\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44() {\n return (function() {\n with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6 }) {\n\nreturn ((id, olds, news) => __awaiter(this, void 0, void 0, function* () {\n let replaces = [];\n if (olds.replace !== news.replace) {\n replaces.push(\"replace\");\n }\n return {\n replaces: replaces,\n };\n }))\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __492fe142c8be132f2ccfdc443ed720d77b1ef3a6() {\n return (function() {\n with({ }) {\n\nreturn (function (thisArg, _arguments, P, generator) {\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n})\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9() {\n return (function() {\n with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6, currentID: 0 }) {\n\nreturn ((inputs) => __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n return {\n id: (currentID++).toString(),\n outs: undefined,\n };\n }))\n\n }\n }).apply({ diff: __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44, create: __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9, update: __599534012ff37f9801d962f9c6059b4bc0778921, delete: __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543 }, undefined).apply(this, arguments);\n}\n\nfunction __599534012ff37f9801d962f9c6059b4bc0778921() {\n return (function() {\n with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6 }) {\n\nreturn ((id, olds, news) => __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n return {};\n }))\n\n }\n }).apply({ diff: __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44, create: __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9, update: __599534012ff37f9801d962f9c6059b4bc0778921, delete: __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543 }, undefined).apply(this, arguments);\n}\n\nfunction __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543() {\n return (function() {\n with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6 }) {\n\nreturn ((id, props) => __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n }))\n\n }\n }).apply({ diff: __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44, create: __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9, update: __599534012ff37f9801d962f9c6059b4bc0778921, delete: __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543 }, undefined).apply(this, arguments);\n}\n\n" - pulumi-nodejs:dynamic:Resource: (delete) [id=0] [urn=urn:pulumi:diffstack::steps::pulumi-nodejs:dynamic:Resource::d] __provider: "exports.handler = __d1295c56b890ca4312c6b6aec1efc37f1270220f;\n\nfunction __d1295c56b890ca4312c6b6aec1efc37f1270220f() {\n return (function() {\n with({ provider: { diff: __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44, create: __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9, update: __599534012ff37f9801d962f9c6059b4bc0778921, delete: __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543 } }) {\n\nreturn (() => provider)\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44() {\n return (function() {\n with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6 }) {\n\nreturn ((id, olds, news) => __awaiter(this, void 0, void 0, function* () {\n let replaces = [];\n if (olds.replace !== news.replace) {\n replaces.push(\"replace\");\n }\n return {\n replaces: replaces,\n };\n }))\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __492fe142c8be132f2ccfdc443ed720d77b1ef3a6() {\n return (function() {\n with({ }) {\n\nreturn (function (thisArg, _arguments, P, generator) {\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n})\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9() {\n return (function() {\n with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6, currentID: 0 }) {\n\nreturn ((inputs) => __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n return {\n id: (currentID++).toString(),\n outs: undefined,\n };\n }))\n\n }\n }).apply({ diff: __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44, create: __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9, update: __599534012ff37f9801d962f9c6059b4bc0778921, delete: __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543 }, undefined).apply(this, arguments);\n}\n\nfunction __599534012ff37f9801d962f9c6059b4bc0778921() {\n return (function() {\n with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6 }) {\n\nreturn ((id, olds, news) => __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n return {};\n }))\n\n }\n }).apply({ diff: __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44, create: __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9, update: __599534012ff37f9801d962f9c6059b4bc0778921, delete: __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543 }, undefined).apply(this, arguments);\n}\n\nfunction __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543() {\n return (function() {\n with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6 }) {\n\nreturn ((id, props) => __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n }))\n\n }\n }).apply({ diff: __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44, create: __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9, update: __599534012ff37f9801d962f9c6059b4bc0778921, delete: __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543 }, undefined).apply(this, arguments);\n}\n\n" info: 2 changes performed: + 1 resource created - 1 resource deleted 4 resources unchanged` assertPreviewOutput(t, expected, buf.String()) buf.Reset() }, }, { Dir: "step3", Additive: true, Stdout: &buf, Verbose: true, ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) { checkpoint := stack.Checkpoint assert.NotNil(t, checkpoint.Latest) assert.Equal(t, 4, len(checkpoint.Latest.Resources)) stackRes := checkpoint.Latest.Resources[0] assert.Equal(t, resource.RootStackType, stackRes.URN.Type()) a := checkpoint.Latest.Resources[1] assert.Equal(t, "a", string(a.URN.Name())) c := checkpoint.Latest.Resources[2] assert.Equal(t, "c", string(c.URN.Name())) e := checkpoint.Latest.Resources[3] assert.Equal(t, "e", string(e.URN.Name())) expected := `Performing changes: * pulumi:pulumi:Stack: (same) [urn=urn:pulumi:diffstack::steps::pulumi:pulumi:Stack::steps-diffstack] - pulumi-nodejs:dynamic:Resource: (delete) [id=0] [urn=urn:pulumi:diffstack::steps::pulumi-nodejs:dynamic:Resource::b] __provider: "exports.handler = __d1295c56b890ca4312c6b6aec1efc37f1270220f;\n\nfunction __d1295c56b890ca4312c6b6aec1efc37f1270220f() {\n return (function() {\n with({ provider: { diff: __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44, create: __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9, update: __599534012ff37f9801d962f9c6059b4bc0778921, delete: __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543 } }) {\n\nreturn (() => provider)\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44() {\n return (function() {\n with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6 }) {\n\nreturn ((id, olds, news) => __awaiter(this, void 0, void 0, function* () {\n let replaces = [];\n if (olds.replace !== news.replace) {\n replaces.push(\"replace\");\n }\n return {\n replaces: replaces,\n };\n }))\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __492fe142c8be132f2ccfdc443ed720d77b1ef3a6() {\n return (function() {\n with({ }) {\n\nreturn (function (thisArg, _arguments, P, generator) {\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n})\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9() {\n return (function() {\n with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6, currentID: 0 }) {\n\nreturn ((inputs) => __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n return {\n id: (currentID++).toString(),\n outs: undefined,\n };\n }))\n\n }\n }).apply({ diff: __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44, create: __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9, update: __599534012ff37f9801d962f9c6059b4bc0778921, delete: __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543 }, undefined).apply(this, arguments);\n}\n\nfunction __599534012ff37f9801d962f9c6059b4bc0778921() {\n return (function() {\n with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6 }) {\n\nreturn ((id, olds, news) => __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n return {};\n }))\n\n }\n }).apply({ diff: __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44, create: __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9, update: __599534012ff37f9801d962f9c6059b4bc0778921, delete: __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543 }, undefined).apply(this, arguments);\n}\n\nfunction __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543() {\n return (function() {\n with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6 }) {\n\nreturn ((id, props) => __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n }))\n\n }\n }).apply({ diff: __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44, create: __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9, update: __599534012ff37f9801d962f9c6059b4bc0778921, delete: __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543 }, undefined).apply(this, arguments);\n}\n\n" info: 1 change performed: - 1 resource deleted 4 resources unchanged` assertPreviewOutput(t, expected, buf.String()) buf.Reset() }, }, { Dir: "step4", Additive: true, Stdout: &buf, Verbose: true, ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) { checkpoint := stack.Checkpoint assert.NotNil(t, checkpoint.Latest) // assert.Equal(t, 5, len(checkpoint.Latest.Resources)) assert.Equal(t, 4, len(checkpoint.Latest.Resources)) stackRes := checkpoint.Latest.Resources[0] assert.Equal(t, resource.RootStackType, stackRes.URN.Type()) a := checkpoint.Latest.Resources[1] assert.Equal(t, "a", string(a.URN.Name())) c := checkpoint.Latest.Resources[2] assert.Equal(t, "c", string(c.URN.Name())) e := checkpoint.Latest.Resources[3] assert.Equal(t, "e", string(e.URN.Name())) // aPendingDelete := checkpoint.Latest.Resources[4] // assert.Equal(t, "a", string(aPendingDelete.URN.Name())) // assert.True(t, aPendingDelete.Delete) expected := `Performing changes: * pulumi:pulumi:Stack: (same) [urn=urn:pulumi:diffstack::steps::pulumi:pulumi:Stack::steps-diffstack] info: no changes required:` assertPreviewOutput(t, expected, buf.String()) buf.Reset() }, }, { Dir: "step5", Additive: true, Stdout: &buf, Verbose: true, ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) { checkpoint := stack.Checkpoint assert.NotNil(t, checkpoint.Latest) assert.Equal(t, 1, len(checkpoint.Latest.Resources)) stackRes := checkpoint.Latest.Resources[0] assert.Equal(t, resource.RootStackType, stackRes.URN.Type()) expected := `Performing changes: * pulumi:pulumi:Stack: (same) [urn=urn:pulumi:diffstack::steps::pulumi:pulumi:Stack::steps-diffstack] - pulumi-nodejs:dynamic:Resource: (delete) [id=0] [urn=urn:pulumi:diffstack::steps::pulumi-nodejs:dynamic:Resource::e] __provider: "exports.handler = __d1295c56b890ca4312c6b6aec1efc37f1270220f;\n\nfunction __d1295c56b890ca4312c6b6aec1efc37f1270220f() {\n return (function() {\n with({ provider: { diff: __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44, create: __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9, update: __599534012ff37f9801d962f9c6059b4bc0778921, delete: __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543 } }) {\n\nreturn (() => provider)\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44() {\n return (function() {\n with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6 }) {\n\nreturn ((id, olds, news) => __awaiter(this, void 0, void 0, function* () {\n let replaces = [];\n if (olds.replace !== news.replace) {\n replaces.push(\"replace\");\n }\n return {\n replaces: replaces,\n };\n }))\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __492fe142c8be132f2ccfdc443ed720d77b1ef3a6() {\n return (function() {\n with({ }) {\n\nreturn (function (thisArg, _arguments, P, generator) {\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n})\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9() {\n return (function() {\n with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6, currentID: 0 }) {\n\nreturn ((inputs) => __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n return {\n id: (currentID++).toString(),\n outs: undefined,\n };\n }))\n\n }\n }).apply({ diff: __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44, create: __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9, update: __599534012ff37f9801d962f9c6059b4bc0778921, delete: __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543 }, undefined).apply(this, arguments);\n}\n\nfunction __599534012ff37f9801d962f9c6059b4bc0778921() {\n return (function() {\n with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6 }) {\n\nreturn ((id, olds, news) => __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n return {};\n }))\n\n }\n }).apply({ diff: __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44, create: __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9, update: __599534012ff37f9801d962f9c6059b4bc0778921, delete: __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543 }, undefined).apply(this, arguments);\n}\n\nfunction __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543() {\n return (function() {\n with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6 }) {\n\nreturn ((id, props) => __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n }))\n\n }\n }).apply({ diff: __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44, create: __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9, update: __599534012ff37f9801d962f9c6059b4bc0778921, delete: __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543 }, undefined).apply(this, arguments);\n}\n\n" - pulumi-nodejs:dynamic:Resource: (delete) [id=0] [urn=urn:pulumi:diffstack::steps::pulumi-nodejs:dynamic:Resource::c] __provider: "exports.handler = __d1295c56b890ca4312c6b6aec1efc37f1270220f;\n\nfunction __d1295c56b890ca4312c6b6aec1efc37f1270220f() {\n return (function() {\n with({ provider: { diff: __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44, create: __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9, update: __599534012ff37f9801d962f9c6059b4bc0778921, delete: __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543 } }) {\n\nreturn (() => provider)\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44() {\n return (function() {\n with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6 }) {\n\nreturn ((id, olds, news) => __awaiter(this, void 0, void 0, function* () {\n let replaces = [];\n if (olds.replace !== news.replace) {\n replaces.push(\"replace\");\n }\n return {\n replaces: replaces,\n };\n }))\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __492fe142c8be132f2ccfdc443ed720d77b1ef3a6() {\n return (function() {\n with({ }) {\n\nreturn (function (thisArg, _arguments, P, generator) {\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n})\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9() {\n return (function() {\n with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6, currentID: 0 }) {\n\nreturn ((inputs) => __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n return {\n id: (currentID++).toString(),\n outs: undefined,\n };\n }))\n\n }\n }).apply({ diff: __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44, create: __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9, update: __599534012ff37f9801d962f9c6059b4bc0778921, delete: __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543 }, undefined).apply(this, arguments);\n}\n\nfunction __599534012ff37f9801d962f9c6059b4bc0778921() {\n return (function() {\n with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6 }) {\n\nreturn ((id, olds, news) => __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n return {};\n }))\n\n }\n }).apply({ diff: __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44, create: __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9, update: __599534012ff37f9801d962f9c6059b4bc0778921, delete: __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543 }, undefined).apply(this, arguments);\n}\n\nfunction __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543() {\n return (function() {\n with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6 }) {\n\nreturn ((id, props) => __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n }))\n\n }\n }).apply({ diff: __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44, create: __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9, update: __599534012ff37f9801d962f9c6059b4bc0778921, delete: __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543 }, undefined).apply(this, arguments);\n}\n\n" - pulumi-nodejs:dynamic:Resource: (delete) [id=0] [urn=urn:pulumi:diffstack::steps::pulumi-nodejs:dynamic:Resource::a] __provider: "exports.handler = __d1295c56b890ca4312c6b6aec1efc37f1270220f;\n\nfunction __d1295c56b890ca4312c6b6aec1efc37f1270220f() {\n return (function() {\n with({ provider: { diff: __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44, create: __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9, update: __599534012ff37f9801d962f9c6059b4bc0778921, delete: __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543 } }) {\n\nreturn (() => provider)\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44() {\n return (function() {\n with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6 }) {\n\nreturn ((id, olds, news) => __awaiter(this, void 0, void 0, function* () {\n let replaces = [];\n if (olds.replace !== news.replace) {\n replaces.push(\"replace\");\n }\n return {\n replaces: replaces,\n };\n }))\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __492fe142c8be132f2ccfdc443ed720d77b1ef3a6() {\n return (function() {\n with({ }) {\n\nreturn (function (thisArg, _arguments, P, generator) {\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n})\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9() {\n return (function() {\n with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6, currentID: 0 }) {\n\nreturn ((inputs) => __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n return {\n id: (currentID++).toString(),\n outs: undefined,\n };\n }))\n\n }\n }).apply({ diff: __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44, create: __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9, update: __599534012ff37f9801d962f9c6059b4bc0778921, delete: __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543 }, undefined).apply(this, arguments);\n}\n\nfunction __599534012ff37f9801d962f9c6059b4bc0778921() {\n return (function() {\n with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6 }) {\n\nreturn ((id, olds, news) => __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n return {};\n }))\n\n }\n }).apply({ diff: __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44, create: __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9, update: __599534012ff37f9801d962f9c6059b4bc0778921, delete: __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543 }, undefined).apply(this, arguments);\n}\n\nfunction __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543() {\n return (function() {\n with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6 }) {\n\nreturn ((id, props) => __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n }))\n\n }\n }).apply({ diff: __effb2ddb97a3dfc121870990c86c6ee8f7b1dc44, create: __e47b2d874cf3cf54cd5a54e8e0cf1c8a4a3a25e9, update: __599534012ff37f9801d962f9c6059b4bc0778921, delete: __6c325c14b9ed07e0c974ca9a339ed96d2b5dd543 }, undefined).apply(this, arguments);\n}\n\n" info: 3 changes performed: - 3 resources deleted 1 resource unchanged` assertPreviewOutput(t, expected, buf.String()) buf.Reset() }, }, }, } integration.ProgramTest(t, &opts) } func assertPreviewOutput(t *testing.T, expected, outputWithControlSeqeunces string) { // Remove the first and last lines. The first contains the local path that the test is running // in and last line contains the duration. lines := strings.Split(outputWithControlSeqeunces, "\n") outputWithControlSeqeunces = strings.Join(lines[1:len(lines)-2], "\n") assertProgramOutput(t, expected, outputWithControlSeqeunces) } func assertProgramOutput(t *testing.T, expected, outputWithControlSeqeunces string) { // Now convert all the color control sequences over to a simpler form for test baseline purposes. actual := convertControlSequences(outputWithControlSeqeunces) if expected != actual { diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ A: difflib.SplitLines(expected), B: difflib.SplitLines(actual), FromFile: "Expected", FromDate: "", ToFile: "Actual", ToDate: "", Context: 0, }) assert.Fail(t, "Difference between expected and actual:\n"+diff) } } type ColorEnum string const ( Clear ColorEnum = "clear" Unchanged ColorEnum = "unchanged" Added ColorEnum = "added" Removed ColorEnum = "removed" Info ColorEnum = "info" ) // convertControlSequences takes in the output of the pulumi update command (including color sequences) // and converts it to a simpler form that is easier to baseline. Control sequences, // like '<{%fg 2%}}', are converted to simpler code like , with reset controls closing those // tags. // // It's a lot of string munging, but makes it much easier to baseline and validate update diffs. func convertControlSequences(text string) string { getColor := func(startInclusive, endExclusive int) ColorEnum { switch text[startInclusive:endExclusive] { case "<{%reset%}>": return Clear case "<{%fg 1%}>": return Removed case "<{%fg 2%}>": return Added case "<{%fg 5%}>": return Info case "<{%fg 8%}>": return Unchanged default: panic("Unexpected match: " + text[startInclusive:endExclusive]) } } allWhitespace := func(startInclusive, endExclusive int) bool { for i := startInclusive; i < endExclusive; i++ { if !unicode.IsSpace(rune(text[i])) { return false } } return true } // Normalize all \r\n to \n's. it makes all the string processing we need to do much simpler. text = strings.Replace(text, "\r\n", "\n", -1) var result bytes.Buffer currentColor := Clear index := 0 anyTagRegex := regexp.MustCompile(`<\{.*?\}>`) allTagStartEndPairs := anyTagRegex.FindAllStringIndex(text, -1) for pairIndex, startEndPair := range allTagStartEndPairs { startInclusive := startEndPair[0] endExclusive := startEndPair[1] if startInclusive > index { result.WriteString(text[index:startInclusive]) } nextColor := getColor(startInclusive, endExclusive) index = endExclusive if nextColor == currentColor { // Ignore it if we see two of the same color in a row. continue } if nextColor == Clear { // ignore a clear if it's just followed by whitespace, and then a switch back to // our current color. i.e. something of the form: ... whitespace ... if pairIndex+1 < len(allTagStartEndPairs) { nextNextPair := allTagStartEndPairs[pairIndex+1] nextNextColor := getColor(nextNextPair[0], nextNextPair[1]) if nextNextColor == currentColor { if allWhitespace(endExclusive, nextNextPair[0]) { continue } } } result.WriteString("") } else { result.WriteString("<" + string(nextColor) + ">") } currentColor = nextColor } if index < len(text) { result.WriteString(text[index:]) } if currentColor != Clear { result.WriteString("") } taggedString := result.String() // We'll routinely end up with a line, followed by a newline, followed by and endtag (due to // reset chars being written after lines are written). To make this cleaner in the baseline // swap the two so the line ends with the endtag and then is followed by the newline.s r, _ := regexp.Compile(`(\n)(\<\/[a-z]+\>)`) replacedString := r.ReplaceAllString(taggedString, "$2$1") return replacedString }