Files
arcrun-collector/direct_sync_now_test.go
T
Leo 4d3a6a09a6 v0.18.9:collector 併進同一支執行檔——磁碟上不再攤出第二支 exe
leo 08-06 裁決:「不要兩支,寫成一支檔案」。

## 為什麼
v0.18.7-8 的「單一 exe」其實是**一支包著另一支**:collector.exe 被 go:embed
進 Arcrun.exe,執行時攤到 ~/.arcrun-rag/bin/ 再跑。
那正是防毒軟體眼中的 dropper 特徵 —— 封測者實撞
`Trojan:Win32/Sabsik.FL.A!ml`,檔案當場被隔離、自動刪除。

⚠️ 誠實界定:`!ml` 結尾=**機器學習判定**,Sabsik 是最常見的通用誤判家族,
   主因是「未簽章+下載次數少」,**不是**特別指向 dropper 行為。
   所以本次改動**不保證**解除誤判——真正的解是上架 MS Store(微軟簽章)。
   但「執行時把第二支 PE 寫到磁碟再執行」本來就該拿掉,這是對的方向且順手變小。

## 怎麼做
- `collector/` 39 個檔 `package main` → `package collector`,`main()` → 匯出的 `Run(args) int`
- 新增 `collector/cmd/collector/`(薄殼 CLI,讓單獨跑 collector 這條路仍可用)
- App 直接 import 該套件;`main()` 第一件事就判 `--collector`,是的話走 `collector.Run` 不碰 GUI
- `supervisor` 加 `ArgPrefix`,App 把 `BinPath` 指向 `os.Executable()` 自己
- 刪掉 `bundled_collector_{windows,other}.go`(embed + 攤檔那套)
- 三支打包腳本不再編/複製第二支;版本注入同時打到兩個 package
- build-win.sh 的機械閘改成**直接問它**:`--collector --version` 回得出版本才放行
  (舊閘是比大小,只能證明「有 embed」,證明不了「分派是對的」)

## 驗(真機實跑)
· `.app/Contents/MacOS/` 只有 **一個** 執行檔(原本兩個)
· 跑起來兩個行程是**同一個 exe**:
    …/MacOS/arcrun-app
    …/MacOS/arcrun-app --collector direct --config …
· `~/.arcrun-rag/bin` **不存在**(沒有任何東西被攤出來)
· 端到端:丟檔進看守資料夾 → collector.log `"status":"ingested","http_status":200`
· `lsappinfo` 仍是 `type="UIElement"`、`Version="0.18.9"`
· collector 39 檔測試全過;app 測試過;go vet 全綠;mac + windows 交叉編譯皆過
· 單檔 26MB → 22MB(不再夾帶第二份完整程式)
2026-08-06 16:00:47 +08:00

122 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// direct_sync_now_test.go — t98 立刻同步訊號檔邏輯單元測試。
package collector
import (
"os"
"path/filepath"
"runtime"
"testing"
)
// TestConsumeSyncNowSignal_NoFile:訊號檔不存在時回 false,不建立任何檔案。
func TestConsumeSyncNowSignal_NoFile(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "sync-now")
got := consumeSyncNowSignal(path)
if got {
t.Error("訊號檔不存在時應回 false")
}
if _, err := os.Stat(path); err == nil {
t.Error("consumeSyncNowSignal 不應建立訊號檔")
}
}
// TestConsumeSyncNowSignal_FileExists:訊號檔存在時回 true 並刪除它。
func TestConsumeSyncNowSignal_FileExists(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "sync-now")
// 建立訊號檔
if err := os.WriteFile(path, []byte{}, 0o644); err != nil {
t.Fatal(err)
}
got := consumeSyncNowSignal(path)
if !got {
t.Error("訊號檔存在時應回 true")
}
// 訊號檔應已被刪除
if _, err := os.Stat(path); err == nil {
t.Error("consumeSyncNowSignal 後訊號檔應已被刪除")
}
}
// TestConsumeSyncNowSignal_Idempotentconsume 後再次呼叫應回 false(訊號只觸發一輪)。
func TestConsumeSyncNowSignal_Idempotent(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "sync-now")
if err := os.WriteFile(path, []byte{}, 0o644); err != nil {
t.Fatal(err)
}
first := consumeSyncNowSignal(path)
second := consumeSyncNowSignal(path)
if !first {
t.Error("第一次 consume 應回 true")
}
if second {
t.Error("第二次 consume(已刪)應回 false,確保每次訊號只觸發一輪")
}
}
// TestConsumeSyncNowSignal_TriggerRunCounting:訊號檔驅動「跑一輪」計數正確。
// 用 fake runner 模擬:寫 N 個訊號檔,逐一 consume,驗 runner 呼叫次數。
// (實際 runDirect 迴圈已在 direct.go 整合;此處只驗 consume 回傳值驅動的呼叫計數)
func TestConsumeSyncNowSignal_TriggerRunCounting(t *testing.T) {
if runtime.GOOS == "windows" {
// WriteFile race on Windows tempdir 偶爾有 flush 時序問題;邏輯已由前三條跨平台測試覆蓋
t.Skip("此驗計數測試僅跑 Unix")
}
dir := t.TempDir()
path := filepath.Join(dir, "sync-now")
runCount := 0
fakeRun := func() { runCount++ }
// 第 1 輪:有訊號 → 跑
if err := os.WriteFile(path, []byte{}, 0o644); err != nil {
t.Fatal(err)
}
if consumeSyncNowSignal(path) {
fakeRun()
}
// 第 2 輪:無訊號 → 不跑
if consumeSyncNowSignal(path) {
fakeRun()
}
// 第 3 輪:再寫訊號 → 跑
if err := os.WriteFile(path, []byte{}, 0o644); err != nil {
t.Fatal(err)
}
if consumeSyncNowSignal(path) {
fakeRun()
}
if runCount != 2 {
t.Errorf("期望 fakeRun 呼叫 2 次(有訊號輪),got %d", runCount)
}
}
// TestSyncNowSignalPathConsistencycollector 的 SyncNowSignalPath 與 tray 預期路徑一致。
// tray 寫入 appDir()/sync-nowcollector 的函式從 manifest 路徑推導同目錄的 sync-now——
// 兩者必須解析到同一個絕對路徑,否則訊號永遠不會被 collector 讀到。
func TestSyncNowSignalPathConsistency(t *testing.T) {
dir := t.TempDir()
manifest := filepath.Join(dir, "manifest.json")
// collector 算出的訊號路徑
collectorPath := SyncNowSignalPath(manifest)
// tray 寫的是 appDir()/sync-now;此測試驗「同目錄邏輯」:
// 若 manifest 在 dir,訊號應在 dir/sync-now
wantPath := filepath.Join(dir, "sync-now")
if collectorPath != wantPath {
t.Errorf("SyncNowSignalPath(%q) = %q, want %qtray 與 collector 路徑必須一致)",
manifest, collectorPath, wantPath)
}
}