Compare commits

...

43 commits

Author SHA1 Message Date
Anton Tayanovskyy 5d84f19c10 Use NPM instead of yarn 2021-09-20 15:39:25 -04:00
Anton Tayanovskyy f87cce811b Revert another redundant mechanism 2021-09-20 14:46:10 -04:00
Anton Tayanovskyy 7ea1a887f7 Revert unexpected changes around violated pointer equality 2021-09-20 14:39:55 -04:00
Anton Tayanovskyy a987213849 Skip node tests everywhere except where they are actually written 2021-09-20 12:38:28 -04:00
Anton Tayanovskyy be53d8ffba Make output-funcs test pass on Node 2021-09-20 12:31:44 -04:00
Anton Tayanovskyy ebc8f5e87b Fix bad merge 2021-09-20 11:56:15 -04:00
Anton Tayanovskyy 4d1f52ac9e WIP 2021-09-20 11:50:05 -04:00
Anton Tayanovskyy 793fadc2af Simplify a bit 2021-09-20 11:50:05 -04:00
Anton Tayanovskyy e98d9ff7dd Make it compile 2021-09-20 11:50:05 -04:00
Anton Tayanovskyy 7644edc14e Revert .sdkcodegenignore drift 2021-09-20 11:50:05 -04:00
Anton Tayanovskyy 3b152acd86 Revert sdk_driver.go drift 2021-09-20 11:50:03 -04:00
Anton Tayanovskyy f1906d20cf Revert helpers.go drift 2021-09-20 11:49:38 -04:00
Anton Tayanovskyy 8b1b66aea2 Finish up unit tests 2021-09-20 11:49:04 -04:00
Anton Tayanovskyy 65fc505f50 Restore most of the existing unit tests 2021-09-20 11:49:04 -04:00
Anton Tayanovskyy fa3d827a40 Tidy up tests 2021-09-20 11:49:04 -04:00
Anton Tayanovskyy fe5c782a61 WIP 2021-09-20 11:49:00 -04:00
Anton Tayanovskyy 533c8fd102 Actually wrap new type with mkInput forms 2021-09-20 11:46:55 -04:00
Anton Tayanovskyy 809b6841f9 Accept other funcs 2021-09-20 11:46:55 -04:00
Anton Tayanovskyy 8587bf4049 Accept codegen for now 2021-09-20 11:46:55 -04:00
Anton Tayanovskyy 30c91e2c5c Gen all schemata 2021-09-20 11:46:55 -04:00
Anton Tayanovskyy baed6beddc Fix compilation and test compilation 2021-09-20 11:46:55 -04:00
Anton Tayanovskyy 60438a8cba Revert helpers.go changes 2021-09-20 11:46:53 -04:00
Anton Tayanovskyy af3eb07a8a Merged 2021-09-20 11:45:13 -04:00
Anton Tayanovskyy f473cb5510 Node: rename to fnApply and accept fixtures 2021-09-20 11:45:13 -04:00
Anton Tayanovskyy a408388ad7 Add const example 2021-09-20 11:45:13 -04:00
Anton Tayanovskyy 8f54f616d5 Initial node support for output funcs and a first set of tests 2021-09-20 11:45:13 -04:00
Anton Tayanovskyy 38dcb00450 Fix lint 2021-09-20 11:35:44 -04:00
Anton Tayanovskyy 09a49fff40 Make go tests work by copying them into the tree from go-extras 2021-09-20 11:22:29 -04:00
Anton Tayanovskyy a6a25f5715 Update pkg/codegen/internal/test/sdk_driver.go
Co-authored-by: Ian Wahbe <ian@wahbe.com>
2021-09-20 09:42:01 -04:00
Anton Tayanovskyy cbb76c8aad Build against local Go SDK 2021-09-20 09:42:01 -04:00
Anton Tayanovskyy b79939d7de Address lint issues on Go code 2021-09-20 09:42:01 -04:00
Anton Tayanovskyy 761c5c52ca Move tree schma tests to a proper place 2021-09-20 09:42:01 -04:00
Anton Tayanovskyy e1e288d1fe Infer skipping tests from skipping compiles 2021-09-20 09:42:01 -04:00
Anton Tayanovskyy fed3e82dc8 Start building a test-running pass 2021-09-20 09:42:01 -04:00
Anton Tayanovskyy bcf9c1f401 Fix nested-module 2021-09-20 09:42:01 -04:00
Anton Tayanovskyy 15ae1c0618 Fix dash-named-schema 2021-09-20 09:42:01 -04:00
Anton Tayanovskyy 90b67ecc49 Fix simple-enum-schema 2021-09-20 09:42:01 -04:00
Anton Tayanovskyy 71fbbf43dc Sounds like we can use non-github package names to ensure things are local 2021-09-20 09:42:01 -04:00
Anton Tayanovskyy 1f77c7509f WIP 2021-09-20 09:42:01 -04:00
Anton Tayanovskyy cc26af2960 Update dotnet; need to follow up on version.txt quirks 2021-09-20 09:41:57 -04:00
Anton Tayanovskyy 71f2330ae0 Upgrade Node 2021-09-20 09:40:21 -04:00
Anton Tayanovskyy dedffe1011 Remove temp debug output 2021-09-20 09:40:21 -04:00
Anton Tayanovskyy f1b6920630 Multi-pass, in-place checks for SDK codegen tests; toward working Python checks 2021-09-20 09:40:21 -04:00
65 changed files with 2420 additions and 408 deletions

3
.gitignore vendored
View file

@ -47,3 +47,6 @@ pulumi-python3-shim.cmd
pulumi-python-shim.cmd
pulumi-analyzer-policy.cmd
pulumi-analyzer-policy-python.cmd
__pycache__

View file

