gemba 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/THIRD_PARTY_NOTICES +37 -2
- data/assets/placeholder_boxart.png +0 -0
- data/bin/gemba +2 -2
- data/ext/gemba/extconf.rb +23 -1
- data/ext/gemba/gemba_ext.c +436 -2
- data/ext/gemba/gemba_ext.h +2 -0
- data/gemba.gemspec +5 -3
- data/lib/gemba/achievements/achievement.rb +23 -0
- data/lib/gemba/achievements/backend.rb +190 -0
- data/lib/gemba/achievements/cache.rb +70 -0
- data/lib/gemba/achievements/credentials_presenter.rb +142 -0
- data/lib/gemba/achievements/fake_backend.rb +205 -0
- data/lib/gemba/achievements/null_backend.rb +11 -0
- data/lib/gemba/achievements/offline_backend.rb +168 -0
- data/lib/gemba/achievements/retro_achievements/backend.rb +511 -0
- data/lib/gemba/achievements/retro_achievements/cli_sync_requester.rb +64 -0
- data/lib/gemba/achievements/retro_achievements/ping_worker.rb +28 -0
- data/lib/gemba/achievements/retro_achievements/unlock_retry_worker.rb +35 -0
- data/lib/gemba/achievements.rb +19 -0
- data/lib/gemba/achievements_window.rb +556 -0
- data/lib/gemba/app_controller.rb +1036 -0
- data/lib/gemba/bios.rb +54 -0
- data/lib/gemba/boxart_fetcher/libretro_backend.rb +39 -0
- data/lib/gemba/boxart_fetcher/null_backend.rb +12 -0
- data/lib/gemba/boxart_fetcher.rb +79 -0
- data/lib/gemba/bus_emitter.rb +13 -0
- data/lib/gemba/child_window.rb +24 -1
- data/lib/gemba/cli/commands/config_cmd.rb +83 -0
- data/lib/gemba/cli/commands/decode.rb +154 -0
- data/lib/gemba/cli/commands/patch.rb +78 -0
- data/lib/gemba/cli/commands/play.rb +78 -0
- data/lib/gemba/cli/commands/record.rb +114 -0
- data/lib/gemba/cli/commands/replay.rb +161 -0
- data/lib/gemba/cli/commands/retro_achievements.rb +213 -0
- data/lib/gemba/cli/commands/version.rb +22 -0
- data/lib/gemba/cli.rb +52 -364
- data/lib/gemba/config.rb +154 -1
- data/lib/gemba/data/gb_games.json +1 -0
- data/lib/gemba/data/gb_md5.json +1 -0
- data/lib/gemba/data/gba_games.json +1 -0
- data/lib/gemba/data/gba_md5.json +1 -0
- data/lib/gemba/data/gbc_games.json +1 -0
- data/lib/gemba/data/gbc_md5.json +1 -0
- data/lib/gemba/emulator_frame.rb +1084 -0
- data/lib/gemba/event_bus.rb +48 -0
- data/lib/gemba/frame_stack.rb +70 -0
- data/lib/gemba/game_index.rb +84 -0
- data/lib/gemba/game_picker_frame.rb +309 -0
- data/lib/gemba/gamepad_map.rb +103 -0
- data/lib/gemba/headless.rb +6 -5
- data/lib/gemba/headless_player.rb +33 -3
- data/lib/gemba/help_window.rb +61 -0
- data/lib/gemba/hotkey_map.rb +3 -1
- data/lib/gemba/input_recorder.rb +107 -0
- data/lib/gemba/input_replayer.rb +119 -0
- data/lib/gemba/keyboard_map.rb +90 -0
- data/lib/gemba/list_picker_frame.rb +271 -0
- data/lib/gemba/locales/en.yml +109 -5
- data/lib/gemba/locales/ja.yml +109 -5
- data/lib/gemba/main_window.rb +56 -0
- data/lib/gemba/modal_stack.rb +81 -0
- data/lib/gemba/patcher_window.rb +223 -0
- data/lib/gemba/platform/gb.rb +21 -0
- data/lib/gemba/platform/gba.rb +21 -0
- data/lib/gemba/platform/gbc.rb +23 -0
- data/lib/gemba/platform.rb +20 -0
- data/lib/gemba/platform_open.rb +19 -0
- data/lib/gemba/recorder.rb +4 -3
- data/lib/gemba/replay_player.rb +691 -0
- data/lib/gemba/rom_info.rb +57 -0
- data/lib/gemba/rom_info_window.rb +16 -3
- data/lib/gemba/rom_library.rb +106 -0
- data/lib/gemba/rom_overrides.rb +47 -0
- data/lib/gemba/rom_patcher/bps.rb +161 -0
- data/lib/gemba/rom_patcher/ips.rb +101 -0
- data/lib/gemba/rom_patcher/ups.rb +119 -0
- data/lib/gemba/rom_patcher.rb +109 -0
- data/lib/gemba/{rom_loader.rb → rom_resolver.rb} +7 -6
- data/lib/gemba/runtime.rb +59 -26
- data/lib/gemba/save_state_manager.rb +4 -7
- data/lib/gemba/save_state_picker.rb +17 -4
- data/lib/gemba/session_logger.rb +64 -0
- data/lib/gemba/settings/audio_tab.rb +77 -0
- data/lib/gemba/settings/gamepad_tab.rb +351 -0
- data/lib/gemba/settings/hotkeys_tab.rb +259 -0
- data/lib/gemba/settings/paths.rb +11 -0
- data/lib/gemba/settings/recording_tab.rb +83 -0
- data/lib/gemba/settings/save_states_tab.rb +91 -0
- data/lib/gemba/settings/system_tab.rb +377 -0
- data/lib/gemba/settings/video_tab.rb +318 -0
- data/lib/gemba/settings_window.rb +162 -1036
- data/lib/gemba/version.rb +1 -1
- data/lib/gemba/virtual_keyboard.rb +19 -0
- data/lib/gemba.rb +2 -12
- data/test/achievements_window/test_bulk_sync.rb +218 -0
- data/test/achievements_window/test_bus_events.rb +125 -0
- data/test/achievements_window/test_close_confirmation.rb +201 -0
- data/test/achievements_window/test_initial_state.rb +164 -0
- data/test/achievements_window/test_sorting.rb +227 -0
- data/test/achievements_window/test_tree_rendering.rb +133 -0
- data/test/fixtures/fake_bios.bin +0 -0
- data/test/fixtures/pong.gba +0 -0
- data/test/fixtures/test.gb +0 -0
- data/test/fixtures/test.gbc +0 -0
- data/test/fixtures/test_quicksave.ss +0 -0
- data/test/screenshots/no_focus.png +0 -0
- data/test/shared/teek_test_worker.rb +17 -1
- data/test/shared/tk_test_helper.rb +92 -4
- data/test/support/achievements_window_helpers.rb +18 -0
- data/test/support/fake_core.rb +25 -0
- data/test/support/fake_ra_runtime.rb +74 -0
- data/test/support/fake_requester.rb +78 -0
- data/test/support/player_helpers.rb +20 -5
- data/test/test_achievement.rb +32 -0
- data/test/{test_player.rb → test_app_controller.rb} +353 -85
- data/test/test_bios.rb +123 -0
- data/test/test_boxart_fetcher.rb +150 -0
- data/test/test_cli.rb +17 -265
- data/test/test_cli_config.rb +64 -0
- data/test/test_cli_decode.rb +97 -0
- data/test/test_cli_patch.rb +58 -0
- data/test/test_cli_play.rb +213 -0
- data/test/test_cli_ra.rb +175 -0
- data/test/test_cli_record.rb +69 -0
- data/test/test_cli_replay.rb +72 -0
- data/test/test_cli_sync_requester.rb +152 -0
- data/test/test_cli_version.rb +27 -0
- data/test/test_config.rb +3 -3
- data/test/test_config_ra.rb +69 -0
- data/test/test_core.rb +62 -1
- data/test/test_credentials_presenter.rb +192 -0
- data/test/test_event_bus.rb +100 -0
- data/test/test_fake_backend_achievements.rb +130 -0
- data/test/test_fake_backend_auth.rb +68 -0
- data/test/test_game_index.rb +77 -0
- data/test/test_game_picker_frame.rb +310 -0
- data/test/test_gamepad_map.rb +1 -3
- data/test/test_headless_player.rb +17 -3
- data/test/test_help_window.rb +82 -0
- data/test/test_hotkey_map.rb +22 -1
- data/test/test_input_recorder.rb +179 -0
- data/test/test_input_replay_determinism.rb +113 -0
- data/test/test_input_replayer.rb +162 -0
- data/test/test_keyboard_map.rb +1 -3
- data/test/test_libretro_backend.rb +41 -0
- data/test/test_list_picker_frame.rb +391 -0
- data/test/test_locale.rb +1 -1
- data/test/test_logging.rb +123 -0
- data/test/test_null_backend.rb +42 -0
- data/test/test_offline_backend.rb +116 -0
- data/test/test_overlay_renderer.rb +1 -1
- data/test/test_platform.rb +149 -0
- data/test/test_ra_backend.rb +313 -0
- data/test/test_ra_backend_unlock_gate.rb +56 -0
- data/test/test_ra_backend_unlock_retry.rb +123 -0
- data/test/test_recorder.rb +0 -3
- data/test/test_replay_player.rb +316 -0
- data/test/test_rom_info.rb +149 -0
- data/test/test_rom_overrides.rb +86 -0
- data/test/test_rom_patcher.rb +383 -0
- data/test/{test_rom_loader.rb → test_rom_resolver.rb} +25 -26
- data/test/test_save_state_manager.rb +2 -4
- data/test/test_settings_audio.rb +107 -0
- data/test/test_settings_hotkeys.rb +83 -66
- data/test/test_settings_recording.rb +49 -0
- data/test/test_settings_save_states.rb +97 -0
- data/test/test_settings_system.rb +133 -0
- data/test/test_settings_video.rb +450 -0
- data/test/test_settings_window.rb +76 -507
- data/test/test_tip_service.rb +6 -6
- data/test/test_toast_overlay.rb +1 -1
- data/test/test_virtual_events.rb +221 -0
- data/test/test_virtual_keyboard.rb +1 -1
- data/vendor/rcheevos/CHANGELOG.md +495 -0
- data/vendor/rcheevos/LICENSE +21 -0
- data/vendor/rcheevos/Package.swift +33 -0
- data/vendor/rcheevos/README.md +67 -0
- data/vendor/rcheevos/include/module.modulemap +70 -0
- data/vendor/rcheevos/include/rc_api_editor.h +296 -0
- data/vendor/rcheevos/include/rc_api_info.h +280 -0
- data/vendor/rcheevos/include/rc_api_request.h +77 -0
- data/vendor/rcheevos/include/rc_api_runtime.h +417 -0
- data/vendor/rcheevos/include/rc_api_user.h +262 -0
- data/vendor/rcheevos/include/rc_client.h +877 -0
- data/vendor/rcheevos/include/rc_client_raintegration.h +101 -0
- data/vendor/rcheevos/include/rc_consoles.h +138 -0
- data/vendor/rcheevos/include/rc_error.h +59 -0
- data/vendor/rcheevos/include/rc_export.h +100 -0
- data/vendor/rcheevos/include/rc_hash.h +200 -0
- data/vendor/rcheevos/include/rc_runtime.h +148 -0
- data/vendor/rcheevos/include/rc_runtime_types.h +452 -0
- data/vendor/rcheevos/include/rc_util.h +51 -0
- data/vendor/rcheevos/include/rcheevos.h +8 -0
- data/vendor/rcheevos/src/rapi/rc_api_common.c +1379 -0
- data/vendor/rcheevos/src/rapi/rc_api_common.h +88 -0
- data/vendor/rcheevos/src/rapi/rc_api_editor.c +625 -0
- data/vendor/rcheevos/src/rapi/rc_api_info.c +587 -0
- data/vendor/rcheevos/src/rapi/rc_api_runtime.c +901 -0
- data/vendor/rcheevos/src/rapi/rc_api_user.c +483 -0
- data/vendor/rcheevos/src/rc_client.c +6941 -0
- data/vendor/rcheevos/src/rc_client_external.c +281 -0
- data/vendor/rcheevos/src/rc_client_external.h +177 -0
- data/vendor/rcheevos/src/rc_client_external_versions.h +171 -0
- data/vendor/rcheevos/src/rc_client_internal.h +409 -0
- data/vendor/rcheevos/src/rc_client_raintegration.c +566 -0
- data/vendor/rcheevos/src/rc_client_raintegration_internal.h +61 -0
- data/vendor/rcheevos/src/rc_client_types.natvis +396 -0
- data/vendor/rcheevos/src/rc_compat.c +251 -0
- data/vendor/rcheevos/src/rc_compat.h +121 -0
- data/vendor/rcheevos/src/rc_libretro.c +915 -0
- data/vendor/rcheevos/src/rc_libretro.h +98 -0
- data/vendor/rcheevos/src/rc_util.c +199 -0
- data/vendor/rcheevos/src/rc_version.c +11 -0
- data/vendor/rcheevos/src/rc_version.h +32 -0
- data/vendor/rcheevos/src/rcheevos/alloc.c +312 -0
- data/vendor/rcheevos/src/rcheevos/condition.c +754 -0
- data/vendor/rcheevos/src/rcheevos/condset.c +777 -0
- data/vendor/rcheevos/src/rcheevos/consoleinfo.c +1215 -0
- data/vendor/rcheevos/src/rcheevos/format.c +330 -0
- data/vendor/rcheevos/src/rcheevos/lboard.c +287 -0
- data/vendor/rcheevos/src/rcheevos/memref.c +805 -0
- data/vendor/rcheevos/src/rcheevos/operand.c +607 -0
- data/vendor/rcheevos/src/rcheevos/rc_internal.h +390 -0
- data/vendor/rcheevos/src/rcheevos/rc_runtime_types.natvis +541 -0
- data/vendor/rcheevos/src/rcheevos/rc_validate.c +1406 -0
- data/vendor/rcheevos/src/rcheevos/rc_validate.h +18 -0
- data/vendor/rcheevos/src/rcheevos/richpresence.c +922 -0
- data/vendor/rcheevos/src/rcheevos/runtime.c +852 -0
- data/vendor/rcheevos/src/rcheevos/runtime_progress.c +1073 -0
- data/vendor/rcheevos/src/rcheevos/trigger.c +344 -0
- data/vendor/rcheevos/src/rcheevos/value.c +935 -0
- data/vendor/rcheevos/src/rhash/aes.c +480 -0
- data/vendor/rcheevos/src/rhash/aes.h +49 -0
- data/vendor/rcheevos/src/rhash/cdreader.c +838 -0
- data/vendor/rcheevos/src/rhash/hash.c +1402 -0
- data/vendor/rcheevos/src/rhash/hash_disc.c +1340 -0
- data/vendor/rcheevos/src/rhash/hash_encrypted.c +566 -0
- data/vendor/rcheevos/src/rhash/hash_rom.c +426 -0
- data/vendor/rcheevos/src/rhash/hash_zip.c +460 -0
- data/vendor/rcheevos/src/rhash/md5.c +382 -0
- data/vendor/rcheevos/src/rhash/md5.h +91 -0
- data/vendor/rcheevos/src/rhash/rc_hash_internal.h +116 -0
- data/vendor/rcheevos/test/libretro.h +205 -0
- data/vendor/rcheevos/test/rapi/test_rc_api_common.c +941 -0
- data/vendor/rcheevos/test/rapi/test_rc_api_editor.c +931 -0
- data/vendor/rcheevos/test/rapi/test_rc_api_info.c +545 -0
- data/vendor/rcheevos/test/rapi/test_rc_api_runtime.c +2213 -0
- data/vendor/rcheevos/test/rapi/test_rc_api_user.c +998 -0
- data/vendor/rcheevos/test/rcheevos/mock_memory.h +32 -0
- data/vendor/rcheevos/test/rcheevos/test_condition.c +570 -0
- data/vendor/rcheevos/test/rcheevos/test_condset.c +5170 -0
- data/vendor/rcheevos/test/rcheevos/test_consoleinfo.c +203 -0
- data/vendor/rcheevos/test/rcheevos/test_format.c +112 -0
- data/vendor/rcheevos/test/rcheevos/test_lboard.c +746 -0
- data/vendor/rcheevos/test/rcheevos/test_memref.c +520 -0
- data/vendor/rcheevos/test/rcheevos/test_operand.c +692 -0
- data/vendor/rcheevos/test/rcheevos/test_rc_validate.c +502 -0
- data/vendor/rcheevos/test/rcheevos/test_richpresence.c +1564 -0
- data/vendor/rcheevos/test/rcheevos/test_runtime.c +1667 -0
- data/vendor/rcheevos/test/rcheevos/test_runtime_progress.c +1821 -0
- data/vendor/rcheevos/test/rcheevos/test_timing.c +166 -0
- data/vendor/rcheevos/test/rcheevos/test_trigger.c +2521 -0
- data/vendor/rcheevos/test/rcheevos/test_value.c +870 -0
- data/vendor/rcheevos/test/rcheevos-test.sln +46 -0
- data/vendor/rcheevos/test/rcheevos-test.vcxproj +239 -0
- data/vendor/rcheevos/test/rcheevos-test.vcxproj.filters +335 -0
- data/vendor/rcheevos/test/rhash/data.c +657 -0
- data/vendor/rcheevos/test/rhash/data.h +32 -0
- data/vendor/rcheevos/test/rhash/mock_filereader.c +236 -0
- data/vendor/rcheevos/test/rhash/mock_filereader.h +31 -0
- data/vendor/rcheevos/test/rhash/test_cdreader.c +920 -0
- data/vendor/rcheevos/test/rhash/test_hash.c +310 -0
- data/vendor/rcheevos/test/rhash/test_hash_disc.c +1450 -0
- data/vendor/rcheevos/test/rhash/test_hash_rom.c +899 -0
- data/vendor/rcheevos/test/rhash/test_hash_zip.c +551 -0
- data/vendor/rcheevos/test/test.c +113 -0
- data/vendor/rcheevos/test/test_framework.h +205 -0
- data/vendor/rcheevos/test/test_rc_client.c +10509 -0
- data/vendor/rcheevos/test/test_rc_client_external.c +2197 -0
- data/vendor/rcheevos/test/test_rc_client_raintegration.c +441 -0
- data/vendor/rcheevos/test/test_rc_libretro.c +952 -0
- data/vendor/rcheevos/test/test_types.natvis +9 -0
- data/vendor/rcheevos/validator/validator.c +658 -0
- data/vendor/rcheevos/validator/validator.vcxproj +152 -0
- data/vendor/rcheevos/validator/validator.vcxproj.filters +82 -0
- metadata +277 -10
- data/lib/gemba/input_mappings.rb +0 -214
- data/lib/gemba/player.rb +0 -1525
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require "gemba/headless"
|
|
5
|
+
|
|
6
|
+
class TestPlatform < Minitest::Test
|
|
7
|
+
# -- Factory ---------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
def test_for_gba
|
|
10
|
+
core = MockCore.new("GBA")
|
|
11
|
+
platform = Gemba::Platform.for(core)
|
|
12
|
+
assert_instance_of Gemba::Platform::GBA, platform
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_for_gb
|
|
16
|
+
core = MockCore.new("GB")
|
|
17
|
+
platform = Gemba::Platform.for(core)
|
|
18
|
+
assert_instance_of Gemba::Platform::GB, platform
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_for_gbc
|
|
22
|
+
core = MockCore.new("GBC")
|
|
23
|
+
platform = Gemba::Platform.for(core)
|
|
24
|
+
assert_instance_of Gemba::Platform::GBC, platform
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_for_unknown_defaults_to_gb
|
|
28
|
+
core = MockCore.new("Unknown")
|
|
29
|
+
platform = Gemba::Platform.for(core)
|
|
30
|
+
assert_instance_of Gemba::Platform::GB, platform
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test_default_is_gba
|
|
34
|
+
platform = Gemba::Platform.default
|
|
35
|
+
assert_instance_of Gemba::Platform::GBA, platform
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# -- GBA -------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
def test_gba_resolution
|
|
41
|
+
p = Gemba::Platform::GBA.new
|
|
42
|
+
assert_equal 240, p.width
|
|
43
|
+
assert_equal 160, p.height
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def test_gba_fps
|
|
47
|
+
p = Gemba::Platform::GBA.new
|
|
48
|
+
assert_in_delta 59.7272, p.fps, 0.001
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def test_gba_fps_fraction
|
|
52
|
+
num, den = Gemba::Platform::GBA.new.fps_fraction
|
|
53
|
+
assert_in_delta 59.7272, num.to_f / den, 0.001
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def test_gba_aspect
|
|
57
|
+
assert_equal [3, 2], Gemba::Platform::GBA.new.aspect
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_gba_name
|
|
61
|
+
assert_equal "Game Boy Advance", Gemba::Platform::GBA.new.name
|
|
62
|
+
assert_equal "GBA", Gemba::Platform::GBA.new.short_name
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def test_gba_buttons_include_lr
|
|
66
|
+
buttons = Gemba::Platform::GBA.new.buttons
|
|
67
|
+
assert_includes buttons, :l
|
|
68
|
+
assert_includes buttons, :r
|
|
69
|
+
assert_equal 10, buttons.size
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def test_gba_thumb_size
|
|
73
|
+
assert_equal [120, 80], Gemba::Platform::GBA.new.thumb_size
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# -- GB --------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
def test_gb_resolution
|
|
79
|
+
p = Gemba::Platform::GB.new
|
|
80
|
+
assert_equal 160, p.width
|
|
81
|
+
assert_equal 144, p.height
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def test_gb_fps
|
|
85
|
+
assert_in_delta 59.7275, Gemba::Platform::GB.new.fps, 0.001
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def test_gb_fps_fraction
|
|
89
|
+
num, den = Gemba::Platform::GB.new.fps_fraction
|
|
90
|
+
assert_in_delta 59.7275, num.to_f / den, 0.001
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def test_gb_aspect
|
|
94
|
+
assert_equal [10, 9], Gemba::Platform::GB.new.aspect
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def test_gb_name
|
|
98
|
+
assert_equal "Game Boy", Gemba::Platform::GB.new.name
|
|
99
|
+
assert_equal "GB", Gemba::Platform::GB.new.short_name
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def test_gb_buttons_no_lr
|
|
103
|
+
buttons = Gemba::Platform::GB.new.buttons
|
|
104
|
+
refute_includes buttons, :l
|
|
105
|
+
refute_includes buttons, :r
|
|
106
|
+
assert_equal 8, buttons.size
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def test_gb_thumb_size
|
|
110
|
+
assert_equal [80, 72], Gemba::Platform::GB.new.thumb_size
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# -- GBC -------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
def test_gbc_resolution_same_as_gb
|
|
116
|
+
p = Gemba::Platform::GBC.new
|
|
117
|
+
assert_equal 160, p.width
|
|
118
|
+
assert_equal 144, p.height
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def test_gbc_name_differs_from_gb
|
|
122
|
+
assert_equal "Game Boy Color", Gemba::Platform::GBC.new.name
|
|
123
|
+
assert_equal "GBC", Gemba::Platform::GBC.new.short_name
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def test_gbc_buttons_no_lr
|
|
127
|
+
buttons = Gemba::Platform::GBC.new.buttons
|
|
128
|
+
refute_includes buttons, :l
|
|
129
|
+
refute_includes buttons, :r
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# -- Equality --------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
def test_same_platform_equal
|
|
135
|
+
assert_equal Gemba::Platform::GBA.new, Gemba::Platform::GBA.new
|
|
136
|
+
assert_equal Gemba::Platform::GB.new, Gemba::Platform::GB.new
|
|
137
|
+
assert_equal Gemba::Platform::GBC.new, Gemba::Platform::GBC.new
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def test_different_platforms_not_equal
|
|
141
|
+
refute_equal Gemba::Platform::GBA.new, Gemba::Platform::GB.new
|
|
142
|
+
refute_equal Gemba::Platform::GBA.new, Gemba::Platform::GBC.new
|
|
143
|
+
refute_equal Gemba::Platform::GB.new, Gemba::Platform::GBC.new
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
private
|
|
147
|
+
|
|
148
|
+
MockCore = Struct.new(:platform)
|
|
149
|
+
end
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require "gemba/headless"
|
|
5
|
+
require_relative "support/fake_ra_runtime"
|
|
6
|
+
require_relative "support/fake_requester"
|
|
7
|
+
require_relative "support/fake_core"
|
|
8
|
+
|
|
9
|
+
PATCH_RESPONSE = {
|
|
10
|
+
"PatchData" => {
|
|
11
|
+
"RichPresencePatch" => "",
|
|
12
|
+
"Achievements" => [
|
|
13
|
+
{ "ID" => 101, "Title" => "First Blood", "Description" => "Get a kill",
|
|
14
|
+
"Points" => 5, "MemAddr" => "0=1", "Flags" => 3 },
|
|
15
|
+
{ "ID" => 102, "Title" => "Survivor", "Description" => "Survive 60s",
|
|
16
|
+
"Points" => 10, "MemAddr" => "1=1", "Flags" => 3 },
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
# Tests for Gemba::Achievements::RetroAchievements::Backend.
|
|
22
|
+
#
|
|
23
|
+
# FakeRequester replaces BackgroundWork so all HTTP callbacks fire synchronously
|
|
24
|
+
# in-process — no Tk event loop, no subprocesses, no wait_until.
|
|
25
|
+
class TestRABackend < Minitest::Test
|
|
26
|
+
Backend = Gemba::Achievements::RetroAchievements::Backend
|
|
27
|
+
|
|
28
|
+
def setup
|
|
29
|
+
@rt = FakeRARuntime.new
|
|
30
|
+
@req = FakeRequester.new
|
|
31
|
+
@b = Backend.new(app: nil, runtime: @rt, requester: @req)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Authenticate @b via the real login_with_token path.
|
|
35
|
+
def login(username: "user", token: "tok")
|
|
36
|
+
@req.stub(r: "login2", body: { "Success" => true })
|
|
37
|
+
@b.login_with_token(username: username, token: token)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Drive the full gameid→patch→unlocks chain.
|
|
41
|
+
def load_game(earned_ids: [], patch: PATCH_RESPONSE)
|
|
42
|
+
@req.stub(r: "gameid", body: { "GameID" => 42 })
|
|
43
|
+
@req.stub(r: "patch", body: patch)
|
|
44
|
+
@req.stub(r: "unlocks", body: { "Success" => true, "UserUnlocks" => earned_ids })
|
|
45
|
+
Dir.mktmpdir do |dir|
|
|
46
|
+
rom = File.join(dir, "test.gba")
|
|
47
|
+
File.write(rom, "FAKEGBAROM")
|
|
48
|
+
@b.load_game(nil, rom, "deadbeef" * 4)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
# Initial state
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
def test_not_authenticated_by_default
|
|
57
|
+
refute @b.authenticated?
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_enabled
|
|
61
|
+
assert @b.enabled?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_achievement_list_empty_before_game_load
|
|
65
|
+
assert_empty @b.achievement_list
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def test_rich_presence_message_nil_initially
|
|
69
|
+
assert_nil @b.rich_presence_message
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
# Authentication
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
def test_login_with_password_success
|
|
77
|
+
@req.stub(r: "login2", body: { "Success" => true, "Token" => "tok123" })
|
|
78
|
+
result = nil
|
|
79
|
+
@b.on_auth_change { |status, payload| result = [status, payload] }
|
|
80
|
+
@b.login_with_password(username: "user", password: "hunter2")
|
|
81
|
+
|
|
82
|
+
assert_equal :ok, result[0]
|
|
83
|
+
assert_equal "tok123", result[1]
|
|
84
|
+
assert @b.authenticated?
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def test_login_with_password_failure
|
|
88
|
+
@req.stub(r: "login2", body: { "Success" => false, "Error" => "Invalid credentials" })
|
|
89
|
+
result = nil
|
|
90
|
+
@b.on_auth_change { |status, msg| result = [status, msg] }
|
|
91
|
+
@b.login_with_password(username: "user", password: "wrong")
|
|
92
|
+
|
|
93
|
+
assert_equal :error, result[0]
|
|
94
|
+
assert_match(/invalid credentials/i, result[1])
|
|
95
|
+
refute @b.authenticated?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def test_login_with_token_success
|
|
99
|
+
result = nil
|
|
100
|
+
@b.on_auth_change { |status, _| result = status }
|
|
101
|
+
login
|
|
102
|
+
assert_equal :ok, result
|
|
103
|
+
assert @b.authenticated?
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def test_login_with_token_failure
|
|
107
|
+
@req.stub(r: "login2", body: { "Success" => false, "Error" => "Token invalid" })
|
|
108
|
+
result = nil
|
|
109
|
+
@b.on_auth_change { |status, msg| result = [status, msg] }
|
|
110
|
+
@b.login_with_token(username: "user", token: "bad")
|
|
111
|
+
|
|
112
|
+
assert_equal :error, result[0]
|
|
113
|
+
refute @b.authenticated?
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def test_token_test_success
|
|
117
|
+
login
|
|
118
|
+
result = nil
|
|
119
|
+
@b.on_auth_change { |status, _| result = status }
|
|
120
|
+
@b.token_test
|
|
121
|
+
assert_equal :ok, result
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def test_token_test_failure
|
|
125
|
+
login
|
|
126
|
+
@req.stub(r: "login2", body: { "Success" => false, "Error" => "Token invalid" })
|
|
127
|
+
result = nil
|
|
128
|
+
@b.on_auth_change { |status, _| result = status }
|
|
129
|
+
@b.token_test
|
|
130
|
+
assert_equal :error, result
|
|
131
|
+
refute @b.authenticated?
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def test_logout_clears_auth_state
|
|
135
|
+
login
|
|
136
|
+
@b.logout
|
|
137
|
+
refute @b.authenticated?
|
|
138
|
+
assert_empty @b.achievement_list
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# ---------------------------------------------------------------------------
|
|
142
|
+
# Game load chain
|
|
143
|
+
# ---------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
def test_load_game_skipped_when_not_authenticated
|
|
146
|
+
load_game
|
|
147
|
+
assert_empty @b.achievement_list
|
|
148
|
+
refute @req.requested?("gameid"), "should not hit network when unauthenticated"
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def test_load_game_populates_achievement_list
|
|
152
|
+
login
|
|
153
|
+
load_game
|
|
154
|
+
|
|
155
|
+
assert_equal 2, @b.total_count
|
|
156
|
+
assert_equal "101", @b.achievement_list[0].id
|
|
157
|
+
assert_equal "102", @b.achievement_list[1].id
|
|
158
|
+
assert_equal 2, @rt.count
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def test_load_game_marks_preearned_achievements
|
|
162
|
+
login
|
|
163
|
+
load_game(earned_ids: [101])
|
|
164
|
+
|
|
165
|
+
list = @b.achievement_list
|
|
166
|
+
assert list.find { |a| a.id == "101" }&.earned?, "101 should be earned"
|
|
167
|
+
refute list.find { |a| a.id == "102" }&.earned?, "102 should not be earned"
|
|
168
|
+
assert_includes @rt.deactivated, "101"
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def test_load_game_aborts_when_game_id_zero
|
|
172
|
+
login
|
|
173
|
+
@req.stub(r: "gameid", body: { "GameID" => 0 })
|
|
174
|
+
Dir.mktmpdir do |dir|
|
|
175
|
+
rom = File.join(dir, "test.gba")
|
|
176
|
+
File.write(rom, "FAKE")
|
|
177
|
+
@b.load_game(nil, rom, "deadbeef" * 4)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
assert_empty @b.achievement_list
|
|
181
|
+
refute @req.requested?("patch"), "patch must not be requested when GameID is 0"
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def test_load_game_activates_rich_presence_script
|
|
185
|
+
rp_patch = PATCH_RESPONSE.merge(
|
|
186
|
+
"PatchData" => PATCH_RESPONSE["PatchData"].merge("RichPresencePatch" => "Display: Hello")
|
|
187
|
+
)
|
|
188
|
+
login
|
|
189
|
+
load_game(patch: rp_patch)
|
|
190
|
+
|
|
191
|
+
assert_equal "Display: Hello", @rt.rp_script
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def test_unload_game_clears_achievement_list
|
|
195
|
+
login
|
|
196
|
+
load_game
|
|
197
|
+
@b.unload_game
|
|
198
|
+
assert_empty @b.achievement_list
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def test_sync_unlocks_repopulates_list
|
|
202
|
+
login
|
|
203
|
+
load_game
|
|
204
|
+
|
|
205
|
+
# Re-stub for the sync re-fetch
|
|
206
|
+
@req.stub(r: "patch", body: PATCH_RESPONSE)
|
|
207
|
+
@req.stub(r: "unlocks", body: { "Success" => true, "UserUnlocks" => [102] })
|
|
208
|
+
@b.sync_unlocks
|
|
209
|
+
|
|
210
|
+
list = @b.achievement_list
|
|
211
|
+
refute list.find { |a| a.id == "101" }&.earned?
|
|
212
|
+
assert list.find { |a| a.id == "102" }&.earned?
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# ---------------------------------------------------------------------------
|
|
216
|
+
# do_frame
|
|
217
|
+
# ---------------------------------------------------------------------------
|
|
218
|
+
|
|
219
|
+
def test_do_frame_silent_before_game_loaded
|
|
220
|
+
unlocked = []
|
|
221
|
+
@b.on_unlock { |a| unlocked << a }
|
|
222
|
+
@b.do_frame(FakeCore.new)
|
|
223
|
+
assert_empty unlocked
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def test_do_frame_fires_unlock_and_submits_to_server
|
|
227
|
+
login
|
|
228
|
+
load_game
|
|
229
|
+
|
|
230
|
+
@req.stub(r: "awardachievement", body: { "Success" => true })
|
|
231
|
+
@rt.queue_triggers("101")
|
|
232
|
+
|
|
233
|
+
unlocked = []
|
|
234
|
+
@b.on_unlock { |a| unlocked << a }
|
|
235
|
+
@b.do_frame(FakeCore.new)
|
|
236
|
+
|
|
237
|
+
assert_equal 1, unlocked.size
|
|
238
|
+
assert_equal "101", unlocked.first.id
|
|
239
|
+
assert @req.requested?("awardachievement")
|
|
240
|
+
assert_equal "101", @req.requests_for("awardachievement").first[:a].to_s
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def test_do_frame_skips_already_earned_achievement
|
|
244
|
+
login
|
|
245
|
+
load_game(earned_ids: [101])
|
|
246
|
+
|
|
247
|
+
@rt.queue_triggers("101")
|
|
248
|
+
unlocked = []
|
|
249
|
+
@b.on_unlock { |a| unlocked << a }
|
|
250
|
+
@b.do_frame(FakeCore.new)
|
|
251
|
+
|
|
252
|
+
assert_empty unlocked, "already-earned achievement must not fire again"
|
|
253
|
+
refute @req.requested?("awardachievement")
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def test_do_frame_rich_presence_fires_callback_when_enabled
|
|
257
|
+
login
|
|
258
|
+
load_game
|
|
259
|
+
|
|
260
|
+
@rt.rp_message = "Playing Stage 1"
|
|
261
|
+
@b.rich_presence_enabled = true
|
|
262
|
+
@b.instance_variable_set(:@rp_eval_frame, 239)
|
|
263
|
+
|
|
264
|
+
fired = []
|
|
265
|
+
@b.on_rich_presence_changed { |m| fired << m }
|
|
266
|
+
@b.do_frame(FakeCore.new)
|
|
267
|
+
|
|
268
|
+
assert_equal ["Playing Stage 1"], fired
|
|
269
|
+
assert_equal "Playing Stage 1", @b.rich_presence_message
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def test_do_frame_rich_presence_silent_when_disabled
|
|
273
|
+
login
|
|
274
|
+
load_game
|
|
275
|
+
|
|
276
|
+
@rt.rp_message = "Playing Stage 1"
|
|
277
|
+
@b.rich_presence_enabled = false
|
|
278
|
+
@b.instance_variable_set(:@rp_eval_frame, 239)
|
|
279
|
+
|
|
280
|
+
fired = []
|
|
281
|
+
@b.on_rich_presence_changed { |m| fired << m }
|
|
282
|
+
@b.do_frame(FakeCore.new)
|
|
283
|
+
|
|
284
|
+
assert_empty fired
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# ---------------------------------------------------------------------------
|
|
288
|
+
# FakeRARuntime self-tests
|
|
289
|
+
# ---------------------------------------------------------------------------
|
|
290
|
+
|
|
291
|
+
def test_fake_runtime_activate_deactivate
|
|
292
|
+
@rt.activate("101", "0=1")
|
|
293
|
+
@rt.activate("102", "1=1")
|
|
294
|
+
assert_equal 2, @rt.count
|
|
295
|
+
@rt.deactivate("101")
|
|
296
|
+
assert_equal 1, @rt.count
|
|
297
|
+
refute @rt.activated.key?("101")
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def test_fake_runtime_clear_resets_state
|
|
301
|
+
@rt.activate("101", "0=1")
|
|
302
|
+
@rt.queue_triggers("101")
|
|
303
|
+
@rt.clear
|
|
304
|
+
assert_equal 0, @rt.count
|
|
305
|
+
assert_empty @rt.do_frame(nil)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def test_fake_runtime_queue_consumed_once
|
|
309
|
+
@rt.queue_triggers("101")
|
|
310
|
+
assert_equal ["101"], @rt.do_frame(nil)
|
|
311
|
+
assert_empty @rt.do_frame(nil)
|
|
312
|
+
end
|
|
313
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require "gemba/headless"
|
|
5
|
+
require "gemba/achievements"
|
|
6
|
+
require_relative "support/fake_core"
|
|
7
|
+
|
|
8
|
+
# Tests that RetroAchievements::Backend never awards achievements before the
|
|
9
|
+
# server's earned list is known.
|
|
10
|
+
#
|
|
11
|
+
# The bug scenario:
|
|
12
|
+
# fetch_patch_data completes → runtime activated with 89 achievements
|
|
13
|
+
# emulator starts → do_frame fires → some conditions true at frame 0
|
|
14
|
+
# @earned is empty (r=unlocks still in flight) → all are re-awarded
|
|
15
|
+
#
|
|
16
|
+
# The fix: @achievements stays empty until fetch_unlocks completes.
|
|
17
|
+
# do_frame's `return if @achievements.empty?` guards the window by construction.
|
|
18
|
+
#
|
|
19
|
+
# These tests verify the fix through the public API (no instance_variable_set):
|
|
20
|
+
# if @achievements is empty, do_frame must be silent regardless of what the
|
|
21
|
+
# underlying runtime would report.
|
|
22
|
+
class TestRABackendUnlockGate < Minitest::Test
|
|
23
|
+
def setup
|
|
24
|
+
@backend = Gemba::Achievements::RetroAchievements::Backend.new(app: nil)
|
|
25
|
+
@unlocked = []
|
|
26
|
+
@backend.on_unlock { |ach| @unlocked << ach }
|
|
27
|
+
@core = FakeCore.new
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Before any game is loaded, @achievements is empty → do_frame is a no-op.
|
|
31
|
+
def test_do_frame_silent_before_game_loaded
|
|
32
|
+
@backend.do_frame(@core)
|
|
33
|
+
assert_empty @unlocked
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @achievements only becomes non-empty after fetch_unlocks completes (HTTP).
|
|
37
|
+
# Since we can't make real HTTP calls, verify the state via achievement_list
|
|
38
|
+
# and total_count — they reflect @achievements.
|
|
39
|
+
def test_achievements_empty_until_unlocks_arrive
|
|
40
|
+
assert_equal 0, @backend.total_count,
|
|
41
|
+
"achievement list must be empty before fetch_unlocks completes"
|
|
42
|
+
assert_empty @backend.achievement_list
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# do_frame with an empty achievement list never fires unlock callbacks.
|
|
46
|
+
# This is the structural guarantee — as long as @achievements is empty,
|
|
47
|
+
# no award can happen even if the C runtime were somehow active.
|
|
48
|
+
def test_do_frame_never_fires_when_achievement_list_empty
|
|
49
|
+
# Simulate being "mid-load": patch data fetched but unlocks not yet back.
|
|
50
|
+
# In the new design @achievements stays [] during this window.
|
|
51
|
+
5.times { @backend.do_frame(@core) }
|
|
52
|
+
assert_empty @unlocked,
|
|
53
|
+
"no unlock must fire during the patch→unlocks window"
|
|
54
|
+
assert_equal 0, @backend.earned_count
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require "gemba/headless"
|
|
5
|
+
require_relative "support/fake_ra_runtime"
|
|
6
|
+
require_relative "support/fake_requester"
|
|
7
|
+
require_relative "support/fake_core"
|
|
8
|
+
|
|
9
|
+
# Tests for the unlock retry queue in RetroAchievements::Backend.
|
|
10
|
+
#
|
|
11
|
+
# FakeRequester fires on_progress synchronously so no event loop is needed.
|
|
12
|
+
# For worker-style calls (drain_unlock_queue) it yields [ok, id] to match
|
|
13
|
+
# what UnlockRetryWorker produces in production.
|
|
14
|
+
class TestRABackendUnlockRetry < Minitest::Test
|
|
15
|
+
Backend = Gemba::Achievements::RetroAchievements::Backend
|
|
16
|
+
|
|
17
|
+
PATCH = {
|
|
18
|
+
"PatchData" => {
|
|
19
|
+
"RichPresencePatch" => "",
|
|
20
|
+
"Achievements" => [
|
|
21
|
+
{ "ID" => 101, "Title" => "First Blood", "Description" => "Kill",
|
|
22
|
+
"Points" => 5, "MemAddr" => "0=1", "Flags" => 3 },
|
|
23
|
+
{ "ID" => 102, "Title" => "Survivor", "Description" => "Survive",
|
|
24
|
+
"Points" => 10, "MemAddr" => "1=1", "Flags" => 3 },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
def setup
|
|
30
|
+
@rt = FakeRARuntime.new
|
|
31
|
+
@req = FakeRequester.new
|
|
32
|
+
@b = Backend.new(app: nil, runtime: @rt, requester: @req)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def login_and_load
|
|
36
|
+
@req.stub(r: "login2", body: { "Success" => true })
|
|
37
|
+
@req.stub(r: "gameid", body: { "GameID" => 42 })
|
|
38
|
+
@req.stub(r: "patch", body: PATCH)
|
|
39
|
+
@req.stub(r: "unlocks", body: { "Success" => true, "UserUnlocks" => [] })
|
|
40
|
+
@b.login_with_token(username: "user", token: "tok")
|
|
41
|
+
Dir.mktmpdir do |dir|
|
|
42
|
+
rom = File.join(dir, "test.gba")
|
|
43
|
+
File.write(rom, "FAKEGBA")
|
|
44
|
+
@b.load_game(nil, rom, "deadbeef" * 4)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# -- Queue builds on initial failure ----------------------------------------
|
|
49
|
+
|
|
50
|
+
def test_failed_unlock_enqueues_for_retry
|
|
51
|
+
login_and_load
|
|
52
|
+
@req.stub(r: "awardachievement", ok: false, body: { "Success" => false })
|
|
53
|
+
@rt.queue_triggers("101")
|
|
54
|
+
@b.do_frame(FakeCore.new)
|
|
55
|
+
assert_equal 1, @b.unlock_queue.size
|
|
56
|
+
assert_equal "101", @b.unlock_queue.first[:id]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_successful_unlock_does_not_enqueue
|
|
60
|
+
login_and_load
|
|
61
|
+
@req.stub(r: "awardachievement", ok: true, body: { "Success" => true })
|
|
62
|
+
@rt.queue_triggers("101")
|
|
63
|
+
@b.do_frame(FakeCore.new)
|
|
64
|
+
assert_empty @b.unlock_queue
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def test_multiple_failed_unlocks_all_enqueue
|
|
68
|
+
login_and_load
|
|
69
|
+
@req.stub(r: "awardachievement", ok: false, body: { "Success" => false })
|
|
70
|
+
@rt.queue_triggers("101", "102")
|
|
71
|
+
@b.do_frame(FakeCore.new)
|
|
72
|
+
assert_equal 2, @b.unlock_queue.size
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# -- drain_unlock_queue -----------------------------------------------------
|
|
76
|
+
|
|
77
|
+
def test_drain_sends_retry_request_per_entry
|
|
78
|
+
login_and_load
|
|
79
|
+
@req.stub(r: "awardachievement", ok: true, body: { "Success" => true })
|
|
80
|
+
@b.unlock_queue << { id: "101", hardcore: false }
|
|
81
|
+
@b.unlock_queue << { id: "102", hardcore: false }
|
|
82
|
+
@b.drain_unlock_queue
|
|
83
|
+
assert_equal 2, @req.requests_for("awardachievement").size
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_drain_clears_queue_on_success
|
|
87
|
+
login_and_load
|
|
88
|
+
@req.stub(r: "awardachievement", ok: true, body: { "Success" => true })
|
|
89
|
+
@b.unlock_queue << { id: "101", hardcore: false }
|
|
90
|
+
@b.drain_unlock_queue
|
|
91
|
+
assert_empty @b.unlock_queue
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def test_drain_keeps_queue_on_failure
|
|
95
|
+
login_and_load
|
|
96
|
+
@req.stub(r: "awardachievement", ok: false, body: { "Success" => false })
|
|
97
|
+
@b.unlock_queue << { id: "101", hardcore: false }
|
|
98
|
+
@b.drain_unlock_queue
|
|
99
|
+
assert_equal 1, @b.unlock_queue.size
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def test_drain_partial_success_removes_only_succeeded
|
|
103
|
+
login_and_load
|
|
104
|
+
@req.stub_queue(r: "awardachievement", ok: true, body: { "Success" => true })
|
|
105
|
+
@req.stub_queue(r: "awardachievement", ok: false, body: { "Success" => false })
|
|
106
|
+
@b.unlock_queue << { id: "101", hardcore: false }
|
|
107
|
+
@b.unlock_queue << { id: "102", hardcore: false }
|
|
108
|
+
@b.drain_unlock_queue
|
|
109
|
+
assert_equal 1, @b.unlock_queue.size
|
|
110
|
+
assert_equal "102", @b.unlock_queue.first[:id]
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# -- shutdown ---------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
def test_shutdown_logs_pending_and_does_not_raise
|
|
116
|
+
@b.unlock_queue << { id: "101", hardcore: false }
|
|
117
|
+
@b.shutdown
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def test_shutdown_with_empty_queue_does_not_raise
|
|
121
|
+
@b.shutdown
|
|
122
|
+
end
|
|
123
|
+
end
|
data/test/test_recorder.rb
CHANGED
|
@@ -7,9 +7,6 @@ require "tmpdir"
|
|
|
7
7
|
class TestRecorder < Minitest::Test
|
|
8
8
|
TEST_ROM = File.expand_path("fixtures/test.gba", __dir__)
|
|
9
9
|
|
|
10
|
-
def setup
|
|
11
|
-
skip "Run: ruby gemba/scripts/generate_test_rom.rb" unless File.exist?(TEST_ROM)
|
|
12
|
-
end
|
|
13
10
|
|
|
14
11
|
def test_record_and_decode_round_trip
|
|
15
12
|
skip "ffmpeg not installed" unless ffmpeg_available?
|