github.com/immortal/immortal@v0.0.0-20240201195854-d8073cd41019/daemon_test.go (about)

     1  package immortal
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/rand"
     6  	"encoding/base64"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"log"
    10  	"os"
    11  	"os/user"
    12  	"path/filepath"
    13  	"strings"
    14  	"sync/atomic"
    15  	"testing"
    16  	"time"
    17  )
    18  
    19  func TestDaemonNewCtl(t *testing.T) {
    20  	dir, err := ioutil.TempDir("", "TestDaemonNewCtl")
    21  	if err != nil {
    22  		t.Error(err)
    23  	}
    24  	defer os.RemoveAll(dir)
    25  	cfg := &Config{
    26  		Cwd: dir,
    27  		ctl: dir,
    28  	}
    29  	d, err := New(cfg)
    30  	if err != nil {
    31  		t.Fatal(err)
    32  	}
    33  	if _, err = os.Stat(filepath.Join(dir, "lock")); err != nil {
    34  		t.Fatal(err)
    35  	}
    36  	expect(t, uint32(0), d.lock)
    37  	expect(t, uint32(0), d.lockOnce)
    38  	// test lock
    39  	_, err = New(cfg)
    40  	if err == nil {
    41  		t.Error("Expecting error: resource temporarily unavailable")
    42  	}
    43  }
    44  
    45  func TestDaemonNewCtlErr(t *testing.T) {
    46  	dir, err := ioutil.TempDir("", "TestDaemonNewCtlErr")
    47  	if err != nil {
    48  		t.Error(err)
    49  	}
    50  	defer os.RemoveAll(dir)
    51  	cwd, err := os.Getwd()
    52  	if err != nil {
    53  		t.Error(err)
    54  	}
    55  	defer os.Chdir(cwd)
    56  	if err := os.Chdir(dir); err != nil {
    57  		t.Error(err)
    58  	}
    59  	os.Chmod(dir, 0000)
    60  	cfg := &Config{
    61  		ctl: dir,
    62  	}
    63  	_, err = New(cfg)
    64  	if err == nil {
    65  		t.Error("Expecting error")
    66  	}
    67  }
    68  
    69  func TestDaemonNewCtlCwd(t *testing.T) {
    70  	dir, err := ioutil.TempDir("", "TestDaemonNewCtrlCwd")
    71  	if err != nil {
    72  		t.Error(err)
    73  	}
    74  	defer os.RemoveAll(dir)
    75  	cwd, err := os.Getwd()
    76  	if err != nil {
    77  		t.Error(err)
    78  	}
    79  	defer os.Chdir(cwd)
    80  	if err := os.Chdir(dir); err != nil {
    81  		t.Error(err)
    82  	}
    83  	cfg := &Config{
    84  		ctl: dir,
    85  	}
    86  	d, err := New(cfg)
    87  	if err != nil {
    88  		t.Error(err)
    89  	}
    90  	if _, err = os.Stat(filepath.Join(dir, "lock")); err != nil {
    91  		t.Fatal(err)
    92  	}
    93  	expect(t, uint32(0), d.lock)
    94  	expect(t, uint32(0), d.lockOnce)
    95  	// test lock
    96  	_, err = New(cfg)
    97  	if err == nil {
    98  		t.Error("Expecting error: resource temporarily unavailable")
    99  	}
   100  }
   101  
   102  func TestBadUid(t *testing.T) {
   103  	dir, err := ioutil.TempDir("", "TestBadUid")
   104  	if err != nil {
   105  		t.Error(err)
   106  	}
   107  	defer os.RemoveAll(dir)
   108  	cfg := &Config{
   109  		command: []string{"go"},
   110  		user:    &user.User{Uid: "uid", Gid: "0"},
   111  		ctl:     dir,
   112  	}
   113  	d, err := New(cfg)
   114  	if err != nil {
   115  		t.Fatal(err)
   116  	}
   117  	_, err = d.Run(NewProcess(cfg))
   118  	if err == nil {
   119  		t.Error("Expecting error")
   120  	}
   121  }
   122  
   123  func TestBadGid(t *testing.T) {
   124  	dir, err := ioutil.TempDir("", "TestBadGid")
   125  	if err != nil {
   126  		t.Error(err)
   127  	}
   128  	defer os.RemoveAll(dir)
   129  	cfg := &Config{
   130  		command: []string{"go"},
   131  		user:    &user.User{Uid: "0", Gid: "gid"},
   132  		ctl:     dir,
   133  	}
   134  	d, err := New(cfg)
   135  	if err != nil {
   136  		t.Fatal(err)
   137  	}
   138  	_, err = d.Run(NewProcess(cfg))
   139  	if err == nil {
   140  		t.Error("Expecting error")
   141  	}
   142  }
   143  
   144  func TestBadPid(t *testing.T) {
   145  	dir, err := ioutil.TempDir("", "TestBadPid")
   146  	if err != nil {
   147  		t.Error(err)
   148  	}
   149  	defer os.RemoveAll(dir)
   150  	cfg := &Config{
   151  		command: []string{"go"},
   152  		ctl:     dir,
   153  	}
   154  	d, err := New(cfg)
   155  	if err != nil {
   156  		t.Fatal(err)
   157  	}
   158  	_, err = d.ReadPidFile("/dev/null/non-existent")
   159  	if err == nil {
   160  		t.Error("Expecting error")
   161  	}
   162  }
   163  
   164  func TestUser(t *testing.T) {
   165  	dir, err := ioutil.TempDir("", "TestUser")
   166  	if err != nil {
   167  		t.Error(err)
   168  	}
   169  	defer os.RemoveAll(dir)
   170  	cfg := &Config{
   171  		command: []string{"go"},
   172  		user:    &user.User{Uid: "0", Gid: "0"},
   173  		ctl:     dir,
   174  	}
   175  	d, err := New(cfg)
   176  	if err != nil {
   177  		t.Fatal(err)
   178  	}
   179  	_, err = d.Run(NewProcess(cfg))
   180  	if err == nil {
   181  		t.Error("Expecting error")
   182  	}
   183  }
   184  
   185  func TestBadWritePidParent(t *testing.T) {
   186  	dir, err := ioutil.TempDir("", "TestBadWritePidParent")
   187  	if err != nil {
   188  		t.Error(err)
   189  	}
   190  	defer os.RemoveAll(dir)
   191  	var mylog bytes.Buffer
   192  	log.SetOutput(&mylog)
   193  	log.SetFlags(0)
   194  	cfg := &Config{
   195  		command: []string{"go"},
   196  		Pid: Pid{
   197  			Parent: "/dev/null/parent.pid",
   198  		},
   199  		ctl: dir,
   200  	}
   201  	d, err := New(cfg)
   202  	if err != nil {
   203  		t.Fatal(err)
   204  	}
   205  	_, err = d.Run(NewProcess(cfg))
   206  	if err != nil {
   207  		t.Fatal(err)
   208  	}
   209  	expect(t, "open /dev/null/parent.pid: not a directory", strings.TrimSpace(mylog.String()))
   210  }
   211  
   212  func TestBadWritePidChild(t *testing.T) {
   213  	var mylog bytes.Buffer
   214  	log.SetOutput(&mylog)
   215  	log.SetFlags(0)
   216  	cfg := &Config{
   217  		command: []string{"go"},
   218  		Pid: Pid{
   219  			Child: "/dev/null/child.pid",
   220  		},
   221  	}
   222  	d, err := New(cfg)
   223  	if err != nil {
   224  		t.Fatal(err)
   225  	}
   226  	_, err = d.Run(NewProcess(cfg))
   227  	if err != nil {
   228  		t.Fatal(err)
   229  	}
   230  	expect(t, "open /dev/null/child.pid: not a directory", strings.TrimSpace(mylog.String()))
   231  	ctl := &Controller{}
   232  	err = ctl.PurgeServices(filepath.Join(d.supDir, "immortal.sock"))
   233  	if err != nil {
   234  		t.Fatal(err)
   235  	}
   236  }
   237  
   238  func TestSignalsUDOT(t *testing.T) {
   239  	sdir, err := ioutil.TempDir("", "TestSignalsUDOT")
   240  	if err != nil {
   241  		t.Error(err)
   242  	}
   243  	defer os.RemoveAll(sdir)
   244  	tmpfile, err := ioutil.TempFile(sdir, "log.")
   245  	if err != nil {
   246  		t.Error(err)
   247  	}
   248  	cfg := &Config{
   249  		Env:     map[string]string{"GO_WANT_HELPER_PROCESS": "signalsUDOT"},
   250  		command: []string{os.Args[0]},
   251  		Cwd:     sdir,
   252  		ctl:     sdir,
   253  		Pid: Pid{
   254  			Parent: filepath.Join(sdir, "parent.pid"),
   255  			Child:  filepath.Join(sdir, "child.pid"),
   256  		},
   257  		Log: Log{
   258  			File: tmpfile.Name(),
   259  		},
   260  	}
   261  	// create new daemon
   262  	d, err := New(cfg)
   263  	if err != nil {
   264  		t.Fatal(err)
   265  	}
   266  
   267  	np := NewProcess(cfg)
   268  	expect(t, 0, np.Pid())
   269  	p, err := d.Run(np)
   270  	if err != nil {
   271  		t.Error(err)
   272  	}
   273  
   274  	// create socket
   275  	if err := d.Listen(); err != nil {
   276  		t.Fatal(err)
   277  	}
   278  
   279  	// check pids
   280  	if pid, err := d.ReadPidFile(filepath.Join(sdir, "parent.pid")); err != nil {
   281  		t.Error(err)
   282  	} else {
   283  		expect(t, os.Getpid(), pid)
   284  	}
   285  	if pid, err := d.ReadPidFile(filepath.Join(sdir, "child.pid")); err != nil {
   286  		t.Error(err, pid)
   287  	} else {
   288  		expect(t, p.Pid(), pid)
   289  	}
   290  
   291  	// check lock
   292  	if _, err = os.Stat(filepath.Join(sdir, "immortal.sock")); err != nil {
   293  		t.Fatal(err)
   294  	}
   295  
   296  	status := &Status{}
   297  	ctl := &Controller{}
   298  	signalResponse := &SignalResponse{}
   299  	if status, err = ctl.GetStatus(filepath.Join(sdir, "immortal.sock")); err != nil {
   300  		t.Fatal(err)
   301  	}
   302  	expect(t, true, strings.HasSuffix(status.Cmd, "/immortal.test"))
   303  	expect(t, 1, status.Count)
   304  
   305  	// http socket client
   306  	// test "k", process should restart and get a new pid
   307  	t.Log("testing k")
   308  	expect(t, p.Pid(), status.Pid)
   309  
   310  	if signalResponse, err = ctl.SendSignal(filepath.Join(sdir, "immortal.sock"), "k"); err != nil {
   311  		t.Fatal(err)
   312  	}
   313  	expect(t, "", signalResponse.Err)
   314  
   315  	// wait for process to finish
   316  	err = <-p.errch
   317  	atomic.StoreUint32(&d.lock, d.lockOnce)
   318  	expect(t, "signal: killed", err.Error())
   319  	p, err = d.Run(NewProcess(cfg))
   320  	if err != nil {
   321  		t.Error(err)
   322  	}
   323  
   324  	if status.Pid == p.Pid() {
   325  		t.Fatalf("Expecting a new pid")
   326  	}
   327  
   328  	// before when not using TestMain
   329  	// $ pgrep -fl TestHelperProcessSignalsUDO
   330  	// PID _test/immortal.test -test.run=TestHelperProcessSignalsUDOT --
   331  
   332  	// test "d", (keep it down and don't restart)
   333  	t.Log("testing d")
   334  	if _, err := ctl.SendSignal(filepath.Join(sdir, "immortal.sock"), "d"); err != nil {
   335  		t.Fatal(err)
   336  	}
   337  	// wait for process to finish
   338  	err = <-p.errch
   339  	atomic.StoreUint32(&d.lock, d.lockOnce)
   340  	expect(t, "signal: terminated", err.Error())
   341  	np = NewProcess(cfg)
   342  	p, err = d.Run(np)
   343  	if err == nil {
   344  		t.Error("Expecting an error")
   345  	} else {
   346  		close(np.quit)
   347  	}
   348  
   349  	// test "u"
   350  	t.Log("testing up")
   351  	go func() {
   352  		if _, err := ctl.SendSignal(filepath.Join(sdir, "immortal.sock"), "up"); err != nil {
   353  			t.Fatal(err)
   354  		}
   355  	}()
   356  	<-d.run
   357  	p, err = d.Run(NewProcess(cfg))
   358  	if err != nil {
   359  		t.Error(err)
   360  	}
   361  
   362  	// test "once", process should not restart after going down
   363  	t.Log("testing once")
   364  	if _, err := ctl.SendSignal(filepath.Join(sdir, "immortal.sock"), "o"); err != nil {
   365  		t.Fatal(err)
   366  	}
   367  
   368  	if _, err := ctl.SendSignal(filepath.Join(sdir, "immortal.sock"), "k"); err != nil {
   369  		t.Fatal(err)
   370  	}
   371  	// wait for process to finish
   372  	err = <-p.errch
   373  	atomic.StoreUint32(&d.lock, d.lockOnce)
   374  	expect(t, "signal: killed", err.Error())
   375  	np = NewProcess(cfg)
   376  	p, err = d.Run(np)
   377  	if err == nil {
   378  		t.Error("Expecting an error")
   379  	} else {
   380  		close(np.quit)
   381  	}
   382  
   383  	if status, err = ctl.GetStatus(filepath.Join(sdir, "immortal.sock")); err != nil {
   384  		t.Fatal(err)
   385  	}
   386  	expect(t, 3, status.Count)
   387  
   388  	// test "u"
   389  	t.Log("testing u")
   390  	go func() {
   391  		if _, err := ctl.SendSignal(filepath.Join(sdir, "immortal.sock"), "u"); err != nil {
   392  			t.Fatal(err)
   393  		}
   394  	}()
   395  	<-d.run
   396  	p, err = d.Run(NewProcess(cfg))
   397  	if err != nil {
   398  		t.Error(err)
   399  	}
   400  	oldPid := p.Pid()
   401  
   402  	// test "t"
   403  	t.Log("testing t")
   404  	if _, err := ctl.SendSignal(filepath.Join(sdir, "immortal.sock"), "t"); err != nil {
   405  		t.Fatal(err)
   406  	}
   407  	err = <-p.errch
   408  	atomic.StoreUint32(&d.lock, d.lockOnce)
   409  	expect(t, "signal: terminated", err.Error())
   410  
   411  	// restart to get new pid
   412  	p, err = d.Run(NewProcess(cfg))
   413  	if err != nil {
   414  		t.Error(err)
   415  	}
   416  	if oldPid == p.Pid() {
   417  		t.Fatal("Expecting a new pid")
   418  	}
   419  	if _, err := ctl.SendSignal(filepath.Join(sdir, "immortal.sock"), "kill"); err != nil {
   420  		t.Fatal(err)
   421  	}
   422  	err = <-p.errch
   423  	atomic.StoreUint32(&d.lock, d.lockOnce)
   424  	expect(t, "signal: killed", err.Error())
   425  
   426  	// test after
   427  	p, err = d.Run(NewProcess(cfg))
   428  	if err != nil {
   429  		t.Error(err)
   430  	}
   431  
   432  DONE:
   433  	for {
   434  		select {
   435  		case err := <-p.errch:
   436  			expect(t, "signal: killed", err.Error())
   437  			break DONE
   438  		case <-time.After(1 * time.Second):
   439  			if _, err := ctl.SendSignal(filepath.Join(sdir, "immortal.sock"), "kill"); err != nil {
   440  				t.Fatal(err)
   441  			}
   442  		}
   443  	}
   444  
   445  	if status, err = ctl.GetStatus(filepath.Join(sdir, "immortal.sock")); err != nil {
   446  		t.Fatal(err)
   447  	}
   448  	expect(t, 6, status.Count)
   449  
   450  	// test log content
   451  	t.Log("testing logfile")
   452  	content, err := ioutil.ReadFile(tmpfile.Name())
   453  	if err != nil {
   454  		t.Fatal(err)
   455  	}
   456  
   457  	lines := strings.Split(string(content), "\n")
   458  	expect(t, true, strings.HasSuffix(lines[0], "5D675098-45D7-4089-A72C-3628713EA5BA"))
   459  
   460  	// halt
   461  	if _, err := ctl.SendSignal(filepath.Join(sdir, "immortal.sock"), "halt"); err != nil {
   462  		t.Fatal(err)
   463  	}
   464  	// wait for socket to be close
   465  	d.wg.Wait()
   466  
   467  	err = ctl.PurgeServices(filepath.Join(sdir, "immortal.sock"))
   468  	if err == nil {
   469  		t.Fatal(err)
   470  	}
   471  
   472  	// remove log.* file
   473  	files, err := filepath.Glob(filepath.Join(sdir, "log.*"))
   474  	if err != nil {
   475  		t.Fatal(err)
   476  	}
   477  	for _, f := range files {
   478  		if err := os.Remove(f); err != nil {
   479  			t.Fatal(err)
   480  		}
   481  	}
   482  	// remove child.pid and parent.pid
   483  	os.Remove(filepath.Join(sdir, "child.pid"))
   484  	os.Remove(filepath.Join(sdir, "parent.pid"))
   485  
   486  	// purgeServices
   487  	err = ctl.PurgeServices(filepath.Join(sdir, "immortal.sock"))
   488  	if err != nil {
   489  		t.Fatal(err)
   490  	}
   491  }
   492  
   493  func TestDaemonNewEnvHOME(t *testing.T) {
   494  	cfg := &Config{}
   495  	home := os.Getenv("HOME")
   496  	defer func() { os.Setenv("HOME", home) }()
   497  	os.Setenv("HOME", "")
   498  	expect(t, true, home != os.Getenv("HOME"))
   499  	d, err := New(cfg)
   500  	if err != nil {
   501  		t.Fatal(err)
   502  	}
   503  	expect(t, true, strings.HasPrefix(d.supDir, home))
   504  	ctl := &Controller{}
   505  	err = ctl.PurgeServices(filepath.Join(d.supDir, "immortal.sock"))
   506  	if err != nil {
   507  		t.Fatal(err)
   508  	}
   509  }
   510  
   511  func TestDaemonConfigFile(t *testing.T) {
   512  	sdir, err := ioutil.TempDir("", "TestDaemonConfigFile")
   513  	if err != nil {
   514  		t.Error(err)
   515  	}
   516  	defer os.RemoveAll(sdir)
   517  	b := make([]byte, 3)
   518  	_, err = rand.Read(b)
   519  	if err != nil {
   520  		fmt.Println("error:", err)
   521  		return
   522  	}
   523  	expectedName := base64.URLEncoding.EncodeToString(b)
   524  	cfg := &Config{
   525  		configFile: filepath.Join(sdir, fmt.Sprintf("%s.yml", expectedName)),
   526  	}
   527  	d, err := New(cfg)
   528  	if err != nil {
   529  		t.Fatal(err)
   530  	}
   531  	expect(t, true, strings.HasSuffix(d.supDir, expectedName))
   532  }
   533  
   534  func TestDaemonFailSdir(t *testing.T) {
   535  	cfg := &Config{}
   536  	home := os.Getenv("HOME")
   537  	defer func() { os.Setenv("HOME", home) }()
   538  	os.Setenv("HOME", "/dev/null")
   539  	_, err := New(cfg)
   540  	if err == nil {
   541  		t.Fatal(err)
   542  	}
   543  }