@ -157,12 +157,12 @@ func initTestPackageSpec(t *testing.T) {
{{% /example %}}
{{% /examples %}}
## Import
## Import
The import docs would be here
` + codeFence + `sh
$ pulumi import prov:module/resource:Resource test test
$ pulumi import prov:module/resource:Resource test test
` + codeFence + `
`,
},
@ -413,5 +413,8 @@ func generatePackage(tool string, pkg *schema.Package, extraFiles map[string][]b
func TestGeneratePackage(t *testing.T) {
// TODO: do we have a compile step on templates?
test.TestSDKCodegen(t, "docs", generatePackage, func(*testing.T, string) {})
test.TestSDKCodegen(t, &test.SDKCodegenOptions{
Language: "docs",
GenPackage: generatePackage,
})
}

View file

@ -1,8 +1,6 @@
package dotnet
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
@ -15,21 +13,30 @@ import (
)
func TestGeneratePackage(t *testing.T) {
test.TestSDKCodegen(t, "dotnet", GeneratePackage, typeCheckGeneratedPackage)
test.TestSDKCodegen(t, &test.SDKCodegenOptions{
Language: "dotnet",
GenPackage: generatePackageWithVersion,
Checks: map[string]test.CodegenCheck{
"dotnet/compile": typeCheckGeneratedPackage,
},
})
}
// TODO replace this with GeneratePackage when https://github.com/pulumi/pulumi/pull/7938 lands.
func generatePackageWithVersion(
tool string,
pkg *schema.Package,
extraFiles map[string][]byte) (map[string][]byte, error) {
if extraFiles == nil {
extraFiles = make(map[string][]byte)
}
extraFiles["version.txt"] = []byte("0.0.0\n")
return GeneratePackage(tool, pkg, extraFiles)
}
func typeCheckGeneratedPackage(t *testing.T, pwd string) {
var err error
var dotnet string
// TODO remove when https://github.com/pulumi/pulumi/pull/7938 lands
version := "0.0.0\n"
err = os.WriteFile(filepath.Join(pwd, "version.txt"), []byte(version), 0600)
if !os.IsExist(err) {
require.NoError(t, err)
}
// endTODO
dotnet, err = executable.FindExecutable("dotnet")
require.NoError(t, err)
cmdOptions := integration.ProgramTestOptions{}

View file

@ -1,29 +1,23 @@
package gen
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/pulumi/pulumi/pkg/v3/codegen/internal/test"
"github.com/pulumi/pulumi/pkg/v3/codegen/internal/test/testdata/simple-enum-schema/go/plant"
tree "github.com/pulumi/pulumi/pkg/v3/codegen/internal/test/testdata/simple-enum-schema/go/plant/tree/v1"
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
"github.com/pulumi/pulumi/pkg/v3/testing/integration"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/executable"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func TestInputUsage(t *testing.T) {
@ -66,53 +60,63 @@ func TestGoPackageName(t *testing.T) {
func TestGeneratePackage(t *testing.T) {
generatePackage := func(tool string, pkg *schema.Package, files map[string][]byte) (map[string][]byte, error) {
for f := range files {
t.Logf("Ignoring extraFile %s", f)
}
return GeneratePackage(tool, pkg)
}
test.TestSDKCodegen(t, "go", generatePackage, typeCheckGeneratedPackage)
test.TestSDKCodegen(t, &test.SDKCodegenOptions{
Language: "go",
GenPackage: generatePackage,
Checks: map[string]test.CodegenCheck{
"go/compile": typeCheckGeneratedPackage,
"go/test": testGeneratedPackage,
},
})
}
func typeCheckGeneratedPackage(t *testing.T, pwd string) {
var err error
var ex string
ex, err = executable.FindExecutable("go")
require.NoError(t, err)
// go SDKs live in their own folder:
// typecheck/go/$NAME/$FILES
// where FILES contains all the project files (including go.mod)
func inferModuleName(codeDir string) string {
// For example for this path:
//
// This is not true when `package.language.go.rootPackageName` is set.
dir, err := ioutil.ReadDir(pwd)
require.NoError(t, err)
root := pwd
if len(dir) == 1 {
require.True(t, dir[0].IsDir())
root = filepath.Join(pwd, dir[0].Name())
}
t.Logf("*** Testing go in dir: %q ***", root)
// codeDir = "../internal/test/testdata/external-resource-schema/go/"
//
// We will generate "$codeDir/go.mod" using
// `external-resource-schema` as the module name so that it
// can compile independently.
return filepath.Base(filepath.Dir(codeDir))
}
var stdout, stderr bytes.Buffer
cmdOptions := integration.ProgramTestOptions{Stderr: &stderr, Stdout: &stdout}
// We don't want to corrupt the global go.mod and go.sum with packages we
// don't actually depend on. For this, we need to have each go package be
// it's own module.
err = integration.RunCommand(t, "mod init",
[]string{ex, "mod", "init", "github.com/pulumi/test/internal"}, root, &cmdOptions)
if err != nil {
stdout := stdout.String()
stderr := stderr.String()
if len(stdout) > 0 {
t.Logf("stdout: %s", stdout)
}
if len(stderr) > 0 {
t.Logf("stderr: %s", stderr)
}
t.FailNow()
func typeCheckGeneratedPackage(t *testing.T, codeDir string) {
sdk, err := filepath.Abs(filepath.Join("..", "..", "..", "sdk"))
require.NoError(t, err)
goExe, err := executable.FindExecutable("go")
require.NoError(t, err)
// Remove existing `go.mod` first otherise `go mod init` fails.
goMod := filepath.Join(codeDir, "go.mod")
alreadyHaveGoMod, err := test.PathExists(goMod)
require.NoError(t, err)
if alreadyHaveGoMod {
err := os.Remove(goMod)
require.NoError(t, err)
}
err = integration.RunCommand(t, "get", []string{ex, "get"}, root, &cmdOptions)
require.NoError(t, err)
err = integration.RunCommand(t, "go build", []string{ex, "build"}, root, &cmdOptions)
test.RunCommand(t, "go_mod_init", codeDir, goExe, "mod", "init", inferModuleName(codeDir))
replacement := fmt.Sprintf("github.com/pulumi/pulumi/sdk/v3=%s", sdk)
test.RunCommand(t, "go_mod_edit", codeDir, goExe, "mod", "edit", "-replace", replacement)
test.RunCommand(t, "go_mod_tidy", codeDir, goExe, "mod", "tidy")
test.RunCommand(t, "go_build", codeDir, goExe, "build", "-v", "all")
}
func testGeneratedPackage(t *testing.T, codeDir string) {
goExe, err := executable.FindExecutable("go")
require.NoError(t, err)
test.RunCommand(t, "go-test", codeDir, goExe, "test", fmt.Sprintf("%s/...", inferModuleName(codeDir)))
}
func TestGenerateTypeNames(t *testing.T) {
@ -135,177 +139,6 @@ func TestGenerateTypeNames(t *testing.T) {
})
}
type mocks int
func (mocks) NewResource(args pulumi.MockResourceArgs) (string, resource.PropertyMap, error) {
return args.Name + "_id", args.Inputs, nil
}
func (mocks) Call(args pulumi.MockCallArgs) (resource.PropertyMap, error) {
return args.Args, nil
}
func TestEnumUsage(t *testing.T) {
t.Run("Success", func(t *testing.T) {
require.NoError(t, pulumi.RunErr(func(ctx *pulumi.Context) error {
rubberTree, err := tree.NewRubberTree(ctx, "blah", &tree.RubberTreeArgs{
Container: &plant.ContainerArgs{
Color: plant.ContainerColorRed,
Material: pulumi.String("ceramic"),
Size: plant.ContainerSizeFourInch,
},
Farm: tree.Farm_Plants_R_Us,
Type: tree.RubberTreeVarietyRuby,
})
require.NoError(t, err)
require.NotNil(t, rubberTree)
var wg sync.WaitGroup
wg.Add(1)
pulumi.All(
rubberTree.URN(),
rubberTree.Container.Material(),
rubberTree.Container.Color(),
rubberTree.Container.Size(),
rubberTree.Container.Brightness(),
rubberTree.Type,
).ApplyT(func(all []interface{}) error {
urn := all[0].(pulumi.URN)
material := all[1].(*string)
color := all[2].(*string)
size := all[3].(*plant.ContainerSize)
brightness := all[4].(*plant.ContainerBrightness)
typ := all[5].(tree.RubberTreeVariety)
assert.Equal(t, *material, "ceramic", "unexpected material on resource: %v", urn)
assert.Equal(t, *color, "red", "unexpected color on resource: %v", urn)
assert.Equal(t, *size, plant.ContainerSizeFourInch, "unexpected size on resource: %v", urn)
assert.Nil(t, brightness)
assert.Equal(t, typ, tree.RubberTreeVarietyRuby, "unexpected type on resource: %v", urn)
wg.Done()
return nil
})
wg.Wait()
return nil
}, pulumi.WithMocks("project", "stack", mocks(0))))
})
t.Run("StringsForRelaxedEnum", func(t *testing.T) {
require.NoError(t, pulumi.RunErr(func(ctx *pulumi.Context) error {
rubberTree, err := tree.NewRubberTree(ctx, "blah", &tree.RubberTreeArgs{
Container: plant.ContainerArgs{
Color: pulumi.String("Magenta"),
Material: pulumi.String("ceramic"),
Size: plant.ContainerSize(22),
},
Farm: tree.Farm_Plants_R_Us,
Type: tree.RubberTreeVarietyRuby,
})
require.NoError(t, err)
require.NotNil(t, rubberTree)
var wg sync.WaitGroup
wg.Add(1)
pulumi.All(
rubberTree.URN(),
rubberTree.Container.Material(),
rubberTree.Container.Color(),
rubberTree.Container.Size(),
rubberTree.Type,
).ApplyT(func(all []interface{}) error {
urn := all[0].(pulumi.URN)
material := all[1].(*string)
color := all[2].(*string)
size := all[3].(*plant.ContainerSize)
typ := all[4].(tree.RubberTreeVariety)
assert.Equal(t, *material, "ceramic", "unexpected material on resource: %v", urn)
assert.Equal(t, *color, "Magenta", "unexpected color on resource: %v", urn)
assert.Equal(t, *size, plant.ContainerSize(22), "unexpected size on resource: %v", urn)
assert.Equal(t, typ, tree.RubberTreeVarietyRuby, "unexpected type on resource: %v", urn)
wg.Done()
return nil
})
wg.Wait()
return nil
}, pulumi.WithMocks("project", "stack", mocks(1))))
})
t.Run("StringsForStrictEnum", func(t *testing.T) {
require.NoError(t, pulumi.RunErr(func(ctx *pulumi.Context) error {
rubberTree, err := tree.NewRubberTree(ctx, "blah", &tree.RubberTreeArgs{
Container: plant.ContainerArgs{
Color: pulumi.String("Magenta"),
Material: pulumi.String("ceramic"),
Size: plant.ContainerSize(22),
},
Farm: tree.Farm_Plants_R_Us,
Type: tree.RubberTreeVarietyBurgundy,
})
require.NoError(t, err)
require.NotNil(t, rubberTree)
var wg sync.WaitGroup
wg.Add(1)
pulumi.All(
rubberTree.URN(),
rubberTree.Container.Material(),
rubberTree.Container.Color(),
rubberTree.Container.Size(),
rubberTree.Type,
).ApplyT(func(all []interface{}) error {
urn := all[0].(pulumi.URN)
material := all[1].(*string)
color := all[2].(*string)
size := all[3].(*plant.ContainerSize)
typ := all[4].(tree.RubberTreeVariety)
assert.Equal(t, *material, "ceramic", "unexpected material on resource: %v", urn)
assert.Equal(t, *color, "Magenta", "unexpected color on resource: %v", urn)
assert.Equal(t, *size, plant.ContainerSize(22), "unexpected size on resource: %v", urn)
assert.Equal(t, typ, tree.RubberTreeVarietyBurgundy, "unexpected type on resource: %v", urn)
wg.Done()
return nil
})
wg.Wait()
return nil
}, pulumi.WithMocks("project", "stack", mocks(1))))
})
t.Run("EnumOutputs", func(t *testing.T) {
require.NoError(t, pulumi.RunErr(func(ctx *pulumi.Context) error {
rubberTree, err := tree.NewRubberTree(ctx, "blah", &tree.RubberTreeArgs{
Container: plant.ContainerArgs{
Color: plant.ContainerColor("Magenta").ToContainerColorOutput().ToStringOutput(),
Material: pulumi.String("ceramic").ToStringOutput(),
Size: plant.ContainerSize(22).ToContainerSizeOutput(),
},
Farm: tree.Farm_Plants_R_Us.ToFarmPtrOutput().ToStringPtrOutput(),
Type: tree.RubberTreeVarietyBurgundy.ToRubberTreeVarietyOutput(),
})
require.NoError(t, err)
require.NotNil(t, rubberTree)
var wg sync.WaitGroup
wg.Add(1)
pulumi.All(
rubberTree.URN(),
rubberTree.Container.Material(),
rubberTree.Container.Color(),
rubberTree.Container.Size(),
rubberTree.Type,
).ApplyT(func(all []interface{}) error {
urn := all[0].(pulumi.URN)
material := all[1].(*string)
color := all[2].(*string)
size := all[3].(*plant.ContainerSize)
typ := all[4].(tree.RubberTreeVariety)
assert.Equal(t, *material, "ceramic", "unexpected material on resource: %v", urn)
assert.Equal(t, *color, "Magenta", "unexpected color on resource: %v", urn)
assert.Equal(t, *size, plant.ContainerSize(22), "unexpected size on resource: %v", urn)
assert.Equal(t, typ, tree.RubberTreeVarietyBurgundy, "unexpected type on resource: %v", urn)
wg.Done()
return nil
})
wg.Wait()
return nil
}, pulumi.WithMocks("project", "stack", mocks(1))))
})
}
func TestGenerateOutputFuncs(t *testing.T) {
testDir := filepath.Join("..", "internal", "test", "testdata", "output-funcs")

View file

@ -17,10 +17,12 @@ package test
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
@ -28,6 +30,8 @@ import (
"gopkg.in/yaml.v3"
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
"github.com/pulumi/pulumi/pkg/v3/testing/integration"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/executable"
)
// GenPkgSignature corresponds to the shape of the codegen GeneratePackage functions.
@ -76,7 +80,11 @@ func LoadFiles(dir, lang string, files []string) (map[string][]byte, error) {
return result, nil
}
func loadDirectory(fs map[string][]byte, root, path string) error {
// Recursively loads files from a directory into the `fs` map. Ignores
// entries that match `ignore(path)==true`, also skips descending into
// directories that are ignored. This is useful for example to avoid
// `node_modules`.
func loadDirectory(fs map[string][]byte, root, path string, ignore func(path string) bool) error {
entries, err := os.ReadDir(path)
if err != nil {
return err
@ -84,8 +92,12 @@ func loadDirectory(fs map[string][]byte, root, path string) error {
for _, e := range entries {
entryPath := filepath.Join(path, e.Name())
if e.IsDir() {
if err = loadDirectory(fs, root, entryPath); err != nil {
relativeEntryPath := entryPath[len(root)+1:]
baseName := filepath.Base(relativeEntryPath)
if ignore != nil && (ignore(relativeEntryPath) || ignore(baseName)) {
// pass
} else if e.IsDir() {
if err = loadDirectory(fs, root, entryPath, ignore); err != nil {
return err
}
} else {
@ -93,8 +105,7 @@ func loadDirectory(fs map[string][]byte, root, path string) error {
if err != nil {
return err
}
name := filepath.ToSlash(entryPath[len(root)+1:])
name := filepath.ToSlash(relativeEntryPath)
fs[name] = contents
}
}
@ -102,12 +113,92 @@ func loadDirectory(fs map[string][]byte, root, path string) error {
return nil
}
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if os.IsNotExist(err) {
return false, nil
}
if err == nil {
return true, nil
}
return false, err
}
// Reads `.sdkcodegenignore` file if present to use as loadDirectory ignore func.
func loadIgnoreMap(dir string) (func(path string) bool, error) {
load1 := func(dir string, ignoredPathSet map[string]bool) error {
p := filepath.Join(dir, ".sdkcodegenignore")
gotIgnore, err := PathExists(p)
if err != nil {
return err
}
if gotIgnore {
contents, err := os.ReadFile(p)
if err != nil {
return err
}
for _, s := range strings.Split(string(contents), "\n") {
s = strings.Trim(s, " \r\n\t")
if s != "" {
ignoredPathSet[s] = true
}
}
}
return nil
}
loadAll := func(dir string, ignoredPathSet map[string]bool) error {
for {
atTopOfRepo, err := PathExists(filepath.Join(dir, ".git"))
if err != nil {
return err
}
err = load1(dir, ignoredPathSet)
if err != nil {
return err
}
if atTopOfRepo || dir == "." {
return nil
}
dir = filepath.Dir(dir)
}
}
ignoredPathSet := make(map[string]bool)
err := loadAll(dir, ignoredPathSet)
if err != nil {
return nil, err
}
return func(path string) bool {
path = strings.ReplaceAll(path, "\\", "/")
_, ignoredPath := ignoredPathSet[path]
return ignoredPath
}, nil
}
// LoadBaseline loads the contents of the given baseline directory.
func LoadBaseline(dir, lang string) (map[string][]byte, error) {
dir = filepath.Join(dir, lang)
fs := map[string][]byte{}
if err := loadDirectory(fs, dir, dir); err != nil {
ignore, err := loadIgnoreMap(dir)
if err != nil {
return nil, err
}
if err := loadDirectory(fs, dir, dir, ignore); err != nil {
return nil, err
}
return fs, nil
@ -117,14 +208,20 @@ func LoadBaseline(dir, lang string) (map[string][]byte, error) {
func ValidateFileEquality(t *testing.T, actual, expected map[string][]byte) bool {
ok := true
for name, file := range expected {
if !assert.Contains(t, actual, name) || !assert.Equal(t, string(file), string(actual[name]), name) {
t.Logf("%s did not agree", name)
_, inActual := actual[name]
if inActual {
if !assert.Equal(t, string(file), string(actual[name]), name) {
t.Logf("%s did not agree", name)
ok = false
}
} else {
t.Logf("File %s was expected but is missing from the actual fileset", name)
ok = false
}
}
for name := range actual {
if _, has := expected[name]; !has {
t.Logf("missing data for %s", name)
if _, inExpected := expected[name]; !inExpected {
t.Logf("File %s from the actual fileset was not expected", name)
ok = false
}
}
@ -142,38 +239,80 @@ func RewriteFilesWhenPulumiAccept(t *testing.T, dir, lang string, actual map[str
baseline := filepath.Join(dir, lang)
// Remove the baseline directory's current contents.
entries, err := os.ReadDir(baseline)
_, err := os.ReadDir(baseline)
switch {
case err == nil:
for _, e := range entries {
err = os.RemoveAll(filepath.Join(baseline, e.Name()))
require.NoError(t, err)
}
err = os.RemoveAll(baseline)
require.NoError(t, err)
case os.IsNotExist(err):
// OK
default:
require.NoError(t, err)
}
WriteTestFiles(t, dir, lang, actual)
for file, bytes := range actual {
relPath := filepath.FromSlash(file)
path := filepath.Join(dir, lang, relPath)
err := writeFileEnsuringDir(path, bytes)
require.NoError(t, err)
}
return true
}
// WriteTestFiles writes out the files generated by GeneratePackage to a directory.
func WriteTestFiles(t *testing.T, dir, lang string, files map[string][]byte) {
var err error
for file, bytes := range files {
relPath := filepath.FromSlash(file)
path := filepath.Join(dir, lang, relPath)
// Useful for populating code-generated destination
// `codeDir=$dir/$lang` with extra manually written files such as the
// unit test files. These files are copied from `$dir/$lang-extras`
// folder if present.
func CopyExtraFiles(t *testing.T, dir, lang string) {
codeDir := filepath.Join(dir, lang)
extrasDir := filepath.Join(dir, fmt.Sprintf("%s-extras", lang))
gotExtras, err := PathExists(extrasDir)
if err = os.MkdirAll(filepath.Dir(path), 0755); err != nil && !os.IsExist(err) {
require.NoError(t, err)
}
err = ioutil.WriteFile(path, bytes, 0600)
require.NoError(t, err)
if !gotExtras {
return
}
if err != nil {
require.NoError(t, err)
return
}
err = filepath.Walk(extrasDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
relPath, err := filepath.Rel(extrasDir, path)
if err != nil {
return err
}
destPath := filepath.Join(codeDir, relPath)
bytes, err := ioutil.ReadFile(path)
if err != nil {
return err
}
err = writeFileEnsuringDir(destPath, bytes)
if err != nil {
return err
}
t.Logf("Copied %s to %s", path, destPath)
return nil
})
require.NoError(t, err)
}
func writeFileEnsuringDir(path string, bytes []byte) error {
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil && !os.IsExist(err) {
return err
}
return ioutil.WriteFile(path, bytes, 0600)
}
// CheckAllFilesGenerated ensures that the set of expected and actual files generated
@ -238,3 +377,32 @@ func ValidateFileTransformer(
ValidateFileEquality(t, actual, expected)
}
func RunCommand(t *testing.T, name string, cwd string, exec string, args ...string) {
exec, err := executable.FindExecutable(exec)
if err != nil {
t.Error(err)
t.FailNow()
}
wd, err := filepath.Abs(cwd)
require.NoError(t, err)
var stdout, stderr bytes.Buffer
cmdOptions := integration.ProgramTestOptions{Stderr: &stderr, Stdout: &stdout, Verbose: true}
err = integration.RunCommand(t,
name,
append([]string{exec}, args...),
wd,
&cmdOptions)
require.NoError(t, err)
if err != nil {
stdout := stdout.String()
stderr := stderr.String()
if len(stdout) > 0 {
t.Logf("stdout: %s", stdout)
}
if len(stderr) > 0 {
t.Logf("stderr: %s", stderr)
}
t.FailNow()
}
}

View file

@ -1,28 +1,40 @@
package test
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/pulumi/pulumi/pkg/v3/codegen"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
)
type ThenFunc func(t *testing.T, testDir string)
// Defines an extra check logic that accepts the directory with the
// generated code, typically `$TestDir/$test.Directory/$language`.
type CodegenCheck func(t *testing.T, codedir string)
type sdkTest struct {
Directory string
Description string
Directory string
Description string
// Extra checks for this test. They keys of this map
// are of the form "$language/$check" such as "go/compile".
Checks map[string]CodegenCheck
// Skip checks, identified by "$language/$check".
Skip codegen.StringSet
// Do not compile the generated code for the languages in this set.
// This is a helper form of `Skip`.
SkipCompileCheck codegen.StringSet
Then map[string]ThenFunc
}
const (
python = "python"
nodejs = "nodejs"
dotnet = "dotnet"
golang = "go"
@ -32,10 +44,12 @@ var sdkTests = []sdkTest{
{
Directory: "input-collision",
Description: "Schema with types that could potentially produce collisions (go).",
Skip: codegen.NewStringSet("nodejs/test"),
},
{
Directory: "dash-named-schema",
Description: "Simple schema with a two part name (foo-bar)",
Skip: codegen.NewStringSet("nodejs/test"),
},
{
Directory: "external-resource-schema",
@ -55,41 +69,37 @@ var sdkTests = []sdkTest{
{
Directory: "plain-schema-gh6957",
Description: "Repro for #6957",
Skip: codegen.NewStringSet("nodejs/test"),
},
{
Directory: "resource-args-python",
Description: "Resource args with same named resource and type",
Then: map[string]ThenFunc{
"go": func(t *testing.T, testDir string) {
cmd := exec.Command("go", "test", "./...")
cmd.Dir = filepath.Join(testDir, "go-program")
out, err := cmd.CombinedOutput()
if !assert.NoError(t, err) {
t.Logf("output: %v", string(out))
}
},
},
Skip: codegen.NewStringSet("nodejs/test"),
},
{
Directory: "simple-enum-schema",
Description: "Simple schema with enum types",
Skip: codegen.NewStringSet("nodejs/test"),
},
{
Directory: "simple-plain-schema",
Description: "Simple schema with plain properties",
Skip: codegen.NewStringSet("nodejs/test"),
},
{
Directory: "simple-plain-schema-with-root-package",
Description: "Simple schema with root package set",
Skip: codegen.NewStringSet("nodejs/test"),
},
{
Directory: "simple-resource-schema",
Description: "Simple schema with local resource properties",
Skip: codegen.NewStringSet("nodejs/test"),
},
{
Directory: "simple-resource-schema-custom-pypackage-name",
Description: "Simple schema with local resource properties and custom Python package name",
Skip: codegen.NewStringSet("nodejs/test"),
},
{
Directory: "simple-methods-schema",
@ -99,25 +109,44 @@ var sdkTests = []sdkTest{
{
Directory: "simple-yaml-schema",
Description: "Simple schema encoded using YAML",
Skip: codegen.NewStringSet("nodejs/test"),
},
{
Directory: "provider-config-schema",
Description: "Simple provider config schema",
SkipCompileCheck: codegen.NewStringSet(dotnet),
Skip: codegen.NewStringSet("nodejs/test"),
},
{
Directory: "replace-on-change",
Description: "Simple use of replaceOnChange in schema",
SkipCompileCheck: codegen.NewStringSet(golang),
Skip: codegen.NewStringSet("nodejs/test"),
},
{
Directory: "resource-property-overlap",
Description: "A resource with the same name as it's property",
SkipCompileCheck: codegen.NewStringSet(dotnet, nodejs),
},
{
Directory: "output-funcs",
Description: "Test generation of $fn_Output helper forms",
SkipCompileCheck: codegen.NewStringSet(dotnet, python, golang),
},
}
type checkPackageSignature = func(t *testing.T, pwd string)
type SDKCodegenOptions struct {
// Name of the programming language.
Language string
// Language-aware code generator; such as `GeneratePackage`.
// from `codgen/dotnet`.
GenPackage GenPkgSignature
// Extra checks for all the tests. They keys of this map are
// of the form "$language/$check" such as "go/compile".
Checks map[string]CodegenCheck
}
// TestSDKCodegen runs the complete set of SDK code generation tests against a particular language's code
// generator. It also verifies that the generated code is structurally sound.
@ -133,11 +162,13 @@ type checkPackageSignature = func(t *testing.T, pwd string)
//
// The schema is the only piece that must be manually authored. Once the schema has been written, the expected outputs
// can be generated by running `PULUMI_ACCEPT=true go test ./..." from the `pkg/codegen` directory.
func TestSDKCodegen(t *testing.T, language string, genPackage GenPkgSignature, checkPackage checkPackageSignature) {
func TestSDKCodegen(t *testing.T, opts *SDKCodegenOptions) {
testDir := filepath.Join("..", "internal", "test", "testdata")
for _, tt := range sdkTests {
t.Run(tt.Description, func(t *testing.T) {
t.Run(tt.Directory, func(t *testing.T) {
t.Log(tt.Description)
dirPath := filepath.Join(testDir, filepath.FromSlash(tt.Directory))
schemaPath := filepath.Join(dirPath, "schema.json")
@ -145,31 +176,74 @@ func TestSDKCodegen(t *testing.T, language string, genPackage GenPkgSignature, c
schemaPath = filepath.Join(dirPath, "schema.yaml")
}
files, err := GeneratePackageFilesFromSchema(schemaPath, genPackage)
files, err := GeneratePackageFilesFromSchema(schemaPath, opts.GenPackage)
require.NoError(t, err)
// Check output is valid code (will type-check). If code will not
// type-check, we don't allow the user to run PULUMI_ACCEPT=true and
// replace the test files.
if !tt.SkipCompileCheck.Has(language) {
typeCheckPath := filepath.Join(dirPath, "typecheck")
langTypeCheckPath := filepath.Join(typeCheckPath, language)
contract.IgnoreError(os.RemoveAll(langTypeCheckPath))
WriteTestFiles(t, typeCheckPath, language, files)
checkPackage(t, langTypeCheckPath)
}
if !RewriteFilesWhenPulumiAccept(t, dirPath, language, files) {
expectedFiles, err := LoadBaseline(dirPath, language)
if !RewriteFilesWhenPulumiAccept(t, dirPath, opts.Language, files) {
expectedFiles, err := LoadBaseline(dirPath, opts.Language)
require.NoError(t, err)
if !ValidateFileEquality(t, files, expectedFiles) {
return
t.Fail()
}
}
if then, ok := tt.Then[language]; ok {
then(t, dirPath)
CopyExtraFiles(t, dirPath, opts.Language)
// Merge language-specific global and
// test-specific checks, with test-specific
// having precedence.
allChecks := make(map[string]CodegenCheck)
for k, v := range opts.Checks {
allChecks[k] = v
}
for k, v := range tt.Checks {
allChecks[k] = v
}
// Define check filter.
shouldSkipCheck := func(check string) bool {
// Only language-specific checks.
if !strings.HasPrefix(check, opts.Language+"/") {
return true
}
// Obey SkipCompileCheck to skip compile and test targets.
if tt.SkipCompileCheck != nil &&
tt.SkipCompileCheck.Has(opts.Language) &&
(check == fmt.Sprintf("%s/compile", opts.Language) ||
check == fmt.Sprintf("%s/test", opts.Language)) {
return true
}
// Obey Skip.
if tt.Skip != nil && tt.Skip.Has(check) {
return true
}
return false
}
// Sort the checks in alphabetical order.
var checkOrder []string
for check := range allChecks {
checkOrder = append(checkOrder, check)
}
sort.Strings(checkOrder)
codeDir := filepath.Join(dirPath, opts.Language)
// Perform the checks.
for _, checkVar := range checkOrder {
check := checkVar
t.Run(check, func(t *testing.T) {
if shouldSkipCheck(check) {
t.Skip()
}
checkFun := allChecks[check]
checkFun(t, codeDir)
})
}
})
}

View file

@ -0,0 +1,4 @@
package-lock.json
go.sum
go.mod
tests

View file

@ -0,0 +1,10 @@
__pycache__
bin
node_modules
tests
yarn.lock
package-lock.json
obj
command-output
go.sum
go.mod

View file

@ -6,8 +6,8 @@ package submodule1
import (
"fmt"
"dash-named-schema/foo"
"github.com/blang/semver"
"github.com/pulumi/pulumi/pkg/v3/codegen/internal/test/testdata/dash-named-schema/go/foo"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

View file

@ -7,7 +7,7 @@ import (
"context"
"reflect"
"github.com/pulumi/pulumi/pkg/v3/codegen/internal/test/testdata/dash-named-schema/go/foo"
"dash-named-schema/foo"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

View file

@ -44,7 +44,7 @@
}
},
"go": {
"importBasePath": "github.com/pulumi/pulumi/pkg/v3/codegen/internal/test/testdata/dash-named-schema/go/foo"
"importBasePath": "dash-named-schema/foo"
},
"nodejs": {
"dependencies": {

View file

@ -28,3 +28,11 @@ export interface ArgFunctionArgs {
export interface ArgFunctionResult {
readonly age?: number;
}
export function argFunctionOutput(args?: ArgFunctionOutputArgs, opts?: pulumi.InvokeOptions): pulumi.Output<ArgFunctionResult> {
return pulumi.output(args).apply(a => argFunction(a, opts))
}
export interface ArgFunctionOutputArgs {
name?: pulumi.Input<pulumiRandom.RandomPet>;
}

View file

@ -1 +1 @@
0.0.1
0.0.0

View file

@ -7,8 +7,8 @@ import (
"fmt"
"github.com/blang/semver"
"github.com/pulumi/pulumi-foo-bar/sdk/v2/go/foo-bar"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"nested-module-thirdparty/foo"
)
type module struct {
@ -32,7 +32,7 @@ func (m *module) Construct(ctx *pulumi.Context, name, typ, urn string) (r pulumi
}
func init() {
version, err := foo - bar.PkgVersion()
version, err := foo.PkgVersion()
if err != nil {
fmt.Printf("failed to determine package version. defaulting to v1: %v\n", err)
}

View file

@ -29,6 +29,9 @@
"packageReferences": {
"Pulumi": "3.12"
}
},
"go": {
"importBasePath": "nested-module-thirdparty/foo"
}
}
}

View file

@ -1 +1 @@
0.0.1
0.0.0

View file

@ -7,8 +7,8 @@ import (
"fmt"
"github.com/blang/semver"
"github.com/pulumi/pulumi/pkg/v3/codegen/internal/test/testdata/nested-module/go/foo"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"nested-module/foo"
)
type module struct {

View file

@ -25,7 +25,7 @@
}
},
"go": {
"importBasePath": "github.com/pulumi/pulumi/pkg/v3/codegen/internal/test/testdata/nested-module/go/foo"
"importBasePath": "nested-module/foo"
},
"nodejs": {},
"python": {}

View file

@ -0,0 +1,182 @@
// Copyright 2016-2021, 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.
import 'mocha';
import * as assert from 'assert';
import * as pulumi from '@pulumi/pulumi';
import { listStorageAccountKeysOutput, ListStorageAccountKeysResult } from '../listStorageAccountKeys';
import { funcWithAllOptionalInputsOutput } from '../funcWithAllOptionalInputs';
import { funcWithDefaultValueOutput } from '../funcWithDefaultValue';
import { funcWithListParamOutput } from '../funcWithListParam';
import { funcWithDictParamOutput } from '../funcWithDictParam';
import * as giro from '../getIntegrationRuntimeObjectMetadatum';
pulumi.runtime.setMocks({
newResource: function(_: pulumi.runtime.MockResourceArgs): {id: string, state: any} {
throw new Error('newResource not implemented');
},
call: function(args: pulumi.runtime.MockCallArgs) {
if (args.token == 'mypkg::listStorageAccountKeys') {
return {
'keys': [{
'creationTime': 'my-creation-time',
'keyName': 'my-key-name',
'permissions': 'my-permissions',
'value': JSON.stringify(args.inputs),
}]
};
}
if (args.token == 'mypkg::getIntegrationRuntimeObjectMetadatum') {
return {nextLink: JSON.stringify(args.inputs)};
}
if (args.token == 'mypkg::funcWithAllOptionalInputs' ||
args.token == 'mypkg::funcWithDefaultValue' ||
args.token == 'mypkg::funcWithListParam' ||
args.token == 'mypkg::funcWithDictParam')
{
return {r: JSON.stringify(args.inputs)};
}
throw new Error('call not implemented for ' + args.token);
},
});
function checkTable(done: any, transform: (res: any) => any, table: {given: pulumi.Output<any>, expect: any}[]) {
checkOutput(done, pulumi.all(table.map(x => x.given)), res => {
res.map((actual, i) => {
assert.deepStrictEqual(transform(actual), table[i].expect);
});
});
}
describe('output-funcs', () => {
it('funcWithAllOptionalInputsOutput', (done) => {
checkTable(done, res => JSON.parse(res.r), [
{given: funcWithAllOptionalInputsOutput({}),
expect: {}},
{given: funcWithAllOptionalInputsOutput({a: pulumi.output('my-a')}),
expect: {'a': 'my-a'}},
{given: funcWithAllOptionalInputsOutput({a: pulumi.output('my-a'),
b: pulumi.output('my-b')}),
expect: {'a': 'my-a', 'b': 'my-b'}}
]);
});
// TODO[pulumi/pulumi#7973] Node codegen does not respect default
// values at the moment, otherwise 'b' parameter would receive the
// default value from the schema.
it('funcWithDefaultValueOutput', (done) => {
checkTable(done, res => JSON.parse(res.r), [
{given: funcWithDefaultValueOutput({'a': pulumi.output('my-a')}),
expect: {'a': 'my-a'}},
{given: funcWithDefaultValueOutput({'a': pulumi.output('my-a'),
'b': pulumi.output('my-b')}),
expect: {'a': 'my-a', 'b': 'my-b'}}
]);
});
it('funcWithListParamOutput', (done) => {
const l = ['a', 'b', 'c'];
checkTable(done, res => JSON.parse(res.r), [
{given: funcWithListParamOutput({}),
expect: {}},
{given: funcWithListParamOutput({'a': pulumi.output(l)}),
expect: {'a': l}},
{given: funcWithListParamOutput({'a': pulumi.output(l),
'b': pulumi.output('my-b')}),
expect: {'a': l, 'b': 'my-b'}},
]);
});
it('funcWithDictParamOutput', (done) => {
const d = {'key-a': 'value-a', 'key-b': 'value-b'};
checkTable(done, res => JSON.parse(res.r), [
{given: funcWithDictParamOutput({}),
expect: {}},
{given: funcWithDictParamOutput({'a': pulumi.output(d)}),
expect: {'a': d}},
{given: funcWithDictParamOutput({'a': pulumi.output(d),
'b': pulumi.output('my-b')}),
expect: {'a': d, 'b': 'my-b'}},
]);
});
it('listStorageAccountKeysOutput', (done) => {
const output = listStorageAccountKeysOutput({
accountName: pulumi.output('my-account-name'),
resourceGroupName: pulumi.output('my-resource-group-name'),
});
checkOutput(done, output, (res: ListStorageAccountKeysResult) => {
assert.equal(res.keys.length, 1);
const k = res.keys[0];
assert.equal(k.creationTime, 'my-creation-time');
assert.equal(k.keyName, 'my-key-name');
assert.equal(k.permissions, 'my-permissions');
assert.deepStrictEqual(JSON.parse(k.value), {
'accountName': 'my-account-name',
'resourceGroupName': 'my-resource-group-name'
});
});
});
it('listStorageAccountKeysOutput with optional arg set', (done) => {
const output = listStorageAccountKeysOutput({
accountName: pulumi.output('my-account-name'),
resourceGroupName: pulumi.output('my-resource-group-name'),
expand: pulumi.output('my-expand'),
});
checkOutput(done, output, (res: ListStorageAccountKeysResult) => {
assert.equal(res.keys.length, 1);
const k = res.keys[0];
assert.equal(k.creationTime, 'my-creation-time');
assert.equal(k.keyName, 'my-key-name');
assert.equal(k.permissions, 'my-permissions');
assert.deepStrictEqual(JSON.parse(k.value), {
'accountName': 'my-account-name',
'resourceGroupName': 'my-resource-group-name',
'expand': 'my-expand'
});
});
});
it('getIntegrationRuntimeObjectMetadatumOutput', (done) => {
checkTable(
done,
(res: giro.GetIntegrationRuntimeObjectMetadatumResult) =>
JSON.parse(res.nextLink || "{}"),
[{given: giro.getIntegrationRuntimeObjectMetadatumOutput({
factoryName: pulumi.output('my-factory-name'),
integrationRuntimeName: pulumi.output('my-integration-runtime-name'),
metadataPath: pulumi.output('my-metadata-path'),
resourceGroupName: pulumi.output('my-resource-group-name')}),
expect: {'factoryName': 'my-factory-name',
'integrationRuntimeName': 'my-integration-runtime-name',
'metadataPath': 'my-metadata-path',
'resourceGroupName': 'my-resource-group-name'}}]
);
});
});
function checkOutput<T>(done: any, output: pulumi.Output<T>, check: (value: T) => void) {
output.apply(value => {
try {
check(value);
done();
} catch (error) {
done(error);
}
});
}

View file

@ -0,0 +1,54 @@
// *** WARNING: this file was generated by test. ***
// *** Do not edit by hand unless you're certain you know what you are doing! ***
import * as pulumi from "@pulumi/pulumi";
import { input as inputs, output as outputs } from "./types";
import * as utilities from "./utilities";
/**
* Check codegen of functions with all optional inputs.
*/
export function funcWithAllOptionalInputs(args?: FuncWithAllOptionalInputsArgs, opts?: pulumi.InvokeOptions): Promise<FuncWithAllOptionalInputsResult> {
args = args || {};
if (!opts) {
opts = {}
}
if (!opts.version) {
opts.version = utilities.getVersion();
}
return pulumi.runtime.invoke("mypkg::funcWithAllOptionalInputs", {
"a": args.a,
"b": args.b,
}, opts);
}
export interface FuncWithAllOptionalInputsArgs {
/**
* Property A
*/
a?: string;
/**
* Property B
*/
b?: string;
}
export interface FuncWithAllOptionalInputsResult {
readonly r: string;
}
export function funcWithAllOptionalInputsOutput(args?: FuncWithAllOptionalInputsOutputArgs, opts?: pulumi.InvokeOptions): pulumi.Output<FuncWithAllOptionalInputsResult> {
return pulumi.output(args).apply(a => funcWithAllOptionalInputs(a, opts))
}
export interface FuncWithAllOptionalInputsOutputArgs {
/**
* Property A
*/
a?: pulumi.Input<string>;
/**
* Property B
*/
b?: pulumi.Input<string>;
}

View file

@ -0,0 +1,27 @@
// *** WARNING: this file was generated by test. ***
// *** Do not edit by hand unless you're certain you know what you are doing! ***
import * as pulumi from "@pulumi/pulumi";
import { input as inputs, output as outputs } from "./types";
import * as utilities from "./utilities";
/**
* Codegen demo with const inputs
*/
export function funcWithConstInput(args?: FuncWithConstInputArgs, opts?: pulumi.InvokeOptions): Promise<void> {
args = args || {};
if (!opts) {
opts = {}
}
if (!opts.version) {
opts.version = utilities.getVersion();
}
return pulumi.runtime.invoke("mypkg::funcWithConstInput", {
"plainInput": args.plainInput,
}, opts);
}
export interface FuncWithConstInputArgs {
plainInput?: "fixed";
}

View file

@ -0,0 +1,41 @@
// *** WARNING: this file was generated by test. ***
// *** Do not edit by hand unless you're certain you know what you are doing! ***
import * as pulumi from "@pulumi/pulumi";
import { input as inputs, output as outputs } from "./types";
import * as utilities from "./utilities";
/**
* Check codegen of functions with default values.
*/
export function funcWithDefaultValue(args: FuncWithDefaultValueArgs, opts?: pulumi.InvokeOptions): Promise<FuncWithDefaultValueResult> {
if (!opts) {
opts = {}
}
if (!opts.version) {
opts.version = utilities.getVersion();
}
return pulumi.runtime.invoke("mypkg::funcWithDefaultValue", {
"a": args.a,
"b": args.b,
}, opts);
}
export interface FuncWithDefaultValueArgs {
a: string;
b?: string;
}
export interface FuncWithDefaultValueResult {
readonly r: string;
}
export function funcWithDefaultValueOutput(args: FuncWithDefaultValueOutputArgs, opts?: pulumi.InvokeOptions): pulumi.Output<FuncWithDefaultValueResult> {
return pulumi.output(args).apply(a => funcWithDefaultValue(a, opts))
}
export interface FuncWithDefaultValueOutputArgs {
a: pulumi.Input<string>;
b?: pulumi.Input<string>;
}

View file

@ -0,0 +1,42 @@
// *** WARNING: this file was generated by test. ***
// *** Do not edit by hand unless you're certain you know what you are doing! ***
import * as pulumi from "@pulumi/pulumi";
import { input as inputs, output as outputs } from "./types";
import * as utilities from "./utilities";
/**
* Check codegen of functions with a Dict<str,str> parameter.
*/
export function funcWithDictParam(args?: FuncWithDictParamArgs, opts?: pulumi.InvokeOptions): Promise<FuncWithDictParamResult> {
args = args || {};
if (!opts) {
opts = {}
}
if (!opts.version) {
opts.version = utilities.getVersion();
}
return pulumi.runtime.invoke("mypkg::funcWithDictParam", {
"a": args.a,
"b": args.b,
}, opts);
}
export interface FuncWithDictParamArgs {
a?: {[key: string]: string};
b?: string;
}
export interface FuncWithDictParamResult {
readonly r: string;
}
export function funcWithDictParamOutput(args?: FuncWithDictParamOutputArgs, opts?: pulumi.InvokeOptions): pulumi.Output<FuncWithDictParamResult> {
return pulumi.output(args).apply(a => funcWithDictParam(a, opts))
}
export interface FuncWithDictParamOutputArgs {
a?: pulumi.Input<{[key: string]: pulumi.Input<string>}>;
b?: pulumi.Input<string>;
}

View file

@ -0,0 +1,42 @@
// *** WARNING: this file was generated by test. ***
// *** Do not edit by hand unless you're certain you know what you are doing! ***
import * as pulumi from "@pulumi/pulumi";
import { input as inputs, output as outputs } from "./types";
import * as utilities from "./utilities";
/**
* Check codegen of functions with a List parameter.
*/
export function funcWithListParam(args?: FuncWithListParamArgs, opts?: pulumi.InvokeOptions): Promise<FuncWithListParamResult> {
args = args || {};
if (!opts) {
opts = {}
}
if (!opts.version) {
opts.version = utilities.getVersion();
}
return pulumi.runtime.invoke("mypkg::funcWithListParam", {
"a": args.a,
"b": args.b,
}, opts);
}
export interface FuncWithListParamArgs {
a?: string[];
b?: string;
}
export interface FuncWithListParamResult {
readonly r: string;
}
export function funcWithListParamOutput(args?: FuncWithListParamOutputArgs, opts?: pulumi.InvokeOptions): pulumi.Output<FuncWithListParamResult> {
return pulumi.output(args).apply(a => funcWithListParam(a, opts))
}
export interface FuncWithListParamOutputArgs {
a?: pulumi.Input<pulumi.Input<string>[]>;
b?: pulumi.Input<string>;
}

View file

@ -0,0 +1,43 @@
// *** WARNING: this file was generated by test. ***
// *** Do not edit by hand unless you're certain you know what you are doing! ***
import * as pulumi from "@pulumi/pulumi";
import { input as inputs, output as outputs } from "./types";
import * as utilities from "./utilities";
/**
* Failing example taken from azure-native. Original doc: Use this function to access the current configuration of the native Azure provider.
*/
export function getClientConfig(opts?: pulumi.InvokeOptions): Promise<GetClientConfigResult> {
if (!opts) {
opts = {}
}
if (!opts.version) {
opts.version = utilities.getVersion();
}
return pulumi.runtime.invoke("mypkg::getClientConfig", {
}, opts);
}
/**
* Configuration values returned by getClientConfig.
*/
export interface GetClientConfigResult {
/**
* Azure Client ID (Application Object ID).
*/
readonly clientId: string;
/**
* Azure Object ID of the current user or service principal.
*/
readonly objectId: string;
/**
* Azure Subscription ID
*/
readonly subscriptionId: string;
/**
* Azure Tenant ID
*/
readonly tenantId: string;
}

View file

@ -0,0 +1,82 @@
// *** WARNING: this file was generated by test. ***
// *** Do not edit by hand unless you're certain you know what you are doing! ***
import * as pulumi from "@pulumi/pulumi";
import { input as inputs, output as outputs } from "./types";
import * as utilities from "./utilities";
/**
* Another failing example. A list of SSIS object metadata.
* API Version: 2018-06-01.
*/
export function getIntegrationRuntimeObjectMetadatum(args: GetIntegrationRuntimeObjectMetadatumArgs, opts?: pulumi.InvokeOptions): Promise<GetIntegrationRuntimeObjectMetadatumResult> {
if (!opts) {
opts = {}
}
if (!opts.version) {
opts.version = utilities.getVersion();
}
return pulumi.runtime.invoke("mypkg::getIntegrationRuntimeObjectMetadatum", {
"factoryName": args.factoryName,
"integrationRuntimeName": args.integrationRuntimeName,
"metadataPath": args.metadataPath,
"resourceGroupName": args.resourceGroupName,
}, opts);
}
export interface GetIntegrationRuntimeObjectMetadatumArgs {
/**
* The factory name.
*/
factoryName: string;
/**
* The integration runtime name.
*/
integrationRuntimeName: string;
/**
* Metadata path.
*/
metadataPath?: string;
/**
* The resource group name.
*/
resourceGroupName: string;
}
/**
* A list of SSIS object metadata.
*/
export interface GetIntegrationRuntimeObjectMetadatumResult {
/**
* The link to the next page of results, if any remaining results exist.
*/
readonly nextLink?: string;
/**
* List of SSIS object metadata.
*/
readonly value?: outputs.SsisEnvironmentResponse | outputs.SsisFolderResponse | outputs.SsisPackageResponse | outputs.SsisProjectResponse[];
}
export function getIntegrationRuntimeObjectMetadatumOutput(args: GetIntegrationRuntimeObjectMetadatumOutputArgs, opts?: pulumi.InvokeOptions): pulumi.Output<GetIntegrationRuntimeObjectMetadatumResult> {
return pulumi.output(args).apply(a => getIntegrationRuntimeObjectMetadatum(a, opts))
}
export interface GetIntegrationRuntimeObjectMetadatumOutputArgs {
/**
* The factory name.
*/
factoryName: pulumi.Input<string>;
/**
* The integration runtime name.
*/
integrationRuntimeName: pulumi.Input<string>;
/**
* Metadata path.
*/
metadataPath?: pulumi.Input<string>;
/**
* The resource group name.
*/
resourceGroupName: pulumi.Input<string>;
}

View file

@ -0,0 +1,35 @@
// *** WARNING: this file was generated by test. ***
// *** Do not edit by hand unless you're certain you know what you are doing! ***
import * as pulumi from "@pulumi/pulumi";
import * as utilities from "./utilities";
// Export members:
export * from "./funcWithAllOptionalInputs";
export * from "./funcWithConstInput";
export * from "./funcWithDefaultValue";
export * from "./funcWithDictParam";
export * from "./funcWithListParam";
export * from "./getClientConfig";
export * from "./getIntegrationRuntimeObjectMetadatum";
export * from "./listStorageAccountKeys";
export * from "./provider";
// Export sub-modules:
import * as types from "./types";
export {
types,
};
import { Provider } from "./provider";
pulumi.runtime.registerResourcePackage("mypkg", {
version: utilities.getVersion(),
constructProvider: (name: string, type: string, urn: string): pulumi.ProviderResource => {
if (type !== "pulumi:providers:mypkg") {
throw new Error(`unknown provider type ${type}`);
}
return new Provider(name, <any>undefined, { urn });
},
});

View file

@ -0,0 +1,69 @@
// *** WARNING: this file was generated by test. ***
// *** Do not edit by hand unless you're certain you know what you are doing! ***
import * as pulumi from "@pulumi/pulumi";
import { input as inputs, output as outputs } from "./types";
import * as utilities from "./utilities";
/**
* The response from the ListKeys operation.
* API Version: 2021-02-01.
*/
export function listStorageAccountKeys(args: ListStorageAccountKeysArgs, opts?: pulumi.InvokeOptions): Promise<ListStorageAccountKeysResult> {
if (!opts) {
opts = {}
}
if (!opts.version) {
opts.version = utilities.getVersion();
}
return pulumi.runtime.invoke("mypkg::listStorageAccountKeys", {
"accountName": args.accountName,
"expand": args.expand,
"resourceGroupName": args.resourceGroupName,
}, opts);
}
export interface ListStorageAccountKeysArgs {
/**
* The name of the storage account within the specified resource group. Storage account names must be between 3 and 24 characters in length and use numbers and lower-case letters only.
*/
accountName: string;
/**
* Specifies type of the key to be listed. Possible value is kerb.
*/
expand?: string;
/**
* The name of the resource group within the user's subscription. The name is case insensitive.
*/
resourceGroupName: string;
}
/**
* The response from the ListKeys operation.
*/
export interface ListStorageAccountKeysResult {
/**
* Gets the list of storage account keys and their properties for the specified storage account.
*/
readonly keys: outputs.StorageAccountKeyResponse[];
}
export function listStorageAccountKeysOutput(args: ListStorageAccountKeysOutputArgs, opts?: pulumi.InvokeOptions): pulumi.Output<ListStorageAccountKeysResult> {
return pulumi.output(args).apply(a => listStorageAccountKeys(a, opts))
}
export interface ListStorageAccountKeysOutputArgs {
/**
* The name of the storage account within the specified resource group. Storage account names must be between 3 and 24 characters in length and use numbers and lower-case letters only.
*/
accountName: pulumi.Input<string>;
/**
* Specifies type of the key to be listed. Possible value is kerb.
*/
expand?: pulumi.Input<string>;
/**
* The name of the resource group within the user's subscription. The name is case insensitive.
*/
resourceGroupName: pulumi.Input<string>;
}

View file

@ -0,0 +1,19 @@
{
"name": "@pulumi/mypkg",
"version": "0.0.1",
"scripts": {
"build": "tsc",
"test": "mocha -r ts-node/register tests/**/*.spec.ts"
},
"devDependencies": {
"@pulumi/pulumi": "file:../../../../../../../sdk/nodejs/bin",
"@types/mocha": "latest",
"@types/node": "latest",
"mocha": "latest",
"ts-node": "latest",
"typescript": "^4.3.5"
},
"pulumi": {
"resource": true
}
}

View file

@ -0,0 +1,46 @@
// *** WARNING: this file was generated by test. ***
// *** Do not edit by hand unless you're certain you know what you are doing! ***
import * as pulumi from "@pulumi/pulumi";
import * as utilities from "./utilities";
export class Provider extends pulumi.ProviderResource {
/** @internal */
public static readonly __pulumiType = 'mypkg';
/**
* Returns true if the given object is an instance of Provider. This is designed to work even
* when multiple copies of the Pulumi SDK have been loaded into the same process.
*/
public static isInstance(obj: any): obj is Provider {
if (obj === undefined || obj === null) {
return false;
}
return obj['__pulumiType'] === Provider.__pulumiType;
}
/**
* Create a Provider resource with the given unique name, arguments, and options.
*
* @param name The _unique_ name of the resource.
* @param args The arguments to use to populate this resource's properties.
* @param opts A bag of options that control this resource's behavior.
*/
constructor(name: string, args?: ProviderArgs, opts?: pulumi.ResourceOptions) {
let inputs: pulumi.Inputs = {};
opts = opts || {};
{
}
if (!opts.version) {
opts = pulumi.mergeOptions(opts, { version: utilities.getVersion()});
}
super(Provider.__pulumiType, name, inputs, opts);
}
}
/**
* The set of arguments for constructing a Provider resource.
*/
export interface ProviderArgs {
}

View file

@ -0,0 +1,32 @@
{
"compilerOptions": {
"outDir": "bin",
"target": "es2016",
"module": "commonjs",
"moduleResolution": "node",
"declaration": true,
"sourceMap": true,
"stripInternal": true,
"experimentalDecorators": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true,
"strict": true
},
"files": [
"funcWithAllOptionalInputs.ts",
"funcWithConstInput.ts",
"funcWithDefaultValue.ts",
"funcWithDictParam.ts",
"funcWithListParam.ts",
"getClientConfig.ts",
"getIntegrationRuntimeObjectMetadatum.ts",
"index.ts",
"listStorageAccountKeys.ts",
"provider.ts",
"tests/codegen.spec.ts",
"types/index.ts",
"types/input.ts",
"types/output.ts",
"utilities.ts"
]
}

View file

@ -0,0 +1,11 @@
// *** WARNING: this file was generated by test. ***
// *** Do not edit by hand unless you're certain you know what you are doing! ***
// Export sub-modules:
import * as input from "./input";
import * as output from "./output";
export {
input,
output,
};

View file

@ -0,0 +1,6 @@
// *** WARNING: this file was generated by test. ***
// *** Do not edit by hand unless you're certain you know what you are doing! ***
import * as pulumi from "@pulumi/pulumi";
import { input as inputs, output as outputs } from "../types";

View file

@ -0,0 +1,270 @@
// *** WARNING: this file was generated by test. ***
// *** Do not edit by hand unless you're certain you know what you are doing! ***
import * as pulumi from "@pulumi/pulumi";
import { input as inputs, output as outputs } from "../types";
/**
* Ssis environment reference.
*/
export interface SsisEnvironmentReferenceResponse {
/**
* Environment folder name.
*/
environmentFolderName?: string;
/**
* Environment name.
*/
environmentName?: string;
/**
* Environment reference id.
*/
id?: number;
/**
* Reference type
*/
referenceType?: string;
}
/**
* Ssis environment.
*/
export interface SsisEnvironmentResponse {
/**
* Metadata description.
*/
description?: string;
/**
* Folder id which contains environment.
*/
folderId?: number;
/**
* Metadata id.
*/
id?: number;
/**
* Metadata name.
*/
name?: string;
/**
* The type of SSIS object metadata.
* Expected value is 'Environment'.
*/
type: "Environment";
/**
* Variable in environment
*/
variables?: outputs.SsisVariableResponse[];
}
/**
* Ssis folder.
*/
export interface SsisFolderResponse {
/**
* Metadata description.
*/
description?: string;
/**
* Metadata id.
*/
id?: number;
/**
* Metadata name.
*/
name?: string;
/**
* The type of SSIS object metadata.
* Expected value is 'Folder'.
*/
type: "Folder";
}
/**
* Ssis Package.
*/
export interface SsisPackageResponse {
/**
* Metadata description.
*/
description?: string;
/**
* Folder id which contains package.
*/
folderId?: number;
/**
* Metadata id.
*/
id?: number;
/**
* Metadata name.
*/
name?: string;
/**
* Parameters in package
*/
parameters?: outputs.SsisParameterResponse[];
/**
* Project id which contains package.
*/
projectId?: number;
/**
* Project version which contains package.
*/
projectVersion?: number;
/**
* The type of SSIS object metadata.
* Expected value is 'Package'.
*/
type: "Package";
}
/**
* Ssis parameter.
*/
export interface SsisParameterResponse {
/**
* Parameter type.
*/
dataType?: string;
/**
* Default value of parameter.
*/
defaultValue?: string;
/**
* Parameter description.
*/
description?: string;
/**
* Design default value of parameter.
*/
designDefaultValue?: string;
/**
* Parameter id.
*/
id?: number;
/**
* Parameter name.
*/
name?: string;
/**
* Whether parameter is required.
*/
required?: boolean;
/**
* Whether parameter is sensitive.
*/
sensitive?: boolean;
/**
* Default sensitive value of parameter.
*/
sensitiveDefaultValue?: string;
/**
* Parameter value set.
*/
valueSet?: boolean;
/**
* Parameter value type.
*/
valueType?: string;
/**
* Parameter reference variable.
*/
variable?: string;
}
/**
* Ssis project.
*/
export interface SsisProjectResponse {
/**
* Metadata description.
*/
description?: string;
/**
* Environment reference in project
*/
environmentRefs?: outputs.SsisEnvironmentReferenceResponse[];
/**
* Folder id which contains project.
*/
folderId?: number;
/**
* Metadata id.
*/
id?: number;
/**
* Metadata name.
*/
name?: string;
/**
* Parameters in project
*/
parameters?: outputs.SsisParameterResponse[];
/**
* The type of SSIS object metadata.
* Expected value is 'Project'.
*/
type: "Project";
/**
* Project version.
*/
version?: number;
}
/**
* Ssis variable.
*/
export interface SsisVariableResponse {
/**
* Variable type.
*/
dataType?: string;
/**
* Variable description.
*/
description?: string;
/**
* Variable id.
*/
id?: number;
/**
* Variable name.
*/
name?: string;
/**
* Whether variable is sensitive.
*/
sensitive?: boolean;
/**
* Variable sensitive value.
*/
sensitiveValue?: string;
/**
* Variable value.
*/
value?: string;
}
/**
* An access key for the storage account.
*/
export interface StorageAccountKeyResponse {
/**
* Creation time of the key, in round trip date format.
*/
creationTime: string;
/**
* Name of the key.
*/
keyName: string;
/**
* Permissions for the key -- read-only or full permissions.
*/
permissions: string;
/**
* Base 64-encoded value of the key.
*/
value: string;
}

View file

@ -0,0 +1,49 @@
// *** WARNING: this file was generated by test. ***
// *** Do not edit by hand unless you're certain you know what you are doing! ***
export function getEnv(...vars: string[]): string | undefined {
for (const v of vars) {
const value = process.env[v];
if (value) {
return value;
}
}
return undefined;
}
export function getEnvBoolean(...vars: string[]): boolean | undefined {
const s = getEnv(...vars);
if (s !== undefined) {
// NOTE: these values are taken from https://golang.org/src/strconv/atob.go?s=351:391#L1, which is what
// Terraform uses internally when parsing boolean values.
if (["1", "t", "T", "true", "TRUE", "True"].find(v => v === s) !== undefined) {
return true;
}
if (["0", "f", "F", "false", "FALSE", "False"].find(v => v === s) !== undefined) {
return false;
}
}
return undefined;
}
export function getEnvNumber(...vars: string[]): number | undefined {
const s = getEnv(...vars);
if (s !== undefined) {
const f = parseFloat(s);
if (!isNaN(f)) {
return f;
}
}
return undefined;
}
export function getVersion(): string {
let version = require('./package.json').version;
// Node allows for the version to be prefixed by a "v", while semver doesn't.
// If there is a v, strip it off.
if (version.indexOf('v') === 0) {
version = version.slice(1);
}
return version;
}

View file

@ -0,0 +1,591 @@
{
"name": "mypkg",
"version": "0.0.1",
"functions": {
"mypkg::funcWithAllOptionalInputs": {
"description": "Check codegen of functions with all optional inputs.",
"inputs": {
"type": "object",
"properties": {
"a": {
"type": "string",
"description": "Property A"
},
"b": {
"type": "string",
"description": "Property B"
}
}
},
"outputs": {
"properties": {
"r": {
"type": "string"
}
},
"type": "object",
"required": [
"r"
]
}
},
"mypkg::funcWithConstInput": {
"description": "Codegen demo with const inputs",
"inputs": {
"type": "object",
"properties": {
"plainInput": {
"type": "string",
"const": "fixed"
}
}
}
},
"mypkg::funcWithDefaultValue": {
"description": "Check codegen of functions with default values.",
"inputs": {
"type": "object",
"required": [
"a"
],
"properties": {
"a": {
"type": "string"
},
"b": {
"type": "string",
"default": "b-default"
}
}
},
"outputs": {
"properties": {
"r": {
"type": "string"
}
},
"type": "object",
"required": [
"r"
]
}
},
"mypkg::funcWithDictParam": {
"description": "Check codegen of functions with a Dict<str,str> parameter.",
"inputs": {
"type": "object",
"properties": {
"a": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"b": {
"type": "string"
}
}
},
"outputs": {
"properties": {
"r": {
"type": "string"
}
},
"type": "object",
"required": [
"r"
]
}
},
"mypkg::funcWithListParam": {
"description": "Check codegen of functions with a List parameter.",
"inputs": {
"type": "object",
"properties": {
"a": {
"type": "array",
"items": {
"type": "string"
}
},
"b": {
"type": "string"
}
}
},
"outputs": {
"properties": {
"r": {
"type": "string"
}
},
"type": "object",
"required": [
"r"
]
}
},
"mypkg::getClientConfig": {
"description": "Failing example taken from azure-native. Original doc: Use this function to access the current configuration of the native Azure provider.",
"outputs": {
"description": "Configuration values returned by getClientConfig.",
"properties": {
"clientId": {
"type": "string",
"description": "Azure Client ID (Application Object ID)."
},
"objectId": {
"type": "string",
"description": "Azure Object ID of the current user or service principal."
},
"subscriptionId": {
"type": "string",
"description": "Azure Subscription ID"
},
"tenantId": {
"type": "string",
"description": "Azure Tenant ID"
}
},
"type": "object",
"required": [
"clientId",
"objectId",
"subscriptionId",
"tenantId"
]
}
},
"mypkg::getIntegrationRuntimeObjectMetadatum": {
"description": "Another failing example. A list of SSIS object metadata.\nAPI Version: 2018-06-01.",
"inputs": {
"properties": {
"factoryName": {
"type": "string",
"description": "The factory name."
},
"integrationRuntimeName": {
"type": "string",
"description": "The integration runtime name."
},
"metadataPath": {
"type": "string",
"description": "Metadata path."
},
"resourceGroupName": {
"type": "string",
"description": "The resource group name."
}
},
"type": "object",
"required": [
"factoryName",
"integrationRuntimeName",
"resourceGroupName"
]
},
"outputs": {
"description": "A list of SSIS object metadata.",
"properties": {
"nextLink": {
"type": "string",
"description": "The link to the next page of results, if any remaining results exist."
},
"value": {
"type": "array",
"items": {
"oneOf": [
{
"type": "object",
"$ref": "#/types/mypkg::SsisEnvironmentResponse"
},
{
"type": "object",
"$ref": "#/types/mypkg::SsisFolderResponse"
},
{
"type": "object",
"$ref": "#/types/mypkg::SsisPackageResponse"
},
{
"type": "object",
"$ref": "#/types/mypkg::SsisProjectResponse"
}
],
"discriminator": {
"propertyName": "type",
"mapping": {
"Environment": "#/types/mypkg::SsisEnvironmentResponse",
"Folder": "#/types/mypkg::SsisFolderResponse",
"Package": "#/types/mypkg::SsisPackageResponse",
"Project": "#/types/mypkg::SsisProjectResponse"
}
}
},
"description": "List of SSIS object metadata."
}
},
"type": "object"
}
},
"mypkg::listStorageAccountKeys": {
"description": "The response from the ListKeys operation.\nAPI Version: 2021-02-01.",
"inputs": {
"properties": {
"accountName": {
"type": "string",
"description": "The name of the storage account within the specified resource group. Storage account names must be between 3 and 24 characters in length and use numbers and lower-case letters only."
},
"expand": {
"type": "string",
"description": "Specifies type of the key to be listed. Possible value is kerb."
},
"resourceGroupName": {
"type": "string",
"description": "The name of the resource group within the user's subscription. The name is case insensitive."
}
},
"type": "object",
"required": [
"accountName",
"resourceGroupName"
]
},
"outputs": {
"description": "The response from the ListKeys operation.",
"properties": {
"keys": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/types/mypkg::StorageAccountKeyResponse"
},
"description": "Gets the list of storage account keys and their properties for the specified storage account."
}
},
"type": "object",
"required": [
"keys"
]
}
}
},
"types": {
"mypkg::SsisEnvironmentResponse": {
"description": "Ssis environment.",
"properties": {
"description": {
"type": "string",
"description": "Metadata description."
},
"folderId": {
"type": "number",
"description": "Folder id which contains environment."
},
"id": {
"type": "number",
"description": "Metadata id."
},
"name": {
"type": "string",
"description": "Metadata name."
},
"type": {
"type": "string",
"description": "The type of SSIS object metadata.\nExpected value is 'Environment'.",
"const": "Environment"
},
"variables": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/types/mypkg::SsisVariableResponse"
},
"description": "Variable in environment"
}
},
"type": "object",
"required": [
"type"
]
},
"mypkg::SsisFolderResponse": {
"description": "Ssis folder.",
"properties": {
"description": {
"type": "string",
"description": "Metadata description."
},
"id": {
"type": "number",
"description": "Metadata id."
},
"name": {
"type": "string",
"description": "Metadata name."
},
"type": {
"type": "string",
"description": "The type of SSIS object metadata.\nExpected value is 'Folder'.",
"const": "Folder"
}
},
"type": "object",
"required": [
"type"
]
},
"mypkg::SsisPackageResponse": {
"description": "Ssis Package.",
"properties": {
"description": {
"type": "string",
"description": "Metadata description."
},
"folderId": {
"type": "number",
"description": "Folder id which contains package."
},
"id": {
"type": "number",
"description": "Metadata id."
},
"name": {
"type": "string",
"description": "Metadata name."
},
"parameters": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/types/mypkg::SsisParameterResponse"
},
"description": "Parameters in package"
},
"projectId": {
"type": "number",
"description": "Project id which contains package."
},
"projectVersion": {
"type": "number",
"description": "Project version which contains package."
},
"type": {
"type": "string",
"description": "The type of SSIS object metadata.\nExpected value is 'Package'.",
"const": "Package"
}
},
"type": "object",
"required": [
"type"
]
},
"mypkg::SsisProjectResponse": {
"description": "Ssis project.",
"properties": {
"description": {
"type": "string",
"description": "Metadata description."
},
"environmentRefs": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/types/mypkg::SsisEnvironmentReferenceResponse"
},
"description": "Environment reference in project"
},
"folderId": {
"type": "number",
"description": "Folder id which contains project."
},
"id": {
"type": "number",
"description": "Metadata id."
},
"name": {
"type": "string",
"description": "Metadata name."
},
"parameters": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/types/mypkg::SsisParameterResponse"
},
"description": "Parameters in project"
},
"type": {
"type": "string",
"description": "The type of SSIS object metadata.\nExpected value is 'Project'.",
"const": "Project"
},
"version": {
"type": "number",
"description": "Project version."
}
},
"type": "object",
"required": [
"type"
]
},
"mypkg::SsisEnvironmentReferenceResponse": {
"description": "Ssis environment reference.",
"properties": {
"environmentFolderName": {
"type": "string",
"description": "Environment folder name."
},
"environmentName": {
"type": "string",
"description": "Environment name."
},
"id": {
"type": "number",
"description": "Environment reference id."
},
"referenceType": {
"type": "string",
"description": "Reference type"
}
},
"type": "object"
},
"mypkg::SsisParameterResponse": {
"description": "Ssis parameter.",
"properties": {
"dataType": {
"type": "string",
"description": "Parameter type."
},
"defaultValue": {
"type": "string",
"description": "Default value of parameter."
},
"description": {
"type": "string",
"description": "Parameter description."
},
"designDefaultValue": {
"type": "string",
"description": "Design default value of parameter."
},
"id": {
"type": "number",
"description": "Parameter id."
},
"name": {
"type": "string",
"description": "Parameter name."
},
"required": {
"type": "boolean",
"description": "Whether parameter is required."
},
"sensitive": {
"type": "boolean",
"description": "Whether parameter is sensitive."
},
"sensitiveDefaultValue": {
"type": "string",
"description": "Default sensitive value of parameter."
},
"valueSet": {
"type": "boolean",
"description": "Parameter value set."
},
"valueType": {
"type": "string",
"description": "Parameter value type."
},
"variable": {
"type": "string",
"description": "Parameter reference variable."
}
},
"type": "object"
},
"mypkg::SsisVariableResponse": {
"description": "Ssis variable.",
"properties": {
"dataType": {
"type": "string",
"description": "Variable type."
},
"description": {
"type": "string",
"description": "Variable description."
},
"id": {
"type": "number",
"description": "Variable id."
},
"name": {
"type": "string",
"description": "Variable name."
},
"sensitive": {
"type": "boolean",
"description": "Whether variable is sensitive."
},
"sensitiveValue": {
"type": "string",
"description": "Variable sensitive value."
},
"value": {
"type": "string",
"description": "Variable value."
}
},
"type": "object"
},
"mypkg::StorageAccountKeyResponse": {
"description": "An access key for the storage account.",
"properties": {
"creationTime": {
"type": "string",
"description": "Creation time of the key, in round trip date format."
},
"keyName": {
"type": "string",
"description": "Name of the key."
},
"permissions": {
"type": "string",
"description": "Permissions for the key -- read-only or full permissions."
},
"value": {
"type": "string",
"description": "Base 64-encoded value of the key."
}
},
"type": "object",
"required": [
"creationTime",
"keyName",
"permissions",
"value"
]
}
},
"language": {
"nodejs": {
"devDependencies": {
"@types/mocha": "latest",
"@types/node": "latest",
"mocha": "latest",
"ts-node": "latest",
"@pulumi/pulumi": "file:../../../../../../../sdk/nodejs/bin"
},
"extraTypeScriptFiles": [
"tests/codegen.spec.ts"
],
"scripts": {
"test": "mocha -r ts-node/register tests/**/*.spec.ts"
}
}
}
}

