Skip to content

Commit

Permalink
establish auto-discovery and auto-testing of match report files in te…
Browse files Browse the repository at this point in the history
…sts/mocks_data/match_reports
  • Loading branch information
cahna committed Dec 23, 2023
1 parent 3ed4d7f commit bc0bd3a
Show file tree
Hide file tree
Showing 13 changed files with 335 additions and 239 deletions.
Empty file added tests/mock_data/__init__.py
Empty file.
12 changes: 12 additions & 0 deletions tests/mock_data/match_reports/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Mock match reports for tests

The following pattern(s) *must* be followed for automatic discovery and testing. See `./__init__.py` for details.

## Add a match report for testing

1. Add the report text file to `tests/mock_data/match_reports` (ex: `report_foo.txt`)
- The stem of the filename must conform to python source file naming requirements (rule of thumb: `^[a-z][_a-z0-9]*\.txt$`)
- Sanitize competitor names
2. Add a corresponding python source file of the same name (ex: `report_foo.py`) that exports a function named `assert_match_report`
- the exported function just be of type `Callable[[ParsedMatchReport], None]`
- it should assert the expected state of the corresponding match report
16 changes: 16 additions & 0 deletions tests/mock_data/match_reports/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import importlib
from pathlib import Path
from typing import Callable, Dict

from hitfactorpy.parsers.match_report.models import ParsedMatchReport

AssertMatchReportCallable = Callable[[ParsedMatchReport], None]

DIRECTORY = Path(__file__).resolve().parent
REPORT_FILES = list(DIRECTORY.glob("*.txt"))
BY_FILENAME = {str(f).split("tests/mock_data/")[-1]: f.read_text() for f in REPORT_FILES}
VALIDATORS: Dict[str, AssertMatchReportCallable] = {
fname: importlib.import_module(f".{Path(fname).stem}", package="tests.mock_data.match_reports").assert_match_report
# fname: __import__(f".{Path(fname).stem}", globals(), locals(), [], 1).assert_match_report
for fname in BY_FILENAME.keys()
}
91 changes: 91 additions & 0 deletions tests/mock_data/match_reports/pcsl_1gun_20231129.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import datetime

from pytest import approx

from hitfactorpy.enums import Classification, Division, PowerFactor, Scoring
from hitfactorpy.parsers.match_report.models import ParsedMatchReport


def assert_pcsl_match_report_1gun_20231129(report: ParsedMatchReport):
"""Assert the expected state of the 1-gun 20231129 report"""
assert report
assert report.name == "matchName"
assert report.match_level == 1
assert report.platform == "IOS"
assert report.ps_product == "PractiScore (iOS)"
assert report.ps_version == "1.682"
assert report.club_name == "clubName"
assert report.club_code == "clubCode"
assert report.region == "USPSA"
assert report.raw_date == "11/29/2023"
assert report.date == datetime.datetime(2023, 11, 29, 0, 0)
assert report.stages
assert len(report.stages) == 3
assert report.competitors
assert len(report.competitors) == 5
assert report.stage_scores
assert len(report.stage_scores) == 13

# Verify a stage
assert report.stages[0].name == "Surefire"
assert report.stages[0].min_rounds == 30
assert report.stages[0].max_points == 150
assert report.stages[0].classifier is False
assert report.stages[0].classifier_number in ["nan", "", None]
assert report.stages[0].internal_id == 1
assert report.stages[0].scoring_type == Scoring.COMSTOCK
assert report.stages[0].number == 1
assert report.stages[0].gun_type == "Pistol"

# Verify a competitor
assert report.competitors[0].internal_id == 1
assert report.competitors[0].first_name == "Person"
assert report.competitors[0].last_name == "A"
assert report.competitors[0].classification == Classification.UNKNOWN
assert report.competitors[0].division == Division.UNKNOWN
assert report.competitors[0].member_number == "NUMBER1"
assert report.competitors[0].power_factor == PowerFactor.MINOR
assert report.competitors[0].dq is False
assert report.competitors[0].reentry is False

