P16-01-DISMISS: 一般従業員の通常操作での解雇(退職金を含む。P16-01 の補助の作業。原単位への加算 0)
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
extends SceneTree
|
||||
|
||||
## P16-01-DISMISS F7/F8, reader phase. Runs after the writer suite
|
||||
## (test_general_dismissal_restart_1_write.gd) as a separate engine process. It
|
||||
## loads every ledger save, continues the day script and requires exactly the
|
||||
## uninterrupted ledger: one severance and one payment of the held wages,
|
||||
## whether the save was made before or right after the dismissal command or
|
||||
## around the day 2 00:00 close. It loads the A6, A7 and A8 command boundary
|
||||
## saves, requires the writer's state and no live reference to the dismissed
|
||||
## employee, and continues them to the writer's later state. A changed package
|
||||
## is refused.
|
||||
|
||||
const Scenes := preload("res://tests/helpers/p16_dismissal_fixture.gd")
|
||||
|
||||
var _checks := 0
|
||||
var _failures := 0
|
||||
var _scenes: Scenes
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
print("P16-01-DISMISS: restart reader")
|
||||
_scenes = Scenes.new(_expect)
|
||||
var handoff := _read_handoff()
|
||||
if not handoff.is_empty():
|
||||
_check_ledger(handoff)
|
||||
_check_boundaries(handoff)
|
||||
_check_tampered(handoff)
|
||||
await process_frame
|
||||
if _failures == 0:
|
||||
print("PASS: %d checks" % _checks)
|
||||
else:
|
||||
push_error("FAIL: %d of %d checks failed" % [_failures, _checks])
|
||||
quit(_failures)
|
||||
|
||||
|
||||
func _read_handoff() -> Dictionary:
|
||||
_expect(FileAccess.file_exists(Scenes.HANDOFF_PATH), "the writer phase left its handoff (run test_general_dismissal_restart_1_write.gd first)")
|
||||
if not FileAccess.file_exists(Scenes.HANDOFF_PATH):
|
||||
return {}
|
||||
var parsed: Variant = JSON.parse_string(FileAccess.get_file_as_string(Scenes.HANDOFF_PATH))
|
||||
_expect(parsed is Dictionary and str((parsed as Dictionary).get("schema", "")) == Scenes.HANDOFF_SCHEMA, "handoff schema")
|
||||
if not parsed is Dictionary or str((parsed as Dictionary).get("schema", "")) != Scenes.HANDOFF_SCHEMA:
|
||||
return {}
|
||||
var handoff: Dictionary = parsed
|
||||
var writer_pid := int(handoff.get("writer_pid", 0))
|
||||
_expect(writer_pid > 0 and writer_pid != OS.get_process_id(), "a separate engine process wrote the saves")
|
||||
_expect(str(handoff.get("writer_source_sha256", "")) == FileAccess.get_sha256(Scenes.WRITER_SOURCE), "the handoff comes from the current writer suite")
|
||||
_expect(int(handoff.get("ledger_seed", -1)) == Scenes.LEDGER_SEED and int(handoff.get("last_minute", -1)) == Scenes.LAST_MINUTE, "same fixture and day script")
|
||||
print("P16_RESTART_READER pid=%d writer_pid=%d handoff_sha256=%s" % [OS.get_process_id(), writer_pid, FileAccess.get_sha256(Scenes.HANDOFF_PATH)])
|
||||
return handoff
|
||||
|
||||
|
||||
func _decode(text: Variant) -> Variant:
|
||||
return bytes_to_var(Marshalls.base64_to_raw(str(text)))
|
||||
|
||||
|
||||
func _check_ledger(handoff: Dictionary) -> void:
|
||||
var straight := _scenes.new_ledger_fixture()
|
||||
_scenes.play_ledger(straight, 360, Scenes.LAST_MINUTE, Callable())
|
||||
var straight_runtime: Variant = straight[&"runtime"]
|
||||
var straight_economy := _scenes.economy_bytes(straight_runtime)
|
||||
var straight_state := _scenes.state_value(straight_runtime, straight[&"clock"])
|
||||
_expect(Scenes.sha256(straight_economy) == str(handoff.get("straight_economy_sha256", "")), "the uninterrupted day script gives the same ledger in both processes")
|
||||
_expect(straight_state == _decode(handoff.get("straight_state_base64", "")), "the uninterrupted day script gives the same employees, Tasks and balances in both processes")
|
||||
var saves: Array = handoff.get("ledger_saves", [])
|
||||
var points: Array = []
|
||||
for save: Variant in saves:
|
||||
points.append([int((save as Dictionary).get("minute", -1)), str((save as Dictionary).get("phase", ""))] if save is Dictionary else [])
|
||||
_expect(points == Scenes.SAVE_POINTS, "the writer saved before and right after the dismissal, a minute later and around the day 2 close")
|
||||
for save: Variant in saves:
|
||||
if not save is Dictionary:
|
||||
continue
|
||||
var minute := int((save as Dictionary)["minute"])
|
||||
var name := str((save as Dictionary)["name"])
|
||||
var path := Scenes.READER_SAVE % name
|
||||
if not _materialize(path, (save as Dictionary).get("files", {}), false):
|
||||
continue
|
||||
var resumed := _scenes.new_ledger_fixture()
|
||||
var runtime: Variant = resumed[&"runtime"]
|
||||
_expect(bool(runtime.load_from_path(path).get(&"ok", false)) and int(resumed[&"clock"].absolute_minute) == minute, "%s: this process loads the save at minute %d" % [name, minute])
|
||||
var dismissed_before: bool = not runtime.staff_service.has_staff(Scenes.DISMISSED)
|
||||
_expect(dismissed_before == (name != "ledger-1500-before"), "%s: the save holds the employee exactly when it was made before the command" % name)
|
||||
_scenes.play_ledger(resumed, minute, Scenes.LAST_MINUTE, Callable())
|
||||
_expect(_scenes.economy_bytes(runtime) == straight_economy, "%s: resuming gives exactly the uninterrupted ledger" % name)
|
||||
_expect(_scenes.state_value(runtime, resumed[&"clock"]) == straight_state, "%s: resuming gives the same employees, Tasks and balances" % name)
|
||||
_expect(_scenes.count_category(runtime, FinancePolicy.SEVERANCE_CATEGORY) == 1 and runtime.economy_service.get_balance(FinancePolicy.SEVERANCE_EXPENSE) == 360,
|
||||
"%s: the severance is paid once" % name)
|
||||
_expect(runtime.staff_service.get_departed_unpaid_wages(Scenes.DISMISSED) == 0
|
||||
and int(runtime.operations_coordinator.get_day_summary(2)[&"settlement"][&"input_payments"][0][&"amount"]) == 120, "%s: the held 120 is paid once, at the day 2 close" % name)
|
||||
_scenes.free_fixture(resumed)
|
||||
_scenes.free_fixture(straight)
|
||||
|
||||
|
||||
func _check_boundaries(handoff: Dictionary) -> void:
|
||||
var boundaries: Array = handoff.get("boundaries", [])
|
||||
var labels: Array = []
|
||||
for boundary: Variant in boundaries:
|
||||
labels.append(str((boundary as Dictionary).get("label", "")) if boundary is Dictionary else "")
|
||||
_expect(labels == ["A6", "A7", "A8"], "the writer saved the A6, A7 and A8 command boundaries")
|
||||
for boundary: Variant in boundaries:
|
||||
if not boundary is Dictionary:
|
||||
continue
|
||||
var name := str((boundary as Dictionary)["name"])
|
||||
var staff_id := StringName(str((boundary as Dictionary)["staff_id"]))
|
||||
var path := Scenes.READER_SAVE % name
|
||||
if not _materialize(path, (boundary as Dictionary).get("files", {}), false):
|
||||
continue
|
||||
var fixture := _scenes.new_fixture(1)
|
||||
var runtime: Variant = fixture[&"runtime"]
|
||||
_expect(bool(runtime.load_from_path(path).get(&"ok", false)), "%s: this process loads the save made right after the command" % name)
|
||||
var dangling: Array[String] = _scenes.dangling_references(runtime, staff_id)
|
||||
_expect(dangling.is_empty(), "%s: no live reference to the dismissed employee after the load %s" % [name, dangling])
|
||||
_expect(runtime.staff_service.validate_invariants()[&"ok"] and runtime.economy_service.validate_ledger()[&"ok"], "%s: ownership and the ledger are consistent after the load" % name)
|
||||
_expect(_scenes.state_value(runtime, fixture[&"clock"]) == _decode((boundary as Dictionary)["at_command_base64"]), "%s: the loaded game is the writer's game at the command" % name)
|
||||
var saved: Dictionary = runtime.create_snapshot()
|
||||
_expect(runtime.restore_snapshot(saved) and runtime.create_snapshot() == saved, "%s: the loaded game saves and restores exactly" % name)
|
||||
fixture[&"clock"].advance_minutes(int((boundary as Dictionary)["continue_minutes"]))
|
||||
_expect(_scenes.state_value(runtime, fixture[&"clock"]) == _decode((boundary as Dictionary)["later_base64"]), "%s: it continues exactly as the writer's game" % name)
|
||||
_scenes.free_fixture(fixture)
|
||||
|
||||
|
||||
func _check_tampered(handoff: Dictionary) -> void:
|
||||
var saves: Array = handoff.get("ledger_saves", [])
|
||||
if saves.is_empty() or not saves[0] is Dictionary:
|
||||
_expect(false, "a save package to change")
|
||||
return
|
||||
var path := Scenes.READER_SAVE % "tampered"
|
||||
if not _materialize(path, (saves[0] as Dictionary).get("files", {}), true):
|
||||
return
|
||||
var fixture := _scenes.new_ledger_fixture()
|
||||
var runtime: Variant = fixture[&"runtime"]
|
||||
var before := _scenes.economy_bytes(runtime)
|
||||
_expect(not bool(runtime.load_from_path(path).get(&"ok", true)), "a changed save package is refused")
|
||||
_expect(_scenes.economy_bytes(runtime) == before and int(fixture[&"clock"].absolute_minute) == 360, "a refused load changes no Gold and no time")
|
||||
_scenes.free_fixture(fixture)
|
||||
|
||||
|
||||
# Writes the writer's exact package bytes into this process's own profile.
|
||||
func _materialize(path: String, files: Variant, change_world: bool) -> bool:
|
||||
var directory := ProjectSettings.globalize_path(path)
|
||||
_expect(files is Dictionary and (files as Dictionary).has("manifest.json") and (files as Dictionary).has("world.bin"), "handoff package %s is complete" % path)
|
||||
if not files is Dictionary:
|
||||
return false
|
||||
_expect(DirAccess.make_dir_recursive_absolute(directory) == OK, "package directory %s" % path)
|
||||
for name: String in (files as Dictionary):
|
||||
var pin: Dictionary = (files as Dictionary)[name]
|
||||
var bytes := Marshalls.base64_to_raw(str(pin.get("base64", "")))
|
||||
_expect(Scenes.sha256(bytes) == str(pin.get("sha256", "")) and bytes.size() == int(pin.get("bytes", -1)), "handoff bytes of %s are exact" % name)
|
||||
if change_world and name == "world.bin" and not bytes.is_empty():
|
||||
bytes[bytes.size() - 1] = bytes[bytes.size() - 1] ^ 0xff
|
||||
var file := FileAccess.open(directory.path_join(name), FileAccess.WRITE)
|
||||
_expect(file != null, "write %s into %s" % [name, path])
|
||||
if file == null:
|
||||
return false
|
||||
file.store_buffer(bytes)
|
||||
file.close()
|
||||
return true
|
||||
|
||||
|
||||
func _expect(condition: bool, message: String) -> void:
|
||||
_checks += 1
|
||||
if not condition:
|
||||
_failures += 1
|
||||
push_error(message)
|
||||
Reference in New Issue
Block a user