Compare commits

...
Author SHA1 Message Date
修雨 735ef6b3f2 test(markdown): make disappearing-upload-file cases deterministic
The three temporary_file_disappears_before_stat subtests raced a polling
goroutine against a 16MB write, hoping the removal landed between write
and stat. On fast CI runners the window is routinely missed, failing
Coverage jobs on main and every open PR. Replace the race with an
injectable markdownStatUpload seam that removes the file before
delegating to os.Stat.
2026-07-24 11:12:52 +08:00
2 changed files with 24 additions and 61 deletions
+7 -3
View File
@@ -27,6 +27,10 @@ import (
"github.com/spf13/cobra"
)
// markdownStatUpload stats the prepared upload file. Tests override it to
// deterministically simulate the file disappearing between write and stat.
var markdownStatUpload = os.Stat
func newMarkdownCommand() *cobra.Command {
root := &cobra.Command{
Use: "markdown",
@@ -257,7 +261,7 @@ func runMarkdownCreate(cmd *cobra.Command, _ []string) error {
if !hasMarkdownExtension(nameFlag) {
return fmt.Errorf("--name 必须以 .md 结尾,当前: %s", nameFlag)
}
info, err := os.Stat(uploadPath)
info, err := markdownStatUpload(uploadPath)
if err != nil {
return fmt.Errorf("读取上传文件失败: %w", err)
}
@@ -421,7 +425,7 @@ func runMarkdownOverwrite(cmd *cobra.Command, _ []string) error {
if !hasMarkdownExtension(nameFlag) {
return fmt.Errorf("--name 必须以 .md 结尾,当前: %s", nameFlag)
}
info, err := os.Stat(uploadPath)
info, err := markdownStatUpload(uploadPath)
if err != nil {
return fmt.Errorf("读取上传文件失败: %w", err)
}
@@ -563,7 +567,7 @@ func runMarkdownPatch(cmd *cobra.Command, _ []string) error {
if err := os.WriteFile(uploadPath, []byte(newContent), 0o600); err != nil {
return fmt.Errorf("写入临时文件失败: %w", err)
}
info, err := os.Stat(uploadPath)
info, err := markdownStatUpload(uploadPath)
if err != nil {
return fmt.Errorf("读取临时文件失败: %w", err)
}
+17 -58
View File
@@ -12,10 +12,8 @@ import (
"io"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
"github.com/spf13/cobra"
)
@@ -26,47 +24,20 @@ func (markdownCIFailingReader) Read([]byte) (int, error) {
return 0, errors.New("forced read failure")
}
func startMarkdownCITempFileRemover(t *testing.T, root, dirPrefix, fileName string) <-chan error {
// installMarkdownStatUploadRemover overrides the markdownStatUpload seam so
// the prepared upload file is deterministically removed before stat runs,
// simulating the file disappearing between write and stat without racing a
// background goroutine.
func installMarkdownStatUploadRemover(t *testing.T) {
t.Helper()
result := make(chan error, 1)
go func() {
deadline := time.Now().Add(10 * time.Second)
for time.Now().Before(deadline) {
entries, err := os.ReadDir(root)
if err != nil {
result <- err
return
}
for _, entry := range entries {
if !entry.IsDir() || !strings.HasPrefix(entry.Name(), dirPrefix) {
continue
}
target := filepath.Join(root, entry.Name(), fileName)
if err := os.Remove(target); err == nil {
result <- nil
return
} else if !os.IsNotExist(err) {
result <- err
return
}
}
runtime.Gosched()
original := markdownStatUpload
markdownStatUpload = func(name string) (os.FileInfo, error) {
if err := os.Remove(name); err != nil && !os.IsNotExist(err) {
t.Errorf("removing upload file %q: %v", name, err)
}
result <- fmt.Errorf("timed out waiting for %s/%s", dirPrefix, fileName)
}()
return result
}
func waitMarkdownCIRemover(t *testing.T, result <-chan error) {
t.Helper()
select {
case err := <-result:
if err != nil {
t.Fatal(err)
}
case <-time.After(12 * time.Second):
t.Fatal("timed out waiting for temporary-file remover")
return os.Stat(name)
}
t.Cleanup(func() { markdownStatUpload = original })
}
func TestMarkdownCICoverageFetchAndOutputPaths(t *testing.T) {
@@ -189,13 +160,9 @@ func TestMarkdownCICoverageCreateEdges(t *testing.T) {
t.Run("temporary file disappears before stat", func(t *testing.T) {
installMarkdownDriveDeps(t, &markdownDriveCaller{format: "json"})
tempRoot := t.TempDir()
t.Setenv("TMPDIR", tempRoot)
removed := startMarkdownCITempFileRemover(t, tempRoot, "dws-markdown-create-", "a.md")
content := strings.Repeat("x", 16<<20)
installMarkdownStatUploadRemover(t)
err := executeMarkdownDriveCommand(t, newMarkdownCommand(), nil,
"markdown", "create", "--name", "a.md", "--content", content)
waitMarkdownCIRemover(t, removed)
"markdown", "create", "--name", "a.md", "--content", "body")
if err == nil || !strings.Contains(err.Error(), "读取上传文件失败") {
t.Fatalf("error = %v", err)
}
@@ -270,14 +237,10 @@ func TestMarkdownCICoverageOverwriteEdges(t *testing.T) {
t.Run("temporary file disappears before stat", func(t *testing.T) {
installMarkdownDriveDeps(t, &markdownDriveCaller{format: "json"})
tempRoot := t.TempDir()
t.Setenv("TMPDIR", tempRoot)
removed := startMarkdownCITempFileRemover(t, tempRoot, "dws-markdown-overwrite-", "a.md")
content := strings.Repeat("x", 16<<20)
installMarkdownStatUploadRemover(t)
err := executeMarkdownDriveCommand(t, newMarkdownCommand(), nil,
"markdown", "overwrite", "--node", "node-1", "--space-id", "space-1",
"--name", "a.md", "--content", content)
waitMarkdownCIRemover(t, removed)
"--name", "a.md", "--content", "body")
if err == nil || !strings.Contains(err.Error(), "读取上传文件失败") {
t.Fatalf("error = %v", err)
}
@@ -451,14 +414,10 @@ func TestMarkdownCICoveragePatchEdges(t *testing.T) {
}
installMarkdownDriveDeps(t, caller)
installMarkdownHTTPGet(t, "old")
tempRoot := t.TempDir()
t.Setenv("TMPDIR", tempRoot)
removed := startMarkdownCITempFileRemover(t, tempRoot, "dws-markdown-patch-", "current.md")
replacement := strings.Repeat("x", 16<<20)
installMarkdownStatUploadRemover(t)
err := executeMarkdownDriveCommand(t, newMarkdownCommand(), nil,
"markdown", "patch", "--node", "node-1", "--space-id", "space-1",
"--pattern", "old", "--content", replacement, "--yes")
waitMarkdownCIRemover(t, removed)
"--pattern", "old", "--content", "new", "--yes")
if err == nil || !strings.Contains(err.Error(), "读取临时文件失败") {
t.Fatalf("error = %v", err)
}