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  }