Skip to content
Open
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
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import datetime

import pytest

from treeherder.perf.models import PerformanceAlertSummary


@pytest.fixture
def test_perf_alert_summary_for_modifier(
test_repository, push_stored, test_perf_framework, test_issue_tracker
):
return PerformanceAlertSummary.objects.create(
repository=test_repository,
framework=test_perf_framework,
prev_push_id=1,
push_id=2,
manually_created=False,
created=datetime.datetime.now(),
bug_number=None,
)


@pytest.fixture
def create_perf_alert_summary(
test_repository, test_perf_framework, push_stored, test_issue_tracker
):
counter = {"value": 3}

def _create_summary(**kwargs):
push_id = counter["value"]
counter["value"] += 1
defaults = {
"repository": test_repository,
"framework": test_perf_framework,
"prev_push_id": push_id,
"push_id": push_id + 1,
"manually_created": False,
"created": datetime.datetime.now(),
"bug_number": None,
}
defaults.update(kwargs)
return PerformanceAlertSummary.objects.create(**defaults)

return _create_summary
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
from datetime import datetime
from unittest.mock import Mock, patch

import pytest

from treeherder.perf.auto_perf_sheriffing.performance_alerting.alert_modifier import (
PerformanceAlertSummaryModifier,
)
from treeherder.perf.models import PerformanceAlertSummary


class TestResolutionModifier:
@pytest.fixture
def resolution_modifier_class(self):
for updater in PerformanceAlertSummaryModifier.get_updaters():
if updater.__name__ == "ResolutionModifier":
return updater
pytest.fail("ResolutionModifier not found in updaters list")

@patch("treeherder.perf.auto_perf_sheriffing.performance_alerting.alert_modifier.BugSearcher")
def test_update_alerts_no_bugs(self, mock_bug_searcher_class, resolution_modifier_class):
mock_searcher = Mock()
mock_searcher.get_today_date.return_value = datetime.now().date()
mock_searcher.get_bugs.return_value = {"bugs": []}
mock_bug_searcher_class.return_value = mock_searcher

updates, summaries = resolution_modifier_class.update_alerts()

assert updates == {}
assert summaries == {}

@pytest.mark.parametrize(
"resolution, expected_bug_status",
[
("FIXED", PerformanceAlertSummary.BUG_FIXED),
("INVALID", PerformanceAlertSummary.BUG_INVALID),
("WONTFIX", PerformanceAlertSummary.BUG_WONTFIX),
("DUPLICATE", PerformanceAlertSummary.BUG_DUPLICATE),
("WORKSFORME", PerformanceAlertSummary.BUG_WORKSFORME),
("INCOMPLETE", PerformanceAlertSummary.BUG_INCOMPLETE),
("MOVED", PerformanceAlertSummary.BUG_MOVED),
],
)
@patch("treeherder.perf.auto_perf_sheriffing.performance_alerting.alert_modifier.BugSearcher")
def test_update_alerts_resolution_mapping(
self,
mock_bug_searcher_class,
resolution,
expected_bug_status,
resolution_modifier_class,
test_perf_alert_summary_for_modifier,
):
mock_searcher = Mock()
mock_searcher.get_today_date.return_value = datetime.now().date()
mock_searcher.get_bugs.return_value = {
"bugs": [{"id": 12345, "resolution": resolution, "status": "RESOLVED"}]
}
mock_bug_searcher_class.return_value = mock_searcher

test_perf_alert_summary_for_modifier.bug_number = 12345
test_perf_alert_summary_for_modifier.save()

updates, summaries = resolution_modifier_class.update_alerts()

assert (
updates[str(test_perf_alert_summary_for_modifier.id)]["bug_status"]
== expected_bug_status
)
assert str(test_perf_alert_summary_for_modifier.id) in summaries

@patch("treeherder.perf.auto_perf_sheriffing.performance_alerting.alert_modifier.BugSearcher")
def test_update_alerts_with_multiple_bugs(
self,
mock_bug_searcher_class,
resolution_modifier_class,
test_perf_alert_summary_for_modifier,
create_perf_alert_summary,
):
mock_searcher = Mock()
mock_searcher.get_today_date.return_value = datetime.now().date()
mock_searcher.get_bugs.return_value = {
"bugs": [
{"id": 12345, "resolution": "FIXED", "status": "RESOLVED"},
{"id": 67890, "resolution": "INVALID", "status": "RESOLVED"},
]
}
mock_bug_searcher_class.return_value = mock_searcher

