oneshot_coverage 0.2.1 → 0.4.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: 7f17a50188404018a1e6a33f0ab5c71ddddabe4ac2686d3089bd6fb4b4e690d5
4
- data.tar.gz: c4a47f69c6fd77ab84a12b698fb1a725c4719a4451d3afdb95d40fbd99b35e39
3
+ metadata.gz: 293537ff9297d5f5aa840aee43fcc4f80e01f6f386117211887be7f643b3c311
4
+ data.tar.gz: 05d17ad02efabffa178b7ca1f414bdfe131b3f4a28c62d272359f4bcf3f8e4dc
5
5
  SHA512:
6
- metadata.gz: 0c09df9d74e6e6975e41933653b6c7904097e54eccaf50d24a18b5dd3f047d18eee8c8bd503c59bfc1bc1bccebd8381e0375b4359882c79b9ef95fd54bca84c9
7
- data.tar.gz: bcb475a7d9ef58378738c0bd93126ba3b5ba1edb183272bb8f624f492b16b03616d84faace863c05b212f24c1d1bf2b9631135496283675d47a25b786e777448
6
+ metadata.gz: 555c67956799e0fd4284360938c64d41c3aea1b829306ef2e5d0a7308134694ccaa3250e591dd953ab8e816f4f62dcb53e49ddc120777ce92239ea1f47f4369a
7
+ data.tar.gz: fe81e8fc2256a7b1792eb13719f6958a1a67198ed4082cf1f2821cf7f19f3f5f17875620b7aa6446ced775b0956806e38823faa599655f787e5f47c5ba724f35
@@ -0,0 +1,19 @@
1
+ name: Ruby
2
+
3
+ on: [push,pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ strategy:
9
+ matrix:
10
+ ruby: [ '2.6', '2.7', '3.0' ]
11
+ name: Test on Ruby ${{ matrix.ruby }}
12
+ steps:
13
+ - uses: actions/checkout@v2
14
+ - uses: ruby/setup-ruby@v1
15
+ with:
16
+ ruby-version: ${{ matrix.ruby }}
17
+ bundler-cache: true
18
+ - name: Execute test
19
+ run: bundle exec rake
data/.gitignore CHANGED
@@ -41,6 +41,7 @@ Temporary Items
41
41
  /test/tmp/
42
42
  /test/version_tmp/
43
43
  /tmp/
44
+ Gemfile.lock
44
45
 
45
46
  # Used by dotenv library to load environment variables.
46
47
  # .env
data/CHANGELOG ADDED
@@ -0,0 +1,4 @@
1
+ ## 0.4.0
2
+
3
+ - Provide options for checking bundle_path (#7)
4
+ - Fix error when empty log file is exist (#4)
data/Gemfile CHANGED
@@ -4,3 +4,8 @@ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
4
 
5
5
  # Specify your gem's dependencies in oneshot_coverage.gemspec
6
6
  gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+ gem "bundler", "> 1.17"
10
+
11
+ gem "minitest", "~> 5.0"
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # OneshotCoverage
2
2
 
3
- This gem may not be very useful when you want to use [Coverage onetshot mode](https://bugs.ruby-lang.org/issues/15022),
3
+ This gem may not be very useful when you want to use [Coverage oneshot mode](https://bugs.ruby-lang.org/issues/15022),
4
4
  however, It could be good example to study how to implement by yourself.
5
5
 
6
6
  This gem provides simple tools to use oneshot mode easier. It gives you:
@@ -9,7 +9,7 @@ This gem provides simple tools to use oneshot mode easier. It gives you:
9
9
  - Pluggable logger interface
10
10
 
11
11
  Please notice that it records code executions under the target path(usually, project base path).
12
- If you have bundle gem path under target path, It will be ignored automatically.
12
+ If you have bundle gem path under target path, It will be ignored by default.
13
13
 
14
14
  ## Installation
15
15
 
@@ -35,7 +35,8 @@ Or install it yourself as:
35
35
  OneshotCoverage.configure(
36
36
  target_path: '/base/project/path',
37
37
  logger: OneshotCoverage::Logger::NullLogger.new,
38
- max_emit_at_once: nil, # Flush all when it set to `nil`, which is default
38
+ emit_term: nil, # emit per `emit_term` seconds. It tries to emit per request when `nil`.
39
+ cover_bundle_path: false, # record bundle gem path. Default value is false.
39
40
  )
40
41
  OneshotCoverage.start
41
42
  ```
@@ -44,25 +45,44 @@ As default, OneshotCoverage supports 2 logger.
44
45
 
45
46
  - OneshotCoverage::Logger::NullLogger (default)
46
47
  - OneshotCoverage::Logger::StdoutLogger
48
+ - OneshotCoverage::Logger::FileLogger
47
49
 
48
50
  Only required interface is `#post` instance method, so you could implement
49
51
  by yourself easily.
50
52
 
51
53
  ```ruby
52
- class SampleFluentLogger
53
- def initialize
54
- @logger = Fluent::Logger::FluentLogger.new('tag_prefix')
54
+ class FileLogger
55
+ def initialize(log_path)
56
+ @log_path = log_path
55
57
  end
56
58
 
57
- def post(path:, md5_hash:, lineno:)
58
- @logger.post(nil, path: path, md5_hash: md5_hash, lineno: lineno)
59
+ # new_logs: Struct.new(:path, :md5_hash, :lines)
60
+ def post(new_logs)
61
+ current_coverage = fetch
62
+
63
+ new_logs.each do |new_log|
64
+ key = "#{new_log.path}-#{new_log.md5_hash}"
65
+
66
+ logged_lines = current_coverage.fetch(key, [])
67
+ current_coverage[key] = logged_lines | new_log.lines
68
+ end
69
+ save(current_coverage)
70
+ end
71
+
72
+ private
73
+
74
+ def fetch
75
+ JSON.load(File.read(@log_path))
76
+ rescue Errno::ENOENT
77
+ {}
78
+ end
79
+
80
+ def save(data)
81
+ File.write(@log_path, JSON.dump(data))
59
82
  end
60
83
  end
61
84
  ```
62
85
 
63
- Please note that it will retry to send data if `#post` returns falsy value.
64
- Hence, make sure to return `true` if you don't want to retry.
65
-
66
86
  ### Emit logs
67
87
 
68
88
  #### With rack application
data/Rakefile CHANGED
@@ -1,2 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/gem_tasks"
2
- task :default => :spec
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ end
11
+
12
+ task default: %i[test]
@@ -19,53 +19,67 @@ module OneshotCoverage
19
19
  end
20
20
  end
21
21
 
22
+ OneshotLog = Struct.new(:path, :md5_hash, :lines)
23
+
22
24
  class Reporter
23
- def initialize(target_path:, logger:, max_emit_at_once:)
25
+ def initialize(target_path:, logger:, emit_term: nil, cover_bundle_path: false)
24
26
  @target_path = target_path
25
27
  @logger = logger
26
- @buffer = []
27
- @max_emit_at_once = max_emit_at_once
28
+ @emit_term = emit_term
29
+ if @emit_term
30
+ @next_emit_time = Time.now.to_i + rand(@emit_term)
31
+ end
32
+
28
33
  if defined?(Bundler)
29
34
  @bundler_path = Bundler.bundle_path.to_s
35
+ @cover_bundle_path = cover_bundle_path
30
36
  end
31
37
  end
32
38
 
33
- def emit
34
- Coverage.result(clear: true, stop: false).
39
+ def emit(force_emit)
40
+ if !force_emit
41
+ if !time_to_emit?
42
+ return
43
+ end
44
+ end
45
+
46
+ logs =
47
+ Coverage.result(clear: true, stop: false).
35
48
  select { |k, v| is_target?(k, v) }.
36
- flat_map { |k, v| transform(k, v) }.
37
- each { |row| @buffer << row }
49
+ map do |filepath, v|
50
+ OneshotLog.new(relative_path(filepath), md5_hash_for(filepath), v[:oneshot_lines])
51
+ end
52
+
53
+ if logs.size > 0
54
+ @logger.post(logs)
55
+ end
56
+ end
57
+
58
+ private
38
59
 
39
- @buffer.shift(emit_at_once).each do |row|
40
- # Retry when fail to post
41
- unless @logger.post(row)
42
- @buffer << row
60
+ def time_to_emit?
61
+ if @emit_term
62
+ if @next_emit_time > Time.now.to_i
63
+ return false # Do not emit until next_emit_time
64
+ else
65
+ @next_emit_time += @emit_term
43
66
  end
44
67
  end
68
+ true
45
69
  end
46
70
 
47
71
  def is_target?(filepath, value)
48
72
  return false if value[:oneshot_lines].empty?
73
+ return @cover_bundle_path if @bundler_path && filepath.start_with?(@bundler_path)
49
74
  return false if !filepath.start_with?(@target_path)
50
- return false if @bundler_path && filepath.start_with?(@bundler_path)
51
75
  true
52
76
  end
53
77
 
54
- def transform(filepath, value)
55
- rel_path = filepath[@target_path.size..]
56
- md5_hash =
57
- if md5_hash_cache.key?(filepath)
58
- md5_hash_cache[filepath]
59
- else
60
- md5_hash_cache[filepath] = Digest::MD5.file(filepath).hexdigest
61
- end
62
-
63
- value[:oneshot_lines].map do |line|
64
- {
65
- path: rel_path,
66
- md5_hash: md5_hash,
67
- lineno: line
68
- }
78
+ def relative_path(filepath)
79
+ if filepath.include?(@target_path)
80
+ filepath[@target_path.size..-1]
81
+ else
82
+ filepath
69
83
  end
70
84
  end
71
85
 
@@ -73,8 +87,12 @@ module OneshotCoverage
73
87
  @md5_hash_cache ||= {}
74
88
  end
75
89
 
76
- def emit_at_once
77
- @max_emit_at_once || @buffer.size
90
+ def md5_hash_for(filepath)
91
+ if md5_hash_cache.key? filepath
92
+ md5_hash_cache[filepath]
93
+ else
94
+ md5_hash_cache[filepath] = Digest::MD5.file(filepath).hexdigest
95
+ end
78
96
  end
79
97
  end
80
98
 
@@ -85,25 +103,20 @@ module OneshotCoverage
85
103
 
86
104
  # To handle execution with exit immediatly
87
105
  at_exit do
88
- OneshotCoverage.emit
106
+ OneshotCoverage.emit(force_emit: true)
89
107
  end
90
108
  end
91
109
 
92
- def emit
93
- @reporter&.emit
110
+ def emit(force_emit: false)
111
+ @reporter&.emit(force_emit)
94
112
  end
95
113
 
96
- def configure(target_path:, logger: OneshotCoverage::Logger::NullLogger.new, max_emit_at_once: nil)
97
- target_path_by_pathname =
98
- if target_path.is_a? Pathname
99
- target_path
100
- else
101
- Pathname.new(target_path)
102
- end
114
+ def configure(target_path:, logger: OneshotCoverage::Logger::NullLogger.new, emit_term: nil, cover_bundle_path: false)
103
115
  @reporter = OneshotCoverage::Reporter.new(
104
- target_path: target_path_by_pathname.cleanpath.to_s + "/",
116
+ target_path: Pathname.new(target_path).cleanpath.to_s + "/",
105
117
  logger: logger,
106
- max_emit_at_once: max_emit_at_once
118
+ emit_term: emit_term,
119
+ cover_bundle_path: cover_bundle_path
107
120
  )
108
121
  end
109
122
  end
@@ -0,0 +1,35 @@
1
+ require 'json'
2
+
3
+ module OneshotCoverage
4
+ module Logger
5
+ class FileLogger
6
+ def initialize(log_path)
7
+ @log_path = log_path
8
+ end
9
+
10
+ def post(new_logs)
11
+ current_coverage = fetch
12
+
13
+ new_logs.each do |new_log|
14
+ key = "#{new_log.path}-#{new_log.md5_hash}"
15
+
16
+ logged_lines = current_coverage.fetch(key, [])
17
+ current_coverage[key] = logged_lines | new_log.lines
18
+ end
19
+ save(current_coverage)
20
+ end
21
+
22
+ private
23
+
24
+ def fetch
25
+ JSON.load(File.read(@log_path)) || {}
26
+ rescue Errno::ENOENT
27
+ {}
28
+ end
29
+
30
+ def save(data)
31
+ File.write(@log_path, JSON.dump(data))
32
+ end
33
+ end
34
+ end
35
+ end
@@ -3,7 +3,6 @@ module OneshotCoverage
3
3
  class NullLogger
4
4
  def post(_row)
5
5
  # Do nothing
6
- true
7
6
  end
8
7
  end
9
8
  end
@@ -1,11 +1,12 @@
1
1
  module OneshotCoverage
2
2
  module Logger
3
3
  class StdoutLogger
4
- def post(path:, md5_hash:, lineno:)
5
- $stdout.puts(
6
- "[OneshotCoverage] logged path: #{path}, md5_hash: #{md5_hash}, lineno: #{lineno}"
7
- )
8
- true
4
+ def post(logs)
5
+ logs.each do |log|
6
+ $stdout.puts(
7
+ "[OneshotCoverage] logged path: #{log.path}, md5_hash: #{log.md5_hash}, executed_lines: #{log.lines}"
8
+ )
9
+ end
9
10
  end
10
11
  end
11
12
  end
@@ -1,3 +1,3 @@
1
1
  module OneshotCoverage
2
- VERSION = "0.2.1"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -36,7 +36,4 @@ Gem::Specification.new do |spec|
36
36
 
37
37
  # Release ruby version lock temperary
38
38
  # spec.required_ruby_version = '>= 2.6'
39
-
40
- spec.add_development_dependency "bundler", "~> 1.17"
41
- spec.add_development_dependency "rake", "~> 10.0"
42
39
  end
metadata CHANGED
@@ -1,43 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oneshot_coverage
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shia
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-03-22 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: bundler
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '1.17'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '1.17'
27
- - !ruby/object:Gem::Dependency
28
- name: rake
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '10.0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '10.0'
11
+ date: 2021-07-05 00:00:00.000000000 Z
12
+ dependencies: []
41
13
  description: OneshotCoverage will help you to measure oneshot coverage on production
42
14
  email:
43
15
  - rise.shia@gmail.com
@@ -45,16 +17,18 @@ executables: []
45
17
  extensions: []
46
18
  extra_rdoc_files: []
47
19
  files:
20
+ - ".github/workflows/main.yml"
48
21
  - ".gitignore"
22
+ - CHANGELOG
49
23
  - CODE_OF_CONDUCT.md
50
24
  - Gemfile
51
- - Gemfile.lock
52
25
  - LICENSE.txt
53
26
  - README.md
54
27
  - Rakefile
55
28
  - bin/console
56
29
  - bin/setup
57
30
  - lib/oneshot_coverage.rb
31
+ - lib/oneshot_coverage/logger/file_logger.rb
58
32
  - lib/oneshot_coverage/logger/null_logger.rb
59
33
  - lib/oneshot_coverage/logger/stdout_logger.rb
60
34
  - lib/oneshot_coverage/railtie.rb
@@ -67,7 +41,7 @@ metadata:
67
41
  homepage_uri: https://github.com/riseshia/oneshot_coverage
68
42
  source_code_uri: https://github.com/riseshia/oneshot_coverage
69
43
  changelog_uri: https://github.com/riseshia/oneshot_coverage
70
- post_install_message:
44
+ post_install_message:
71
45
  rdoc_options: []
72
46
  require_paths:
73
47
  - lib
@@ -82,8 +56,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
82
56
  - !ruby/object:Gem::Version
83
57
  version: '0'
84
58
  requirements: []
85
- rubygems_version: 3.0.3
86
- signing_key:
59
+ rubygems_version: 3.2.3
60
+ signing_key:
87
61
  specification_version: 4
88
62
  summary: Simple toolbox for oneshot coverage
89
63
  test_files: []
data/Gemfile.lock DELETED
@@ -1,20 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- oneshot_coverage (0.1.0)
5
-
6
- GEM
7
- remote: https://rubygems.org/
8
- specs:
9
- rake (10.5.0)
10
-
11
- PLATFORMS
12
- ruby
13
-
14
- DEPENDENCIES
15
- bundler (~> 1.17)
16
- oneshot_coverage!
17
- rake (~> 10.0)
18
-
19
- BUNDLED WITH
20
- 1.17.2