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
data/spec/cov_loupe_opts_spec.rb
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'spec_helper'
|
|
4
|
-
|
|
5
|
-
RSpec.describe 'COV_LOUPE_OPTS Environment Variable' do
|
|
6
|
-
let(:cli) { CovLoupe::CoverageCLI.new }
|
|
7
|
-
|
|
8
|
-
around do |example|
|
|
9
|
-
original_value = ENV['COV_LOUPE_OPTS']
|
|
10
|
-
example.run
|
|
11
|
-
ensure
|
|
12
|
-
ENV['COV_LOUPE_OPTS'] = original_value
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
describe 'CLI option parsing from environment' do
|
|
16
|
-
it 'parses simple options from COV_LOUPE_OPTS' do
|
|
17
|
-
ENV['COV_LOUPE_OPTS'] = '--error-mode off --format json'
|
|
18
|
-
env_opts = CovLoupe.send(:extract_env_opts)
|
|
19
|
-
|
|
20
|
-
swallow_system_exit do
|
|
21
|
-
silence_output do
|
|
22
|
-
cli.send(:run, env_opts + ['summary', 'lib/foo.rb'])
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
rescue CovLoupe::Error => e
|
|
26
|
-
# Expected to fail due to missing file, but options should be parsed
|
|
27
|
-
puts "DEBUG: Caught exception: #{e.class}: #{e.message}" if ENV['DEBUG']
|
|
28
|
-
ensure
|
|
29
|
-
expect(cli.config.error_mode).to eq(:off)
|
|
30
|
-
expect(cli.config.format).to eq(:json)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
it 'handles quoted options with spaces' do
|
|
34
|
-
test_path = File.join(Dir.tmpdir, 'test path with spaces', '.resultset.json')
|
|
35
|
-
ENV['COV_LOUPE_OPTS'] = "--resultset \"#{test_path}\""
|
|
36
|
-
env_opts = CovLoupe.send(:extract_env_opts)
|
|
37
|
-
|
|
38
|
-
exit_status = swallow_system_exit do
|
|
39
|
-
silence_output do
|
|
40
|
-
cli.send(:run, env_opts + ['--help'])
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
expect(exit_status).to eq(0) # --help exits cleanly
|
|
45
|
-
expect(cli.config.resultset).to eq(test_path)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
it 'supports setting log-file to stdout from environment' do
|
|
49
|
-
ENV['COV_LOUPE_OPTS'] = '--log-file stdout'
|
|
50
|
-
env_opts = CovLoupe.send(:extract_env_opts)
|
|
51
|
-
|
|
52
|
-
swallow_system_exit do
|
|
53
|
-
silence_output do
|
|
54
|
-
cli.send(:run, env_opts + ['--help'])
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
expect(cli.config.log_file).to eq('stdout')
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
it 'command line arguments override environment options' do
|
|
62
|
-
ENV['COV_LOUPE_OPTS'] = '--error-mode off'
|
|
63
|
-
env_opts = CovLoupe.send(:extract_env_opts)
|
|
64
|
-
|
|
65
|
-
begin
|
|
66
|
-
args = env_opts + ['--error-mode', 'debug', 'summary', 'lib/foo.rb']
|
|
67
|
-
silence_output { cli.send(:run, args) }
|
|
68
|
-
rescue SystemExit, CovLoupe::Error
|
|
69
|
-
# Expected to fail, but options should be parsed
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# Command line should override environment
|
|
73
|
-
expect(cli.config.error_mode).to eq(:debug)
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
it 'handles malformed COV_LOUPE_OPTS gracefully' do
|
|
77
|
-
ENV['COV_LOUPE_OPTS'] = '--option "unclosed quote'
|
|
78
|
-
|
|
79
|
-
# Should catch the ConfigurationError and exit cleanly
|
|
80
|
-
_out, _err, status = run_cli_with_status('summary', 'lib/foo.rb')
|
|
81
|
-
expect(status).not_to eq(0)
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
it 'returns empty array when COV_LOUPE_OPTS is not set' do
|
|
85
|
-
# ENV is already cleared by around block
|
|
86
|
-
opts = CovLoupe.send(:extract_env_opts)
|
|
87
|
-
expect(opts).to eq([])
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
it 'returns empty array when COV_LOUPE_OPTS is empty' do
|
|
91
|
-
ENV['COV_LOUPE_OPTS'] = ''
|
|
92
|
-
opts = CovLoupe.send(:extract_env_opts)
|
|
93
|
-
expect(opts).to eq([])
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
describe 'CLI mode detection with COV_LOUPE_OPTS' do
|
|
98
|
-
it 'respects --force-cli from environment variable' do
|
|
99
|
-
ENV['COV_LOUPE_OPTS'] = '--force-cli'
|
|
100
|
-
|
|
101
|
-
# This would normally be MCP mode (no TTY, no subcommand)
|
|
102
|
-
stdin = double('stdin', tty?: false)
|
|
103
|
-
|
|
104
|
-
env_opts = CovLoupe.send(:extract_env_opts)
|
|
105
|
-
full_argv = env_opts + []
|
|
106
|
-
|
|
107
|
-
expect(CovLoupe::ModeDetector.cli_mode?(full_argv, stdin: stdin)).to be true
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
it 'handles parse errors gracefully in mode detection' do
|
|
111
|
-
ENV['COV_LOUPE_OPTS'] = '--option "unclosed quote'
|
|
112
|
-
|
|
113
|
-
# Should return empty array and not crash
|
|
114
|
-
opts = CovLoupe.send(:extract_env_opts)
|
|
115
|
-
expect(opts).to eq([])
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
it 'actually runs CLI when --force-cli is in COV_LOUPE_OPTS' do
|
|
119
|
-
ENV['COV_LOUPE_OPTS'] = '--force-cli'
|
|
120
|
-
|
|
121
|
-
# Mock STDIN to not be a TTY (would normally trigger MCP server mode)
|
|
122
|
-
allow($stdin).to receive(:tty?).and_return(false)
|
|
123
|
-
|
|
124
|
-
# Run with --help which should produce help output
|
|
125
|
-
output = nil
|
|
126
|
-
silence_output do |out, err|
|
|
127
|
-
swallow_system_exit do
|
|
128
|
-
CovLoupe.run(['--help'])
|
|
129
|
-
end
|
|
130
|
-
output = out.string + err.string
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
# Verify CLI actually ran by checking for help text
|
|
134
|
-
expect(output).to include('Usage:')
|
|
135
|
-
expect(output).to include('cov-loupe')
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
it 'actually runs MCP server mode when no CLI indicators present' do
|
|
139
|
-
ENV['COV_LOUPE_OPTS'] = ''
|
|
140
|
-
|
|
141
|
-
# Mock STDIN to not be a TTY and to provide valid JSON-RPC
|
|
142
|
-
allow($stdin).to receive(:tty?).and_return(false)
|
|
143
|
-
|
|
144
|
-
# Provide a minimal JSON-RPC request that the server can handle
|
|
145
|
-
json_request = JSON.generate({
|
|
146
|
-
jsonrpc: '2.0',
|
|
147
|
-
id: 1,
|
|
148
|
-
method: 'initialize',
|
|
149
|
-
params: {
|
|
150
|
-
protocolVersion: '2024-11-05',
|
|
151
|
-
capabilities: {},
|
|
152
|
-
clientInfo: { name: 'test', version: '1.0' }
|
|
153
|
-
}
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
allow($stdin).to receive(:gets).and_return(json_request, nil)
|
|
157
|
-
|
|
158
|
-
# Capture output to verify MCP server response
|
|
159
|
-
output = nil
|
|
160
|
-
silence_output do |out, err|
|
|
161
|
-
CovLoupe.run([])
|
|
162
|
-
output = out.string + err.string
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
# Verify MCP server ran by checking for JSON-RPC response
|
|
166
|
-
expect(output).to include('"jsonrpc"')
|
|
167
|
-
expect(output).to include('"result"')
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
describe 'integration with actual CLI usage' do
|
|
172
|
-
it 'works end-to-end with --resultset option' do
|
|
173
|
-
test_resultset = File.join(Dir.tmpdir, 'test_coverage', '.resultset.json')
|
|
174
|
-
ENV['COV_LOUPE_OPTS'] = "--resultset #{test_resultset} --format json"
|
|
175
|
-
env_opts = CovLoupe.send(:extract_env_opts)
|
|
176
|
-
|
|
177
|
-
swallow_system_exit do
|
|
178
|
-
silence_output { cli.send(:run, env_opts + ['--help']) }
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
expect(cli.config.resultset).to eq(test_resultset)
|
|
182
|
-
expect(cli.config.format).to eq(:json)
|
|
183
|
-
end
|
|
184
|
-
end
|
|
185
|
-
end
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'spec_helper'
|
|
4
|
-
|
|
5
|
-
RSpec.describe CovLoupe::CoverageReporter do
|
|
6
|
-
let(:model) { instance_double(CovLoupe::CoverageModel) }
|
|
7
|
-
# Data is pre-sorted by percentage ascending (as model.all_files returns)
|
|
8
|
-
let(:all_files_data) do
|
|
9
|
-
[
|
|
10
|
-
{ 'file' => '/project/lib/zero.rb', 'percentage' => 0.0, 'covered' => 0, 'total' => 10 },
|
|
11
|
-
{ 'file' => '/project/lib/low.rb', 'percentage' => 25.0, 'covered' => 5, 'total' => 20 },
|
|
12
|
-
{ 'file' => '/project/lib/medium.rb', 'percentage' => 60.0, 'covered' => 12, 'total' => 20 },
|
|
13
|
-
{ 'file' => '/project/lib/high.rb', 'percentage' => 95.0, 'covered' => 19, 'total' => 20 }
|
|
14
|
-
]
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
before do
|
|
18
|
-
allow(model).to receive(:all_files).with(sort_order: :ascending).and_return(all_files_data)
|
|
19
|
-
allow(model).to receive(:relativize) do |files|
|
|
20
|
-
files.map { |f| f.merge('file' => f['file'].sub('/project/', '')) }
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
describe '.report' do
|
|
25
|
-
it 'returns formatted low coverage files string' do
|
|
26
|
-
result = described_class.report(threshold: 80, count: 5, model: model)
|
|
27
|
-
|
|
28
|
-
expect(result).to be_a(String)
|
|
29
|
-
expect(result).to include('Lowest coverage files (< 80%):')
|
|
30
|
-
expect(result).to include('lib/zero.rb')
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
it 'includes files below threshold sorted by coverage ascending' do
|
|
34
|
-
result = described_class.report(threshold: 80, count: 5, model: model)
|
|
35
|
-
|
|
36
|
-
expect(result).to include('lib/zero.rb', 'lib/low.rb', 'lib/medium.rb')
|
|
37
|
-
expect(result).not_to include('lib/high.rb')
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
it 'respects count parameter' do
|
|
41
|
-
result = described_class.report(threshold: 80, count: 2, model: model)
|
|
42
|
-
|
|
43
|
-
expect(result).to include('lib/zero.rb')
|
|
44
|
-
expect(result).to include('lib/low.rb')
|
|
45
|
-
expect(result).not_to include('lib/medium.rb')
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
it 'returns nil when no files below threshold' do
|
|
49
|
-
result = described_class.report(threshold: 0, count: 5, model: model)
|
|
50
|
-
|
|
51
|
-
expect(result).to be_nil
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
it 'uses threshold in header' do
|
|
55
|
-
result = described_class.report(threshold: 90, count: 5, model: model)
|
|
56
|
-
|
|
57
|
-
expect(result).to include('< 90%')
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
it 'uses default threshold of 80' do
|
|
61
|
-
result = described_class.report(count: 5, model: model)
|
|
62
|
-
|
|
63
|
-
expect(result).to include('< 80%')
|
|
64
|
-
expect(result).not_to include('lib/high.rb')
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
it 'uses default count of 5' do
|
|
68
|
-
result = described_class.report(threshold: 100, model: model)
|
|
69
|
-
|
|
70
|
-
# All 4 files are below 100%
|
|
71
|
-
expect(result).to include('lib/zero.rb')
|
|
72
|
-
expect(result).to include('lib/high.rb')
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
it 'relativizes file paths' do
|
|
76
|
-
result = described_class.report(threshold: 80, count: 5, model: model)
|
|
77
|
-
|
|
78
|
-
expect(result).to include('lib/zero.rb')
|
|
79
|
-
expect(result).not_to include('/project/')
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
it 'aligns percentages correctly' do
|
|
83
|
-
result = described_class.report(threshold: 100, count: 5, model: model)
|
|
84
|
-
lines = result.split("\n")
|
|
85
|
-
|
|
86
|
-
# lines[0] is empty (leading newline), lines[1] is header, lines[2..] are data
|
|
87
|
-
expect(lines[2]).to match(/^\s+0\.0%/)
|
|
88
|
-
expect(lines[3]).to match(/^\s+25\.0%/)
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
describe 'module_function behavior' do
|
|
93
|
-
it 'report is available as a module method' do
|
|
94
|
-
expect(described_class).to respond_to(:report)
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
it 'report is available as a private instance method when included' do
|
|
98
|
-
klass = Class.new { include CovLoupe::CoverageReporter }
|
|
99
|
-
expect(klass.private_instance_methods).to include(:report)
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
end
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'spec_helper'
|
|
4
|
-
require 'cov_loupe/tools/coverage_table_tool'
|
|
5
|
-
|
|
6
|
-
RSpec.describe CovLoupe::Tools::CoverageTableTool do
|
|
7
|
-
let(:root) { (FIXTURES_DIR / 'project1').to_s }
|
|
8
|
-
let(:server_context) { instance_double('ServerContext').as_null_object }
|
|
9
|
-
|
|
10
|
-
before do
|
|
11
|
-
setup_mcp_response_stub
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def run_tool(staleness: :off)
|
|
15
|
-
# Let real CoverageModel work to test actual format_table behavior
|
|
16
|
-
described_class.call(root: root, staleness: staleness,
|
|
17
|
-
server_context: server_context).payload.first['text']
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
it 'returns a formatted table as a string' do
|
|
21
|
-
output = run_tool
|
|
22
|
-
|
|
23
|
-
# Contains table structure, headers, and file data
|
|
24
|
-
expect(output).to include(
|
|
25
|
-
'┌', '┬', '┐', '│', '├', '┼', '┤', '└', '┴', '┘',
|
|
26
|
-
'File', 'Covered', 'Total', ' │ Stale │',
|
|
27
|
-
'lib/foo.rb', 'lib/bar.rb',
|
|
28
|
-
'Files: total 2, ok 0, stale 2'
|
|
29
|
-
)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
it 'configures CLI to enforce stale checking when requested' do
|
|
33
|
-
model = instance_double(CovLoupe::CoverageModel,
|
|
34
|
-
all_files: [
|
|
35
|
-
{ 'file' => "#{root}/lib/foo.rb", 'percentage' => 100.0, 'covered' => 10, 'total' => 10,
|
|
36
|
-
'stale' => false }
|
|
37
|
-
],
|
|
38
|
-
relativize: ->(payload) { payload },
|
|
39
|
-
format_table: 'Mock table output'
|
|
40
|
-
)
|
|
41
|
-
allow(CovLoupe::CoverageModel).to receive(:new).with(
|
|
42
|
-
root: root,
|
|
43
|
-
resultset: nil,
|
|
44
|
-
staleness: :error,
|
|
45
|
-
tracked_globs: nil
|
|
46
|
-
).and_return(model)
|
|
47
|
-
allow(model).to receive(:format_table).and_return('Mock table output')
|
|
48
|
-
|
|
49
|
-
described_class.call(root: root, staleness: :error, server_context: server_context)
|
|
50
|
-
|
|
51
|
-
expect(CovLoupe::CoverageModel).to have_received(:new).with(
|
|
52
|
-
root: root,
|
|
53
|
-
resultset: nil,
|
|
54
|
-
staleness: :error,
|
|
55
|
-
tracked_globs: nil
|
|
56
|
-
)
|
|
57
|
-
expect(model).to have_received(:format_table)
|
|
58
|
-
end
|
|
59
|
-
end
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'spec_helper'
|
|
4
|
-
require 'cov_loupe/tools/coverage_totals_tool'
|
|
5
|
-
|
|
6
|
-
RSpec.describe CovLoupe::Tools::CoverageTotalsTool do
|
|
7
|
-
subject(:tool_response) { described_class.call(root: root, server_context: server_context) }
|
|
8
|
-
|
|
9
|
-
let(:root) { (FIXTURES_DIR / 'project1').to_s }
|
|
10
|
-
let(:server_context) { instance_double('ServerContext').as_null_object }
|
|
11
|
-
|
|
12
|
-
before do
|
|
13
|
-
setup_mcp_response_stub
|
|
14
|
-
model = instance_double(CovLoupe::CoverageModel)
|
|
15
|
-
allow(CovLoupe::CoverageModel).to receive(:new).and_return(model)
|
|
16
|
-
|
|
17
|
-
payload = {
|
|
18
|
-
'lines' => { 'total' => 42, 'covered' => 40, 'uncovered' => 2 },
|
|
19
|
-
'percentage' => 95.24,
|
|
20
|
-
'files' => { 'total' => 4, 'ok' => 4, 'stale' => 0 }
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
presenter = instance_double(CovLoupe::Presenters::ProjectTotalsPresenter)
|
|
24
|
-
allow(CovLoupe::Presenters::ProjectTotalsPresenter).to receive(:new).and_return(presenter)
|
|
25
|
-
allow(presenter).to receive(:relativized_payload).and_return(payload)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
it_behaves_like 'an MCP tool that returns text JSON'
|
|
29
|
-
|
|
30
|
-
it 'returns aggregated totals' do
|
|
31
|
-
data, = expect_mcp_text_json(tool_response, expected_keys: ['lines', 'percentage', 'files'])
|
|
32
|
-
|
|
33
|
-
expect(data['lines']).to include('total' => 42, 'covered' => 40, 'uncovered' => 2)
|
|
34
|
-
expect(data['files']).to include('total' => 4, 'stale' => 0)
|
|
35
|
-
expect(data['percentage']).to eq(95.24)
|
|
36
|
-
end
|
|
37
|
-
end
|
data/spec/error_handler_spec.rb
DELETED
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'spec_helper'
|
|
4
|
-
|
|
5
|
-
RSpec.describe CovLoupe::ErrorHandler do
|
|
6
|
-
subject(:handler) { described_class.new(error_mode: :log, logger: logger) }
|
|
7
|
-
|
|
8
|
-
let(:logger) do
|
|
9
|
-
Class.new do
|
|
10
|
-
attr_reader :messages
|
|
11
|
-
|
|
12
|
-
def initialize = @messages = []
|
|
13
|
-
def error(msg) = @messages << msg
|
|
14
|
-
end.new
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
it 'maps filesystem errors to friendly custom errors' do
|
|
19
|
-
e = handler.convert_standard_error(Errno::EISDIR.new('Is a directory @ rb_sysopen - a_dir'))
|
|
20
|
-
expect(e).to be_a(CovLoupe::NotAFileError)
|
|
21
|
-
|
|
22
|
-
e = handler.convert_standard_error(
|
|
23
|
-
Errno::ENOENT.new('No such file or directory @ rb_sysopen - missing.txt')
|
|
24
|
-
)
|
|
25
|
-
expect(e).to be_a(CovLoupe::FileNotFoundError)
|
|
26
|
-
|
|
27
|
-
e = handler.convert_standard_error(Errno::EACCES.new('Permission denied @ rb_sysopen - secret'))
|
|
28
|
-
expect(e).to be_a(CovLoupe::FilePermissionError)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
it 'maps JSON::ParserError to CoverageDataError' do
|
|
32
|
-
e = handler.convert_standard_error(JSON::ParserError.new('unexpected token'))
|
|
33
|
-
expect(e).to be_a(CovLoupe::CoverageDataError)
|
|
34
|
-
expect(e.user_friendly_message).to include('Invalid coverage data format')
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
it 'maps ArgumentError by message' do
|
|
38
|
-
e = handler.convert_standard_error(
|
|
39
|
-
ArgumentError.new('wrong number of arguments (given 1, expected 2)')
|
|
40
|
-
)
|
|
41
|
-
expect(e).to be_a(CovLoupe::UsageError)
|
|
42
|
-
|
|
43
|
-
e = handler.convert_standard_error(ArgumentError.new('invalid option'))
|
|
44
|
-
expect(e).to be_a(CovLoupe::ConfigurationError)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
it 'maps NoMethodError to CoverageDataError with helpful info' do
|
|
48
|
-
e = handler.convert_standard_error(
|
|
49
|
-
NoMethodError.new("undefined method `fetch' for #<Hash:0x123>")
|
|
50
|
-
)
|
|
51
|
-
expect(e).to be_a(CovLoupe::CoverageDataError)
|
|
52
|
-
expect(e.user_friendly_message).to include('Invalid coverage data structure')
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
it 'maps runtime strings from util to friendly errors' do
|
|
56
|
-
e = handler.convert_standard_error(
|
|
57
|
-
RuntimeError.new('Could not find .resultset.json under /path; run tests')
|
|
58
|
-
)
|
|
59
|
-
expect(e).to be_a(CovLoupe::CoverageDataError)
|
|
60
|
-
expect(e.user_friendly_message).to include('run your tests first')
|
|
61
|
-
|
|
62
|
-
e = handler.convert_standard_error(
|
|
63
|
-
RuntimeError.new('No .resultset.json found in directory: /path')
|
|
64
|
-
)
|
|
65
|
-
expect(e).to be_a(CovLoupe::CoverageDataError)
|
|
66
|
-
|
|
67
|
-
e = handler.convert_standard_error(
|
|
68
|
-
RuntimeError.new('Specified resultset not found: /nowhere/file.json')
|
|
69
|
-
)
|
|
70
|
-
expect(e).to be_a(CovLoupe::ResultsetNotFoundError)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
it 'logs via provided logger' do
|
|
74
|
-
begin
|
|
75
|
-
handler.handle_error(Errno::ENOENT.new('No such file or directory @ rb_sysopen - x'),
|
|
76
|
-
context: 'test', reraise: false)
|
|
77
|
-
rescue
|
|
78
|
-
# reraise disabled
|
|
79
|
-
end
|
|
80
|
-
expect(logger.messages.join).to include('Error in test')
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
it 'converts TypeError to CoverageDataError for invalid data structures' do
|
|
84
|
-
error = TypeError.new('wrong argument type')
|
|
85
|
-
result = handler.convert_standard_error(error)
|
|
86
|
-
|
|
87
|
-
expect(result).to be_a(CovLoupe::CoverageDataError)
|
|
88
|
-
expect(result.user_friendly_message).to include('Invalid coverage data structure')
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
it 'returns generic Error for unrecognized SystemCallError' do
|
|
92
|
-
error = Errno::EEXIST.new('File exists')
|
|
93
|
-
result = handler.convert_standard_error(error)
|
|
94
|
-
|
|
95
|
-
expect(result).to be_a(CovLoupe::Error)
|
|
96
|
-
expect(result.user_friendly_message).to include('An unexpected error occurred')
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
it 'handles NoMethodError with non-standard message format' do
|
|
100
|
-
error = NoMethodError.new('some weird error message without the expected pattern')
|
|
101
|
-
result = handler.convert_standard_error(error)
|
|
102
|
-
|
|
103
|
-
expect(result).to be_a(CovLoupe::CoverageDataError)
|
|
104
|
-
expect(result.user_friendly_message).to include('some weird error message')
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
describe 'else branch for non-StandardError exceptions' do
|
|
108
|
-
# This tests the else clause in convert_standard_error for exceptions
|
|
109
|
-
# that don't inherit from StandardError
|
|
110
|
-
it 'returns generic Error for Exception subclasses not inheriting from StandardError' do
|
|
111
|
-
# Create a custom exception that inherits from Exception, not StandardError
|
|
112
|
-
custom_exception_class = Class.new(StandardError) do
|
|
113
|
-
def message
|
|
114
|
-
'Custom non-standard exception'
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
error = custom_exception_class.new
|
|
119
|
-
result = handler.convert_standard_error(error)
|
|
120
|
-
|
|
121
|
-
expect(result).to be_a(CovLoupe::Error)
|
|
122
|
-
expect(result.user_friendly_message).to include('An unexpected error occurred')
|
|
123
|
-
expect(result.user_friendly_message).to include('Custom non-standard exception')
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
it 'returns generic Error for ScriptError subclasses' do
|
|
127
|
-
# ScriptError inherits from Exception, not StandardError
|
|
128
|
-
error = NotImplementedError.new('This feature is not implemented')
|
|
129
|
-
result = handler.convert_standard_error(error)
|
|
130
|
-
|
|
131
|
-
expect(result).to be_a(CovLoupe::Error)
|
|
132
|
-
expect(result.user_friendly_message).to include('An unexpected error occurred')
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
describe 'extract_method_info fallback' do
|
|
137
|
-
# This tests the fallback path in extract_method_info when NoMethodError
|
|
138
|
-
# message doesn't match the expected pattern
|
|
139
|
-
it 'returns original message when pattern does not match' do
|
|
140
|
-
# Test various NoMethodError formats that won't match the regex
|
|
141
|
-
test_messages = [
|
|
142
|
-
'method not found',
|
|
143
|
-
'private method called',
|
|
144
|
-
'undefined local variable or method',
|
|
145
|
-
''
|
|
146
|
-
]
|
|
147
|
-
|
|
148
|
-
test_messages.each do |msg|
|
|
149
|
-
error = NoMethodError.new(msg)
|
|
150
|
-
result = handler.convert_standard_error(error)
|
|
151
|
-
|
|
152
|
-
expect(result).to be_a(CovLoupe::CoverageDataError)
|
|
153
|
-
# The original message should be preserved
|
|
154
|
-
expect(result.message).to include(msg) unless msg.empty?
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
# ErrorHandler#convert_runtime_error handles RuntimeErrors differently based on context:
|
|
160
|
-
# - :coverage_loading assumes errors relate to coverage data and maps them to
|
|
161
|
-
# CoverageDataError or ResultsetNotFoundError
|
|
162
|
-
# - :general (or any other context) maps unrecognized errors to generic Error
|
|
163
|
-
# This tests the final else branch in convert_runtime_error.
|
|
164
|
-
describe 'convert_runtime_error with general context' do
|
|
165
|
-
it 'converts RuntimeError with unrecognized message to generic Error' do
|
|
166
|
-
error = RuntimeError.new('Some completely unexpected runtime error')
|
|
167
|
-
|
|
168
|
-
result = handler.convert_standard_error(error, context: :general)
|
|
169
|
-
|
|
170
|
-
expect(result).to be_a(CovLoupe::Error)
|
|
171
|
-
expect(result.user_friendly_message)
|
|
172
|
-
.to include('An unexpected error occurred', 'unexpected runtime error')
|
|
173
|
-
end
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
describe '#handle_error with reraise' do
|
|
177
|
-
it 're-raises CovLoupe::Error when reraise is true' do
|
|
178
|
-
error = CovLoupe::FileNotFoundError.new('Test file not found')
|
|
179
|
-
|
|
180
|
-
expect { handler.handle_error(error, context: 'test', reraise: true) }
|
|
181
|
-
.to raise_error(CovLoupe::FileNotFoundError, 'Test file not found')
|
|
182
|
-
|
|
183
|
-
# Verify it was logged
|
|
184
|
-
expect(logger.messages.join).to include('Error in test')
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
it 'converts and re-raises StandardError when reraise is true' do
|
|
188
|
-
error = Errno::ENOENT.new('No such file or directory @ rb_sysopen - missing.rb')
|
|
189
|
-
|
|
190
|
-
expect { handler.handle_error(error, context: 'test', reraise: true) }
|
|
191
|
-
.to raise_error(CovLoupe::FileNotFoundError)
|
|
192
|
-
|
|
193
|
-
# Verify it was logged
|
|
194
|
-
expect(logger.messages.join).to include('Error in test')
|
|
195
|
-
end
|
|
196
|
-
end
|
|
197
|
-
end
|