diff --git a/.github/workflows/codspeed.yml b/.github/workflows/codspeed.yml index 7fa25e44..9f1a2195 100644 --- a/.github/workflows/codspeed.yml +++ b/.github/workflows/codspeed.yml @@ -38,7 +38,7 @@ jobs: run: pip install -e .[test] - name: Run benchmarks - uses: CodSpeedHQ/action@3959e9e296ef25296e93e32afcc97196f966e57f # v4.1.0 + uses: CodSpeedHQ/action@c6574d0c2a990bca2842ce9af71549c5bfd7fbe0 # v4.2.1 with: token: ${{ secrets.CODSPEED_TOKEN }} run: pytest tests/ --codspeed diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fd734573..46d7da4b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: trailing-whitespace - id: name-tests-test - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.13.3 + rev: v0.14.1 hooks: - id: ruff-check args: [ --fix ] @@ -31,9 +31,10 @@ repos: hooks: - id: codespell - repo: https://github.com/commit-check/commit-check - rev: v0.10.2 + rev: v2.0.0 hooks: - id: check-message + stages: [commit-msg] - id: check-branch # uncomment if you need. - id: check-author-name # uncomment if you need. - id: check-author-email # uncomment if you need. diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 171fd8aa..15456a62 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -3,6 +3,7 @@ description: ensures commit message to match regex entry: commit-check args: [--message] + stages: [commit-msg] pass_filenames: true language: python - id: check-branch diff --git a/README.rst b/README.rst index 213dbff4..4a3d8c26 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ Commit Check ============ -.. |pypi-version| image:: https://img.shields.io/pypi/v/commit-check?logo=python&logoColor=white&color=%2334D058 +.. |pypi-version| image:: https://img.shields.io/pypi/v/commit-check?logo=python&logoColor=white&color=%232c9ccd :target: https://pypi.org/project/commit-check/ :alt: PyPI @@ -9,23 +9,23 @@ Commit Check :target: https://github.com/commit-check/commit-check/actions/workflows/main.yml :alt: CI -.. |sonar-badge| image:: https://sonarcloud.io/api/project_badges/measure?project=commit-check_commit-check&metric=alert_status&color=%2334D058 +.. |sonar-badge| image:: https://sonarcloud.io/api/project_badges/measure?project=commit-check_commit-check&metric=alert_status :target: https://sonarcloud.io/summary/new_code?id=commit-check_commit-check :alt: Quality Gate Status -.. |codecov-badge| image:: https://codecov.io/gh/commit-check/commit-check/branch/main/graph/badge.svg?token=GC2U5V5ZRT&color=%2334D058 +.. |codecov-badge| image:: https://codecov.io/gh/commit-check/commit-check/branch/main/graph/badge.svg?token=GC2U5V5ZRT :target: https://codecov.io/gh/commit-check/commit-check :alt: CodeCov -.. |commit-check-badge| image:: https://img.shields.io/badge/commit--check-enabled-brightgreen?logo=Git&logoColor=white&color=%2334D058 +.. |commit-check-badge| image:: https://img.shields.io/badge/commit--check-enabled-brightgreen?logo=Git&logoColor=white&color=%232c9ccd :target: https://github.com/commit-check/commit-check :alt: commit-check -.. |slsa-badge| image:: https://slsa.dev/images/gh-badge-level3.svg?color=%2334D058 +.. |slsa-badge| image:: https://slsa.dev/images/gh-badge-level3.svg :target: https://slsa.dev :alt: SLSA -|pypi-version| |ci-badge| |sonar-badge| |commit-check-badge| |codecov-badge| |slsa-badge| +|ci-badge| |sonar-badge| |pypi-version| |commit-check-badge| |codecov-badge| |slsa-badge| Overview -------- @@ -142,7 +142,7 @@ Badging your repository You can add a badge to your repository to show that you use commit-check! -.. image:: https://img.shields.io/badge/commit--check-enabled-brightgreen?logo=Git&logoColor=white +.. image:: https://img.shields.io/badge/commit--check-enabled-brightgreen?logo=Git&logoColor=white&color=%232c9ccd :target: https://github.com/commit-check/commit-check :alt: commit-check @@ -150,13 +150,13 @@ Markdown .. code-block:: text - [![commit-check](https://img.shields.io/badge/commit--check-enabled-brightgreen?logo=Git&logoColor=white&color=%2334D058)](https://github.com/commit-check/commit-check) + [![commit-check](https://img.shields.io/badge/commit--check-enabled-brightgreen?logo=Git&logoColor=white&color=%232c9ccd)](https://github.com/commit-check/commit-check) reStructuredText .. code-block:: text - .. image:: https://img.shields.io/badge/commit--check-enabled-brightgreen?logo=Git&logoColor=white&color=%2334D058 + .. image:: https://img.shields.io/badge/commit--check-enabled-brightgreen?logo=Git&logoColor=white&color=%232c9ccd :target: https://github.com/commit-check/commit-check :alt: commit-check diff --git a/commit_check/__init__.py b/commit_check/__init__.py index 99aaf25f..2066ba85 100644 --- a/commit_check/__init__.py +++ b/commit_check/__init__.py @@ -44,8 +44,8 @@ # Handle different default values for different rules DEFAULT_BOOLEAN_RULES = { - "subject_capitalized": True, - "subject_imperative": True, + "subject_capitalized": False, + "subject_imperative": False, "allow_merge_commits": True, "allow_revert_commits": True, "allow_empty_commits": True, diff --git a/commit_check/imperatives.py b/commit_check/imperatives.py index 1a1684f5..6c1857b2 100644 --- a/commit_check/imperatives.py +++ b/commit_check/imperatives.py @@ -12,7 +12,7 @@ "adjust", "aggregate", "allow", - "append", + "alignappend", "apply", "archive", "assert", @@ -22,6 +22,7 @@ "authorize", "break", "build", + "bump", "cache", "calculate", "call", diff --git a/docs/_static/extra_css.css b/docs/_static/extra_css.css index ef001fb5..34f05968 100644 --- a/docs/_static/extra_css.css +++ b/docs/_static/extra_css.css @@ -7,10 +7,47 @@ thead { .md-header, .md-tabs, .md-nav--primary .md-nav__title[for="__drawer"] { - background-color: #009485; + background-color: #2c9ccd; } /* Fix table header visibility for both light and dark modes */ .md-content table th { color: var(--md-typeset-color) !important; } + +/* Custom color scheme to match logo */ +:root { + --md-primary-fg-color: #2c9ccd; + --md-primary-fg-color--light: #5bb3d9; + --md-primary-fg-color--dark: #1e85a8; +} + +/* Navigation and links */ +.md-nav__link--active, +.md-nav__link:hover { + color: #2c9ccd; +} + +/* Buttons and accent elements */ +.md-button--primary { + background-color: #2c9ccd; + border-color: #2c9ccd; +} + +.md-button--primary:hover { + background-color: #1e85a8; + border-color: #1e85a8; +} + +/* Code blocks and syntax highlighting accents */ +.md-typeset .codehilite .hll, +.md-typeset .highlight .hll { + background-color: rgba(44, 156, 205, 0.1); +} + +/* Admonition titles with your brand color */ +.md-typeset .admonition.note > .admonition-title, +.md-typeset .admonition.tip > .admonition-title { + background-color: rgba(44, 156, 205, 0.1); + border-color: #2c9ccd; +} diff --git a/docs/conf.py b/docs/conf.py index b42a5904..bc75624c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -55,8 +55,8 @@ { "media": "(prefers-color-scheme: light)", "scheme": "default", - "primary": "light-blue", - "accent": "deep-purple", + "primary": "blue", + "accent": "light-blue", "toggle": { "icon": "material/lightbulb-outline", "name": "Switch to dark mode", @@ -65,8 +65,8 @@ { "media": "(prefers-color-scheme: dark)", "scheme": "slate", - "primary": "light-blue", - "accent": "deep-purple", + "primary": "blue", + "accent": "light-blue", "toggle": { "icon": "material/lightbulb", "name": "Switch to light mode", diff --git a/docs/configuration.rst b/docs/configuration.rst index 5d2bff65..ee37bcc7 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -25,8 +25,8 @@ Example Configuration [commit] # https://www.conventionalcommits.org conventional_commits = true - subject_capitalized = true - subject_imperative = true + subject_capitalized = false + subject_imperative = false # subject_max_length = 50 # Optional - no limit by default # subject_min_length = 5 # Optional - no limit by default allow_commit_types = ["feat", "fix", "docs", "style", "refactor", "test", "chore"] diff --git a/docs/example.rst b/docs/example.rst index e7301f13..a064199e 100644 --- a/docs/example.rst +++ b/docs/example.rst @@ -31,6 +31,7 @@ Running as pre-commit hook rev: the tag or revision hooks: - id: check-message + stages: [commit-msg] - id: check-branch - id: check-author-name - id: check-author-email diff --git a/noxfile.py b/noxfile.py index 0cb42d58..44072ced 100644 --- a/noxfile.py +++ b/noxfile.py @@ -53,7 +53,7 @@ def coverage(session): @nox.session() def docs(session): session.install(".[docs]") - session.run("sphinx-build", "-E", "-W", "-b", "html", "docs", "_build/html") + session.run("sphinx-build", "-E", "-b", "html", "docs", "_build/html") @nox.session(name="docs-live") diff --git a/tests/config_edge_test.py b/tests/config_edge_test.py index 817ec8df..ccafb4df 100644 --- a/tests/config_edge_test.py +++ b/tests/config_edge_test.py @@ -6,6 +6,7 @@ from commit_check.config import load_config +@pytest.mark.benchmark def test_load_config_invalid_toml(): """Test handling of invalid TOML syntax.""" invalid_toml = b""" @@ -23,6 +24,7 @@ def test_load_config_invalid_toml(): os.unlink(f.name) +@pytest.mark.benchmark def test_load_config_file_permission_error(): """Test handling of file permission errors.""" config_content = b""" @@ -45,6 +47,7 @@ def test_load_config_file_permission_error(): os.unlink(f.name) +@pytest.mark.benchmark def test_tomli_import_fallback(): """Test the tomli import fallback when tomllib is not available.""" # We need to test the import fallback behavior diff --git a/tests/config_fallback_test.py b/tests/config_fallback_test.py index e839bf23..0c6a8e24 100644 --- a/tests/config_fallback_test.py +++ b/tests/config_fallback_test.py @@ -3,9 +3,11 @@ import sys import tempfile import os +import pytest from unittest.mock import patch +@pytest.mark.benchmark def test_config_tomli_fallback_direct(): """Test config.py fallback to tomli by manipulating imports.""" diff --git a/tests/config_import_test.py b/tests/config_import_test.py index 1bfc06f0..083e84fb 100644 --- a/tests/config_import_test.py +++ b/tests/config_import_test.py @@ -3,8 +3,10 @@ import tempfile import os from unittest.mock import patch +import pytest +@pytest.mark.benchmark def test_tomli_import_fallback_simulation(): """Test tomli import fallback by simulating the ImportError condition.""" @@ -63,6 +65,7 @@ def load(f): return __import__(name, *args, **kwargs) +@pytest.mark.benchmark def test_import_paths_coverage(): """Ensure both import paths are conceptually tested.""" # This test verifies that both the tomllib and tomli code paths diff --git a/tests/config_test.py b/tests/config_test.py index 83d9baa8..fb2d5259 100644 --- a/tests/config_test.py +++ b/tests/config_test.py @@ -9,6 +9,7 @@ class TestConfig: + @pytest.mark.benchmark def test_load_config_with_path_hint(self): """Test loading config with explicit path hint.""" config_content = b""" @@ -28,6 +29,7 @@ def test_load_config_with_path_hint(self): finally: os.unlink(f.name) + @pytest.mark.benchmark def test_load_config_with_nonexistent_path_hint(self): """Test loading config when path hint doesn't exist - should raise FileNotFoundError.""" # Test that specifying a nonexistent config file raises an error @@ -36,6 +38,7 @@ def test_load_config_with_nonexistent_path_hint(self): ): load_config("nonexistent.toml") + @pytest.mark.benchmark def test_load_config_default_cchk_toml(self): """Test loading config from default cchk.toml path.""" config_content = b""" @@ -55,6 +58,7 @@ def test_load_config_default_cchk_toml(self): finally: os.chdir(original_cwd) + @pytest.mark.benchmark def test_load_config_default_commit_check_toml(self): """Test loading config from default commit-check.toml path.""" config_content = b""" @@ -74,6 +78,7 @@ def test_load_config_default_commit_check_toml(self): finally: os.chdir(original_cwd) + @pytest.mark.benchmark def test_load_config_file_not_found(self): """Test returning empty config when no default config files exist.""" original_cwd = os.getcwd() @@ -86,6 +91,7 @@ def test_load_config_file_not_found(self): finally: os.chdir(original_cwd) + @pytest.mark.benchmark def test_load_config_file_not_found_with_invalid_path_hint(self): """Test FileNotFoundError when specified path hint doesn't exist.""" original_cwd = os.getcwd() @@ -100,12 +106,14 @@ def test_load_config_file_not_found_with_invalid_path_hint(self): finally: os.chdir(original_cwd) + @pytest.mark.benchmark def test_default_config_paths_constant(self): """Test that DEFAULT_CONFIG_PATHS contains expected paths.""" assert len(DEFAULT_CONFIG_PATHS) == 2 assert Path("cchk.toml") in DEFAULT_CONFIG_PATHS assert Path("commit-check.toml") in DEFAULT_CONFIG_PATHS + @pytest.mark.benchmark def test_toml_load_function_exists(self): """Test that toml_load function is properly set up.""" from commit_check.config import toml_load @@ -128,6 +136,7 @@ def test_toml_load_function_exists(self): finally: os.unlink(f.name) + @pytest.mark.benchmark def test_tomli_import_fallback(self): """Test that tomli is imported when tomllib is not available (lines 10-13).""" import sys diff --git a/tests/engine_comprehensive_test.py b/tests/engine_comprehensive_test.py index 256e3453..8ce16da3 100644 --- a/tests/engine_comprehensive_test.py +++ b/tests/engine_comprehensive_test.py @@ -17,9 +17,11 @@ CommitTypeValidator, ) from commit_check.rule_builder import ValidationRule +import pytest class TestValidationResult: + @pytest.mark.benchmark def test_validation_result_values(self): """Test ValidationResult enum values.""" assert ValidationResult.PASS == 0 @@ -27,6 +29,7 @@ def test_validation_result_values(self): class TestValidationContext: + @pytest.mark.benchmark def test_validation_context_creation(self): """Test ValidationContext creation.""" context = ValidationContext() @@ -41,6 +44,7 @@ def test_validation_context_creation(self): class TestCommitMessageValidator: + @pytest.mark.benchmark def test_commit_message_validator_creation(self): """Test CommitMessageValidator creation.""" rule = ValidationRule( @@ -53,6 +57,7 @@ def test_commit_message_validator_creation(self): assert validator.rule == rule @patch("commit_check.engine.has_commits") + @pytest.mark.benchmark def test_commit_message_validator_with_stdin(self, mock_has_commits): """Test CommitMessageValidator with stdin text.""" mock_has_commits.return_value = True @@ -71,6 +76,7 @@ def test_commit_message_validator_with_stdin(self, mock_has_commits): @patch("commit_check.engine.get_commit_info") @patch("commit_check.engine.has_commits") + @pytest.mark.benchmark def test_commit_message_validator_failure( self, mock_has_commits, mock_get_commit_info ): @@ -92,6 +98,7 @@ def test_commit_message_validator_failure( mock_print.assert_called_once() @patch("commit_check.engine.has_commits") + @pytest.mark.benchmark def test_commit_message_validator_skip_validation(self, mock_has_commits): """Test CommitMessageValidator skips when no commits and no stdin.""" mock_has_commits.return_value = False @@ -110,6 +117,7 @@ def test_commit_message_validator_skip_validation(self, mock_has_commits): class TestSubjectCapitalizationValidator: + @pytest.mark.benchmark def test_subject_capitalization_pass(self): """Test SubjectCapitalizationValidator pass case.""" rule = ValidationRule( @@ -125,6 +133,7 @@ def test_subject_capitalization_pass(self): result = validator.validate(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_subject_capitalization_fail(self): """Test SubjectCapitalizationValidator fail case.""" rule = ValidationRule( @@ -144,6 +153,7 @@ def test_subject_capitalization_fail(self): class TestSubjectImperativeValidator: + @pytest.mark.benchmark def test_subject_imperative_pass(self): """Test SubjectImperativeValidator pass case.""" rule = ValidationRule( @@ -159,6 +169,7 @@ def test_subject_imperative_pass(self): result = validator.validate(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_subject_imperative_fail(self): """Test SubjectImperativeValidator fail case.""" rule = ValidationRule( @@ -178,6 +189,7 @@ def test_subject_imperative_fail(self): class TestSubjectLengthValidator: + @pytest.mark.benchmark def test_subject_length_pass(self): """Test SubjectLengthValidator pass case.""" rule = ValidationRule( @@ -193,6 +205,7 @@ def test_subject_length_pass(self): result = validator.validate(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_subject_length_fail(self): """Test SubjectLengthValidator fail case.""" rule = ValidationRule( @@ -212,6 +225,7 @@ def test_subject_length_fail(self): class TestValidationEngine: + @pytest.mark.benchmark def test_validation_engine_creation(self): """Test ValidationEngine creation.""" rules = [ @@ -225,6 +239,7 @@ def test_validation_engine_creation(self): engine = ValidationEngine(rules) assert engine.rules == rules + @pytest.mark.benchmark def test_validation_engine_validator_map(self): """Test ValidationEngine VALIDATOR_MAP contains expected mappings.""" engine = ValidationEngine([]) @@ -252,6 +267,7 @@ def test_validation_engine_validator_map(self): for check, validator_class in expected_mappings.items(): assert engine.VALIDATOR_MAP[check] == validator_class + @pytest.mark.benchmark def test_validation_engine_validate_all_pass(self): """Test ValidationEngine validate_all with all passing rules.""" rules = [ @@ -269,6 +285,7 @@ def test_validation_engine_validate_all_pass(self): result = engine.validate_all(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_validation_engine_validate_all_fail(self): """Test ValidationEngine validate_all with failing rule.""" rules = [ @@ -286,6 +303,7 @@ def test_validation_engine_validate_all_fail(self): result = engine.validate_all(context) assert result == ValidationResult.FAIL + @pytest.mark.benchmark def test_validation_engine_unknown_validator(self): """Test ValidationEngine with unknown validator type.""" rules = [ diff --git a/tests/engine_test.py b/tests/engine_test.py index f34b1159..8cdabf7d 100644 --- a/tests/engine_test.py +++ b/tests/engine_test.py @@ -24,6 +24,7 @@ class TestValidationResult: + @pytest.mark.benchmark def test_validation_result_enum(self): """Test ValidationResult enum values.""" assert ValidationResult.PASS.value == 0 @@ -31,6 +32,7 @@ def test_validation_result_enum(self): class TestValidationContext: + @pytest.mark.benchmark def test_validation_context_creation(self): """Test ValidationContext creation and properties.""" context = ValidationContext( @@ -39,6 +41,7 @@ def test_validation_context_creation(self): assert context.stdin_text == "test message" assert context.commit_file == "/path/to/commit" + @pytest.mark.benchmark def test_validation_context_defaults(self): """Test ValidationContext with default values.""" context = ValidationContext() @@ -47,6 +50,7 @@ def test_validation_context_defaults(self): class TestBaseValidator: + @pytest.mark.benchmark def test_base_validator_is_abstract(self): """Test that BaseValidator cannot be instantiated directly.""" with pytest.raises(TypeError): @@ -54,6 +58,7 @@ def test_base_validator_is_abstract(self): class TestCommitMessageValidator: + @pytest.mark.benchmark def test_commit_message_validator_valid_conventional_commit(self): """Test CommitMessageValidator with valid conventional commit.""" rule = ValidationRule( @@ -66,6 +71,7 @@ def test_commit_message_validator_valid_conventional_commit(self): result = validator.validate(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_commit_message_validator_invalid_commit(self): """Test CommitMessageValidator with invalid commit message.""" rule = ValidationRule( @@ -78,6 +84,7 @@ def test_commit_message_validator_invalid_commit(self): result = validator.validate(context) assert result == ValidationResult.FAIL + @pytest.mark.benchmark def test_commit_message_validator_with_file(self): """Test CommitMessageValidator reading from file.""" rule = ValidationRule(check="message", regex=r"^(feat|fix):") @@ -95,6 +102,7 @@ def test_commit_message_validator_with_file(self): os.unlink(f.name) @patch("commit_check.engine.get_commit_info") + @pytest.mark.benchmark def test_commit_message_validator_file_not_found(self, mock_get_commit_info): """Test CommitMessageValidator with non-existent file.""" # Mock git fallback to return a message that doesn't match regex @@ -112,6 +120,7 @@ def test_commit_message_validator_file_not_found(self, mock_get_commit_info): assert result == ValidationResult.FAIL @patch("commit_check.engine.get_commit_info") + @pytest.mark.benchmark def test_commit_message_validator_from_git(self, mock_get_commit_info): """Test CommitMessageValidator reading from git.""" # Mock both subject ("s") and body ("b") calls @@ -133,6 +142,7 @@ def test_commit_message_validator_from_git(self, mock_get_commit_info): class TestBranchValidator: @patch("commit_check.engine.has_commits") @patch("commit_check.engine.get_branch_name") + @pytest.mark.benchmark def test_branch_validator_valid_branch( self, mock_get_branch_name, mock_has_commits ): @@ -150,6 +160,7 @@ def test_branch_validator_valid_branch( @patch("commit_check.engine.has_commits") @patch("commit_check.engine.get_branch_name") + @pytest.mark.benchmark def test_branch_validator_invalid_branch( self, mock_get_branch_name, mock_has_commits ): @@ -165,6 +176,7 @@ def test_branch_validator_invalid_branch( @patch("commit_check.engine.get_branch_name") @patch("commit_check.engine.get_commit_info") + @pytest.mark.benchmark def test_branch_validator_ignored_author( self, mock_get_commit_info, mock_get_branch_name ): @@ -178,6 +190,7 @@ def test_branch_validator_ignored_author( result = validator.validate(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_validate_with_stdin_text(self): """Test branch validation with stdin_text.""" rule = ValidationRule(check="branch", regex=r"^feature/") @@ -187,6 +200,7 @@ def test_validate_with_stdin_text(self): result = validator.validate(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_validate_without_regex(self): """Test branch validation without regex (should pass).""" rule = ValidationRule(check="branch") @@ -200,6 +214,7 @@ def test_validate_without_regex(self): class TestAuthorValidator: @patch("commit_check.engine.has_commits") @patch("commit_check.engine.get_commit_info") + @pytest.mark.benchmark def test_author_validator_name_valid(self, mock_get_commit_info, mock_has_commits): """Test AuthorValidator for author name.""" mock_has_commits.return_value = True @@ -213,6 +228,7 @@ def test_author_validator_name_valid(self, mock_get_commit_info, mock_has_commit @patch("commit_check.engine.has_commits") @patch("commit_check.engine.get_commit_info") + @pytest.mark.benchmark def test_author_validator_email_valid(self, mock_get_commit_info, mock_has_commits): """Test AuthorValidator for author email.""" mock_has_commits.return_value = True @@ -237,6 +253,7 @@ def test_author_validator_email_valid(self, mock_get_commit_info, mock_has_commi assert mock_get_commit_info.call_args_list[1][0][0] == "ae" @patch("commit_check.engine.get_commit_info") + @pytest.mark.benchmark def test_author_validator_ignored_author(self, mock_get_commit_info): """Test AuthorValidator skips validation for ignored author.""" mock_get_commit_info.return_value = "ignored" @@ -247,6 +264,7 @@ def test_author_validator_ignored_author(self, mock_get_commit_info): result = validator.validate(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_validate_author_with_allowed_list(self): """Test author validation with allowed list.""" rule = ValidationRule(check="author_name", allowed=["John Doe", "Jane Smith"]) @@ -258,6 +276,7 @@ def test_validate_author_with_allowed_list(self): result = validator.validate(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_validate_author_not_in_allowed_list(self): """Test author validation with name not in allowed list.""" rule = ValidationRule(check="author_name", allowed=["John Doe", "Jane Smith"]) @@ -270,6 +289,7 @@ def test_validate_author_not_in_allowed_list(self): result = validator.validate(context) assert result == ValidationResult.FAIL + @pytest.mark.benchmark def test_validate_author_in_ignored_list(self): """Test author validation with ignored authors.""" rule = ValidationRule(check="author_name", ignored=["Bot User", "CI User"]) @@ -281,6 +301,7 @@ def test_validate_author_in_ignored_list(self): result = validator.validate(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_get_author_value_with_email_format(self): """Test _get_author_value with email format.""" rule = ValidationRule(check="author_email") @@ -295,6 +316,7 @@ def test_get_author_value_with_email_format(self): class TestCommitTypeValidator: + @pytest.mark.benchmark def test_commit_type_validator_merge_commits(self): """Test CommitTypeValidator with merge commits.""" rule = ValidationRule(check="allow_merge_commits", value=True) @@ -304,6 +326,7 @@ def test_commit_type_validator_merge_commits(self): result = validator.validate(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_commit_type_validator_revert_commits(self): """Test CommitTypeValidator with revert commits.""" rule = ValidationRule(check="allow_revert_commits", value=True) @@ -313,6 +336,7 @@ def test_commit_type_validator_revert_commits(self): result = validator.validate(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_validate_merge_commit_allowed(self): """Test merge commit validation when allowed.""" rule = ValidationRule(check="allow_merge_commits", value=True) @@ -329,6 +353,7 @@ def test_validate_merge_commit_allowed(self): result = validator.validate(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_validate_merge_commit_not_allowed(self): """Test merge commit validation when not allowed.""" rule = ValidationRule(check="allow_merge_commits", value=False) @@ -346,6 +371,7 @@ def test_validate_merge_commit_not_allowed(self): result = validator.validate(context) assert result == ValidationResult.FAIL + @pytest.mark.benchmark def test_validate_revert_commit_allowed(self): """Test revert commit validation when allowed.""" rule = ValidationRule(check="allow_revert_commits", value=True) @@ -362,6 +388,7 @@ def test_validate_revert_commit_allowed(self): result = validator.validate(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_validate_fixup_commit_not_allowed(self): """Test fixup commit validation when not allowed.""" rule = ValidationRule(check="allow_fixup_commits", value=False) @@ -379,6 +406,7 @@ def test_validate_fixup_commit_not_allowed(self): result = validator.validate(context) assert result == ValidationResult.FAIL + @pytest.mark.benchmark def test_validate_wip_commit_allowed(self): """Test WIP commit validation when allowed.""" rule = ValidationRule(check="allow_wip_commits", value=True) @@ -397,6 +425,7 @@ def test_validate_wip_commit_allowed(self): class TestSubjectLengthValidator: + @pytest.mark.benchmark def test_subject_length_validator_max_valid(self): """Test SubjectLengthValidator with valid max length.""" rule = ValidationRule(check="subject_max_length", value=50) @@ -406,6 +435,7 @@ def test_subject_length_validator_max_valid(self): result = validator.validate(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_subject_length_validator_max_too_long(self): """Test SubjectLengthValidator with message too long.""" rule = ValidationRule(check="subject_max_length", value=20) @@ -417,6 +447,7 @@ def test_subject_length_validator_max_too_long(self): result = validator.validate(context) assert result == ValidationResult.FAIL + @pytest.mark.benchmark def test_subject_length_validator_min_valid(self): """Test SubjectLengthValidator with valid min length.""" rule = ValidationRule(check="subject_min_length", value=10) @@ -426,6 +457,7 @@ def test_subject_length_validator_min_valid(self): result = validator.validate(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_subject_length_validator_min_too_short(self): """Test SubjectLengthValidator with message too short.""" rule = ValidationRule(check="subject_min_length", value=20) @@ -437,6 +469,7 @@ def test_subject_length_validator_min_too_short(self): class TestSignoffValidator: + @pytest.mark.benchmark def test_signoff_validator_valid(self): """Test SignoffValidator with valid signoff.""" rule = ValidationRule( @@ -450,6 +483,7 @@ def test_signoff_validator_valid(self): result = validator.validate(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_signoff_validator_missing_signoff(self): """Test SignoffValidator with missing signoff.""" rule = ValidationRule(check="require_signed_off_by") @@ -459,6 +493,7 @@ def test_signoff_validator_missing_signoff(self): result = validator.validate(context) assert result == ValidationResult.FAIL + @pytest.mark.benchmark def test_validate_with_signoff_in_stdin(self): """Test signoff validation with stdin message containing signoff.""" rule = ValidationRule(check="require_signed_off_by", regex=r".*Signed-off-by.*") @@ -470,6 +505,7 @@ def test_validate_with_signoff_in_stdin(self): result = validator.validate(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_validate_without_signoff(self): """Test signoff validation without signoff.""" rule = ValidationRule(check="require_signed_off_by") @@ -480,6 +516,7 @@ def test_validate_without_signoff(self): result = validator.validate(context) assert result == ValidationResult.FAIL + @pytest.mark.benchmark def test_get_commit_message_from_context_file(self): """Test _get_commit_message with commit_file.""" rule = ValidationRule(check="require_signed_off_by") @@ -493,6 +530,7 @@ def test_get_commit_message_from_context_file(self): class TestSubjectCapitalizationValidator: + @pytest.mark.benchmark def test_subject_capitalization_validator_valid(self): """Test SubjectCapitalizationValidator with capitalized subject.""" rule = ValidationRule(check="subject_capitalized") @@ -502,6 +540,7 @@ def test_subject_capitalization_validator_valid(self): result = validator.validate(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_subject_capitalization_validator_not_capitalized(self): """Test SubjectCapitalizationValidator with non-capitalized subject.""" rule = ValidationRule(check="subject_capitalized") @@ -513,6 +552,7 @@ def test_subject_capitalization_validator_not_capitalized(self): class TestBodyValidator: + @pytest.mark.benchmark def test_body_validator_with_body(self): """Test BodyValidator with commit body.""" rule = ValidationRule(check="require_body") @@ -524,6 +564,7 @@ def test_body_validator_with_body(self): result = validator.validate(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_body_validator_no_body(self): """Test BodyValidator without commit body.""" rule = ValidationRule(check="require_body") @@ -533,6 +574,7 @@ def test_body_validator_no_body(self): result = validator.validate(context) assert result == ValidationResult.FAIL + @pytest.mark.benchmark def test_validate_with_body_present(self): """Test body validation with body present.""" rule = ValidationRule(check="require_body") @@ -542,6 +584,7 @@ def test_validate_with_body_present(self): result = validator.validate(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_validate_with_empty_lines_and_body(self): """Test body validation with empty lines before body.""" rule = ValidationRule(check="require_body") @@ -553,6 +596,7 @@ def test_validate_with_empty_lines_and_body(self): result = validator.validate(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_validate_without_body(self): """Test body validation without body.""" rule = ValidationRule(check="require_body") @@ -566,6 +610,7 @@ def test_validate_without_body(self): class TestMergeBaseValidator: @patch("commit_check.util.git_merge_base") + @pytest.mark.benchmark def test_merge_base_validator_valid(self, mock_git_merge_base): """Test MergeBaseValidator with valid merge base.""" mock_git_merge_base.return_value = 0 @@ -580,6 +625,7 @@ def test_merge_base_validator_valid(self, mock_git_merge_base): @patch("commit_check.engine.has_commits") @patch("commit_check.engine.get_branch_name") @patch("commit_check.util.git_merge_base") + @pytest.mark.benchmark def test_merge_base_validator_invalid( self, mock_git_merge_base, mock_get_branch_name, mock_has_commits ): @@ -597,6 +643,7 @@ def test_merge_base_validator_invalid( result = validator.validate(context) assert result == ValidationResult.FAIL + @pytest.mark.benchmark def test_validate_with_merge_base_ahead(self): """Test merge base validation when branch is ahead.""" rule = ValidationRule(check="merge_base") @@ -607,6 +654,7 @@ def test_validate_with_merge_base_ahead(self): result = validator.validate(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_validate_with_merge_base_skip_conditions(self): """Test merge base validation skip conditions.""" rule = ValidationRule(check="merge_base") @@ -619,6 +667,7 @@ def test_validate_with_merge_base_skip_conditions(self): class TestValidationEngine: + @pytest.mark.benchmark def test_validation_engine_creation(self): """Test ValidationEngine creation.""" rules = [ @@ -630,6 +679,7 @@ def test_validation_engine_creation(self): assert len(engine.rules) == 2 assert engine.rules == rules + @pytest.mark.benchmark def test_validation_engine_validate_all_pass(self): """Test ValidationEngine with all validations passing.""" rules = [ValidationRule(check="message", regex=r"^feat:")] @@ -639,6 +689,7 @@ def test_validation_engine_validate_all_pass(self): result = engine.validate_all(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_validation_engine_validate_all_fail(self): """Test ValidationEngine with some validations failing.""" rules = [ @@ -651,6 +702,7 @@ def test_validation_engine_validate_all_fail(self): result = engine.validate_all(context) assert result == ValidationResult.FAIL + @pytest.mark.benchmark def test_validation_engine_empty_rules(self): """Test ValidationEngine with no rules.""" engine = ValidationEngine([]) @@ -659,6 +711,7 @@ def test_validation_engine_empty_rules(self): result = engine.validate_all(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_validation_engine_unknown_validator_type(self): """Test ValidationEngine with unknown validator type.""" rules = [ValidationRule(check="unknown_check", regex=r".*")] @@ -669,6 +722,7 @@ def test_validation_engine_unknown_validator_type(self): result = engine.validate_all(context) assert result == ValidationResult.PASS # No validation performed = PASS + @pytest.mark.benchmark def test_validate_all_with_unknown_validator(self): """Test validation engine with unknown validator type.""" rules = [ @@ -683,6 +737,7 @@ def test_validate_all_with_unknown_validator(self): result == ValidationResult.PASS ) # Unknown validator skipped, remaining passes + @pytest.mark.benchmark def test_validate_all_mixed_results(self): """Test validation engine with mixed pass/fail results.""" rules = [ @@ -700,6 +755,7 @@ def test_validate_all_mixed_results(self): class TestSubjectValidator: """Test SubjectValidator base class.""" + @pytest.mark.benchmark def test_get_subject_with_context_stdin(self): """Test _get_subject with stdin_text.""" rule = ValidationRule(check="subject_capitalized") @@ -709,6 +765,7 @@ def test_get_subject_with_context_stdin(self): subject = validator._get_subject(context) assert subject == "feat: add new feature" + @pytest.mark.benchmark def test_get_subject_with_context_file(self): """Test _get_subject with commit_file.""" rule = ValidationRule(check="subject_capitalized") @@ -721,6 +778,7 @@ def test_get_subject_with_context_file(self): subject = validator._get_subject(context) assert subject == "fix: resolve bug" + @pytest.mark.benchmark def test_get_subject_fallback_to_git(self): """Test _get_subject fallback to git.""" rule = ValidationRule(check="subject_capitalized") @@ -733,6 +791,7 @@ def test_get_subject_fallback_to_git(self): subject = validator._get_subject(context) assert subject == "chore: update deps" + @pytest.mark.benchmark def test_get_subject_with_file_not_found(self): """Test _get_subject when commit file not found.""" rule = ValidationRule(check="subject_capitalized") @@ -749,6 +808,7 @@ def test_get_subject_with_file_not_found(self): class TestSubjectImperativeValidator: """Test SubjectImperativeValidator edge cases.""" + @pytest.mark.benchmark def test_validate_with_imperative_subject(self): """Test validation with proper imperative subject.""" rule = ValidationRule(check="imperative") @@ -758,6 +818,7 @@ def test_validate_with_imperative_subject(self): result = validator.validate(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_validate_with_non_imperative_subject(self): """Test validation with non-imperative subject.""" rule = ValidationRule(check="imperative") @@ -769,6 +830,7 @@ def test_validate_with_non_imperative_subject(self): result = validator.validate(context) assert result == ValidationResult.FAIL + @pytest.mark.benchmark def test_validate_short_subject(self): """Test validation with very short subject (edge case).""" rule = ValidationRule(check="imperative") @@ -779,6 +841,7 @@ def test_validate_short_subject(self): result = validator.validate(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_validate_with_breaking_change(self): """Test validation with breaking change notation.""" rule = ValidationRule(check="imperative") @@ -789,6 +852,7 @@ def test_validate_with_breaking_change(self): result = validator.validate(context) assert result == ValidationResult.PASS + @pytest.mark.benchmark def test_validate_with_scoped_breaking_change(self): """Test validation with scoped breaking change notation.""" rule = ValidationRule(check="imperative") diff --git a/tests/main_test.py b/tests/main_test.py index 34d2e485..b03ab76c 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -8,6 +8,7 @@ class TestMain: + @pytest.mark.benchmark def test_help(self, capfd): sys.argv = [CMD, "--help"] with pytest.raises(SystemExit): @@ -15,17 +16,20 @@ def test_help(self, capfd): out, _ = capfd.readouterr() assert "usage:" in out + @pytest.mark.benchmark def test_version(self): # argparse defines --version sys.argv = [CMD, "--version"] with pytest.raises(SystemExit): main() + @pytest.mark.benchmark def test_no_args_shows_help(self, capfd): """When no arguments are provided, should show help and exit 0.""" sys.argv = [CMD] assert main() == 0 + @pytest.mark.benchmark def test_message_validation_with_valid_commit(self, mocker): """Test that a valid commit message passes validation.""" # Mock stdin to provide a valid commit message @@ -35,6 +39,7 @@ def test_message_validation_with_valid_commit(self, mocker): sys.argv = [CMD, "-m"] assert main() == 0 + @pytest.mark.benchmark def test_message_validation_with_invalid_commit(self, mocker): """Test that an invalid commit message fails validation.""" # Mock stdin to provide an invalid commit message @@ -47,6 +52,7 @@ def test_message_validation_with_invalid_commit(self, mocker): sys.argv = [CMD, "-m"] assert main() == 1 + @pytest.mark.benchmark def test_message_validation_from_file(self): """Test validation of commit message from a file.""" with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: @@ -59,6 +65,7 @@ def test_message_validation_from_file(self): finally: os.unlink(f.name) + @pytest.mark.benchmark def test_branch_validation(self, mocker): """Test branch name validation.""" # Mock git command to return a valid branch name @@ -72,6 +79,7 @@ def test_branch_validation(self, mocker): sys.argv = [CMD, "-b"] assert main() == 0 + @pytest.mark.benchmark def test_author_name_validation(self, mocker): """Test author name validation.""" # Mock git command to return a valid author name @@ -85,6 +93,7 @@ def test_author_name_validation(self, mocker): sys.argv = [CMD, "-n"] assert main() == 0 + @pytest.mark.benchmark def test_author_email_validation(self, mocker): """Test author email validation.""" # Mock git command to return a valid author email @@ -98,6 +107,7 @@ def test_author_email_validation(self, mocker): sys.argv = [CMD, "-e"] assert main() == 0 + @pytest.mark.benchmark def test_dry_run_always_passes(self, mocker): """Test that dry run mode always returns 0.""" # Mock stdin to provide an invalid commit message @@ -111,6 +121,7 @@ def test_dry_run_always_passes(self, mocker): class TestStdinReader: """Test StdinReader edge cases.""" + @pytest.mark.benchmark def test_read_piped_input_with_exception(self, mocker): """Test StdinReader when stdin raises exception.""" reader = StdinReader() @@ -120,6 +131,7 @@ def test_read_piped_input_with_exception(self, mocker): result = reader.read_piped_input() assert result is None + @pytest.mark.benchmark def test_read_piped_input_with_ioerror(self, mocker): """Test StdinReader when stdin raises IOError.""" reader = StdinReader() @@ -133,6 +145,7 @@ def test_read_piped_input_with_ioerror(self, mocker): class TestGetMessageContent: """Test _get_message_content function edge cases.""" + @pytest.mark.benchmark def test_get_message_content_empty_string_with_stdin(self, mocker): """Test _get_message_content with empty string and stdin available.""" reader = StdinReader() @@ -141,6 +154,7 @@ def test_get_message_content_empty_string_with_stdin(self, mocker): result = _get_message_content("", reader) assert result == "piped message" + @pytest.mark.benchmark def test_get_message_content_empty_string_no_stdin_with_git(self, mocker): """Test _get_message_content with empty string, no stdin, fallback to git.""" reader = StdinReader() @@ -152,6 +166,7 @@ def test_get_message_content_empty_string_no_stdin_with_git(self, mocker): result = _get_message_content("", reader) assert result == "git commit message" + @pytest.mark.benchmark def test_get_message_content_empty_string_no_stdin_git_fails(self, capsys, mocker): """Test _get_message_content with empty string, no stdin, git fails.""" reader = StdinReader() @@ -166,6 +181,7 @@ def test_get_message_content_empty_string_no_stdin_git_fails(self, capsys, mocke captured = capsys.readouterr() assert "Error: No commit message provided" in captured.err + @pytest.mark.benchmark def test_get_message_content_file_read_error(self, capsys): """Test _get_message_content with file read error.""" reader = StdinReader() @@ -176,6 +192,7 @@ def test_get_message_content_file_read_error(self, capsys): captured = capsys.readouterr() assert "Error reading message file" in captured.err + @pytest.mark.benchmark def test_get_message_content_file_permission_error(self, capsys, mocker): """Test _get_message_content with file permission error.""" reader = StdinReader() @@ -191,6 +208,7 @@ def test_get_message_content_file_permission_error(self, capsys, mocker): class TestMainFunctionEdgeCases: """Test main function edge cases for better coverage.""" + @pytest.mark.benchmark def test_main_with_message_file_argument(self): """Test main function with --message pointing to a file.""" with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: @@ -204,6 +222,7 @@ def test_main_with_message_file_argument(self): finally: os.unlink(f.name) + @pytest.mark.benchmark def test_main_with_message_empty_string_and_stdin(self, mocker): """Test main function with --message (empty) and stdin input.""" mocker.patch("sys.stdin.isatty", return_value=False) @@ -213,6 +232,7 @@ def test_main_with_message_empty_string_and_stdin(self, mocker): result = main() assert result == 0 + @pytest.mark.benchmark def test_main_with_message_empty_string_no_stdin_with_git(self, mocker): """Test main function with --message (empty), no stdin, git fallback.""" mocker.patch("sys.stdin.isatty", return_value=True) @@ -226,6 +246,7 @@ def test_main_with_message_empty_string_no_stdin_with_git(self, mocker): # Removed problematic config and multi-check tests due to complex validation dependencies + @pytest.mark.benchmark def test_main_with_invalid_config_file(self, mocker): """Test main function with invalid config file.""" mocker.patch("sys.stdin.isatty", return_value=False) @@ -243,6 +264,7 @@ def test_main_with_invalid_config_file(self, mocker): # Removed problematic tests that had configuration dependency issues + @pytest.mark.benchmark def test_main_with_dry_run_all_checks(self, mocker): """Test main function with dry run and all checks.""" # Mock git operations @@ -265,6 +287,7 @@ def test_main_with_dry_run_all_checks(self, mocker): result = main() assert result == 0 # Dry run always returns 0 + @pytest.mark.benchmark def test_main_error_handling_subprocess_failure(self, mocker, capsys): """Test main function when subprocess operations fail.""" # Mock subprocess to fail @@ -277,6 +300,7 @@ def test_main_error_handling_subprocess_failure(self, mocker, capsys): # Even if subprocess fails, main should not crash assert result in [0, 1] # Either passes or fails gracefully + @pytest.mark.benchmark def test_nonexistent_config_file_error(self, capsys): """Test that specifying a non-existent config file returns error.""" sys.argv = [ diff --git a/tests/rule_builder_test.py b/tests/rule_builder_test.py index 66bd88ce..09daa0e2 100644 --- a/tests/rule_builder_test.py +++ b/tests/rule_builder_test.py @@ -2,9 +2,11 @@ from commit_check.rule_builder import ValidationRule, RuleBuilder from commit_check.rules_catalog import RuleCatalogEntry +import pytest class TestValidationRule: + @pytest.mark.benchmark def test_validation_rule_to_dict_with_ignored(self): """Test ValidationRule.to_dict() method with ignored field.""" rule = ValidationRule(check="test_check", ignored=["ignored1", "ignored2"]) @@ -14,6 +16,7 @@ def test_validation_rule_to_dict_with_ignored(self): class TestRuleBuilder: + @pytest.mark.benchmark def test_rule_builder_conventional_commits_disabled(self): """Test RuleBuilder when conventional_commits is disabled (line 115).""" config = {"commit": {"conventional_commits": False}} @@ -27,6 +30,7 @@ def test_rule_builder_conventional_commits_disabled(self): rule = builder._build_conventional_commit_rule(catalog_entry) assert rule is None + @pytest.mark.benchmark def test_rule_builder_conventional_branch_disabled(self): """Test RuleBuilder when conventional_branch is disabled (line 133).""" config = {"branch": {"conventional_branch": False}} @@ -38,6 +42,7 @@ def test_rule_builder_conventional_branch_disabled(self): rule = builder._build_conventional_branch_rule(catalog_entry) assert rule is None + @pytest.mark.benchmark def test_rule_builder_ignore_authors_list(self): """Test RuleBuilder with ignore_authors list.""" config = {"commit": {"ignore_authors": ["spam@example.com", "bot@example.com"]}} @@ -56,6 +61,7 @@ def test_rule_builder_ignore_authors_list(self): assert rule.check == "ignore_authors" assert rule.ignored == ["spam@example.com", "bot@example.com"] + @pytest.mark.benchmark def test_rule_builder_invalid_author_list_type(self): """Test RuleBuilder with invalid author list type returns None.""" config = {"commit": {"ignore_authors": "not_a_list"}} @@ -69,6 +75,7 @@ def test_rule_builder_invalid_author_list_type(self): rule = builder._build_author_list_rule(catalog_entry, "ignore_authors") assert rule is None + @pytest.mark.benchmark def test_rule_builder_length_rule_with_format(self): """Test RuleBuilder length rule with formatted error message (lines 154-160).""" config = {"commit": {"max_length": 50}} @@ -89,6 +96,7 @@ def test_rule_builder_length_rule_with_format(self): assert rule.suggest == "Keep message short" assert rule.value == 50 + @pytest.mark.benchmark def test_rule_builder_merge_base_rule_valid_target(self): """Test RuleBuilder merge base rule with valid target (line 193).""" config = {"branch": {"require_rebase_target": "main"}} @@ -109,6 +117,7 @@ def test_rule_builder_merge_base_rule_valid_target(self): assert rule.error == "Branch must be rebased on target" assert rule.suggest == "Rebase on target branch" + @pytest.mark.benchmark def test_rule_builder_boolean_rule_enabled(self): """Test RuleBuilder boolean rule when enabled (line 236).""" config = {"commit": {"require_signed_off_by": True}} @@ -130,6 +139,7 @@ def test_rule_builder_boolean_rule_enabled(self): assert rule.suggest == "Add Signed-off-by line" assert rule.value is True + @pytest.mark.benchmark def test_rule_builder_boolean_rule_subject_disabled(self): """Test RuleBuilder boolean rule for subject checks when disabled (line 232).""" config = {"commit": {"subject_capitalized": False}} diff --git a/tests/util_test.py b/tests/util_test.py index b5870550..fda3e898 100644 --- a/tests/util_test.py +++ b/tests/util_test.py @@ -93,6 +93,7 @@ class TestGitMergeBase: (128, 128), # error case ], ) + @pytest.mark.benchmark def test_git_merge_base(self, mocker, returncode, expected): mock_run = mocker.patch("subprocess.run") if returncode == 128: @@ -123,6 +124,7 @@ class TestGetCommitInfo: ("ae"), ], ) + @pytest.mark.benchmark def test_get_commit_info(self, mocker, format_string): # Must call get_commit_info with given argument when there are commits. mocker.patch("commit_check.util.has_commits", return_value=True) @@ -204,6 +206,7 @@ def test_cmd_output(self, mocker): (1, None, "err"), ], ) + @pytest.mark.benchmark def test_cmd_output_err(self, mocker, returncode, stdout, stderr): # Must return stderr when subprocess.run returns not empty stderr. m_subprocess_run = mocker.patch( @@ -230,6 +233,7 @@ def test_cmd_output_err(self, mocker, returncode, stdout, stderr): (1, None, ""), ], ) + @pytest.mark.benchmark def test_cmd_output_err_with_len0_stderr( self, mocker, returncode, stdout, stderr ): @@ -269,6 +273,7 @@ def test_print_error_header(self, capfd): ("signoff", "check failed ==>"), ], ) + @pytest.mark.benchmark def test_print_error_message(self, capfd, check_type, type_failed_msg): # Must print on stdout with given argument. dummy_regex = "dummy regex"