414 lines
12 KiB
Go
414 lines
12 KiB
Go
package app
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestRunBuildTopPackageUsesCachedCompiledOutputs(t *testing.T) {
|
|
root := t.TempDir()
|
|
mkdirAll(t, filepath.Join(root, "build"))
|
|
mkdirAll(t, filepath.Join(root, ".cache", "2da"))
|
|
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, ".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")
|
|
writeFile(t, filepath.Join(root, "topdata", "data", "repadjust", "base.json"), "{ this is intentionally invalid json }\n")
|
|
|
|
sourceTime := time.Now().Add(-2 * time.Hour)
|
|
outputTime := time.Now().Add(-1 * time.Hour)
|
|
setFileTime(t, filepath.Join(root, "topdata", "assets", "gui", "testicon.png"), sourceTime)
|
|
setFileTime(t, filepath.Join(root, "topdata", "data", "repadjust", "base.json"), sourceTime)
|
|
setFileTime(t, filepath.Join(root, ".cache", "2da", "repadjust.2da"), outputTime)
|
|
setFileTime(t, filepath.Join(root, "build", "sow_tlk.tlk"), outputTime)
|
|
|
|
var stdout bytes.Buffer
|
|
ctx := context{
|
|
stdout: &stdout,
|
|
stderr: &bytes.Buffer{},
|
|
cwd: root,
|
|
args: []string{"build-top-package"},
|
|
}
|
|
|
|
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())
|
|
}
|
|
if _, err := os.Stat(filepath.Join(root, "build", "sow_top.hak")); err != nil {
|
|
t.Fatalf("expected packaged hak output: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestRunBuildHAKsEmitsCompactSummary(t *testing.T) {
|
|
root := t.TempDir()
|
|
mkdirAll(t, filepath.Join(root, "assets", "envi", "music", "westgate"))
|
|
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, "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")
|
|
|
|
ffprobePath := filepath.Join(root, "ffprobe")
|
|
writeFile(t, ffprobePath, "#!/bin/sh\nprintf '%s\\n' '{\"format\":{\"tags\":{\"artist\":\"Unknown\",\"title\":\"Broken Metadata\"}}}'\n")
|
|
if err := os.Chmod(ffprobePath, 0o755); err != nil {
|
|
t.Fatalf("chmod ffprobe: %v", err)
|
|
}
|
|
|
|
ffmpegPath := filepath.Join(root, "ffmpeg")
|
|
writeFile(t, ffmpegPath, "#!/bin/sh\nfor last; do :; done\nprintf 'bmu-data' > \"$last\"\n")
|
|
if err := os.Chmod(ffmpegPath, 0o755); err != nil {
|
|
t.Fatalf("chmod ffmpeg: %v", err)
|
|
}
|
|
|
|
t.Setenv("SOW_FFMPEG", ffmpegPath)
|
|
t.Setenv("SOW_FFPROBE", ffprobePath)
|
|
|
|
var stdout bytes.Buffer
|
|
ctx := context{
|
|
stdout: &stdout,
|
|
stderr: &bytes.Buffer{},
|
|
cwd: root,
|
|
args: []string{"build-haks"},
|
|
}
|
|
|
|
if err := runBuildHAKs(ctx); err != nil {
|
|
t.Fatalf("runBuildHAKs failed: %v", err)
|
|
}
|
|
|
|
output := stdout.String()
|
|
if !strings.Contains(output, "Build HAKs ----------") {
|
|
t.Fatalf("expected build header, got %q", output)
|
|
}
|
|
if !strings.Contains(output, "mapped: 1 music file(s); use --verbose to list mappings") {
|
|
t.Fatalf("expected compact mapping summary, got %q", output)
|
|
}
|
|
if strings.Contains(output, "AleandAnecdotes.mp3 -> mus_wg_andnc.bmu") {
|
|
t.Fatalf("did not expect verbose mapping in normal mode, got %q", output)
|
|
}
|
|
if !strings.Contains(output, "manifest: build/haks.json") {
|
|
t.Fatalf("expected relative manifest path, got %q", output)
|
|
}
|
|
}
|
|
|
|
func TestRunBuildHAKsVerboseListsMappings(t *testing.T) {
|
|
root := t.TempDir()
|
|
mkdirAll(t, filepath.Join(root, "assets", "envi", "music", "westgate"))
|
|
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, "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")
|
|
|
|
ffprobePath := filepath.Join(root, "ffprobe")
|
|
writeFile(t, ffprobePath, "#!/bin/sh\nprintf '%s\\n' '{\"format\":{\"tags\":{\"artist\":\"Unknown\",\"title\":\"Broken Metadata\"}}}'\n")
|
|
if err := os.Chmod(ffprobePath, 0o755); err != nil {
|
|
t.Fatalf("chmod ffprobe: %v", err)
|
|
}
|
|
|
|
ffmpegPath := filepath.Join(root, "ffmpeg")
|
|
writeFile(t, ffmpegPath, "#!/bin/sh\nfor last; do :; done\nprintf 'bmu-data' > \"$last\"\n")
|
|
if err := os.Chmod(ffmpegPath, 0o755); err != nil {
|
|
t.Fatalf("chmod ffmpeg: %v", err)
|
|
}
|
|
|
|
t.Setenv("SOW_FFMPEG", ffmpegPath)
|
|
t.Setenv("SOW_FFPROBE", ffprobePath)
|
|
|
|
var stdout bytes.Buffer
|
|
ctx := context{
|
|
stdout: &stdout,
|
|
stderr: &bytes.Buffer{},
|
|
cwd: root,
|
|
args: []string{"build-haks", "--verbose"},
|
|
}
|
|
|
|
if err := runBuildHAKs(ctx); err != nil {
|
|
t.Fatalf("runBuildHAKs failed: %v", err)
|
|
}
|
|
|
|
output := stdout.String()
|
|
if !strings.Contains(output, "mappings:") {
|
|
t.Fatalf("expected verbose mappings header, got %q", output)
|
|
}
|
|
if !strings.Contains(output, "AleandAnecdotes.mp3 -> mus_wg_andnc.bmu") {
|
|
t.Fatalf("expected verbose mapping output, got %q", output)
|
|
}
|
|
if !strings.Contains(output, "wrote: envi (1 assets)") {
|
|
t.Fatalf("expected verbose archive action, got %q", output)
|
|
}
|
|
}
|
|
|
|
func TestRunMusicScanUsesConfiguredDatasetsWithoutWriting(t *testing.T) {
|
|
root := t.TempDir()
|
|
mkdirAll(t, filepath.Join(root, "assets", "audio", "westgate"))
|
|
mkdirAll(t, filepath.Join(root, "build"))
|
|
writeFile(t, filepath.Join(root, "nwn-tool.yaml"), `
|
|
module:
|
|
name: Test Module
|
|
resref: testmod
|
|
paths:
|
|
assets: assets
|
|
build: build
|
|
music:
|
|
datasets:
|
|
westgate_audio:
|
|
source: audio/westgate
|
|
output: generated/music
|
|
prefix: wg_
|
|
`)
|
|
writeFile(t, filepath.Join(root, "assets", "audio", "westgate", "Theme Song.mp3"), "source-mp3")
|
|
|
|
var stdout bytes.Buffer
|
|
ctx := context{
|
|
stdout: &stdout,
|
|
stderr: &bytes.Buffer{},
|
|
cwd: root,
|
|
args: []string{"music", "scan", "--dataset", "westgate_audio"},
|
|
}
|
|
|
|
if err := runMusic(ctx); err != nil {
|
|
t.Fatalf("runMusic failed: %v", err)
|
|
}
|
|
output := stdout.String()
|
|
if !strings.Contains(output, "music scan") || !strings.Contains(output, "tracks: 1") {
|
|
t.Fatalf("unexpected music scan output: %q", output)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(root, ".cache", "credits")); !os.IsNotExist(err) {
|
|
t.Fatalf("music scan should not write credits artifacts, stat err=%v", err)
|
|
}
|
|
}
|
|
|
|
func TestRunMusicListDatasets(t *testing.T) {
|
|
root := t.TempDir()
|
|
mkdirAll(t, filepath.Join(root, "assets"))
|
|
mkdirAll(t, filepath.Join(root, "build"))
|
|
writeFile(t, filepath.Join(root, "nwn-tool.yaml"), `
|
|
module:
|
|
name: Test Module
|
|
resref: testmod
|
|
paths:
|
|
assets: assets
|
|
music:
|
|
datasets:
|
|
westgate_audio:
|
|
source: audio/westgate
|
|
output: generated/music
|
|
prefix: wg_
|
|
`)
|
|
|
|
var stdout bytes.Buffer
|
|
ctx := context{
|
|
stdout: &stdout,
|
|
stderr: &bytes.Buffer{},
|
|
cwd: root,
|
|
args: []string{"music", "list-datasets"},
|
|
}
|
|
|
|
if err := runMusic(ctx); err != nil {
|
|
t.Fatalf("runMusic failed: %v", err)
|
|
}
|
|
if !strings.Contains(stdout.String(), "westgate_audio: source=audio/westgate output=generated/music prefix=wg_") {
|
|
t.Fatalf("unexpected dataset list: %q", stdout.String())
|
|
}
|
|
}
|
|
|
|
func TestRunConfigEffectiveJSONShowsDefaultsAndProvenance(t *testing.T) {
|
|
root := t.TempDir()
|
|
writeFile(t, filepath.Join(root, "nwn-tool.yaml"), `
|
|
module:
|
|
name: Test Module
|
|
resref: testmod
|
|
paths:
|
|
source: src
|
|
`)
|
|
|
|
var stdout bytes.Buffer
|
|
ctx := context{
|
|
stdout: &stdout,
|
|
stderr: &bytes.Buffer{},
|
|
cwd: root,
|
|
args: []string{"config", "effective", "--json"},
|
|
}
|
|
|
|
if err := runConfig(ctx); err != nil {
|
|
t.Fatalf("runConfig failed: %v", err)
|
|
}
|
|
output := stdout.String()
|
|
if !strings.Contains(output, `"hak_manifest": "haks.json"`) {
|
|
t.Fatalf("expected HAK manifest default in effective config, got %q", output)
|
|
}
|
|
if !strings.Contains(output, `"paths.build":`) || !strings.Contains(output, `"toolkit default"`) {
|
|
t.Fatalf("expected default provenance in effective config, got %q", output)
|
|
}
|
|
}
|
|
|
|
func TestRunConfigExplainReportsYAMLSource(t *testing.T) {
|
|
root := t.TempDir()
|
|
writeFile(t, filepath.Join(root, "nwn-tool.yaml"), `
|
|
module:
|
|
name: Test Module
|
|
resref: testmod
|
|
paths:
|
|
build: output
|
|
`)
|
|
|
|
var stdout bytes.Buffer
|
|
ctx := context{
|
|
stdout: &stdout,
|
|
stderr: &bytes.Buffer{},
|
|
cwd: root,
|
|
args: []string{"config", "explain", "paths.build"},
|
|
}
|
|
|
|
if err := runConfig(ctx); err != nil {
|
|
t.Fatalf("runConfig failed: %v", err)
|
|
}
|
|
output := stdout.String()
|
|
if !strings.Contains(output, "value: \"output\"") {
|
|
t.Fatalf("expected configured build value, got %q", output)
|
|
}
|
|
if !strings.Contains(output, "source: yaml") {
|
|
t.Fatalf("expected YAML source, got %q", output)
|
|
}
|
|
}
|
|
|
|
func TestRunConfigValidateDoesNotScanSourceInventory(t *testing.T) {
|
|
root := t.TempDir()
|
|
mkdirAll(t, filepath.Join(root, "build"))
|
|
writeFile(t, filepath.Join(root, "nwn-tool.yaml"), `
|
|
module:
|
|
name: Test Module
|
|
resref: testmod
|
|
paths:
|
|
source: missing-src
|
|
build: build
|
|
`)
|
|
|
|
var stdout bytes.Buffer
|
|
ctx := context{
|
|
stdout: &stdout,
|
|
stderr: &bytes.Buffer{},
|
|
cwd: root,
|
|
args: []string{"config", "validate"},
|
|
}
|
|
|
|
if err := runConfig(ctx); err != nil {
|
|
t.Fatalf("runConfig failed: %v", err)
|
|
}
|
|
if !strings.Contains(stdout.String(), "config: ok") {
|
|
t.Fatalf("expected config validation output, got %q", stdout.String())
|
|
}
|
|
}
|
|
|
|
func TestRunConfigSourcesListsActiveOverrides(t *testing.T) {
|
|
root := t.TempDir()
|
|
writeFile(t, filepath.Join(root, "nwn-tool.yaml"), `
|
|
module:
|
|
name: Test Module
|
|
resref: testmod
|
|
paths:
|
|
source: src
|
|
`)
|
|
t.Setenv("SOW_BUILD_HAKS_KEEP_EXISTING", "1")
|
|
|
|
var stdout bytes.Buffer
|
|
ctx := context{
|
|
stdout: &stdout,
|
|
stderr: &bytes.Buffer{},
|
|
cwd: root,
|
|
args: []string{"config", "sources"},
|
|
}
|
|
|
|
if err := runConfig(ctx); err != nil {
|
|
t.Fatalf("runConfig failed: %v", err)
|
|
}
|
|
output := stdout.String()
|
|
if !strings.Contains(output, "active overrides:") {
|
|
t.Fatalf("expected active overrides section, got %q", output)
|
|
}
|
|
if !strings.Contains(output, "build.keep_existing_haks=1") {
|
|
t.Fatalf("expected keep existing override, got %q", output)
|
|
}
|
|
}
|
|
|
|
func mkdirAll(t *testing.T, path string) {
|
|
t.Helper()
|
|
if err := os.MkdirAll(path, 0o755); err != nil {
|
|
t.Fatalf("mkdir %s: %v", path, err)
|
|
}
|
|
}
|
|
|
|
func writeFile(t *testing.T, path, content string) {
|
|
t.Helper()
|
|
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
|
t.Fatalf("write %s: %v", path, err)
|
|
}
|
|
}
|
|
|
|
func setFileTime(t *testing.T, path string, modTime time.Time) {
|
|
t.Helper()
|
|
if err := os.Chtimes(path, modTime, modTime); err != nil {
|
|
t.Fatalf("set file time %s: %v", path, err)
|
|
}
|
|
}
|