blob: ba538adda9b843c1dcbef94c1f0186caec2b1ab3 [file]
"""Build step class for executing unit tests."""
from __future__ import absolute_import
import logging
import os
from helpers import git_utils
from slave import base_step
from slave.step import shell_step
class Error(RuntimeError):
"""Error for this module."""
class FuchsiaUnitTestStep(base_step.BaseStep):
"""Build step class to run runner scripts as unit tests."""
def __init__(self, out_dir, fuchsia_workdir, recipe_steps,
name='fuchsia_unit_test', **kwargs):
base_step.BaseStep.__init__(self, name=name, **kwargs)
self._changed_files = None
self._fuchsia_workdir = fuchsia_workdir
self._out_dir = out_dir
self._step_kwargs = kwargs
self._recipe_steps = recipe_steps
def _current_step_index(self):
"""Returns first index of this class in _recipe_steps."""
for step in self._recipe_steps:
# Assume there is only one instanceof this class.
if isinstance(step, FuchsiaUnitTestStep):
return self._recipe_steps.index(step)
return -1
def _gunit_retry_if(self, returncode, stdout, stderr):
"""Returns whether or not the chromium unit test should be retried.
See more:
//chromium/src/build/fuchsia/runner_exceptions.py
"""
# Test runner exits between [60, 80] for infra related flakes.
if 60 < returncode < 80:
return True
# Not all exceptions are caught by the test runner, and some require
# extra checks
reasons = [
# Package server failed to transfer blobs in time for test
"Error while resolving",
# Long running tests may experience the ssh connection dropping
"Timeout when executing",
]
for retry_reason in reasons:
if retry_reason in stdout:
return True
return False
def add_unittest_step(self, suite_name, runner_cmd, fail_fast=False):
# 15 mins max timeout per test suite. Some tests like net_unittest
# take more than 5 mins to run.
max_timeout_secs = 15 * 60
# This step name makes it easy to filter Fuchsia test flakiness in the
# CQ build reliability dashboard
step_name = "Fuchsia Unittest %s" % suite_name
step = shell_step.ShellStep(
command=runner_cmd,
num_retries=2,
retry_if=self._gunit_retry_if,
directory=os.getcwd(),
halt_on_failure=fail_fast,
name=step_name,
timeout_secs=max_timeout_secs,
**self._step_kwargs)
# This step should be added after the current fuchsia unittest step.
# However, it is created during the recipe run, which is non-trivial.
current_step_index = self._current_step_index()
if current_step_index == -1:
raise Error('Could not find FuchsiaUnitTestStep class to append to')
self._recipe_steps.insert(current_step_index + 1, step)
def read_file(self, file_path): # pylint: disable=method-hidden
"""Reads a file.
Args:
file_path: A string with the path to the file open.
Returns:
A list of strings with the file content.
"""
content_list = []
with open(file_path) as f:
content_list = f.readlines()
return [line.strip() for line in content_list]
def run(self):
test_list_path = os.path.join(os.getcwd(), self._out_dir, 'tests',
'fuchsia_run_test_list.txt')
runner_folder = os.path.join(os.getcwd(), self._out_dir, 'bin')
test_list = self.read_file(test_list_path)
for suite_cmd in test_list:
suite_cmd_parts = suite_cmd.split()
suite_name = suite_cmd_parts[0]
runner_script = 'run_{}'.format(suite_name)
fuchsia_out_dir = os.path.join(self._fuchsia_workdir, 'packages')
logs_dir = os.path.join(self.get_gcs_dir(), suite_name)
os.makedirs(logs_dir, exist_ok=True)
runner_cmd = [
os.path.join(runner_folder, runner_script),
'--logs-dir', logs_dir,
'--device', 'device',
'--fuchsia-out-dir', fuchsia_out_dir
] + suite_cmd_parts[1:] # Append gtest filters to the command
self.add_unittest_step(suite_name, runner_cmd, fail_fast=False)
return True