Esempio n. 1
0
 async def _start_companion_command(
     self, arguments: List[str]
 ) -> AsyncGenerator[asyncio.subprocess.Process, None]:
     companion_path = self._companion_path
     if companion_path is None:
         if platform == "darwin":
             raise IdbException("Companion path not provided")
         else:
             raise IdbException(
                 "Companion interactions do not work on non-macOS platforms"
             )
     cmd: List[str] = [companion_path]
     device_set_path = self._device_set_path
     if device_set_path is not None:
         cmd.extend(["--device-set-path", device_set_path])
     cmd.extend(arguments)
     process = await asyncio.create_subprocess_exec(*cmd,
                                                    stdout=subprocess.PIPE,
                                                    stderr=None)
     logger = self._logger.getChild(f"{process.pid}:{' '.join(arguments)}")
     logger.info("Launched process")
     try:
         yield process
     finally:
         await _terminate_process(process=process,
                                  timeout=self._companion_teardown_timeout,
                                  logger=logger)
Esempio n. 2
0
 async def func_wrapper(*args: Any, **kwargs: Any) -> Any:  # pyre-ignore
     try:
         return await func(*args, **kwargs)
     except GRPCError as e:
         raise IdbException(e.message) from e  # noqa B306
     except (ProtocolError, StreamTerminatedError) as e:
         raise IdbException(e.args) from e
Esempio n. 3
0
 async def get_companion_info(self,
                              target_udid: Optional[str]) -> CompanionInfo:
     async with self._use_stored_companions() as companions:
         # If we get a target by udid we expect only one value.
         if target_udid is not None:
             matching = [
                 companion for companion in companions
                 if companion.udid == target_udid
             ]
             if len(matching) == 1:
                 return matching[0]
             elif len(matching) > 1:
                 raise IdbException(
                     f"More than one companion matching udid {target_udid}: {matching}"
                 )
             else:
                 raise IdbException(
                     f"No companion for {target_udid}, existing {companions}"
                 )
         # With no udid provided make sure there is only a single match
         elif len(companions) == 1:
             companion = companions[0]
             self.logger.info(
                 f"Using sole default companion with udid {companion.udid}")
             return companion
         elif len(companions) > 1:
             raise IdbException(
                 f"No UDID provided and there's multiple companions: {companions}"
             )
         else:
             raise IdbException("No UDID provided and no companions exist")
Esempio n. 4
0
 async def from_udid(self,
                     udid: Optional[str]) -> AsyncGenerator[Client, None]:
     companions = {
         companion.udid: companion
         for companion in await self._companion_set.get_companions()
     }
     if udid is not None and udid in companions:
         companion = companions[udid]
         self._logger.debug(
             f"Got existing companion {companion} for udid {udid}")
     elif udid is not None:
         self._logger.debug(
             f"No running companion for {udid}, spawning one")
         companion = await self._spawn_companion_server(udid=udid)
     elif len(companions) == 1:
         self._logger.debug(
             "No udid provided, and there is a sole companion, using it")
         companion = list(companions.values())[0]
     elif len(companions) == 0:
         raise IdbException(
             "No udid provided and there no companions, unclear which target to run against. Please specify a UDID"
         )
     else:
         raise IdbException(
             f"No udid provided and there are multiple companions to run against {companions.keys()}. Please specify a UDID unclear which target to run against"
         )
     async with Client.build(
             address=companion.address,
             logger=self._logger,
     ) as client:
         self._logger.debug(f"Constructed client for companion {companion}")
         yield client
Esempio n. 5
0
 async def _tramp(*args: Any, **kwargs: Any) -> Any:  # pyre-ignore
     try:
         client = await _make_client()
         return await call(client, *args, **kwargs)
     except GRPCError as e:
         raise IdbException(e.message) from e  # noqa B306
     except (ProtocolError, StreamTerminatedError) as e:
         raise IdbException(e.args) from e
Esempio n. 6
0
 async def func_wrapper_gen(*args: Any, **kwargs: Any) -> Any:
     try:
         async for item in func(*args, **kwargs):
             yield item
     except GRPCError as e:
         raise IdbException(e.message) from e  # noqa B306
     except (ProtocolError, StreamTerminatedError) as e:
         raise IdbException(e.args) from e
