graphics_dEQP: add/update scripts.
These scripts are used to maintain dEQP expectations.
Keep them in the tree.
BUG=None.
TEST=None.
Change-Id: I3f76b501a729215ce96e226507b0123ce5444b25
Reviewed-on: https://chromium-review.googlesource.com/334435
Trybot-Ready: Ilja Friedel <ihf@chromium.org>
Tested-by: Ilja Friedel <ihf@chromium.org>
Reviewed-by: Stéphane Marchesin <marcheu@chromium.org>
diff --git a/client/site_tests/graphics_dEQP/diff.sh b/client/site_tests/graphics_dEQP/diff.sh
deleted file mode 100755
index 51bb471..0000000
--- a/client/site_tests/graphics_dEQP/diff.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/bash
-
-gpus=(baytrail broadwell haswell ivybridge sandybridge)
-
-for gpu in ${gpus[*]}
-do
- rm expectations/${gpu}/*.json
- cat expectations/${gpu}/* | sort > /tmp/${gpu}.sorted
- cat expectations/${gpu}/* | sort | uniq > /tmp/${gpu}.sorted_uniq
- diff /tmp/${gpu}.sorted /tmp/${gpu}.sorted_uniq > ${gpu}.diff
-done
-
diff --git a/client/site_tests/graphics_dEQP/scripts/failure_matrix.py b/client/site_tests/graphics_dEQP/scripts/failure_matrix.py
new file mode 100755
index 0000000..bf84335
--- /dev/null
+++ b/client/site_tests/graphics_dEQP/scripts/failure_matrix.py
@@ -0,0 +1,134 @@
+#!/usr/bin/python
+# Copyright 2016 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.
+
+# pylint: disable-msg=W0311
+
+import argparse
+import json
+import os
+
+
+gpu_list = [
+ #'pinetrail',
+ 'sandybridge',
+ 'ivybridge',
+ 'baytrail',
+ 'haswell',
+ 'broadwell',
+ 'braswell',
+ 'skylake',
+ 'mali-t604',
+ 'mali-t628',
+ 'mali-t760',
+ 'rogue',
+]
+
+_PROBLEM_STATUS = ['Fail', 'Flaky']
+
+status_dict = {
+ 'Fail': 'FAIL ',
+ 'Flaky': 'flaky',
+ 'Pass': ' + ',
+ 'NotSupported': ' --- ',
+ 'QualityWarning': 'qw ',
+ 'CompatibilityWarning': 'cw ',
+ 'Unknown': ' ??? ',
+ None: ' n/a ',
+}
+
+def load_expectation_dict(json_file):
+ data = {}
+ if os.path.isfile(json_file):
+ with open(json_file, 'r') as f:
+ text = f.read()
+ data = json.loads(text)
+ return data
+
+
+def load_expectations(json_file):
+ data = load_expectation_dict(json_file)
+ expectations = {}
+ # Convert from dictionary of lists to dictionary of sets.
+ for key in data:
+ expectations[key] = set(data[key])
+ return expectations
+
+
+def get_problem_count(dict, gpu):
+ count = 0
+ if gpu in dict:
+ for status in _PROBLEM_STATUS:
+ if status in dict[gpu]:
+ count = count + len((dict[gpu])[status])
+ else:
+ print 'Warning: %s not found in dict!' % gpu
+ return count
+
+
+def get_problem_tests(dict):
+ tests = set([])
+ for gpu in dict:
+ for status in _PROBLEM_STATUS:
+ if status in dict[gpu]:
+ tests = tests.union((dict[gpu])[status])
+ return sorted(list(tests))
+
+
+def get_test_result(dict, test):
+ for key in dict:
+ if test in dict[key]:
+ return key
+ return None
+
+
+argparser = argparse.ArgumentParser(
+ description='Create a matrix of failing tests per GPU.')
+argparser.add_argument('interface',
+ default='gles2',
+ help='Interface for matrix (gles2, gles3, gles31).')
+args = argparser.parse_args()
+status = '%s-master.json' % args.interface
+
+dict = {}
+for gpu in gpu_list:
+ filename = 'expectations/%s/%s' % (gpu, status)
+ dict[gpu] = load_expectations(filename)
+
+tests = get_problem_tests(dict)
+
+print 'Legend:'
+for key in status_dict:
+ print '%s --> %s' % (status_dict[key], key)
+print
+
+offset = ''
+for gpu in gpu_list:
+ print '%s%s' % (offset, gpu)
+ offset = '%s | ' % offset
+print offset
+
+text_count = ''
+text_del = ''
+for gpu in gpu_list:
+ text_count = '%s%5d ' % (text_count, get_problem_count(dict, gpu))
+ text_del = '%s=========' % text_del
+text_count = '%s Total failure count (Fail + Flaky)' % text_count
+print text_del
+print text_count
+print text_del
+
+for test in tests:
+ text = ''
+ for gpu in gpu_list:
+ result = get_test_result(dict[gpu], test)
+ status = status_dict[result]
+ text = '%s %s ' % (text, status)
+ text = '%s %s' % (text, test)
+ print text
+
+print text_del
+print '%s repeated' % text_count
+print text_del
+
diff --git a/client/site_tests/graphics_dEQP/process_logs.py b/client/site_tests/graphics_dEQP/scripts/process_logs.py
similarity index 68%
rename from client/site_tests/graphics_dEQP/process_logs.py
rename to client/site_tests/graphics_dEQP/scripts/process_logs.py
index e52b1d0..b4eab0a 100644
--- a/client/site_tests/graphics_dEQP/process_logs.py
+++ b/client/site_tests/graphics_dEQP/scripts/process_logs.py
@@ -15,7 +15,9 @@
import subprocess
_EXPECTATIONS_DIR = 'expectations'
-_AUTOTEST_RESULT_TEMPLATE = 'gs://chromeos-autotest-results/%s-chromeos-test/chromeos*/graphics_dEQP/debug/graphics_dEQP.DEBUG'
+_AUTOTEST_RESULT_ID_TEMPLATE = 'gs://chromeos-autotest-results/%s-chromeos-test/chromeos*/graphics_dEQP/debug/graphics_dEQP.DEBUG'
+#_AUTOTEST_RESULT_TAG_TEMPLATE = 'gs://chromeos-autotest-results/%s/graphics_dEQP/debug/graphics_dEQP.DEBUG'
+_AUTOTEST_RESULT_TAG_TEMPLATE = 'gs://chromeos-autotest-results/%s/debug/client.0.DEBUG'
# Use this template for tryjob results:
#_AUTOTEST_RESULT_TEMPLATE = 'gs://chromeos-autotest-results/%s-ihf/*/graphics_dEQP/debug/graphics_dEQP.DEBUG'
_BOARD_REGEX = re.compile(r'ChromeOS BOARD = (.+)')
@@ -30,7 +32,8 @@
re.MULTILINE)
_HASTY_TEST_RESULT_REGEX = re.compile(
r'\[stdout\] Test case \'(.+?)\'..$\n'
- r'.+?\[stdout\] (Pass|Fail|QualityWarning) \((.+)\)', re.MULTILINE)
+ r'.+?\[stdout\] (Pass|NotSupported|QualityWarning|CompatibilityWarning|'
+ r'Fail|ResourceError|Crash|Timeout|InternalError|Skipped) \((.+)\)', re.MULTILINE)
Logfile = namedtuple('Logfile', 'job_id name gs_path')
@@ -52,7 +55,7 @@
return board, gpu, filter, hasty
-def get_logs_from_gs(autotest_result_path):
+def copy_logs_from_gs_path(autotest_result_path):
logs = []
gs_paths = execute(['gsutil', 'ls', autotest_result_path]).splitlines()
for gs_path in gs_paths:
@@ -88,7 +91,8 @@
def get_not_passing_tests(text):
not_passing = []
for test, result in re.findall(_TEST_RESULT_REGEX, text):
- if not (result == 'Pass' or result == 'NotSupported'):
+ if not (result == 'Pass' or result == 'NotSupported' or result == 'Skipped' or
+ result == 'QualityWarning' or result == 'CompatibilityWarning'):
not_passing.append((test, result))
for test, result, details in re.findall(_HASTY_TEST_RESULT_REGEX, text):
if result != 'Pass':
@@ -99,7 +103,7 @@
def load_expectation_dict(json_file):
data = {}
if os.path.isfile(json_file):
- print('Loading file ' + json_file)
+ print 'Loading file ' + json_file
with open(json_file, 'r') as f:
text = f.read()
data = json.loads(text)
@@ -168,7 +172,7 @@
if key != 'Flaky':
not_flaky = list(status_dict[key] - flaky)
not_flaky.sort()
- print('Number of "%s" is %d.' % (key, len(not_flaky)))
+ print 'Number of "%s" is %d.' % (key, len(not_flaky))
clean_dict[key] = not_flaky
# And finally process flaky list/set.
@@ -209,23 +213,36 @@
'TestCase: ' in line or 'Result: ' in line or
'Test Options: ' in line or 'Running in hasty mode.' in line or
# For hasty logs we have:
- ' Pass (' in line or ' Fail (' in line or 'QualityWarning (' in line or
+ 'Pass (' in line or 'NotSupported (' in line or 'Skipped (' in line or
+ 'QualityWarning (' in line or 'CompatibilityWarning (' in line or
+ 'Fail (' in line or 'ResourceError (' in line or 'Crash (' in line or
+ 'Timeout (' in line or 'InternalError (' in line or
' Test case \'' in line):
text += line + '\n'
# TODO(ihf): Warn about or reject log files missing the end marker.
return text
+def all_passing(tests):
+ for _, result in tests:
+ if not (result == 'Pass'):
+ return False
+ return True
+
+
def process_logs(logs):
for log in logs:
text = load_log(log.name)
if text:
- print('================================================================')
- print('Loading %s...' % log.name)
+ print '================================================================'
+ print 'Loading %s...' % log.name
_, gpu, filter, hasty = get_metadata(text)
tests = get_all_tests(text)
- print('Found %d test results.' % len(tests))
- if tests:
+ print 'Found %d test results.' % len(tests)
+ if all_passing(tests):
+ # Delete logs that don't contain failures.
+ os.remove(log.name)
+ else:
# GPU family goes first in path to simplify adding/deleting families.
output_path = os.path.join(_EXPECTATIONS_DIR, gpu)
if not os.access(output_path, os.R_OK):
@@ -236,22 +253,77 @@
merge_expectation_list(expectation_path, tests)
+JOB_TAGS_ALL = (
+'select distinct job_tag from chromeos_autotest_db.tko_test_view_2 '
+'where not job_tag like "%%hostless" and '
+'test_name="graphics_dEQP" and '
+'build_version>="%s" and '
+'build_version<="%s" and '
+'((status = "FAIL" and not job_name like "%%.NotPass") or '
+'job_name like "%%.functional" or '
+'job_name like "%%-master")' )
+
+JOB_TAGS_MASTER = (
+'select distinct job_tag from chromeos_autotest_db.tko_test_view_2 '
+'where not job_tag like "%%hostless" and '
+'test_name="graphics_dEQP" and '
+'build_version>="%s" and '
+'build_version<="%s" and '
+'job_name like "%%-master"' )
+
+def get_result_paths_from_autotest_db(host, user, password, build_from,
+ build_to):
+ paths = []
+ # TODO(ihf): Introduce flag to toggle between JOB_TAGS_ALL and _MASTER.
+ sql = JOB_TAGS_ALL % (build_from, build_to)
+ cmd = ['mysql', '-u%s' % user, '-p%s' % password, '--host', host, '-e', sql]
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
+ for line in p.communicate()[0].splitlines():
+ # Skip over unrelated sql spew (really first line only):
+ if line and 'chromeos-test' in line:
+ paths.append(_AUTOTEST_RESULT_TAG_TEMPLATE % line.rstrip())
+ print 'Found %d potential results in the database.' % len(paths)
+ return paths
+
+
+def copy_logs_from_gs_paths(paths):
+ i = 1
+ for gs_path in paths:
+ print '[%d/%d] %s' % (i, len(paths), gs_path)
+ copy_logs_from_gs_path(gs_path)
+ i = i+1
+
+
argparser = argparse.ArgumentParser(
description='Download from GS and process dEQP logs into expectations.')
argparser.add_argument(
- 'result_ids',
- metavar='result_id',
- nargs='*', # Zero or more result_ids specified.
- help='List of result log IDs (wildcards for gsutil like 5678* are ok).')
+ '--host',
+ dest='host',
+ default='chromeos-server38.cbf',
+ help='Host containing autotest result DB.')
+argparser.add_argument('--user', dest='user', help='Database user account.')
+argparser.add_argument(
+ '--password',
+ dest='password',
+ help='Password for user account.')
+argparser.add_argument(
+ '--from',
+ dest='build_from',
+ help='Lowest build revision to include. Example: R51-8100.0.0')
+argparser.add_argument(
+ '--to',
+ dest='build_to',
+ help='Highest build revision to include. Example: R51-8101.0.0')
+
args = argparser.parse_args()
print pprint.pformat(args)
# This is somewhat optional. Remove existing expectations to start clean, but
# feel free to process them incrementally.
execute(['rm', '-rf', _EXPECTATIONS_DIR])
-for id in args.result_ids:
- gs_path = _AUTOTEST_RESULT_TEMPLATE % id
- logs = get_logs_from_gs(gs_path)
+
+copy_logs_from_gs_paths(get_result_paths_from_autotest_db(
+ args.host, args.user, args.password, args.build_from, args.build_to))
# This will include the just downloaded logs from GS as well.
logs = get_local_logs()