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 }