cover_rage 1.1.0 → 1.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2d22a15a12e4376aa1a441dd5f4d21e7a6043390e1cef64e8739da1e6fcb81eb
4
- data.tar.gz: ec07f2d32071bce21d03771e3d3ba3c6dd24e0e1cae745962fe0a36b89bc00ef
3
+ metadata.gz: 5621019f54428ba46ed131c128c14da48bb2e9fd2bc10e283a3c0c8f8642b2c0
4
+ data.tar.gz: a1a8912ce53c591c178923f790c6eba60651fb22834489513b89368a785dfea2
5
5
  SHA512:
6
- metadata.gz: 16754fdef9cc29c0f6304e56a6f0c70dd91bc18c294bc944a7d3f927b642b07127acb079ecb9617156c3d0f703ec440536433f97b0f53d9742f6541d7df087cf
7
- data.tar.gz: 94bddf50ab05b0f8227e42743910343b5f7c595c74774994bc60f5ecfc807dc1ec3f4bfcd5227ccb24a5207a2cfad77864077e934a986f91d4c8e254921cf359
6
+ metadata.gz: 10fafa62c5ddc3c37b1be895d7875c116c44343ea2bed5b18ff3571003958c6867ef40c2945ec70f4747a84760dc786e9ef5fc6c14865e770f7283a05a6004a0
7
+ data.tar.gz: 5d60032466d783830936617366c326df471fde464428fbabf00b0e8e390b1f9b1c79d9c384ea15f5d80448a1f6edd4d3ec5e369b273dbc659e19f6ba053da3d9
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CoverRage
4
- Record = Data.define(:path, :revision, :source, :execution_count) do
4
+ Record = Data.define(:path, :revision, :source, :execution_count, :last_executed_at) do
5
5
  def self.merge(existing, current)
6
6
  records_to_save = []
7
7
  current.each do |record|
@@ -20,6 +20,13 @@ module CoverRage
20
20
  with(
21
21
  execution_count: execution_count.map.with_index do |item, index|
22
22
  item.nil? ? nil : item + other.execution_count[index]
23
+ end,
24
+ last_executed_at: last_executed_at.map.with_index do |item, index|
25
+ if item.nil? && other.last_executed_at[index].nil? then nil
26
+ elsif item.nil? then other.last_executed_at[index]
27
+ elsif other.last_executed_at[index].nil? then item
28
+ else [item.to_i, other.last_executed_at[index].to_i].max
29
+ end
23
30
  end
24
31
  )
25
32
  end
@@ -26,7 +26,7 @@ module CoverRage
26
26
  interval = Config.interval
27
27
  jitter = 0.15
28
28
  loop do
29
- sleep(interval + rand * interval * jitter)
29
+ sleep(interval + (rand * interval * jitter))
30
30
  save(Coverage.result(stop: false, clear: true))
31
31
  end
32
32
  end
@@ -42,14 +42,23 @@ module CoverRage
42
42
  relative_path = filepath.delete_prefix(@path_prefix)
43
43
  revision, source = read_file_with_revision(filepath)
44
44
 
45
+ now = Time.now.to_i
46
+ last_executed_at = execution_count.map { |c| c&.positive? ? now : nil }
47
+
45
48
  records << Record.new(
46
49
  path: relative_path,
47
- revision: revision,
48
- source: source,
49
- execution_count: execution_count
50
+ revision:,
51
+ source:,
52
+ execution_count:,
53
+ last_executed_at:
50
54
  )
51
55
  end
52
- @store.import(records) if records.any?
56
+ return unless records.any?
57
+
58
+ @store.transaction do
59
+ records_to_save = Record.merge(@store.list, records)
60
+ @store.update(records_to_save)
61
+ end
53
62
  end
54
63
 
55
64
  private
@@ -27,11 +27,18 @@
27
27
  .number {
28
28
  display: inline-block;
29
29
  text-align: right;
30
- margin-right: 0.5em;
31
30
  background-color: lightgray;
32
31
  padding: 0 0.5em 0 1.5em;
33
32
  }
34
33
 
34
+ .timestamp {
35
+ display: inline-block;
36
+ text-align: right;
37
+ margin-right: 0.5em;
38
+ background-color: lightgray;
39
+ padding: 0 0.5em 0 0.5em;
40
+ }
41
+
35
42
  .nav {
36
43
  display: flex;
37
44
  list-style: none;
@@ -123,9 +130,13 @@
123
130
  if (typeof value === "number")
124
131
  color = value > 0 ? "green" : "red";
125
132
  const number = typeof value === "number" ? value : "-";
126
- return `<span class="line ${color}"><span class="number">${number
127
- .toString()
128
- .padStart(digit_width, " ")}</span>${line}</span>`;
133
+ const posix_time = record.last_executed_at[index];
134
+ const iso8601_time = typeof posix_time === "number" ? new Date(posix_time * 1000).toISOString().slice(0, 10) : "-";
135
+ return `<span class="line ${color}"><span class="number">${
136
+ number.toString().padStart(digit_width, " ")
137
+ }</span><time class="timestamp">${
138
+ iso8601_time.padStart(10, " ")
139
+ }</time>${line}</span>`;
129
140
  })
130
141
  .join("\n");
131
142
  }