summary2 = create_perf_alert_summary(bug_number=67890)
test_perf_alert_summary_for_modifier.bug_number = 12345
test_perf_alert_summary_for_modifier.save()

updates, summaries = resolution_modifier_class.update_alerts()

assert (
updates[str(test_perf_alert_summary_for_modifier.id)]["bug_status"]
== PerformanceAlertSummary.BUG_FIXED
)
assert updates[str(summary2.id)]["bug_status"] == PerformanceAlertSummary.BUG_INVALID
assert str(test_perf_alert_summary_for_modifier.id) in summaries
assert str(summary2.id) in summaries

@patch("treeherder.perf.auto_perf_sheriffing.performance_alerting.alert_modifier.BugSearcher")
def test_update_alerts_bug_not_matching_any_summary(
self,
mock_bug_searcher_class,
resolution_modifier_class,
test_perf_alert_summary_for_modifier,
):
mock_searcher = Mock()
mock_searcher.get_today_date.return_value = datetime.now().date()
mock_searcher.get_bugs.return_value = {
"bugs": [{"id": 99999, "resolution": "FIXED", "status": "RESOLVED"}]
}
mock_bug_searcher_class.return_value = mock_searcher

test_perf_alert_summary_for_modifier.bug_number = 12345
test_perf_alert_summary_for_modifier.save()

updates, summaries = resolution_modifier_class.update_alerts()

assert updates == {}
assert summaries == {}

@patch("treeherder.perf.auto_perf_sheriffing.performance_alerting.alert_modifier.BugSearcher")
def test_update_alerts_unknown_resolution_maps_to_bug_new(
self,
mock_bug_searcher_class,
resolution_modifier_class,
test_perf_alert_summary_for_modifier,
):
mock_searcher = Mock()
mock_searcher.get_today_date.return_value = datetime.now().date()
mock_searcher.get_bugs.return_value = {
"bugs": [{"id": 12345, "resolution": "UNKNOWN_RESOLUTION", "status": "RESOLVED"}]
}
mock_bug_searcher_class.return_value = mock_searcher

test_perf_alert_summary_for_modifier.bug_number = 12345
test_perf_alert_summary_for_modifier.save()

updates, summaries = resolution_modifier_class.update_alerts()

assert updates == {}
assert summaries == {}

@patch("treeherder.perf.auto_perf_sheriffing.performance_alerting.alert_modifier.BugSearcher")
def test_update_alerts_with_empty_resolution_defaults_to_bug_new(
self,
mock_bug_searcher_class,
resolution_modifier_class,
test_perf_alert_summary_for_modifier,
):
mock_searcher = Mock()
mock_searcher.get_today_date.return_value = datetime.now().date()
mock_searcher.get_bugs.return_value = {
"bugs": [{"id": 12345, "resolution": "", "status": "ASSIGNED"}]
}
mock_bug_searcher_class.return_value = mock_searcher

test_perf_alert_summary_for_modifier.bug_number = 12345
test_perf_alert_summary_for_modifier.save()

updates, summaries = resolution_modifier_class.update_alerts()

assert (
updates[str(test_perf_alert_summary_for_modifier.id)]["bug_status"]
== PerformanceAlertSummary.BUG_NEW
)
assert str(test_perf_alert_summary_for_modifier.id) in summaries

@patch("treeherder.perf.auto_perf_sheriffing.performance_alerting.alert_modifier.BugSearcher")
def test_update_alerts_skips_summary_already_at_target_status(
self,
mock_bug_searcher_class,
resolution_modifier_class,
test_perf_alert_summary_for_modifier,
):
mock_searcher = Mock()
mock_searcher.get_today_date.return_value = datetime.now().date()
mock_searcher.get_bugs.return_value = {
"bugs": [{"id": 12345, "resolution": "FIXED", "status": "RESOLVED"}]
}
mock_bug_searcher_class.return_value = mock_searcher

test_perf_alert_summary_for_modifier.bug_number = 12345
test_perf_alert_summary_for_modifier.bug_status = PerformanceAlertSummary.BUG_FIXED
test_perf_alert_summary_for_modifier.save()

updates, summaries = resolution_modifier_class.update_alerts()

assert str(test_perf_alert_summary_for_modifier.id) not in updates

@patch("treeherder.perf.auto_perf_sheriffing.performance_alerting.alert_modifier.BugSearcher")
def test_update_alerts_exception_handling(
self, mock_bug_searcher_class, resolution_modifier_class, caplog
):
mock_searcher = Mock()
mock_searcher.get_today_date.return_value = datetime.now().date()
mock_searcher.get_bugs.side_effect = Exception("API Error")
mock_bug_searcher_class.return_value = mock_searcher

