pulumi/tests/integration/integration_test.go
Pat Gavlin 5c0b62e1aa
Serialize resource registration after inputs resolve. (#882)
As it stands, we serialize more than is correct when registering
resources: in addition to serializing the RegisterResource RPC, we also
wait for input properties to resolve in the same context. Unfortunately,
this means that we can create cycles in the promise graph when a
resource A is constructed in an earlier turn than some resource B and
one of B's output properties is an input to resource A. These changes
fix this issue by allowing input properties to resolve *before*
serializing the RegisterResource RPC.

Some integration tests had taken a dependency on the ordering of resources in
either the output of the `pulumi` command or the checkpoint file. The
only test that took a dependency on command output was updated s.t. its
resources have exactly one legal topographical sort (and therefore their
ordering is deterministic). The other tests were updated s.t. their
validation did not depend on resource ordering.
2018-02-05 16:29:20 -08:00

244 lines
8.3 KiB
Go

// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package ints
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/pulumi/pulumi/pkg/pack"
"github.com/pulumi/pulumi/pkg/resource"
ptesting "github.com/pulumi/pulumi/pkg/testing"
"github.com/pulumi/pulumi/pkg/testing/integration"
"github.com/pulumi/pulumi/pkg/tokens"
)
// TestProjectMain tests out the ability to override the main entrypoint.
func TestProjectMain(t *testing.T) {
var test integration.ProgramTestOptions
test = integration.ProgramTestOptions{
Dir: "project_main",
Dependencies: []string{"pulumi"},
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
// Simple runtime validation that just ensures the checkpoint was written and read.
assert.Equal(t, test.GetStackName(), stackInfo.Checkpoint.Stack)
},
}
integration.ProgramTest(t, &test)
t.Run("Error_AbsolutePath", func(t *testing.T) {
e := ptesting.NewEnvironment(t)
defer func() {
if !t.Failed() {
e.DeleteEnvironment()
}
}()
e.RunCommand("git", "init")
e.RunCommand("pulumi", "init")
e.ImportDirectory("project_main_abs")
e.RunCommand("pulumi", "stack", "init", "--local", "main-abs")
stdout, stderr := e.RunCommandExpectError("pulumi", "update")
assert.Equal(t, "", stdout)
assert.Contains(t, stderr, "project 'main' must be a relative path")
})
t.Run("Error_ParentFolder", func(t *testing.T) {
e := ptesting.NewEnvironment(t)
defer func() {
if !t.Failed() {
e.DeleteEnvironment()
}
}()
e.RunCommand("git", "init")
e.RunCommand("pulumi", "init")
e.ImportDirectory("project_main_parent")
e.RunCommand("pulumi", "stack", "init", "--local", "main-parent")
stdout, stderr := e.RunCommandExpectError("pulumi", "update")
assert.Equal(t, "", stdout)
assert.Contains(t, stderr, "project 'main' must be a subfolder")
})
}
// TestStackProjectName ensures we can read the Pulumi stack and project name from within the program.
func TestStackProjectName(t *testing.T) {
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: "stack_project_name",
Dependencies: []string{"pulumi"},
Quick: true,
})
}
// TestStackOutputs ensures we can export variables from a stack and have them get recorded as outputs.
func TestStackOutputs(t *testing.T) {
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: "stack_outputs",
Dependencies: []string{"pulumi"},
Quick: true,
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
// Ensure the checkpoint contains a single resource, the Stack, with two outputs.
assert.NotNil(t, stackInfo.Checkpoint.Latest)
if assert.Equal(t, 1, len(stackInfo.Checkpoint.Latest.Resources)) {
stackRes := stackInfo.Checkpoint.Latest.Resources[0]
assert.NotNil(t, stackRes)
assert.Equal(t, resource.RootStackType, stackRes.URN.Type())
assert.Equal(t, 0, len(stackRes.Inputs))
assert.Equal(t, 2, len(stackRes.Outputs))
assert.Equal(t, "ABC", stackRes.Outputs["xyz"])
assert.Equal(t, float64(42), stackRes.Outputs["foo"])
}
},
})
}
// TestStackParenting tests out that stacks and components are parented correctly.
func TestStackParenting(t *testing.T) {
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: "stack_parenting",
Dependencies: []string{"pulumi"},
Quick: true,
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
// Ensure the checkpoint contains resources parented correctly. This should look like this:
//
// A F
// / \ \
// B C G
// / \
// D E
//
// with the caveat, of course, that A and F will share a common parent, the implicit stack.
assert.NotNil(t, stackInfo.Checkpoint.Latest)
if assert.Equal(t, 8, len(stackInfo.Checkpoint.Latest.Resources)) {
stackRes := stackInfo.Checkpoint.Latest.Resources[0]
assert.NotNil(t, stackRes)
assert.Equal(t, resource.RootStackType, stackRes.Type)
assert.Equal(t, "", string(stackRes.Parent))
urns := make(map[string]resource.URN)
for _, res := range stackInfo.Checkpoint.Latest.Resources[1:] {
assert.NotNil(t, res)
urns[string(res.URN.Name())] = res.URN
switch res.URN.Name() {
case "a", "f":
assert.NotEqual(t, "", res.Parent)
assert.Equal(t, stackRes.URN, res.Parent)
case "b", "c":
assert.Equal(t, urns["a"], res.Parent)
case "d", "e":
assert.Equal(t, urns["c"], res.Parent)
case "g":
assert.Equal(t, urns["f"], res.Parent)
default:
t.Fatalf("unexpected name %s", res.URN.Name())
}
}
}
},
})
}
// TestConfigSave ensures that config commands in the Pulumi CLI work as expected.
func TestConfigSave(t *testing.T) {
e := ptesting.NewEnvironment(t)
defer func() {
if !t.Failed() {
e.DeleteEnvironment()
}
}()
// Initialize an empty stack.
pkgpath := filepath.Join(e.RootPath, "Pulumi.yaml")
err := pack.Save(pkgpath, &pack.Package{
Name: "testing-config",
Runtime: "nodejs",
})
assert.NoError(t, err)
e.RunCommand("git", "init")
e.RunCommand("pulumi", "init")
e.RunCommand("pulumi", "stack", "init", "--local", "testing-2")
e.RunCommand("pulumi", "stack", "init", "--local", "testing-1")
// Now configure and save a few different things:
// 1) do not save.
e.RunCommand("pulumi", "config", "set", "configA", "value1", "--save=false")
// 2) save to the project file, under the current stack.
e.RunCommand("pulumi", "config", "set", "configB", "value2")
// 3) save to the project file, underneath an entirely different stack.
e.RunCommand("pulumi", "config", "set", "configC", "value3", "--stack", "testing-2")
// 4) save to the project file, across all stacks.
e.RunCommand("pulumi", "config", "set", "configD", "value4", "--all")
// 5) save the same config key with a different value in the stack versus all stacks.
e.RunCommand("pulumi", "config", "set", "configE", "value55")
e.RunCommand("pulumi", "config", "set", "configE", "value66", "--all")
// Now read back the config using the CLI:
{
stdout, _ := e.RunCommand("pulumi", "config", "get", "configA")
assert.Equal(t, "value1\n", stdout)
}
{
stdout, _ := e.RunCommand("pulumi", "config", "get", "configB")
assert.Equal(t, "value2\n", stdout)
}
{
// config is in a different stack, should yield a stderr:
stdout, stderr := e.RunCommandExpectError("pulumi", "config", "get", "configC")
assert.Equal(t, "", stdout)
assert.NotEqual(t, "", stderr)
}
{
stdout, _ := e.RunCommand("pulumi", "config", "get", "configC", "--stack", "testing-2")
assert.Equal(t, "value3\n", stdout)
}
{
stdout, _ := e.RunCommand("pulumi", "config", "get", "configD")
assert.Equal(t, "value4\n", stdout)
}
{
stdout, _ := e.RunCommand("pulumi", "config", "get", "configE")
assert.Equal(t, "value55\n", stdout)
}
// Finally, check that the project file contains what we expected.
cfgkey := func(k string) tokens.ModuleMember { return tokens.ModuleMember("testing-config:config:" + k) }
pkg, err := pack.Load(pkgpath)
assert.NoError(t, err)
assert.Equal(t, 2, len(pkg.Config)) // --all
d, ok := pkg.Config[cfgkey("configD")]
assert.True(t, ok)
dv, err := d.Value(nil)
assert.NoError(t, err)
assert.Equal(t, "value4", dv)
ee, ok := pkg.Config[cfgkey("configE")]
assert.True(t, ok)
ev, err := ee.Value(nil)
assert.NoError(t, err)
assert.Equal(t, "value66", ev)
assert.Equal(t, 2, len(pkg.Stacks))
assert.Equal(t, 2, len(pkg.Stacks["testing-1"].Config))
b, ok := pkg.Stacks["testing-1"].Config[cfgkey("configB")]
assert.True(t, ok)
bv, err := b.Value(nil)
assert.NoError(t, err)
assert.Equal(t, "value2", bv)
e2, ok := pkg.Stacks["testing-1"].Config[cfgkey("configE")]
assert.True(t, ok)
e2v, err := e2.Value(nil)
assert.NoError(t, err)
assert.Equal(t, "value55", e2v)
assert.Equal(t, 1, len(pkg.Stacks["testing-2"].Config))
c, ok := pkg.Stacks["testing-2"].Config[cfgkey("configC")]
assert.True(t, ok)
cv, err := c.Value(nil)
assert.NoError(t, err)
assert.Equal(t, "value3", cv)
// We do not allow storing secrets for all stacks, since the encryption key for the secret is tied to the stack
e.RunCommandExpectError("pulumi", "config", "set", "secretA", "valueA", "--all", "--secret")
}