mirror of https://github.com/hashicorp/consul
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
293 lines
6.7 KiB
293 lines
6.7 KiB
// Copyright (c) HashiCorp, Inc. |
|
// SPDX-License-Identifier: MPL-2.0 |
|
|
|
package agent |
|
|
|
import ( |
|
"bufio" |
|
"fmt" |
|
"os" |
|
"os/exec" |
|
"os/signal" |
|
"runtime" |
|
"testing" |
|
"time" |
|
|
|
"github.com/hashicorp/consul/sdk/testutil" |
|
"github.com/stretchr/testify/require" |
|
) |
|
|
|
func TestStringHashSHA256(t *testing.T) { |
|
t.Parallel() |
|
in := "hello world\n" |
|
expected := "a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447" |
|
|
|
if out := stringHashSHA256(in); out != expected { |
|
t.Fatalf("bad: %s expected %s", out, expected) |
|
} |
|
} |
|
|
|
func TestSetFilePermissions(t *testing.T) { |
|
t.Parallel() |
|
if runtime.GOOS == "windows" { |
|
t.SkipNow() |
|
} |
|
tempFile := testutil.TempFile(t, "consul") |
|
path := tempFile.Name() |
|
|
|
// Bad UID fails |
|
if err := setFilePermissions(path, "%", "", ""); err == nil { |
|
t.Fatalf("should fail") |
|
} |
|
|
|
// Bad GID fails |
|
if err := setFilePermissions(path, "", "%", ""); err == nil { |
|
t.Fatalf("should fail") |
|
} |
|
|
|
// Bad mode fails |
|
if err := setFilePermissions(path, "", "", "%"); err == nil { |
|
t.Fatalf("should fail") |
|
} |
|
|
|
// Allows omitting user/group/mode |
|
if err := setFilePermissions(path, "", "", ""); err != nil { |
|
t.Fatalf("err: %s", err) |
|
} |
|
|
|
// Doesn't change mode if not given |
|
if err := os.Chmod(path, 0700); err != nil { |
|
t.Fatalf("err: %s", err) |
|
} |
|
if err := setFilePermissions(path, "", "", ""); err != nil { |
|
t.Fatalf("err: %s", err) |
|
} |
|
fi, err := os.Stat(path) |
|
if err != nil { |
|
t.Fatalf("err: %s", err) |
|
} |
|
if fi.Mode().String() != "-rwx------" { |
|
t.Fatalf("bad: %s", fi.Mode()) |
|
} |
|
|
|
// Changes mode if given |
|
if err := setFilePermissions(path, "", "", "0777"); err != nil { |
|
t.Fatalf("err: %s", err) |
|
} |
|
fi, err = os.Stat(path) |
|
if err != nil { |
|
t.Fatalf("err: %s", err) |
|
} |
|
if fi.Mode().String() != "-rwxrwxrwx" { |
|
t.Fatalf("bad: %s", fi.Mode()) |
|
} |
|
} |
|
|
|
func TestDurationFixer(t *testing.T) { |
|
obj := map[string]interface{}{ |
|
"key1": []map[string]interface{}{ |
|
{ |
|
"subkey1": "10s", |
|
}, |
|
{ |
|
"subkey2": "5d", |
|
}, |
|
}, |
|
"key2": map[string]interface{}{ |
|
"subkey3": "30s", |
|
"subkey4": "20m", |
|
}, |
|
"key3": "11s", |
|
"key4": "49h", |
|
} |
|
expected := map[string]interface{}{ |
|
"key1": []map[string]interface{}{ |
|
{ |
|
"subkey1": 10 * time.Second, |
|
}, |
|
{ |
|
"subkey2": "5d", |
|
}, |
|
}, |
|
"key2": map[string]interface{}{ |
|
"subkey3": "30s", |
|
"subkey4": 20 * time.Minute, |
|
}, |
|
"key3": "11s", |
|
"key4": 49 * time.Hour, |
|
} |
|
|
|
fixer := NewDurationFixer("key4", "subkey1", "subkey4") |
|
if err := fixer.FixupDurations(obj); err != nil { |
|
t.Fatal(err) |
|
} |
|
|
|
// Ensure we only processed the intended fieldnames |
|
require.Equal(t, expected, obj) |
|
} |
|
|
|
// helperProcessSentinel is a sentinel value that is put as the first |
|
// argument following "--" and is used to determine if TestHelperProcess |
|
// should run. |
|
const helperProcessSentinel = "GO_WANT_HELPER_PROCESS" |
|
|
|
// helperProcess returns an *exec.Cmd that can be used to execute the |
|
// TestHelperProcess function below. This can be used to test multi-process |
|
// interactions. |
|
func helperProcess(s ...string) (*exec.Cmd, func()) { |
|
cs := []string{"-test.run=TestHelperProcess", "--", helperProcessSentinel} |
|
cs = append(cs, s...) |
|
|
|
cmd := exec.Command(os.Args[0], cs...) |
|
destroy := func() { |
|
if p := cmd.Process; p != nil { |
|
p.Kill() |
|
} |
|
} |
|
|
|
return cmd, destroy |
|
} |
|
|
|
// This is not a real test. This is just a helper process kicked off by tests |
|
// using the helperProcess helper function. |
|
func TestHelperProcess(t *testing.T) { |
|
args := os.Args |
|
for len(args) > 0 { |
|
if args[0] == "--" { |
|
args = args[1:] |
|
break |
|
} |
|
|
|
args = args[1:] |
|
} |
|
|
|
if len(args) == 0 || args[0] != helperProcessSentinel { |
|
return |
|
} |
|
|
|
defer os.Exit(0) |
|
args = args[1:] // strip sentinel value |
|
cmd := args[0] |
|
|
|
switch cmd { |
|
case "parent-signal": |
|
// This subcommand forwards signals to a child process subcommand "print-signal". |
|
|
|
limitProcessLifetime(2 * time.Minute) |
|
|
|
cmd, destroy := helperProcess("print-signal") |
|
defer destroy() |
|
cmd.Stdout = os.Stdout |
|
cmd.Stderr = os.Stderr |
|
|
|
if err := cmd.Start(); err != nil { |
|
fmt.Fprintf(os.Stderr, "child process failed to start: %v\n", err) |
|
os.Exit(1) |
|
} |
|
|
|
doneCh := make(chan struct{}) |
|
defer func() { close(doneCh) }() |
|
logFn := func(err error) { |
|
fmt.Fprintf(os.Stderr, "could not forward signal: %s\n", err) |
|
os.Exit(1) |
|
} |
|
ForwardSignals(cmd, logFn, doneCh) |
|
|
|
if err := cmd.Wait(); err != nil { |
|
fmt.Fprintf(os.Stderr, "unexpected error waiting for child: %v", err) |
|
os.Exit(1) |
|
} |
|
|
|
case "print-signal": |
|
// This subcommand is instrumented to help verify signals are passed correctly. |
|
|
|
limitProcessLifetime(2 * time.Minute) |
|
|
|
ch := make(chan os.Signal, 10) |
|
signal.Notify(ch, forwardSignals...) |
|
defer signal.Stop(ch) |
|
|
|
fmt.Fprintf(os.Stdout, "ready\n") |
|
|
|
s := <-ch |
|
|
|
fmt.Fprintf(os.Stdout, "signal: %s\n", s) |
|
|
|
default: |
|
fmt.Fprintf(os.Stderr, "Unknown command: %q\n", cmd) |
|
os.Exit(2) |
|
} |
|
} |
|
|
|
// limitProcessLifetime installs a background goroutine that self-exits after |
|
// the specified duration elapses to prevent leaking processes from tests that |
|
// may spawn them. |
|
func limitProcessLifetime(dur time.Duration) { |
|
go time.AfterFunc(dur, func() { |
|
os.Exit(99) |
|
}) |
|
} |
|
|
|
func TestForwardSignals(t *testing.T) { |
|
if testing.Short() { |
|
t.Skip("too slow for testing.Short") |
|
} |
|
|
|
for _, s := range forwardSignals { |
|
t.Run("signal-"+s.String(), func(t *testing.T) { |
|
testForwardSignal(t, s) |
|
}) |
|
} |
|
} |
|
|
|
func testForwardSignal(t *testing.T, s os.Signal) { |
|
t.Helper() |
|
|
|
if s == os.Kill { |
|
t.Fatalf("you can't forward SIGKILL") |
|
} |
|
|
|
// Launch a child process which registers the forwarding signal handler |
|
// under test and then that in turn launches a grand child process that is |
|
// our test instrument. |
|
cmd, destroy := helperProcess("parent-signal") |
|
defer destroy() |
|
|
|
cmd.Stderr = os.Stderr |
|
prc, err := cmd.StdoutPipe() |
|
if err != nil { |
|
t.Fatalf("could not open stdout pipe for child process: %v", err) |
|
} |
|
defer prc.Close() |
|
|
|
if err := cmd.Start(); err != nil { |
|
t.Fatalf("child process failed to start: %v", err) |
|
} |
|
scan := bufio.NewScanner(prc) |
|
|
|
// Wait until the grandchild relays back to us that it's ready to receive |
|
// signals. |
|
expectLine(t, "ready", scan) |
|
|
|
// Relay our chosen signal down through the intermediary process. |
|
if err := cmd.Process.Signal(s); err != nil { |
|
t.Fatalf("signalling child failed: %v", err) |
|
} |
|
|
|
// Verify that the signal we intended made it all the way to the grandchild. |
|
expectLine(t, "signal: "+s.String(), scan) |
|
} |
|
|
|
func expectLine(t *testing.T, expect string, scan *bufio.Scanner) { |
|
if !scan.Scan() { |
|
if scan.Err() != nil { |
|
t.Fatalf("expected to read line %q but failed: %v", expect, scan.Err()) |
|
} else { |
|
t.Fatalf("expected to read line %q but got no line", expect) |
|
} |
|
} |
|
|
|
if line := scan.Text(); expect != line { |
|
t.Fatalf("expected to read line %q but got %q", expect, line) |
|
} |
|
}
|
|
|