Esempio n. 7
0
 async def target_description(
         self,
         udid: str,
         only: Optional[TargetType] = None) -> TargetDescription:
     all_details = await self.list_targets(only=only)
     details = [target for target in all_details if target.udid == udid]
     if len(details) > 1:
         raise IdbException(f"More than one device info found {details}")
     if len(details) == 0:
         raise IdbException(f"No device info found, got {all_details}")
     return details[0]
Esempio n. 8
0
 async def _run_companion_command(self, arguments: List[str]) -> str:
     timeout = self._companion_command_timeout
     async with self._start_companion_command(
             arguments=arguments) as process:
         try:
             (output, _) = await asyncio.wait_for(process.communicate(),
                                                  timeout=timeout)
             if process.returncode != 0:
                 raise IdbException(f"Failed to run {arguments}")
             self.logger.info(f"Ran {arguments} successfully.")
             return output.decode()
         except asyncio.TimeoutError:
             raise IdbException(
                 f"Timed out after {timeout} secs on command {' '.join(arguments)}"
             )
Esempio n. 9
0
 async def target_description(
     self,
     udid: Optional[str] = None,
     only: Optional[OnlyFilter] = None,
     timeout: Optional[timedelta] = None,
 ) -> TargetDescription:
     all_details = await self.list_targets(only=only, timeout=timeout)
     details = all_details
     if udid is not None:
         details = [target for target in all_details if target.udid == udid]
     if len(details) > 1:
         raise IdbException(f"More than one device info found {details}")
     if len(details) == 0:
         raise IdbException(f"No device info found, got {all_details}")
     return details[0]
Esempio n. 10
0
 async def connect(
     self,
     destination: ConnectionDestination,
     metadata: Optional[Dict[str, str]] = None,
 ) -> CompanionInfo:
     self._logger.debug(
         f"Connecting directly to {destination} with meta {metadata}")
     if isinstance(destination, TCPAddress) or isinstance(
             destination, DomainSocketAddress):
         async with IdbClient.build(address=destination,
                                    is_local=False,
                                    logger=self._logger) as client:
             with tempfile.NamedTemporaryFile(mode="w+b") as f:
                 response = await client.stub.connect(
                     ConnectRequest(metadata=metadata,
                                    local_file_path=f.name))
         companion = CompanionInfo(
             address=destination,
             udid=response.companion.udid,
             is_local=response.companion.is_local,
         )
         self._logger.debug(f"Connected directly to {companion}")
         await self._direct_companion_manager.add_companion(companion)
         return companion
     else:
         companion = await self._spawn_companion(target_udid=destination)
         if companion:
             return companion
         else:
             raise IdbException(f"can't find target for udid {destination}")
Esempio n. 11
0
def target_type_from_string(output: str) -> TargetType:
    normalized = output.lower()
    if "sim" in normalized:
        return TargetType.SIMULATOR
    if "dev" in normalized:
        return TargetType.DEVICE
    raise IdbException(f"Could not interpret target type from {output}")
Esempio n. 12
0
    async def start(
        stub: CompanionServiceStub, logger: logging.Logger, pkg_id: str
    ) -> AsyncGenerator["RemoteDapServer", None]:
        """
        Created a RemoteDapServer starting a new grpc stream and sending a start dap server request to companion
        """
        logger.info("Starting dap connection")
        async with stub.dap.open() as stream:
            await stream.send_message(
                DapRequest(start=DapRequest.Start(debugger_pkg_id=pkg_id))
            )

            response = await stream.recv_message()
            logger.debug(f"Dap response after start request: {response}")
            if response and response.started:
                logger.info("Dap stream ready to receive messages")
                dap_server = RemoteDapServer(
                    stream=stream,
                    logger=logger,
                )
                try:
                    yield dap_server
                finally:
                    await dap_server.__stop()
            else:
                logger.error(f"Starting dap server failed! {response}")
                raise IdbException("Failed to spawn dap server.")

        logger.info("Dap grpc stream is closed.")
