Skip to content

Commit fce847e

Browse files
authored
Merge pull request #327 from neongreen/copilot/investigate-flaky-test
Fix flaky "text file busy" error in gopls tests
2 parents 88246db + c15e2b8 commit fce847e

1 file changed

Lines changed: 47 additions & 3 deletions

File tree

dissect/pkg/dependencies/manager.go

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,59 @@ func (m *Manager) findOrInstall(tool string, importPath string) (string, error)
9494
return "", fmt.Errorf("installation of %s timed out after 2 minutes", tool)
9595
}
9696

97-
// Verify installation
98-
if _, err := os.Stat(toolPath); err != nil {
99-
return "", fmt.Errorf("%s was not installed at expected path %s", tool, toolPath)
97+
// Verify installation and wait for the binary to be executable.
98+
// After go install completes, there can be a brief window where the file exists
99+
// but is still being written (causing "text file busy" errors on execution).
100+
if err := waitForExecutable(toolPath, 5*time.Second); err != nil {
101+
return "", fmt.Errorf("%s installation verification failed: %w", tool, err)
100102
}
101103

102104
slog.Info("Tool installed successfully", "tool", tool, "path", toolPath)
103105
return toolPath, nil
104106
}
105107

108+
// waitForExecutable waits until a binary file exists and can be opened for reading.
109+
// This handles the race condition where go install has written the file
110+
// but the OS hasn't fully released the write handle yet.
111+
func waitForExecutable(path string, timeout time.Duration) error {
112+
deadline := time.Now().Add(timeout)
113+
var lastErr error
114+
115+
for time.Now().Before(deadline) {
116+
// Check if file exists
117+
info, err := os.Stat(path)
118+
if err != nil {
119+
lastErr = err
120+
time.Sleep(50 * time.Millisecond)
121+
continue
122+
}
123+
124+
// Check if it's a regular file
125+
if !info.Mode().IsRegular() {
126+
lastErr = fmt.Errorf("%s is not a regular file", path)
127+
time.Sleep(50 * time.Millisecond)
128+
continue
129+
}
130+
131+
// Try to open the file for reading to verify it's not locked
132+
f, err := os.Open(path)
133+
if err != nil {
134+
lastErr = err
135+
time.Sleep(50 * time.Millisecond)
136+
continue
137+
}
138+
f.Close()
139+
140+
// File is ready
141+
return nil
142+
}
143+
144+
if lastErr != nil {
145+
return fmt.Errorf("timed out waiting for %s to be ready: %w", path, lastErr)
146+
}
147+
return fmt.Errorf("timed out waiting for %s to be ready", path)
148+
}
149+
106150
// getGOBIN returns the GOBIN directory for the project
107151
func (m *Manager) getGOBIN() (string, error) {
108152
// First try to get GOBIN from go env

0 commit comments

Comments
 (0)