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
修雨 eaf53bc2d2 Merge pull request #781 from DingTalk-Real-AI/automation/homebrew-beta-v1.0.55-beta.2
chore: update Homebrew beta formula for v1.0.55-beta.2
2026-07-23 23:28:52 +08:00
DWS Release Bot 3e148c6745 chore: update beta formula for v1.0.55-beta.2 2026-07-23 11:10:16 +00:00
3 changed files with 35 additions and 72 deletions
+11 -11
View File
@@ -1,33 +1,33 @@
class DingtalkWorkspaceCliBeta < Formula
desc "Automate DingTalk workspace tasks from the terminal (beta channel)"
homepage "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli"
version "1.0.54-beta.2"
version "1.0.55-beta.2"
license "Apache-2.0"
keg_only "it is the beta channel and conflicts with dingtalk-workspace-cli"
on_macos do
if Hardware::CPU.arm?
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.54-beta.2/dws-darwin-arm64.tar.gz"
sha256 "46b57bed1f6e9f7ba007d8a86a6f5eb280fdeb557fc9bb5946f14f9b1f8f0c9f"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.55-beta.2/dws-darwin-arm64.tar.gz"
sha256 "fd23353c473419ce8cfaf316e7afff757cc4606291f4ad41a62c6f5625f45c8f"
else
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.54-beta.2/dws-darwin-amd64.tar.gz"
sha256 "1b7fd08e64b1c86bbcee217604ffe07e0e8f1b3b5c4de518534386972bcf0f9b"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.55-beta.2/dws-darwin-amd64.tar.gz"
sha256 "0b429950a6449736338b504386d8009a4f2cdb0668f64f72a61cd503a25df193"
end
end
on_linux do
if Hardware::CPU.arm?
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.54-beta.2/dws-linux-arm64.tar.gz"
sha256 "108d3861ef606519f9934530d29654eab55a73607d1ee6775461f98ef5a6acd4"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.55-beta.2/dws-linux-arm64.tar.gz"
sha256 "c213a22d6187a8f68596b9f25d6277f5fd58cea5120f5b7caeef011fb41f1104"
else
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.54-beta.2/dws-linux-amd64.tar.gz"
sha256 "6cb96ee09419bbbcc1eb336218ac2aa1d9ca0ed5cbd5a80c79bc20e1e1f03ff7"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.55-beta.2/dws-linux-amd64.tar.gz"
sha256 "4e041ba7b1d8038fa34deba3a3f78af8377f4361131d8bfdb1f1913e5f9454dc"
end
end
resource "skills" do
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.54-beta.2/dws-skills.zip"
sha256 "572b93f04a10268d185ad1f8e70e0d412949ae056be8494a9949387076fd14bc"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.55-beta.2/dws-skills.zip"
sha256 "1cec445c73b9ff569ca002b6548f4ee8383712447bc4cebd7701c6065774524a"
end
def install
+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)
}