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.
Files changed (281) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +230 -0
  3. data/CLAUDE.md +5 -0
  4. data/CODE_OF_CONDUCT.md +62 -0
  5. data/CONTRIBUTING.md +102 -0
  6. data/GEMINI.md +5 -0
  7. data/README.md +154 -51
  8. data/RELEASE_NOTES.md +452 -0
  9. data/dev/images/cov-loupe-icon-lores.png +0 -0
  10. data/dev/images/cov-loupe-icon-square.png +0 -0
  11. data/dev/images/cov-loupe-icon.png +0 -0
  12. data/dev/images/cov-loupe-logo.png +0 -0
  13. data/dev/prompts/README.md +74 -0
  14. data/dev/prompts/archive/architectural-review-and-actions-prompt.md +53 -0
  15. data/dev/prompts/archive/investigate-and-report-issues-prompt.md +33 -0
  16. data/dev/prompts/archive/produce-action-items-prompt.md +25 -0
  17. data/dev/prompts/guidelines/ai-code-evaluator-guidelines.md +337 -0
  18. data/dev/prompts/improve/refactor-test-suite.md +18 -0
  19. data/dev/prompts/improve/simplify-code-logic.md +133 -0
  20. data/dev/prompts/improve/update-documentation.md +21 -0
  21. data/dev/prompts/review/comprehensive-codebase-review.md +176 -0
  22. data/dev/prompts/review/identify-action-items.md +143 -0
  23. data/dev/prompts/review/verify-code-changes.md +54 -0
  24. data/dev/prompts/validate/create-screencast-outline.md +234 -0
  25. data/dev/prompts/validate/test-documentation-examples.md +180 -0
  26. data/docs/QUICKSTART.md +63 -0
  27. data/docs/assets/images/cov-loupe-logo-lores.png +0 -0
  28. data/docs/assets/images/cov-loupe-logo.png +0 -0
  29. data/docs/assets/images/favicon.png +0 -0
  30. data/docs/assets/stylesheets/branding.css +16 -0
  31. data/docs/assets/stylesheets/extra.css +15 -0
  32. data/docs/code_of_conduct.md +1 -0
  33. data/docs/contributing.md +1 -0
  34. data/docs/dev/ARCHITECTURE.md +56 -11
  35. data/docs/dev/DEVELOPMENT.md +116 -12
  36. data/docs/dev/FUTURE_ENHANCEMENTS.md +14 -0
  37. data/docs/dev/README.md +3 -2
  38. data/docs/dev/RELEASING.md +2 -0
  39. data/docs/dev/arch-decisions/README.md +10 -7
  40. data/docs/dev/arch-decisions/application-architecture.md +259 -0
  41. data/docs/dev/arch-decisions/coverage-data-quality.md +193 -0
  42. data/docs/dev/arch-decisions/output-character-mode.md +217 -0
  43. data/docs/dev/arch-decisions/path-resolution.md +90 -0
  44. data/docs/dev/arch-decisions/{004-x-arch-decision.md → policy-validation.md} +32 -28
  45. data/docs/dev/arch-decisions/{005-x-arch-decision.md → simplecov-integration.md} +47 -44
  46. data/docs/dev/presentations/cov-loupe-presentation.md +15 -13
  47. data/docs/examples/mcp-inputs.md +3 -0
  48. data/docs/examples/prompts.md +3 -0
  49. data/docs/examples/success_predicates.md +3 -0
  50. data/docs/fixtures/demo_project/.resultset.json +170 -0
  51. data/docs/fixtures/demo_project/README.md +6 -0
  52. data/docs/fixtures/demo_project/app/controllers/admin/audit_logs_controller.rb +19 -0
  53. data/docs/fixtures/demo_project/app/controllers/orders_controller.rb +26 -0
  54. data/docs/fixtures/demo_project/app/models/order.rb +20 -0
  55. data/docs/fixtures/demo_project/app/models/user.rb +19 -0
  56. data/docs/fixtures/demo_project/lib/api/client.rb +22 -0
  57. data/docs/fixtures/demo_project/lib/ops/jobs/cleanup_job.rb +16 -0
  58. data/docs/fixtures/demo_project/lib/ops/jobs/report_job.rb +17 -0
  59. data/docs/fixtures/demo_project/lib/payments/processor.rb +15 -0
  60. data/docs/fixtures/demo_project/lib/payments/refund_service.rb +15 -0
  61. data/docs/fixtures/demo_project/lib/payments/reporting/exporter.rb +16 -0
  62. data/docs/index.md +1 -0
  63. data/docs/license.md +3 -0
  64. data/docs/release_notes.md +3 -0
  65. data/docs/user/ADVANCED_USAGE.md +208 -115
  66. data/docs/user/CLI_FALLBACK_FOR_LLMS.md +2 -0
  67. data/docs/user/CLI_USAGE.md +276 -101
  68. data/docs/user/ERROR_HANDLING.md +4 -4
  69. data/docs/user/EXAMPLES.md +121 -128
  70. data/docs/user/INSTALLATION.md +9 -28
  71. data/docs/user/LIBRARY_API.md +227 -122
  72. data/docs/user/MCP_INTEGRATION.md +114 -203
  73. data/docs/user/README.md +5 -1
  74. data/docs/user/TROUBLESHOOTING.md +49 -27
  75. data/docs/user/installing-a-prelease-version-of-covloupe.md +43 -0
  76. data/docs/user/{V2-BREAKING-CHANGES.md → migrations/MIGRATING_TO_V2.md} +62 -72
  77. data/docs/user/migrations/MIGRATING_TO_V3.md +72 -0
  78. data/docs/user/migrations/MIGRATING_TO_V4.md +591 -0
  79. data/docs/user/migrations/README.md +22 -0
  80. data/docs/user/prompts/README.md +9 -0
  81. data/docs/user/prompts/non-web-coverage-analysis-prompt.md +103 -0
  82. data/docs/user/prompts/rails-coverage-analysis-prompt.md +94 -0
  83. data/docs/user/prompts/use-cli-not-mcp-prompt.md +53 -0
  84. data/examples/cli_demo.sh +77 -0
  85. data/examples/filter_and_table_demo-output.md +114 -0
  86. data/examples/filter_and_table_demo.rb +174 -0
  87. data/examples/fixtures/demo_project/coverage/.resultset.json +10 -0
  88. data/examples/mcp-inputs/README.md +66 -0
  89. data/examples/mcp-inputs/coverage_detailed.json +1 -0
  90. data/examples/mcp-inputs/coverage_raw.json +1 -0
  91. data/examples/mcp-inputs/coverage_summary.json +1 -0
  92. data/examples/mcp-inputs/list.json +1 -0
  93. data/examples/mcp-inputs/uncovered_lines.json +1 -0
  94. data/examples/prompts/README.md +27 -0
  95. data/examples/prompts/custom_resultset.txt +2 -0
  96. data/examples/prompts/detailed_with_source.txt +2 -0
  97. data/examples/prompts/list_lowest.txt +2 -0
  98. data/examples/prompts/summary.txt +2 -0
  99. data/examples/prompts/uncovered.txt +2 -0
  100. data/examples/success_predicates/README.md +198 -0
  101. data/examples/success_predicates/all_files_above_threshold_predicate.rb +21 -0
  102. data/examples/success_predicates/directory_specific_thresholds_predicate.rb +30 -0
  103. data/examples/success_predicates/project_coverage_minimum_predicate.rb +6 -0
  104. data/lib/cov_loupe/base_tool.rb +229 -20
  105. data/lib/cov_loupe/cli.rb +132 -23
  106. data/lib/cov_loupe/commands/base_command.rb +25 -6
  107. data/lib/cov_loupe/commands/command_factory.rb +0 -1
  108. data/lib/cov_loupe/commands/detailed_command.rb +10 -5
  109. data/lib/cov_loupe/commands/list_command.rb +2 -1
  110. data/lib/cov_loupe/commands/raw_command.rb +7 -5
  111. data/lib/cov_loupe/commands/summary_command.rb +12 -7
  112. data/lib/cov_loupe/commands/totals_command.rb +74 -10
  113. data/lib/cov_loupe/commands/uncovered_command.rb +7 -5
  114. data/lib/cov_loupe/commands/validate_command.rb +11 -3
  115. data/lib/cov_loupe/commands/version_command.rb +6 -4
  116. data/lib/cov_loupe/{app_config.rb → config/app_config.rb} +13 -5
  117. data/lib/cov_loupe/config/app_context.rb +43 -0
  118. data/lib/cov_loupe/config/boolean_type.rb +91 -0
  119. data/lib/cov_loupe/config/logger.rb +92 -0
  120. data/lib/cov_loupe/{option_normalizers.rb → config/option_normalizers.rb} +55 -24
  121. data/lib/cov_loupe/{option_parser_builder.rb → config/option_parser_builder.rb} +46 -24
  122. data/lib/cov_loupe/coverage/coverage_calculator.rb +53 -0
  123. data/lib/cov_loupe/coverage/coverage_reporter.rb +63 -0
  124. data/lib/cov_loupe/coverage/coverage_table_formatter.rb +133 -0
  125. data/lib/cov_loupe/{error_handler.rb → errors/error_handler.rb} +21 -33
  126. data/lib/cov_loupe/{errors.rb → errors/errors.rb} +48 -71
  127. data/lib/cov_loupe/formatters/formatters.rb +75 -0
  128. data/lib/cov_loupe/formatters/source_formatter.rb +18 -7
  129. data/lib/cov_loupe/formatters/table_formatter.rb +80 -0
  130. data/lib/cov_loupe/loaders/all.rb +15 -0
  131. data/lib/cov_loupe/loaders/all_cli.rb +10 -0
  132. data/lib/cov_loupe/loaders/all_mcp.rb +23 -0
  133. data/lib/cov_loupe/loaders/resultset_loader.rb +147 -0
  134. data/lib/cov_loupe/mcp_server.rb +3 -2
  135. data/lib/cov_loupe/model/model.rb +520 -0
  136. data/lib/cov_loupe/model/model_data.rb +13 -0
  137. data/lib/cov_loupe/model/model_data_cache.rb +116 -0
  138. data/lib/cov_loupe/option_parsers/env_options_parser.rb +17 -6
  139. data/lib/cov_loupe/option_parsers/error_helper.rb +16 -10
  140. data/lib/cov_loupe/output_chars.rb +192 -0
  141. data/lib/cov_loupe/paths/glob_utils.rb +100 -0
  142. data/lib/cov_loupe/{path_relativizer.rb → paths/path_relativizer.rb} +5 -13
  143. data/lib/cov_loupe/paths/path_utils.rb +265 -0
  144. data/lib/cov_loupe/paths/volume_case_sensitivity.rb +173 -0
  145. data/lib/cov_loupe/presenters/base_coverage_presenter.rb +9 -13
  146. data/lib/cov_loupe/presenters/coverage_payload_presenter.rb +21 -0
  147. data/lib/cov_loupe/presenters/payload_caching.rb +23 -0
  148. data/lib/cov_loupe/presenters/project_coverage_presenter.rb +73 -21
  149. data/lib/cov_loupe/presenters/project_totals_presenter.rb +16 -10
  150. data/lib/cov_loupe/repositories/coverage_repository.rb +149 -0
  151. data/lib/cov_loupe/resolvers/coverage_line_resolver.rb +90 -76
  152. data/lib/cov_loupe/resolvers/{resolver_factory.rb → resolver_helpers.rb} +6 -5
  153. data/lib/cov_loupe/resolvers/resultset_path_resolver.rb +40 -12
  154. data/lib/cov_loupe/scripts/command_execution.rb +113 -0
  155. data/lib/cov_loupe/scripts/latest_ci_status.rb +97 -0
  156. data/lib/cov_loupe/scripts/pre_release_check.rb +164 -0
  157. data/lib/cov_loupe/scripts/setup_doc_server.rb +23 -0
  158. data/lib/cov_loupe/scripts/start_doc_server.rb +24 -0
  159. data/lib/cov_loupe/staleness/stale_status.rb +23 -0
  160. data/lib/cov_loupe/staleness/staleness_checker.rb +328 -0
  161. data/lib/cov_loupe/staleness/staleness_message_formatter.rb +91 -0
  162. data/lib/cov_loupe/tools/coverage_detailed_tool.rb +14 -15
  163. data/lib/cov_loupe/tools/coverage_raw_tool.rb +14 -14
  164. data/lib/cov_loupe/tools/coverage_summary_tool.rb +16 -16
  165. data/lib/cov_loupe/tools/coverage_table_tool.rb +139 -21
  166. data/lib/cov_loupe/tools/coverage_totals_tool.rb +31 -13
  167. data/lib/cov_loupe/tools/help_tool.rb +16 -20
  168. data/lib/cov_loupe/tools/list_tool.rb +65 -0
  169. data/lib/cov_loupe/tools/uncovered_lines_tool.rb +14 -14
  170. data/lib/cov_loupe/tools/validate_tool.rb +18 -24
  171. data/lib/cov_loupe/tools/version_tool.rb +8 -3
  172. data/lib/cov_loupe/version.rb +1 -1
  173. data/lib/cov_loupe.rb +83 -55
  174. metadata +184 -154
  175. data/docs/dev/BRANCH_ONLY_COVERAGE.md +0 -158
  176. data/docs/dev/arch-decisions/001-x-arch-decision.md +0 -95
  177. data/docs/dev/arch-decisions/002-x-arch-decision.md +0 -159
  178. data/docs/dev/arch-decisions/003-x-arch-decision.md +0 -165
  179. data/lib/cov_loupe/app_context.rb +0 -26
  180. data/lib/cov_loupe/constants.rb +0 -22
  181. data/lib/cov_loupe/coverage_reporter.rb +0 -31
  182. data/lib/cov_loupe/formatters.rb +0 -51
  183. data/lib/cov_loupe/mode_detector.rb +0 -56
  184. data/lib/cov_loupe/model.rb +0 -339
  185. data/lib/cov_loupe/presenters/coverage_detailed_presenter.rb +0 -14
  186. data/lib/cov_loupe/presenters/coverage_raw_presenter.rb +0 -14
  187. data/lib/cov_loupe/presenters/coverage_summary_presenter.rb +0 -14
  188. data/lib/cov_loupe/presenters/coverage_uncovered_presenter.rb +0 -14
  189. data/lib/cov_loupe/resultset_loader.rb +0 -131
  190. data/lib/cov_loupe/staleness_checker.rb +0 -247
  191. data/lib/cov_loupe/table_formatter.rb +0 -64
  192. data/lib/cov_loupe/tools/all_files_coverage_tool.rb +0 -51
  193. data/lib/cov_loupe/util.rb +0 -88
  194. data/spec/MCP_INTEGRATION_TESTS_README.md +0 -111
  195. data/spec/TIMESTAMPS.md +0 -48
  196. data/spec/all_files_coverage_tool_spec.rb +0 -53
  197. data/spec/app_config_spec.rb +0 -142
  198. data/spec/base_tool_spec.rb +0 -62
  199. data/spec/cli/show_default_report_spec.rb +0 -33
  200. data/spec/cli_enumerated_options_spec.rb +0 -90
  201. data/spec/cli_error_spec.rb +0 -184
  202. data/spec/cli_format_spec.rb +0 -123
  203. data/spec/cli_json_options_spec.rb +0 -50
  204. data/spec/cli_source_spec.rb +0 -44
  205. data/spec/cli_spec.rb +0 -192
  206. data/spec/cli_table_spec.rb +0 -28
  207. data/spec/cli_usage_spec.rb +0 -42
  208. data/spec/commands/base_command_spec.rb +0 -107
  209. data/spec/commands/command_factory_spec.rb +0 -76
  210. data/spec/commands/detailed_command_spec.rb +0 -34
  211. data/spec/commands/list_command_spec.rb +0 -28
  212. data/spec/commands/raw_command_spec.rb +0 -69
  213. data/spec/commands/summary_command_spec.rb +0 -34
  214. data/spec/commands/totals_command_spec.rb +0 -34
  215. data/spec/commands/uncovered_command_spec.rb +0 -55
  216. data/spec/commands/validate_command_spec.rb +0 -213
  217. data/spec/commands/version_command_spec.rb +0 -38
  218. data/spec/constants_spec.rb +0 -61
  219. data/spec/cov_loupe/formatters/source_formatter_spec.rb +0 -267
  220. data/spec/cov_loupe/formatters_spec.rb +0 -76
  221. data/spec/cov_loupe/presenters/base_coverage_presenter_spec.rb +0 -79
  222. data/spec/cov_loupe_model_spec.rb +0 -454
  223. data/spec/cov_loupe_module_spec.rb +0 -37
  224. data/spec/cov_loupe_opts_spec.rb +0 -185
  225. data/spec/coverage_reporter_spec.rb +0 -102
  226. data/spec/coverage_table_tool_spec.rb +0 -59
  227. data/spec/coverage_totals_tool_spec.rb +0 -37
  228. data/spec/error_handler_spec.rb +0 -197
  229. data/spec/error_mode_spec.rb +0 -139
  230. data/spec/errors_edge_cases_spec.rb +0 -312
  231. data/spec/errors_stale_spec.rb +0 -83
  232. data/spec/file_based_mcp_tools_spec.rb +0 -99
  233. data/spec/help_tool_spec.rb +0 -26
  234. data/spec/integration_spec.rb +0 -789
  235. data/spec/logging_fallback_spec.rb +0 -128
  236. data/spec/mcp_logging_spec.rb +0 -44
  237. data/spec/mcp_server_integration_spec.rb +0 -23
  238. data/spec/mcp_server_spec.rb +0 -106
  239. data/spec/mode_detector_spec.rb +0 -153
  240. data/spec/model_error_handling_spec.rb +0 -269
  241. data/spec/model_staleness_spec.rb +0 -79
  242. data/spec/option_normalizers_spec.rb +0 -203
  243. data/spec/option_parsers/env_options_parser_spec.rb +0 -221
  244. data/spec/option_parsers/error_helper_spec.rb +0 -222
  245. data/spec/path_relativizer_spec.rb +0 -98
  246. data/spec/presenters/coverage_detailed_presenter_spec.rb +0 -19
  247. data/spec/presenters/coverage_raw_presenter_spec.rb +0 -15
  248. data/spec/presenters/coverage_summary_presenter_spec.rb +0 -15
  249. data/spec/presenters/coverage_uncovered_presenter_spec.rb +0 -16
  250. data/spec/presenters/project_coverage_presenter_spec.rb +0 -87
  251. data/spec/presenters/project_totals_presenter_spec.rb +0 -144
  252. data/spec/resolvers/coverage_line_resolver_spec.rb +0 -282
  253. data/spec/resolvers/resolver_factory_spec.rb +0 -61
  254. data/spec/resolvers/resultset_path_resolver_spec.rb +0 -60
  255. data/spec/resultset_loader_spec.rb +0 -167
  256. data/spec/shared_examples/README.md +0 -115
  257. data/spec/shared_examples/coverage_presenter_examples.rb +0 -66
  258. data/spec/shared_examples/file_based_mcp_tools.rb +0 -179
  259. data/spec/shared_examples/formatted_command_examples.rb +0 -64
  260. data/spec/shared_examples/mcp_tool_text_json_response.rb +0 -16
  261. data/spec/spec_helper.rb +0 -127
  262. data/spec/staleness_checker_spec.rb +0 -374
  263. data/spec/staleness_more_spec.rb +0 -42
  264. data/spec/support/cli_helpers.rb +0 -22
  265. data/spec/support/control_flow_helpers.rb +0 -20
  266. data/spec/support/fake_mcp.rb +0 -40
  267. data/spec/support/io_helpers.rb +0 -29
  268. data/spec/support/mcp_helpers.rb +0 -35
  269. data/spec/support/mcp_runner.rb +0 -66
  270. data/spec/support/mocking_helpers.rb +0 -30
  271. data/spec/table_format_spec.rb +0 -70
  272. data/spec/tools/validate_tool_spec.rb +0 -132
  273. data/spec/tools_error_handling_spec.rb +0 -130
  274. data/spec/util_spec.rb +0 -154
  275. data/spec/version_spec.rb +0 -123
  276. data/spec/version_tool_spec.rb +0 -141
  277. /data/{spec/fixtures/project1 → examples/fixtures/demo_project}/lib/bar.rb +0 -0
  278. /data/{spec/fixtures/project1 → examples/fixtures/demo_project}/lib/foo.rb +0 -0
  279. /data/lib/cov_loupe/{config_parser.rb → config/config_parser.rb} +0 -0
  280. /data/lib/cov_loupe/{predicate_evaluator.rb → config/predicate_evaluator.rb} +0 -0
  281. /data/lib/cov_loupe/{error_handler_factory.rb → errors/error_handler_factory.rb} +0 -0
