blob: 8e804e7389b07ec2f6be3adab2dcbe2d8404a9a5 [file]
"""Step class to run tests against a local x86 cast shell."""
from __future__ import absolute_import
import datetime
import glob
import json
import logging
import os
import time
import traceback
from helpers import cast_shell_utils
from helpers import py_env_utils
from slave import base_step
_FRAGGLE_ROCK_CONFIG_FILES_ENV_VAR = 'FRAGGLE_ROCK_CONFIG_FILES'
_LOG_DIR = 'integration_test_logs'
# pylint: disable=method-hidden
class RunIntegrationTestStep(base_step.BaseStep):
"""Step for running integration test against a local x86 cast shell."""
def __init__(self, test_case_class, test_case_module, test_sequence_class,
test_sequence_module, test_config_file_path,
device_class='LocalX86CastShellDevice',
device_module='eurtest.device.local_x86_cast_shell_device',
assistant_enabled=False,
headless=False,
debug_output=True,
**kwargs):
"""Creates a RunIntegrationTestStep instance.
Args:
test_case_class: A list of TestCase implementations to run. Each TestCase
class is expected to be defined in the test_case_module.
test_case_module: Module containing TestCase implementation to run.
test_sequence_class: TestSequence implementation to run.
test_sequence_module: Module containing TestSequence implementation to
run.
test_config_file_path: Dot-notation path to the test's config file
relative to config_file_root_dir.
device_class: A string with the name of Python class that represents the
device.
device_module: Dot-notation path to the module containing |device_class|.
assistant_enabled: Whether the x86 cast_shell against which tests will
be run is assistant-enabled (in which case, some special flags are
specified with the cast_shell is started).
headless: Whether the x86 cast_shell uses headless ozone platform.
debug_output: Log debug output from the test.
**kwargs: Any additional args to pass to BaseStep.
"""
base_step.BaseStep.__init__(self, name='Run integration tests', **kwargs)
self._test_case_class = test_case_class
self._test_case_module = test_case_module
self._test_sequence_class = test_sequence_class
self._test_sequence_module = test_sequence_module
self._test_config_file_path = test_config_file_path
self._device_class = device_class
self._device_module = device_module
self._device_ip = '127.0.0.1'
self._config_file_root_dir = os.path.join(
self.get_project_path('continuous-tests'), 'lab_system', 'master',
'config')
self._assistant_enabled = assistant_enabled
self._headless = headless
self._debug_output = debug_output
def _remove_existing_results_json(self):
"""Remove any results JSON files from previous runs."""
previous_results = glob.glob(os.path.join(os.getcwd(), '*results.json'))
for result_found in previous_results:
os.remove(result_found)
def _execute_tests(self, venv_root):
"""Execute the integration tests using eurtest.framework.test_runner.
Args:
venv_root: Path to virtual environment root directory.
Returns:
A Tuple of (a dict representing a review object,
a boolean indicating if tests passed)
"""
self._remove_existing_results_json()
short_wait_secs = 5
env = {
_FRAGGLE_ROCK_CONFIG_FILES_ENV_VAR: self._config_file_root_dir,
'PYTHONPATH': self._get_python_path()
}
runner = os.path.abspath(
os.path.join(self.get_project_path('test'),
'eurtest', 'framework', 'test_runner.py'))
run_info = {}
run_info['start_timestamp'] = datetime.datetime.now().isoformat()
run_info['branch_name'] = self.manifest_branch
run_info['platform'] = 'clang_x86'
cmd = [
runner,
'--test_case_class', ','.join(self._test_case_class),
'--test_case_module', self._test_case_module,
'--test_sequence_class', self._test_sequence_class,
'--test_sequence_module', self._test_sequence_module,
'--target_device_ethernet_ip', self._device_ip,
'--target_device_wifi_ip', self._device_ip,
'--device_class', self._device_class,
'--device_module', self._device_module,
'--run_information_json', json.dumps(run_info),
'--test_config_file_path', self._test_config_file_path,
'--nouse_tunnel'
]
if self._debug_output:
cmd.append('--debug')
returncode, stdout, stderr = py_env_utils.exec_within_virtual_env(
self, venv_root, cmd, env=env)
if returncode != 0:
logging.error('Failed to run tests: %s\n%s', stdout, stderr)
review = ({'message': 'no result found'}, False)
# A JSON file will be saved to results_path directory within a few seconds
time.sleep(short_wait_secs)
results = glob.glob(os.path.join(os.getcwd(), '*results.json'))
if results:
# Assumption: there should be only one results JSON file
review = self.generate_review(results[0])
return review
def generate_review(self, results):
"""Generate a review object from integration test results.
Args:
results: File path to result JSON file.
Returns:
A Tuple of (a dict representing a review object,
a boolean indicating if tests passed)
"""
passing = True
result_messages = []
results_data = []
summary = 'Suites: {}, Run: {}, Errors: {}, Failures: {}'
with open(results) as results_json:
json_output = json.load(results_json)
if isinstance(json_output, list):
results_data.extend(json_output)
else:
results_data.append(json_output)
for result in results_data:
suite_count = 0
test_count = 0
failure_count = 0
error_count = 0
for suite in result['test_suites']:
suite_count += 1
test_count += suite['test_count']
failure_count += suite['failure_count']
error_count += suite['error_count']
if failure_count + error_count > 0:
message = ('Test: {}, {}, Time Taken(sec): {:.2f}').format(
result['test_name'],
summary.format(suite_count, test_count, error_count, failure_count),
result['elapsed_secs'])
result_messages.append(message)
passing = False
if passing:
status = 'Looks Good. All tests passed.'
else:
status = 'Cast compliance tests failed. See the build logs for details.'
review = {
'message': ('{} \n{}'.format(status, '\n'.join(result_messages)))
}
return review, passing
def _get_python_path(self):
"""Returns the PYTHONPATH to execute tests with."""
projects = ['test', 'continuous-tests']
python_paths = [
os.path.abspath(self.get_project_path(project)) for project in projects]
return os.pathsep.join(python_paths)
def run(self):
"""Starts a local x86 cast shell and run integration tests against it.
Returns:
True iff all tests pass.
"""
log_fd = None
log_file_path = None
try:
# Launch cast_shell
_, log_fd, log_file_path = cast_shell_utils.start_cast_shell(
self,
self.get_project_path('test'),
self.get_step_data(cast_shell_utils.CAST_SHELL_EXE_PATH),
self.get_tmp_dir(),
enable_cma_media_pipeline=True,
use_headless_ozone_platform=(
self._headless or self._assistant_enabled),
launch_utility_process=self._assistant_enabled)
venv_root = self.get_step_data('virtual_env_root')
review_message, is_passing = self._execute_tests(venv_root)
except: # pylint: disable=bare-except
review_message = {
'message': ('Cast compliance test setup failed.\n{}'.format(
traceback.format_exc()))
}
is_passing = False
if log_fd and log_file_path:
cast_shell_utils.upload_cast_shell_logs(log_fd,
log_file_path,
self.get_gcs_dir(), _LOG_DIR,
'cast_shell_console.txt')
if not is_passing:
self.add_review(review_message)
else:
logging.info(review_message.get('message'))
return is_passing