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/app_config_spec.rb
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'spec_helper'
|
|
4
|
-
|
|
5
|
-
RSpec.describe CovLoupe::AppConfig do
|
|
6
|
-
describe '#initialize' do
|
|
7
|
-
it 'creates a config with default values' do
|
|
8
|
-
config = described_class.new
|
|
9
|
-
expect(config.root).to eq('.')
|
|
10
|
-
expect(config.format).to eq(:table)
|
|
11
|
-
expect(config.sort_order).to eq(:descending)
|
|
12
|
-
expect(config.source_context).to eq(2)
|
|
13
|
-
expect(config.error_mode).to eq(:log)
|
|
14
|
-
expect(config.staleness).to eq(:off)
|
|
15
|
-
expect(config.resultset).to be_nil
|
|
16
|
-
expect(config.source_mode).to be_nil
|
|
17
|
-
expect(config.tracked_globs).to be_nil
|
|
18
|
-
expect(config.log_file).to be_nil
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
it 'allows overriding defaults via keyword arguments' do
|
|
22
|
-
config = described_class.new(
|
|
23
|
-
root: '/custom',
|
|
24
|
-
format: :json,
|
|
25
|
-
sort_order: :descending,
|
|
26
|
-
staleness: :error
|
|
27
|
-
)
|
|
28
|
-
expect(config.root).to eq('/custom')
|
|
29
|
-
expect(config.format).to eq(:json)
|
|
30
|
-
expect(config.sort_order).to eq(:descending)
|
|
31
|
-
expect(config.staleness).to eq(:error)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
it 'is mutable (struct fields can be changed)' do
|
|
35
|
-
config = described_class.new
|
|
36
|
-
config.root = '/new/root'
|
|
37
|
-
config.format = :json
|
|
38
|
-
expect(config.root).to eq('/new/root')
|
|
39
|
-
expect(config.format).to eq(:json)
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
describe '#model_options' do
|
|
44
|
-
it 'returns hash suitable for CoverageModel.new' do
|
|
45
|
-
config = described_class.new(
|
|
46
|
-
root: '/custom/root',
|
|
47
|
-
resultset: '/custom/.resultset.json',
|
|
48
|
-
staleness: :error,
|
|
49
|
-
tracked_globs: ['lib/**/*.rb']
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
options = config.model_options
|
|
53
|
-
expect(options).to eq({
|
|
54
|
-
root: '/custom/root',
|
|
55
|
-
resultset: '/custom/.resultset.json',
|
|
56
|
-
staleness: :error,
|
|
57
|
-
tracked_globs: ['lib/**/*.rb']
|
|
58
|
-
})
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
it 'handles nil values correctly' do
|
|
62
|
-
config = described_class.new
|
|
63
|
-
options = config.model_options
|
|
64
|
-
expect(options[:root]).to eq('.')
|
|
65
|
-
expect(options[:resultset]).to be_nil
|
|
66
|
-
expect(options[:staleness]).to eq(:off)
|
|
67
|
-
expect(options[:tracked_globs]).to be_nil
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
describe '#formatter_options' do
|
|
72
|
-
it 'returns hash suitable for SourceFormatter.new' do
|
|
73
|
-
config = described_class.new(color: true)
|
|
74
|
-
options = config.formatter_options
|
|
75
|
-
expect(options).to eq({ color_enabled: true })
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
it 'handles false color setting' do
|
|
79
|
-
config = described_class.new(color: false)
|
|
80
|
-
options = config.formatter_options
|
|
81
|
-
expect(options).to eq({ color_enabled: false })
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
describe 'struct behavior' do
|
|
86
|
-
it 'supports equality comparison' do
|
|
87
|
-
config1 = described_class.new(root: '/foo', format: :json)
|
|
88
|
-
config2 = described_class.new(root: '/foo', format: :json)
|
|
89
|
-
config3 = described_class.new(root: '/bar', format: :json)
|
|
90
|
-
|
|
91
|
-
expect(config1).to eq(config2)
|
|
92
|
-
expect(config1).not_to eq(config3)
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
it 'provides readable inspect output' do
|
|
96
|
-
config = described_class.new(root: '/test', format: :json)
|
|
97
|
-
output = config.inspect
|
|
98
|
-
expect(output).to include('root="/test"')
|
|
99
|
-
expect(output).to include('format=:json')
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
it 'converts to hash' do
|
|
103
|
-
config = described_class.new(root: '/test', format: :json)
|
|
104
|
-
hash = config.to_h
|
|
105
|
-
expect(hash).to be_a(Hash)
|
|
106
|
-
expect(hash[:root]).to eq('/test')
|
|
107
|
-
expect(hash[:format]).to eq(:json)
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
describe 'symbol enumerated values' do
|
|
112
|
-
it 'uses symbols for format' do
|
|
113
|
-
config = described_class.new(format: :json)
|
|
114
|
-
expect(config.format).to eq(:json)
|
|
115
|
-
expect(config.format).to be_a(Symbol)
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
it 'uses symbols for sort_order' do
|
|
119
|
-
config = described_class.new(sort_order: :descending)
|
|
120
|
-
expect(config.sort_order).to eq(:descending)
|
|
121
|
-
expect(config.sort_order).to be_a(Symbol)
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
it 'uses symbols for staleness' do
|
|
125
|
-
config = described_class.new(staleness: :error)
|
|
126
|
-
expect(config.staleness).to eq(:error)
|
|
127
|
-
expect(config.staleness).to be_a(Symbol)
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
it 'uses symbols for error_mode' do
|
|
131
|
-
config = described_class.new(error_mode: :debug)
|
|
132
|
-
expect(config.error_mode).to eq(:debug)
|
|
133
|
-
expect(config.error_mode).to be_a(Symbol)
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
it 'uses symbols for source_mode' do
|
|
137
|
-
config = described_class.new(source_mode: :uncovered)
|
|
138
|
-
expect(config.source_mode).to eq(:uncovered)
|
|
139
|
-
expect(config.source_mode).to be_a(Symbol)
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
end
|
data/spec/base_tool_spec.rb
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'spec_helper'
|
|
4
|
-
|
|
5
|
-
RSpec.describe CovLoupe::BaseTool do
|
|
6
|
-
let(:handler) { CovLoupe::ErrorHandler.new(error_mode: :log, logger: test_logger) }
|
|
7
|
-
let(:test_logger) do
|
|
8
|
-
Class.new do
|
|
9
|
-
attr_reader :messages
|
|
10
|
-
|
|
11
|
-
def initialize = @messages = []
|
|
12
|
-
def error(msg) = @messages << msg
|
|
13
|
-
end.new
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
let(:orig_handler) do
|
|
17
|
-
CovLoupe.error_handler
|
|
18
|
-
rescue
|
|
19
|
-
nil
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
before do
|
|
23
|
-
CovLoupe.error_handler = handler
|
|
24
|
-
setup_mcp_response_stub
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
after do
|
|
28
|
-
CovLoupe.error_handler = orig_handler if orig_handler
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
shared_examples 'friendly response and logged' do
|
|
32
|
-
it 'returns friendly text' do
|
|
33
|
-
resp = described_class.handle_mcp_error(error, tool, error_mode: :log)
|
|
34
|
-
expect(resp).to be_a(MCP::Tool::Response)
|
|
35
|
-
expect(resp.payload.first['text']).to match(expected_pattern)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
it 'respects error_mode :off' do
|
|
39
|
-
resp = described_class.handle_mcp_error(error, tool, error_mode: :off)
|
|
40
|
-
expect(resp).to be_a(MCP::Tool::Response)
|
|
41
|
-
expect(resp.payload.first['text']).to match(expected_pattern)
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
context 'with CovLoupe::Error' do
|
|
46
|
-
let(:error) { CovLoupe::UsageError.new('invalid args') }
|
|
47
|
-
let(:tool) { 'coverage_summary' }
|
|
48
|
-
let(:expected_pattern) { /Error: invalid args/ }
|
|
49
|
-
let(:log_fragment) { 'invalid args' }
|
|
50
|
-
|
|
51
|
-
it_behaves_like 'friendly response and logged'
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
context 'with standard error' do
|
|
55
|
-
let(:error) { Errno::ENOENT.new('No such file or directory @ rb_sysopen - missing.rb') }
|
|
56
|
-
let(:tool) { 'coverage_raw' }
|
|
57
|
-
let(:expected_pattern) { /Error: .*File not found: missing.rb/ }
|
|
58
|
-
let(:log_fragment) { 'File not found' }
|
|
59
|
-
|
|
60
|
-
it_behaves_like 'friendly response and logged'
|
|
61
|
-
end
|
|
62
|
-
end
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'spec_helper'
|
|
4
|
-
|
|
5
|
-
RSpec.describe CovLoupe::CoverageCLI do
|
|
6
|
-
let(:root) { (FIXTURES_DIR / 'project1').to_s }
|
|
7
|
-
let(:cli) { described_class.new }
|
|
8
|
-
|
|
9
|
-
before do
|
|
10
|
-
cli.config.root = root
|
|
11
|
-
cli.config.resultset = 'coverage'
|
|
12
|
-
cli.config.staleness = :off
|
|
13
|
-
cli.config.tracked_globs = nil
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
describe '#show_default_report' do
|
|
17
|
-
it 'prints JSON summary using relativized payload when json mode is enabled' do
|
|
18
|
-
cli.config.format = :json
|
|
19
|
-
|
|
20
|
-
output = nil
|
|
21
|
-
silence_output do |stdout, _stderr|
|
|
22
|
-
cli.show_default_report(sort_order: :ascending, output: stdout)
|
|
23
|
-
output = stdout.string
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
payload = JSON.parse(output)
|
|
27
|
-
|
|
28
|
-
expect(payload['files']).to be_an(Array)
|
|
29
|
-
expect(payload['files'].first['file']).to eq('lib/bar.rb').or eq('lib/foo.rb')
|
|
30
|
-
expect(payload['counts']).to include('total', 'ok', 'stale')
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'spec_helper'
|
|
4
|
-
|
|
5
|
-
RSpec.describe 'CLI enumerated option parsing' do
|
|
6
|
-
def parse!(argv)
|
|
7
|
-
cli = CovLoupe::CoverageCLI.new
|
|
8
|
-
cli.send(:parse_options!, argv.dup)
|
|
9
|
-
cli
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
describe 'accepts short and long forms' do
|
|
13
|
-
cases = [
|
|
14
|
-
{ argv: ['--sort-order', 'a', 'list'], accessor: :sort_order, expected: :ascending },
|
|
15
|
-
{ argv: ['--sort-order', 'd', 'list'], accessor: :sort_order, expected: :descending },
|
|
16
|
-
{ argv: ['--sort-order', 'ascending', 'list'], accessor: :sort_order, expected: :ascending },
|
|
17
|
-
{ argv: ['--sort-order', 'descending', 'list'], accessor: :sort_order,
|
|
18
|
-
expected: :descending },
|
|
19
|
-
|
|
20
|
-
{ argv: ['--source', 'f', 'summary', 'lib/foo.rb'], accessor: :source_mode, expected: :full },
|
|
21
|
-
{ argv: ['--source', 'u', 'summary', 'lib/foo.rb'], accessor: :source_mode,
|
|
22
|
-
expected: :uncovered },
|
|
23
|
-
{ argv: ['--source', 'full', 'summary', 'lib/foo.rb'], accessor: :source_mode,
|
|
24
|
-
expected: :full },
|
|
25
|
-
{ argv: ['--source', 'uncovered', 'summary', 'lib/foo.rb'], accessor: :source_mode,
|
|
26
|
-
expected: :uncovered },
|
|
27
|
-
|
|
28
|
-
{ argv: ['-S', 'e', 'list'], accessor: :staleness, expected: :error },
|
|
29
|
-
{ argv: ['-S', 'o', 'list'], accessor: :staleness, expected: :off },
|
|
30
|
-
{ argv: ['--staleness', 'e', 'list'], accessor: :staleness, expected: :error },
|
|
31
|
-
{ argv: ['--staleness', 'o', 'list'], accessor: :staleness, expected: :off },
|
|
32
|
-
|
|
33
|
-
{ argv: ['--error-mode', 'off', 'list'], accessor: :error_mode, expected: :off },
|
|
34
|
-
{ argv: ['--error-mode', 'o', 'list'], accessor: :error_mode, expected: :off },
|
|
35
|
-
{ argv: ['--error-mode', 'log', 'list'], accessor: :error_mode, expected: :log },
|
|
36
|
-
{ argv: ['--error-mode', 'debug', 'list'], accessor: :error_mode, expected: :debug }
|
|
37
|
-
]
|
|
38
|
-
|
|
39
|
-
cases.each do |c|
|
|
40
|
-
it "parses #{c[:argv].join(' ')}" do
|
|
41
|
-
cli = parse!(c[:argv])
|
|
42
|
-
expect(cli.config.public_send(c[:accessor])).to eq(c[:expected])
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
describe 'rejects invalid values' do
|
|
48
|
-
invalid_cases = [
|
|
49
|
-
{ argv: ['--sort-order', 'asc', 'list'] },
|
|
50
|
-
{ argv: ['--source', 'x', 'summary', 'lib/foo.rb'] },
|
|
51
|
-
{ argv: ['-S', 'x', 'list'] },
|
|
52
|
-
{ argv: ['--staleness', 'x', 'list'] },
|
|
53
|
-
{ argv: ['--error-mode', 'bad', 'list'] },
|
|
54
|
-
{ argv: ['--error-mode', 'on', 'list'] },
|
|
55
|
-
{ argv: ['--error-mode', 'trace', 'list'] }
|
|
56
|
-
]
|
|
57
|
-
|
|
58
|
-
invalid_cases.each do |c|
|
|
59
|
-
it "exits 1 for #{c[:argv].join(' ')}" do
|
|
60
|
-
_out, err, status = run_cli_with_status(*c[:argv])
|
|
61
|
-
expect(status).to eq(1)
|
|
62
|
-
expect(err).to include('Error:')
|
|
63
|
-
expect(err).to include('invalid argument')
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
describe 'missing value hints' do
|
|
69
|
-
it 'exits 1 when -S is provided without a value' do
|
|
70
|
-
_out, err, status = run_cli_with_status('-S', 'list')
|
|
71
|
-
expect(status).to eq(1)
|
|
72
|
-
expect(err).to include('invalid argument')
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
it 'exits 1 when --staleness is provided without a value' do
|
|
76
|
-
_out, err, status = run_cli_with_status('--staleness', 'list')
|
|
77
|
-
expect(status).to eq(1)
|
|
78
|
-
expect(err).to include('invalid argument')
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
it 'exits 1 when --source is provided without a value' do
|
|
82
|
-
_out, err, status = run_cli_with_status('--source', 'summary', 'lib/foo.rb')
|
|
83
|
-
expect(status).to eq(1)
|
|
84
|
-
# Depending on OptParse implementation for required argument, it might say "missing argument"
|
|
85
|
-
# But usually it consumes next arg. If 'summary' is consumed as argument for source:
|
|
86
|
-
# normalize_source_mode('summary') -> raises InvalidArgument.
|
|
87
|
-
expect(err).to include('invalid argument')
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|
data/spec/cli_error_spec.rb
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'spec_helper'
|
|
4
|
-
|
|
5
|
-
RSpec.describe CovLoupe::CoverageCLI do
|
|
6
|
-
let(:root) { (FIXTURES_DIR / 'project1').to_s }
|
|
7
|
-
|
|
8
|
-
it 'shows help and exits 0' do
|
|
9
|
-
out, err, status = run_cli_with_status('--help')
|
|
10
|
-
expect(status).to eq(0)
|
|
11
|
-
expect(out).to match(/Usage:.*cov-loupe/)
|
|
12
|
-
expect(out).to include(
|
|
13
|
-
'Repository: https://github.com/keithrbennett/cov-loupe',
|
|
14
|
-
'Subcommands:'
|
|
15
|
-
)
|
|
16
|
-
expect(out).to match(/Version:.*#{CovLoupe::VERSION}/)
|
|
17
|
-
expect(err).to eq('')
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
shared_examples 'maps error to exit 1 with message' do
|
|
21
|
-
before do
|
|
22
|
-
# Build a fake model that raises the specified error from the specified method
|
|
23
|
-
fake_model = Class.new do
|
|
24
|
-
def initialize(*)
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
error_to_raise = raised_error
|
|
28
|
-
fake_model.define_method(model_method) { |*| raise error_to_raise }
|
|
29
|
-
stub_const('CovLoupe::CoverageModel', fake_model)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
it 'exits with status 1 and friendly message' do
|
|
33
|
-
_out, err, status = run_cli_with_status(*invoke_args)
|
|
34
|
-
expect(status).to eq(1)
|
|
35
|
-
expect(err).to include(expected_message)
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
context 'when mapping ENOENT' do
|
|
40
|
-
let(:model_method) { :summary_for }
|
|
41
|
-
let(:raised_error) { Errno::ENOENT.new('No such file or directory @ rb_sysopen - missing.rb') }
|
|
42
|
-
let(:invoke_args) { ['--root', root, '--resultset', 'coverage', 'summary', 'lib/missing.rb'] }
|
|
43
|
-
let(:expected_message) { 'File error: File not found: lib/missing.rb' }
|
|
44
|
-
|
|
45
|
-
it_behaves_like 'maps error to exit 1 with message'
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
context 'when mapping EACCES' do
|
|
49
|
-
let(:model_method) { :raw_for }
|
|
50
|
-
let(:raised_error) { Errno::EACCES.new('Permission denied @ rb_sysopen - secret.rb') }
|
|
51
|
-
let(:invoke_args) { ['--root', root, '--resultset', 'coverage', 'raw', 'lib/secret.rb'] }
|
|
52
|
-
let(:expected_message) { 'Permission denied: lib/secret.rb' }
|
|
53
|
-
|
|
54
|
-
it_behaves_like 'maps error to exit 1 with message'
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
it 'emits detailed stale coverage info and exits 1' do
|
|
58
|
-
mock_resultset_with_timestamp(root, VERY_OLD_TIMESTAMP, coverage: {
|
|
59
|
-
File.join(root, 'lib', 'foo.rb') => { 'lines' => [1, 0, 1] }
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
_out, err, status = run_cli_with_status('--root', root, '--resultset', 'coverage',
|
|
63
|
-
'--staleness', 'error', 'summary', 'lib/foo.rb')
|
|
64
|
-
expect(status).to eq(1)
|
|
65
|
-
expect(err).to include('Coverage data stale:')
|
|
66
|
-
expect(err).to match(/File\s+- time:/)
|
|
67
|
-
expect(err).to match('Coverage\s+- time:')
|
|
68
|
-
expect(err).to match(/Delta\s+- file is [+-]?\d+s newer than coverage/)
|
|
69
|
-
expect(err).to match('Resultset\s+-')
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
it 'honors --no-strict-staleness to disable checks' do
|
|
73
|
-
mock_resultset_with_timestamp(root, VERY_OLD_TIMESTAMP, coverage: {
|
|
74
|
-
File.join(root, 'lib', 'foo.rb') => { 'lines' => [1, 0, 1] }
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
_out, err, status = run_cli_with_status('--root', root, '--resultset', 'coverage',
|
|
78
|
-
'--staleness', 'off', 'summary', 'lib/foo.rb')
|
|
79
|
-
expect(status).to eq(0)
|
|
80
|
-
expect(err).to eq('')
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
it 'handles source rendering errors gracefully with fallback message' do
|
|
84
|
-
# Test that source rendering with problematic coverage data doesn't crash
|
|
85
|
-
# This is a regression test for the "can't convert nil into Integer" crash
|
|
86
|
-
# that was previously mentioned in comments
|
|
87
|
-
out, err, status = run_cli_with_status(
|
|
88
|
-
'--root', root, '--resultset', 'coverage', '--source', 'uncovered', '--context-lines', '2',
|
|
89
|
-
'--no-color', 'uncovered', 'lib/foo.rb'
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
expect(status).to eq(0)
|
|
93
|
-
expect(err).to eq('')
|
|
94
|
-
expect(out).to match(/File:\s+lib\/foo\.rb/)
|
|
95
|
-
expect(out).to include('│') # Table format
|
|
96
|
-
expect(out).to show_source_table_or_fallback
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
it 'renders source with full mode without crashing' do
|
|
100
|
-
# Additional regression test for source rendering with full mode
|
|
101
|
-
out, err, status = run_cli_with_status(
|
|
102
|
-
'--root', root, '--resultset', 'coverage', '--source', 'full', '--no-color',
|
|
103
|
-
'summary', 'lib/foo.rb'
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
expect(status).to eq(0)
|
|
107
|
-
expect(err).to eq('')
|
|
108
|
-
expect(out).to include('lib/foo.rb')
|
|
109
|
-
expect(out).to include('66.67%')
|
|
110
|
-
expect(out).to show_source_table_or_fallback
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
it 'shows fallback message when source file is unreadable' do
|
|
114
|
-
# Test the fallback path when source files can't be read
|
|
115
|
-
# Temporarily rename the source file to make it unreadable
|
|
116
|
-
foo_path = File.join(root, 'lib', 'foo.rb')
|
|
117
|
-
temp_path = "#{foo_path}.hidden"
|
|
118
|
-
|
|
119
|
-
begin
|
|
120
|
-
File.rename(foo_path, temp_path) if File.exist?(foo_path)
|
|
121
|
-
|
|
122
|
-
out, err, status = run_cli_with_status(
|
|
123
|
-
'--root', root, '--resultset', 'coverage', '--source', 'full', '--no-color',
|
|
124
|
-
'summary', 'lib/foo.rb'
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
expect(status).to eq(0)
|
|
128
|
-
expect(err).to eq('')
|
|
129
|
-
expect(out).to include('lib/foo.rb')
|
|
130
|
-
expect(out).to include('66.67%')
|
|
131
|
-
expect(out).to include('[source not available]')
|
|
132
|
-
ensure
|
|
133
|
-
# Restore the file
|
|
134
|
-
File.rename(temp_path, foo_path) if File.exist?(temp_path)
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
describe 'invalid option handling' do
|
|
139
|
-
it 'suggests subcommand for --subcommand-like option' do
|
|
140
|
-
_out, err, status = run_cli_with_status('--summary')
|
|
141
|
-
expect(status).to eq(1)
|
|
142
|
-
expect(err).to include(
|
|
143
|
-
"Error: '--summary' is not a valid option. Did you mean the 'summary' subcommand?"
|
|
144
|
-
)
|
|
145
|
-
expect(err).to include('Try: cov-loupe summary [args]')
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
it 'reports invalid enum value for --opt=value' do
|
|
149
|
-
_out, err, status = run_cli_with_status('--staleness=bogus', 'list')
|
|
150
|
-
expect(status).to eq(1)
|
|
151
|
-
expect(err).to include('invalid argument: --staleness=bogus')
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
it 'reports invalid enum value for --opt value' do
|
|
155
|
-
_out, err, status = run_cli_with_status('--staleness', 'bogus', 'list')
|
|
156
|
-
expect(status).to eq(1)
|
|
157
|
-
expect(err).to include('invalid argument: bogus')
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
it 'handles generic invalid options' do
|
|
161
|
-
_out, err, status = run_cli_with_status('--no-such-option')
|
|
162
|
-
expect(status).to eq(1)
|
|
163
|
-
expect(err).to include('Error: invalid option: --no-such-option')
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
describe 'subcommand error handling' do
|
|
168
|
-
it 'handles generic exceptions from subcommands' do
|
|
169
|
-
# Stub the CommandFactory to return a command that raises a StandardError
|
|
170
|
-
fake_command = Class.new do
|
|
171
|
-
def initialize(_cli) = nil
|
|
172
|
-
def execute(_args) = raise(StandardError, 'Unexpected error in subcommand')
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
allow(CovLoupe::Commands::CommandFactory).to receive(:create)
|
|
176
|
-
.and_return(fake_command.new(nil))
|
|
177
|
-
|
|
178
|
-
_out, err, status = run_cli_with_status('--root', root, '--resultset', 'coverage', 'summary',
|
|
179
|
-
'lib/foo.rb')
|
|
180
|
-
expect(status).to eq(1)
|
|
181
|
-
expect(err).to include('Unexpected error in subcommand')
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
end
|
data/spec/cli_format_spec.rb
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'spec_helper'
|
|
4
|
-
|
|
5
|
-
RSpec.describe CovLoupe::CoverageCLI, 'format option' do
|
|
6
|
-
let(:root) { (FIXTURES_DIR / 'project1').to_s }
|
|
7
|
-
|
|
8
|
-
def run_cli(*argv)
|
|
9
|
-
cli = CovLoupe::CoverageCLI.new
|
|
10
|
-
output = nil
|
|
11
|
-
silence_output do |stdout, _stderr|
|
|
12
|
-
cli.send(:run, argv)
|
|
13
|
-
output = stdout.string
|
|
14
|
-
end
|
|
15
|
-
output
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
describe 'format normalization' do
|
|
19
|
-
it 'normalizes short format aliases' do
|
|
20
|
-
output = run_cli('--root', root, '--resultset', 'coverage', '--format', 'j', 'list')
|
|
21
|
-
expect(output).to include('"files":', '"percentage":')
|
|
22
|
-
data = JSON.parse(output)
|
|
23
|
-
expect(data['files']).to be_an(Array)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
it 'normalizes table format' do
|
|
27
|
-
output = run_cli('--root', root, '--resultset', 'coverage', '--format', 't', 'list')
|
|
28
|
-
expect(output).to include('File', '%') # Table output
|
|
29
|
-
expect(output).not_to include('"files"') # Not JSON
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
it 'supports yaml format' do
|
|
33
|
-
output = run_cli('--root', root, '--resultset', 'coverage', '--format', 'y', 'list')
|
|
34
|
-
expect(output).to include('---', 'files:', 'file:')
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
it 'supports awesome_print format' do
|
|
38
|
-
output = run_cli('--root', root, '--resultset', 'coverage', '--format', 'a', 'list')
|
|
39
|
-
# AwesomePrint output contains colored/formatted structure
|
|
40
|
-
expect(output).to match(/:files|"files"/)
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
describe 'option order requirements' do
|
|
45
|
-
it 'works with format option before subcommand' do
|
|
46
|
-
output = run_cli('--root', root, '--resultset', 'coverage', '--format', 'json', 'list')
|
|
47
|
-
data = JSON.parse(output)
|
|
48
|
-
expect(data).to have_key('files')
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
it 'shows helpful error when global option comes after subcommand' do
|
|
52
|
-
_out, err, status = run_cli_with_status(
|
|
53
|
-
'--root', root, '--resultset', 'coverage', 'list', '--format', 'json'
|
|
54
|
-
)
|
|
55
|
-
expect(status).to eq(1)
|
|
56
|
-
expect(err).to include(
|
|
57
|
-
'Global option(s) must come BEFORE the subcommand',
|
|
58
|
-
'You used: list --format',
|
|
59
|
-
'Correct: --format list',
|
|
60
|
-
'Example:'
|
|
61
|
-
)
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
describe 'format with different subcommands' do
|
|
66
|
-
it 'works with totals subcommand' do
|
|
67
|
-
output = run_cli('--root', root, '--resultset', 'coverage', '--format', 'json', 'totals')
|
|
68
|
-
data = JSON.parse(output)
|
|
69
|
-
expect(data).to have_key('lines')
|
|
70
|
-
expect(data).to have_key('percentage')
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
it 'works with summary subcommand' do
|
|
74
|
-
output = run_cli('--root', root, '--resultset', 'coverage', '--format', 'json',
|
|
75
|
-
'summary', 'lib/foo.rb')
|
|
76
|
-
data = JSON.parse(output)
|
|
77
|
-
expect(data).to have_key('file')
|
|
78
|
-
expect(data).to have_key('summary')
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
it 'works with version subcommand' do
|
|
82
|
-
output = run_cli('--format', 'json', 'version')
|
|
83
|
-
data = JSON.parse(output)
|
|
84
|
-
expect(data).to have_key('version')
|
|
85
|
-
expect(data).to have_key('gem_root')
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
describe 'comprehensive misplaced option detection' do
|
|
90
|
-
# Array of test cases: [description, args_array, expected_option_in_error]
|
|
91
|
-
[
|
|
92
|
-
# Short-form options
|
|
93
|
-
['short -f after list', ['list', '-f', 'json'], '-f'],
|
|
94
|
-
['short -r after totals', ['totals', '-r', '.resultset.json'], '-r'],
|
|
95
|
-
['short -R after list', ['list', '-R', '/tmp'], '-R'],
|
|
96
|
-
['short -o after list', ['list', '-o', 'a'], '-o'],
|
|
97
|
-
['short -s after list', ['list', '-s', 'full'], '-s'],
|
|
98
|
-
['short -S after list', ['list', '-S', 'error'], '-S'],
|
|
99
|
-
|
|
100
|
-
# Long-form options
|
|
101
|
-
['--sort-order after list', ['list', '--sort-order', 'ascending'], '--sort-order'],
|
|
102
|
-
['--source after list', ['list', '--source', 'full'], '--source'],
|
|
103
|
-
['--staleness after totals', ['totals', '--staleness', 'error'], '--staleness'],
|
|
104
|
-
['--color after list', ['list', '--color'], '--color'],
|
|
105
|
-
['--no-color after list', ['list', '--no-color'], '--no-color'],
|
|
106
|
-
['--log-file after list', ['list', '--log-file', '/tmp/test.log'], '--log-file'],
|
|
107
|
-
|
|
108
|
-
# Different subcommands
|
|
109
|
-
['option after version', ['version', '--format', 'json'], '--format'],
|
|
110
|
-
['option after summary', ['summary', 'lib/foo.rb', '--format', 'json'], '--format'],
|
|
111
|
-
['option after raw', ['raw', 'lib/foo.rb', '-f', 'json'], '-f'],
|
|
112
|
-
['option after detailed', ['detailed', 'lib/foo.rb', '-f', 'json'], '-f'],
|
|
113
|
-
['option after uncovered', ['uncovered', 'lib/foo.rb', '--root', '/tmp'], '--root']
|
|
114
|
-
].each do |desc, args, option|
|
|
115
|
-
it "detects #{desc}" do
|
|
116
|
-
_out, err, status = run_cli_with_status(*args)
|
|
117
|
-
expect(status).to eq(1)
|
|
118
|
-
expect(err).to include('Global option(s) must come BEFORE the subcommand')
|
|
119
|
-
expect(err).to include(option)
|
|
120
|
-
end
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
end
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'spec_helper'
|
|
4
|
-
|
|
5
|
-
RSpec.describe CovLoupe::CoverageCLI, 'json format options' do
|
|
6
|
-
let(:root) { (FIXTURES_DIR / 'project1').to_s }
|
|
7
|
-
|
|
8
|
-
def run_cli_output(*argv)
|
|
9
|
-
cli = CovLoupe::CoverageCLI.new
|
|
10
|
-
output = nil
|
|
11
|
-
silence_output do |stdout, _stderr|
|
|
12
|
-
cli.send(:run, argv)
|
|
13
|
-
output = stdout.string
|
|
14
|
-
end
|
|
15
|
-
output
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
describe 'JSON format options' do
|
|
19
|
-
it 'produces compact JSON with -f j' do
|
|
20
|
-
output = run_cli_output('--root', root, '--resultset', 'coverage', '-f', 'j', 'list')
|
|
21
|
-
|
|
22
|
-
expect(output.strip.lines.count).to eq(1)
|
|
23
|
-
data = JSON.parse(output)
|
|
24
|
-
expect(data['files']).to be_an(Array)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
it 'produces pretty JSON with -f pretty-json' do
|
|
28
|
-
output = run_cli_output('--root', root, '--resultset', 'coverage', '-f', 'pretty-json',
|
|
29
|
-
'list')
|
|
30
|
-
expect(output.strip.lines.count).to be > 1
|
|
31
|
-
data = JSON.parse(output)
|
|
32
|
-
expect(data['files']).to be_an(Array)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
it 'produces pretty JSON with -f pretty_json (underscore variant)' do
|
|
36
|
-
output = run_cli_output('--root', root, '--resultset', 'coverage', '-f', 'pretty_json',
|
|
37
|
-
'list')
|
|
38
|
-
expect(output.strip.lines.count).to be > 1
|
|
39
|
-
data = JSON.parse(output)
|
|
40
|
-
expect(data['files']).to be_an(Array)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
it 'produces compact JSON with -f json' do
|
|
44
|
-
output = run_cli_output('--root', root, '--resultset', 'coverage', '-f', 'json', 'list')
|
|
45
|
-
expect(output.strip.lines.count).to eq(1)
|
|
46
|
-
data = JSON.parse(output)
|
|
47
|
-
expect(data['files']).to be_an(Array)
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|