deep-cover 0.1.13 → 0.1.14

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
  SHA1:
3
- metadata.gz: ef02156435418688aa0934036aa4273c3179468c
4
- data.tar.gz: ba37d24e7cbf1e34f0fbd0db4f3f2720527825f4
3
+ metadata.gz: e6de45ce3979398ef5abb25a8147848ad13fa370
4
+ data.tar.gz: 74dfa0b7b03271419f0b2c7094e124b447392c46
5
5
  SHA512:
6
- metadata.gz: 0c209ce060a984b41dc8e454fccebb6d9911a464a1b76de267ccdd326e567b9af9e427c42e66f75007b7fd81cdb27d4bc5b513772cf2d0d0e11c98a8d152a164
7
- data.tar.gz: 492ebdb2f56861e7b9d8d195cfdc2818568ff245e7e64c778d9062290785d420c31e43cb1cdcd2efbd204ef40141ba6e80d7d05e220250beba4bf88759b39a1d
6
+ metadata.gz: 2a455c059204f7eeb91f57ca3d4453a7a79021ece85e3228b4a312034dcaef7d022f0776dc9179bcb1dc44049971ba5248fcdc94badcacc6ab1c08bbd9da195e
7
+ data.tar.gz: 8c6ec4f7204c95968a329d1ae3896cf9c0e41c68973883add1beca6d1efdcdba353e09ff11dfa97651488a75e2cb4f6dd7cc70d5f23159b21efaba6e967233e4
data/Rakefile CHANGED
@@ -14,6 +14,7 @@ namespace :dev do
14
14
  if RUBY_VERSION >= '2.2.2' && (!defined?(RUBY_ENGINE) || RUBY_ENGINE != 'jruby')
15
15
  commands << 'bundle install --gemfile=spec/full_usage/rails51_project/Gemfile'
16
16
  end
17
+ commands << 'bundle install --gemfile=spec/cli_fixtures/simple_rails42_app/Gemfile'
17
18
 
18
19
  commands.each do |command|
19
20
  puts "Running: #{command}"
@@ -8,7 +8,8 @@ def gem_list
8
8
  rspec-support thread_safe rspec-core rspec-expectations rspec-mocks rspec mini_portile
9
9
  multi_json rspec-support rspec-expectations rspec-mocks mail nokogiri rack rspec unf
10
10
  json thread_safe rspec-core json rack-protection sdoc docile faraday multi_xml coderay
11
- mime-types net-scp multi_json turbolinks formatador coffee-script rack-test]
11
+ mime-types net-scp multi_json turbolinks formatador coffee-script rack-test] +
12
+ %w[devise]
12
13
  # How I got those:
13
14
  require 'gems'
14
15
  Gems.most_downloaded
@@ -12,7 +12,6 @@
12
12
  # Other differences from wwtd:
13
13
  # * automatically installs the bundler gem if it is missing from a ruby version.
14
14
  #
15
- require 'term/ansicolor'
16
15
  require 'yaml'
17
16
  TRAVIS_CONFIG = '.travis.yml'
18
17
 
@@ -1,5 +1,7 @@
1
1
  module DeepCover
2
2
  require 'parser'
3
+ require 'term/ansicolor'
4
+ require 'pry'
3
5
  require_relative 'deep_cover/backports'
4
6
  require_relative 'deep_cover/tools'
5
7
  require_relative_dir 'deep_cover/parser_ext'
@@ -33,7 +33,13 @@ module DeepCover
33
33
  def each_node(from = covered_code.root, &block)
34
34
  return to_enum(:each_node) unless block_given?
35
35
  children = node_children(from)
36
- yield from, children unless from.is_a?(Node::Root)
36
+ begin
37
+ yield from, children unless from.is_a?(Node::Root)
38
+ rescue ProblemWithDiagnostic
39
+ raise
40
+ rescue StandardError, SystemStackError => e
41
+ raise ProblemWithDiagnostic.new(covered_code, from.diagnostic_expression, e)
42
+ end
37
43
  children.each do |child|
38
44
  each_node(child, &block)
39
45
  end
@@ -9,7 +9,7 @@ module DeepCover
9
9
  runs = node_runs(node)
10
10
  if runs != nil
11
11
  node.proper_range.each do |pos|