View file

@ -1,10 +1,10 @@
package main
package tests
import (
"reflect"
"testing"
"github.com/pulumi/pulumi/pkg/v3/codegen/internal/test/testdata/resource-args-python/go/example"
"resource-args-python/example"
"github.com/stretchr/testify/assert"
)

View file

@ -0,0 +1,186 @@
package tests
import (
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"simple-enum-schema/plant"
tree "simple-enum-schema/plant/tree/v1"
)
func TestEnumUsage(t *testing.T) {
t.Run("Success", func(t *testing.T) {
require.NoError(t, pulumi.RunErr(func(ctx *pulumi.Context) error {
rubberTree, err := tree.NewRubberTree(ctx, "blah", &tree.RubberTreeArgs{
Container: &plant.ContainerArgs{
Color: plant.ContainerColorRed,
Material: pulumi.String("ceramic"),
Size: plant.ContainerSizeFourInch,
},
Farm: tree.Farm_Plants_R_Us,
Type: tree.RubberTreeVarietyRuby,
})
require.NoError(t, err)
require.NotNil(t, rubberTree)
var wg sync.WaitGroup
wg.Add(1)
pulumi.All(
rubberTree.URN(),
rubberTree.Container.Material(),
rubberTree.Container.Color(),
rubberTree.Container.Size(),
rubberTree.Container.Brightness(),
rubberTree.Type,
).ApplyT(func(all []interface{}) error {
urn := all[0].(pulumi.URN)
material := all[1].(*string)
color := all[2].(*string)
size := all[3].(*plant.ContainerSize)
brightness := all[4].(*plant.ContainerBrightness)
typ := all[5].(tree.RubberTreeVariety)
assert.Equal(t, *material, "ceramic", "unexpected material on resource: %v", urn)
assert.Equal(t, *color, "red", "unexpected color on resource: %v", urn)
assert.Equal(t, *size, plant.ContainerSizeFourInch, "unexpected size on resource: %v", urn)
assert.Nil(t, brightness)
assert.Equal(t, typ, tree.RubberTreeVarietyRuby, "unexpected type on resource: %v", urn)
wg.Done()
return nil
})
wg.Wait()
return nil
}, pulumi.WithMocks("project", "stack", mocks(0))))
})
t.Run("StringsForRelaxedEnum", func(t *testing.T) {
require.NoError(t, pulumi.RunErr(func(ctx *pulumi.Context) error {
rubberTree, err := tree.NewRubberTree(ctx, "blah", &tree.RubberTreeArgs{
Container: plant.ContainerArgs{
Color: pulumi.String("Magenta"),
Material: pulumi.String("ceramic"),
Size: plant.ContainerSize(22),
},
Farm: tree.Farm_Plants_R_Us,
Type: tree.RubberTreeVarietyRuby,
})
require.NoError(t, err)
require.NotNil(t, rubberTree)
var wg sync.WaitGroup
wg.Add(1)
pulumi.All(
rubberTree.URN(),
rubberTree.Container.Material(),
rubberTree.Container.Color(),
rubberTree.Container.Size(),
rubberTree.Type,
).ApplyT(func(all []interface{}) error {
urn := all[0].(pulumi.URN)
material := all[1].(*string)
color := all[2].(*string)
size := all[3].(*plant.ContainerSize)
typ := all[4].(tree.RubberTreeVariety)
assert.Equal(t, *material, "ceramic", "unexpected material on resource: %v", urn)
assert.Equal(t, *color, "Magenta", "unexpected color on resource: %v", urn)
assert.Equal(t, *size, plant.ContainerSize(22), "unexpected size on resource: %v", urn)
assert.Equal(t, typ, tree.RubberTreeVarietyRuby, "unexpected type on resource: %v", urn)
wg.Done()
return nil
})
wg.Wait()
return nil
}, pulumi.WithMocks("project", "stack", mocks(1))))
})
t.Run("StringsForStrictEnum", func(t *testing.T) {
require.NoError(t, pulumi.RunErr(func(ctx *pulumi.Context) error {
rubberTree, err := tree.NewRubberTree(ctx, "blah", &tree.RubberTreeArgs{
Container: plant.ContainerArgs{
Color: pulumi.String("Magenta"),
Material: pulumi.String("ceramic"),
Size: plant.ContainerSize(22),
},
Farm: tree.Farm_Plants_R_Us,
Type: tree.RubberTreeVarietyBurgundy,
})
require.NoError(t, err)
require.NotNil(t, rubberTree)
var wg sync.WaitGroup
wg.Add(1)
pulumi.All(
rubberTree.URN(),
rubberTree.Container.Material(),
rubberTree.Container.Color(),
rubberTree.Container.Size(),
rubberTree.Type,
).ApplyT(func(all []interface{}) error {
urn := all[0].(pulumi.URN)
material := all[1].(*string)
color := all[2].(*string)
size := all[3].(*plant.ContainerSize)
typ := all[4].(tree.RubberTreeVariety)
assert.Equal(t, *material, "ceramic", "unexpected material on resource: %v", urn)
assert.Equal(t, *color, "Magenta", "unexpected color on resource: %v", urn)
assert.Equal(t, *size, plant.ContainerSize(22), "unexpected size on resource: %v", urn)
assert.Equal(t, typ, tree.RubberTreeVarietyBurgundy, "unexpected type on resource: %v", urn)
wg.Done()
return nil
})
wg.Wait()
return nil
}, pulumi.WithMocks("project", "stack", mocks(1))))
})
t.Run("EnumOutputs", func(t *testing.T) {
require.NoError(t, pulumi.RunErr(func(ctx *pulumi.Context) error {
rubberTree, err := tree.NewRubberTree(ctx, "blah", &tree.RubberTreeArgs{
Container: plant.ContainerArgs{
Color: plant.ContainerColor("Magenta").ToContainerColorOutput().ToStringOutput(),
Material: pulumi.String("ceramic").ToStringOutput(),
Size: plant.ContainerSize(22).ToContainerSizeOutput(),
},
Farm: tree.Farm_Plants_R_Us.ToFarmPtrOutput().ToStringPtrOutput(),
Type: tree.RubberTreeVarietyBurgundy.ToRubberTreeVarietyOutput(),
})
require.NoError(t, err)
require.NotNil(t, rubberTree)
var wg sync.WaitGroup
wg.Add(1)
pulumi.All(
rubberTree.URN(),
rubberTree.Container.Material(),
rubberTree.Container.Color(),
rubberTree.Container.Size(),
rubberTree.Type,
).ApplyT(func(all []interface{}) error {
urn := all[0].(pulumi.URN)
material := all[1].(*string)
color := all[2].(*string)
size := all[3].(*plant.ContainerSize)
typ := all[4].(tree.RubberTreeVariety)
assert.Equal(t, *material, "ceramic", "unexpected material on resource: %v", urn)
assert.Equal(t, *color, "Magenta", "unexpected color on resource: %v", urn)
assert.Equal(t, *size, plant.ContainerSize(22), "unexpected size on resource: %v", urn)
assert.Equal(t, typ, tree.RubberTreeVarietyBurgundy, "unexpected type on resource: %v", urn)
wg.Done()
return nil
})
wg.Wait()
return nil
}, pulumi.WithMocks("project", "stack", mocks(1))))
})
}
type mocks int
func (mocks) NewResource(args pulumi.MockResourceArgs) (string, resource.PropertyMap, error) {
return args.Name + "_id", args.Inputs, nil
}
func (mocks) Call(args pulumi.MockCallArgs) (resource.PropertyMap, error) {
return args.Args, nil
}

