diff --git a/internal/devconfig/configfile/ast.go b/internal/devconfig/configfile/ast.go index f42ab82ccb4..bd92c6fac47 100644 --- a/internal/devconfig/configfile/ast.go +++ b/internal/devconfig/configfile/ast.go @@ -2,6 +2,7 @@ package configfile import ( "bytes" + "cmp" "regexp" "slices" @@ -430,7 +431,10 @@ func (c *configAST) createMemberIfMissing(key string) *hujson.ObjectMember { i := c.memberIndex(c.root.Value.(*hujson.Object), key) if i == -1 { c.root.Value.(*hujson.Object).Members = append(c.root.Value.(*hujson.Object).Members, hujson.ObjectMember{ - Name: hujson.Value{Value: hujson.String(key)}, + Name: hujson.Value{ + Value: hujson.String(key), + BeforeExtra: []byte{'\n'}, + }, }) i = len(c.root.Value.(*hujson.Object).Members) - 1 } @@ -441,10 +445,17 @@ func mapToObjectMembers(env map[string]string) []hujson.ObjectMember { members := make([]hujson.ObjectMember, 0, len(env)) for k, v := range env { members = append(members, hujson.ObjectMember{ - Name: hujson.Value{Value: hujson.String(k)}, + Name: hujson.Value{ + Value: hujson.String(k), + BeforeExtra: []byte{'\n'}, + }, Value: hujson.Value{Value: hujson.String(v)}, }) } + // Make the order deterministic so we don't keep moving fields around. + slices.SortFunc(members, func(a, b hujson.ObjectMember) int { + return cmp.Compare(a.Name.Value.(hujson.Literal).String(), b.Name.Value.(hujson.Literal).String()) + }) return members } diff --git a/internal/devconfig/configfile/ast_test.go b/internal/devconfig/configfile/ast_test.go deleted file mode 100644 index ac63b9094d4..00000000000 --- a/internal/devconfig/configfile/ast_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package configfile - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/tailscale/hujson" -) - -func TestSetEnv(t *testing.T) { - tests := []struct { - name string - initial string - env map[string]string - expected string - }{ - { - name: "add env to empty config", - initial: "{}", - env: map[string]string{ - "FOO": "bar", - "BAZ": "qux", - }, - expected: `{"env": {"FOO": "bar", "BAZ": "qux"}} -`, - }, - { - name: "update existing env", - initial: `{ - "env": { - "EXISTING": "value" - } -}`, - env: map[string]string{ - "FOO": "bar", - }, - expected: `{ - "env": {"FOO": "bar"} -} -`, - }, - { - name: "clear env with empty map", - initial: `{ - "env": { - "EXISTING": "value" - } -}`, - env: map[string]string{}, - expected: `{ - "env": {} -} -`, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - val, err := hujson.Parse([]byte(tt.initial)) - assert.NoError(t, err) - - ast := &configAST{root: val} - ast.setEnv(tt.env) - - actual := string(ast.root.Pack()) - assert.Equal(t, tt.expected, actual) - }) - } -} diff --git a/internal/devconfig/configfile/file_test.go b/internal/devconfig/configfile/file_test.go index 661cc799bbc..c58f8a08086 100644 --- a/internal/devconfig/configfile/file_test.go +++ b/internal/devconfig/configfile/file_test.go @@ -684,6 +684,78 @@ func TestSetAllowInsecure(t *testing.T) { } } +func TestSetEnv(t *testing.T) { + in, want := parseConfigTxtarTest(t, ` +-- in -- +{} +-- want -- +{ + "env": { + "BAZ": "qux", + "FOO": "bar" + } +}`) + + in.SetEnv(map[string]string{ + "FOO": "bar", + "BAZ": "qux", + }) + if diff := cmp.Diff(want, in.Bytes(), optParseHujson()); diff != "" { + t.Errorf("wrong parsed config json (-want +got):\n%s", diff) + } + if diff := cmp.Diff(want, in.Bytes()); diff != "" { + t.Errorf("wrong raw config hujson (-want +got):\n%s", diff) + } +} + +func TestSetEnvExisting(t *testing.T) { + in, want := parseConfigTxtarTest(t, ` +-- in -- +{ + "env": { + "EXISTING": "value" + } +} +-- want -- +{ + "env": { + "FOO": "bar" + } +}`) + + in.SetEnv(map[string]string{ + "FOO": "bar", + }) + if diff := cmp.Diff(want, in.Bytes(), optParseHujson()); diff != "" { + t.Errorf("wrong parsed config json (-want +got):\n%s", diff) + } + if diff := cmp.Diff(want, in.Bytes()); diff != "" { + t.Errorf("wrong raw config hujson (-want +got):\n%s", diff) + } +} + +func TestSetEnvClear(t *testing.T) { + in, want := parseConfigTxtarTest(t, ` +-- in -- +{ + "env": { + "EXISTING": "value" + } +} +-- want -- +{ + "env": {} +}`) + + in.SetEnv(map[string]string{}) + if diff := cmp.Diff(want, in.Bytes(), optParseHujson()); diff != "" { + t.Errorf("wrong parsed config json (-want +got):\n%s", diff) + } + if diff := cmp.Diff(want, in.Bytes()); diff != "" { + t.Errorf("wrong raw config hujson (-want +got):\n%s", diff) + } +} + func TestNixpkgsValidation(t *testing.T) { testCases := map[string]struct { commit string