coverband 6.2.2 → 7.0.0.rc.1

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 (109) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/diagram.yml +2 -2
  3. data/.github/workflows/main.yml +35 -7
  4. data/Gemfile +15 -0
  5. data/Gemfile.rails7.0 +11 -0
  6. data/Gemfile.rails7.1 +5 -1
  7. data/Gemfile.rails7.2 +4 -0
  8. data/Gemfile.rails8.0 +14 -0
  9. data/README.md +218 -12
  10. data/Rakefile +16 -5
  11. data/changes.md +59 -0
  12. data/coverband.gemspec +17 -12
  13. data/lib/coverband/adapters/active_support_cache_store.rb +54 -0
  14. data/lib/coverband/adapters/base.rb +42 -10
  15. data/lib/coverband/adapters/file_store.rb +2 -2
  16. data/lib/coverband/adapters/hash_redis_store.rb +22 -3
  17. data/lib/coverband/adapters/null_store.rb +0 -4
  18. data/lib/coverband/adapters/redis_cleanup.rb +101 -0
  19. data/lib/coverband/adapters/redis_store.rb +32 -65
  20. data/lib/coverband/adapters/session_coverage.rb +172 -0
  21. data/lib/coverband/adapters/stdout_store.rb +0 -4
  22. data/lib/coverband/adapters/tracker_storage/base.rb +79 -0
  23. data/lib/coverband/adapters/tracker_storage/document_repository.rb +49 -0
  24. data/lib/coverband/adapters/tracker_storage/factory.rb +57 -0
  25. data/lib/coverband/adapters/tracker_storage/redis_hash_repository.rb +127 -0
  26. data/lib/coverband/collectors/abstract_tracker.rb +135 -51
  27. data/lib/coverband/collectors/query_burst_tracker.rb +72 -19
  28. data/lib/coverband/collectors/route_tracker.rb +6 -0
  29. data/lib/coverband/collectors/tracker_registry.rb +63 -0
  30. data/lib/coverband/collectors/translation_tracker.rb +6 -0
  31. data/lib/coverband/collectors/view_tracker.rb +11 -5
  32. data/lib/coverband/configuration.rb +95 -131
  33. data/lib/coverband/mcp/http_handler.rb +1 -1
  34. data/lib/coverband/mcp/server.rb +4 -1
  35. data/lib/coverband/mcp/tools/get_storage_health.rb +28 -0
  36. data/lib/coverband/reporters/base.rb +0 -2
  37. data/lib/coverband/reporters/html_report.rb +2 -3
  38. data/lib/coverband/reporters/json_report.rb +2 -2
  39. data/lib/coverband/reporters/web.rb +72 -15
  40. data/lib/coverband/storage/document.rb +173 -0
  41. data/lib/coverband/storage/generation.rb +165 -0
  42. data/lib/coverband/storage/generation_change.rb +53 -0
  43. data/lib/coverband/storage/generation_coordinator.rb +145 -0
  44. data/lib/coverband/storage/io_guard.rb +29 -0
  45. data/lib/coverband/storage/read_fallback.rb +30 -0
  46. data/lib/coverband/storage/redis_target.rb +74 -0
  47. data/lib/coverband/storage/session.rb +527 -0
  48. data/lib/coverband/storage/target.rb +117 -0
  49. data/lib/coverband/storage/writer.rb +194 -0
  50. data/lib/coverband/storage_health.rb +90 -0
  51. data/lib/coverband/utils/configuration_template.rb +14 -1
  52. data/lib/coverband/utils/railtie.rb +2 -2
  53. data/lib/coverband/utils/result.rb +5 -5
  54. data/lib/coverband/utils/results.rb +32 -16
  55. data/lib/coverband/utils/source_file.rb +17 -27
  56. data/lib/coverband/utils/tasks.rb +71 -6
  57. data/lib/coverband/version.rb +1 -1
  58. data/lib/coverband.rb +98 -20
  59. data/public/application.css +540 -146
  60. data/test/benchmarks/benchmark.rake +13 -0
  61. data/test/coverband/adapters/active_support_cache_store_test.rb +613 -0
  62. data/test/coverband/adapters/capabilities_test.rb +80 -0
  63. data/test/coverband/adapters/redis_store_test.rb +63 -2
  64. data/test/coverband/adapters/solid_cache_store_test.rb +181 -0
  65. data/test/coverband/adapters/tracker_storage_test.rb +170 -0
  66. data/test/coverband/collectors/query_burst_tracker_test.rb +155 -10
  67. data/test/coverband/collectors/route_tracker_test.rb +2 -13
  68. data/test/coverband/collectors/tracker_registry_test.rb +218 -0
  69. data/test/coverband/collectors/translation_tracker_test.rb +76 -16
  70. data/test/coverband/collectors/view_tracker_test.rb +49 -12
  71. data/test/coverband/configuration_test.rb +36 -38
  72. data/test/coverband/mcp/server_test.rb +2 -1
  73. data/test/coverband/mcp/tools/get_storage_health_test.rb +41 -0
  74. data/test/coverband/reporters/html_test.rb +26 -0
  75. data/test/coverband/reporters/json_test.rb +53 -0
  76. data/test/coverband/reporters/web_test.rb +96 -0
  77. data/test/coverband/storage/generation_coordinator_test.rb +191 -0
  78. data/test/coverband/storage/io_guard_test.rb +127 -0
  79. data/test/coverband/storage/protocol_conformance_test.rb +725 -0
  80. data/test/coverband/storage_health_test.rb +99 -0
  81. data/test/coverband/track_key_test.rb +8 -0
  82. data/test/coverband/utils/cleanup_tasks_test.rb +188 -0
  83. data/test/coverband/utils/file_list_test.rb +2 -2
  84. data/test/coverband/utils/result_test.rb +4 -4
  85. data/test/coverband/utils/results_test.rb +100 -10
  86. data/test/coverband/utils/source_file_test.rb +40 -6
  87. data/test/forked/rails_cache_full_stack_test.rb +64 -0
  88. data/test/forked/rails_full_stack_test.rb +7 -0
  89. data/test/integration/full_stack_deferred_eager_test.rb +0 -1
  90. data/test/integration/full_stack_send_deferred_eager_test.rb +0 -1
  91. data/test/integration/full_stack_test.rb +0 -1
  92. data/test/integration/mcp_integration_test.rb +4 -2
  93. data/test/integration/standalone_json_report_test.rb +46 -0
  94. data/test/integration/without_redis_test.rb +76 -0
  95. data/test/rails7_dummy/config/application.rb +1 -0
  96. data/test/rails7_dummy/config/coverband_missing_redis.rb +0 -2
  97. data/test/rails7_dummy/config/coverband_rails_cache.rb +16 -0
  98. data/test/rails8_dummy/config/application.rb +1 -0
  99. data/test/rails8_dummy/config/coverband_rails_cache.rb +3 -0
  100. data/test/rails_test_helper.rb +2 -1
  101. data/test/test_helper.rb +5 -17
  102. data/views/file_list.erb +2 -2
  103. data/views/source_file.erb +4 -3
  104. metadata +51 -29
  105. data/lib/coverband/adapters/memcached_store.rb +0 -85
  106. data/lib/coverband/adapters/web_service_store.rb +0 -152
  107. data/lib/coverband/collectors/view_tracker_service.rb +0 -59
  108. data/test/coverband/adapters/memecached_store_test.rb +0 -26
  109. data/test/coverband/adapters/web_service_store_test.rb +0 -56
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 20188ba8fca24027b2a7bd367ac88586b7dace04a6eadfd9714bdf509849683e
4
- data.tar.gz: c2db00de6ffaa32256d083c7d51290a5936f5e2ee46c774eed901f91fa0ba545
3
+ metadata.gz: 122ec261f93431af71ca87c0113241a9197af2daa07c3d32013b98d893cd3a78
4
+ data.tar.gz: 785fa634bdba056657cb2b0e358f387decb4aa020faee0f50580dedd929ddcaa
5
5
  SHA512:
