github.com/KyaXTeam/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 }