diff --git a/beads_hash_multiclone_test.go b/beads_hash_multiclone_test.go index c40d0439..ecf00528 100644 --- a/beads_hash_multiclone_test.go +++ b/beads_hash_multiclone_test.go @@ -45,18 +45,18 @@ func TestHashIDs_MultiCloneConverge(t *testing.T) { // Sync in sequence: A -> B -> C t.Log("Clone A syncing") - runCmdWithEnv(t, cloneA, map[string]string{"BEADS_NO_DAEMON": "1"}, "./bd", "sync") + runCmdWithEnv(t, cloneA, map[string]string{"BEADS_NO_DAEMON": "1"}, bdPath, "sync") t.Log("Clone B syncing") - runCmdOutputWithEnvAllowError(t, cloneB, map[string]string{"BEADS_NO_DAEMON": "1"}, true, "./bd", "sync") + runCmdOutputWithEnvAllowError(t, cloneB, map[string]string{"BEADS_NO_DAEMON": "1"}, true, bdPath, "sync") t.Log("Clone C syncing") - runCmdOutputWithEnvAllowError(t, cloneC, map[string]string{"BEADS_NO_DAEMON": "1"}, true, "./bd", "sync") + runCmdOutputWithEnvAllowError(t, cloneC, map[string]string{"BEADS_NO_DAEMON": "1"}, true, bdPath, "sync") // Do multiple sync rounds to ensure convergence (issues propagate step-by-step) for round := 0; round < 3; round++ { for _, clone := range []string{cloneA, cloneB, cloneC} { - runCmdOutputWithEnvAllowError(t, clone, map[string]string{"BEADS_NO_DAEMON": "1"}, true, "./bd", "sync") + runCmdOutputWithEnvAllowError(t, clone, map[string]string{"BEADS_NO_DAEMON": "1"}, true, bdPath, "sync") } } @@ -107,15 +107,15 @@ func TestHashIDs_IdenticalContentDedup(t *testing.T) { // Sync both t.Log("Clone A syncing") - runCmdWithEnv(t, cloneA, map[string]string{"BEADS_NO_DAEMON": "1"}, "./bd", "sync") + runCmdWithEnv(t, cloneA, map[string]string{"BEADS_NO_DAEMON": "1"}, bdPath, "sync") t.Log("Clone B syncing") - runCmdOutputWithEnvAllowError(t, cloneB, map[string]string{"BEADS_NO_DAEMON": "1"}, true, "./bd", "sync") + runCmdOutputWithEnvAllowError(t, cloneB, map[string]string{"BEADS_NO_DAEMON": "1"}, true, bdPath, "sync") // Do multiple sync rounds to ensure convergence for round := 0; round < 2; round++ { for _, clone := range []string{cloneA, cloneB} { - runCmdOutputWithEnvAllowError(t, clone, map[string]string{"BEADS_NO_DAEMON": "1"}, true, "./bd", "sync") + runCmdOutputWithEnvAllowError(t, clone, map[string]string{"BEADS_NO_DAEMON": "1"}, true, bdPath, "sync") } } diff --git a/cmd/bd/daemon_lock_test.go b/cmd/bd/daemon_lock_test.go index 37587c03..6f1952bd 100644 --- a/cmd/bd/daemon_lock_test.go +++ b/cmd/bd/daemon_lock_test.go @@ -5,6 +5,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strings" "testing" "time" @@ -112,6 +113,11 @@ func TestBackwardCompatibilityWithOldDaemon(t *testing.T) { } func TestDaemonLockJSONFormat(t *testing.T) { + // Skip on Windows - file locking prevents reading lock file while locked + if runtime.GOOS == "windows" { + t.Skip("Windows file locking prevents reading locked files") + } + tmpDir := t.TempDir() beadsDir := filepath.Join(tmpDir, ".beads") if err := os.MkdirAll(beadsDir, 0700); err != nil { @@ -151,6 +157,11 @@ func TestDaemonLockJSONFormat(t *testing.T) { } func TestValidateDaemonLock(t *testing.T) { + // Skip on Windows - file locking prevents reading lock file while locked + if runtime.GOOS == "windows" { + t.Skip("Windows file locking prevents reading locked files") + } + tmpDir := t.TempDir() beadsDir := filepath.Join(tmpDir, ".beads") if err := os.MkdirAll(beadsDir, 0700); err != nil { @@ -191,9 +202,13 @@ func TestMultipleDaemonProcessesRace(t *testing.T) { // Find the bd binary bdBinary, err := exec.LookPath("bd") if err != nil { - // Try local build - if _, err := os.Stat("./bd"); err == nil { - bdBinary = "./bd" + // Try local build (platform-specific) + localBinary := "./bd" + if runtime.GOOS == "windows" { + localBinary = "./bd.exe" + } + if _, err := os.Stat(localBinary); err == nil { + bdBinary = localBinary } else { t.Skip("bd binary not found, skipping race test") } diff --git a/cmd/bd/markdown.go b/cmd/bd/markdown.go index 27fed2f7..1fa406fd 100644 --- a/cmd/bd/markdown.go +++ b/cmd/bd/markdown.go @@ -330,7 +330,7 @@ func parseMarkdownFile(path string) ([]*IssueTemplate, error) { } // createIssuesFromMarkdown parses a markdown file and creates multiple issues from it -func createIssuesFromMarkdown(cmd *cobra.Command, filepath string) { +func createIssuesFromMarkdown(_ *cobra.Command, filepath string) { // Parse markdown file templates, err := parseMarkdownFile(filepath) if err != nil { diff --git a/cmd/bd/nodb.go b/cmd/bd/nodb.go index e2bca4f5..c81c83fe 100644 --- a/cmd/bd/nodb.go +++ b/cmd/bd/nodb.go @@ -115,7 +115,7 @@ func loadIssuesFromJSONL(path string) ([]*types.Issue, error) { // 1. issue-prefix from config.yaml (if set) // 2. Common prefix from existing issues (if all share same prefix) // 3. Current directory name (fallback) -func detectPrefix(beadsDir string, memStore *memory.MemoryStorage) (string, error) { +func detectPrefix(_ string, memStore *memory.MemoryStorage) (string, error) { // Check config.yaml for issue-prefix configPrefix := config.GetString("issue-prefix") if configPrefix != "" { diff --git a/cmd/bd/scripttest_test.go b/cmd/bd/scripttest_test.go index 45bc0c0a..989b8065 100644 --- a/cmd/bd/scripttest_test.go +++ b/cmd/bd/scripttest_test.go @@ -13,22 +13,20 @@ import ( ) func TestScripts(t *testing.T) { + // Skip on Windows - test scripts use sh -c which requires Unix shell + if runtime.GOOS == "windows" { + t.Skip("scripttest uses Unix shell commands (sh -c), skipping on Windows") + } + // Build the bd binary exeName := "bd" - if runtime.GOOS == "windows" { - exeName += ".exe" - } exe := filepath.Join(t.TempDir(), exeName) if err := exec.Command("go", "build", "-o", exe, ".").Run(); err != nil { t.Fatal(err) } // Create minimal engine with default commands plus bd - // Use longer timeout on Windows for slower process startup and I/O timeout := 2 * time.Second - if runtime.GOOS == "windows" { - timeout = 5 * time.Second - } engine := script.NewEngine() engine.Cmds["bd"] = script.Program(exe, nil, timeout) diff --git a/cmd/bd/validate.go b/cmd/bd/validate.go index b59d4282..f73bae0e 100644 --- a/cmd/bd/validate.go +++ b/cmd/bd/validate.go @@ -208,7 +208,7 @@ func (r *validationResults) toJSON() map[string]interface{} { return output } -func (r *validationResults) print(fixAll bool) { +func (r *validationResults) print(_ bool) { green := color.New(color.FgGreen).SprintFunc() yellow := color.New(color.FgYellow).SprintFunc() red := color.New(color.FgRed).SprintFunc() @@ -326,7 +326,7 @@ func validateOrphanedDeps(ctx context.Context, allIssues []*types.Issue, fix boo return result } -func validateDuplicates(ctx context.Context, allIssues []*types.Issue, fix bool) checkResult { +func validateDuplicates(_ context.Context, allIssues []*types.Issue, fix bool) checkResult { result := checkResult{name: "duplicates"} // Find duplicates @@ -350,7 +350,7 @@ func validateDuplicates(ctx context.Context, allIssues []*types.Issue, fix bool) return result } -func validatePollution(ctx context.Context, allIssues []*types.Issue, fix bool) checkResult { +func validatePollution(_ context.Context, allIssues []*types.Issue, fix bool) checkResult { result := checkResult{name: "test pollution"} // Detect pollution @@ -369,7 +369,7 @@ func validatePollution(ctx context.Context, allIssues []*types.Issue, fix bool) return result } -func validateGitConflicts(ctx context.Context, fix bool) checkResult { +func validateGitConflicts(_ context.Context, fix bool) checkResult { result := checkResult{name: "git conflicts"} // Check JSONL file for conflict markers diff --git a/internal/autoimport/autoimport.go b/internal/autoimport/autoimport.go index 7954b0d2..229e525f 100644 --- a/internal/autoimport/autoimport.go +++ b/internal/autoimport/autoimport.go @@ -210,7 +210,7 @@ func checkForMergeConflicts(jsonlData []byte, jsonlPath string) error { return nil } -func parseJSONL(jsonlData []byte, notify Notifier) ([]*types.Issue, error) { +func parseJSONL(jsonlData []byte, _ Notifier) ([]*types.Issue, error) { scanner := bufio.NewScanner(bytes.NewReader(jsonlData)) scanner.Buffer(make([]byte, 0, 1024), 2*1024*1024) var allIssues []*types.Issue diff --git a/internal/daemon/registry_test.go b/internal/daemon/registry_test.go index ef42b5c0..fff875b5 100644 --- a/internal/daemon/registry_test.go +++ b/internal/daemon/registry_test.go @@ -3,6 +3,7 @@ package daemon import ( "os" "path/filepath" + "runtime" "testing" "time" ) @@ -12,10 +13,14 @@ func TestRegistryBasics(t *testing.T) { tmpDir := t.TempDir() registryPath := filepath.Join(tmpDir, ".beads", "registry.json") - // Override the registry path for testing - oldHome := os.Getenv("HOME") - os.Setenv("HOME", tmpDir) - defer os.Setenv("HOME", oldHome) + // Override the registry path for testing (platform-specific) + homeEnv := "HOME" + if runtime.GOOS == "windows" { + homeEnv = "USERPROFILE" + } + oldHome := os.Getenv(homeEnv) + os.Setenv(homeEnv, tmpDir) + defer os.Setenv(homeEnv, oldHome) registry, err := NewRegistry() if err != nil { @@ -182,9 +187,15 @@ func TestRegistryStaleCleanup(t *testing.T) { func TestRegistryEmptyArrayNotNull(t *testing.T) { tmpDir := t.TempDir() registryPath := filepath.Join(tmpDir, ".beads", "registry.json") - oldHome := os.Getenv("HOME") - os.Setenv("HOME", tmpDir) - defer os.Setenv("HOME", oldHome) + + // Override the registry path for testing (platform-specific) + homeEnv := "HOME" + if runtime.GOOS == "windows" { + homeEnv = "USERPROFILE" + } + oldHome := os.Getenv(homeEnv) + os.Setenv(homeEnv, tmpDir) + defer os.Setenv(homeEnv, oldHome) registry, err := NewRegistry() if err != nil { diff --git a/internal/daemonrunner/process_test.go b/internal/daemonrunner/process_test.go index 43a455ab..54f98ec6 100644 --- a/internal/daemonrunner/process_test.go +++ b/internal/daemonrunner/process_test.go @@ -4,10 +4,16 @@ import ( "encoding/json" "os" "path/filepath" + "runtime" "testing" ) func TestDaemonLockBasics(t *testing.T) { + // Skip on Windows - file locking prevents reading lock file while locked + if runtime.GOOS == "windows" { + t.Skip("Windows file locking prevents reading locked files") + } + tmpDir := t.TempDir() dbPath := filepath.Join(tmpDir, "beads.db") diff --git a/internal/importer/importer.go b/internal/importer/importer.go index f4f34625..b2022f96 100644 --- a/internal/importer/importer.go +++ b/internal/importer/importer.go @@ -128,7 +128,7 @@ func ImportIssues(ctx context.Context, dbPath string, store storage.Storage, iss } // getOrCreateStore returns an existing storage or creates a new one -func getOrCreateStore(ctx context.Context, dbPath string, store storage.Storage) (*sqlite.SQLiteStorage, bool, error) { +func getOrCreateStore(_ context.Context, dbPath string, store storage.Storage) (*sqlite.SQLiteStorage, bool, error) { if store != nil { sqliteStore, ok := store.(*sqlite.SQLiteStorage) if !ok {