Skip to content

Commit dfe1e20

Browse files
Tarun-Arorathealphadollar
andauthored
[FIX] Fix bot PR comments & platform test results difference (#877)
* Fix bot PR comments & platform test results difference Signed-off-by: Tarun Arora <tarun.arora.030402@gmail.com> * remove: unused imports --------- Signed-off-by: Tarun Arora <tarun.arora.030402@gmail.com> Co-authored-by: thealphadollar <s-a-kumar@mercari.com>
1 parent 8acd63a commit dfe1e20

File tree

3 files changed

+141
-178
lines changed

3 files changed

+141
-178
lines changed

mod_ci/controllers.py

Lines changed: 52 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@
2121
from lxml import etree
2222
from markdown2 import markdown
2323
from pymysql.err import IntegrityError
24-
from sqlalchemy import and_, func, or_
25-
from sqlalchemy.orm.query import Query
24+
from sqlalchemy import and_, func
2625
from sqlalchemy.sql import label
2726
from sqlalchemy.sql.functions import count
2827
from werkzeug.utils import secure_filename
@@ -32,15 +31,14 @@
3231
from mod_auth.controllers import check_access_rights, login_required
3332
from mod_auth.models import Role
3433
from mod_ci.forms import AddUsersToBlacklist, DeleteUserForm
35-
from mod_ci.models import (BlockedUsers, GcpInstance, MaintenanceMode,
36-
PrCommentInfo, Status)
34+
from mod_ci.models import (BlockedUsers, CategoryTestInfo, GcpInstance,
35+
MaintenanceMode, PrCommentInfo, Status)
3736
from mod_customized.models import CustomizedTest
3837
from mod_home.models import CCExtractorVersion, GeneralData
3938
from mod_regression.models import (Category, RegressionTest,
40-
RegressionTestOutput,
41-
RegressionTestOutputFiles,
42-
regressionTestLinkTable)
39+
RegressionTestOutput)
4340
from mod_sample.models import Issue
41+
from mod_test.controllers import get_test_results
4442
from mod_test.models import (Fork, Test, TestPlatform, TestProgress,
4543
TestResult, TestResultFile, TestStatus, TestType)
4644
from utility import is_valid_signature, request_from_github
@@ -1138,8 +1136,11 @@ def update_build_badge(status, test) -> None:
11381136
shutil.copyfile(original_location, build_status_location)
11391137
g.log.info('Build badge updated successfully!')
11401138

1141-
regression_testid_passed = get_query_regression_testid_passed(test.id)
1142-
test_ids_to_update = [result[0] for result in regression_testid_passed]
1139+
test_results = get_test_results(test)
1140+
test_ids_to_update = []
1141+
for category_results in test_results:
1142+
test_ids_to_update.extend([test['test'].id for test in category_results['tests'] if not test['error']])
1143+
11431144
g.db.query(RegressionTest).filter(RegressionTest.id.in_(test_ids_to_update)
11441145
).update({"last_passed_on": test.id}, synchronize_session=False)
11451146
g.db.commit()
@@ -1292,7 +1293,7 @@ def progress_type_request(log, test, test_id, request) -> bool:
12921293
state = Status.SUCCESS
12931294
message = 'Tests completed'
12941295
if test.test_type == TestType.pull_request:
1295-
state = comment_pr(test.id, test.pr_nr, test.platform.name)
1296+
state = comment_pr(test)
12961297
update_build_badge(state, test)
12971298

12981299
else:
@@ -1552,99 +1553,64 @@ def set_avg_time(platform, process_type: str, time_taken: int) -> None:
15521553
g.db.commit()
15531554

15541555

