From b4583dee4fdeae5ed23050769fa61f7fba0fb465 Mon Sep 17 00:00:00 2001 From: Roger Peppe Date: Wed, 13 Sep 2023 15:33:23 +0100 Subject: [PATCH] internal/cueexperiment: parse CUE_EXPERIMENT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows code the cue command to enable experimentation with new features, and modules in particular to start with. In the future, this API might be adapted to allow enabling experimental features in the Go API too, but for now it's firmly oriented towards the top level cue command. Signed-off-by: Roger Peppe Change-Id: I76d5c9343bd791ae0ff9ef39fdf7af9dec5c2d8f Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1168930 Reviewed-by: Daniel Martí Reviewed-by: Paul Jolly TryBot-Result: CUEcueckoo --- cmd/cue/cmd/root.go | 5 ++ .../testdata/script/experiment_unknown.txtar | 3 + internal/cueexperiment/exp.go | 40 +++++++++++++ internal/cueexperiment/exp_test.go | 60 +++++++++++++++++++ 4 files changed, 108 insertions(+) create mode 100644 cmd/cue/cmd/testdata/script/experiment_unknown.txtar create mode 100644 internal/cueexperiment/exp.go create mode 100644 internal/cueexperiment/exp_test.go diff --git a/cmd/cue/cmd/root.go b/cmd/cue/cmd/root.go index 81374ab9d49..4d62084a3f6 100644 --- a/cmd/cue/cmd/root.go +++ b/cmd/cue/cmd/root.go @@ -29,6 +29,7 @@ import ( "cuelang.org/go/cue/interpreter/wasm" "cuelang.org/go/cue/stats" "cuelang.org/go/internal/core/adt" + "cuelang.org/go/internal/cueexperiment" "cuelang.org/go/internal/encoding" "cuelang.org/go/internal/filetypes" ) @@ -84,6 +85,10 @@ func mkRunE(c *Command, f runFunction) func(*cobra.Command, []string) error { statsEnc := statsEncoder(c) + if err := cueexperiment.Init(); err != nil { + return err + } + err := f(c, args) if statsEnc != nil { diff --git a/cmd/cue/cmd/testdata/script/experiment_unknown.txtar b/cmd/cue/cmd/testdata/script/experiment_unknown.txtar new file mode 100644 index 00000000000..2015f0917ac --- /dev/null +++ b/cmd/cue/cmd/testdata/script/experiment_unknown.txtar @@ -0,0 +1,3 @@ +env CUE_EXPERIMENT=xxx +! exec cue eval something +stderr 'unknown CUE_EXPERIMENT xxx' diff --git a/internal/cueexperiment/exp.go b/internal/cueexperiment/exp.go new file mode 100644 index 00000000000..0c1d1cf74b6 --- /dev/null +++ b/internal/cueexperiment/exp.go @@ -0,0 +1,40 @@ +package cueexperiment + +import ( + "fmt" + "os" + "reflect" + "strings" +) + +// Flags holds the set of CUE_EXPERIMENT flags. It is initialized +// by Init. +var Flags struct { + Modules bool +} + +// Init initializes Flags. Note: this isn't named "init" because we +// don't always want it to be called (for example we don't want it to be +// called when running "cue help"), and also because we want the failure +// mode to be one of error not panic, which would be the only option if +// it was a top level init function +func Init() error { + exp := os.Getenv("CUE_EXPERIMENT") + if exp == "" { + return nil + } + names := make(map[string]int) + fv := reflect.ValueOf(&Flags).Elem() + ft := fv.Type() + for i := 0; i < ft.NumField(); i++ { + names[strings.ToLower(ft.Field(i).Name)] = i + } + for _, uexp := range strings.Split(exp, ",") { + index, ok := names[uexp] + if !ok { + return fmt.Errorf("unknown CUE_EXPERIMENT %s", uexp) + } + fv.Field(index).SetBool(true) + } + return nil +} diff --git a/internal/cueexperiment/exp_test.go b/internal/cueexperiment/exp_test.go new file mode 100644 index 00000000000..5b39fc0100e --- /dev/null +++ b/internal/cueexperiment/exp_test.go @@ -0,0 +1,60 @@ +package cueexperiment + +import ( + "testing" + + "github.com/go-quicktest/qt" +) + +var tests = []struct { + testName string + cueExperiment string + flagVal *bool + want bool + wantError string +}{{ + testName: "Empty", + cueExperiment: "", + flagVal: &Flags.Modules, + want: false, +}, { + testName: "Unknown", + cueExperiment: "foo", + flagVal: &Flags.Modules, + wantError: "unknown CUE_EXPERIMENT foo", +}, { + testName: "Set", + cueExperiment: "modules", + flagVal: &Flags.Modules, + want: true, +}, { + testName: "SetTwice", + cueExperiment: "modules,modules", + flagVal: &Flags.Modules, + want: true, +}, { + testName: "SetWithUnknown", + cueExperiment: "modules,other", + flagVal: &Flags.Modules, + wantError: "unknown CUE_EXPERIMENT other", +}} + +func TestInit(t *testing.T) { + for _, test := range tests { + t.Run(test.testName, func(t *testing.T) { + setZero(&Flags) + t.Setenv("CUE_EXPERIMENT", test.cueExperiment) + err := Init() + if test.wantError != "" { + qt.Assert(t, qt.ErrorMatches(err, test.wantError)) + return + } + qt.Assert(t, qt.IsNil(err)) + qt.Assert(t, qt.Equals(*test.flagVal, test.want)) + }) + } +} + +func setZero[T any](x *T) { + *x = *new(T) +}