Repository: mozilla/bugbug

Issue:
Move exp_queue from repository.py to utils.py
Since it is now being used in other places too and it is not directly related to repository.py, we should move exp_queue to utils.py.


Discussion:

Resolving patch:
diff --git a/bugbug/repository.py b/bugbug/repository.py
--- a/bugbug/repository.py
+++ b/bugbug/repository.py
@@ -14,7 +14,6 @@
 import pickle
 import sys
 import threading
-from collections import deque
 from datetime import datetime
 
 import hglib
@@ -345,59 +344,6 @@ def get_revs(hg, rev_start=0, rev_end="tip"):
     return x.splitlines()
 
 
-class exp_queue:
-    def __init__(self, start_day, maxlen, default):
-        self.list = deque([default] * maxlen, maxlen=maxlen)
-        self.start_day = start_day - (maxlen - 1)
-        self.default = default
-
-    def __deepcopy__(self, memo):
-        result = exp_queue.__new__(exp_queue)
-
-        # We don't need to deepcopy the list, as elements in the list are immutable.
-        result.list = self.list.copy()
-        result.start_day = self.start_day
-        result.default = self.default
-
-        return result
-
-    @property
-    def last_day(self):
-        return self.start_day + (self.list.maxlen - 1)
-
-    def __getitem__(self, day):
-        assert (
-            day >= self.start_day
-        ), f"Can't get a day ({day}) from earlier than start day ({self.start_day})"
-
-        if day < 0:
-            return self.default
-
-        if day > self.last_day:
-            return self.list[-1]
-
-        return self.list[day - self.start_day]
-
-    def __setitem__(self, day, value):
-        if day == self.last_day:
-            self.list[day - self.start_day] = value
-        elif day > self.last_day:
-            last_val = self.list[-1]
-            # We need to extend the list except for 2 elements (the last, which
-            # is going to be the same, and the one we are adding now).
-            range_end = min(day - self.last_day, self.list.maxlen) - 2
-            if range_end > 0:
-                self.list.extend(last_val for _ in range(range_end))
-
-            self.start_day = day - (self.list.maxlen - 1)
-
-            self.list.append(value)
-        else:
-            assert False, "Can't insert in the past"
-
-        assert day == self.last_day
-
-
 def calculate_experiences(commits, first_pushdate, save=True):
     print(f"Analyzing experiences from {len(commits)} commits...")
 
@@ -429,7 +375,7 @@ def get_experience(exp_type, commit_type, item, day, default):
             experiences[exp_type][commit_type] = {}
 
         if item not in experiences[exp_type][commit_type]:
-            experiences[exp_type][commit_type][item] = exp_queue(
+            experiences[exp_type][commit_type][item] = utils.ExpQueue(
                 day, EXPERIENCE_TIMESPAN + 1, default
             )
 
diff --git a/bugbug/utils.py b/bugbug/utils.py
--- a/bugbug/utils.py
+++ b/bugbug/utils.py
@@ -7,6 +7,7 @@
 import json
 import os
 import time
+from collections import deque
 
 import dateutil.parser
 import numpy as np
@@ -184,3 +185,56 @@ def default(self, obj):
             pass
 
         return super().default(self, obj)
+
+
+class ExpQueue:
+    def __init__(self, start_day, maxlen, default):
+        self.list = deque([default] * maxlen, maxlen=maxlen)
+        self.start_day = start_day - (maxlen - 1)
+        self.default = default
+
+    def __deepcopy__(self, memo):
+        result = ExpQueue.__new__(ExpQueue)
+
+        # We don't need to deepcopy the list, as elements in the list are immutable.
+        result.list = self.list.copy()
+        result.start_day = self.start_day
+        result.default = self.default
+
+        return result
+
+    @property
+    def last_day(self):
+        return self.start_day + (self.list.maxlen - 1)
+
+    def __getitem__(self, day):
+        assert (
+            day >= self.start_day
+        ), f"Can't get a day ({day}) from earlier than start day ({self.start_day})"
+
+        if day < 0:
+            return self.default
+
+        if day > self.last_day:
+            return self.list[-1]
+
+        return self.list[day - self.start_day]
+
+    def __setitem__(self, day, value):
+        if day == self.last_day:
+            self.list[day - self.start_day] = value
+        elif day > self.last_day:
+            last_val = self.list[-1]
+            # We need to extend the list except for 2 elements (the last, which
+            # is going to be the same, and the one we are adding now).
+            range_end = min(day - self.last_day, self.list.maxlen) - 2
+            if range_end > 0:
+                self.list.extend(last_val for _ in range(range_end))
+
+            self.start_day = day - (self.list.maxlen - 1)
+
+            self.list.append(value)
+        else:
+            assert False, "Can't insert in the past"
+
+        assert day == self.last_day


Test patch:
diff --git a/scripts/test_scheduling_history_retriever.py b/scripts/test_scheduling_history_retriever.py
--- a/scripts/test_scheduling_history_retriever.py
+++ b/scripts/test_scheduling_history_retriever.py
@@ -14,7 +14,7 @@
 from tqdm import tqdm
 
 from bugbug import db, repository, test_scheduling
-from bugbug.utils import download_check_etag, zstd_compress
+from bugbug.utils import ExpQueue, download_check_etag, zstd_compress
 
 basicConfig(level=INFO)
 logger = getLogger(__name__)
@@ -97,9 +97,7 @@ def retrieve_test_scheduling_history(self):
 
         def get_past_failures(task, push_num):
             if task not in past_failures:
-                past_failures[task] = repository.exp_queue(
-                    push_num, HISTORICAL_TIMESPAN + 1, 0
-                )
+                past_failures[task] = ExpQueue(push_num, HISTORICAL_TIMESPAN + 1, 0)
 
             return past_failures[task][push_num]
 
diff --git a/tests/test_repository.py b/tests/test_repository.py
--- a/tests/test_repository.py
+++ b/tests/test_repository.py
@@ -468,71 +468,6 @@ def test_get_directories():
     ) == {"dom", "tools", "tools/code-coverage"}
 
 