1555-
def get_query_regression_testid_passed(test_id: int) -> Query:
1556-
"""Get sqlalchemy query to fetch all regression tests which passed on given test id.
1556+
def get_info_for_pr_comment(test: Test) -> PrCommentInfo:
1557+
"""
1558+
Return info about the given test for use in a PR comment.
15571559
1558-
:param test_id: test id of the test whose query is required
1559-
:type test_id: int
1560-
:return: Query object for passed regression tests
1561-
:rtype: sqlalchemy.orm.query.Query
1562-
"""
1563-
regression_testid_passed = g.db.query(TestResult.regression_test_id).outerjoin(
1564-
TestResultFile, TestResult.test_id == TestResultFile.test_id).filter(
1565-
TestResult.test_id == test_id,
1566-
TestResult.expected_rc == TestResult.exit_code,
1567-
and_(
1568-
RegressionTestOutput.regression_id == TestResult.regression_test_id,
1569-
RegressionTestOutput.ignore.is_(True)
1570-
)).distinct().subquery()
1571-
1572-
regression_testid_passed = g.db.query(TestResult.regression_test_id).outerjoin(
1573-
TestResultFile, TestResult.test_id == TestResultFile.test_id).filter(
1574-
TestResult.test_id == test_id,
1575-
TestResult.expected_rc == TestResult.exit_code,
1576-
or_(
1577-
TestResult.exit_code != 0,
1578-
and_(TestResult.exit_code == 0,
1579-
TestResult.regression_test_id == TestResultFile.regression_test_id,
1580-
or_(TestResultFile.got.is_(None),
1581-
and_(
1582-
RegressionTestOutputFiles.regression_test_output_id == TestResultFile.regression_test_output_id,
1583-
TestResultFile.got == RegressionTestOutputFiles.file_hashes
1584-
)))
1585-
)).distinct().union(g.db.query(regression_testid_passed.c.regression_test_id))
1586-
1587-
return regression_testid_passed
1588-
1589-
1590-
def get_info_for_pr_comment(test_id: int, platform) -> PrCommentInfo:
1591-
"""
1592-
Return info about the given test id for use in a PR comment.
1593-
1594-
:param test_id: The identity of Test whose report will be uploaded
1595-
:type test_id: str
1596-
:param platform
1597-
:type: str
1598-
"""
1599-
platform = TestPlatform.from_string(platform)
1560+
:param test: The test whose report will be returned
1561+
:type test: Test
1562+
"""
16001563
last_test_master = g.db.query(Test).filter(Test.branch == "master", Test.test_type == TestType.commit,
1601-
Test.platform == platform).join(
1564+
Test.platform == test.platform).join(
16021565
TestProgress, Test.id == TestProgress.test_id).filter(
16031566
TestProgress.status == TestStatus.completed).order_by(TestProgress.id.desc()).first()
1604-
regression_test_passed = get_query_regression_testid_passed(test_id)
1605-
1606-
passed = g.db.query(label('category_id', Category.id), label(
1607-
'success', count(regressionTestLinkTable.c.regression_id))).filter(
1608-
regressionTestLinkTable.c.regression_id.in_(regression_test_passed),
1609-
Category.id == regressionTestLinkTable.c.category_id).group_by(
1610-
regressionTestLinkTable.c.category_id).subquery()
1611-
tot = g.db.query(label('category', Category.name), label('total', count(regressionTestLinkTable.c.regression_id)),
1612-
label('success', passed.c.success)).outerjoin(
1613-
passed, passed.c.category_id == Category.id).filter(
1614-
Category.id == regressionTestLinkTable.c.category_id).group_by(
1615-
regressionTestLinkTable.c.category_id).all()
1616-
regression_test_failed = RegressionTest.query.filter(RegressionTest.id.notin_(regression_test_passed)).all()
1617-
1618-
last_test_master_id = last_test_master.id if last_test_master else 0
1619-
extra_failed_tests = [test for test in regression_test_failed if test.last_passed_on == last_test_master]
1620-
fixed_tests = RegressionTest.query.filter(RegressionTest.id.in_(regression_test_passed),
1621-
RegressionTest.last_passed_on != last_test_master_id).all()
1622-
common_failed_tests = [test for test in regression_test_failed if test.last_passed_on != last_test_master]
1623-
return PrCommentInfo(tot, extra_failed_tests, fixed_tests, common_failed_tests, last_test_master)
1624-
1625-
1626-
def comment_pr(test_id, pr_nr, platform) -> str:
1567+
1568+
extra_failed_tests = []
1569+
common_failed_tests = []
1570+
fixed_tests = []
1571+
category_stats = []
1572+
1573+
test_results = get_test_results(test)
1574+
for category_results in test_results:
1575+
category_name = category_results['category'].name
1576+
1577+
category_test_pass_count = 0
1578+
for test in category_results['tests']:
1579+
if not test['error']:
1580+
category_test_pass_count += 1
1581+
if test['test'].last_passed_on != last_test_master:
1582+
fixed_tests.append(test['test'])
1583+
else:
1584+
if test['test'].last_passed_on != last_test_master:
1585+
common_failed_tests.append(test['test'])
1586+
else:
1587+
extra_failed_tests.append(test['test'])
1588+
1589+
category_stats.append(CategoryTestInfo(category_name, len(category_results['tests']), category_test_pass_count))
1590+
1591+
return PrCommentInfo(category_stats, extra_failed_tests, fixed_tests, common_failed_tests, last_test_master)
1592+
1593+
1594+
def comment_pr(test: Test) -> str:
16271595
"""
16281596
Upload the test report to the GitHub PR as comment.
16291597
1630-
:param test_id: The identity of Test whose report will be uploaded
1631-
:type test_id: str
1632-
:param pr_nr: PR number to which test commit is related and comment will be uploaded
1633-
:type: str
1634-
:param platform
1635-
:type: str
1598+
:param test: The test whose report will be uploaded
1599+
:type test: Test
16361600
"""
16371601
from run import app, log
16381602

