From 2589b1b8341ac9789092bc22675f3b9f346835d5 Mon Sep 17 00:00:00 2001 From: Amirkeivan Mohtashami Date: Fri, 1 May 2026 12:43:16 +0200 Subject: [PATCH] Use rounded subtask scores for computing task scores and store rounded scores instead of unrounded ones. --- cms/db/task.py | 3 +- cms/grading/scoretypes/__init__.py | 5 ++- cms/grading/scoretypes/abc.py | 34 ++++++++++----- cms/grading/scoring.py | 10 +++-- cms/server/admin/handlers/contestranking.py | 2 +- cms/server/admin/templates/submission.html | 4 +- cms/server/contest/formatting.py | 4 +- cms/server/contest/handlers/tasksubmission.py | 24 +++++------ .../contest/templates/macro/submission.html | 8 ++-- .../contest/templates/task_submissions.html | 8 ++-- cms/service/ProxyService.py | 1 - .../grading/scoretypes/GroupMinTest.py | 40 +++++++++--------- .../grading/scoretypes/GroupMulTest.py | 40 +++++++++--------- .../grading/scoretypes/GroupThresholdTest.py | 42 +++++++++---------- .../unit_tests/grading/scoretypes/SumTest.py | 12 +++--- .../unit_tests/grading/scoring_test.py | 28 ++----------- 16 files changed, 126 insertions(+), 139 deletions(-) diff --git a/cms/db/task.py b/cms/db/task.py index c753efdce1..acd83b2f11 100644 --- a/cms/db/task.py +++ b/cms/db/task.py @@ -500,7 +500,8 @@ def score_type_object(self) -> "ScoreType": from cms.grading.scoretypes import get_score_type # This can raise. self._cached_score_type_object = get_score_type( - self.score_type, self.score_type_parameters, public_testcases) + self.score_type, self.score_type_parameters, public_testcases, + self.task.score_precision) # If an exception is raised these updates don't take place: # that way, next time this property is accessed, we get a # cache miss again and the same exception is raised again. diff --git a/cms/grading/scoretypes/__init__.py b/cms/grading/scoretypes/__init__.py index cdcaa9f642..eb97f6ecb7 100644 --- a/cms/grading/scoretypes/__init__.py +++ b/cms/grading/scoretypes/__init__.py @@ -46,7 +46,8 @@ def get_score_type_class(name: str): def get_score_type( - name: str, parameters: object, public_testcases: dict[str, bool] + name: str, parameters: object, public_testcases: dict[str, bool], + score_precision: int, ) -> ScoreType: """Construct the ScoreType specified by parameters. @@ -63,4 +64,4 @@ def get_score_type( """ class_ = get_score_type_class(name) - return class_(parameters, public_testcases) + return class_(parameters, public_testcases, score_precision) diff --git a/cms/grading/scoretypes/abc.py b/cms/grading/scoretypes/abc.py index aa1d1eb92b..127067f1d4 100644 --- a/cms/grading/scoretypes/abc.py +++ b/cms/grading/scoretypes/abc.py @@ -59,7 +59,8 @@ class ScoreType(metaclass=ABCMeta): TEMPLATE = "" - def __init__(self, parameters: object, public_testcases: dict[str, bool]): + def __init__(self, parameters: object, public_testcases: dict[str, bool], + score_precision: int): """Initializer. parameters: format is specified in the subclasses. @@ -70,6 +71,7 @@ def __init__(self, parameters: object, public_testcases: dict[str, bool]): """ self.parameters = parameters self.public_testcases = public_testcases + self.score_precision = score_precision # Preload the maximum possible scores. try: @@ -87,7 +89,6 @@ def format_score( score: float, max_score: float, unused_score_details: object, - score_precision: int, translation: Translation = DEFAULT_TRANSLATION, ) -> str: """Produce the string of the score that is shown in CWS. @@ -102,16 +103,14 @@ def format_score( max_score: the maximum score that can be achieved. unused_score_details: the opaque data structure that the ScoreType produced for the submission when scoring it. - score_precision: the maximum number of digits of the - fractional digits to show. translation: the translation to use. return: the message to show. """ return "%s / %s" % ( - translation.format_decimal(round(score, score_precision)), - translation.format_decimal(round(max_score, score_precision))) + translation.format_decimal(score), + translation.format_decimal(max_score)) def get_html_details( self, @@ -348,9 +347,17 @@ class ScoreTypeGroup(ScoreTypeAlone): def get_max_score(self, group_parameter: ScoreTypeGroupParameters) -> float: if isinstance(group_parameter, tuple) or isinstance(group_parameter, list): - return group_parameter[0] + score = group_parameter[0] else: - return group_parameter["max_score"] + score = group_parameter["max_score"] + assert ( + round( + score, + self.score_precision + ) == score + ), (f"The max score for a subtask" + "has more precision than the task allows.") + return score def get_testcases(self, group_parameter: ScoreTypeGroupParameters) -> int | str | list[str]: if isinstance(group_parameter, tuple) or isinstance(group_parameter, list): @@ -507,7 +514,7 @@ def compute_score(self, submission_result): tc["show_in_oi_restricted_feedback"] = ( tc["idx"] == tc_first_lowest_idx) - score += st_score + score += rounded_score subtasks.append({ "idx": st_idx, # We store the fraction so that an "example" testcase @@ -519,12 +526,17 @@ def compute_score(self, submission_result): "max_score": self.get_max_score(parameter), "testcases": testcases}) if all(self.public_testcases[tc_idx] for tc_idx in target): - public_score += st_score + public_score += rounded_score public_subtasks.append(subtasks[-1]) else: public_subtasks.append({"idx": st_idx, "testcases": public_testcases}) - ranking_details.append("%g" % st_score) + ranking_details.append("%g" % rounded_score) + + # The following line should be unnecessary since subtask scores + # are rounded. However we are using floats not Decimals + # and this can cause errors. So we round again to be sure. + score = round(score, score_precision) return score, subtasks, public_score, public_subtasks, ranking_details diff --git a/cms/grading/scoring.py b/cms/grading/scoring.py index 114f560e41..b74782620a 100644 --- a/cms/grading/scoring.py +++ b/cms/grading/scoring.py @@ -108,7 +108,6 @@ def task_score( task: Task, public: bool = False, only_tokened: bool = False, - rounded: bool = False, ) -> tuple[float, bool]: """Return the score of a contest's user on a task. @@ -122,7 +121,6 @@ def task_score( at the results of tokened submissions (that is, the score that the user would obtain if all non-tokened submissions scored 0.0, or equivalently had not been scored yet). - rounded: if True, round the score to the task's score_precision. return: the score of user on task, and True if not all submissions of the participation in the task have been scored. @@ -177,8 +175,12 @@ def task_score( score = _task_score_max_tokened_last(score_details_tokened) else: raise ValueError("Unknown score mode '%s'" % task.score_mode) - if rounded: - score = round(score, task.score_precision) + + # The following line should be unnecessary since subtask scores + # are rounded. However we are using floats not Decimals + # and this can cause errors. So we round again to be sure. + score = round(score, task.score_precision) + return score, partial diff --git a/cms/server/admin/handlers/contestranking.py b/cms/server/admin/handlers/contestranking.py index 2adc4f2600..ca8806a67a 100644 --- a/cms/server/admin/handlers/contestranking.py +++ b/cms/server/admin/handlers/contestranking.py @@ -66,7 +66,7 @@ def get(self, contest_id, format="online"): total_score = 0.0 partial = False for task in self.contest.tasks: - t_score, t_partial = task_score(p, task, rounded=True) + t_score, t_partial = task_score(p, task) p.scores.append((t_score, t_partial)) total_score += t_score partial = partial or t_partial diff --git a/cms/server/admin/templates/submission.html b/cms/server/admin/templates/submission.html index 727e043671..7213ab56d1 100644 --- a/cms/server/admin/templates/submission.html +++ b/cms/server/admin/templates/submission.html @@ -129,9 +129,9 @@