@@ -1,44 +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 'renders uncovered source without error for fixture file' do
9
- out, err, status = run_cli_with_status(
10
- '--root', root, '--resultset', 'coverage', '--source', 'uncovered', '--context-lines', '1',
11
- '--no-color', 'uncovered', 'lib/foo.rb'
12
- )
13
- expect(status).to eq(0)
14
- expect(err).to eq('')
15
- expect(out).to match(/File:\s+lib\/foo\.rb/)
16
- expect(out).to include('│') # Table format
17
- expect(out).to show_source_table_or_fallback
18
- end
19
-
20
- it 'renders full source for uncovered command without brittle spacing' do
21
- out, err, status = run_cli_with_status(
22
- '--root', root, '--resultset', 'coverage', '--source', 'full', '--no-color',
23
- 'uncovered', 'lib/foo.rb'
24
- )
25
- expect(status).to eq(0)
26
- expect(err).to eq('')
27
- expect(out).to include('│') # Table format
28
- expect(out).to include('66.67%')
29
- expect(out).to show_source_table_or_fallback
30
- end
31
-
32
- it 'renders source for summary with uncovered mode without crashing' do
33
- out, err, status = run_cli_with_status(
34
- '--root', root, '--resultset', 'coverage', '--source', 'uncovered', '--context-lines', '1',
35
- '--no-color', 'summary', 'lib/foo.rb'
36
- )
37
- expect(status).to eq(0)
38
- expect(err).to eq('')
39
- expect(out).to include('lib/foo.rb')
40
- expect(out).to include('66.67%')
41
- expect(out).to include('│') # Table format
42
- expect(out).to show_source_table_or_fallback
43
- end
44
- end
data/spec/cli_spec.rb DELETED
@@ -1,192 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
- require 'tempfile'
5
-
6
- RSpec.describe CovLoupe::CoverageCLI do
7
- let(:root) { (FIXTURES_DIR / 'project1').to_s }
8
-
9
- def run_cli(*argv)
10
- cli = described_class.new
11
- silence_output do |out, _err|
12
- begin
13
- cli.run(argv.flatten)
14
- rescue SystemExit
15
- # Ignore exit, just capture output
16
- end
17
- return out.string
18
- end
19
- end
20
-
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
27
-
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
33
-
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
39
-
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
59
- end
60
-
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
65
- end
66
-
67
- it 'list subcommand with --json outputs JSON with sort order' do
68
- output = run_cli(
69
- '--format', 'json', '--root', root, '--resultset', 'coverage', '--sort-order', 'a', 'list'
70
- )
71
- asc = JSON.parse(output)
72
- expect(asc['files']).to be_an(Array)
73
- expect(asc['files'].first['file']).to end_with('lib/bar.rb')
74
-
75
- # Includes counts for total/ok/stale and they are consistent
76
- expect(asc['counts']).to include('total', 'ok', 'stale')
77
- total = asc['counts']['total']
78
- ok = asc['counts']['ok']
79
- stale = asc['counts']['stale']
80
- expect(total).to eq(asc['files'].length)
81
- expect(ok + stale).to eq(total)
82
-
83
- output = run_cli(
84
- '--format', 'json', '--root', root, '--resultset', 'coverage', '--sort-order', 'd', 'list'
85
- )
86
- desc = JSON.parse(output)
87
- expect(desc['files'].first['file']).to end_with('lib/foo.rb')
88
- end
89
-
90
- it 'list subcommand outputs formatted table' do
91
- output = run_cli('--root', root, '--resultset', 'coverage', 'list')
92
- expect(output).to include('File')
93
- expect(output).to include('lib/foo.rb')
94
- expect(output).to include('lib/bar.rb')
95
- expect(output).to match(/Files: total \d+/)
96
- end
97
-
98
- it 'list subcommand retains rows when using an absolute tracked glob' do
99
- absolute_glob = File.join(root, 'lib', '**', '*.rb')
100
- output = run_cli('--root', root, '--resultset', 'coverage', '--tracked-globs',
101
- absolute_glob, 'list')
102
- expect(output).not_to include('No coverage data found')
103
- expect(output).to include('lib/foo.rb')
104
- expect(output).to include('lib/bar.rb')
105
- end
106
-
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
112
- end
113
-
114
- it 'can include source in JSON payload (nil if file missing)' do
115
- output = run_cli('--format', 'json', '--root', root, '--resultset', 'coverage',
116
- '--source', 'full', 'summary', 'lib/foo.rb')
117
- data = JSON.parse(output)
118
- expect(data).to have_key('source')
119
- end
120
-
121
- describe 'log file configuration' do
122
- it 'passes --log-file path into the CLI execution context' do
123
- Dir.mktmpdir do |dir|
124
- log_path = File.join(dir, 'custom.log')
125
- expect(CovLoupe).to receive(:create_context)
126
- .and_wrap_original do |m, error_handler:, log_target:, mode:|
127
- # Ensure CLI forwards the requested log path into the context without changing other fields.
128
- expect(log_target).to eq(log_path)
129
- m.call(error_handler: error_handler, log_target: log_target, mode: mode)
130
- end
131
- original_target = CovLoupe.active_log_file
132
- run_cli('--format', 'json', '--root', root, '--resultset', 'coverage',
133
- '--log-file', log_path, 'summary', 'lib/foo.rb')
134
- expect(CovLoupe.active_log_file).to eq(original_target)
135
- end
136
- end
137
-
138
- it 'supports stdout logging within the CLI context' do
139
- expect(CovLoupe).to receive(:create_context)
140
- .and_wrap_original do |m, error_handler:, log_target:, mode:|
141
- # For stdout logging, verify the context is still constructed with the expected value.
142
- expect(log_target).to eq('stdout')
143
- m.call(error_handler: error_handler, log_target: log_target, mode: mode)
144
- end
145
- original_target = CovLoupe.active_log_file
146
- run_cli('--format', 'json', '--root', root, '--resultset', 'coverage',
147
- '--log-file', 'stdout', 'summary', 'lib/foo.rb')
148
- expect(CovLoupe.active_log_file).to eq(original_target)
149
- end
150
- end
151
-
152
-
153
-
154
-
155
-
156
- describe 'version command' do
157
- it 'prints version as plain text by default' do
158
- output = run_cli('version')
159
- expect(output).to include('│') # Table format
160
- expect(output).to include(CovLoupe::VERSION)
161
- expect(output).not_to include('{')
162
- expect(output).not_to include('}')
163
- end
164
-
165
- it 'prints version as JSON when --json flag is used' do
166
- output = run_cli('--format', 'json', 'version')
167
- data = JSON.parse(output)
168
- expect(data).to have_key('version')
169
- expect(data['version']).to eq(CovLoupe::VERSION)
170
- end
171
-
172
- it 'works with version command and other flags' do
173
- output = run_cli('--root', root, 'version')
174
- expect(output).to include('│') # Table format
175
- expect(output).to include(CovLoupe::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
183
- expect(output).to include(CovLoupe::VERSION)
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(CovLoupe::VERSION)
190
- end
191
- end
192
- end
@@ -1,28 +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
- def run_cli(*argv)
9
- cli = described_class.new
10
- silence_output do |out, _err|
11
- cli.run(argv.flatten)
12
- return out.string
13
- end
14
- end
15
-
16
- it 'prints default table when no subcommand is given' do
17
- output = run_cli('--root', root, '--resultset', 'coverage')
18
-
19
- # Contains a header row and at least one data row with expected columns
20
- expect(output).to include('File')
21
- expect(output).to include('Covered')
22
- expect(output).to include('Total')
23
-
24
- # Should list fixture files from the demo project
25
- expect(output).to include('lib/foo.rb')
26
- expect(output).to include('lib/bar.rb')
27
- end
28
- end
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
- require 'tempfile'
5
-
6
- RSpec.describe CovLoupe::CoverageCLI do
7
- let(:root) { (FIXTURES_DIR / 'project1').to_s }
8
-
9
- it 'errors with usage when summary path is missing' do
10
- _out, err, status = run_cli_with_status('--root', root, '--resultset', 'coverage', 'summary')
11
- expect(status).to eq(1)
12
- expect(err).to include('Usage: cov-loupe summary <path>')
13
- end
14
-
15
- it 'errors with meaningful message for unknown subcommand' do
16
- _out, err, status = run_cli_with_status('--root', root, '--resultset', 'coverage', 'bogus')
17
- expect(status).to eq(1)
18
- expect(err).to include("Unknown subcommand: 'bogus'", 'Valid subcommands:')
19
- end
20
-
21
- it 'list honors stale=error and tracked_globs by exiting 1 when project is stale' do
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
- )
29
- expect(status).to eq(1)
30
- expect(err).to include('Coverage data stale (project)')
31
- end
32
- end
33
-
34
- it 'list with stale=off prints table and exits 0' do
35
- out, err, status = run_cli_with_status(
36
- '--root', root, '--resultset', 'coverage', '--staleness', 'off', 'list'
37
- )
38
- expect(status).to eq(0)
39
- expect(err).to eq('')
40
- expect(out).to include('File', 'lib/foo.rb')
41
- end
42
- end
@@ -1,107 +0,0 @@
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
@@ -1,76 +0,0 @@
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
@@ -1,34 +0,0 @@
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
@@ -1,28 +0,0 @@
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
@@ -1,69 +0,0 @@
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
@@ -1,34 +0,0 @@
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