1639-
comment_info = get_info_for_pr_comment(test_id, platform)
1603+
test_id = test.id
1604+
platform = test.platform.name
1605+
comment_info = get_info_for_pr_comment(test)
16401606
template = app.jinja_env.get_or_select_template('ci/pr_comment.txt')
16411607
message = template.render(comment_info=comment_info, test_id=test_id, platform=platform)
16421608
log.debug(f"GitHub PR Comment Message Created for Test_id: {test_id}")
16431609
try:
16441610
gh = Github(g.github['bot_token'])
16451611
repository = gh.get_repo(f"{g.github['repository_owner']}/{g.github['repository']}")
16461612
# Pull requests are just issues with code, so GitHub considers PR comments in issues
1647-
pull_request = repository.get_pull(number=pr_nr)
1613+
pull_request = repository.get_pull(number=test.pr_nr)
16481614
comments = pull_request.get_issue_comments()
16491615
bot_name = gh.get_user().login
16501616
for comment in comments:

mod_test/controllers.py

Lines changed: 63 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import os
44
from datetime import datetime
5-
from typing import Any, Dict
5+
from typing import Any, Dict, List
66

77
from flask import (Blueprint, Response, abort, g, jsonify, redirect, request,
88
url_for)
@@ -56,63 +56,15 @@ def index():
5656
}
5757

5858