# Verify DQ'ed competitor
assert report.competitors[4].dq is True

# Verify a stage score with a DQ
assert report.stage_scores[-1].stage_id == 3
assert report.stage_scores[-1].competitor_id == 4
assert report.stage_scores[-1].a == 9
assert report.stage_scores[-1].b == 0
assert report.stage_scores[-1].c == 5
assert report.stage_scores[-1].d == 0
assert report.stage_scores[-1].m == 0
assert report.stage_scores[-1].ns == 0
assert report.stage_scores[-1].npm == 0
assert report.stage_scores[-1].procedural == 0
assert report.stage_scores[-1].late_shot == 0
assert report.stage_scores[-1].extra_shot == 0
assert report.stage_scores[-1].extra_hit == 0
assert report.stage_scores[-1].other_penalty == 0
assert report.stage_scores[-1].t1 == approx(9.43)
assert report.stage_scores[-1].t2 == approx(0)
assert report.stage_scores[-1].t3 == approx(0)
assert report.stage_scores[-1].t4 == approx(0)
assert report.stage_scores[-1].t5 == approx(0)
assert report.stage_scores[-1].time == approx(9.43)
assert report.stage_scores[-1].raw_points == approx(60)
assert report.stage_scores[-1].penalty_points == approx(0)
assert report.stage_scores[-1].total_points == approx(60)
assert report.stage_scores[-1].hit_factor == approx(0)
assert report.stage_scores[-1].stage_points == approx(0)
assert report.stage_scores[-1].stage_place == 4
assert report.stage_scores[-1].dq is True
assert report.stage_scores[-1].dnf is False
assert report.stage_scores[-1].stage_power_factor is None

# Verify DQ'ed competitor exists in competitors list, and was DQ'ed
dqed_competitor = next((c for c in report.competitors if c.internal_id == 4), None)
assert dqed_competitor
assert dqed_competitor.dq is True


assert_match_report = assert_pcsl_match_report_1gun_20231129
86 changes: 86 additions & 0 deletions tests/mock_data/match_reports/pcsl_2gun_20231129.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import datetime

from pytest import approx

from hitfactorpy.enums import Classification, Division, PowerFactor, Scoring
from hitfactorpy.parsers.match_report.models import ParsedMatchReport


def assert_pcsl_match_report_2gun_20231129(report: ParsedMatchReport):
"""Assert the expected state of the 2-gun 20231129 report"""
assert report
assert report.name == "matchName"
assert report.match_level == 1
assert report.platform == "IOS"
assert report.ps_product == "PractiScore (iOS)"
assert report.ps_version == "1.682"
assert report.club_name == "clubName"
assert report.club_code == "clubCode"
assert report.region == "USPSA"
assert report.raw_date == "11/29/2023"
assert report.date == datetime.datetime(2023, 11, 29, 0, 0)
assert report.stages
assert len(report.stages) == 3
assert report.competitors
assert len(report.competitors) == 3
assert report.stage_scores
assert len(report.stage_scores) == 8

# Verify a stage
assert report.stages[0].name == "Surefire"
assert report.stages[0].min_rounds == 54
assert report.stages[0].max_points == 270
assert report.stages[0].classifier is False
assert report.stages[0].classifier_number in ["nan", "", None]
assert report.stages[0].internal_id == 1
assert report.stages[0].scoring_type == Scoring.COMSTOCK
assert report.stages[0].number == 1
assert report.stages[0].gun_type == "Pistol"

# Verify a competitor
assert report.competitors[1].internal_id == 2
assert report.competitors[1].first_name == "Person"
assert report.competitors[1].last_name == "B"
assert report.competitors[1].classification == Classification.UNKNOWN
assert report.competitors[1].division == Division.UNKNOWN
assert report.competitors[1].member_number == "NUMBER2"
assert report.competitors[1].power_factor == PowerFactor.MINOR
assert report.competitors[1].dq is False
assert report.competitors[1].reentry is False

# Verify no DQ'ed competitors exist in this report
assert report.competitors[-1].dq is True

