cov-loupe 3.0.0 → 4.0.0.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/AGENTS.md +230 -0
- data/CLAUDE.md +5 -0
- data/CODE_OF_CONDUCT.md +62 -0
- data/CONTRIBUTING.md +102 -0
- data/GEMINI.md +5 -0
- data/README.md +154 -51
- data/RELEASE_NOTES.md +452 -0
- data/dev/images/cov-loupe-icon-lores.png +0 -0
- data/dev/images/cov-loupe-icon-square.png +0 -0
- data/dev/images/cov-loupe-icon.png +0 -0
- data/dev/images/cov-loupe-logo.png +0 -0
- data/dev/prompts/README.md +74 -0
- data/dev/prompts/archive/architectural-review-and-actions-prompt.md +53 -0
- data/dev/prompts/archive/investigate-and-report-issues-prompt.md +33 -0
- data/dev/prompts/archive/produce-action-items-prompt.md +25 -0
- data/dev/prompts/guidelines/ai-code-evaluator-guidelines.md +337 -0
- data/dev/prompts/improve/refactor-test-suite.md +18 -0
- data/dev/prompts/improve/simplify-code-logic.md +133 -0
- data/dev/prompts/improve/update-documentation.md +21 -0
- data/dev/prompts/review/comprehensive-codebase-review.md +176 -0
- data/dev/prompts/review/identify-action-items.md +143 -0
- data/dev/prompts/review/verify-code-changes.md +54 -0
- data/dev/prompts/validate/create-screencast-outline.md +234 -0
- data/dev/prompts/validate/test-documentation-examples.md +180 -0
- data/docs/QUICKSTART.md +63 -0
- data/docs/assets/images/cov-loupe-logo-lores.png +0 -0
- data/docs/assets/images/cov-loupe-logo.png +0 -0
- data/docs/assets/images/favicon.png +0 -0
- data/docs/assets/stylesheets/branding.css +16 -0
- data/docs/assets/stylesheets/extra.css +15 -0
- data/docs/code_of_conduct.md +1 -0
- data/docs/contributing.md +1 -0
- data/docs/dev/ARCHITECTURE.md +56 -11
- data/docs/dev/DEVELOPMENT.md +116 -12
- data/docs/dev/FUTURE_ENHANCEMENTS.md +14 -0
- data/docs/dev/README.md +3 -2
- data/docs/dev/RELEASING.md +2 -0
- data/docs/dev/arch-decisions/README.md +10 -7
- data/docs/dev/arch-decisions/application-architecture.md +259 -0
- data/docs/dev/arch-decisions/coverage-data-quality.md +193 -0
- data/docs/dev/arch-decisions/output-character-mode.md +217 -0
- data/docs/dev/arch-decisions/path-resolution.md +90 -0
- data/docs/dev/arch-decisions/{004-x-arch-decision.md → policy-validation.md} +32 -28
- data/docs/dev/arch-decisions/{005-x-arch-decision.md → simplecov-integration.md} +47 -44
- data/docs/dev/presentations/cov-loupe-presentation.md +15 -13
- data/docs/examples/mcp-inputs.md +3 -0
- data/docs/examples/prompts.md +3 -0
- data/docs/examples/success_predicates.md +3 -0
- data/docs/fixtures/demo_project/.resultset.json +170 -0
- data/docs/fixtures/demo_project/README.md +6 -0
- data/docs/fixtures/demo_project/app/controllers/admin/audit_logs_controller.rb +19 -0
- data/docs/fixtures/demo_project/app/controllers/orders_controller.rb +26 -0
- data/docs/fixtures/demo_project/app/models/order.rb +20 -0
- data/docs/fixtures/demo_project/app/models/user.rb +19 -0
- data/docs/fixtures/demo_project/lib/api/client.rb +22 -0
- data/docs/fixtures/demo_project/lib/ops/jobs/cleanup_job.rb +16 -0
- data/docs/fixtures/demo_project/lib/ops/jobs/report_job.rb +17 -0
- data/docs/fixtures/demo_project/lib/payments/processor.rb +15 -0
- data/docs/fixtures/demo_project/lib/payments/refund_service.rb +15 -0
- data/docs/fixtures/demo_project/lib/payments/reporting/exporter.rb +16 -0
- data/docs/index.md +1 -0
- data/docs/license.md +3 -0
- data/docs/release_notes.md +3 -0
- data/docs/user/ADVANCED_USAGE.md +208 -115
- data/docs/user/CLI_FALLBACK_FOR_LLMS.md +2 -0
- data/docs/user/CLI_USAGE.md +276 -101
- data/docs/user/ERROR_HANDLING.md +4 -4
- data/docs/user/EXAMPLES.md +121 -128
- data/docs/user/INSTALLATION.md +9 -28
- data/docs/user/LIBRARY_API.md +227 -122
- data/docs/user/MCP_INTEGRATION.md +114 -203
- data/docs/user/README.md +5 -1
- data/docs/user/TROUBLESHOOTING.md +49 -27
- data/docs/user/installing-a-prelease-version-of-covloupe.md +43 -0
- data/docs/user/{V2-BREAKING-CHANGES.md → migrations/MIGRATING_TO_V2.md} +62 -72
- data/docs/user/migrations/MIGRATING_TO_V3.md +72 -0
- data/docs/user/migrations/MIGRATING_TO_V4.md +591 -0
- data/docs/user/migrations/README.md +22 -0
- data/docs/user/prompts/README.md +9 -0
- data/docs/user/prompts/non-web-coverage-analysis-prompt.md +103 -0
- data/docs/user/prompts/rails-coverage-analysis-prompt.md +94 -0
- data/docs/user/prompts/use-cli-not-mcp-prompt.md +53 -0
- data/examples/cli_demo.sh +77 -0
- data/examples/filter_and_table_demo-output.md +114 -0
- data/examples/filter_and_table_demo.rb +174 -0
- data/examples/fixtures/demo_project/coverage/.resultset.json +10 -0
- data/examples/mcp-inputs/README.md +66 -0
- data/examples/mcp-inputs/coverage_detailed.json +1 -0
- data/examples/mcp-inputs/coverage_raw.json +1 -0
- data/examples/mcp-inputs/coverage_summary.json +1 -0
- data/examples/mcp-inputs/list.json +1 -0
- data/examples/mcp-inputs/uncovered_lines.json +1 -0
- data/examples/prompts/README.md +27 -0
- data/examples/prompts/custom_resultset.txt +2 -0
- data/examples/prompts/detailed_with_source.txt +2 -0
- data/examples/prompts/list_lowest.txt +2 -0
- data/examples/prompts/summary.txt +2 -0
- data/examples/prompts/uncovered.txt +2 -0
- data/examples/success_predicates/README.md +198 -0
- data/examples/success_predicates/all_files_above_threshold_predicate.rb +21 -0
- data/examples/success_predicates/directory_specific_thresholds_predicate.rb +30 -0
- data/examples/success_predicates/project_coverage_minimum_predicate.rb +6 -0
- data/lib/cov_loupe/base_tool.rb +229 -20
- data/lib/cov_loupe/cli.rb +132 -23
- data/lib/cov_loupe/commands/base_command.rb +25 -6
- data/lib/cov_loupe/commands/command_factory.rb +0 -1
- data/lib/cov_loupe/commands/detailed_command.rb +10 -5
- data/lib/cov_loupe/commands/list_command.rb +2 -1
- data/lib/cov_loupe/commands/raw_command.rb +7 -5
- data/lib/cov_loupe/commands/summary_command.rb +12 -7
- data/lib/cov_loupe/commands/totals_command.rb +74 -10
- data/lib/cov_loupe/commands/uncovered_command.rb +7 -5
- data/lib/cov_loupe/commands/validate_command.rb +11 -3
- data/lib/cov_loupe/commands/version_command.rb +6 -4
- data/lib/cov_loupe/{app_config.rb → config/app_config.rb} +13 -5
- data/lib/cov_loupe/config/app_context.rb +43 -0
- data/lib/cov_loupe/config/boolean_type.rb +91 -0
- data/lib/cov_loupe/config/logger.rb +92 -0
- data/lib/cov_loupe/{option_normalizers.rb → config/option_normalizers.rb} +55 -24
- data/lib/cov_loupe/{option_parser_builder.rb → config/option_parser_builder.rb} +46 -24
- data/lib/cov_loupe/coverage/coverage_calculator.rb +53 -0
- data/lib/cov_loupe/coverage/coverage_reporter.rb +63 -0
- data/lib/cov_loupe/coverage/coverage_table_formatter.rb +133 -0
- data/lib/cov_loupe/{error_handler.rb → errors/error_handler.rb} +21 -33
- data/lib/cov_loupe/{errors.rb → errors/errors.rb} +48 -71
- data/lib/cov_loupe/formatters/formatters.rb +75 -0
- data/lib/cov_loupe/formatters/source_formatter.rb +18 -7
- data/lib/cov_loupe/formatters/table_formatter.rb +80 -0
- data/lib/cov_loupe/loaders/all.rb +15 -0
- data/lib/cov_loupe/loaders/all_cli.rb +10 -0
- data/lib/cov_loupe/loaders/all_mcp.rb +23 -0
- data/lib/cov_loupe/loaders/resultset_loader.rb +147 -0
- data/lib/cov_loupe/mcp_server.rb +3 -2
- data/lib/cov_loupe/model/model.rb +520 -0
- data/lib/cov_loupe/model/model_data.rb +13 -0
- data/lib/cov_loupe/model/model_data_cache.rb +116 -0
- data/lib/cov_loupe/option_parsers/env_options_parser.rb +17 -6
- data/lib/cov_loupe/option_parsers/error_helper.rb +16 -10
- data/lib/cov_loupe/output_chars.rb +192 -0
- data/lib/cov_loupe/paths/glob_utils.rb +100 -0
- data/lib/cov_loupe/{path_relativizer.rb → paths/path_relativizer.rb} +5 -13
- data/lib/cov_loupe/paths/path_utils.rb +265 -0
- data/lib/cov_loupe/paths/volume_case_sensitivity.rb +173 -0
- data/lib/cov_loupe/presenters/base_coverage_presenter.rb +9 -13
- data/lib/cov_loupe/presenters/coverage_payload_presenter.rb +21 -0
- data/lib/cov_loupe/presenters/payload_caching.rb +23 -0
- data/lib/cov_loupe/presenters/project_coverage_presenter.rb +73 -21
- data/lib/cov_loupe/presenters/project_totals_presenter.rb +16 -10
- data/lib/cov_loupe/repositories/coverage_repository.rb +149 -0
- data/lib/cov_loupe/resolvers/coverage_line_resolver.rb +90 -76
- data/lib/cov_loupe/resolvers/{resolver_factory.rb → resolver_helpers.rb} +6 -5
- data/lib/cov_loupe/resolvers/resultset_path_resolver.rb +40 -12
- data/lib/cov_loupe/scripts/command_execution.rb +113 -0
- data/lib/cov_loupe/scripts/latest_ci_status.rb +97 -0
- data/lib/cov_loupe/scripts/pre_release_check.rb +164 -0
- data/lib/cov_loupe/scripts/setup_doc_server.rb +23 -0
- data/lib/cov_loupe/scripts/start_doc_server.rb +24 -0
- data/lib/cov_loupe/staleness/stale_status.rb +23 -0
- data/lib/cov_loupe/staleness/staleness_checker.rb +328 -0
- data/lib/cov_loupe/staleness/staleness_message_formatter.rb +91 -0
- data/lib/cov_loupe/tools/coverage_detailed_tool.rb +14 -15
- data/lib/cov_loupe/tools/coverage_raw_tool.rb +14 -14
- data/lib/cov_loupe/tools/coverage_summary_tool.rb +16 -16
- data/lib/cov_loupe/tools/coverage_table_tool.rb +139 -21
- data/lib/cov_loupe/tools/coverage_totals_tool.rb +31 -13
- data/lib/cov_loupe/tools/help_tool.rb +16 -20
- data/lib/cov_loupe/tools/list_tool.rb +65 -0
- data/lib/cov_loupe/tools/uncovered_lines_tool.rb +14 -14
- data/lib/cov_loupe/tools/validate_tool.rb +18 -24
- data/lib/cov_loupe/tools/version_tool.rb +8 -3
- data/lib/cov_loupe/version.rb +1 -1
- data/lib/cov_loupe.rb +83 -55
- metadata +184 -154
- data/docs/dev/BRANCH_ONLY_COVERAGE.md +0 -158
- data/docs/dev/arch-decisions/001-x-arch-decision.md +0 -95
- data/docs/dev/arch-decisions/002-x-arch-decision.md +0 -159
- data/docs/dev/arch-decisions/003-x-arch-decision.md +0 -165
- data/lib/cov_loupe/app_context.rb +0 -26
- data/lib/cov_loupe/constants.rb +0 -22
- data/lib/cov_loupe/coverage_reporter.rb +0 -31
- data/lib/cov_loupe/formatters.rb +0 -51
- data/lib/cov_loupe/mode_detector.rb +0 -56
- data/lib/cov_loupe/model.rb +0 -339
- data/lib/cov_loupe/presenters/coverage_detailed_presenter.rb +0 -14
- data/lib/cov_loupe/presenters/coverage_raw_presenter.rb +0 -14
- data/lib/cov_loupe/presenters/coverage_summary_presenter.rb +0 -14
- data/lib/cov_loupe/presenters/coverage_uncovered_presenter.rb +0 -14
- data/lib/cov_loupe/resultset_loader.rb +0 -131
- data/lib/cov_loupe/staleness_checker.rb +0 -247
- data/lib/cov_loupe/table_formatter.rb +0 -64
- data/lib/cov_loupe/tools/all_files_coverage_tool.rb +0 -51
- data/lib/cov_loupe/util.rb +0 -88
- data/spec/MCP_INTEGRATION_TESTS_README.md +0 -111
- data/spec/TIMESTAMPS.md +0 -48
- data/spec/all_files_coverage_tool_spec.rb +0 -53
- data/spec/app_config_spec.rb +0 -142
- data/spec/base_tool_spec.rb +0 -62
- data/spec/cli/show_default_report_spec.rb +0 -33
- data/spec/cli_enumerated_options_spec.rb +0 -90
- data/spec/cli_error_spec.rb +0 -184
- data/spec/cli_format_spec.rb +0 -123
- data/spec/cli_json_options_spec.rb +0 -50
- data/spec/cli_source_spec.rb +0 -44
- data/spec/cli_spec.rb +0 -192
- data/spec/cli_table_spec.rb +0 -28
- data/spec/cli_usage_spec.rb +0 -42
- data/spec/commands/base_command_spec.rb +0 -107
- data/spec/commands/command_factory_spec.rb +0 -76
- data/spec/commands/detailed_command_spec.rb +0 -34
- data/spec/commands/list_command_spec.rb +0 -28
- data/spec/commands/raw_command_spec.rb +0 -69
- data/spec/commands/summary_command_spec.rb +0 -34
- data/spec/commands/totals_command_spec.rb +0 -34
- data/spec/commands/uncovered_command_spec.rb +0 -55
- data/spec/commands/validate_command_spec.rb +0 -213
- data/spec/commands/version_command_spec.rb +0 -38
- data/spec/constants_spec.rb +0 -61
- data/spec/cov_loupe/formatters/source_formatter_spec.rb +0 -267
- data/spec/cov_loupe/formatters_spec.rb +0 -76
- data/spec/cov_loupe/presenters/base_coverage_presenter_spec.rb +0 -79
- data/spec/cov_loupe_model_spec.rb +0 -454
- data/spec/cov_loupe_module_spec.rb +0 -37
- data/spec/cov_loupe_opts_spec.rb +0 -185
- data/spec/coverage_reporter_spec.rb +0 -102
- data/spec/coverage_table_tool_spec.rb +0 -59
- data/spec/coverage_totals_tool_spec.rb +0 -37
- data/spec/error_handler_spec.rb +0 -197
- data/spec/error_mode_spec.rb +0 -139
- data/spec/errors_edge_cases_spec.rb +0 -312
- data/spec/errors_stale_spec.rb +0 -83
- data/spec/file_based_mcp_tools_spec.rb +0 -99
- data/spec/help_tool_spec.rb +0 -26
- data/spec/integration_spec.rb +0 -789
- data/spec/logging_fallback_spec.rb +0 -128
- data/spec/mcp_logging_spec.rb +0 -44
- data/spec/mcp_server_integration_spec.rb +0 -23
- data/spec/mcp_server_spec.rb +0 -106
- data/spec/mode_detector_spec.rb +0 -153
- data/spec/model_error_handling_spec.rb +0 -269
- data/spec/model_staleness_spec.rb +0 -79
- data/spec/option_normalizers_spec.rb +0 -203
- data/spec/option_parsers/env_options_parser_spec.rb +0 -221
- data/spec/option_parsers/error_helper_spec.rb +0 -222
- data/spec/path_relativizer_spec.rb +0 -98
- data/spec/presenters/coverage_detailed_presenter_spec.rb +0 -19
- data/spec/presenters/coverage_raw_presenter_spec.rb +0 -15
- data/spec/presenters/coverage_summary_presenter_spec.rb +0 -15
- data/spec/presenters/coverage_uncovered_presenter_spec.rb +0 -16
- data/spec/presenters/project_coverage_presenter_spec.rb +0 -87
- data/spec/presenters/project_totals_presenter_spec.rb +0 -144
- data/spec/resolvers/coverage_line_resolver_spec.rb +0 -282
- data/spec/resolvers/resolver_factory_spec.rb +0 -61
- data/spec/resolvers/resultset_path_resolver_spec.rb +0 -60
- data/spec/resultset_loader_spec.rb +0 -167
- data/spec/shared_examples/README.md +0 -115
- data/spec/shared_examples/coverage_presenter_examples.rb +0 -66
- data/spec/shared_examples/file_based_mcp_tools.rb +0 -179
- data/spec/shared_examples/formatted_command_examples.rb +0 -64
- data/spec/shared_examples/mcp_tool_text_json_response.rb +0 -16
- data/spec/spec_helper.rb +0 -127
- data/spec/staleness_checker_spec.rb +0 -374
- data/spec/staleness_more_spec.rb +0 -42
- data/spec/support/cli_helpers.rb +0 -22
- data/spec/support/control_flow_helpers.rb +0 -20
- data/spec/support/fake_mcp.rb +0 -40
- data/spec/support/io_helpers.rb +0 -29
- data/spec/support/mcp_helpers.rb +0 -35
- data/spec/support/mcp_runner.rb +0 -66
- data/spec/support/mocking_helpers.rb +0 -30
- data/spec/table_format_spec.rb +0 -70
- data/spec/tools/validate_tool_spec.rb +0 -132
- data/spec/tools_error_handling_spec.rb +0 -130
- data/spec/util_spec.rb +0 -154
- data/spec/version_spec.rb +0 -123
- data/spec/version_tool_spec.rb +0 -141
- /data/{spec/fixtures/project1 → examples/fixtures/demo_project}/lib/bar.rb +0 -0
- /data/{spec/fixtures/project1 → examples/fixtures/demo_project}/lib/foo.rb +0 -0
- /data/lib/cov_loupe/{config_parser.rb → config/config_parser.rb} +0 -0
- /data/lib/cov_loupe/{predicate_evaluator.rb → config/predicate_evaluator.rb} +0 -0
- /data/lib/cov_loupe/{error_handler_factory.rb → errors/error_handler_factory.rb} +0 -0
|
@@ -18,13 +18,6 @@ module CovLoupe
|
|
|
18
18
|
'uncovered' => :uncovered
|
|
19
19
|
}.freeze
|
|
20
20
|
|
|
21
|
-
STALENESS_MAP = {
|
|
22
|
-
'o' => :off,
|
|
23
|
-
'off' => :off,
|
|
24
|
-
'e' => :error,
|
|
25
|
-
'error' => :error
|
|
26
|
-
}.freeze
|
|
27
|
-
|
|
28
21
|
ERROR_MODE_MAP = {
|
|
29
22
|
'off' => :off,
|
|
30
23
|
'o' => :off,
|
|
@@ -44,9 +37,26 @@ module CovLoupe
|
|
|
44
37
|
'pretty-json' => :pretty_json,
|
|
45
38
|
'y' => :yaml,
|
|
46
39
|
'yaml' => :yaml,
|
|
47
|
-
'a' => :
|
|
48
|
-
'awesome_print' => :
|
|
49
|
-
'ap' => :
|
|
40
|
+
'a' => :amazing_print,
|
|
41
|
+
'awesome_print' => :amazing_print,
|
|
42
|
+
'ap' => :amazing_print,
|
|
43
|
+
'amazing_print' => :amazing_print
|
|
44
|
+
}.freeze
|
|
45
|
+
|
|
46
|
+
MODE_MAP = {
|
|
47
|
+
'cli' => :cli,
|
|
48
|
+
'c' => :cli,
|
|
49
|
+
'mcp' => :mcp,
|
|
50
|
+
'm' => :mcp
|
|
51
|
+
}.freeze
|
|
52
|
+
|
|
53
|
+
OUTPUT_CHARS_MAP = {
|
|
54
|
+
'd' => :default,
|
|
55
|
+
'default' => :default,
|
|
56
|
+
'f' => :fancy,
|
|
57
|
+
'fancy' => :fancy,
|
|
58
|
+
'a' => :ascii,
|
|
59
|
+
'ascii' => :ascii
|
|
50
60
|
}.freeze
|
|
51
61
|
|
|
52
62
|
module_function def normalize_sort_order(value, strict: true)
|
|
@@ -70,19 +80,6 @@ module CovLoupe
|
|
|
70
80
|
nil
|
|
71
81
|
end
|
|
72
82
|
|
|
73
|
-
# Normalize stale mode value.
|
|
74
|
-
# @param value [String, Symbol] The value to normalize
|
|
75
|
-
# @param strict [Boolean] If true, raises on invalid value; if false, returns nil
|
|
76
|
-
# @return [Symbol, nil] The normalized symbol or nil if invalid and not strict
|
|
77
|
-
# @raise [OptionParser::InvalidArgument] If strict and value is invalid
|
|
78
|
-
module_function def normalize_staleness(value, strict: true)
|
|
79
|
-
normalized = STALENESS_MAP[value.to_s.downcase]
|
|
80
|
-
return normalized if normalized
|
|
81
|
-
raise OptionParser::InvalidArgument, "invalid argument: #{value}" if strict
|
|
82
|
-
|
|
83
|
-
nil
|
|
84
|
-
end
|
|
85
|
-
|
|
86
83
|
# Normalize error mode value.
|
|
87
84
|
# @param value [String, Symbol, nil] The value to normalize
|
|
88
85
|
# @param strict [Boolean] If true, raises on invalid value; if false, returns default
|
|
@@ -92,6 +89,7 @@ module CovLoupe
|
|
|
92
89
|
module_function def normalize_error_mode(value, strict: true, default: :log)
|
|
93
90
|
normalized = ERROR_MODE_MAP[value.to_s.downcase]
|
|
94
91
|
return normalized if normalized
|
|
92
|
+
|
|
95
93
|
raise OptionParser::InvalidArgument, "invalid argument: #{value}" if strict
|
|
96
94
|
|
|
97
95
|
default
|
|
@@ -103,11 +101,44 @@ module CovLoupe
|
|
|
103
101
|
# @return [Symbol, nil] The normalized symbol or nil if invalid and not strict
|
|
104
102
|
# @raise [OptionParser::InvalidArgument] If strict and value is invalid
|
|
105
103
|
module_function def normalize_format(value, strict: true)
|
|
106
|
-
|
|
104
|
+
# Try exact match first (preserves case-sensitive 'J' for pretty_json)
|
|
105
|
+
normalized = FORMAT_MAP[value.to_s] || FORMAT_MAP[value.to_s.downcase]
|
|
107
106
|
return normalized if normalized
|
|
107
|
+
|
|
108
108
|
raise OptionParser::InvalidArgument, "invalid argument: #{value}" if strict
|
|
109
109
|
|
|
110
110
|
nil
|
|
111
111
|
end
|
|
112
|
+
|
|
113
|
+
# Normalize mode value (cli or mcp).
|
|
114
|
+
# @param value [String, Symbol] The value to normalize
|
|
115
|
+
# @param strict [Boolean] If true, raises on invalid value; if false, returns default
|
|
116
|
+
# @param default [Symbol] The default value to return if invalid and not strict
|
|
117
|
+
# @return [Symbol] The normalized symbol (:cli or :mcp)
|
|
118
|
+
# @raise [OptionParser::InvalidArgument] If strict and value is invalid
|
|
119
|
+
module_function def normalize_mode(value, strict: true, default: :cli)
|
|
120
|
+
normalized = MODE_MAP[value.to_s.downcase]
|
|
121
|
+
return normalized if normalized
|
|
122
|
+
|
|
123
|
+
raise OptionParser::InvalidArgument, "invalid argument: #{value}" if strict
|
|
124
|
+
|
|
125
|
+
default
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Normalize output_chars value.
|
|
129
|
+
# Controls ASCII vs Unicode (fancy) output for tables and text.
|
|
130
|
+
# @param value [String, Symbol] The value to normalize
|
|
131
|
+
# @param strict [Boolean] If true, raises on invalid value; if false, returns default
|
|
132
|
+
# @param default [Symbol] The default value to return if invalid and not strict
|
|
133
|
+
# @return [Symbol] The normalized symbol (:default, :fancy, or :ascii)
|
|
134
|
+
# @raise [OptionParser::InvalidArgument] If strict and value is invalid
|
|
135
|
+
module_function def normalize_output_chars(value, strict: true, default: :default)
|
|
136
|
+
normalized = OUTPUT_CHARS_MAP[value.to_s.downcase]
|
|
137
|
+
return normalized if normalized
|
|
138
|
+
|
|
139
|
+
raise OptionParser::InvalidArgument, "invalid argument: #{value}" if strict
|
|
140
|
+
|
|
141
|
+
default
|
|
142
|
+
end
|
|
112
143
|
end
|
|
113
144
|
end
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'option_normalizers'
|
|
4
|
-
require_relative 'version'
|
|
4
|
+
require_relative '../version'
|
|
5
|
+
require_relative 'boolean_type'
|
|
6
|
+
require_relative '../errors/errors'
|
|
5
7
|
|
|
6
8
|
module CovLoupe
|
|
7
9
|
class OptionParserBuilder
|
|
8
10
|
HORIZONTAL_RULE = '-' * 79
|
|
9
|
-
SUBCOMMANDS = %w[list summary raw uncovered detailed totals validate version].freeze
|
|
10
11
|
|
|
11
12
|
attr_reader :config
|
|
12
13
|
|
|
@@ -25,11 +26,14 @@ module CovLoupe
|
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
private def configure_banner(parser)
|
|
29
|
+
gem_root = File.expand_path('../..', __dir__)
|
|
28
30
|
parser.banner = <<~BANNER
|
|
29
31
|
#{HORIZONTAL_RULE}
|
|
30
|
-
Usage:
|
|
31
|
-
Repository:
|
|
32
|
-
|
|
32
|
+
Usage: cov-loupe [options] [subcommand] [args] (default subcommand: list)
|
|
33
|
+
Repository: https://github.com/keithrbennett/cov-loupe
|
|
34
|
+
Documentation (Web): https://keithrbennett.github.io/cov-loupe/
|
|
35
|
+
Documentation (Local): #{gem_root}/**/*.md
|
|
36
|
+
Version: #{CovLoupe::VERSION}
|
|
33
37
|
#{HORIZONTAL_RULE}
|
|
34
38
|
|
|
35
39
|
BANNER
|
|
@@ -45,7 +49,7 @@ module CovLoupe
|
|
|
45
49
|
totals Show aggregated line totals and average %
|
|
46
50
|
uncovered <path> Show uncovered lines and a summary
|
|
47
51
|
validate <file> Evaluate coverage policy from file (exit 0=pass, 1=fail, 2=error)
|
|
48
|
-
validate -
|
|
52
|
+
validate -i <code> Evaluate coverage policy from code string
|
|
49
53
|
version Show version information
|
|
50
54
|
|
|
51
55
|
SUBCOMMANDS
|
|
@@ -63,7 +67,7 @@ module CovLoupe
|
|
|
63
67
|
end
|
|
64
68
|
parser.on(
|
|
65
69
|
'-f', '--format FORMAT', String,
|
|
66
|
-
'Output format: t[able]|j[son]|pretty-json|y[aml]|a[
|
|
70
|
+
'Output format: t[able]|j[son]|pretty-json|y[aml]|a[mazing_print] (default: table)'
|
|
67
71
|
) do |value|
|
|
68
72
|
config.format = normalize_format(value)
|
|
69
73
|
end
|
|
@@ -77,38 +81,52 @@ module CovLoupe
|
|
|
77
81
|
end
|
|
78
82
|
parser.on('-c', '--context-lines N', Integer,
|
|
79
83
|
'Context lines around uncovered lines (non-negative, default: 2)') do |value|
|
|
84
|
+
raise UsageError, 'Context lines cannot be negative' if value.negative?
|
|
85
|
+
|
|
80
86
|
config.source_context = value
|
|
81
87
|
end
|
|
82
|
-
parser.on('
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
88
|
+
parser.on('-C', '--color BOOLEAN', BooleanType::IS_BOOLEAN_STRING_VALUE,
|
|
89
|
+
'Enable/disable ANSI colors for source output (default: true). ' \
|
|
90
|
+
"Accepts: #{BooleanType::BOOLEAN_VALUES_DISPLAY_STRING}") do |value|
|
|
91
|
+
config.color = BooleanType.parse(value)
|
|
92
|
+
end
|
|
93
|
+
parser.on('-S', '--raise-on-stale BOOLEAN', BooleanType::IS_BOOLEAN_STRING_VALUE,
|
|
94
|
+
'Raise error if coverage is stale (default: false). ' \
|
|
95
|
+
"Accepts: #{BooleanType::BOOLEAN_VALUES_DISPLAY_STRING}") do |value|
|
|
96
|
+
config.raise_on_stale = BooleanType.parse(value)
|
|
87
97
|
end
|
|
88
98
|
parser.on('-g', '--tracked-globs x,y,z', Array,
|
|
89
|
-
'
|
|
99
|
+
'Used to exclude unwanted results and/or include files with or without coverage data',
|
|
100
|
+
'Default: [] (shows all files in resultset)',
|
|
101
|
+
'Best practice: match your SimpleCov track_files patterns',
|
|
102
|
+
'Example: --tracked-globs lib/**/*.rb,app/**/*.rb') do |value|
|
|
90
103
|
config.tracked_globs = value
|
|
91
104
|
end
|
|
92
105
|
parser.on('-h', '--help', 'Show help') do
|
|
93
106
|
puts parser
|
|
94
|
-
gem_root = File.expand_path('../..', __dir__)
|
|
95
|
-
puts "\nFor more detailed help, consult README.md and docs/user/**/*.md"
|
|
96
|
-
puts "in the installed gem at: #{gem_root}"
|
|
97
107
|
exit 0
|
|
98
108
|
end
|
|
99
109
|
parser.on('-l', '--log-file PATH', String,
|
|
100
110
|
'Log file path (default ./cov_loupe.log, use stdout/stderr for streams)') do |value|
|
|
101
111
|
config.log_file = value
|
|
102
112
|
end
|
|
103
|
-
parser.on('--
|
|
113
|
+
parser.on('-m', '--mode MODE', String,
|
|
114
|
+
'Execution mode: cli|mcp (default: cli)') do |value|
|
|
115
|
+
config.mode = normalize_mode(value)
|
|
116
|
+
end
|
|
117
|
+
parser.on('-e', '--error-mode MODE', String,
|
|
104
118
|
'Error handling mode: o[ff]|l[og]|d[ebug] (default log). ' \
|
|
105
119
|
'off (silent), log (log errors to file), debug (verbose with backtraces)') do |value|
|
|
106
120
|
config.error_mode = normalize_error_mode(value)
|
|
107
121
|
end
|
|
108
|
-
parser.on('
|
|
109
|
-
|
|
122
|
+
parser.on('-O', '--output-chars MODE', String,
|
|
123
|
+
'Output character mode: d[efault]|f[ancy]|a[scii] (default: default). ' \
|
|
124
|
+
'default: UTF-8 encoding uses fancy (Unicode), else ascii. ' \
|
|
125
|
+
'fancy: use Unicode box-drawing and symbols. ' \
|
|
126
|
+
'ascii: use ASCII-only characters (0x00-0x7F).') do |value|
|
|
127
|
+
config.output_chars = normalize_output_chars(value)
|
|
110
128
|
end
|
|
111
|
-
parser.on('-v', '--version', 'Show version information and exit') do
|
|
129
|
+
parser.on('-v', '--version', 'Show version information and exit.') do
|
|
112
130
|
config.show_version = true
|
|
113
131
|
end
|
|
114
132
|
end
|
|
@@ -132,10 +150,6 @@ module CovLoupe
|
|
|
132
150
|
OptionNormalizers.normalize_source_mode(value, strict: true)
|
|
133
151
|
end
|
|
134
152
|
|
|
135
|
-
private def normalize_staleness(value)
|
|
136
|
-
OptionNormalizers.normalize_staleness(value, strict: true)
|
|
137
|
-
end
|
|
138
|
-
|
|
139
153
|
private def normalize_error_mode(value)
|
|
140
154
|
OptionNormalizers.normalize_error_mode(value, strict: true)
|
|
141
155
|
end
|
|
@@ -143,5 +157,13 @@ module CovLoupe
|
|
|
143
157
|
private def normalize_format(value)
|
|
144
158
|
OptionNormalizers.normalize_format(value, strict: true)
|
|
145
159
|
end
|
|
160
|
+
|
|
161
|
+
private def normalize_mode(value)
|
|
162
|
+
OptionNormalizers.normalize_mode(value, strict: true)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
private def normalize_output_chars(value)
|
|
166
|
+
OptionNormalizers.normalize_output_chars(value, strict: true)
|
|
167
|
+
end
|
|
146
168
|
end
|
|
147
169
|
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CovLoupe
|
|
4
|
+
# Provides coverage data transformations and calculations.
|
|
5
|
+
# Handles summary statistics, uncovered line identification, and detailed line-by-line analysis.
|
|
6
|
+
class CoverageCalculator
|
|
7
|
+
# Calculates coverage summary statistics from a coverage array.
|
|
8
|
+
#
|
|
9
|
+
# @param coverage_lines [Array<Integer, nil>] SimpleCov coverage array where each element
|
|
10
|
+
# represents a line: Integer for hit count, nil for non-code lines
|
|
11
|
+
# @return [Hash] summary with 'covered', 'total', and 'percentage' keys
|
|
12
|
+
def self.summary(coverage_lines)
|
|
13
|
+
total = 0
|
|
14
|
+
covered = 0
|
|
15
|
+
coverage_lines.compact.each do |hits|
|
|
16
|
+
total += 1
|
|
17
|
+
covered += 1 if hits.to_i > 0
|
|
18
|
+
end
|
|
19
|
+
percentage = total <= 0 ? nil : (covered.to_f / total * 100.0).round(2)
|
|
20
|
+
{ 'covered' => covered, 'total' => total, 'percentage' => percentage }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Identifies uncovered line numbers from a coverage array.
|
|
24
|
+
#
|
|
25
|
+
# @param coverage_lines [Array<Integer, nil>] SimpleCov coverage array
|
|
26
|
+
# @return [Array<Integer>] array of uncovered line numbers (1-indexed)
|
|
27
|
+
def self.uncovered(coverage_lines)
|
|
28
|
+
out = []
|
|
29
|
+
|
|
30
|
+
coverage_lines.each_with_index do |hits, i|
|
|
31
|
+
next if hits.nil?
|
|
32
|
+
|
|
33
|
+
out << (i + 1) if hits.to_i.zero?
|
|
34
|
+
end
|
|
35
|
+
out
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Generates detailed line-by-line coverage information.
|
|
39
|
+
#
|
|
40
|
+
# @param coverage_lines [Array<Integer, nil>] SimpleCov coverage array
|
|
41
|
+
# @return [Array<Hash>] array of hashes with 'line', 'hits', and 'covered' keys
|
|
42
|
+
def self.detailed(coverage_lines)
|
|
43
|
+
rows = []
|
|
44
|
+
coverage_lines.each_with_index do |hits, i|
|
|
45
|
+
next if hits.nil?
|
|
46
|
+
|
|
47
|
+
h = hits.to_i
|
|
48
|
+
rows << { 'line' => i + 1, 'hits' => h, 'covered' => h.positive? }
|
|
49
|
+
end
|
|
50
|
+
rows
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CovLoupe
|
|
4
|
+
require_relative '../staleness/stale_status'
|
|
5
|
+
|
|
6
|
+
# Reports files with coverage below a specified threshold.
|
|
7
|
+
# Useful for displaying low coverage files after test runs.
|
|
8
|
+
#
|
|
9
|
+
# @example Basic usage in spec_helper.rb
|
|
10
|
+
# SimpleCov.at_exit do
|
|
11
|
+
# SimpleCov.result.format!
|
|
12
|
+
# report = CovLoupe::CoverageReporter.report(threshold: 80, count: 5)
|
|
13
|
+
# puts report if report
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# @example With custom resultset path
|
|
17
|
+
# CovLoupe::CoverageReporter.report(
|
|
18
|
+
# threshold: 80,
|
|
19
|
+
# count: 5,
|
|
20
|
+
# resultset: 'custom/coverage/.resultset.json'
|
|
21
|
+
# )
|
|
22
|
+
#
|
|
23
|
+
# @example With custom project root
|
|
24
|
+
# CovLoupe::CoverageReporter.report(
|
|
25
|
+
# threshold: 80,
|
|
26
|
+
# count: 5,
|
|
27
|
+
# root: '/path/to/project'
|
|
28
|
+
# )
|
|
29
|
+
#
|
|
30
|
+
module CoverageReporter
|
|
31
|
+
module_function def report(threshold: 80, count: 5, model: nil, root: nil, resultset: nil)
|
|
32
|
+
# Determine default root from SimpleCov if available
|
|
33
|
+
default_root = defined?(SimpleCov) ? SimpleCov.root : '.'
|
|
34
|
+
|
|
35
|
+
# Determine default resultset from SimpleCov if available
|
|
36
|
+
default_resultset = if defined?(SimpleCov)
|
|
37
|
+
File.join(SimpleCov.root, SimpleCov.coverage_dir, '.resultset.json')
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
model ||= CoverageModel.new(
|
|
41
|
+
root: root || default_root,
|
|
42
|
+
resultset: resultset || default_resultset
|
|
43
|
+
)
|
|
44
|
+
list_result = model.list(sort_order: :ascending)
|
|
45
|
+
file_list = list_result['files']
|
|
46
|
+
.select { |f| f['percentage'] && f['percentage'] < threshold }
|
|
47
|
+
.first(count)
|
|
48
|
+
file_list = model.relativize(file_list)
|
|
49
|
+
|
|
50
|
+
return nil if file_list.empty?
|
|
51
|
+
|
|
52
|
+
lines = ["\nLowest coverage files (< #{threshold}%):"]
|
|
53
|
+
file_list.each do |f|
|
|
54
|
+
label = f['file']
|
|
55
|
+
if StaleStatus.stale?(f['stale'])
|
|
56
|
+
label = "#{label} (stale: #{f['stale']})"
|
|
57
|
+
end
|
|
58
|
+
lines << format(' %5.1f%% %s', f['percentage'], label)
|
|
59
|
+
end
|
|
60
|
+
lines.join("\n")
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../staleness/stale_status'
|
|
4
|
+
require_relative '../output_chars'
|
|
5
|
+
|
|
6
|
+
module CovLoupe
|
|
7
|
+
# Formats coverage data as a table with box-drawing characters
|
|
8
|
+
# Extracted from CoverageModel to separate presentation from domain logic
|
|
9
|
+
class CoverageTableFormatter
|
|
10
|
+
# Format coverage rows as a table with box-drawing or ASCII characters.
|
|
11
|
+
#
|
|
12
|
+
# @param rows [Array<Hash>] Coverage rows with keys: 'file', 'percentage', 'covered', 'total', 'stale'
|
|
13
|
+
# @param output_chars [Symbol] Output character mode (:default, :fancy, or :ascii)
|
|
14
|
+
# @return [String] Formatted table with borders and summary
|
|
15
|
+
def self.format(rows, output_chars: :default)
|
|
16
|
+
return 'No coverage data found' if rows.empty?
|
|
17
|
+
|
|
18
|
+
# Resolve mode and get appropriate charset
|
|
19
|
+
resolved_mode = OutputChars.resolve_mode(output_chars)
|
|
20
|
+
charset = OutputChars.charset_for(resolved_mode)
|
|
21
|
+
|
|
22
|
+
# Convert file paths and other string content to ASCII if needed
|
|
23
|
+
converted_rows = rows.map do |row|
|
|
24
|
+
row.merge('file' => OutputChars.convert(row['file'], resolved_mode))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
widths = compute_table_widths(converted_rows)
|
|
28
|
+
lines = []
|
|
29
|
+
lines << border_line(widths, charset[:top_left], charset[:top_tee], charset[:top_right], charset)
|
|
30
|
+
lines << header_row(widths, charset)
|
|
31
|
+
lines << border_line(widths, charset[:left_tee], charset[:cross], charset[:right_tee], charset)
|
|
32
|
+
converted_rows.each { |file_data| lines << data_row(file_data, widths, charset) }
|
|
33
|
+
lines << border_line(widths, charset[:bottom_left], charset[:bottom_tee], charset[:bottom_right],
|
|
34
|
+
charset)
|
|
35
|
+
lines << summary_counts(converted_rows)
|
|
36
|
+
if converted_rows.any? { |f| StaleStatus.stale?(f['stale']) }
|
|
37
|
+
lines <<
|
|
38
|
+
'Staleness: error, missing, newer, length_mismatch'
|
|
39
|
+
end
|
|
40
|
+
lines.join("\n")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Calculate column widths based on data
|
|
44
|
+
#
|
|
45
|
+
# @param rows [Array<Hash>] Coverage rows
|
|
46
|
+
# @return [Hash] Width for each column (:file, :pct, :covered, :total, :stale)
|
|
47
|
+
private_class_method def self.compute_table_widths(rows)
|
|
48
|
+
max_file_length = rows.map { |f| f['file'].length }.max.to_i
|
|
49
|
+
file_width = [max_file_length, 'File'.length].max + 2
|
|
50
|
+
pct_width = 8
|
|
51
|
+
max_covered = rows.map { |f| f['covered'].to_s.length }.max
|
|
52
|
+
max_total = rows.map { |f| f['total'].to_s.length }.max
|
|
53
|
+
covered_width = [max_covered, 'Covered'.length].max + 2
|
|
54
|
+
total_width = [max_total, 'Total'.length].max + 2
|
|
55
|
+
max_stale_label = rows.map { |f| StaleStatus.stale?(f['stale']) ? f['stale'].to_s.length : 0 }.max.to_i
|
|
56
|
+
stale_width = [max_stale_label, 'Stale'.length].max
|
|
57
|
+
{
|
|
58
|
+
file: file_width,
|
|
59
|
+
pct: pct_width,
|
|
60
|
+
covered: covered_width,
|
|
61
|
+
total: total_width,
|
|
62
|
+
stale: stale_width
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Generate a border line for the table
|
|
67
|
+
#
|
|
68
|
+
# @param widths [Hash] Column widths
|
|
69
|
+
# @param left [String] Left edge character
|
|
70
|
+
# @param middle [String] Column separator character
|
|
71
|
+
# @param right [String] Right edge character
|
|
72
|
+
# @param charset [Hash] Character set for borders
|
|
73
|
+
# @return [String] Border line
|
|
74
|
+
private_class_method def self.border_line(widths, left, middle, right, charset)
|
|
75
|
+
h = charset[:horizontal]
|
|
76
|
+
h_line = ->(col_width) { h * (col_width + 2) }
|
|
77
|
+
left +
|
|
78
|
+
h_line.call(widths[:file]) +
|
|
79
|
+
middle + h_line.call(widths[:pct]) +
|
|
80
|
+
middle + h_line.call(widths[:covered]) +
|
|
81
|
+
middle + h_line.call(widths[:total]) +
|
|
82
|
+
middle + h_line.call(widths[:stale]) +
|
|
83
|
+
right
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Generate the header row
|
|
87
|
+
#
|
|
88
|
+
# @param widths [Hash] Column widths
|
|
89
|
+
# @param charset [Hash] Character set for borders
|
|
90
|
+
# @return [String] Header row
|
|
91
|
+
private_class_method def self.header_row(widths, charset)
|
|
92
|
+
v = charset[:vertical]
|
|
93
|
+
Kernel.format(
|
|
94
|
+
"#{v} %-#{widths[:file]}s #{v} %#{widths[:pct]}s #{v} %#{widths[:covered]}s " \
|
|
95
|
+
"#{v} %#{widths[:total]}s #{v} %#{widths[:stale]}s #{v}",
|
|
96
|
+
'File', ' %', 'Covered', 'Total', 'Stale'.center(widths[:stale])
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Generate a data row for a single file
|
|
101
|
+
#
|
|
102
|
+
# @param file_data [Hash] Coverage data for one file
|
|
103
|
+
# @param widths [Hash] Column widths
|
|
104
|
+
# @param charset [Hash] Character set for borders
|
|
105
|
+
# @return [String] Data row
|
|
106
|
+
private_class_method def self.data_row(file_data, widths, charset)
|
|
107
|
+
fd = file_data
|
|
108
|
+
ws = widths
|
|
109
|
+
v = charset[:vertical]
|
|
110
|
+
is_stale = StaleStatus.stale?(fd['stale'])
|
|
111
|
+
stale_str = is_stale ? fd['stale'].to_s.center(ws[:stale]) : ''
|
|
112
|
+
pct_str = if fd['percentage']
|
|
113
|
+
Kernel.format("%#{ws[:pct] - 1}.2f%%", fd['percentage'])
|
|
114
|
+
else
|
|
115
|
+
'n/a'.rjust(ws[:pct])
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
format_str = "#{v} %-#{ws[:file]}s #{v} %s #{v} %#{ws[:covered]}d #{v} %#{ws[:total]}d #{v} %#{ws[:stale]}s #{v}"
|
|
119
|
+
Kernel.format(format_str, fd['file'], pct_str, fd['covered'], fd['total'], stale_str)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Generate summary counts footer
|
|
123
|
+
#
|
|
124
|
+
# @param rows [Array<Hash>] Coverage rows
|
|
125
|
+
# @return [String] Summary line
|
|
126
|
+
private_class_method def self.summary_counts(rows)
|
|
127
|
+
total = rows.length
|
|
128
|
+
stale_count = rows.count { |f| StaleStatus.stale?(f['stale']) }
|
|
129
|
+
ok_count = total - stale_count
|
|
130
|
+
"Files: total #{total}, ok #{ok_count}, stale #{stale_count}"
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
4
|
require_relative 'errors'
|
|
5
|
-
require_relative 'util'
|
|
6
5
|
|
|
7
6
|
module CovLoupe
|
|
8
7
|
# Handles error reporting and logging with configurable behavior
|
|
@@ -32,7 +31,7 @@ module CovLoupe
|
|
|
32
31
|
def handle_error(error, context: nil, reraise: true)
|
|
33
32
|
log_error(error, context)
|
|
34
33
|
if reraise
|
|
35
|
-
raise error.is_a?(CovLoupe::Error) ? error : convert_standard_error(error)
|
|
34
|
+
raise error.is_a?(CovLoupe::Error) ? error : convert_standard_error(error, context: context)
|
|
36
35
|
end
|
|
37
36
|
end
|
|
38
37
|
|
|
@@ -48,18 +47,24 @@ module CovLoupe
|
|
|
48
47
|
when Errno::EISDIR
|
|
49
48
|
filename = extract_filename(error.message)
|
|
50
49
|
NotAFileError.new("Expected file but found directory: #{filename}", error)
|
|
50
|
+
when Errno::EMFILE, Errno::ENOSPC, IOError
|
|
51
|
+
FileError.new(error.message, error)
|
|
52
|
+
when Errno::EROFS
|
|
53
|
+
FilePermissionError.new(error.message, error)
|
|
51
54
|
when JSON::ParserError
|
|
52
55
|
CoverageDataError.new("Invalid coverage data format: #{error.message}", error)
|
|
56
|
+
when EncodingError
|
|
57
|
+
CoverageDataError.new("Invalid encoding in coverage data: #{error.message}", error)
|
|
58
|
+
when RangeError
|
|
59
|
+
CoverageDataError.new("Numeric overflow or range error: #{error.message}", error)
|
|
53
60
|
when TypeError
|
|
54
61
|
CoverageDataError.new("Invalid coverage data structure: #{error.message}", error)
|
|
55
62
|
when ArgumentError
|
|
56
63
|
convert_argument_error(error, context)
|
|
57
64
|
when NoMethodError
|
|
58
65
|
convert_no_method_error(error, context)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
else
|
|
62
|
-
Error.new("An unexpected error occurred: #{error.message}", error)
|
|
66
|
+
else # including RuntimeError
|
|
67
|
+
UnknownError.new(error.message, error)
|
|
63
68
|
end
|
|
64
69
|
end
|
|
65
70
|
|
|
@@ -100,40 +105,23 @@ module CovLoupe
|
|
|
100
105
|
end
|
|
101
106
|
end
|
|
102
107
|
|
|
103
|
-
private def convert_runtime_error(error, context)
|
|
104
|
-
message = error.message
|
|
105
|
-
if message.include?('Could not find .resultset.json')
|
|
106
|
-
dir_info = message.match(/under (.+?)(?:;|$)/)&.[](1) || 'project directory'
|
|
107
|
-
CoverageDataError.new(
|
|
108
|
-
"Coverage data not found in #{dir_info} - please run your tests first", error)
|
|
109
|
-
elsif message.include?('No .resultset.json found in directory')
|
|
110
|
-
dir_info = message.match(/directory: (.+)$/)&.[](1) || 'specified directory'
|
|
111
|
-
CoverageDataError.new("Coverage data not found in directory: #{dir_info}", error)
|
|
112
|
-
elsif message.include?('Specified resultset not found')
|
|
113
|
-
# Preserve the original message format for consistency with existing tests
|
|
114
|
-
ResultsetNotFoundError.new(message, error)
|
|
115
|
-
elsif context == :coverage_loading
|
|
116
|
-
if message.downcase.include?('resultset')
|
|
117
|
-
ResultsetNotFoundError.new(message, error)
|
|
118
|
-
else
|
|
119
|
-
CoverageDataError.new("Failed to load coverage data: #{message}", error)
|
|
120
|
-
end
|
|
121
|
-
else
|
|
122
|
-
Error.new("An unexpected error occurred: #{message}", error)
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
108
|
|
|
126
109
|
private def log_error(error, context)
|
|
127
110
|
return unless log_errors?
|
|
128
111
|
|
|
129
112
|
message = build_log_message(error, context)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
else
|
|
133
|
-
CovUtil.log(message)
|
|
134
|
-
end
|
|
113
|
+
active_logger = @logger || CovLoupe.logger
|
|
114
|
+
active_logger.error(message)
|
|
135
115
|
end
|
|
136
116
|
|
|
117
|
+
# Build log message for error logging.
|
|
118
|
+
# NOTE: Log messages are NOT converted to ASCII in ASCII output mode.
|
|
119
|
+
# This is intentional because:
|
|
120
|
+
# - Log files are system/debugging artifacts, not user-facing output
|
|
121
|
+
# - The output_chars feature targets terminal errors/warnings, not log files
|
|
122
|
+
# - Converting would lose debugging information (exact file paths, error details)
|
|
123
|
+
# - Creates inconsistency between logged paths and actual filesystem paths
|
|
124
|
+
# - No user value since logs are developer artifacts
|
|
137
125
|
private def build_log_message(error, context)
|
|
138
126
|
context_suffix = context ? " in #{context}" : ''
|
|
139
127
|
parts = ["Error#{context_suffix}: #{error.class}: #{error.message}"]
|