View file

@ -7,8 +7,8 @@ import (
"fmt"
"github.com/blang/semver"
"github.com/pulumi/pulumi/pkg/v3/codegen/internal/test/testdata/simple-enum-schema/go/plant"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"simple-enum-schema/plant"
)
type module struct {

View file

@ -8,8 +8,8 @@ import (
"reflect"
"github.com/pkg/errors"
"github.com/pulumi/pulumi/pkg/v3/codegen/internal/test/testdata/simple-enum-schema/go/plant"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"simple-enum-schema/plant"
)
type RubberTree struct {

View file

@ -254,10 +254,7 @@
}
},
"go": {
"importBasePath": "github.com/pulumi/pulumi/pkg/v3/codegen/internal/test/testdata/simple-enum-schema/go/plant",
"packageImportAliases": {
"github.com/pulumi/pulumi/pkg/v3/codegen/internal/test/testdata/simple-enum-schema/go/plant/tree/v1": "treev1"
}
"importBasePath": "simple-enum-schema/plant"
},
"nodejs": {
"dependencies": {

View file

@ -28,3 +28,11 @@ export interface ArgFunctionArgs {
export interface ArgFunctionResult {
readonly result?: Resource;
}
export function argFunctionOutput(args?: ArgFunctionOutputArgs, opts?: pulumi.InvokeOptions): pulumi.Output<ArgFunctionResult> {
return pulumi.output(args).apply(a => argFunction(a, opts))
}
export interface ArgFunctionOutputArgs {
arg1?: pulumi.Input<Resource>;
}

View file

@ -28,3 +28,11 @@ export interface ArgFunctionArgs {
export interface ArgFunctionResult {
readonly result?: Resource;
}
export function argFunctionOutput(args?: ArgFunctionOutputArgs, opts?: pulumi.InvokeOptions): pulumi.Output<ArgFunctionResult> {
return pulumi.output(args).apply(a => argFunction(a, opts))
}
export interface ArgFunctionOutputArgs {
arg1?: pulumi.Input<Resource>;
}

View file

@ -28,3 +28,11 @@ export interface ArgFunctionArgs {
export interface ArgFunctionResult {
readonly result?: Resource;
}
export function argFunctionOutput(args?: ArgFunctionOutputArgs, opts?: pulumi.InvokeOptions): pulumi.Output<ArgFunctionResult> {
return pulumi.output(args).apply(a => argFunction(a, opts))
}
export interface ArgFunctionOutputArgs {
arg1?: pulumi.Input<Resource>;
}

View file

@ -870,28 +870,16 @@ func (mod *modContext) genFunction(w io.Writer, fun *schema.Function) {
// Now, emit the function signature.
var argsig string
argsOptional := true
argsOptional := functionArgsOptional(fun)
if fun.Inputs != nil {
for _, p := range fun.Inputs.Properties {
if p.IsRequired() {
argsOptional = false
break
}
}
optFlag := ""
if argsOptional {
optFlag = "?"
}
argsig = fmt.Sprintf("args%s: %sArgs, ", optFlag, title(name))
}
var retty string
if fun.Outputs == nil {
retty = "void"
} else {
retty = title(name) + "Result"
}
fmt.Fprintf(w, "export function %s(%sopts?: pulumi.InvokeOptions): Promise<%s> {\n", name, argsig, retty)
fmt.Fprintf(w, "export function %s(%sopts?: pulumi.InvokeOptions): Promise<%s> {\n",
name, argsig, functionReturnType(fun))
if fun.DeprecationMessage != "" && mod.compatibility != kubernetes20 {
fmt.Fprintf(w, " pulumi.log.warn(\"%s is deprecated: %s\")\n", name, fun.DeprecationMessage)
}
@ -930,6 +918,67 @@ func (mod *modContext) genFunction(w io.Writer, fun *schema.Function) {
fmt.Fprintf(w, "\n")
mod.genPlainType(w, title(name)+"Result", fun.Outputs.Comment, fun.Outputs.Properties, false, true, 0)
}
mod.genFunctionOutputVersion(w, fun)
}
func functionArgsOptional(fun *schema.Function) bool {
argsOptional := true
if fun.Inputs != nil {
for _, p := range fun.Inputs.Properties {
if p.IsRequired() {
argsOptional = false
break
}
}
}
return argsOptional
}
func functionReturnType(fun *schema.Function) string {
name := tokenToFunctionName(fun.Token)
var retty string
if fun.Outputs == nil {
retty = "void"
} else {
retty = title(name) + "Result"
}
return retty
}
// Generates `function ${fn}Output(..)` version lifted to work on
// `Input`-warpped arguments and producing an `Output`-wrapped result.
func (mod *modContext) genFunctionOutputVersion(w io.Writer, fun *schema.Function) {
if !fun.NeedsOutputVersion() {
return
}
originalName := tokenToFunctionName(fun.Token)
fnOutput := fmt.Sprintf("%sOutput", originalName)
argTypeName := fmt.Sprintf("%sArgs", title(fnOutput))
var argsig string
argsOptional := functionArgsOptional(fun)
optFlag := ""
if argsOptional {
optFlag = "?"
}
argsig = fmt.Sprintf("args%s: %s, ", optFlag, argTypeName)
fmt.Fprintf(w, `
export function %s(%sopts?: pulumi.InvokeOptions): pulumi.Output<%s> {
return pulumi.output(args).apply(a => %s(a, opts))
}
`, fnOutput, argsig, functionReturnType(fun), originalName)
fmt.Fprintf(w, "\n")
mod.genPlainType(w,
argTypeName,
fun.Inputs.Comment,
fun.Inputs.InputShape.Properties,
true, /* input */
false, /* readonly */
0 /* level */)
}
func visitObjectTypes(properties []*schema.Property, visitor func(*schema.ObjectType)) {
@ -1796,22 +1845,30 @@ func genNPMPackageMetadata(pkg *schema.Package, info NodePackageInfo) string {
packageVersion = pkg.Version.String()
}
// Ideally, this `scripts` section would include an install
// script that installs the provider, however, doing so causes
// problems when we try to restore package dependencies, since
// we must do an install for that. So we have another process
// that adds the install script when generating the
// package.json that we actually publish.
scripts := map[string]string{
"build": "tsc",
}
for k, v := range info.Scripts {
scripts[k] = v
}
// Create info that will get serialized into an NPM package.json.
npminfo := npmPackage{
Name: packageName,
Version: packageVersion,
Description: info.PackageDescription,
Keywords: pkg.Keywords,
Homepage: pkg.Homepage,
Repository: pkg.Repository,
License: pkg.License,
// Ideally, this `scripts` section would include an install script that installs the provider, however, doing
// so causes problems when we try to restore package dependencies, since we must do an install for that. So
// we have another process that adds the install script when generating the package.json that we actually
// publish.
Scripts: map[string]string{
"build": "tsc",
},
Name: packageName,
Version: packageVersion,
Description: info.PackageDescription,
Keywords: pkg.Keywords,
Homepage: pkg.Homepage,
Repository: pkg.Repository,
License: pkg.License,
Scripts: scripts,
DevDependencies: devDependencies,
Pulumi: npmPulumiManifest{
Resource: true,
@ -1890,6 +1947,8 @@ func genTypeScriptProjectFile(info NodePackageInfo, files fs) string {
tsFiles = append(tsFiles, f)
}
}
tsFiles = append(tsFiles, info.ExtraTypeScriptFiles...)
sort.Strings(tsFiles)
for i, file := range tsFiles {
@ -2113,7 +2172,6 @@ func GeneratePackage(tool string, pkg *schema.Package, extraFiles map[string][]b
}
}
// Finally emit the package metadata (NPM, TypeScript, and so on).
genPackageMetadata(pkg, info, files)
return files, nil
}

View file

@ -2,61 +2,32 @@
package nodejs
import (
"bytes"
"io/ioutil"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/pulumi/pulumi/pkg/v3/codegen/internal/test"
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
"github.com/pulumi/pulumi/pkg/v3/testing/integration"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/executable"
)
func TestGeneratePackage(t *testing.T) {
test.TestSDKCodegen(t, "nodejs", GeneratePackage, typeCheckGeneratedPackage)
test.TestSDKCodegen(t, &test.SDKCodegenOptions{
Language: "nodejs",
GenPackage: GeneratePackage,
Checks: map[string]test.CodegenCheck{
"nodejs/compile": typeCheckGeneratedPackage,
"nodejs/test": testGeneratedPackage,
},
})
}
func typeCheckGeneratedPackage(t *testing.T, pwd string) {
var err error
var npm string
npm, err = executable.FindExecutable("npm")
require.NoError(t, err)
test.RunCommand(t, "npm_install", pwd, "npm", "install")
test.RunCommand(t, "npm_build", pwd, "npm", "run", "build")
}
var stdout, stderr bytes.Buffer
cmdOptions := integration.ProgramTestOptions{
Verbose: true,
Stderr: &stderr,
Stdout: &stdout,
}
// TODO remove when https://github.com/pulumi/pulumi/pull/7938 lands
file := filepath.Join(pwd, "package.json")
oldFile, err := ioutil.ReadFile(file)
require.NoError(t, err)
newFile := strings.ReplaceAll(string(oldFile), "${VERSION}", "0.0.1")
err = ioutil.WriteFile(file, []byte(newFile), 0600)
require.NoError(t, err)
err = integration.RunCommand(t, "npm install", []string{npm, "i"}, pwd, &cmdOptions)
require.NoError(t, err)
err = integration.RunCommand(t, "typecheck ts",
[]string{filepath.Join(".", "node_modules", ".bin", "tsc"), "--noEmit"}, pwd, &cmdOptions)
if err != nil {
stderr := stderr.String()
if len(stderr) > 0 {
t.Logf("stderr: %s", stderr)
}
stdout := stdout.String()
if len(stdout) > 0 {
t.Logf("stdout: %s", stdout)
}
}
require.NoError(t, err)
func testGeneratedPackage(t *testing.T, pwd string) {
test.RunCommand(t, "npm_test", pwd, "npm", "run", "test")
}
func TestGenerateTypeNames(t *testing.T) {

View file

@ -58,6 +58,16 @@ type NodePackageInfo struct {
PluginName string `json:"pluginName,omitempty"`
// The version of the plugin, which might be different from the version of the package..
PluginVersion string `json:"pluginVersion,omitempty"`
// Additional files to include in TypeScript compilation.
// These paths are added to the `files` section of the
// generated `tsconfig.json`. A typical use case for this is
// compiling hand-authored unit test files that check the
// generated code.
ExtraTypeScriptFiles []string `json:"extraTypeScriptFiles,omitempty"`
// NPM script definitions. These define the `scripts` property
// of the generated `package.json`. See also:
// https://docs.npmjs.com/cli/v7/using-npm/scripts
Scripts map[string]string `json:"scripts,omitempty"`
}
// NodeObjectInfo contains NodeJS-specific information for an object.

View file

@ -59,21 +59,27 @@ func TestRelPathToRelImport(t *testing.T) {
}
func TestGeneratePackage(t *testing.T) {
test.TestSDKCodegen(t, "python", GeneratePackage, typeCheckGeneratedPackage)
test.TestSDKCodegen(t, &test.SDKCodegenOptions{
Language: "python",
GenPackage: GeneratePackage,
Checks: map[string]test.CodegenCheck{
"python/py_compile": pyCompileCheck,
},
})
}
// We can't type check a python program. We just check for syntax errors.
func typeCheckGeneratedPackage(t *testing.T, pwd string) {
// Checks generated code for syntax errors with `python -m compile`.
func pyCompileCheck(t *testing.T, codeDir string) {
ex, _, err := python.CommandPath()
require.NoError(t, err)
cmdOptions := integration.ProgramTestOptions{}
err = filepath.Walk(pwd, func(path string, info filesystem.FileInfo, err error) error {
err = filepath.Walk(codeDir, func(path string, info filesystem.FileInfo, err error) error {
require.NoError(t, err) // an error in the walk
if info.Mode().IsRegular() && strings.HasSuffix(info.Name(), ".py") {
path, err = filepath.Abs(path)
require.NoError(t, err)
err = integration.RunCommand(t, "python syntax check",
[]string{ex, "-m", "py_compile", path}, pwd, &cmdOptions)
[]string{ex, "-m", "py_compile", path}, codeDir, &cmdOptions)
require.NoError(t, err)
}
return nil

View file

@ -83,9 +83,15 @@ func RunCommand(t *testing.T, name string, args []string, wd string, opts *Progr
t.Logf("Invoke '%v' failed: %s\n", command, cmdutil.DetailedError(runerr))
if !opts.Verbose {
stderr := opts.Stderr
if stderr == nil {
stderr = os.Stderr
}
// Make sure we write the output in case of a failure to stderr so
// tests can assert the shape of the error message.
_, _ = fmt.Fprintf(opts.Stderr, "%s\n", string(runout))
_, _ = fmt.Fprintf(stderr, "%s\n", string(runout))
}
}