coverband 6.1.5 → 6.1.7
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/.github/workflows/main.yml +4 -4
- data/README.md +123 -0
- data/agents.md +217 -0
- data/bin/coverband-mcp +42 -0
- data/changes.md +23 -0
- data/coverband/log.272267 +1 -0
- data/coverband.gemspec +3 -1
- data/lib/coverband/adapters/hash_redis_store.rb +1 -3
- data/lib/coverband/collectors/route_tracker.rb +1 -1
- data/lib/coverband/collectors/view_tracker.rb +21 -13
- data/lib/coverband/configuration.rb +57 -18
- data/lib/coverband/integrations/resque.rb +2 -2
- data/lib/coverband/mcp/http_handler.rb +118 -0
- data/lib/coverband/mcp/server.rb +116 -0
- data/lib/coverband/mcp/tools/get_coverage_summary.rb +41 -0
- data/lib/coverband/mcp/tools/get_dead_methods.rb +69 -0
- data/lib/coverband/mcp/tools/get_file_coverage.rb +72 -0
- data/lib/coverband/mcp/tools/get_route_tracker_data.rb +60 -0
- data/lib/coverband/mcp/tools/get_translation_tracker_data.rb +60 -0
- data/lib/coverband/mcp/tools/get_uncovered_files.rb +73 -0
- data/lib/coverband/mcp/tools/get_view_tracker_data.rb +60 -0
- data/lib/coverband/mcp.rb +27 -0
- data/lib/coverband/reporters/base.rb +2 -4
- data/lib/coverband/reporters/web.rb +17 -14
- data/lib/coverband/utils/lines_classifier.rb +1 -1
- data/lib/coverband/utils/railtie.rb +1 -1
- data/lib/coverband/utils/result.rb +2 -1
- data/lib/coverband/utils/source_file.rb +5 -5
- data/lib/coverband/utils/tasks.rb +31 -0
- data/lib/coverband/version.rb +1 -1
- data/lib/coverband.rb +7 -7
- data/test/benchmarks/benchmark.rake +7 -15
- data/test/coverband/file_store_integration_test.rb +72 -0
- data/test/coverband/file_store_redis_error_test.rb +56 -0
- data/test/coverband/github_issue_586_test.rb +46 -0
- data/test/coverband/initialization_timing_test.rb +71 -0
- data/test/coverband/mcp/http_handler_test.rb +159 -0
- data/test/coverband/mcp/security_test.rb +145 -0
- data/test/coverband/mcp/server_test.rb +125 -0
- data/test/coverband/mcp/tools/get_coverage_summary_test.rb +75 -0
- data/test/coverband/mcp/tools/get_dead_methods_test.rb +162 -0
- data/test/coverband/mcp/tools/get_file_coverage_test.rb +159 -0
- data/test/coverband/mcp/tools/get_route_tracker_data_test.rb +122 -0
- data/test/coverband/mcp/tools/get_translation_tracker_data_test.rb +122 -0
- data/test/coverband/mcp/tools/get_uncovered_files_test.rb +177 -0
- data/test/coverband/mcp/tools/get_view_tracker_data_test.rb +122 -0
- data/test/coverband/reporters/web_test.rb +5 -0
- data/test/coverband/track_key_test.rb +9 -9
- data/test/coverband/tracker_initialization_test.rb +75 -0
- data/test/coverband/user_environment_simulation_test.rb +75 -0
- data/test/coverband/utils/lines_classifier_test.rb +1 -1
- data/test/integration/mcp_integration_test.rb +175 -0
- data/test/test_helper.rb +4 -5
- metadata +67 -11
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coverband
|
|
4
|
+
module MCP
|
|
5
|
+
module Tools
|
|
6
|
+
class GetUncoveredFiles < ::MCP::Tool
|
|
7
|
+
description "Get files with coverage below a specified threshold. " \
|
|
8
|
+
"Useful for finding code that may need more production testing or could be dead code."
|
|
9
|
+
|
|
10
|
+
input_schema(
|
|
11
|
+
properties: {
|
|
12
|
+
threshold: {
|
|
13
|
+
type: "number",
|
|
14
|
+
description: "Coverage percentage threshold (default: 50). Files below this are returned."
|
|
15
|
+
},
|
|
16
|
+
include_never_loaded: {
|
|
17
|
+
type: "boolean",
|
|
18
|
+
description: "Include files that were never loaded in production (default: true)"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
def self.call(server_context:, threshold: 50, include_never_loaded: true, **)
|
|
24
|
+
store = Coverband.configuration.store
|
|
25
|
+
report = Coverband::Reporters::JSONReport.new(store, line_coverage: false)
|
|
26
|
+
data = JSON.parse(report.report)
|
|
27
|
+
|
|
28
|
+
files = data["files"] || {}
|
|
29
|
+
|
|
30
|
+
uncovered = files.select do |_path, file_data|
|
|
31
|
+
percent = file_data["covered_percent"] || 0
|
|
32
|
+
never_loaded = file_data["never_loaded"]
|
|
33
|
+
|
|
34
|
+
if include_never_loaded
|
|
35
|
+
percent < threshold || never_loaded
|
|
36
|
+
else
|
|
37
|
+
percent < threshold && !never_loaded
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Sort by coverage percentage ascending (least covered first)
|
|
42
|
+
sorted = uncovered.sort_by { |_path, data| data["covered_percent"] || 0 }
|
|
43
|
+
|
|
44
|
+
result = sorted.map do |path, file_data|
|
|
45
|
+
{
|
|
46
|
+
file: path,
|
|
47
|
+
covered_percent: file_data["covered_percent"],
|
|
48
|
+
lines_of_code: file_data["lines_of_code"],
|
|
49
|
+
lines_covered: file_data["lines_covered"],
|
|
50
|
+
lines_missed: file_data["lines_missed"],
|
|
51
|
+
never_loaded: file_data["never_loaded"]
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
::MCP::Tool::Response.new([{
|
|
56
|
+
type: "text",
|
|
57
|
+
text: JSON.pretty_generate({
|
|
58
|
+
threshold: threshold,
|
|
59
|
+
include_never_loaded: include_never_loaded,
|
|
60
|
+
total_uncovered_files: result.length,
|
|
61
|
+
files: result
|
|
62
|
+
})
|
|
63
|
+
}])
|
|
64
|
+
rescue => e
|
|
65
|
+
::MCP::Tool::Response.new([{
|
|
66
|
+
type: "text",
|
|
67
|
+
text: "Error getting uncovered files: #{e.message}"
|
|
68
|
+
}])
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coverband
|
|
4
|
+
module MCP
|
|
5
|
+
module Tools
|
|
6
|
+
class GetViewTrackerData < ::MCP::Tool
|
|
7
|
+
description "Get Rails view template usage tracking data. Shows which view templates " \
|
|
8
|
+
"have been rendered in production and which have never been accessed."
|
|
9
|
+
|
|
10
|
+
input_schema(
|
|
11
|
+
properties: {
|
|
12
|
+
show_unused_only: {
|
|
13
|
+
type: "boolean",
|
|
14
|
+
description: "Only return unused views (default: false)"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
def self.call(server_context:, show_unused_only: false, **)
|
|
20
|
+
tracker = Coverband.configuration.view_tracker
|
|
21
|
+
|
|
22
|
+
unless tracker
|
|
23
|
+
return ::MCP::Tool::Response.new([{
|
|
24
|
+
type: "text",
|
|
25
|
+
text: "View tracking is not enabled. Enable it with `config.track_views = true` in your coverband configuration."
|
|
26
|
+
}])
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
data = JSON.parse(tracker.as_json)
|
|
30
|
+
|
|
31
|
+
result = if show_unused_only
|
|
32
|
+
{
|
|
33
|
+
tracking_since: tracker.tracking_since,
|
|
34
|
+
unused_views: data["unused_keys"] || [],
|
|
35
|
+
total_unused: data["unused_keys"]&.length || 0
|
|
36
|
+
}
|
|
37
|
+
else
|
|
38
|
+
{
|
|
39
|
+
tracking_since: tracker.tracking_since,
|
|
40
|
+
used_views: data["used_keys"] || [],
|
|
41
|
+
unused_views: data["unused_keys"] || [],
|
|
42
|
+
total_used: data["used_keys"]&.length || 0,
|
|
43
|
+
total_unused: data["unused_keys"]&.length || 0
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
::MCP::Tool::Response.new([{
|
|
48
|
+
type: "text",
|
|
49
|
+
text: JSON.pretty_generate(result)
|
|
50
|
+
}])
|
|
51
|
+
rescue => e
|
|
52
|
+
::MCP::Tool::Response.new([{
|
|
53
|
+
type: "text",
|
|
54
|
+
text: "Error getting view tracker data: #{e.message}"
|
|
55
|
+
}])
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require "mcp"
|
|
5
|
+
rescue LoadError
|
|
6
|
+
raise LoadError, <<~MSG
|
|
7
|
+
The 'mcp' gem is required for MCP server support.
|
|
8
|
+
Add `gem 'mcp'` to your Gemfile and run `bundle install`.
|
|
9
|
+
MSG
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
require "coverband"
|
|
13
|
+
require "coverband/utils/html_formatter"
|
|
14
|
+
require "coverband/utils/result"
|
|
15
|
+
require "coverband/utils/file_list"
|
|
16
|
+
require "coverband/utils/source_file"
|
|
17
|
+
require "coverband/utils/lines_classifier"
|
|
18
|
+
require "coverband/utils/results"
|
|
19
|
+
require "coverband/reporters/json_report"
|
|
20
|
+
|
|
21
|
+
# Load dead methods support if available
|
|
22
|
+
if defined?(RubyVM::AbstractSyntaxTree)
|
|
23
|
+
require "coverband/utils/dead_methods"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
require_relative "mcp/server"
|
|
27
|
+
require_relative "mcp/http_handler"
|
|
@@ -55,13 +55,11 @@ module Coverband
|
|
|
55
55
|
fixed_report[name] = {}
|
|
56
56
|
report.each_pair do |key, vals|
|
|
57
57
|
filename = Coverband::Utils::AbsoluteFileConverter.convert(key)
|
|
58
|
-
|
|
58
|
+
if fixed_report[name].key?(filename) && fixed_report[name][filename][DATA_KEY] && vals[DATA_KEY]
|
|
59
59
|
merged_data = merge_arrays(fixed_report[name][filename][DATA_KEY], vals[DATA_KEY])
|
|
60
60
|
vals[DATA_KEY] = merged_data
|
|
61
|
-
vals
|
|
62
|
-
else
|
|
63
|
-
vals
|
|
64
61
|
end
|
|
62
|
+
fixed_report[name][filename] = vals
|
|
65
63
|
end
|
|
66
64
|
end
|
|
67
65
|
end
|
|
@@ -62,9 +62,9 @@ module Coverband
|
|
|
62
62
|
Coverband.configuration.trackers.each do |tracker|
|
|
63
63
|
if request_path_info.match(tracker.class::REPORT_ROUTE)
|
|
64
64
|
tracker_route = true
|
|
65
|
-
if request_path_info =~ %r{
|
|
65
|
+
if request_path_info =~ %r{/clear_.*_key}
|
|
66
66
|
return clear_abstract_tracking_key(tracker)
|
|
67
|
-
elsif request_path_info =~ %r{
|
|
67
|
+
elsif request_path_info =~ %r{/clear_.*}
|
|
68
68
|
return clear_abstract_tracking(tracker)
|
|
69
69
|
else
|
|
70
70
|
return [200, {"content-type" => "text/html"}, [display_abstract_tracker(tracker)]]
|
|
@@ -75,9 +75,9 @@ module Coverband
|
|
|
75
75
|
unless tracker_route
|
|
76
76
|
if request.post?
|
|
77
77
|
case request_path_info
|
|
78
|
-
when %r{
|
|
78
|
+
when %r{/clear_file}
|
|
79
79
|
clear_file
|
|
80
|
-
when %r{
|
|
80
|
+
when %r{/clear}
|
|
81
81
|
clear
|
|
82
82
|
else
|
|
83
83
|
[404, coverband_headers, ["404 error!"]]
|
|
@@ -86,21 +86,21 @@ module Coverband
|
|
|
86
86
|
case request_path_info
|
|
87
87
|
when /.*\.(css|js|gif|png)/
|
|
88
88
|
@static.call(env)
|
|
89
|
-
when %r{
|
|
89
|
+
when %r{/settings}
|
|
90
90
|
[200, coverband_headers, [settings]]
|
|
91
|
-
when %r{
|
|
91
|
+
when %r{/view_tracker_data}
|
|
92
92
|
[200, coverband_headers(content_type: "text/json"), [view_tracker_data]]
|
|
93
|
-
when %r{
|
|
93
|
+
when %r{/enriched_debug_data}
|
|
94
94
|
[200, coverband_headers(content_type: "text/json"), [enriched_debug_data]]
|
|
95
|
-
when %r{
|
|
95
|
+
when %r{/debug_data}
|
|
96
96
|
[200, coverband_headers(content_type: "text/json"), [debug_data]]
|
|
97
|
-
when %r{
|
|
97
|
+
when %r{/load_file_details}
|
|
98
98
|
[200, coverband_headers(content_type: "text/json"), [load_file_details]]
|
|
99
|
-
when %r{
|
|
99
|
+
when %r{/json}
|
|
100
100
|
[200, coverband_headers(content_type: "text/json"), [json]]
|
|
101
|
-
when %r{
|
|
101
|
+
when %r{/report_json}
|
|
102
102
|
[200, coverband_headers(content_type: "text/json"), [report_json]]
|
|
103
|
-
when %r{
|
|
103
|
+
when %r{/$}
|
|
104
104
|
[200, coverband_headers, [index]]
|
|
105
105
|
else
|
|
106
106
|
[404, coverband_headers, ["404 error!"]]
|
|
@@ -125,7 +125,10 @@ module Coverband
|
|
|
125
125
|
end
|
|
126
126
|
|
|
127
127
|
def json
|
|
128
|
-
Coverband::Reporters::JSONReport.new(
|
|
128
|
+
Coverband::Reporters::JSONReport.new(
|
|
129
|
+
Coverband.configuration.store,
|
|
130
|
+
line_coverage: request.params["line_coverage"] == "true"
|
|
131
|
+
).report
|
|
129
132
|
end
|
|
130
133
|
|
|
131
134
|
def report_json
|
|
@@ -241,7 +244,7 @@ module Coverband
|
|
|
241
244
|
# %r{\/.*\/}.match?(request.path) ? request.path.match("\/.*\/")[0] : "/"
|
|
242
245
|
# ^^ the above is NOT valid Ruby 2.3/2.4 even though rubocop / standard think it is
|
|
243
246
|
def base_path
|
|
244
|
-
(request.path =~ %r{
|
|
247
|
+
(request.path =~ %r{/.*/}) ? request.path.match("/.*/")[0] : "/"
|
|
245
248
|
end
|
|
246
249
|
end
|
|
247
250
|
end
|
|
@@ -14,12 +14,13 @@ module Coverband
|
|
|
14
14
|
module Utils
|
|
15
15
|
class Result
|
|
16
16
|
extend Forwardable
|
|
17
|
+
|
|
17
18
|
# Returns the original Coverage.result used for this instance of Coverband::Result
|
|
18
19
|
attr_reader :original_result
|
|
19
20
|
# Returns all files that are applicable to this result (sans filters!)
|
|
20
21
|
# as instances of Coverband::SourceFile. Aliased as :source_files
|
|
21
22
|
attr_reader :files
|
|
22
|
-
|
|
23
|
+
alias_method :source_files, :files
|
|
23
24
|
# Explicitly set the Time this result has been created
|
|
24
25
|
attr_writer :created_at
|
|
25
26
|
|
|
@@ -29,9 +29,9 @@ module Coverband
|
|
|
29
29
|
attr_reader :coverage_posted
|
|
30
30
|
|
|
31
31
|
# Lets grab some fancy aliases, shall we?
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
alias_method :source, :src
|
|
33
|
+
alias_method :line, :line_number
|
|
34
|
+
alias_method :number, :line_number
|
|
35
35
|
|
|
36
36
|
def initialize(src, line_number, coverage, coverage_posted = nil)
|
|
37
37
|
raise ArgumentError, "Only String accepted for source" unless src.is_a?(String)
|
|
@@ -132,14 +132,14 @@ module Coverband
|
|
|
132
132
|
# suppress reading unused source code.
|
|
133
133
|
@src ||= File.open(filename, "rb", &:readlines)
|
|
134
134
|
end
|
|
135
|
-
|
|
135
|
+
alias_method :source, :src
|
|
136
136
|
|
|
137
137
|
# Returns all source lines for this file as instances of SimpleCov::SourceFile::Line,
|
|
138
138
|
# and thus including coverage data. Aliased as :source_lines
|
|
139
139
|
def lines
|
|
140
140
|
@lines ||= build_lines
|
|
141
141
|
end
|
|
142
|
-
|
|
142
|
+
alias_method :source_lines, :lines
|
|
143
143
|
|
|
144
144
|
def build_lines
|
|
145
145
|
coverage_exceeding_source_warn if coverage.size > src.size
|
|
@@ -126,6 +126,37 @@ namespace :coverband do
|
|
|
126
126
|
Port: ENV.fetch("COVERBAND_COVERAGE_PORT", 9022).to_i
|
|
127
127
|
end
|
|
128
128
|
|
|
129
|
+
desc "Start MCP server for AI assistant integration (set COVERBAND_MCP_HTTP=true for HTTP mode)"
|
|
130
|
+
task :mcp do
|
|
131
|
+
if Rake::Task.task_defined?("environment")
|
|
132
|
+
Rake.application["environment"].invoke
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
begin
|
|
136
|
+
require "coverband/mcp"
|
|
137
|
+
rescue LoadError
|
|
138
|
+
abort "The 'mcp' gem is required for MCP server support. Add `gem 'mcp'` to your Gemfile."
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
server = Coverband::MCP::Server.new
|
|
142
|
+
|
|
143
|
+
if ENV["COVERBAND_MCP_HTTP"]
|
|
144
|
+
# HTTP mode with Streamable HTTP transport (SSE)
|
|
145
|
+
begin
|
|
146
|
+
require "rackup"
|
|
147
|
+
rescue LoadError
|
|
148
|
+
abort "The 'rackup' gem is required for HTTP mode. Add `gem 'rackup'` to your Gemfile."
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
port = ENV.fetch("COVERBAND_MCP_PORT", 9023).to_i
|
|
152
|
+
host = ENV.fetch("COVERBAND_MCP_HOST", "localhost")
|
|
153
|
+
server.run_http(port: port, host: host)
|
|
154
|
+
else
|
|
155
|
+
# Default stdio mode
|
|
156
|
+
server.run_stdio
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
129
160
|
# experimental dead method detection using RubyVM::AbstractSyntaxTree
|
|
130
161
|
# combined with the coverband coverage.
|
|
131
162
|
if defined?(RubyVM::AbstractSyntaxTree)
|
data/lib/coverband/version.rb
CHANGED
data/lib/coverband.rb
CHANGED
|
@@ -51,8 +51,8 @@ module Coverband
|
|
|
51
51
|
yield(configuration)
|
|
52
52
|
elsif File.exist?(configuration_file)
|
|
53
53
|
load configuration_file
|
|
54
|
-
|
|
55
|
-
configuration.logger.debug("using default configuration")
|
|
54
|
+
elsif Coverband.configuration.verbose
|
|
55
|
+
configuration.logger.debug("using default configuration")
|
|
56
56
|
end
|
|
57
57
|
@@configured = true
|
|
58
58
|
coverage_instance.reset_instance
|
|
@@ -107,16 +107,16 @@ module Coverband
|
|
|
107
107
|
# @raise [ArgumentError] If the tracker_type is not supported
|
|
108
108
|
def self.track_key(tracker_type, key)
|
|
109
109
|
return false unless key
|
|
110
|
-
|
|
110
|
+
|
|
111
111
|
supported_trackers = [:view_tracker, :translations_tracker, :routes_tracker]
|
|
112
|
-
|
|
112
|
+
|
|
113
113
|
unless supported_trackers.include?(tracker_type)
|
|
114
|
-
raise ArgumentError, "Unsupported tracker type: #{tracker_type}. Must be one of: #{supported_trackers.join(
|
|
114
|
+
raise ArgumentError, "Unsupported tracker type: #{tracker_type}. Must be one of: #{supported_trackers.join(", ")}"
|
|
115
115
|
end
|
|
116
|
-
|
|
116
|
+
|
|
117
117
|
begin
|
|
118
118
|
tracker = configuration.send(tracker_type)
|
|
119
|
-
return false unless tracker
|
|
119
|
+
return false unless tracker&.respond_to?(:track_key)
|
|
120
120
|
|
|
121
121
|
tracker.track_key(key)
|
|
122
122
|
true
|
|
@@ -102,7 +102,7 @@ namespace :benchmarks do
|
|
|
102
102
|
lines = 45
|
|
103
103
|
non_nil_lines = 18
|
|
104
104
|
lines.times.map do |line|
|
|
105
|
-
|
|
105
|
+
(line < non_nil_lines) ? rand(5) : nil
|
|
106
106
|
end
|
|
107
107
|
end
|
|
108
108
|
|
|
@@ -146,7 +146,7 @@ namespace :benchmarks do
|
|
|
146
146
|
x.report("store_reports_all") { store.save_report(report) }
|
|
147
147
|
end
|
|
148
148
|
keys_subset = report.keys.first(100)
|
|
149
|
-
report_subset = report.
|
|
149
|
+
report_subset = report.slice(*keys_subset)
|
|
150
150
|
Benchmark.ips do |x|
|
|
151
151
|
x.config(time: 20, warmup: 5)
|
|
152
152
|
x.report("store_reports_subset") { store.save_report(report_subset) }
|
|
@@ -183,7 +183,7 @@ namespace :benchmarks do
|
|
|
183
183
|
|
|
184
184
|
def measure_memory_report_coverage
|
|
185
185
|
require "memory_profiler"
|
|
186
|
-
|
|
186
|
+
fake_report
|
|
187
187
|
store = benchmark_redis_store
|
|
188
188
|
store.clear!
|
|
189
189
|
mock_files(store)
|
|
@@ -258,37 +258,29 @@ namespace :benchmarks do
|
|
|
258
258
|
# about 2mb
|
|
259
259
|
puts(ObjectSpace.memsize_of(data) / 2**20)
|
|
260
260
|
|
|
261
|
-
|
|
261
|
+
JSON.parse(data)
|
|
262
262
|
# this seems to just show the value of the pointer
|
|
263
263
|
# puts(ObjectSpace.memsize_of(json_data) / 2**20)
|
|
264
264
|
# implies json takes 10-12 mb
|
|
265
265
|
puts(ObjectSpace.memsize_of_all / 2**20)
|
|
266
|
-
|
|
267
|
-
json_data = nil
|
|
268
266
|
GC.start
|
|
269
|
-
|
|
267
|
+
JSON.parse(data)
|
|
270
268
|
# this seems to just show the value of the pointer
|
|
271
269
|
# puts(ObjectSpace.memsize_of(json_data) / 2**20)
|
|
272
270
|
# implies json takes 10-12 mb
|
|
273
271
|
puts(ObjectSpace.memsize_of_all / 2**20)
|
|
274
|
-
|
|
275
|
-
json_data = nil
|
|
276
272
|
GC.start
|
|
277
|
-
|
|
273
|
+
JSON.parse(data)
|
|
278
274
|
# this seems to just show the value of the pointer
|
|
279
275
|
# puts(ObjectSpace.memsize_of(json_data) / 2**20)
|
|
280
276
|
# implies json takes 10-12 mb
|
|
281
277
|
puts(ObjectSpace.memsize_of_all / 2**20)
|
|
282
|
-
|
|
283
|
-
json_data = nil
|
|
284
278
|
GC.start
|
|
285
|
-
|
|
279
|
+
JSON.parse(data)
|
|
286
280
|
# this seems to just show the value of the pointer
|
|
287
281
|
# puts(ObjectSpace.memsize_of(json_data) / 2**20)
|
|
288
282
|
# implies json takes 10-12 mb
|
|
289
283
|
puts(ObjectSpace.memsize_of_all / 2**20)
|
|
290
|
-
|
|
291
|
-
json_data = nil
|
|
292
284
|
GC.start
|
|
293
285
|
puts(ObjectSpace.memsize_of_all / 2**20)
|
|
294
286
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require File.expand_path("../test_helper", File.dirname(__FILE__))
|
|
4
|
+
|
|
5
|
+
class FileStoreIntegrationTest < Minitest::Test
|
|
6
|
+
def setup
|
|
7
|
+
super
|
|
8
|
+
# Reset state
|
|
9
|
+
Thread.current[:coverband_instance] = nil
|
|
10
|
+
Coverband.class_variable_set(:@@configured, false)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def teardown
|
|
14
|
+
super
|
|
15
|
+
Thread.current[:coverband_instance] = nil
|
|
16
|
+
Coverband.configure do |config|
|
|
17
|
+
# Reset to default
|
|
18
|
+
end
|
|
19
|
+
Coverband.class_variable_set(:@@configured, false)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
test "auto-start with FileStore configuration does not try Redis" do
|
|
23
|
+
# Clear any existing store to simulate fresh start
|
|
24
|
+
Coverband.configuration.instance_variable_set(:@store, nil)
|
|
25
|
+
|
|
26
|
+
# This simulates the typical user setup
|
|
27
|
+
Coverband.configure do |config|
|
|
28
|
+
config.root = Dir.pwd
|
|
29
|
+
config.background_reporting_enabled = false
|
|
30
|
+
config.store = Coverband::Adapters::FileStore.new("tmp/integration_test_coverage")
|
|
31
|
+
config.logger = Logger.new($stdout)
|
|
32
|
+
config.verbose = false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Start coverband - this should work without Redis errors
|
|
36
|
+
begin
|
|
37
|
+
Coverband.start
|
|
38
|
+
Coverband.report_coverage
|
|
39
|
+
rescue Redis::CannotConnectError => e
|
|
40
|
+
flunk "Should not try to connect to Redis when FileStore is configured: #{e.message}"
|
|
41
|
+
rescue => e
|
|
42
|
+
flunk "Unexpected error: #{e.class}: #{e.message}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Verify we're using the correct store
|
|
46
|
+
assert_instance_of Coverband::Adapters::FileStore, Coverband.configuration.store
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
test "store fallback works even in auto-start scenario" do
|
|
50
|
+
# Backup original ENV to avoid side effects
|
|
51
|
+
original_disable = ENV["COVERBAND_DISABLE_AUTO_START"]
|
|
52
|
+
|
|
53
|
+
begin
|
|
54
|
+
# Enable auto-start
|
|
55
|
+
ENV.delete("COVERBAND_DISABLE_AUTO_START")
|
|
56
|
+
|
|
57
|
+
# Clear configuration state
|
|
58
|
+
Coverband.configuration.instance_variable_set(:@store, nil)
|
|
59
|
+
Coverband.class_variable_set(:@@configured, false)
|
|
60
|
+
|
|
61
|
+
# Mock Redis to fail
|
|
62
|
+
Redis.expects(:new).raises(Redis::CannotConnectError.new("Connection refused")).at_least_once
|
|
63
|
+
|
|
64
|
+
# This should not raise an error even with Redis unavailable
|
|
65
|
+
store = Coverband.configuration.store
|
|
66
|
+
|
|
67
|
+
assert_instance_of Coverband::Adapters::NullStore, store
|
|
68
|
+
ensure
|
|
69
|
+
ENV["COVERBAND_DISABLE_AUTO_START"] = original_disable
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require File.expand_path("../test_helper", File.dirname(__FILE__))
|
|
4
|
+
|
|
5
|
+
class FileStoreRedisErrorTest < Minitest::Test
|
|
6
|
+
def setup
|
|
7
|
+
super
|
|
8
|
+
# Reset the singleton instance to ensure clean state
|
|
9
|
+
Coverband::Collectors::Coverage.instance.reset_instance
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def teardown
|
|
13
|
+
super
|
|
14
|
+
Thread.current[:coverband_instance] = nil
|
|
15
|
+
Coverband.configure do |config|
|
|
16
|
+
# Reset to default redis store
|
|
17
|
+
end
|
|
18
|
+
Coverband::Collectors::Coverage.instance.reset_instance
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
test "Default store gracefully handles Redis connection failure" do
|
|
22
|
+
# Clear any existing store configuration
|
|
23
|
+
Coverband.configuration.instance_variable_set(:@store, nil)
|
|
24
|
+
|
|
25
|
+
# Mock Redis to simulate connection failure
|
|
26
|
+
Redis.expects(:new).raises(Redis::CannotConnectError.new("Connection refused"))
|
|
27
|
+
|
|
28
|
+
# This should not raise an error, instead it should fall back to NullStore
|
|
29
|
+
store = Coverband.configuration.store
|
|
30
|
+
|
|
31
|
+
assert_instance_of Coverband::Adapters::NullStore, store
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
test "FileStore configuration overrides default store" do
|
|
35
|
+
# Clear any existing store
|
|
36
|
+
Coverband.configuration.instance_variable_set(:@store, nil)
|
|
37
|
+
|
|
38
|
+
file_store = Coverband::Adapters::FileStore.new("tmp/test_coverage")
|
|
39
|
+
Coverband.configure do |config|
|
|
40
|
+
config.store = file_store
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
assert_same file_store, Coverband.configuration.store
|
|
44
|
+
assert_instance_of Coverband::Adapters::FileStore, Coverband.configuration.store
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
test "FileStore is properly set after configuration" do
|
|
48
|
+
store_path = "tmp/test_coverage"
|
|
49
|
+
Coverband.configure do |config|
|
|
50
|
+
config.store = Coverband::Adapters::FileStore.new(store_path)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
assert_instance_of Coverband::Adapters::FileStore, Coverband.configuration.store
|
|
54
|
+
refute Coverband.configuration.store.is_a?(Coverband::Adapters::RedisStore)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# This test demonstrates the fix for GitHub issue #586
|
|
4
|
+
# https://github.com/danmayer/coverband/issues/586
|
|
5
|
+
|
|
6
|
+
require File.expand_path("../test_helper", File.dirname(__FILE__))
|
|
7
|
+
|
|
8
|
+
class GitHubIssue586Test < Minitest::Test
|
|
9
|
+
def setup
|
|
10
|
+
super
|
|
11
|
+
# Reset the singleton instance to ensure clean state
|
|
12
|
+
Coverband::Collectors::Coverage.instance.reset_instance
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def teardown
|
|
16
|
+
super
|
|
17
|
+
Thread.current[:coverband_instance] = nil
|
|
18
|
+
Coverband.configure do |config|
|
|
19
|
+
# Reset to default
|
|
20
|
+
end
|
|
21
|
+
Coverband::Collectors::Coverage.instance.reset_instance
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
test "GitHub issue #586: FileStore should not cause Redis connection errors" do
|
|
25
|
+
# This reproduces the exact configuration from the GitHub issue
|
|
26
|
+
Coverband.configure do |config|
|
|
27
|
+
config.root = Dir.pwd
|
|
28
|
+
config.background_reporting_enabled = false
|
|
29
|
+
config.store = Coverband::Adapters::FileStore.new("tmp/coverband_log")
|
|
30
|
+
config.logger = Logger.new($stdout)
|
|
31
|
+
config.verbose = false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# This should not raise Redis connection errors
|
|
35
|
+
begin
|
|
36
|
+
# Simulate running ruby code to analyze
|
|
37
|
+
Coverband.start
|
|
38
|
+
Coverband.report_coverage
|
|
39
|
+
|
|
40
|
+
# Verify the store is what the user configured
|
|
41
|
+
assert_instance_of Coverband::Adapters::FileStore, Coverband.configuration.store
|
|
42
|
+
rescue Redis::CannotConnectError => e
|
|
43
|
+
flunk "Should not get Redis connection error when using FileStore: #{e.message}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|