Esempio n. 13
0
 async def connect(
     self,
     destination: ConnectionDestination,
     metadata: Optional[Dict[str, str]] = None,
 ) -> CompanionInfo:
     self.logger.debug(f"Connecting directly to {destination} with meta {metadata}")
     if isinstance(destination, Address):
         channel = Channel(
             destination.host, destination.port, loop=asyncio.get_event_loop()
         )
         stub = CompanionServiceStub(channel=channel)
         with tempfile.NamedTemporaryFile(mode="w+b") as f:
             response = await stub.connect(
                 ConnectRequest(
                     destination=destination_to_grpc(destination),
                     metadata=metadata,
                     local_file_path=f.name,
                 )
             )
         companion = CompanionInfo(
             udid=response.companion.udid,
             host=destination.host,
             port=destination.port,
             is_local=response.companion.is_local,
         )
         self.logger.debug(f"Connected directly to {companion}")
         self.direct_companion_manager.add_companion(companion)
         channel.close()
         return companion
     else:
         companion = await self.spawn_companion(target_udid=destination)
         if companion:
             return companion
         else:
             raise IdbException(f"can't find target for udid {destination}")
Esempio n. 14
0
 async def build(
     cls,
     address: Address,
     logger: logging.Logger,
     is_local: Optional[bool] = None,
     exchange_metadata: bool = True,
     extra_metadata: Optional[Dict[str, str]] = None,
     use_tls: bool = False,
 ) -> AsyncGenerator["Client", None]:
     metadata_to_companion = (
         {
             **{
                 key: value
                 for (key, value) in plugin.resolve_metadata(logger=logger).items()
                 if isinstance(value, str)
             },
             **(extra_metadata or {}),
         }
         if exchange_metadata
         else {}
     )
     ssl_context = plugin.channel_ssl_context() if use_tls else None
     if use_tls:
         assert ssl_context is not None
     async with (
         Channel(
             host=address.host,
             port=address.port,
             loop=asyncio.get_event_loop(),
             ssl=ssl_context,
         )
         if isinstance(address, TCPAddress)
         else Channel(path=address.path, loop=asyncio.get_event_loop())
     ) as channel:
         stub = CompanionServiceStub(channel=channel)
         with tempfile.NamedTemporaryFile(mode="w+b") as f:
             try:
                 response = await stub.connect(
                     ConnectRequest(
                         metadata=metadata_to_companion, local_file_path=f.name
                     )
                 )
             except Exception as ex:
                 raise IdbException(
                     f"Failed to connect to companion at address {address}: {ex}"
                 )
         companion = companion_to_py(
             companion=response.companion, address=address, is_local=is_local
         )
         if exchange_metadata:
             metadata_from_companion = {
                 key: value
                 for (key, value) in companion.metadata.items()
                 if isinstance(value, str)
             }
             plugin.append_companion_metadata(
                 logger=logger, metadata=metadata_from_companion
             )
         yield Client(stub=stub, companion=companion, logger=logger)
Esempio n. 15
0
 async def _run_companion_command(self, arguments: List[str]) -> str:
     async with self._start_companion_command(
             arguments=arguments) as process:
         (output, _) = await process.communicate()
         if process.returncode != 0:
             raise IdbException(f"Failed to run {arguments}")
         self.logger.info(f"Ran {arguments} successfully.")
         return output.decode()
Esempio n. 16
0
 async def _run_companion_command(self, arguments: List[str],
                                  timeout: Optional[timedelta]) -> str:
     timeout = timeout if timeout is not None else DEFAULT_COMPANION_COMMAND_TIMEOUT
     async with self._start_companion_command(
             arguments=arguments) as process:
         try:
             (output,
              _) = await asyncio.wait_for(process.communicate(),
                                          timeout=timeout.total_seconds())
             if process.returncode != 0:
                 raise IdbException(f"Failed to run {arguments}")
             self._logger.info(f"Ran {arguments} successfully.")
             return output.decode()
         except asyncio.TimeoutError:
             raise IdbException(
                 f"Timed out after {timeout} secs on command {' '.join(arguments)}"
             )
