cover_rage 1.0.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5475081d89068cc543f39035786796e346ca90364f30c525680f4458118c211c
4
- data.tar.gz: 954ed2e2a6d4173e99b533c8914fd4c517730b53262bdbce0193c937644ad524
3
+ metadata.gz: e35c3c64110ebafa0432d4ac37f2d05b19a552df8505263f751f4fb85a841525
4
+ data.tar.gz: 9cd16923a839fd2ddcd6f5b585d28bfc19ab40a466d810ac8a93378bf5c13b02
5
5
  SHA512:
6
- metadata.gz: e73234c452933ed861dec4cde8735e455ed5ea36ccbc9fc682bd7f695f43573ebfc5f5fb723b57c490af2fdd56743480e5812efd67a521f3f8672169d2d7af7d
7
- data.tar.gz: 36635a50188153c1f5d1df5b24eb024f12bcfc3b28496ec0deb2eaa918cd13cb571822945109b674f552b2f7fcad23519ef3347fc99d6e8f1ef91f85fdfdd179
6
+ metadata.gz: b1b158a2dc50611a60482aeae2f448c9f4f8ae59f3d1ebb6ecd29bc03fea38a82a934b4b84906a4f331a7b293ccfcb4780ab7d956fe24015c42c54a8d4a52cdc
7
+ data.tar.gz: b06bdca3118bf5d80f7225e4792d258a18ef17a27f9255e0993a0e9523aab81e1aa88051b6c6670241ea688bfd10df10118ce9934e84e1c4379d4fcae02b7282
@@ -27,16 +27,7 @@ module CoverRage
27
27
  end
28
28
 
29
29
  def self.interval
30
- @interval ||= begin
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
@@ -7,5 +7,12 @@ module CoverRage
7
7
  CoverRage::Launcher.start if pid.zero?
8
8
  pid
9
9
  end
10
+
11
+ def daemon(...)
12
+ CoverRage::Launcher.recorder.save(Coverage.result(stop: false))
13
+ returned = super
14
+ CoverRage::Launcher.start
15
+ returned
16
+ end
10
17
  end
11
18
  end
@@ -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
@@ -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(INTERVAL))
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: revision,
49
- source: source,
50
- execution_count: execution_count
47
+ revision:,
48
+ source:,
49
+ execution_count:
51
50
  )
52
51
  end
53
- @store.import(records) if records.any?
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 import(records)
13
+ def transaction
14
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 }
15
+ @transaction = true
16
+ yield
17
+ ensure
18
+ @transaction = false
18
19
  end
19
20
  end
20
21
 
21
- def find(path)
22
- @store.transaction { @store[path] }
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
- @store.transaction do
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
- @store.transaction do
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: url, ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_NONE })
15
+ ::Redis.new(url:, ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_NONE })
16
16
  else
17
- ::Redis.new(url: url)
17
+ ::Redis.new(url:)
18
18
  end
19
19
  end
20
20
 
21
- def import(records)
21
+ def transaction(&)
22
22
  loop do
23
23
  break if @redis.watch(KEY) do
24
- records_to_save = Record.merge(list, records)
25
- if records_to_save.any?
26
- arguments = []
27
- records_to_save.each do |record|
28
- arguments.push(record.path, JSON.dump(record.to_h))
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 find(path)
37
- result = @redis.hget(KEY, path)
38
- return nil if result.nil?
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
- Record.new(**JSON.parse(result))
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
- end
21
-
22
- def import(records)
23
- @db.transaction do
24
- records_to_save = Record.merge(list, records)
25
- if records_to_save.any?
26
- @db.execute(
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 find(path)
44
- rows = @db.execute(
45
- 'select revision, source, execution_count from records where path = ? limit 1',
46
- [path]
47
- )
48
- return nil if rows.empty?
34
+ def transaction(&)
35
+ @mutex.synchronize do
36
+ @db.transaction(:exclusive, &)
37
+ rescue SQLite3::BusyException
38
+ retry
39
+ end
40
+ end
49
41
 
50
- revision, source, execution_count = rows.first
51
- Record.new(
52
- path: path,
53
- revision: revision,
54
- source: source,
55
- execution_count: JSON.parse(execution_count)
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! do |(path, revision, source, execution_count)|
61
+ .map do |(path, revision, source, execution_count)|
63
62
  Record.new(
64
- path: path,
65
- revision: revision,
66
- source: 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.0.0
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-25 00:00:00.000000000 Z
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: minitest
41
+ name: redis
29
42
  requirement: !ruby/object:Gem::Requirement
30
43
  requirements:
31
44
  - - "~>"
32
45
  - !ruby/object:Gem::Version
33
- version: '5.18'
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.18'
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
- A Ruby production code coverage tool designed to assist you in identifying unused code, offering the following features:
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. zero dependencies
45
- 2. super easy setup
46
- 3. support process forking without additional configuration
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
- post_install_message:
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: 2.3.0
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.5.23
87
- signing_key:
114
+ rubygems_version: 3.6.2
88
115
  specification_version: 4
89
- summary: A Ruby production code coverage tool
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: []