mirror of
https://github.com/cheat/cheat.git
synced 2026-03-07 03:03:32 +01:00
chore: housekeeping and refactoring (bump to 4.7.1)
- Remove unused parameters, dead files, and inaccurate doc.go files - Extract shared helpers, eliminate duplication - Rename cheatpath.Cheatpath to cheatpath.Path - Optimize filesystem walks (WalkDir, skip .git) - Move sheet name validation to sheet.Validate - Move integration tests to test/integration/ - Consolidate internal/mock into mocks/ - Move fuzz.sh to test/ - Inline loadSheets helper into command callers - Extract config.New into its own file - Fix stale references in HACKING.md and CLAUDE.md - Restore plan9 build target - Remove redundant and low-value tests - Clean up project documentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
129
test/integration/brief_integration_test.go
Normal file
129
test/integration/brief_integration_test.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestBriefFlagIntegration exercises the -b/--brief flag end-to-end.
|
||||
func TestBriefFlagIntegration(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("integration test uses Unix-specific env vars")
|
||||
}
|
||||
|
||||
// Build the cheat binary once for all sub-tests.
|
||||
binPath := filepath.Join(t.TempDir(), "cheat_test")
|
||||
build := exec.Command("go", "build", "-o", binPath, "./cmd/cheat")
|
||||
build.Dir = repoRoot(t)
|
||||
if output, err := build.CombinedOutput(); err != nil {
|
||||
t.Fatalf("failed to build cheat: %v\nOutput: %s", err, output)
|
||||
}
|
||||
|
||||
// Set up a temp environment with some cheatsheets.
|
||||
root := t.TempDir()
|
||||
sheetsDir := filepath.Join(root, "sheets")
|
||||
os.MkdirAll(sheetsDir, 0755)
|
||||
|
||||
os.WriteFile(
|
||||
filepath.Join(sheetsDir, "tar"),
|
||||
[]byte("---\nsyntax: bash\ntags: [ compression ]\n---\ntar xf archive.tar\n"),
|
||||
0644,
|
||||
)
|
||||
os.WriteFile(
|
||||
filepath.Join(sheetsDir, "curl"),
|
||||
[]byte("---\nsyntax: bash\ntags: [ networking, http ]\n---\ncurl https://example.com\n"),
|
||||
0644,
|
||||
)
|
||||
|
||||
confPath := filepath.Join(root, "conf.yml")
|
||||
conf := fmt.Sprintf("---\neditor: vi\ncolorize: false\ncheatpaths:\n - name: test\n path: %s\n readonly: true\n", sheetsDir)
|
||||
os.WriteFile(confPath, []byte(conf), 0644)
|
||||
|
||||
env := []string{
|
||||
"CHEAT_CONFIG_PATH=" + confPath,
|
||||
"HOME=" + root,
|
||||
"PATH=" + os.Getenv("PATH"),
|
||||
"EDITOR=vi",
|
||||
}
|
||||
|
||||
run := func(t *testing.T, args ...string) string {
|
||||
t.Helper()
|
||||
cmd := exec.Command(binPath, args...)
|
||||
cmd.Dir = root
|
||||
cmd.Env = env
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("cheat %v failed: %v\nOutput: %s", args, err, output)
|
||||
}
|
||||
return string(output)
|
||||
}
|
||||
|
||||
t.Run("brief output omits file path column", func(t *testing.T) {
|
||||
output := run(t, "-b")
|
||||
lines := strings.Split(strings.TrimSpace(output), "\n")
|
||||
|
||||
// Header should have title and tags but not file
|
||||
if !strings.Contains(lines[0], "title:") {
|
||||
t.Errorf("expected title: in header, got: %s", lines[0])
|
||||
}
|
||||
if !strings.Contains(lines[0], "tags:") {
|
||||
t.Errorf("expected tags: in header, got: %s", lines[0])
|
||||
}
|
||||
if strings.Contains(lines[0], "file:") {
|
||||
t.Errorf("brief output should not contain file: column, got: %s", lines[0])
|
||||
}
|
||||
|
||||
// Data lines should not contain the sheets directory path
|
||||
for _, line := range lines[1:] {
|
||||
if strings.Contains(line, sheetsDir) {
|
||||
t.Errorf("brief output should not contain file paths, got: %s", line)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("list output still includes file path column", func(t *testing.T) {
|
||||
output := run(t, "-l")
|
||||
lines := strings.Split(strings.TrimSpace(output), "\n")
|
||||
|
||||
if !strings.Contains(lines[0], "file:") {
|
||||
t.Errorf("list output should contain file: column, got: %s", lines[0])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("brief with filter works", func(t *testing.T) {
|
||||
output := run(t, "-b", "tar")
|
||||
if !strings.Contains(output, "tar") {
|
||||
t.Errorf("expected tar in output, got: %s", output)
|
||||
}
|
||||
if strings.Contains(output, "curl") {
|
||||
t.Errorf("filter should exclude curl, got: %s", output)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("combined -lb works identically to -b", func(t *testing.T) {
|
||||
briefOnly := run(t, "-b", "tar")
|
||||
combined := run(t, "-lb", "tar")
|
||||
if briefOnly != combined {
|
||||
t.Errorf("-b and -lb should produce identical output\n-b:\n%s\n-lb:\n%s", briefOnly, combined)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("brief with tag filter works", func(t *testing.T) {
|
||||
output := run(t, "-b", "-t", "networking")
|
||||
if !strings.Contains(output, "curl") {
|
||||
t.Errorf("expected curl in tag-filtered output, got: %s", output)
|
||||
}
|
||||
if strings.Contains(output, "tar") {
|
||||
// tar is tagged "compression", not "networking"
|
||||
t.Errorf("tag filter should exclude tar, got: %s", output)
|
||||
}
|
||||
if strings.Contains(output, "file:") {
|
||||
t.Errorf("brief output should not contain file: column, got: %s", output)
|
||||
}
|
||||
})
|
||||
}
|
||||
246
test/integration/cheatpath_integration_test.go
Normal file
246
test/integration/cheatpath_integration_test.go
Normal file
@@ -0,0 +1,246 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// hasCwdCheatpath checks whether the --directories output contains a
|
||||
// cheatpath named "cwd". The output format is "name: path\n" per line
|
||||
// (tabwriter-aligned), so we look for a line beginning with "cwd".
|
||||
func hasCwdCheatpath(output string) bool {
|
||||
for _, line := range strings.Split(output, "\n") {
|
||||
if strings.HasPrefix(line, "cwd") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TestLocalCheatpathIntegration exercises the recursive .cheat directory
|
||||
// discovery end-to-end: it builds the real cheat binary, sets up filesystem
|
||||
// layouts, and verifies behaviour from the user's perspective.
|
||||
func TestLocalCheatpathIntegration(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("integration test uses Unix-specific env vars")
|
||||
}
|
||||
|
||||
// Build the cheat binary once for all sub-tests.
|
||||
binPath := filepath.Join(t.TempDir(), "cheat_test")
|
||||
build := exec.Command("go", "build", "-o", binPath, "./cmd/cheat")
|
||||
build.Dir = repoRoot(t)
|
||||
if output, err := build.CombinedOutput(); err != nil {
|
||||
t.Fatalf("failed to build cheat: %v\nOutput: %s", err, output)
|
||||
}
|
||||
|
||||
// cheatEnv returns a minimal environment for the cheat binary.
|
||||
cheatEnv := func(confPath, home string) []string {
|
||||
return []string{
|
||||
"CHEAT_CONFIG_PATH=" + confPath,
|
||||
"HOME=" + home,
|
||||
"PATH=" + os.Getenv("PATH"),
|
||||
"EDITOR=vi",
|
||||
}
|
||||
}
|
||||
|
||||
// writeConfig writes a minimal valid config file referencing sheetsDir.
|
||||
writeConfig := func(t *testing.T, dir, sheetsDir string) string {
|
||||
t.Helper()
|
||||
conf := fmt.Sprintf("---\neditor: vi\ncolorize: false\ncheatpaths:\n - name: base\n path: %s\n readonly: true\n", sheetsDir)
|
||||
confPath := filepath.Join(dir, "conf.yml")
|
||||
if err := os.WriteFile(confPath, []byte(conf), 0644); err != nil {
|
||||
t.Fatalf("failed to write config: %v", err)
|
||||
}
|
||||
return confPath
|
||||
}
|
||||
|
||||
t.Run("parent .cheat is discovered from subdirectory", func(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
|
||||
// Configured cheatpath (empty but must exist for validation)
|
||||
sheetsDir := filepath.Join(root, "sheets")
|
||||
os.MkdirAll(sheetsDir, 0755)
|
||||
|
||||
// .cheat at root with a cheatsheet
|
||||
dotCheat := filepath.Join(root, ".cheat")
|
||||
os.Mkdir(dotCheat, 0755)
|
||||
os.WriteFile(
|
||||
filepath.Join(dotCheat, "localsheet"),
|
||||
[]byte("---\nsyntax: bash\n---\necho hello from local\n"),
|
||||
0644,
|
||||
)
|
||||
|
||||
confPath := writeConfig(t, root, sheetsDir)
|
||||
|
||||
// Work from a subdirectory
|
||||
workDir := filepath.Join(root, "src", "pkg")
|
||||
os.MkdirAll(workDir, 0755)
|
||||
env := cheatEnv(confPath, root)
|
||||
|
||||
// --directories should list "cwd" cheatpath
|
||||
cmd := exec.Command(binPath, "--directories")
|
||||
cmd.Dir = workDir
|
||||
cmd.Env = env
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("cheat --directories failed: %v\nOutput: %s", err, output)
|
||||
}
|
||||
if !hasCwdCheatpath(string(output)) {
|
||||
t.Errorf("expected 'cwd' cheatpath in --directories output:\n%s", output)
|
||||
}
|
||||
|
||||
// Viewing the cheatsheet should show its content
|
||||
cmd2 := exec.Command(binPath, "localsheet")
|
||||
cmd2.Dir = workDir
|
||||
cmd2.Env = env
|
||||
output2, err := cmd2.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("cheat localsheet failed: %v\nOutput: %s", err, output2)
|
||||
}
|
||||
if !strings.Contains(string(output2), "echo hello from local") {
|
||||
t.Errorf("expected cheatsheet content, got:\n%s", output2)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("grandparent .cheat is discovered from deep subdirectory", func(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
|
||||
sheetsDir := filepath.Join(root, "sheets")
|
||||
os.MkdirAll(sheetsDir, 0755)
|
||||
|
||||
dotCheat := filepath.Join(root, ".cheat")
|
||||
os.Mkdir(dotCheat, 0755)
|
||||
os.WriteFile(
|
||||
filepath.Join(dotCheat, "deepsheet"),
|
||||
[]byte("---\nsyntax: bash\n---\ndeep discovery works\n"),
|
||||
0644,
|
||||
)
|
||||
|
||||
confPath := writeConfig(t, root, sheetsDir)
|
||||
|
||||
deepDir := filepath.Join(root, "a", "b", "c", "d", "e")
|
||||
os.MkdirAll(deepDir, 0755)
|
||||
|
||||
cmd := exec.Command(binPath, "deepsheet")
|
||||
cmd.Dir = deepDir
|
||||
cmd.Env = cheatEnv(confPath, root)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("cheat deepsheet failed: %v\nOutput: %s", err, output)
|
||||
}
|
||||
if !strings.Contains(string(output), "deep discovery works") {
|
||||
t.Errorf("expected cheatsheet content, got:\n%s", output)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("nearest .cheat wins over ancestor .cheat", func(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
|
||||
sheetsDir := filepath.Join(root, "sheets")
|
||||
os.MkdirAll(sheetsDir, 0755)
|
||||
|
||||
// .cheat at root
|
||||
rootCheat := filepath.Join(root, ".cheat")
|
||||
os.Mkdir(rootCheat, 0755)
|
||||
os.WriteFile(
|
||||
filepath.Join(rootCheat, "shared"),
|
||||
[]byte("---\nsyntax: bash\n---\nfrom root\n"),
|
||||
0644,
|
||||
)
|
||||
|
||||
// .cheat at project/ (nearer)
|
||||
projectDir := filepath.Join(root, "project")
|
||||
os.MkdirAll(projectDir, 0755)
|
||||
projectCheat := filepath.Join(projectDir, ".cheat")
|
||||
os.Mkdir(projectCheat, 0755)
|
||||
os.WriteFile(
|
||||
filepath.Join(projectCheat, "shared"),
|
||||
[]byte("---\nsyntax: bash\n---\nfrom project nearest\n"),
|
||||
0644,
|
||||
)
|
||||
|
||||
confPath := writeConfig(t, root, sheetsDir)
|
||||
|
||||
workDir := filepath.Join(projectDir, "src")
|
||||
os.MkdirAll(workDir, 0755)
|
||||
env := cheatEnv(confPath, root)
|
||||
|
||||
// --directories should list the nearer cheatpath
|
||||
cmd := exec.Command(binPath, "--directories")
|
||||
cmd.Dir = workDir
|
||||
cmd.Env = env
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("cheat --directories failed: %v\nOutput: %s", err, output)
|
||||
}
|
||||
if !strings.Contains(string(output), projectCheat) {
|
||||
t.Errorf("expected project .cheat path in output, got:\n%s", output)
|
||||
}
|
||||
|
||||
// "shared" sheet should come from the nearer .cheat
|
||||
cmd2 := exec.Command(binPath, "shared")
|
||||
cmd2.Dir = workDir
|
||||
cmd2.Env = env
|
||||
output2, err := cmd2.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("cheat shared failed: %v\nOutput: %s", err, output2)
|
||||
}
|
||||
if !strings.Contains(string(output2), "from project nearest") {
|
||||
t.Errorf("expected nearest .cheat content, got:\n%s", output2)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no .cheat directory means no cwd cheatpath", func(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
|
||||
sheetsDir := filepath.Join(root, "sheets")
|
||||
os.MkdirAll(sheetsDir, 0755)
|
||||
// Need at least one sheet for --directories to work without error
|
||||
os.WriteFile(filepath.Join(sheetsDir, "placeholder"),
|
||||
[]byte("---\nsyntax: bash\n---\nplaceholder\n"), 0644)
|
||||
|
||||
confPath := writeConfig(t, root, sheetsDir)
|
||||
|
||||
// No .cheat anywhere under root
|
||||
cmd := exec.Command(binPath, "--directories")
|
||||
cmd.Dir = root
|
||||
cmd.Env = cheatEnv(confPath, root)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("cheat --directories failed: %v\nOutput: %s", err, output)
|
||||
}
|
||||
if hasCwdCheatpath(string(output)) {
|
||||
t.Errorf("'cwd' cheatpath should not appear when no .cheat exists:\n%s", output)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run(".cheat file (not directory) is ignored", func(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
|
||||
sheetsDir := filepath.Join(root, "sheets")
|
||||
os.MkdirAll(sheetsDir, 0755)
|
||||
os.WriteFile(filepath.Join(sheetsDir, "placeholder"),
|
||||
[]byte("---\nsyntax: bash\n---\nplaceholder\n"), 0644)
|
||||
|
||||
// Create .cheat as a regular file
|
||||
os.WriteFile(filepath.Join(root, ".cheat"), []byte("not a dir"), 0644)
|
||||
|
||||
confPath := writeConfig(t, root, sheetsDir)
|
||||
|
||||
cmd := exec.Command(binPath, "--directories")
|
||||
cmd.Dir = root
|
||||
cmd.Env = cheatEnv(confPath, root)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("cheat --directories failed: %v\nOutput: %s", err, output)
|
||||
}
|
||||
if hasCwdCheatpath(string(output)) {
|
||||
t.Errorf("'cwd' should not appear for a .cheat file:\n%s", output)
|
||||
}
|
||||
})
|
||||
}
|
||||
305
test/integration/first_run_integration_test.go
Normal file
305
test/integration/first_run_integration_test.go
Normal file
@@ -0,0 +1,305 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestFirstRunIntegration exercises the end-to-end first-run experience:
|
||||
// no config exists, the binary creates one, and subsequent runs succeed.
|
||||
// This is the regression test for issues #721, #771, and #730.
|
||||
func TestFirstRunIntegration(t *testing.T) {
|
||||
// Build the cheat binary
|
||||
binName := "cheat_test"
|
||||
if runtime.GOOS == "windows" {
|
||||
binName += ".exe"
|
||||
}
|
||||
binPath := filepath.Join(t.TempDir(), binName)
|
||||
build := exec.Command("go", "build", "-o", binPath, "./cmd/cheat")
|
||||
build.Dir = repoRoot(t)
|
||||
if output, err := build.CombinedOutput(); err != nil {
|
||||
t.Fatalf("failed to build cheat: %v\nOutput: %s", err, output)
|
||||
}
|
||||
|
||||
t.Run("init comments out community", func(t *testing.T) {
|
||||
testHome := t.TempDir()
|
||||
env := firstRunEnv(testHome)
|
||||
|
||||
cmd := exec.Command(binPath, "--init")
|
||||
cmd.Env = env
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("--init failed: %v\nOutput: %s", err, output)
|
||||
}
|
||||
outStr := string(output)
|
||||
|
||||
// No placeholder strings should survive (regression for #721)
|
||||
assertNoPlaceholders(t, outStr)
|
||||
|
||||
// Community cheatpath should be commented out
|
||||
assertCommunityCommentedOut(t, outStr)
|
||||
|
||||
// Personal and work cheatpaths should be active (uncommented)
|
||||
assertCheatpathActive(t, outStr, "personal")
|
||||
assertCheatpathActive(t, outStr, "work")
|
||||
|
||||
// Should include clone instructions
|
||||
if !strings.Contains(outStr, "git clone") {
|
||||
t.Error("expected git clone instructions in --init output")
|
||||
}
|
||||
|
||||
// Save the config and verify it loads without errors.
|
||||
// --init only outputs config, it doesn't create directories,
|
||||
// so we need to create the cheatpath dirs the config references.
|
||||
confpath := filepath.Join(testHome, "conf.yml")
|
||||
if err := os.WriteFile(confpath, output, 0644); err != nil {
|
||||
t.Fatalf("failed to write config: %v", err)
|
||||
}
|
||||
|
||||
// Determine the confdir that --init used (same logic as cmd_init.go)
|
||||
initConfpaths := firstRunConfpaths(testHome)
|
||||
initConfdir := filepath.Dir(initConfpaths[0])
|
||||
for _, name := range []string{"personal", "work"} {
|
||||
dir := filepath.Join(initConfdir, "cheatsheets", name)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
t.Fatalf("failed to create %s dir: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
cmd2 := exec.Command(binPath, "--directories")
|
||||
cmd2.Env = append(append([]string{}, env...), "CHEAT_CONFIG_PATH="+confpath)
|
||||
output2, err := cmd2.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("config from --init failed to load: %v\nOutput: %s", err, output2)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("decline config creation", func(t *testing.T) {
|
||||
testHome := t.TempDir()
|
||||
env := firstRunEnv(testHome)
|
||||
|
||||
cmd := exec.Command(binPath)
|
||||
cmd.Env = env
|
||||
cmd.Stdin = strings.NewReader("n\n")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("cheat exited with error: %v\nOutput: %s", err, output)
|
||||
}
|
||||
|
||||
// Verify no config was created
|
||||
if firstRunConfigExists(testHome) {
|
||||
t.Error("config file was created despite user declining")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("accept config decline community", func(t *testing.T) {
|
||||
testHome := t.TempDir()
|
||||
env := firstRunEnv(testHome)
|
||||
|
||||
// First run: yes to create config, no to community cheatsheets
|
||||
cmd := exec.Command(binPath)
|
||||
cmd.Env = env
|
||||
cmd.Stdin = strings.NewReader("y\nn\n")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("first run failed: %v\nOutput: %s", err, output)
|
||||
}
|
||||
outStr := string(output)
|
||||
|
||||
// Parse the config path from output
|
||||
confpath := parseCreatedConfPath(t, outStr)
|
||||
if confpath == "" {
|
||||
t.Fatalf("could not find config path in output:\n%s", outStr)
|
||||
}
|
||||
|
||||
// Verify config file exists
|
||||
if _, err := os.Stat(confpath); os.IsNotExist(err) {
|
||||
t.Fatalf("config file not found at %s", confpath)
|
||||
}
|
||||
|
||||
// Verify config file contents
|
||||
content, err := os.ReadFile(confpath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read config: %v", err)
|
||||
}
|
||||
contentStr := string(content)
|
||||
|
||||
// No placeholder strings should survive (regression for #721)
|
||||
assertNoPlaceholders(t, contentStr)
|
||||
|
||||
// Community cheatpath should be commented out
|
||||
assertCommunityCommentedOut(t, contentStr)
|
||||
|
||||
// Personal and work cheatpaths should be active (uncommented)
|
||||
assertCheatpathActive(t, contentStr, "personal")
|
||||
assertCheatpathActive(t, contentStr, "work")
|
||||
|
||||
// Verify personal and work directories were created
|
||||
confdir := filepath.Dir(confpath)
|
||||
for _, name := range []string{"personal", "work"} {
|
||||
dir := filepath.Join(confdir, "cheatsheets", name)
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
t.Errorf("expected %s directory at %s", name, dir)
|
||||
}
|
||||
}
|
||||
|
||||
// Community directory should NOT exist
|
||||
communityDir := filepath.Join(confdir, "cheatsheets", "community")
|
||||
if _, err := os.Stat(communityDir); err == nil {
|
||||
t.Error("community directory should not exist when declined")
|
||||
}
|
||||
|
||||
// --- Second run: verify the config loads successfully ---
|
||||
// This is the core regression test for #721/#771/#730:
|
||||
// previously, the second run would fail because config.New()
|
||||
// hard-errored on the missing community cheatpath directory.
|
||||
// Use --directories (not --list, which exits 2 when no sheets exist).
|
||||
cmd2 := exec.Command(binPath, "--directories")
|
||||
cmd2.Env = append(append([]string{}, env...), "CHEAT_CONFIG_PATH="+confpath)
|
||||
output2, err := cmd2.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf(
|
||||
"second run failed (regression for #721/#771/#730): %v\nOutput: %s",
|
||||
err, output2,
|
||||
)
|
||||
}
|
||||
|
||||
// Verify the output lists the expected cheatpaths
|
||||
outStr2 := string(output2)
|
||||
if !strings.Contains(outStr2, "personal") {
|
||||
t.Errorf("expected 'personal' cheatpath in --directories output:\n%s", outStr2)
|
||||
}
|
||||
if !strings.Contains(outStr2, "work") {
|
||||
t.Errorf("expected 'work' cheatpath in --directories output:\n%s", outStr2)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// firstRunEnv returns a minimal environment for a clean first-run test.
|
||||
func firstRunEnv(home string) []string {
|
||||
env := []string{
|
||||
"PATH=" + os.Getenv("PATH"),
|
||||
}
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
env = append(env,
|
||||
"APPDATA="+filepath.Join(home, "AppData", "Roaming"),
|
||||
"USERPROFILE="+home,
|
||||
"SystemRoot="+os.Getenv("SystemRoot"),
|
||||
)
|
||||
default:
|
||||
env = append(env,
|
||||
"HOME="+home,
|
||||
"EDITOR=vi",
|
||||
)
|
||||
}
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
// parseCreatedConfPath extracts the config file path from the installer's
|
||||
// "Created config file: <path>" output. The message may appear mid-line
|
||||
// (after prompt text), so we search for the substring anywhere in the output.
|
||||
func parseCreatedConfPath(t *testing.T, output string) string {
|
||||
t.Helper()
|
||||
const marker = "Created config file: "
|
||||
idx := strings.Index(output, marker)
|
||||
if idx < 0 {
|
||||
return ""
|
||||
}
|
||||
rest := output[idx+len(marker):]
|
||||
// the path ends at the next newline
|
||||
if nl := strings.IndexByte(rest, '\n'); nl >= 0 {
|
||||
rest = rest[:nl]
|
||||
}
|
||||
return strings.TrimSpace(rest)
|
||||
}
|
||||
|
||||
// firstRunConfpaths returns the config file paths that cheat would check
|
||||
// for the given home directory, matching the logic in config.Paths().
|
||||
func firstRunConfpaths(home string) []string {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
return []string{
|
||||
filepath.Join(home, "AppData", "Roaming", "cheat", "conf.yml"),
|
||||
}
|
||||
default:
|
||||
return []string{
|
||||
filepath.Join(home, ".config", "cheat", "conf.yml"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// assertNoPlaceholders verifies that no template placeholder strings survived
|
||||
// in the config output. This is the regression check for #721 (literal
|
||||
// PAGER_PATH appearing in the config).
|
||||
func assertNoPlaceholders(t *testing.T, content string) {
|
||||
t.Helper()
|
||||
placeholders := []string{
|
||||
"PAGER_PATH",
|
||||
"COMMUNITY_PATH",
|
||||
"PERSONAL_PATH",
|
||||
"WORK_PATH",
|
||||
}
|
||||
for _, p := range placeholders {
|
||||
if strings.Contains(content, p) {
|
||||
t.Errorf("placeholder %q was not replaced in config", p)
|
||||
}
|
||||
}
|
||||
// EDITOR_PATH is special: it survives if no editor is found.
|
||||
// In our test env EDITOR=vi is set, so it should be replaced.
|
||||
if strings.Contains(content, "editor: EDITOR_PATH") {
|
||||
t.Error("placeholder EDITOR_PATH was not replaced in config")
|
||||
}
|
||||
}
|
||||
|
||||
// assertCommunityCommentedOut verifies that the community cheatpath entry
|
||||
// is commented out (not active) in the config.
|
||||
func assertCommunityCommentedOut(t *testing.T, content string) {
|
||||
t.Helper()
|
||||
for _, line := range strings.Split(content, "\n") {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if trimmed == "- name: community" {
|
||||
t.Error("community cheatpath should be commented out")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !strings.Contains(content, "#- name: community") {
|
||||
t.Error("expected commented-out community cheatpath")
|
||||
}
|
||||
}
|
||||
|
||||
// assertCheatpathActive verifies that a named cheatpath is present and
|
||||
// uncommented in the config.
|
||||
func assertCheatpathActive(t *testing.T, content string, name string) {
|
||||
t.Helper()
|
||||
marker := "- name: " + name
|
||||
for _, line := range strings.Split(content, "\n") {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if trimmed == marker {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Errorf("expected active (uncommented) cheatpath %q", name)
|
||||
}
|
||||
|
||||
// firstRunConfigExists checks whether a cheat config file exists under the
|
||||
// given home directory at any of the standard locations.
|
||||
func firstRunConfigExists(home string) bool {
|
||||
candidates := []string{
|
||||
filepath.Join(home, ".config", "cheat", "conf.yml"),
|
||||
filepath.Join(home, ".cheat", "conf.yml"),
|
||||
filepath.Join(home, "AppData", "Roaming", "cheat", "conf.yml"),
|
||||
}
|
||||
for _, p := range candidates {
|
||||
if _, err := os.Stat(p); err == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
30
test/integration/helpers_test.go
Normal file
30
test/integration/helpers_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// repoRoot returns the absolute path to the repository root.
|
||||
// It derives this from the known location of this source file
|
||||
// (test/integration/) relative to the repo root.
|
||||
func repoRoot(t *testing.T) string {
|
||||
t.Helper()
|
||||
_, file, _, ok := runtime.Caller(0)
|
||||
if !ok {
|
||||
t.Fatal("failed to determine repo root via runtime.Caller")
|
||||
}
|
||||
// file is <repo>/test/integration/helpers_test.go → go up two dirs
|
||||
return filepath.Dir(filepath.Dir(filepath.Dir(file)))
|
||||
}
|
||||
|
||||
// repoRootBench is the same as repoRoot but for use in benchmarks.
|
||||
func repoRootBench(b *testing.B) string {
|
||||
b.Helper()
|
||||
_, file, _, ok := runtime.Caller(0)
|
||||
if !ok {
|
||||
b.Fatal("failed to determine repo root via runtime.Caller")
|
||||
}
|
||||
return filepath.Dir(filepath.Dir(filepath.Dir(file)))
|
||||
}
|
||||
229
test/integration/path_traversal_integration_test.go
Normal file
229
test/integration/path_traversal_integration_test.go
Normal file
@@ -0,0 +1,229 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestPathTraversalIntegration tests that the cheat binary properly blocks
|
||||
// path traversal attempts when invoked as a subprocess.
|
||||
func TestPathTraversalIntegration(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("integration test uses Unix-specific env and tools")
|
||||
}
|
||||
|
||||
// Build the cheat binary
|
||||
binPath := filepath.Join(t.TempDir(), "cheat_test")
|
||||
build := exec.Command("go", "build", "-o", binPath, "./cmd/cheat")
|
||||
build.Dir = repoRoot(t)
|
||||
if output, err := build.CombinedOutput(); err != nil {
|
||||
t.Fatalf("Failed to build cheat: %v\nOutput: %s", err, output)
|
||||
}
|
||||
|
||||
// Set up test environment
|
||||
testDir := t.TempDir()
|
||||
sheetsDir := filepath.Join(testDir, "sheets")
|
||||
os.MkdirAll(sheetsDir, 0755)
|
||||
|
||||
// Create config
|
||||
config := fmt.Sprintf(`---
|
||||
editor: echo
|
||||
colorize: false
|
||||
pager: cat
|
||||
cheatpaths:
|
||||
- name: test
|
||||
path: %s
|
||||
readonly: false
|
||||
`, sheetsDir)
|
||||
|
||||
configPath := filepath.Join(testDir, "config.yml")
|
||||
if err := os.WriteFile(configPath, []byte(config), 0644); err != nil {
|
||||
t.Fatalf("Failed to write config: %v", err)
|
||||
}
|
||||
|
||||
// Test table
|
||||
tests := []struct {
|
||||
name string
|
||||
command []string
|
||||
wantFail bool
|
||||
wantMsg string
|
||||
}{
|
||||
// Blocked patterns
|
||||
{
|
||||
name: "block parent traversal edit",
|
||||
command: []string{"--edit", "../evil"},
|
||||
wantFail: true,
|
||||
wantMsg: "cannot contain '..'",
|
||||
},
|
||||
{
|
||||
name: "block absolute path edit",
|
||||
command: []string{"--edit", "/etc/passwd"},
|
||||
wantFail: true,
|
||||
wantMsg: "cannot be an absolute path",
|
||||
},
|
||||
{
|
||||
name: "block home dir edit",
|
||||
command: []string{"--edit", "~/.ssh/config"},
|
||||
wantFail: true,
|
||||
wantMsg: "cannot start with '~'",
|
||||
},
|
||||
{
|
||||
name: "block parent traversal remove",
|
||||
command: []string{"--rm", "../evil"},
|
||||
wantFail: true,
|
||||
wantMsg: "cannot contain '..'",
|
||||
},
|
||||
{
|
||||
name: "block complex traversal",
|
||||
command: []string{"--edit", "foo/../../bar"},
|
||||
wantFail: true,
|
||||
wantMsg: "cannot contain '..'",
|
||||
},
|
||||
{
|
||||
name: "block just dots",
|
||||
command: []string{"--edit", ".."},
|
||||
wantFail: true,
|
||||
wantMsg: "cannot contain '..'",
|
||||
},
|
||||
{
|
||||
name: "block empty name",
|
||||
command: []string{"--edit", ""},
|
||||
wantFail: true,
|
||||
wantMsg: "cannot be empty",
|
||||
},
|
||||
// Allowed patterns
|
||||
{
|
||||
name: "allow simple name",
|
||||
command: []string{"--edit", "docker"},
|
||||
wantFail: false,
|
||||
},
|
||||
{
|
||||
name: "allow nested name",
|
||||
command: []string{"--edit", "lang/go"},
|
||||
wantFail: false,
|
||||
},
|
||||
{
|
||||
name: "block hidden file",
|
||||
command: []string{"--edit", ".gitignore"},
|
||||
wantFail: true,
|
||||
wantMsg: "cannot start with '.'",
|
||||
},
|
||||
{
|
||||
name: "allow current dir",
|
||||
command: []string{"--edit", "./local"},
|
||||
wantFail: false,
|
||||
},
|
||||
}
|
||||
|
||||
// Run tests
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cmd := exec.Command(binPath, tc.command...)
|
||||
cmd.Env = []string{
|
||||
fmt.Sprintf("CHEAT_CONFIG_PATH=%s", configPath),
|
||||
fmt.Sprintf("HOME=%s", testDir),
|
||||
}
|
||||
output, err := cmd.CombinedOutput()
|
||||
|
||||
if tc.wantFail {
|
||||
if err == nil {
|
||||
t.Errorf("Expected failure but command succeeded. Output: %s", output)
|
||||
}
|
||||
if !strings.Contains(string(output), "invalid cheatsheet name") {
|
||||
t.Errorf("Expected 'invalid cheatsheet name' error, got: %s", output)
|
||||
}
|
||||
if tc.wantMsg != "" && !strings.Contains(string(output), tc.wantMsg) {
|
||||
t.Errorf("Expected message %q in output, got: %s", tc.wantMsg, output)
|
||||
}
|
||||
} else {
|
||||
// Command might fail for other reasons (e.g., editor not found)
|
||||
// but should NOT fail with "invalid cheatsheet name"
|
||||
if strings.Contains(string(output), "invalid cheatsheet name") {
|
||||
t.Errorf("Command incorrectly blocked. Output: %s", output)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestPathTraversalRealWorld tests with more realistic scenarios
|
||||
func TestPathTraversalRealWorld(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("integration test uses Unix-specific env and tools")
|
||||
}
|
||||
|
||||
// This test ensures our protection works with actual file operations
|
||||
|
||||
// Build cheat
|
||||
binPath := filepath.Join(t.TempDir(), "cheat_test")
|
||||
build := exec.Command("go", "build", "-o", binPath, "./cmd/cheat")
|
||||
build.Dir = repoRoot(t)
|
||||
if output, err := build.CombinedOutput(); err != nil {
|
||||
t.Fatalf("Failed to build: %v\n%s", err, output)
|
||||
}
|
||||
|
||||
// Create test structure
|
||||
testRoot := t.TempDir()
|
||||
sheetsDir := filepath.Join(testRoot, "cheatsheets")
|
||||
secretDir := filepath.Join(testRoot, "secrets")
|
||||
os.MkdirAll(sheetsDir, 0755)
|
||||
os.MkdirAll(secretDir, 0755)
|
||||
|
||||
// Create a "secret" file that should not be accessible
|
||||
secretFile := filepath.Join(secretDir, "secret.txt")
|
||||
os.WriteFile(secretFile, []byte("SECRET DATA"), 0644)
|
||||
|
||||
// Create config using vim in non-interactive mode
|
||||
config := fmt.Sprintf(`---
|
||||
editor: vim -u NONE -n --cmd "set noswapfile" --cmd "wq"
|
||||
colorize: false
|
||||
pager: cat
|
||||
cheatpaths:
|
||||
- name: personal
|
||||
path: %s
|
||||
readonly: false
|
||||
`, sheetsDir)
|
||||
|
||||
configPath := filepath.Join(testRoot, "config.yml")
|
||||
os.WriteFile(configPath, []byte(config), 0644)
|
||||
|
||||
// Test 1: Try to edit a file outside cheatsheets using traversal
|
||||
cmd := exec.Command(binPath, "--edit", "../secrets/secret")
|
||||
cmd.Env = []string{
|
||||
fmt.Sprintf("CHEAT_CONFIG_PATH=%s", configPath),
|
||||
fmt.Sprintf("HOME=%s", testRoot),
|
||||
}
|
||||
output, err := cmd.CombinedOutput()
|
||||
|
||||
if err == nil || !strings.Contains(string(output), "invalid cheatsheet name") {
|
||||
t.Errorf("Path traversal was not blocked! Output: %s", output)
|
||||
}
|
||||
|
||||
// Test 2: Verify the secret file is still intact
|
||||
content, _ := os.ReadFile(secretFile)
|
||||
if string(content) != "SECRET DATA" {
|
||||
t.Errorf("Secret file was modified!")
|
||||
}
|
||||
|
||||
// Test 3: Verify no files were created outside sheets directory
|
||||
err = filepath.Walk(testRoot, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() &&
|
||||
path != configPath &&
|
||||
path != secretFile &&
|
||||
!strings.HasPrefix(path, sheetsDir) {
|
||||
t.Errorf("File created outside allowed directory: %s", path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Walk error: %v", err)
|
||||
}
|
||||
}
|
||||
196
test/integration/search_bench_test.go
Normal file
196
test/integration/search_bench_test.go
Normal file
@@ -0,0 +1,196 @@
|
||||
//go:build integration
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
)
|
||||
|
||||
// BenchmarkSearchCommand benchmarks the actual cheat search command
|
||||
func BenchmarkSearchCommand(b *testing.B) {
|
||||
root := repoRootBench(b)
|
||||
|
||||
// Build the cheat binary in .tmp (using absolute path)
|
||||
tmpDir := filepath.Join(root, ".tmp", "bench-test")
|
||||
if err := os.MkdirAll(tmpDir, 0755); err != nil {
|
||||
b.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
|
||||
cheatBin := filepath.Join(tmpDir, "cheat-bench")
|
||||
|
||||
// Clean up the binary when done
|
||||
b.Cleanup(func() {
|
||||
os.Remove(cheatBin)
|
||||
})
|
||||
|
||||
cmd := exec.Command("go", "build", "-o", cheatBin, "./cmd/cheat")
|
||||
cmd.Dir = root
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
b.Fatalf("Failed to build cheat: %v\nOutput: %s", err, output)
|
||||
}
|
||||
|
||||
// Set up test environment in .tmp
|
||||
configDir := filepath.Join(tmpDir, "config")
|
||||
cheatsheetDir := filepath.Join(configDir, "cheatsheets", "community")
|
||||
|
||||
// Clone community cheatsheets (or reuse if already exists)
|
||||
if _, err := os.Stat(cheatsheetDir); os.IsNotExist(err) {
|
||||
b.Logf("Cloning community cheatsheets to %s...", cheatsheetDir)
|
||||
_, err := git.PlainClone(cheatsheetDir, false, &git.CloneOptions{
|
||||
URL: "https://github.com/cheat/cheatsheets.git",
|
||||
Depth: 1,
|
||||
SingleBranch: true,
|
||||
ReferenceName: plumbing.ReferenceName("refs/heads/master"),
|
||||
Progress: nil,
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to clone cheatsheets: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a minimal config file
|
||||
configFile := filepath.Join(configDir, "conf.yml")
|
||||
configContent := fmt.Sprintf(`---
|
||||
cheatpaths:
|
||||
- name: community
|
||||
path: %s
|
||||
tags: [ community ]
|
||||
readonly: true
|
||||
`, cheatsheetDir)
|
||||
|
||||
if err := os.MkdirAll(configDir, 0755); err != nil {
|
||||
b.Fatalf("Failed to create config dir: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(configFile, []byte(configContent), 0644); err != nil {
|
||||
b.Fatalf("Failed to write config: %v", err)
|
||||
}
|
||||
|
||||
// Set environment to use our config
|
||||
env := append(os.Environ(),
|
||||
fmt.Sprintf("CHEAT_CONFIG_PATH=%s", configFile),
|
||||
)
|
||||
|
||||
// Define test cases
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
}{
|
||||
{"SimpleSearch", []string{"-s", "echo"}},
|
||||
{"RegexSearch", []string{"-r", "-s", "^#.*example"}},
|
||||
{"ColorizedSearch", []string{"-c", "-s", "grep"}},
|
||||
{"ComplexRegex", []string{"-r", "-s", "(git|hg|svn)\\s+(add|commit|push)"}},
|
||||
{"AllCheatpaths", []string{"-a", "-s", "list"}},
|
||||
}
|
||||
|
||||
// Warm up - run once to ensure everything is loaded
|
||||
warmupCmd := exec.Command(cheatBin, "-l")
|
||||
warmupCmd.Env = env
|
||||
warmupCmd.Run()
|
||||
|
||||
// Run benchmarks
|
||||
for _, tc := range testCases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
// Reset timer to exclude setup
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
cmd := exec.Command(cheatBin, tc.args...)
|
||||
cmd.Env = env
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
b.Fatalf("Command failed: %v\nStderr: %s", err, stderr.String())
|
||||
}
|
||||
|
||||
if stdout.Len() == 0 {
|
||||
b.Fatal("No output from search")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkListCommand benchmarks the list command for comparison
|
||||
func BenchmarkListCommand(b *testing.B) {
|
||||
root := repoRootBench(b)
|
||||
|
||||
// Build the cheat binary in .tmp (using absolute path)
|
||||
tmpDir := filepath.Join(root, ".tmp", "bench-test")
|
||||
if err := os.MkdirAll(tmpDir, 0755); err != nil {
|
||||
b.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
|
||||
cheatBin := filepath.Join(tmpDir, "cheat-bench")
|
||||
|
||||
// Clean up the binary when done
|
||||
b.Cleanup(func() {
|
||||
os.Remove(cheatBin)
|
||||
})
|
||||
|
||||
cmd := exec.Command("go", "build", "-o", cheatBin, "./cmd/cheat")
|
||||
cmd.Dir = root
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
b.Fatalf("Failed to build cheat: %v\nOutput: %s", err, output)
|
||||
}
|
||||
|
||||
// Set up test environment (simplified - reuse if possible)
|
||||
configDir := filepath.Join(tmpDir, "config")
|
||||
cheatsheetDir := filepath.Join(configDir, "cheatsheets", "community")
|
||||
|
||||
// Check if we need to clone
|
||||
if _, err := os.Stat(cheatsheetDir); os.IsNotExist(err) {
|
||||
_, err := git.PlainClone(cheatsheetDir, false, &git.CloneOptions{
|
||||
URL: "https://github.com/cheat/cheatsheets.git",
|
||||
Depth: 1,
|
||||
SingleBranch: true,
|
||||
ReferenceName: plumbing.ReferenceName("refs/heads/master"),
|
||||
Progress: nil,
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to clone cheatsheets: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Create config
|
||||
configFile := filepath.Join(configDir, "conf.yml")
|
||||
configContent := fmt.Sprintf(`---
|
||||
cheatpaths:
|
||||
- name: community
|
||||
path: %s
|
||||
tags: [ community ]
|
||||
readonly: true
|
||||
`, cheatsheetDir)
|
||||
|
||||
os.MkdirAll(configDir, 0755)
|
||||
os.WriteFile(configFile, []byte(configContent), 0644)
|
||||
|
||||
env := append(os.Environ(),
|
||||
fmt.Sprintf("CHEAT_CONFIG_PATH=%s", configFile),
|
||||
)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
cmd := exec.Command(cheatBin, "-l")
|
||||
cmd.Env = env
|
||||
|
||||
var stdout bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
b.Fatalf("Command failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user