simplecov-mcp 1.0.1 → 2.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 +4 -4
- data/README.md +98 -50
- data/docs/{ARCHITECTURE.md → dev/ARCHITECTURE.md} +11 -10
- data/docs/dev/BRANCH_ONLY_COVERAGE.md +158 -0
- data/docs/{DEVELOPMENT.md → dev/DEVELOPMENT.md} +2 -1
- data/docs/dev/README.md +10 -0
- data/docs/dev/RELEASING.md +146 -0
- data/docs/{arch-decisions → dev/arch-decisions}/001-x-arch-decision.md +3 -1
- data/docs/{arch-decisions → dev/arch-decisions}/002-x-arch-decision.md +7 -5
- data/docs/{arch-decisions → dev/arch-decisions}/003-x-arch-decision.md +2 -0
- data/docs/{arch-decisions → dev/arch-decisions}/004-x-arch-decision.md +6 -2
- data/docs/{arch-decisions → dev/arch-decisions}/005-x-arch-decision.md +4 -2
- data/docs/{arch-decisions → dev/arch-decisions}/README.md +3 -3
- data/docs/{presentations → dev/presentations}/simplecov-mcp-presentation.md +28 -22
- data/docs/fixtures/demo_project/README.md +9 -0
- data/docs/{ADVANCED_USAGE.md → user/ADVANCED_USAGE.md} +129 -319
- data/docs/user/CLI_FALLBACK_FOR_LLMS.md +34 -0
- data/docs/user/CLI_USAGE.md +750 -0
- data/docs/{ERROR_HANDLING.md → user/ERROR_HANDLING.md} +12 -12
- data/docs/user/EXAMPLES.md +588 -0
- data/docs/user/INSTALLATION.md +130 -0
- data/docs/{LIBRARY_API.md → user/LIBRARY_API.md} +90 -32
- data/docs/{MCP_INTEGRATION.md → user/MCP_INTEGRATION.md} +36 -34
- data/docs/user/README.md +14 -0
- data/docs/{TROUBLESHOOTING.md → user/TROUBLESHOOTING.md} +21 -100
- data/docs/user/V2-BREAKING-CHANGES.md +472 -0
- data/exe/simplecov-mcp +1 -1
- data/lib/simplecov_mcp/{cli_config.rb → app_config.rb} +12 -12
- data/lib/simplecov_mcp/app_context.rb +1 -1
- data/lib/simplecov_mcp/base_tool.rb +66 -38
- data/lib/simplecov_mcp/cli.rb +67 -123
- data/lib/simplecov_mcp/commands/base_command.rb +16 -27
- data/lib/simplecov_mcp/commands/command_factory.rb +8 -2
- data/lib/simplecov_mcp/commands/detailed_command.rb +16 -2
- data/lib/simplecov_mcp/commands/list_command.rb +1 -1
- data/lib/simplecov_mcp/commands/raw_command.rb +18 -2
- data/lib/simplecov_mcp/commands/summary_command.rb +20 -3
- data/lib/simplecov_mcp/commands/totals_command.rb +53 -0
- data/lib/simplecov_mcp/commands/uncovered_command.rb +24 -5
- data/lib/simplecov_mcp/commands/validate_command.rb +60 -0
- data/lib/simplecov_mcp/commands/version_command.rb +19 -4
- data/lib/simplecov_mcp/config_parser.rb +32 -0
- data/lib/simplecov_mcp/constants.rb +3 -3
- data/lib/simplecov_mcp/coverage_reporter.rb +31 -0
- data/lib/simplecov_mcp/error_handler.rb +81 -40
- data/lib/simplecov_mcp/error_handler_factory.rb +2 -2
- data/lib/simplecov_mcp/errors.rb +12 -19
- data/lib/simplecov_mcp/formatters/source_formatter.rb +23 -19
- data/lib/simplecov_mcp/formatters.rb +51 -0
- data/lib/simplecov_mcp/mcp_server.rb +9 -7
- data/lib/simplecov_mcp/mode_detector.rb +6 -5
- data/lib/simplecov_mcp/model.rb +122 -88
- data/lib/simplecov_mcp/option_normalizers.rb +39 -18
- data/lib/simplecov_mcp/option_parser_builder.rb +82 -65
- data/lib/simplecov_mcp/option_parsers/env_options_parser.rb +3 -5
- data/lib/simplecov_mcp/option_parsers/error_helper.rb +18 -17
- data/lib/simplecov_mcp/path_relativizer.rb +17 -14
- data/lib/simplecov_mcp/predicate_evaluator.rb +72 -0
- data/lib/simplecov_mcp/presenters/base_coverage_presenter.rb +1 -3
- data/lib/simplecov_mcp/presenters/coverage_detailed_presenter.rb +1 -3
- data/lib/simplecov_mcp/presenters/coverage_raw_presenter.rb +1 -3
- data/lib/simplecov_mcp/presenters/coverage_summary_presenter.rb +1 -3
- data/lib/simplecov_mcp/presenters/coverage_uncovered_presenter.rb +1 -3
- data/lib/simplecov_mcp/presenters/project_coverage_presenter.rb +1 -3
- data/lib/simplecov_mcp/presenters/project_totals_presenter.rb +27 -0
- data/lib/simplecov_mcp/resolvers/coverage_line_resolver.rb +14 -18
- data/lib/simplecov_mcp/resolvers/resultset_path_resolver.rb +7 -9
- data/lib/simplecov_mcp/resultset_loader.rb +20 -25
- data/lib/simplecov_mcp/staleness_checker.rb +50 -46
- data/lib/simplecov_mcp/table_formatter.rb +64 -0
- data/lib/simplecov_mcp/tools/all_files_coverage_tool.rb +20 -50
- data/lib/simplecov_mcp/tools/coverage_detailed_tool.rb +13 -7
- data/lib/simplecov_mcp/tools/coverage_raw_tool.rb +12 -7
- data/lib/simplecov_mcp/tools/coverage_summary_tool.rb +13 -8
- data/lib/simplecov_mcp/tools/coverage_table_tool.rb +20 -60
- data/lib/simplecov_mcp/tools/coverage_totals_tool.rb +44 -0
- data/lib/simplecov_mcp/tools/help_tool.rb +38 -66
- data/lib/simplecov_mcp/tools/uncovered_lines_tool.rb +13 -8
- data/lib/simplecov_mcp/tools/validate_tool.rb +72 -0
- data/lib/simplecov_mcp/tools/version_tool.rb +7 -14
- data/lib/simplecov_mcp/util.rb +18 -12
- data/lib/simplecov_mcp/version.rb +1 -1
- data/lib/simplecov_mcp.rb +23 -29
- data/spec/all_files_coverage_tool_spec.rb +4 -3
- data/spec/{cli_config_spec.rb → app_config_spec.rb} +31 -26
- data/spec/base_tool_spec.rb +17 -14
- data/spec/cli/show_default_report_spec.rb +2 -2
- data/spec/cli_enumerated_options_spec.rb +31 -9
- data/spec/cli_error_spec.rb +46 -23
- data/spec/cli_format_spec.rb +123 -0
- data/spec/cli_json_options_spec.rb +50 -0
- data/spec/cli_source_spec.rb +11 -63
- data/spec/cli_spec.rb +82 -97
- data/spec/cli_usage_spec.rb +15 -15
- data/spec/commands/base_command_spec.rb +12 -92
- data/spec/commands/command_factory_spec.rb +7 -3
- data/spec/commands/detailed_command_spec.rb +10 -24
- data/spec/commands/list_command_spec.rb +28 -0
- data/spec/commands/raw_command_spec.rb +43 -20
- data/spec/commands/summary_command_spec.rb +10 -23
- data/spec/commands/totals_command_spec.rb +34 -0
- data/spec/commands/uncovered_command_spec.rb +29 -23
- data/spec/commands/validate_command_spec.rb +213 -0
- data/spec/commands/version_command_spec.rb +38 -0
- data/spec/constants_spec.rb +3 -3
- data/spec/coverage_reporter_spec.rb +102 -0
- data/spec/coverage_table_tool_spec.rb +21 -10
- data/spec/coverage_totals_tool_spec.rb +37 -0
- data/spec/error_handler_spec.rb +120 -4
- data/spec/error_mode_spec.rb +18 -22
- data/spec/errors_edge_cases_spec.rb +101 -28
- data/spec/errors_stale_spec.rb +34 -0
- data/spec/file_based_mcp_tools_spec.rb +6 -6
- data/spec/fixtures/project1/lib/bar.rb +2 -0
- data/spec/fixtures/project1/lib/foo.rb +2 -0
- data/spec/help_tool_spec.rb +2 -18
- data/spec/integration_spec.rb +103 -161
- data/spec/logging_fallback_spec.rb +3 -3
- data/spec/mcp_server_integration_spec.rb +1 -1
- data/spec/mcp_server_spec.rb +70 -53
- data/spec/mode_detector_spec.rb +46 -41
- data/spec/model_error_handling_spec.rb +139 -78
- data/spec/model_staleness_spec.rb +13 -13
- data/spec/option_normalizers_spec.rb +111 -112
- data/spec/option_parsers/env_options_parser_spec.rb +25 -37
- data/spec/option_parsers/error_helper_spec.rb +56 -56
- data/spec/path_relativizer_spec.rb +15 -0
- data/spec/presenters/coverage_detailed_presenter_spec.rb +1 -1
- data/spec/presenters/coverage_summary_presenter_spec.rb +1 -1
- data/spec/presenters/coverage_uncovered_presenter_spec.rb +1 -1
- data/spec/presenters/project_coverage_presenter_spec.rb +9 -8
- data/spec/presenters/project_totals_presenter_spec.rb +144 -0
- data/spec/resolvers/coverage_line_resolver_spec.rb +261 -36
- data/spec/resolvers/resultset_path_resolver_spec.rb +13 -8
- data/spec/shared_examples/file_based_mcp_tools.rb +23 -18
- data/spec/shared_examples/formatted_command_examples.rb +64 -0
- data/spec/simple_cov_mcp_module_spec.rb +24 -3
- data/spec/simplecov_mcp/formatters/source_formatter_spec.rb +267 -0
- data/spec/simplecov_mcp/formatters_spec.rb +76 -0
- data/spec/simplecov_mcp/presenters/base_coverage_presenter_spec.rb +79 -0
- data/spec/simplecov_mcp_model_spec.rb +97 -47
- data/spec/simplecov_mcp_opts_spec.rb +42 -39
- data/spec/spec_helper.rb +27 -92
- data/spec/staleness_checker_spec.rb +10 -9
- data/spec/staleness_more_spec.rb +4 -4
- 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 +10 -8
- 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 +34 -48
- data/spec/util_spec.rb +5 -4
- data/spec/version_spec.rb +7 -7
- data/spec/version_tool_spec.rb +20 -22
- metadata +90 -23
- data/docs/BRANCH_ONLY_COVERAGE.md +0 -81
- data/docs/CLI_USAGE.md +0 -637
- data/docs/EXAMPLES.md +0 -430
- data/docs/INSTALLATION.md +0 -352
- data/spec/cli_success_predicate_spec.rb +0 -141
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe SimpleCovMcp::CoverageCLI, 'format option' do
|
|
6
|
+
let(:root) { (FIXTURES_DIR / 'project1').to_s }
|
|
7
|
+
|
|
8
|
+
def run_cli(*argv)
|
|
9
|
+
cli = SimpleCovMcp::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
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe SimpleCovMcp::CoverageCLI, 'json format options' do
|
|
6
|
+
let(:root) { (FIXTURES_DIR / 'project1').to_s }
|
|
7
|
+
|
|
8
|
+
def run_cli_output(*argv)
|
|
9
|
+
cli = SimpleCovMcp::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
|
data/spec/cli_source_spec.rb
CHANGED
|
@@ -7,90 +7,38 @@ RSpec.describe SimpleCovMcp::CoverageCLI do
|
|
|
7
7
|
|
|
8
8
|
it 'renders uncovered source without error for fixture file' do
|
|
9
9
|
out, err, status = run_cli_with_status(
|
|
10
|
-
'
|
|
11
|
-
'--
|
|
10
|
+
'--root', root, '--resultset', 'coverage', '--source', 'uncovered', '--context-lines', '1',
|
|
11
|
+
'--no-color', 'uncovered', 'lib/foo.rb'
|
|
12
12
|
)
|
|
13
13
|
expect(status).to eq(0)
|
|
14
14
|
expect(err).to eq('')
|
|
15
15
|
expect(out).to match(/File:\s+lib\/foo\.rb/)
|
|
16
|
-
expect(out).to
|
|
16
|
+
expect(out).to include('│') # Table format
|
|
17
17
|
expect(out).to show_source_table_or_fallback
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
it 'renders full source for uncovered command without brittle spacing' do
|
|
21
21
|
out, err, status = run_cli_with_status(
|
|
22
|
-
'
|
|
23
|
-
'
|
|
22
|
+
'--root', root, '--resultset', 'coverage', '--source', 'full', '--no-color',
|
|
23
|
+
'uncovered', 'lib/foo.rb'
|
|
24
24
|
)
|
|
25
25
|
expect(status).to eq(0)
|
|
26
26
|
expect(err).to eq('')
|
|
27
|
-
#
|
|
28
|
-
expect(out).to
|
|
27
|
+
expect(out).to include('│') # Table format
|
|
28
|
+
expect(out).to include('66.67%')
|
|
29
29
|
expect(out).to show_source_table_or_fallback
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
it 'renders source for summary with uncovered mode without crashing' do
|
|
33
33
|
out, err, status = run_cli_with_status(
|
|
34
|
-
'
|
|
35
|
-
'--
|
|
34
|
+
'--root', root, '--resultset', 'coverage', '--source', 'uncovered', '--context-lines', '1',
|
|
35
|
+
'--no-color', 'summary', 'lib/foo.rb'
|
|
36
36
|
)
|
|
37
37
|
expect(status).to eq(0)
|
|
38
38
|
expect(err).to eq('')
|
|
39
39
|
expect(out).to include('lib/foo.rb')
|
|
40
|
-
|
|
41
|
-
expect(out).to
|
|
42
|
-
expect(out).to match(/\b2\/3\b/)
|
|
40
|
+
expect(out).to include('66.67%')
|
|
41
|
+
expect(out).to include('│') # Table format
|
|
43
42
|
expect(out).to show_source_table_or_fallback
|
|
44
43
|
end
|
|
45
|
-
|
|
46
|
-
context 'source option without equals sign' do
|
|
47
|
-
it 'parses --source uncovered correctly (space-separated argument)' do
|
|
48
|
-
out, err, status = run_cli_with_status(
|
|
49
|
-
'summary', 'lib/foo.rb', '--root', root, '--resultset', 'coverage',
|
|
50
|
-
'--source', 'uncovered', '--source-context', '1', '--no-color'
|
|
51
|
-
)
|
|
52
|
-
expect(status).to eq(0)
|
|
53
|
-
expect(err).to eq('')
|
|
54
|
-
expect(out).to include('lib/foo.rb')
|
|
55
|
-
expect(out).to match(/66\.67%/)
|
|
56
|
-
expect(out).to match(/\b2\/3\b/)
|
|
57
|
-
expect(out).to show_source_table_or_fallback
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
it 'parses -s full correctly (short form with space-separated argument)' do
|
|
61
|
-
out, err, status = run_cli_with_status(
|
|
62
|
-
'uncovered', 'lib/foo.rb', '--root', root, '--resultset', 'coverage',
|
|
63
|
-
'-s', 'full', '--no-color'
|
|
64
|
-
)
|
|
65
|
-
expect(status).to eq(0)
|
|
66
|
-
expect(err).to eq('')
|
|
67
|
-
expect(out).to match(/Summary:\s*\d+\.\d{2}%\s*\d+\/\d+/)
|
|
68
|
-
expect(out).to show_source_table_or_fallback
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
it 'handles --source uncovered in default report (no subcommand)' do
|
|
72
|
-
out, err, status = run_cli_with_status(
|
|
73
|
-
'--root', root, '--resultset', 'coverage',
|
|
74
|
-
'--source', 'uncovered', '--no-color'
|
|
75
|
-
)
|
|
76
|
-
expect(status).to eq(0)
|
|
77
|
-
expect(err).to eq('')
|
|
78
|
-
expect(out).to match(/66\.67%/)
|
|
79
|
-
# Default report doesn't show source tables, that's OK - just check it parses correctly
|
|
80
|
-
expect(out).not_to include('Unknown subcommand')
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
it 'does not misinterpret following token as subcommand when using --source' do
|
|
84
|
-
# This test specifically addresses the bug where --source uncovered
|
|
85
|
-
# was interpreting 'uncovered' as a subcommand
|
|
86
|
-
out, err, status = run_cli_with_status(
|
|
87
|
-
'--root', root, '--resultset', 'coverage',
|
|
88
|
-
'--source', 'uncovered'
|
|
89
|
-
)
|
|
90
|
-
expect(status).to eq(0)
|
|
91
|
-
expect(err).to eq('')
|
|
92
|
-
expect(out).not_to include('Unknown subcommand')
|
|
93
|
-
expect(out).to match(/66\.67%/)
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
44
|
end
|
data/spec/cli_spec.rb
CHANGED
|
@@ -9,48 +9,65 @@ RSpec.describe SimpleCovMcp::CoverageCLI do
|
|
|
9
9
|
def run_cli(*argv)
|
|
10
10
|
cli = described_class.new
|
|
11
11
|
silence_output do |out, _err|
|
|
12
|
-
|
|
12
|
+
begin
|
|
13
|
+
cli.run(argv.flatten)
|
|
14
|
+
rescue SystemExit
|
|
15
|
+
# Ignore exit, just capture output
|
|
16
|
+
end
|
|
13
17
|
return out.string
|
|
14
18
|
end
|
|
15
19
|
end
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
describe 'JSON output' do
|
|
22
|
+
def with_json_output(command, *args)
|
|
23
|
+
output = run_cli('--format', 'json', '--root', root, '--resultset', 'coverage',
|
|
24
|
+
command, *args)
|
|
25
|
+
yield JSON.parse(output)
|
|
26
|
+
end
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
end
|
|
28
|
+
it 'prints summary as JSON' do
|
|
29
|
+
with_json_output('summary', 'lib/foo.rb') do |data|
|
|
30
|
+
expect(data['summary']).to include('covered' => 2)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
it 'prints raw as JSON' do
|
|
35
|
+
with_json_output('raw', 'lib/foo.rb') do |data|
|
|
36
|
+
expect(data['lines']).to eq([1, 0, nil, 2])
|
|
37
|
+
end
|
|
38
|
+
end
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
it 'prints uncovered as JSON' do
|
|
41
|
+
with_json_output('uncovered', 'lib/foo.rb') do |data|
|
|
42
|
+
expect(data['uncovered']).to eq([2])
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'prints detailed as JSON' do
|
|
47
|
+
with_json_output('detailed', 'lib/foo.rb') do |data|
|
|
48
|
+
expect(data['lines']).to be_an(Array)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'prints totals as JSON' do
|
|
53
|
+
with_json_output('totals') do |data|
|
|
54
|
+
expect(data['lines']).to include('total' => 6, 'covered' => 3, 'uncovered' => 3)
|
|
55
|
+
expect(data['files']).to include('total' => 2)
|
|
56
|
+
expect(data['files']['ok'] + data['files']['stale']).to eq(data['files']['total'])
|
|
57
|
+
end
|
|
58
|
+
end
|
|
42
59
|
end
|
|
43
60
|
|
|
44
|
-
it 'prints
|
|
45
|
-
output = run_cli('
|
|
46
|
-
|
|
47
|
-
expect(
|
|
48
|
-
expect(data['lines'].first).to include('line', 'hits', 'covered')
|
|
61
|
+
it 'prints raw lines as text' do
|
|
62
|
+
output = run_cli('--root', root, '--resultset', 'coverage', 'raw', 'lib/foo.rb')
|
|
63
|
+
expect(output).to include('File: lib/foo.rb')
|
|
64
|
+
expect(output).to include('│') # Table format
|
|
49
65
|
end
|
|
50
66
|
|
|
51
67
|
it 'list subcommand with --json outputs JSON with sort order' do
|
|
52
|
-
output = run_cli(
|
|
53
|
-
'a'
|
|
68
|
+
output = run_cli(
|
|
69
|
+
'--format', 'json', '--root', root, '--resultset', 'coverage', '--sort-order', 'a', 'list'
|
|
70
|
+
)
|
|
54
71
|
asc = JSON.parse(output)
|
|
55
72
|
expect(asc['files']).to be_an(Array)
|
|
56
73
|
expect(asc['files'].first['file']).to end_with('lib/bar.rb')
|
|
@@ -63,14 +80,15 @@ RSpec.describe SimpleCovMcp::CoverageCLI do
|
|
|
63
80
|
expect(total).to eq(asc['files'].length)
|
|
64
81
|
expect(ok + stale).to eq(total)
|
|
65
82
|
|
|
66
|
-
output = run_cli(
|
|
67
|
-
'd'
|
|
83
|
+
output = run_cli(
|
|
84
|
+
'--format', 'json', '--root', root, '--resultset', 'coverage', '--sort-order', 'd', 'list'
|
|
85
|
+
)
|
|
68
86
|
desc = JSON.parse(output)
|
|
69
87
|
expect(desc['files'].first['file']).to end_with('lib/foo.rb')
|
|
70
88
|
end
|
|
71
89
|
|
|
72
90
|
it 'list subcommand outputs formatted table' do
|
|
73
|
-
output = run_cli('
|
|
91
|
+
output = run_cli('--root', root, '--resultset', 'coverage', 'list')
|
|
74
92
|
expect(output).to include('File')
|
|
75
93
|
expect(output).to include('lib/foo.rb')
|
|
76
94
|
expect(output).to include('lib/bar.rb')
|
|
@@ -79,20 +97,23 @@ RSpec.describe SimpleCovMcp::CoverageCLI do
|
|
|
79
97
|
|
|
80
98
|
it 'list subcommand retains rows when using an absolute tracked glob' do
|
|
81
99
|
absolute_glob = File.join(root, 'lib', '**', '*.rb')
|
|
82
|
-
output = run_cli('
|
|
83
|
-
absolute_glob)
|
|
100
|
+
output = run_cli('--root', root, '--resultset', 'coverage', '--tracked-globs',
|
|
101
|
+
absolute_glob, 'list')
|
|
84
102
|
expect(output).not_to include('No coverage data found')
|
|
85
103
|
expect(output).to include('lib/foo.rb')
|
|
86
104
|
expect(output).to include('lib/bar.rb')
|
|
87
105
|
end
|
|
88
106
|
|
|
89
|
-
it '
|
|
90
|
-
|
|
107
|
+
it 'totals subcommand prints a readable summary by default' do
|
|
108
|
+
output = run_cli('--root', root, '--resultset', 'coverage', 'totals')
|
|
109
|
+
expect(output).to include('│') # Table format
|
|
110
|
+
expect(output).to include('Lines')
|
|
111
|
+
# expect(output).to include('Average coverage:') # Not in table version
|
|
91
112
|
end
|
|
92
113
|
|
|
93
114
|
it 'can include source in JSON payload (nil if file missing)' do
|
|
94
|
-
output = run_cli('
|
|
95
|
-
'--source')
|
|
115
|
+
output = run_cli('--format', 'json', '--root', root, '--resultset', 'coverage',
|
|
116
|
+
'--source', 'full', 'summary', 'lib/foo.rb')
|
|
96
117
|
data = JSON.parse(output)
|
|
97
118
|
expect(data).to have_key('source')
|
|
98
119
|
end
|
|
@@ -108,8 +129,8 @@ RSpec.describe SimpleCovMcp::CoverageCLI do
|
|
|
108
129
|
m.call(error_handler: error_handler, log_target: log_target, mode: mode)
|
|
109
130
|
end
|
|
110
131
|
original_target = SimpleCovMcp.active_log_file
|
|
111
|
-
run_cli('
|
|
112
|
-
'--log-file', log_path)
|
|
132
|
+
run_cli('--format', 'json', '--root', root, '--resultset', 'coverage',
|
|
133
|
+
'--log-file', log_path, 'summary', 'lib/foo.rb')
|
|
113
134
|
expect(SimpleCovMcp.active_log_file).to eq(original_target)
|
|
114
135
|
end
|
|
115
136
|
end
|
|
@@ -122,86 +143,50 @@ RSpec.describe SimpleCovMcp::CoverageCLI do
|
|
|
122
143
|
m.call(error_handler: error_handler, log_target: log_target, mode: mode)
|
|
123
144
|
end
|
|
124
145
|
original_target = SimpleCovMcp.active_log_file
|
|
125
|
-
run_cli('
|
|
126
|
-
'--log-file', 'stdout')
|
|
146
|
+
run_cli('--format', 'json', '--root', root, '--resultset', 'coverage',
|
|
147
|
+
'--log-file', 'stdout', 'summary', 'lib/foo.rb')
|
|
127
148
|
expect(SimpleCovMcp.active_log_file).to eq(original_target)
|
|
128
149
|
end
|
|
129
150
|
end
|
|
130
151
|
|
|
131
|
-
describe '#load_success_predicate' do
|
|
132
|
-
let(:cli) { described_class.new }
|
|
133
152
|
|
|
134
|
-
def with_temp_predicate(content)
|
|
135
|
-
Tempfile.create(['predicate', '.rb']) do |file|
|
|
136
|
-
file.write(content)
|
|
137
|
-
file.flush
|
|
138
|
-
yield file.path
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
153
|
|
|
142
|
-
it 'loads a callable predicate from file' do
|
|
143
|
-
with_temp_predicate("->(model) { model }\n") do |path|
|
|
144
|
-
predicate = cli.send(:load_success_predicate, path)
|
|
145
|
-
expect(predicate).to respond_to(:call)
|
|
146
|
-
expect(predicate.call(:ok)).to eq(:ok)
|
|
147
|
-
end
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
it 'raises when file does not return callable' do
|
|
151
|
-
with_temp_predicate(":not_callable\n") do |path|
|
|
152
|
-
expect { cli.send(:load_success_predicate, path) }
|
|
153
|
-
.to raise_error(RuntimeError, include('Success predicate must be callable'))
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
154
|
|
|
157
|
-
it 'wraps syntax errors with friendly message' do
|
|
158
|
-
with_temp_predicate("->(model) {\n") do |path|
|
|
159
|
-
expect { cli.send(:load_success_predicate, path) }
|
|
160
|
-
.to raise_error(RuntimeError, include('Syntax error in success predicate file'))
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
describe '#extract_subcommand!' do
|
|
166
|
-
let(:cli) { described_class.new }
|
|
167
|
-
|
|
168
|
-
around do |example|
|
|
169
|
-
original = ENV['SIMPLECOV_MCP_OPTS']
|
|
170
|
-
example.run
|
|
171
|
-
ensure
|
|
172
|
-
ENV['SIMPLECOV_MCP_OPTS'] = original
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
it 'picks up subcommands that appear after env-provided options' do
|
|
176
|
-
ENV['SIMPLECOV_MCP_OPTS'] = '--resultset coverage'
|
|
177
|
-
argv = cli.send(:parse_env_opts) + ['summary', 'lib/foo.rb']
|
|
178
|
-
|
|
179
|
-
expect do
|
|
180
|
-
cli.send(:extract_subcommand!, argv)
|
|
181
|
-
end.to change { cli.instance_variable_get(:@cmd) }.from(nil).to('summary')
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
155
|
|
|
185
156
|
describe 'version command' do
|
|
186
157
|
it 'prints version as plain text by default' do
|
|
187
158
|
output = run_cli('version')
|
|
188
|
-
expect(output).to include('
|
|
159
|
+
expect(output).to include('│') # Table format
|
|
189
160
|
expect(output).to include(SimpleCovMcp::VERSION)
|
|
190
161
|
expect(output).not_to include('{')
|
|
191
162
|
expect(output).not_to include('}')
|
|
192
163
|
end
|
|
193
164
|
|
|
194
165
|
it 'prints version as JSON when --json flag is used' do
|
|
195
|
-
output = run_cli('
|
|
166
|
+
output = run_cli('--format', 'json', 'version')
|
|
196
167
|
data = JSON.parse(output)
|
|
197
168
|
expect(data).to have_key('version')
|
|
198
169
|
expect(data['version']).to eq(SimpleCovMcp::VERSION)
|
|
199
170
|
end
|
|
200
171
|
|
|
201
172
|
it 'works with version command and other flags' do
|
|
202
|
-
output = run_cli('
|
|
203
|
-
expect(output).to include('
|
|
173
|
+
output = run_cli('--root', root, 'version')
|
|
174
|
+
expect(output).to include('│') # Table format
|
|
175
|
+
expect(output).to include(SimpleCovMcp::VERSION)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
describe 'version option (-v)' do
|
|
180
|
+
it 'prints the same version info as the version subcommand' do
|
|
181
|
+
output = run_cli('-v')
|
|
182
|
+
expect(output).to include('│') # Table format
|
|
204
183
|
expect(output).to include(SimpleCovMcp::VERSION)
|
|
205
184
|
end
|
|
185
|
+
|
|
186
|
+
it 'respects --json when -v is used' do
|
|
187
|
+
output = run_cli('-v', '--format', 'json')
|
|
188
|
+
data = JSON.parse(output)
|
|
189
|
+
expect(data['version']).to eq(SimpleCovMcp::VERSION)
|
|
190
|
+
end
|
|
206
191
|
end
|
|
207
192
|
end
|
data/spec/cli_usage_spec.rb
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'spec_helper'
|
|
4
|
+
require 'tempfile'
|
|
4
5
|
|
|
5
6
|
RSpec.describe SimpleCovMcp::CoverageCLI do
|
|
6
7
|
let(:root) { (FIXTURES_DIR / 'project1').to_s }
|
|
7
8
|
|
|
8
9
|
it 'errors with usage when summary path is missing' do
|
|
9
|
-
_out, err, status = run_cli_with_status('
|
|
10
|
+
_out, err, status = run_cli_with_status('--root', root, '--resultset', 'coverage', 'summary')
|
|
10
11
|
expect(status).to eq(1)
|
|
11
12
|
expect(err).to include('Usage: simplecov-mcp summary <path>')
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
it 'errors with meaningful message for unknown subcommand' do
|
|
15
|
-
|
|
16
|
+
_out, err, status = run_cli_with_status('--root', root, '--resultset', 'coverage', 'bogus')
|
|
16
17
|
expect(status).to eq(1)
|
|
17
|
-
expect(err).to include("Unknown subcommand: 'bogus'")
|
|
18
|
-
expect(err).to include('Valid subcommands:')
|
|
18
|
+
expect(err).to include("Unknown subcommand: 'bogus'", 'Valid subcommands:')
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
it 'list honors stale=error and tracked_globs by exiting 1 when project is stale' do
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
_out, err, status = run_cli_with_status(
|
|
26
|
-
'--
|
|
22
|
+
Tempfile.create(['brand_new_file_for_cli_usage_spec', '.rb'], File.join(root, 'lib')) do |f|
|
|
23
|
+
f.write("# new file\n")
|
|
24
|
+
f.flush
|
|
25
|
+
_out, err, status = run_cli_with_status(
|
|
26
|
+
'--root', root, '--resultset', 'coverage', '--staleness', 'error', '--tracked-globs',
|
|
27
|
+
'lib/**/*.rb', 'list'
|
|
28
|
+
)
|
|
27
29
|
expect(status).to eq(1)
|
|
28
30
|
expect(err).to include('Coverage data stale (project)')
|
|
29
|
-
ensure
|
|
30
|
-
File.delete(tmp) if File.exist?(tmp)
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
it 'list with stale=off prints table and exits 0' do
|
|
35
|
-
out, err, status = run_cli_with_status(
|
|
36
|
-
'--
|
|
35
|
+
out, err, status = run_cli_with_status(
|
|
36
|
+
'--root', root, '--resultset', 'coverage', '--staleness', 'off', 'list'
|
|
37
|
+
)
|
|
37
38
|
expect(status).to eq(0)
|
|
38
39
|
expect(err).to eq('')
|
|
39
|
-
expect(out).to include('File')
|
|
40
|
-
expect(out).to include('lib/foo.rb')
|
|
40
|
+
expect(out).to include('File', 'lib/foo.rb')
|
|
41
41
|
end
|
|
42
42
|
end
|