commit 1b18db3e774a4cf90ee75d3d8cbb3e4d5e5eb8bb Author: Ash Garcia Date: Sun Jul 17 22:32:44 2022 -0700 Initial commit diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/analyze.py b/analyze.py new file mode 100644 index 0000000..37444eb --- /dev/null +++ b/analyze.py @@ -0,0 +1,69 @@ +import argparse +from collections import Counter, defaultdict +import shelve +from typing import Counter, Dict, List, Optional + +import simfile + +from smoketest import SimfileError + + +def analyze( + simfile_errors: Dict[str, List[SimfileError]], + *, + traceback_substring: str, + verbose: bool, + number: Optional[int] +) -> None: + error_counter = Counter() + error_occurrences = defaultdict(list) + for filename, errors in simfile_errors.items(): + for error in errors: + line = error.traceback.splitlines()[-1] + if not traceback_substring or traceback_substring in line: + error_counter[line] += 1 + error_occurrences[line].append((filename, error.context)) + for line, count in error_counter.most_common(number): + print(f'=== {line} ({count} occurrences) ===') + if verbose: + for filename, context in error_occurrences[line]: + print(f' * "{filename}" (during {repr(context)})') + print() + + +def main(): + argparser = argparse.ArgumentParser() + argparser.add_argument( + '-s', '--shelf', + help='shelf file to use', + default=f'shelf-{simfile.__version__}', + ) + argparser.add_argument( + '-t', '--traceback-substring', + help='filter tracebacks by substring', + ) + argparser.add_argument( + '-n', '--number', + type=int, + default=10, + help='number of most common errors to print', + ) + argparser.add_argument( + '-v', '--verbose', + help='enable verbose output', + default=False, + action='store_true', + ) + args = argparser.parse_args() + + with shelve.open(args.shelf) as simfile_errors: + analyze( + simfile_errors, + traceback_substring=args.traceback_substring, + verbose=args.verbose, + number=args.number, + ) + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..00001bb --- /dev/null +++ b/poetry.lock @@ -0,0 +1,215 @@ +[[package]] +name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "black" +version = "22.6.0" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} +typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "colorama" +version = "0.4.5" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "fs" +version = "2.4.16" +description = "Python's filesystem abstraction layer" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +appdirs = ">=1.4.3,<1.5.0" +six = ">=1.10,<2.0" + +[package.extras] +scandir = ["scandir (>=1.5,<2.0)"] + +[[package]] +name = "importlib-metadata" +version = "4.12.0" +description = "Read metadata from Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +perf = ["ipython"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] + +[[package]] +name = "msdparser" +version = "2.0.0b5" +description = "Simple MSD parser (rhythm game format)" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "pathspec" +version = "0.9.0" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[[package]] +name = "peewee" +version = "3.15.1" +description = "a little orm" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "platformdirs" +version = "2.5.2" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] +test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] + +[[package]] +name = "semver" +version = "2.13.0" +description = "Python helper for Semantic Versioning (http://semver.org/)" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "simfile" +version = "2.1.0b3" +description = "Modern simfile library for Python" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +fs = ">=2.4.15,<2.5.0" +msdparser = ">=2.0.0b4,<2.1.0" + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "typed-ast" +version = "1.5.4" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "typing-extensions" +version = "4.3.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "zipp" +version = "3.8.1" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.7" +content-hash = "4fdaaca625a1242b7a0012eb7261349b6f25459688543e12b9d4c37eed4f8e3a" + +[metadata.files] +appdirs = [] +black = [] +click = [] +colorama = [] +fs = [] +importlib-metadata = [] +msdparser = [] +mypy-extensions = [] +pathspec = [] +peewee = [] +platformdirs = [] +semver = [] +simfile = [] +six = [] +tomli = [] +typed-ast = [] +typing-extensions = [] +zipp = [] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1a184c7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,19 @@ +[tool.poetry] +name = "simfile-smoketest" +version = "0.1.0" +description = "Smoketest for the `simfile` library" +authors = ["Ash Garcia "] +license = "MIT" + +[tool.poetry.dependencies] +python = "^3.7" +simfile = {version = "^2.1.0b3", allow-prereleases = true} +peewee = "^3.15.1" +semver = "^2.13.0" + +[tool.poetry.dev-dependencies] +black = "^22.6.0" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/smoketest.py b/smoketest.py new file mode 100644 index 0000000..4c8a3b8 --- /dev/null +++ b/smoketest.py @@ -0,0 +1,134 @@ +import argparse +import os +import pprint +import shelve +import traceback +from typing import Counter, Dict, List, Optional + +import simfile +from simfile.notes import NoteData +from simfile.notes.group import group_notes +from simfile.notes.timed import time_notes +from simfile.ssc import SSCChart +from simfile.timing.engine import TimingData +from simfile.timing.displaybpm import displaybpm + + +class SimfileError: + context: str + traceback: Optional[str] + + def __init__(self, context: str): + self.context = context + self.traceback = traceback.format_exc() + + def __repr__(self): + return f"{self.context}: {self.traceback}" + + +def check_simfile(filename: str) -> List[SimfileError]: + errors = [] + try: + sim = simfile.open(filename) + except Exception: + errors.append(SimfileError("parse")) + return errors + + timing_data = None + try: + timing_data = TimingData.from_simfile(sim) + except Exception: + errors.append(SimfileError("timing")) + + try: + displaybpm(sim) + except Exception: + errors.append(SimfileError("displaybpm")) + + for c, chart in enumerate(sim.charts): + try: + note_data = NoteData.from_chart(chart) + except Exception: + errors.append(SimfileError(f"chart {c} note_data")) + try: + for _ in group_notes(note_data, join_heads_to_tails=True): + pass + except Exception: + errors.append(SimfileError(f"chart {c} group_notes")) + + if isinstance(chart, SSCChart): + try: + displaybpm(sim, chart) + except Exception: + errors.append(SimfileError(f"chart {c} displaybpm")) + + # skip the remaining chart tests if timing data parsing failed + if timing_data is None: + continue + + if isinstance(chart, SSCChart): + try: + timing_data = TimingData.from_simfile(sim, chart) + except Exception: + errors.append(SimfileError(f"chart {c} timing")) + + try: + for _ in time_notes(note_data, timing_data): + pass + except Exception: + errors.append(SimfileError(f"chart {c} timed_notes")) + + return errors + + +def scan_directory(path: str, simfile_errors: Dict[str, List[SimfileError]]): + stats = Counter() + for entry in os.scandir(path): + if simfile_errors.get(entry.path) == []: + stats["skipped"] += 1 + continue + if entry.is_dir(): + stats.update(scan_directory(entry.path, simfile_errors)) + elif any(entry.name.endswith(ext) for ext in (".sm", ".ssc")): + errors = check_simfile(entry.path) + stats["checked"] += 1 + simfile_errors[entry.path] = errors + if errors: + stats["error"] += 1 + pprint.pprint({"path": entry.path, "errors": errors}) + else: + stats["success"] += 1 + + return stats + + +def dir_path(string): + if os.path.isdir(string): + return string + else: + raise ValueError("dir_path: not a directory") + + +def main(): + argparser = argparse.ArgumentParser() + argparser.add_argument( + "directory", + help="directory of simfiles to scan", + type=dir_path, + ) + argparser.add_argument( + "-s", + "--shelf", + help="shelf file to use", + default=f"shelf-{simfile.__version__}", + ) + args = argparser.parse_args() + + with shelve.open(args.shelf) as simfile_errors: + stats = scan_directory(args.directory, simfile_errors) + for k, v in stats.items(): + print(k, v) + + +if __name__ == "__main__": + main()