142 lines
5.5 KiB
GDScript3
142 lines
5.5 KiB
GDScript3
|
|
extends SceneTree
|
||
|
|
|
||
|
|
# Headless test: run with
|
||
|
|
# godot --headless -s res://tests/test_planet_map_assignment.gd
|
||
|
|
# Every planet should draw a map of its own surface type at random, and then
|
||
|
|
# keep it - leaving a planet and coming back must land on the same layout, or
|
||
|
|
# the place stops feeling like a place.
|
||
|
|
#
|
||
|
|
# Two things could break that, and both are silent:
|
||
|
|
# - the candidate list is built by scanning a directory, and the order a
|
||
|
|
# filesystem lists files in is not something to seed a choice with. The
|
||
|
|
# pick indexes into that list, so an unstable order re-deals every planet
|
||
|
|
# - the pool itself changes the moment the map editor saves a new setup,
|
||
|
|
# which would re-deal planets mid-session even with a perfect seed
|
||
|
|
#
|
||
|
|
# Covers:
|
||
|
|
# A. the same planet resolves to the same setup, every time
|
||
|
|
# B. the setup it draws is always authored for that planet's own profile
|
||
|
|
# C. different planets do not all collapse onto one setup
|
||
|
|
# D. an assignment survives a new setup file appearing in the pool
|
||
|
|
# E. candidates come back in a deterministic order regardless of scan order
|
||
|
|
|
||
|
|
const SCRATCH_ID: String = "test_assignment_scratch"
|
||
|
|
const SCRATCH_PATH: String = "res://PlanetSetups/%s.tres" % SCRATCH_ID
|
||
|
|
|
||
|
|
|
||
|
|
func _initialize() -> void:
|
||
|
|
_remove_scratch()
|
||
|
|
PlanetSetups.rescan()
|
||
|
|
|
||
|
|
# --- E. deterministic ordering ------------------------------------------
|
||
|
|
var profile_id: String = "rocky"
|
||
|
|
var ordered: Array = PlanetSetups.setups_for_profile(profile_id)
|
||
|
|
if ordered.size() < 2:
|
||
|
|
# Not a failure of the code under test, but this file cannot say
|
||
|
|
# anything without a pool to draw from - say so rather than passing
|
||
|
|
# vacuously.
|
||
|
|
_fail("only %d '%s' setups exist, so nothing here can distinguish a stable draw from a fixed one" \
|
||
|
|
% [ordered.size(), profile_id])
|
||
|
|
return
|
||
|
|
var ids: Array = []
|
||
|
|
for setup in ordered:
|
||
|
|
ids.append(String(setup.setup_id))
|
||
|
|
var sorted_ids: Array = ids.duplicate()
|
||
|
|
sorted_ids.sort()
|
||
|
|
if ids != sorted_ids:
|
||
|
|
_fail("setups_for_profile returned %s, which is not sorted - the draw indexes into this, so the order cannot be left to the filesystem" % str(ids))
|
||
|
|
return
|
||
|
|
print("'%s' pool is deterministic: %s" % [profile_id, str(ids)])
|
||
|
|
|
||
|
|
# --- A/B/C. stable, correctly typed, and actually varied -----------------
|
||
|
|
var first_pass: Dictionary = {}
|
||
|
|
var planet_ids: Array = []
|
||
|
|
for i in range(40):
|
||
|
|
planet_ids.append("planet_%d_%d" % [i * 7, i * 13])
|
||
|
|
for planet_id in planet_ids:
|
||
|
|
var setup: Resource = PlanetSetups.setup_for_planet(planet_id, profile_id)
|
||
|
|
if setup == null:
|
||
|
|
_fail("%s drew no setup at all" % planet_id)
|
||
|
|
return
|
||
|
|
if String(setup.surface_profile_id) != profile_id:
|
||
|
|
_fail("%s is a '%s' planet but drew a '%s' map" \
|
||
|
|
% [planet_id, profile_id, setup.surface_profile_id])
|
||
|
|
return
|
||
|
|
first_pass[planet_id] = setup
|
||
|
|
|
||
|
|
for planet_id in planet_ids:
|
||
|
|
for repeat in range(3):
|
||
|
|
if PlanetSetups.setup_for_planet(planet_id, profile_id) != first_pass[planet_id]:
|
||
|
|
_fail("%s drew a different map on a later visit" % planet_id)
|
||
|
|
return
|
||
|
|
print("40 planets each re-drew the same map on every revisit")
|
||
|
|
|
||
|
|
var distinct: Dictionary = {}
|
||
|
|
for planet_id in first_pass:
|
||
|
|
distinct[first_pass[planet_id].setup_id] = true
|
||
|
|
if distinct.size() < 2:
|
||
|
|
_fail("all 40 planets drew the same map (%s), so the draw is not random at all" % str(distinct.keys()))
|
||
|
|
return
|
||
|
|
print("across 40 planets the draw used %d of the %d available maps" % [distinct.size(), ids.size()])
|
||
|
|
|
||
|
|
# --- D. a new map appearing does not re-deal the planets already visited -
|
||
|
|
var scratch: Resource = load("res://PlanetSetup.gd").new()
|
||
|
|
scratch.setup_id = SCRATCH_ID
|
||
|
|
scratch.display_name = "Assignment Scratch"
|
||
|
|
scratch.surface_profile_id = profile_id
|
||
|
|
var err: int = ResourceSaver.save(scratch, SCRATCH_PATH)
|
||
|
|
if err != OK:
|
||
|
|
_fail("could not write the scratch setup (error %d)" % err)
|
||
|
|
return
|
||
|
|
# Exactly what the map editor does after a save.
|
||
|
|
PlanetSetups.rescan()
|
||
|
|
var live: Dictionary = {}
|
||
|
|
var scratch_in_pool: bool = false
|
||
|
|
for setup in PlanetSetups.setups_for_profile(profile_id):
|
||
|
|
live[setup] = true
|
||
|
|
if String(setup.setup_id) == SCRATCH_ID:
|
||
|
|
scratch_in_pool = true
|
||
|
|
if not scratch_in_pool:
|
||
|
|
_remove_scratch()
|
||
|
|
_fail("the scratch setup did not enter the pool, so this case proves nothing")
|
||
|
|
return
|
||
|
|
|
||
|
|
var moved: int = 0
|
||
|
|
var checked: int = 0
|
||
|
|
for planet_id in planet_ids:
|
||
|
|
# Skipped if the map this planet drew is no longer in the pool. The
|
||
|
|
# suite runs tests in parallel against one project directory, so
|
||
|
|
# another test's scratch setup can appear and vanish underneath this
|
||
|
|
# one - and a planet losing an assignment because its MAP was deleted
|
||
|
|
# is correct behaviour, not the re-deal this case is looking for.
|
||
|
|
if not live.has(first_pass[planet_id]):
|
||
|
|
continue
|
||
|
|
checked += 1
|
||
|
|
if PlanetSetups.setup_for_planet(planet_id, profile_id) != first_pass[planet_id]:
|
||
|
|
moved += 1
|
||
|
|
if checked == 0:
|
||
|
|
_remove_scratch()
|
||
|
|
_fail("every planet's map disappeared from the pool mid-test, so nothing could be checked")
|
||
|
|
return
|
||
|
|
_remove_scratch()
|
||
|
|
PlanetSetups.rescan()
|
||
|
|
if moved > 0:
|
||
|
|
_fail("saving one new map re-dealt %d of %d already-visited planets" % [moved, checked])
|
||
|
|
return
|
||
|
|
print("saving a new map left all %d existing assignments alone" % checked)
|
||
|
|
|
||
|
|
print("TEST_PLANET_MAP_ASSIGNMENT: PASS")
|
||
|
|
quit(0)
|
||
|
|
|
||
|
|
|
||
|
|
func _remove_scratch() -> void:
|
||
|
|
var path: String = ProjectSettings.globalize_path(SCRATCH_PATH)
|
||
|
|
if FileAccess.file_exists(path):
|
||
|
|
DirAccess.remove_absolute(path)
|
||
|
|
if FileAccess.file_exists(path + ".uid"):
|
||
|
|
DirAccess.remove_absolute(path + ".uid")
|
||
|
|
|
||
|
|
|
||
|
|
func _fail(message: String) -> void:
|
||
|
|
print("TEST_PLANET_MAP_ASSIGNMENT: FAIL (%s)" % message)
|
||
|
|
quit(1)
|