Skip to content

Commit 6a2c5ff

Browse files
authored
History in test results (#7213)
1 parent 2d9e5a2 commit 6a2c5ff

File tree

3 files changed

+392
-42
lines changed

3 files changed

+392
-42
lines changed

.github/scripts/tests/generate-summary.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from jinja2 import Environment, FileSystemLoader, StrictUndefined
1515
from junit_utils import get_property_value, iter_xml_files
1616
from gh_status import update_pr_comment_text
17+
from get_test_history import get_test_history
1718

1819

1920
class TestStatus(Enum):
@@ -38,6 +39,7 @@ class TestResult:
3839
status: TestStatus
3940
log_urls: Dict[str, str]
4041
elapsed: float
42+
count_of_passed: int
4143

4244
@property
4345
def status_display(self):
@@ -97,7 +99,7 @@ def from_junit(cls, testcase):
9799
elapsed = 0
98100
print(f"Unable to cast elapsed time for {classname}::{name} value={elapsed!r}")
99101

100-
return cls(classname, name, status, log_urls, elapsed)
102+
return cls(classname, name, status, log_urls, elapsed, 0)
101103

102104

103105
class TestSummaryLine:
@@ -222,12 +224,13 @@ def render_pm(value, url, diff=None):
222224
return text
223225

224226

225-
def render_testlist_html(rows, fn):
227+
def render_testlist_html(rows, fn, build_preset):
226228
TEMPLATES_PATH = os.path.join(os.path.dirname(__file__), "templates")
227229

228230
env = Environment(loader=FileSystemLoader(TEMPLATES_PATH), undefined=StrictUndefined)
229231

230232
status_test = {}
233+
last_n_runs = 5
231234
has_any_log = set()
232235

233236
for t in rows:
@@ -243,8 +246,35 @@ def render_testlist_html(rows, fn):
243246
# remove status group without tests
244247
status_order = [s for s in status_order if s in status_test]
245248

249+
# get failed tests
250+
failed_tests_array = []
251+
history={}
252+
for test in status_test.get(TestStatus.FAIL, []):
253+
failed_tests_array.append(test.full_name)
254+
255+
if failed_tests_array:
256+
try:
257+
history = get_test_history(failed_tests_array, last_n_runs, build_preset)
258+
except Exception as e:
259+
print(f'Error:{e}')
260+
261+
# sorting, at first show tests with passed resuts in history
262+
263+
if TestStatus.FAIL in status_test:
264+
for test in status_test.get(TestStatus.FAIL, []):
265+
if test.full_name in history:
266+
test.count_of_passed = history[test.full_name][
267+
next(iter(history[test.full_name]))
268+
]["count_of_passed"]
269+
else:
270+
test.count_of_passed = 0
271+
status_test[TestStatus.FAIL].sort(key=lambda val: (val.count_of_passed, val.full_name), reverse=True)
272+
246273
content = env.get_template("summary.html").render(
247-
status_order=status_order, tests=status_test, has_any_log=has_any_log
274+
status_order=status_order,
275+
tests=status_test,
276+
has_any_log=has_any_log,
277+
history=history,
248278
)
249279

250280
with open(fn, "w") as fp:
@@ -267,7 +297,7 @@ def write_summary(summary: TestSummary):
267297
fp.close()
268298

269299

270-
def gen_summary(public_dir, public_dir_url, paths, is_retry: bool):
300+
def gen_summary(public_dir, public_dir_url, paths, is_retry: bool, build_preset):
271301
summary = TestSummary(is_retry=is_retry)
272302

273303
for title, html_fn, path in paths:
@@ -281,7 +311,7 @@ def gen_summary(public_dir, public_dir_url, paths, is_retry: bool):
281311
html_fn = os.path.relpath(html_fn, public_dir)
282312
report_url = f"{public_dir_url}/{html_fn}"
283313

284-
render_testlist_html(summary_line.tests, os.path.join(public_dir, html_fn))
314+
render_testlist_html(summary_line.tests, os.path.join(public_dir, html_fn),build_preset)
285315
summary_line.add_report(html_fn, report_url)
286316
summary.add_line(summary_line)
287317

@@ -349,7 +379,7 @@ def main():
349379
paths = iter(args.args)
350380
title_path = list(zip(paths, paths, paths))
351381

352-
summary = gen_summary(args.public_dir, args.public_dir_url, title_path, is_retry=bool(args.is_retry))
382+
summary = gen_summary(args.public_dir, args.public_dir_url, title_path, is_retry=bool(args.is_retry),build_preset=args.build_preset)
353383
write_summary(summary)
354384

355385
if summary.is_failed:
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/usr/bin/env python3
2+
3+
import configparser
4+
import os
5+
import ydb
6+
import datetime
7+
8+
9+
dir = os.path.dirname(__file__)
10+
config = configparser.ConfigParser()
11+
config_file_path = f"{dir}/../../config/ydb_qa_db.ini"
12+
config.read(config_file_path)
13+
14+
DATABASE_ENDPOINT = config["QA_DB"]["DATABASE_ENDPOINT"]
15+
DATABASE_PATH = config["QA_DB"]["DATABASE_PATH"]
16+
17+
18+
def get_test_history(test_names_array, last_n_runs_of_test_amount, build_type):
19+
if "CI_YDB_SERVICE_ACCOUNT_KEY_FILE_CREDENTIALS" not in os.environ:
20+
print(
21+
"Error: Env variable CI_YDB_SERVICE_ACCOUNT_KEY_FILE_CREDENTIALS is missing, skipping"
22+
)
23+
return {}
24+
else:
25+
# Do not set up 'real' variable from gh workflows because it interfere with ydb tests
26+
# So, set up it locally
27+
os.environ["YDB_SERVICE_ACCOUNT_KEY_FILE_CREDENTIALS"] = os.environ[
28+
"CI_YDB_SERVICE_ACCOUNT_KEY_FILE_CREDENTIALS"
29+
]
30+
31+
query = f"""
32+
PRAGMA AnsiInForEmptyOrNullableItemsCollections;
33+
DECLARE $test_names AS List<Utf8>;
34+
DECLARE $rn_max AS Int32;
35+
DECLARE $build_type AS Utf8;
36+
37+
$tests=(
38+
SELECT
39+
suite_folder ||'/' || test_name as full_name,test_name,build_type, commit, branch, run_timestamp, status, status_description,
40+
ROW_NUMBER() OVER (PARTITION BY test_name ORDER BY run_timestamp DESC) AS rn
41+
FROM
42+
`test_results/test_runs_results`
43+
where (job_name ='Nightly-run' or job_name like 'Postcommit%') and
44+
build_type = $build_type and
45+
suite_folder ||'/' || test_name in $test_names
46+
and status != 'skipped'
47+
);
48+
49+
select full_name,test_name,build_type, commit, branch, run_timestamp, status, status_description,rn,
50+
COUNT_IF(status = 'passed') over (PARTITION BY test_name) as count_of_passed
51+
from $tests
52+
WHERE rn <= $rn_max
53+
ORDER BY test_name, run_timestamp;
54+
"""
55+
56+
with ydb.Driver(
57+
endpoint=DATABASE_ENDPOINT,
58+
database=DATABASE_PATH,
59+
credentials=ydb.credentials_from_env_variables(),
60+
) as driver:
61+
driver.wait(timeout=10, fail_fast=True)
62+
session = ydb.retry_operation_sync(
63+
lambda: driver.table_client.session().create()
64+
)
65+
66+
with session.transaction() as transaction:
67+
prepared_query = session.prepare(query)
68+
query_params = {
69+
"$test_names": test_names_array,
70+
"$rn_max": last_n_runs_of_test_amount,
71+
"$build_type": build_type,
72+
}
73+
74+
result_set = session.transaction(ydb.SerializableReadWrite()).execute(
75+
prepared_query, parameters=query_params, commit_tx=True
76+
)
77+
78+
results = {}
79+
for row in result_set[0].rows:
80+
if not row["full_name"].decode("utf-8") in results:
81+
results[row["full_name"].decode("utf-8")] = {}
82+
83+
results[row["full_name"].decode("utf-8")][row["run_timestamp"]] = {
84+
"status": row["status"],
85+
"commit": row["commit"],
86+
"datetime": datetime.datetime.fromtimestamp(int(row["run_timestamp"] / 1000000)).strftime("%H:%m %B %d %Y"),
87+
"count_of_passed": row["count_of_passed"],
88+
}
89+
return results
90+
91+
92+
if __name__ == "__main__":
93+
get_test_history(test_names_array, last_n_runs_of_test_amount, build_type)

0 commit comments

Comments
 (0)