Skip to content

Commit

Permalink
Try #11532:
Browse files Browse the repository at this point in the history
  • Loading branch information
bors[bot] committed Dec 5, 2022
2 parents 86e7d56 + 411c9b9 commit 1adcfcd
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 7 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci-run-test.yml
Expand Up @@ -64,6 +64,7 @@ env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_PROD_ACCESS_TOKEN }}
# Release builds use the service, PR checks and snapshots will use the local backend if possible.
PULUMI_TEST_USE_SERVICE: ${{ !contains(inputs.version, '-') }}
PULUMI_TEST_PYTHON_SHARED_VENV: "true"
PYTHON: python
GO_TEST_PARALLELISM: 8
GO_TEST_PKG_PARALLELISM: 2
Expand Down
3 changes: 3 additions & 0 deletions .vscode/settings.json
@@ -1,6 +1,9 @@
{
"go.buildTags": "all",
"go.testTimeout": "1h",
"go.testEnvVars": {
"PULUMI_TEST_PYTHON_SHARED_VENV": "true",
},
"gopls": {
// A couple of modules get copied as part of builds and this confuse gopls as it sees the module name twice, just ignore the copy in the build folders.
"build.directoryFilters": [
Expand Down
@@ -0,0 +1,4 @@
changes:
- type: fix
scope: pkg/testing
description: Optionally caches python venvs for testing
92 changes: 86 additions & 6 deletions pkg/testing/integration/program.go
Expand Up @@ -17,6 +17,7 @@ package integration
import (
"context"
cryptorand "crypto/rand"
sha256 "crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
Expand Down Expand Up @@ -292,6 +293,12 @@ type ProgramTestOptions struct {
UseAutomaticVirtualEnv bool
// Use the Pipenv tool to manage the virtual environment.
UsePipenv bool
// Use a shared virtual environment for tests based on the contents of the requirements file. Defaults to false.
UseSharedVirtualEnv *bool
// Shared venv path when UseSharedVirtualEnv is true. Defaults to $HOME/.pulumi-test-venvs.
SharedVirtualEnvPath string
// Refers to the shared venv directory when UseSharedVirtualEnv is true. Otherwise defaults to venv
virtualEnvDir string

// If set, this hook is called after the `pulumi preview` command has completed.
PreviewCompletedHook func(dir string) error
Expand All @@ -314,6 +321,13 @@ type ProgramTestOptions struct {
LocalProviders []LocalDependency
}

func (opts *ProgramTestOptions) GetUseSharedVirtualEnv() bool {
if opts.UseSharedVirtualEnv != nil {
return *opts.UseSharedVirtualEnv
}
return false
}

type LocalDependency struct {
Package string
Path string
Expand Down Expand Up @@ -375,6 +389,34 @@ func (opts *ProgramTestOptions) GetStackName() tokens.QName {
return tokens.QName(opts.StackName)
}

// Returns the md5 hash of the file at the given path as a string
func hashFile(path string) (string, error) {
file, err := os.Open(path)
if err != nil {
return "", err
}
defer file.Close()
buf := make([]byte, 32*1024)
hash := sha256.New()
for {
n, err := file.Read(buf)
if n > 0 {
_, err := hash.Write(buf[:n])
if err != nil {
return "", err
}
}
if err == io.EOF {
break
}
if err != nil {
return "", err
}
}
sum := string(hash.Sum(nil))
return sum, nil
}

// GetStackNameWithOwner gets the name of the stack prepended with an owner, if PULUMI_TEST_OWNER is set.
// We use this in CI to create test stacks in an organization that all developers have access to, for debugging.
func (opts *ProgramTestOptions) GetStackNameWithOwner() tokens.QName {
Expand Down Expand Up @@ -547,6 +589,12 @@ func (opts ProgramTestOptions) With(overrides ProgramTestOptions) ProgramTestOpt
if overrides.UsePipenv {
opts.UsePipenv = overrides.UsePipenv
}
if overrides.UseSharedVirtualEnv != nil {
opts.UseSharedVirtualEnv = overrides.UseSharedVirtualEnv
}
if overrides.SharedVirtualEnvPath != "" {
opts.SharedVirtualEnvPath = overrides.SharedVirtualEnvPath
}
if overrides.PreviewCompletedHook != nil {
opts.PreviewCompletedHook = overrides.PreviewCompletedHook
}
Expand Down Expand Up @@ -700,6 +748,24 @@ func prepareProgram(t *testing.T, opts *ProgramTestOptions) {
}
}

if opts.UseSharedVirtualEnv == nil {
if sharedVenv := os.Getenv("PULUMI_TEST_PYTHON_SHARED_VENV"); sharedVenv != "" {
useSharedVenvBool := sharedVenv == "true"
opts.UseSharedVirtualEnv = &useSharedVenvBool
}
}

if opts.virtualEnvDir == "" && !opts.GetUseSharedVirtualEnv() {
opts.virtualEnvDir = "venv"
}

if opts.SharedVirtualEnvPath == "" {
opts.SharedVirtualEnvPath = filepath.Join(os.Getenv("HOME"), ".pulumi-test-venvs")
if sharedVenvPath := os.Getenv("PULUMI_TEST_PYTHON_SHARED_VENV_PATH"); sharedVenvPath != "" {
opts.SharedVirtualEnvPath = sharedVenvPath
}
}

if opts.Quick {
opts.SkipPreview = true
opts.SkipExportImport = true
Expand Down Expand Up @@ -1025,7 +1091,7 @@ func (pt *ProgramTester) runVirtualEnvCommand(name string, args []string, wd str
}()
}

virtualenvBinPath, err := getVirtualenvBinPath(wd, args[0])
virtualenvBinPath, err := getVirtualenvBinPath(wd, args[0], pt)
if err != nil {
return err
}
Expand Down Expand Up @@ -1985,11 +2051,21 @@ func (pt *ProgramTester) preparePythonProject(projinfo *engine.Projinfo) error {
return err
}
} else {
if err = pt.runPythonCommand("python-venv", []string{"-m", "venv", "venv"}, cwd); err != nil {
venvPath := "venv"
if pt.opts.GetUseSharedVirtualEnv() {
requirementsPath := filepath.Join(cwd, "requirements.txt")
requirementsmd5, err := hashFile(requirementsPath)
if err != nil {
return err
}
pt.opts.virtualEnvDir = fmt.Sprintf("pulumi-venv-%x", requirementsmd5)
venvPath = filepath.Join(pt.opts.SharedVirtualEnvPath, pt.opts.virtualEnvDir)
}
if err = pt.runPythonCommand("python-venv", []string{"-m", "venv", venvPath}, cwd); err != nil {
return err
}

projinfo.Proj.Runtime.SetOption("virtualenv", "venv")
projinfo.Proj.Runtime.SetOption("virtualenv", venvPath)
projfile := filepath.Join(projinfo.Root, workspace.ProjectFile+".yaml")
if err = projinfo.Proj.Save(projfile); err != nil {
return fmt.Errorf("saving project: %w", err)
Expand Down Expand Up @@ -2077,10 +2153,14 @@ func (pt *ProgramTester) installPipPackageDeps(cwd string) error {
return nil
}

func getVirtualenvBinPath(cwd, bin string) (string, error) {
virtualenvBinPath := filepath.Join(cwd, "venv", "bin", bin)
func getVirtualenvBinPath(cwd, bin string, pt *ProgramTester) (string, error) {
virtualEnvBasePath := filepath.Join(cwd, pt.opts.virtualEnvDir)
if pt.opts.GetUseSharedVirtualEnv() {
virtualEnvBasePath = filepath.Join(pt.opts.SharedVirtualEnvPath, pt.opts.virtualEnvDir)
}
virtualenvBinPath := filepath.Join(virtualEnvBasePath, "bin", bin)
if runtime.GOOS == windowsOS {
virtualenvBinPath = filepath.Join(cwd, "venv", "Scripts", fmt.Sprintf("%s.exe", bin))
virtualenvBinPath = filepath.Join(virtualEnvBasePath, "Scripts", fmt.Sprintf("%s.exe", bin))
}
if info, err := os.Stat(virtualenvBinPath); err != nil || info.IsDir() {
return "", fmt.Errorf("Expected %s to exist in virtual environment at %q", bin, virtualenvBinPath)
Expand Down
7 changes: 6 additions & 1 deletion tests/integration/integration_python_smoke_test.go
Expand Up @@ -31,6 +31,10 @@ import (
"github.com/pulumi/pulumi/sdk/v3/python"
)

func boolPointer(b bool) *bool {
return &b
}

// TestEmptyPython simply tests that we can run an empty Python project.
func TestEmptyPython(t *testing.T) {
integration.ProgramTest(t, &integration.ProgramTestOptions{
Expand Down Expand Up @@ -144,7 +148,8 @@ func optsForConstructPython(t *testing.T, expectedResourceCount int, localProvid
Secrets: map[string]string{
"secret": "this super secret is encrypted",
},
Quick: true,
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)) {
Expand Down

0 comments on commit 1adcfcd

Please sign in to comment.