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
90
91
92
93
|
namespace LivingVillage.Desktop.Tests
open System.IO
open Microsoft.VisualStudio.TestTools.UnitTesting
/// P71(中文 API/模块说明,docs-only):锁定 docs/api-reference/ 的存在、分节与关键口径,
/// 防止说明文档被误删或章节漂移。
[<TestClass>]
type P71ApiDocsTests () =
let repoRoot () : string =
let mutable dir = System.AppContext.BaseDirectory
let mutable found = None
while found.IsNone && not (System.String.IsNullOrEmpty dir) do
if File.Exists(Path.Combine(dir, "LivingVillage.sln")) then found <- Some dir
else
let parent = Path.GetDirectoryName dir
if parent = dir then dir <- "" else dir <- parent
match found with
| Some d -> d
| None -> failwith "找不到仓库根(LivingVillage.sln)"
let apiDir () = Path.Combine(repoRoot (), "docs", "api-reference")
let expectedFiles =
[ "README.md"
"sim-kernel.md"
"map-and-mapgen.md"
"rendering-desktop.md"
"interaction-and-trade.md"
"occupation.md"
"world-save.md" ]
let requiredSections =
[ "## 职责"
"## 对外 API 概览"
"## 单位与量纲"
"## 不变量"
"## 失败分支"
"## 算法与性能取舍" ]
let allDocs () : string =
expectedFiles
|> List.filter (fun name -> File.Exists(Path.Combine(apiDir (), name)))
|> List.map (fun name -> File.ReadAllText(Path.Combine(apiDir (), name)))
|> String.concat "\n"
[<TestMethod>]
member _.ApiReferenceDirectoryHasAllModuleFiles () =
Assert.IsTrue(Directory.Exists(apiDir ()), sprintf "缺少目录 %s" (apiDir ()))
let missing = expectedFiles |> List.filter (fun name -> not (File.Exists(Path.Combine(apiDir (), name))))
CollectionAssert.AreEqual(
[||],
missing |> List.toArray,
sprintf "docs/api-reference 缺少文件: %s" (String.concat ", " missing))
[<TestMethod>]
member _.EveryModuleSectionHasTheRequiredHeadings () =
let docs = allDocs ()
let missing = requiredSections |> List.filter (fun section -> not (docs.Contains section))
CollectionAssert.AreEqual(
[||],
missing |> List.toArray,
sprintf "api-reference 缺少规定章节: %s" (String.concat ", " missing))
// 六个模块文件各一个「## 职责」,确保每个模块都有齐六个小节。
let responsibilities = requiredSections.[0]
let count =
docs.Split('\n')
|> Array.filter (fun line -> line.Trim() = responsibilities)
|> Array.length
Assert.IsTrue(count >= 6, sprintf "「## 职责」出现 %d 次,应 >= 6(每模块一节)" count)
[<TestMethod>]
member _.ApiReferencePinsKeyInterfacesAndUnits () =
let docs = allDocs ()
let anchors =
[ "ticksPerDay"
"configureBounds"
"953775FAEB2FDDE97289491AA260BD8D390C571E48A7A13AD2CB6FB7124F7F6C"
"WorldSave.save"
"LV_WORLD_SAVE_V2"
"LV_WORLD_SAVE_V3"
"MapGen.paramsForSize"
"MapSnapshot.renderPng"
"CjkGlyphAtlas"
"M5Command"
"PlayerTrade.sell"
"Occupation.saveToken" ]
let missing = anchors |> List.filter (fun anchor -> not (docs.Contains anchor))
CollectionAssert.AreEqual(
[||],
missing |> List.toArray,
sprintf "api-reference 未钉住关键接口/单位: %s" (String.concat ", " missing))
|