pytest-grader 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pytest_grader-0.1.0/PKG-INFO +0 -0
- pytest_grader-0.1.0/README.md +0 -0
- pytest_grader-0.1.0/pyproject.toml +16 -0
- pytest_grader-0.1.0/pytest_grader.egg-info/PKG-INFO +0 -0
- pytest_grader-0.1.0/pytest_grader.egg-info/SOURCES.txt +9 -0
- pytest_grader-0.1.0/pytest_grader.egg-info/dependency_links.txt +1 -0
- pytest_grader-0.1.0/pytest_grader.egg-info/entry_points.txt +2 -0
- pytest_grader-0.1.0/pytest_grader.egg-info/requires.txt +1 -0
- pytest_grader-0.1.0/pytest_grader.egg-info/top_level.txt +1 -0
- pytest_grader-0.1.0/pytest_grader.py +73 -0
- pytest_grader-0.1.0/setup.cfg +4 -0
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pytest-grader"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Pytest extension for scoring programming assignments."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"pytest>=8",
|
|
9
|
+
]
|
|
10
|
+
classifiers = ["Framework :: Pytest"]
|
|
11
|
+
|
|
12
|
+
[tool.setuptools]
|
|
13
|
+
py-modules = ["pytest_grader"]
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
build = "build:create_executable_zip"
|
|
Binary file
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
pytest_grader.py
|
|
4
|
+
pytest_grader.egg-info/PKG-INFO
|
|
5
|
+
pytest_grader.egg-info/SOURCES.txt
|
|
6
|
+
pytest_grader.egg-info/dependency_links.txt
|
|
7
|
+
pytest_grader.egg-info/entry_points.txt
|
|
8
|
+
pytest_grader.egg-info/requires.txt
|
|
9
|
+
pytest_grader.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pytest>=8
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pytest_grader
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def points(n):
|
|
5
|
+
"""Decorator to add a points attribute to a test function."""
|
|
6
|
+
def wrapper(f):
|
|
7
|
+
f.points = n
|
|
8
|
+
return f
|
|
9
|
+
return wrapper
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def has_points(item: pytest.Item):
|
|
13
|
+
return isinstance(item, pytest.Function) and hasattr(item.function, 'points')
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GraderPlugin:
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self.test_items = {}
|
|
19
|
+
self.test_results = []
|
|
20
|
+
self.total_points_in_all_tests = 0
|
|
21
|
+
|
|
22
|
+
def pytest_collection_modifyitems(self, session, config, items):
|
|
23
|
+
self.total_points_in_all_tests = sum(f.function.points for f in items if has_points(f))
|
|
24
|
+
|
|
25
|
+
def pytest_runtest_setup(self, item):
|
|
26
|
+
self.test_items[item.nodeid] = item
|
|
27
|
+
|
|
28
|
+
def pytest_runtest_logreport(self, report):
|
|
29
|
+
if report.when == "call":
|
|
30
|
+
self.test_results.append(report)
|
|
31
|
+
|
|
32
|
+
def pytest_terminal_summary(self, terminalreporter, exitstatus, config):
|
|
33
|
+
if config.getoption("--score"):
|
|
34
|
+
self.print_score_report()
|
|
35
|
+
|
|
36
|
+
def print_score_report(self):
|
|
37
|
+
total_earned = 0
|
|
38
|
+
total_points = 0
|
|
39
|
+
|
|
40
|
+
print('═' * 40)
|
|
41
|
+
for report in self.test_results:
|
|
42
|
+
if report.nodeid in self.test_items:
|
|
43
|
+
test_item = self.test_items[report.nodeid]
|
|
44
|
+
if has_points(test_item):
|
|
45
|
+
points = test_item.function.points
|
|
46
|
+
earned = points if report.outcome == 'passed' else 0
|
|
47
|
+
total_points += points
|
|
48
|
+
total_earned += earned
|
|
49
|
+
test_name = report.nodeid.split("::")[-1]
|
|
50
|
+
emoji = "✅" if report.outcome == 'passed' else "❌"
|
|
51
|
+
print(f" {emoji} {test_name:<25} {earned:>2}/{points} pts")
|
|
52
|
+
|
|
53
|
+
if total_points == self.total_points_in_all_tests:
|
|
54
|
+
percentage = 0.0 if total_points == 0 else round(100.0 * total_earned / total_points, 1)
|
|
55
|
+
decoration = ""
|
|
56
|
+
if total_earned == total_points:
|
|
57
|
+
percentage = "💯"
|
|
58
|
+
decoration = "✨"
|
|
59
|
+
|
|
60
|
+
print('─' * 40)
|
|
61
|
+
print(f" {decoration}Total Score: {total_earned}/{total_points} pts"
|
|
62
|
+
f" ({percentage}%){decoration}")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def pytest_addoption(parser):
|
|
66
|
+
parser.addoption(
|
|
67
|
+
"--score", action="store_true", default=False,
|
|
68
|
+
help="Show score report after running tests"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def pytest_configure(config):
|
|
73
|
+
config.pluginmanager.register(GraderPlugin(), "pytest-grader")
|