12
- bc[buffer.line_for_position(pos)-1][buffer.column_for_position(pos)] = runs > 0 ? ' ' : 'x'
12
+ bc[buffer.line_for_position(pos)-buffer.first_line][buffer.column_for_position(pos)] = runs > 0 ? ' ' : 'x'
13
13
  end
14
14
  end
15
15
  end
@@ -3,7 +3,7 @@ module DeepCover
3
3
  # Returns an array of runs, one per line.
4
4
  def results
5
5
  disallow_partial = !options.fetch(:allow_partial, true)
6
- line_hits = Array.new(covered_code.nb_lines)
6
+ line_hits = Array.new(covered_code.nb_lines + covered_code.lineno - 1)
7
7
  each_node do |node, _children|
8
8
  if (runs = node_runs(node))
9
9
  node.executed_locs.each do |loc|
@@ -1,5 +1,3 @@
1
- require 'term/ansicolor'
2
-
3
1
  module DeepCover
4
2
  module CLI
5
3
  class Debugger
@@ -37,8 +37,9 @@ module DeepCover
37
37
  o.banner = "usage: deep-cover [options] [path/to/app/or/gem]"
38
38
  o.separator ''
39
39
  o.string '-o', '--output', 'output folder', default: './coverage'
40
- o.string '-c', '--command', 'command to run tests', default: 'rake'
40
+ o.string '-c', '--command', 'command to run tests', default: 'bundle exec rake'
41
41
  o.bool '--bundle', 'run bundle before the tests', default: true
42
+ o.bool '--process', 'turn off to only redo the reporting', default: true
42
43
  o.separator 'Coverage options'
43
44
  @ignore_uncovered_map = Analyser.optionally_covered.map do |option|
44
45
  default = Config::DEFAULTS[:ignore_uncovered].include?(option)
@@ -8,10 +8,10 @@ module DeepCover
8
8
  # matches regular files, .files, ..files, but not '.' or '..'
9
9
  GLOB_ALL_CONTENT = '{,.[^.],..?}*'
10
10
 
11
- def initialize(gem_path, command: 'rake', **options)
11
+ def initialize(source_path, command: 'rake', **options)
12
12
  @command = command
13
13
  @options = options
14
- @root_path = gem_path = Pathname.new(gem_path).expand_path
14
+ @root_path = @source_path = Pathname.new(source_path).expand_path
15
15
  unless @root_path.join('Gemfile').exist?
16
16
  # E.g. rails/activesupport
17
17
  @root_path = @root_path.dirname
@@ -20,40 +20,85 @@ module DeepCover
20
20
  @dest_root = Pathname('~/test_deep_cover').expand_path
21
21
  @dest_root = Pathname.new(Dir.mktmpdir("deep_cover_test")) unless @dest_root.exist?
22
22
 
23
- FileUtils.rm_rf(Dir.glob("#{@dest_root}/#{GLOB_ALL_CONTENT}"))
24
- gem_relative_path = gem_path.relative_path_from(@root_path)
23
+ gem_relative_path = @source_path.relative_path_from(@root_path)
25
24
  @main_path = @dest_root.join(gem_relative_path)
25
+ singleton_class.include self.class.const_get(Tools.camelize(style))
26
+ end
27
+
28
+ def clear
29
+ FileUtils.rm_rf(Dir.glob("#{@dest_root}/#{GLOB_ALL_CONTENT}"))
26
30
  end
27
31
 
28
32
  def copy
29
33
  return true if @copied
34
+ puts "Cloning..."
30
35
  FileUtils.cp_r(Dir.glob("#{@root_path}/#{GLOB_ALL_CONTENT}"), @dest_root)
31
36
  @copied = true
32
37
  end
33
38
 
34
39
  def patch_ruby_file(ruby_file)
35
- content = File.read(ruby_file)
40
+ content = ruby_file.read
36
41
  # Insert our code after leading comments:
37
42
  content.sub!(/^((#.*\n+)*)/, "#{$1}require 'deep_cover/auto_run';DeepCover::AutoRun.run! '#{@dest_root}';")
38
- File.write(ruby_file, content)
43
+ ruby_file.write(content)
39
44
  end
40
45
 
41
- def each_gem_path
42
- return to_enum __method__ unless block_given?
43
- if @main_path.join('lib').exist?
44
- yield @main_path
46
+ def style
47
+ if @source_path.join('config/environments/test.rb').exist?
48
+ :rails
49
+ elsif @source_path.join('lib').exist?
50
+ :single_gem
45
51
  else # Rails style
52
+ :gem_collection
53
+ end
54
+ end
55
+
56
+ # Style specific functionality
57
+ module Gem
58
+ def each_main_ruby_files(&block)
59
+ each_gem_path do |dest_path|
60
+ main = dest_path.join('lib/*.rb')
61
+ Pathname.glob(main).select(&:file?).each(&block)
62
+ end
63
+ end
64
+
65
+ def each_dir_to_cover
66
+ each_gem_path do |dest_path|
67
+ yield dest_path.join('lib')
68
+ end
69
+ end
70
+ end
71
+
72
+ module SingleGem
73
+ include Gem
74
+ def each_gem_path
75
+ yield @main_path
76
+ end
77
+ end
78
+
79
+ module GemCollection
80
+ include Gem
81
+ def each_gem_path
46
82
  Pathname.glob(@main_path.join('*/lib')).each{|p| yield p.dirname}
47
83
  end
48
84
  end
49
85
 
86
+ module Rails
87
+ def each_main_ruby_files
88
+ yield @main_path.join('config/environments/test.rb')
89
+ end
90
+
91
+ def each_dir_to_cover
92
+ yield @main_path.join('app')
93
+ yield @main_path.join('lib')
94
+ end
95
+ end
96
+
97
+ # Back to global functionality
50
98
  def patch_main_ruby_files
51
- each_gem_path do |dest_path|
52
- main = dest_path.join('lib/*.rb')
53
- Dir.glob(main).select{|p| File.file?(p) }.each do |main|
54
- puts "Patching #{main}"
55
- patch_ruby_file(main)
56
- end
99
+ each_main_ruby_files do |main|
100
+ puts "Patching #{main}"
101
+ patch_ruby_file(main)
57
102
  end
58
103
  end
59
104
 
@@ -88,11 +133,12 @@ module DeepCover
88
133
 
89
134
  def cover
90
135
  coverage = Coverage.new
91
- each_gem_path do |dest_path|
92
- FileUtils.cp_r("#{dest_path}/lib", "#{dest_path}/lib_original")
93
- Tools.dump_covered_code(File.join(dest_path, 'lib_original'),
136
+ each_dir_to_cover do |to_cover|
137
+ original = to_cover.sub_ext('_original')
138
+ FileUtils.cp_r(to_cover, original)
139
+ Tools.dump_covered_code(original,
94
140
  coverage: coverage, root_path: @dest_root.to_s,
95
- dest_path: File.join(dest_path, 'lib'))
141
+ dest_path: to_cover)
96
142
  end
97
143
  coverage.save(@dest_root.to_s)
98
144
  end
@@ -116,11 +162,14 @@ module DeepCover
116
162
  end
117
163
 
118
164
  def run
119
- copy
120
- cover
121
- patch
122
- bundle if @options.fetch(:bundle, true)
123
- process
165
+ if @options.fetch(:process, true)
166
+ clear
167
+ copy
168
+ cover
169
+ patch
170
+ bundle if @options.fetch(:bundle, true)
171
+ process
172
+ end
124
173
  report
125
174
  end
126
175
  end
@@ -3,7 +3,6 @@ module DeepCover
3
3
  silence_warnings do
4
4
  require 'parser/current'
5
5
  end
6
- require 'pry'
7
6
  require_relative 'covered_code'
8
7
  require 'securerandom'
9
8
 
@@ -133,11 +132,15 @@ module DeepCover
133
132
 
134
133
  def save_trackers(global)
135
134
  saved?
135
+ trackers = eval(global)
136
+ # Some testing involves more than one process, some of which don't run any of our covered code.
137
+ # Don't save anything if that's the case
138
+ return if trackers.nil?
136
139
  basename = TRACKER_TEMPLATE % {unique: SecureRandom.urlsafe_base64}
137
140
  dir_path.join(basename).binwrite(Marshal.dump({
138
141
  version: DeepCover::VERSION,
139
142
  global: global,
140
- trackers: eval(global),
143
+ trackers: trackers,
141
144
  }))
142
145
  end
143
146
 
@@ -6,12 +6,11 @@ module DeepCover
6
6
  @@counter = 0
7
7
  @@globals = Hash.new{|h, global| h[global] = eval("#{global} ||= {}") }
8
8
 
9
- def initialize(path: nil, source: nil, lineno: nil, tracker_global: DEFAULT_TRACKER_GLOBAL, local_var: '_temp', name: nil)
9
+ def initialize(path: nil, source: nil, lineno: 1, tracker_global: DEFAULT_TRACKER_GLOBAL, local_var: '_temp', name: nil)
10
10
  raise "Must provide either path or source" unless path || source
11
11
 
12
- @buffer = ::Parser::Source::Buffer.new(path)
12
+ @buffer = ::Parser::Source::Buffer.new(path, lineno)
13
13
  @buffer.source = source ||= File.read(path)
14
- @lineno = lineno
15
14
  @tracker_count = 0
16
15
  @tracker_global = tracker_global
17
16
  @local_var = local_var
@@ -23,6 +22,10 @@ module DeepCover
23
22
  @buffer.name || "(source: '#{@buffer.source[0..20]}...')"
24
23
  end
25
24
 
25
+ def lineno
26
+ @buffer.first_line
27
+ end
28
+
26
29
  def nb_lines
27
30
  @nb_lines ||= begin
28
31
  lines = buffer.source_lines
@@ -37,7 +40,7 @@ module DeepCover
37
40
  def execute_code(binding: DeepCover::GLOBAL_BINDING.dup)
38
41
  return if has_executed?
39
42
  global[nb] = Array.new(@tracker_count, 0)
40
- eval(@covered_source, binding, @buffer.name || '<raw_code>', @lineno || 1)
43
+ eval(@covered_source, binding, @buffer.name || '<raw_code>', lineno)
41
44
  self
42
45
  end
43
46
 
@@ -30,9 +30,38 @@ module DeepCover
30
30
 
31
31
  ### Public API
32
32
 
33
+ # Search self and descendants for a particular Class or type
34
+ def find_all(lookup)
35
+ case lookup
36
+ when ::Class
37
+ each_node.grep(lookup)
38
+ when ::Symbol
39
+ each_node.find_all{|n| n.type == lookup}
40
+ when ::String
41
+ each_node.find_all{|n| n.source == lookup}
42
+ when ::Regexp
43
+ each_node.find_all{|n| n.source =~ lookup}
44
+ else
45
+ binding.pry
46
+ raise ::TypeError, "Expected class or symbol, got #{lookup.class}: #{lookup.inspect}"
47
+ end
48
+ end
49
+
33
50
  # Shortcut to access children
34
51
  def [](v)
35
- children[v]
52
+ if v.is_a?(Integer)
53
+ children.fetch(v)
54
+ else
55
+ found = find_all(v)
56
+ case found.size
57
+ when 1
58
+ found.first
59
+ when 0
60
+ raise "No children of type #{v}"
61
+ else
62
+ raise "Ambiguous lookup #{v}, found #{found}."
63
+ end
64
+ end
36
65
  end
37
66
 
38
67
  # Shortcut to create a node from source code
@@ -106,7 +135,6 @@ module DeepCover
106
135
 
107
136
  private
108
137
  def diagnose(exception)
109
- exp = base_node.loc.expression
110
138
  msg = if self.class == Node
111
139
  "Unknown node type encountered: #{base_node.type}"
112
140
  else
@@ -116,7 +144,7 @@ module DeepCover
116
144
  'Attempting to continue, but this node will not be handled properly',
117
145
  ('Its subnodes will be ignored' if children.empty?),
118
146
  'Source:',
119
- exp && exp.source,
147
+ expression,
120
148
  "Original exception:",
121
149
  exception.inspect,
122
150
  ].join("\n")
@@ -49,6 +49,10 @@ module DeepCover
49
49
  expression.source if expression
50
50
  end
51
51
 
52
+ def diagnostic_expression
53
+ expression || parent.diagnostic_expression
54
+ end
55
+
52
56
  # Returns an array of character numbers (in the original buffer) that
53
57
  # pertain exclusively to this node (and thus not to any children).
54
58
  def proper_range
@@ -58,6 +58,11 @@ module DeepCover
58
58
  flow_entry_count
59
59
  end
60
60
  end
61
+
62
+ # Returns the counts in a hash
63
+ def counts
64
+ {flow_entry: flow_entry_count, flow_completion: flow_completion_count, execution: execution_count}
65
+ end
61
66
  end
62
67
  end
63
68
  end
@@ -37,16 +37,25 @@ module DeepCover
37
37
 
38
38
  private
39
39
 
40
- # Only need to add them to deal with ambiguous cases where a method is hidden by a local. Ex:
40
+ # In different circumstances, we need ().
41
+ # Deal with ambiguous cases where a method is hidden by a local. Ex:
41
42
  # foo 42, 'hello' #=> Works
42
43
  # foo (42), 'hello' #=> Simplification of what DeepCover would generate, still works
43
44
  # foo = 1; foo 42, 'hello' #=> works
44
45
  # foo = 1; foo (42), 'hello' #=> syntax error.
45
46
  # foo = 1; foo((42), 'hello') #=> works
47
+ # Deal with do/end block. Ex:
48
+ # x.foo 42, 43 # => ok
49
+ # x.foo (42), 43 # => ok
50
+ # x.foo ((42)), 43 # => ok
51
+ # x.foo 42, 43 do ; end # => ok
52
+ # x.foo (42), 43 do ; end # => ok
53
+ # x.foo ((42)), 43 do ; end # => parse error!
46
54
  def need_parentheses?
47
55
  true unless
48
56
  arguments.empty? || # No issue when no arguments
49
- receiver || # No ambiguity if there is a receiver
57
+ loc_hash[:selector_end] || # No issue with foo[bar]= and such
58
+ loc_hash[:operator] || # No issue with foo.bar=
50
59
  loc_hash[:begin] # Ok if has parentheses
51
60
  end
52
61
  end
@@ -0,0 +1,61 @@
1
+ module DeepCover
2
+ class ProblemWithDiagnostic < StandardError
3
+ attr_reader :covered_code, :line_range, :original_exception
4
+
5
+ def initialize(covered_code, line_range, original_exception=nil)
6
+ @covered_code = covered_code
7
+ if line_range.is_a?(Parser::Source::Range)
8
+ @line_range = line_range.line..line_range.last_line
9
+ else
10
+ @line_range = line_range
11
+ end
12
+ @original_exception = original_exception
13
+ end
14
+
15
+ def message
16
+ msg = []
17
+ msg << "You found a problem with DeepCover!"
18
+ msg << "Please open an issue at https://github.com/deep-cover/deep-cover/issues"
19
+ msg << "and include the following diagnostic information:"
20
+ extra = begin
21
+ diagnostic_information_lines.map{|line| "| #{line}"}
22
+ rescue ProblemWithDiagnostic
23
+ ["Oh no! We're in deep trouble!!!"]
24
+ rescue Exception => e
25
+ ["Oh no! Even diagnostics are failing: #{e}\n#{e.backtrace}"]
26
+ end
27
+ msg.concat(extra)
28
+ msg.join("\n")
29
+ end
30
+
31
+ def diagnostic_information_lines
32
+ lines = []
33
+ lines << "Source file: #{covered_code.path}"
34
+ lines << "Line numbers: #{line_range}"
35
+ lines << "Source lines around location:"
36
+ lines.concat source_lines.map{|line| " #{line}" }
37
+ if original_exception
38
+ lines << "Original exception:"
39
+ lines << " #{original_exception.class.name}: #{original_exception.message}"
40
+ backtrace = Tools.truncate_backtrace(original_exception)
41
+ lines.concat backtrace.map{|line| " #{line}"}
42
+ end
43
+ lines
44
+ end
45
+
46
+ def source_lines(nb_context_line: 7)
47
+ first_index = line_range.begin - nb_context_line - buffer.first_line
48
+ first_index = 0 if first_index < 0
49
+ last_index = line_range.end + nb_context_line - buffer.first_line
50
+ last_index = 0 if last_index < 0
51
+
52
+ lines = buffer.source_lines[first_index..last_index]
53
+
54
+ Tools.number_lines(lines, lineno: buffer.first_line, bad_linenos: line_range.to_a)
55
+ end
56
+
57
+ def buffer
58
+ covered_code.buffer
59
+ end
60
+ end
61
+ end
@@ -7,8 +7,10 @@ module DeepCover
7
7
  cur_lineno = lineno + i
8
8
  cur_lineno_s = cur_lineno.to_s.rjust(nb_lineno_digits)
9
9
  if bad_linenos.include?(cur_lineno)
10
+ cur_lineno_s = "*#{cur_lineno_s}" unless bad_linenos.empty?
10
11
  prefix = Term::ANSIColor.red("#{cur_lineno_s} | ")
11
12
  else
13
+ cur_lineno_s = " #{cur_lineno_s}" unless bad_linenos.empty?
12
14
  prefix = Term::ANSIColor.white("#{cur_lineno_s} | ")
13
15
  end
14
16
  "#{prefix}#{line}"
@@ -3,7 +3,7 @@ module DeepCover
3
3
  def our_coverage(source, fn, lineno, **options)
4
4
  covered_code = CoveredCode.new(source:source, path: fn, lineno: lineno)
5
5
  Tools.execute_sample(covered_code)
6
- covered_code.line_coverage(options)
6
+ covered_code.line_coverage(options)[(lineno-1)..-1]
7
7
  end
8
8
  end
9
9
  end
@@ -0,0 +1,30 @@
1
+ module DeepCover
2
+ module Tools::TruncateBacktrace
3
+ def truncate_backtrace(backtrace, extra_context: 10)
4
+ backtrace = backtrace.backtrace if backtrace.is_a?(Exception)
5
+ trace_lines = backtrace.uniq
6
+
7
+ keep_from_begin = 0
8
+ keep_from_end = backtrace.size - 1
9
+
10
+ trace_lines.each do |line|
11
+ from_begin = backtrace.index(line)
12
+ from_end = backtrace.rindex(line)
13
+ if from_begin <= backtrace.size - 1 - from_end
14
+ keep_from_begin = [keep_from_begin, from_begin].max
15
+ else
16
+ keep_from_end = [keep_from_end, from_end].min
17
+ end
18
+ end
19
+
20
+ keep_from_begin += extra_context
21
+ keep_from_end -= extra_context
22
+
23
+ return backtrace if keep_from_begin + 5 >= keep_from_end
24
+
25
+ result = backtrace[0..keep_from_begin]
26
+ result << "... #{keep_from_end - keep_from_begin - 1} levels..."
27
+ result.concat backtrace[keep_from_end..-1]
28
+ end
29
+ end
30
+ end
@@ -1,3 +1,3 @@
1
1
  module DeepCover
2
- VERSION = "0.1.13"
2
+ VERSION = "0.1.14"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deep-cover
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.13
4
+ version: 0.1.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marc-André Lafortune
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2017-11-05 00:00:00.000000000 Z
12
+ date: 2017-11-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: parser
@@ -288,6 +288,7 @@ files:
288
288
  - lib/deep_cover/node/splat.rb
289
289
  - lib/deep_cover/node/variables.rb
290
290
  - lib/deep_cover/parser_ext/range.rb
291
+ - lib/deep_cover/problem_with_diagnostic.rb
291
292
  - lib/deep_cover/reporter.rb
292
293
  - lib/deep_cover/reporter/istanbul.rb
293
294
  - lib/deep_cover/tools.rb
@@ -304,6 +305,7 @@ files:
304
305
  - lib/deep_cover/tools/require_relative_dir.rb
305
306
  - lib/deep_cover/tools/silence_warnings.rb
306
307
  - lib/deep_cover/tools/slice.rb
308
+ - lib/deep_cover/tools/truncate_backtrace.rb
307
309
  - lib/deep_cover/version.rb
308
310
  homepage: http://github.com
309
311
  licenses:
@@ -325,7 +327,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
325
327
  version: '0'
326
328
  requirements: []
327
329
  rubyforge_project:
328
- rubygems_version: 2.6.13
330
+ rubygems_version: 2.6.14
329
331
  signing_key:
330
332
  specification_version: 4
331
333
  summary: Write a short summary, because Rubygems requires one.