github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/codegen/go/gen_test.go (about)

     1  package gen
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"path/filepath"
     9  	"regexp"
    10  	"sort"
    11  	"strings"
    12  	"testing"
    13  
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  
    17  	"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
    18  	"github.com/pulumi/pulumi/pkg/v3/codegen/testing/test"
    19  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/executable"
    20  )
    21  
    22  func TestInputUsage(t *testing.T) {
    23  	t.Parallel()
    24  
    25  	pkg := &pkgContext{}
    26  	arrayUsage := pkg.getInputUsage("FooArray")
    27  	assert.Equal(
    28  		t,
    29  		"FooArrayInput is an input type that accepts FooArray and FooArrayOutput values.\nYou can construct a "+
    30  			"concrete instance of `FooArrayInput` via:\n\n\t\t FooArray{ FooArgs{...} }\n ",
    31  		arrayUsage)
    32  
    33  	mapUsage := pkg.getInputUsage("FooMap")
    34  	assert.Equal(
    35  		t,
    36  		"FooMapInput is an input type that accepts FooMap and FooMapOutput values.\nYou can construct a concrete"+
    37  			" instance of `FooMapInput` via:\n\n\t\t FooMap{ \"key\": FooArgs{...} }\n ",
    38  		mapUsage)
    39  
    40  	ptrUsage := pkg.getInputUsage("FooPtr")
    41  	assert.Equal(
    42  		t,
    43  		"FooPtrInput is an input type that accepts FooArgs, FooPtr and FooPtrOutput values.\nYou can construct a "+
    44  			"concrete instance of `FooPtrInput` via:\n\n\t\t FooArgs{...}\n\n or:\n\n\t\t nil\n ",
    45  		ptrUsage)
    46  
    47  	usage := pkg.getInputUsage("Foo")
    48  	assert.Equal(
    49  		t,
    50  		"FooInput is an input type that accepts FooArgs and FooOutput values.\nYou can construct a concrete instance"+
    51  			" of `FooInput` via:\n\n\t\t FooArgs{...}\n ",
    52  		usage)
    53  }
    54  
    55  func TestGoPackageName(t *testing.T) {
    56  	t.Parallel()
    57  
    58  	assert.Equal(t, "aws", goPackage("aws"))
    59  	assert.Equal(t, "azurenextgen", goPackage("azure-nextgen"))
    60  	assert.Equal(t, "plantprovider", goPackage("plant-provider"))
    61  	assert.Equal(t, "", goPackage(""))
    62  }
    63  
    64  func TestGeneratePackage(t *testing.T) {
    65  	t.Parallel()
    66  
    67  	generatePackage := func(tool string, pkg *schema.Package, files map[string][]byte) (map[string][]byte, error) {
    68  
    69  		for f := range files {
    70  			t.Logf("Ignoring extraFile %s", f)
    71  		}
    72  
    73  		return GeneratePackage(tool, pkg)
    74  	}
    75  	test.TestSDKCodegen(t, &test.SDKCodegenOptions{
    76  		Language:   "go",
    77  		GenPackage: generatePackage,
    78  		Checks: map[string]test.CodegenCheck{
    79  			"go/compile": typeCheckGeneratedPackage,
    80  			"go/test":    testGeneratedPackage,
    81  		},
    82  		TestCases: test.PulumiPulumiSDKTests,
    83  	})
    84  }
    85  
    86  func inferModuleName(codeDir string) string {
    87  	// For example for this path:
    88  	//
    89  	// codeDir = "../testing/test/testdata/external-resource-schema/go/"
    90  	//
    91  	// We will generate "$codeDir/go.mod" using
    92  	// `external-resource-schema` as the module name so that it
    93  	// can compile independently.
    94  	return filepath.Base(filepath.Dir(codeDir))
    95  }
    96  
    97  func typeCheckGeneratedPackage(t *testing.T, codeDir string) {
    98  	sdk, err := filepath.Abs(filepath.Join("..", "..", "..", "sdk"))
    99  	require.NoError(t, err)
   100  
   101  	goExe, err := executable.FindExecutable("go")
   102  	require.NoError(t, err)
   103  
   104  	goMod := filepath.Join(codeDir, "go.mod")
   105  	alreadyHaveGoMod, err := test.PathExists(goMod)
   106  	require.NoError(t, err)
   107  
   108  	if alreadyHaveGoMod {
   109  		t.Logf("Found an existing go.mod, leaving as is")
   110  	} else {
   111  		test.RunCommand(t, "go_mod_init", codeDir, goExe, "mod", "init", inferModuleName(codeDir))
   112  		replacement := fmt.Sprintf("github.com/pulumi/pulumi/sdk/v3=%s", sdk)
   113  		test.RunCommand(t, "go_mod_edit", codeDir, goExe, "mod", "edit", "-replace", replacement)
   114  	}
   115  
   116  	test.RunCommand(t, "go_mod_tidy", codeDir, goExe, "mod", "tidy")
   117  	test.RunCommand(t, "go_build", codeDir, goExe, "build", "-v", "all")
   118  }
   119  
   120  func testGeneratedPackage(t *testing.T, codeDir string) {
   121  	goExe, err := executable.FindExecutable("go")
   122  	require.NoError(t, err)
   123  
   124  	test.RunCommand(t, "go-test", codeDir, goExe, "test", fmt.Sprintf("%s/...", inferModuleName(codeDir)))
   125  }
   126  
   127  func TestGenerateTypeNames(t *testing.T) {
   128  	t.Parallel()
   129  
   130  	test.TestTypeNameCodegen(t, "go", func(pkg *schema.Package) test.TypeNameGeneratorFunc {
   131  		err := pkg.ImportLanguages(map[string]schema.Language{"go": Importer})
   132  		require.NoError(t, err)
   133  
   134  		var goPkgInfo GoPackageInfo
   135  		if goInfo, ok := pkg.Language["go"].(GoPackageInfo); ok {
   136  			goPkgInfo = goInfo
   137  		}
   138  		packages := generatePackageContextMap("test", pkg, goPkgInfo, nil)
   139  
   140  		root, ok := packages[""]
   141  		require.True(t, ok)
   142  
   143  		return func(t schema.Type) string {
   144  			return root.typeString(t)
   145  		}
   146  	})
   147  }
   148  
   149  func readSchemaFile(file string) *schema.Package {
   150  	// Read in, decode, and import the schema.
   151  	schemaBytes, err := ioutil.ReadFile(filepath.Join("..", "testing", "test", "testdata", file))
   152  	if err != nil {
   153  		panic(err)
   154  	}
   155  	var pkgSpec schema.PackageSpec
   156  	if err = json.Unmarshal(schemaBytes, &pkgSpec); err != nil {
   157  		panic(err)
   158  	}
   159  	pkg, err := schema.ImportSpec(pkgSpec, map[string]schema.Language{"go": Importer})
   160  	if err != nil {
   161  		panic(err)
   162  	}
   163  
   164  	return pkg
   165  }
   166  
   167  // We test the naming/module structure of generated packages.
   168  func TestPackageNaming(t *testing.T) {
   169  	t.Parallel()
   170  
   171  	testCases := []struct {
   172  		importBasePath  string
   173  		rootPackageName string
   174  		name            string
   175  		expectedRoot    string
   176  	}{
   177  		{
   178  			importBasePath: "github.com/pulumi/pulumi-azure-quickstart-acr-geo-replication/sdk/go/acr",
   179  			expectedRoot:   "acr",
   180  		},
   181  		{
   182  			importBasePath:  "github.com/ihave/animport",
   183  			rootPackageName: "root",
   184  			expectedRoot:    "",
   185  		},
   186  		{
   187  			name:         "named-package",
   188  			expectedRoot: "namedpackage",
   189  		},
   190  	}
   191  	for _, tt := range testCases {
   192  		tt := tt
   193  		t.Run(tt.expectedRoot, func(t *testing.T) {
   194  			t.Parallel()
   195  
   196  			// This schema is arbitrary. We just needed a filled out schema. All
   197  			// path decisions should be made based off of the Name and
   198  			// Language[go] fields (which we set after import).
   199  			schema := readSchemaFile(filepath.Join("schema", "good-enum-1.json"))
   200  			if tt.name != "" {
   201  				// We want there to be a name, so if one isn't provided we
   202  				// default to the schema.
   203  				schema.Name = tt.name
   204  			}
   205  			schema.Language = map[string]interface{}{
   206  				"go": GoPackageInfo{
   207  					ImportBasePath:  tt.importBasePath,
   208  					RootPackageName: tt.rootPackageName,
   209  				},
   210  			}
   211  			files, err := GeneratePackage("test", schema)
   212  			require.NoError(t, err)
   213  			ordering := make([]string, len(files))
   214  			var i int
   215  			for k := range files {
   216  				ordering[i] = k
   217  				i++
   218  			}
   219  			ordering = sort.StringSlice(ordering)
   220  			require.NotEmpty(t, files, "This test only works when files are generated")
   221  			for _, k := range ordering {
   222  				root := strings.Split(k, "/")[0]
   223  				if tt.expectedRoot != "" {
   224  					require.Equal(t, tt.expectedRoot, root, "Root should precede all cases. Got file %s", k)
   225  				}
   226  				// We should work on a way to assert this is one level higher then it otherwise would be.
   227  			}
   228  		})
   229  	}
   230  }
   231  
   232  func TestTokenToType(t *testing.T) {
   233  	t.Parallel()
   234  
   235  	const awsImportBasePath = "github.com/pulumi/pulumi-aws/sdk/v4/go/aws"
   236  	awsSpec := schema.PackageSpec{
   237  		Name: "aws",
   238  		Meta: &schema.MetadataSpec{
   239  			ModuleFormat: "(.*)(?:/[^/]*)",
   240  		},
   241  	}
   242  
   243  	const googleNativeImportBasePath = "github.com/pulumi/pulumi-google-native/sdk/go/google"
   244  	googleNativeSpec := schema.PackageSpec{
   245  		Name: "google-native",
   246  	}
   247  
   248  	tests := []struct {
   249  		pkg      *pkgContext
   250  		token    string
   251  		expected string
   252  	}{
   253  		{
   254  			pkg: &pkgContext{
   255  				pkg:            importSpec(t, awsSpec),
   256  				importBasePath: awsImportBasePath,
   257  			},
   258  			token:    "aws:s3/BucketWebsite:BucketWebsite",
   259  			expected: "s3.BucketWebsite",
   260  		},
   261  		{
   262  			pkg: &pkgContext{
   263  				pkg:            importSpec(t, awsSpec),
   264  				importBasePath: awsImportBasePath,
   265  				pkgImportAliases: map[string]string{
   266  					"github.com/pulumi/pulumi-aws/sdk/v4/go/aws/s3": "awss3",
   267  				},
   268  			},
   269  			token:    "aws:s3/BucketWebsite:BucketWebsite",
   270  			expected: "awss3.BucketWebsite",
   271  		},
   272  		{
   273  			pkg: &pkgContext{
   274  				pkg:            importSpec(t, googleNativeSpec),
   275  				importBasePath: googleNativeImportBasePath,
   276  				pkgImportAliases: map[string]string{
   277  					"github.com/pulumi/pulumi-google-native/sdk/go/google/dns/v1": "dns",
   278  				},
   279  			},
   280  			token:    "google-native:dns/v1:DnsKeySpec",
   281  			expected: "dns.DnsKeySpec",
   282  		},
   283  	}
   284  	//nolint:paralleltest // false positive because range var isn't used directly in t.Run(name) arg
   285  	for _, tt := range tests {
   286  		tt := tt
   287  		t.Run(tt.token+"=>"+tt.expected, func(t *testing.T) {
   288  			t.Parallel()
   289  
   290  			actual := tt.pkg.tokenToType(tt.token)
   291  			assert.Equal(t, tt.expected, actual)
   292  		})
   293  	}
   294  }
   295  
   296  func TestTokenToResource(t *testing.T) {
   297  	t.Parallel()
   298  
   299  	const awsImportBasePath = "github.com/pulumi/pulumi-aws/sdk/v4/go/aws"
   300  	awsSpec := schema.PackageSpec{
   301  		Name: "aws",
   302  		Meta: &schema.MetadataSpec{
   303  			ModuleFormat: "(.*)(?:/[^/]*)",
   304  		},
   305  	}
   306  
   307  	const googleNativeImportBasePath = "github.com/pulumi/pulumi-google-native/sdk/go/google"
   308  	googleNativeSpec := schema.PackageSpec{
   309  		Name: "google-native",
   310  	}
   311  
   312  	tests := []struct {
   313  		pkg      *pkgContext
   314  		token    string
   315  		expected string
   316  	}{
   317  		{
   318  			pkg: &pkgContext{
   319  				pkg:            importSpec(t, awsSpec),
   320  				importBasePath: awsImportBasePath,
   321  			},
   322  			token:    "aws:s3/Bucket:Bucket",
   323  			expected: "s3.Bucket",
   324  		},
   325  		{
   326  			pkg: &pkgContext{
   327  				pkg:            importSpec(t, awsSpec),
   328  				importBasePath: awsImportBasePath,
   329  				pkgImportAliases: map[string]string{
   330  					"github.com/pulumi/pulumi-aws/sdk/v4/go/aws/s3": "awss3",
   331  				},
   332  			},
   333  			token:    "aws:s3/Bucket:Bucket",
   334  			expected: "awss3.Bucket",
   335  		},
   336  		{
   337  			pkg: &pkgContext{
   338  				pkg:            importSpec(t, googleNativeSpec),
   339  				importBasePath: googleNativeImportBasePath,
   340  				pkgImportAliases: map[string]string{
   341  					"github.com/pulumi/pulumi-google-native/sdk/go/google/dns/v1": "dns",
   342  				},
   343  			},
   344  			token:    "google-native:dns/v1:Policy",
   345  			expected: "dns.Policy",
   346  		},
   347  	}
   348  	//nolint:paralleltest // false positive because range var isn't used directly in t.Run(name) arg
   349  	for _, tt := range tests {
   350  		tt := tt
   351  		t.Run(tt.token+"=>"+tt.expected, func(t *testing.T) {
   352  			t.Parallel()
   353  
   354  			actual := tt.pkg.tokenToResource(tt.token)
   355  			assert.Equal(t, tt.expected, actual)
   356  		})
   357  	}
   358  }
   359  
   360  func importSpec(t *testing.T, spec schema.PackageSpec) *schema.Package {
   361  	importedPkg, err := schema.ImportSpec(spec, map[string]schema.Language{})
   362  	assert.NoError(t, err)
   363  	return importedPkg
   364  }
   365  
   366  func TestGenHeader(t *testing.T) {
   367  	t.Parallel()
   368  
   369  	pkg := &pkgContext{
   370  		tool: "a tool",
   371  		pkg:  &schema.Package{Name: "test-pkg"},
   372  	}
   373  
   374  	s := func() string {
   375  		b := &bytes.Buffer{}
   376  		pkg.genHeader(b, []string{"pkg1", "example.com/foo/123-foo"}, nil)
   377  		return b.String()
   378  	}()
   379  	assert.Equal(t, `// Code generated by a tool DO NOT EDIT.
   380  // *** WARNING: Do not edit by hand unless you're certain you know what you are doing! ***
   381  
   382  package testpkg
   383  
   384  import (
   385  	"pkg1"
   386  	"example.com/foo/123-foo"
   387  )
   388  
   389  `, s)
   390  
   391  	// Compliance is defined by https://pkg.go.dev/cmd/go#hdr-Generate_Go_files_by_processing_source
   392  	autogenerated := regexp.MustCompile(`^// Code generated .* DO NOT EDIT\.$`)
   393  	found := false
   394  	for _, l := range strings.Split(s, "\n") {
   395  		switch {
   396  		case autogenerated.Match([]byte(l)):
   397  			found = true
   398  			break
   399  		case l == "" || strings.HasPrefix(l, "//"):
   400  		default:
   401  			break
   402  		}
   403  	}
   404  	assert.Truef(t, found, `Didn't find a line that complies with "%v"`, autogenerated)
   405  }
   406  func TestTitle(t *testing.T) {
   407  	t.Parallel()
   408  	assert := assert.New(t)
   409  
   410  	assert.Equal("", Title(""))
   411  	assert.Equal("Plugh", Title("plugh"))
   412  	assert.Equal("WaldoThudFred", Title("WaldoThudFred"))
   413  	assert.Equal("WaldoThudFred", Title("waldoThudFred"))
   414  	assert.Equal("WaldoThudFred", Title("waldo-Thud-Fred"))
   415  	assert.Equal("WaldoThudFred", Title("waldo-ThudFred"))
   416  	assert.Equal("WaldoThud_Fred", Title("waldo-Thud_Fred"))
   417  	assert.Equal("WaldoThud_Fred", Title("waldo-thud_Fred"))
   418  }