Regression tests for StackReference in the Python SDK (#3913)

* Make Python StackReference test similar to others (with two steps)
* Include new Python StackReference integration test that uses multiple stacks
* Expose various life cycle methods for ProgramTester
This commit is contained in:
Lee Zen 2020-02-17 10:40:46 -08:00 committed by GitHub
parent eee99e6011
commit f6402882c2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 271 additions and 120 deletions

View file

@ -519,35 +519,10 @@ func GetLogs(
return logs
}
// ProgramTest runs a lifecycle of Pulumi commands in a program working directory, using the `pulumi` and `yarn`
// binaries available on PATH. It essentially executes the following workflow:
//
// yarn install
// yarn link <each opts.Depencies>
// (+) yarn run build
// pulumi init
// (*) pulumi login
// pulumi stack init integrationtesting
// pulumi config set <each opts.Config>
// pulumi config set --secret <each opts.Secrets>
// pulumi preview
// pulumi up
// pulumi stack export --file stack.json
// pulumi stack import --file stack.json
// pulumi preview (expected to be empty)
// pulumi up (expected to be empty)
// pulumi destroy --yes
// pulumi stack rm --yes integrationtesting
//
// (*) Only if PULUMI_ACCESS_TOKEN is set.
// (+) Only if `opts.RunBuild` is true.
//
// All commands must return success return codes for the test to succeed, unless ExpectFailure is true.
func ProgramTest(t *testing.T, opts *ProgramTestOptions) {
func prepareProgram(t *testing.T, opts *ProgramTestOptions) {
// If we're just listing tests, simply print this test's directory.
if listDirs {
fmt.Printf("%s\n", opts.Dir)
return
}
// If we have a matcher, ensure that this test matches its pattern.
@ -595,12 +570,46 @@ func ProgramTest(t *testing.T, opts *ProgramTestOptions) {
if opts.Tracing == "" {
opts.Tracing = os.Getenv("PULUMI_TEST_TRACE_ENDPOINT")
}
}
// ProgramTest runs a lifecycle of Pulumi commands in a program working directory, using the `pulumi` and `yarn`
// binaries available on PATH. It essentially executes the following workflow:
//
// yarn install
// yarn link <each opts.Depencies>
// (+) yarn run build
// pulumi init
// (*) pulumi login
// pulumi stack init integrationtesting
// pulumi config set <each opts.Config>
// pulumi config set --secret <each opts.Secrets>
// pulumi preview
// pulumi up
// pulumi stack export --file stack.json
// pulumi stack import --file stack.json
// pulumi preview (expected to be empty)
// pulumi up (expected to be empty)
// pulumi destroy --yes
// pulumi stack rm --yes integrationtesting
//
// (*) Only if PULUMI_ACCESS_TOKEN is set.
// (+) Only if `opts.RunBuild` is true.
//
// All commands must return success return codes for the test to succeed, unless ExpectFailure is true.
func ProgramTest(t *testing.T, opts *ProgramTestOptions) {
prepareProgram(t, opts)
pt := newProgramTester(t, opts)
err := pt.testLifeCycleInitAndDestroy()
err := pt.TestLifeCycleInitAndDestroy()
assert.NoError(t, err)
}
// ProgramTestManualLifeCycle returns a ProgramTester than must be manually controlled in terms of its lifecycle
func ProgramTestManualLifeCycle(t *testing.T, opts *ProgramTestOptions) *ProgramTester {
prepareProgram(t, opts)
pt := newProgramTester(t, opts)
return pt
}
// fprintf works like fmt.FPrintf, except it explicitly drops the return values. This keeps the linters happy, since
// they don't like to see errors dropped on the floor. It is possible that our call to fmt.Fprintf will fail, even
// for "standard" streams like `stdout` and `stderr`, if they have been set to non-blocking by an external process.
@ -611,8 +620,8 @@ func fprintf(w io.Writer, format string, a ...interface{}) {
contract.IgnoreError(err)
}
// programTester contains state associated with running a single test pass.
type programTester struct {
// ProgramTester contains state associated with running a single test pass.
type ProgramTester struct {
t *testing.T // the Go tester for this run.
opts *ProgramTestOptions // options that control this test run.
bin string // the `pulumi` binary we are using.
@ -622,9 +631,12 @@ type programTester struct {
dotNetBin string // the `dotnet` binary we are using.
eventLog string // The path to the event log for this test.
maxStepTries int // The maximum number of times to retry a failed pulumi step.
tmpdir string // the temporary directory we use for our test environment
projdir string // the project directory we use for this run
TestFinished bool // whether or not the test if finished
}
func newProgramTester(t *testing.T, opts *ProgramTestOptions) *programTester {
func newProgramTester(t *testing.T, opts *ProgramTestOptions) *ProgramTester {
stackName := opts.GetStackName()
maxStepTries := 1
if opts.RetryFailedSteps {
@ -635,7 +647,7 @@ func newProgramTester(t *testing.T, opts *ProgramTestOptions) *programTester {
opts.SkipExportImport = true
opts.SkipEmptyPreviewUpdate = true
}
return &programTester{
return &ProgramTester{
t: t,
opts: opts,
eventLog: filepath.Join(os.TempDir(), string(stackName)+"-events.json"),
@ -643,28 +655,28 @@ func newProgramTester(t *testing.T, opts *ProgramTestOptions) *programTester {
}
}
func (pt *programTester) getBin() (string, error) {
func (pt *ProgramTester) getBin() (string, error) {
return getCmdBin(&pt.bin, "pulumi", pt.opts.Bin)
}
func (pt *programTester) getYarnBin() (string, error) {
func (pt *ProgramTester) getYarnBin() (string, error) {
return getCmdBin(&pt.yarnBin, "yarn", pt.opts.YarnBin)
}
func (pt *programTester) getGoBin() (string, error) {
func (pt *ProgramTester) getGoBin() (string, error) {
return getCmdBin(&pt.goBin, "go", pt.opts.GoBin)
}
// getPipenvBin returns a path to the currently-installed Pipenv tool, or an error if the tool could not be found.
func (pt *programTester) getPipenvBin() (string, error) {
func (pt *ProgramTester) getPipenvBin() (string, error) {
return getCmdBin(&pt.pipenvBin, "pipenv", pt.opts.PipenvBin)
}
func (pt *programTester) getDotNetBin() (string, error) {
func (pt *ProgramTester) getDotNetBin() (string, error) {
return getCmdBin(&pt.dotNetBin, "dotnet", pt.opts.DotNetBin)
}
func (pt *programTester) pulumiCmd(args []string) ([]string, error) {
func (pt *ProgramTester) pulumiCmd(args []string) ([]string, error) {
bin, err := pt.getBin()
if err != nil {
return nil, err
@ -680,7 +692,7 @@ func (pt *programTester) pulumiCmd(args []string) ([]string, error) {
return cmd, nil
}
func (pt *programTester) yarnCmd(args []string) ([]string, error) {
func (pt *ProgramTester) yarnCmd(args []string) ([]string, error) {
bin, err := pt.getYarnBin()
if err != nil {
return nil, err
@ -690,7 +702,7 @@ func (pt *programTester) yarnCmd(args []string) ([]string, error) {
return withOptionalYarnFlags(result), nil
}
func (pt *programTester) pipenvCmd(args []string) ([]string, error) {
func (pt *ProgramTester) pipenvCmd(args []string) ([]string, error) {
bin, err := pt.getPipenvBin()
if err != nil {
return nil, err
@ -700,11 +712,11 @@ func (pt *programTester) pipenvCmd(args []string) ([]string, error) {
return append(cmd, args...), nil
}
func (pt *programTester) runCommand(name string, args []string, wd string) error {
func (pt *ProgramTester) runCommand(name string, args []string, wd string) error {
return RunCommand(pt.t, name, args, wd, pt.opts)
}
func (pt *programTester) runPulumiCommand(name string, args []string, wd string, expectFailure bool) error {
func (pt *ProgramTester) runPulumiCommand(name string, args []string, wd string, expectFailure bool) error {
cmd, err := pt.pulumiCmd(args)
if err != nil {
return err
@ -769,7 +781,7 @@ func (pt *programTester) runPulumiCommand(name string, args []string, wd string,
return err
}
func (pt *programTester) runYarnCommand(name string, args []string, wd string) error {
func (pt *ProgramTester) runYarnCommand(name string, args []string, wd string) error {
cmd, err := pt.yarnCmd(args)
if err != nil {
return err
@ -796,7 +808,7 @@ func (pt *programTester) runYarnCommand(name string, args []string, wd string) e
return err
}
func (pt *programTester) runPipenvCommand(name string, args []string, wd string) error {
func (pt *ProgramTester) runPipenvCommand(name string, args []string, wd string) error {
// Pipenv uses setuptools to install and uninstall packages. Setuptools has an installation mode called "develop"
// that we use to install the package being tested, since it is 1) lightweight and 2) not doing so has its own set
// of annoying problems.
@ -820,7 +832,7 @@ func (pt *programTester) runPipenvCommand(name string, args []string, wd string)
// To avoid this problem, we use pipenvMutex to explicitly serialize installation operations. Doing so avoids the
// problem of multiple processes stomping on the same files in the source tree. Note that pipenvMutex is a file
// mutex, so this strategy works even if the go test runner chooses to split up text execution across multiple
// processes. (Furthermore, each test gets an instance of programTester and thus the mutex, so we'd need to be
// processes. (Furthermore, each test gets an instance of ProgramTester and thus the mutex, so we'd need to be
// sharing the mutex globally in each test process if we weren't using the file system to lock.)
if name == "pipenv-install-package" {
if err := pipenvMutex.Lock(); err != nil {
@ -846,69 +858,79 @@ func (pt *programTester) runPipenvCommand(name string, args []string, wd string)
return pt.runCommand(name, cmd, wd)
}
func (pt *programTester) testLifeCycleInitAndDestroy() error {
// TestLifeCyclePrepare prepares a test by creating a temporary directory
func (pt *ProgramTester) TestLifeCyclePrepare() error {
tmpdir, projdir, err := pt.copyTestToTemporaryDirectory()
pt.tmpdir = tmpdir
pt.projdir = projdir
return err
}
// TestCleanUp cleans up the temporary directory that a test used
func (pt *ProgramTester) TestCleanUp() {
testFinished := pt.TestFinished
if pt.tmpdir != "" {
if !testFinished || pt.t.Failed() {
// Test aborted or failed. Maybe copy to "failed tests" directory.
failedTestsDir := os.Getenv("PULUMI_FAILED_TESTS_DIR")
if failedTestsDir != "" {
dest := filepath.Join(failedTestsDir, pt.t.Name()+uniqueSuffix())
contract.IgnoreError(fsutil.CopyFile(dest, pt.tmpdir, nil))
}
} else {
contract.IgnoreError(os.RemoveAll(pt.tmpdir))
}
} else {
// When tmpdir is empty, we ran "in tree", which means we wrote output
// to the "command-output" folder in the projdir, and we should clean
// it up if the test passed
if testFinished && !pt.t.Failed() {
contract.IgnoreError(os.RemoveAll(filepath.Join(pt.projdir, commandOutputFolderName)))
}
}
}
// TestLifeCycleInitAndDestroy executes the test and cleans up
func (pt *ProgramTester) TestLifeCycleInitAndDestroy() error {
err := pt.TestLifeCyclePrepare()
if err != nil {
return errors.Wrap(err, "copying test to temp dir")
}
testFinished := false
defer func() {
if tmpdir != "" {
if !testFinished || pt.t.Failed() {
// Test aborted or failed. Maybe copy to "failed tests" directory.
failedTestsDir := os.Getenv("PULUMI_FAILED_TESTS_DIR")
if failedTestsDir != "" {
dest := filepath.Join(failedTestsDir, pt.t.Name()+uniqueSuffix())
contract.IgnoreError(fsutil.CopyFile(dest, tmpdir, nil))
}
} else {
contract.IgnoreError(os.RemoveAll(tmpdir))
}
} else {
// When tmpdir is empty, we ran "in tree", which means we wrote output
// to the "command-output" folder in the projdir, and we should clean
// it up if the test passed
if testFinished && !pt.t.Failed() {
contract.IgnoreError(os.RemoveAll(filepath.Join(projdir, commandOutputFolderName)))
}
}
}()
pt.TestFinished = false
defer pt.TestCleanUp()
err = pt.testLifeCycleInitialize(projdir)
err = pt.TestLifeCycleInitialize()
if err != nil {
return errors.Wrap(err, "initializing test project")
}
// Ensure that before we exit, we attempt to destroy and remove the stack.
defer func() {
if projdir != "" {
destroyErr := pt.testLifeCycleDestroy(projdir)
assert.NoError(pt.t, destroyErr)
}
destroyErr := pt.TestLifeCycleDestroy()
assert.NoError(pt.t, destroyErr)
}()
if err = pt.testPreviewUpdateAndEdits(projdir); err != nil {
if err = pt.TestPreviewUpdateAndEdits(); err != nil {
return errors.Wrap(err, "running test preview, update, and edits")
}
if pt.opts.RunUpdateTest {
err = upgradeProjectDeps(projdir, pt)
err = upgradeProjectDeps(pt.projdir, pt)
if err != nil {
return errors.Wrap(err, "upgrading project dependencies")
}
if err = pt.testPreviewUpdateAndEdits(projdir); err != nil {
if err = pt.TestPreviewUpdateAndEdits(); err != nil {
return errors.Wrap(err, "running test preview, update, and edits")
}
}
testFinished = true
pt.TestFinished = true
return nil
}
func upgradeProjectDeps(projectDir string, pt *programTester) error {
func upgradeProjectDeps(projectDir string, pt *ProgramTester) error {
projInfo, err := pt.getProjinfo(projectDir)
if err != nil {
return errors.Wrap(err, "getting project info")
@ -930,7 +952,9 @@ func upgradeProjectDeps(projectDir string, pt *programTester) error {
return nil
}
func (pt *programTester) testLifeCycleInitialize(dir string) error {
// TestLifeCycleInitialize initializes the project directory and stack along with any configuration
func (pt *ProgramTester) TestLifeCycleInitialize() error {
dir := pt.projdir
stackName := pt.opts.GetStackName()
// If RelativeWorkDir is specified, apply that relative to the temp folder for use as working directory during tests.
@ -1011,32 +1035,37 @@ func (pt *programTester) testLifeCycleInitialize(dir string) error {
return nil
}
func (pt *programTester) testLifeCycleDestroy(dir string) error {
// Destroy and remove the stack.
fprintf(pt.opts.Stdout, "Destroying stack\n")
destroy := []string{"destroy", "--non-interactive", "--skip-preview"}
if pt.opts.GetDebugUpdates() {
destroy = append(destroy, "-d")
}
if err := pt.runPulumiCommand("pulumi-destroy", destroy, dir, false); err != nil {
return err
}
// TestLifeCycleDestroy destroys a stack and removes it
func (pt *ProgramTester) TestLifeCycleDestroy() error {
if pt.projdir != "" {
// Destroy and remove the stack.
fprintf(pt.opts.Stdout, "Destroying stack\n")
destroy := []string{"destroy", "--non-interactive", "--skip-preview"}
if pt.opts.GetDebugUpdates() {
destroy = append(destroy, "-d")
}
if err := pt.runPulumiCommand("pulumi-destroy", destroy, pt.projdir, false); err != nil {
return err
}
if pt.t.Failed() {
fprintf(pt.opts.Stdout, "Test failed, retaining stack '%s'\n", pt.opts.GetStackNameWithOwner())
return nil
}
if pt.t.Failed() {
fprintf(pt.opts.Stdout, "Test failed, retaining stack '%s'\n", pt.opts.GetStackNameWithOwner())
return nil
}
if !pt.opts.SkipStackRemoval {
return pt.runPulumiCommand("pulumi-stack-rm", []string{"stack", "rm", "--yes"}, dir, false)
if !pt.opts.SkipStackRemoval {
return pt.runPulumiCommand("pulumi-stack-rm", []string{"stack", "rm", "--yes"}, pt.projdir, false)
}
}
return nil
}
func (pt *programTester) testPreviewUpdateAndEdits(dir string) error {
// TestPreviewUpdateAndEdits runs the preview, update, and any relevant edits
func (pt *ProgramTester) TestPreviewUpdateAndEdits() error {
dir := pt.projdir
// Now preview and update the real changes.
fprintf(pt.opts.Stdout, "Performing primary preview and update\n")
initErr := pt.previewAndUpdate(dir, "initial", pt.opts.ExpectFailure, false, false)
initErr := pt.PreviewAndUpdate(dir, "initial", pt.opts.ExpectFailure, false, false)
// If the initial preview/update failed, just exit without trying the rest (but make sure to destroy).
if initErr != nil {
@ -1058,7 +1087,7 @@ func (pt *programTester) testPreviewUpdateAndEdits(dir string) error {
msg = "(no changes expected)"
}
fprintf(pt.opts.Stdout, "Performing empty preview and update%s\n", msg)
if err := pt.previewAndUpdate(
if err := pt.PreviewAndUpdate(
dir, "empty", false, !pt.opts.AllowEmptyPreviewChanges, !pt.opts.AllowEmptyUpdateChanges); err != nil {
return err
@ -1088,7 +1117,7 @@ func (pt *programTester) testPreviewUpdateAndEdits(dir string) error {
return pt.testEdits(dir)
}
func (pt *programTester) exportImport(dir string) error {
func (pt *ProgramTester) exportImport(dir string) error {
exportCmd := []string{"stack", "export", "--file", "stack.json"}
importCmd := []string{"stack", "import", "--file", "stack.json"}
@ -1103,7 +1132,8 @@ func (pt *programTester) exportImport(dir string) error {
return pt.runPulumiCommand("pulumi-stack-import", importCmd, dir, false)
}
func (pt *programTester) previewAndUpdate(dir string, name string, shouldFail, expectNopPreview,
// PreviewAndUpdate runs pulumi preview followed by pulumi up
func (pt *ProgramTester) PreviewAndUpdate(dir string, name string, shouldFail, expectNopPreview,
expectNopUpdate bool) error {
preview := []string{"preview", "--non-interactive"}
@ -1155,7 +1185,7 @@ func (pt *programTester) previewAndUpdate(dir string, name string, shouldFail, e
return nil
}
func (pt *programTester) query(dir string, name string, shouldFail bool) error {
func (pt *ProgramTester) query(dir string, name string, shouldFail bool) error {
query := []string{"query", "--non-interactive"}
if pt.opts.GetDebugUpdates() {
@ -1182,7 +1212,7 @@ func (pt *programTester) query(dir string, name string, shouldFail bool) error {
return nil
}
func (pt *programTester) testEdits(dir string) error {
func (pt *ProgramTester) testEdits(dir string) error {
for i, edit := range pt.opts.EditDirs {
var err error
if err = pt.testEdit(dir, i, edit); err != nil {
@ -1192,7 +1222,7 @@ func (pt *programTester) testEdits(dir string) error {
return nil
}
func (pt *programTester) testEdit(dir string, i int, edit EditDir) error {
func (pt *ProgramTester) testEdit(dir string, i int, edit EditDir) error {
fprintf(pt.opts.Stdout, "Applying edit '%v' and rerunning preview and update\n", edit.Dir)
if edit.Additive {
@ -1286,7 +1316,7 @@ func (pt *programTester) testEdit(dir string, i int, edit EditDir) error {
}()
if !edit.QueryMode {
if err = pt.previewAndUpdate(dir, fmt.Sprintf("edit-%d", i),
if err = pt.PreviewAndUpdate(dir, fmt.Sprintf("edit-%d", i),
edit.ExpectFailure, edit.ExpectNoChanges, edit.ExpectNoChanges); err != nil {
return err
}
@ -1298,7 +1328,7 @@ func (pt *programTester) testEdit(dir string, i int, edit EditDir) error {
return pt.performExtraRuntimeValidation(edit.ExtraRuntimeValidation, dir)
}
func (pt *programTester) performExtraRuntimeValidation(
func (pt *ProgramTester) performExtraRuntimeValidation(
extraRuntimeValidation func(t *testing.T, stack RuntimeValidationStackInfo), dir string) error {
if extraRuntimeValidation == nil {
@ -1384,7 +1414,7 @@ func (pt *programTester) performExtraRuntimeValidation(
}
// copyTestToTemporaryDirectory creates a temporary directory to run the test in and copies the test to it.
func (pt *programTester) copyTestToTemporaryDirectory() (string, string, error) {
func (pt *ProgramTester) copyTestToTemporaryDirectory() (string, string, error) {
// Get the source dir and project info.
sourceDir := pt.opts.Dir
projinfo, err := pt.getProjinfo(sourceDir)
@ -1450,7 +1480,7 @@ func (pt *programTester) copyTestToTemporaryDirectory() (string, string, error)
return tmpdir, projdir, nil
}
func (pt *programTester) getProjinfo(projectDir string) (*engine.Projinfo, error) {
func (pt *ProgramTester) getProjinfo(projectDir string) (*engine.Projinfo, error) {
// Load up the package so we know things like what language the project is.
projfile := filepath.Join(projectDir, workspace.ProjectFile+".yaml")
proj, err := workspace.LoadProject(projfile)
@ -1461,7 +1491,7 @@ func (pt *programTester) getProjinfo(projectDir string) (*engine.Projinfo, error
}
// prepareProject runs setup necessary to get the project ready for `pulumi` commands.
func (pt *programTester) prepareProject(projinfo *engine.Projinfo) error {
func (pt *ProgramTester) prepareProject(projinfo *engine.Projinfo) error {
// Based on the language, invoke the right routine to prepare the target directory.
switch rt := projinfo.Proj.Runtime.Name(); rt {
case NodeJSRuntime:
@ -1478,7 +1508,7 @@ func (pt *programTester) prepareProject(projinfo *engine.Projinfo) error {
}
// prepareProjectDir runs setup necessary to get the project ready for `pulumi` commands.
func (pt *programTester) prepareProjectDir(projectDir string) error {
func (pt *ProgramTester) prepareProjectDir(projectDir string) error {
projinfo, err := pt.getProjinfo(projectDir)
if err != nil {
return err
@ -1487,7 +1517,7 @@ func (pt *programTester) prepareProjectDir(projectDir string) error {
}
// prepareNodeJSProject runs setup necessary to get a Node.js project ready for `pulumi` commands.
func (pt *programTester) prepareNodeJSProject(projinfo *engine.Projinfo) error {
func (pt *ProgramTester) prepareNodeJSProject(projinfo *engine.Projinfo) error {
if err := pulumi_testing.WriteYarnRCForTest(projinfo.Root); err != nil {
return err
}
@ -1586,7 +1616,7 @@ func writePackageJSON(pathToPackage string, metadata map[string]interface{}) err
}
// preparePythonProject runs setup necessary to get a Python project ready for `pulumi` commands.
func (pt *programTester) preparePythonProject(projinfo *engine.Projinfo) error {
func (pt *ProgramTester) preparePythonProject(projinfo *engine.Projinfo) error {
cwd, _, err := projinfo.GetPwdMain()
if err != nil {
return err
@ -1623,7 +1653,8 @@ func (pt *programTester) preparePythonProject(projinfo *engine.Projinfo) error {
return nil
}
func (pt *programTester) yarnLinkPackageDeps(cwd string) error {
// YarnLinkPackageDeps bring in package dependencies via yarn
func (pt *ProgramTester) yarnLinkPackageDeps(cwd string) error {
for _, dependency := range pt.opts.Dependencies {
if err := pt.runYarnCommand("yarn-link", []string{"link", dependency}, cwd); err != nil {
return err
@ -1633,7 +1664,8 @@ func (pt *programTester) yarnLinkPackageDeps(cwd string) error {
return nil
}
func (pt *programTester) installPipPackageDeps(cwd string) error {
// InstallPipPackageDeps brings in package dependencies via pip install
func (pt *ProgramTester) installPipPackageDeps(cwd string) error {
var err error
for _, dep := range pt.opts.Dependencies {
// If the given filepath isn't absolute, make it absolute. We're about to pass it to pipenv and pipenv is
@ -1655,7 +1687,7 @@ func (pt *programTester) installPipPackageDeps(cwd string) error {
}
// prepareGoProject runs setup necessary to get a Go project ready for `pulumi` commands.
func (pt *programTester) prepareGoProject(projinfo *engine.Projinfo) error {
func (pt *ProgramTester) prepareGoProject(projinfo *engine.Projinfo) error {
// Go programs are compiled, so we will compile the project first.
goBin, err := pt.getGoBin()
if err != nil {
@ -1688,7 +1720,7 @@ func (pt *programTester) prepareGoProject(projinfo *engine.Projinfo) error {
}
// prepareDotNetProject runs setup necessary to get a .NET project ready for `pulumi` commands.
func (pt *programTester) prepareDotNetProject(projinfo *engine.Projinfo) error {
func (pt *ProgramTester) prepareDotNetProject(projinfo *engine.Projinfo) error {
dotNetBin, err := pt.getDotNetBin()
if err != nil {
return errors.Wrap(err, "locating `dotnet` binary")

View file

@ -1146,10 +1146,76 @@ func TestStackReferencePython(t *testing.T) {
Config: map[string]string{
"org": os.Getenv("PULUMI_TEST_OWNER"),
},
EditDirs: []integration.EditDir{
{
Dir: "step1",
Additive: true,
},
{
Dir: "step2",
Additive: true,
},
},
}
integration.ProgramTest(t, opts)
}
func TestMultiStackReferencePython(t *testing.T) {
if runtime.GOOS == WindowsOS {
t.Skip("Temporarily skipping test on Windows - pulumi/pulumi#3811")
}
if owner := os.Getenv("PULUMI_TEST_OWNER"); owner == "" {
t.Skipf("Skipping: PULUMI_TEST_OWNER is not set")
}
// build a stack with an export
exporterOpts := &integration.ProgramTestOptions{
Dir: filepath.Join("stack_reference_multi", "python", "exporter"),
Dependencies: []string{
filepath.Join("..", "..", "sdk", "python", "env", "src"),
},
Quick: true,
Config: map[string]string{
"org": os.Getenv("PULUMI_TEST_OWNER"),
},
NoParallel: true,
}
// we're going to manually initialize and then defer the deletion of this stack
exporterPt := integration.ProgramTestManualLifeCycle(t, exporterOpts)
exporterPt.TestFinished = false
err := exporterPt.TestLifeCyclePrepare()
assert.NoError(t, err)
err = exporterPt.TestLifeCycleInitialize()
assert.NoError(t, err)
defer func() {
destroyErr := exporterPt.TestLifeCycleDestroy()
assert.NoError(t, destroyErr)
exporterPt.TestFinished = true
exporterPt.TestCleanUp()
}()
err = exporterPt.TestPreviewUpdateAndEdits()
assert.NoError(t, err)
exporterStackName := exporterOpts.GetStackName().String()
importerOpts := &integration.ProgramTestOptions{
Dir: filepath.Join("stack_reference_multi", "python", "importer"),
Dependencies: []string{
filepath.Join("..", "..", "sdk", "python", "env", "src"),
},
Quick: true,
Config: map[string]string{
"org": os.Getenv("PULUMI_TEST_OWNER"),
"exporter_stack_name": exporterStackName,
},
NoParallel: true,
}
integration.ProgramTest(t, importerOpts)
}
// Tests that stack references work in .NET.
func TestStackReferenceDotnet(t *testing.T) {
if runtime.GOOS == WindowsOS {

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2020, Pulumi Corporation. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -13,7 +13,4 @@
# limitations under the License.
import pulumi
config = pulumi.Config();
org = config.require('org');
slug = f"{org}/{pulumi.get_project()}/{pulumi.get_stack()}"
a = pulumi.StackReference(slug)
pulumi.export('val', ["a", "b"])

View file

@ -0,0 +1,15 @@
# Copyright 2020, Pulumi Corporation. All rights reserved.
import pulumi
config = pulumi.Config()
org = config.require('org')
slug = f"{org}/{pulumi.get_project()}/{pulumi.get_stack()}"
a = pulumi.StackReference(slug)
oldVal = a.get_output('val')
if len(oldVal) != 2 or oldVal[0] != 'a' or oldVal[1] != 'b':
raise Exception('Invalid result')
pulumi.export('val2', pulumi.Output.secret(['a', 'b']))

View file

@ -0,0 +1,18 @@
# Copyright 2020, Pulumi Corporation. All rights reserved.
import pulumi
config = pulumi.Config()
org = config.require('org')
slug = f"{org}/{pulumi.get_project()}/{pulumi.get_stack()}"
a = pulumi.StackReference(slug)
got_err = False
try:
a.get_output('val2')
except Exception:
got_err = True
if not got_err:
raise Exception('Expected to get error trying to read secret from stack reference.')

View file

@ -0,0 +1,2 @@
*.pyc
venv/

View file

@ -0,0 +1,3 @@
name: exporter
runtime: python
description: A minimal Python Pulumi program

View file

@ -0,0 +1,3 @@
import pulumi
pulumi.export('val', ["a", "b"])

View file

@ -0,0 +1,2 @@
*.pyc
venv/

View file

@ -0,0 +1,3 @@
name: importer
runtime: python
description: A minimal Python Pulumi program

View file

@ -0,0 +1,10 @@
import pulumi
config = pulumi.Config()
exporterStackName = config.require('exporter_stack_name')
org = config.require('org')
a = pulumi.StackReference(f'{org}/exporter/{exporterStackName}')
pulumi.export('val1', a.require_output('val'))
pulumi.export('val2', pulumi.Output.secret(['d', 'x']))