terraform/internal/configs/experiments_test.go

114 lines
4.0 KiB
Go
Raw Normal View History

experiments: a mechanism for opt-in experimental language features Traditionally we've preferred to release new language features in major releases only, because we can then use the beta cycle to gather feedback on the feature and learn about any usability challenges or other situations we didn't consider during our design in time to make those changes before inclusion in a stable release. This "experiments" feature is intended to decouple the feedback cycle for new features from the major release rhythm, and thus allow us to release new features in minor releases by first releasing them as experimental for a minor release or two, adjust for any feedback gathered during that period, and then finally remove the experiment gate and enable the feature for everyone. The intended model here is that anything behind an experiment gate is subject to breaking changes even in patch releases, and so any module using these experimental features will be broken by a future Terraform upgrade. The behavior implemented here is: - Recognize a new "experiments" setting in the "terraform" block which allows module authors to explicitly opt in to experimental features. terraform { experiments = [resource_for_each] } - Generate a warning whenever loading a module that has experiments enabled, to avoid accidentally depending on experimental features and thus risking unexpected breakage on next Terraform upgrade. - We check the enabled experiments against the configuration at module load time, which means that experiments are scoped to a particular module. Enabling an experiment in one module does not automatically enable it in any other module. This experiments mechanism is itself an experiment, and so I'd like to use the resource for_each feature to trial it. Because any configuration using experiments is subject to breaking changes, we are free to adjust this experiments feature in future releases as we see fit, but once for_each is shipped without an experiment gate we'll be blocked from making significant changes to it until the next major release at least.
2019-07-10 21:37:11 +02:00
package configs
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform/internal/experiments"
experiments: a mechanism for opt-in experimental language features Traditionally we've preferred to release new language features in major releases only, because we can then use the beta cycle to gather feedback on the feature and learn about any usability challenges or other situations we didn't consider during our design in time to make those changes before inclusion in a stable release. This "experiments" feature is intended to decouple the feedback cycle for new features from the major release rhythm, and thus allow us to release new features in minor releases by first releasing them as experimental for a minor release or two, adjust for any feedback gathered during that period, and then finally remove the experiment gate and enable the feature for everyone. The intended model here is that anything behind an experiment gate is subject to breaking changes even in patch releases, and so any module using these experimental features will be broken by a future Terraform upgrade. The behavior implemented here is: - Recognize a new "experiments" setting in the "terraform" block which allows module authors to explicitly opt in to experimental features. terraform { experiments = [resource_for_each] } - Generate a warning whenever loading a module that has experiments enabled, to avoid accidentally depending on experimental features and thus risking unexpected breakage on next Terraform upgrade. - We check the enabled experiments against the configuration at module load time, which means that experiments are scoped to a particular module. Enabling an experiment in one module does not automatically enable it in any other module. This experiments mechanism is itself an experiment, and so I'd like to use the resource for_each feature to trial it. Because any configuration using experiments is subject to breaking changes, we are free to adjust this experiments feature in future releases as we see fit, but once for_each is shipped without an experiment gate we'll be blocked from making significant changes to it until the next major release at least.
2019-07-10 21:37:11 +02:00
)
func TestExperimentsConfig(t *testing.T) {
// The experiment registrations are global, so we need to do some special
// patching in order to get a predictable set for our tests.
current := experiments.Experiment("current")
concluded := experiments.Experiment("concluded")
currentExperiments := experiments.NewSet(current)
concludedExperiments := map[experiments.Experiment]string{
concluded: "Reticulate your splines.",
}
defer experiments.OverrideForTesting(t, currentExperiments, concludedExperiments)()
t.Run("current", func(t *testing.T) {
parser := NewParser(nil)
mod, diags := parser.LoadConfigDir("testdata/experiments/current")
if got, want := len(diags), 1; got != want {
t.Fatalf("wrong number of diagnostics %d; want %d", got, want)
}
got := diags[0]
want := &hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: `Experimental feature "current" is active`,
Detail: "Experimental features are subject to breaking changes in future minor or patch releases, based on feedback.\n\nIf you have feedback on the design of this feature, please open a GitHub issue to discuss it.",
Subject: &hcl.Range{
Filename: "testdata/experiments/current/current_experiment.tf",
Start: hcl.Pos{Line: 2, Column: 18, Byte: 29},
End: hcl.Pos{Line: 2, Column: 25, Byte: 36},
},
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("wrong warning\n%s", diff)
}
if got, want := len(mod.ActiveExperiments), 1; got != want {
t.Errorf("wrong number of experiments %d; want %d", got, want)
}
if !mod.ActiveExperiments.Has(current) {
t.Errorf("module does not indicate current experiment as active")
}
})
t.Run("concluded", func(t *testing.T) {
parser := NewParser(nil)
_, diags := parser.LoadConfigDir("testdata/experiments/concluded")
if got, want := len(diags), 1; got != want {
t.Fatalf("wrong number of diagnostics %d; want %d", got, want)
}
got := diags[0]
want := &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: `Experiment has concluded`,
Detail: `Experiment "concluded" is no longer available. Reticulate your splines.`,
Subject: &hcl.Range{
Filename: "testdata/experiments/concluded/concluded_experiment.tf",
Start: hcl.Pos{Line: 2, Column: 18, Byte: 29},
End: hcl.Pos{Line: 2, Column: 27, Byte: 38},
},
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("wrong error\n%s", diff)
}
})
t.Run("concluded", func(t *testing.T) {
parser := NewParser(nil)
_, diags := parser.LoadConfigDir("testdata/experiments/unknown")
if got, want := len(diags), 1; got != want {
t.Fatalf("wrong number of diagnostics %d; want %d", got, want)
}
got := diags[0]
want := &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: `Unknown experiment keyword`,
Detail: `There is no current experiment with the keyword "unknown".`,
Subject: &hcl.Range{
Filename: "testdata/experiments/unknown/unknown_experiment.tf",
Start: hcl.Pos{Line: 2, Column: 18, Byte: 29},
End: hcl.Pos{Line: 2, Column: 25, Byte: 36},
},
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("wrong error\n%s", diff)
}
})
t.Run("invalid", func(t *testing.T) {
parser := NewParser(nil)
_, diags := parser.LoadConfigDir("testdata/experiments/invalid")
if got, want := len(diags), 1; got != want {
t.Fatalf("wrong number of diagnostics %d; want %d", got, want)
}
got := diags[0]
want := &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: `Invalid expression`,
Detail: `A static list expression is required.`,
Subject: &hcl.Range{
Filename: "testdata/experiments/invalid/invalid_experiments.tf",
Start: hcl.Pos{Line: 2, Column: 17, Byte: 28},
End: hcl.Pos{Line: 2, Column: 24, Byte: 35},
},
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("wrong error\n%s", diff)
}
})
}