// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package monitor import ( "encoding/json" "strings" "sync" "testing" "time" "github.com/hashicorp/consul/agent" "github.com/mitchellh/cli" ) func TestMonitorCommand_exitsOnSignalBeforeLinesArrive(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } t.Parallel() a := agent.StartTestAgent(t, agent.TestAgent{}) defer a.Shutdown() shutdownCh := make(chan struct{}) ui := cli.NewMockUi() c := New(ui, shutdownCh) // Only wait for ERR which shouldn't happen so should leave logs empty for a // while args := []string{"-http-addr=" + a.HTTPAddr(), "-log-level=ERR"} // Buffer it so we don't deadlock when blocking send on shutdownCh triggers // Run to return before we can select on it. exitCode := make(chan int, 1) // Run the monitor in another go routine. If this doesn't exit on our "signal" // then the whole test will hang and we'll panic (to not blow up if people run // the suite without -timeout) var wg sync.WaitGroup wg.Add(1) go func() { wg.Done() // Signal that this goroutine is at least running now exitCode <- c.Run(args) }() // Wait for that routine to at least be running wg.Wait() // Simulate signal in a few milliseconds without blocking this thread go func() { time.Sleep(5 * time.Millisecond) shutdownCh <- struct{}{} }() // Wait for a second - shouldn't take long to handle the signal before we // panic. We simulate inside the select since the other goroutine might // already have exited if there was some error and we'd block forever trying // to write to unbuffered shutdownCh. We don't just buffer it because then it // doesn't model the real shutdownCh we use for signal watching and could mask // bugs in the handling. select { case ret := <-exitCode: if ret != 0 { t.Fatal("command returned with non-zero code") } // OK! case <-time.After(1 * time.Second): t.Fatal("timed out waiting for exit") } } func TestMonitorCommand_LogJSONValidFlag(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } t.Parallel() a := agent.StartTestAgent(t, agent.TestAgent{}) defer a.Shutdown() shutdownCh := make(chan struct{}) ui := cli.NewMockUi() c := New(ui, shutdownCh) args := []string{"-http-addr=" + a.HTTPAddr(), "-log-json"} // Buffer it so we don't deadlock when blocking send on shutdownCh triggers // Run to return before we can select on it. exitCode := make(chan int, 1) // Run the monitor in another go routine. If this doesn't exit on our "signal" // then the whole test will hang and we'll panic (to not blow up if people run // the suite without -timeout) var wg sync.WaitGroup wg.Add(1) go func() { wg.Done() // Signal that this goroutine is at least running now exitCode <- c.Run(args) }() // Wait for that routine to at least be running wg.Wait() // Simulate signal in a few milliseconds without blocking this thread go func() { time.Sleep(5 * time.Millisecond) shutdownCh <- struct{}{} }() // Wait for a second - shouldn't take long to handle the signal before we // panic. We simulate inside the select since the other goroutine might // already have exited if there was some error and we'd block forever trying // to write to unbuffered shutdownCh. We don't just buffer it because then it // doesn't model the real shutdownCh we use for signal watching and could mask // bugs in the handling. select { case ret := <-exitCode: if ret != 0 { t.Fatal("command returned with non-zero code") } // OK! case <-time.After(1 * time.Second): t.Fatal("timed out waiting for exit") } } func TestMonitorCommand_LogJSONValidFormat(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } t.Parallel() a := agent.StartTestAgent(t, agent.TestAgent{}) defer a.Shutdown() shutdownCh := make(chan struct{}) ui := cli.NewMockUi() c := New(ui, shutdownCh) args := []string{"-http-addr=" + a.HTTPAddr(), "-log-json"} // Buffer it so we don't deadlock when blocking send on shutdownCh triggers // Run to return before we can select on it. exitCode := make(chan int, 1) // Run the monitor in another go routine. If this doesn't exit on our "signal" // then the whole test will hang and we'll panic (to not blow up if people run // the suite without -timeout) var wg sync.WaitGroup wg.Add(1) go func() { wg.Done() // Signal that this goroutine is at least running now exitCode <- c.Run(args) }() // Wait for that routine to at least be running wg.Wait() // Read the logs and try to json marshall it go func() { time.Sleep(1 * time.Second) outputs := ui.OutputWriter.String() for count, output := range strings.Split(outputs, "\n") { if output != "" && count > 0 { jsonLog := new(map[string]interface{}) err := json.Unmarshal([]byte(output), jsonLog) if err != nil { exitCode <- -1 } if len(*jsonLog) <= 0 { exitCode <- 1 } } } shutdownCh <- struct{}{} }() select { case ret := <-exitCode: if ret != 0 { t.Fatal("command returned with non-zero code") } // OK! case <-time.After(5 * time.Second): t.Fatal("timed out waiting for exit") } }