github.com/kjdelisle/consul@v1.4.5/agent/util_test.go (about)

     1  package agent
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"os"
     7  	"os/exec"
     8  	"os/signal"
     9  	"runtime"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/hashicorp/consul/testutil"
    14  	"github.com/pascaldekloe/goe/verify"
    15  )
    16  
    17  func TestStringHash(t *testing.T) {
    18  	t.Parallel()
    19  	in := "hello world"
    20  	expected := "5eb63bbbe01eeed093cb22bb8f5acdc3"
    21  
    22  	if out := stringHash(in); out != expected {
    23  		t.Fatalf("bad: %s", out)
    24  	}
    25  }
    26  
    27  func TestSetFilePermissions(t *testing.T) {
    28  	t.Parallel()
    29  	if runtime.GOOS == "windows" {
    30  		t.SkipNow()
    31  	}
    32  	tempFile := testutil.TempFile(t, "consul")
    33  	path := tempFile.Name()
    34  	defer os.Remove(path)
    35  
    36  	// Bad UID fails
    37  	if err := setFilePermissions(path, "%", "", ""); err == nil {
    38  		t.Fatalf("should fail")
    39  	}
    40  
    41  	// Bad GID fails
    42  	if err := setFilePermissions(path, "", "%", ""); err == nil {
    43  		t.Fatalf("should fail")
    44  	}
    45  
    46  	// Bad mode fails
    47  	if err := setFilePermissions(path, "", "", "%"); err == nil {
    48  		t.Fatalf("should fail")
    49  	}
    50  
    51  	// Allows omitting user/group/mode
    52  	if err := setFilePermissions(path, "", "", ""); err != nil {
    53  		t.Fatalf("err: %s", err)
    54  	}
    55  
    56  	// Doesn't change mode if not given
    57  	if err := os.Chmod(path, 0700); err != nil {
    58  		t.Fatalf("err: %s", err)
    59  	}
    60  	if err := setFilePermissions(path, "", "", ""); err != nil {
    61  		t.Fatalf("err: %s", err)
    62  	}
    63  	fi, err := os.Stat(path)
    64  	if err != nil {
    65  		t.Fatalf("err: %s", err)
    66  	}
    67  	if fi.Mode().String() != "-rwx------" {
    68  		t.Fatalf("bad: %s", fi.Mode())
    69  	}
    70  
    71  	// Changes mode if given
    72  	if err := setFilePermissions(path, "", "", "0777"); err != nil {
    73  		t.Fatalf("err: %s", err)
    74  	}
    75  	fi, err = os.Stat(path)
    76  	if err != nil {
    77  		t.Fatalf("err: %s", err)
    78  	}
    79  	if fi.Mode().String() != "-rwxrwxrwx" {
    80  		t.Fatalf("bad: %s", fi.Mode())
    81  	}
    82  }
    83  
    84  func TestDurationFixer(t *testing.T) {
    85  	obj := map[string]interface{}{
    86  		"key1": []map[string]interface{}{
    87  			{
    88  				"subkey1": "10s",
    89  			},
    90  			{
    91  				"subkey2": "5d",
    92  			},
    93  		},
    94  		"key2": map[string]interface{}{
    95  			"subkey3": "30s",
    96  			"subkey4": "20m",
    97  		},
    98  		"key3": "11s",
    99  		"key4": "49h",
   100  	}
   101  	expected := map[string]interface{}{
   102  		"key1": []map[string]interface{}{
   103  			{
   104  				"subkey1": 10 * time.Second,
   105  			},
   106  			{
   107  				"subkey2": "5d",
   108  			},
   109  		},
   110  		"key2": map[string]interface{}{
   111  			"subkey3": "30s",
   112  			"subkey4": 20 * time.Minute,
   113  		},
   114  		"key3": "11s",
   115  		"key4": 49 * time.Hour,
   116  	}
   117  
   118  	fixer := NewDurationFixer("key4", "subkey1", "subkey4")
   119  	if err := fixer.FixupDurations(obj); err != nil {
   120  		t.Fatal(err)
   121  	}
   122  
   123  	// Ensure we only processed the intended fieldnames
   124  	verify.Values(t, "", obj, expected)
   125  }
   126  
   127  // helperProcessSentinel is a sentinel value that is put as the first
   128  // argument following "--" and is used to determine if TestHelperProcess
   129  // should run.
   130  const helperProcessSentinel = "GO_WANT_HELPER_PROCESS"
   131  
   132  // helperProcess returns an *exec.Cmd that can be used to execute the
   133  // TestHelperProcess function below. This can be used to test multi-process
   134  // interactions.
   135  func helperProcess(s ...string) (*exec.Cmd, func()) {
   136  	cs := []string{"-test.run=TestHelperProcess", "--", helperProcessSentinel}
   137  	cs = append(cs, s...)
   138  
   139  	cmd := exec.Command(os.Args[0], cs...)
   140  	destroy := func() {
   141  		if p := cmd.Process; p != nil {
   142  			p.Kill()
   143  		}
   144  	}
   145  
   146  	return cmd, destroy
   147  }
   148  
   149  // This is not a real test. This is just a helper process kicked off by tests
   150  // using the helperProcess helper function.
   151  func TestHelperProcess(t *testing.T) {
   152  	args := os.Args
   153  	for len(args) > 0 {
   154  		if args[0] == "--" {
   155  			args = args[1:]
   156  			break
   157  		}
   158  
   159  		args = args[1:]
   160  	}
   161  
   162  	if len(args) == 0 || args[0] != helperProcessSentinel {
   163  		return
   164  	}
   165  
   166  	defer os.Exit(0)
   167  	args = args[1:] // strip sentinel value
   168  	cmd, args := args[0], args[1:]
   169  
   170  	switch cmd {
   171  	case "parent-signal":
   172  		// This subcommand forwards signals to a child process subcommand "print-signal".
   173  
   174  		limitProcessLifetime(2 * time.Minute)
   175  
   176  		cmd, destroy := helperProcess("print-signal")
   177  		defer destroy()
   178  		cmd.Stdout = os.Stdout
   179  		cmd.Stderr = os.Stderr
   180  
   181  		if err := cmd.Start(); err != nil {
   182  			fmt.Fprintf(os.Stderr, "child process failed to start: %v\n", err)
   183  			os.Exit(1)
   184  		}
   185  
   186  		doneCh := make(chan struct{})
   187  		defer func() { close(doneCh) }()
   188  		logFn := func(err error) {
   189  			fmt.Fprintf(os.Stderr, "could not forward signal: %s\n", err)
   190  			os.Exit(1)
   191  		}
   192  		ForwardSignals(cmd, logFn, doneCh)
   193  
   194  		if err := cmd.Wait(); err != nil {
   195  			fmt.Fprintf(os.Stderr, "unexpected error waiting for child: %v", err)
   196  			os.Exit(1)
   197  		}
   198  
   199  	case "print-signal":
   200  		// This subcommand is instrumented to help verify signals are passed correctly.
   201  
   202  		limitProcessLifetime(2 * time.Minute)
   203  
   204  		ch := make(chan os.Signal, 10)
   205  		signal.Notify(ch)
   206  		defer signal.Stop(ch)
   207  
   208  		fmt.Fprintf(os.Stdout, "ready\n")
   209  
   210  		s := <-ch
   211  
   212  		fmt.Fprintf(os.Stdout, "signal: %s\n", s)
   213  
   214  	default:
   215  		fmt.Fprintf(os.Stderr, "Unknown command: %q\n", cmd)
   216  		os.Exit(2)
   217  	}
   218  }
   219  
   220  // limitProcessLifetime installs a background goroutine that self-exits after
   221  // the specified duration elapses to prevent leaking processes from tests that
   222  // may spawn them.
   223  func limitProcessLifetime(dur time.Duration) {
   224  	go time.AfterFunc(dur, func() {
   225  		os.Exit(99)
   226  	})
   227  }
   228  
   229  func TestForwardSignals(t *testing.T) {
   230  	for _, s := range forwardSignals {
   231  		t.Run("signal-"+s.String(), func(t *testing.T) {
   232  			testForwardSignal(t, s)
   233  		})
   234  	}
   235  }
   236  
   237  func testForwardSignal(t *testing.T, s os.Signal) {
   238  	t.Helper()
   239  
   240  	if s == os.Kill {
   241  		t.Fatalf("you can't forward SIGKILL")
   242  	}
   243  
   244  	// Launch a child process which registers the forwarding signal handler
   245  	// under test and then that in turn launches a grand child process that is
   246  	// our test instrument.
   247  	cmd, destroy := helperProcess("parent-signal")
   248  	defer destroy()
   249  
   250  	cmd.Stderr = os.Stderr
   251  	prc, err := cmd.StdoutPipe()
   252  	if err != nil {
   253  		t.Fatalf("could not open stdout pipe for child process: %v", err)
   254  	}
   255  	defer prc.Close()
   256  
   257  	if err := cmd.Start(); err != nil {
   258  		t.Fatalf("child process failed to start: %v", err)
   259  	}
   260  	scan := bufio.NewScanner(prc)
   261  
   262  	// Wait until the grandchild relays back to us that it's ready to receive
   263  	// signals.
   264  	expectLine(t, "ready", scan)
   265  
   266  	// Relay our chosen signal down through the intermediary process.
   267  	if err := cmd.Process.Signal(s); err != nil {
   268  		t.Fatalf("signalling child failed: %v", err)
   269  	}
   270  
   271  	// Verify that the signal we intended made it all the way to the grandchild.
   272  	expectLine(t, "signal: "+s.String(), scan)
   273  }
   274  
   275  func expectLine(t *testing.T, expect string, scan *bufio.Scanner) {
   276  	if !scan.Scan() {
   277  		if scan.Err() != nil {
   278  			t.Fatalf("expected to read line %q but failed: %v", expect, scan.Err())
   279  		} else {
   280  			t.Fatalf("expected to read line %q but got no line", expect)
   281  		}
   282  	}
   283  
   284  	if line := scan.Text(); expect != line {
   285  		t.Fatalf("expected to read line %q but got %q", expect, line)
   286  	}
   287  }