diff --git a/dev-support/findHangingTests.py b/dev-support/findHangingTests.py
index 2daf2e3770d..54275df849f 100755
--- a/dev-support/findHangingTests.py
+++ b/dev-support/findHangingTests.py
@@ -15,14 +15,18 @@
# 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.
-##
-# script to find hanging test from Jenkins build output
-# usage: ./findHangingTests.py
-#
-import re
-import requests
-import sys
+# pylint: disable=invalid-name
+# To disable 'invalid constant name' warnings.
+
+"""
+# Script to find hanging test from Jenkins build output
+# usage: ./findHangingTests.py
+"""
+
+import re
+import sys
+import requests
# If any of these strings appear in the console output, it's a build one should probably ignore
# for analyzing failed/hanging tests.
@@ -31,62 +35,64 @@ BAD_RUN_STRINGS = [
"The forked VM terminated without properly saying goodbye", # JVM crashed.
]
-# Returns [[all tests], [failed tests], [timeout tests], [hanging tests]] if successfully gets
-# the build information.
-# If there is error getting console text or if there are blacklisted strings in console text,
-# then returns None.
-# Definitions:
-# All tests: All testcases which were run.
-# Hanging test: A testcase which started but never finished.
-# Failed test: Testcase which encountered any kind of failure. It can be failing atomic tests,
-# timed out tests, etc
-# Timeout test: A Testcase which encountered timeout. Naturally, all timeout tests will be
-# included in failed tests.
+
def get_bad_tests(console_url):
+ """
+ Returns [[all tests], [failed tests], [timeout tests], [hanging tests]] if successfully gets
+ the build information.
+ If there is error getting console text or if there are blacklisted strings in console text,
+ then returns None.
+ """
response = requests.get(console_url)
if response.status_code != 200:
print "Error getting consoleText. Response = {} {}".format(
response.status_code, response.reason)
return
- all_tests = set()
- hanging_tests = set()
- failed_tests = set()
- timeout_tests = set()
+ # All tests: All testcases which were run.
+ # Hanging test: A testcase which started but never finished.
+ # Failed test: Testcase which encountered any kind of failure. It can be failing atomic tests,
+ # timed out tests, etc
+ # Timeout test: A Testcase which encountered timeout. Naturally, all timeout tests will be
+ # included in failed tests.
+ all_tests_set = set()
+ hanging_tests_set = set()
+ failed_tests_set = set()
+ timeout_tests_set = set()
for line in response.content.splitlines():
- result1 = re.match("^Running org.apache.hadoop.hbase.(\w*\.)*(\w*)", line)
+ result1 = re.match("^Running org.apache.hadoop.hbase.(\\w*\\.)*(\\w*)", line)
if result1:
test_case = result1.group(2)
- if test_case in all_tests:
- print ("ERROR! Multiple tests with same name '{}'. Might get wrong results "
+ if test_case in all_tests_set:
+ print ("ERROR! Multiple tests with same name '{}'. Might get wrong results "
"for this test.".format(test_case))
else:
- hanging_tests.add(test_case)
- all_tests.add(test_case)
- result2 = re.match("^Tests run:.*- in org.apache.hadoop.hbase.(\w*\.)*(\w*)", line)
+ hanging_tests_set.add(test_case)
+ all_tests_set.add(test_case)
+ result2 = re.match("^Tests run:.*- in org.apache.hadoop.hbase.(\\w*\\.)*(\\w*)", line)
if result2:
test_case = result2.group(2)
if "FAILURE!" in line:
- failed_tests.add(test_case)
- if test_case not in hanging_tests:
+ failed_tests_set.add(test_case)
+ if test_case not in hanging_tests_set:
print ("ERROR! No test '{}' found in hanging_tests. Might get wrong results "
"for this test.".format(test_case))
else:
- hanging_tests.remove(test_case)
- result3 = re.match("^\s+(\w*).*\sTestTimedOut", line)
+ hanging_tests_set.remove(test_case)
+ result3 = re.match("^\\s+(\\w*).*\\sTestTimedOut", line)
if result3:
test_case = result3.group(1)
- timeout_tests.add(test_case)
+ timeout_tests_set.add(test_case)
for bad_string in BAD_RUN_STRINGS:
if re.match(".*" + bad_string + ".*", line):
print "Bad string found in build:\n > {}".format(line)
return
print "Result > total tests: {:4} failed : {:4} timedout : {:4} hanging : {:4}".format(
- len(all_tests), len(failed_tests), len(timeout_tests), len(hanging_tests))
- return [all_tests, failed_tests, timeout_tests, hanging_tests]
+ len(all_tests_set), len(failed_tests_set), len(timeout_tests_set), len(hanging_tests_set))
+ return [all_tests_set, failed_tests_set, timeout_tests_set, hanging_tests_set]
if __name__ == "__main__":
- if len(sys.argv) != 2 :
+ if len(sys.argv) != 2:
print "ERROR : Provide the jenkins job console URL as the only argument."
sys.exit(1)
diff --git a/dev-support/flaky-dashboard-template.html b/dev-support/flaky-dashboard-template.html
index 77dfc865c27..70febb48a5e 100644
--- a/dev-support/flaky-dashboard-template.html
+++ b/dev-support/flaky-dashboard-template.html
@@ -49,14 +49,35 @@
Apache HBase Flaky Tests Dashboard
+Last updated: {{datetime}}
+Count of flaky tests (cumulated from all jobs):
+ {{bad_tests_count}}
+List of Jobs
+
+{% set counter = 0 %}
+{% for url in results %}
+{% set counter = counter + 1 %}
+{{ url |e }}
+
+{% endfor %}
+
+
+Results
+
{% set counter = 0 %}
{% for url in results %}
{% set result = results[url] %}
{# Dedup ids since test names may duplicate across urls #}
{% set counter = counter + 1 %}
- Job : {{ url |e }}
- 🔗
+
+ {{ url |e }}
+
+ Go to
+
+
+ Go to top
+
diff --git a/dev-support/report-flakies.py b/dev-support/report-flakies.py
index 8c93b0b84cf..92b78cc185d 100755
--- a/dev-support/report-flakies.py
+++ b/dev-support/report-flakies.py
@@ -16,15 +16,24 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+# pylint: disable=invalid-name
+# To disable 'invalid constant name' warnings.
+# pylint: disable=import-error
+# Testing environment may not have all dependencies.
+
"""
This script uses Jenkins REST api to collect test result(s) of given build/builds and generates
flakyness data about unittests.
Print help: report-flakies.py -h
"""
+
import argparse
-from jinja2 import Template
import logging
import os
+import time
+from collections import OrderedDict
+from jinja2 import Template
+
import requests
import findHangingTests
@@ -115,7 +124,7 @@ all_failed_tests = set()
all_hanging_tests = set()
# Contains { : { : { 'all': [], 'failed': [],
# 'timeout': [], 'hanging': [] } } }
-url_to_bad_test_results = {}
+url_to_bad_test_results = OrderedDict()
# Iterates over each url, gets test results and prints flaky tests.
expanded_urls = expand_multi_config_projects(args)
@@ -160,33 +169,48 @@ for url_max_build in expanded_urls:
bad_tests.update(failed_tests.union(hanging_tests))
# For each bad test, get build ids where it ran, timed out, failed or hanged.
- test_to_build_ids = {key : {'all' : set(), 'timeout': set(), 'failed': set(), 'hanging' : set()}
+ test_to_build_ids = {key : {'all' : set(), 'timeout': set(), 'failed': set(),
+ 'hanging' : set(), 'bad_count' : 0}
for key in bad_tests}
for build in build_id_to_results:
[all_tests, failed_tests, timeout_tests, hanging_tests] = build_id_to_results[build]
for bad_test in test_to_build_ids:
+ is_bad = False
if all_tests.issuperset([bad_test]):
test_to_build_ids[bad_test]["all"].add(build)
if timeout_tests.issuperset([bad_test]):
test_to_build_ids[bad_test]['timeout'].add(build)
+ is_bad = True
if failed_tests.issuperset([bad_test]):
test_to_build_ids[bad_test]['failed'].add(build)
+ is_bad = True
if hanging_tests.issuperset([bad_test]):
test_to_build_ids[bad_test]['hanging'].add(build)
- url_to_bad_test_results[url] = test_to_build_ids
+ is_bad = True
+ if is_bad:
+ test_to_build_ids[bad_test]['bad_count'] += 1
- if len(test_to_build_ids) > 0:
+ # Calculate flakyness % for each test.
+ for bad_test in test_to_build_ids:
+ test_to_build_ids[bad_test]['flakyness'] = (
+ (test_to_build_ids[bad_test]['bad_count']) * 100.0 /
+ len(test_to_build_ids[bad_test]['all']))
+
+ # Sort tests in descending order by flakyness.
+ sorted_test_to_build_ids = OrderedDict(
+ sorted(test_to_build_ids.iteritems(), key=lambda x: x[1]['flakyness'], reverse=True))
+ url_to_bad_test_results[url] = sorted_test_to_build_ids
+
+ if len(sorted_test_to_build_ids) > 0:
print "URL: {}".format(url)
print "{:>60} {:10} {:25} {}".format(
"Test Name", "Total Runs", "Bad Runs(failed/timeout/hanging)", "Flakyness")
- for bad_test in test_to_build_ids:
- failed = len(test_to_build_ids[bad_test]['failed'])
- timeout = len(test_to_build_ids[bad_test]['timeout'])
- hanging = len(test_to_build_ids[bad_test]['hanging'])
- total = len(test_to_build_ids[bad_test]['all'])
+ for bad_test in sorted_test_to_build_ids:
+ test_status = sorted_test_to_build_ids[bad_test]
print "{:>60} {:10} {:7} ( {:4} / {:5} / {:5} ) {:2.0f}%".format(
- bad_test, total, failed + timeout, failed, timeout, hanging,
- (failed + timeout) * 100.0 / total)
+ bad_test, len(test_status['all']), test_status['bad_count'],
+ len(test_status['failed']), len(test_status['timeout']),
+ len(test_status['hanging']), test_status['flakyness'])
else:
print "No flaky tests founds."
if len(build_ids) == len(build_ids_without_tests_run):
@@ -218,4 +242,6 @@ with open(os.path.join(dev_support_dir, "flaky-dashboard-template.html"), "r") a
template = Template(f.read())
with open("dashboard.html", "w") as f:
- f.write(template.render(results=url_to_bad_test_results))
+ datetime = time.strftime("%m/%d/%Y %H:%M:%S")
+ f.write(template.render(datetime=datetime, bad_tests_count=len(all_bad_tests),
+ results=url_to_bad_test_results))