mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 21:31:04 +00:00
Backed out changeset 24db9230985e (bug 1692453) for lint failure. CLOSED TREE
This commit is contained in:
parent
661a5060e8
commit
d8ef0fbfe6
@ -141,5 +141,5 @@ test-info-all:
|
|||||||
cwd: '{checkout}'
|
cwd: '{checkout}'
|
||||||
command: >-
|
command: >-
|
||||||
source taskcluster/scripts/misc/source-test-common.sh &&
|
source taskcluster/scripts/misc/source-test-common.sh &&
|
||||||
./mach test-info report --show-tests --show-summary --verbose --output-file /builds/worker/artifacts/test-info-all-tests.json &&
|
./mach test-info report --show-tests --show-summary --show-activedata --verbose --output-file /builds/worker/artifacts/test-info-all-tests.json &&
|
||||||
./mach test-info report --show-annotations --output-file /builds/worker/artifacts/test-info-manifest-conditions.json
|
./mach test-info report --show-annotations --output-file /builds/worker/artifacts/test-info-manifest-conditions.json
|
||||||
|
@ -774,6 +774,11 @@ class TestInfoCommand(MachCommandBase):
|
|||||||
@CommandArgument(
|
@CommandArgument(
|
||||||
"test_names", nargs=argparse.REMAINDER, help="Test(s) of interest."
|
"test_names", nargs=argparse.REMAINDER, help="Test(s) of interest."
|
||||||
)
|
)
|
||||||
|
@CommandArgument(
|
||||||
|
"--branches",
|
||||||
|
default="mozilla-central,autoland",
|
||||||
|
help="Report for named branches " "(default: mozilla-central,autoland)",
|
||||||
|
)
|
||||||
@CommandArgument(
|
@CommandArgument(
|
||||||
"--start",
|
"--start",
|
||||||
default=(date.today() - timedelta(7)).strftime("%Y-%m-%d"),
|
default=(date.today() - timedelta(7)).strftime("%Y-%m-%d"),
|
||||||
@ -787,6 +792,21 @@ class TestInfoCommand(MachCommandBase):
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help="Retrieve and display general test information.",
|
help="Retrieve and display general test information.",
|
||||||
)
|
)
|
||||||
|
@CommandArgument(
|
||||||
|
"--show-results",
|
||||||
|
action="store_true",
|
||||||
|
help="Retrieve and display ActiveData test result summary.",
|
||||||
|
)
|
||||||
|
@CommandArgument(
|
||||||
|
"--show-durations",
|
||||||
|
action="store_true",
|
||||||
|
help="Retrieve and display ActiveData test duration summary.",
|
||||||
|
)
|
||||||
|
@CommandArgument(
|
||||||
|
"--show-tasks",
|
||||||
|
action="store_true",
|
||||||
|
help="Retrieve and display ActiveData test task names.",
|
||||||
|
)
|
||||||
@CommandArgument(
|
@CommandArgument(
|
||||||
"--show-bugs",
|
"--show-bugs",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@ -796,9 +816,13 @@ class TestInfoCommand(MachCommandBase):
|
|||||||
def test_info_tests(
|
def test_info_tests(
|
||||||
self,
|
self,
|
||||||
test_names,
|
test_names,
|
||||||
|
branches,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
show_info,
|
show_info,
|
||||||
|
show_results,
|
||||||
|
show_durations,
|
||||||
|
show_tasks,
|
||||||
show_bugs,
|
show_bugs,
|
||||||
verbose,
|
verbose,
|
||||||
):
|
):
|
||||||
@ -807,12 +831,56 @@ class TestInfoCommand(MachCommandBase):
|
|||||||
ti = testinfo.TestInfoTests(verbose)
|
ti = testinfo.TestInfoTests(verbose)
|
||||||
ti.report(
|
ti.report(
|
||||||
test_names,
|
test_names,
|
||||||
|
branches,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
show_info,
|
show_info,
|
||||||
|
show_results,
|
||||||
|
show_durations,
|
||||||
|
show_tasks,
|
||||||
show_bugs,
|
show_bugs,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@SubCommand(
|
||||||
|
"test-info",
|
||||||
|
"long-tasks",
|
||||||
|
description="Find tasks approaching their taskcluster max-run-time.",
|
||||||
|
)
|
||||||
|
@CommandArgument(
|
||||||
|
"--branches",
|
||||||
|
default="mozilla-central,autoland",
|
||||||
|
help="Report for named branches " "(default: mozilla-central,autoland)",
|
||||||
|
)
|
||||||
|
@CommandArgument(
|
||||||
|
"--start",
|
||||||
|
default=(date.today() - timedelta(7)).strftime("%Y-%m-%d"),
|
||||||
|
help="Start date (YYYY-MM-DD)",
|
||||||
|
)
|
||||||
|
@CommandArgument(
|
||||||
|
"--end", default=date.today().strftime("%Y-%m-%d"), help="End date (YYYY-MM-DD)"
|
||||||
|
)
|
||||||
|
@CommandArgument(
|
||||||
|
"--max-threshold-pct",
|
||||||
|
default=90.0,
|
||||||
|
help="Count tasks exceeding this percentage of max-run-time.",
|
||||||
|
)
|
||||||
|
@CommandArgument(
|
||||||
|
"--filter-threshold-pct",
|
||||||
|
default=0.5,
|
||||||
|
help="Report tasks exceeding this percentage of long tasks.",
|
||||||
|
)
|
||||||
|
@CommandArgument("--verbose", action="store_true", help="Enable debug logging.")
|
||||||
|
def report_long_running_tasks(
|
||||||
|
self, branches, start, end, max_threshold_pct, filter_threshold_pct, verbose
|
||||||
|
):
|
||||||
|
import testinfo
|
||||||
|
|
||||||
|
max_threshold_pct = float(max_threshold_pct)
|
||||||
|
filter_threshold_pct = float(filter_threshold_pct)
|
||||||
|
|
||||||
|
ti = testinfo.TestInfoLongRunningTasks(verbose)
|
||||||
|
ti.report(branches, start, end, max_threshold_pct, filter_threshold_pct)
|
||||||
|
|
||||||
@SubCommand(
|
@SubCommand(
|
||||||
"test-info",
|
"test-info",
|
||||||
"report",
|
"report",
|
||||||
@ -853,6 +921,11 @@ class TestInfoCommand(MachCommandBase):
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help="Include list of manifest annotation conditions in report.",
|
help="Include list of manifest annotation conditions in report.",
|
||||||
)
|
)
|
||||||
|
@CommandArgument(
|
||||||
|
"--show-activedata",
|
||||||
|
action="store_true",
|
||||||
|
help="Include additional data from ActiveData, like run times and counts.",
|
||||||
|
)
|
||||||
@CommandArgument(
|
@CommandArgument(
|
||||||
"--filter-values",
|
"--filter-values",
|
||||||
help="Comma-separated list of value regular expressions to filter on; "
|
help="Comma-separated list of value regular expressions to filter on; "
|
||||||
@ -872,6 +945,18 @@ class TestInfoCommand(MachCommandBase):
|
|||||||
help="Do not categorize by bugzilla component.",
|
help="Do not categorize by bugzilla component.",
|
||||||
)
|
)
|
||||||
@CommandArgument("--output-file", help="Path to report file.")
|
@CommandArgument("--output-file", help="Path to report file.")
|
||||||
|
@CommandArgument(
|
||||||
|
"--branches",
|
||||||
|
default="mozilla-central,autoland",
|
||||||
|
help="Query ActiveData for named branches "
|
||||||
|
"(default: mozilla-central,autoland)",
|
||||||
|
)
|
||||||
|
@CommandArgument(
|
||||||
|
"--days",
|
||||||
|
type=int,
|
||||||
|
default=7,
|
||||||
|
help="Query ActiveData for specified number of days",
|
||||||
|
)
|
||||||
@CommandArgument("--verbose", action="store_true", help="Enable debug logging.")
|
@CommandArgument("--verbose", action="store_true", help="Enable debug logging.")
|
||||||
def test_report(
|
def test_report(
|
||||||
self,
|
self,
|
||||||
@ -883,10 +968,13 @@ class TestInfoCommand(MachCommandBase):
|
|||||||
show_tests,
|
show_tests,
|
||||||
show_summary,
|
show_summary,
|
||||||
show_annotations,
|
show_annotations,
|
||||||
|
show_activedata,
|
||||||
filter_values,
|
filter_values,
|
||||||
filter_keys,
|
filter_keys,
|
||||||
show_components,
|
show_components,
|
||||||
output_file,
|
output_file,
|
||||||
|
branches,
|
||||||
|
days,
|
||||||
verbose,
|
verbose,
|
||||||
):
|
):
|
||||||
import testinfo
|
import testinfo
|
||||||
@ -909,10 +997,13 @@ class TestInfoCommand(MachCommandBase):
|
|||||||
show_tests,
|
show_tests,
|
||||||
show_summary,
|
show_summary,
|
||||||
show_annotations,
|
show_annotations,
|
||||||
|
show_activedata,
|
||||||
filter_values,
|
filter_values,
|
||||||
filter_keys,
|
filter_keys,
|
||||||
show_components,
|
show_components,
|
||||||
output_file,
|
output_file,
|
||||||
|
branches,
|
||||||
|
days,
|
||||||
)
|
)
|
||||||
|
|
||||||
@SubCommand(
|
@SubCommand(
|
||||||
|
@ -13,12 +13,17 @@ import re
|
|||||||
import requests
|
import requests
|
||||||
import six.moves.urllib_parse as urlparse
|
import six.moves.urllib_parse as urlparse
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import threading
|
||||||
|
import traceback
|
||||||
import mozpack.path as mozpath
|
import mozpack.path as mozpath
|
||||||
from moztest.resolve import TestResolver, TestManifestLoader
|
from moztest.resolve import TestResolver, TestManifestLoader
|
||||||
from mozfile import which
|
from mozfile import which
|
||||||
|
|
||||||
from mozbuild.base import MozbuildObject, MachCommandConditions as conditions
|
from mozbuild.base import MozbuildObject, MachCommandConditions as conditions
|
||||||
|
|
||||||
|
ACTIVEDATA_RECORD_LIMIT = 10000
|
||||||
|
MAX_ACTIVEDATA_CONCURRENCY = 5
|
||||||
|
MAX_ACTIVEDATA_RETRIES = 5
|
||||||
REFERER = "https://wiki.developer.mozilla.org/en-US/docs/Mozilla/Test-Info"
|
REFERER = "https://wiki.developer.mozilla.org/en-US/docs/Mozilla/Test-Info"
|
||||||
|
|
||||||
|
|
||||||
@ -31,11 +36,60 @@ class TestInfo(object):
|
|||||||
self.verbose = verbose
|
self.verbose = verbose
|
||||||
here = os.path.abspath(os.path.dirname(__file__))
|
here = os.path.abspath(os.path.dirname(__file__))
|
||||||
self.build_obj = MozbuildObject.from_environment(cwd=here)
|
self.build_obj = MozbuildObject.from_environment(cwd=here)
|
||||||
|
self.total_activedata_seconds = 0
|
||||||
|
|
||||||
def log_verbose(self, what):
|
def log_verbose(self, what):
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
print(what)
|
print(what)
|
||||||
|
|
||||||
|
def activedata_query(self, query):
|
||||||
|
start_time = datetime.datetime.now()
|
||||||
|
self.log_verbose(start_time)
|
||||||
|
self.log_verbose(json.dumps(query))
|
||||||
|
response = requests.post(
|
||||||
|
"http://activedata.allizom.org/query",
|
||||||
|
data=json.dumps(query),
|
||||||
|
headers={"referer": REFERER},
|
||||||
|
stream=True,
|
||||||
|
)
|
||||||
|
end_time = datetime.datetime.now()
|
||||||
|
self.total_activedata_seconds += (end_time - start_time).total_seconds()
|
||||||
|
self.log_verbose(end_time)
|
||||||
|
self.log_verbose(response)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()["data"]
|
||||||
|
self.log_verbose("response length: %d" % len(data))
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class ActiveDataThread(threading.Thread):
|
||||||
|
"""
|
||||||
|
A thread to query ActiveData and wait for its response.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, name, ti, query, context):
|
||||||
|
threading.Thread.__init__(self, name=name)
|
||||||
|
self.ti = ti
|
||||||
|
self.query = query
|
||||||
|
self.context = context
|
||||||
|
self.response = None
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
attempt = 1
|
||||||
|
while attempt < MAX_ACTIVEDATA_RETRIES and not self.response:
|
||||||
|
try:
|
||||||
|
self.response = self.ti.activedata_query(self.query)
|
||||||
|
if not self.response:
|
||||||
|
self.ti.log_verbose("%s: no data received for query" % self.name)
|
||||||
|
self.response = []
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
self.ti.log_verbose(
|
||||||
|
"%s: Exception on attempt #%d:" % (self.name, attempt)
|
||||||
|
)
|
||||||
|
traceback.print_exc()
|
||||||
|
attempt += 1
|
||||||
|
|
||||||
|
|
||||||
class TestInfoTests(TestInfo):
|
class TestInfoTests(TestInfo):
|
||||||
"""
|
"""
|
||||||
@ -78,6 +132,9 @@ class TestInfoTests(TestInfo):
|
|||||||
# a short file name, rather than the full path;
|
# a short file name, rather than the full path;
|
||||||
# - Bugs may be filed in bugzilla against a simple, short test
|
# - Bugs may be filed in bugzilla against a simple, short test
|
||||||
# name or the full path to the test;
|
# name or the full path to the test;
|
||||||
|
# - In ActiveData, the full path is usually used, but sometimes
|
||||||
|
# also includes additional path components outside of the
|
||||||
|
# mercurial repo (common for reftests).
|
||||||
# This function attempts to find appropriate names for different
|
# This function attempts to find appropriate names for different
|
||||||
# queries based on the specified test name.
|
# queries based on the specified test name.
|
||||||
|
|
||||||
@ -134,6 +191,57 @@ class TestInfoTests(TestInfo):
|
|||||||
if self.short_name and self.short_name == self.test_name:
|
if self.short_name and self.short_name == self.test_name:
|
||||||
self.short_name = None
|
self.short_name = None
|
||||||
|
|
||||||
|
if not (self.show_results or self.show_durations or self.show_tasks):
|
||||||
|
# no need to determine ActiveData name if not querying
|
||||||
|
return
|
||||||
|
|
||||||
|
def set_activedata_test_name(self):
|
||||||
|
# activedata_test_name is name in ActiveData
|
||||||
|
self.activedata_test_name = None
|
||||||
|
simple_names = [self.full_test_name, self.test_name, self.short_name]
|
||||||
|
simple_names = [x for x in simple_names if x]
|
||||||
|
searches = [
|
||||||
|
{"in": {"result.test": simple_names}},
|
||||||
|
]
|
||||||
|
regex_names = [".*%s.*" % re.escape(x) for x in simple_names if x]
|
||||||
|
for r in regex_names:
|
||||||
|
searches.append({"regexp": {"result.test": r}})
|
||||||
|
query = {
|
||||||
|
"from": "unittest",
|
||||||
|
"format": "list",
|
||||||
|
"limit": 10,
|
||||||
|
"groupby": ["result.test"],
|
||||||
|
"where": {
|
||||||
|
"and": [
|
||||||
|
{"or": searches},
|
||||||
|
{"in": {"build.branch": self.branches.split(",")}},
|
||||||
|
{"gt": {"run.timestamp": {"date": self.start}}},
|
||||||
|
{"lt": {"run.timestamp": {"date": self.end}}},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
print("Querying ActiveData...") # Following query can take a long time
|
||||||
|
data = self.activedata_query(query)
|
||||||
|
if data and len(data) > 0:
|
||||||
|
self.activedata_test_name = [
|
||||||
|
d["result"]["test"]
|
||||||
|
for p in simple_names + regex_names
|
||||||
|
for d in data
|
||||||
|
if re.match(p + "$", d["result"]["test"])
|
||||||
|
][
|
||||||
|
0
|
||||||
|
] # first match is best match
|
||||||
|
if self.activedata_test_name:
|
||||||
|
print(
|
||||||
|
"Found records matching '%s' in ActiveData." % self.activedata_test_name
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"Unable to find matching records in ActiveData; using %s!"
|
||||||
|
% self.test_name
|
||||||
|
)
|
||||||
|
self.activedata_test_name = self.test_name
|
||||||
|
|
||||||
def get_platform(self, record):
|
def get_platform(self, record):
|
||||||
if "platform" in record["build"]:
|
if "platform" in record["build"]:
|
||||||
platform = record["build"]["platform"]
|
platform = record["build"]["platform"]
|
||||||
@ -171,6 +279,175 @@ class TestInfoTests(TestInfo):
|
|||||||
types_label += run_type
|
types_label += run_type
|
||||||
return "%s/%s:" % (platform, types_label)
|
return "%s/%s:" % (platform, types_label)
|
||||||
|
|
||||||
|
def report_test_results(self):
|
||||||
|
# Report test pass/fail summary from ActiveData
|
||||||
|
query = {
|
||||||
|
"from": "unittest",
|
||||||
|
"format": "list",
|
||||||
|
"limit": 100,
|
||||||
|
"groupby": ["build.platform", "build.type"],
|
||||||
|
"select": [
|
||||||
|
{"aggregate": "count"},
|
||||||
|
{
|
||||||
|
"name": "failures",
|
||||||
|
"value": {
|
||||||
|
"case": [{"when": {"eq": {"result.ok": "F"}}, "then": 1}]
|
||||||
|
},
|
||||||
|
"aggregate": "sum",
|
||||||
|
"default": 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "skips",
|
||||||
|
"value": {
|
||||||
|
"case": [{"when": {"eq": {"result.status": "SKIP"}}, "then": 1}]
|
||||||
|
},
|
||||||
|
"aggregate": "sum",
|
||||||
|
"default": 0,
|
||||||
|
},
|
||||||
|
{"value": "run.type", "aggregate": "union"},
|
||||||
|
],
|
||||||
|
"where": {
|
||||||
|
"and": [
|
||||||
|
{"eq": {"result.test": self.activedata_test_name}},
|
||||||
|
{"in": {"build.branch": self.branches.split(",")}},
|
||||||
|
{"gt": {"run.timestamp": {"date": self.start}}},
|
||||||
|
{"lt": {"run.timestamp": {"date": self.end}}},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
print(
|
||||||
|
"\nTest results for %s on %s between %s and %s"
|
||||||
|
% (self.activedata_test_name, self.branches, self.start, self.end)
|
||||||
|
)
|
||||||
|
data = self.activedata_query(query)
|
||||||
|
if data and len(data) > 0:
|
||||||
|
data.sort(key=self.get_platform)
|
||||||
|
worst_rate = 0.0
|
||||||
|
worst_platform = None
|
||||||
|
total_runs = 0
|
||||||
|
total_failures = 0
|
||||||
|
for record in data:
|
||||||
|
platform = self.get_platform(record)
|
||||||
|
if platform.startswith("-"):
|
||||||
|
continue
|
||||||
|
runs = record["count"]
|
||||||
|
total_runs = total_runs + runs
|
||||||
|
failures = record.get("failures", 0)
|
||||||
|
skips = record.get("skips", 0)
|
||||||
|
total_failures = total_failures + failures
|
||||||
|
rate = (float)(failures) / runs
|
||||||
|
if rate >= worst_rate:
|
||||||
|
worst_rate = rate
|
||||||
|
worst_platform = platform
|
||||||
|
worst_failures = failures
|
||||||
|
worst_runs = runs
|
||||||
|
print(
|
||||||
|
"%-40s %6d failures (%6d skipped) in %6d runs"
|
||||||
|
% (platform, failures, skips, runs)
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"\nTotal: %d failures in %d runs or %.3f failures/run"
|
||||||
|
% (total_failures, total_runs, (float)(total_failures) / total_runs)
|
||||||
|
)
|
||||||
|
if worst_failures > 0:
|
||||||
|
print(
|
||||||
|
"Worst rate on %s %d failures in %d runs or %.3f failures/run"
|
||||||
|
% (worst_platform, worst_failures, worst_runs, worst_rate)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print("No test result data found.")
|
||||||
|
|
||||||
|
def report_test_durations(self):
|
||||||
|
# Report test durations summary from ActiveData
|
||||||
|
query = {
|
||||||
|
"from": "unittest",
|
||||||
|
"format": "list",
|
||||||
|
"limit": 100,
|
||||||
|
"groupby": ["build.platform", "build.type"],
|
||||||
|
"select": [
|
||||||
|
{"value": "result.duration", "aggregate": "average", "name": "average"},
|
||||||
|
{"value": "result.duration", "aggregate": "min", "name": "min"},
|
||||||
|
{"value": "result.duration", "aggregate": "max", "name": "max"},
|
||||||
|
{"aggregate": "count"},
|
||||||
|
{"value": "run.type", "aggregate": "union"},
|
||||||
|
],
|
||||||
|
"where": {
|
||||||
|
"and": [
|
||||||
|
{"eq": {"result.ok": "T"}},
|
||||||
|
{"eq": {"result.test": self.activedata_test_name}},
|
||||||
|
{"in": {"build.branch": self.branches.split(",")}},
|
||||||
|
{"gt": {"run.timestamp": {"date": self.start}}},
|
||||||
|
{"lt": {"run.timestamp": {"date": self.end}}},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
data = self.activedata_query(query)
|
||||||
|
print(
|
||||||
|
"\nTest durations for %s on %s between %s and %s"
|
||||||
|
% (self.activedata_test_name, self.branches, self.start, self.end)
|
||||||
|
)
|
||||||
|
if data and len(data) > 0:
|
||||||
|
data.sort(key=self.get_platform)
|
||||||
|
for record in data:
|
||||||
|
platform = self.get_platform(record)
|
||||||
|
if platform.startswith("-"):
|
||||||
|
continue
|
||||||
|
print(
|
||||||
|
"%-40s %6.2f s (%.2f s - %.2f s over %d runs)"
|
||||||
|
% (
|
||||||
|
platform,
|
||||||
|
record["average"],
|
||||||
|
record["min"],
|
||||||
|
record["max"],
|
||||||
|
record["count"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print("No test durations found.")
|
||||||
|
|
||||||
|
def report_test_tasks(self):
|
||||||
|
# Report test tasks summary from ActiveData
|
||||||
|
query = {
|
||||||
|
"from": "unittest",
|
||||||
|
"format": "list",
|
||||||
|
"limit": 1000,
|
||||||
|
"select": ["build.platform", "build.type", "run.type", "run.name"],
|
||||||
|
"where": {
|
||||||
|
"and": [
|
||||||
|
{"eq": {"result.test": self.activedata_test_name}},
|
||||||
|
{"in": {"build.branch": self.branches.split(",")}},
|
||||||
|
{"gt": {"run.timestamp": {"date": self.start}}},
|
||||||
|
{"lt": {"run.timestamp": {"date": self.end}}},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
data = self.activedata_query(query)
|
||||||
|
print(
|
||||||
|
"\nTest tasks for %s on %s between %s and %s"
|
||||||
|
% (self.activedata_test_name, self.branches, self.start, self.end)
|
||||||
|
)
|
||||||
|
if data and len(data) > 0:
|
||||||
|
data.sort(key=self.get_platform)
|
||||||
|
consolidated = {}
|
||||||
|
for record in data:
|
||||||
|
platform = self.get_platform(record)
|
||||||
|
if platform not in consolidated:
|
||||||
|
consolidated[platform] = {}
|
||||||
|
if record["run"]["name"] in consolidated[platform]:
|
||||||
|
consolidated[platform][record["run"]["name"]] += 1
|
||||||
|
else:
|
||||||
|
consolidated[platform][record["run"]["name"]] = 1
|
||||||
|
for key in sorted(consolidated.keys()):
|
||||||
|
tasks = ""
|
||||||
|
for task in consolidated[key].keys():
|
||||||
|
if tasks:
|
||||||
|
tasks += "\n%-40s " % ""
|
||||||
|
tasks += task
|
||||||
|
tasks += " in %d runs" % consolidated[key][task]
|
||||||
|
print("%-40s %s" % (key, tasks))
|
||||||
|
else:
|
||||||
|
print("No test tasks found.")
|
||||||
|
|
||||||
def report_bugs(self):
|
def report_bugs(self):
|
||||||
# Report open bugs matching test name
|
# Report open bugs matching test name
|
||||||
search = self.full_test_name
|
search = self.full_test_name
|
||||||
@ -192,18 +469,35 @@ class TestInfoTests(TestInfo):
|
|||||||
def report(
|
def report(
|
||||||
self,
|
self,
|
||||||
test_names,
|
test_names,
|
||||||
|
branches,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
show_info,
|
show_info,
|
||||||
|
show_results,
|
||||||
|
show_durations,
|
||||||
|
show_tasks,
|
||||||
show_bugs,
|
show_bugs,
|
||||||
):
|
):
|
||||||
|
self.branches = branches
|
||||||
self.start = start
|
self.start = start
|
||||||
self.end = end
|
self.end = end
|
||||||
self.show_info = show_info
|
self.show_info = show_info
|
||||||
|
self.show_results = show_results
|
||||||
|
self.show_durations = show_durations
|
||||||
|
self.show_tasks = show_tasks
|
||||||
|
|
||||||
if not self.show_info and not show_bugs:
|
if (
|
||||||
|
not self.show_info
|
||||||
|
and not self.show_results
|
||||||
|
and not self.show_durations
|
||||||
|
and not self.show_tasks
|
||||||
|
and not show_bugs
|
||||||
|
):
|
||||||
# by default, show everything
|
# by default, show everything
|
||||||
self.show_info = True
|
self.show_info = True
|
||||||
|
self.show_results = True
|
||||||
|
self.show_durations = True
|
||||||
|
self.show_tasks = True
|
||||||
show_bugs = True
|
show_bugs = True
|
||||||
|
|
||||||
for test_name in test_names:
|
for test_name in test_names:
|
||||||
@ -215,6 +509,101 @@ class TestInfoTests(TestInfo):
|
|||||||
self.set_test_name()
|
self.set_test_name()
|
||||||
if show_bugs:
|
if show_bugs:
|
||||||
self.report_bugs()
|
self.report_bugs()
|
||||||
|
self.set_activedata_test_name()
|
||||||
|
if self.show_results:
|
||||||
|
self.report_test_results()
|
||||||
|
if self.show_durations:
|
||||||
|
self.report_test_durations()
|
||||||
|
if self.show_tasks:
|
||||||
|
self.report_test_tasks()
|
||||||
|
|
||||||
|
|
||||||
|
class TestInfoLongRunningTasks(TestInfo):
|
||||||
|
"""
|
||||||
|
Support 'mach test-info long-tasks': Summary of tasks approaching their max-run-time.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, verbose):
|
||||||
|
TestInfo.__init__(self, verbose)
|
||||||
|
|
||||||
|
def report(self, branches, start, end, threshold_pct, filter_threshold_pct):
|
||||||
|
def get_long_running_ratio(record):
|
||||||
|
count = record["count"]
|
||||||
|
tasks_gt_pct = record["tasks_gt_pct"]
|
||||||
|
# pylint --py3k W1619
|
||||||
|
return count / tasks_gt_pct
|
||||||
|
|
||||||
|
# Search test durations in ActiveData for long-running tests
|
||||||
|
query = {
|
||||||
|
"from": "task",
|
||||||
|
"format": "list",
|
||||||
|
"groupby": ["run.name"],
|
||||||
|
"limit": 1000,
|
||||||
|
"select": [
|
||||||
|
{
|
||||||
|
"value": "task.maxRunTime",
|
||||||
|
"aggregate": "median",
|
||||||
|
"name": "max_run_time",
|
||||||
|
},
|
||||||
|
{"aggregate": "count"},
|
||||||
|
{
|
||||||
|
"value": {
|
||||||
|
"when": {
|
||||||
|
"gt": [
|
||||||
|
{"div": ["action.duration", "task.maxRunTime"]},
|
||||||
|
threshold_pct / 100.0,
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"then": 1,
|
||||||
|
},
|
||||||
|
"aggregate": "sum",
|
||||||
|
"name": "tasks_gt_pct",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"where": {
|
||||||
|
"and": [
|
||||||
|
{"in": {"build.branch": branches.split(",")}},
|
||||||
|
{"gt": {"task.run.start_time": {"date": start}}},
|
||||||
|
{"lte": {"task.run.start_time": {"date": end}}},
|
||||||
|
{"eq": {"task.state": "completed"}},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
data = self.activedata_query(query)
|
||||||
|
print(
|
||||||
|
"\nTasks nearing their max-run-time on %s between %s and %s"
|
||||||
|
% (branches, start, end)
|
||||||
|
)
|
||||||
|
if data and len(data) > 0:
|
||||||
|
filtered = []
|
||||||
|
for record in data:
|
||||||
|
if "tasks_gt_pct" in record:
|
||||||
|
count = record["count"]
|
||||||
|
tasks_gt_pct = record["tasks_gt_pct"]
|
||||||
|
if float(tasks_gt_pct) / count > filter_threshold_pct / 100.0:
|
||||||
|
filtered.append(record)
|
||||||
|
filtered.sort(key=get_long_running_ratio)
|
||||||
|
if not filtered:
|
||||||
|
print("No long running tasks found.")
|
||||||
|
for record in filtered:
|
||||||
|
name = record["run"]["name"]
|
||||||
|
count = record["count"]
|
||||||
|
max_run_time = record["max_run_time"]
|
||||||
|
tasks_gt_pct = record["tasks_gt_pct"]
|
||||||
|
# pylint --py3k W1619
|
||||||
|
print(
|
||||||
|
"%-55s: %d of %d runs (%.1f%%) exceeded %d%% of max-run-time (%d s)"
|
||||||
|
% (
|
||||||
|
name,
|
||||||
|
tasks_gt_pct,
|
||||||
|
count,
|
||||||
|
tasks_gt_pct * 100 / count,
|
||||||
|
threshold_pct,
|
||||||
|
max_run_time,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print("No tasks found.")
|
||||||
|
|
||||||
|
|
||||||
class TestInfoReport(TestInfo):
|
class TestInfoReport(TestInfo):
|
||||||
@ -225,8 +614,54 @@ class TestInfoReport(TestInfo):
|
|||||||
|
|
||||||
def __init__(self, verbose):
|
def __init__(self, verbose):
|
||||||
TestInfo.__init__(self, verbose)
|
TestInfo.__init__(self, verbose)
|
||||||
|
self.total_activedata_matches = 0
|
||||||
self.threads = []
|
self.threads = []
|
||||||
|
|
||||||
|
def add_activedata_for_suite(
|
||||||
|
self, label, branches, days, suite_clause, tests_clause, path_mod
|
||||||
|
):
|
||||||
|
dates_clause = {"date": "today-%dday" % days}
|
||||||
|
where_conditions = [
|
||||||
|
suite_clause,
|
||||||
|
{"in": {"repo.branch.name": branches.split(",")}},
|
||||||
|
{"gt": {"run.timestamp": dates_clause}},
|
||||||
|
]
|
||||||
|
if tests_clause:
|
||||||
|
where_conditions.append(tests_clause)
|
||||||
|
ad_query = {
|
||||||
|
"from": "unittest",
|
||||||
|
"limit": ACTIVEDATA_RECORD_LIMIT,
|
||||||
|
"format": "list",
|
||||||
|
"groupby": ["result.test"],
|
||||||
|
"select": [
|
||||||
|
{"name": "result.count", "aggregate": "count"},
|
||||||
|
{
|
||||||
|
"name": "result.duration",
|
||||||
|
"value": "result.duration",
|
||||||
|
"aggregate": "sum",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "result.failures",
|
||||||
|
"value": {
|
||||||
|
"case": [{"when": {"eq": {"result.ok": "F"}}, "then": 1}]
|
||||||
|
},
|
||||||
|
"aggregate": "sum",
|
||||||
|
"default": 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "result.skips",
|
||||||
|
"value": {
|
||||||
|
"case": [{"when": {"eq": {"result.status": "SKIP"}}, "then": 1}]
|
||||||
|
},
|
||||||
|
"aggregate": "sum",
|
||||||
|
"default": 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"where": {"and": where_conditions},
|
||||||
|
}
|
||||||
|
t = ActiveDataThread(label, self, ad_query, path_mod)
|
||||||
|
self.threads.append(t)
|
||||||
|
|
||||||
def update_report(self, by_component, result, path_mod):
|
def update_report(self, by_component, result, path_mod):
|
||||||
def update_item(item, label, value):
|
def update_item(item, label, value):
|
||||||
# It is important to include any existing item value in case ActiveData
|
# It is important to include any existing item value in case ActiveData
|
||||||
@ -254,6 +689,51 @@ class TestInfoReport(TestInfo):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def collect_activedata_results(self, by_component):
|
||||||
|
# Start the first MAX_ACTIVEDATA_CONCURRENCY threads. If too many
|
||||||
|
# concurrent requests are made to ActiveData, the requests frequently
|
||||||
|
# fail (504 is the typical response).
|
||||||
|
for i in range(min(MAX_ACTIVEDATA_CONCURRENCY, len(self.threads))):
|
||||||
|
t = self.threads[i]
|
||||||
|
t.start()
|
||||||
|
# Wait for running threads (first N threads in self.threads) to complete.
|
||||||
|
# When a thread completes, start the next thread, process the results
|
||||||
|
# from the completed thread, and remove the completed thread from
|
||||||
|
# the thread list.
|
||||||
|
while len(self.threads):
|
||||||
|
running_threads = min(MAX_ACTIVEDATA_CONCURRENCY, len(self.threads))
|
||||||
|
for i in range(running_threads):
|
||||||
|
t = self.threads[i]
|
||||||
|
t.join(1)
|
||||||
|
if not t.isAlive():
|
||||||
|
ad_response = t.response
|
||||||
|
path_mod = t.context
|
||||||
|
name = t.name
|
||||||
|
del self.threads[i]
|
||||||
|
if len(self.threads) >= MAX_ACTIVEDATA_CONCURRENCY:
|
||||||
|
running_threads = min(
|
||||||
|
MAX_ACTIVEDATA_CONCURRENCY, len(self.threads)
|
||||||
|
)
|
||||||
|
self.threads[running_threads - 1].start()
|
||||||
|
if ad_response:
|
||||||
|
if len(ad_response) >= ACTIVEDATA_RECORD_LIMIT:
|
||||||
|
print(
|
||||||
|
"%s: ActiveData query limit reached; data may be missing"
|
||||||
|
% name
|
||||||
|
)
|
||||||
|
matches = 0
|
||||||
|
for record in ad_response:
|
||||||
|
if "result" in record:
|
||||||
|
result = record["result"]
|
||||||
|
if self.update_report(by_component, result, path_mod):
|
||||||
|
matches += 1
|
||||||
|
self.log_verbose(
|
||||||
|
"%s: %d results; %d matches"
|
||||||
|
% (name, len(ad_response), matches)
|
||||||
|
)
|
||||||
|
self.total_activedata_matches += matches
|
||||||
|
break
|
||||||
|
|
||||||
def path_mod_reftest(self, path):
|
def path_mod_reftest(self, path):
|
||||||
# "<path1> == <path2>" -> "<path1>"
|
# "<path1> == <path2>" -> "<path1>"
|
||||||
path = path.split(" ")[0]
|
path = path.split(" ")[0]
|
||||||
@ -296,6 +776,81 @@ class TestInfoReport(TestInfo):
|
|||||||
path = path.split(".ini:")[-1]
|
path = path.split(".ini:")[-1]
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
def add_activedata(self, branches, days, by_component):
|
||||||
|
suites = {
|
||||||
|
# List of known suites requiring special path handling and/or
|
||||||
|
# suites typically containing thousands of test paths.
|
||||||
|
# regexes have been selected by trial and error to partition data
|
||||||
|
# into queries returning less than ACTIVEDATA_RECORD_LIMIT records.
|
||||||
|
"reftest": (
|
||||||
|
self.path_mod_reftest,
|
||||||
|
[
|
||||||
|
{"regex": {"result.test": "layout/reftests/[a-k].*"}},
|
||||||
|
{"regex": {"result.test": "layout/reftests/[^a-k].*"}},
|
||||||
|
{"not": {"regex": {"result.test": "layout/reftests/.*"}}},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
"web-platform-tests": (
|
||||||
|
self.path_mod_wpt,
|
||||||
|
[
|
||||||
|
{"regex": {"result.test": "/[a-g].*"}},
|
||||||
|
{"regex": {"result.test": "/[h-p].*"}},
|
||||||
|
{"not": {"regex": {"result.test": "/[a-p].*"}}},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
"web-platform-tests-reftest": (
|
||||||
|
self.path_mod_wpt,
|
||||||
|
[
|
||||||
|
{"regex": {"result.test": "/css/css-.*"}},
|
||||||
|
{"not": {"regex": {"result.test": "/css/css-.*"}}},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
"crashtest": (
|
||||||
|
None,
|
||||||
|
[
|
||||||
|
{"regex": {"result.test": "[a-g].*"}},
|
||||||
|
{"not": {"regex": {"result.test": "[a-g].*"}}},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
"web-platform-tests-wdspec": (self.path_mod_wpt, [None]),
|
||||||
|
"web-platform-tests-crashtest": (self.path_mod_wpt, [None]),
|
||||||
|
"web-platform-tests-print-reftest": (self.path_mod_wpt, [None]),
|
||||||
|
"xpcshell": (self.path_mod_xpcshell, [None]),
|
||||||
|
"mochitest-plain": (None, [None]),
|
||||||
|
"mochitest-browser-chrome": (None, [None]),
|
||||||
|
"mochitest-media": (None, [None]),
|
||||||
|
"mochitest-devtools-chrome": (None, [None]),
|
||||||
|
"marionette": (self.path_mod_marionette, [None]),
|
||||||
|
"mochitest-chrome": (None, [None]),
|
||||||
|
}
|
||||||
|
unsupported_suites = [
|
||||||
|
# Usually these suites are excluded because currently the test resolver
|
||||||
|
# does not provide test paths for them.
|
||||||
|
"jsreftest",
|
||||||
|
"jittest",
|
||||||
|
"geckoview-junit",
|
||||||
|
"cppunittest",
|
||||||
|
]
|
||||||
|
for suite in suites:
|
||||||
|
suite_clause = {"eq": {"run.suite.name": suite}}
|
||||||
|
path_mod = suites[suite][0]
|
||||||
|
test_clauses = suites[suite][1]
|
||||||
|
suite_count = 1
|
||||||
|
for test_clause in test_clauses:
|
||||||
|
label = "%s-%d" % (suite, suite_count)
|
||||||
|
suite_count += 1
|
||||||
|
self.add_activedata_for_suite(
|
||||||
|
label, branches, days, suite_clause, test_clause, path_mod
|
||||||
|
)
|
||||||
|
# Remainder: All supported suites not handled above.
|
||||||
|
suite_clause = {
|
||||||
|
"not": {"in": {"run.suite.name": unsupported_suites + list(suites)}}
|
||||||
|
}
|
||||||
|
self.add_activedata_for_suite(
|
||||||
|
"remainder", branches, days, suite_clause, None, None
|
||||||
|
)
|
||||||
|
self.collect_activedata_results(by_component)
|
||||||
|
|
||||||
def description(
|
def description(
|
||||||
self,
|
self,
|
||||||
components,
|
components,
|
||||||
@ -306,8 +861,11 @@ class TestInfoReport(TestInfo):
|
|||||||
show_tests,
|
show_tests,
|
||||||
show_summary,
|
show_summary,
|
||||||
show_annotations,
|
show_annotations,
|
||||||
|
show_activedata,
|
||||||
filter_values,
|
filter_values,
|
||||||
filter_keys,
|
filter_keys,
|
||||||
|
branches,
|
||||||
|
days,
|
||||||
):
|
):
|
||||||
# provide a natural language description of the report options
|
# provide a natural language description of the report options
|
||||||
what = []
|
what = []
|
||||||
@ -339,6 +897,11 @@ class TestInfoReport(TestInfo):
|
|||||||
d += " in manifest keys '%s'" % filter_keys
|
d += " in manifest keys '%s'" % filter_keys
|
||||||
else:
|
else:
|
||||||
d += " in any part of manifest entry"
|
d += " in any part of manifest entry"
|
||||||
|
if show_activedata:
|
||||||
|
d += ", including historical run-time data for the last %d days on %s" % (
|
||||||
|
days,
|
||||||
|
branches,
|
||||||
|
)
|
||||||
d += " as of %s." % datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
|
d += " as of %s." % datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@ -352,10 +915,13 @@ class TestInfoReport(TestInfo):
|
|||||||
show_tests,
|
show_tests,
|
||||||
show_summary,
|
show_summary,
|
||||||
show_annotations,
|
show_annotations,
|
||||||
|
show_activedata,
|
||||||
filter_values,
|
filter_values,
|
||||||
filter_keys,
|
filter_keys,
|
||||||
show_components,
|
show_components,
|
||||||
output_file,
|
output_file,
|
||||||
|
branches,
|
||||||
|
days,
|
||||||
):
|
):
|
||||||
def matches_filters(test):
|
def matches_filters(test):
|
||||||
"""
|
"""
|
||||||
@ -555,6 +1121,20 @@ class TestInfoReport(TestInfo):
|
|||||||
for key in by_component["tests"]:
|
for key in by_component["tests"]:
|
||||||
by_component["tests"][key].sort(key=lambda k: k["test"])
|
by_component["tests"][key].sort(key=lambda k: k["test"])
|
||||||
|
|
||||||
|
if show_activedata:
|
||||||
|
try:
|
||||||
|
self.add_activedata(branches, days, by_component)
|
||||||
|
except Exception:
|
||||||
|
print("Failed to retrieve some ActiveData data.")
|
||||||
|
traceback.print_exc()
|
||||||
|
self.log_verbose(
|
||||||
|
"%d tests updated with matching ActiveData data"
|
||||||
|
% self.total_activedata_matches
|
||||||
|
)
|
||||||
|
self.log_verbose(
|
||||||
|
"%d seconds waiting for ActiveData" % self.total_activedata_seconds
|
||||||
|
)
|
||||||
|
|
||||||
by_component["description"] = self.description(
|
by_component["description"] = self.description(
|
||||||
components,
|
components,
|
||||||
flavor,
|
flavor,
|
||||||
@ -564,8 +1144,11 @@ class TestInfoReport(TestInfo):
|
|||||||
show_tests,
|
show_tests,
|
||||||
show_summary,
|
show_summary,
|
||||||
show_annotations,
|
show_annotations,
|
||||||
|
show_activedata,
|
||||||
filter_values,
|
filter_values,
|
||||||
filter_keys,
|
filter_keys,
|
||||||
|
branches,
|
||||||
|
days,
|
||||||
)
|
)
|
||||||
|
|
||||||
if show_summary:
|
if show_summary:
|
||||||
|
Loading…
Reference in New Issue
Block a user