cov-loupe 3.0.0
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +329 -0
- data/docs/dev/ARCHITECTURE.md +80 -0
- data/docs/dev/BRANCH_ONLY_COVERAGE.md +158 -0
- data/docs/dev/DEVELOPMENT.md +83 -0
- data/docs/dev/README.md +10 -0
- data/docs/dev/RELEASING.md +146 -0
- data/docs/dev/arch-decisions/001-x-arch-decision.md +95 -0
- data/docs/dev/arch-decisions/002-x-arch-decision.md +159 -0
- data/docs/dev/arch-decisions/003-x-arch-decision.md +165 -0
- data/docs/dev/arch-decisions/004-x-arch-decision.md +203 -0
- data/docs/dev/arch-decisions/005-x-arch-decision.md +189 -0
- data/docs/dev/arch-decisions/README.md +60 -0
- data/docs/dev/presentations/cov-loupe-presentation.md +255 -0
- data/docs/fixtures/demo_project/README.md +9 -0
- data/docs/user/ADVANCED_USAGE.md +777 -0
- data/docs/user/CLI_FALLBACK_FOR_LLMS.md +34 -0
- data/docs/user/CLI_USAGE.md +750 -0
- data/docs/user/ERROR_HANDLING.md +93 -0
- data/docs/user/EXAMPLES.md +588 -0
- data/docs/user/INSTALLATION.md +130 -0
- data/docs/user/LIBRARY_API.md +693 -0
- data/docs/user/MCP_INTEGRATION.md +490 -0
- data/docs/user/README.md +14 -0
- data/docs/user/TROUBLESHOOTING.md +197 -0
- data/docs/user/V2-BREAKING-CHANGES.md +472 -0
- data/exe/cov-loupe +23 -0
- data/lib/cov_loupe/app_config.rb +56 -0
- data/lib/cov_loupe/app_context.rb +26 -0
- data/lib/cov_loupe/base_tool.rb +102 -0
- data/lib/cov_loupe/cli.rb +178 -0
- data/lib/cov_loupe/commands/base_command.rb +67 -0
- data/lib/cov_loupe/commands/command_factory.rb +45 -0
- data/lib/cov_loupe/commands/detailed_command.rb +38 -0
- data/lib/cov_loupe/commands/list_command.rb +13 -0
- data/lib/cov_loupe/commands/raw_command.rb +38 -0
- data/lib/cov_loupe/commands/summary_command.rb +41 -0
- data/lib/cov_loupe/commands/totals_command.rb +53 -0
- data/lib/cov_loupe/commands/uncovered_command.rb +45 -0
- data/lib/cov_loupe/commands/validate_command.rb +60 -0
- data/lib/cov_loupe/commands/version_command.rb +33 -0
- data/lib/cov_loupe/config_parser.rb +32 -0
- data/lib/cov_loupe/constants.rb +22 -0
- data/lib/cov_loupe/coverage_reporter.rb +31 -0
- data/lib/cov_loupe/error_handler.rb +165 -0
- data/lib/cov_loupe/error_handler_factory.rb +31 -0
- data/lib/cov_loupe/errors.rb +191 -0
- data/lib/cov_loupe/formatters/source_formatter.rb +152 -0
- data/lib/cov_loupe/formatters.rb +51 -0
- data/lib/cov_loupe/mcp_server.rb +42 -0
- data/lib/cov_loupe/mode_detector.rb +56 -0
- data/lib/cov_loupe/model.rb +339 -0
- data/lib/cov_loupe/option_normalizers.rb +113 -0
- data/lib/cov_loupe/option_parser_builder.rb +147 -0
- data/lib/cov_loupe/option_parsers/env_options_parser.rb +48 -0
- data/lib/cov_loupe/option_parsers/error_helper.rb +110 -0
- data/lib/cov_loupe/path_relativizer.rb +64 -0
- data/lib/cov_loupe/predicate_evaluator.rb +72 -0
- data/lib/cov_loupe/presenters/base_coverage_presenter.rb +42 -0
- data/lib/cov_loupe/presenters/coverage_detailed_presenter.rb +14 -0
- data/lib/cov_loupe/presenters/coverage_raw_presenter.rb +14 -0
- data/lib/cov_loupe/presenters/coverage_summary_presenter.rb +14 -0
- data/lib/cov_loupe/presenters/coverage_uncovered_presenter.rb +14 -0
- data/lib/cov_loupe/presenters/project_coverage_presenter.rb +50 -0
- data/lib/cov_loupe/presenters/project_totals_presenter.rb +27 -0
- data/lib/cov_loupe/resolvers/coverage_line_resolver.rb +122 -0
- data/lib/cov_loupe/resolvers/resolver_factory.rb +28 -0
- data/lib/cov_loupe/resolvers/resultset_path_resolver.rb +76 -0
- data/lib/cov_loupe/resultset_loader.rb +131 -0
- data/lib/cov_loupe/staleness_checker.rb +247 -0
- data/lib/cov_loupe/table_formatter.rb +64 -0
- data/lib/cov_loupe/tools/all_files_coverage_tool.rb +51 -0
- data/lib/cov_loupe/tools/coverage_detailed_tool.rb +35 -0
- data/lib/cov_loupe/tools/coverage_raw_tool.rb +34 -0
- data/lib/cov_loupe/tools/coverage_summary_tool.rb +34 -0
- data/lib/cov_loupe/tools/coverage_table_tool.rb +50 -0
- data/lib/cov_loupe/tools/coverage_totals_tool.rb +44 -0
- data/lib/cov_loupe/tools/help_tool.rb +115 -0
- data/lib/cov_loupe/tools/uncovered_lines_tool.rb +34 -0
- data/lib/cov_loupe/tools/validate_tool.rb +72 -0
- data/lib/cov_loupe/tools/version_tool.rb +32 -0
- data/lib/cov_loupe/util.rb +88 -0
- data/lib/cov_loupe/version.rb +5 -0
- data/lib/cov_loupe.rb +140 -0
- data/spec/MCP_INTEGRATION_TESTS_README.md +111 -0
- data/spec/TIMESTAMPS.md +48 -0
- data/spec/all_files_coverage_tool_spec.rb +53 -0
- data/spec/app_config_spec.rb +142 -0
- data/spec/base_tool_spec.rb +62 -0
- data/spec/cli/show_default_report_spec.rb +33 -0
- data/spec/cli_enumerated_options_spec.rb +90 -0
- data/spec/cli_error_spec.rb +184 -0
- data/spec/cli_format_spec.rb +123 -0
- data/spec/cli_json_options_spec.rb +50 -0
- data/spec/cli_source_spec.rb +44 -0
- data/spec/cli_spec.rb +192 -0
- data/spec/cli_table_spec.rb +28 -0
- data/spec/cli_usage_spec.rb +42 -0
- data/spec/commands/base_command_spec.rb +107 -0
- data/spec/commands/command_factory_spec.rb +76 -0
- data/spec/commands/detailed_command_spec.rb +34 -0
- data/spec/commands/list_command_spec.rb +28 -0
- data/spec/commands/raw_command_spec.rb +69 -0
- data/spec/commands/summary_command_spec.rb +34 -0
- data/spec/commands/totals_command_spec.rb +34 -0
- data/spec/commands/uncovered_command_spec.rb +55 -0
- data/spec/commands/validate_command_spec.rb +213 -0
- data/spec/commands/version_command_spec.rb +38 -0
- data/spec/constants_spec.rb +61 -0
- data/spec/cov_loupe/formatters/source_formatter_spec.rb +267 -0
- data/spec/cov_loupe/formatters_spec.rb +76 -0
- data/spec/cov_loupe/presenters/base_coverage_presenter_spec.rb +79 -0
- data/spec/cov_loupe_model_spec.rb +454 -0
- data/spec/cov_loupe_module_spec.rb +37 -0
- data/spec/cov_loupe_opts_spec.rb +185 -0
- data/spec/coverage_reporter_spec.rb +102 -0
- data/spec/coverage_table_tool_spec.rb +59 -0
- data/spec/coverage_totals_tool_spec.rb +37 -0
- data/spec/error_handler_spec.rb +197 -0
- data/spec/error_mode_spec.rb +139 -0
- data/spec/errors_edge_cases_spec.rb +312 -0
- data/spec/errors_stale_spec.rb +83 -0
- data/spec/file_based_mcp_tools_spec.rb +99 -0
- data/spec/fixtures/project1/lib/bar.rb +5 -0
- data/spec/fixtures/project1/lib/foo.rb +6 -0
- data/spec/help_tool_spec.rb +26 -0
- data/spec/integration_spec.rb +789 -0
- data/spec/logging_fallback_spec.rb +128 -0
- data/spec/mcp_logging_spec.rb +44 -0
- data/spec/mcp_server_integration_spec.rb +23 -0
- data/spec/mcp_server_spec.rb +106 -0
- data/spec/mode_detector_spec.rb +153 -0
- data/spec/model_error_handling_spec.rb +269 -0
- data/spec/model_staleness_spec.rb +79 -0
- data/spec/option_normalizers_spec.rb +203 -0
- data/spec/option_parsers/env_options_parser_spec.rb +221 -0
- data/spec/option_parsers/error_helper_spec.rb +222 -0
- data/spec/path_relativizer_spec.rb +98 -0
- data/spec/presenters/coverage_detailed_presenter_spec.rb +19 -0
- data/spec/presenters/coverage_raw_presenter_spec.rb +15 -0
- data/spec/presenters/coverage_summary_presenter_spec.rb +15 -0
- data/spec/presenters/coverage_uncovered_presenter_spec.rb +16 -0
- data/spec/presenters/project_coverage_presenter_spec.rb +87 -0
- data/spec/presenters/project_totals_presenter_spec.rb +144 -0
- data/spec/resolvers/coverage_line_resolver_spec.rb +282 -0
- data/spec/resolvers/resolver_factory_spec.rb +61 -0
- data/spec/resolvers/resultset_path_resolver_spec.rb +60 -0
- data/spec/resultset_loader_spec.rb +167 -0
- data/spec/shared_examples/README.md +115 -0
- data/spec/shared_examples/coverage_presenter_examples.rb +66 -0
- data/spec/shared_examples/file_based_mcp_tools.rb +179 -0
- data/spec/shared_examples/formatted_command_examples.rb +64 -0
- data/spec/shared_examples/mcp_tool_text_json_response.rb +16 -0
- data/spec/spec_helper.rb +127 -0
- data/spec/staleness_checker_spec.rb +374 -0
- data/spec/staleness_more_spec.rb +42 -0
- data/spec/support/cli_helpers.rb +22 -0
- data/spec/support/control_flow_helpers.rb +20 -0
- data/spec/support/fake_mcp.rb +40 -0
- data/spec/support/io_helpers.rb +29 -0
- data/spec/support/mcp_helpers.rb +35 -0
- data/spec/support/mcp_runner.rb +66 -0
- data/spec/support/mocking_helpers.rb +30 -0
- data/spec/table_format_spec.rb +70 -0
- data/spec/tools/validate_tool_spec.rb +132 -0
- data/spec/tools_error_handling_spec.rb +130 -0
- data/spec/util_spec.rb +154 -0
- data/spec/version_spec.rb +123 -0
- data/spec/version_tool_spec.rb +141 -0
- metadata +290 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe CovLoupe::Commands::BaseCommand do
|
|
6
|
+
let(:root) { (FIXTURES_DIR / 'project1').to_s }
|
|
7
|
+
let(:cli_context) { CovLoupe::CoverageCLI.new }
|
|
8
|
+
|
|
9
|
+
# Create a test command class that exposes protected methods for testing
|
|
10
|
+
let(:test_command_class) do
|
|
11
|
+
Class.new(CovLoupe::Commands::BaseCommand) do
|
|
12
|
+
def execute(args)
|
|
13
|
+
# Not needed for these tests
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Expose protected methods for testing
|
|
17
|
+
def public_handle_with_path(args, name, &)
|
|
18
|
+
handle_with_path(args, name, &)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
let(:test_command) { test_command_class.new(cli_context) }
|
|
24
|
+
|
|
25
|
+
describe '#handle_with_path' do
|
|
26
|
+
context 'when Errno::ENOENT is raised' do
|
|
27
|
+
it 'converts to FileNotFoundError with correct message' do
|
|
28
|
+
args = ['lib/missing.rb']
|
|
29
|
+
|
|
30
|
+
# Stub the block to raise Errno::ENOENT
|
|
31
|
+
expect do
|
|
32
|
+
test_command.public_handle_with_path(args, 'test') do |_path|
|
|
33
|
+
raise Errno::ENOENT, 'No such file or directory'
|
|
34
|
+
end
|
|
35
|
+
end.to raise_error(CovLoupe::FileNotFoundError, 'File not found: lib/missing.rb')
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'includes the path from the args in the error message' do
|
|
39
|
+
args = ['some/other/path.rb']
|
|
40
|
+
|
|
41
|
+
expect do
|
|
42
|
+
test_command.public_handle_with_path(args, 'test') do |_path|
|
|
43
|
+
raise Errno::ENOENT, 'No such file or directory'
|
|
44
|
+
end
|
|
45
|
+
end.to raise_error(CovLoupe::FileNotFoundError, /some\/other\/path\.rb/)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
context 'when Errno::EACCES is raised' do
|
|
50
|
+
it 'converts to FilePermissionError with correct message' do
|
|
51
|
+
args = ['lib/secret.rb']
|
|
52
|
+
|
|
53
|
+
# Stub the block to raise Errno::EACCES
|
|
54
|
+
expect do
|
|
55
|
+
test_command.public_handle_with_path(args, 'test') do |_path|
|
|
56
|
+
raise Errno::EACCES, 'Permission denied'
|
|
57
|
+
end
|
|
58
|
+
end.to raise_error(CovLoupe::FilePermissionError, 'Permission denied: lib/secret.rb')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'includes the path from the args in the error message' do
|
|
62
|
+
args = ['/root/protected.rb']
|
|
63
|
+
|
|
64
|
+
expect do
|
|
65
|
+
test_command.public_handle_with_path(args, 'test') do |_path|
|
|
66
|
+
raise Errno::EACCES, 'Permission denied'
|
|
67
|
+
end
|
|
68
|
+
end.to raise_error(CovLoupe::FilePermissionError, /\/root\/protected\.rb/)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
context 'when no path is provided' do
|
|
73
|
+
it 'raises UsageError' do
|
|
74
|
+
args = []
|
|
75
|
+
|
|
76
|
+
expect do
|
|
77
|
+
test_command.public_handle_with_path(args, 'summary') do |_path|
|
|
78
|
+
# Should not reach here
|
|
79
|
+
end
|
|
80
|
+
end.to raise_error(CovLoupe::UsageError, /summary <path>/)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
context 'when successful' do
|
|
85
|
+
it 'yields the path to the block' do
|
|
86
|
+
args = ['lib/foo.rb']
|
|
87
|
+
yielded_path = nil
|
|
88
|
+
|
|
89
|
+
test_command.public_handle_with_path(args, 'test') do |path|
|
|
90
|
+
yielded_path = path
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
expect(yielded_path).to eq('lib/foo.rb')
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it 'shifts the path from args' do
|
|
97
|
+
args = ['lib/foo.rb', 'extra', 'args']
|
|
98
|
+
|
|
99
|
+
test_command.public_handle_with_path(args, 'test') do |_path|
|
|
100
|
+
# Block execution
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
expect(args).to eq(['extra', 'args'])
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe CovLoupe::Commands::CommandFactory do
|
|
6
|
+
let(:cli_context) { CovLoupe::CoverageCLI.new }
|
|
7
|
+
|
|
8
|
+
describe '.create' do
|
|
9
|
+
context 'with valid command names' do
|
|
10
|
+
[
|
|
11
|
+
['list', CovLoupe::Commands::ListCommand],
|
|
12
|
+
['version', CovLoupe::Commands::VersionCommand],
|
|
13
|
+
['summary', CovLoupe::Commands::SummaryCommand],
|
|
14
|
+
['raw', CovLoupe::Commands::RawCommand],
|
|
15
|
+
['uncovered', CovLoupe::Commands::UncoveredCommand],
|
|
16
|
+
['detailed', CovLoupe::Commands::DetailedCommand],
|
|
17
|
+
['totals', CovLoupe::Commands::TotalsCommand],
|
|
18
|
+
['total', CovLoupe::Commands::TotalsCommand] # Alias
|
|
19
|
+
].each do |command_name, command_class|
|
|
20
|
+
it "creates a #{command_class.name.split('::').last} for \"#{command_name}\"" do
|
|
21
|
+
command = described_class.create(command_name, cli_context)
|
|
22
|
+
expect(command).to be_a(command_class)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
context 'with unknown command name' do
|
|
28
|
+
[
|
|
29
|
+
[
|
|
30
|
+
'invalid_cmd',
|
|
31
|
+
'invalid command',
|
|
32
|
+
/list \| summary <path> \| raw <path> \| uncovered <path>/
|
|
33
|
+
],
|
|
34
|
+
[nil, 'nil command', nil],
|
|
35
|
+
['', 'empty string command', nil],
|
|
36
|
+
['sumary', 'misspelled command', nil]
|
|
37
|
+
].each do |command_name, description, pattern|
|
|
38
|
+
it "raises UsageError for #{description}" do
|
|
39
|
+
expect do
|
|
40
|
+
described_class.create(command_name, cli_context)
|
|
41
|
+
end.to raise_error(CovLoupe::UsageError, pattern)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
describe '.available_commands' do
|
|
48
|
+
it 'returns an array of available command names' do
|
|
49
|
+
commands = described_class.available_commands
|
|
50
|
+
expect(commands).to be_an(Array)
|
|
51
|
+
expect(commands).to contain_exactly('list', 'version', 'summary', 'raw', 'uncovered',
|
|
52
|
+
'detailed', 'totals', 'total', 'validate')
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'returns the keys from COMMAND_MAP' do
|
|
56
|
+
expect(described_class.available_commands).to eq(described_class::COMMAND_MAP.keys)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
describe 'COMMAND_MAP' do
|
|
61
|
+
it 'is frozen to prevent modifications' do
|
|
62
|
+
expect(described_class::COMMAND_MAP).to be_frozen
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'maps command names to command classes' do
|
|
66
|
+
expect(described_class::COMMAND_MAP['list']).to eq(CovLoupe::Commands::ListCommand)
|
|
67
|
+
expect(described_class::COMMAND_MAP['version']).to eq(CovLoupe::Commands::VersionCommand)
|
|
68
|
+
expect(described_class::COMMAND_MAP['summary']).to eq(CovLoupe::Commands::SummaryCommand)
|
|
69
|
+
expect(described_class::COMMAND_MAP['raw']).to eq(CovLoupe::Commands::RawCommand)
|
|
70
|
+
expect(described_class::COMMAND_MAP['uncovered']).to eq(CovLoupe::Commands::UncoveredCommand)
|
|
71
|
+
expect(described_class::COMMAND_MAP['detailed']).to eq(CovLoupe::Commands::DetailedCommand)
|
|
72
|
+
expect(described_class::COMMAND_MAP['totals']).to eq(CovLoupe::Commands::TotalsCommand)
|
|
73
|
+
expect(described_class::COMMAND_MAP['total']).to eq(CovLoupe::Commands::TotalsCommand)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require_relative '../shared_examples/formatted_command_examples'
|
|
5
|
+
|
|
6
|
+
RSpec.describe CovLoupe::Commands::DetailedCommand do
|
|
7
|
+
let(:root) { (FIXTURES_DIR / 'project1').to_s }
|
|
8
|
+
let(:cli_context) { CovLoupe::CoverageCLI.new }
|
|
9
|
+
let(:command) { described_class.new(cli_context) }
|
|
10
|
+
|
|
11
|
+
before do
|
|
12
|
+
cli_context.config.root = root
|
|
13
|
+
cli_context.config.resultset = 'coverage'
|
|
14
|
+
cli_context.config.format = :table
|
|
15
|
+
cli_context.config.source_mode = nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe '#execute' do
|
|
19
|
+
context 'with table format' do
|
|
20
|
+
it 'prints the detailed coverage table' do
|
|
21
|
+
output = capture_command_output(command, ['lib/foo.rb'])
|
|
22
|
+
|
|
23
|
+
expect(output).to include('File: lib/foo.rb', 'Line', 'Covered')
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
context 'with stale data' do
|
|
28
|
+
before { stub_staleness_check('L') }
|
|
29
|
+
|
|
30
|
+
it_behaves_like 'a command with formatted output', ['lib/foo.rb'],
|
|
31
|
+
{ 'file' => 'lib/foo.rb', 'lines' => nil, 'summary' => nil, 'stale' => 'L' }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require_relative '../shared_examples/formatted_command_examples'
|
|
5
|
+
|
|
6
|
+
RSpec.describe CovLoupe::Commands::ListCommand do
|
|
7
|
+
let(:root) { (FIXTURES_DIR / 'project1').to_s }
|
|
8
|
+
let(:cli_context) { CovLoupe::CoverageCLI.new }
|
|
9
|
+
let(:command) { described_class.new(cli_context) }
|
|
10
|
+
|
|
11
|
+
before do
|
|
12
|
+
cli_context.config.root = root
|
|
13
|
+
cli_context.config.resultset = 'coverage'
|
|
14
|
+
cli_context.config.format = :table
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe '#execute' do
|
|
18
|
+
context 'with table format' do
|
|
19
|
+
it 'outputs a formatted table' do
|
|
20
|
+
output = capture_command_output(command, [])
|
|
21
|
+
|
|
22
|
+
expect(output).to include('│', 'lib/foo.rb', 'lib/bar.rb')
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it_behaves_like 'a command with formatted output', [], ['files', 'counts']
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require_relative '../shared_examples/formatted_command_examples'
|
|
5
|
+
|
|
6
|
+
RSpec.describe CovLoupe::Commands::RawCommand do
|
|
7
|
+
let(:root) { (FIXTURES_DIR / 'project1').to_s }
|
|
8
|
+
let(:cli_context) { CovLoupe::CoverageCLI.new }
|
|
9
|
+
let(:command) { described_class.new(cli_context) }
|
|
10
|
+
|
|
11
|
+
before do
|
|
12
|
+
cli_context.config.root = root
|
|
13
|
+
cli_context.config.resultset = 'coverage'
|
|
14
|
+
cli_context.config.format = :table
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe '#execute' do
|
|
18
|
+
context 'with table format' do
|
|
19
|
+
it 'prints the raw coverage lines for the requested file' do
|
|
20
|
+
output = capture_command_output(command, ['lib/foo.rb'])
|
|
21
|
+
|
|
22
|
+
expect(output).to include('│', 'lib/foo.rb', 'Line', 'Coverage')
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
context 'when the file is fully covered' do
|
|
27
|
+
it 'still prints the raw table' do
|
|
28
|
+
mock_presenter(
|
|
29
|
+
CovLoupe::Presenters::CoverageRawPresenter,
|
|
30
|
+
absolute_payload: {
|
|
31
|
+
'file' => 'lib/perfect.rb',
|
|
32
|
+
'lines' => [1, 1, 1],
|
|
33
|
+
'stale' => false
|
|
34
|
+
},
|
|
35
|
+
relative_path: 'lib/perfect.rb'
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
output = capture_command_output(command, ['lib/perfect.rb'])
|
|
39
|
+
|
|
40
|
+
expect(output).to include('│', '│ 1 │ 1 │')
|
|
41
|
+
expect(output).not_to include('All lines covered!')
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
context 'with JSON output' do
|
|
46
|
+
before { cli_context.config.format = :json }
|
|
47
|
+
|
|
48
|
+
it 'emits JSON with specific line data' do
|
|
49
|
+
stub_staleness_check('L') # Needed for stale data
|
|
50
|
+
|
|
51
|
+
output = capture_command_output(command, ['lib/foo.rb'])
|
|
52
|
+
|
|
53
|
+
payload = JSON.parse(output)
|
|
54
|
+
expect(payload['file']).to eq('lib/foo.rb')
|
|
55
|
+
expect(payload['lines']).to be_an(Array)
|
|
56
|
+
expect(payload['lines'][0]).to eq(1) # specific value
|
|
57
|
+
expect(payload['lines'][1]).to eq(0) # specific value
|
|
58
|
+
expect(payload['stale']).to eq('L')
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
context 'with stale data (other formats)' do
|
|
63
|
+
before { stub_staleness_check('L') }
|
|
64
|
+
|
|
65
|
+
# Use an array for expected_json_keys as we don't need exact value matching for these generic format tests
|
|
66
|
+
it_behaves_like 'a command with formatted output', ['lib/foo.rb'], ['file', 'lines', 'stale']
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require_relative '../shared_examples/formatted_command_examples'
|
|
5
|
+
|
|
6
|
+
RSpec.describe CovLoupe::Commands::SummaryCommand do
|
|
7
|
+
let(:root) { (FIXTURES_DIR / 'project1').to_s }
|
|
8
|
+
let(:cli_context) { CovLoupe::CoverageCLI.new }
|
|
9
|
+
let(:command) { described_class.new(cli_context) }
|
|
10
|
+
|
|
11
|
+
before do
|
|
12
|
+
cli_context.config.root = root
|
|
13
|
+
cli_context.config.resultset = 'coverage'
|
|
14
|
+
cli_context.config.format = :table
|
|
15
|
+
cli_context.config.source_mode = nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe '#execute' do
|
|
19
|
+
context 'with table format' do
|
|
20
|
+
it 'prints a coverage summary line with a relative path' do
|
|
21
|
+
output = capture_command_output(command, ['lib/foo.rb'])
|
|
22
|
+
|
|
23
|
+
expect(output).to include('│', '66.67%', 'lib/foo.rb')
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
context 'with stale data' do
|
|
28
|
+
before { stub_staleness_check('L') }
|
|
29
|
+
|
|
30
|
+
it_behaves_like 'a command with formatted output', ['lib/foo.rb'],
|
|
31
|
+
{ 'file' => 'lib/foo.rb', 'summary' => nil, 'stale' => 'L' }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require_relative '../shared_examples/formatted_command_examples'
|
|
5
|
+
|
|
6
|
+
RSpec.describe CovLoupe::Commands::TotalsCommand do
|
|
7
|
+
let(:root) { (FIXTURES_DIR / 'project1').to_s }
|
|
8
|
+
let(:cli_context) { CovLoupe::CoverageCLI.new }
|
|
9
|
+
let(:command) { described_class.new(cli_context) }
|
|
10
|
+
|
|
11
|
+
before do
|
|
12
|
+
cli_context.config.root = root
|
|
13
|
+
cli_context.config.resultset = 'coverage'
|
|
14
|
+
cli_context.config.format = :table
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe '#execute' do
|
|
18
|
+
context 'with table format' do
|
|
19
|
+
it 'prints aggregated totals for the project' do
|
|
20
|
+
output = capture_command_output(command, [])
|
|
21
|
+
|
|
22
|
+
expect(output).to include('│', 'Lines', '50.00%')
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it_behaves_like 'a command with formatted output', [], ['lines', 'files', 'percentage']
|
|
27
|
+
|
|
28
|
+
it 'raises when unexpected arguments are provided' do
|
|
29
|
+
expect do
|
|
30
|
+
command.execute(['extra'])
|
|
31
|
+
end.to raise_error(CovLoupe::UsageError, include('totals'))
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require_relative '../shared_examples/formatted_command_examples'
|
|
5
|
+
|
|
6
|
+
RSpec.describe CovLoupe::Commands::UncoveredCommand do
|
|
7
|
+
let(:root) { (FIXTURES_DIR / 'project1').to_s }
|
|
8
|
+
let(:cli_context) { CovLoupe::CoverageCLI.new }
|
|
9
|
+
let(:command) { described_class.new(cli_context) }
|
|
10
|
+
|
|
11
|
+
before do
|
|
12
|
+
cli_context.config.root = root
|
|
13
|
+
cli_context.config.resultset = 'coverage'
|
|
14
|
+
cli_context.config.format = :table
|
|
15
|
+
cli_context.config.source_mode = nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe '#execute' do
|
|
19
|
+
context 'with table format' do
|
|
20
|
+
it 'prints uncovered line numbers with the summary' do
|
|
21
|
+
output = capture_command_output(command, ['lib/bar.rb'])
|
|
22
|
+
|
|
23
|
+
expect(output).to include('│', 'lib/bar.rb', '33.33%')
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
context 'when the file is fully covered' do
|
|
28
|
+
before do
|
|
29
|
+
mock_presenter(
|
|
30
|
+
CovLoupe::Presenters::CoverageUncoveredPresenter,
|
|
31
|
+
absolute_payload: {
|
|
32
|
+
'file' => 'lib/perfect.rb',
|
|
33
|
+
'uncovered' => [],
|
|
34
|
+
'summary' => { 'covered' => 10, 'total' => 10, 'percentage' => 100.0 }
|
|
35
|
+
},
|
|
36
|
+
relative_path: 'lib/perfect.rb'
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'prints a success message instead of a table' do
|
|
41
|
+
output = capture_command_output(command, ['lib/perfect.rb'])
|
|
42
|
+
|
|
43
|
+
expect(output).to include('All lines covered!', '100.00%')
|
|
44
|
+
expect(output).not_to include('│')
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
context 'with stale data' do
|
|
49
|
+
before { stub_staleness_check('L') }
|
|
50
|
+
|
|
51
|
+
it_behaves_like 'a command with formatted output', ['lib/foo.rb'],
|
|
52
|
+
{ 'file' => 'lib/foo.rb', 'uncovered' => [2], 'summary' => nil, 'stale' => 'L' }
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'tempfile'
|
|
5
|
+
|
|
6
|
+
RSpec.describe CovLoupe::Commands::ValidateCommand do
|
|
7
|
+
let(:root) { (FIXTURES_DIR / 'project1').to_s }
|
|
8
|
+
|
|
9
|
+
def with_temp_predicate(content)
|
|
10
|
+
Tempfile.create(['predicate', '.rb']) do |file|
|
|
11
|
+
file.write(content)
|
|
12
|
+
file.flush
|
|
13
|
+
yield file.path
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe 'validate subcommand with file' do
|
|
18
|
+
it 'exits 0 when predicate returns truthy value' do
|
|
19
|
+
with_temp_predicate("->(model) { true }\n") do |path|
|
|
20
|
+
_out, _err, status = run_cli_with_status(
|
|
21
|
+
'--root', root,
|
|
22
|
+
'--resultset', 'coverage',
|
|
23
|
+
'validate', path
|
|
24
|
+
)
|
|
25
|
+
expect(status).to eq(0)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'exits 1 when predicate returns falsy value' do
|
|
30
|
+
with_temp_predicate("->(model) { false }\n") do |path|
|
|
31
|
+
_out, _err, status = run_cli_with_status(
|
|
32
|
+
'--root', root,
|
|
33
|
+
'--resultset', 'coverage',
|
|
34
|
+
'validate', path
|
|
35
|
+
)
|
|
36
|
+
expect(status).to eq(1)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'exits 2 when predicate raises an error' do
|
|
41
|
+
with_temp_predicate("->(model) { raise 'Boom!' }\n") do |path|
|
|
42
|
+
_out, err, status = run_cli_with_status(
|
|
43
|
+
'--root', root,
|
|
44
|
+
'--resultset', 'coverage',
|
|
45
|
+
'validate', path
|
|
46
|
+
)
|
|
47
|
+
expect(status).to eq(2)
|
|
48
|
+
expect(err).to include('Predicate error: Boom!')
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'shows backtrace when predicate errors with --error-mode debug' do
|
|
53
|
+
with_temp_predicate("->(model) { raise 'Boom!' }\n") do |path|
|
|
54
|
+
_out, err, status = run_cli_with_status(
|
|
55
|
+
'--error-mode', 'debug',
|
|
56
|
+
'--root', root,
|
|
57
|
+
'--resultset', 'coverage',
|
|
58
|
+
'validate', path
|
|
59
|
+
)
|
|
60
|
+
expect(status).to eq(2)
|
|
61
|
+
expect(err).to include('Predicate error: Boom!')
|
|
62
|
+
# With trace mode, should show backtrace
|
|
63
|
+
expect(err).to match(/predicate.*\.rb:\d+/)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'exits 2 when predicate file is not found' do
|
|
68
|
+
_out, err, status = run_cli_with_status(
|
|
69
|
+
'--root', root,
|
|
70
|
+
'--resultset', 'coverage',
|
|
71
|
+
'validate', '/nonexistent/predicate.rb'
|
|
72
|
+
)
|
|
73
|
+
expect(status).to eq(2)
|
|
74
|
+
expect(err).to include('Predicate file not found')
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'exits 2 when predicate has syntax error' do
|
|
78
|
+
with_temp_predicate("-> { this is invalid syntax\n") do |path|
|
|
79
|
+
_out, err, status = run_cli_with_status(
|
|
80
|
+
'--root', root,
|
|
81
|
+
'--resultset', 'coverage',
|
|
82
|
+
'validate', path
|
|
83
|
+
)
|
|
84
|
+
expect(status).to eq(2)
|
|
85
|
+
expect(err).to include('Syntax error in predicate file')
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'exits 2 when predicate is not callable' do
|
|
90
|
+
with_temp_predicate("42\n") do |path|
|
|
91
|
+
_out, err, status = run_cli_with_status(
|
|
92
|
+
'--root', root,
|
|
93
|
+
'--resultset', 'coverage',
|
|
94
|
+
'validate', path
|
|
95
|
+
)
|
|
96
|
+
expect(status).to eq(2)
|
|
97
|
+
expect(err).to include('Predicate must be callable')
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it 'provides model to predicate that can query coverage' do
|
|
102
|
+
# Test that the predicate receives a working CoverageModel
|
|
103
|
+
with_temp_predicate(<<~RUBY) do |path|
|
|
104
|
+
->(model) do
|
|
105
|
+
# Access coverage data via the model
|
|
106
|
+
summary = model.summary_for('lib/foo.rb')
|
|
107
|
+
summary['summary']['percentage'] > 50 # Should be true for foo.rb
|
|
108
|
+
end
|
|
109
|
+
RUBY
|
|
110
|
+
_out, _err, status = run_cli_with_status(
|
|
111
|
+
'--root', root,
|
|
112
|
+
'--resultset', 'coverage',
|
|
113
|
+
'validate', path
|
|
114
|
+
)
|
|
115
|
+
expect(status).to eq(0)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
describe 'validate subcommand with -i/--inline flag' do
|
|
121
|
+
it 'exits 0 when predicate code returns truthy value' do
|
|
122
|
+
_out, _err, status = run_cli_with_status(
|
|
123
|
+
'--root', root,
|
|
124
|
+
'--resultset', 'coverage',
|
|
125
|
+
'validate', '-i', '->(model) { true }'
|
|
126
|
+
)
|
|
127
|
+
expect(status).to eq(0)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it 'exits 1 when predicate code returns falsy value' do
|
|
131
|
+
_out, _err, status = run_cli_with_status(
|
|
132
|
+
'--root', root,
|
|
133
|
+
'--resultset', 'coverage',
|
|
134
|
+
'validate', '-i', '->(model) { false }'
|
|
135
|
+
)
|
|
136
|
+
expect(status).to eq(1)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it 'exits 2 when predicate code raises an error' do
|
|
140
|
+
_out, err, status = run_cli_with_status(
|
|
141
|
+
'--root', root,
|
|
142
|
+
'--resultset', 'coverage',
|
|
143
|
+
'validate', '-i', "->(model) { raise 'Boom!' }"
|
|
144
|
+
)
|
|
145
|
+
expect(status).to eq(2)
|
|
146
|
+
expect(err).to include('Predicate error: Boom!')
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it 'exits 2 when predicate code has syntax error' do
|
|
150
|
+
_out, err, status = run_cli_with_status(
|
|
151
|
+
'--root', root,
|
|
152
|
+
'--resultset', 'coverage',
|
|
153
|
+
'validate', '-i', '-> { invalid syntax'
|
|
154
|
+
)
|
|
155
|
+
expect(status).to eq(2)
|
|
156
|
+
expect(err).to include('Syntax error in predicate code')
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it 'exits 2 when predicate code is not callable' do
|
|
160
|
+
_out, err, status = run_cli_with_status(
|
|
161
|
+
'--root', root,
|
|
162
|
+
'--resultset', 'coverage',
|
|
163
|
+
'validate', '-i', '42'
|
|
164
|
+
)
|
|
165
|
+
expect(status).to eq(2)
|
|
166
|
+
expect(err).to include('Predicate must be callable')
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
it 'provides model to predicate that can query coverage' do
|
|
170
|
+
code = <<~RUBY.strip
|
|
171
|
+
->(model) { model.summary_for('lib/foo.rb')['summary']['percentage'] > 50 }
|
|
172
|
+
RUBY
|
|
173
|
+
_out, _err, status = run_cli_with_status(
|
|
174
|
+
'--root', root,
|
|
175
|
+
'--resultset', 'coverage',
|
|
176
|
+
'validate', '-i', code
|
|
177
|
+
)
|
|
178
|
+
expect(status).to eq(0)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
describe 'error handling' do
|
|
183
|
+
it 'raises error when no file or -i flag provided' do
|
|
184
|
+
_out, err, status = run_cli_with_status(
|
|
185
|
+
'--root', root,
|
|
186
|
+
'--resultset', 'coverage',
|
|
187
|
+
'validate'
|
|
188
|
+
)
|
|
189
|
+
expect(status).to eq(1)
|
|
190
|
+
expect(err).to include('validate <file> | -i <code>')
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
it 'raises error when -i flag provided without code' do
|
|
194
|
+
_out, err, status = run_cli_with_status(
|
|
195
|
+
'--root', root,
|
|
196
|
+
'--resultset', 'coverage',
|
|
197
|
+
'validate', '-i'
|
|
198
|
+
)
|
|
199
|
+
expect(status).to eq(1)
|
|
200
|
+
expect(err).to include('validate -i <code>')
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
it 'raises error when unknown option is provided' do
|
|
204
|
+
_out, err, status = run_cli_with_status(
|
|
205
|
+
'--root', root,
|
|
206
|
+
'--resultset', 'coverage',
|
|
207
|
+
'validate', '--unknown-option'
|
|
208
|
+
)
|
|
209
|
+
expect(status).to eq(1)
|
|
210
|
+
expect(err).to include('Unknown option for validate: --unknown-option')
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|