-def test_exp_queue():
-    q = repository.exp_queue(0, 4, 0)
-    q[0] = 1
-    assert q[0] == 1
-    q[0] = 2
-    assert q[0] == 2
-
-    q = repository.exp_queue(366, 91, 0)
-    assert q[366] == 0
-    assert q[276] == 0
-    q[366] += 1
-    assert q[367] == 1
-    assert q[277] == 0
-
-    q = repository.exp_queue(0, 4, 0)
-    assert q[0] == 0
-    q[0] += 1
-    assert q[0] == 1
-    q[0] += 1
-    assert q[0] == 2
-    assert q[1] == 2
-    q[1] += 1
-    assert q[1] == 3
-    assert q[9] == 3
-    q[9] += 1
-    assert q[9] == 4
-    assert q[6] == 3
-    assert q[11] == 4
-    q[11] += 1
-    assert q[11] == 5
-    q[12] += 1
-    assert q[12] == 6
-    q[13] += 1
-    assert q[13] == 7
-    q[14] += 1
-    assert q[14] == 8
-    q[15] += 1
-    assert q[15] == 9
-
-    q = repository.exp_queue(0, 4, 0)
-    assert q[0] == 0
-    q[0] += 1
-    assert q[0] == 1
-    assert q[1] == 1
-    assert q[9] == 1
-    q[9] += 1
-    assert q[9] == 2
-    assert q[10] == 2
-    assert q[8] == 1
-    assert q[7] == 1
-    assert q[6] == 1
-
-    q = repository.exp_queue(9, 3, 0)
-    assert q[8] == 0
-    assert q[9] == 0
-    q[9] += 1
-    assert q[11] == 1
-    assert q[10] == 1
-    assert q[8] == 0
-    assert q[11] == 1
-    assert q[8] == 0
-    assert q[9] == 1
-    assert q[12] == 1
-
-
 def test_set_commits_to_ignore(tmpdir):
     tmp_path = tmpdir.strpath
 
diff --git a/tests/test_utils.py b/tests/test_utils.py
new file mode 100644
--- /dev/null
+++ b/tests/test_utils.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from bugbug import utils
+
+
+def test_exp_queue():
+    q = utils.ExpQueue(0, 4, 0)
+    q[0] = 1
+    assert q[0] == 1
+    q[0] = 2
+    assert q[0] == 2
+
+    q = utils.ExpQueue(366, 91, 0)
+    assert q[366] == 0
+    assert q[276] == 0
+    q[366] += 1
+    assert q[367] == 1
+    assert q[277] == 0
+
+    q = utils.ExpQueue(0, 4, 0)
+    assert q[0] == 0
+    q[0] += 1
+    assert q[0] == 1
+    q[0] += 1
+    assert q[0] == 2
+    assert q[1] == 2
+    q[1] += 1
+    assert q[1] == 3
+    assert q[9] == 3
+    q[9] += 1
+    assert q[9] == 4
+    assert q[6] == 3
+    assert q[11] == 4
+    q[11] += 1
+    assert q[11] == 5
+    q[12] += 1
+    assert q[12] == 6
+    q[13] += 1
+    assert q[13] == 7
+    q[14] += 1
+    assert q[14] == 8
+    q[15] += 1
+    assert q[15] == 9
+
+    q = utils.ExpQueue(0, 4, 0)
+    assert q[0] == 0
+    q[0] += 1
+    assert q[0] == 1
+    assert q[1] == 1
+    assert q[9] == 1
+    q[9] += 1
+    assert q[9] == 2
+    assert q[10] == 2
+    assert q[8] == 1
+    assert q[7] == 1
+    assert q[6] == 1
+
+    q = utils.ExpQueue(9, 3, 0)
+    assert q[8] == 0
+    assert q[9] == 0
+    q[9] += 1
+    assert q[11] == 1
+    assert q[10] == 1
+    assert q[8] == 0
+    assert q[11] == 1
+    assert q[8] == 0
+    assert q[9] == 1
+    assert q[12] == 1