@@ -593,5 +593,334 @@ def test_all_examples_complete(self):
593593 f"{ isa_name } { elf_path } : Should complete in <{ max_steps } steps, took { steps } " )
594594
595595
596+ class TestGuessGameAllISAs (unittest .TestCase ):
597+ """Test guess_game interactive program on all ISAs.
598+
599+ The guess_game is an interactive number guessing game that:
600+ 1. Prints welcome messages
601+ 2. Reads user input (syscall 5 = read_int)
602+ 3. Compares guess to secret number (42)
603+ 4. Exits when correct
604+
605+ Since it's interactive, we test:
606+ - Program loads and starts correctly
607+ - Required symbols exist
608+ - Program reaches input syscall
609+ - With correct input injected, completes successfully
610+ """
611+
612+ GUESS_GAME_EXAMPLES = [
613+ ("examples/riscv/guess_game/guess_game" , "RISCV" ),
614+ ("examples/arm/guess_game/guess_game" , "ARM" ),
615+ ("examples/x86_64/guess_game/guess_game" , "X86_64" ),
616+ ("examples/mips/guess_game/guess_game" , "MIPS" ),
617+ ]
618+
619+ def _get_syscall_regs (self , isa_name : str ):
620+ """Get syscall register mappings for ISA."""
621+ if isa_name == "X86_64" :
622+ return (0 , 7 , 0 ) # rax=syscall#, rdi=arg0, return in rax
623+ elif isa_name == "MIPS" :
624+ return (2 , 4 , 2 ) # $v0=syscall#, $a0=arg0, return in $v0
625+ elif isa_name == "ARM" :
626+ return (8 , 0 , 0 ) # x8=syscall#, x0=arg0, return in x0
627+ else : # RISCV
628+ return (17 , 10 , 10 ) # a7=syscall#, a0=arg0, return in a0
629+
630+ def _run_with_input (self , sim , input_value : int , isa_name : str , max_steps : int = 5000 ):
631+ """Run program, injecting input when read_int syscall is hit.
632+
633+ Note: We handle syscalls manually instead of using check_termination()
634+ because check_termination() calls _handle_syscall() which uses input().
635+ """
636+ syscall_reg , _ , result_reg = self ._get_syscall_regs (isa_name )
637+
638+ for step in range (max_steps ):
639+ result = sim .step ()
640+
641+ if result == StepResult .SYSCALL :
642+ syscall_num = sim .get_reg (syscall_reg )
643+ if syscall_num == 5 : # read_int - inject our value
644+ # For ARM64, result_reg is 0 (x0) which is writable
645+ if isa_name == "ARM" :
646+ sim ._uc .reg_write (sim ._config .get_gpr_reg (result_reg ), input_value )
647+ else :
648+ sim .set_reg (result_reg , input_value )
649+ elif syscall_num == 10 or syscall_num == 93 : # exit
650+ return step + 1 , "syscall_exit"
651+ # For other syscalls (like print_string), let them pass
652+ # They don't need handling since we're not checking output
653+
654+ elif result == StepResult .HALT :
655+ return step + 1 , "halt"
656+ elif result == StepResult .ERROR :
657+ return step + 1 , "error"
658+
659+ return max_steps , "timeout"
660+
661+ def test_riscv_guess_game_loads (self ):
662+ """RISC-V guess_game loads and has required symbols"""
663+ elf = Path ("examples/riscv/guess_game/guess_game" )
664+ if not elf .exists ():
665+ self .skipTest (f"Not found: { elf } " )
666+
667+ sim = create_simulator (str (elf ))
668+ symbols = sim .get_symbols ()
669+
670+ # Verify required symbols exist
671+ self .assertIn ("secret" , symbols , "Should have 'secret' symbol" )
672+ self .assertIn ("welcome" , symbols , "Should have 'welcome' symbol" )
673+ self .assertIn ("_start" , symbols , "Should have '_start' symbol" )
674+
675+ def test_arm_guess_game_loads (self ):
676+ """ARM64 guess_game loads and has required symbols"""
677+ elf = Path ("examples/arm/guess_game/guess_game" )
678+ if not elf .exists ():
679+ self .skipTest (f"Not found: { elf } " )
680+
681+ sim = create_simulator (str (elf ))
682+ symbols = sim .get_symbols ()
683+
684+ self .assertIn ("secret" , symbols , "Should have 'secret' symbol" )
685+ self .assertIn ("welcome" , symbols , "Should have 'welcome' symbol" )
686+
687+ def test_x86_guess_game_loads (self ):
688+ """x86-64 guess_game loads and has required symbols"""
689+ elf = Path ("examples/x86_64/guess_game/guess_game" )
690+ if not elf .exists ():
691+ self .skipTest (f"Not found: { elf } " )
692+
693+ sim = create_simulator (str (elf ))
694+ symbols = sim .get_symbols ()
695+
696+ self .assertIn ("secret" , symbols , "Should have 'secret' symbol" )
697+ self .assertIn ("welcome" , symbols , "Should have 'welcome' symbol" )
698+
699+ def test_mips_guess_game_loads (self ):
700+ """MIPS guess_game loads and has required symbols"""
701+ elf = Path ("examples/mips/guess_game/guess_game" )
702+ if not elf .exists ():
703+ self .skipTest (f"Not found: { elf } " )
704+
705+ sim = create_simulator (str (elf ))
706+ symbols = sim .get_symbols ()
707+
708+ self .assertIn ("secret" , symbols , "Should have 'secret' symbol" )
709+ self .assertIn ("welcome" , symbols , "Should have 'welcome' symbol" )
710+
711+ def test_riscv_guess_game_correct_guess (self ):
712+ """RISC-V guess_game completes when correct answer (42) is given"""
713+ elf = Path ("examples/riscv/guess_game/guess_game" )
714+ if not elf .exists ():
715+ self .skipTest (f"Not found: { elf } " )
716+
717+ sim = create_simulator (str (elf ))
718+ steps , reason = self ._run_with_input (sim , 42 , "RISCV" )
719+
720+ self .assertIn (reason , ["exit" , "syscall_exit" ],
721+ f"RISC-V guess_game should exit cleanly with correct answer, got { reason } " )
722+ self .assertLess (steps , 5000 ,
723+ f"RISC-V guess_game should complete quickly with correct answer" )
724+
725+ def test_arm_guess_game_correct_guess (self ):
726+ """ARM64 guess_game completes when correct answer (42) is given"""
727+ elf = Path ("examples/arm/guess_game/guess_game" )
728+ if not elf .exists ():
729+ self .skipTest (f"Not found: { elf } " )
730+
731+ sim = create_simulator (str (elf ))
732+ steps , reason = self ._run_with_input (sim , 42 , "ARM" )
733+
734+ self .assertIn (reason , ["exit" , "syscall_exit" ],
735+ f"ARM64 guess_game should exit cleanly with correct answer, got { reason } " )
736+ self .assertLess (steps , 5000 ,
737+ f"ARM64 guess_game should complete quickly with correct answer" )
738+
739+ def test_x86_guess_game_correct_guess (self ):
740+ """x86-64 guess_game completes when correct answer (42) is given"""
741+ elf = Path ("examples/x86_64/guess_game/guess_game" )
742+ if not elf .exists ():
743+ self .skipTest (f"Not found: { elf } " )
744+
745+ sim = create_simulator (str (elf ))
746+ steps , reason = self ._run_with_input (sim , 42 , "X86_64" )
747+
748+ self .assertIn (reason , ["exit" , "syscall_exit" ],
749+ f"x86-64 guess_game should exit cleanly with correct answer, got { reason } " )
750+ self .assertLess (steps , 5000 ,
751+ f"x86-64 guess_game should complete quickly with correct answer" )
752+
753+ def test_mips_guess_game_correct_guess (self ):
754+ """MIPS guess_game completes when correct answer (42) is given"""
755+ elf = Path ("examples/mips/guess_game/guess_game" )
756+ if not elf .exists ():
757+ self .skipTest (f"Not found: { elf } " )
758+
759+ sim = create_simulator (str (elf ))
760+ steps , reason = self ._run_with_input (sim , 42 , "MIPS" )
761+
762+ self .assertIn (reason , ["exit" , "syscall_exit" ],
763+ f"MIPS guess_game should exit cleanly with correct answer, got { reason } " )
764+ self .assertLess (steps , 5000 ,
765+ f"MIPS guess_game should complete quickly with correct answer" )
766+
767+ def test_riscv_guess_game_wrong_then_right (self ):
768+ """RISC-V guess_game handles wrong guess then correct"""
769+ elf = Path ("examples/riscv/guess_game/guess_game" )
770+ if not elf .exists ():
771+ self .skipTest (f"Not found: { elf } " )
772+
773+ sim = create_simulator (str (elf ))
774+ syscall_reg , _ , result_reg = self ._get_syscall_regs ("RISCV" )
775+
776+ guesses = [10 , 42 ] # Wrong, then correct
777+ guess_idx = 0
778+
779+ for step in range (10000 ):
780+ result = sim .step ()
781+
782+ if result == StepResult .SYSCALL :
783+ syscall_num = sim .get_reg (syscall_reg )
784+ if syscall_num == 5 : # read_int
785+ if guess_idx < len (guesses ):
786+ sim .set_reg (result_reg , guesses [guess_idx ])
787+ guess_idx += 1
788+ else :
789+ sim .set_reg (result_reg , 42 ) # Fallback
790+ elif syscall_num == 10 or syscall_num == 93 : # exit
791+ break
792+ # Other syscalls (print_string, etc.) - continue execution
793+
794+ elif result == StepResult .HALT :
795+ break
796+ elif result == StepResult .ERROR :
797+ self .fail ("RISC-V guess_game encountered an error" )
798+ else :
799+ self .fail ("RISC-V guess_game should complete with two guesses" )
800+
801+ self .assertEqual (guess_idx , 2 , "Should have used exactly 2 guesses" )
802+
803+
804+ class TestComprehensiveISACoverage (unittest .TestCase ):
805+ """Comprehensive test coverage for all ISAs and example programs.
806+
807+ This test class ensures every ISA/program combination works correctly.
808+ """
809+
810+ ALL_ISAS = ["riscv" , "arm" , "x86_64" , "mips" ]
811+
812+ NON_INTERACTIVE_PROGRAMS = [
813+ "hello_asm" ,
814+ "fibonacci" ,
815+ "array_stats" ,
816+ "matrix_multiply" ,
817+ ]
818+
819+ def _get_program_path (self , isa : str , program : str ) -> Path :
820+ """Get path to program binary, handling naming variations."""
821+ if program == "matrix_multiply" :
822+ return Path (f"examples/{ isa } /{ program } /matrix_mult" )
823+ return Path (f"examples/{ isa } /{ program } /{ program } " )
824+
825+ def test_all_isas_load_all_programs (self ):
826+ """Every ISA can load every non-interactive example program"""
827+ for isa in self .ALL_ISAS :
828+ for program in self .NON_INTERACTIVE_PROGRAMS :
829+ with self .subTest (isa = isa , program = program ):
830+ path = self ._get_program_path (isa , program )
831+ if not path .exists ():
832+ self .skipTest (f"Not found: { path } " )
833+
834+ sim = create_simulator (str (path ))
835+ self .assertIsNotNone (sim , f"{ isa } /{ program } should load" )
836+
837+ # Verify basic execution
838+ pc_before = sim .get_pc ()
839+ sim .step ()
840+ pc_after = sim .get_pc ()
841+
842+ self .assertNotEqual (pc_before , pc_after ,
843+ f"{ isa } /{ program } : PC should advance after step" )
844+
845+ def test_all_isas_have_symbols (self ):
846+ """Every program has expected symbols"""
847+ expected_symbols = {
848+ "hello_asm" : ["_start" ],
849+ "fibonacci" : ["_start" , "fibonacci" ],
850+ "array_stats" : ["_start" , "array" ],
851+ "matrix_multiply" : ["_start" , "matrix_a" , "matrix_b" , "matrix_c" ],
852+ }
853+
854+ for isa in self .ALL_ISAS :
855+ for program , symbols in expected_symbols .items ():
856+ with self .subTest (isa = isa , program = program ):
857+ path = self ._get_program_path (isa , program )
858+ if not path .exists ():
859+ self .skipTest (f"Not found: { path } " )
860+
861+ sim = create_simulator (str (path ))
862+ actual_symbols = sim .get_symbols ()
863+
864+ for sym in symbols :
865+ self .assertIn (sym , actual_symbols ,
866+ f"{ isa } /{ program } should have '{ sym } ' symbol" )
867+
868+ def test_all_isas_complete_programs (self ):
869+ """Every non-interactive program completes within step limits"""
870+ step_limits = {
871+ "hello_asm" : 100 ,
872+ "fibonacci" : 1000 ,
873+ "array_stats" : 5000 ,
874+ "matrix_multiply" : 5000 ,
875+ }
876+
877+ for isa in self .ALL_ISAS :
878+ for program , max_steps in step_limits .items ():
879+ with self .subTest (isa = isa , program = program ):
880+ path = self ._get_program_path (isa , program )
881+ if not path .exists ():
882+ self .skipTest (f"Not found: { path } " )
883+
884+ sim = create_simulator (str (path ))
885+ steps = sim .run (max_steps = max_steps )
886+
887+ self .assertLess (steps , max_steps ,
888+ f"{ isa } /{ program } should complete in <{ max_steps } steps" )
889+
890+ def test_all_isas_disassembly_works (self ):
891+ """Disassembly works for all ISAs"""
892+ for isa in self .ALL_ISAS :
893+ with self .subTest (isa = isa ):
894+ path = self ._get_program_path (isa , "hello_asm" )
895+ if not path .exists ():
896+ self .skipTest (f"Not found: { path } " )
897+
898+ sim = create_simulator (str (path ))
899+ pc = sim .get_pc ()
900+ disasm = sim .disasm (pc )
901+
902+ self .assertIsInstance (disasm , str , f"{ isa } disasm should return string" )
903+ self .assertGreater (len (disasm ), 0 , f"{ isa } disasm should not be empty" )
904+ self .assertNotIn ("invalid" , disasm .lower (),
905+ f"{ isa } disasm should produce valid output" )
906+
907+ def test_all_isas_memory_access (self ):
908+ """Memory read/write works for all ISAs"""
909+ for isa in self .ALL_ISAS :
910+ with self .subTest (isa = isa ):
911+ path = self ._get_program_path (isa , "array_stats" )
912+ if not path .exists ():
913+ self .skipTest (f"Not found: { path } " )
914+
915+ sim = create_simulator (str (path ))
916+ symbols = sim .get_symbols ()
917+
918+ if "array" in symbols :
919+ addr = symbols ["array" ]
920+ # Read some bytes
921+ data = sim .read_mem (addr , 4 )
922+ self .assertEqual (len (data ), 4 , f"{ isa } should read 4 bytes" )
923+
924+
596925if __name__ == "__main__" :
597926 unittest .main (verbosity = 2 )
0 commit comments