cov-loupe 3.0.0 → 4.0.0.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/AGENTS.md +230 -0
- data/CLAUDE.md +5 -0
- data/CODE_OF_CONDUCT.md +62 -0
- data/CONTRIBUTING.md +102 -0
- data/GEMINI.md +5 -0
- data/README.md +154 -51
- data/RELEASE_NOTES.md +452 -0
- data/dev/images/cov-loupe-icon-lores.png +0 -0
- data/dev/images/cov-loupe-icon-square.png +0 -0
- data/dev/images/cov-loupe-icon.png +0 -0
- data/dev/images/cov-loupe-logo.png +0 -0
- data/dev/prompts/README.md +74 -0
- data/dev/prompts/archive/architectural-review-and-actions-prompt.md +53 -0
- data/dev/prompts/archive/investigate-and-report-issues-prompt.md +33 -0
- data/dev/prompts/archive/produce-action-items-prompt.md +25 -0
- data/dev/prompts/guidelines/ai-code-evaluator-guidelines.md +337 -0
- data/dev/prompts/improve/refactor-test-suite.md +18 -0
- data/dev/prompts/improve/simplify-code-logic.md +133 -0
- data/dev/prompts/improve/update-documentation.md +21 -0
- data/dev/prompts/review/comprehensive-codebase-review.md +176 -0
- data/dev/prompts/review/identify-action-items.md +143 -0
- data/dev/prompts/review/verify-code-changes.md +54 -0
- data/dev/prompts/validate/create-screencast-outline.md +234 -0
- data/dev/prompts/validate/test-documentation-examples.md +180 -0
- data/docs/QUICKSTART.md +63 -0
- data/docs/assets/images/cov-loupe-logo-lores.png +0 -0
- data/docs/assets/images/cov-loupe-logo.png +0 -0
- data/docs/assets/images/favicon.png +0 -0
- data/docs/assets/stylesheets/branding.css +16 -0
- data/docs/assets/stylesheets/extra.css +15 -0
- data/docs/code_of_conduct.md +1 -0
- data/docs/contributing.md +1 -0
- data/docs/dev/ARCHITECTURE.md +56 -11
- data/docs/dev/DEVELOPMENT.md +116 -12
- data/docs/dev/FUTURE_ENHANCEMENTS.md +14 -0
- data/docs/dev/README.md +3 -2
- data/docs/dev/RELEASING.md +2 -0
- data/docs/dev/arch-decisions/README.md +10 -7
- data/docs/dev/arch-decisions/application-architecture.md +259 -0
- data/docs/dev/arch-decisions/coverage-data-quality.md +193 -0
- data/docs/dev/arch-decisions/output-character-mode.md +217 -0
- data/docs/dev/arch-decisions/path-resolution.md +90 -0
- data/docs/dev/arch-decisions/{004-x-arch-decision.md → policy-validation.md} +32 -28
- data/docs/dev/arch-decisions/{005-x-arch-decision.md → simplecov-integration.md} +47 -44
- data/docs/dev/presentations/cov-loupe-presentation.md +15 -13
- data/docs/examples/mcp-inputs.md +3 -0
- data/docs/examples/prompts.md +3 -0
- data/docs/examples/success_predicates.md +3 -0
- data/docs/fixtures/demo_project/.resultset.json +170 -0
- data/docs/fixtures/demo_project/README.md +6 -0
- data/docs/fixtures/demo_project/app/controllers/admin/audit_logs_controller.rb +19 -0
- data/docs/fixtures/demo_project/app/controllers/orders_controller.rb +26 -0
- data/docs/fixtures/demo_project/app/models/order.rb +20 -0
- data/docs/fixtures/demo_project/app/models/user.rb +19 -0
- data/docs/fixtures/demo_project/lib/api/client.rb +22 -0
- data/docs/fixtures/demo_project/lib/ops/jobs/cleanup_job.rb +16 -0
- data/docs/fixtures/demo_project/lib/ops/jobs/report_job.rb +17 -0
- data/docs/fixtures/demo_project/lib/payments/processor.rb +15 -0
- data/docs/fixtures/demo_project/lib/payments/refund_service.rb +15 -0
- data/docs/fixtures/demo_project/lib/payments/reporting/exporter.rb +16 -0
- data/docs/index.md +1 -0
- data/docs/license.md +3 -0
- data/docs/release_notes.md +3 -0
- data/docs/user/ADVANCED_USAGE.md +208 -115
- data/docs/user/CLI_FALLBACK_FOR_LLMS.md +2 -0
- data/docs/user/CLI_USAGE.md +276 -101
- data/docs/user/ERROR_HANDLING.md +4 -4
- data/docs/user/EXAMPLES.md +121 -128
- data/docs/user/INSTALLATION.md +9 -28
- data/docs/user/LIBRARY_API.md +227 -122
- data/docs/user/MCP_INTEGRATION.md +114 -203
- data/docs/user/README.md +5 -1
- data/docs/user/TROUBLESHOOTING.md +49 -27
- data/docs/user/installing-a-prelease-version-of-covloupe.md +43 -0
- data/docs/user/{V2-BREAKING-CHANGES.md → migrations/MIGRATING_TO_V2.md} +62 -72
- data/docs/user/migrations/MIGRATING_TO_V3.md +72 -0
- data/docs/user/migrations/MIGRATING_TO_V4.md +591 -0
- data/docs/user/migrations/README.md +22 -0
- data/docs/user/prompts/README.md +9 -0
- data/docs/user/prompts/non-web-coverage-analysis-prompt.md +103 -0
- data/docs/user/prompts/rails-coverage-analysis-prompt.md +94 -0
- data/docs/user/prompts/use-cli-not-mcp-prompt.md +53 -0
- data/examples/cli_demo.sh +77 -0
- data/examples/filter_and_table_demo-output.md +114 -0
- data/examples/filter_and_table_demo.rb +174 -0
- data/examples/fixtures/demo_project/coverage/.resultset.json +10 -0
- data/examples/mcp-inputs/README.md +66 -0
- data/examples/mcp-inputs/coverage_detailed.json +1 -0
- data/examples/mcp-inputs/coverage_raw.json +1 -0
- data/examples/mcp-inputs/coverage_summary.json +1 -0
- data/examples/mcp-inputs/list.json +1 -0
- data/examples/mcp-inputs/uncovered_lines.json +1 -0
- data/examples/prompts/README.md +27 -0
- data/examples/prompts/custom_resultset.txt +2 -0
- data/examples/prompts/detailed_with_source.txt +2 -0
- data/examples/prompts/list_lowest.txt +2 -0
- data/examples/prompts/summary.txt +2 -0
- data/examples/prompts/uncovered.txt +2 -0
- data/examples/success_predicates/README.md +198 -0
- data/examples/success_predicates/all_files_above_threshold_predicate.rb +21 -0
- data/examples/success_predicates/directory_specific_thresholds_predicate.rb +30 -0
- data/examples/success_predicates/project_coverage_minimum_predicate.rb +6 -0
- data/lib/cov_loupe/base_tool.rb +229 -20
- data/lib/cov_loupe/cli.rb +132 -23
- data/lib/cov_loupe/commands/base_command.rb +25 -6
- data/lib/cov_loupe/commands/command_factory.rb +0 -1
- data/lib/cov_loupe/commands/detailed_command.rb +10 -5
- data/lib/cov_loupe/commands/list_command.rb +2 -1
- data/lib/cov_loupe/commands/raw_command.rb +7 -5
- data/lib/cov_loupe/commands/summary_command.rb +12 -7
- data/lib/cov_loupe/commands/totals_command.rb +74 -10
- data/lib/cov_loupe/commands/uncovered_command.rb +7 -5
- data/lib/cov_loupe/commands/validate_command.rb +11 -3
- data/lib/cov_loupe/commands/version_command.rb +6 -4
- data/lib/cov_loupe/{app_config.rb → config/app_config.rb} +13 -5
- data/lib/cov_loupe/config/app_context.rb +43 -0
- data/lib/cov_loupe/config/boolean_type.rb +91 -0
- data/lib/cov_loupe/config/logger.rb +92 -0
- data/lib/cov_loupe/{option_normalizers.rb → config/option_normalizers.rb} +55 -24
- data/lib/cov_loupe/{option_parser_builder.rb → config/option_parser_builder.rb} +46 -24
- data/lib/cov_loupe/coverage/coverage_calculator.rb +53 -0
- data/lib/cov_loupe/coverage/coverage_reporter.rb +63 -0
- data/lib/cov_loupe/coverage/coverage_table_formatter.rb +133 -0
- data/lib/cov_loupe/{error_handler.rb → errors/error_handler.rb} +21 -33
- data/lib/cov_loupe/{errors.rb → errors/errors.rb} +48 -71
- data/lib/cov_loupe/formatters/formatters.rb +75 -0
- data/lib/cov_loupe/formatters/source_formatter.rb +18 -7
- data/lib/cov_loupe/formatters/table_formatter.rb +80 -0
- data/lib/cov_loupe/loaders/all.rb +15 -0
- data/lib/cov_loupe/loaders/all_cli.rb +10 -0
- data/lib/cov_loupe/loaders/all_mcp.rb +23 -0
- data/lib/cov_loupe/loaders/resultset_loader.rb +147 -0
- data/lib/cov_loupe/mcp_server.rb +3 -2
- data/lib/cov_loupe/model/model.rb +520 -0
- data/lib/cov_loupe/model/model_data.rb +13 -0
- data/lib/cov_loupe/model/model_data_cache.rb +116 -0
- data/lib/cov_loupe/option_parsers/env_options_parser.rb +17 -6
- data/lib/cov_loupe/option_parsers/error_helper.rb +16 -10
- data/lib/cov_loupe/output_chars.rb +192 -0
- data/lib/cov_loupe/paths/glob_utils.rb +100 -0
- data/lib/cov_loupe/{path_relativizer.rb → paths/path_relativizer.rb} +5 -13
- data/lib/cov_loupe/paths/path_utils.rb +265 -0
- data/lib/cov_loupe/paths/volume_case_sensitivity.rb +173 -0
- data/lib/cov_loupe/presenters/base_coverage_presenter.rb +9 -13
- data/lib/cov_loupe/presenters/coverage_payload_presenter.rb +21 -0
- data/lib/cov_loupe/presenters/payload_caching.rb +23 -0
- data/lib/cov_loupe/presenters/project_coverage_presenter.rb +73 -21
- data/lib/cov_loupe/presenters/project_totals_presenter.rb +16 -10
- data/lib/cov_loupe/repositories/coverage_repository.rb +149 -0
- data/lib/cov_loupe/resolvers/coverage_line_resolver.rb +90 -76
- data/lib/cov_loupe/resolvers/{resolver_factory.rb → resolver_helpers.rb} +6 -5
- data/lib/cov_loupe/resolvers/resultset_path_resolver.rb +40 -12
- data/lib/cov_loupe/scripts/command_execution.rb +113 -0
- data/lib/cov_loupe/scripts/latest_ci_status.rb +97 -0
- data/lib/cov_loupe/scripts/pre_release_check.rb +164 -0
- data/lib/cov_loupe/scripts/setup_doc_server.rb +23 -0
- data/lib/cov_loupe/scripts/start_doc_server.rb +24 -0
- data/lib/cov_loupe/staleness/stale_status.rb +23 -0
- data/lib/cov_loupe/staleness/staleness_checker.rb +328 -0
- data/lib/cov_loupe/staleness/staleness_message_formatter.rb +91 -0
- data/lib/cov_loupe/tools/coverage_detailed_tool.rb +14 -15
- data/lib/cov_loupe/tools/coverage_raw_tool.rb +14 -14
- data/lib/cov_loupe/tools/coverage_summary_tool.rb +16 -16
- data/lib/cov_loupe/tools/coverage_table_tool.rb +139 -21
- data/lib/cov_loupe/tools/coverage_totals_tool.rb +31 -13
- data/lib/cov_loupe/tools/help_tool.rb +16 -20
- data/lib/cov_loupe/tools/list_tool.rb +65 -0
- data/lib/cov_loupe/tools/uncovered_lines_tool.rb +14 -14
- data/lib/cov_loupe/tools/validate_tool.rb +18 -24
- data/lib/cov_loupe/tools/version_tool.rb +8 -3
- data/lib/cov_loupe/version.rb +1 -1
- data/lib/cov_loupe.rb +83 -55
- metadata +184 -154
- data/docs/dev/BRANCH_ONLY_COVERAGE.md +0 -158
- data/docs/dev/arch-decisions/001-x-arch-decision.md +0 -95
- data/docs/dev/arch-decisions/002-x-arch-decision.md +0 -159
- data/docs/dev/arch-decisions/003-x-arch-decision.md +0 -165
- data/lib/cov_loupe/app_context.rb +0 -26
- data/lib/cov_loupe/constants.rb +0 -22
- data/lib/cov_loupe/coverage_reporter.rb +0 -31
- data/lib/cov_loupe/formatters.rb +0 -51
- data/lib/cov_loupe/mode_detector.rb +0 -56
- data/lib/cov_loupe/model.rb +0 -339
- data/lib/cov_loupe/presenters/coverage_detailed_presenter.rb +0 -14
- data/lib/cov_loupe/presenters/coverage_raw_presenter.rb +0 -14
- data/lib/cov_loupe/presenters/coverage_summary_presenter.rb +0 -14
- data/lib/cov_loupe/presenters/coverage_uncovered_presenter.rb +0 -14
- data/lib/cov_loupe/resultset_loader.rb +0 -131
- data/lib/cov_loupe/staleness_checker.rb +0 -247
- data/lib/cov_loupe/table_formatter.rb +0 -64
- data/lib/cov_loupe/tools/all_files_coverage_tool.rb +0 -51
- data/lib/cov_loupe/util.rb +0 -88
- data/spec/MCP_INTEGRATION_TESTS_README.md +0 -111
- data/spec/TIMESTAMPS.md +0 -48
- data/spec/all_files_coverage_tool_spec.rb +0 -53
- data/spec/app_config_spec.rb +0 -142
- data/spec/base_tool_spec.rb +0 -62
- data/spec/cli/show_default_report_spec.rb +0 -33
- data/spec/cli_enumerated_options_spec.rb +0 -90
- data/spec/cli_error_spec.rb +0 -184
- data/spec/cli_format_spec.rb +0 -123
- data/spec/cli_json_options_spec.rb +0 -50
- data/spec/cli_source_spec.rb +0 -44
- data/spec/cli_spec.rb +0 -192
- data/spec/cli_table_spec.rb +0 -28
- data/spec/cli_usage_spec.rb +0 -42
- data/spec/commands/base_command_spec.rb +0 -107
- data/spec/commands/command_factory_spec.rb +0 -76
- data/spec/commands/detailed_command_spec.rb +0 -34
- data/spec/commands/list_command_spec.rb +0 -28
- data/spec/commands/raw_command_spec.rb +0 -69
- data/spec/commands/summary_command_spec.rb +0 -34
- data/spec/commands/totals_command_spec.rb +0 -34
- data/spec/commands/uncovered_command_spec.rb +0 -55
- data/spec/commands/validate_command_spec.rb +0 -213
- data/spec/commands/version_command_spec.rb +0 -38
- data/spec/constants_spec.rb +0 -61
- data/spec/cov_loupe/formatters/source_formatter_spec.rb +0 -267
- data/spec/cov_loupe/formatters_spec.rb +0 -76
- data/spec/cov_loupe/presenters/base_coverage_presenter_spec.rb +0 -79
- data/spec/cov_loupe_model_spec.rb +0 -454
- data/spec/cov_loupe_module_spec.rb +0 -37
- data/spec/cov_loupe_opts_spec.rb +0 -185
- data/spec/coverage_reporter_spec.rb +0 -102
- data/spec/coverage_table_tool_spec.rb +0 -59
- data/spec/coverage_totals_tool_spec.rb +0 -37
- data/spec/error_handler_spec.rb +0 -197
- data/spec/error_mode_spec.rb +0 -139
- data/spec/errors_edge_cases_spec.rb +0 -312
- data/spec/errors_stale_spec.rb +0 -83
- data/spec/file_based_mcp_tools_spec.rb +0 -99
- data/spec/help_tool_spec.rb +0 -26
- data/spec/integration_spec.rb +0 -789
- data/spec/logging_fallback_spec.rb +0 -128
- data/spec/mcp_logging_spec.rb +0 -44
- data/spec/mcp_server_integration_spec.rb +0 -23
- data/spec/mcp_server_spec.rb +0 -106
- data/spec/mode_detector_spec.rb +0 -153
- data/spec/model_error_handling_spec.rb +0 -269
- data/spec/model_staleness_spec.rb +0 -79
- data/spec/option_normalizers_spec.rb +0 -203
- data/spec/option_parsers/env_options_parser_spec.rb +0 -221
- data/spec/option_parsers/error_helper_spec.rb +0 -222
- data/spec/path_relativizer_spec.rb +0 -98
- data/spec/presenters/coverage_detailed_presenter_spec.rb +0 -19
- data/spec/presenters/coverage_raw_presenter_spec.rb +0 -15
- data/spec/presenters/coverage_summary_presenter_spec.rb +0 -15
- data/spec/presenters/coverage_uncovered_presenter_spec.rb +0 -16
- data/spec/presenters/project_coverage_presenter_spec.rb +0 -87
- data/spec/presenters/project_totals_presenter_spec.rb +0 -144
- data/spec/resolvers/coverage_line_resolver_spec.rb +0 -282
- data/spec/resolvers/resolver_factory_spec.rb +0 -61
- data/spec/resolvers/resultset_path_resolver_spec.rb +0 -60
- data/spec/resultset_loader_spec.rb +0 -167
- data/spec/shared_examples/README.md +0 -115
- data/spec/shared_examples/coverage_presenter_examples.rb +0 -66
- data/spec/shared_examples/file_based_mcp_tools.rb +0 -179
- data/spec/shared_examples/formatted_command_examples.rb +0 -64
- data/spec/shared_examples/mcp_tool_text_json_response.rb +0 -16
- data/spec/spec_helper.rb +0 -127
- data/spec/staleness_checker_spec.rb +0 -374
- data/spec/staleness_more_spec.rb +0 -42
- data/spec/support/cli_helpers.rb +0 -22
- data/spec/support/control_flow_helpers.rb +0 -20
- data/spec/support/fake_mcp.rb +0 -40
- data/spec/support/io_helpers.rb +0 -29
- data/spec/support/mcp_helpers.rb +0 -35
- data/spec/support/mcp_runner.rb +0 -66
- data/spec/support/mocking_helpers.rb +0 -30
- data/spec/table_format_spec.rb +0 -70
- data/spec/tools/validate_tool_spec.rb +0 -132
- data/spec/tools_error_handling_spec.rb +0 -130
- data/spec/util_spec.rb +0 -154
- data/spec/version_spec.rb +0 -123
- data/spec/version_tool_spec.rb +0 -141
- /data/{spec/fixtures/project1 → examples/fixtures/demo_project}/lib/bar.rb +0 -0
- /data/{spec/fixtures/project1 → examples/fixtures/demo_project}/lib/foo.rb +0 -0
- /data/lib/cov_loupe/{config_parser.rb → config/config_parser.rb} +0 -0
- /data/lib/cov_loupe/{predicate_evaluator.rb → config/predicate_evaluator.rb} +0 -0
- /data/lib/cov_loupe/{error_handler_factory.rb → errors/error_handler_factory.rb} +0 -0
data/docs/user/LIBRARY_API.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Library API Guide
|
|
2
2
|
|
|
3
|
-
[Back to main README](../
|
|
3
|
+
[Back to main README](../index.md)
|
|
4
4
|
|
|
5
5
|
Use this gem programmatically to inspect coverage without running the CLI or MCP server. The primary entry point is `CovLoupe::CoverageModel`.
|
|
6
6
|
|
|
@@ -11,7 +11,6 @@ Use this gem programmatically to inspect coverage without running the CLI or MCP
|
|
|
11
11
|
- [Return Types](#return-types)
|
|
12
12
|
- [Error Handling](#error-handling)
|
|
13
13
|
- [Advanced Recipes](#advanced-recipes)
|
|
14
|
-
- [API Stability](#api-stability)
|
|
15
14
|
|
|
16
15
|
## Quick Start
|
|
17
16
|
|
|
@@ -21,45 +20,54 @@ require "cov_loupe"
|
|
|
21
20
|
# Defaults (omit args; shown here with comments):
|
|
22
21
|
# - root: "."
|
|
23
22
|
# - resultset: resolved from common paths under root
|
|
24
|
-
# -
|
|
25
|
-
# - tracked_globs:
|
|
23
|
+
# - raise_on_stale: false (don't raise on stale data)
|
|
24
|
+
# - tracked_globs: [] (no project-level file-set checks)
|
|
26
25
|
model = CovLoupe::CoverageModel.new
|
|
27
26
|
|
|
28
27
|
# Custom configuration (non-default values):
|
|
29
28
|
model = CovLoupe::CoverageModel.new(
|
|
30
|
-
root:
|
|
31
|
-
resultset: "build/coverage",
|
|
32
|
-
|
|
33
|
-
tracked_globs: ["lib/**/*.rb"]
|
|
29
|
+
root: File.join(Dir.home, 'project'), # non-default project root
|
|
30
|
+
resultset: "build/coverage", # file or directory containing .resultset.json
|
|
31
|
+
raise_on_stale: true, # enable strict staleness checks (raise on stale)
|
|
32
|
+
tracked_globs: ["lib/cov_loupe/tools/**/*.rb"] # for 'list' staleness: flag new/missing files
|
|
34
33
|
)
|
|
35
34
|
|
|
36
35
|
# List all files with coverage summary
|
|
37
|
-
|
|
36
|
+
list_result = model.list
|
|
37
|
+
files = list_result['files']
|
|
38
38
|
# Per-file queries
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
|
|
40
|
+
target = 'lib/cov_loupe/base_tool.rb'
|
|
41
|
+
summary = model.summary_for(target)
|
|
42
|
+
uncovered = model.uncovered_for(target)
|
|
43
|
+
detailed = model.detailed_for(target)
|
|
44
|
+
raw = model.raw_for(target)
|
|
43
45
|
```
|
|
44
46
|
|
|
45
47
|
## Method Reference
|
|
46
48
|
|
|
47
|
-
### `
|
|
49
|
+
### `list(sort_order: :descending, raise_on_stale: nil, tracked_globs: [])`
|
|
48
50
|
|
|
49
51
|
Returns coverage summary for all files in the resultset.
|
|
50
52
|
|
|
51
53
|
**Parameters:**
|
|
52
54
|
- `sort_order` (Symbol, optional): `:descending` (default) or `:ascending` by coverage percentage
|
|
55
|
+
- `raise_on_stale` (Boolean, optional): Whether to raise error if project is stale. Defaults to model setting.
|
|
56
|
+
- `tracked_globs` (Array<String>, optional): Patterns to filter files (also used for staleness checks)
|
|
53
57
|
|
|
54
|
-
**Returns:** `
|
|
58
|
+
**Returns:** `Hash` - See [list return type](#list)
|
|
55
59
|
|
|
56
60
|
**Example:**
|
|
57
61
|
```ruby
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
list_result = model.list
|
|
63
|
+
files = list_result['files']
|
|
64
|
+
# => [ { 'file' => '/abs/path/lib/foo.rb', 'covered' => 12, 'total' => 14, 'percentage' => 85.71, 'stale' => "ok" }, ... ]
|
|
60
65
|
|
|
61
66
|
# Get worst coverage first
|
|
62
|
-
worst_files = model.
|
|
67
|
+
worst_files = model.list(sort_order: :ascending)['files'].first(10)
|
|
68
|
+
|
|
69
|
+
# Force staleness check
|
|
70
|
+
model.list(raise_on_stale: true)
|
|
63
71
|
```
|
|
64
72
|
|
|
65
73
|
### `summary_for(path)`
|
|
@@ -67,7 +75,7 @@ worst_files = model.all_files(sort_order: :ascending).first(10)
|
|
|
67
75
|
Returns coverage summary for a specific file.
|
|
68
76
|
|
|
69
77
|
**Parameters:**
|
|
70
|
-
- `path` (String): File path (absolute
|
|
78
|
+
- `path` (String): File path (absolute or relative to root)
|
|
71
79
|
|
|
72
80
|
**Returns:** `Hash` - See [summary_for return type](#summary_for)
|
|
73
81
|
|
|
@@ -75,8 +83,8 @@ Returns coverage summary for a specific file.
|
|
|
75
83
|
|
|
76
84
|
**Example:**
|
|
77
85
|
```ruby
|
|
78
|
-
summary = model.summary_for(
|
|
79
|
-
# => { 'file' => '/abs/.../lib/foo.rb', 'summary' => {'covered'=>12, 'total'=>14, 'percentage'=>85.71}
|
|
86
|
+
summary = model.summary_for(target)
|
|
87
|
+
# => { 'file' => '/abs/.../lib/foo.rb', 'summary' => {'covered'=>12, 'total'=>14, 'percentage'=>85.71} }
|
|
80
88
|
```
|
|
81
89
|
|
|
82
90
|
### `uncovered_for(path)`
|
|
@@ -84,7 +92,7 @@ summary = model.summary_for("lib/foo.rb")
|
|
|
84
92
|
Returns list of uncovered line numbers for a specific file.
|
|
85
93
|
|
|
86
94
|
**Parameters:**
|
|
87
|
-
- `path` (String): File path (absolute
|
|
95
|
+
- `path` (String): File path (absolute or relative to root)
|
|
88
96
|
|
|
89
97
|
**Returns:** `Hash` - See [uncovered_for return type](#uncovered_for)
|
|
90
98
|
|
|
@@ -93,7 +101,7 @@ Returns list of uncovered line numbers for a specific file.
|
|
|
93
101
|
**Example:**
|
|
94
102
|
```ruby
|
|
95
103
|
uncovered = model.uncovered_for("lib/foo.rb")
|
|
96
|
-
# => { 'file' => '/abs/.../lib/foo.rb', 'uncovered' => [5, 9, 12], 'summary' => { ... }
|
|
104
|
+
# => { 'file' => '/abs/.../lib/foo.rb', 'uncovered' => [5, 9, 12], 'summary' => { ... } }
|
|
97
105
|
```
|
|
98
106
|
|
|
99
107
|
### `detailed_for(path)`
|
|
@@ -101,7 +109,7 @@ uncovered = model.uncovered_for("lib/foo.rb")
|
|
|
101
109
|
Returns per-line coverage details with hit counts.
|
|
102
110
|
|
|
103
111
|
**Parameters:**
|
|
104
|
-
- `path` (String): File path (absolute
|
|
112
|
+
- `path` (String): File path (absolute or relative to root)
|
|
105
113
|
|
|
106
114
|
**Returns:** `Hash` - See [detailed_for return type](#detailed_for)
|
|
107
115
|
|
|
@@ -110,7 +118,7 @@ Returns per-line coverage details with hit counts.
|
|
|
110
118
|
**Example:**
|
|
111
119
|
```ruby
|
|
112
120
|
detailed = model.detailed_for("lib/foo.rb")
|
|
113
|
-
# => { 'file' => '/abs/.../lib/foo.rb', 'lines' => [{'line' => 1, 'hits' => 1, 'covered' => true}, ...], 'summary' => { ... }
|
|
121
|
+
# => { 'file' => '/abs/.../lib/foo.rb', 'lines' => [{'line' => 1, 'hits' => 1, 'covered' => true}, ...], 'summary' => { ... } }
|
|
114
122
|
```
|
|
115
123
|
|
|
116
124
|
### `raw_for(path)`
|
|
@@ -118,7 +126,7 @@ detailed = model.detailed_for("lib/foo.rb")
|
|
|
118
126
|
Returns raw SimpleCov lines array for a specific file.
|
|
119
127
|
|
|
120
128
|
**Parameters:**
|
|
121
|
-
- `path` (String): File path (absolute
|
|
129
|
+
- `path` (String): File path (absolute or relative to root)
|
|
122
130
|
|
|
123
131
|
**Returns:** `Hash` - See [raw_for return type](#raw_for)
|
|
124
132
|
|
|
@@ -127,16 +135,18 @@ Returns raw SimpleCov lines array for a specific file.
|
|
|
127
135
|
**Example:**
|
|
128
136
|
```ruby
|
|
129
137
|
raw = model.raw_for("lib/foo.rb")
|
|
130
|
-
# => { 'file' => '/abs/.../lib/foo.rb', 'lines' => [nil, 1, 0, 3, ...]
|
|
138
|
+
# => { 'file' => '/abs/.../lib/foo.rb', 'lines' => [nil, 1, 0, 3, ...] }
|
|
131
139
|
```
|
|
132
140
|
|
|
133
|
-
### `format_table(rows = nil, sort_order: :descending)`
|
|
141
|
+
### `format_table(rows = nil, sort_order: :descending, raise_on_stale: nil, tracked_globs: nil)`
|
|
134
142
|
|
|
135
143
|
Generates formatted ASCII table string.
|
|
136
144
|
|
|
137
145
|
**Parameters:**
|
|
138
|
-
- `rows` (Array<Hash>, optional): Custom row data; defaults to `
|
|
146
|
+
- `rows` (Array<Hash>, optional): Custom row data; defaults to `list`
|
|
139
147
|
- `sort_order` (Symbol, optional): `:descending` (default) or `:ascending`
|
|
148
|
+
- `raise_on_stale` (Boolean, optional): Whether to raise error if project is stale. Defaults to model setting.
|
|
149
|
+
- `tracked_globs` (Array<String>, optional): Patterns to filter files.
|
|
140
150
|
|
|
141
151
|
**Returns:** `String` - Formatted table with Unicode borders
|
|
142
152
|
|
|
@@ -147,29 +157,38 @@ table = model.format_table
|
|
|
147
157
|
puts table
|
|
148
158
|
|
|
149
159
|
# Custom rows
|
|
150
|
-
lib_files = model.
|
|
160
|
+
lib_files = model.list['files'].select { |f| f['file'].include?('/lib/') }
|
|
151
161
|
lib_table = model.format_table(lib_files, sort_order: :descending)
|
|
152
162
|
puts lib_table
|
|
153
163
|
```
|
|
154
164
|
|
|
155
|
-
### `project_totals(tracked_globs: nil)`
|
|
165
|
+
### `project_totals(tracked_globs: [], raise_on_stale: nil)`
|
|
156
166
|
|
|
157
167
|
Returns aggregated coverage totals across all files.
|
|
158
168
|
|
|
159
169
|
**Parameters:**
|
|
160
170
|
- `tracked_globs` (Array<String> or String, optional): Glob patterns to filter files
|
|
171
|
+
- `raise_on_stale` (Boolean, optional): Whether to raise error if project is stale. Defaults to model setting.
|
|
161
172
|
|
|
162
173
|
**Returns:** `Hash` - See [project_totals return type](#project_totals)
|
|
163
174
|
|
|
164
175
|
**Example:**
|
|
165
176
|
```ruby
|
|
166
177
|
totals = model.project_totals
|
|
167
|
-
# => {
|
|
178
|
+
# => {
|
|
179
|
+
# 'lines' => { 'total' => 123, 'covered' => 100, 'uncovered' => 23, 'percent_covered' => 81.3 },
|
|
180
|
+
# 'tracking' => { 'enabled' => true, 'globs' => ['lib/**/*.rb'] },
|
|
181
|
+
# 'files' => { 'total' => 4, 'with_coverage' => { 'total' => 4, 'ok' => 4, 'stale' => { ... } } }
|
|
182
|
+
# }
|
|
168
183
|
|
|
169
184
|
# Filter to specific directory
|
|
170
185
|
lib_totals = model.project_totals(tracked_globs: 'lib/**/*.rb')
|
|
171
186
|
```
|
|
172
187
|
|
|
188
|
+
When `raise_on_stale: true` is set, the method raises on stale coverage instead of returning totals. Otherwise, totals exclude stale files (`M`, `T`, `L`, `E`) from line counts and report stale breakdowns under `files['with_coverage']['stale']`.
|
|
189
|
+
|
|
190
|
+
Note: The `without_coverage` hash will only be present if `tracked_globs` were specified.
|
|
191
|
+
|
|
173
192
|
### `relativize(data)`
|
|
174
193
|
|
|
175
194
|
Converts absolute file paths in coverage data to relative paths from project root.
|
|
@@ -182,29 +201,40 @@ Converts absolute file paths in coverage data to relative paths from project roo
|
|
|
182
201
|
**Example:**
|
|
183
202
|
```ruby
|
|
184
203
|
summary = model.summary_for('lib/cov_loupe/model.rb')
|
|
185
|
-
# => { 'file' => '/
|
|
204
|
+
# => { 'file' => '/path/to/project/lib/cov_loupe/model.rb', ... }
|
|
186
205
|
|
|
187
206
|
relative_summary = model.relativize(summary)
|
|
188
207
|
# => { 'file' => 'lib/cov_loupe/model.rb', ... }
|
|
189
208
|
|
|
190
209
|
# Works with arrays too
|
|
191
|
-
|
|
210
|
+
list_result = model.list
|
|
211
|
+
files = list_result['files']
|
|
192
212
|
relative_files = model.relativize(files)
|
|
193
213
|
```
|
|
194
214
|
|
|
195
215
|
## Return Types
|
|
196
216
|
|
|
197
|
-
### `
|
|
217
|
+
### `list`
|
|
198
218
|
|
|
199
|
-
Returns `
|
|
219
|
+
Returns `Hash` with file data and staleness metadata:
|
|
200
220
|
|
|
201
221
|
```ruby
|
|
202
222
|
{
|
|
203
|
-
'
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
223
|
+
'files' => [
|
|
224
|
+
{
|
|
225
|
+
'file' => String, # Absolute file path
|
|
226
|
+
'covered' => Integer, # Number of covered lines
|
|
227
|
+
'total' => Integer, # Total relevant lines
|
|
228
|
+
'percentage' => Float, # Coverage percentage (0.00-100.00)
|
|
229
|
+
'stale' => String # Staleness indicator: "ok", "error", "missing", "newer", or "length_mismatch"
|
|
230
|
+
}
|
|
231
|
+
],
|
|
232
|
+
'skipped_files' => Array<String>, # Files skipped due to coverage errors
|
|
233
|
+
'missing_tracked_files' => Array<String>,# Tracked files missing from coverage
|
|
234
|
+
'newer_files' => Array<String>, # Files newer than coverage
|
|
235
|
+
'deleted_files' => Array<String>, # Coverage entries for deleted files
|
|
236
|
+
'length_mismatch_files' => Array<String>,# Files whose line counts differ from coverage
|
|
237
|
+
'unreadable_files' => Array<String> # Files that could not be read
|
|
208
238
|
}
|
|
209
239
|
```
|
|
210
240
|
|
|
@@ -282,19 +312,44 @@ Returns `Hash`:
|
|
|
282
312
|
```ruby
|
|
283
313
|
{
|
|
284
314
|
'lines' => {
|
|
285
|
-
'total' => Integer,
|
|
286
|
-
'covered' => Integer,
|
|
287
|
-
'uncovered' => Integer
|
|
315
|
+
'total' => Integer, # Total relevant lines across all files
|
|
316
|
+
'covered' => Integer, # Total covered lines
|
|
317
|
+
'uncovered' => Integer, # Total uncovered lines
|
|
318
|
+
'percent_covered' => Float # Overall percent covered
|
|
319
|
+
},
|
|
320
|
+
'tracking' => {
|
|
321
|
+
'enabled' => Boolean, # Whether tracked_globs are active
|
|
322
|
+
'globs' => Array<String> # Active tracked globs (empty when disabled)
|
|
288
323
|
},
|
|
289
|
-
'percentage' => Float, # Overall coverage percentage
|
|
290
324
|
'files' => {
|
|
291
|
-
'total' => Integer,
|
|
292
|
-
'
|
|
293
|
-
|
|
325
|
+
'total' => Integer, # Total number of files (with + without coverage)
|
|
326
|
+
'with_coverage' => {
|
|
327
|
+
'total' => Integer, # Files with coverage entries
|
|
328
|
+
'ok' => Integer, # Fresh coverage entries
|
|
329
|
+
'stale' => {
|
|
330
|
+
'total' => Integer, # Stale coverage entries
|
|
331
|
+
'by_type' => {
|
|
332
|
+
'missing_from_disk' => Integer,
|
|
333
|
+
'newer' => Integer,
|
|
334
|
+
'length_mismatch' => Integer,
|
|
335
|
+
'unreadable' => Integer
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
'without_coverage' => {
|
|
340
|
+
'total' => Integer, # Tracked files missing coverage entries
|
|
341
|
+
'by_type' => {
|
|
342
|
+
'missing_from_coverage' => Integer,
|
|
343
|
+
'unreadable' => Integer,
|
|
344
|
+
'skipped' => Integer
|
|
345
|
+
}
|
|
346
|
+
}
|
|
294
347
|
}
|
|
295
348
|
}
|
|
296
349
|
```
|
|
297
350
|
|
|
351
|
+
Note: The `without_coverage` hash will only be present if `tracked_globs` were specified.
|
|
352
|
+
|
|
298
353
|
## Error Handling
|
|
299
354
|
|
|
300
355
|
### Exception Types
|
|
@@ -303,7 +358,7 @@ The library raises these custom exceptions:
|
|
|
303
358
|
|
|
304
359
|
- **`CovLoupe::ResultsetNotFoundError`** - Coverage data file not found
|
|
305
360
|
- **`CovLoupe::FileError`** - Requested file not in coverage data
|
|
306
|
-
- **`CovLoupe::CoverageDataStaleError`** - Coverage data is stale (only when `
|
|
361
|
+
- **`CovLoupe::CoverageDataStaleError`** - Coverage data is stale (only when `raise_on_stale: true`)
|
|
307
362
|
- **`CovLoupe::CoverageDataError`** - Invalid coverage data format or structure
|
|
308
363
|
|
|
309
364
|
All exceptions inherit from `CovLoupe::Error`.
|
|
@@ -331,8 +386,8 @@ end
|
|
|
331
386
|
|
|
332
387
|
```ruby
|
|
333
388
|
# Option 1: Check staleness without raising
|
|
334
|
-
model = CovLoupe::CoverageModel.new(
|
|
335
|
-
files = model.
|
|
389
|
+
model = CovLoupe::CoverageModel.new(raise_on_stale: false)
|
|
390
|
+
files = model.list['files']
|
|
336
391
|
|
|
337
392
|
stale_files = files.select { |f| f['stale'] }
|
|
338
393
|
if stale_files.any?
|
|
@@ -344,8 +399,8 @@ end
|
|
|
344
399
|
|
|
345
400
|
# Option 2: Raise on staleness
|
|
346
401
|
begin
|
|
347
|
-
model = CovLoupe::CoverageModel.new(
|
|
348
|
-
files = model.
|
|
402
|
+
model = CovLoupe::CoverageModel.new(raise_on_stale: true)
|
|
403
|
+
files = model.list['files']
|
|
349
404
|
rescue CovLoupe::CoverageDataStaleError => e
|
|
350
405
|
puts "Stale coverage detected: #{e.message}"
|
|
351
406
|
puts "Re-run tests: bundle exec rspec"
|
|
@@ -428,9 +483,9 @@ require "cov_loupe"
|
|
|
428
483
|
|
|
429
484
|
class CoverageValidator
|
|
430
485
|
THRESHOLDS = {
|
|
431
|
-
'lib/' => 90.0,
|
|
432
|
-
'app/' =>
|
|
433
|
-
'
|
|
486
|
+
'lib/api/' => 90.0, # API layer needs 90%+
|
|
487
|
+
'app/models/' => 85.0, # Models need 85%+
|
|
488
|
+
'app/controllers/' => 75.0, # Controllers need 75%+
|
|
434
489
|
}
|
|
435
490
|
|
|
436
491
|
def initialize(model)
|
|
@@ -438,7 +493,7 @@ class CoverageValidator
|
|
|
438
493
|
end
|
|
439
494
|
|
|
440
495
|
def validate!
|
|
441
|
-
files = @model.
|
|
496
|
+
files = @model.list['files']
|
|
442
497
|
failures = []
|
|
443
498
|
|
|
444
499
|
files.each do |file|
|
|
@@ -497,7 +552,7 @@ directory_stats = patterns.map do |pattern|
|
|
|
497
552
|
{
|
|
498
553
|
directory: pattern,
|
|
499
554
|
files: totals['files']['total'],
|
|
500
|
-
coverage: totals['
|
|
555
|
+
coverage: totals['lines']['percent_covered'].round(2),
|
|
501
556
|
covered: totals['lines']['covered'],
|
|
502
557
|
total: totals['lines']['total']
|
|
503
558
|
}
|
|
@@ -522,7 +577,7 @@ class CoverageDeltaTracker
|
|
|
522
577
|
end
|
|
523
578
|
|
|
524
579
|
def save_baseline
|
|
525
|
-
current = @model.
|
|
580
|
+
current = @model.list['files']
|
|
526
581
|
File.write(@baseline_path, JSON.pretty_generate(current))
|
|
527
582
|
puts "Saved coverage baseline (#{current.length} files)"
|
|
528
583
|
end
|
|
@@ -534,7 +589,7 @@ class CoverageDeltaTracker
|
|
|
534
589
|
end
|
|
535
590
|
|
|
536
591
|
baseline = JSON.parse(File.read(@baseline_path))
|
|
537
|
-
current = @model.
|
|
592
|
+
current = @model.list['files']
|
|
538
593
|
|
|
539
594
|
improved = []
|
|
540
595
|
regressed = []
|
|
@@ -600,57 +655,64 @@ class CoverageReporter
|
|
|
600
655
|
end
|
|
601
656
|
|
|
602
657
|
def generate_markdown_report(output_path)
|
|
603
|
-
files = @model.
|
|
658
|
+
files = @model.list['files']
|
|
604
659
|
totals = @model.project_totals
|
|
605
660
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
# Overall stats
|
|
613
|
-
overall_percentage = totals['percentage']
|
|
614
|
-
total_lines = totals['lines']['total']
|
|
615
|
-
covered_lines = totals['lines']['covered']
|
|
616
|
-
total_files = totals['files']['total']
|
|
617
|
-
|
|
618
|
-
f.puts "## Overall Coverage: #{overall_percentage}%"
|
|
619
|
-
f.puts
|
|
620
|
-
f.puts "- Total Files: #{total_files}"
|
|
621
|
-
f.puts "- Total Lines: #{total_lines}"
|
|
622
|
-
f.puts "- Covered Lines: #{covered_lines}"
|
|
623
|
-
f.puts
|
|
624
|
-
|
|
625
|
-
# Files below threshold
|
|
626
|
-
threshold = 80.0
|
|
627
|
-
low_coverage = files.select { |file| file['percentage'] < threshold }
|
|
628
|
-
|
|
629
|
-
if low_coverage.any?
|
|
630
|
-
f.puts "## Files Below #{threshold}% Coverage"
|
|
631
|
-
f.puts
|
|
632
|
-
f.puts "| File | Coverage | Missing Lines |"
|
|
633
|
-
f.puts "|------|----------|---------------|"
|
|
634
|
-
|
|
635
|
-
low_coverage.sort_by { |file| file['percentage'] }.each do |file|
|
|
636
|
-
uncovered = @model.uncovered_for(file['file'])
|
|
637
|
-
missing_count = uncovered['uncovered'].length
|
|
638
|
-
f.puts "| #{file['file']} | #{file['percentage']}% | #{missing_count} |"
|
|
639
|
-
end
|
|
640
|
-
f.puts
|
|
641
|
-
end
|
|
661
|
+
# Overall stats
|
|
662
|
+
overall_percentage = totals['lines']['percent_covered']
|
|
663
|
+
total_lines = totals['lines']['total']
|
|
664
|
+
covered_lines = totals['lines']['covered']
|
|
665
|
+
total_files = totals['files']['total']
|
|
642
666
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
f.puts "| File | Coverage |"
|
|
647
|
-
f.puts "|------|----------|"
|
|
667
|
+
# Files below threshold
|
|
668
|
+
threshold = 80.0
|
|
669
|
+
low_coverage = files.select { |file| file['percentage'] < threshold }
|
|
648
670
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
671
|
+
# Build low coverage table
|
|
672
|
+
low_coverage_section = if low_coverage.any?
|
|
673
|
+
rows = low_coverage.sort_by { |file| file['percentage'] }.map do |file|
|
|
674
|
+
uncovered = @model.uncovered_for(file['file'])
|
|
675
|
+
missing_count = uncovered['uncovered'].length
|
|
676
|
+
"| #{file['file']} | #{file['percentage']}% | #{missing_count} |"
|
|
677
|
+
end.join("\n")
|
|
678
|
+
|
|
679
|
+
<<~LOW_COVERAGE_TABLE
|
|
680
|
+
|
|
681
|
+
## Files Below #{threshold}% Coverage
|
|
682
|
+
|
|
683
|
+
| File | Coverage | Missing Lines |
|
|
684
|
+
|------|----------|---------------|
|
|
685
|
+
#{rows}
|
|
686
|
+
LOW_COVERAGE_TABLE
|
|
687
|
+
else
|
|
688
|
+
""
|
|
652
689
|
end
|
|
653
690
|
|
|
691
|
+
# Build top performers table
|
|
692
|
+
top_rows = files.sort_by { |file| -file['percentage'] }.take(10).map do |file|
|
|
693
|
+
"| #{file['file']} | #{file['percentage']}% |"
|
|
694
|
+
end.join("\n")
|
|
695
|
+
|
|
696
|
+
# Generate report
|
|
697
|
+
report = <<~COVERAGE_REPORT
|
|
698
|
+
# Coverage Report
|
|
699
|
+
|
|
700
|
+
Generated: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}
|
|
701
|
+
|
|
702
|
+
## Overall Coverage: #{overall_percentage}%
|
|
703
|
+
|
|
704
|
+
- Total Files: #{total_files}
|
|
705
|
+
- Total Lines: #{total_lines}
|
|
706
|
+
- Covered Lines: #{covered_lines}
|
|
707
|
+
#{low_coverage_section}
|
|
708
|
+
## Top 10 Best Covered Files
|
|
709
|
+
|
|
710
|
+
| File | Coverage |
|
|
711
|
+
|------|----------|
|
|
712
|
+
#{top_rows}
|
|
713
|
+
COVERAGE_REPORT
|
|
714
|
+
|
|
715
|
+
File.write(output_path, report)
|
|
654
716
|
puts "Report saved to #{output_path}"
|
|
655
717
|
end
|
|
656
718
|
end
|
|
@@ -660,30 +722,73 @@ reporter = CoverageReporter.new(model)
|
|
|
660
722
|
reporter.generate_markdown_report("coverage_report.md")
|
|
661
723
|
```
|
|
662
724
|
|
|
663
|
-
|
|
725
|
+
### Per-Model Context (Advanced)
|
|
726
|
+
|
|
727
|
+
By default, all `CoverageModel` instances share the global context for error handling and logging. For advanced scenarios where you need different models with different logging or error handling configurations in the same process, you can pass a custom context to each model.
|
|
728
|
+
|
|
729
|
+
```ruby
|
|
730
|
+
require "cov_loupe"
|
|
731
|
+
|
|
732
|
+
# Scenario: Analyzing coverage for multiple projects in one script
|
|
733
|
+
|
|
734
|
+
# Project A: Detailed logging for debugging
|
|
735
|
+
context_a = CovLoupe.create_context(
|
|
736
|
+
error_handler: CovLoupe::ErrorHandlerFactory.for_library,
|
|
737
|
+
log_target: 'project_a_coverage.log'
|
|
738
|
+
)
|
|
664
739
|
|
|
665
|
-
|
|
740
|
+
model_a = CovLoupe::CoverageModel.new(
|
|
741
|
+
root: '/path/to/project_a',
|
|
742
|
+
resultset: '/path/to/project_a/coverage/.resultset.json',
|
|
743
|
+
context: context_a
|
|
744
|
+
)
|
|
666
745
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
- `'T'` - **Timestamp**: File modified more recently than coverage data
|
|
670
|
-
- `'L'` - **Length**: Source file line count differs from coverage data
|
|
746
|
+
# Project B: Different log file
|
|
747
|
+
context_b = context_a.with(log_target: 'project_b_coverage.log')
|
|
671
748
|
|
|
672
|
-
|
|
749
|
+
model_b = CovLoupe::CoverageModel.new(
|
|
750
|
+
root: '/path/to/project_b',
|
|
751
|
+
resultset: '/path/to/project_b/coverage/.resultset.json',
|
|
752
|
+
context: context_b
|
|
753
|
+
)
|
|
754
|
+
|
|
755
|
+
# Each model logs to its own file
|
|
756
|
+
summary_a = model_a.summary_for('lib/foo.rb') # Logs to project_a_coverage.log
|
|
757
|
+
summary_b = model_b.summary_for('lib/bar.rb') # Logs to project_b_coverage.log
|
|
758
|
+
|
|
759
|
+
# You can also change a model's context at runtime
|
|
760
|
+
model_a.context = CovLoupe.context # Switch to global context
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
**When to use per-model contexts:**
|
|
764
|
+
- Managing coverage for multiple projects in one script
|
|
765
|
+
- Different error handling strategies per model
|
|
766
|
+
- Separate log files for different data sources
|
|
767
|
+
- Testing scenarios requiring isolated configurations
|
|
768
|
+
|
|
769
|
+
**Simple use case (most common):**
|
|
770
|
+
```ruby
|
|
771
|
+
# For most use cases, just configure the global context once
|
|
772
|
+
CovLoupe.error_handler = CovLoupe::ErrorHandlerFactory.for_library
|
|
773
|
+
CovLoupe.default_log_file = 'coverage_analysis.log'
|
|
774
|
+
|
|
775
|
+
# All models automatically use the global context
|
|
776
|
+
model = CovLoupe::CoverageModel.new
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
## Staleness Detection
|
|
673
780
|
|
|
674
|
-
|
|
781
|
+
The `list` method returns a `'stale'` field for each file with one of these values:
|
|
675
782
|
|
|
676
|
-
|
|
783
|
+
- `"ok"` - Coverage data is current
|
|
784
|
+
- `"missing"` - **Missing**: File no longer exists on disk
|
|
785
|
+
- `"newer"` - **Timestamp**: File modified more recently than coverage data
|
|
786
|
+
- `"length_mismatch"` - **Length**: Source file line count differs from coverage data
|
|
787
|
+
- `"error"` - **Error**: Staleness check failed
|
|
677
788
|
|
|
678
|
-
|
|
679
|
-
- `CovLoupe::CoverageModel.new(root:, resultset:, staleness: 'off', tracked_globs: nil)`
|
|
680
|
-
- `#raw_for(path)`, `#summary_for(path)`, `#uncovered_for(path)`, `#detailed_for(path)`, `#all_files(sort_order:)`, `#format_table(rows: nil, sort_order:, check_stale:, tracked_globs:)`
|
|
681
|
-
- Return shapes shown in the [Return Types](#return-types) section
|
|
682
|
-
- Exception types documented in [Error Handling](#error-handling)
|
|
789
|
+
**Note:** Per-file methods (`summary_for`, `uncovered_for`, `detailed_for`, `raw_for`) do not include staleness information in their return values. To check staleness for individual files, use `list` and filter the results.
|
|
683
790
|
|
|
684
|
-
|
|
685
|
-
- CLI (`CovLoupe.run(argv)`) and MCP tools remain stable but are separate surfaces
|
|
686
|
-
- Internal helpers under `CovLoupe::CovUtil` may change; prefer `CoverageModel` unless you need low-level access
|
|
791
|
+
When `raise_on_stale: true` is enabled in `CoverageModel.new`, the model will raise `CovLoupe::CoverageDataStaleError` exceptions when stale files are detected during method calls.
|
|
687
792
|
|
|
688
793
|
## Related Documentation
|
|
689
794
|
|