package core_test import ( "bytes" "io" "os" "path/filepath" "testing" "git.hrafn.xyz/aether/gosick/internal/homesick/core" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) type RcSuite struct { suite.Suite tmpDir string homeDir string reposDir string stdout *bytes.Buffer stderr *bytes.Buffer app *core.App } func TestRcSuite(t *testing.T) { suite.Run(t, new(RcSuite)) } func (s *RcSuite) SetupTest() { s.tmpDir = s.T().TempDir() s.homeDir = filepath.Join(s.tmpDir, "home") s.reposDir = filepath.Join(s.homeDir, ".homesick", "repos") require.NoError(s.T(), os.MkdirAll(s.reposDir, 0o755)) s.stdout = &bytes.Buffer{} s.stderr = &bytes.Buffer{} s.app = &core.App{ HomeDir: s.homeDir, ReposDir: s.reposDir, Stdout: s.stdout, Stderr: s.stderr, } } func (s *RcSuite) createCastle(name string) string { castleRoot := filepath.Join(s.reposDir, name) require.NoError(s.T(), os.MkdirAll(castleRoot, 0o755)) return castleRoot } var _ io.Writer // TestRc_UnknownCastleReturnsError ensures Rc returns an error when the // castle directory does not exist. func (s *RcSuite) TestRc_UnknownCastleReturnsError() { err := s.app.Rc("nonexistent") require.Error(s.T(), err) } // TestRc_NoScriptsAndNoHomesickrc is a no-op when neither .homesick.d nor // .homesickrc are present. func (s *RcSuite) TestRc_NoScriptsAndNoHomesickrcIsNoop() { s.createCastle("dotfiles") require.NoError(s.T(), s.app.Rc("dotfiles")) } // TestRc_ExecutesScriptsInSortedOrder verifies that executable scripts inside // .homesick.d are run in lexicographic (sorted) order. func (s *RcSuite) TestRc_ExecutesScriptsInSortedOrder() { castleRoot := s.createCastle("dotfiles") homesickD := filepath.Join(castleRoot, ".homesick.d") require.NoError(s.T(), os.MkdirAll(homesickD, 0o755)) orderFile := filepath.Join(s.tmpDir, "order.txt") scriptA := filepath.Join(homesickD, "10_a.sh") scriptB := filepath.Join(homesickD, "20_b.sh") require.NoError(s.T(), os.WriteFile(scriptA, []byte("#!/bin/sh\necho a >> "+orderFile+"\n"), 0o755)) require.NoError(s.T(), os.WriteFile(scriptB, []byte("#!/bin/sh\necho b >> "+orderFile+"\n"), 0o755)) require.NoError(s.T(), s.app.Rc("dotfiles")) content, err := os.ReadFile(orderFile) require.NoError(s.T(), err) require.Equal(s.T(), "a\nb\n", string(content)) } // TestRc_SkipsNonExecutableFiles ensures that files without the executable bit // are not run. func (s *RcSuite) TestRc_SkipsNonExecutableFiles() { castleRoot := s.createCastle("dotfiles") homesickD := filepath.Join(castleRoot, ".homesick.d") require.NoError(s.T(), os.MkdirAll(homesickD, 0o755)) notExec := filepath.Join(homesickD, "10_script.sh") // Write a script that would exit 1 if actually run — verify it is skipped. require.NoError(s.T(), os.WriteFile(notExec, []byte("#!/bin/sh\nexit 1\n"), 0o644)) require.NoError(s.T(), s.app.Rc("dotfiles")) } // TestRc_HomesickrcCreatesRubyWrapper verifies that a .homesickrc file causes // a Ruby wrapper to be written into .homesick.d before execution. func (s *RcSuite) TestRc_HomesickrcCreatesRubyWrapper() { castleRoot := s.createCastle("dotfiles") homesickRc := filepath.Join(castleRoot, ".homesickrc") require.NoError(s.T(), os.WriteFile(homesickRc, []byte("# ruby setup code\n"), 0o644)) require.NoError(s.T(), s.app.Rc("dotfiles")) wrapperPath := filepath.Join(castleRoot, ".homesick.d", "00_homesickrc.rb") require.FileExists(s.T(), wrapperPath) info, err := os.Stat(wrapperPath) require.NoError(s.T(), err) require.NotZero(s.T(), info.Mode()&0o111, "wrapper must be executable") content, err := os.ReadFile(wrapperPath) require.NoError(s.T(), err) require.Contains(s.T(), string(content), ".homesickrc") } // TestRc_HomesickrcWrapperRunsBeforeOtherScripts ensures the wrapper file // (00_homesickrc.rb) sorts before typical user scripts and is present in // .homesick.d after Rc returns. func (s *RcSuite) TestRc_HomesickrcWrapperCreatedBeforeExecution() { castleRoot := s.createCastle("dotfiles") homesickRc := filepath.Join(castleRoot, ".homesickrc") require.NoError(s.T(), os.WriteFile(homesickRc, []byte("# ruby setup code\n"), 0o644)) homesickD := filepath.Join(castleRoot, ".homesick.d") require.NoError(s.T(), os.MkdirAll(homesickD, 0o755)) // A sentinel script that records whether the wrapper already exists. orderFile := filepath.Join(s.tmpDir, "check.txt") sentinel := filepath.Join(homesickD, "50_check.sh") wrapperPath := filepath.Join(homesickD, "00_homesickrc.rb") require.NoError(s.T(), os.WriteFile(sentinel, []byte( "#!/bin/sh\n[ -f "+wrapperPath+" ] && echo present >> "+orderFile+"\n", ), 0o755)) require.NoError(s.T(), s.app.Rc("dotfiles")) content, err := os.ReadFile(orderFile) require.NoError(s.T(), err) require.Equal(s.T(), "present\n", string(content)) } // TestRc_FailingScriptReturnsError ensures that a non-zero exit from a script // propagates as an error. func (s *RcSuite) TestRc_FailingScriptReturnsError() { castleRoot := s.createCastle("dotfiles") homesickD := filepath.Join(castleRoot, ".homesick.d") require.NoError(s.T(), os.MkdirAll(homesickD, 0o755)) failing := filepath.Join(homesickD, "10_fail.sh") require.NoError(s.T(), os.WriteFile(failing, []byte("#!/bin/sh\nexit 42\n"), 0o755)) err := s.app.Rc("dotfiles") require.Error(s.T(), err) } // TestRc_ScriptOutputForwarded verifies that stdout and stderr from scripts // are forwarded to the App's writers. func (s *RcSuite) TestRc_ScriptOutputForwarded() { castleRoot := s.createCastle("dotfiles") homesickD := filepath.Join(castleRoot, ".homesick.d") require.NoError(s.T(), os.MkdirAll(homesickD, 0o755)) script := filepath.Join(homesickD, "10_output.sh") require.NoError(s.T(), os.WriteFile(script, []byte("#!/bin/sh\necho hello\necho world >&2\n"), 0o755)) require.NoError(s.T(), s.app.Rc("dotfiles")) require.Contains(s.T(), s.stdout.String(), "hello") require.Contains(s.T(), s.stderr.String(), "world") } // TestRc_ScriptsRunWithCwdSetToCastleRoot verifies scripts execute with the // castle root as the working directory. func (s *RcSuite) TestRc_ScriptsRunWithCwdSetToCastleRoot() { castleRoot := s.createCastle("dotfiles") homesickD := filepath.Join(castleRoot, ".homesick.d") require.NoError(s.T(), os.MkdirAll(homesickD, 0o755)) script := filepath.Join(homesickD, "10_pwd.sh") require.NoError(s.T(), os.WriteFile(script, []byte("#!/bin/sh\npwd\n"), 0o755)) require.NoError(s.T(), s.app.Rc("dotfiles")) require.Contains(s.T(), s.stdout.String(), castleRoot) }