@@ -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,30 +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
- arguments = []
26
- records_to_save.each do |record|
27
- arguments.push(record.path, JSON.dump(record.to_h))
24
+ @redis.multi do |multi|
25
+ Thread.current[:redis_multi] = multi
26
+ yield
27
+ ensure
28
+ Thread.current[:redis_multi] = nil
28
29
  end
29
- @redis.multi { _1.hset(KEY, *arguments) }
30
30
  end
31
31
  end
32
32
  end
33
33
 
34
- def find(path)
35
- result = @redis.hget(KEY, path)
36
- 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
37
39
 
38
- Record.new(**JSON.parse(result))
40
+ client = Thread.current[:redis_multi] || @redis
41
+ client.hset(KEY, *arguments)
39
42
  end
40
43
 
41
44
  def list
@@ -8,13 +8,17 @@ 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)
14
+ @db.busy_handler { true }
12
15
  @db.execute <<-SQL
13
16
  create table if not exists records (
14
17
  path text primary key not null,
15
18
  revision blob not null,
16
19
  source text not null,
17
- execution_count text not null
20
+ execution_count text not null,
21
+ last_executed_at text not null
18
22
  )
19
23
  SQL
20
24
  process_ext = Module.new
@@ -23,58 +27,46 @@ module CoverRage
23
27
  store.instance_variable_get(:@db).close
24
28
  pid = super()
25
29
  store.instance_variable_set(:@db, SQLite3::Database.new(path))
30
+ store.instance_variable_get(:@db).busy_handler { true }
26
31
  pid
27
32
  end
28
33
  end
29
34
  Process.singleton_class.prepend(process_ext)
30
35
  end
31
36
 
32
- def import(records)
33
- @db.transaction(:exclusive) do
34
- records_to_save = Record.merge(list, records)
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
- )
37
+ def transaction(&)
38
+ @mutex.synchronize do
39
+ @db.transaction(:exclusive, &)
48
40
  end
49
- rescue SQLite3::BusyException
50
- retry
51
41
  end
52
42
 
53
- def find(path)
54
- rows = @db.execute(
55
- 'select revision, source, execution_count from records where path = ? limit 1',
56
- [path]
57
- )
58
- return nil if rows.empty?
59
-
60
- revision, source, execution_count = rows.first
61
- Record.new(
62
- path: path,
63
- revision: revision,
64
- source: source,
65
- execution_count: JSON.parse(execution_count)
43
+ def update(records)
44
+ @db.execute(
45
+ "insert or replace into records (path, revision, source, execution_count, last_executed_at) values #{
46
+ (['(?,?,?,?,?)'] * records.length).join(',')
47
+ }",
48
+ records.each_with_object([]) do |record, memo|
49
+ memo.push(
50
+ record.path,
51
+ record.revision,
52
+ record.source,
53
+ JSON.dump(record.execution_count),
54
+ JSON.dump(record.last_executed_at)
55
+ )
56
+ end
66
57
  )
67
58
  end
68
59
 
69
60
  def list
70
61
  @db
71
- .execute('select path, revision, source, execution_count from records')
72
- .map do |(path, revision, source, execution_count)|
62
+ .execute('select path, revision, source, execution_count, last_executed_at from records')
63
+ .map do |(path, revision, source, execution_count, last_executed_at)|
73
64
  Record.new(
74
- path: path,
75
- revision: revision,
76
- source: source,
77
- execution_count: JSON.parse(execution_count)
65
+ path:,
66
+ revision:,
67
+ source:,
68
+ execution_count: JSON.parse(execution_count),
69
+ last_executed_at: JSON.parse(last_executed_at)
78
70
  )
79
71
  end
80
72
  end
metadata CHANGED
@@ -1,70 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cover_rage
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Weihang Jian
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2024-12-28 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
- name: minitest
13
+ name: pstore
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
- - - "~>"
16
+ - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '5.18'
19
- type: :development
18
+ version: '0'
19
+ type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
- - - "~>"
23
+ - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: '5.18'
26
- - !ruby/object:Gem::Dependency
27
- name: rake
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - "~>"
31
- - !ruby/object:Gem::Version
32
- version: '13.0'
33
- type: :development
34
- prerelease: false
35
- version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: '13.0'
40
- - !ruby/object:Gem::Dependency
41
- name: redis
42
- requirement: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - "~>"
45
- - !ruby/object:Gem::Version
46
- version: '5.3'
47
- type: :development
48
- prerelease: false
49
- version_requirements: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
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'
25
+ version: '0'
68
26
  description: |
69
27
  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
28
 
@@ -95,7 +53,8 @@ files:
95
53
  homepage: https://github.com/tonytonyjan/cover_rage
96
54
  licenses:
97
55
  - MIT
98
- metadata: {}
56
+ metadata:
57
+ rubygems_mfa_required: 'true'
99
58
  rdoc_options: []
100
59
  require_paths:
101
60
  - lib
@@ -110,7 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
110
69
  - !ruby/object:Gem::Version
111
70
  version: '0'
112
71
  requirements: []
113
- rubygems_version: 3.6.2
72
+ rubygems_version: 4.0.4
114
73
  specification_version: 4
115
74
  summary: cover_rage is a Ruby code coverage tool designed to be simple and easy to
116
75
  use. It can be used not only for test coverage but also in production services to