mirror of https://github.com/pulumi/pulumi.git
756 lines
24 KiB
Go
756 lines
24 KiB
Go
// 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.
|
|
|
|
//go:build python || all
|
|
|
|
package ints
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/pulumi/pulumi/pkg/v3/testing/integration"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
|
ptesting "github.com/pulumi/pulumi/sdk/v3/go/common/testing"
|
|
"github.com/pulumi/pulumi/sdk/v3/python/toolchain"
|
|
)
|
|
|
|
func boolPointer(b bool) *bool {
|
|
return &b
|
|
}
|
|
|
|
// TestEmptyPython simply tests that we can run an empty Python project.
|
|
//
|
|
//nolint:paralleltest // ProgramTest calls t.Parallel()
|
|
func TestEmptyPython(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("empty", "python"),
|
|
Dependencies: []string{
|
|
filepath.Join("..", "..", "sdk", "python", "env", "src"),
|
|
},
|
|
Quick: true,
|
|
})
|
|
}
|
|
|
|
// Tests dynamic provider in Python.
|
|
//
|
|
//nolint:paralleltest // ProgramTest calls t.Parallel()
|
|
func TestDynamicPython(t *testing.T) {
|
|
var randomVal string
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("dynamic", "python"),
|
|
Dependencies: []string{
|
|
filepath.Join("..", "..", "sdk", "python", "env", "src"),
|
|
},
|
|
ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
|
|
randomVal = stack.Outputs["random_val"].(string)
|
|
},
|
|
EditDirs: []integration.EditDir{{
|
|
Dir: filepath.Join("dynamic", "python", "step1"),
|
|
Additive: true,
|
|
ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
|
|
assert.Equal(t, randomVal, stack.Outputs["random_val"].(string))
|
|
|
|
// Regression testing the workaround for https://github.com/pulumi/pulumi/issues/8265
|
|
// Ensure the __provider input and output was marked secret
|
|
assertIsSecret := func(v interface{}) {
|
|
switch v := v.(type) {
|
|
case string:
|
|
assert.Fail(t, "__provider was not a secret")
|
|
case map[string]interface{}:
|
|
assert.Equal(t, resource.SecretSig, v[resource.SigKey])
|
|
}
|
|
}
|
|
|
|
dynRes := stack.Deployment.Resources[2]
|
|
assertIsSecret(dynRes.Inputs["__provider"])
|
|
assertIsSecret(dynRes.Outputs["__provider"])
|
|
|
|
// Ensure there are no diagnostic events other than debug.
|
|
for _, event := range stack.Events {
|
|
if event.DiagnosticEvent != nil {
|
|
assert.Equal(t, "debug", event.DiagnosticEvent.Severity,
|
|
"unexpected diagnostic event: %#v", event.DiagnosticEvent)
|
|
}
|
|
}
|
|
},
|
|
}},
|
|
UseSharedVirtualEnv: boolPointer(false),
|
|
})
|
|
}
|
|
|
|
// Test remote component construction in Python.
|
|
func TestConstructPython(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testDir := "construct_component"
|
|
runComponentSetup(t, testDir)
|
|
|
|
tests := []struct {
|
|
componentDir string
|
|
expectedResourceCount int
|
|
env []string
|
|
}{
|
|
{
|
|
componentDir: "testcomponent",
|
|
expectedResourceCount: 9,
|
|
// TODO[pulumi/pulumi#5455]: Dynamic providers fail to load when used from multi-lang components.
|
|
// Until we've addressed this, set PULUMI_TEST_YARN_LINK_PULUMI, which tells the integration test
|
|
// module to run `yarn install && yarn link @pulumi/pulumi` in the Go program's directory, allowing
|
|
// the Node.js dynamic provider plugin to load.
|
|
// When the underlying issue has been fixed, the use of this environment variable inside the integration
|
|
// test module should be removed.
|
|
env: []string{"PULUMI_TEST_YARN_LINK_PULUMI=true"},
|
|
},
|
|
{
|
|
componentDir: "testcomponent-python",
|
|
expectedResourceCount: 9,
|
|
},
|
|
{
|
|
componentDir: "testcomponent-go",
|
|
expectedResourceCount: 8, // One less because no dynamic provider.
|
|
},
|
|
}
|
|
|
|
//nolint:paralleltest // ProgramTest calls t.Parallel()
|
|
for _, test := range tests {
|
|
test := test
|
|
t.Run(test.componentDir, func(t *testing.T) {
|
|
localProviders := []integration.LocalDependency{
|
|
{Package: "testcomponent", Path: filepath.Join(testDir, test.componentDir)},
|
|
}
|
|
integration.ProgramTest(t,
|
|
optsForConstructPython(t, test.expectedResourceCount, localProviders, test.env...))
|
|
})
|
|
}
|
|
}
|
|
|
|
func optsForConstructPython(
|
|
t *testing.T, expectedResourceCount int, localProviders []integration.LocalDependency, env ...string,
|
|
) *integration.ProgramTestOptions {
|
|
return &integration.ProgramTestOptions{
|
|
Env: env,
|
|
Dir: filepath.Join("construct_component", "python"),
|
|
Dependencies: []string{
|
|
filepath.Join("..", "..", "sdk", "python", "env", "src"),
|
|
},
|
|
LocalProviders: localProviders,
|
|
Secrets: map[string]string{
|
|
"secret": "this super secret is encrypted",
|
|
},
|
|
Quick: true,
|
|
UseSharedVirtualEnv: boolPointer(false),
|
|
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
|
|
assert.NotNil(t, stackInfo.Deployment)
|
|
if assert.Equal(t, expectedResourceCount, len(stackInfo.Deployment.Resources)) {
|
|
stackRes := stackInfo.Deployment.Resources[0]
|
|
assert.NotNil(t, stackRes)
|
|
assert.Equal(t, resource.RootStackType, stackRes.Type)
|
|
assert.Equal(t, "", string(stackRes.Parent))
|
|
|
|
// Check that dependencies flow correctly between the originating program and the remote component
|
|
// plugin.
|
|
urns := make(map[string]resource.URN)
|
|
for _, res := range stackInfo.Deployment.Resources[1:] {
|
|
assert.NotNil(t, res)
|
|
|
|
urns[res.URN.Name()] = res.URN
|
|
switch res.URN.Name() {
|
|
case "child-a":
|
|
for _, deps := range res.PropertyDependencies {
|
|
assert.Empty(t, deps)
|
|
}
|
|
case "child-b":
|
|
expected := []resource.URN{urns["a"]}
|
|
assert.ElementsMatch(t, expected, res.Dependencies)
|
|
assert.ElementsMatch(t, expected, res.PropertyDependencies["echo"])
|
|
case "child-c":
|
|
expected := []resource.URN{urns["a"], urns["child-a"]}
|
|
assert.ElementsMatch(t, expected, res.Dependencies)
|
|
assert.ElementsMatch(t, expected, res.PropertyDependencies["echo"])
|
|
case "a", "b", "c":
|
|
secretPropValue, ok := res.Outputs["secret"].(map[string]interface{})
|
|
assert.Truef(t, ok, "secret output was not serialized as a secret")
|
|
assert.Equal(t, resource.SecretSig, secretPropValue[resource.SigKey].(string))
|
|
}
|
|
}
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
//nolint:paralleltest // Sets env vars
|
|
func TestConstructComponentConfigureProviderPython(t *testing.T) {
|
|
// This uses the tls plugin so needs to be able to download it
|
|
t.Setenv("PULUMI_DISABLE_AUTOMATIC_PLUGIN_ACQUISITION", "false")
|
|
|
|
const testDir = "construct_component_configure_provider"
|
|
runComponentSetup(t, testDir)
|
|
pulumiRoot, err := filepath.Abs("../..")
|
|
require.NoError(t, err)
|
|
pulumiPySDK := filepath.Join("..", "..", "sdk", "python", "env", "src")
|
|
componentSDK := filepath.Join(pulumiRoot, "pkg/codegen/testing/test/testdata/methods-return-plain-resource/python")
|
|
opts := testConstructComponentConfigureProviderCommonOptions()
|
|
opts = opts.With(integration.ProgramTestOptions{
|
|
Dir: filepath.Join(testDir, "python"),
|
|
Dependencies: []string{pulumiPySDK, componentSDK},
|
|
NoParallel: true,
|
|
})
|
|
integration.ProgramTest(t, &opts)
|
|
}
|
|
|
|
// Regresses https://github.com/pulumi/pulumi/issues/6471
|
|
func TestAutomaticVenvCreation(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Do not use integration.ProgramTest to avoid automatic venv
|
|
// handling by test harness; we actually are testing venv
|
|
// handling by the pulumi CLI itself.
|
|
|
|
check := func(t *testing.T, venvPathTemplate string, dir string) {
|
|
e := ptesting.NewEnvironment(t)
|
|
defer e.DeleteIfNotFailed()
|
|
|
|
venvPath := strings.ReplaceAll(venvPathTemplate, "${root}", e.RootPath)
|
|
t.Logf("venvPath = %s (IsAbs = %v)", venvPath, filepath.IsAbs(venvPath))
|
|
|
|
e.ImportDirectory(dir)
|
|
|
|
// replace "virtualenv: venv" with "virtualenv: ${venvPath}" in Pulumi.yaml
|
|
pulumiYaml := filepath.Join(e.RootPath, "Pulumi.yaml")
|
|
|
|
oldYaml, err := os.ReadFile(pulumiYaml)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
newYaml := []byte(strings.ReplaceAll(string(oldYaml),
|
|
"virtualenv: venv",
|
|
"virtualenv: >-\n "+venvPath))
|
|
|
|
if err := os.WriteFile(pulumiYaml, newYaml, 0o600); err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
t.Logf("Wrote Pulumi.yaml:\n%s\n", string(newYaml))
|
|
|
|
// Make a subdir and change to it to ensure paths aren't just relative to the working directory.
|
|
subdir := filepath.Join(e.RootPath, "subdir")
|
|
err = os.Mkdir(subdir, 0o755)
|
|
require.NoError(t, err)
|
|
e.CWD = subdir
|
|
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
e.RunCommand("pulumi", "stack", "init", "teststack")
|
|
e.RunCommand("pulumi", "preview")
|
|
|
|
var absVenvPath string
|
|
if filepath.IsAbs(venvPath) {
|
|
absVenvPath = venvPath
|
|
} else {
|
|
absVenvPath = filepath.Join(e.RootPath, venvPath)
|
|
}
|
|
|
|
if !toolchain.IsVirtualEnv(absVenvPath) {
|
|
t.Errorf("Expected a virtual environment to be created at %s but it is not there",
|
|
absVenvPath)
|
|
}
|
|
}
|
|
|
|
t.Run("RelativePath", func(t *testing.T) {
|
|
t.Parallel()
|
|
check(t, "venv", filepath.Join("python", "venv"))
|
|
})
|
|
|
|
t.Run("AbsolutePath", func(t *testing.T) {
|
|
t.Parallel()
|
|
check(t, filepath.Join("${root}", "absvenv"), filepath.Join("python", "venv"))
|
|
})
|
|
|
|
t.Run("RelativePathWithMain", func(t *testing.T) {
|
|
t.Parallel()
|
|
check(t, "venv", filepath.Join("python", "venv-with-main"))
|
|
})
|
|
|
|
t.Run("AbsolutePathWithMain", func(t *testing.T) {
|
|
t.Parallel()
|
|
check(t, filepath.Join("${root}", "absvenv"), filepath.Join("python", "venv-with-main"))
|
|
})
|
|
|
|
t.Run("TestInitVirtualEnvBeforePythonVersionCheck", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer e.DeleteIfNotFailed()
|
|
|
|
dir := filepath.Join("python", "venv")
|
|
e.ImportDirectory(dir)
|
|
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
e.RunCommand("pulumi", "stack", "init", "teststack")
|
|
stdout, stderr, _ := e.GetCommandResults("pulumi", "preview")
|
|
// pulumi/pulumi#9175
|
|
// Ensures this error message doesn't show up for uninitialized
|
|
// virtualenv
|
|
// `Failed to resolve python version command: ` +
|
|
// `fork/exec <path>/venv/bin/python: ` +
|
|
// `no such file or directory`
|
|
assert.NotContains(t, stdout, "fork/exec")
|
|
assert.NotContains(t, stderr, "fork/exec")
|
|
})
|
|
}
|
|
|
|
//nolint:paralleltest // Poetry causes issues when run in parallel on windows. See pulumi/pulumi#17183
|
|
func TestAutomaticVenvCreationPoetry(t *testing.T) {
|
|
if runtime.GOOS != "windows" {
|
|
t.Parallel()
|
|
}
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer e.DeleteIfNotFailed()
|
|
|
|
e.ImportDirectory(filepath.Join("python", "poetry"))
|
|
|
|
// Make a subdir and change to it to ensure paths aren't just relative to the working directory.
|
|
subdir := filepath.Join(e.RootPath, "subdir")
|
|
err := os.Mkdir(subdir, 0o755)
|
|
require.NoError(t, err)
|
|
e.CWD = subdir
|
|
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
e.RunCommand("pulumi", "stack", "init", "teststack")
|
|
e.RunCommand("pulumi", "preview")
|
|
|
|
localPoetryVenv := filepath.Join(e.RootPath, ".venv")
|
|
if !toolchain.IsVirtualEnv(localPoetryVenv) {
|
|
t.Errorf("Expected a virtual environment to be created at %s but it is not there", localPoetryVenv)
|
|
}
|
|
}
|
|
|
|
//nolint:paralleltest // Poetry causes issues when run in parallel on windows. See pulumi/pulumi#17183
|
|
func TestPoetryInstallParentDirectory(t *testing.T) {
|
|
if runtime.GOOS != "windows" {
|
|
t.Parallel()
|
|
}
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer e.DeleteIfNotFailed()
|
|
|
|
e.ImportDirectory(filepath.Join("python", "poetry-parent"))
|
|
// Run from the subdir with the Pulumi.yaml file
|
|
e.CWD = filepath.Join(e.RootPath, "subfolder")
|
|
|
|
e.RunCommand("pulumi", "install")
|
|
|
|
localPoetryVenv := filepath.Join(e.RootPath, ".venv")
|
|
if !toolchain.IsVirtualEnv(localPoetryVenv) {
|
|
t.Errorf("Expected a virtual environment to be created at %s but it is not there", localPoetryVenv)
|
|
}
|
|
}
|
|
|
|
//nolint:paralleltest // Poetry causes issues when run in parallel on windows. See pulumi/pulumi#17183
|
|
func TestPoetryInstallWithMain(t *testing.T) {
|
|
if runtime.GOOS != "windows" {
|
|
t.Parallel()
|
|
}
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer e.DeleteIfNotFailed()
|
|
|
|
e.ImportDirectory(filepath.Join("python", "poetry-main"))
|
|
|
|
e.RunCommand("pulumi", "install")
|
|
|
|
localPoetryVenv := filepath.Join(e.RootPath, ".venv")
|
|
if !toolchain.IsVirtualEnv(localPoetryVenv) {
|
|
t.Errorf("Expected a virtual environment to be created at %s but it is not there", localPoetryVenv)
|
|
}
|
|
}
|
|
|
|
//nolint:paralleltest // Poetry causes issues when run in parallel on windows. See pulumi/pulumi#17183
|
|
func TestPoetryInstallWithMainAndParent(t *testing.T) {
|
|
if runtime.GOOS != "windows" {
|
|
t.Parallel()
|
|
}
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer e.DeleteIfNotFailed()
|
|
|
|
e.ImportDirectory(filepath.Join("python", "poetry-main-and-parent"))
|
|
|
|
e.RunCommand("pulumi", "install")
|
|
|
|
localPoetryVenv := filepath.Join(e.RootPath, "src", ".venv")
|
|
if !toolchain.IsVirtualEnv(localPoetryVenv) {
|
|
t.Errorf("Expected a virtual environment to be created at %s but it is not there", localPoetryVenv)
|
|
}
|
|
}
|
|
|
|
func TestUv(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, test := range []struct {
|
|
template string
|
|
cwd string
|
|
expectedVenv string
|
|
}{
|
|
{
|
|
template: "uv",
|
|
expectedVenv: "my-venv",
|
|
},
|
|
{
|
|
template: "uv-main",
|
|
expectedVenv: "my-venv",
|
|
},
|
|
{
|
|
template: "uv-parent",
|
|
cwd: "subfolder",
|
|
expectedVenv: "subfolder/my-venv",
|
|
},
|
|
{
|
|
template: "uv-no-venv-option",
|
|
expectedVenv: ".venv",
|
|
},
|
|
{
|
|
template: "uv-no-venv-option-parent",
|
|
cwd: "subfolder",
|
|
expectedVenv: ".venv", // The virtualenv is relative to pyproject.toml
|
|
},
|
|
} {
|
|
test := test
|
|
// On windows, when running in parallel, we can run into issues when Uv tries
|
|
// to write the same cache file concurrently. This is the same issue we see
|
|
// for Poetry https://github.com/pulumi/pulumi/pull/17337
|
|
//nolint:paralleltest
|
|
t.Run(test.template, func(t *testing.T) {
|
|
if runtime.GOOS != "windows" {
|
|
t.Parallel()
|
|
}
|
|
e := ptesting.NewEnvironment(t)
|
|
defer e.DeleteIfNotFailed()
|
|
|
|
e.ImportDirectory(filepath.Join("python", test.template))
|
|
|
|
if test.cwd != "" {
|
|
e.CWD = filepath.Join(e.RootPath, test.cwd)
|
|
}
|
|
|
|
e.RunCommand("pulumi", "install")
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
e.RunCommand("pulumi", "stack", "init", ptesting.RandomStackName())
|
|
e.RunCommand("pulumi", "preview")
|
|
|
|
venv := filepath.Join(e.RootPath, test.expectedVenv)
|
|
if !toolchain.IsVirtualEnv(venv) {
|
|
t.Errorf("Expected a virtual environment to be created at %s but it is not there", venv)
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("convert requirements.txt", func(t *testing.T) {
|
|
if runtime.GOOS != "windows" {
|
|
t.Parallel()
|
|
}
|
|
e := ptesting.NewEnvironment(t)
|
|
defer e.DeleteIfNotFailed()
|
|
|
|
e.ImportDirectory(filepath.Join("python", "uv-convert-requirements"))
|
|
|
|
e.RunCommand("pulumi", "install")
|
|
|
|
venv := filepath.Join(e.RootPath, "my-venv")
|
|
if !toolchain.IsVirtualEnv(venv) {
|
|
t.Errorf("Expected a virtual environment to be created at %s but it is not there", venv)
|
|
}
|
|
|
|
require.True(t, e.PathExists("pyproject.toml"), "pyproject.toml should have been created")
|
|
require.False(t, e.PathExists("requirements.txt"), "requirements.txt should have been deleted")
|
|
})
|
|
|
|
t.Run("convert requirements.txt subfolder", func(t *testing.T) {
|
|
if runtime.GOOS != "windows" {
|
|
t.Parallel()
|
|
}
|
|
e := ptesting.NewEnvironment(t)
|
|
defer e.DeleteIfNotFailed()
|
|
|
|
e.ImportDirectory(filepath.Join("python", "uv-convert-requirements-subfolder"))
|
|
e.CWD = filepath.Join(e.RootPath, "subfolder")
|
|
|
|
e.RunCommand("pulumi", "install")
|
|
|
|
venv := filepath.Join(e.RootPath, "subfolder", "my-venv")
|
|
if !toolchain.IsVirtualEnv(venv) {
|
|
t.Errorf("Expected a virtual environment to be created at %s but it is not there", venv)
|
|
}
|
|
|
|
e.CWD = e.RootPath // Reset the CWD so we can use e.PathExists
|
|
require.True(t, e.PathExists("pyproject.toml"), "pyproject.toml should have been created")
|
|
require.False(t, e.PathExists("requirements.txt"), "requirements.txt should have been deleted")
|
|
})
|
|
}
|
|
|
|
//nolint:paralleltest // ProgramTest calls t.Parallel()
|
|
func TestMypySupport(t *testing.T) {
|
|
validation := func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
|
|
// Should get an event for the mypy failure.
|
|
messages := []string{}
|
|
for _, event := range stack.Events {
|
|
if event.DiagnosticEvent != nil {
|
|
messages = append(messages,
|
|
strings.Replace(event.DiagnosticEvent.Message, "\r\n", "\n", -1))
|
|
}
|
|
}
|
|
expected := "__main__.py:8: error: " +
|
|
"Argument 1 to \"export\" has incompatible type \"int\"; expected \"str\"" +
|
|
" [arg-type]\n\n"
|
|
assert.Contains(t, messages, expected, "Did not find expected mypy diagnostic event")
|
|
}
|
|
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("python", "mypy"),
|
|
// mypy doesn't really support editable packages, so we install pulumi normally from pip for this test.
|
|
Quick: true,
|
|
ExpectFailure: true,
|
|
ExtraRuntimeValidation: validation,
|
|
})
|
|
}
|
|
|
|
//nolint:paralleltest // ProgramTest calls t.Parallel()
|
|
func TestPyrightSupport(t *testing.T) {
|
|
validation := func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
|
|
// Should get an event for the pyright failure.
|
|
found := false
|
|
expected := "__main__.py:8:15 - error:" +
|
|
" Argument of type \"Literal[42]\" cannot be assigned to parameter \"name\"" +
|
|
" of type \"str\" in function \"export\"\n\n"
|
|
for _, event := range stack.Events {
|
|
if event.DiagnosticEvent != nil {
|
|
message := strings.Replace(event.DiagnosticEvent.Message, "\r\n", "\n", -1)
|
|
if strings.HasSuffix(message, expected) {
|
|
found = true
|
|
}
|
|
}
|
|
}
|
|
assert.True(t, found, "Did not find expected pyright diagnostic event")
|
|
}
|
|
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("python", "pyright"),
|
|
// to match the mypy test we install pulumi normally from pip for this test.
|
|
Quick: true,
|
|
ExpectFailure: true,
|
|
ExtraRuntimeValidation: validation,
|
|
})
|
|
}
|
|
|
|
//nolint:paralleltest // ProgramTest calls t.Parallel()
|
|
func TestTypecheckerMissingError(t *testing.T) {
|
|
validation := func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
|
|
// Should get an event for the pyright failure.
|
|
found := false
|
|
expected := "The typechecker option is set to pyright, but pyright is not installed." +
|
|
" Please add an entry for pyright to requirements.txt"
|
|
for _, event := range stack.Events {
|
|
if event.DiagnosticEvent != nil {
|
|
if strings.Contains(event.DiagnosticEvent.Message, expected) {
|
|
found = true
|
|
}
|
|
}
|
|
}
|
|
assert.True(t, found, "Did not find expected pyright diagnostic event")
|
|
}
|
|
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("python", "pyright-missing"),
|
|
Quick: true,
|
|
ExpectFailure: true,
|
|
ExtraRuntimeValidation: validation,
|
|
})
|
|
}
|
|
|
|
func TestNewPythonUsesPip(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer e.DeleteIfNotFailed()
|
|
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
stdout, _ := e.RunCommand("pulumi", "new", "python", "--force", "--non-interactive", "--yes", "--generate-only")
|
|
|
|
require.Contains(t, stdout, "pulumi install")
|
|
|
|
expected := map[string]interface{}{
|
|
"toolchain": "pip",
|
|
"virtualenv": "venv",
|
|
}
|
|
integration.CheckRuntimeOptions(t, e.RootPath, expected)
|
|
}
|
|
|
|
//nolint:paralleltest // Modifies env
|
|
func TestNewPythonUsesPipNonInteractive(t *testing.T) {
|
|
// Force interactive mode to properly test `--yes`.
|
|
t.Setenv("PULUMI_TEST_INTERACTIVE", "1")
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer e.DeleteIfNotFailed()
|
|
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
stdout, _ := e.RunCommand("pulumi", "new", "python", "--force", "--yes", "--generate-only")
|
|
|
|
require.Contains(t, stdout, "pulumi install")
|
|
|
|
expected := map[string]interface{}{
|
|
"toolchain": "pip",
|
|
"virtualenv": "venv",
|
|
}
|
|
integration.CheckRuntimeOptions(t, e.RootPath, expected)
|
|
}
|
|
|
|
//nolint:paralleltest // Modifies env
|
|
func TestNewPythonChoosePoetry(t *testing.T) {
|
|
// The windows acceptance tests are run using git bash, but the survey library does not support this
|
|
// https://github.com/AlecAivazis/survey/issues/148
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("Skipping: survey library does not support git bash on Windows")
|
|
}
|
|
|
|
t.Setenv("PULUMI_TEST_INTERACTIVE", "1")
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer e.DeleteIfNotFailed()
|
|
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
|
|
e.Stdin = strings.NewReader("poetry\n")
|
|
e.RunCommand("pulumi", "new", "python", "--force", "--generate-only",
|
|
"--name", "test_project",
|
|
"--description", "A python test using poetry as toolchain",
|
|
"--stack", "test",
|
|
)
|
|
|
|
expected := map[string]interface{}{
|
|
"toolchain": "poetry",
|
|
}
|
|
integration.CheckRuntimeOptions(t, e.RootPath, expected)
|
|
}
|
|
|
|
//nolint:paralleltest // Modifies env
|
|
func TestNewPythonChooseUv(t *testing.T) {
|
|
// The windows acceptance tests are run using git bash, but the survey library does not support this
|
|
// https://github.com/AlecAivazis/survey/issues/148
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("Skipping: survey library does not support git bash on Windows")
|
|
}
|
|
|
|
t.Setenv("PULUMI_TEST_INTERACTIVE", "1")
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer e.DeleteIfNotFailed()
|
|
|
|
e.Stdin = strings.NewReader("uv\n")
|
|
e.RunCommand("pulumi", "new", "python", "--force", "--generate-only",
|
|
"--name", "test_project",
|
|
"--description", "A python test using uv as toolchain",
|
|
)
|
|
|
|
expected := map[string]interface{}{
|
|
"toolchain": "uv",
|
|
}
|
|
integration.CheckRuntimeOptions(t, e.RootPath, expected)
|
|
|
|
e.RunCommand("pulumi", "install")
|
|
|
|
require.True(t, e.PathExists(".venv"))
|
|
require.True(t, e.PathExists("uv.lock"))
|
|
require.True(t, e.PathExists("pyproject.toml"))
|
|
require.False(t, e.PathExists("requirements.txt"))
|
|
}
|
|
|
|
//nolint:paralleltest // Poetry causes issues when run in parallel on windows. See pulumi/pulumi#17183
|
|
func TestNewPythonRuntimeOptions(t *testing.T) {
|
|
if runtime.GOOS != "windows" {
|
|
t.Parallel()
|
|
}
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer e.DeleteIfNotFailed()
|
|
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
e.RunCommand("pulumi", "new", "python", "--force", "--non-interactive", "--yes", "--generate-only",
|
|
"--name", "test_project",
|
|
"--description", "A python test using poetry as toolchain",
|
|
"--stack", "test",
|
|
"--runtime-options", "toolchain=pip,virtualenv=mytestenv",
|
|
)
|
|
|
|
expected := map[string]interface{}{
|
|
"toolchain": "pip",
|
|
"virtualenv": "mytestenv",
|
|
}
|
|
integration.CheckRuntimeOptions(t, e.RootPath, expected)
|
|
}
|
|
|
|
//nolint:paralleltest // Poetry causes issues when run in parallel on windows. See pulumi/pulumi#17183
|
|
func TestNewPythonConvertRequirementsTxt(t *testing.T) {
|
|
if runtime.GOOS != "windows" {
|
|
t.Parallel()
|
|
}
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer e.DeleteIfNotFailed()
|
|
|
|
// Add a poetry.toml to make poetry create the virtualenv inside the temp
|
|
// directory. That way it gets cleaned up with the test.
|
|
poetryToml := `[virtualenvs]
|
|
in-project = true`
|
|
err := os.WriteFile(filepath.Join(e.RootPath, "poetry.toml"), []byte(poetryToml), 0o600)
|
|
require.NoError(t, err)
|
|
|
|
template := "python"
|
|
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
out, _ := e.RunCommand("pulumi", "new", template, "--force", "--non-interactive", "--yes",
|
|
"--name", "test_project",
|
|
"--description", "A python test using poetry as toolchain",
|
|
"--stack", "test",
|
|
"--runtime-options", "toolchain=poetry",
|
|
)
|
|
|
|
require.Contains(t, out, "Deleted requirements.txt")
|
|
require.True(t, e.PathExists("pyproject.toml"), "pyproject.toml was created")
|
|
require.False(t, e.PathExists("requirements.txt"), "requirements.txt was removed")
|
|
|
|
b, err := os.ReadFile(filepath.Join(e.RootPath, "pyproject.toml"))
|
|
require.NoError(t, err)
|
|
require.Equal(t, `[build-system]
|
|
requires = ["poetry-core"]
|
|
build-backend = "poetry.core.masonry.api"
|
|
|
|
[tool]
|
|
[tool.poetry]
|
|
package-mode = false
|
|
[tool.poetry.dependencies]
|
|
pulumi = ">=3.0.0,<4.0.0"
|
|
python = "^3.8"
|
|
`, string(b))
|
|
}
|