github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/codegen/testing/test/helpers.go (about) 1 // Copyright 2016-2021, Pulumi Corporation. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package test 16 17 import ( 18 "bytes" 19 "encoding/json" 20 "fmt" 21 "io" 22 "io/ioutil" 23 "os" 24 "path/filepath" 25 "sort" 26 "testing" 27 28 "github.com/stretchr/testify/assert" 29 "github.com/stretchr/testify/require" 30 "gopkg.in/yaml.v3" 31 32 "github.com/pulumi/pulumi/pkg/v3/codegen/schema" 33 "github.com/pulumi/pulumi/pkg/v3/codegen/testing/utils" 34 "github.com/pulumi/pulumi/pkg/v3/testing/integration" 35 "github.com/pulumi/pulumi/sdk/v3/go/common/util/executable" 36 ) 37 38 // GenPkgSignature corresponds to the shape of the codegen GeneratePackage functions. 39 type GenPkgSignature func(string, *schema.Package, map[string][]byte) (map[string][]byte, error) 40 41 // GeneratePackageFilesFromSchema loads a schema and generates files using the provided GeneratePackage function. 42 func GeneratePackageFilesFromSchema(schemaPath string, genPackageFunc GenPkgSignature) (map[string][]byte, error) { 43 // Read in, decode, and import the schema. 44 schemaBytes, err := ioutil.ReadFile(schemaPath) 45 if err != nil { 46 return nil, err 47 } 48 49 ext := filepath.Ext(schemaPath) 50 51 var pkgSpec schema.PackageSpec 52 if ext == ".yaml" || ext == ".yml" { 53 err = yaml.Unmarshal(schemaBytes, &pkgSpec) 54 } else { 55 err = json.Unmarshal(schemaBytes, &pkgSpec) 56 } 57 if err != nil { 58 return nil, err 59 } 60 61 loader := schema.NewPluginLoader(utils.NewHost(testdataPath)) 62 pkg, diags, err := schema.BindSpec(pkgSpec, loader) 63 if err != nil { 64 return nil, err 65 } else if diags.HasErrors() { 66 return nil, diags 67 } 68 69 return genPackageFunc("test", pkg, nil) 70 } 71 72 // LoadFiles loads the provided list of files from a directory. 73 func LoadFiles(dir, lang string, files []string) (map[string][]byte, error) { 74 result := map[string][]byte{} 75 for _, file := range files { 76 fileBytes, err := ioutil.ReadFile(filepath.Join(dir, lang, file)) 77 if err != nil { 78 return nil, err 79 } 80 81 result[file] = fileBytes 82 } 83 84 return result, nil 85 } 86 87 func PathExists(path string) (bool, error) { 88 _, err := os.Stat(path) 89 90 if os.IsNotExist(err) { 91 return false, nil 92 } 93 94 if err == nil { 95 return true, nil 96 } 97 98 return false, err 99 } 100 101 // `LoadBaseline` loads the contents of the given baseline directory, 102 // by inspecting its `codegen-manifest.json`. 103 func LoadBaseline(dir, lang string) (map[string][]byte, error) { 104 cm := &codegenManifest{} 105 err := cm.load(filepath.Join(dir, lang)) 106 if err != nil { 107 return nil, fmt.Errorf("Failed to load codegen-manifest.json: %w", err) 108 } 109 110 files := make(map[string][]byte) 111 112 for _, f := range cm.EmittedFiles { 113 bytes, err := ioutil.ReadFile(filepath.Join(dir, lang, f)) 114 if err != nil { 115 return nil, fmt.Errorf("Failed to load file %s referenced in codegen-manifest.json: %w", f, err) 116 } 117 files[f] = bytes 118 } 119 120 return files, nil 121 } 122 123 type codegenManifest struct { 124 EmittedFiles []string `json:"emittedFiles"` 125 } 126 127 func (cm *codegenManifest) load(dir string) error { 128 bytes, err := ioutil.ReadFile(filepath.Join(dir, "codegen-manifest.json")) 129 if err != nil { 130 return err 131 } 132 return json.Unmarshal(bytes, cm) 133 } 134 135 func (cm *codegenManifest) save(dir string) error { 136 sort.Strings(cm.EmittedFiles) 137 buf := &bytes.Buffer{} 138 enc := json.NewEncoder(buf) 139 enc.SetEscapeHTML(false) 140 enc.SetIndent("", " ") 141 err := enc.Encode(cm) 142 if err != nil { 143 return err 144 } 145 data := buf.Bytes() 146 return ioutil.WriteFile(filepath.Join(dir, "codegen-manifest.json"), data, 0600) 147 } 148 149 // ValidateFileEquality compares maps of files for equality. 150 func ValidateFileEquality(t *testing.T, actual, expected map[string][]byte) bool { 151 ok := true 152 for name, file := range expected { 153 _, inActual := actual[name] 154 if inActual { 155 if !assert.Equal(t, string(file), string(actual[name]), name) { 156 t.Logf("%s did not agree", name) 157 ok = false 158 } 159 } else { 160 t.Logf("File %s was expected but is missing from the actual fileset", name) 161 ok = false 162 } 163 } 164 for name := range actual { 165 if _, inExpected := expected[name]; !inExpected { 166 t.Logf("File %s from the actual fileset was not expected", name) 167 ok = false 168 } 169 } 170 return ok 171 } 172 173 // If PULUMI_ACCEPT is set, writes out actual output to the expected 174 // file set, so we can continue enjoying golden tests without manually 175 // modifying the expected output. 176 func RewriteFilesWhenPulumiAccept(t *testing.T, dir, lang string, actual map[string][]byte) bool { 177 if os.Getenv("PULUMI_ACCEPT") == "" { 178 return false 179 } 180 181 cm := &codegenManifest{} 182 183 baseline := filepath.Join(dir, lang) 184 185 // Remove the baseline directory's current contents. 186 _, err := os.ReadDir(baseline) 187 switch { 188 case err == nil: 189 err = os.RemoveAll(baseline) 190 require.NoError(t, err) 191 case os.IsNotExist(err): 192 // OK 193 default: 194 require.NoError(t, err) 195 } 196 197 for file, bytes := range actual { 198 relPath := filepath.FromSlash(file) 199 path := filepath.Join(dir, lang, relPath) 200 cm.EmittedFiles = append(cm.EmittedFiles, relPath) 201 err := writeFileEnsuringDir(path, bytes) 202 require.NoError(t, err) 203 } 204 205 err = cm.save(filepath.Join(dir, lang)) 206 require.NoError(t, err) 207 208 return true 209 } 210 211 // Useful for populating code-generated destination 212 // `codeDir=$dir/$lang` with extra manually written files such as the 213 // unit test files. These files are copied from `$dir/$lang-extras` 214 // folder if present. 215 func CopyExtraFiles(t *testing.T, dir, lang string) { 216 codeDir := filepath.Join(dir, lang) 217 extrasDir := filepath.Join(dir, fmt.Sprintf("%s-extras", lang)) 218 gotExtras, err := PathExists(extrasDir) 219 220 if !gotExtras { 221 return 222 } 223 224 if err != nil { 225 require.NoError(t, err) 226 return 227 } 228 229 err = filepath.Walk(extrasDir, func(path string, info os.FileInfo, err error) error { 230 if err != nil { 231 return err 232 } 233 if info.IsDir() { 234 return nil 235 } 236 relPath, err := filepath.Rel(extrasDir, path) 237 if err != nil { 238 return err 239 } 240 destPath := filepath.Join(codeDir, relPath) 241 242 bytes, err := ioutil.ReadFile(path) 243 if err != nil { 244 return err 245 } 246 err = writeFileEnsuringDir(destPath, bytes) 247 if err != nil { 248 return err 249 } 250 t.Logf("Copied %s to %s", path, destPath) 251 return nil 252 }) 253 254 require.NoError(t, err) 255 } 256 257 func writeFileEnsuringDir(path string, bytes []byte) error { 258 if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil && !os.IsExist(err) { 259 return err 260 } 261 262 return ioutil.WriteFile(path, bytes, 0600) 263 } 264 265 // CheckAllFilesGenerated ensures that the set of expected and actual files generated 266 // are exactly equivalent. 267 func CheckAllFilesGenerated(t *testing.T, actual, expected map[string][]byte) { 268 seen := map[string]bool{} 269 for x := range expected { 270 seen[x] = true 271 } 272 for a := range actual { 273 assert.Contains(t, seen, a, "Unexpected file generated: %s", a) 274 if seen[a] { 275 delete(seen, a) 276 } 277 } 278 279 for s := range seen { 280 assert.Fail(t, "No content generated for expected file %s", s) 281 } 282 } 283 284 // Validates a transformer on a single file. 285 func ValidateFileTransformer( 286 t *testing.T, 287 inputFile string, 288 expectedOutputFile string, 289 transformer func(reader io.Reader, writer io.Writer) error) { 290 291 reader, err := os.Open(inputFile) 292 if err != nil { 293 t.Error(err) 294 return 295 } 296 297 var buf bytes.Buffer 298 299 err = transformer(reader, &buf) 300 if err != nil { 301 t.Error(err) 302 return 303 } 304 305 actualBytes := buf.Bytes() 306 307 if os.Getenv("PULUMI_ACCEPT") != "" { 308 err := ioutil.WriteFile(expectedOutputFile, actualBytes, 0600) 309 if err != nil { 310 t.Error(err) 311 return 312 } 313 } 314 315 actual := map[string][]byte{expectedOutputFile: actualBytes} 316 317 expectedBytes, err := ioutil.ReadFile(expectedOutputFile) 318 if err != nil { 319 t.Error(err) 320 return 321 } 322 323 expected := map[string][]byte{expectedOutputFile: expectedBytes} 324 325 ValidateFileEquality(t, actual, expected) 326 } 327 328 func RunCommand(t *testing.T, name string, cwd string, exec string, args ...string) { 329 RunCommandWithOptions(t, &integration.ProgramTestOptions{}, name, cwd, exec, args...) 330 } 331 332 func RunCommandWithOptions( 333 t *testing.T, 334 opts *integration.ProgramTestOptions, 335 name string, cwd string, exec string, args ...string) { 336 337 exec, err := executable.FindExecutable(exec) 338 if err != nil { 339 t.Error(err) 340 t.FailNow() 341 } 342 wd, err := filepath.Abs(cwd) 343 require.NoError(t, err) 344 var stdout, stderr bytes.Buffer 345 opts.Stdout = &stdout 346 opts.Stderr = &stderr 347 opts.Verbose = true 348 err = integration.RunCommand(t, 349 name, 350 append([]string{exec}, args...), 351 wd, 352 opts) 353 if !assert.NoError(t, err) { 354 stdout := stdout.String() 355 stderr := stderr.String() 356 if len(stdout) > 0 { 357 t.Logf("stdout: %s", stdout) 358 } 359 if len(stderr) > 0 { 360 t.Logf("stderr: %s", stderr) 361 } 362 t.FailNow() 363 } 364 } 365 366 type SchemaVersion = string 367 368 // Schemas are downloaded in the makefile, and the versions specified here 369 // should be in sync with the makefile. 370 const ( 371 AwsSchema SchemaVersion = "4.26.0" 372 AzureNativeSchema SchemaVersion = "1.29.0" 373 AzureSchema SchemaVersion = "4.18.0" 374 KubernetesSchema SchemaVersion = "3.7.2" 375 RandomSchema SchemaVersion = "4.2.0" 376 EksSchema SchemaVersion = "0.37.1" 377 )