238 lines
8.3 KiB
Go
238 lines
8.3 KiB
Go
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_HomesickrcRequiresForce ensures legacy .homesickrc does not run
|
|
// unless force mode is enabled.
|
|
func (s *RcSuite) TestRc_HomesickrcRequiresForce() {
|
|
castleRoot := s.createCastle("dotfiles")
|
|
homesickRc := filepath.Join(castleRoot, ".homesickrc")
|
|
require.NoError(s.T(), os.WriteFile(homesickRc, []byte("# ruby setup code\n"), 0o644))
|
|
|
|
err := s.app.Rc("dotfiles")
|
|
require.Error(s.T(), err)
|
|
require.Contains(s.T(), err.Error(), "--force")
|
|
require.NoFileExists(s.T(), filepath.Join(castleRoot, ".homesick.d", "parity.rb"))
|
|
}
|
|
|
|
// TestRc_HomesickrcRunsWithForce ensures legacy .homesickrc handling proceeds
|
|
// when force mode is enabled.
|
|
func (s *RcSuite) TestRc_HomesickrcRunsWithForce() {
|
|
castleRoot := s.createCastle("dotfiles")
|
|
homesickRc := filepath.Join(castleRoot, ".homesickrc")
|
|
require.NoError(s.T(), os.WriteFile(homesickRc, []byte("# ruby setup code\n"), 0o644))
|
|
|
|
s.app.Force = true
|
|
require.NoError(s.T(), s.app.Rc("dotfiles"))
|
|
require.FileExists(s.T(), filepath.Join(castleRoot, ".homesick.d", "parity.rb"))
|
|
}
|
|
|
|
// 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 called parity.rb 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))
|
|
s.app.Force = true
|
|
|
|
require.NoError(s.T(), s.app.Rc("dotfiles"))
|
|
|
|
wrapperPath := filepath.Join(castleRoot, ".homesick.d", "parity.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_HomesickrcWrapperNotOverwrittenIfExists verifies that an existing
|
|
// parity.rb is not overwritten when Rc is called again.
|
|
func (s *RcSuite) TestRc_HomesickrcWrapperNotOverwrittenIfExists() {
|
|
castleRoot := s.createCastle("dotfiles")
|
|
homesickRc := filepath.Join(castleRoot, ".homesickrc")
|
|
require.NoError(s.T(), os.WriteFile(homesickRc, []byte("# ruby setup code\n"), 0o644))
|
|
s.app.Force = true
|
|
|
|
homesickD := filepath.Join(castleRoot, ".homesick.d")
|
|
require.NoError(s.T(), os.MkdirAll(homesickD, 0o755))
|
|
wrapperPath := filepath.Join(homesickD, "parity.rb")
|
|
originalContent := []byte("#!/bin/sh\n# pre-existing wrapper\n")
|
|
require.NoError(s.T(), os.WriteFile(wrapperPath, originalContent, 0o755))
|
|
|
|
require.NoError(s.T(), s.app.Rc("dotfiles"))
|
|
|
|
content, err := os.ReadFile(wrapperPath)
|
|
require.NoError(s.T(), err)
|
|
require.Equal(s.T(), originalContent, content, "existing parity.rb must not be overwritten")
|
|
}
|
|
|
|
// TestRc_HomesickrcWrapperCreatedBeforeExecution ensures parity.rb is present
|
|
// in .homesick.d before any scripts in that directory are executed.
|
|
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))
|
|
s.app.Force = true
|
|
|
|
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, "parity.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)
|
|
}
|