Repository: repobee/repobee-csvgrades

Issue:
Crash multiple issues matching the same grade spec
This causes a crash when pushing to the heap, as the issues themselves are then used for comparison, and `<` isn't implemented for them.

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/repobee/repobee-csvgrades/pull/23?src=pr&el=h1) Report
> Merging [#23](https://codecov.io/gh/repobee/repobee-csvgrades/pull/23?src=pr&el=desc) (fd7b504) into [master](https://codecov.io/gh/repobee/repobee-csvgrades/commit/71270f80f42a801b6405e6778da9401af102ed4f?el=desc) (71270f8) will **increase** coverage by `0.29%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/repobee/repobee-csvgrades/pull/23/graphs/tree.svg?width=650&height=150&src=pr&token=4Oj0FO36Xc)](https://codecov.io/gh/repobee/repobee-csvgrades/pull/23?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master      #23      +/-   ##
==========================================
+ Coverage   89.65%   89.95%   +0.29%     
==========================================
  Files           8        8              
  Lines         203      209       +6     
  Branches       36       37       +1     
==========================================
+ Hits          182      188       +6     
  Misses         15       15              
  Partials        6        6              
```


| [Impacted Files](https://codecov.io/gh/repobee/repobee-csvgrades/pull/23?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [repobee\_csvgrades/\_marker.py](https://codecov.io/gh/repobee/repobee-csvgrades/pull/23/diff?src=pr&el=tree#diff-cmVwb2JlZV9jc3ZncmFkZXMvX21hcmtlci5weQ==) | `94.20% <100.00%> (+0.55%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/repobee/repobee-csvgrades/pull/23?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/repobee/repobee-csvgrades/pull/23?src=pr&el=footer). Last update [71270f8...fd7b504](https://codecov.io/gh/repobee/repobee-csvgrades/pull/23?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/repobee_csvgrades/_marker.py b/repobee_csvgrades/_marker.py
index 4c70e7e..6ed87a4 100644
--- a/repobee_csvgrades/_marker.py
+++ b/repobee_csvgrades/_marker.py
@@ -10,6 +10,7 @@ import itertools
 import re
 import heapq
 import contextlib
+import dataclasses
 from typing import List
 
 import daiquiri
@@ -17,9 +18,18 @@ import daiquiri
 import repobee_plug as plug
 
 from repobee_csvgrades import _exception
+from repobee_csvgrades import _containers
 
 LOGGER = daiquiri.getLogger(__file__)
 
+@dataclasses.dataclass(frozen=True, order=True)
+class _SpeccedIssue:
+    """Wrapper for an issue and associated grade spec, which is ordered by the
+    gradespec.
+    """
+
+    spec: _containers.GradeSpec = dataclasses.field(compare=True)
+    issue: plug.platform.Issue = dataclasses.field(compare=False)
 
 def get_authorized_issues(issues, teachers, grade_spec, repo_name):
     matched_issues = [
@@ -61,13 +71,13 @@ def mark_grade(
     issue_heap = []
     for spec in grade_specs:
         for issue in get_authorized_issues(issues, teachers, spec, repo_name):
-            heapq.heappush(issue_heap, (spec, issue))
+            heapq.heappush(issue_heap, _SpeccedIssue(spec, issue))
 
     graded_students = []
     author = None
     symbol = None
     if issue_heap:
-        spec, issue = issue_heap[0]
+        spec, issue = issue_heap[0].spec, issue_heap[0].issue
         for student in team.members:
             with log_error(_exception.GradingError):
                 old = grades.set(student, master_repo_name, spec)
diff --git a/requirements.txt b/requirements.txt
index a0618ec..3c3c9c4 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,1 +1,2 @@
-repobee>=3.0.0
+repobee>=3.5.0
+dataclasses>='0.7';python_version<'3.7'
diff --git a/setup.py b/setup.py
index a833643..c9db413 100644
--- a/setup.py
+++ b/setup.py
@@ -11,7 +11,7 @@ with open("repobee_csvgrades/__version.py", mode="r", encoding="utf-8") as f:
     assert re.match(r"^\d+(\.\d+){2}(-(alpha|beta|rc)(\.\d+)?)?$", __version__)
 
 test_requirements = ["pytest", "pytest-cov", "pytest-mock", "tox"]
-required = ["repobee>=3.0.0"]
+required = ["repobee>=3.5.0", "dataclasses>='0.7';python_version<'3.7'"]
 
 setup(
     name="repobee-csvgrades",


Test patch:
diff --git a/tests/test_csvgrades.py b/tests/test_csvgrades.py
index a5e8eb6..a659a7d 100644
--- a/tests/test_csvgrades.py
+++ b/tests/test_csvgrades.py
@@ -36,11 +36,11 @@ FAIL_GRADESPEC_FORMAT = "2:F:[Ff]ail"
 KOMP_GRADESPEC_FORMAT = "3:K:[Kk]omplettering"
 
 
-def create_pass_hookresult(author):
+def create_pass_hookresult(author, number=3):
     pass_issue = plug.Issue(
         title="Pass",
         body="This is a pass",
-        number=3,
+        number=number,
         created_at=datetime(1992, 9, 19),
         author=author,
     )
@@ -79,6 +79,17 @@ def create_komp_and_pass_hookresult(author):
     )
 
 
+def create_duplicated_pass_hookresult(author):
+    first_pass = create_pass_hookresult(author, number=3)
+    second_pass = create_pass_hookresult(author, number=4)
+    return plug.Result(
+        name="list-issues",
+        status=plug.Status.SUCCESS,
+        msg=None,
+        data={**first_pass.data, **second_pass.data},
+    )
+
+
 @pytest.fixture
 def mocked_hook_results(mocker):
     """Hook results with passes for glassey-glennol in week-1 and week-2, and
@@ -91,7 +102,7 @@ def mocked_hook_results(mocker):
         for team, repo_name, result in [
             (slarse, "week-1", create_komp_hookresult(SLARSE_TA)),
             (slarse, "week-2", create_komp_hookresult(SLARSE_TA)),
-            (slarse, "week-4", create_pass_hookresult(SLARSE_TA)),
+            (slarse, "week-4", create_duplicated_pass_hookresult(SLARSE_TA)),
             (slarse, "week-6", create_komp_and_pass_hookresult(SLARSE_TA)),
             (
                 glassey_glennol,