cover_rage 0.0.6 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/cover_rage/config.rb +10 -14
- data/lib/cover_rage/fork_hook.rb +7 -0
- data/lib/cover_rage/launcher.rb +3 -4
- data/lib/cover_rage/recorder.rb +13 -12
- data/lib/cover_rage/stores/pstore.rb +38 -0
- data/lib/cover_rage/stores/redis.rb +4 -6
- data/lib/cover_rage/stores/sqlite.rb +27 -17
- metadata +45 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2d22a15a12e4376aa1a441dd5f4d21e7a6043390e1cef64e8739da1e6fcb81eb
|
4
|
+
data.tar.gz: ec07f2d32071bce21d03771e3d3ba3c6dd24e0e1cae745962fe0a36b89bc00ef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 16754fdef9cc29c0f6304e56a6f0c70dd91bc18c294bc944a7d3f927b642b07127acb079ecb9617156c3d0f703ec440536433f97b0f53d9742f6541d7df087cf
|
7
|
+
data.tar.gz: 94bddf50ab05b0f8227e42743910343b5f7c595c74774994bc60f5ecfc807dc1ec3f4bfcd5227ccb24a5207a2cfad77864077e934a986f91d4c8e254921cf359
|
data/lib/cover_rage/config.rb
CHANGED
@@ -3,13 +3,13 @@
|
|
3
3
|
require 'uri'
|
4
4
|
module CoverRage
|
5
5
|
module Config
|
6
|
-
def self.
|
7
|
-
@
|
6
|
+
def self.path_prefix
|
7
|
+
@path_prefix ||= ENV.fetch('COVER_RAGE_PATH_PREFIX', defined?(Rails) && Rails.root ? Rails.root.to_s : Dir.pwd)
|
8
8
|
end
|
9
9
|
|
10
10
|
def self.store
|
11
11
|
@store ||= begin
|
12
|
-
uri = URI.parse(ENV.fetch('COVER_RAGE_STORE_URL'))
|
12
|
+
uri = URI.parse(ENV.fetch('COVER_RAGE_STORE_URL', "pstore:#{File.join(Dir.pwd, 'cover_rage.pstore')}"))
|
13
13
|
case uri.scheme
|
14
14
|
when 'redis', 'rediss'
|
15
15
|
require 'cover_rage/stores/redis'
|
@@ -17,21 +17,17 @@ module CoverRage
|
|
17
17
|
when 'sqlite'
|
18
18
|
require 'cover_rage/stores/sqlite'
|
19
19
|
CoverRage::Stores::Sqlite.new(uri.path)
|
20
|
+
when 'pstore'
|
21
|
+
require 'cover_rage/stores/pstore'
|
22
|
+
CoverRage::Stores::Pstore.new(uri.path)
|
23
|
+
else
|
24
|
+
raise "Unsupported store: #{uri.scheme}"
|
20
25
|
end
|
21
26
|
end
|
22
27
|
end
|
23
28
|
|
24
|
-
def self.
|
25
|
-
@
|
26
|
-
args =
|
27
|
-
ENV.fetch('COVER_RAGE_SLEEP_DURATION', '60:90').split(':').map!(&:to_i).first(2)
|
28
|
-
args.push(args.first.succ) if args.length < 2
|
29
|
-
Range.new(*args, true)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def self.disable?
|
34
|
-
@disable ||= ENV.key?('COVER_RAGE_DISABLE')
|
29
|
+
def self.interval
|
30
|
+
@interval ||= ENV.fetch('COVER_RAGE_INTERVAL', '60').to_i
|
35
31
|
end
|
36
32
|
end
|
37
33
|
end
|
data/lib/cover_rage/fork_hook.rb
CHANGED
data/lib/cover_rage/launcher.rb
CHANGED
@@ -7,18 +7,17 @@ require 'coverage'
|
|
7
7
|
|
8
8
|
module CoverRage
|
9
9
|
class Launcher
|
10
|
+
singleton_class.attr_reader :recorder
|
11
|
+
|
10
12
|
def self.start(**kwargs)
|
11
13
|
if @recorder.nil?
|
12
14
|
@recorder = CoverRage::Recorder.new(
|
13
15
|
store: CoverRage::Config.store,
|
14
|
-
|
16
|
+
path_prefix: CoverRage::Config.path_prefix,
|
15
17
|
**kwargs
|
16
18
|
)
|
17
19
|
end
|
18
20
|
@recorder.start
|
19
|
-
return unless Process.respond_to?(:_fork)
|
20
|
-
return if Process.singleton_class < CoverRage::ForkHook
|
21
|
-
|
22
21
|
Process.singleton_class.prepend(CoverRage::ForkHook)
|
23
22
|
end
|
24
23
|
end
|
data/lib/cover_rage/recorder.rb
CHANGED
@@ -6,13 +6,11 @@ require 'digest'
|
|
6
6
|
|
7
7
|
module CoverRage
|
8
8
|
class Recorder
|
9
|
-
SLEEP_DURATION = Config.sleep_duration
|
10
|
-
COVERAGE_STOP_STATES = %i[idle suspended].freeze
|
11
9
|
attr_reader :store
|
12
10
|
|
13
|
-
def initialize(
|
11
|
+
def initialize(path_prefix:, store:)
|
14
12
|
@store = store
|
15
|
-
@
|
13
|
+
@path_prefix = path_prefix.end_with?('/') ? path_prefix : "#{path_prefix}/"
|
16
14
|
@digest = Digest::MD5.new
|
17
15
|
@file_cache = {}
|
18
16
|
end
|
@@ -20,27 +18,28 @@ module CoverRage
|
|
20
18
|
def start
|
21
19
|
return if @thread&.alive?
|
22
20
|
|
23
|
-
Coverage.
|
21
|
+
unless Coverage.running?
|
22
|
+
Coverage.start
|
23
|
+
at_exit { save(Coverage.result) }
|
24
|
+
end
|
24
25
|
@thread = Thread.new do
|
26
|
+
interval = Config.interval
|
27
|
+
jitter = 0.15
|
25
28
|
loop do
|
26
|
-
sleep(rand
|
29
|
+
sleep(interval + rand * interval * jitter)
|
27
30
|
save(Coverage.result(stop: false, clear: true))
|
28
31
|
end
|
29
|
-
ensure
|
30
|
-
save(Coverage.result)
|
31
32
|
end
|
32
33
|
end
|
33
34
|
|
34
|
-
private
|
35
|
-
|
36
35
|
def save(coverage_result)
|
37
36
|
records = []
|
38
37
|
coverage_result.map do |filepath, execution_count|
|
39
38
|
filepath = File.expand_path(filepath) unless filepath.start_with?('/')
|
40
|
-
next unless filepath.start_with?(@
|
39
|
+
next unless filepath.start_with?(@path_prefix)
|
41
40
|
next if execution_count.all? { _1.nil? || _1.zero? }
|
42
41
|
|
43
|
-
relative_path = filepath.delete_prefix(@
|
42
|
+
relative_path = filepath.delete_prefix(@path_prefix)
|
44
43
|
revision, source = read_file_with_revision(filepath)
|
45
44
|
|
46
45
|
records << Record.new(
|
@@ -53,6 +52,8 @@ module CoverRage
|
|
53
52
|
@store.import(records) if records.any?
|
54
53
|
end
|
55
54
|
|
55
|
+
private
|
56
|
+
|
56
57
|
def read_file_with_revision(path)
|
57
58
|
return @file_cache[path] if @file_cache.key?(path)
|
58
59
|
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cover_rage/record'
|
4
|
+
require 'pstore'
|
5
|
+
|
6
|
+
module CoverRage
|
7
|
+
module Stores
|
8
|
+
class Pstore
|
9
|
+
def initialize(path)
|
10
|
+
@store = PStore.new(path, true)
|
11
|
+
end
|
12
|
+
|
13
|
+
def import(records)
|
14
|
+
@store.transaction do
|
15
|
+
persisted_records = @store.keys.map { @store[_1] }
|
16
|
+
records_to_save = Record.merge(persisted_records, records)
|
17
|
+
records_to_save.each { @store[_1.path] = _1 }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def find(path)
|
22
|
+
@store.transaction { @store[path] }
|
23
|
+
end
|
24
|
+
|
25
|
+
def list
|
26
|
+
@store.transaction do
|
27
|
+
@store.keys.map { @store[_1] }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def clear
|
32
|
+
@store.transaction do
|
33
|
+
@store.keys.each { @store.delete(_1) }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -22,13 +22,11 @@ module CoverRage
|
|
22
22
|
loop do
|
23
23
|
break if @redis.watch(KEY) do
|
24
24
|
records_to_save = Record.merge(list, records)
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
arguments.push(record.path, JSON.dump(record.to_h))
|
29
|
-
end
|
30
|
-
@redis.multi { _1.hset(KEY, *arguments) }
|
25
|
+
arguments = []
|
26
|
+
records_to_save.each do |record|
|
27
|
+
arguments.push(record.path, JSON.dump(record.to_h))
|
31
28
|
end
|
29
|
+
@redis.multi { _1.hset(KEY, *arguments) }
|
32
30
|
end
|
33
31
|
end
|
34
32
|
end
|
@@ -17,27 +17,37 @@ module CoverRage
|
|
17
17
|
execution_count text not null
|
18
18
|
)
|
19
19
|
SQL
|
20
|
+
process_ext = Module.new
|
21
|
+
process_ext.module_exec(self) do |store|
|
22
|
+
define_method :_fork do
|
23
|
+
store.instance_variable_get(:@db).close
|
24
|
+
pid = super()
|
25
|
+
store.instance_variable_set(:@db, SQLite3::Database.new(path))
|
26
|
+
pid
|
27
|
+
end
|
28
|
+
end
|
29
|
+
Process.singleton_class.prepend(process_ext)
|
20
30
|
end
|
21
31
|
|
22
32
|
def import(records)
|
23
|
-
@db.transaction do
|
33
|
+
@db.transaction(:exclusive) do
|
24
34
|
records_to_save = Record.merge(list, records)
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
)
|
39
|
-
end
|
35
|
+
@db.execute(
|
36
|
+
"insert or replace into records (path, revision, source, execution_count) values #{
|
37
|
+
(['(?,?,?,?)'] * records_to_save.length).join(',')
|
38
|
+
}",
|
39
|
+
records_to_save.each_with_object([]) do |record, memo|
|
40
|
+
memo.push(
|
41
|
+
record.path,
|
42
|
+
record.revision,
|
43
|
+
record.source,
|
44
|
+
JSON.dump(record.execution_count)
|
45
|
+
)
|
46
|
+
end
|
47
|
+
)
|
40
48
|
end
|
49
|
+
rescue SQLite3::BusyException
|
50
|
+
retry
|
41
51
|
end
|
42
52
|
|
43
53
|
def find(path)
|
@@ -59,7 +69,7 @@ module CoverRage
|
|
59
69
|
def list
|
60
70
|
@db
|
61
71
|
.execute('select path, revision, source, execution_count from records')
|
62
|
-
.map
|
72
|
+
.map do |(path, revision, source, execution_count)|
|
63
73
|
Record.new(
|
64
74
|
path: path,
|
65
75
|
revision: revision,
|
metadata
CHANGED
@@ -1,15 +1,28 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cover_rage
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Weihang Jian
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2024-12-28 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: minitest
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '5.18'
|
19
|
+
type: :development
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '5.18'
|
13
26
|
- !ruby/object:Gem::Dependency
|
14
27
|
name: rake
|
15
28
|
requirement: !ruby/object:Gem::Requirement
|
@@ -25,25 +38,41 @@ dependencies:
|
|
25
38
|
- !ruby/object:Gem::Version
|
26
39
|
version: '13.0'
|
27
40
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
41
|
+
name: redis
|
29
42
|
requirement: !ruby/object:Gem::Requirement
|
30
43
|
requirements:
|
31
44
|
- - "~>"
|
32
45
|
- !ruby/object:Gem::Version
|
33
|
-
version: '5.
|
46
|
+
version: '5.3'
|
34
47
|
type: :development
|
35
48
|
prerelease: false
|
36
49
|
version_requirements: !ruby/object:Gem::Requirement
|
37
50
|
requirements:
|
38
51
|
- - "~>"
|
39
52
|
- !ruby/object:Gem::Version
|
40
|
-
version: '5.
|
53
|
+
version: '5.3'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: sqlite3
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '2.5'
|
61
|
+
type: :development
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '2.5'
|
41
68
|
description: |
|
42
|
-
|
69
|
+
cover_rage is a Ruby code coverage tool designed to be simple and easy to use. It can be used not only for test coverage but also in production services to identify unused code.
|
70
|
+
|
71
|
+
Key features:
|
43
72
|
|
44
|
-
1.
|
45
|
-
2.
|
46
|
-
3.
|
73
|
+
1. Runs in continuous processes (e.g., Rails servers)
|
74
|
+
2. Zero dependencies
|
75
|
+
3. Supports forking and daemonization without additional setup
|
47
76
|
email: tonytonyjan@gmail.com
|
48
77
|
executables:
|
49
78
|
- cover_rage
|
@@ -60,13 +89,13 @@ files:
|
|
60
89
|
- lib/cover_rage/reporters/html_reporter.rb
|
61
90
|
- lib/cover_rage/reporters/html_reporter/index.html.erb
|
62
91
|
- lib/cover_rage/reporters/json_reporter.rb
|
92
|
+
- lib/cover_rage/stores/pstore.rb
|
63
93
|
- lib/cover_rage/stores/redis.rb
|
64
94
|
- lib/cover_rage/stores/sqlite.rb
|
65
95
|
homepage: https://github.com/tonytonyjan/cover_rage
|
66
96
|
licenses:
|
67
97
|
- MIT
|
68
98
|
metadata: {}
|
69
|
-
post_install_message:
|
70
99
|
rdoc_options: []
|
71
100
|
require_paths:
|
72
101
|
- lib
|
@@ -74,15 +103,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
74
103
|
requirements:
|
75
104
|
- - ">="
|
76
105
|
- !ruby/object:Gem::Version
|
77
|
-
version:
|
106
|
+
version: '3.2'
|
78
107
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
108
|
requirements:
|
80
109
|
- - ">="
|
81
110
|
- !ruby/object:Gem::Version
|
82
111
|
version: '0'
|
83
112
|
requirements: []
|
84
|
-
rubygems_version: 3.
|
85
|
-
signing_key:
|
113
|
+
rubygems_version: 3.6.2
|
86
114
|
specification_version: 4
|
87
|
-
summary:
|
115
|
+
summary: cover_rage is a Ruby code coverage tool designed to be simple and easy to
|
116
|
+
use. It can be used not only for test coverage but also in production services to
|
117
|
+
identify unused code.
|
88
118
|
test_files: []
|