919 lines
23 KiB
Go
919 lines
23 KiB
Go
package pipeline
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.westgate.pw/ShadowsOverWestgate/sow-tools/internal/erf"
|
|
"gitea.westgate.pw/ShadowsOverWestgate/sow-tools/internal/project"
|
|
)
|
|
|
|
func TestBuildThenExtract(t *testing.T) {
|
|
root := t.TempDir()
|
|
mustMkdir(t, filepath.Join(root, "src"))
|
|
mustMkdir(t, filepath.Join(root, "assets"))
|
|
mustMkdir(t, filepath.Join(root, "build"))
|
|
|
|
mustWriteFile(t, filepath.Join(root, "nwn-tool.json"), `{
|
|
"module": {
|
|
"name": "Test Module",
|
|
"resref": "testmod"
|
|
},
|
|
"paths": {
|
|
"source": "src",
|
|
"assets": "assets",
|
|
"build": "build"
|
|
}
|
|
}
|
|
`)
|
|
|
|
mustMkdir(t, filepath.Join(root, "src", "module"))
|
|
mustWriteFile(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"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
p, err := project.Load(root)
|
|
if err != nil {
|
|
t.Fatalf("load project: %v", err)
|
|
}
|
|
if err := p.ValidateLayout(); err != nil {
|
|
t.Fatalf("validate layout: %v", err)
|
|
}
|
|
if err := p.Scan(); err != nil {
|
|
t.Fatalf("scan: %v", err)
|
|
}
|
|
|
|
buildResult, err := Build(p)
|
|
if err != nil {
|
|
t.Fatalf("build: %v", err)
|
|
}
|
|
if buildResult.Resources != 1 {
|
|
t.Fatalf("expected 1 resource, got %d", buildResult.Resources)
|
|
}
|
|
|
|
if err := os.Remove(filepath.Join(root, "src", "module", "module.ifo.json")); err != nil {
|
|
t.Fatalf("remove source file: %v", err)
|
|
}
|
|
if err := p.Scan(); err != nil {
|
|
t.Fatalf("rescan: %v", err)
|
|
}
|
|
|
|
extractResult, err := Extract(p)
|
|
if err != nil {
|
|
t.Fatalf("extract: %v", err)
|
|
}
|
|
if extractResult.Written != 1 {
|
|
t.Fatalf("expected 1 extracted file, got %d", extractResult.Written)
|
|
}
|
|
|
|
if _, err := os.Stat(filepath.Join(root, "src", "module", "module.ifo.json")); err != nil {
|
|
t.Fatalf("expected extracted module file: %v", err)
|
|
}
|
|
if err := p.Scan(); err != nil {
|
|
t.Fatalf("scan after extract: %v", err)
|
|
}
|
|
if _, err := BuildModule(p); err != nil {
|
|
t.Fatalf("rebuild after extract: %v", err)
|
|
}
|
|
|
|
compareResult, err := Compare(p)
|
|
if err != nil {
|
|
t.Fatalf("compare: %v", err)
|
|
}
|
|
if compareResult.Checked != 1 {
|
|
t.Fatalf("expected 1 compared resource, got %d", compareResult.Checked)
|
|
}
|
|
}
|
|
|
|
func TestExtractReadsHAKAssets(t *testing.T) {
|
|
root := t.TempDir()
|
|
mustMkdir(t, filepath.Join(root, "src"))
|
|
mustMkdir(t, filepath.Join(root, "assets"))
|
|
mustMkdir(t, filepath.Join(root, "build"))
|
|
|
|
mustWriteFile(t, filepath.Join(root, "nwn-tool.json"), `{
|
|
"module": {
|
|
"name": "Test Module",
|
|
"resref": "testmod"
|
|
},
|
|
"paths": {
|
|
"source": "src",
|
|
"assets": "assets",
|
|
"build": "build"
|
|
}
|
|
}
|
|
`)
|
|
|
|
p, err := project.Load(root)
|
|
if err != nil {
|
|
t.Fatalf("load project: %v", err)
|
|
}
|
|
if err := p.ValidateLayout(); err != nil {
|
|
t.Fatalf("validate layout: %v", err)
|
|
}
|
|
if err := p.Scan(); err != nil {
|
|
t.Fatalf("scan: %v", err)
|
|
}
|
|
|
|
modFile, err := os.Create(filepath.Join(root, "build", "testmod.mod"))
|
|
if err != nil {
|
|
t.Fatalf("create mod: %v", err)
|
|
}
|
|
if err := erf.Write(modFile, erf.New("MOD ", nil)); err != nil {
|
|
t.Fatalf("write mod: %v", err)
|
|
}
|
|
if err := modFile.Close(); err != nil {
|
|
t.Fatalf("close mod: %v", err)
|
|
}
|
|
|
|
hakFile, err := os.Create(filepath.Join(root, "build", "vfx.hak"))
|
|
if err != nil {
|
|
t.Fatalf("create hak: %v", err)
|
|
}
|
|
if err := erf.Write(hakFile, erf.New("HAK ", []erf.Resource{
|
|
{Name: "test_vfx", Type: 0x07D2, Data: []byte("mdl-data")},
|
|
{Name: "test_icon", Type: 0x0006, Data: []byte("plt-data")},
|
|
})); err != nil {
|
|
t.Fatalf("write hak: %v", err)
|
|
}
|
|
if err := hakFile.Close(); err != nil {
|
|
t.Fatalf("close hak: %v", err)
|
|
}
|
|
|
|
result, err := Extract(p)
|
|
if err != nil {
|
|
t.Fatalf("extract: %v", err)
|
|
}
|
|
if len(result.HAKPaths) != 1 {
|
|
t.Fatalf("expected 1 hak path, got %d", len(result.HAKPaths))
|
|
}
|
|
if _, err := os.Stat(filepath.Join(root, "assets", "mdl", "test_vfx.mdl")); err != nil {
|
|
t.Fatalf("expected extracted mdl asset: %v", err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(root, "assets", "plt", "test_icon.plt")); err != nil {
|
|
t.Fatalf("expected extracted plt asset: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestBuildSplitsConfiguredHAKs(t *testing.T) {
|
|
root := t.TempDir()
|
|
mustMkdir(t, filepath.Join(root, "src"))
|
|
mustMkdir(t, filepath.Join(root, "assets", "core"))
|
|
mustMkdir(t, filepath.Join(root, "build"))
|
|
|
|
mustWriteFile(t, filepath.Join(root, "nwn-tool.json"), `{
|
|
"module": {
|
|
"name": "Test Module",
|
|
"resref": "testmod",
|
|
"hak_order": ["group:core"]
|
|
},
|
|
"paths": {
|
|
"source": "src",
|
|
"assets": "assets",
|
|
"build": "build"
|
|
},
|
|
"haks": [
|
|
{
|
|
"name": "core",
|
|
"priority": 1,
|
|
"max_bytes": 300,
|
|
"split": true,
|
|
"include": ["core/**"]
|
|
}
|
|
]
|
|
}
|
|
`)
|
|
mustMkdir(t, filepath.Join(root, "src", "module"))
|
|
mustWriteFile(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"
|
|
},
|
|
{
|
|
"label": "Mod_HakList",
|
|
"type": "List",
|
|
"value": []
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
mustWriteFile(t, filepath.Join(root, "assets", "core", "a.tga"), strings.Repeat("a", 80))
|
|
mustWriteFile(t, filepath.Join(root, "assets", "core", "b.tga"), strings.Repeat("b", 80))
|
|
|
|
p, err := project.Load(root)
|
|
if err != nil {
|
|
t.Fatalf("load project: %v", err)
|
|
}
|
|
if err := p.ValidateLayout(); err != nil {
|
|
t.Fatalf("validate layout: %v", err)
|
|
}
|
|
if err := p.Scan(); err != nil {
|
|
t.Fatalf("scan: %v", err)
|
|
}
|
|
|
|
result, err := BuildHAKs(p)
|
|
if err != nil {
|
|
t.Fatalf("build haks: %v", err)
|
|
}
|
|
if len(result.HAKPaths) != 2 {
|
|
t.Fatalf("expected 2 hak outputs, got %d", len(result.HAKPaths))
|
|
}
|
|
if _, err := os.Stat(filepath.Join(root, "build", "core_01.hak")); err != nil {
|
|
t.Fatalf("expected first split hak: %v", err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(root, "build", "core_02.hak")); err != nil {
|
|
t.Fatalf("expected second split hak: %v", err)
|
|
}
|
|
|
|
rawManifest, err := os.ReadFile(filepath.Join(root, "build", "haks.json"))
|
|
if err != nil {
|
|
t.Fatalf("read manifest: %v", err)
|
|
}
|
|
var manifest BuildManifest
|
|
if err := json.Unmarshal(rawManifest, &manifest); err != nil {
|
|
t.Fatalf("parse manifest: %v", err)
|
|
}
|
|
if len(manifest.HAKs) != 2 {
|
|
t.Fatalf("expected 2 manifest entries, got %d", len(manifest.HAKs))
|
|
}
|
|
if len(manifest.ModuleHAKs) != 2 || manifest.ModuleHAKs[0] != "core_01" || manifest.ModuleHAKs[1] != "core_02" {
|
|
t.Fatalf("unexpected module hak order: %#v", manifest.ModuleHAKs)
|
|
}
|
|
|
|
applyResult, err := ApplyHAKManifest(p, filepath.Join(root, "build", "haks.json"))
|
|
if err != nil {
|
|
t.Fatalf("apply hak manifest: %v", err)
|
|
}
|
|
if applyResult.HAKCount != 2 {
|
|
t.Fatalf("expected 2 applied hak entries, got %d", applyResult.HAKCount)
|
|
}
|
|
|
|
updated, err := os.ReadFile(filepath.Join(root, "src", "module", "module.ifo.json"))
|
|
if err != nil {
|
|
t.Fatalf("read updated ifo: %v", err)
|
|
}
|
|
if !strings.Contains(string(updated), "\"value\": \"core_01\"") || !strings.Contains(string(updated), "\"value\": \"core_02\"") {
|
|
t.Fatalf("updated ifo did not contain expected hak entries:\n%s", string(updated))
|
|
}
|
|
}
|
|
|
|
func TestBuildOrdersMultipleHAKGroups(t *testing.T) {
|
|
root := t.TempDir()
|
|
mustMkdir(t, filepath.Join(root, "src"))
|
|
mustMkdir(t, filepath.Join(root, "src", "module"))
|
|
mustMkdir(t, filepath.Join(root, "assets", "core"))
|
|
mustMkdir(t, filepath.Join(root, "assets", "vfx"))
|
|
mustMkdir(t, filepath.Join(root, "build"))
|
|
|
|
mustWriteFile(t, filepath.Join(root, "nwn-tool.json"), `{
|
|
"module": {
|
|
"name": "Test Module",
|
|
"resref": "testmod",
|
|
"hak_order": ["group:core", "manual_top", "group:vfx"]
|
|
},
|
|
"paths": {
|
|
"source": "src",
|
|
"assets": "assets",
|
|
"build": "build"
|
|
},
|
|
"haks": [
|
|
{
|
|
"name": "core",
|
|
"priority": 1,
|
|
"max_bytes": 0,
|
|
"split": false,
|
|
"include": ["core/**"]
|
|
},
|
|
{
|
|
"name": "vfx",
|
|
"priority": 2,
|
|
"max_bytes": 0,
|
|
"split": false,
|
|
"include": ["vfx/**"]
|
|
}
|
|
]
|
|
}
|
|
`)
|
|
mustWriteFile(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"
|
|
},
|
|
{
|
|
"label": "Mod_HakList",
|
|
"type": "List",
|
|
"value": []
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
mustWriteFile(t, filepath.Join(root, "assets", "core", "a.tga"), "core-data")
|
|
mustWriteFile(t, filepath.Join(root, "assets", "vfx", "b.tga"), "vfx-data")
|
|
|
|
p, err := project.Load(root)
|
|
if err != nil {
|
|
t.Fatalf("load project: %v", err)
|
|
}
|
|
if err := p.ValidateLayout(); err != nil {
|
|
t.Fatalf("validate layout: %v", err)
|
|
}
|
|
if err := p.Scan(); err != nil {
|
|
t.Fatalf("scan: %v", err)
|
|
}
|
|
|
|
result, err := BuildHAKs(p)
|
|
if err != nil {
|
|
t.Fatalf("build haks: %v", err)
|
|
}
|
|
if len(result.HAKPaths) != 2 {
|
|
t.Fatalf("expected 2 hak outputs, got %d", len(result.HAKPaths))
|
|
}
|
|
|
|
rawManifest, err := os.ReadFile(filepath.Join(root, "build", "haks.json"))
|
|
if err != nil {
|
|
t.Fatalf("read manifest: %v", err)
|
|
}
|
|
var manifest BuildManifest
|
|
if err := json.Unmarshal(rawManifest, &manifest); err != nil {
|
|
t.Fatalf("parse manifest: %v", err)
|
|
}
|
|
if len(manifest.ModuleHAKs) != 3 {
|
|
t.Fatalf("expected 3 module hak entries, got %#v", manifest.ModuleHAKs)
|
|
}
|
|
if manifest.ModuleHAKs[0] != "core" || manifest.ModuleHAKs[1] != "manual_top" || manifest.ModuleHAKs[2] != "vfx" {
|
|
t.Fatalf("unexpected module hak order: %#v", manifest.ModuleHAKs)
|
|
}
|
|
}
|
|
|
|
func TestBuildSplitsMultipleHAKGroupsDeterministically(t *testing.T) {
|
|
root := t.TempDir()
|
|
mustMkdir(t, filepath.Join(root, "src"))
|
|
mustMkdir(t, filepath.Join(root, "src", "module"))
|
|
mustMkdir(t, filepath.Join(root, "assets", "core"))
|
|
mustMkdir(t, filepath.Join(root, "assets", "vfx"))
|
|
mustMkdir(t, filepath.Join(root, "build"))
|
|
|
|
mustWriteFile(t, filepath.Join(root, "nwn-tool.json"), `{
|
|
"module": {
|
|
"name": "Test Module",
|
|
"resref": "testmod",
|
|
"hak_order": ["group:core", "manual_top", "group:vfx"]
|
|
},
|
|
"paths": {
|
|
"source": "src",
|
|
"assets": "assets",
|
|
"build": "build"
|
|
},
|
|
"haks": [
|
|
{
|
|
"name": "core",
|
|
"priority": 1,
|
|
"max_bytes": 310,
|
|
"split": true,
|
|
"include": ["core/**"]
|
|
},
|
|
{
|
|
"name": "vfx",
|
|
"priority": 2,
|
|
"max_bytes": 310,
|
|
"split": true,
|
|
"include": ["vfx/**"]
|
|
}
|
|
]
|
|
}
|
|
|
|
`)
|
|
mustWriteFile(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"
|
|
},
|
|
{
|
|
"label": "Mod_HakList",
|
|
"type": "List",
|
|
"value": []
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
mustWriteFile(t, filepath.Join(root, "assets", "core", "a.tga"), strings.Repeat("a", 80))
|
|
mustWriteFile(t, filepath.Join(root, "assets", "core", "b.tga"), strings.Repeat("b", 80))
|
|
mustWriteFile(t, filepath.Join(root, "assets", "vfx", "c.tga"), strings.Repeat("c", 80))
|
|
mustWriteFile(t, filepath.Join(root, "assets", "vfx", "d.tga"), strings.Repeat("d", 80))
|
|
|
|
p, err := project.Load(root)
|
|
if err != nil {
|
|
t.Fatalf("load project: %v", err)
|
|
}
|
|
if err := p.ValidateLayout(); err != nil {
|
|
t.Fatalf("validate layout: %v", err)
|
|
}
|
|
if err := p.Scan(); err != nil {
|
|
t.Fatalf("scan: %v", err)
|
|
}
|
|
|
|
result, err := BuildHAKs(p)
|
|
if err != nil {
|
|
t.Fatalf("build haks: %v", err)
|
|
}
|
|
if len(result.HAKPaths) != 4 {
|
|
t.Fatalf("expected 4 hak outputs, got %d", len(result.HAKPaths))
|
|
}
|
|
|
|
expectedFiles := []string{
|
|
"core_01.hak",
|
|
"core_02.hak",
|
|
"vfx_01.hak",
|
|
"vfx_02.hak",
|
|
}
|
|
for _, name := range expectedFiles {
|
|
if _, err := os.Stat(filepath.Join(root, "build", name)); err != nil {
|
|
t.Fatalf("expected split hak %s: %v", name, err)
|
|
}
|
|
}
|
|
|
|
rawManifest, err := os.ReadFile(filepath.Join(root, "build", "haks.json"))
|
|
if err != nil {
|
|
t.Fatalf("read manifest: %v", err)
|
|
}
|
|
var manifest BuildManifest
|
|
if err := json.Unmarshal(rawManifest, &manifest); err != nil {
|
|
t.Fatalf("parse manifest: %v", err)
|
|
}
|
|
if len(manifest.HAKs) != 4 {
|
|
t.Fatalf("expected 4 manifest hak entries, got %d", len(manifest.HAKs))
|
|
}
|
|
|
|
actualManifestNames := make([]string, 0, len(manifest.HAKs))
|
|
for _, hak := range manifest.HAKs {
|
|
actualManifestNames = append(actualManifestNames, hak.Name)
|
|
}
|
|
expectedManifestNames := []string{"core_01", "core_02", "vfx_01", "vfx_02"}
|
|
if strings.Join(actualManifestNames, ",") != strings.Join(expectedManifestNames, ",") {
|
|
t.Fatalf("unexpected manifest hak order: %#v", actualManifestNames)
|
|
}
|
|
|
|
expectedModuleHAKs := []string{"core_01", "core_02", "manual_top", "vfx_01", "vfx_02"}
|
|
if strings.Join(manifest.ModuleHAKs, ",") != strings.Join(expectedModuleHAKs, ",") {
|
|
t.Fatalf("unexpected module hak order: %#v", manifest.ModuleHAKs)
|
|
}
|
|
|
|
applyResult, err := ApplyHAKManifest(p, filepath.Join(root, "build", "haks.json"))
|
|
if err != nil {
|
|
t.Fatalf("apply hak manifest: %v", err)
|
|
}
|
|
if applyResult.HAKCount != 5 {
|
|
t.Fatalf("expected 5 applied hak entries, got %d", applyResult.HAKCount)
|
|
}
|
|
|
|
updated, err := os.ReadFile(filepath.Join(root, "src", "module", "module.ifo.json"))
|
|
if err != nil {
|
|
t.Fatalf("read updated ifo: %v", err)
|
|
}
|
|
for _, name := range expectedModuleHAKs {
|
|
if !strings.Contains(string(updated), "\"value\": \""+name+"\"") {
|
|
t.Fatalf("updated ifo missing hak %s:\n%s", name, string(updated))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildSkipsEmptyHAKGroupsInModuleOrder(t *testing.T) {
|
|
root := t.TempDir()
|
|
mustMkdir(t, filepath.Join(root, "src"))
|
|
mustMkdir(t, filepath.Join(root, "src", "module"))
|
|
mustMkdir(t, filepath.Join(root, "assets", "core"))
|
|
mustMkdir(t, filepath.Join(root, "assets", "vfx"))
|
|
mustMkdir(t, filepath.Join(root, "build"))
|
|
|
|
mustWriteFile(t, filepath.Join(root, "nwn-tool.json"), `{
|
|
"module": {
|
|
"name": "Test Module",
|
|
"resref": "testmod",
|
|
"hak_order": ["manual_top", "group:core", "group:empty", "group:vfx"]
|
|
},
|
|
"paths": {
|
|
"source": "src",
|
|
"assets": "assets",
|
|
"build": "build"
|
|
},
|
|
"haks": [
|
|
{
|
|
"name": "core",
|
|
"priority": 1,
|
|
"max_bytes": 1024,
|
|
"split": false,
|
|
"include": ["core/**"]
|
|
},
|
|
{
|
|
"name": "empty",
|
|
"priority": 2,
|
|
"max_bytes": 1024,
|
|
"split": false,
|
|
"include": ["empty/**"]
|
|
},
|
|
{
|
|
"name": "vfx",
|
|
"priority": 3,
|
|
"max_bytes": 1024,
|
|
"split": false,
|
|
"include": ["vfx/**"]
|
|
}
|
|
]
|
|
}`)
|
|
|
|
mustWriteFile(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"
|
|
},
|
|
{
|
|
"label": "Mod_HakList",
|
|
"type": "List",
|
|
"value": []
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
mustWriteFile(t, filepath.Join(root, "assets", "core", "core_a.tga"), "core-a")
|
|
mustWriteFile(t, filepath.Join(root, "assets", "vfx", "vfx_a.tga"), "vfx-a")
|
|
|
|
p, err := project.Load(root)
|
|
if err != nil {
|
|
t.Fatalf("load project: %v", err)
|
|
}
|
|
if err := p.ValidateLayout(); err != nil {
|
|
t.Fatalf("validate layout: %v", err)
|
|
}
|
|
if err := p.Scan(); err != nil {
|
|
t.Fatalf("scan: %v", err)
|
|
}
|
|
|
|
result, err := BuildHAKs(p)
|
|
if err != nil {
|
|
t.Fatalf("build haks: %v", err)
|
|
}
|
|
|
|
rawManifest, err := os.ReadFile(result.Manifest)
|
|
if err != nil {
|
|
t.Fatalf("read manifest: %v", err)
|
|
}
|
|
var manifest BuildManifest
|
|
if err := json.Unmarshal(rawManifest, &manifest); err != nil {
|
|
t.Fatalf("parse manifest: %v", err)
|
|
}
|
|
if got, want := strings.Join(manifest.ModuleHAKs, ","), "manual_top,core,vfx"; got != want {
|
|
t.Fatalf("unexpected module hak order: got %q want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestExtractOverwritesAndRemovesStaleFiles(t *testing.T) {
|
|
root := t.TempDir()
|
|
mustMkdir(t, filepath.Join(root, "src", "module"))
|
|
mustMkdir(t, filepath.Join(root, "src", "areas"))
|
|
mustMkdir(t, filepath.Join(root, "assets"))
|
|
mustMkdir(t, filepath.Join(root, "build"))
|
|
|
|
mustWriteFile(t, filepath.Join(root, "nwn-tool.json"), `{
|
|
"module": {
|
|
"name": "Test Module",
|
|
"resref": "testmod"
|
|
},
|
|
"paths": {
|
|
"source": "src",
|
|
"assets": "assets",
|
|
"build": "build"
|
|
}
|
|
}
|
|
`)
|
|
|
|
mustWriteFile(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": "Original Module"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
p, err := project.Load(root)
|
|
if err != nil {
|
|
t.Fatalf("load project: %v", err)
|
|
}
|
|
if err := p.ValidateLayout(); err != nil {
|
|
t.Fatalf("validate layout: %v", err)
|
|
}
|
|
if err := p.Scan(); err != nil {
|
|
t.Fatalf("scan: %v", err)
|
|
}
|
|
|
|
if _, err := BuildModule(p); err != nil {
|
|
t.Fatalf("build module: %v", err)
|
|
}
|
|
|
|
mustWriteFile(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": "Changed In Toolset"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
mustWriteFile(t, filepath.Join(root, "src", "areas", "area001.are.json"), `{"stale": true}`)
|
|
|
|
if err := p.Scan(); err != nil {
|
|
t.Fatalf("rescan before extract: %v", err)
|
|
}
|
|
|
|
result, err := Extract(p)
|
|
if err != nil {
|
|
t.Fatalf("extract: %v", err)
|
|
}
|
|
if result.Overwritten != 1 {
|
|
t.Fatalf("expected 1 overwritten file, got %d", result.Overwritten)
|
|
}
|
|
if result.Removed != 1 {
|
|
t.Fatalf("expected 1 removed stale file, got %d", result.Removed)
|
|
}
|
|
|
|
raw, err := os.ReadFile(filepath.Join(root, "src", "module", "module.ifo.json"))
|
|
if err != nil {
|
|
t.Fatalf("read extracted module file: %v", err)
|
|
}
|
|
if !strings.Contains(string(raw), "Original Module") {
|
|
t.Fatalf("expected extracted module file to be overwritten with archive contents:\n%s", string(raw))
|
|
}
|
|
if _, err := os.Stat(filepath.Join(root, "src", "areas", "area001.are.json")); !os.IsNotExist(err) {
|
|
t.Fatalf("expected stale area file to be removed, stat err=%v", err)
|
|
}
|
|
}
|
|
|
|
func TestExtractNormalizesResourceNamesToLowercase(t *testing.T) {
|
|
root := t.TempDir()
|
|
mustMkdir(t, filepath.Join(root, "src", "blueprints", "items"))
|
|
mustMkdir(t, filepath.Join(root, "assets"))
|
|
mustMkdir(t, filepath.Join(root, "build"))
|
|
|
|
mustWriteFile(t, filepath.Join(root, "nwn-tool.json"), `{
|
|
"module": {
|
|
"name": "Test Module",
|
|
"resref": "testmod"
|
|
},
|
|
"paths": {
|
|
"source": "src",
|
|
"assets": "assets",
|
|
"build": "build"
|
|
}
|
|
}
|
|
`)
|
|
|
|
mustWriteFile(t, filepath.Join(root, "src", "blueprints", "items", "I_ELVENCHAIN.uti.json"), `{
|
|
"file_type": "UTI ",
|
|
"file_version": "V3.2",
|
|
"root": {
|
|
"struct_type": 0,
|
|
"fields": [
|
|
{
|
|
"label": "LocalizedName",
|
|
"type": "CExoString",
|
|
"value": "Elven Chain"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
p, err := project.Load(root)
|
|
if err != nil {
|
|
t.Fatalf("load project: %v", err)
|
|
}
|
|
if err := p.ValidateLayout(); err != nil {
|
|
t.Fatalf("validate layout: %v", err)
|
|
}
|
|
if err := p.Scan(); err != nil {
|
|
t.Fatalf("scan: %v", err)
|
|
}
|
|
|
|
if _, err := BuildModule(p); err != nil {
|
|
t.Fatalf("build module: %v", err)
|
|
}
|
|
if err := os.Remove(filepath.Join(root, "src", "blueprints", "items", "I_ELVENCHAIN.uti.json")); err != nil {
|
|
t.Fatalf("remove uppercase source file: %v", err)
|
|
}
|
|
if err := p.Scan(); err != nil {
|
|
t.Fatalf("rescan before extract: %v", err)
|
|
}
|
|
|
|
result, err := Extract(p)
|
|
if err != nil {
|
|
t.Fatalf("extract: %v", err)
|
|
}
|
|
if result.Written != 1 {
|
|
t.Fatalf("expected 1 written file, got %d", result.Written)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(root, "src", "blueprints", "items", "i_elvenchain.uti.json")); err != nil {
|
|
t.Fatalf("expected lowercase extracted resource: %v", err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(root, "src", "blueprints", "items", "I_ELVENCHAIN.uti.json")); !os.IsNotExist(err) {
|
|
t.Fatalf("expected uppercase extracted resource path to be absent, stat err=%v", err)
|
|
}
|
|
}
|
|
|
|
func TestCompareFailsWhenBuildIsStale(t *testing.T) {
|
|
root := t.TempDir()
|
|
mustMkdir(t, filepath.Join(root, "src", "module"))
|
|
mustMkdir(t, filepath.Join(root, "assets"))
|
|
mustMkdir(t, filepath.Join(root, "build"))
|
|
|
|
mustWriteFile(t, filepath.Join(root, "nwn-tool.json"), `{
|
|
"module": {
|
|
"name": "Test Module",
|
|
"resref": "testmod"
|
|
},
|
|
"paths": {
|
|
"source": "src",
|
|
"assets": "assets",
|
|
"build": "build"
|
|
}
|
|
}
|
|
`)
|
|
mustWriteFile(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": "Original Module"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
p, err := project.Load(root)
|
|
if err != nil {
|
|
t.Fatalf("load project: %v", err)
|
|
}
|
|
if err := p.ValidateLayout(); err != nil {
|
|
t.Fatalf("validate layout: %v", err)
|
|
}
|
|
if err := p.Scan(); err != nil {
|
|
t.Fatalf("scan: %v", err)
|
|
}
|
|
|
|
if _, err := BuildModule(p); err != nil {
|
|
t.Fatalf("build module: %v", err)
|
|
}
|
|
mustWriteFile(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": "Updated Module"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
if err := p.Scan(); err != nil {
|
|
t.Fatalf("rescan: %v", err)
|
|
}
|
|
|
|
_, err = Compare(p)
|
|
if err == nil {
|
|
t.Fatal("expected stale compare error")
|
|
}
|
|
if !strings.Contains(err.Error(), "built archives are older than source file") {
|
|
t.Fatalf("unexpected compare error: %v", err)
|
|
}
|
|
}
|
|
|
|
func mustMkdir(t *testing.T, path string) {
|
|
t.Helper()
|
|
if err := os.MkdirAll(path, 0o755); err != nil {
|
|
t.Fatalf("mkdir %s: %v", path, err)
|
|
}
|
|
}
|
|
|
|
func mustWriteFile(t *testing.T, path, data string) {
|
|
t.Helper()
|
|
if err := os.WriteFile(path, []byte(data), 0o644); err != nil {
|
|
t.Fatalf("write %s: %v", path, err)
|
|
}
|
|
}
|
|
|
|
func TestBuildHAKsFailsForDuplicateResourcesInSameHAK(t *testing.T) {
|
|
root := t.TempDir()
|
|
mustMkdir(t, filepath.Join(root, "src"))
|
|
mustMkdir(t, filepath.Join(root, "assets", "core", "a"))
|
|
mustMkdir(t, filepath.Join(root, "assets", "core", "b"))
|
|
mustMkdir(t, filepath.Join(root, "build"))
|
|
|
|
mustWriteFile(t, filepath.Join(root, "nwn-tool.json"), `{
|
|
"module": {
|
|
"name": "Test Module",
|
|
"resref": "testmod"
|
|
},
|
|
"paths": {
|
|
"source": "src",
|
|
"assets": "assets",
|
|
"build": "build"
|
|
},
|
|
"haks": [
|
|
{
|
|
"name": "core",
|
|
"priority": 1,
|
|
"max_bytes": 0,
|
|
"split": false,
|
|
"include": ["core/**"]
|
|
}
|
|
]
|
|
}
|
|
`)
|
|
mustWriteFile(t, filepath.Join(root, "assets", "core", "a", "shared.mdl"), "one")
|
|
mustWriteFile(t, filepath.Join(root, "assets", "core", "b", "shared.mdl"), "two")
|
|
|
|
p, err := project.Load(root)
|
|
if err != nil {
|
|
t.Fatalf("load project: %v", err)
|
|
}
|
|
if err := p.ValidateLayout(); err != nil {
|
|
t.Fatalf("validate layout: %v", err)
|
|
}
|
|
if err := p.Scan(); err != nil {
|
|
t.Fatalf("scan: %v", err)
|
|
}
|
|
|
|
_, err = BuildHAKs(p)
|
|
if err == nil {
|
|
t.Fatal("expected duplicate same-hak build error")
|
|
}
|
|
if !strings.Contains(err.Error(), "conflicts") {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|