Provide a way to override packages during a test run

Add a new property to ProgramTestOptions, `Overrides` that allows a
test to request a different version of a package is used instead of
what would be listed in the package.json file.

This will be used by our nightly automation to run everything "at head"
This commit is contained in:
Matt Ellis 2018-11-14 13:27:32 -08:00
parent 9a044ff865
commit 872c7661e3
3 changed files with 110 additions and 22 deletions

View file

@ -21,26 +21,23 @@ func TestExamples(t *testing.T) {
return
}
var minimal integration.ProgramTestOptions
minimal = integration.ProgramTestOptions{
Dir: path.Join(cwd, "minimal"),
Dependencies: []string{"@pulumi/pulumi"},
Config: map[string]string{
"name": "Pulumi",
},
Secrets: map[string]string{
"secret": "this is my secret message",
},
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
// Simple runtime validation that just ensures the checkpoint was written and read.
assert.NotNil(t, stackInfo.Deployment)
},
RunBuild: true,
}
var formattableStdout, formattableStderr bytes.Buffer
examples := []integration.ProgramTestOptions{
minimal,
{
Dir: path.Join(cwd, "minimal"),
Dependencies: []string{"@pulumi/pulumi"},
Config: map[string]string{
"name": "Pulumi",
},
Secrets: map[string]string{
"secret": "this is my secret message",
},
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
// Simple runtime validation that just ensures the checkpoint was written and read.
assert.NotNil(t, stackInfo.Deployment)
},
RunBuild: true,
},
{
Dir: path.Join(cwd, "dynamic-provider/simple"),
Dependencies: []string{"@pulumi/pulumi"},
@ -82,8 +79,7 @@ func TestExamples(t *testing.T) {
Dependencies: []string{"@pulumi/pulumi"},
},
{
Dir: path.Join(cwd, "compat/v0.10.0/minimal"),
Dependencies: []string{"@pulumi/pulumi"},
Dir: path.Join(cwd, "compat/v0.10.0/minimal"),
Config: map[string]string{
"name": "Pulumi",
},

View file

@ -115,9 +115,12 @@ type ProgramTestOptions struct {
Dir string
// Array of NPM packages which must be `yarn linked` (e.g. {"pulumi", "@pulumi/aws"})
Dependencies []string
// Map of config keys and values to set (e.g. {"aws:config:region": "us-east-2"})
// Map of package names to versions. The test will use the specified versions of these packages instead of what
// is declared in `package.json`.
Overrides map[string]string
// Map of config keys and values to set (e.g. {"aws:region": "us-east-2"})
Config map[string]string
// Map of secure config keys and values to set on the stack (e.g. {"aws:config:region": "us-east-2"})
// Map of secure config keys and values to set on the stack (e.g. {"aws:region": "us-east-2"})
Secrets map[string]string
// EditDirs is an optional list of edits to apply to the example, as subsequent deployments.
EditDirs []EditDir
@ -270,6 +273,9 @@ func (opts ProgramTestOptions) With(overrides ProgramTestOptions) ProgramTestOpt
if overrides.Dependencies != nil {
opts.Dependencies = overrides.Dependencies
}
if overrides.Overrides != nil {
opts.Overrides = overrides.Overrides
}
for k, v := range overrides.Config {
if opts.Config == nil {
opts.Config = make(map[string]string)
@ -1130,10 +1136,46 @@ func (pt *programTester) prepareNodeJSProject(projinfo *engine.Projinfo) error {
return err
}
// If the test requested some packages to be overridden, we do two things. First, if the package is listed as a
// direct dependency of the project, we change the version constraint in the package.json. For transitive
// dependeices, we use yarn's "resolutions" feature to force them to a specific version.
if len(pt.opts.Overrides) > 0 {
packageJSON, err := readPackageJSON(cwd)
if err != nil {
return err
}
overrides := make(map[string]interface{})
for packageName, packageVersion := range pt.opts.Overrides {
for _, section := range []string{"dependencies", "devDependencies"} {
if _, has := packageJSON[section]; has {
entry := packageJSON[section].(map[string]interface{})
if _, has := entry[packageName]; has {
entry[packageName] = packageVersion
}
}
}
fprintf(pt.opts.Stdout, "adding resolution for %s to version %s\n", packageName, packageVersion)
overrides["**/"+packageName] = packageVersion
}
// Wack any existing overrides section with our newly computed one.
packageJSON["overrides"] = overrides
if err := writePackageJSON(cwd, packageJSON); err != nil {
return err
}
}
// Now ensure dependencies are present.
if err = pt.runYarnCommand("yarn-install", []string{"install", "--verbose"}, cwd); err != nil {
return err
}
for _, dependency := range pt.opts.Dependencies {
if err = pt.runYarnCommand("yarn-link", []string{"link", dependency}, cwd); err != nil {
return err
@ -1151,6 +1193,36 @@ func (pt *programTester) prepareNodeJSProject(projinfo *engine.Projinfo) error {
}
// readPackageJSON unmarshals the package.json file located in pathToPackage.
func readPackageJSON(pathToPackage string) (map[string]interface{}, error) {
f, err := os.Open(filepath.Join(pathToPackage, "package.json"))
if err != nil {
return nil, errors.Wrap(err, "opening package.json")
}
defer contract.IgnoreClose(f)
var ret map[string]interface{}
if err := json.NewDecoder(f).Decode(&ret); err != nil {
return nil, errors.Wrap(err, "decoding package.json")
}
return ret, nil
}
func writePackageJSON(pathToPackage string, metadata map[string]interface{}) error {
// os.Create truncates the already existing file.
f, err := os.Create(filepath.Join(pathToPackage, "package.json"))
if err != nil {
return errors.Wrap(err, "opening package.json")
}
defer contract.IgnoreClose(f)
encoder := json.NewEncoder(f)
encoder.SetIndent("", " ")
return errors.Wrap(encoder.Encode(metadata), "writing package.json")
}
// preparePythonProject runs setup necessary to get a Python project ready for `pulumi` commands.
func (pt *programTester) preparePythonProject(projinfo *engine.Projinfo) error {
cwd, _, err := projinfo.GetPwdMain()

View file

@ -30,6 +30,26 @@ import (
"github.com/pulumi/pulumi/pkg/util/contract"
)
// DecodeMapString takes a string of the form key1=value1:key2=value2 and returns a go map.
func DecodeMapString(val string) (map[string]string, error) {
newMap := make(map[string]string)
if val != "" {
for _, overrideClause := range strings.Split(val, ":") {
data := strings.Split(overrideClause, "=")
if len(data) != 2 {
return nil, errors.Errorf(
"could not decode %s as an override, should be of the form <package>=<version>", overrideClause)
}
packageName := data[0]
packageVersion := data[1]
newMap[packageName] = packageVersion
}
}
return newMap, nil
}
// ReplaceInFile does a find and replace for a given string within a file.
func ReplaceInFile(old, new, path string) error {
rawContents, err := ioutil.ReadFile(path)