59-
def get_data_for_test(test, title=None) -> Dict[str, Any]:
59+
def get_test_results(test) -> List[Dict[str, Any]]:
6060
"""
61-
Retrieve the data for a single test, with an optional title.
61+
Get test results for each category.
6262
6363
:param test: The test to retrieve the data for.
6464
:type test: Test
65-
:param title: The title to use in the result. If empty, it's set to 'test {id}'
66-
:type title: str
67-
:return: A dictionary with the appropriate values.
68-
:rtype: dict
6965
"""
70-
if title is None:
71-
title = f"test {test.id}"
72-
7366
populated_categories = g.db.query(regressionTestLinkTable.c.category_id).subquery()
7467
categories = Category.query.filter(Category.id.in_(populated_categories)).order_by(Category.name.asc()).all()
75-
hours = 0.00
76-
minutes = 0.00
77-
queued_tests = 0
78-
79-
"""
80-
evaluating estimated time if the test is still in queue
81-
estimated time = (number of tests already in queue + 1) * (average time of that platform)
82-
- (time already spend by those tests)
83-
calculates time in minutes and hours
84-
"""
85-
if len(test.progress) == 0:
86-
var_average = 'average_time_' + test.platform.value
87-
88-
# get average build and prep time.
89-
prep_average_key = 'avg_prep_time_' + test.platform.value
90-
average_prep_time = int(float(GeneralData.query.filter(GeneralData.key == prep_average_key).first().value))
91-
92-
test_progress_last_entry = g.db.query(func.max(TestProgress.test_id)).first()
93-
queued_gcp_instance = g.db.query(GcpInstance.test_id).filter(GcpInstance.test_id < test.id).subquery()
94-
queued_gcp_instance_entries = g.db.query(Test.id).filter(
95-
and_(Test.id.in_(queued_gcp_instance), Test.platform == test.platform)
96-
).subquery()
97-
gcp_instance_test = g.db.query(TestProgress.test_id, label('time', func.group_concat(
98-
TestProgress.timestamp))).filter(TestProgress.test_id.in_(queued_gcp_instance_entries)).group_by(
99-
TestProgress.test_id).all()
100-
number_gcp_instance_test = g.db.query(Test.id).filter(
101-
and_(Test.id > test_progress_last_entry[0], Test.id < test.id, Test.platform == test.platform)
102-
).count()
103-
average_duration = float(GeneralData.query.filter(GeneralData.key == var_average).first().value)
104-
queued_tests = number_gcp_instance_test
105-
time_run = 0.00
106-
for pr_test in gcp_instance_test:
107-
timestamps = pr_test.time.split(',')
108-
start = datetime.strptime(timestamps[0], '%Y-%m-%d %H:%M:%S')
109-
end = datetime.strptime(timestamps[-1], '%Y-%m-%d %H:%M:%S')
110-
time_run += (end - start).total_seconds()
111-
# subtracting current running tests
112-
total = (average_prep_time + (average_duration * (queued_tests + 1))) - time_run
113-
minutes = (total % 3600) // 60
114-
hours = total // 3600
115-
11668
results = [{
11769
'category': category,
11870
'tests': [{
@@ -164,6 +116,66 @@ def get_data_for_test(test, title=None) -> Dict[str, Any]:
164116
error = error or test_error
165117
category['error'] = error
166118
results.sort(key=lambda entry: entry['category'].name)
119+
return results
120+
121+
122+
def get_data_for_test(test, title=None) -> Dict[str, Any]:
123+
"""
124+
Retrieve the data for a single test, with an optional title.
125+
126+
:param test: The test to retrieve the data for.
127+
:type test: Test
128+
:param title: The title to use in the result. If empty, it's set to 'test {id}'
129+
:type title: str
130+
:return: A dictionary with the appropriate values.
131+
:rtype: dict
132+
"""
133+
if title is None:
134+
title = f"test {test.id}"
135+
136+
hours = 0.00
137+
minutes = 0.00
138+
queued_tests = 0
139+
140+
"""
141+
evaluating estimated time if the test is still in queue
142+
estimated time = (number of tests already in queue + 1) * (average time of that platform)
143+
- (time already spend by those tests)
144+
calculates time in minutes and hours
145+
"""
146+
if len(test.progress) == 0:
147+
var_average = 'average_time_' + test.platform.value
148+
149+
# get average build and prep time.
150+
prep_average_key = 'avg_prep_time_' + test.platform.value
151+
average_prep_time = int(float(GeneralData.query.filter(GeneralData.key == prep_average_key).first().value))
152+
153+
test_progress_last_entry = g.db.query(func.max(TestProgress.test_id)).first()
154+
queued_gcp_instance = g.db.query(GcpInstance.test_id).filter(GcpInstance.test_id < test.id).subquery()
155+
queued_gcp_instance_entries = g.db.query(Test.id).filter(
156+
and_(Test.id.in_(queued_gcp_instance), Test.platform == test.platform)
157+
).subquery()
158+
gcp_instance_test = g.db.query(TestProgress.test_id, label('time', func.group_concat(
159+
TestProgress.timestamp))).filter(TestProgress.test_id.in_(queued_gcp_instance_entries)).group_by(
160+
TestProgress.test_id).all()
161+
number_gcp_instance_test = g.db.query(Test.id).filter(
162+
and_(Test.id > test_progress_last_entry[0], Test.id < test.id, Test.platform == test.platform)
163+
).count()
164+
average_duration = float(GeneralData.query.filter(GeneralData.key == var_average).first().value)
165+
queued_tests = number_gcp_instance_test
166+
time_run = 0.00
167+
for pr_test in gcp_instance_test:
168+
timestamps = pr_test.time.split(',')
169+
start = datetime.strptime(timestamps[0], '%Y-%m-%d %H:%M:%S')
170+
end = datetime.strptime(timestamps[-1], '%Y-%m-%d %H:%M:%S')
171+
time_run += (end - start).total_seconds()
172+
# subtracting current running tests
173+
total = average_prep_time + average_duration - time_run
174+
minutes = (total % 3600) // 60
175+
hours = total // 3600
176+
177+
results = get_test_results(test)
178+
167179
return {
168180
'test': test,
169181
'TestType': TestType,

0 commit comments

Comments
 (0)