async def test_interact_with_game(backend, depth: int, failure_at: Optional[int]): # Setup backend._fetch_game_status = AsyncMock() backend._world = MagicMock() if depth > 0 else None backend._get_inventory = AsyncMock() if failure_at == 1: backend._get_inventory.side_effect = MemoryOperationException( "error at _get_inventory") backend._has_pending_op = depth <= 1 backend._update_magic_item = AsyncMock() if failure_at == 2: backend._update_magic_item.side_effect = MemoryOperationException( "error at _check_for_collected_index") backend.message_cooldown = 2 expected_depth = min(depth, failure_at) if failure_at is not None else depth # Run await backend._interact_with_game(1) # Assert assert backend.message_cooldown == (2 if expected_depth < 2 else 1) backend._fetch_game_status.assert_awaited_once_with() if expected_depth > 0: backend._get_inventory.assert_awaited_once_with() else: backend._get_inventory.assert_not_awaited() if expected_depth > 1: backend._update_magic_item.assert_awaited_once_with() else: backend._update_magic_item.assert_not_awaited() if 0 < depth < (failure_at or 999): assert backend._world is not None else: assert backend._world is None
async def test_update_current_world_invalid(backend, query_result): # Setup backend.patches = dol_patcher.ALL_VERSIONS_PATCHES[0] if query_result is None: backend._perform_memory_operations.side_effect = MemoryOperationException("Error") else: add_memory_op_result(backend, query_result) # Run await backend._update_current_world() # Assert assert backend._world is None
async def _send_requests_to_socket(self, requests: List[RequestBatch]) -> List[bytes]: all_responses = [] try: for request in requests: data = request.build_request_data() self._socket.writer.write(data) await self._socket.writer.drain() if request.output_bytes > 0: response = await asyncio.wait_for(self._socket.reader.read(1024), timeout=15) all_responses.append(response) else: all_responses.append(b"") except (OSError, asyncio.TimeoutError) as e: if isinstance(e, asyncio.TimeoutError): self.logger.warning(f"Timeout when reading response from {self._ip}") self._socket_error = MemoryOperationException(f"Timeout when reading response") else: self.logger.warning(f"Unable to send {len(requests)} request to {self._ip}:{self._port}: {e}") self._socket_error = MemoryOperationException(f"Unable to send {len(requests)} requests: {e}") self._disconnect() raise self._socket_error from e return all_responses
def _memory_operation( self, op: MemoryOperation, pointers: Dict[int, Optional[int]]) -> Optional[bytes]: op.validate_byte_sizes() address = op.address if op.offset is not None: if address not in pointers: raise MemoryOperationException( f"Invalid op: {address} is not in pointers") address = pointers[address] + op.offset _validate_range(address, op.byte_count) result = None if op.read_byte_count is not None: result = self.dolphin.read_bytes(address, op.read_byte_count) if op.write_bytes is not None: self.dolphin.write_bytes(address, op.write_bytes) self.logger.debug(f"Wrote {op.write_bytes.hex()} to {address:x}") return result
def _validate_range(address: int, size: int): if address < MEM1_START or address + size > MEM1_END: raise MemoryOperationException( f"Range {address:x} -> {address + size:x} is outside of the GameCube memory range." )
def _was_invalid_address(response: bytes, i: int) -> bool: try: return not response[i // 8] & (1 << (i % 8)) except IndexError: raise MemoryOperationException("Server response too short for validator bytes")