Esempio n. 17
0
 async def boot(self) -> None:
     if self.target_udid:
         cmd: List[str] = ["idb_companion", "--boot", none_throws(self.target_udid)]
         process = await asyncio.create_subprocess_exec(
             *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
         )
         await process.communicate()
     else:
         raise IdbException("boot needs --udid to work")
Esempio n. 18
0
    def func_wrapper(client, *args, **kwargs):  # pyre-ignore

        try:
            client.companion_info: CompanionInfo = client.direct_companion_manager.get_companion_info(
                target_udid=client.target_udid)
            client.logger.info(f"using companion {client.companion_info}")
            client.channel = Channel(
                client.companion_info.host,
                client.companion_info.port,
                loop=asyncio.get_event_loop(),
            )
            client.stub: Optional[CompanionServiceStub] = CompanionServiceStub(
                channel=client.channel)
            return func(client, *args, **kwargs)

        except GRPCError as e:
            raise IdbException(e.message) from e  # noqa B306
        except (ProtocolError, StreamTerminatedError) as e:
            raise IdbException(e.args) from e
Esempio n. 19
0
 async def _run_impl(self, args: Namespace) -> None:
     companion_path = args.companion_path
     if companion_path is None:
         raise IdbException(
             "Companion interactions do not work on non-macOS platforms")
     await self.run_with_companion(
         args=args,
         companion=LocalCompanion(companion_path=companion_path,
                                  device_set_path=None,
                                  logger=self.logger),
     )
Esempio n. 20
0
 async def unix_domain_server(self, udid: str,
                              path: str) -> AsyncContextManager[str]:
     async with self._start_companion_command(
         ["--udid", udid, "--grpc-domain-sock", path]) as process:
         line = await none_throws(process.stdout).readline()
         output = parse_json_line(line)
         grpc_path = output.get("grpc_path")
         if grpc_path is None:
             raise IdbException(f"No grpc_path in {line}")
         self._logger.info(f"Started domain sock server on {grpc_path}")
         yield grpc_path
Esempio n. 21
0
 def get_companion_info(self, target_udid: Optional[str]) -> CompanionInfo:
     self.load_companions()
     if target_udid:
         companions = [
             companion
             for companion in self.companions
             if companion.udid == target_udid
         ]
         if len(companions) > 0:
             return companions[0]
         else:
             raise IdbException(
                 f"Couldn't find companion for target with udid {target_udid}"
             )
     elif len(self.companions) >= 1:
         companion = self.companions[0]
         self.logger.info(f"using default companion with udid {companion.udid}")
         return companion
     else:
         raise IdbException("No UDID provided and couldn't find a default companion")
Esempio n. 22
0
async def _local_target_type(companion: Companion, udid: str) -> TargetType:
    if udid == "mac":
        return TargetType.MAC
    targets = {
        target.udid: target
        for target in await companion.list_targets(only=None)
    }
    target = targets.get(udid)
    if target is None:
        raise IdbException(
            f"Cannot spawn companion for {udid}, no matching target in available udids {targets.keys()}"
        )
    return target.target_type
Esempio n. 23
0
 async def unix_domain_server(
     self, udid: str, path: str, only: Optional[OnlyFilter] = None
 ) -> AsyncGenerator[str, None]:
     async with self._start_companion_command(
         ["--udid", udid, "--grpc-domain-sock", path]
         + _only_arg_from_filter(only=only)
     ) as process:
         line = await none_throws(process.stdout).readline()
         output = parse_json_line(line)
         grpc_path = output.get("grpc_path")
         if grpc_path is None:
             raise IdbException(f"No grpc_path in {line}")
         self._logger.info(f"Started domain sock server on {grpc_path}")
         yield grpc_path
