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)