cover_rage 1.0.0 → 1.1.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.
- checksums.yaml +4 -4
- data/lib/cover_rage/config.rb +1 -10
- data/lib/cover_rage/fork_hook.rb +7 -0
- data/lib/cover_rage/launcher.rb +2 -3
- data/lib/cover_rage/recorder.rb +14 -8
- data/lib/cover_rage/stores/pstore.rb +23 -8
- data/lib/cover_rage/stores/redis.rb +15 -14
- data/lib/cover_rage/stores/sqlite.rb +34 -35
- metadata +46 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e35c3c64110ebafa0432d4ac37f2d05b19a552df8505263f751f4fb85a841525
|
4
|
+
data.tar.gz: 9cd16923a839fd2ddcd6f5b585d28bfc19ab40a466d810ac8a93378bf5c13b02
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b1b158a2dc50611a60482aeae2f448c9f4f8ae59f3d1ebb6ecd29bc03fea38a82a934b4b84906a4f331a7b293ccfcb4780ab7d956fe24015c42c54a8d4a52cdc
|
7
|
+
data.tar.gz: b06bdca3118bf5d80f7225e4792d258a18ef17a27f9255e0993a0e9523aab81e1aa88051b6c6670241ea688bfd10df10118ce9934e84e1c4379d4fcae02b7282
|
data/lib/cover_rage/config.rb
CHANGED
@@ -27,16 +27,7 @@ module CoverRage
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def self.interval
|
30
|
-
@interval ||=
|
31
|
-
args =
|
32
|
-
ENV.fetch('COVER_RAGE_INTERVAL', '60:90').split(':').map!(&:to_i).first(2)
|
33
|
-
args.push(args.first.succ) if args.length < 2
|
34
|
-
Range.new(*args, true)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def self.disable?
|
39
|
-
@disable ||= ENV.key?('COVER_RAGE_DISABLE')
|
30
|
+
@interval ||= ENV.fetch('COVER_RAGE_INTERVAL', '60').to_i
|
40
31
|
end
|
41
32
|
end
|
42
33
|
end
|
data/lib/cover_rage/fork_hook.rb
CHANGED
data/lib/cover_rage/launcher.rb
CHANGED
@@ -7,6 +7,8 @@ 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(
|
@@ -16,9 +18,6 @@ module CoverRage
|
|
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,7 +6,6 @@ require 'digest'
|
|
6
6
|
|
7
7
|
module CoverRage
|
8
8
|
class Recorder
|
9
|
-
INTERVAL = Config.interval
|
10
9
|
attr_reader :store
|
11
10
|
|
12
11
|
def initialize(path_prefix:, store:)
|
@@ -24,15 +23,15 @@ module CoverRage
|
|
24
23
|
at_exit { save(Coverage.result) }
|
25
24
|
end
|
26
25
|
@thread = Thread.new do
|
26
|
+
interval = Config.interval
|
27
|
+
jitter = 0.15
|
27
28
|
loop do
|
28
|
-
sleep(rand
|
29
|
+
sleep(interval + (rand * interval * jitter))
|
29
30
|
save(Coverage.result(stop: false, clear: true))
|
30
31
|
end
|
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|
|
@@ -45,14 +44,21 @@ module CoverRage
|
|
45
44
|
|
46
45
|
records << Record.new(
|
47
46
|
path: relative_path,
|
48
|
-
revision
|
49
|
-
source
|
50
|
-
execution_count:
|
47
|
+
revision:,
|
48
|
+
source:,
|
49
|
+
execution_count:
|
51
50
|
)
|
52
51
|
end
|
53
|
-
|
52
|
+
return unless records.any?
|
53
|
+
|
54
|
+
@store.transaction do
|
55
|
+
records_to_save = Record.merge(@store.list, records)
|
56
|
+
@store.update(records_to_save)
|
57
|
+
end
|
54
58
|
end
|
55
59
|
|
60
|
+
private
|
61
|
+
|
56
62
|
def read_file_with_revision(path)
|
57
63
|
return @file_cache[path] if @file_cache.key?(path)
|
58
64
|
|
@@ -10,27 +10,42 @@ module CoverRage
|
|
10
10
|
@store = PStore.new(path, true)
|
11
11
|
end
|
12
12
|
|
13
|
-
def
|
13
|
+
def transaction
|
14
14
|
@store.transaction do
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
@transaction = true
|
16
|
+
yield
|
17
|
+
ensure
|
18
|
+
@transaction = false
|
18
19
|
end
|
19
20
|
end
|
20
21
|
|
21
|
-
def
|
22
|
-
@
|
22
|
+
def update(records)
|
23
|
+
if @transaction
|
24
|
+
records.each { @store[_1.path] = _1 }
|
25
|
+
else
|
26
|
+
@store.transaction do
|
27
|
+
records.each { @store[_1.path] = _1 }
|
28
|
+
end
|
29
|
+
end
|
23
30
|
end
|
24
31
|
|
25
32
|
def list
|
26
|
-
@
|
33
|
+
if @transaction
|
27
34
|
@store.keys.map { @store[_1] }
|
35
|
+
else
|
36
|
+
@store.transaction do
|
37
|
+
@store.keys.map { @store[_1] }
|
38
|
+
end
|
28
39
|
end
|
29
40
|
end
|
30
41
|
|
31
42
|
def clear
|
32
|
-
@
|
43
|
+
if @transaction
|
33
44
|
@store.keys.each { @store.delete(_1) }
|
45
|
+
else
|
46
|
+
@store.transaction do
|
47
|
+
@store.keys.each { @store.delete(_1) }
|
48
|
+
end
|
34
49
|
end
|
35
50
|
end
|
36
51
|
end
|
@@ -12,32 +12,33 @@ module CoverRage
|
|
12
12
|
def initialize(url)
|
13
13
|
@redis =
|
14
14
|
if url.start_with?('rediss')
|
15
|
-
::Redis.new(url
|
15
|
+
::Redis.new(url:, ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_NONE })
|
16
16
|
else
|
17
|
-
::Redis.new(url:
|
17
|
+
::Redis.new(url:)
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
21
|
+
def transaction(&)
|
22
22
|
loop do
|
23
23
|
break if @redis.watch(KEY) do
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
end
|
30
|
-
@redis.multi { _1.hset(KEY, *arguments) }
|
24
|
+
@redis.multi do |multi|
|
25
|
+
Thread.current[:redis_multi] = multi
|
26
|
+
yield
|
27
|
+
ensure
|
28
|
+
Thread.current[:redis_multi] = nil
|
31
29
|
end
|
32
30
|
end
|
33
31
|
end
|
34
32
|
end
|
35
33
|
|
36
|
-
def
|
37
|
-
|
38
|
-
|
34
|
+
def update(records)
|
35
|
+
arguments = []
|
36
|
+
records.each do |record|
|
37
|
+
arguments.push(record.path, JSON.dump(record.to_h))
|
38
|
+
end
|
39
39
|
|
40
|
-
|
40
|
+
client = Thread.current[:redis_multi] || @redis
|
41
|
+
client.hset(KEY, *arguments)
|
41
42
|
end
|
42
43
|
|
43
44
|
def list
|
@@ -8,6 +8,8 @@ module CoverRage
|
|
8
8
|
module Stores
|
9
9
|
class Sqlite
|
10
10
|
def initialize(path)
|
11
|
+
@mutex = Mutex.new
|
12
|
+
@path = path
|
11
13
|
@db = SQLite3::Database.new(path)
|
12
14
|
@db.execute <<-SQL
|
13
15
|
create table if not exists records (
|
@@ -17,53 +19,50 @@ module CoverRage
|
|
17
19
|
execution_count text not null
|
18
20
|
)
|
19
21
|
SQL
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
"insert or replace into records (path, revision, source, execution_count) values #{
|
28
|
-
(['(?,?,?,?)'] * records_to_save.length).join(',')
|
29
|
-
}",
|
30
|
-
records_to_save.each_with_object([]) do |record, memo|
|
31
|
-
memo.push(
|
32
|
-
record.path,
|
33
|
-
record.revision,
|
34
|
-
record.source,
|
35
|
-
JSON.dump(record.execution_count)
|
36
|
-
)
|
37
|
-
end
|
38
|
-
)
|
22
|
+
process_ext = Module.new
|
23
|
+
process_ext.module_exec(self) do |store|
|
24
|
+
define_method :_fork do
|
25
|
+
store.instance_variable_get(:@db).close
|
26
|
+
pid = super()
|
27
|
+
store.instance_variable_set(:@db, SQLite3::Database.new(path))
|
28
|
+
pid
|
39
29
|
end
|
40
30
|
end
|
31
|
+
Process.singleton_class.prepend(process_ext)
|
41
32
|
end
|
42
33
|
|
43
|
-
def
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
34
|
+
def transaction(&)
|
35
|
+
@mutex.synchronize do
|
36
|
+
@db.transaction(:exclusive, &)
|
37
|
+
rescue SQLite3::BusyException
|
38
|
+
retry
|
39
|
+
end
|
40
|
+
end
|
49
41
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
42
|
+
def update(records)
|
43
|
+
@db.execute(
|
44
|
+
"insert or replace into records (path, revision, source, execution_count) values #{
|
45
|
+
(['(?,?,?,?)'] * records.length).join(',')
|
46
|
+
}",
|
47
|
+
records.each_with_object([]) do |record, memo|
|
48
|
+
memo.push(
|
49
|
+
record.path,
|
50
|
+
record.revision,
|
51
|
+
record.source,
|
52
|
+
JSON.dump(record.execution_count)
|
53
|
+
)
|
54
|
+
end
|
56
55
|
)
|
57
56
|
end
|
58
57
|
|
59
58
|
def list
|
60
59
|
@db
|
61
60
|
.execute('select path, revision, source, execution_count from records')
|
62
|
-
.map
|
61
|
+
.map do |(path, revision, source, execution_count)|
|
63
62
|
Record.new(
|
64
|
-
path
|
65
|
-
revision
|
66
|
-
source
|
63
|
+
path:,
|
64
|
+
revision:,
|
65
|
+
source:,
|
67
66
|
execution_count: JSON.parse(execution_count)
|
68
67
|
)
|
69
68
|
end
|
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: 1.
|
4
|
+
version: 1.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Weihang Jian
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date: 2024-12-
|
10
|
+
date: 2024-12-30 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,26 +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.
|
47
|
-
4. minimal performance overhead
|
73
|
+
1. Runs in continuous processes (e.g., Rails servers)
|
74
|
+
2. Zero dependencies
|
75
|
+
3. Supports forking and daemonization without additional setup
|
48
76
|
email: tonytonyjan@gmail.com
|
49
77
|
executables:
|
50
78
|
- cover_rage
|
@@ -67,8 +95,8 @@ files:
|
|
67
95
|
homepage: https://github.com/tonytonyjan/cover_rage
|
68
96
|
licenses:
|
69
97
|
- MIT
|
70
|
-
metadata:
|
71
|
-
|
98
|
+
metadata:
|
99
|
+
rubygems_mfa_required: 'true'
|
72
100
|
rdoc_options: []
|
73
101
|
require_paths:
|
74
102
|
- lib
|
@@ -76,15 +104,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
76
104
|
requirements:
|
77
105
|
- - ">="
|
78
106
|
- !ruby/object:Gem::Version
|
79
|
-
version:
|
107
|
+
version: '3.2'
|
80
108
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
109
|
requirements:
|
82
110
|
- - ">="
|
83
111
|
- !ruby/object:Gem::Version
|
84
112
|
version: '0'
|
85
113
|
requirements: []
|
86
|
-
rubygems_version: 3.
|
87
|
-
signing_key:
|
114
|
+
rubygems_version: 3.6.2
|
88
115
|
specification_version: 4
|
89
|
-
summary:
|
116
|
+
summary: cover_rage is a Ruby code coverage tool designed to be simple and easy to
|
117
|
+
use. It can be used not only for test coverage but also in production services to
|
118
|
+
identify unused code.
|
90
119
|
test_files: []
|