Example #1
0
func TestStart_runsCommandOnChange(t *testing.T) {
	t.Parallel()

	consul := testutil.NewTestServerConfig(t, func(c *testutil.TestServerConfig) {
		c.Stdout = ioutil.Discard
		c.Stderr = ioutil.Discard
	})
	consul.SetKV("foo/bar", []byte("one"))
	defer consul.Stop()

	config := testConfig(fmt.Sprintf(`
		consul = "%s"
		upcase = true

		prefix {
			path = "foo"
		}
	`, consul.HTTPAddr), t)

	f := test.CreateTempfile(nil, t)
	defer os.Remove(f.Name())
	os.Remove(f.Name())

	runner, err := NewRunner(config, []string{"sh", "-c", "echo $BAR > " + f.Name()}, true)
	if err != nil {
		t.Fatal(err)
	}

	runner.outStream, runner.errStream = ioutil.Discard, ioutil.Discard

	go runner.Start()
	defer runner.Stop()

	test.WaitForFileContents(f.Name(), []byte("one\n"), t)
}
Example #2
0
func testDedupManager(t *testing.T, templ []*Template) (*testutil.TestServer, *DedupManager) {
	consul := testutil.NewTestServerConfig(t, func(c *testutil.TestServerConfig) {
		c.Stdout = ioutil.Discard
		c.Stderr = ioutil.Discard
	})

	// Setup the configuration
	config := DefaultConfig()
	config.Consul = consul.HTTPAddr

	// Create the clientset
	clients, err := newClientSet(config)
	if err != nil {
		t.Fatalf("runner: %s", err)
	}

	// Setup a brain
	brain := NewBrain()

	// Create the dedup manager
	dedup, err := NewDedupManager(config, clients, brain, templ)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	return consul, dedup
}
Example #3
0
func NewTestServer(t *testing.T) *testutil.TestServer {
	// Create a server
	srv := testutil.NewTestServerConfig(t, func(c *testutil.TestServerConfig) {
		if !testing.Verbose() {
			c.LogLevel = "err"
		}
	})
	return srv
}
Example #4
0
func TestRun_merges(t *testing.T) {
	t.Parallel()

	consul := testutil.NewTestServerConfig(t, func(c *testutil.TestServerConfig) {
		c.Stdout = ioutil.Discard
		c.Stderr = ioutil.Discard
	})
	defer consul.Stop()

	consul.SetKV("config/global/address", []byte("1.2.3.4"))
	consul.SetKV("config/global/port", []byte("5598"))
	consul.SetKV("config/redis/port", []byte("8000"))

	config := testConfig(fmt.Sprintf(`
		consul = "%s"
		upcase = true

		prefix {
			path = "config/global"
		}

		prefix {
			path = "config/redis"
		}
	`, consul.HTTPAddr), t)

	runner, err := NewRunner(config, []string{"env"}, true)
	if err != nil {
		t.Fatal(err)
	}

	outStream, errStream := new(bytes.Buffer), new(bytes.Buffer)
	runner.outStream, runner.errStream = outStream, errStream

	go runner.Start()
	defer runner.Stop()

	select {
	case err := <-runner.ErrCh:
		t.Fatal(err)
	case <-runner.ExitCh:
		expected := "ADDRESS=1.2.3.4"
		if !strings.Contains(outStream.String(), expected) {
			t.Fatalf("expected %q to include %q", outStream.String(), expected)
		}

		expected = "PORT=8000"
		if !strings.Contains(outStream.String(), expected) {
			t.Fatalf("expected %q to include %q", outStream.String(), expected)
		}
	}
}
Example #5
0
// 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)
	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
		}
	}
}
Example #6
0
func makeClientWithConfig(t *testing.T) (*consulapi.Client, *testutil.TestServer) {

	// Make client config
	conf := consulapi.DefaultConfig()

	// Create server
	server := testutil.NewTestServerConfig(t, nil)
	conf.Address = server.HTTPAddr

	// Create client
	client, err := consulapi.NewClient(conf)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	return client, server
}
Example #7
0
func TestRun_stdin(t *testing.T) {
	t.Parallel()

	consul := testutil.NewTestServerConfig(t, func(c *testutil.TestServerConfig) {
		c.Stdout = ioutil.Discard
		c.Stderr = ioutil.Discard
	})
	defer consul.Stop()

	consul.SetKV("foo/bar/bar", []byte("baz"))

	config := testConfig(fmt.Sprintf(`
		consul = "%s"
		prefix {
			path = "foo/bar"
		}
	`, consul.HTTPAddr), t)

	runner, err := NewRunner(config, []string{"cat"}, true)
	if err != nil {
		t.Fatal(err)
	}

	outStream, errStream := gatedio.NewByteBuffer(), gatedio.NewByteBuffer()
	inStream := gatedio.NewByteBuffer()
	runner.outStream, runner.errStream = outStream, errStream
	runner.inStream = inStream

	go runner.Start()
	defer runner.Stop()

	if _, err := inStream.WriteString("foo"); err != nil {
		t.Fatal(err)
	}

	select {
	case err := <-runner.ErrCh:
		t.Fatal(err)
	case <-runner.ExitCh:
		expected := "foo"
		if !strings.Contains(outStream.String(), expected) {
			t.Fatalf("expected %q to include %q", outStream.String(), expected)
		}
	}
}
Example #8
0
func TestRun_pristine(t *testing.T) {
	t.Parallel()

	consul := testutil.NewTestServerConfig(t, func(c *testutil.TestServerConfig) {
		c.Stdout = ioutil.Discard
		c.Stderr = ioutil.Discard
	})
	defer consul.Stop()

	consul.SetKV("foo/bar/bar", []byte("baz"))

	config := testConfig(fmt.Sprintf(`
		consul = "%s"
		pristine = true
		prefix {
			path = "foo/bar"
		}
	`, consul.HTTPAddr), t)

	runner, err := NewRunner(config, []string{"env"}, true)
	if err != nil {
		t.Fatal(err)
	}

	outStream, errStream := new(bytes.Buffer), new(bytes.Buffer)
	runner.outStream, runner.errStream = outStream, errStream

	go runner.Start()
	defer runner.Stop()

	select {
	case err := <-runner.ErrCh:
		t.Fatal(err)
	case <-runner.ExitCh:
		expected := "bar=baz"
		if !strings.Contains(outStream.String(), expected) {
			t.Fatalf("expected %q to include %q", outStream.String(), expected)
		}

		notExpected := "HOME="
		if strings.Contains(outStream.String(), notExpected) {
			t.Fatalf("did not expect %q to include %q", outStream.String(), notExpected)
		}
	}
}
// testConsulServer is a helper for creating a Consul server and returning the
// appropriate configuration to connect to it.
func testConsulServer(t *testing.T) (*ClientSet, *testutil.TestServer) {
	t.Parallel()

	consul := testutil.NewTestServerConfig(t, func(c *testutil.TestServerConfig) {
		c.Stdout = ioutil.Discard
		c.Stderr = ioutil.Discard
	})

	clients := NewClientSet()
	if err := clients.CreateConsulClient(&CreateConsulClientInput{
		Address: consul.HTTPAddr,
	}); err != nil {
		consul.Stop()
		t.Fatalf("clientset: %s", err)
	}

	return clients, consul
}
Example #10
0
func TestRun_overwrites(t *testing.T) {
	t.Parallel()

	consul := testutil.NewTestServerConfig(t, func(c *testutil.TestServerConfig) {
		c.Stdout = ioutil.Discard
		c.Stderr = ioutil.Discard
	})
	defer consul.Stop()

	consul.SetKV("config/global/address", []byte("1.2.3.4"))

	config := testConfig(fmt.Sprintf(`
		consul = "%s"

		prefix {
			path = "config/global"
		}
	`, consul.HTTPAddr), t)

	runner, err := NewRunner(config, []string{"env"}, true)
	if err != nil {
		t.Fatal(err)
	}

	outStream, errStream := gatedio.NewByteBuffer(), gatedio.NewByteBuffer()
	runner.outStream, runner.errStream = outStream, errStream

	// Set the env to ensure it overwrites
	os.Setenv("address", "should_be_overwritten")
	defer os.Unsetenv("address")

	go runner.Start()
	defer runner.Stop()

	select {
	case err := <-runner.ErrCh:
		t.Fatal(err)
	case <-runner.ExitCh:
		expected := "address=1.2.3.4"
		if !strings.Contains(outStream.String(), expected) {
			t.Fatalf("expected %q to include %q", outStream.String(), expected)
		}
	}
}
Example #11
0
// testConsul returns a Syncer configured with an embedded Consul server.
//
// Callers must defer Syncer.Shutdown() and TestServer.Stop()
//
func testConsul(t *testing.T) (*Syncer, *testutil.TestServer) {
	// Create an embedded Consul server
	testconsul := testutil.NewTestServerConfig(t, func(c *testutil.TestServerConfig) {
		// If -v wasn't specified squelch consul logging
		if !testing.Verbose() {
			c.Stdout = ioutil.Discard
			c.Stderr = ioutil.Discard
		}
	})

	// Configure Syncer to talk to the test server
	cconf := config.DefaultConsulConfig()
	cconf.Addr = testconsul.HTTPAddr

	cs, err := NewSyncer(cconf, nil, logger)
	if err != nil {
		t.Fatalf("Error creating Syncer: %v", err)
	}
	return cs, testconsul
}
Example #12
0
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())
	}
}
Example #13
0
func makeClientWithConfig(
	t *testing.T,
	cb1 configCallback,
	cb2 testutil.ServerConfigCallback) (*Client, *testutil.TestServer) {

	// Make client config
	conf := DefaultConfig()
	if cb1 != nil {
		cb1(conf)
	}

	// Create server
	server := testutil.NewTestServerConfig(t, cb2)
	conf.Address = server.HTTPAddr

	// Create client
	client, err := NewClient(conf)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	return client, server
}
// testConsulServer is a helper for creating a Consul server and returning the
// appropriate configuration to connect to it.
func testConsulServer(t *testing.T) (*ClientSet, *testutil.TestServer) {
	t.Parallel()

	consul := testutil.NewTestServerConfig(t, func(c *testutil.TestServerConfig) {
		c.Stdout = ioutil.Discard
		c.Stderr = ioutil.Discard
	})

	config := consulapi.DefaultConfig()
	config.Address = consul.HTTPAddr
	client, err := consulapi.NewClient(config)
	if err != nil {
		consul.Stop()
		t.Fatalf("consul api client err: %s", err)
	}

	clients := NewClientSet()
	if err := clients.Add(client); err != nil {
		consul.Stop()
		t.Fatalf("clientset err: %s", err)
	}

	return clients, consul
}
Example #15
0
func TestRun_vaultPrecedenceOverConsul(t *testing.T) {
	t.Parallel()

	consul := testutil.NewTestServerConfig(t, func(c *testutil.TestServerConfig) {
		c.Stdout = ioutil.Discard
		c.Stderr = ioutil.Discard
	})
	defer consul.Stop()

	consul.SetKV("secret/foo/secret_foo_zip", []byte("baz"))

	vaultCore, _, token := vault.TestCoreUnsealed(t)
	ln, vaultAddr := http.TestServer(t, vaultCore)
	defer ln.Close()

	req := &logical.Request{
		Operation: logical.WriteOperation,
		Path:      "secret/foo",
		Data: map[string]interface{}{
			"zip":  "zap",
			"ding": "dong",
		},
		ClientToken: token,
	}
	if _, err := vaultCore.HandleRequest(req); err != nil {
		t.Fatal(err)
	}

	vaultconfig := vaultapi.DefaultConfig()
	vaultconfig.Address = vaultAddr
	client, err := vaultapi.NewClient(vaultconfig)
	if err != nil {
		t.Fatal(err)
	}
	client.SetToken(token)

	// Create a new token - the core token is a root token and is therefore
	// not renewable
	secret, err := client.Auth().Token().Create(&vaultapi.TokenCreateRequest{
		Lease: "1h",
	})
	if err != nil {
		t.Fatal(err)
	}

	config := testConfig(fmt.Sprintf(`
		consul = "%s"

		vault {
			address = "%s"
			token   = "%s"
			ssl {
				enabled = false
			}
		}

		secret {
			path = "secret/foo"
		}

		prefix {
			path = "secret/foo"
		}
	`, consul.HTTPAddr, vaultAddr, secret.Auth.ClientToken), t)

	runner, err := NewRunner(config, []string{"env"}, true)
	if err != nil {
		t.Fatal(err)
	}

	outStream, errStream := new(bytes.Buffer), new(bytes.Buffer)
	runner.outStream, runner.errStream = outStream, errStream

	go runner.Start()
	defer runner.Stop()

	select {
	case err := <-runner.ErrCh:
		t.Fatal(err)
	case <-runner.ExitCh:
	case <-time.After(250 * time.Millisecond):
	}

	expected := "secret_foo_zip=zap"
	if !strings.Contains(outStream.String(), expected) {
		t.Errorf("expected %q to include %q", outStream.String(), expected)
	}

	expected = "secret_foo_ding=dong"
	if !strings.Contains(outStream.String(), expected) {
		t.Errorf("expected %q to include %q", outStream.String(), expected)
	}
}
Example #16
0
func TestSyncerChaos(t *testing.T) {
	// Create an embedded Consul server
	testconsul := testutil.NewTestServerConfig(t, func(c *testutil.TestServerConfig) {
		// If -v wasn't specified squelch consul logging
		if !testing.Verbose() {
			c.Stdout = ioutil.Discard
			c.Stderr = ioutil.Discard
		}
	})
	defer testconsul.Stop()

	// Configure Syncer to talk to the test server
	cconf := config.DefaultConsulConfig()
	cconf.Addr = testconsul.HTTPAddr

	clientSyncer, err := NewSyncer(cconf, nil, logger)
	if err != nil {
		t.Fatalf("Error creating Syncer: %v", err)
	}
	defer clientSyncer.Shutdown()

	execSyncer, err := NewSyncer(cconf, nil, logger)
	if err != nil {
		t.Fatalf("Error creating Syncer: %v", err)
	}
	defer execSyncer.Shutdown()

	clientService := &structs.Service{Name: "nomad-client"}
	services := map[ServiceKey]*structs.Service{
		GenerateServiceKey(clientService): clientService,
	}
	if err := clientSyncer.SetServices("client", services); err != nil {
		t.Fatalf("error setting client service: %v", err)
	}

	const execn = 100
	const reapern = 2
	errors := make(chan error, 100)
	wg := sync.WaitGroup{}

	// Start goroutines to concurrently SetServices
	for i := 0; i < execn; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			domain := ServiceDomain(fmt.Sprintf("exec-%d", i))
			services := map[ServiceKey]*structs.Service{}
			for ii := 0; ii < 10; ii++ {
				s := &structs.Service{Name: fmt.Sprintf("exec-%d-%d", i, ii)}
				services[GenerateServiceKey(s)] = s
				if err := execSyncer.SetServices(domain, services); err != nil {
					select {
					case errors <- err:
					default:
					}
					return
				}
				time.Sleep(1)
			}
		}(i)
	}

	// SyncServices runs a timer started by Syncer.Run which we don't use
	// in this test, so run SyncServices concurrently
	wg.Add(1)
	go func() {
		defer wg.Done()
		for i := 0; i < execn; i++ {
			if err := execSyncer.SyncServices(); err != nil {
				select {
				case errors <- err:
				default:
				}
				return
			}
			time.Sleep(100)
		}
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()
		if err := clientSyncer.ReapUnmatched([]ServiceDomain{"nomad-client"}); err != nil {
			select {
			case errors <- err:
			default:
			}
			return
		}
	}()

	// Reap all but exec-0-*
	wg.Add(1)
	go func() {
		defer wg.Done()
		for i := 0; i < execn; i++ {
			if err := execSyncer.ReapUnmatched([]ServiceDomain{"exec-0", ServiceDomain(fmt.Sprintf("exec-%d", i))}); err != nil {
				select {
				case errors <- err:
				default:
				}
			}
			time.Sleep(100)
		}
	}()

	go func() {
		wg.Wait()
		close(errors)
	}()

	for err := range errors {
		if err != nil {
			t.Errorf("error setting service from executor goroutine: %v", err)
		}
	}

	// Do a final ReapUnmatched to get consul back into a deterministic state
	if err := execSyncer.ReapUnmatched([]ServiceDomain{"exec-0"}); err != nil {
		t.Fatalf("error doing final reap: %v", err)
	}

	// flattenedServices should be fully populated as ReapUnmatched doesn't
	// touch Syncer's internal state
	expected := map[string]struct{}{}
	for i := 0; i < execn; i++ {
		for ii := 0; ii < 10; ii++ {
			expected[fmt.Sprintf("exec-%d-%d", i, ii)] = struct{}{}
		}
	}

	for _, s := range execSyncer.flattenedServices() {
		_, ok := expected[s.Name]
		if !ok {
			t.Errorf("%s unexpected", s.Name)
		}
		delete(expected, s.Name)
	}
	if len(expected) > 0 {
		left := []string{}
		for s := range expected {
			left = append(left, s)
		}
		sort.Strings(left)
		t.Errorf("Couldn't find %d names in flattened services:\n%s", len(expected), strings.Join(left, "\n"))
	}

	// All but exec-0 and possibly some of exec-99 should have been reaped
	{
		services, err := execSyncer.client.Agent().Services()
		if err != nil {
			t.Fatalf("Error getting services: %v", err)
		}
		expected := []int{}
		for k, service := range services {
			if service.Service == "consul" {
				continue
			}
			i := -1
			ii := -1
			fmt.Sscanf(service.Service, "exec-%d-%d", &i, &ii)
			switch {
			case i == -1 || ii == -1:
				t.Errorf("invalid service: %s -> %s", k, service.Service)
			case i != 0 || ii > 9:
				t.Errorf("unexpected service: %s -> %s", k, service.Service)
			default:
				expected = append(expected, ii)
			}
		}
		if len(expected) != 10 {
			t.Errorf("expected 0-9 but found: %#q", expected)
		}
	}
}
// 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_quiescenceIntegrated(t *testing.T) {
	consul := testutil.NewTestServerConfig(t, func(c *testutil.TestServerConfig) {
		c.Stdout = ioutil.Discard
		c.Stderr = ioutil.Discard
	})
	defer consul.Stop()

	in, out := make([]*os.File, 2), make([]*os.File, 2)
	for i := 0; i < 2; i++ {
		in[i] = test.CreateTempfile([]byte(`
    {{ range service "consul" "any" }}{{.Node}}{{ end }}
  `), t)
		defer test.DeleteTempfile(in[i], t)

		out[i] = test.CreateTempfile(nil, t)
		test.DeleteTempfile(out[i], t)
	}

	config := testConfig(fmt.Sprintf(`
		consul = "%s"
		wait = "100ms:200ms"

		template {
			source = "%s"
			destination = "%s"
		}

		template {
			source = "%s"
			destination = "%s"
                        wait = "300ms:400ms"
		}
	`, consul.HTTPAddr, in[0].Name(), out[0].Name(),
		in[1].Name(), out[1].Name()), t)

	runner, err := NewRunner(config, false, false)
	if err != nil {
		t.Fatal(err)
	}

	go runner.Start()
	defer runner.Stop()

	// Watch for the appearance of the first template, which needs to at
	// least take 100 ms. We don't have enough certainty with Consul's
	// interactions with us to put tighter bounds.
	start := time.Now()
	for {
		dur := time.Now().Sub(start)

		_, err = os.Stat(out[0].Name())
		if !os.IsNotExist(err) {
			if dur < 100*time.Millisecond {
				t.Fatalf("template appeared too quickly, %9.6f", dur.Seconds())
			}
			break
		}

		if dur > 500*time.Millisecond {
			t.Fatalf("template should have appeared")
		}

		time.Sleep(1 * time.Millisecond)
	}

	// Now we know that the previous template just got rendered, so there
	// should have been tick() call on the second template. This is a clean
	// time base to check from and we can use tighter bounds here.
	start = time.Now()
	checks := []struct {
		eventCh   <-chan time.Time
		fileExist bool
	}{
		{time.After(1 * time.Millisecond), false},
		{time.After(250 * time.Millisecond), false},
		{time.After(350 * time.Millisecond), true},
	}
	for {
		for idx, check := range checks {
			select {
			case <-check.eventCh:
				_, err = os.Stat(out[1].Name())
				if os.IsNotExist(err) == check.fileExist {
					t.Errorf("check %d failed", idx)
				}
				if idx == len(checks)-1 {
					return
				}
			default:
			}
		}

		time.Sleep(1 * time.Millisecond)
	}
}
func TestRunner_execRestart(t *testing.T) {
	t.Parallel()

	consul := testutil.NewTestServerConfig(t, func(c *testutil.TestServerConfig) {
		c.Stdout = ioutil.Discard
		c.Stderr = ioutil.Discard
	})
	defer consul.Stop()

	tmpl := test.CreateTempfile([]byte(`{{ key "foo" }}`), t)
	defer test.DeleteTempfile(tmpl, t)

	out := test.CreateTempfile(nil, t)
	defer test.DeleteTempfile(out, t)

	// Create a tiny bash script for us to run as a "program"
	script := test.CreateTempfile([]byte(strings.TrimSpace(`
#!/usr/bin/env bash
while true; do
	: # Hang
done
	`)), t)
	if err := os.Chmod(script.Name(), 0700); err != nil {
		t.Fatal(err)
	}
	defer test.DeleteTempfile(script, t)

	config := testConfig(fmt.Sprintf(`
		consul = "%s"

		template {
			source      = "%s"
			destination = "%s"
		}

		exec {
			command      = "%s"
			kill_timeout = "10ms" # Faster tests
		}
	`, consul.HTTPAddr, tmpl.Name(), out.Name(), script.Name()), t)

	runner, err := NewRunner(config, true, false)
	if err != nil {
		t.Fatal(err)
	}

	go runner.Start()
	defer runner.Stop()

	doneCh := make(chan struct{}, 1)
	go func() {
		for {
			if runner.child != nil {
				close(doneCh)
				return
			}
			time.Sleep(50 * time.Millisecond)
		}
	}()

	select {
	case err := <-runner.ErrCh:
		t.Fatal(err)
	case <-doneCh:
		// Childprocess is started, we can send it signals now
	case <-time.After(2 * time.Second):
		t.Fatal("child process should have started")
	}

	// Grab the current child pid - this will help us confirm the child was
	// restarted on template change.
	opid := runner.child.Pid()

	// Change a dependent value in Consul, which will force the runner to cycle.
	consul.SetKV("foo", []byte("bar"))

	// Give the runner time to do its thing.
	time.Sleep(1 * time.Second)

	npid := runner.child.Pid()
	if opid == npid {
		t.Errorf("expected %d to be different from %d", opid, npid)
	}
}
func TestRunner_execReload(t *testing.T) {
	t.Parallel()

	consul := testutil.NewTestServerConfig(t, func(c *testutil.TestServerConfig) {
		c.Stdout = ioutil.Discard
		c.Stderr = ioutil.Discard
	})
	defer consul.Stop()

	out := test.CreateTempfile(nil, t)
	defer test.DeleteTempfile(out, t)

	tmpl := test.CreateTempfile([]byte(`{{ key "foo" }}`), t)
	defer test.DeleteTempfile(tmpl, t)

	// Create a tiny bash script for us to run as a "program"
	script := test.CreateTempfile([]byte(strings.TrimSpace(fmt.Sprintf(`
#!/usr/bin/env bash
trap "echo 'one' >> %s" SIGUSR1
trap "echo 'two' >> %s" SIGUSR2

while true; do
	: # Hang
done
	`, out.Name(), out.Name()))), t)
	if err := os.Chmod(script.Name(), 0700); err != nil {
		t.Fatal(err)
	}
	defer test.DeleteTempfile(script, t)

	config := testConfig(fmt.Sprintf(`
		consul = "%s"

		template {
			source = "%s"
		}

		exec {
			command       = "%s"
			reload_signal = "sigusr1"
			kill_signal   = "sigusr2"

			# We used SIGUSR2 to check, so there's force-kill shortly to make the
			# test faster.
			kill_timeout  = "10ms"
		}
	`, consul.HTTPAddr, tmpl.Name(), script.Name()), t)

	runner, err := NewRunner(config, true, false)
	if err != nil {
		t.Fatal(err)
	}

	go runner.Start()
	defer runner.Stop()

	doneCh := make(chan struct{}, 1)
	go func() {
		for {
			if runner.child != nil {
				close(doneCh)
				return
			}
			time.Sleep(50 * time.Millisecond)
		}
	}()

	select {
	case err := <-runner.ErrCh:
		t.Fatal(err)
	case <-doneCh:
		// Childprocess is started, we can send it signals now
	case <-time.After(2 * time.Second):
		t.Fatal("child process should have started")
	}

	// Grab the current child pid - this will help us confirm the child was not
	// restarted on template change.
	opid := runner.child.Pid()

	// Change a dependent value in Consul, which will force the runner to cycle.
	consul.SetKV("foo", []byte("bar"))

	// Check that the reload signal was sent.
	test.WaitForFileContents(out.Name(), []byte("one\n"), t)

	npid := runner.child.Pid()
	if opid != npid {
		t.Errorf("expected %d to be the same as %d", opid, npid)
	}

	// Kill the child to check that the kill signal is properly sent.
	runner.child.Stop()

	test.WaitForFileContents(out.Name(), []byte("one\ntwo\n"), t)
}
func TestRunner_dedup(t *testing.T) {
	t.Parallel()

	// Create a template
	in := test.CreateTempfile([]byte(`
    {{ range service "consul" }}{{.Node}}{{ end }}
  `), t)
	defer test.DeleteTempfile(in, t)

	out1 := test.CreateTempfile(nil, t)
	defer test.DeleteTempfile(out1, t)

	out2 := test.CreateTempfile(nil, t)
	defer test.DeleteTempfile(out2, t)

	// Start consul
	consul := testutil.NewTestServerConfig(t, func(c *testutil.TestServerConfig) {
		c.Stdout = ioutil.Discard
		c.Stderr = ioutil.Discard
	})
	defer consul.Stop()

	// Setup the runner config
	config := DefaultConfig()
	config.Merge(&Config{
		ConfigTemplates: []*ConfigTemplate{
			&ConfigTemplate{Source: in.Name(), Destination: out1.Name(), Wait: &watch.Wait{}},
		},
	})
	config.Deduplicate.Enabled = true
	config.Consul = consul.HTTPAddr
	config.set("consul")
	config.set("deduplicate")
	config.set("deduplicate.enabled")

	config2 := DefaultConfig()
	config2.Merge(&Config{
		ConfigTemplates: []*ConfigTemplate{
			&ConfigTemplate{Source: in.Name(), Destination: out2.Name(), Wait: &watch.Wait{}},
		},
	})
	config2.Deduplicate.Enabled = true
	config2.Consul = consul.HTTPAddr
	config2.set("consul")
	config2.set("deduplicate")
	config2.set("deduplicate.enabled")

	// Create the runners
	r1, err := NewRunner(config, false, false)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	go r1.Start()
	defer r1.Stop()

	r2, err := NewRunner(config2, false, false)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	go r2.Start()
	defer r2.Stop()

	// Wait until the output file exists
	testutil.WaitForResult(func() (bool, error) {
		_, err := os.Stat(out1.Name())
		if err != nil {
			return false, nil
		}
		return true, nil
	}, func(err error) {
		t.Fatalf("err: %v", err)
	})

	// Wait until the output file exists
	testutil.WaitForResult(func() (bool, error) {
		_, err := os.Stat(out2.Name())
		if err != nil {
			return false, nil
		}
		return true, nil
	}, func(err error) {
		t.Fatalf("err: %v", err)
	})

	// Should only be a single total watcher
	total := r1.watcher.Size() + r2.watcher.Size()
	if total > 1 {
		t.Fatalf("too many watchers: %d", total)
	}
}