When running tests from command line, capturing SIGINT works fine. However, is there a way to pass SIGINT signal to code when running tests from GoLand IDE?
When running from command line:
go test -v -run TestSth and then calling Ctrl + C it captures fine.
Example code:
EDIT: it seems my example code now captures SIGINT as intended (Goland 2022.3.2)
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"testing"
"time"
)
func TestMain(m *testing.M) {
terminate := make(chan os.Signal)
signal.Notify(terminate, syscall.SIGINT)
go func() {
<-terminate
fmt.Println()
fmt.Println("CAPTURED!!!") // want to get here when running tests from IDE
}()
exitCode := m.Run()
os.Exit(exitCode)
}
func TestSth(t *testing.T) {
time.Sleep(time.Second * 5)
}
Get the current process information by calling FindProcess on the current PID and signal the interrupt to it using Process.Signal
func TestSth(t *testing.T) {
go func() {
// Sleep added for demonstrative purposes, can be removed
time.Sleep(time.Second * 1)
p, err := os.FindProcess(os.Getpid())
if err != nil {
panic(err)
}
p.Signal(syscall.SIGINT)
}()
time.Sleep(time.Second * 5)
}
Related
I'm running a Goroutine which, after some delay, logs a specific line to os.Stderr. I'd like to wait until that line is logged. So far, what I've tried is
package main
import (
"bufio"
"log"
"os"
"strings"
"time"
)
func main() {
go func() {
time.Sleep(time.Second)
log.Println("Hello, world!")
}()
scanner := bufio.NewScanner(os.Stderr)
for scanner.Scan() {
if strings.Contains(scanner.Text(), "Hello, world!") {
break
}
}
}
However, if I run this, it just blocks:
> go run main.go
2022/04/17 00:31:43 Hello, world!
Should this scanner not capture the standard error output and hit the break statement?
If you want to intercept output, you can use a pipe, like so:
r, w, _ := os.Pipe()
log.SetOutput(w)
go func() {
time.Sleep(time.Second)
log.Println("Hello, world!")
}()
scanner := bufio.NewScanner(r)
https://go.dev/play/p/HdEs5tbDYDE
It seems that if the io.Reader is os.Stderr, scanner.Scan() blocks indefinitely, whereas if I set it to a custom reader like a *bytes.Buffer, it returns immediately before the Goroutine has a chance to write to it.
Since the context of this logic is a unit test, I worked around this by using assert.Eventually:
func TestScan(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
log.SetOutput(buf)
go func() {
time.Sleep(time.Second)
log.Println("Hello, world!")
}()
assert.Eventually(t, func() bool {
scanner := bufio.NewScanner(buf)
for scanner.Scan() {
if strings.Contains(scanner.Text(), "Hello, world!") {
return true
}
}
return false
}, 10*time.Second, 100*time.Millisecond)
}
This test passes in 1.1 seconds as expected:
> go test ./... -v
=== RUN TestScan
--- PASS: TestScan (1.10s)
PASS
(Granted, this is not the most efficient solution as it presumably reads the entire output each time, but it should do the job).
I want to run a command in the Bash shell every one minute, and serve the output via http, on http://localhost:8080/feed
package main
import (
"fmt"
"os/exec"
)
func main() {
cmd := `<a piped command>`
out, err := exec.Command("bash", "-c", cmd).Output()
if err != nil {
fmt.Sprintf("Failed to execute command: %s", cmd)
}
fmt.Println(string(out))
}
UPDATE :
package main
import (
"fmt"
"log"
"net/http"
"os/exec"
)
func handler(w http.ResponseWriter, r *http.Request) {
cmd := `<a piped command>`
out, err := exec.Command("bash", "-c", cmd).Output()
if err != nil {
fmt.Sprintf("Failed to execute command: %s", cmd)
}
fmt.Fprintf(w, string(out))
}
func main() {
http.HandleFunc("/feed", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
With the above code, the command is run every time http://localhost:8080/feed is accessed. How do I make it cache(?) the output of the command for one minute, and then run the command again only after the cache time is expired?
If output is not too big, you can keep it in memory variable.
I took approach to wait for result in case that script is executed (using mutex):
package main
import (
"fmt"
"net/http"
"os/exec"
"sync"
"time"
)
var LoopDelay = 60*time.Second
type Output struct {
sync.Mutex
content string
}
func main() {
var output *Output = new(Output)
go updateResult(output)
http.HandleFunc("/feed", initHandle(output))
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println("ERROR", err)
}
}
func initHandle(output *Output) func(http.ResponseWriter, *http.Request) {
return func(respw http.ResponseWriter, req *http.Request) {
output.Lock()
defer output.Unlock()
_, err := respw.Write([]byte(output.content))
if err != nil {
fmt.Println("ERROR: Unable to write response: ", err)
}
}
}
func updateResult(output *Output) {
var execFn = func() { /* Extracted so that 'defer' executes at the end of loop iteration */
output.Lock()
defer output.Unlock()
command := exec.Command("bash", "-c", "date | nl ")
output1, err := command.CombinedOutput()
if err != nil {
output.content = err.Error()
} else {
output.content = string(output1)
}
}
for {
execFn()
time.Sleep(LoopDelay)
}
}
Executing
date; curl http://localhost:8080/feed
gives output (on multiple calls):
1 dimanche 13 octobre 2019, 09:41:40 (UTC+0200)
http-server-cmd-output> date; curl http://localhost:8080/feed
dimanche 13 octobre 2019, 09:42:05 (UTC+0200)
1 dimanche 13 octobre 2019, 09:41:40 (UTC+0200)
Few things to consider:
- Used 'date | nl' as command with pipe example
- If output too big, write to file
- Very likely it is good idea to keep mutex only for content update (no need to wait during script execution) - you can try to improve it
- Go routine may have variable (channel) to exit on signal (ex: when program ends)
EDIT: variable moved to main function
How do I make it cache(?) the output of the command for one minute,
and then run the command again only after the cache time is expired?
In below solution two goroutines are declared.
First goroutine loop until the context is done to execute the command at regular interval and send a copy to the second go routine.
The second routine, tries to get from the first goroutine, or distribute its value to other goroutines.
The http handler, third goroutine, only queries the value from the getter and does something with it.
The reason to use three routines instead of two in this example is to prevent blocking the http routines if the command is being executed. With that additional routines, http requests only wait for the synchronization to occur.
type state is a dummy struct to transport the values within channels.
We prevent race conditions because of two facts, the state is passed by value, cmd.Output() allocates a new buffer each time it runs.
To retrieve original command in the http handler, OP should build a custom error type and attach those information to the recorded error, within the http handler, OP should type assert the error to its custom type and get the specific details from there.
package main
import (
"context"
"fmt"
"log"
"net/http"
"os/exec"
"time"
)
type state struct {
out []byte
err error
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
set := make(chan state, 1)
go func() {
ticker := time.NewTicker(2 * time.Second)
cmd := []string{"date"}
s := state{}
s.out, s.err = exec.Command(cmd[0], cmd[1:]...).Output()
set <- s
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
s.out, s.err = exec.Command(cmd[0], cmd[1:]...).Output()
set <- s
}
}
}()
get := make(chan state)
go func() {
s := state{}
for {
select {
case <-ctx.Done():
return
case s = <-set: // get the fresh value, if any
case get <- s: // distribute your copy
}
}
}()
http.HandleFunc("/feed", func(w http.ResponseWriter, r *http.Request) {
state := <-get
if state.err != nil {
fmt.Printf("Failed to execute command: %v", state.err)
}
fmt.Fprintf(w, string(state.out))
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
You could:
write the last command instance (the "string(out)") to a log file: see "Go/Golang write log to file"
serve that log file (and only the file) in a Go http server: see "How to serve a file to a specific URL path with the FileServer function in go/golang"
That way, any time you go to your server, you will get the last command output.
How do I make it cache(?) the output of the command for one minute, and then run the command again only after the cache time is expired?
By keeping the execution of the command separate from the command output being served.
That is why you must kept the two asynchronous, typically through a goroutine, a a time.NewTicker.
See "Is there a way to do repetitive tasks at intervals?"
I can run some cleanup on interruption (when I press ctrlc).
$ go build
$ ./exit
^Creceived interrupt signal
Is it possible to trap the same way os.Exit call and run some code before program exits? The code:
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
handleInterrupt(2)
time.Sleep(2 * time.Second)
os.Exit(1) // how to trap it?
}
func handleInterrupt(intrptChSize int) {
s := make(chan os.Signal, intrptChSize)
signal.Notify(s,
syscall.SIGABRT,
syscall.SIGALRM,
syscall.SIGBUS,
syscall.SIGCHLD,
syscall.SIGCONT,
syscall.SIGEMT,
syscall.SIGFPE,
syscall.SIGHUP,
syscall.SIGILL,
syscall.SIGINFO,
syscall.SIGINT,
syscall.SIGIO,
syscall.SIGIOT,
syscall.SIGKILL,
syscall.SIGPIPE,
syscall.SIGPROF,
syscall.SIGQUIT,
syscall.SIGSEGV,
syscall.SIGSTOP,
syscall.SIGSYS,
syscall.SIGTERM,
syscall.SIGTRAP,
syscall.SIGTSTP,
syscall.SIGTTIN,
syscall.SIGTTOU,
syscall.SIGURG,
syscall.SIGUSR1,
syscall.SIGUSR2,
syscall.SIGVTALRM,
syscall.SIGWINCH,
syscall.SIGXCPU,
syscall.SIGXFSZ)
go func() {
for sig := range s {
fmt.Printf("received %s signal\n", sig)
//cleanup()
}
}()
}
I know I can just run cleanup before each os.Exit() code manually in this example:
cleanup()
os.Exit(1)
But in my real project I am importing code which I cannot edit. This code contains os.Exit calls and I would like to do some cleanup before the program exits without editing imported code.
You can't. from TFM:
The program terminates immediately; deferred functions are not run.
Is there any way to exit a Go program, but execute all the pending defer statements?
I've been clearing up temporary files by using defer, but the deferred statements aren't executed when the program is interrupted with Ctrl+C or even os.Exit.
After exiting this program with Ctrl+C, both foo.txt and bar.txt are left over:
package main
import (
"fmt"
"io/ioutil"
"os"
"os/signal"
"syscall"
)
func main() {
ioutil.WriteFile("./foo.txt", []byte("foo"), 0644)
defer os.RemoveAll("./foo.txt")
go func() {
ioutil.WriteFile("./bar.txt", []byte("bar"), 0644)
defer os.RemoveAll("./bar.txt")
for {
// various long running things
}
}()
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
signal.Notify(c, syscall.SIGTERM)
go func() {
<-c
fmt.Println("Received OS interrupt - exiting.")
os.Exit(0)
}()
for {
// various long running things
}
}
From golang reference:
A "defer" statement invokes a function whose execution is deferred to
the moment the surrounding function returns
When you call os.Exit(0) you bypass the normal return procedure and your deferred functions are not executed.
Also, even if the deferred worked inside the main goroutine, the defers in other goroutines would not work since they would die before returning.
A better code architecture would allow you to get something similar. You need to think about your long running processes as workers. Export every long running process in workers and defer any clean up right after calling the worker. Use a select in the main goroutine to capture signals and synchronise work
package main
import (
"fmt"
"io/ioutil"
"os"
"os/signal"
"syscall"
"time"
)
func check(e error) {
if e != nil {
panic(e)
}
}
func main() {
ioutil.WriteFile("./foo.txt", []byte("foo"), 0644)
defer os.RemoveAll("./foo.txt")
// Worker 1
done := make(chan bool, 1)
go func(done chan bool) {
fmt.Println("worker 1 with bar ...")
ioutil.WriteFile("./bar.txt", []byte("bar"), 0644)
// various long running things
time.Sleep(3 * time.Second)
done <- true
}(done)
defer os.RemoveAll("./bar.txt")
// End worker1
s := make(chan os.Signal, 1)
signal.Notify(s, os.Interrupt)
signal.Notify(s, syscall.SIGTERM)
// Worker 2
done2 := make(chan bool, 1)
go func(done chan bool) {
fmt.Println("worker 2 ...")
time.Sleep(6 * time.Second)
done <- true
}(done2)
// End worker 2
select {
case <-s:
fmt.Println("Quiting with signal - exit")
case <-done:
<-done2
case <-done2:
<-done
}
return
}
This select is a quick and dirty way to handle two workers, a better way would be to use sync.WaitGroup
I would recommend not relying on defer, but defining a reusable function that can be used in a defer or in the signal block. Something like this:
package main
import (
"fmt"
"io/ioutil"
"os"
"os/signal"
"syscall"
)
func main() {
ioutil.WriteFile("./foo.txt", []byte("foo"), 0644)
cleanup := func(){
os.RemoveAll("./foo.txt")
os.RemoveAll("./bar.txt")
}
defer cleanup() //for normal return
go func() {
ioutil.WriteFile("./bar.txt", []byte("bar"), 0644)
for {
// various long running things
}
}()
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
signal.Notify(c, syscall.SIGTERM)
go func() {
<-c
fmt.Println("Received OS interrupt - exiting.")
cleanup()
os.Exit(0)
}()
for {
// various long running things
}
}
I'm trying to stop the process started with exec.Command("go", "run", "server.go") and all its child processes.
But when I call cmd.Process.Kill() and the go process stops, the child process (a web server in server.go) continues to run.
package main
import (
"fmt"
"os/exec"
"time"
)
func run() *exec.Cmd {
cmd := exec.Command("go", "run", "server.go")
err := cmd.Start()
if err != nil {
panic(err)
}
return cmd
}
func main() {
cmd := run()
time.Sleep(time.Second * 2)
err := cmd.Process.Kill()
if err != nil {
panic(err)
}
cmd.Process.Wait()
// === Web server is still running! ===
fmt.Scanln()
}
It looks like Process.Kill() only stops the go (run) process, but leaves its child process (web server) running.
^C kills the whole process group, including all child (and sub-child) processes. How can I do the same?
I tried cmd.Process.Signal(os.Interrupt), syscall.SIGINT, syscall.SIGQUIT and syscall.SIGKILL, none of which did anything.
Don't use the go run command. Use the go install command to install your packages and programs and then execute your program.