Esempio n. 24
0
 async def _start_companion_command(
     self, arguments: List[str]
 ) -> AsyncContextManager[asyncio.subprocess.Process]:
     companion_path = self.companion_path
     if companion_path is None:
         if platform == "darwin":
             raise IdbException("Companion path not provided")
         else:
             raise IdbException(
                 "Companion interactions do not work on non-macOS platforms"
             )
     cmd: List[str] = [companion_path]
     device_set_path = self.device_set_path
     if device_set_path is not None:
         cmd.extend(["--device-set-path", device_set_path])
     cmd.extend(arguments)
     process = await asyncio.create_subprocess_exec(*cmd,
                                                    stdout=subprocess.PIPE,
                                                    stderr=None)
     try:
         yield process
     finally:
         await _terminate_process(process=process, logger=self.logger)
Esempio n. 25
0
async def _open_lockfile(filename: str) -> AsyncGenerator[None, None]:
    timeout = 3
    retry_time = 0.05
    deadline = datetime.now() + timedelta(seconds=timeout)
    lock_path = filename + ".lock"
    lock = None
    try:
        while lock is None:
            try:
                lock = os.open(lock_path, os.O_CREAT | os.O_EXCL)
                yield None
            except FileExistsError:
                if datetime.now() >= deadline:
                    raise IdbException("Failed to open the lockfile {lock_path}")
                await asyncio.sleep(retry_time)
    finally:
        if lock is not None:
            os.close(lock)
        os.unlink(lock_path)
Esempio n. 26
0
 async def connect(
     self,
     destination: ConnectionDestination,
     metadata: Optional[Dict[str, str]] = None,
 ) -> CompanionInfo:
     self._logger.debug(
         f"Connecting directly to {destination} with meta {metadata}")
     if isinstance(destination, TCPAddress) or isinstance(
             destination, DomainSocketAddress):
         async with Client.build(address=destination,
                                 logger=self._logger) as client:
             companion = client.companion
         self._logger.debug(f"Connected directly to {companion}")
         await self._companion_set.add_companion(companion)
         return companion
     else:
         companion = await self._spawn_companion_server(udid=destination)
         if companion:
             return companion
         else:
             raise IdbException(f"can't find target for udid {destination}")
Esempio n. 27
0
    async def install_dsym_test_bundle(
            self, args: Namespace, client: Client,
            test_bundle_location: str) -> Optional[str]:
        dsym_name = None
        dsym_path_location = args.install_dsym_test_bundle
        if args.install_dsym_test_bundle == NO_SPECIFIED_PATH:
            dsym_path_location = test_bundle_location + ".dSYM"

        if not os.path.exists(dsym_path_location):
            raise IdbException(
                "XCTest run failed! Error: --install-dsym flag was used but there is no file at location {}."
                .format(dsym_path_location))

        async for install_response in client.install_dsym(
                dsym=dsym_path_location,
                bundle_id=args.test_bundle_id,
                bundle_type=FileContainerType.XCTEST,
                compression=None,
        ):
            if install_response.progress == 0.0:
                dsym_name = install_response.name
        return dsym_name
Esempio n. 28
0
 async def _spawn_companion_server(self, udid: str) -> CompanionInfo:
     companion = self._companion
     if companion is None:
         raise IdbException(
             f"Cannot spawn companion for {udid}, no companion executable")
     target_type = await _local_target_type(companion=companion, udid=udid)
     path = os.path.join(BASE_IDB_FILE_PATH, f"{udid}_companion.sock")
     address = DomainSocketAddress(path=path)
     self._logger.info(
         f"Checking whether domain sock {path} is bound for {udid}")
     is_bound = await _check_domain_socket_is_bound(path=path)
     if is_bound:
         self._logger.info(
             f"Domain socket {path} is bound for {udid}, connecting to it.")
         companion_info = await self.connect(destination=address)
     else:
         self._logger.info(
             f"No existing companion at {path}, spawning one...")
         process = await companion.spawn_domain_sock_server(
             config=CompanionServerConfig(
                 udid=udid,
                 only=target_type,
                 log_file_path=None,
                 cwd=None,
                 tmp_path=None,
                 reparent=True,
             ),
             path=path,
         )
         self._logger.info(f"Companion at {path} spawned for {udid}")
         companion_info = CompanionInfo(
             address=address,
             udid=udid,
             is_local=True,
             pid=process.pid,
         )
     await self._companion_set.add_companion(companion_info)
     return companion_info