Improve validate solutions script & fix pre-commit error (#3253)

* Trying to time every solution

* Proposal 2 for timing PE solutions:

- Use pytest fixture along with --capture=no flag to print out the
  top DURATIONS slowest solution at the end of the test sessions.
- Remove the print part and try ... except ... block from the test
  function.

* Proposal 3 for timing PE solutions:

Completely changed the way I was performing the tests. Instead of
parametrizing the problem numbers and expected output, I will
parametrize the solution file path.

Steps:
- Collect all the solution file paths
- Convert the paths into a Python module
- Call solution on the module
- Assert the answer with the expected results

For assertion, it was needed to convert the JSON list object to
Python dictionary object which required changing the JSON file itself.

* Add type hints for variables

* Fix whitespace in single_qubit_measure
This commit is contained in:
Dhruv 2020-10-13 15:41:12 +05:30 committed by GitHub
parent 6e01004535
commit 29b32d3553
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 767 additions and 2942 deletions

View File

@ -16,10 +16,8 @@ jobs:
script: script:
- pytest --doctest-modules --durations=10 --cov-report=term-missing:skip-covered --cov=project_euler/ project_euler/ - pytest --doctest-modules --durations=10 --cov-report=term-missing:skip-covered --cov=project_euler/ project_euler/
- name: Project Euler Solution - name: Project Euler Solution
install:
- pip install pytest-subtests
script: script:
- pytest --tb=short project_euler/validate_solutions.py - pytest --tb=short --durations=10 project_euler/validate_solutions.py
after_success: after_success:
- scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md - scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md
notifications: notifications:

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@ import importlib.util
import json import json
import pathlib import pathlib
from types import ModuleType from types import ModuleType
from typing import Generator from typing import Dict, List
import pytest import pytest
@ -13,42 +13,44 @@ PROJECT_EULER_ANSWERS_PATH = PROJECT_EULER_DIR_PATH.joinpath(
) )
with open(PROJECT_EULER_ANSWERS_PATH) as file_handle: with open(PROJECT_EULER_ANSWERS_PATH) as file_handle:
PROBLEM_ANSWERS = json.load(file_handle) PROBLEM_ANSWERS: Dict[str, str] = json.load(file_handle)
def generate_solution_modules( def convert_path_to_module(file_path: pathlib.Path) -> ModuleType:
dir_path: pathlib.Path, """Converts a file path to a Python module"""
) -> Generator[ModuleType, None, None]:
# Iterating over every file or directory
for file_path in dir_path.iterdir():
if file_path.suffix != ".py" or file_path.name.startswith(("_", "test")):
continue
# Importing the source file through the given path
# https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
spec = importlib.util.spec_from_file_location(file_path.name, str(file_path)) spec = importlib.util.spec_from_file_location(file_path.name, str(file_path))
module = importlib.util.module_from_spec(spec) module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module) spec.loader.exec_module(module)
yield module return module
@pytest.mark.parametrize("problem_number, expected", PROBLEM_ANSWERS) def collect_solution_file_paths() -> List[pathlib.Path]:
def test_project_euler(subtests, problem_number: int, expected: str): """Collects all the solution file path in the Project Euler directory"""
problem_dir = PROJECT_EULER_DIR_PATH.joinpath(f"problem_{problem_number:02}") solution_file_paths = []
# Check if the problem directory exist. If not, then skip. for problem_dir_path in PROJECT_EULER_DIR_PATH.iterdir():
if problem_dir.is_dir(): if problem_dir_path.is_file() or problem_dir_path.name.startswith("_"):
for solution_module in generate_solution_modules(problem_dir): continue
# All the tests in a loop is considered as one test by pytest so, use for file_path in problem_dir_path.iterdir():
# subtests to make sure all the subtests are considered as different. if file_path.suffix != ".py" or file_path.name.startswith(("_", "test")):
with subtests.test( continue
msg=f"Problem {problem_number} tests", solution_module=solution_module solution_file_paths.append(file_path)
): return solution_file_paths
try:
def expand_parameters(param: pathlib.Path) -> str:
"""Expand parameters in pytest parametrize"""
project_dirname = param.parent.name
solution_filename = param.name
return f"{project_dirname}/{solution_filename}"
@pytest.mark.parametrize(
"solution_path", collect_solution_file_paths(), ids=expand_parameters
)
def test_project_euler(solution_path: pathlib.Path):
"""Testing for all Project Euler solutions"""
problem_number: str = solution_path.parent.name[8:] # problem_[extract his part]
expected: str = PROBLEM_ANSWERS[problem_number]
solution_module = convert_path_to_module(solution_path)
answer = str(solution_module.solution()) answer = str(solution_module.solution())
assert answer == expected, f"Expected {expected} but got {answer}" assert answer == expected, f"Expected {expected} but got {answer}"
except (AssertionError, AttributeError, TypeError) as err:
print(
f"problem_{problem_number:02}/{solution_module.__name__}: {err}"
)
raise
else:
pytest.skip(f"Solution {problem_number} does not exist yet.")