diff --git a/README.md b/README.md index 87bbc8b..abc3e73 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,9 @@ viper.OnConfigChange(func(e fsnotify.Event) { }) ``` +If you wish to stop watching the configPaths, simply call viper.CancelWatchConfig(). +Note: This might be necessary if your tests involve trying out various config files. + ### Reading Config from io.Reader Viper predefines many configuration sources such as files, environment diff --git a/viper.go b/viper.go index a32ab73..aa407b7 100644 --- a/viper.go +++ b/viper.go @@ -202,6 +202,7 @@ type Viper struct { properties *properties.Properties onConfigChange func(fsnotify.Event) + watchConfigCancel func() (waiter func()) } // New returns an initialized Viper instance. @@ -300,6 +301,13 @@ func (v *Viper) WatchConfig() { eventsWG := sync.WaitGroup{} eventsWG.Add(1) + v.watchConfigCancel = func() (waiter func()) { + watcher.Close() + v.watchConfigCancel = nil + return func() { + eventsWG.Wait() + } + } go func() { for { select { @@ -346,6 +354,16 @@ func (v *Viper) WatchConfig() { initWG.Wait() // make sure that the go routine above fully ended before returning } +func CancelWatchConfig() (future func()) { + return v.CancelWatchConfig() +} +func (v *Viper) CancelWatchConfig() (future func()) { + if v.watchConfigCancel != nil { + return v.watchConfigCancel() + } + return func(){} +} + // SetConfigFile explicitly defines the path, name and extension of the config file. // Viper will use this and not check any of the config paths. func SetConfigFile(in string) { v.SetConfigFile(in) } diff --git a/viper_test.go b/viper_test.go index c8fa1f4..bc22d10 100644 --- a/viper_test.go +++ b/viper_test.go @@ -1510,6 +1510,44 @@ func TestWatchFile(t *testing.T) { assert.Equal(t, "baz", v.Get("foo")) }) + t.Run("file content changed after cancel", func(t *testing.T) { + // given a `config.yaml` file being watched + v, configFile, cleanup := newViperWithConfigFile(t) + defer cleanup() + _, err := os.Stat(configFile) + require.NoError(t, err) + t.Logf("test config file: %s\n", configFile) + + // first run through with watching enabled + wg := sync.WaitGroup{} + wg.Add(1) + v.OnConfigChange(func(in fsnotify.Event) { + t.Logf("WatchConfig saw first config change") + wg.Done() + }) + v.WatchConfig() + // when overwriting the file and waiting for the custom change notification handler to be triggered + err = ioutil.WriteFile(configFile, []byte("foo: baz\n"), 0640) + wg.Wait() + // then the config value should have changed + require.Nil(t, err) + assert.Equal(t, "baz", v.Get("foo")) + + // cancel and wait for the canceling to finish. + waitForCancel := v.CancelWatchConfig() + v.OnConfigChange(func(in fsnotify.Event) { + t.Error("CancelWatchConfig did not prevent second change from being seen.") + }) + waitForCancel() + + err = ioutil.WriteFile(configFile, []byte("foo: quz\n"), 0640) + // We need to sleep a bit here because there isn't any signal of use for this invisible write. + time.Sleep(30 * time.Millisecond) + + // the config value should still be the same. + require.Nil(t, err) + assert.Equal(t, "baz", v.Get("foo"), "CancelWatchConfig did not prevent second change from being seen.") + }) } func BenchmarkGetBool(b *testing.B) {