6
- metadata.gz: 22b5b1281136a3ed3172383d84526f1659568e6b204cc38b636d929c76b8ec84c9b70d20e61a54484cf97d8604e268de9da87cc596bbff2cbb81752c1078ca8b
7
- data.tar.gz: aa55b358d2a3a196fd5de29f850ad41a877274649ccf377395a114c6e940bc926424fccbe6a3e7642225eaa81432f73094443b5874c8e79ac75cccb39a6c3422
6
+ metadata.gz: 990b9e30e696866dd1e68e8c26b394608c3697f8564ea0ff2683592d1b02fabaae5a0dcc0aa4e3c7a12e73cef259aa2f441fd154a910cb7694c0d10acfb9dab7
7
+ data.tar.gz: 43c6e3982076da3771c7a961a9ac7c409c8576731d8e76b4a943a67323ff4e3391f9b96de89ba5050ee1c2868a436b25c9a386265d643bf82b053c6b65953f20
@@ -6,7 +6,7 @@ on:
6
6
  - main
7
7
  jobs:
8
8
  get_data:
9
- runs-on: ubuntu-latest
9
+ runs-on: ubuntu-24.04
10
10
  steps:
11
11
  - name: Checkout code
12
12
  uses: actions/checkout@master
@@ -14,4 +14,4 @@ jobs:
14
14
  uses: githubocto/repo-visualizer@main
15
15
  with:
16
16
  excluded_paths: "ignore,.github"
17
- branch: diagram
17
+ branch: diagram
@@ -13,8 +13,8 @@ jobs:
13
13
  fail-fast: false
14
14
  matrix:
15
15
  # need to figure out how to use redis on macos github actions
16
- # os: [ubuntu, macos]
17
- os: [ubuntu]
16
+ # os: [ubuntu-24.04, macos-15]
17
+ os: [ubuntu-24.04]
18
18
  # remove until I sort out CI issues for truffle
19
19
  # truffleruby,
20
20
  # truffleruby-head,
@@ -25,9 +25,11 @@ jobs:
25
25
  redis-version: [4, 5, 6, 7]
26
26
  exclude:
27
27
  # Rails 8 requires ruby 3.2+.
28
- - gemfile: 'rails_8.0'
28
+ - gemfile: 'Gemfile.rails8.0'
29
29
  ruby: '3.1'
30
- runs-on: ${{ matrix.os }}-latest
30
+ runs-on: ${{ matrix.os }}
31
+ env:
32
+ BUNDLE_GEMFILE: ${{ matrix.gemfile }}
31
33
  services:
32
34
  redis:
33
35
  image: redis:${{ matrix.redis-version }}
@@ -38,6 +40,15 @@ jobs:
38
40
  --health-interval 10s
39
41
  --health-timeout 5s
40
42
  --health-retries 5
43
+ memcached:
44
+ image: memcached:1.6
45
+ ports:
46
+ - 11211:11211
47
+ options: >-
48
+ --health-cmd "timeout 2 bash -c '</dev/tcp/127.0.0.1/11211'"
49
+ --health-interval 10s
50
+ --health-timeout 5s
51
+ --health-retries 5
41
52
  steps:
42
53
  - uses: actions/checkout@v7
43
54
  - uses: ruby/setup-ruby@v1
@@ -45,9 +56,26 @@ jobs:
45
56
  ruby-version: ${{ matrix.ruby }}
46
57
  bundler-cache: true # runs 'bundle install' and caches installed gems automatically
47
58
  - run: bundle exec rake test:all
59
+ env:
60
+ # the memcached adapter has an ENV gate so local runs need no service,
61
+ # which also meant CI never exercised it
62
+ COVERBAND_MEMCACHED: "true"
48
63
  - run: "RUBYOPT='--enable=frozen-string-literal --debug=frozen-string-literal' bundle exec rake"
64
+ env:
65
+ COVERBAND_MEMCACHED: "true"
66
+ # Solid Cache needs Rails 7.2+, and its engine changes process wide state,
67
+ # so it runs on its own, only where it is available.
68
+ #
69
+ # Its gems sit in an optional bundler group, which bundler-cache does not
70
+ # install, so the group is installed here rather than assumed present.
71
+ - name: Solid Cache adapter tests
72
+ if: matrix.gemfile == 'Gemfile.rails8.0'
73
+ run: |
74
+ bundle config set --local with solid_cache
75
+ bundle install --jobs 4
76
+ bundle exec rake test:solid_cache
49
77
  starndardrb:
50
- runs-on: ubuntu-latest
78
+ runs-on: ubuntu-24.04
51
79
  steps:
52
80
  - uses: actions/checkout@v7
53
81
  - uses: ruby/setup-ruby@v1
@@ -56,10 +84,10 @@ jobs:
56
84
  bundler-cache: true # runs 'bundle install' and caches installed gems automatically
57
85
  - run: bundle exec standardrb --format github
58
86
  javascript:
59
- runs-on: ubuntu-latest
87
+ runs-on: ubuntu-24.04
60
88
  steps:
61
89
  - uses: actions/checkout@v7
62
- - uses: actions/setup-node@v4
90
+ - uses: actions/setup-node@v7
63
91
  with:
64
92
  node-version: "20"
