2022-11-03 20:30:35 +00:00
|
|
|
// 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.
|
|
|
|
|
|
|
|
// The linter doesn't see the uses since the consumers are conditionally compiled tests.
|
|
|
|
//
|
2023-01-06 00:07:45 +00:00
|
|
|
//nolint:unused,deadcode,varcheck
|
2022-11-03 20:30:35 +00:00
|
|
|
package ints
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
2023-02-02 20:46:22 +00:00
|
|
|
"sync"
|
2022-11-03 20:30:35 +00:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/pulumi/pulumi/pkg/v3/testing/integration"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
|
|
|
|
ptesting "github.com/pulumi/pulumi/sdk/v3/go/common/testing"
|
2023-03-07 18:19:10 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/testing/iotest"
|
2022-11-03 20:30:35 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/fsutil"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/rpcutil"
|
|
|
|
pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go"
|
|
|
|
"github.com/stretchr/testify/assert"
|
2023-02-02 20:46:22 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
2022-11-03 20:30:35 +00:00
|
|
|
"google.golang.org/grpc"
|
2023-01-11 19:54:31 +00:00
|
|
|
"google.golang.org/grpc/credentials/insecure"
|
2022-11-03 20:30:35 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const WindowsOS = "windows"
|
|
|
|
|
|
|
|
// assertPerfBenchmark implements the integration.TestStatsReporter interface, and reports test
|
|
|
|
// failures when a scenario exceeds the provided threshold.
|
|
|
|
type assertPerfBenchmark struct {
|
|
|
|
T *testing.T
|
|
|
|
MaxPreviewDuration time.Duration
|
|
|
|
MaxUpdateDuration time.Duration
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t assertPerfBenchmark) ReportCommand(stats integration.TestCommandStats) {
|
|
|
|
var maxDuration *time.Duration
|
|
|
|
if strings.HasPrefix(stats.StepName, "pulumi-preview") {
|
|
|
|
maxDuration = &t.MaxPreviewDuration
|
|
|
|
}
|
|
|
|
if strings.HasPrefix(stats.StepName, "pulumi-update") {
|
|
|
|
maxDuration = &t.MaxUpdateDuration
|
|
|
|
}
|
|
|
|
|
|
|
|
if maxDuration != nil && *maxDuration != 0 {
|
|
|
|
if stats.ElapsedSeconds < maxDuration.Seconds() {
|
|
|
|
t.T.Logf(
|
|
|
|
"Test step %q was under threshold. %.2fs (max %.2fs)",
|
|
|
|
stats.StepName, stats.ElapsedSeconds, maxDuration.Seconds())
|
|
|
|
} else {
|
|
|
|
t.T.Errorf(
|
|
|
|
"Test step %q took longer than expected. %.2fs vs. max %.2fs",
|
|
|
|
stats.StepName, stats.ElapsedSeconds, maxDuration.Seconds())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func testComponentSlowLocalProvider(t *testing.T) integration.LocalDependency {
|
|
|
|
return integration.LocalDependency{
|
|
|
|
Package: "testcomponent",
|
|
|
|
Path: filepath.Join("construct_component_slow", "testcomponent"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func testComponentProviderSchema(t *testing.T, path string) {
|
|
|
|
runComponentSetup(t, "component_provider_schema")
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
env []string
|
|
|
|
version int32
|
|
|
|
expected string
|
|
|
|
expectedError string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "Default",
|
|
|
|
expected: "{}",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Schema",
|
|
|
|
env: []string{"INCLUDE_SCHEMA=true"},
|
|
|
|
expected: `{"hello": "world"}`,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Invalid Version",
|
|
|
|
version: 15,
|
|
|
|
expectedError: "unsupported schema version 15",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, test := range tests {
|
|
|
|
test := test
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
// Start the plugin binary.
|
|
|
|
cmd := exec.Command(path, "ignored")
|
|
|
|
cmd.Env = append(os.Environ(), test.env...)
|
|
|
|
stdout, err := cmd.StdoutPipe()
|
|
|
|
assert.NoError(t, err)
|
|
|
|
err = cmd.Start()
|
|
|
|
assert.NoError(t, err)
|
|
|
|
defer func() {
|
|
|
|
// Ignore the error as it may fail with access denied on Windows.
|
2023-01-06 00:07:45 +00:00
|
|
|
cmd.Process.Kill() //nolint:errcheck
|
2022-11-03 20:30:35 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
// Read the port from standard output.
|
|
|
|
reader := bufio.NewReader(stdout)
|
|
|
|
bytes, err := reader.ReadBytes('\n')
|
|
|
|
assert.NoError(t, err)
|
|
|
|
port := strings.TrimSpace(string(bytes))
|
|
|
|
|
|
|
|
// Create a connection to the server.
|
2023-01-11 19:54:31 +00:00
|
|
|
conn, err := grpc.Dial(
|
|
|
|
"127.0.0.1:"+port,
|
|
|
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
|
|
|
rpcutil.GrpcChannelOptions(),
|
|
|
|
)
|
2022-11-03 20:30:35 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
client := pulumirpc.NewResourceProviderClient(conn)
|
|
|
|
|
|
|
|
// Call GetSchema and verify the results.
|
|
|
|
resp, err := client.GetSchema(context.Background(), &pulumirpc.GetSchemaRequest{Version: test.version})
|
|
|
|
if test.expectedError != "" {
|
2023-12-15 17:45:32 +00:00
|
|
|
assert.ErrorContains(t, err, test.expectedError)
|
2022-11-03 20:30:35 +00:00
|
|
|
} else {
|
|
|
|
assert.Equal(t, test.expected, resp.GetSchema())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test remote component inputs properly handle unknowns.
|
|
|
|
func testConstructUnknown(t *testing.T, lang string, dependencies ...string) {
|
|
|
|
const testDir = "construct_component_unknown"
|
|
|
|
runComponentSetup(t, testDir)
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
componentDir string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
componentDir: "testcomponent",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
componentDir: "testcomponent-python",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
componentDir: "testcomponent-go",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, test := range tests {
|
|
|
|
test := test
|
|
|
|
t.Run(test.componentDir, func(t *testing.T) {
|
2023-03-03 16:36:39 +00:00
|
|
|
localProviders := []integration.LocalDependency{
|
|
|
|
{Package: "testcomponent", Path: filepath.Join(testDir, test.componentDir)},
|
|
|
|
}
|
2022-11-03 20:30:35 +00:00
|
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
|
|
Dir: filepath.Join(testDir, lang),
|
|
|
|
Dependencies: dependencies,
|
|
|
|
LocalProviders: localProviders,
|
|
|
|
SkipRefresh: true,
|
|
|
|
SkipPreview: false,
|
|
|
|
SkipUpdate: true,
|
|
|
|
SkipExportImport: true,
|
|
|
|
SkipEmptyPreviewUpdate: true,
|
|
|
|
Quick: false,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test methods properly handle unknowns.
|
|
|
|
func testConstructMethodsUnknown(t *testing.T, lang string, dependencies ...string) {
|
|
|
|
const testDir = "construct_component_methods_unknown"
|
|
|
|
runComponentSetup(t, testDir)
|
|
|
|
tests := []struct {
|
|
|
|
componentDir string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
componentDir: "testcomponent",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
componentDir: "testcomponent-python",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
componentDir: "testcomponent-go",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, test := range tests {
|
|
|
|
test := test
|
|
|
|
|
|
|
|
t.Run(test.componentDir, func(t *testing.T) {
|
2023-03-03 16:36:39 +00:00
|
|
|
localProviders := []integration.LocalDependency{
|
|
|
|
{Package: "testcomponent", Path: filepath.Join(testDir, test.componentDir)},
|
|
|
|
}
|
2022-11-03 20:30:35 +00:00
|
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
|
|
Dir: filepath.Join(testDir, lang),
|
|
|
|
Dependencies: dependencies,
|
|
|
|
LocalProviders: localProviders,
|
|
|
|
SkipRefresh: true,
|
|
|
|
SkipPreview: false,
|
|
|
|
SkipUpdate: true,
|
|
|
|
SkipExportImport: true,
|
|
|
|
SkipEmptyPreviewUpdate: true,
|
|
|
|
Quick: false,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func runComponentSetup(t *testing.T, testDir string) {
|
|
|
|
ptesting.YarnInstallMutex.Lock()
|
|
|
|
defer ptesting.YarnInstallMutex.Unlock()
|
|
|
|
|
|
|
|
setupFilename, err := filepath.Abs("component_setup.sh")
|
2023-02-03 01:38:12 +00:00
|
|
|
require.NoError(t, err, "could not determine absolute path")
|
|
|
|
// Even for Windows, we want forward slashes as bash treats backslashes as escape sequences.
|
2022-11-03 20:30:35 +00:00
|
|
|
setupFilename = filepath.ToSlash(setupFilename)
|
2023-02-03 01:38:12 +00:00
|
|
|
|
|
|
|
synchronouslyDo(t, filepath.Join(testDir, ".lock"), 10*time.Minute, func() {
|
2023-03-07 18:19:10 +00:00
|
|
|
out := iotest.LogWriter(t)
|
2023-02-03 21:57:12 +00:00
|
|
|
|
|
|
|
cmd := exec.Command("bash", "-x", setupFilename)
|
2022-11-03 20:30:35 +00:00
|
|
|
cmd.Dir = testDir
|
2023-02-03 21:57:12 +00:00
|
|
|
cmd.Stdout = out
|
|
|
|
cmd.Stderr = out
|
|
|
|
err := cmd.Run()
|
2023-02-03 01:38:12 +00:00
|
|
|
|
|
|
|
// This runs in a separate goroutine, so don't use 'require'.
|
2023-02-03 21:57:12 +00:00
|
|
|
assert.NoError(t, err, "failed to run setup script")
|
2023-02-03 01:38:12 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
// The function above runs in a separate goroutine
|
|
|
|
// so it can't halt test execution.
|
|
|
|
// Verify that it didn't fail separately
|
|
|
|
// and halt execution if it did.
|
|
|
|
require.False(t, t.Failed(), "component setup failed")
|
2022-11-03 20:30:35 +00:00
|
|
|
}
|
|
|
|
|
2023-02-02 20:46:22 +00:00
|
|
|
func synchronouslyDo(t testing.TB, lockfile string, timeout time.Duration, fn func()) {
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
|
|
|
defer cancel()
|
2022-11-03 20:30:35 +00:00
|
|
|
|
2023-02-02 20:46:22 +00:00
|
|
|
lockWait := make(chan struct{})
|
2022-11-03 20:30:35 +00:00
|
|
|
go func() {
|
2023-02-02 20:46:22 +00:00
|
|
|
mutex := fsutil.NewFileMutex(lockfile)
|
|
|
|
|
|
|
|
// ctx.Err will be non-nil when the context finishes
|
|
|
|
// either because it timed out or because it got canceled.
|
|
|
|
for ctx.Err() == nil {
|
2022-11-03 20:30:35 +00:00
|
|
|
if err := mutex.Lock(); err != nil {
|
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
continue
|
|
|
|
}
|
all: Fix revive issues
Fixes the following issues found by revive
included in the latest release of golangci-lint.
Full list of issues:
**pkg**
```
backend/display/object_diff.go:47:10: superfluous-else: if block ends with a break statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) (revive)
backend/display/object_diff.go:716:12: redefines-builtin-id: redefinition of the built-in function delete (revive)
backend/display/object_diff.go:742:14: redefines-builtin-id: redefinition of the built-in function delete (revive)
backend/display/object_diff.go:983:10: superfluous-else: if block ends with a continue statement, so drop this else and outdent its block (revive)
backend/httpstate/backend.go:1814:4: redefines-builtin-id: redefinition of the built-in function cap (revive)
backend/httpstate/backend.go:1824:5: redefines-builtin-id: redefinition of the built-in function cap (revive)
backend/httpstate/client/client.go:444:2: if-return: redundant if ...; err != nil check, just return error instead. (revive)
backend/httpstate/client/client.go:455:2: if-return: redundant if ...; err != nil check, just return error instead. (revive)
cmd/pulumi/org.go:113:4: if-return: redundant if ...; err != nil check, just return error instead. (revive)
cmd/pulumi/util.go:216:2: if-return: redundant if ...; err != nil check, just return error instead. (revive)
codegen/docs/gen.go:428:2: redefines-builtin-id: redefinition of the built-in function copy (revive)
codegen/hcl2/model/expression.go:2151:5: redefines-builtin-id: redefinition of the built-in function close (revive)
codegen/hcl2/syntax/comments.go:151:2: redefines-builtin-id: redefinition of the built-in function close (revive)
codegen/hcl2/syntax/comments.go:329:3: redefines-builtin-id: redefinition of the built-in function close (revive)
codegen/hcl2/syntax/comments.go:381:5: redefines-builtin-id: redefinition of the built-in function close (revive)
codegen/nodejs/gen.go:1367:5: redefines-builtin-id: redefinition of the built-in function copy (revive)
codegen/python/gen_program_expressions.go:136:2: redefines-builtin-id: redefinition of the built-in function close (revive)
codegen/python/gen_program_expressions.go:142:3: redefines-builtin-id: redefinition of the built-in function close (revive)
codegen/report/report.go:126:6: redefines-builtin-id: redefinition of the built-in function panic (revive)
codegen/schema/docs_test.go:210:10: superfluous-else: if block ends with a continue statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) (revive)
codegen/schema/schema.go:790:2: redefines-builtin-id: redefinition of the built-in type any (revive)
codegen/schema/schema.go:793:4: redefines-builtin-id: redefinition of the built-in type any (revive)
resource/deploy/plan.go:506:2: if-return: redundant if ...; err != nil check, just return error instead. (revive)
resource/deploy/snapshot_test.go:59:3: redefines-builtin-id: redefinition of the built-in function copy (revive)
resource/deploy/state_builder.go:108:2: redefines-builtin-id: redefinition of the built-in function copy (revive)
```
**sdk**
```
go/common/resource/plugin/context.go:142:2: redefines-builtin-id: redefinition of the built-in function copy (revive)
go/common/resource/plugin/plugin.go:142:12: superfluous-else: if block ends with a break statement, so drop this else and outdent its block (revive)
go/common/resource/properties_diff.go:114:2: redefines-builtin-id: redefinition of the built-in function len (revive)
go/common/resource/properties_diff.go:117:4: redefines-builtin-id: redefinition of the built-in function len (revive)
go/common/resource/properties_diff.go:122:4: redefines-builtin-id: redefinition of the built-in function len (revive)
go/common/resource/properties_diff.go:127:4: redefines-builtin-id: redefinition of the built-in function len (revive)
go/common/resource/properties_diff.go:132:4: redefines-builtin-id: redefinition of the built-in function len (revive)
go/common/util/deepcopy/copy.go:30:1: redefines-builtin-id: redefinition of the built-in function copy (revive)
go/common/workspace/creds.go:242:2: if-return: redundant if ...; err != nil check, just return error instead. (revive)
go/pulumi-language-go/main.go:569:2: if-return: redundant if ...; err != nil check, just return error instead. (revive)
go/pulumi-language-go/main.go:706:2: if-return: redundant if ...; err != nil check, just return error instead. (revive)
go/pulumi/run_test.go:925:2: redefines-builtin-id: redefinition of the built-in type any (revive)
go/pulumi/run_test.go:933:3: redefines-builtin-id: redefinition of the built-in type any (revive)
nodejs/cmd/pulumi-language-nodejs/main.go:778:2: if-return: redundant if ...; err != nil check, just return error instead. (revive)
python/cmd/pulumi-language-python/main.go:1011:2: if-return: redundant if ...; err != nil check, just return error instead. (revive)
python/cmd/pulumi-language-python/main.go:863:2: if-return: redundant if ...; err != nil check, just return error instead. (revive)
python/python.go:230:2: redefines-builtin-id: redefinition of the built-in function print (revive)
```
**tests**
```
integration/integration_util_test.go:282:11: superfluous-else: if block ends with a continue statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) (revive)
```
2023-03-20 23:48:02 +00:00
|
|
|
|
|
|
|
defer func() {
|
|
|
|
assert.NoError(t, mutex.Unlock())
|
|
|
|
}()
|
|
|
|
break
|
2022-11-03 20:30:35 +00:00
|
|
|
}
|
|
|
|
|
2023-02-02 20:46:22 +00:00
|
|
|
// Context may hav expired
|
|
|
|
// by the time we acquired the lock.
|
|
|
|
if ctx.Err() == nil {
|
|
|
|
fn()
|
|
|
|
close(lockWait)
|
|
|
|
}
|
2022-11-03 20:30:35 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
select {
|
2023-02-02 20:46:22 +00:00
|
|
|
case <-ctx.Done():
|
2022-11-03 20:30:35 +00:00
|
|
|
t.Fatalf("timed out waiting for lock on %s", lockfile)
|
|
|
|
case <-lockWait:
|
|
|
|
// waited for fn, success.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-02 20:46:22 +00:00
|
|
|
// Verifies that if a file lock is already acquired,
|
|
|
|
// synchronouslyDo is able to time out properly.
|
|
|
|
func TestSynchronouslyDo_timeout(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
path := filepath.Join(t.TempDir(), "foo")
|
|
|
|
mu := fsutil.NewFileMutex(path)
|
|
|
|
require.NoError(t, mu.Lock())
|
|
|
|
defer func() {
|
|
|
|
assert.NoError(t, mu.Unlock())
|
|
|
|
}()
|
|
|
|
|
|
|
|
fakeT := nonfatalT{T: t}
|
|
|
|
synchronouslyDo(&fakeT, path, 10*time.Millisecond, func() {
|
|
|
|
t.Errorf("timed-out operation should not be called")
|
|
|
|
})
|
|
|
|
|
|
|
|
assert.True(t, fakeT.fatal, "must have a fatal failure")
|
|
|
|
if assert.Len(t, fakeT.messages, 1) {
|
|
|
|
assert.Contains(t, fakeT.messages[0], "timed out waiting")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// nonfatalT wraps a testing.T to capture fatal errors.
|
|
|
|
type nonfatalT struct {
|
|
|
|
*testing.T
|
|
|
|
|
|
|
|
mu sync.Mutex
|
|
|
|
fatal bool
|
|
|
|
messages []string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *nonfatalT) Fatalf(msg string, args ...interface{}) {
|
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
|
|
|
|
t.fatal = true
|
|
|
|
t.messages = append(t.messages, fmt.Sprintf(msg, args...))
|
|
|
|
}
|
|
|
|
|
2022-11-03 20:30:35 +00:00
|
|
|
// Test methods that create resources.
|
|
|
|
func testConstructMethodsResources(t *testing.T, lang string, dependencies ...string) {
|
|
|
|
const testDir = "construct_component_methods_resources"
|
|
|
|
runComponentSetup(t, testDir)
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
componentDir string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
componentDir: "testcomponent",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
componentDir: "testcomponent-python",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
componentDir: "testcomponent-go",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, test := range tests {
|
|
|
|
test := test
|
|
|
|
t.Run(test.componentDir, func(t *testing.T) {
|
2023-03-03 16:36:39 +00:00
|
|
|
localProviders := []integration.LocalDependency{
|
|
|
|
{Package: "testcomponent", Path: filepath.Join(testDir, test.componentDir)},
|
|
|
|
}
|
2022-11-03 20:30:35 +00:00
|
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
|
|
Dir: filepath.Join(testDir, lang),
|
|
|
|
Dependencies: dependencies,
|
|
|
|
LocalProviders: localProviders,
|
|
|
|
Quick: true,
|
|
|
|
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
|
|
|
|
assert.NotNil(t, stackInfo.Deployment)
|
|
|
|
assert.Equal(t, 6, len(stackInfo.Deployment.Resources))
|
|
|
|
var hasExpectedResource bool
|
|
|
|
var result string
|
|
|
|
for _, res := range stackInfo.Deployment.Resources {
|
2023-11-20 08:59:00 +00:00
|
|
|
if res.URN.Name() == "myrandom" {
|
2022-11-03 20:30:35 +00:00
|
|
|
hasExpectedResource = true
|
|
|
|
result = res.Outputs["result"].(string)
|
|
|
|
assert.Equal(t, float64(10), res.Inputs["length"])
|
|
|
|
assert.Equal(t, 10, len(result))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert.True(t, hasExpectedResource)
|
|
|
|
assert.Equal(t, result, stackInfo.Outputs["result"])
|
|
|
|
},
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test failures returned from methods are observed.
|
|
|
|
func testConstructMethodsErrors(t *testing.T, lang string, dependencies ...string) {
|
|
|
|
const testDir = "construct_component_methods_errors"
|
|
|
|
runComponentSetup(t, testDir)
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
componentDir string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
componentDir: "testcomponent",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
componentDir: "testcomponent-python",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
componentDir: "testcomponent-go",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, test := range tests {
|
|
|
|
test := test
|
|
|
|
t.Run(test.componentDir, func(t *testing.T) {
|
|
|
|
stderr := &bytes.Buffer{}
|
|
|
|
expectedError := "the failure reason (the failure property)"
|
|
|
|
|
|
|
|
localProvider := integration.LocalDependency{
|
|
|
|
Package: "testcomponent", Path: filepath.Join(testDir, test.componentDir),
|
|
|
|
}
|
|
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
|
|
Dir: filepath.Join(testDir, lang),
|
|
|
|
Dependencies: dependencies,
|
|
|
|
LocalProviders: []integration.LocalDependency{localProvider},
|
|
|
|
Quick: true,
|
|
|
|
Stderr: stderr,
|
|
|
|
ExpectFailure: true,
|
|
|
|
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
|
|
|
|
output := stderr.String()
|
|
|
|
assert.Contains(t, output, expectedError)
|
|
|
|
},
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
[sdk/nodejs] Fix provider for resource methods (#13796)
The `Resource` class in the Node.js SDK has the following internal
property:
```typescript
/** @internal */
readonly __prov?: ProviderResource;
```
When a resource is created, the provider specified for the resource is
stored in this property. If it is set, it is passed along in the `Call`
request when a method is called on the resource.
Prior to #13282, the property was only set for custom resources in
`Resource`'s constructor:
```typescript
this.__prov = custom ? opts.provider : undefined;
```
With #13282, it was changed to also store the value for remote
components:
```diff
- this.__prov = custom ? opts.provider : undefined;
+ this.__prov = custom || remote ? opts.provider : undefined;
```
This regressed the behavior when calling a method on a remote component
that had an explicit provider that wasn't the component provider, but
some other provider (e.g. AWS provider) specified as:
```typescript
const component = new MyRemoteComponent("comp", {
}, { provider: awsProvider });
```
The `awsProvider` was being stored in `Resource.__prov`, and when making
the method call on the resource, it would try to invoke `Call` on the
AWS provider, rather than calling the remote component provider's
`Call`, which resulted in an error.
Note that specifying the AWS provider using the more verbose `providers:
[awsProvider]` works around the issue.
The fix is to only set `__prov` if the provider's package is the same as
the resource's package. Otherwise, don't set it, because the user is
specifying a provider with the `provider: awsProvider` syntax as
shorthand for `providers: [awsProvider]`.
Fixes #13777
2023-08-30 14:49:53 +00:00
|
|
|
// Tests methods work when there is an explicit provider for another provider set on the component.
|
|
|
|
func testConstructMethodsProvider(t *testing.T, lang string, dependencies ...string) {
|
|
|
|
const testDir = "construct_component_methods_provider"
|
|
|
|
runComponentSetup(t, testDir)
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
componentDir string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
componentDir: "testcomponent",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
componentDir: "testcomponent-python",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
componentDir: "testcomponent-go",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, test := range tests {
|
|
|
|
test := test
|
|
|
|
t.Run(test.componentDir, func(t *testing.T) {
|
|
|
|
localProvider := integration.LocalDependency{
|
|
|
|
Package: "testcomponent", Path: filepath.Join(testDir, test.componentDir),
|
|
|
|
}
|
|
|
|
testProvider := integration.LocalDependency{
|
|
|
|
Package: "testprovider", Path: filepath.Join("..", "testprovider"),
|
|
|
|
}
|
|
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
|
|
Dir: filepath.Join(testDir, lang),
|
|
|
|
Dependencies: dependencies,
|
|
|
|
LocalProviders: []integration.LocalDependency{localProvider, testProvider},
|
|
|
|
Quick: true,
|
|
|
|
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
|
|
|
|
assert.Equal(t, "Hello World, Alice!", stackInfo.Outputs["message1"])
|
|
|
|
assert.Equal(t, "Hi There, Bob!", stackInfo.Outputs["message2"])
|
|
|
|
},
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-03 20:30:35 +00:00
|
|
|
func testConstructOutputValues(t *testing.T, lang string, dependencies ...string) {
|
|
|
|
const testDir = "construct_component_output_values"
|
|
|
|
runComponentSetup(t, testDir)
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
componentDir string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
componentDir: "testcomponent",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
componentDir: "testcomponent-python",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
componentDir: "testcomponent-go",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, test := range tests {
|
|
|
|
test := test
|
|
|
|
t.Run(test.componentDir, func(t *testing.T) {
|
2023-03-03 16:36:39 +00:00
|
|
|
localProviders := []integration.LocalDependency{
|
|
|
|
{Package: "testcomponent", Path: filepath.Join(testDir, test.componentDir)},
|
|
|
|
}
|
2022-11-03 20:30:35 +00:00
|
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
|
|
Dir: filepath.Join(testDir, lang),
|
|
|
|
Dependencies: dependencies,
|
|
|
|
LocalProviders: localProviders,
|
|
|
|
Quick: true,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var previewSummaryRegex = regexp.MustCompile(
|
|
|
|
`{\s+"steps": \[[\s\S]+],\s+"duration": \d+,\s+"changeSummary": {[\s\S]+}\s+}`)
|
|
|
|
|
|
|
|
func assertOutputContainsEvent(t *testing.T, evt apitype.EngineEvent, output string) {
|
|
|
|
evtJSON := bytes.Buffer{}
|
|
|
|
encoder := json.NewEncoder(&evtJSON)
|
|
|
|
encoder.SetEscapeHTML(false)
|
|
|
|
err := encoder.Encode(evt)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Contains(t, output, evtJSON.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
// printfTestValidation is used by the TestPrintfXYZ test cases in the language-specific test
|
|
|
|
// files. It validates that there are a precise count of expected stdout/stderr lines in the test output.
|
|
|
|
func printfTestValidation(t *testing.T, stack integration.RuntimeValidationStackInfo) {
|
|
|
|
var foundStdout int
|
|
|
|
var foundStderr int
|
|
|
|
for _, ev := range stack.Events {
|
|
|
|
if de := ev.DiagnosticEvent; de != nil {
|
|
|
|
if strings.HasPrefix(de.Message, fmt.Sprintf("Line %d", foundStdout)) {
|
|
|
|
foundStdout++
|
|
|
|
} else if strings.HasPrefix(de.Message, fmt.Sprintf("Errln %d", foundStderr+10)) {
|
|
|
|
foundStderr++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert.Equal(t, 11, foundStdout)
|
|
|
|
assert.Equal(t, 11, foundStderr)
|
|
|
|
}
|
2023-06-26 06:20:14 +00:00
|
|
|
|
|
|
|
func testConstructProviderExplicit(t *testing.T, lang string, dependencies []string) {
|
|
|
|
const testDir = "construct_component_provider_explicit"
|
|
|
|
runComponentSetup(t, testDir)
|
|
|
|
|
|
|
|
localProvider := integration.LocalDependency{
|
|
|
|
Package: "testcomponent", Path: filepath.Join(testDir, "testcomponent-go"),
|
|
|
|
}
|
|
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
|
|
Dir: filepath.Join(testDir, lang),
|
|
|
|
Dependencies: dependencies,
|
|
|
|
LocalProviders: []integration.LocalDependency{localProvider},
|
|
|
|
Quick: true,
|
|
|
|
NoParallel: true, // already called by tests
|
|
|
|
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
|
|
|
|
assert.Equal(t, "hello world", stackInfo.Outputs["message"])
|
|
|
|
assert.Equal(t, "hello world", stackInfo.Outputs["nestedMessage"])
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
Support returning plain values from methods (#13592)
Support returning plain values from methods.
Implements Node, Python and Go support.
Remaining:
- [x] test receiving unknowns
- [x] acceptance tests written and passing locally for Node, Python, Go
clients against a Go server
- [x] acceptance tests passing in CI
- [x] tickets filed for remaining languages
- [x] https://github.com/pulumi/pulumi-yaml/issues/499
- [x] https://github.com/pulumi/pulumi-java/issues/1193
- [x] https://github.com/pulumi/pulumi-dotnet/issues/170
Known limitations:
- this is technically a breaking change in case there is code out there
that already uses methods that return Plain: true
- struct-wrapping limitation: the provider for the component resource
needs to still wrap the plain-returning Method response with a 1-arg
struct; by convention the field is named "res", and this is how it
travels through the plumbing
- resources cannot return plain values yet
- the provider for the component resource cannot have unknown
configuration, if it does, the methods will not be called
- Per Luke https://github.com/pulumi/pulumi/issues/11520 this might not
be supported/realizable yet
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/12709
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [ ] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-18 06:02:06 +00:00
|
|
|
|
|
|
|
func testConstructComponentConfigureProviderCommonOptions() integration.ProgramTestOptions {
|
|
|
|
const testDir = "construct_component_configure_provider"
|
|
|
|
localProvider := integration.LocalDependency{
|
|
|
|
Package: "metaprovider", Path: filepath.Join(testDir, "testcomponent-go"),
|
|
|
|
}
|
|
|
|
return integration.ProgramTestOptions{
|
2023-12-02 17:16:09 +00:00
|
|
|
NoParallel: true,
|
Support returning plain values from methods (#13592)
Support returning plain values from methods.
Implements Node, Python and Go support.
Remaining:
- [x] test receiving unknowns
- [x] acceptance tests written and passing locally for Node, Python, Go
clients against a Go server
- [x] acceptance tests passing in CI
- [x] tickets filed for remaining languages
- [x] https://github.com/pulumi/pulumi-yaml/issues/499
- [x] https://github.com/pulumi/pulumi-java/issues/1193
- [x] https://github.com/pulumi/pulumi-dotnet/issues/170
Known limitations:
- this is technically a breaking change in case there is code out there
that already uses methods that return Plain: true
- struct-wrapping limitation: the provider for the component resource
needs to still wrap the plain-returning Method response with a 1-arg
struct; by convention the field is named "res", and this is how it
travels through the plumbing
- resources cannot return plain values yet
- the provider for the component resource cannot have unknown
configuration, if it does, the methods will not be called
- Per Luke https://github.com/pulumi/pulumi/issues/11520 this might not
be supported/realizable yet
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/12709
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [ ] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-11-18 06:02:06 +00:00
|
|
|
Config: map[string]string{
|
|
|
|
"proxy": "FromEnv",
|
|
|
|
},
|
|
|
|
LocalProviders: []integration.LocalDependency{localProvider},
|
|
|
|
Quick: false, // intentional, need to test preview here
|
|
|
|
AllowEmptyPreviewChanges: true, // Pulumi will warn that provider has unknowns in its config
|
|
|
|
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
|
|
|
|
assert.Contains(t, stackInfo.Outputs, "keyAlgo")
|
|
|
|
assert.Equal(t, "ECDSA", stackInfo.Outputs["keyAlgo"])
|
|
|
|
assert.Contains(t, stackInfo.Outputs, "keyAlgo2")
|
|
|
|
assert.Equal(t, "ECDSA", stackInfo.Outputs["keyAlgo2"])
|
|
|
|
|
|
|
|
var providerURNID string
|
|
|
|
for _, r := range stackInfo.Deployment.Resources {
|
|
|
|
if strings.Contains(string(r.URN), "PrivateKey") {
|
|
|
|
providerURNID = r.Provider
|
|
|
|
}
|
|
|
|
}
|
|
|
|
require.NotEmptyf(t, providerURNID, "Did not find the provider of PrivateKey resource")
|
|
|
|
var providerFromEnvSetting *bool
|
|
|
|
for _, r := range stackInfo.Deployment.Resources {
|
|
|
|
if fmt.Sprintf("%s::%s", r.URN, r.ID) == providerURNID {
|
|
|
|
providerFromEnvSetting = new(bool)
|
|
|
|
|
|
|
|
proxy, ok := r.Inputs["proxy"]
|
|
|
|
require.Truef(t, ok, "expected %q Inputs to contain 'proxy'", providerURNID)
|
|
|
|
|
|
|
|
proxyMap, ok := proxy.(map[string]any)
|
|
|
|
require.Truef(t, ok, "expected %q Inputs 'proxy' to be of type map[string]any", providerURNID)
|
|
|
|
|
|
|
|
fromEnv, ok := proxyMap["fromEnv"]
|
|
|
|
require.Truef(t, ok, "expected %q Inputs 'proxy' to contain 'fromEnv'", providerURNID)
|
|
|
|
|
|
|
|
fromEnvB, ok := fromEnv.(bool)
|
|
|
|
require.Truef(t, ok, "expected %q Inputs 'proxy.fromEnv' to have type bool", providerURNID)
|
|
|
|
|
|
|
|
*providerFromEnvSetting = fromEnvB
|
|
|
|
}
|
|
|
|
}
|
|
|
|
require.NotNilf(t, providerFromEnvSetting,
|
|
|
|
"Did not find the inputs of the provider PrivateKey was provisioned with")
|
|
|
|
require.Truef(t, *providerFromEnvSetting,
|
|
|
|
"Expected PrivateKey to be provisioned with a provider with fromEnv=true")
|
|
|
|
|
|
|
|
require.Equalf(t, float64(42), stackInfo.Outputs["meaningOfLife"],
|
|
|
|
"Expected meaningOfLife output to be set to the integer 42")
|
|
|
|
require.Equalf(t, float64(42), stackInfo.Outputs["meaningOfLife2"],
|
|
|
|
"Expected meaningOfLife2 output to be set to the integer 42")
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|