Submission details

Score as seen by user {% if s.token is not none %} - {{ st.format_score(sr.score, st.max_score, sr.score_details, s.task.score_precision) }} + {{ st.format_score(sr.score, st.max_score, sr.score_details) }} {% else %} - {{ st.format_score(sr.public_score, st.max_public_score, sr.public_score_details, s.task.score_precision) }} + {{ st.format_score(sr.public_score, st.max_public_score, sr.public_score_details) }} {% endif %} diff --git a/cms/server/contest/formatting.py b/cms/server/contest/formatting.py index 2b670fa35d..7b04bc1b0c 100644 --- a/cms/server/contest/formatting.py +++ b/cms/server/contest/formatting.py @@ -130,7 +130,7 @@ def format_token_rules( return result -def get_score_class(score: float, max_score: float, score_precision: int) -> str: +def get_score_class(score: float, max_score: float) -> str: """Return a CSS class to visually represent the score/max_score score: the score of the submission. @@ -139,8 +139,6 @@ def get_score_class(score: float, max_score: float, score_precision: int) -> str return: class name """ - score = round(score, score_precision) - max_score = round(max_score, score_precision) if score <= 0: return "score_0" elif score >= max_score: diff --git a/cms/server/contest/handlers/tasksubmission.py b/cms/server/contest/handlers/tasksubmission.py index a5cdedee4b..9a88f41dbd 100644 --- a/cms/server/contest/handlers/tasksubmission.py +++ b/cms/server/contest/handlers/tasksubmission.py @@ -143,9 +143,9 @@ def get(self, task_name): ) public_score, is_public_score_partial = task_score( - participation, task, public=True, rounded=True) + participation, task, public=True) tokened_score, is_tokened_score_partial = task_score( - participation, task, only_tokened=True, rounded=True) + participation, task, only_tokened=True) # These two should be the same, anyway. is_score_partial = is_public_score_partial or is_tokened_score_partial @@ -221,9 +221,9 @@ def add_task_score(self, participation: Participation, task: Task, data: dict): .options(joinedload(Submission.results))\ .all() data["task_public_score"], public_score_is_partial = \ - task_score(participation, task, public=True, rounded=True) + task_score(participation, task, public=True) data["task_tokened_score"], tokened_score_is_partial = \ - task_score(participation, task, only_tokened=True, rounded=True) + task_score(participation, task, only_tokened=True) # These two should be the same, anyway. data["task_score_is_partial"] = \ public_score_is_partial or tokened_score_is_partial @@ -267,26 +267,22 @@ def get(self, task_name, opaque_id): score_type = task.active_dataset.score_type_object if score_type.max_public_score > 0: - data["max_public_score"] = \ - round(score_type.max_public_score, task.score_precision) + data["max_public_score"] = score_type.max_public_score if data["status"] == SubmissionResult.SCORED: - data["public_score"] = \ - round(sr.public_score, task.score_precision) + data["public_score"] = sr.public_score data["public_score_message"] = score_type.format_score( sr.public_score, score_type.max_public_score, - sr.public_score_details, task.score_precision, + sr.public_score_details, translation=self.translation) if score_type.max_public_score < score_type.max_score: - data["max_score"] = \ - round(score_type.max_score, task.score_precision) + data["max_score"] = score_type.max_score if data["status"] == SubmissionResult.SCORED \ and (submission.token is not None or self.r_params["actual_phase"] == 3): - data["score"] = \ - round(sr.score, task.score_precision) + data["score"] = sr.score data["score_message"] = score_type.format_score( sr.score, score_type.max_score, - sr.score_details, task.score_precision, + sr.score_details, translation=self.translation) self.write(data) diff --git a/cms/server/contest/templates/macro/submission.html b/cms/server/contest/templates/macro/submission.html index 172cdcb691..944ade423e 100644 --- a/cms/server/contest/templates/macro/submission.html +++ b/cms/server/contest/templates/macro/submission.html @@ -169,8 +169,8 @@ {% if score_type is defined and score_type.max_public_score > 0 %} {% if status == SubmissionResult.SCORED %} - - {{ score_type.format_score(sr.public_score, score_type.max_public_score, sr.public_score_details, task.score_precision, translation=translation) }} + + {{ score_type.format_score(sr.public_score, score_type.max_public_score, sr.public_score_details, translation=translation) }} {% else %} @@ -180,8 +180,8 @@ {% endif %} {% if score_type is defined and score_type.max_public_score < score_type.max_score %} {% if status == SubmissionResult.SCORED and (s.token is not none or actual_phase == 3) %} - - {{ score_type.format_score(sr.score, score_type.max_score, sr.score_details, task.score_precision, translation=translation) }} + + {{ score_type.format_score(sr.score, score_type.max_score, sr.score_details, translation=translation) }} {% else %} diff --git a/cms/server/contest/templates/task_submissions.html b/cms/server/contest/templates/task_submissions.html index dddba88f8c..6ffc3d1ba3 100644 --- a/cms/server/contest/templates/task_submissions.html +++ b/cms/server/contest/templates/task_submissions.html @@ -193,7 +193,7 @@