65
93
  - run: node --test test/javascript/*.test.mjs
data/Gemfile CHANGED
@@ -11,3 +11,18 @@ gem "webrick"
11
11
 
12
12
  # Required for Ruby 3.4+ (extracted from stdlib)
13
13
  gem "cgi"
14
+
15
+ # ActiveSupport 8.1's MemCacheStore passes positional options to ConnectionPool,
16
+ # which connection_pool 3.x turned into keyword arguments. Pinned so the
17
+ # memcached backed cache adapter tests can actually run.
18
+ gem "connection_pool", "~> 2.5"
19
+
20
+ # Solid Cache is a central target for the ActiveSupport::Cache adapter: it is
21
+ # how Coverband reaches Postgres, MySQL, and SQLite. Needs Rails 7.2+.
22
+ #
23
+ # Kept out of the default group on purpose: the dummy Rails apps require every
24
+ # default gem, and Solid Cache's engine expects an ActiveJob they do not load.
25
+ group :solid_cache, optional: true do
26
+ gem "solid_cache"
27
+ gem "sqlite3"
28
+ end
data/Gemfile.rails7.0 CHANGED
@@ -9,5 +9,16 @@ gem "haml"
9
9
  gem "slim"
10
10
  gem "webrick"
11
11
 
12
+ # ActiveSupport's MemCacheStore passes positional options to ConnectionPool,
13
+ # which connection_pool 3.x turned into keyword arguments.
14
+ gem "connection_pool", "~> 2.5"
15
+
16
+ # Rack 2 requires ostruct, which Ruby 4 no longer ships as a default gem.
17
+ gem "ostruct"
18
+
12
19
  # Required for Ruby 3.4+ (extracted from stdlib)
13
20
  gem "cgi"
21
+
22
+ # railties 7.0 requires tsort without declaring it, and Ruby 4.1 no longer
23
+ # ships it as a default gem.
24
+ gem "tsort"
data/Gemfile.rails7.1 CHANGED
@@ -9,5 +9,9 @@ gem "haml"
9
9
  gem "slim"
10
10
  gem "webrick"
11
11
 
12
+ # ActiveSupport's MemCacheStore passes positional options to ConnectionPool,
13
+ # which connection_pool 3.x turned into keyword arguments.
14
+ gem "connection_pool", "~> 2.5"
15
+
12
16
  # Required for Ruby 3.4+ (extracted from stdlib)
13
- gem "cgi"
17
+ gem "cgi"
data/Gemfile.rails7.2 CHANGED
@@ -9,5 +9,9 @@ gem "haml"
9
9
  gem "slim"
10
10
  gem "webrick"
11
11
 
12
+ # ActiveSupport's MemCacheStore passes positional options to ConnectionPool,
13
+ # which connection_pool 3.x turned into keyword arguments.
14
+ gem "connection_pool", "~> 2.5"
15
+
12
16
  # Required for Ruby 3.4+ (extracted from stdlib)
13
17
  gem "cgi"
data/Gemfile.rails8.0 CHANGED
@@ -9,5 +9,19 @@ gem "haml"
9
9
  gem "slim"
10
10
  gem "webrick"
11
11
 
12
+ # ActiveSupport's MemCacheStore passes positional options to ConnectionPool,
13
+ # which connection_pool 3.x turned into keyword arguments.
14
+ gem "connection_pool", "~> 2.5"
15
+
12
16
  # Required for Ruby 3.4+ (extracted from stdlib)
13
17
  gem "cgi"
18
+
19
+ # Solid Cache is a central target for the ActiveSupport::Cache adapter: it is
20
+ # how Coverband reaches Postgres, MySQL, and SQLite. Needs Rails 7.2+.
21
+ #
22
+ # Kept out of the default group on purpose: the dummy Rails apps require every
23
+ # default gem, and Solid Cache's engine expects an ActiveJob they do not load.
24
+ group :solid_cache, optional: true do
25
+ gem "solid_cache"
26
+ gem "sqlite3"
27
+ end
data/README.md CHANGED
@@ -3,7 +3,6 @@
3
3
  # Coverband
4
4
 
5
5
  [![GithubCI](https://github.com/danmayer/coverband/workflows/CI/badge.svg)](https://github.com/danmayer/coverband/actions)
6
- [![Coverage Status](https://coveralls.io/repos/github/danmayer/coverband/badge.svg?branch=main)](https://coveralls.io/github/danmayer/coverband?branch=main)
7
6
  [![Maintainability](https://api.codeclimate.com/v1/badges/1e6682f9540d75f26da7/maintainability)](https://codeclimate.com/github/danmayer/coverband/maintainability)
8
7
  [![Discord Shield](https://img.shields.io/discord/609509533999562753)](https://discord.gg/KAH38EV)
9
8
 
@@ -69,12 +68,15 @@ The `rediss://` scheme works with:
69
68
 
70
69
  ## Gem Installation
71
70
 
72
- Add this line to your application's `Gemfile`, remember to `bundle install` after updating:
71
+ Add these lines to your application's `Gemfile`, remember to `bundle install` after updating:
73
72
 
74
- ```bash
73
+ ```ruby
75
74
  gem 'coverband'
75
+ gem 'redis' # the default store
76
76
  ```
77
77
 
78
+ Redis is the default store and needs no configuration beyond a reachable Redis. Since Coverband 7.0 the `redis` gem is no longer installed for you, so add it yourself as above. If you would rather keep coverage somewhere else (Solid Cache, memcached, files), leave out `redis` and set `config.store`, see [ActiveSupport::Cache Store](#activesupportcache-store). Without the `redis` gem and without a configured store, Coverband logs a warning at boot and records nothing. Upgrading from 6.x? See [Upgrading to 7.0](#upgrading-to-70).
79
+
78
80
  ### No custom code or middleware required
79
81
 
80
82
  With older versions of Coverband, projects would report to redis using rack or sidekiq middleware. After Coverband 4.0, this should no longer be required and could cause performance issues. Reporting to redis is now automatically done within a background thread with no custom code needed.
@@ -97,6 +99,46 @@ use Coverband::BackgroundMiddleware
97
99
  run ActionController::Dispatcher.new
98
100
  ```
99
101
 
102
+ ## Upgrading to 7.0
103
+
104
+ 7.0 is a major release with breaking changes. Most apps only need the first two steps. The full list is in the [changelog](https://github.com/danmayer/coverband/blob/main/changes.md).
105
+
106
+ 1. **Add the `redis` gem if you use Redis.** Coverband no longer installs it for you. Redis is still the default store and still needs no other configuration once the gem is in your Gemfile:
107
+
108
+ ```ruby
109
+ gem 'coverband'
110
+ gem 'redis'
111
+ ```
112
+
113
+ Without it, and without a configured `config.store`, Coverband logs a warning at boot and records nothing.
114
+
115
+ 2. **Coverage starts fresh.** The storage format changed, so data recorded by 6.x is ignored rather than migrated. Once you are happy with the new data, delete the old keys with `rake coverband:clear_legacy` (Redis only).
116
+
117
+ 3. **Delete removed settings from `config/coverband.rb`.** These now do nothing. Assigning one logs a warning naming the setting, but the app still boots:
118
+
119
+ | Setting | What to do |
120
+ | --- | --- |
121
+ | `reporter` | Delete it. It has had no effect since Coverband 5.0. |
122
+ | `api_key`, `service_url`, `coverband_timeout`, `service_dev_mode`, `service_test_mode`, `process_type` | Delete them, see the coverband.io step below. |
123
+ | `s3_region`, `s3_bucket`, `s3_access_key_id`, `s3_secret_access_key`, `track_gems`, `gem_details`, `simulate_oneshot_lines_coverage` | Delete them. They were already no-ops. |
124
+
125
+ 4. **coverband.io users: configure a store.** coverband.io is no longer maintained, so reporting to it (`WebServiceStore`) has been removed, along with `config/coverband_service.rb` and the `COVERBAND_API_KEY`, `COVERBAND_URL`, `COVERBAND_ENABLE_DEV_MODE`, `COVERBAND_ENABLE_TEST_MODE`, and `PROCESS_TYPE` environment variables. Move any settings from `config/coverband_service.rb` into `config/coverband.rb` and use Redis (step 1) or an [ActiveSupport::Cache store](#activesupportcache-store).
126
+
127
+ 5. **`MemcachedStore` users: switch to `ActiveSupportCacheStore`.** `MemcachedStore` has been removed. The replacement takes the same cache and adds tracker support:
128
+
129
+ ```ruby
130
+ # before
131
+ config.store = Coverband::Adapters::MemcachedStore.new(cache, memcached_namespace: "myapp")
132
+ # after
133
+ config.store = Coverband::Adapters::ActiveSupportCacheStore.new(cache, cache_namespace: "myapp")
134
+ ```
135
+
136
+ 6. **Custom code against Coverband internals.** Only relevant if you wrote a tracker or call adapters directly:
137
+ - In a custom tracker, use `storage` instead of `redis_store`.
138
+ - Instead of `store.raw_store`, use `store.cleanup` (`clear_legacy!`, `clear_orphans!`), which returns `nil` for stores that cannot enumerate keys.
139
+ - Replace calls to the misspelled `Coverband::Utils::Results#runtime_relavent_lines` with `#runtime_relevant_lines`; the old spelling was removed in 7.0.
140
+ - `Coverband::Utils::SourceFile` and `Coverband::Utils::Result` take stored report records (`{"data" => [...]}`) rather than bare line arrays.
141
+
100
142
  ## Coverband Web UI
101
143
 
102
144
  ![image](https://raw.github.com/danmayer/coverband/main/docs/coverband_web_ui.png)
@@ -129,15 +171,20 @@ run ActionController::Dispatcher.new
129
171
 
130
172
  The columns in the web UI are as follows:
131
173
 
132
- - **% covered** - Percentage of total relevant lines covered
133
- - **% runtime** - Percentage of the runtime lines covered where runtime lines are lines that are hit after the application has been eagerly loaded
174
+ - **% covered** - Percentage of relevant lines covered, during either eager loading or runtime
175
+ - **% runtime** - Percentage of the runtime relevant lines that were hit at runtime. Runtime relevant lines are the relevant lines that were *not* hit while the application eager loaded, so this is the share of the code that only real usage can exercise (method bodies, branches, etc.) which real usage actually exercised
134
176
  - **Lines** - Total lines in the file including lines unreachable or uncover-able. An unreachable line would be an empty line with no code, comments, or `end` statements.
135
177
  - **Relevant lines** - Lines that are coverable, i.e. not empty
136
- - **Lines runtime** - Total lines minus uncoverable lines minus the lines that are only hit during eager loading of application
178
+ - **Lines covered** - Relevant lines hit during either eager loading or runtime
179
+ - **Lines runtime** - Relevant lines hit at least once at runtime
137
180
  - **Lines missed** - Relevant lines not covered
138
181
  - **Avg hits/line** - Total of coverage to the file divided by relevant lines.
139
182
 
140
- When viewing an individual file, a line of code such as a class or method definition may appear green because it is eager loaded by the application, but still not be hit at all in runtime by actual users.
183
+ The individual file view also lists the file's **runtime relevant lines**, the denominator of **% runtime**. A file only reports 100% runtime when every one of its runtime relevant lines was hit at runtime. If a file was never eager loaded (the default in development), every relevant line is runtime relevant, so **% runtime** matches **% covered**.
184
+
185
+ **Lines runtime** can include load time lines such as class and method definitions. Those lines are hit at runtime when a process loads the file lazily after Coverband switched to runtime coverage, e.g. a console, runner, or job process that does not eager load. Such lines still count as eager loaded for **% runtime** as long as any process loaded them during eager loading.
186
+
187
+ When viewing an individual file, a line of code such as a class or method definition may appear green because it is eager loaded by the application, but still not be hit at all in runtime by actual users. Each line's badge shows its `load` (eager loading) and `runtime` hit counts separately.
141
188
 
142
189
  ![example of a file with lines not hit at runtime](https://user-images.githubusercontent.com/96786/63541229-aa98a580-c4eb-11e9-8eb8-c004fe1369db.png)
143
190
  #### Coverage first seen and Last activity recorded
@@ -233,7 +280,7 @@ Take Coverband for a spin on the live Heroku deployed [Coverband Demo](https://c
233
280
  If you need to configure Coverband, this can be done by creating a `config/coverband.rb` file relative to your project root.
234
281
 
235
282
  - See [lib/coverband/configuration.rb](https://github.com/danmayer/coverband/blob/main/lib/coverband/configuration.rb) for all options
236
- - By default Coverband will try to store data to Redis \* Redis endpoint is looked for in this order: `ENV['COVERBAND_REDIS_URL']`, `ENV['REDIS_URL']`, or `localhost`
283
+ - By default Coverband will try to store data to Redis (when the `redis` gem is installed) \* Redis endpoint is looked for in this order: `ENV['COVERBAND_REDIS_URL']`, `ENV['REDIS_URL']`, or `localhost`
237
284
 
238
285
  Below is an example config file for a Rails 5 app:
239
286
 
@@ -358,6 +405,61 @@ For each tracked key, the Query Burst Tracker reports:
358
405
 
359
406
  Tip: use this tracker in production-like traffic, where query patterns are most realistic.
360
407
 
408
+ ### Writing a Custom Tracker
409
+
410
+ Trackers can be shipped by an application or a separate gem without changing Coverband. A tracker subclasses `Coverband::Collectors::AbstractTracker`, registers itself once when its file is required, and is initialized at Rails boot when its enablement block returns true.
411
+
412
+ ```ruby
413
+ # lib/my_app/coverband/feature_flag_tracker.rb
414
+ class FeatureFlagTracker < Coverband::Collectors::AbstractTracker
415
+ # required: the path this tracker reports on inside the Coverband web report,
416
+ # and the label for its tab
417
+ REPORT_ROUTE = "feature_flag_tracker"
418
+ TITLE = "Feature Flags"
419
+
420
+ def railtie!
421
+ tracker = self
422
+ ActiveSupport::Notifications.subscribe("feature_flag.my_app") do |_name, _start, _finish, _id, payload|
423
+ tracker.track_key(payload.fetch(:key))
424
+ end
425
+ end
426
+
427
+ private
428
+
429
+ def concrete_target
430
+ MyApp::FeatureFlags.all.map(&:key)
431
+ end
432
+ end
433
+
434
+ Coverband::Configuration.add_tracker_flag(:track_feature_flags, default: false)
435
+ Coverband::Collectors::TrackerRegistry.register(
436
+ :feature_flag_tracker,
437
+ tracker_class: FeatureFlagTracker,
438
+ enabled: ->(config) { config.track_feature_flags }
439
+ )
440
+ ```
441
+
442
+ Require the tracker before enabling it in `config/coverband.rb`:
443
+
444
+ ```ruby
445
+ require "my_app/coverband/feature_flag_tracker"
446
+
447
+ Coverband.configure do |config|
448
+ config.track_feature_flags = true
449
+ end
450
+ ```
451
+
452
+ Every tracker must declare its own `REPORT_ROUTE`. `AbstractTracker`'s default of `"/"` matches every path in the web report, so a tracker that inherits it would answer all of Coverband's own report routes. Registering such a tracker raises `ArgumentError` rather than letting it take over the UI.
453
+
454
+ `tracker_class` may also be a callable that returns a tracker instance when initialization needs to choose an implementation. Registration and enablement are evaluated at boot, not on every request. Duplicate tracker names raise `ArgumentError` so load-order mistakes fail early. Coverband's Query Burst Tracker is a bundled example of the same registration pattern.
455
+
456
+ Trackers registered this way have no dedicated `Coverband.configuration` accessor, so reach them by their registered name:
457
+
458
+ ```ruby
459
+ Coverband.configuration.tracker_for(:feature_flag_tracker)
460
+ Coverband.track_key(:feature_flag_tracker, "flags.new_checkout")
461
+ ```
462
+
361
463
  ### Hiding settings
362
464
 
363
465
  Coverband provides a view of all of its current settings. Sometimes you might want to hide this view,
@@ -376,6 +478,14 @@ The web report includes a client-side "dead-code session" toolbar to help drive
376
478
 
377
479
  Session state lives in your browser's localStorage: "Reset session" clears the hidden set; "Start over" (in the modal) clears the marked list.
378
480
 
481
+ ### Dark mode
482
+
483
+ The web report follows your operating system's appearance setting through the CSS `prefers-color-scheme` media query. There is nothing to configure and no toggle in the UI: if your desktop is in dark mode, the report is dark, and it switches back when your desktop does. Printing always uses the light theme.
484
+
485
+ The coverage row colors (covered / missed / never / skipped) are chosen for contrast on the dark surface rather than darkened mechanically, so the states stay as easy to tell apart as in the light theme.
486
+
487
+ If you restyle the report, every color in `public/application.css` resolves through a custom property defined on `:root`, and the dark theme is a single `@media (prefers-color-scheme: dark)` block that redefines those properties. Overriding a property is enough to change both themes from one place.
488
+
379
489
  ### Fixing Coverage Only Shows Loading Hits
380
490
 
381
491
  If all your coverage is being counted as loading or eager_loading coverage, and nothing is showing as runtime Coverage the initialization hook failed for some reason. The most likely reason for this issue is manually calling `eager_load!` on some Plugin/Gem. If you or a plugin is altering the Rails initialization process, you can manually flip Coverband to runtime coverage by calling these two lines, in an `after_initialize` block, in `application.rb`.
@@ -441,6 +551,55 @@ Coverband on very high volume sites with many server processes reporting can hav
441
551
 
442
552
  See more discussion [here](https://github.com/danmayer/coverband/issues/384).
443
553
 
554
+ ### ActiveSupport::Cache Store
555
+
556
+ Coverband can store coverage in any `ActiveSupport::Cache::Store`, which covers
557
+ Redis, Memcached, files, and — through [Solid Cache](https://github.com/rails/solid_cache) —
558
+ Postgres, MySQL, and SQLite.
559
+
560
+ ```ruby
561
+ # Rails.cache does not exist yet when config/coverband.rb loads, so pass a block
562
+ config.store = Coverband::Adapters::ActiveSupportCacheStore.new { Rails.cache }
563
+
564
+ # or hand it a store directly
565
+ config.store = Coverband::Adapters::ActiveSupportCacheStore.new(
566
+ ActiveSupport::Cache::MemCacheStore.new("localhost:11211")
567
+ )
568
+ ```
569
+
570
+ The block form is the one to use with Solid Cache: its models come from a Rails
571
+ engine that has not loaded when `config/coverband.rb` runs, so building a
572
+ `SolidCache::Store` there raises `uninitialized constant
573
+ SolidCache::Store::Entries::Entry`.
574
+
575
+ Coverband starts from `before_configuration`, which is earlier than Rails
576
+ assigns `Rails.cache`, so the first reporting cycle can run before the block can
577
+ resolve. That cycle's coverage is held and written once the cache appears; it is
578
+ not lost, and it does not raise into the booting app.
579
+
580
+ This adapter supports the view, route, translation, and query burst trackers.
581
+ It replaces `MemcachedStore`, which was removed in 7.0 (see
582
+ [Upgrading to 7.0](#upgrading-to-70)).
583
+
584
+ Things to know before pointing it at a cache:
585
+
586
+ - **A cache may evict what Coverband writes.** Use a store that will not expire
587
+ the data: a dedicated Solid Cache database with `max_age: nil` plus a size
588
+ limit, or a Redis instance with no eviction policy. Coverband detects the loss
589
+ and reports it in the web UI when it can, but it cannot prevent it.
590
+ - **Memcached's 1MB value limit applies to every document**, trackers included.
591
+ A large translation or view tracker can exceed it, in which case the write is
592
+ refused and Coverband logs what could not be stored. Raise the slab limit
593
+ (`-I`) or use Redis/Solid Cache for large apps.
594
+ - **`ActiveSupport::Cache::MemoryStore` is per process**, so it is only useful
595
+ for development and tests.
596
+ - **Solid Cache** writes go through ActiveRecord, so size your connection pool
597
+ for one extra connection per process, and prefer a separate cache database so
598
+ Coverband's writes do not evict your application's own cached data.
599
+ - For very large multi-process deployments `Coverband::Adapters::HashRedisStore`
600
+ is still the best option: it avoids write conflicts entirely rather than
601
+ repairing them.
602
+
444
603
  ### Clear Coverage
445
604
 
446
605
  Now that Coverband uses MD5 hashes there should be no reason to manually clear coverage unless one is testing, changing versions, or possibly debugging Coverband itself.
@@ -452,6 +611,28 @@ This will clear both the coverage and trackers data. It can also be done through
452
611
  To clear only coverage data, run `rake coverband:clear_coverage`.
453
612
  To clear only trackers data, run `rake coverband:clear_tracker`.
454
613
 
614
+ Redis-backed stores also support two maintenance tasks:
615
+
616
+ - `rake coverband:clear_legacy` removes Coverband-owned keys from storage
617
+ formats retired by the 7.0 upgrade.
618
+ - `rake coverband:clear_orphans` removes old generation documents that no
619
+ pointer references, after rechecking that each candidate is not live or too
620
+ young to delete safely.
621
+
622
+ In Coverband 7.0, adapters no longer expose their backend through `raw_store`.
623
+ Code that needs these supported maintenance operations can request the narrow
624
+ capability instead:
625
+
626
+ ```ruby
627
+ cleanup = Coverband.configuration.store.cleanup
628
+ cleanup&.clear_legacy!
629
+ cleanup&.clear_orphans!
630
+ ```
631
+
632
+ `RedisStore` and `HashRedisStore` provide this capability. Adapters that cannot
633
+ enumerate their keys return `nil`; clear the backing cache or rely on its expiry
634
+ policy for those stores.
635
+
455
636
  ### Adding Rake Tasks outside of Rails
456
637
 
457
638
  Rails apps should automatically include the tasks via the Railtie.
@@ -550,6 +731,28 @@ The MCP server provides the following tools:
550
731
  | `get_view_tracker_data` | Get view/template usage data |
551
732
  | `get_route_tracker_data` | Get route usage statistics |
552
733
  | `get_translation_tracker_data` | Get translation key usage data |
734
+ | `get_storage_health` | Get observed coverage and tracker storage protocol state |
735
+
736
+ #### Storage health
737
+
738
+ `Coverband.storage_health` returns normalized diagnostics for coverage and every initialized
739
+ tracker. The normal JSON report also includes this value as its top-level `storage_health`
740
+ field, and MCP exposes the same representation through `get_storage_health`:
741
+
742
+ ```ruby
743
+ Coverband.storage_health
744
+ # => {
745
+ # coverage: {status: "ok", data_loss: nil, unwritten: nil},
746
+ # trackers: {view_tracker: {status: "stalled", data_loss: nil,
747
+ # unwritten: {deltas: 3, since: "2026-08-19T12:01:00Z"}}}
748
+ # }
749
+ ```
750
+
751
+ Statuses are `ok`, `stalled`, `data_loss`, or `unsupported`; `data_loss` takes precedence
752
+ when both loss and unwritten work are present. These diagnostics describe state Coverband's
753
+ storage protocol has already observed. They are not an active backend availability or
754
+ connectivity probe. SimpleCov-compatible JSON generated with `for_merged_report` does not
755
+ include this metadata.
553
756
 
554
757
  #### Running the MCP Server
555
758
 
@@ -673,7 +876,7 @@ Outputs:
673
876
  # Prerequisites
674
877
 
675
878
  - Ruby 3.1+
676
- - Coverband currently requires Redis for production usage
879
+ - A production store: Redis by default (add the `redis` gem), or any `ActiveSupport::Cache` store such as Solid Cache or memcached
677
880
 
678
881
  ### Ruby and Rails Version Support
679
882
 
@@ -721,13 +924,16 @@ If you submit a change please make sure the tests and benchmarks are passing.
721
924
  - **total fail** on front end code, for line for line coverage, because of the precompiled template step basically coverage doesn't work well for `erb`, `slim`, and the like.
722
925
  - related it will try to report something, but the line numbers reported for `ERB` files are often off and aren't considered useful. I recommend filtering out .erb using the `config.ignore` option. The default configuration excludes these files
723
926
  - **NOTE:** We now have file level coverage for view files, but don't support line level detail
724
- - **Coverage does NOT work when used alongside Scout APM Auto Instrumentation**
725
- - In an environment that uses Scout's `AUTO_INSTRUMENT=true` (usually production or staging) it stops reporting any coverage, it will show one or two files that have been loaded at the start but everything else will show up as having 0% coverage
927
+ - **Scout APM Auto Instrumentation** — fixed in Ruby 3.4.10 / 4.0.4, broken on earlier Rubies
928
+ - **Symptom:** in an environment that uses Scout's auto instruments (usually production or staging) Coverband stops reporting coverage. You will see one or two files loaded at the start, and everything else shows up as 0% / never loaded. This is dangerous: live code, particularly your controllers, gets reported as dead code.
929
+ - **Cause:** this is a Ruby bug, not a bug in Coverband or Scout. Scout's auto instrumentation installs a `RubyVM::InstructionSequence.load_iseq` hook and loads your files through `RubyVM::InstructionSequence.compile` / `.compile_file`. Until recently those methods did not register a file with the `Coverage` module the way a normal `require` does, so the file was never measured and never even appeared in `Coverage.result`. Note that this affected *every* file loaded while the hook was installed, not just the controllers Scout rewrites, which is why coverage appears to vanish entirely.
930
+ - **Fix:** upgrade to **Ruby >= 3.4.10 or >= 4.0.4**, which include the fix for [Ruby Bug #22018](https://bugs.ruby-lang.org/issues/22018). Coverband and Scout auto instruments then work together with no configuration changes and no minimum version of either gem. Both `lines` and `oneshot_lines` collection modes are unaffected.
931
+ - **Workaround for Ruby <= 3.3:** the fix was not backported to Ruby 3.1, 3.2, or 3.3, so on those versions you must disable auto instruments to collect coverage — set `auto_instruments: false` in `scout_apm.yml` or `SCOUT_AUTO_INSTRUMENTS=false` in the environment.
726
932
  - Bug tracked here: https://github.com/scoutapp/scout_apm_ruby/issues/343
727
933
  - **Coverband, [Elastic APM](https://github.com/elastic/apm-agent-ruby) and resque**
728
934
  - In an environment that uses the Elastic APM ruby agent, resque jobs will fail with `Transactions may not be nested. Already inside #<ElasticAPM::Transaction>` if the `elastic-apm` gem is loaded _before_ the `coverband` gem
729
935
  - Put `coverage` ahead of `elastic-apm` in your Gemfile
730
- - **Bootsnap**, The methods used by [bootsnap do not support having coverage enabled](https://github.com/Shopify/bootsnap/blob/main/lib/bootsnap/compile_cache/iseq.rb#L87), so you can either have Coverband or bootsnap, but not both.
936
+ - **Bootsnap**, you can run bootsnap alongside Coverband, but you lose part of the boot time speedup. [Bootsnap detects that coverage is enabled](https://github.com/Shopify/bootsnap/blob/main/lib/bootsnap/compile_cache/iseq.rb) and disables its `ISeq` compile cache, because a cached instruction sequence would not carry the `Coverage` instrumentation. Bootsnap's other caches (load path, YAML, JSON) are unaffected and keep working.
731
937
 
732
938
  ### Debugging Redis Store
733
939
 
data/Rakefile CHANGED
@@ -21,6 +21,22 @@ Rake::TestTask.new(:test) do |test|
21
21
  test.verbose = true
22
22
  end
23
23
 
24
+ ###
25
+ # Solid Cache brings a Rails engine with it, which changes how the rest of the
26
+ # suite behaves in the same process. It gets its own task and its own process.
27
+ ###
28
+ desc "run the Solid Cache backed adapter tests (SQLite, needs solid_cache)"
29
+ task :"test:solid_cache" do
30
+ # BUNDLE_WITH opts the optional group onto the load path for this run only.
31
+ # It has to already be installed: `bundle install --with solid_cache`.
32
+ sh({"COVERBAND_SOLID_CACHE" => "true", "BUNDLE_WITH" => "solid_cache"},
33
+ "bundle exec ruby -Ilib -Itest test/coverband/adapters/solid_cache_store_test.rb") do |ok, _res|
34
+ unless ok
35
+ abort "solid_cache tests failed. If the gems are missing, run: bundle install --with solid_cache"
36
+ end
37
+ end
38
+ end
39
+
24
40
  desc "run the web report's javascript unit tests (node --test)"
25
41
  task :"test:javascript" do
26
42
  # Pass expanded file paths rather than the bare directory: node's
@@ -51,8 +67,3 @@ desc "start webserver"
51
67
  task :server do
52
68
  exec "rackup -I lib"
53
69
  end
54
-
55
- desc "publish gem with 2 factor auth, reminder how"
56
- task :publish_gem do
57
- exec "gem push pkg/coverband-4.2.3.XXX.gem"
58
- end
data/changes.md CHANGED
@@ -1,3 +1,62 @@
1
+ ### 7.0.0
2
+
3
+ **Breaking changes**
4
+
5
+ * **Upgrading from 6.x? Follow [Upgrading to 7.0](https://github.com/danmayer/coverband#upgrading-to-70) in the README**, which lists what to change for each item below. Removed APIs are removed outright rather than deprecated first.
6
+ * **The `redis` gem is no longer a dependency of Coverband. If you use Redis (the default), add `gem "redis"` to your Gemfile when you upgrade.** Redis is still the default store and still needs no other configuration: whenever the `redis` gem is installed Coverband loads it and stores coverage there exactly as before. Apps that only got `redis` through Coverband will otherwise boot with a warning telling them to add it, and record nothing until they do. Apps that store coverage elsewhere (`ActiveSupportCacheStore` with Solid Cache, memcached, or files) no longer need Redis installed at all
7
+ * **Coverage and tracker history do not survive this upgrade.** Stored documents now carry the metadata the new merge protocol needs, and keys are scoped by a generation token, so the storage format version changed for both `RedisStore` and the cache adapters. Old keys are ignored rather than migrated, and coverage accumulates fresh from the upgrade. Delete the old `coverband_3_2.*` keys once you are happy with the new data.
8
+ * **Reporting to coverband.io (`WebServiceStore`) has been removed, as the hosted service is no longer maintained.** This also removes `ViewTrackerService`, loading `config/coverband_service.rb`, the `api_key`, `service_url`, `coverband_timeout`, `service_dev_mode`, `service_test_mode`, and `process_type` settings, and the `COVERBAND_API_KEY`, `COVERBAND_URL`, `COVERBAND_ENABLE_DEV_MODE`, `COVERBAND_ENABLE_TEST_MODE`, and `PROCESS_TYPE` environment variables. Setting `COVERBAND_API_KEY` no longer switches Coverband into service mode. Move settings into `config/coverband.rb` and configure Redis or an `ActiveSupportCacheStore`
9
+ * Removed configuration settings that no longer had production consumers: `reporter` (no effect since 5.0), `s3_region`, `s3_bucket`, `s3_access_key_id`, `s3_secret_access_key`, `track_gems`, `gem_details`, and `simulate_oneshot_lines_coverage`, plus the coverband.io settings above. Assigning any of them logs a warning naming the setting and how to fix it, and is otherwise ignored, so an old `config/coverband.rb` still boots.
10
+ * `Coverband::Utils::SourceFile` and `Coverband::Utils::Result` now accept only enriched file records from stored Coverband reports. Passing the legacy bare line-coverage Array is no longer supported.
11
+ * **`Coverband::Adapters::MemcachedStore` has been removed.** Use `ActiveSupportCacheStore.new(cache, cache_namespace: "...")` with the same memcached cache; it also supports the trackers. `ActiveSupportCacheStore` no longer accepts `memcached_namespace`
12
+ * **`background_reporting_sleep_seconds` now defaults to 600** for the new cache adapter (`HashRedisStore` still defaults to 300, everything else to 60), because each report rewrites a whole document.
13
+ * `Adapters::Base#size` now returns bytes as an Integer or `nil`; `size_in_mib` owns the "N/A" presentation. A store returning the string `"N/A"` previously reported `0.00` MiB.
14
+ * `Adapters::Base#get_coverage_report` now reports the receiver's coverage instead of routing through `Coverband.configuration.store`, which quietly returned another store's data when the receiver was not the configured one.
15
+ * Removed `AbstractTracker#redis_store`; custom trackers use `#storage`. Trackers no longer reach through the store to raw Redis commands.
16
+ * Removed the misspelled `Coverband::Utils::Results#runtime_relavent_lines`; use `#runtime_relevant_lines` instead.
17
+ * Removed `Adapters::Base#raw_store` and the Redis adapters' raw-client escape hatch. Redis maintenance now uses the narrow `store.cleanup` capability (`clear_legacy!` and `clear_orphans!`); adapters that cannot enumerate keys return `nil`.
18
+ * Work is retained when generation resolution fails, so a backend blip during a reporting cycle no longer discards that cycle's coverage. Previously only deltas that had reached the pending queue were protected, and the generation is resolved before the payload is enqueued. The retention caps apply to that held work too, so a sustained outage cannot grow the queue without limit
19
+ * A cache store that is not resolvable yet (`ActiveSupportCacheStore.new { Rails.cache }` running before Rails assigns `Rails.cache`) is no longer a `NoMethodError` on a nil target. It holds the cycle's work, logs, and resolves on a later cycle. A resolver returning something that is not a cache store now names the problem instead of failing deep in the storage layer
20
+ * Rake tasks that reach the store (`coverband:clear`, `clear_coverage`, `clear_tracker`, `clear_legacy`, `clear_orphans`, `coverage`, `coverage_json`, `coverage_html`) load the Rails environment first. With a lazily configured store they previously failed with an unavailable cache unless `rake environment` was named explicitly
21
+ * Coverage data loss (eviction, dropped pending deltas) is now surfaced on the web report index. It was logged and shown on the tracker tabs, but the coverage index reported partial numbers as though they were complete
22
+ * `Adapters::SessionCoverage#save_report` returns the protocol state from `Session#record` rather than `nil`, so a caller can tell a write that landed from one that was deferred or refused
23
+ * Coverage now absorbs the two-cycle outage the design commits to. The cap bounds the queue, and the cycle that recovers takes a slot in it, so a cap of 2 only ever absorbed one; it is now 3
24
+ * Data loss from dropping a delta that was already written and merely awaiting confirmation is reported as `unconfirmed_dropped` rather than `pending_dropped`, and the web report says the numbers may be undercounted instead of claiming earlier results are unavailable. Only the second kind is lost coverage
25
+ * Trackers keep their own keys whenever a report is not stored (`:failed`, `:unavailable`, or an error), so an unreachable backend is no longer lossier for them than one that raises. `TrackerStorage::Base#retains_pending?` is removed; the decision comes from what `record` returns
26
+ * A write refused or errored *after* the delta was enqueued now reports `:retained` rather than `:failed`, since the work is in the storage queue and a caller holding a second copy would replay it. This is the documented memcached >1MB path, where it double counted `QueryBurstTracker`
27
+ * `QueryBurstTracker` reports on a quiet cycle when storage is holding work it could not write, instead of waiting for the next burst to flush it
28
+ * A document that cannot be written is now reported while it is happening, rather than an hour later. Its retention caps never fire on a quiet tracker -- nothing new arrives for them to drop -- so only the absolute age cap eventually turned a permanently refused write (a tracker document past memcached's 1MB limit, for instance) into a loss event, and until then the tab read "0 used" exactly like an app that used none. The web report now says how many reports could not be stored and since when
29
+ * Presence trackers (views, routes, translations) re-supply their known keys when storage reports work dropped at its retention caps, so a backend outage longer than the queue no longer leaves a used view reading as unused. Their merge is idempotent, so this cannot double count; `QueryBurstTracker` sums, so its loss stays irreducible and reported
30
+ * `save_report` no longer mutates the report it is given. The merge used to sum stored counts back into the caller's line arrays, so a caller that reused its report object re-submitted counts that were already recorded.
31
+
32
+ **Features**
33
+
34
+ * Replaced the generation lifecycle mixin and its implicit instance-variable/hook contract with a state-owning coordinator and typed generation-change events (#656)
35
+ * Added structured storage health through `Coverband.storage_health`, normal JSON reports, and the MCP `get_storage_health` tool, so monitoring can distinguish healthy, stalled, data-loss, and unsupported diagnostic states (#658)
36
+ * Added `Coverband::Adapters::ActiveSupportCacheStore`, which stores coverage in any `ActiveSupport::Cache::Store` — Redis, Memcached, files, or Solid Cache for Postgres/MySQL/SQLite. The cache target can be passed lazily (`ActiveSupportCacheStore.new { Rails.cache }`) so it works from `config/coverband.rb`, where `Rails.cache` does not exist yet (#533)
37
+ * Trackers (views, routes, translations, query bursts) now work on non-Redis stores. They previously reached through the store to raw Redis commands, so file and memcached users got trackers that silently collected nothing and empty web UI tabs
38
+ * Fixed the read-modify-write conflict Coverband has always had on `RedisStore`: concurrent reports could silently drop one process's contribution. Reports are now applied under a per-writer sequence, so a conflicting write is detected and repaired on the next cycle without double counting. `QueryBurstTracker` had the same conflict on its cumulative counters and is fixed the same way
39
+ * Resets are now strong: `clear!` and `reset_recordings` retire a whole generation key, so a straggling write from before the reset cannot undo it
40
+ * Coverband now detects when a backend has dropped its data (cache eviction, `Rails.cache.clear`) and reports it in the web UI rather than silently showing partial numbers
41
+ * Trackers are skipped with an explanatory log line when the configured store cannot support them, instead of collecting nothing
42
+ * Query bursts no longer count Coverband's own storage queries against the surrounding request, which would otherwise inflate query counts on a database backed cache
43
+ * Added `persistent_coverage?`, `supports_trackers?`, and `supports_paged_reports?` capability predicates on adapters, plus `file_count` and `cached_file_count` defaults so non-Redis stores no longer raise from the web UI's datatables path
44
+ * `redis_ttl` applies to stored coverage documents again; generation pointers deliberately never expire, since a pointer that outlived its document would leave live coverage unreachable
45
+ * Storage reads degrade to empty and log when the backend is unavailable (an unreachable cache, or a Solid Cache table that has not been created yet) rather than raising into the request rendering the report; write failures still reach the reporting paths that log them
46
+ * Added `rake test:solid_cache`, an ENV gated SQLite backed Solid Cache suite covering atomic pointer creation, batched pointer reads, ActiveRecord being unavailable, a missing Solid Cache schema, and Coverband's own SQL not being attributed to the request that triggered the report
47
+ * Added `rake coverband:clear_orphans` to reclaim generation keys no longer referenced by a pointer, and scoped `coverband:clear_legacy` to Coverband's own namespaces and tracker names so it cannot delete unrelated application keys
48
+ * CI now runs a memcached service, so the memcached backed adapter is actually exercised
49
+ * Coverage adapters share one implementation of the merge protocol, and the generation lifecycle is shared between the session and the Redis hash repository, rather than each reimplementing the same bookkeeping
50
+ * The web report has a dark theme. It follows the operating system through `prefers-color-scheme`, so there is nothing to configure and no toggle; every color in `public/application.css` now resolves through a custom property on `:root`, which makes the report restylable from one place. Coverage row colors are picked for contrast on the dark surface rather than darkened mechanically, so covered / missed / never / skipped stay as easy to tell apart; thanks @naofumi-fujii (#665, #666, #667)
51
+ * Added `Coverband::Collectors::TrackerRegistry` so applications and gems can register their own trackers, along with `Coverband::Configuration.add_tracker_flag` for their enablement flags and `Coverband.configuration.tracker_for` / `Coverband.track_key` support for trackers without a dedicated accessor; bundled trackers now self-register and are initialized generically at Rails boot (#651)
52
+
53
+ **Bug fixes**
54
+
55
+ * The web report's `/report_json` route and `JSONReport.new(store, as_report: true)` no longer raise `NoMethodError` outside Rails, where ActiveSupport's `as_json` is not loaded; thanks @dajiaohuang (#669)
56
+ * The web report's password check accepts passwords containing `:` (it split the credentials on every colon) and compares in constant time, as does the MCP endpoint's bearer token check. This also drops the runtime dependency on the `base64` gem
57
+ * `Coverband::Utils::Results#respond_to?(name, true)` no longer raises `ArgumentError`
58
+ * The runtime % no longer reads 100% for files with runtime relevant lines that were never hit, and no longer divides by every line of a file that was never eager loaded. It only counts runtime hits on lines that eager loading did not cover, and takes line relevance from real coverage data rather than the never loaded placeholder, which marked blank and comment lines as relevant. Runtime relevant lines on the file page use the same calculation, and the runtime % there is colored by its own value rather than by % covered (#668)
59
+
1
60
  ### 6.2.2
2
61
 
3
62
  * Feature: Added a dead-code review session to the web report — hide files/folders by hovering any path segment, mark files for deletion with an optional comment, and export marked entries to CSV to drive an out-of-band code-removal process; session state lives in browser localStorage; thanks @obie (#647, #649)