Wrapper tool hardening and cleanup

This commit is contained in:
2026-05-13 19:06:24 +02:00
parent 53cb1e6996
commit 7d286551b3
19 changed files with 1376 additions and 149 deletions
+255 -54
View File
@@ -16,18 +16,16 @@ func TestRunBuildTopPackageUsesCachedCompiledOutputs(t *testing.T) {
mkdirAll(t, filepath.Join(root, "topdata", "assets", "gui"))
mkdirAll(t, filepath.Join(root, "topdata", "data", "repadjust"))
writeFile(t, filepath.Join(root, "nwn-tool.json"), `{
"module": {
"name": "Test Module",
"resref": "test_module"
},
"topdata": {
"source": "topdata",
"build": ".cache",
"package_hak": "sow_top.hak",
"package_tlk": "sow_tlk.tlk"
}
}`+"\n")
writeFile(t, filepath.Join(root, "nwn-tool.yaml"), `
module:
name: Test Module
resref: test_module
topdata:
source: topdata
build: .cache
package_hak: sow_top.hak
package_tlk: sow_tlk.tlk
`)
writeFile(t, filepath.Join(root, ".cache", "2da", "repadjust.2da"), "2DA V2.0\n\n Label\n0 TEST_LABEL\n")
writeFile(t, filepath.Join(root, "build", "sow_tlk.tlk"), "compiled tlk")
writeFile(t, filepath.Join(root, "topdata", "assets", "gui", "testicon.png"), "icon-data")
@@ -51,8 +49,12 @@ func TestRunBuildTopPackageUsesCachedCompiledOutputs(t *testing.T) {
if err := runBuildTopPackage(ctx); err != nil {
t.Fatalf("runBuildTopPackage failed: %v", err)
}
if !strings.Contains(stdout.String(), "top package hak:") {
t.Fatalf("expected build-top-package output, got %q", stdout.String())
output := stdout.String()
if !strings.Contains(output, "top package hak: build/sow_top.hak") {
t.Fatalf("expected build-top-package output, got %q", output)
}
if strings.Contains(output, "[build-top-package]") {
t.Fatalf("did not expect raw progress lines in normal output, got %q", output)
}
if _, err := os.Stat(filepath.Join(root, "build", "sow_top.hak")); err != nil {
t.Fatalf("expected packaged hak output: %v", err)
@@ -65,26 +67,22 @@ func TestRunBuildHAKsEmitsCompactSummary(t *testing.T) {
mkdirAll(t, filepath.Join(root, "build"))
mkdirAll(t, filepath.Join(root, "src"))
writeFile(t, filepath.Join(root, "nwn-tool.json"), `{
"module": {
"name": "Test Module",
"resref": "testmod"
},
"paths": {
"source": "src",
"assets": "assets",
"build": "build"
},
"haks": [
{
"name": "envi",
"priority": 1,
"max_bytes": 1048576,
"split": false,
"include": ["envi/**"]
}
]
}`+"\n")
writeFile(t, filepath.Join(root, "nwn-tool.yaml"), `
module:
name: Test Module
resref: testmod
paths:
source: src
assets: assets
build: build
haks:
- name: envi
priority: 1
max_bytes: 1048576
split: false
include:
- envi/**
`)
writeFile(t, filepath.Join(root, "assets", "envi", "music", "westgate", "AleandAnecdotes.mp3"), "source-mp3")
writeFile(t, filepath.Join(root, "assets", "envi", "music", "westgate", "CREDITS.md"), "# NWN Music Pack Credits\n\n| Artist | Title | Output File | Original File | Album | Date | License / Copyright | Notes |\n|---|---|---|---|---|---|---|---|\n| Darren Curtis | Ale and Anecdotes | `mus_wg_andnc.bmu` | `AleandAnecdotes.mp3` | Darren's Commercially Free Music | | Darren Curtis Music | |\n")
@@ -136,26 +134,22 @@ func TestRunBuildHAKsVerboseListsMappings(t *testing.T) {
mkdirAll(t, filepath.Join(root, "build"))
mkdirAll(t, filepath.Join(root, "src"))
writeFile(t, filepath.Join(root, "nwn-tool.json"), `{
"module": {
"name": "Test Module",
"resref": "testmod"
},
"paths": {
"source": "src",
"assets": "assets",
"build": "build"
},
"haks": [
{
"name": "envi",
"priority": 1,
"max_bytes": 1048576,
"split": false,
"include": ["envi/**"]
}
]
}`+"\n")
writeFile(t, filepath.Join(root, "nwn-tool.yaml"), `
module:
name: Test Module
resref: testmod
paths:
source: src
assets: assets
build: build
haks:
- name: envi
priority: 1
max_bytes: 1048576
split: false
include:
- envi/**
`)
writeFile(t, filepath.Join(root, "assets", "envi", "music", "westgate", "AleandAnecdotes.mp3"), "source-mp3")
writeFile(t, filepath.Join(root, "assets", "envi", "music", "westgate", "CREDITS.md"), "# NWN Music Pack Credits\n\n| Artist | Title | Output File | Original File | Album | Date | License / Copyright | Notes |\n|---|---|---|---|---|---|---|---|\n| Darren Curtis | Ale and Anecdotes | `mus_wg_andnc.bmu` | `AleandAnecdotes.mp3` | Darren's Commercially Free Music | | Darren Curtis Music | |\n")
@@ -198,6 +192,204 @@ func TestRunBuildHAKsVerboseListsMappings(t *testing.T) {
}
}
func TestTopdataConsoleSuppressesProgressInNormalMode(t *testing.T) {
var stdout bytes.Buffer
console := &topdataConsole{
stdout: &stdout,
projectRoot: "/workspace/project",
projectName: "Test Module",
commandName: "build-topdata",
commandLabel: "Build Topdata",
level: logLevelNormal,
}
console.progress("Packaging compiled topdata resources into sow_top.hak...")
if stdout.String() != "" {
t.Fatalf("expected no normal-mode progress output, got %q", stdout.String())
}
}
func TestTopdataConsoleDebugProgressAndRelativePaths(t *testing.T) {
var stdout bytes.Buffer
console := &topdataConsole{
stdout: &stdout,
projectRoot: "/workspace/project",
projectName: "Test Module",
commandName: "deploy-wiki",
commandLabel: "Deploy Wiki",
level: logLevelDebug,
}
console.progress("NodeBB wiki plan: create 1, update 2, skip 3, drift 0")
console.emitWikiDeployResult(10, 1, 2, 3, 4, 0, "/workspace/project/build/wiki/deploy-manifest.json")
output := stdout.String()
if !strings.Contains(output, "[debug] NodeBB wiki plan: create 1, update 2, skip 3, drift 0") {
t.Fatalf("expected debug progress line, got %q", output)
}
if !strings.Contains(output, "manifest: build/wiki/deploy-manifest.json") {
t.Fatalf("expected relative deploy manifest path, got %q", output)
}
}
func TestRunBuildModuleToolkitPreflightRefreshesManifestAndSkipsBrokenCompiler(t *testing.T) {
root := t.TempDir()
mkdirAll(t, filepath.Join(root, "src", "module"))
mkdirAll(t, filepath.Join(root, "src", "placeables"))
mkdirAll(t, filepath.Join(root, "src", "scripts"))
mkdirAll(t, filepath.Join(root, "build"))
mkdirAll(t, filepath.Join(root, "scripts"))
writeFile(t, filepath.Join(root, "nwn-tool.yaml"), `
module:
name: Test Module
resref: testmod
paths:
source: src
build: build
scripts:
compiler:
search:
- tools/script-compiler/nwn_script_comp
`)
writeFile(t, filepath.Join(root, "src", "module", "module.ifo.json"), `{
"file_type": "IFO ",
"file_version": "V3.2",
"root": {
"struct_type": 0,
"fields": [
{"label": "Mod_Name", "type": "CExoString", "value": "Test Module"}
]
}
}
`)
writeFile(t, filepath.Join(root, "src", "placeables", "thing.utp.json"), `{
"file_type": "UTP ",
"file_version": "V3.2",
"root": {
"struct_type": 0,
"fields": [
{"label": "OnUsed", "type": "ResRef", "value": "use_thing"}
]
}
}
`)
writeFile(t, filepath.Join(root, "src", "scripts", "use_thing.nss"), "void main() {}\n")
writeFile(t, filepath.Join(root, "scripts", "fetch-hak-manifest.sh"), "#!/usr/bin/env sh\nset -eu\nmkdir -p \"$(dirname \"$1\")\"\nprintf '{\"module_haks\":[\"sow_core\"],\"haks\":[]}\n' >\"$1\"\n")
if err := os.Chmod(filepath.Join(root, "scripts", "fetch-hak-manifest.sh"), 0o755); err != nil {
t.Fatalf("chmod fetch-hak-manifest.sh: %v", err)
}
badCompiler := filepath.Join(root, "bad-compiler.sh")
writeFile(t, badCompiler, "#!/usr/bin/env sh\nexit 1\n")
if err := os.Chmod(badCompiler, 0o755); err != nil {
t.Fatalf("chmod bad compiler: %v", err)
}
t.Setenv("SOW_NWN_SCRIPT_COMPILER", badCompiler)
t.Setenv("SOW_MODULE_SKIP_NSS_COMPILATION", "")
var stdout bytes.Buffer
var stderr bytes.Buffer
ctx := context{
stdout: &stdout,
stderr: &stderr,
cwd: root,
args: []string{"build-module"},
}
if err := runBuildModule(ctx); err != nil {
t.Fatalf("runBuildModule failed: %v", err)
}
moduleSource := mustReadFile(t, filepath.Join(root, "src", "module", "module.ifo.json"))
if !strings.Contains(moduleSource, "\"Mod_HakList\"") || !strings.Contains(moduleSource, "\"sow_core\"") {
t.Fatalf("expected manifest-applied module source, got %q", moduleSource)
}
if _, err := os.Stat(filepath.Join(root, ".cache", "ncs", "use_thing.ncs")); !os.IsNotExist(err) {
t.Fatalf("expected broken explicit compiler to skip compilation, got err=%v", err)
}
if !strings.Contains(stdout.String(), "Skipping NWScript compilation for this build.") {
t.Fatalf("expected skip-compilation progress in output, got %q", stdout.String())
}
}
func TestRunBuildModuleToolkitPreflightInstallsCompilerWhenNeeded(t *testing.T) {
root := t.TempDir()
mkdirAll(t, filepath.Join(root, "src", "module"))
mkdirAll(t, filepath.Join(root, "src", "placeables"))
mkdirAll(t, filepath.Join(root, "src", "scripts"))
mkdirAll(t, filepath.Join(root, "build"))
mkdirAll(t, filepath.Join(root, "scripts"))
writeFile(t, filepath.Join(root, "nwn-tool.yaml"), `
module:
name: Test Module
resref: testmod
paths:
source: src
build: build
scripts:
compiler:
search:
- tools/script-compiler/nwn_script_comp
`)
writeFile(t, filepath.Join(root, "src", "module", "module.ifo.json"), `{
"file_type": "IFO ",
"file_version": "V3.2",
"root": {
"struct_type": 0,
"fields": [
{"label": "Mod_Name", "type": "CExoString", "value": "Test Module"}
]
}
}
`)
writeFile(t, filepath.Join(root, "src", "placeables", "thing.utp.json"), `{
"file_type": "UTP ",
"file_version": "V3.2",
"root": {
"struct_type": 0,
"fields": [
{"label": "OnUsed", "type": "ResRef", "value": "use_thing"}
]
}
}
`)
writeFile(t, filepath.Join(root, "src", "scripts", "use_thing.nss"), "void main() {}\n")
writeFile(t, filepath.Join(root, "scripts", "fetch-hak-manifest.sh"), "#!/usr/bin/env sh\nset -eu\nmkdir -p \"$(dirname \"$1\")\"\nprintf '{\"module_haks\":[\"sow_core\"],\"haks\":[]}\n' >\"$1\"\n")
writeFile(t, filepath.Join(root, "scripts", "install-script-compiler.sh"), "#!/usr/bin/env sh\nset -eu\ninstall_dir=\"${SOW_SCRIPT_COMPILER_INSTALL_DIR:-./tools/script-compiler}\"\nmkdir -p \"$install_dir\"\ncat >\"$install_dir/nwn_script_comp\" <<'SH'\n#!/usr/bin/env sh\nset -eu\nif [ \"${1:-}\" = \"--help\" ]; then\n exit 0\nfi\nout=\"\"\nsrc=\"\"\nwhile [ \"$#\" -gt 0 ]; do\n case \"$1\" in\n -o)\n out=\"$2\"\n shift 2\n ;;\n --dirs)\n shift 2\n ;;\n *)\n src=\"$1\"\n shift\n ;;\n esac\ndone\nprintf 'compiled:%s\\n' \"$(basename \"$src\")\" >\"$out\"\nSH\nchmod +x \"$install_dir/nwn_script_comp\"\nprintf 'installed: %s\\n' \"$install_dir\"\n")
if err := os.Chmod(filepath.Join(root, "scripts", "fetch-hak-manifest.sh"), 0o755); err != nil {
t.Fatalf("chmod fetch-hak-manifest.sh: %v", err)
}
if err := os.Chmod(filepath.Join(root, "scripts", "install-script-compiler.sh"), 0o755); err != nil {
t.Fatalf("chmod install-script-compiler.sh: %v", err)
}
t.Setenv("SOW_NWN_SCRIPT_COMPILER", "")
t.Setenv("SOW_MODULE_SKIP_NSS_COMPILATION", "")
var stdout bytes.Buffer
var stderr bytes.Buffer
ctx := context{
stdout: &stdout,
stderr: &stderr,
cwd: root,
args: []string{"build-module"},
}
if err := runBuildModule(ctx); err != nil {
t.Fatalf("runBuildModule failed: %v\nstdout:\n%s\nstderr:\n%s", err, stdout.String(), stderr.String())
}
compiledScript := filepath.Join(root, ".cache", "ncs", "use_thing.ncs")
if _, err := os.Stat(compiledScript); err != nil {
t.Fatalf("expected installed compiler to compile script: %v", err)
}
if !strings.Contains(stdout.String(), "NWScript compiler not found locally. Installing it now...") {
t.Fatalf("expected install progress in output, got %q", stdout.String())
}
}
func TestRunMusicScanUsesConfiguredDatasetsWithoutWriting(t *testing.T) {
root := t.TempDir()
mkdirAll(t, filepath.Join(root, "assets", "audio", "westgate"))
@@ -448,3 +640,12 @@ func setFileTime(t *testing.T, path string, modTime time.Time) {
t.Fatalf("set file time %s: %v", path, err)
}
}
func mustReadFile(t *testing.T, path string) string {
t.Helper()
raw, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read %s: %v", path, err)
}
return string(raw)
}