Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 18 additions & 47 deletions mod_test/controllers.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
"""Logic to find all tests, their progress and details of individual test."""

import os
from datetime import datetime
from typing import Any, Dict, List

from flask import (Blueprint, Response, abort, g, jsonify, redirect, request,
url_for)
from sqlalchemy import and_, func
from sqlalchemy.sql import label
from sqlalchemy import and_

from decorators import template_renderer
from exceptions import TestNotFoundException
from mod_auth.controllers import check_access_rights, login_required
from mod_auth.models import Role
from mod_ci.models import GcpInstance
from mod_customized.models import TestFork
from mod_home.models import CCExtractorVersion, GeneralData
from mod_regression.models import (Category, RegressionTestOutput,
Expand Down Expand Up @@ -133,47 +130,23 @@ def get_data_for_test(test, title=None) -> Dict[str, Any]:
if title is None:
title = f"test {test.id}"

hours = 0.00
minutes = 0.00
queued_tests = 0

"""
evaluating estimated time if the test is still in queue
estimated time = (number of tests already in queue + 1) * (average time of that platform)
- (time already spend by those tests)
calculates time in minutes and hours
"""
# Calculate average runtime for this platform (used when test hasn't started yet)
avg_minutes = 0
if len(test.progress) == 0:
var_average = 'average_time_' + test.platform.value

# get average build and prep time.
prep_average_key = 'avg_prep_time_' + test.platform.value
average_prep_time = int(float(GeneralData.query.filter(GeneralData.key == prep_average_key).first().value))

test_progress_last_entry = g.db.query(func.max(TestProgress.test_id)).first()
last_test_id = test_progress_last_entry[0] if test_progress_last_entry is not None else 0
queued_gcp_instance = g.db.query(GcpInstance.test_id).filter(GcpInstance.test_id < test.id).subquery()
queued_gcp_instance_entries = g.db.query(Test.id).filter(
and_(Test.id.in_(queued_gcp_instance), Test.platform == test.platform)
).subquery()
gcp_instance_test = g.db.query(TestProgress.test_id, label('time', func.group_concat(
TestProgress.timestamp))).filter(TestProgress.test_id.in_(queued_gcp_instance_entries)).group_by(
TestProgress.test_id).all()
number_gcp_instance_test = g.db.query(Test.id).filter(
and_(Test.id > last_test_id, Test.id < test.id, Test.platform == test.platform)
).count()
average_duration = float(GeneralData.query.filter(GeneralData.key == var_average).first().value)
queued_tests = number_gcp_instance_test
time_run = 0.00
for pr_test in gcp_instance_test:
timestamps = pr_test.time.split(',')
start = datetime.strptime(timestamps[0], '%Y-%m-%d %H:%M:%S')
end = datetime.strptime(timestamps[-1], '%Y-%m-%d %H:%M:%S')
time_run += (end - start).total_seconds()
# subtracting current running tests
total = average_prep_time + average_duration - time_run
minutes = (total % 3600) // 60
hours = total // 3600
try:
avg_time_key = 'average_time_' + test.platform.value
prep_time_key = 'avg_prep_time_' + test.platform.value

avg_time_record = GeneralData.query.filter(GeneralData.key == avg_time_key).first()
prep_time_record = GeneralData.query.filter(GeneralData.key == prep_time_key).first()

avg_duration = float(avg_time_record.value) if avg_time_record else 0
avg_prep = float(prep_time_record.value) if prep_time_record else 0

# Total average time in minutes
avg_minutes = int((avg_duration + avg_prep) / 60)
except (ValueError, AttributeError):
avg_minutes = 0

results = get_test_results(test)

Expand All @@ -182,9 +155,7 @@ def get_data_for_test(test, title=None) -> Dict[str, Any]:
'TestType': TestType,
'results': results,
'title': title,
'next': queued_tests,
'min': minutes,
'hr': hours
'avg_minutes': avg_minutes
}


Expand Down
2 changes: 1 addition & 1 deletion templates/test/by_id.html
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ <h1>Test progress for {{ title }}</h1>
</table>
</div>
{% else %}
<p>This test is still queued. There are {{ next }} tests queued before this one. Based on the average runtime, this test will be finished in {{ hr }} hours {{ min }} minutes.</p>
<p>Your test VM is being provisioned.{% if avg_minutes > 0 %} Tests on {{ test.platform.description }} typically complete in about {{ avg_minutes }} minutes.{% endif %}</p>
{% endif %}
{% if test.finished %}
{% if test.failed %}
Expand Down
21 changes: 13 additions & 8 deletions tests/test_test/test_controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from mod_regression.models import RegressionTest
from mod_test.models import (Test, TestPlatform, TestProgress, TestResult,
TestResultFile, TestStatus)
from tests.base import BaseTestCase, create_mock_db_query
from tests.base import BaseTestCase
from tests.test_auth.test_controllers import MockUser


Expand Down Expand Up @@ -122,25 +122,30 @@ def test_ccextractor_version_not_found(self):
self.assertEqual(response.status_code, 404)
self.assert_template_used('test/test_not_found.html')

@mock.patch('mod_test.controllers.g')
@mock.patch('mod_test.controllers.GeneralData')
@mock.patch('mod_test.controllers.Category')
@mock.patch('mod_test.controllers.TestProgress')
def test_data_for_test(self, mock_test_progress, mock_category, mock_gen_data, mock_g):
def test_data_for_test(self, mock_category, mock_gen_data):
"""Test get_data_for_test method."""
from mod_test.controllers import get_data_for_test

mock_test = mock.MagicMock()
mock_test.progress = [] # No progress yet, so avg_minutes should be calculated
mock_test.platform.value = 'linux'

# Set up mock db query chain to avoid AsyncMock behavior in Python 3.13+
create_mock_db_query(mock_g)
# Mock GeneralData query responses for average times
mock_avg_time = mock.MagicMock()
mock_avg_time.value = '300' # 5 minutes in seconds
mock_prep_time = mock.MagicMock()
mock_prep_time.value = '60' # 1 minute in seconds
mock_gen_data.query.filter.return_value.first.side_effect = [mock_avg_time, mock_prep_time]

result = get_data_for_test(mock_test)

self.assertIsInstance(result, dict)
self.assertEqual(6, mock_g.db.query.call_count)
self.assertIn('avg_minutes', result)
self.assertEqual(result['avg_minutes'], 6) # (300 + 60) / 60 = 6 minutes
mock_category.query.filter.assert_called_once()
mock_gen_data.query.filter.assert_called()
self.assertEqual(mock_gen_data.query.filter.call_count, 2)

@mock.patch('mod_test.controllers.Test')
def test_get_json_data_no_test(self, mock_test):
Expand Down