1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
namespace LivingVillage.Desktop.Tests
open System.Threading
open System.Threading.Tasks
open Microsoft.VisualStudio.TestTools.UnitTesting
open LivingVillage.Headless
/// M3-1 可观测性回归:批次输出格式与逐世界流式驱动。
[<TestClass>]
type M3BatchOutputTests () =
let sampleBase =
"world=3 seed=45 chats_max=9 chats_avg=2.00 ratio=4.500 gini=0.100 relcnt_max=5 relcnt_min=1 relconn=6/6 nonfinite=0 check_a=PASS check_b=PASS check_c=PASS old_crit=PASS OK"
[<TestMethod>]
member _.WorldLineKeepsPrefixAndAppendsElapsed () =
let line = BatchOutput.worldLine sampleBase 123.4
Assert.IsTrue(line.StartsWith("world=3 seed=45 "), "world 行前缀必须保持不变")
Assert.IsTrue(line.StartsWith(sampleBase), "判据字段必须原样保留")
Assert.IsTrue(line.EndsWith("elapsed_s=123.4"), "耗时字段追加在行尾")
// 旧判据 token 仍在(监督器据此判定 OK)。
Assert.AreEqual<string>(sampleBase + " elapsed_s=123.4", line)
[<TestMethod>]
member _.DoneLineReportsProgressAndElapsed () =
Assert.AreEqual<string>("[done 2/4] elapsed=12.3s world=1", BatchOutput.doneLine 2 4 12.3 1)
Assert.AreEqual<string>("[done 4/4] elapsed=600.0s world=3", BatchOutput.doneLine 4 4 600.0 3)
[<TestMethod>]
member _.ProgressLineReportsSteppedDayWithinWorld () =
Assert.AreEqual<string>("[world 7] progress day=5/15 elapsed=61.2s", BatchOutput.progressLine 7 5L 15L 61.2)
Assert.AreEqual<string>("[world 0] progress day=15/15 elapsed=260.0s", BatchOutput.progressLine 0 15L 15L 260.0)
[<TestMethod>]
member _.SummaryLineKeepsOldFieldsAndAppendsWallClock () =
let line = BatchOutput.summaryLine 4 3 1 4 1.100 9.900 0.010 0.400 4 610.5
// 监督器正则只读前三个字段,前缀必须逐字保持。
Assert.IsTrue(line.StartsWith("batch_summary worlds=4 passed=3 failed=1 old_passed=4/4 "), line)
Assert.IsTrue(line.Contains("ratio_min=1.100 ratio_max=9.900 gini_min=0.010 gini_max=0.400"), line)
Assert.IsTrue(line.Contains("elapsed_s=610.5"), line)
Assert.IsTrue(line.EndsWith("workers=4 wall_s=610.5"), line)
[<TestMethod>]
member _.CostLineDerivesThroughputFromMeasuredTicks () =
let line = BatchOutput.costLine 10L 51840000L 254.0 12345678L 3L 1L 0L
Assert.IsTrue(line.StartsWith("cost days=10 ticks=51840000 elapsed_s=254.000 "), line)
Assert.IsTrue(line.Contains("ticks_per_s=204094.5"), line)
Assert.IsTrue(line.EndsWith("allocated_bytes=12345678 gen0=3 gen1=1 gen2=0"), line)
[<TestMethod>]
member _.DefaultCostLadderIsOneFiveTenTwentyFiftyHundred () =
CollectionAssert.AreEqual([| 1L; 5L; 10L; 20L; 50L; 100L |], List.toArray BatchOutput.defaultCostLadder)
[<TestMethod>]
member _.RunStreamingEmitsEachWorldBeforeTheNextStarts () =
// workers=1:每次 work 后必须立刻 emit 该世界(证明不是收完再打印)。
let events = ResizeArray<string> ()
let results =
BatchOutput.runStreaming 3 1
(fun k ->
lock events (fun () -> events.Add(sprintf "work%d" k))
k * k)
(fun k _ _ ->
lock events (fun () -> events.Add(sprintf "emit%d" k)))
CollectionAssert.AreEqual([| 0; 1; 4 |], results)
CollectionAssert.AreEqual(
[| "work0"; "emit0"; "work1"; "emit1"; "work2"; "emit2" |],
events.ToArray())
[<TestMethod>]
member _.RunStreamingIsCompleteUnderParallelism () =
let total = 12
let emitted = System.Collections.Concurrent.ConcurrentDictionary<int, int> ()
let completions = ResizeArray<int> ()
let results =
BatchOutput.runStreaming total 4
(fun k -> k + 100)
(fun k result doneCount ->
Assert.AreEqual<int>(k + 100, result)
emitted.[k] <- doneCount
lock completions (fun () -> completions.Add doneCount))
Assert.AreEqual<int>(total, results.Length)
Assert.AreEqual<int>(total, emitted.Count)
for k in 0 .. total - 1 do
Assert.IsTrue(emitted.ContainsKey k, sprintf "world=%d 未 emit" k)
// 完成计数恰为 1..total 的一个排列(并发下不重不漏)。
CollectionAssert.AreEqual(
[| 1 .. total |],
completions |> Seq.sort |> Seq.toArray)
|