| # |
| # Copyright 2017 The Cobalt Authors. All Rights Reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| """Linux implementation of Starboard launcher abstraction.""" |
| |
| import errno |
| import os |
| import signal |
| import socket |
| import subprocess |
| import sys |
| import time |
| import traceback |
| |
| from starboard.tools import abstract_launcher |
| from starboard.tools import send_link |
| |
| STATUS_CHANGE_TIMEOUT = 15 |
| |
| # This file is still executed with Python2 in CI. |
| # pylint:disable=consider-using-f-string,super-with-arguments |
| |
| IS_MODULAR_BUILD = os.getenv("MODULAR_BUILD", "0") == "1" |
| |
| |
| def GetProcessStatus(pid): |
| """Returns process running status given its pid, or empty string if not found. |
| |
| Args: |
| pid: process id of specified cobalt instance. |
| """ |
| output = subprocess.check_output([f"ps -o state= -p {pid}"], |
| shell=True).decode() |
| return output |
| |
| |
| class Launcher(abstract_launcher.AbstractLauncher): |
| """Class for launching Cobalt/tools on Linux.""" |
| |
| def __init__(self, platform, target_name, config, device_id, **kwargs): |
| super().__init__(platform, target_name, config, device_id, **kwargs) |
| # Starts should be generally quick on Linux, default is 2 minutes |
| self.startup_timeout_seconds = 15 |
| if self.device_id: |
| self.device_ip = self.device_id |
| else: |
| if os.getenv("IPV6_AVAILABLE", "1") == "1" and socket.has_ipv6: |
| # If the device supports IPv6: |
| self.device_id = "[::1]" # Use IPv6 loopback address (rfc2732 format). |
| else: |
| self.device_id = socket.gethostbyname("localhost") # pylint: disable=W6503 |
| self.device_ip = socket.gethostbyname(socket.gethostname()) |
| |
| self.executable = self.GetTargetPath() |
| if IS_MODULAR_BUILD: |
| self.executable += "_loader" |
| if not os.path.exists(self.executable): |
| self.executable = os.path.abspath( |
| os.path.join(self.out_directory, "starboard", self.target_name)) |
| |
| env = os.environ.copy() |
| env.update(self.env_variables) |
| self.full_env = env |
| |
| # Ensure that if the binary has code coverage or profiling instrumentation, |
| # the output will be written to a file in the coverage_directory named as |
| # the target_name with '.profraw' postfixed. |
| if self.coverage_directory: |
| target_profraw = os.path.join(self.coverage_directory, |
| target_name + ".profraw") |
| env.update({"LLVM_PROFILE_FILE": target_profraw}) |
| |
| # Remove any stale profraw file that may already exist. |
| try: |
| os.remove(target_profraw) |
| except OSError as e: |
| if e.errno != errno.ENOENT: |
| raise |
| |
| self.proc = None |
| self.pid = None |
| |
| def Run(self): |
| """Runs launcher's executable.""" |
| |
| self.proc = subprocess.Popen( # pylint: disable=consider-using-with |
| [self.executable] + self.target_command_line_params, |
| stdout=self.output_file, |
| stderr=self.output_file, |
| env=self.full_env, |
| close_fds=True, |
| cwd=self.out_directory) |
| self.pid = self.proc.pid |
| self.proc.wait() |
| return self.proc.returncode |
| |
| def Kill(self): |
| sys.stderr.write("\n***Killing Launcher***\n") |
| if self.pid: |
| try: |
| self.proc.kill() |
| except OSError: |
| sys.stderr.write("Error killing launcher with SIGKILL:\n") |
| traceback.print_exc(file=sys.stderr) |
| else: |
| sys.stderr.write("Kill() called before Run(), cannot kill.\n") |
| |
| def SupportsSuspendResume(self): |
| return True |
| |
| def SendResume(self): |
| """Sends continue to the launcher's executable.""" |
| sys.stderr.write("\n***Sending continue signal to executable***\n") |
| if self.proc: |
| self.proc.send_signal(signal.SIGCONT) |
| # Wait for process status change in Linux system. |
| self.WaitForProcessStatus("R", STATUS_CHANGE_TIMEOUT) |
| else: |
| sys.stderr.write("Cannot send continue to executable; it is closed.\n") |
| |
| def SendSuspend(self): |
| """Sends suspend to the launcher's executable.""" |
| sys.stderr.write("\n***Sending suspend signal to executable***\n") |
| if self.proc: |
| self.proc.send_signal(signal.SIGUSR1) |
| # Wait for process status change in Linux system. |
| self.WaitForProcessStatus("T", STATUS_CHANGE_TIMEOUT) |
| else: |
| sys.stderr.write("Cannot send suspend to executable; it is closed.\n") |
| |
| def SendConceal(self): |
| """Sends conceal to the launcher's executable.""" |
| sys.stderr.write("\n***Sending conceal signal to executable***\n") |
| if self.proc: |
| self.proc.send_signal(signal.SIGUSR1) |
| # Wait for process status change in Linux system. |
| self.WaitForProcessStatus("S", STATUS_CHANGE_TIMEOUT) |
| else: |
| sys.stderr.write("Cannot send conceal to executable; it is closed.\n") |
| |
| def SendFocus(self): |
| """Sends focus to the launcher's executable.""" |
| sys.stderr.write("\n***Sending focus signal to executable***\n") |
| if self.proc: |
| self.proc.send_signal(signal.SIGCONT) |
| # Wait for process status change in Linux system. |
| self.WaitForProcessStatus("R", STATUS_CHANGE_TIMEOUT) |
| else: |
| sys.stderr.write("Cannot send unpause to executable; it is closed.\n") |
| |
| def SendFreeze(self): |
| """Sends freeze to the launcher's executable.""" |
| sys.stderr.write("\n***Sending freeze signal to executable***\n") |
| if self.proc: |
| self.proc.send_signal(signal.SIGTSTP) |
| # Wait for process status change in Linux system. |
| self.WaitForProcessStatus("T", STATUS_CHANGE_TIMEOUT) |
| else: |
| sys.stderr.write("Cannot send freeze to executable; it is closed.\n") |
| |
| def SendStop(self): |
| """Sends stop to the launcher's executable.""" |
| sys.stderr.write("\n***Sending stop signal to executable***\n") |
| if self.proc: |
| self.proc.send_signal(signal.SIGPWR) |
| # Wait for process status change in Linux system. |
| self.WaitForProcessStatus("T", STATUS_CHANGE_TIMEOUT) |
| else: |
| sys.stderr.write("Cannot send stop to executable; it is closed.\n") |
| |
| def SupportsDeepLink(self): |
| return True |
| |
| def SendDeepLink(self, link): |
| # The connect call in SendLink occasionally fails. Retry a few times if |
| # this happens. |
| connection_attempts = 3 |
| return send_link.SendLink( |
| os.path.basename(self.executable), link, connection_attempts) == 0 |
| |
| def WaitForProcessStatus(self, target_status, timeout): |
| """Wait for Cobalt to turn to target status within specified timeout limit. |
| |
| Args: |
| target_status: A character representing application status: R-running; |
| T-stopped/suspended; S-sleep/paused; |
| timeout: Time limit in unit of seconds. |
| """ |
| elapsed_time = 0 |
| while not GetProcessStatus(pid=self.pid).startswith(target_status): |
| if elapsed_time >= timeout: |
| return |
| else: |
| elapsed_time += .005 |
| time.sleep(.005) |
| |
| def GetDeviceIp(self): |
| """Gets the device IP.""" |
| return self.device_ip |
| |
| def GetDeviceOutputPath(self): |
| """Writable path where test targets can output files""" |
| return "/tmp/testoutput" |
| |
| def IgnoreAsanLeaks(self): |
| asan_options = self.env_variables.get("ASAN_OPTIONS", "") |
| asan_options = [ |
| opt for opt in asan_options.split(":") if "detect_leaks" not in opt |
| ] |
| asan_options.append("detect_leaks=0") |
| asan_options.append("detect_odr_violation=0") |
| self.env_variables["ASAN_OPTIONS"] = ":".join(asan_options) |