| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- // Copyright (c) Tailscale Inc & AUTHORS
- // SPDX-License-Identifier: BSD-3-Clause
- package source
- import (
- "cmp"
- "testing"
- "time"
- "tailscale.com/util/must"
- "tailscale.com/util/syspolicy/setting"
- )
- func TestReaderLifecycle(t *testing.T) {
- tests := []struct {
- name string
- origin *setting.Origin
- definitions []*setting.Definition
- wantReads []TestExpectedReads
- initStrings []TestSetting[string]
- initUInt64s []TestSetting[uint64]
- initWant *setting.Snapshot
- addStrings []TestSetting[string]
- addStringLists []TestSetting[[]string]
- newWant *setting.Snapshot
- }{
- {
- name: "read-all-settings-once",
- origin: setting.NewNamedOrigin("Test", setting.DeviceScope),
- definitions: []*setting.Definition{
- setting.NewDefinition("StringValue", setting.DeviceSetting, setting.StringValue),
- setting.NewDefinition("IntegerValue", setting.DeviceSetting, setting.IntegerValue),
- setting.NewDefinition("BooleanValue", setting.DeviceSetting, setting.BooleanValue),
- setting.NewDefinition("StringListValue", setting.DeviceSetting, setting.StringListValue),
- setting.NewDefinition("DurationValue", setting.DeviceSetting, setting.DurationValue),
- setting.NewDefinition("PreferenceOptionValue", setting.DeviceSetting, setting.PreferenceOptionValue),
- setting.NewDefinition("VisibilityValue", setting.DeviceSetting, setting.VisibilityValue),
- },
- wantReads: []TestExpectedReads{
- {Key: "StringValue", Type: setting.StringValue, NumTimes: 1},
- {Key: "IntegerValue", Type: setting.IntegerValue, NumTimes: 1},
- {Key: "BooleanValue", Type: setting.BooleanValue, NumTimes: 1},
- {Key: "StringListValue", Type: setting.StringListValue, NumTimes: 1},
- {Key: "DurationValue", Type: setting.StringValue, NumTimes: 1}, // duration is string from the [Store]'s perspective
- {Key: "PreferenceOptionValue", Type: setting.StringValue, NumTimes: 1}, // and so are [setting.PreferenceOption]s
- {Key: "VisibilityValue", Type: setting.StringValue, NumTimes: 1}, // and [setting.Visibility]
- },
- initWant: setting.NewSnapshot(nil, setting.NewNamedOrigin("Test", setting.DeviceScope)),
- },
- {
- name: "re-read-all-settings-when-the-policy-changes",
- origin: setting.NewNamedOrigin("Test", setting.DeviceScope),
- definitions: []*setting.Definition{
- setting.NewDefinition("StringValue", setting.DeviceSetting, setting.StringValue),
- setting.NewDefinition("IntegerValue", setting.DeviceSetting, setting.IntegerValue),
- setting.NewDefinition("BooleanValue", setting.DeviceSetting, setting.BooleanValue),
- setting.NewDefinition("StringListValue", setting.DeviceSetting, setting.StringListValue),
- setting.NewDefinition("DurationValue", setting.DeviceSetting, setting.DurationValue),
- setting.NewDefinition("PreferenceOptionValue", setting.DeviceSetting, setting.PreferenceOptionValue),
- setting.NewDefinition("VisibilityValue", setting.DeviceSetting, setting.VisibilityValue),
- },
- wantReads: []TestExpectedReads{
- {Key: "StringValue", Type: setting.StringValue, NumTimes: 1},
- {Key: "IntegerValue", Type: setting.IntegerValue, NumTimes: 1},
- {Key: "BooleanValue", Type: setting.BooleanValue, NumTimes: 1},
- {Key: "StringListValue", Type: setting.StringListValue, NumTimes: 1},
- {Key: "DurationValue", Type: setting.StringValue, NumTimes: 1}, // duration is string from the [Store]'s perspective
- {Key: "PreferenceOptionValue", Type: setting.StringValue, NumTimes: 1}, // and so are [setting.PreferenceOption]s
- {Key: "VisibilityValue", Type: setting.StringValue, NumTimes: 1}, // and [setting.Visibility]
- },
- initWant: setting.NewSnapshot(nil, setting.NewNamedOrigin("Test", setting.DeviceScope)),
- addStrings: []TestSetting[string]{TestSettingOf("StringValue", "S1")},
- addStringLists: []TestSetting[[]string]{TestSettingOf("StringListValue", []string{"S1", "S2", "S3"})},
- newWant: setting.NewSnapshot(map[setting.Key]setting.RawItem{
- "StringValue": setting.RawItemWith("S1", nil, setting.NewNamedOrigin("Test", setting.DeviceScope)),
- "StringListValue": setting.RawItemWith([]string{"S1", "S2", "S3"}, nil, setting.NewNamedOrigin("Test", setting.DeviceScope)),
- }, setting.NewNamedOrigin("Test", setting.DeviceScope)),
- },
- {
- name: "read-settings-if-in-scope/device",
- origin: setting.NewNamedOrigin("Test", setting.DeviceScope),
- definitions: []*setting.Definition{
- setting.NewDefinition("DeviceSetting", setting.DeviceSetting, setting.StringValue),
- setting.NewDefinition("ProfileSetting", setting.ProfileSetting, setting.IntegerValue),
- setting.NewDefinition("UserSetting", setting.UserSetting, setting.BooleanValue),
- },
- wantReads: []TestExpectedReads{
- {Key: "DeviceSetting", Type: setting.StringValue, NumTimes: 1},
- {Key: "ProfileSetting", Type: setting.IntegerValue, NumTimes: 1},
- {Key: "UserSetting", Type: setting.BooleanValue, NumTimes: 1},
- },
- },
- {
- name: "read-settings-if-in-scope/profile",
- origin: setting.NewNamedOrigin("Test", setting.CurrentProfileScope),
- definitions: []*setting.Definition{
- setting.NewDefinition("DeviceSetting", setting.DeviceSetting, setting.StringValue),
- setting.NewDefinition("ProfileSetting", setting.ProfileSetting, setting.IntegerValue),
- setting.NewDefinition("UserSetting", setting.UserSetting, setting.BooleanValue),
- },
- wantReads: []TestExpectedReads{
- // Device settings cannot be configured at the profile scope and should not be read.
- {Key: "ProfileSetting", Type: setting.IntegerValue, NumTimes: 1},
- {Key: "UserSetting", Type: setting.BooleanValue, NumTimes: 1},
- },
- },
- {
- name: "read-settings-if-in-scope/user",
- origin: setting.NewNamedOrigin("Test", setting.CurrentUserScope),
- definitions: []*setting.Definition{
- setting.NewDefinition("DeviceSetting", setting.DeviceSetting, setting.StringValue),
- setting.NewDefinition("ProfileSetting", setting.ProfileSetting, setting.IntegerValue),
- setting.NewDefinition("UserSetting", setting.UserSetting, setting.BooleanValue),
- },
- wantReads: []TestExpectedReads{
- // Device and profile settings cannot be configured at the profile scope and should not be read.
- {Key: "UserSetting", Type: setting.BooleanValue, NumTimes: 1},
- },
- },
- {
- name: "read-stringy-settings",
- origin: setting.NewNamedOrigin("Test", setting.DeviceScope),
- definitions: []*setting.Definition{
- setting.NewDefinition("DurationValue", setting.DeviceSetting, setting.DurationValue),
- setting.NewDefinition("PreferenceOptionValue", setting.DeviceSetting, setting.PreferenceOptionValue),
- setting.NewDefinition("VisibilityValue", setting.DeviceSetting, setting.VisibilityValue),
- },
- wantReads: []TestExpectedReads{
- {Key: "DurationValue", Type: setting.StringValue, NumTimes: 1}, // duration is string from the [Store]'s perspective
- {Key: "PreferenceOptionValue", Type: setting.StringValue, NumTimes: 1}, // and so are [setting.PreferenceOption]s
- {Key: "VisibilityValue", Type: setting.StringValue, NumTimes: 1}, // and [setting.Visibility]
- },
- initStrings: []TestSetting[string]{
- TestSettingOf("DurationValue", "2h30m"),
- TestSettingOf("PreferenceOptionValue", "always"),
- TestSettingOf("VisibilityValue", "show"),
- },
- initWant: setting.NewSnapshot(map[setting.Key]setting.RawItem{
- "DurationValue": setting.RawItemWith(must.Get(time.ParseDuration("2h30m")), nil, setting.NewNamedOrigin("Test", setting.DeviceScope)),
- "PreferenceOptionValue": setting.RawItemWith(setting.AlwaysByPolicy, nil, setting.NewNamedOrigin("Test", setting.DeviceScope)),
- "VisibilityValue": setting.RawItemWith(setting.VisibleByPolicy, nil, setting.NewNamedOrigin("Test", setting.DeviceScope)),
- }, setting.NewNamedOrigin("Test", setting.DeviceScope)),
- },
- {
- name: "read-erroneous-stringy-settings",
- origin: setting.NewNamedOrigin("Test", setting.CurrentUserScope),
- definitions: []*setting.Definition{
- setting.NewDefinition("DurationValue1", setting.UserSetting, setting.DurationValue),
- setting.NewDefinition("DurationValue2", setting.UserSetting, setting.DurationValue),
- setting.NewDefinition("PreferenceOptionValue", setting.UserSetting, setting.PreferenceOptionValue),
- setting.NewDefinition("VisibilityValue", setting.UserSetting, setting.VisibilityValue),
- },
- wantReads: []TestExpectedReads{
- {Key: "DurationValue1", Type: setting.StringValue, NumTimes: 1}, // duration is string from the [Store]'s perspective
- {Key: "DurationValue2", Type: setting.StringValue, NumTimes: 1}, // duration is string from the [Store]'s perspective
- {Key: "PreferenceOptionValue", Type: setting.StringValue, NumTimes: 1}, // and so are [setting.PreferenceOption]s
- {Key: "VisibilityValue", Type: setting.StringValue, NumTimes: 1}, // and [setting.Visibility]
- },
- initStrings: []TestSetting[string]{
- TestSettingOf("DurationValue1", "soon"),
- TestSettingWithError[string]("DurationValue2", setting.NewErrorText("bang!")),
- TestSettingOf("PreferenceOptionValue", "sometimes"),
- },
- initUInt64s: []TestSetting[uint64]{
- TestSettingOf[uint64]("VisibilityValue", 42), // type mismatch
- },
- initWant: setting.NewSnapshot(map[setting.Key]setting.RawItem{
- "DurationValue1": setting.RawItemWith(nil, setting.NewErrorText("time: invalid duration \"soon\""), setting.NewNamedOrigin("Test", setting.CurrentUserScope)),
- "DurationValue2": setting.RawItemWith(nil, setting.NewErrorText("bang!"), setting.NewNamedOrigin("Test", setting.CurrentUserScope)),
- "PreferenceOptionValue": setting.RawItemWith(setting.ShowChoiceByPolicy, nil, setting.NewNamedOrigin("Test", setting.CurrentUserScope)),
- "VisibilityValue": setting.RawItemWith(setting.VisibleByPolicy, setting.NewErrorText("type mismatch in ReadString: got uint64"), setting.NewNamedOrigin("Test", setting.CurrentUserScope)),
- }, setting.NewNamedOrigin("Test", setting.CurrentUserScope)),
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- setting.SetDefinitionsForTest(t, tt.definitions...)
- store := NewTestStore(t)
- store.SetStrings(tt.initStrings...)
- store.SetUInt64s(tt.initUInt64s...)
- reader, err := newReader(store, tt.origin)
- if err != nil {
- t.Fatalf("newReader failed: %v", err)
- }
- if got := reader.GetSettings(); tt.initWant != nil && !got.Equal(tt.initWant) {
- t.Errorf("Settings do not match: got %v, want %v", got, tt.initWant)
- }
- if tt.wantReads != nil {
- store.ReadsMustEqual(tt.wantReads...)
- }
- // Should not result in new reads as there were no changes.
- N := 100
- for range N {
- reader.GetSettings()
- }
- if tt.wantReads != nil {
- store.ReadsMustEqual(tt.wantReads...)
- }
- store.ResetCounters()
- got, err := reader.ReadSettings()
- if err != nil {
- t.Fatalf("ReadSettings failed: %v", err)
- }
- if tt.initWant != nil && !got.Equal(tt.initWant) {
- t.Errorf("Settings do not match: got %v, want %v", got, tt.initWant)
- }
- if tt.wantReads != nil {
- store.ReadsMustEqual(tt.wantReads...)
- }
- store.ResetCounters()
- if len(tt.addStrings) != 0 || len(tt.addStringLists) != 0 {
- store.SetStrings(tt.addStrings...)
- store.SetStringLists(tt.addStringLists...)
- // As the settings have changed, GetSettings needs to re-read them.
- if got, want := reader.GetSettings(), cmp.Or(tt.newWant, tt.initWant); !got.Equal(want) {
- t.Errorf("New Settings do not match: got %v, want %v", got, want)
- }
- if tt.wantReads != nil {
- store.ReadsMustEqual(tt.wantReads...)
- }
- }
- select {
- case <-reader.Done():
- t.Fatalf("the reader is closed")
- default:
- }
- store.Close()
- <-reader.Done()
- })
- }
- }
- func TestReadingSession(t *testing.T) {
- setting.SetDefinitionsForTest(t, setting.NewDefinition("StringValue", setting.DeviceSetting, setting.StringValue))
- store := NewTestStore(t)
- origin := setting.NewOrigin(setting.DeviceScope)
- reader, err := newReader(store, origin)
- if err != nil {
- t.Fatalf("newReader failed: %v", err)
- }
- session, err := reader.OpenSession()
- if err != nil {
- t.Fatalf("failed to open a reading session: %v", err)
- }
- t.Cleanup(session.Close)
- if got, want := session.GetSettings(), setting.NewSnapshot(nil, origin); !got.Equal(want) {
- t.Errorf("Settings do not match: got %v, want %v", got, want)
- }
- select {
- case _, ok := <-session.PolicyChanged():
- if ok {
- t.Fatalf("the policy changed notification was sent prematurely")
- } else {
- t.Fatalf("the session was closed prematurely")
- }
- default:
- }
- store.SetStrings(TestSettingOf("StringValue", "S1"))
- _, ok := <-session.PolicyChanged()
- if !ok {
- t.Fatalf("the session was closed prematurely")
- }
- want := setting.NewSnapshot(map[setting.Key]setting.RawItem{
- "StringValue": setting.RawItemWith("S1", nil, origin),
- }, origin)
- if got := session.GetSettings(); !got.Equal(want) {
- t.Errorf("Settings do not match: got %v, want %v", got, want)
- }
- store.Close()
- if _, ok = <-session.PolicyChanged(); ok {
- t.Fatalf("the session must be closed")
- }
- }
|