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

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Build a simple bare-minimum quantum circuit that starts with a single Build a simple bare-minimum quantum circuit that starts with a single
qubit (by default, in state 0), runs the experiment 1000 times, and qubit (by default, in state 0), runs the experiment 1000 times, and
finally prints the total count of the states finally observed. finally prints the total count of the states finally observed.
Qiskit Docs: https://qiskit.org/documentation/getting_started.html Qiskit Docs: https://qiskit.org/documentation/getting_started.html
""" """