{% trans name=task.title, short_name=task.name %}{{ name }} ({{ short_name } {% if score_type.max_public_score > 0 %} {# Show the public score (alone, if everything is public or tokens are disabled, or together with the tokened score). #}
+ class="{{ "span6" if two_task_scores else "span12" }} well well-small task_score {{ get_score_class(public_score, score_type.max_public_score) }}"> {% if score_type.max_public_score == score_type.max_score %} {% trans %}Score:{% endtrans %} @@ -203,7 +203,7 @@

{% trans name=task.title, short_name=task.name %}{{ name }} ({{ short_name }
- {{ score_type.format_score(public_score, score_type.max_public_score, none, task.score_precision, translation=translation) }} + {{ score_type.format_score(public_score, score_type.max_public_score, none, translation=translation) }} {% if is_score_partial %} {% endif %} @@ -214,7 +214,7 @@

{% trans name=task.title, short_name=task.name %}{{ name }} ({{ short_name } {% if score_type.max_public_score < score_type.max_score %} {# Show the tokened score (alone if everything is non-public, or together with the public score). #}
+ class="{{ "span6" if two_task_scores else "span12" }} well well-small task_score {{ get_score_class(tokened_score, score_type.max_score) if can_use_tokens else "undefined" }}"> {% if can_use_tokens %} {% trans %}Score of tokened submissions:{% endtrans %} @@ -225,7 +225,7 @@

{% trans name=task.title, short_name=task.name %}{{ name }} ({{ short_name }
{% if can_use_tokens %} - {{ score_type.format_score(tokened_score, score_type.max_score, none, task.score_precision, translation=translation) }} + {{ score_type.format_score(tokened_score, score_type.max_score, none, translation=translation) }} {% if is_score_partial %} {% endif %} diff --git a/cms/service/ProxyService.py b/cms/service/ProxyService.py index 6b8dfa7665..f59ea1db87 100644 --- a/cms/service/ProxyService.py +++ b/cms/service/ProxyService.py @@ -405,7 +405,6 @@ def operations_for_score(self, submission: Submission): # This check is probably useless. if submission_result is not None and submission_result.scored(): - # We're sending the unrounded score to RWS subchange_data["score"] = submission_result.score subchange_data["extra"] = submission_result.ranking_score_details diff --git a/cmstestsuite/unit_tests/grading/scoretypes/GroupMinTest.py b/cmstestsuite/unit_tests/grading/scoretypes/GroupMinTest.py index 0104e8bd49..294471c750 100755 --- a/cmstestsuite/unit_tests/grading/scoretypes/GroupMinTest.py +++ b/cmstestsuite/unit_tests/grading/scoretypes/GroupMinTest.py @@ -42,47 +42,47 @@ def setUp(self): def test_paramaters_correct(self): """Test that correct parameters do not throw.""" - GroupMin([], self._public_testcases) - GroupMin([[40, 2], [60.0, 2]], self._public_testcases) - GroupMin([[40, "1_*"], [60.0, "2_*"]], self._public_testcases) + GroupMin([], self._public_testcases, 2) + GroupMin([[40, 2], [60.0, 2]], self._public_testcases, 2) + GroupMin([[40, "1_*"], [60.0, "2_*"]], self._public_testcases, 2) def test_paramaters_invalid_types(self): with self.assertRaises(ValueError): - GroupMin([1], self._public_testcases) + GroupMin([1], self._public_testcases, 2) with self.assertRaises(ValueError): - GroupMin(1, self._public_testcases) + GroupMin(1, self._public_testcases, 2) def test_paramaters_invalid_wrong_item_len(self): with self.assertRaises(ValueError): - GroupMin([[]], self._public_testcases) + GroupMin([[]], self._public_testcases, 2) with self.assertRaises(ValueError): - GroupMin([[1]], self._public_testcases) + GroupMin([[1]], self._public_testcases, 2) @unittest.skip("Not yet detected.") def test_paramaters_invalid_wrong_item_len_not_caught(self): with self.assertRaises(ValueError): - GroupMin([[1, 2, 3]], self._public_testcases) + GroupMin([[1, 2, 3]], self._public_testcases, 2) def test_parameter_invalid_wrong_max_score_type(self): with self.assertRaises(ValueError): - GroupMin([["a", 10]], self._public_testcases) + GroupMin([["a", 10]], self._public_testcases, 2) def test_parameter_invalid_wrong_testcases_type(self): with self.assertRaises(ValueError): - GroupMin([[100, 1j]], self._public_testcases) + GroupMin([[100, 1j]], self._public_testcases, 2) def test_parameter_invalid_inconsistent_testcases_type(self): with self.assertRaises(ValueError): - GroupMin([[40, 10], [40, "1_*"]], self._public_testcases) + GroupMin([[40, 10], [40, "1_*"]], self._public_testcases, 2) @unittest.skip("Not yet detected.") def test_paramaters_invalid_testcases_too_many(self): with self.assertRaises(ValueError): - GroupMin([[100, 20]], self._public_testcases) + GroupMin([[100, 20]], self._public_testcases, 2) def test_parameter_invalid_testcases_regex_no_match_type(self): with self.assertRaises(ValueError): - GroupMin([[100, "9_*"]], self._public_testcases) + GroupMin([[100, "9_*"]], self._public_testcases, 2) def test_max_scores_regexp(self): """Test max score is correct when groups are regexp-defined.""" @@ -93,19 +93,19 @@ def test_max_scores_regexp(self): # Only group 1_* is public. public_testcases = dict(self._public_testcases) - self.assertEqual(GroupMin(parameters, public_testcases).max_scores(), + self.assertEqual(GroupMin(parameters, public_testcases, 2).max_scores(), (s1 + s2 + s3, s1, header)) # All groups are public for testcase in public_testcases.keys(): public_testcases[testcase] = True - self.assertEqual(GroupMin(parameters, public_testcases).max_scores(), + self.assertEqual(GroupMin(parameters, public_testcases, 2).max_scores(), (s1 + s2 + s3, s1 + s2 + s3, header)) # No groups are public for testcase in public_testcases.keys(): public_testcases[testcase] = False - self.assertEqual(GroupMin(parameters, public_testcases).max_scores(), + self.assertEqual(GroupMin(parameters, public_testcases, 2).max_scores(), (s1 + s2 + s3, 0, header)) def test_max_scores_number(self): @@ -117,25 +117,25 @@ def test_max_scores_number(self): # Only group 1_* is public. public_testcases = dict(self._public_testcases) - self.assertEqual(GroupMin(parameters, public_testcases).max_scores(), + self.assertEqual(GroupMin(parameters, public_testcases, 2).max_scores(), (s1 + s2 + s3, s1, header)) # All groups are public for testcase in public_testcases.keys(): public_testcases[testcase] = True - self.assertEqual(GroupMin(parameters, public_testcases).max_scores(), + self.assertEqual(GroupMin(parameters, public_testcases, 2).max_scores(), (s1 + s2 + s3, s1 + s2 + s3, header)) # No groups are public for testcase in public_testcases.keys(): public_testcases[testcase] = False - self.assertEqual(GroupMin(parameters, public_testcases).max_scores(), + self.assertEqual(GroupMin(parameters, public_testcases, 2).max_scores(), (s1 + s2 + s3, 0.0, header)) def test_compute_score(self): s1, s2, s3 = 10.5, 30.5, 59 parameters = [[0, "0_*"], [s1, "1_*"], [s2, "2_*"], [s3, "3_*"]] - gmin = GroupMin(parameters, self._public_testcases) + gmin = GroupMin(parameters, self._public_testcases, 2) sr = self.get_submission_result(self._public_testcases) # All correct. diff --git a/cmstestsuite/unit_tests/grading/scoretypes/GroupMulTest.py b/cmstestsuite/unit_tests/grading/scoretypes/GroupMulTest.py index dbcb44e8b5..c163792d39 100755 --- a/cmstestsuite/unit_tests/grading/scoretypes/GroupMulTest.py +++ b/cmstestsuite/unit_tests/grading/scoretypes/GroupMulTest.py @@ -42,47 +42,47 @@ def setUp(self): def test_paramaters_correct(self): """Test that correct parameters do not throw.""" - GroupMul([], self._public_testcases) - GroupMul([[40, 2], [60.0, 2]], self._public_testcases) - GroupMul([[40, "1_*"], [60.0, "2_*"]], self._public_testcases) + GroupMul([], self._public_testcases, 2) + GroupMul([[40, 2], [60.0, 2]], self._public_testcases, 2) + GroupMul([[40, "1_*"], [60.0, "2_*"]], self._public_testcases, 2) def test_paramaters_invalid_types(self): with self.assertRaises(ValueError): - GroupMul([1], self._public_testcases) + GroupMul([1], self._public_testcases, 2) with self.assertRaises(ValueError): - GroupMul(1, self._public_testcases) + GroupMul(1, self._public_testcases, 2) def test_paramaters_invalid_wrong_item_len(self): with self.assertRaises(ValueError): - GroupMul([[]], self._public_testcases) + GroupMul([[]], self._public_testcases, 2) with self.assertRaises(ValueError): - GroupMul([[1]], self._public_testcases) + GroupMul([[1]], self._public_testcases, 2) @unittest.skip("Not yet detected.") def test_paramaters_invalid_wrong_item_len_not_caught(self): with self.assertRaises(ValueError): - GroupMul([[1, 2, 3]], self._public_testcases) + GroupMul([[1, 2, 3]], self._public_testcases, 2) def test_parameter_invalid_wrong_max_score_type(self): with self.assertRaises(ValueError): - GroupMul([["a", 10]], self._public_testcases) + GroupMul([["a", 10]], self._public_testcases, 2) def test_parameter_invalid_wrong_testcases_type(self): with self.assertRaises(ValueError): - GroupMul([[100, 1j]], self._public_testcases) + GroupMul([[100, 1j]], self._public_testcases, 2) def test_parameter_invalid_inconsistent_testcases_type(self): with self.assertRaises(ValueError): - GroupMul([[40, 10], [40, "1_*"]], self._public_testcases) + GroupMul([[40, 10], [40, "1_*"]], self._public_testcases, 2) @unittest.skip("Not yet detected.") def test_paramaters_invalid_testcases_too_many(self): with self.assertRaises(ValueError): - GroupMul([[100, 20]], self._public_testcases) + GroupMul([[100, 20]], self._public_testcases, 2) def test_parameter_invalid_testcases_regex_no_match_type(self): with self.assertRaises(ValueError): - GroupMul([[100, "9_*"]], self._public_testcases) + GroupMul([[100, "9_*"]], self._public_testcases, 2) def test_max_scores_regexp(self): """Test max score is correct when groups are regexp-defined.""" @@ -93,19 +93,19 @@ def test_max_scores_regexp(self): # Only group 1_* is public. public_testcases = dict(self._public_testcases) - self.assertEqual(GroupMul(parameters, public_testcases).max_scores(), + self.assertEqual(GroupMul(parameters, public_testcases, 2).max_scores(), (s1 + s2 + s3, s1, header)) # All groups are public for testcase in public_testcases.keys(): public_testcases[testcase] = True - self.assertEqual(GroupMul(parameters, public_testcases).max_scores(), + self.assertEqual(GroupMul(parameters, public_testcases, 2).max_scores(), (s1 + s2 + s3, s1 + s2 + s3, header)) # No groups are public for testcase in public_testcases.keys(): public_testcases[testcase] = False - self.assertEqual(GroupMul(parameters, public_testcases).max_scores(), + self.assertEqual(GroupMul(parameters, public_testcases, 2).max_scores(), (s1 + s2 + s3, 0, header)) def test_max_scores_number(self): @@ -117,25 +117,25 @@ def test_max_scores_number(self): # Only group 1_* is public. public_testcases = dict(self._public_testcases) - self.assertEqual(GroupMul(parameters, public_testcases).max_scores(), + self.assertEqual(GroupMul(parameters, public_testcases, 2).max_scores(), (s1 + s2 + s3, s1, header)) # All groups are public for testcase in public_testcases.keys(): public_testcases[testcase] = True - self.assertEqual(GroupMul(parameters, public_testcases).max_scores(), + self.assertEqual(GroupMul(parameters, public_testcases, 2).max_scores(), (s1 + s2 + s3, s1 + s2 + s3, header)) # No groups are public for testcase in public_testcases.keys(): public_testcases[testcase] = False - self.assertEqual(GroupMul(parameters, public_testcases).max_scores(), + self.assertEqual(GroupMul(parameters, public_testcases, 2).max_scores(), (s1 + s2 + s3, 0, header)) def test_compute_score(self): s1, s2, s3 = 10.5, 30.5, 59 parameters = [[0, "0_*"], [s1, "1_*"], [s2, "2_*"], [s3, "3_*"]] - gmul = GroupMul(parameters, self._public_testcases) + gmul = GroupMul(parameters, self._public_testcases, 2) sr = self.get_submission_result(self._public_testcases) # All correct. diff --git a/cmstestsuite/unit_tests/grading/scoretypes/GroupThresholdTest.py b/cmstestsuite/unit_tests/grading/scoretypes/GroupThresholdTest.py index e6215736e3..8601345225 100755 --- a/cmstestsuite/unit_tests/grading/scoretypes/GroupThresholdTest.py +++ b/cmstestsuite/unit_tests/grading/scoretypes/GroupThresholdTest.py @@ -42,55 +42,55 @@ def setUp(self): def test_paramaters_correct(self): """Test that correct parameters do not throw.""" - GroupThreshold([], self._public_testcases) + GroupThreshold([], self._public_testcases, 2) GroupThreshold([[40, 2, 500], [60.0, 2, 1000]], - self._public_testcases) + self._public_testcases, 2) GroupThreshold([[40, "1_*", 500.5], [60.0, "2_*", 1000]], - self._public_testcases) + self._public_testcases, 2) def test_paramaters_invalid_types(self): with self.assertRaises(ValueError): - GroupThreshold([1], self._public_testcases) + GroupThreshold([1], self._public_testcases, 2) with self.assertRaises(ValueError): - GroupThreshold(1, self._public_testcases) + GroupThreshold(1, self._public_testcases, 2) def test_paramaters_invalid_wrong_item_len(self): with self.assertRaises(ValueError): - GroupThreshold([[]], self._public_testcases) + GroupThreshold([[]], self._public_testcases, 2) with self.assertRaises(ValueError): - GroupThreshold([[1]], self._public_testcases) + GroupThreshold([[1]], self._public_testcases, 2) @unittest.skip("Not yet detected.") def test_paramaters_invalid_wrong_item_len_not_caught(self): with self.assertRaises(ValueError): - GroupThreshold([[1, 2]], self._public_testcases) + GroupThreshold([[1, 2]], self._public_testcases, 2) def test_parameter_invalid_wrong_max_score_type(self): with self.assertRaises(ValueError): - GroupThreshold([["a", 10, 1000]], self._public_testcases) + GroupThreshold([["a", 10, 1000]], self._public_testcases, 2) def test_parameter_invalid_wrong_testcases_type(self): with self.assertRaises(ValueError): - GroupThreshold([[100, 1j, 1000]], self._public_testcases) + GroupThreshold([[100, 1j, 1000]], self._public_testcases, 2) def test_parameter_invalid_inconsistent_testcases_type(self): with self.assertRaises(ValueError): GroupThreshold([[40, 10, 500], [40, "1_*", 1000]], - self._public_testcases) + self._public_testcases, 2) @unittest.skip("Not yet detected.") def test_paramaters_invalid_testcases_too_many(self): with self.assertRaises(ValueError): - GroupThreshold([[100, 20, 1000]], self._public_testcases) + GroupThreshold([[100, 20, 1000]], self._public_testcases, 2) def test_parameter_invalid_testcases_regex_no_match_type(self): with self.assertRaises(ValueError): - GroupThreshold([[100, "9_*", 1000]], self._public_testcases) + GroupThreshold([[100, "9_*", 1000]], self._public_testcases, 2) @unittest.skip("Not yet detected.") def test_parameter_invalid_wrong_threshold_type_not_caught(self): with self.assertRaises(ValueError): - GroupThreshold([[100, 1, 1000j]], self._public_testcases) + GroupThreshold([[100, 1, 1000j]], self._public_testcases, 2) def test_max_scores_regexp(self): """Test max score is correct when groups are regexp-defined.""" @@ -102,21 +102,21 @@ def test_max_scores_regexp(self): # Only group 1_* is public. public_testcases = dict(self._public_testcases) self.assertEqual( - GroupThreshold(parameters, public_testcases).max_scores(), + GroupThreshold(parameters, public_testcases, 2).max_scores(), (s1 + s2 + s3, s1, header)) # All groups are public for testcase in public_testcases.keys(): public_testcases[testcase] = True self.assertEqual( - GroupThreshold(parameters, public_testcases).max_scores(), + GroupThreshold(parameters, public_testcases, 2).max_scores(), (s1 + s2 + s3, s1 + s2 + s3, header)) # No groups are public for testcase in public_testcases.keys(): public_testcases[testcase] = False self.assertEqual( - GroupThreshold(parameters, public_testcases).max_scores(), + GroupThreshold(parameters, public_testcases, 2).max_scores(), (s1 + s2 + s3, 0, header)) def test_max_scores_number(self): @@ -129,28 +129,28 @@ def test_max_scores_number(self): # Only group 1_* is public. public_testcases = dict(self._public_testcases) self.assertEqual( - GroupThreshold(parameters, public_testcases).max_scores(), + GroupThreshold(parameters, public_testcases, 2).max_scores(), (s1 + s2 + s3, s1, header)) # All groups are public for testcase in public_testcases.keys(): public_testcases[testcase] = True self.assertEqual( - GroupThreshold(parameters, public_testcases).max_scores(), + GroupThreshold(parameters, public_testcases, 2).max_scores(), (s1 + s2 + s3, s1 + s2 + s3, header)) # No groups are public for testcase in public_testcases.keys(): public_testcases[testcase] = False self.assertEqual( - GroupThreshold(parameters, public_testcases).max_scores(), + GroupThreshold(parameters, public_testcases, 2).max_scores(), (s1 + s2 + s3, 0, header)) def test_compute_score(self): s1, s2, s3 = 10.5, 30.5, 59 parameters = [[0, "0_*", 0], [s1, "1_*", 10], [s2, "2_*", 20], [s3, "3_*", 30]] - st = GroupThreshold(parameters, self._public_testcases) + st = GroupThreshold(parameters, self._public_testcases, 2) sr = self.get_submission_result(self._public_testcases) # All correct (below threshold). diff --git a/cmstestsuite/unit_tests/grading/scoretypes/SumTest.py b/cmstestsuite/unit_tests/grading/scoretypes/SumTest.py index b952e1b04b..1a243026cf 100755 --- a/cmstestsuite/unit_tests/grading/scoretypes/SumTest.py +++ b/cmstestsuite/unit_tests/grading/scoretypes/SumTest.py @@ -39,24 +39,24 @@ def setUp(self): def test_paramaters_correct(self): """Test that correct parameters do not throw.""" - Sum(100, self._public_testcases) - Sum(1.5, self._public_testcases) + Sum(100, self._public_testcases, 2) + Sum(1.5, self._public_testcases, 2) def test_paramaters_invalid(self): with self.assertRaises(ValueError): - Sum([], self._public_testcases) + Sum([], self._public_testcases, 2) with self.assertRaises(ValueError): - Sum("1", self._public_testcases) + Sum("1", self._public_testcases, 2) def test_max_scores(self): testcase_score = 10.5 self.assertEqual(Sum(testcase_score, - self._public_testcases).max_scores(), + self._public_testcases, 2).max_scores(), (testcase_score * 4, testcase_score, [])) def test_compute_score(self): testcase_score = 10.5 - st = Sum(testcase_score, self._public_testcases) + st = Sum(testcase_score, self._public_testcases, 2) sr = self.get_submission_result(self._public_testcases) # All correct. diff --git a/cmstestsuite/unit_tests/grading/scoring_test.py b/cmstestsuite/unit_tests/grading/scoring_test.py index c7b95969f3..cca9d8e574 100755 --- a/cmstestsuite/unit_tests/grading/scoring_test.py +++ b/cmstestsuite/unit_tests/grading/scoring_test.py @@ -46,10 +46,9 @@ def setUp(self): def at(self, timestamp): return self.timestamp + timedelta(seconds=timestamp) - def call(self, public=False, only_tokened=False, rounded=False): + def call(self, public=False, only_tokened=False): return task_score(self.participation, self.task, - public=public, only_tokened=only_tokened, - rounded=rounded) + public=public, only_tokened=only_tokened) def add_result(self, timestamp, score, tokened=False, score_details=None, public_score=None, public_score_details=None): @@ -245,21 +244,6 @@ def test_partial(self): self.session.flush() self.assertEqual(self.call(), (30 * 0.2 + 40 * 0.5 + 30 * 0.2, True)) - def test_rounding(self): - # No rounding should happen at the subtask or task level. - self.add_result(self.at(1), 80 + 0.000_2, - score_details=[ - self.subtask(1, 80, 1.0), - self.subtask(2, 20, 0.000_01), - ]) - self.add_result(self.at(2), 0.000_4, - score_details=[ - self.subtask(1, 80, 0.0), - self.subtask(2, 20, 0.000_02), - ]) - self.session.flush() - self.assertEqual(self.call(), (80 + 0.000_4, False)) - def test_public(self): self.add_result(self.at(1), 30 * 1.0 + 40 * 1.0 + 30 * 1.0, @@ -369,17 +353,11 @@ def test_only_tokened(self): self.session.flush() self.assertEqual(self.call(only_tokened=True), (44.4, False)) - def test_unrounded(self): - self.add_result(self.at(1), 44.44444, tokened=False) - self.add_result(self.at(2), 44.44443, tokened=False) - self.session.flush() - self.assertEqual(self.call(), (44.44444, False)) - def test_rounded(self): self.add_result(self.at(1), 44.44444, tokened=False) self.add_result(self.at(2), 44.44443, tokened=False) self.session.flush() - self.assertEqual(self.call(rounded=True), (44.44, False)) + self.assertEqual(self.call(), (44.44, False)) if __name__ == "__main__":