src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/shell/interact_test.go (about)

     1  package shell
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"testing"
    10  	"time"
    11  
    12  	"src.elv.sh/pkg/daemon"
    13  	"src.elv.sh/pkg/daemon/daemondefs"
    14  	"src.elv.sh/pkg/env"
    15  	"src.elv.sh/pkg/must"
    16  	. "src.elv.sh/pkg/prog/progtest"
    17  	"src.elv.sh/pkg/testutil"
    18  )
    19  
    20  func TestInteract_Eval(t *testing.T) {
    21  	setupCleanHomePaths(t)
    22  	testutil.InTempDir(t)
    23  	must.WriteFile("rc.elv", "echo hello from rc.elv")
    24  	must.WriteFile("rc-dnc.elv", "echo $a")
    25  	must.WriteFile("rc-fail.elv", "fail bad")
    26  
    27  	Test(t, &Program{},
    28  		thatElvishInteract().WithStdin("echo hello\n").WritesStdout("hello\n"),
    29  		thatElvishInteract().WithStdin("fail mock\n").WritesStderrContaining("fail mock"),
    30  
    31  		thatElvishInteract("-rc", "rc.elv").WritesStdout("hello from rc.elv\n"),
    32  		// rc file does not compile
    33  		thatElvishInteract("-rc", "rc-dnc.elv").
    34  			WritesStderrContaining("variable $a not found"),
    35  		// rc file throws exception
    36  		thatElvishInteract("-rc", "rc-fail.elv").WritesStderrContaining("fail bad"),
    37  		// rc file not existing is OK
    38  		thatElvishInteract("-rc", "rc-nonexistent.elv").DoesNothing(),
    39  	)
    40  }
    41  
    42  func TestInteract_RCPath_XDG_CONFIG_HOME(t *testing.T) {
    43  	setupCleanHomePaths(t)
    44  	xdgConfigHome := testutil.Setenv(t, env.XDG_CONFIG_HOME, testutil.TempDir(t))
    45  	must.WriteFile(
    46  		filepath.Join(xdgConfigHome, "elvish", "rc.elv"),
    47  		"echo hello XDG_CONFIG_HOME rc.elv")
    48  
    49  	Test(t, &Program{},
    50  		thatElvishInteract().WritesStdout("hello XDG_CONFIG_HOME rc.elv\n"),
    51  	)
    52  }
    53  
    54  func TestInteract_ConnectsToDaemon(t *testing.T) {
    55  	sockPath := startDaemon(t)
    56  
    57  	Test(t, &Program{ActivateDaemon: fakeActivate(sockPath)},
    58  		thatElvishInteract().
    59  			WithStdin("use daemon; echo $daemon:pid\n").
    60  			WritesStdout(fmt.Sprintln(os.Getpid())),
    61  	)
    62  }
    63  
    64  func TestInteract_DoesNotStoreEmptyCommandInHistory(t *testing.T) {
    65  	sockPath := startDaemon(t)
    66  	Test(t, &Program{ActivateDaemon: fakeActivate(sockPath)},
    67  		thatElvishInteract().
    68  			WithStdin("\n"+"use store; print (store:next-cmd-seq)\n").
    69  			WritesStdout("1"),
    70  	)
    71  }
    72  
    73  func TestInteract_ErrorInActivateDaemon(t *testing.T) {
    74  	activate := func(io.Writer, *daemondefs.SpawnConfig) (daemondefs.Client, error) {
    75  		return nil, errors.New("fake error")
    76  	}
    77  	Test(t, &Program{ActivateDaemon: activate},
    78  		thatElvishInteract().
    79  			WritesStderrContaining("Cannot connect to daemon: fake error"),
    80  	)
    81  }
    82  
    83  func TestInteract_DBPath_XDG_STATE_HOME(t *testing.T) {
    84  	sockPath := startDaemon(t)
    85  	setupCleanHomePaths(t)
    86  	xdgStateHome := testutil.Setenv(t, env.XDG_STATE_HOME, t.TempDir())
    87  
    88  	Test(t, &Program{ActivateDaemon: fakeActivate(sockPath)},
    89  		thatElvishInteract().
    90  			WritesStderrContaining("db requested: "+
    91  				filepath.Join(xdgStateHome, "elvish", "db.bolt")),
    92  	)
    93  }
    94  
    95  func thatElvishInteract(args ...string) Case {
    96  	return ThatElvish(args...).WritesStderrContaining("")
    97  }
    98  
    99  // Starts a daemon, and returns the socket path to connect it with.
   100  func startDaemon(t *testing.T) string {
   101  	t.Helper()
   102  	// Run the daemon in the same process for simplicity.
   103  	dir := testutil.TempDir(t)
   104  	sockPath := filepath.Join(dir, "sock")
   105  	sigCh := make(chan os.Signal)
   106  	readyCh := make(chan struct{})
   107  	daemonDone := make(chan struct{})
   108  	go func() {
   109  		daemon.Serve(sockPath, filepath.Join(dir, "db.bolt"),
   110  			daemon.ServeOpts{Ready: readyCh, Signals: sigCh})
   111  		close(daemonDone)
   112  	}()
   113  	t.Cleanup(func() {
   114  		t.Helper()
   115  		close(sigCh)
   116  		select {
   117  		case <-daemonDone:
   118  		case <-time.After(testutil.Scaled(2 * time.Second)):
   119  			t.Errorf("timed out waiting for daemon to quit")
   120  		}
   121  	})
   122  	select {
   123  	case <-readyCh:
   124  		// Do nothing
   125  	case <-time.After(testutil.Scaled(2 * time.Second)):
   126  		t.Fatalf("timed out waiting for daemon to start")
   127  	}
   128  	return sockPath
   129  }
   130  
   131  func fakeActivate(sockPath string) daemondefs.ActivateFunc {
   132  	return func(stderr io.Writer, cfg *daemondefs.SpawnConfig) (daemondefs.Client, error) {
   133  		fmt.Fprintln(stderr, "db requested:", cfg.DbPath)
   134  		// Always connect to the in-process daemon just started.
   135  		return daemon.NewClient(sockPath), nil
   136  	}
   137  }