Unit test
This commit is contained in:
@@ -27,6 +27,16 @@ Or run directly with Go:
|
|||||||
GOCACHE="$PWD/.cache/go-build" go run ./cmd/nwn-tool --help
|
GOCACHE="$PWD/.cache/go-build" go run ./cmd/nwn-tool --help
|
||||||
```
|
```
|
||||||
|
|
||||||
|
When changing native topdata id allocation, lockfile persistence, generated
|
||||||
|
families, or null-and-relocate behavior, run the repeat-build determinism test:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go test ./internal/topdata -run TestBuildNativeKeepsGeneratedIDsStableAcrossRepeatedBuilds
|
||||||
|
```
|
||||||
|
|
||||||
|
That test builds the same topdata project ten times in one process and asserts
|
||||||
|
that relocated/generated ids stay pinned across every pass.
|
||||||
|
|
||||||
## Repository Layout
|
## Repository Layout
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
|||||||
@@ -8897,6 +8897,148 @@ func TestBuildBaseitemsAllowsKeyOnlyOverrideBeforeSameKeyIsRelocatedAndReadded(t
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuildNativeKeepsGeneratedIDsStableAcrossRepeatedBuilds(t *testing.T) {
|
||||||
|
root := testProjectRoot(t)
|
||||||
|
writeFeatGeneratedHarness(t, root, map[string]string{
|
||||||
|
"skill_focus.json": `{
|
||||||
|
"family": "skill_focus",
|
||||||
|
"family_key": "skill_focus",
|
||||||
|
"template": "masterfeats:skillfocus",
|
||||||
|
"name_prefix": "Skill Focus",
|
||||||
|
"label_prefix": "FEAT_SKILL_FOCUS",
|
||||||
|
"constant_prefix": "FEAT_SKILL_FOCUS",
|
||||||
|
"default_fields": {
|
||||||
|
"CRValue": "0.5",
|
||||||
|
"ReqSkillMinRanks": "1",
|
||||||
|
"ALLCLASSESCANUSE": "1",
|
||||||
|
"TOOLSCATEGORIES": "6",
|
||||||
|
"PreReqEpic": "0",
|
||||||
|
"ReqAction": "1"
|
||||||
|
},
|
||||||
|
"apply_after_modules": true,
|
||||||
|
"child_ref_field": "REQSKILL",
|
||||||
|
"child_source": {
|
||||||
|
"dataset": "skills",
|
||||||
|
"predicate": "accessible"
|
||||||
|
}
|
||||||
|
}` + "\n",
|
||||||
|
}, map[string]int{
|
||||||
|
"feat:skill_focus_intimidate": 1333,
|
||||||
|
})
|
||||||
|
mkdirAll(t, filepath.Join(root, "topdata", "data", "baseitems", "modules", "10_vanillaoverrides"))
|
||||||
|
writeFile(t, filepath.Join(root, "topdata", "data", "baseitems", "base.json"), `{
|
||||||
|
"output": "baseitems.2da",
|
||||||
|
"columns": ["label", "MaxRange"],
|
||||||
|
"rows": [
|
||||||
|
{"id": 58, "key": "baseitems:shortspear", "label": "shortspear", "MaxRange": "100"}
|
||||||
|
]
|
||||||
|
}`+"\n")
|
||||||
|
writeFile(t, filepath.Join(root, "topdata", "data", "baseitems", "lock.json"), `{
|
||||||
|
"baseitems:shortspear": 126
|
||||||
|
}`+"\n")
|
||||||
|
writeFile(t, filepath.Join(root, "topdata", "data", "baseitems", "modules", "10_vanillaoverrides", "ovr_baseitems_spear.json"), `{
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"id": 58,
|
||||||
|
"key": "baseitems:spear",
|
||||||
|
"label": "spear"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`+"\n")
|
||||||
|
writeFile(t, filepath.Join(root, "topdata", "data", "baseitems", "modules", "add_baseitems_shortspear.json"), `{
|
||||||
|
"entries": {
|
||||||
|
"baseitems:shortspear": {
|
||||||
|
"label": "shortspear",
|
||||||
|
"MaxRange": "255"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`+"\n")
|
||||||
|
writeFile(t, filepath.Join(root, "topdata", "data", "skills", "base.json"), `{
|
||||||
|
"output": "skills.2da",
|
||||||
|
"columns": ["Label", "Name", "Description", "Icon", "Untrained", "KeyAbility", "ArmorCheckPenalty", "AllClassesCanUse", "Category", "MaxCR", "Constant", "HostileSkill", "HideFromLevelUp"],
|
||||||
|
"rows": [
|
||||||
|
{"id": 18, "key": "skills:intimidate", "Label": "Intimidate", "Name": "8756", "Description": "8786", "Icon": "isk_x2inti", "Untrained": "1", "KeyAbility": "CHA", "ArmorCheckPenalty": "0", "AllClassesCanUse": "1", "Category": "****", "MaxCR": "****", "Constant": "SKILL_INTIMIDATE", "HostileSkill": "1", "HideFromLevelUp": "0"}
|
||||||
|
]
|
||||||
|
}`+"\n")
|
||||||
|
writeFile(t, filepath.Join(root, "topdata", "data", "skills", "lock.json"), `{
|
||||||
|
"skills:intimidate": 18
|
||||||
|
}`+"\n")
|
||||||
|
writeFile(t, filepath.Join(root, "topdata", "data", "feat", "base.json"), `{
|
||||||
|
"output": "feat.2da",
|
||||||
|
"compare_reference": false,
|
||||||
|
"columns": [
|
||||||
|
"LABEL","FEAT","DESCRIPTION","ICON","MINATTACKBONUS","MINSTR","MINDEX","MININT","MINWIS","MINCON","MINCHA",
|
||||||
|
"MINSPELLLVL","PREREQFEAT1","PREREQFEAT2","GAINMULTIPLE","EFFECTSSTACK","ALLCLASSESCANUSE","CATEGORY","MAXCR",
|
||||||
|
"SPELLID","SUCCESSOR","CRValue","USESPERDAY","MASTERFEAT","TARGETSELF","OrReqFeat0","OrReqFeat1","OrReqFeat2",
|
||||||
|
"OrReqFeat3","OrReqFeat4","REQSKILL","ReqSkillMinRanks","REQSKILL2","ReqSkillMinRanks2","Constant","TOOLSCATEGORIES",
|
||||||
|
"HostileFeat","MinLevel","MinLevelClass","MaxLevel","MinFortSave","PreReqEpic","ReqAction"
|
||||||
|
],
|
||||||
|
"rows": [
|
||||||
|
{"id": 39, "key": "feat:stunning_fist", "LABEL": "FEAT_STUNNING_FIST", "MASTERFEAT": "****", "Constant": "FEAT_STUNNING_FIST"},
|
||||||
|
{"id": 916, "key": "feat:skill_focus_intimidate", "LABEL": "FEAT_SKILL_FOCUS_INTIMIDATE", "REQSKILL": 24, "MASTERFEAT": 4, "Constant": "FEAT_SKILL_FOCUS_INTIMIDATE"}
|
||||||
|
]
|
||||||
|
}`+"\n")
|
||||||
|
writeFile(t, filepath.Join(root, "topdata", "data", "feat", "lock.json"), `{
|
||||||
|
"feat:skill_focus_intimidate": 1333,
|
||||||
|
"feat:stunning_fist": 1116
|
||||||
|
}`+"\n")
|
||||||
|
mkdirAll(t, filepath.Join(root, "topdata", "data", "feat", "modules", "00_removed"))
|
||||||
|
writeFile(t, filepath.Join(root, "topdata", "data", "feat", "modules", "00_removed", "rmv_feat_intimidate.json"), `{
|
||||||
|
"overrides": [
|
||||||
|
{"id": 916, "null": true, "key": null}
|
||||||
|
]
|
||||||
|
}`+"\n")
|
||||||
|
writeFile(t, filepath.Join(root, "topdata", "data", "feat", "modules", "00_removed", "rmv_feat_stunningfist.json"), `{
|
||||||
|
"overrides": [
|
||||||
|
{"id": 39, "null": true, "key": null}
|
||||||
|
]
|
||||||
|
}`+"\n")
|
||||||
|
mkdirAll(t, filepath.Join(root, "topdata", "data", "feat", "modules", "activecombat"))
|
||||||
|
writeFile(t, filepath.Join(root, "topdata", "data", "feat", "modules", "activecombat", "add_feat_stunning_fist.json"), `{
|
||||||
|
"entries": {
|
||||||
|
"feat:stunning_fist": {
|
||||||
|
"LABEL": "FEAT_STUNNING_FIST",
|
||||||
|
"Constant": "FEAT_STUNNING_FIST"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`+"\n")
|
||||||
|
mkdirAll(t, filepath.Join(root, "reference"))
|
||||||
|
writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n")
|
||||||
|
|
||||||
|
expectedBaseitems := map[string]int{
|
||||||
|
"baseitems:spear": 58,
|
||||||
|
"baseitems:shortspear": 126,
|
||||||
|
}
|
||||||
|
expectedFeat := map[string]int{
|
||||||
|
"feat:skill_focus_intimidate": 1333,
|
||||||
|
"feat:stunning_fist": 1116,
|
||||||
|
}
|
||||||
|
for run := 1; run <= 10; run++ {
|
||||||
|
if _, err := BuildNative(testProject(root), nil); err != nil {
|
||||||
|
t.Fatalf("BuildNative run %d failed: %v", run, err)
|
||||||
|
}
|
||||||
|
assertLockIDs(t, run, filepath.Join(root, "topdata", "data", "baseitems", "lock.json"), expectedBaseitems)
|
||||||
|
assertLockIDs(t, run, filepath.Join(root, "topdata", "data", "feat", "lock.json"), expectedFeat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertLockIDs(t *testing.T, run int, path string, expected map[string]int) {
|
||||||
|
t.Helper()
|
||||||
|
raw, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("run %d: read lockfile %s: %v", run, path, err)
|
||||||
|
}
|
||||||
|
lockData := map[string]int{}
|
||||||
|
if err := json.Unmarshal(raw, &lockData); err != nil {
|
||||||
|
t.Fatalf("run %d: parse lockfile %s: %v\n%s", run, path, err, string(raw))
|
||||||
|
}
|
||||||
|
for key, want := range expected {
|
||||||
|
if got := lockData[key]; got != want {
|
||||||
|
t.Fatalf("run %d: expected %s to stay locked to id %d, got %d in %s:\n%s", run, key, want, got, path, string(raw))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBuildSkillsIgnoresStaleBaseSpanLockDuringInitialLoad(t *testing.T) {
|
func TestBuildSkillsIgnoresStaleBaseSpanLockDuringInitialLoad(t *testing.T) {
|
||||||
root := testProjectRoot(t)
|
root := testProjectRoot(t)
|
||||||
mkdirAll(t, filepath.Join(root, "topdata", "data", "skills", "modules"))
|
mkdirAll(t, filepath.Join(root, "topdata", "data", "skills", "modules"))
|
||||||
|
|||||||
Reference in New Issue
Block a user