func TestReload_sighup(t *testing.T) { template := test.CreateTempfile([]byte("initial value"), t) defer test.DeleteTempfile(template, t) out := test.CreateTempfile(nil, t) defer test.DeleteTempfile(out, t) outStream := gatedio.NewByteBuffer() cli := NewCLI(outStream, outStream) command := fmt.Sprintf("consul-template -template %s:%s", template.Name(), out.Name()) args := strings.Split(command, " ") go func(args []string) { if exit := cli.Run(args); exit != 0 { t.Fatalf("bad exit code: %d", exit) } }(args) defer cli.stop() // Ensure we have run at least once test.WaitForFileContents(out.Name(), []byte("initial value"), t) newValue := []byte("new value") ioutil.WriteFile(template.Name(), newValue, 0644) syscall.Kill(syscall.Getpid(), syscall.SIGHUP) test.WaitForFileContents(out.Name(), []byte("new value"), t) }
func TestRunner_onceAlreadyRenderedDoesNotHangOrRunCommands(t *testing.T) { outFile := test.CreateTempfile(nil, t) os.Remove(outFile.Name()) defer os.Remove(outFile.Name()) out := test.CreateTempfile([]byte("redis"), t) defer os.Remove(out.Name()) in := test.CreateTempfile([]byte(`{{ key "service_name"}}`), t) defer test.DeleteTempfile(in, t) outTemplateA := test.CreateTempfile(nil, t) defer test.DeleteTempfile(outTemplateA, t) config := DefaultConfig() config.Merge(&Config{ ConfigTemplates: []*ConfigTemplate{ &ConfigTemplate{ Source: in.Name(), Destination: out.Name(), Command: fmt.Sprintf("echo 'foo' >> %s", outFile.Name()), }, }, }) runner, err := NewRunner(config, false, true, &sync.RWMutex{}) if err != nil { t.Fatal(err) } d, err := dep.ParseStoreKey("service_name") if err != nil { t.Fatal(err) } data := "redis" runner.dependencies[d.HashCode()] = d runner.watcher.ForceWatching(d, true) runner.Receive(d, data) go runner.Start() select { case err := <-runner.ErrCh: t.Fatal(err) case <-runner.DoneCh: case <-time.After(5 * time.Millisecond): t.Fatal("runner should have stopped") runner.Stop() } _, err = os.Stat(outFile.Name()) if err == nil { t.Fatal("expected command to not be run") } if !os.IsNotExist(err) { t.Fatal(err) } }
func TestRun_executesCommand(t *testing.T) { outFile := test.CreateTempfile(nil, t) os.Remove(outFile.Name()) defer os.Remove(outFile.Name()) inTemplate := test.CreateTempfile([]byte(` {{ range service "consul@nyc1"}}{{ end }} `), t) defer test.DeleteTempfile(inTemplate, t) outTemplate := test.CreateTempfile(nil, t) defer test.DeleteTempfile(outTemplate, t) config := DefaultConfig() config.Merge(&Config{ ConfigTemplates: []*ConfigTemplate{ &ConfigTemplate{ Source: inTemplate.Name(), Destination: outTemplate.Name(), Command: fmt.Sprintf("echo 'foo' > %s", outFile.Name()), }, }, }) runner, err := NewRunner(config, false, false, &sync.RWMutex{}) if err != nil { t.Fatal(err) } d, err := dep.ParseHealthServices("consul@nyc1") if err != nil { t.Fatal(err) } data := []*dep.HealthService{ &dep.HealthService{ Node: "consul", Address: "1.2.3.4", ID: "consul@nyc1", Name: "consul", }, } runner.dependencies[d.HashCode()] = d runner.watcher.ForceWatching(d, true) runner.Receive(d, data) if err := runner.Run(); err != nil { t.Fatal(err) } _, err = os.Stat(outFile.Name()) if err != nil { t.Fatal(err) } }
// Warning: this is a super fragile and time-dependent test. If it's failing, // check the demo Consul cluster and your own sanity before you assume your // code broke something... func TestRunner_quiescence(t *testing.T) { consul := testutil.NewTestServerConfig(t, func(c *testutil.TestServerConfig) { c.Stdout = ioutil.Discard c.Stderr = ioutil.Discard }) defer consul.Stop() in := test.CreateTempfile([]byte(` {{ range service "consul" "any" }}{{.Node}}{{ end }} `), t) defer test.DeleteTempfile(in, t) out := test.CreateTempfile(nil, t) test.DeleteTempfile(out, t) config := testConfig(fmt.Sprintf(` consul = "%s" wait = "50ms:200ms" template { source = "%s" destination = "%s" } `, consul.HTTPAddr, in.Name(), out.Name()), t) runner, err := NewRunner(config, false, false, &sync.RWMutex{}) if err != nil { t.Fatal(err) } go runner.Start() defer runner.Stop() min := time.After(10 * time.Millisecond) max := time.After(500 * time.Millisecond) for { select { case <-min: if _, err = os.Stat(out.Name()); !os.IsNotExist(err) { t.Errorf("expected quiescence timer to not fire for yet") } continue case <-max: if _, err = os.Stat(out.Name()); os.IsNotExist(err) { t.Errorf("expected template to be rendered by now") } return } } }
func TestRender_sameContentsDoesNotExecuteCommand(t *testing.T) { outFile := test.CreateTempfile(nil, t) os.Remove(outFile.Name()) defer os.Remove(outFile.Name()) inTemplate := test.CreateTempfile([]byte(` {{ range service "consul@nyc1" }}{{.Node}}{{ end }} `), t) defer test.DeleteTempfile(inTemplate, t) outTemplate := test.CreateTempfile([]byte(` consul1consul2 `), t) defer test.DeleteTempfile(outTemplate, t) config := DefaultConfig() config.Merge(&Config{ ConfigTemplates: []*ConfigTemplate{ &ConfigTemplate{ Source: inTemplate.Name(), Destination: outTemplate.Name(), Command: fmt.Sprintf("echo 'foo' > %s", outFile.Name()), }, }, }) runner, err := NewRunner(config, false, false, &sync.RWMutex{}) if err != nil { t.Fatal(err) } d, err := dep.ParseHealthServices("consul@nyc1") if err != nil { t.Fatal(err) } data := []*dep.HealthService{ &dep.HealthService{Node: "consul1"}, &dep.HealthService{Node: "consul2"}, } runner.Receive(d, data) if err := runner.Run(); err != nil { t.Fatal(err) } _, err = os.Stat(outFile.Name()) if !os.IsNotExist(err) { t.Fatalf("expected command to not be run") } }
func TestRun_singlePass(t *testing.T) { in := test.CreateTempfile([]byte(` {{ range service "consul@nyc1"}}{{ end }} {{ range service "consul@nyc2"}}{{ end }} {{ range service "consul@nyc3"}}{{ end }} `), t) defer test.DeleteTempfile(in, t) config := DefaultConfig() config.Merge(&Config{ ConfigTemplates: []*ConfigTemplate{ &ConfigTemplate{Source: in.Name()}, }, }) runner, err := NewRunner(config, true, false, &sync.RWMutex{}) if err != nil { t.Fatal(err) } if len(runner.dependencies) != 0 { t.Errorf("expected %d to be %d", len(runner.dependencies), 0) } if err := runner.Run(); err != nil { t.Fatal(err) } if len(runner.dependencies) != 3 { t.Errorf("expected %d to be %d", len(runner.dependencies), 3) } }
func TestExecte_badFuncs(t *testing.T) { in := test.CreateTempfile([]byte(`{{ tickle_me_pink }}`), t) defer test.DeleteTempfile(in, t) tmpl, err := NewTemplate(in.Name()) if err != nil { t.Fatal(err) } brain := NewBrain() used, missing, result, err := tmpl.Execute(brain) if err == nil { t.Fatal("expected error, but nothing was returned") } expected := `function "tickle_me_pink" not defined` if !strings.Contains(err.Error(), expected) { t.Errorf("expected %q to contain %q", err.Error(), expected) } if used != nil { t.Errorf("expected used to be nil") } if missing != nil { t.Errorf("expected missing to be nil") } if result != nil { t.Errorf("expected result to be nil") } }
func TestRunner_pidCreate(t *testing.T) { pidfile := test.CreateTempfile(nil, t) os.Remove(pidfile.Name()) defer os.Remove(pidfile.Name()) config := testConfig(fmt.Sprintf(` pid_file = "%s" `, pidfile.Name()), t) runner, err := NewRunner(config, false, false, &sync.RWMutex{}) if err != nil { t.Fatal(err) } go runner.Start() defer runner.Stop() select { case err := <-runner.ErrCh: t.Fatal(err) case <-time.After(100 * time.Millisecond): } _, err = os.Stat(pidfile.Name()) if err != nil { t.Fatal("expected pidfile to exist") } }
func TestExecute_timeout(t *testing.T) { tmpfile := test.CreateTempfile(nil, t) defer test.DeleteTempfile(tmpfile, t) config := testConfig(` consul = "1.2.3.4:5678" token = "abcd1234" auth { username = "******" password = "******" } ssl { enabled = true verify = false } `, t) runner, err := NewRunner(config, false, false, &sync.RWMutex{}) if err != nil { t.Fatal(err) } err = runner.execute("sleep 10", 100*time.Millisecond) if err == nil { t.Fatal("expected error, but nothing was returned") } expected := "did not return for 100ms" if !strings.Contains(err.Error(), expected) { t.Errorf("expected %q to include %q", err.Error(), expected) } }
func TestDedup_IsLeader(t *testing.T) { t.Parallel() // Create a template in := test.CreateTempfile([]byte(` {{ range service "consul" }}{{.Node}}{{ end }} `), t) defer test.DeleteTempfile(in, t) tmpl, err := NewTemplate(in.Name()) if err != nil { t.Fatalf("err: %v", err) } consul, dedup := testDedupManager(t, []*Template{tmpl}) defer consul.Stop() // Start dedup if err := dedup.Start(); err != nil { t.Fatalf("err: %v", err) } defer dedup.Stop() // Wait until we are leader select { case <-dedup.UpdateCh(): case <-time.After(2 * time.Second): t.Fatalf("timeout") } // Check that we are the leader if !dedup.IsLeader(tmpl) { t.Fatalf("should be leader") } }
func TestExecute_funcs(t *testing.T) { in := test.CreateTempfile([]byte(` {{ range service "release.webapp" }}{{.Address}}{{ end }} {{ key "service/redis/maxconns" }} {{ range ls "service/redis/config" }}{{.Key}}{{ end }} `), t) defer test.DeleteTempfile(in, t) tmpl, err := NewTemplate(in.Name()) if err != nil { t.Fatal(err) } brain := NewBrain() used, missing, _, err := tmpl.Execute(brain) if err != nil { t.Fatal(err) } if num := len(missing); num != 3 { t.Fatalf("expected 3 missing, got: %d", num) } if num := len(used); num != 3 { t.Fatalf("expected 3 used, got: %d", num) } }
func TestExecute_duplicateFuncs(t *testing.T) { in := test.CreateTempfile([]byte(` {{ key "service/redis/maxconns" }} {{ key "service/redis/maxconns" }} {{ key "service/redis/maxconns" }} `), t) defer test.DeleteTempfile(in, t) tmpl, err := NewTemplate(in.Name()) if err != nil { t.Fatal(err) } brain := NewBrain() used, missing, _, err := tmpl.Execute(brain) if err != nil { t.Fatal(err) } if num := len(missing); num != 1 { t.Fatalf("expected 1 missing, got: %d", num) } if num := len(used); num != 1 { t.Fatalf("expected 1 used, got: %d", num) } }
func TestFileFetch_waits(t *testing.T) { data := `{"foo":"bar"}` inTemplate := test.CreateTempfile([]byte(data), t) defer test.DeleteTempfile(inTemplate, t) dep := &File{ rawKey: inTemplate.Name(), } _, _, err := dep.Fetch(nil, nil) if err != nil { t.Fatal(err) } doneCh := make(chan struct{}) errCh := make(chan error) go func() { if _, _, err := dep.Fetch(nil, nil); err != nil { errCh <- err return } close(doneCh) }() select { case err := <-errCh: t.Fatal(err) case <-doneCh: t.Fatal("received data, but should not have") case <-time.After(1000 * time.Nanosecond): return } }
func TestExecute_noDependencies(t *testing.T) { contents := []byte("This is a template with just text") in := test.CreateTempfile(contents, t) defer test.DeleteTempfile(in, t) tmpl, err := NewTemplate(in.Name()) if err != nil { t.Fatal(err) } brain := NewBrain() used, missing, result, err := tmpl.Execute(brain) if err != nil { t.Fatal(err) } if num := len(used); num != 0 { t.Errorf("expected 0 missing, got: %d", num) } if num := len(missing); num != 0 { t.Errorf("expected 0 missing, got: %d", num) } if !bytes.Equal(result, contents) { t.Errorf("expected %q to be %q", result, contents) } }
func TestRun_noopIfMissingData(t *testing.T) { in := test.CreateTempfile([]byte(` {{ range service "consul@nyc1" }}{{ end }} `), t) defer test.DeleteTempfile(in, t) config := DefaultConfig() config.Merge(&Config{ ConfigTemplates: []*ConfigTemplate{ &ConfigTemplate{Source: in.Name()}, }, }) runner, err := NewRunner(config, false, false, &sync.RWMutex{}) if err != nil { t.Fatal(err) } buff := gatedio.NewByteBuffer() runner.outStream, runner.errStream = buff, buff if err := runner.Run(); err != nil { t.Fatal(err) } if num := len(buff.Bytes()); num != 0 { t.Errorf("expected %d to be %d", num, 0) } }
func TestRun_doublePass(t *testing.T) { in := test.CreateTempfile([]byte(` {{ range ls "services" }} {{ range service .Key }} {{.Node}} {{.Address}}:{{.Port}} {{ end }} {{ end }} `), t) defer test.DeleteTempfile(in, t) config := DefaultConfig() config.Merge(&Config{ ConfigTemplates: []*ConfigTemplate{ &ConfigTemplate{Source: in.Name()}, }, }) runner, err := NewRunner(config, true, false, &sync.RWMutex{}) if err != nil { t.Fatal(err) } if len(runner.dependencies) != 0 { t.Errorf("expected %d to be %d", len(runner.dependencies), 0) } if err := runner.Run(); err != nil { t.Fatal(err) } if len(runner.dependencies) != 1 { t.Errorf("expected %d to be %d", len(runner.dependencies), 1) } d, err := dep.ParseStoreKeyPrefix("services") if err != nil { t.Fatal(err) } data := []*dep.KeyPair{ &dep.KeyPair{Key: "service1"}, &dep.KeyPair{Key: "service2"}, &dep.KeyPair{Key: "service3"}, } runner.Receive(d, data) if err := runner.Run(); err != nil { t.Fatal(err) } if len(runner.dependencies) != 4 { t.Errorf("expected %d to be %d", len(runner.dependencies), 4) } }
func TestRun_onceFlag(t *testing.T) { consul := testutil.NewTestServerConfig(t, func(c *testutil.TestServerConfig) { c.Stdout = ioutil.Discard c.Stderr = ioutil.Discard }) defer consul.Stop() consul.SetKV("foo", []byte("bar")) template := test.CreateTempfile([]byte(` {{key "foo"}} `), t) defer test.DeleteTempfile(template, t) out := test.CreateTempfile(nil, t) defer test.DeleteTempfile(out, t) outStream := gatedio.NewByteBuffer() cli := NewCLI(outStream, outStream) command := fmt.Sprintf("consul-template -consul %s -template %s:%s -once -log-level debug", consul.HTTPAddr, template.Name(), out.Name()) args := strings.Split(command, " ") ch := make(chan int, 1) go func() { ch <- cli.Run(args) }() select { case status := <-ch: if status != ExitCodeOK { t.Errorf("expected %d to eq %d", status, ExitCodeOK) t.Errorf("out: %s", outStream.String()) } case <-time.After(500 * time.Millisecond): t.Errorf("expected exit, did not exit after 500ms") t.Errorf("out: %s", outStream.String()) } }
func TestRun_doesNotExecuteCommandMissingDependencies(t *testing.T) { outFile := test.CreateTempfile(nil, t) os.Remove(outFile.Name()) defer os.Remove(outFile.Name()) inTemplate := test.CreateTempfile([]byte(` {{ range service "consul@nyc1"}}{{ end }} `), t) defer test.DeleteTempfile(inTemplate, t) outTemplate := test.CreateTempfile(nil, t) defer test.DeleteTempfile(outTemplate, t) config := DefaultConfig() config.Merge(&Config{ ConfigTemplates: []*ConfigTemplate{ &ConfigTemplate{ Source: inTemplate.Name(), Destination: outTemplate.Name(), Command: fmt.Sprintf("echo 'foo' > %s", outFile.Name()), }, }, }) runner, err := NewRunner(config, false, false, &sync.RWMutex{}) if err != nil { t.Fatal(err) } if err := runner.Run(); err != nil { t.Fatal(err) } _, err = os.Stat(outFile.Name()) if !os.IsNotExist(err) { t.Fatalf("expected command to not be run") } }
func TestRun_dry(t *testing.T) { in := test.CreateTempfile([]byte(` {{ range service "consul@nyc1" }}{{.Node}}{{ end }} `), t) defer test.DeleteTempfile(in, t) config := DefaultConfig() config.Merge(&Config{ ConfigTemplates: []*ConfigTemplate{ &ConfigTemplate{ Source: in.Name(), Destination: "/out/file.txt", }, }, }) runner, err := NewRunner(config, true, false, &sync.RWMutex{}) if err != nil { t.Fatal(err) } d, err := dep.ParseHealthServices("consul@nyc1") if err != nil { t.Fatal(err) } data := []*dep.HealthService{ &dep.HealthService{Node: "consul1"}, &dep.HealthService{Node: "consul2"}, } runner.dependencies[d.HashCode()] = d runner.watcher.ForceWatching(d, true) runner.Receive(d, data) buff := gatedio.NewByteBuffer() runner.outStream, runner.errStream = buff, buff if err := runner.Run(); err != nil { t.Fatal(err) } actual := bytes.TrimSpace(buff.Bytes()) expected := bytes.TrimSpace([]byte(` > /out/file.txt consul1consul2 `)) if !bytes.Equal(actual, expected) { t.Errorf("expected \n%q\n to equal \n%q\n", actual, expected) } }
func TestConfigFromPath_singleFile(t *testing.T) { configFile := test.CreateTempfile([]byte(` consul = "127.0.0.1" `), t) defer test.DeleteTempfile(configFile, t) config, err := ConfigFromPath(configFile.Name()) if err != nil { t.Fatal(err) } expected := "127.0.0.1" if config.Consul != expected { t.Errorf("expected %q to be %q", config.Consul, expected) } }
func TestParseConfig_parseWaitError(t *testing.T) { configFile := test.CreateTempfile([]byte(` wait = "not_valid:duration" `), t) defer test.DeleteTempfile(configFile, t) _, err := ParseConfig(configFile.Name()) if err == nil { t.Fatal("expected error, but nothing was returned") } expectedErr := "time: invalid duration" if !strings.Contains(err.Error(), expectedErr) { t.Fatalf("expected error %q to contain %q", err.Error(), expectedErr) } }
func TestParseConfig_mapstructureError(t *testing.T) { configFile := test.CreateTempfile([]byte(` consul = true `), t) defer test.DeleteTempfile(configFile, t) _, err := ParseConfig(configFile.Name()) if err == nil { t.Fatal("expected error, but nothing was returned") } expectedErr := "unconvertible type 'bool'" if !strings.Contains(err.Error(), expectedErr) { t.Fatalf("expected error %q to contain %q", err.Error(), expectedErr) } }
func TestExecute_missingDependencies(t *testing.T) { contents := []byte(`{{key "foo"}}`) in := test.CreateTempfile(contents, t) defer test.DeleteTempfile(in, t) tmpl, err := NewTemplate(in.Name()) if err != nil { t.Fatal(err) } brain := NewBrain() used, missing, result, err := tmpl.Execute(brain) if err != nil { t.Fatal(err) } if num := len(used); num != 1 { t.Fatalf("expected 1 used, got: %d", num) } if num := len(missing); num != 1 { t.Fatalf("expected 1 missing, got: %d", num) } expected, err := dep.ParseStoreKey("foo") if err != nil { t.Fatal(err) } if !reflect.DeepEqual(missing[0], expected) { t.Errorf("expected %v to be %v", missing[0], expected) } if num := len(used); num != 1 { t.Fatalf("expected 1 used, got %d", num) } if !reflect.DeepEqual(used[0], expected) { t.Errorf("expected %v to be %v", used[0], expected) } expectedResult := []byte("") if !bytes.Equal(result, expectedResult) { t.Errorf("expected %q to be %q", result, expectedResult) } }
func TestParseConfig_extraKeys(t *testing.T) { configFile := test.CreateTempfile([]byte(` fake_key = "nope" another_fake_key = "never" `), t) defer test.DeleteTempfile(configFile, t) _, err := ParseConfig(configFile.Name()) if err == nil { t.Fatal("expected error") } expected := "invalid keys: another_fake_key, fake_key" if !strings.Contains(err.Error(), expected) { t.Errorf("expected %q to be %q", err.Error(), expected) } }
func TestFileFetch(t *testing.T) { data := `{"foo":"bar"}` inTemplate := test.CreateTempfile([]byte(data), t) defer test.DeleteTempfile(inTemplate, t) dep := &File{ rawKey: inTemplate.Name(), } read, _, err := dep.Fetch(nil, nil) if err != nil { t.Fatal(err) } if read != data { t.Fatalf("expected %q to be %q", read, data) } }
func TestDedup_UpdateDeps(t *testing.T) { t.Parallel() // Create a template in := test.CreateTempfile([]byte(` {{ range service "consul" }}{{.Node}}{{ end }} `), t) defer test.DeleteTempfile(in, t) tmpl, err := NewTemplate(in.Name()) if err != nil { t.Fatalf("err: %v", err) } consul, dedup := testDedupManager(t, []*Template{tmpl}) defer consul.Stop() // Start dedup if err := dedup.Start(); err != nil { t.Fatalf("err: %v", err) } defer dedup.Stop() // Wait until we are leader select { case <-dedup.UpdateCh(): case <-time.After(2 * time.Second): t.Fatalf("timeout") } // Create the dependency dep, err := dependency.ParseHealthServices("consul") if err != nil { t.Fatalf("err: %v", err) } // Inject data into the brain dedup.brain.Remember(dep, 123) // Update the dependencies err = dedup.UpdateDeps(tmpl, []dependency.Dependency{dep}) if err != nil { t.Fatalf("err: %v", err) } }
func TestNewTemplate_setsPathAndContents(t *testing.T) { contents := []byte("some content") in := test.CreateTempfile(contents, t) defer test.DeleteTempfile(in, t) tmpl, err := NewTemplate(in.Name()) if err != nil { t.Fatal(err) } if tmpl.Path != in.Name() { t.Errorf("expected %q to be %q", tmpl.Path, in.Name()) } if tmpl.contents != string(contents) { t.Errorf("expected %q to be %q", tmpl.contents, string(contents)) } }
func TestNewTemplate_setsPathAndMD5(t *testing.T) { contents := []byte("some content") in := test.CreateTempfile(contents, t) defer test.DeleteTempfile(in, t) tmpl, err := NewTemplate(in.Name()) if err != nil { t.Fatal(err) } if tmpl.Path != in.Name() { t.Errorf("expected %q to be %q", tmpl.Path, in.Name()) } expect := "9893532233caff98cd083a116b013c0b" if tmpl.hexMD5 != expect { t.Errorf("expected %q to be %q", tmpl.hexMD5, expect) } }
func TestRun_removesUnusedDependencies(t *testing.T) { in := test.CreateTempfile([]byte(nil), t) defer test.DeleteTempfile(in, t) config := DefaultConfig() config.Merge(&Config{ ConfigTemplates: []*ConfigTemplate{ &ConfigTemplate{Source: in.Name()}, }, }) runner, err := NewRunner(config, true, false, &sync.RWMutex{}) if err != nil { t.Fatal(err) } d, err := dep.ParseHealthServices("consul@nyc2") if err != nil { t.Fatal(err) } runner.dependencies = map[string]dep.Dependency{"consul@nyc2": d} if err := runner.Run(); err != nil { t.Fatal(err) } if len(runner.dependencies) != 0 { t.Errorf("expected %d to be %d", len(runner.dependencies), 0) } if runner.watcher.Watching(d) { t.Errorf("expected watcher to stop watching dependency") } if _, ok := runner.brain.Recall(d); ok { t.Errorf("expected brain to forget dependency") } }
func TestFileFetch_firesChanges(t *testing.T) { data := `{"foo":"bar"}` inTemplate := test.CreateTempfile([]byte(data), t) defer test.DeleteTempfile(inTemplate, t) dep := &File{ rawKey: inTemplate.Name(), } _, _, err := dep.Fetch(nil, nil) if err != nil { t.Fatal(err) } dataCh := make(chan interface{}) errCh := make(chan error) go func() { data, _, err := dep.Fetch(nil, nil) if err != nil { errCh <- err return } dataCh <- data }() newData := `{"bar": "baz"}` ioutil.WriteFile(inTemplate.Name(), []byte(newData), 0644) select { case d := <-dataCh: if d != newData { t.Fatalf("expected %q to be %q", d, newData) } case <-time.After(5 * time.Second): t.Fatal("did not receive data from file changes") } }