blob: 1199440773251c404afac5a7fa7c154aba43999b [file] [log] [blame]
# Copyright 2014 The Chromium OS Authors. All rights reserved.
# 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 re
import shutil
import tempfile
from autotest_lib.client.bin import utils
from autotest_lib.client.common_lib import error
from autotest_lib.client.cros import cros_logging, service_stopper
from autotest_lib.client.cros.graphics import graphics_utils
class graphics_parallel_dEQP(graphics_utils.GraphicsTest):
"""Run the drawElements Quality Program test suite."""
version = 1
_services = None
_shard_number = 0
_shard_count = 1
_board = None
_cpu_type = None
_gpu_type = None
_surface = None
_filter = None
_width = 256 # Use smallest width for which all tests run/pass.
_height = 256 # Use smallest height for which all tests7 run/pass.
_caselist = None
_log_path = None # Location for detailed test output logs (in /tmp/).
_debug = False # Analyze kernel messages.
_log_reader = None # Reader to analyze (kernel) messages log.
_log_filter = re.compile('.* .* kernel:') # kernel messages filter.
_env = None # environment for test processes
_skips = []
_fails = []
_flakes = []
# We do not consider these results as failures.
TEST_RESULT_FILTER = [
'pass', 'notsupported', 'internalerror', 'qualitywarning',
'compatibilitywarning', 'skipped'
]
def initialize(self):
super(graphics_parallel_dEQP, self).initialize()
self._api_helper = graphics_utils.GraphicsApiHelper()
self._board = utils.get_board()
self._cpu_type = utils.get_cpu_soc_family()
self._gpu_type = utils.get_gpu_family()
# deqp may depend on libraries that are present only on test images.
# Those libraries are installed in /usr/local.
self._env = os.environ.copy()
old_ld_path = self._env.get('LD_LIBRARY_PATH', '')
if old_ld_path:
self._env[
'LD_LIBRARY_PATH'] = '/usr/local/lib:/usr/local/lib64:' + old_ld_path
else:
self._env['LD_LIBRARY_PATH'] = '/usr/local/lib:/usr/local/lib64'
self._services = service_stopper.ServiceStopper(['ui', 'powerd'])
# Valid choices are fbo and pbuffer. The latter avoids dEQP assumptions.
self._surface = 'pbuffer'
self._services.stop_services()
def cleanup(self):
if self._services:
self._services.restore_services()
super(graphics_parallel_dEQP, self).cleanup()
def _get_executable(self, api):
"""Return the executable path of the api."""
return self._api_helper.get_deqp_executable(api)
def _can_run(self, api):
"""Check if specific api is supported in this board."""
return api in self._api_helper.get_supported_apis()
def read_file(self, filename):
expects_path = os.path.join(self.autodir, 'tests',
'graphics_parallel_dEQP', 'boards',
filename)
try:
with open(expects_path) as f:
logging.debug(
"Reading board test list from {}".format(expects_path))
return f.readlines()
except IOError as e:
logging.debug("No file found at {}".format(expects_path))
return []
def setup_case_list_filters(self):
"""Set up the skip/flake/fails filter lists.
The expected fails list will be entries like
'dEQP-SUITE.test.name,Crash', such as you find in a failures.csv,
results.csv, or the "Some failures found:" stdout output of a previous
run. Enter a test here when it has an expected state other than Pass or
Skip.
The skips list is a list of regexs to match test names to not run at
all. This is good for tests that are too slow or uninteresting to ever
see status for.
The flakes list is a list of regexes to match test names that may have
unreliable status. Any unexpected result of that test will be marked
with the Flake status and not cause the test run to fail. The runner
does automatic flake detection on its own to try to mitigate
intermittent failures, but even with that we can see too many spurious
failures in CI when run across many boards and many builds, so this lets
you run those tests while avoiding having them fail out CI runs until
the source of the flakiness can be resolved.
The primary source of board skip/flake/fails will be files in this test
directory under boards/, but we also list some common entries directly
in the code here to save repetition of the explanations. The files may
contain empty lines or comments starting with '#'.
We could avoid adding filters for other apis than the one being tested,
but it's harmless to have unused tests in the lists and makes
copy-and-paste mistakes less likely.
We don't care to run the performance or stress tests from dEQP-GLES on
any boards -- they're very slow but don't generally fail."""
self._skips.append('dEQP-GLES.*.performance.*')
self._skips.append('dEQP-GLES.*.stress.*')
# This set of tests may emit warnings, but is not required for
# conformance and I've never seen anyone pay attention to it.
self._skips.append('dEQP-GLES.*.accuracy.*')
# The flush_finish tests throw warnings when behavior doesn't match
# their expectations, except that tiling GPUs don't behave the way they
# expect in their timing setup so the tests take exceptionally long
# while just wasting developer time. Not required to be warnings-free
# for conformance.
if self._gpu_type in ['qualcomm', 'rogue'
] or self._gpu_type.startswith('Mali'):
self._skips.append('dEQP-GLES.*.functional.flush_finish.*')
# Add any board-specific expectations
self._skips += self.read_file(self._board + '-' + 'skips.txt')
self._skips += self.read_file(self._gpu_type + '-' + 'skips.txt')
self._fails += self.read_file(self._board + '-' + 'fails.txt')
self._fails += self.read_file(self._gpu_type + '-' + 'fails.txt')
self._flakes += self.read_file(self._board + '-' + 'flakes.txt')
self._flakes += self.read_file(self._gpu_type + '-' + 'flakes.txt')
# TODO: Add more fails lists for various boards
# TODO: Fix the Missing results from the runner on trogdor
# TODO: Add the other VK shards on trogdor
def add_filter_arg(self, command, list, arg, filename):
if not list:
return
path = os.path.join(self._log_path, filename)
with open(path, "w") as f:
for test in list:
f.write(test + '\n')
command.append(arg + '=' + path)
def run_once(self, opts=None):
options = dict(
api=None,
caselist=None,
filter='',
subset_to_run='Pass', # Pass, Fail, Timeout, NotPass...
shard_number='0',
shard_count='1',
debug='False',
perf_failure_description=None)
if opts is None:
opts = []
options.update(utils.args_to_dict(opts))
logging.info('Test Options: %s', options)
self._caselist = options['caselist']
self._shard_number = int(options['shard_number'])
self._shard_count = int(options['shard_count'])
self._debug = (options['debug'] == 'True')
api = options['api']
if not self._can_run(api):
logging.info('Skipping on %s due to lack of %s API support',
self._gpu_type, api)
return
if options['perf_failure_description']:
self._test_failure_description = options[
'perf_failure_description']
else:
# Do not report failure if failure description is not specified.
self._test_failure_report_enable = False
# Some information to help post-process logs.
logging.info('ChromeOS BOARD = %s', self._board)
logging.info('ChromeOS CPU family = %s', self._cpu_type)
logging.info('ChromeOS GPU family = %s', self._gpu_type)
self.setup_case_list_filters()
# Create a place to put detailed test output logs.
filter_name = self._filter or os.path.basename(self._caselist)
logging.info('dEQP test filter = %s', filter_name)
self._log_path = os.path.join(tempfile.gettempdir(),
'%s-logs' % filter_name)
shutil.rmtree(self._log_path, ignore_errors=True)
os.mkdir(self._log_path)
if self._debug:
# LogReader works on /var/log/messages by default.
self._log_reader = cros_logging.LogReader()
self._log_reader.set_start_by_current()
executable = self._get_executable(api)
# Must be in the executable directory when running for it to find it's
# test data files!
os.chdir(os.path.dirname(executable))
command = ['deqp-runner', 'run']
command.append('--output=%s' % self._log_path)
command.append('--deqp=%s' % executable)
command.append('--caselist=%s' % self._caselist)
if self._shard_number != 0:
command.append('--fraction-start=%d' % (self._shard_number + 1))
if self._shard_count != 1:
command.append('--fraction=%d' % self._shard_count)
self.add_filter_arg(command, self._flakes, '--flakes',
'known_flakes.txt')
self.add_filter_arg(command, self._skips, '--skips', 'skips.txt')
self.add_filter_arg(command, self._fails, '--baseline', 'fails.txt')
command.append('--')
command.append('--deqp-surface-type=%s' % self._surface)
command.append('--deqp-surface-width=%s' % self._width)
command.append('--deqp-surface-height=%s' % self._height)
command.append('--deqp-gl-config-name=rgba8888d24s8ms0')
# TODO: Get the deqp ebuild to include testlog-to-xml and the
# stylesheets, and have the runner invoke it for failures.
command = ' '.join(command)
# Must be in the executable directory when running for it to find it's
# test data files!
os.chdir(os.path.dirname(executable))
# Must initialize because some errors don't repopulate
# run_result, leaving old results.
run_result = {}
try:
run_result = utils.run(command,
env=self._env,
ignore_status=True,
stdout_tee=utils.TEE_TO_LOGS,
stdout_level=logging.INFO,
stderr_tee=utils.TEE_TO_LOGS)
except error.CmdError:
raise error.TestFail("Failed starting '%s'" % command)
# Update failing tests to the chrome perf dashboard records.
try:
with open(os.path.join(self._log_path,
'failures.csv')) as fails_file:
for line in fails_file.readlines():
self.add_failures(line)
except IOError:
# failures.csv not created if there were were no failures
pass
# TODO: We should have a chrome perf dashboard where we report the
# Flakes out of results.csv so we can preemptively manage the flakes
# list per board, to avoid causing run failures for known flakes when
# the automatic flake detection doesn't catch it.
if run_result.exit_status != 0:
raise error.TestFail('dEQP run failed with status code %d' %
run_result.exit_status)
# TODO: Figure out how to get autotest to save off our self._log_path
# self.write_perf_keyval(test_results) # XXX