github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/codegen/nodejs/gen_test.go (about) 1 // nolint: lll 2 package nodejs 3 4 import ( 5 "bytes" 6 "encoding/json" 7 "io" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "strings" 12 "sync" 13 "testing" 14 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 ) 20 21 // For better CI test to job distribution, we split the test cases into three tests. 22 23 var genPkgBatchSize = len(test.PulumiPulumiSDKTests) / 3 24 25 func TestGeneratePackageOne(t *testing.T) { 26 t.Parallel() 27 28 testGeneratePackageBatch(t, test.PulumiPulumiSDKTests[0:genPkgBatchSize]) 29 } 30 31 func TestGeneratePackageTwo(t *testing.T) { 32 t.Parallel() 33 34 testGeneratePackageBatch(t, test.PulumiPulumiSDKTests[genPkgBatchSize:2*genPkgBatchSize]) 35 } 36 37 func TestGeneratePackageThree(t *testing.T) { 38 t.Parallel() 39 40 testGeneratePackageBatch(t, test.PulumiPulumiSDKTests[2*genPkgBatchSize:]) 41 } 42 43 func testGeneratePackageBatch(t *testing.T, testCases []*test.SDKTest) { 44 test.TestSDKCodegen(t, &test.SDKCodegenOptions{ 45 Language: "nodejs", 46 GenPackage: GeneratePackage, 47 Checks: map[string]test.CodegenCheck{ 48 "nodejs/compile": func(t *testing.T, pwd string) { 49 typeCheckGeneratedPackage(t, pwd, true) 50 }, 51 "nodejs/test": testGeneratedPackage, 52 }, 53 TestCases: testCases, 54 }) 55 } 56 57 // Runs unit tests against the generated code. 58 func testGeneratedPackage(t *testing.T, pwd string) { 59 60 // Some tests have do not have mocha as a dependency. 61 hasMocha := false 62 for _, c := range getYarnCommands(t, pwd) { 63 if c == "mocha" { 64 hasMocha = true 65 break 66 } 67 } 68 69 // We are attempting to ensure that we don't write tests that are not run. The `nodejs-extras` 70 // folder exists to mixin tests of the form `*.spec.ts`. We assume that if this folder is 71 // present and contains `*.spec.ts` files, we want to run those tests. 72 foundTests := false 73 findTests := func(path string, _ os.DirEntry, _ error) error { 74 if strings.HasSuffix(path, ".spec.ts") { 75 foundTests = true 76 } 77 return nil 78 } 79 mixinFolder := filepath.Join(filepath.Dir(pwd), "nodejs-extras") 80 if err := filepath.WalkDir(mixinFolder, findTests); !hasMocha && !os.IsNotExist(err) && foundTests { 81 t.Errorf("%s has at least one nodejs-extras/**/*.spec.ts file , but does not have mocha as a dependency."+ 82 " Tests were not run. Please add mocha as a dependency in the schema or remove the *.spec.ts files.", 83 pwd) 84 } 85 86 if hasMocha { 87 // If mocha is a dev dependency but no test files exist, this will fail. 88 test.RunCommand(t, "mocha", pwd, 89 "yarn", "run", "mocha", 90 "--require", "ts-node/register", 91 "tests/**/*.spec.ts") 92 } else { 93 t.Logf("No mocha tests found for %s", pwd) 94 } 95 } 96 97 // Get the commands runnable with yarn run 98 func getYarnCommands(t *testing.T, pwd string) []string { 99 cmd := exec.Command("yarn", "run", "--json") 100 cmd.Dir = pwd 101 out, err := cmd.Output() 102 if err != nil { 103 t.Errorf("Got error determining valid commands: %s", err) 104 } 105 dec := json.NewDecoder(bytes.NewReader(out)) 106 parsed := []map[string]interface{}{} 107 for { 108 var m map[string]interface{} 109 if err := dec.Decode(&m); err != nil { 110 if err == io.EOF { 111 break 112 } 113 t.FailNow() 114 } 115 parsed = append(parsed, m) 116 } 117 var cmds []string 118 119 addProvidedCmds := func(c map[string]interface{}) { 120 // If this fails, we want the test to fail. We don't want to accidentally skip tests. 121 data := c["data"].(map[string]interface{}) 122 if data["type"] == "possibleCommands" { 123 return 124 } 125 for _, cmd := range data["items"].([]interface{}) { 126 cmds = append(cmds, cmd.(string)) 127 } 128 } 129 130 addBinaryCmds := func(c map[string]interface{}) { 131 data := c["data"].(string) 132 if !strings.HasPrefix(data, "Commands available from binary scripts:") { 133 return 134 } 135 cmdList := data[strings.Index(data, ":")+1:] 136 for _, cmd := range strings.Split(cmdList, ",") { 137 cmds = append(cmds, strings.TrimSpace(cmd)) 138 } 139 } 140 141 for _, c := range parsed { 142 switch c["type"] { 143 case "list": 144 addProvidedCmds(c) 145 case "info": 146 addBinaryCmds(c) 147 } 148 } 149 t.Logf("Found yarn commands in %s: %v", pwd, cmds) 150 return cmds 151 } 152 153 func TestGenerateTypeNames(t *testing.T) { 154 t.Parallel() 155 156 test.TestTypeNameCodegen(t, "nodejs", func(pkg *schema.Package) test.TypeNameGeneratorFunc { 157 modules, info, err := generateModuleContextMap("test", pkg, nil) 158 require.NoError(t, err) 159 160 pkg.Language["nodejs"] = info 161 162 root, ok := modules[""] 163 require.True(t, ok) 164 165 // Parallel tests will use the TypeNameGeneratorFunc 166 // from multiple goroutines, but root.typeString is 167 // not safe. Mutex is needed to avoid panics on 168 // concurrent map write. 169 // 170 // Note this problem is test-only since prod code 171 // works on a single goroutine. 172 173 var mutex sync.Mutex 174 return func(t schema.Type) string { 175 mutex.Lock() 176 defer mutex.Unlock() 177 return root.typeString(t, false, nil) 178 } 179 }) 180 } 181 182 func TestPascalCases(t *testing.T) { 183 t.Parallel() 184 185 tests := []struct { 186 input string 187 expected string 188 }{ 189 { 190 input: "hi", 191 expected: "Hi", 192 }, 193 { 194 input: "NothingChanges", 195 expected: "NothingChanges", 196 }, 197 { 198 input: "everything-changed", 199 expected: "EverythingChanged", 200 }, 201 } 202 for _, tt := range tests { 203 result := pascal(tt.input) 204 require.Equal(t, tt.expected, result) 205 } 206 } 207 208 func Test_isStringType(t *testing.T) { 209 t.Parallel() 210 211 tests := []struct { 212 name string 213 input schema.Type 214 expected bool 215 }{ 216 {"string", schema.StringType, true}, 217 {"int", schema.IntType, false}, 218 {"Input[string]", &schema.InputType{ElementType: schema.StringType}, true}, 219 {"Input[int]", &schema.InputType{ElementType: schema.IntType}, false}, 220 {"StrictStringEnum", &schema.EnumType{ElementType: schema.StringType}, true}, 221 {"StrictIntEnum", &schema.EnumType{ElementType: schema.IntType}, false}, 222 {"RelaxedStringEnum", &schema.UnionType{ 223 ElementTypes: []schema.Type{&schema.EnumType{ElementType: schema.StringType}, schema.StringType}, 224 }, true}, 225 {"RelaxedIntEnum", &schema.UnionType{ 226 ElementTypes: []schema.Type{&schema.EnumType{ElementType: schema.IntType}, schema.IntType}, 227 }, false}, 228 } 229 for _, tt := range tests { 230 tt := tt 231 t.Run(tt.name, func(t *testing.T) { 232 t.Parallel() 233 if got := isStringType(tt.input); got != tt.expected { 234 t.Errorf("isStringType() = %v, want %v", got, tt.expected) 235 } 236 }) 237 } 238 } 239 240 // This test asserts that getRelativePath() 241 // returns the right relative path. This smoke test 242 // functions to pin the expected behavior to prevent regressions. 243 func TestGetRelativePath(t *testing.T) { 244 t.Parallel() 245 type TestCase struct { 246 filename string 247 expected string 248 } 249 // Recall that arguments are assumed to be directory names, 250 // even if they contain an extension. 251 var cases = []TestCase{ 252 { 253 filename: "foo.ts", 254 expected: "..", 255 }, { 256 filename: "foo/bar", 257 expected: "../..", 258 }, { 259 filename: "types/accessanalyzer/input", 260 expected: "../../..", 261 }, { 262 filename: "types/accessanalyzer/nested/input.ts", 263 expected: "../../../..", 264 }, { 265 filename: "types", 266 expected: "..", 267 }, { 268 filename: "./types/aws", 269 expected: "../..", 270 }, { 271 filename: "./types", 272 expected: "..", 273 }} 274 for _, tc := range cases { 275 var observed = getRelativePath(tc.filename) 276 require.Equal( 277 t, 278 tc.expected, 279 observed, 280 "Case (%s): Expected %s, Observed %s", 281 tc.filename, 282 tc.expected, 283 observed, 284 ) 285 } 286 }