# Verify a stage score
assert report.stage_scores[1].stage_id == 1
assert report.stage_scores[1].competitor_id == 2
assert report.stage_scores[1].a == 45
assert report.stage_scores[1].b == 0
assert report.stage_scores[1].c == 9
assert report.stage_scores[1].d == 0
assert report.stage_scores[1].m == 0
assert report.stage_scores[1].ns == 0
assert report.stage_scores[1].npm == 0
assert report.stage_scores[1].procedural == 0
assert report.stage_scores[1].late_shot == 0
assert report.stage_scores[1].extra_shot == 0
assert report.stage_scores[1].extra_hit == 0
assert report.stage_scores[1].other_penalty == 0
assert report.stage_scores[1].t1 == approx(49.21)
assert report.stage_scores[1].t2 == approx(0)
assert report.stage_scores[1].t3 == approx(0)
assert report.stage_scores[1].t4 == approx(0)
assert report.stage_scores[1].t5 == approx(0)
assert report.stage_scores[1].time == approx(49.21)
assert report.stage_scores[1].raw_points == approx(252)
assert report.stage_scores[1].penalty_points == approx(0)
assert report.stage_scores[1].total_points == approx(252)
assert report.stage_scores[1].hit_factor == approx(5.1209)
assert report.stage_scores[1].stage_points == approx(248.82)
assert report.stage_scores[1].stage_place == 2
assert report.stage_scores[1].dq is False
assert report.stage_scores[1].dnf is False
assert report.stage_scores[1].stage_power_factor is None


assert_match_report = assert_pcsl_match_report_2gun_20231129
105 changes: 105 additions & 0 deletions tests/mock_data/match_reports/uspsa_20230108.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
from pytest import approx

from hitfactorpy.enums import Classification, Division, PowerFactor, Scoring
from hitfactorpy.parsers.match_report.models import ParsedMatchReport


def assert_uspsa_report_20230108(report: ParsedMatchReport):
"""Assert the expected state of the report after sucessful parsing"""
assert report
assert report.name == "Paul Bunyan USPSA - January 2023 NW01"
assert report.stages
assert len(report.stages) == 7
assert report.competitors
assert len(report.competitors) == 72
assert report.stage_scores
assert len(report.stage_scores) == 494
assert report.platform == "IOS"
assert report.ps_product
assert report.ps_version == "1.682"
assert report.club_name == "PaulBunyan"
assert report.club_code == "PB"

# Verify a stage
assert report.stages[0].name == "A New Dawn"
assert report.stages[0].min_rounds == 32
assert report.stages[0].max_points == 160
assert report.stages[0].classifier is False
assert report.stages[0].internal_id == 1
assert report.stages[0].scoring_type == Scoring.COMSTOCK
assert report.stages[0].number == 1
assert report.stages[0].gun_type == "Pistol"

assert report.stages[2].name == "CM 22-06 Blue's Don't Care"

# Verify a classifier stage
assert report.stages[2].classifier is True
assert report.stages[2].classifier_number == "22-06"
assert report.stages[2].internal_id == 3

# Verify a competitor
assert report.competitors[0].internal_id == 1
assert report.competitors[0].first_name == "Emily"
assert report.competitors[0].last_name == "Smith"
assert report.competitors[0].classification == Classification.M
assert report.competitors[0].division == Division.CARRY_OPTICS
assert report.competitors[0].member_number == "L4898"
assert report.competitors[0].power_factor == PowerFactor.MINOR
assert report.competitors[0].dq is False
assert report.competitors[0].reentry is False

# Verify DQ'ed competitor
assert report.competitors[37].dq is True
assert report.competitors[70].dq is True

# Verify weird/missing member numbers
assert report.competitors[1].member_number == "TY104096"
assert report.competitors[17].member_number == "L1770NO"
assert report.competitors[26].member_number == ""
assert report.competitors[60].member_number == "B49"

