Skip to content

Commit d46330a

Browse files
dhruvmanilacclauss
andauthored
Add algorithm for testing Project Euler solutions (TheAlgorithms#2471)
* Add file for testing Project Euler solutions * Remove the importlib import * Update project_euler/solution_test.py Co-authored-by: Christian Clauss <cclauss@me.com> * Small tweaks to project_euler/solution_test.py * Test Project Euler solutions through Travis * Improved testing for Project Euler solutions: - Renamed file so that it isn't picked up by pytest - Fail fast on validating solutions through Travis CI * Update validate_solutions.py * Use namedtuple for input parameters and answer - Remove logging - Remove unnecessary checks for PROJECT_EULER_PATH as Travis CI picks up the same path * Fix flake8 errors: line too long * Small tweaks to validate_solutions.py * Add all answers & back to using dictionary * Using pytest for testing Project Euler solutions - As we want to fail fast on testing solutions, we need to test using this script first before we use tests written by the author. - As pytest stops testing as soon as it receives a traceback, we need to use pytest-subtests to tell pytest to test all the iterations for the function with given parameters. * Print error messages in oneline format * Separated answers into a separate file: - Add custom print function to print all the error messages at the end of all the tests - Let Travis skip if this failed Co-authored-by: Christian Clauss <cclauss@me.com>
1 parent 187e8cc commit d46330a

File tree

2 files changed

+70
-1
lines changed

2 files changed

+70
-1
lines changed

Diff for: .travis.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ jobs:
1717
- mypy --ignore-missing-imports . || true # https://github.com/python/mypy/issues/7907
1818
- pytest --doctest-modules --ignore=project_euler/ --durations=10 --cov-report=term-missing:skip-covered --cov=. .
1919
- name: Project Euler
20-
before_script: pip install pytest-cov
20+
before_script:
21+
- pip install pytest-cov pytest-subtests
22+
- pytest --tb=no --no-summary --capture=no project_euler/validate_solutions.py || true # fail fast on wrong solution
2123
script:
2224
- pytest --doctest-modules --durations=10 --cov-report=term-missing:skip-covered --cov=project_euler/ project_euler/
2325
after_success:

Diff for: project_euler/validate_solutions.py

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/usr/bin/env python3
2+
import importlib.util
3+
import json
4+
import pathlib
5+
from types import ModuleType
6+
from typing import Generator
7+
8+
import pytest
9+
10+
PROJECT_EULER_DIR_PATH = pathlib.Path.cwd().joinpath("project_euler")
11+
PROJECT_EULER_ANSWERS_PATH = PROJECT_EULER_DIR_PATH.joinpath(
12+
"project_euler_answers.json"
13+
)
14+
15+
with open(PROJECT_EULER_ANSWERS_PATH) as file_handle:
16+
PROBLEM_ANSWERS = json.load(file_handle)
17+
18+
error_msgs = []
19+
20+
21+
def generate_solution_modules(
22+
dir_path: pathlib.Path,
23+
) -> Generator[ModuleType, None, None]:
24+
# Iterating over every file or directory
25+
for file_path in dir_path.iterdir():
26+
if file_path.suffix != ".py" or file_path.name.startswith(("_", "test")):
27+
continue
28+
# Importing the source file through the given path
29+
# https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
30+
spec = importlib.util.spec_from_file_location(file_path.name, str(file_path))
31+
module = importlib.util.module_from_spec(spec)
32+
spec.loader.exec_module(module)
33+
yield module
34+
35+
36+
@pytest.mark.parametrize("problem_number, expected", PROBLEM_ANSWERS)
37+
def test_project_euler(subtests, problem_number: int, expected: str):
38+
problem_dir = PROJECT_EULER_DIR_PATH.joinpath(f"problem_{problem_number:02}")
39+
# Check if the problem directory exist. If not, then skip.
40+
if problem_dir.is_dir():
41+
for solution_module in generate_solution_modules(problem_dir):
42+
# All the tests in a loop is considered as one test by pytest so, use
43+
# subtests to make sure all the subtests are considered as different.
44+
with subtests.test(
45+
msg=f"Problem {problem_number} tests", solution_module=solution_module
46+
):
47+
try:
48+
answer = str(solution_module.solution())
49+
assert answer == expected, f"Expected {expected} but got {answer}"
50+
except (AssertionError, AttributeError, TypeError) as err:
51+
error_msgs.append(
52+
f"problem_{problem_number}/{solution_module.__name__}: {err}"
53+
)
54+
raise # We still want pytest to know that this test failed
55+
else:
56+
pytest.skip(f"Solution {problem_number} does not exist yet.")
57+
58+
59+
# Run this function at the end of all the tests
60+
# https://stackoverflow.com/a/52873379
61+
@pytest.fixture(scope="session", autouse=True)
62+
def custom_print_message(request):
63+
def print_error_messages():
64+
if error_msgs:
65+
print("\n" + "\n".join(error_msgs))
66+
67+
request.addfinalizer(print_error_messages)

0 commit comments

Comments
 (0)