chromium/third_party/blink/tools/blinkpy/web_tests/port/ios_simulator_server_process.py

# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import logging
import os
import select
import socket

from blinkpy.web_tests.port.server_process import ServerProcess
from blinkpy.web_tests.port import driver

_log = logging.getLogger(__name__)

CONN_WAITING_TIMEOUT = 20


# Custom version of ServerProcess that runs processes on the iOS simulator.
class IOSSimulatorServerProcess(ServerProcess):
    def __init__(self,
                 port_obj,
                 name,
                 cmd,
                 env=None,
                 treat_no_data_as_crash=False,
                 more_logging=False):
        super(IOSSimulatorServerProcess,
              self).__init__(port_obj, name, cmd, env, treat_no_data_as_crash,
                             more_logging)
        self._port.check_simulator_is_booted()

    def _start(self):
        if self._proc:
            raise ValueError('%s already running' % self._name)
        self._reset()

        # The iOS simulator doesn't allow to use stdin stream for packaged apps.
        # Additionally, the stdout from run-test-suite not only includes
        # additional data from the iOS test infrastructure but also combines
        # stderr and stdout. As a result, when executing content_shell on the
        # iOS simulator, it becomes impractical to employ stdin for transmitting
        # a list of tests or to dependably utilize stdout for outputting
        # results. To address this issue in the context of web tests, we employ
        # a workaround by redirecting stdin and stdout to a TCP socket connected
        # to the web test runner. Similar to Fuchsia, the runner also uses the
        # --stdio-redirect flag to specify the address and port for stdin and
        # stdout redirection.
        listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        listen_socket.bind(('127.0.0.1', self._port.stdio_redirect_port()))
        listen_socket.listen(1)

        proc = self._host.executive.popen(self._cmd,
                                          stdin=self._host.executive.PIPE,
                                          stdout=self._host.executive.PIPE,
                                          stderr=self._host.executive.PIPE,
                                          env=self._env)

        # Wait for incoming connection from the iOS content_shell.
        fd = listen_socket.fileno()

        read_fds, _, _ = select.select([fd], [], [], CONN_WAITING_TIMEOUT)
        while fd not in read_fds:
            _log.error(
                'Timed out waiting connection from content_shell. Try to launch the content_shell again.'
            )
            proc.kill()
            # TODO(gyuyoung): There is sometimes no incoming connection when
            # attempting to launch a content shell. We need to investigate why
            # launching the content shell occasionally fails. To resolve the
            # issue, consider repeatedly attempting to launch the content shell
            # until it successfully launches.
            proc = self._host.executive.popen(self._cmd,
                                              stdin=self._host.executive.PIPE,
                                              stdout=self._host.executive.PIPE,
                                              stderr=self._host.executive.PIPE,
                                              env=self._env)
            read_fds, _, _ = select.select([fd], [], [], CONN_WAITING_TIMEOUT)

        stdio_socket, _ = listen_socket.accept()
        fd = stdio_socket.fileno()  # pylint: disable=no-member
        stdin_pipe = os.fdopen(os.dup(fd), 'wb', 0)
        stdout_pipe = os.fdopen(os.dup(fd), 'rb', 0)
        stdio_socket.close()

        proc.stdin = stdin_pipe
        proc.stdout = stdout_pipe

        self._set_proc(proc)