# Verify a stage score
assert report.stage_scores[0].stage_id == 1
assert report.stage_scores[0].competitor_id == 1
assert report.stage_scores[0].a == 27
assert report.stage_scores[0].b == 0
assert report.stage_scores[0].c == 5
assert report.stage_scores[0].d == 0
assert report.stage_scores[0].m == 0
assert report.stage_scores[0].ns == 0
assert report.stage_scores[0].npm == 0
assert report.stage_scores[0].procedural == 0
assert report.stage_scores[0].late_shot == 0
assert report.stage_scores[0].extra_shot == 0
assert report.stage_scores[0].extra_hit == 0
assert report.stage_scores[0].other_penalty == 0
assert report.stage_scores[0].t1 == approx(17.34)
assert report.stage_scores[0].t2 == approx(0)
assert report.stage_scores[0].t3 == approx(0)
assert report.stage_scores[0].t4 == approx(0)
assert report.stage_scores[0].t5 == approx(0)
assert report.stage_scores[0].time == approx(17.34)
assert report.stage_scores[0].raw_points == approx(150.0)
assert report.stage_scores[0].penalty_points == approx(0)
assert report.stage_scores[0].total_points == approx(150.0)
assert report.stage_scores[0].hit_factor == approx(8.6505)
assert report.stage_scores[0].stage_points == approx(160.0)
assert report.stage_scores[0].stage_place == 1
assert report.stage_scores[0].dq is False
assert report.stage_scores[0].dnf is False
assert report.stage_scores[0].stage_power_factor is None

# Verify a stage score with a DQ
assert report.stage_scores[37].dq is True
assert report.stage_scores[37].dnf is False
assert next(
(c for c in report.competitors if c.internal_id == report.stage_scores[37].competitor_id), None
), "DQ'ed competitor not found in competitors list"

# Verify a stage score with a DNF
assert report.stage_scores[69].dnf is True
assert report.stage_scores[69].dq is False
assert report.stage_scores[69].competitor_id == 70


assert_match_report = assert_uspsa_report_20230108
37 changes: 15 additions & 22 deletions tests/test_cli/test_parse_match.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,21 @@
from ..test_parsers_match_report.mock_data import EXAMPLE_REPORT
import pytest


def test_parse_match_default(cli_invoke, tmp_path):
match_file = tmp_path / "test-match.txt"
match_file.write_text(EXAMPLE_REPORT)
result = cli_invoke(["parse-match", str(match_file)])
assert result.exit_code == 0
assert result.stdout.lstrip().startswith("{")
assert result.stdout.rstrip().endswith("}")
from ..mock_data import match_reports


def test_parse_match_pandas(cli_invoke, tmp_path):
match_file = tmp_path / "test-match1.txt"
match_file.write_text(EXAMPLE_REPORT)
result = cli_invoke(["parse-match", str(match_file), "--parser=pandas"])
assert result.exit_code == 0
assert result.stdout.lstrip().startswith("{")
assert result.stdout.rstrip().endswith("}")


def test_parse_match_strict(cli_invoke, tmp_path):
match_file = tmp_path / "test-match2.txt"
match_file.write_text(EXAMPLE_REPORT)
result = cli_invoke(["parse-match", str(match_file), "--parser=strict"])
@pytest.mark.parametrize("match_report_file", [*match_reports.BY_FILENAME.keys()])
@pytest.mark.parametrize(
"cmd_args",
[
pytest.param([], id="default"),
pytest.param(["--parser=pandas"], id="opt-parser-pandas"),
pytest.param(["--parser=strict"], id="opt-parser-strict"),
],
)
def test_cli_parse_match(match_report_file, cmd_args, cli_invoke, tmp_path):
match_file = tmp_path / "test-match.txt"
match_file.write_text(match_reports.BY_FILENAME[match_report_file])
result = cli_invoke(["parse-match", str(match_file), *cmd_args])
assert result.exit_code == 0
assert result.stdout.lstrip().startswith("{")
assert result.stdout.rstrip().endswith("}")
Loading

0 comments on commit bc0bd3a

Please sign in to comment.