updates, summaries = resolution_modifier_class.update_alerts()

assert updates == {}
assert summaries == {}
assert "Failed to get bugs for alert resolution updates" in caplog.text
assert "API Error" in caplog.text
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import logging

from treeherder.perf.auto_perf_sheriffing.base_alert_manager import AlertManager
from treeherder.perf.auto_perf_sheriffing.performance_alerting.alert_modifier import (
PerformanceAlertSummaryModifier,
)
from treeherder.perf.models import PerformanceAlertSummary

MODIFIABLE_ALERT_SUMMARY_FIELDS = ("bug_status",)

logger = logging.getLogger(__name__)


class PerformanceAlertManager(AlertManager):
def __init__(self):
super().__init__(bug_manager=None, email_manager=None)

def update_alerts(self, alerts, *args, **kwargs):
alert_updates, summaries_with_updates = (
PerformanceAlertSummaryModifier.get_alert_summary_updates()
)
if not alert_updates:
return

summaries_to_update = set()
fields_to_update = set()

for summary_id, summary in summaries_with_updates.items():
updates = alert_updates.get(summary_id)
if not updates:
continue
for field, value in updates.items():
if field not in MODIFIABLE_ALERT_SUMMARY_FIELDS:
continue
summaries_to_update.add(summary)
fields_to_update.add(field)
logger.info(
f"Summary ID {summary_id}, {field}: {getattr(summary, field)} to {value}"
)
setattr(summary, field, value)

num_updated = PerformanceAlertSummary.objects.bulk_update(
list(summaries_to_update), list(fields_to_update)
)
logger.info(f"{num_updated} summaries updated")

def comment_alert_bugs(self, alerts, *args, **kwargs):
pass

def file_alert_bugs(self, alerts, *args, **kwargs):
pass

def modify_alert_bugs(self, alerts, *args, **kwargs):
pass

def email_alerts(self, alerts, *args, **kwargs):
pass

def house_keeping(self, alerts, *args, **kwargs):
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import logging
from datetime import timedelta

from treeherder.perf.auto_perf_sheriffing.bug_searcher import BugSearcher
from treeherder.perf.models import PerformanceAlertSummary

logger = logging.getLogger(__name__)


class PerformanceAlertSummaryModifier:
updaters = []

@staticmethod
def add(updater_class):
PerformanceAlertSummaryModifier.updaters.append(updater_class)

@staticmethod
def get_updaters():
return PerformanceAlertSummaryModifier.updaters

@staticmethod
def get_alert_summary_updates(*args, **kwargs):
all_updates = {}
all_summaries_to_update = {}
for updater in PerformanceAlertSummaryModifier.get_updaters():
updates, summaries_to_update = updater.update_alerts(**kwargs)
if not updates:
continue
all_updates.update(updates)
all_summaries_to_update.update(summaries_to_update)
return all_updates, all_summaries_to_update


@PerformanceAlertSummaryModifier.add
class ResolutionModifier:
@staticmethod
def update_alerts(**kwargs):
bug_searcher = BugSearcher()
start_date = bug_searcher.get_today_date() - timedelta(days=7)
bug_searcher.set_include_fields(["id", "resolution", "status"])
bug_searcher.set_query(
{
"f1": "keywords",
"o1": "anywords",
"v1": "perf-alert",
"f2": "resolution",
"o2": "changedafter",
"v2": start_date,
}
)

try:
bugs = bug_searcher.get_bugs()
except Exception as e:
logger.warning(f"Failed to get bugs for alert resolution updates: {str(e)}")
return ({}, {})

alert_summaries = PerformanceAlertSummary.objects.filter(
bug_number__in=[bug_info["id"] for bug_info in bugs["bugs"]]
)

bug_status_map = {label: value for value, label in PerformanceAlertSummary.BUG_STATUSES}
bugs_by_id = {bug["id"]: bug for bug in bugs["bugs"]}
updates = {}
summaries_to_update = {}
for summary in alert_summaries:
bug = bugs_by_id.get(summary.bug_number)
if not bug:
continue
new_bug_status = bug_status_map.get(bug["resolution"])
if new_bug_status is None and bug["resolution"] == "":
new_bug_status = PerformanceAlertSummary.BUG_NEW
if new_bug_status is None:
continue
if summary.bug_status == new_bug_status:
continue
updates[str(summary.id)] = {"bug_status": new_bug_status}
summaries_to_update[str(summary.id)] = summary

return updates, summaries_to_update
Loading