232 lines
7.6 KiB
Go
232 lines
7.6 KiB
Go
package studio
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
const legendSrc = `{
|
|
"_comment": "what the colours mean",
|
|
"image": "Map3.jpg",
|
|
"classes": [
|
|
{ "name": "ocean", "rgb": [91, 175, 185], "sea": true, "depth_m": 512 },
|
|
|
|
{ "_comment_plain": "why the floor is a tenth of the rate, at length",
|
|
"name": "lowland", "rgb": [153, 204, 102], "uplift_mm_yr": 0.08, "k_mult": 1.0,
|
|
"massif": { "floor_mm_yr": 0.012, "fraction": 0.16 },
|
|
"coastal_plain_km": 1.0 },
|
|
|
|
{ "name": "highland", "rgb": [68, 170, 102], "uplift_mm_yr": 0.25, "k_mult": 1.0 }
|
|
]
|
|
}`
|
|
|
|
// The whole reason this is text surgery and not MarshalIndent: a legend is mostly commentary, and the
|
|
// commentary has to survive a save byte for byte.
|
|
func TestPatchingKeepsEverythingItDidNotChange(t *testing.T) {
|
|
out, err := patchClassNumber(legendSrc, "lowland", "uplift_mm_yr", 0.12)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(out, `"uplift_mm_yr": 0.12`) {
|
|
t.Error("the new value is not there")
|
|
}
|
|
if strings.Contains(out, `"uplift_mm_yr": 0.08`) {
|
|
t.Error("the old value is still there")
|
|
}
|
|
if !strings.Contains(out, `"_comment_plain": "why the floor is a tenth of the rate, at length"`) {
|
|
t.Error("the comment was dropped")
|
|
}
|
|
if !strings.Contains(out, `"uplift_mm_yr": 0.25`) {
|
|
t.Error("the other class's rate was touched")
|
|
}
|
|
// And nothing else moved: the only difference from the original is those four characters.
|
|
if a, b := strings.Replace(out, "0.12", "0.08", 1), legendSrc; a != b {
|
|
t.Errorf("the file changed somewhere else:\n--- got\n%s\n--- want\n%s", a, b)
|
|
}
|
|
}
|
|
|
|
func TestPatchingAddsAKeyThatIsNotThere(t *testing.T) {
|
|
out, err := patchClassNumber(legendSrc, "highland", "coastal_plain_km", 4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(out, `"coastal_plain_km": 4`) {
|
|
t.Fatalf("the key was not added:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, `"name": "highland", "coastal_plain_km": 4`) {
|
|
t.Errorf("it did not go in after the name:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestPatchingAMassifBlock(t *testing.T) {
|
|
order := []string{"floor_mm_yr", "fraction"}
|
|
out, err := patchClassObject(legendSrc, "lowland", "massif",
|
|
map[string]float64{"floor_mm_yr": 0.02, "fraction": 0.25}, order)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(out, `"massif": { "floor_mm_yr": 0.02, "fraction": 0.25 }`) {
|
|
t.Fatalf("the massif block was not rewritten:\n%s", out)
|
|
}
|
|
|
|
// Adding one to a class that has none.
|
|
out, err = patchClassObject(legendSrc, "highland", "massif",
|
|
map[string]float64{"floor_mm_yr": 0.045, "fraction": 0.3}, order)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(out, `"massif": { "floor_mm_yr": 0.045, "fraction": 0.3 }`) {
|
|
t.Fatalf("the massif block was not added:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestRemovingAMassifBlock(t *testing.T) {
|
|
out, err := patchClassObject(legendSrc, "lowland", "massif", nil, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if strings.Contains(out, "massif") {
|
|
t.Fatalf("the massif block is still there:\n%s", out)
|
|
}
|
|
// The class has to still parse: no doubled or dangling comma where it was.
|
|
if strings.Contains(out, ",,") || strings.Contains(out, ", }") && !strings.Contains(legendSrc, ", }") {
|
|
t.Errorf("the comma was left in a bad state:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestPatchingARefusesAClassThatIsNotThere(t *testing.T) {
|
|
if _, err := patchClassNumber(legendSrc, "tundra", "uplift_mm_yr", 0.1); err == nil {
|
|
t.Fatal("it accepted a class the legend does not have")
|
|
}
|
|
}
|
|
|
|
const manifestSrc = `{
|
|
"level": "/Game/Maps/L_Planet",
|
|
"planet": {
|
|
"_comment_scale": "why the cell is eight metres",
|
|
"template": "Templates/Map3.jpg",
|
|
"circumference_km": 100,
|
|
"coast_jitter_px": 48
|
|
}
|
|
}`
|
|
|
|
func TestPatchingTheManifestPlanetBlock(t *testing.T) {
|
|
out, err := patchTopNumber(manifestSrc, "planet", "coast_jitter_px", 64)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(out, `"coast_jitter_px": 64`) {
|
|
t.Fatalf("not patched:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, `"_comment_scale"`) {
|
|
t.Error("the comment was dropped")
|
|
}
|
|
|
|
out, err = patchTopNumber(manifestSrc, "planet", "massif_wavelength_km", 7)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(out, `"massif_wavelength_km": 7`) {
|
|
t.Fatalf("a missing key was not added:\n%s", out)
|
|
}
|
|
|
|
out, err = patchTopString(manifestSrc, "planet", "template", "Templates/Map3.png")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(out, `"template": "Templates/Map3.png"`) {
|
|
t.Fatalf("the template path was not repointed:\n%s", out)
|
|
}
|
|
}
|
|
|
|
// The base map is an input a person made by hand and there is no undo for it outside this process. Saving
|
|
// versions rather than overwriting is the whole contract, and the second half of it is that saving twice
|
|
// gives _001 and _002 rather than _001 and _001_002.
|
|
func TestSavingNeverOverwritesAndNumbersUpwards(t *testing.T) {
|
|
dir := t.TempDir()
|
|
base := filepath.Join(dir, "Map3.jpg")
|
|
if err := os.WriteFile(base, []byte("the base map"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
first, err := nextVersion(base)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if filepath.Base(first) != "Map3_001.png" {
|
|
t.Errorf("first save went to %q, want Map3_001.png", filepath.Base(first))
|
|
}
|
|
if err := os.WriteFile(first, []byte("v1"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Asked again from the *versioned* path, which is what the manifest now points at.
|
|
second, err := nextVersion(first)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if filepath.Base(second) != "Map3_002.png" {
|
|
t.Errorf("second save went to %q, want Map3_002.png", filepath.Base(second))
|
|
}
|
|
|
|
// And the base map is still exactly what it was.
|
|
if b, err := os.ReadFile(base); err != nil || string(b) != "the base map" {
|
|
t.Errorf("the base map was touched: %q %v", b, err)
|
|
}
|
|
}
|
|
|
|
// The overlay legend is patched by the same three functions - a mark is an object with a "name" like a class
|
|
// is - so the thing worth testing separately is the one that is new: removing a key.
|
|
const overlaySrc = `{
|
|
"image": "Map3.overlay.png",
|
|
"marks": [
|
|
{ "_comment": "why this shore is pinned, at length",
|
|
"name": "drawn_coast", "rgb": [255, 0, 255], "coast_jitter": 0 },
|
|
{ "name": "forest", "rgb": [0, 128, 0] },
|
|
{ "name": "road", "rgb": [90, 60, 30], "kind": "path", "width_m": 8 }
|
|
]
|
|
}`
|
|
|
|
func TestRemovingAKeyLeavesNoTrace(t *testing.T) {
|
|
// Add one, then take it away: the file has to come back exactly as it started, or every later diff
|
|
// carries the scar of a setting somebody tried once.
|
|
with, err := patchClassNumber(overlaySrc, "forest", "coast_jitter", 0.5)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(with, `"coast_jitter": 0.5`) {
|
|
t.Fatalf("the key was not added:\n%s", with)
|
|
}
|
|
back, err := patchClassRemove(with, "forest", "coast_jitter")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if back != overlaySrc {
|
|
t.Errorf("a round trip changed the file:\n--- want ---\n%s\n--- got ---\n%s", overlaySrc, back)
|
|
}
|
|
|
|
// Removing one that is not there is not an error: the studio sends "this mark says nothing about the
|
|
// coast" whether or not it ever did.
|
|
same, err := patchClassRemove(overlaySrc, "road", "coast_jitter")
|
|
if err != nil || same != overlaySrc {
|
|
t.Errorf("removing an absent key should be a no-op; err=%v changed=%v", err, same != overlaySrc)
|
|
}
|
|
|
|
// And the one that is there, on a mark carrying commentary.
|
|
out, err := patchClassRemove(overlaySrc, "drawn_coast", "coast_jitter")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if strings.Contains(out, "coast_jitter") {
|
|
t.Error("the key is still there")
|
|
}
|
|
if !strings.Contains(out, `"_comment": "why this shore is pinned, at length"`) {
|
|
t.Error("the comment went with it")
|
|
}
|
|
if !strings.Contains(out, `"name": "drawn_coast", "rgb": [255, 0, 255] }`) {
|
|
t.Errorf("the trailing comma and its space were not cleaned up:\n%s", out)
|
|
}
|
|
}
|