solargraph_test_coverage 0.2.4 → 0.3.0

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: afacac1a6d557ea7ef39c86081fc7f3aafcb7e09c3a8243679e9d252ba47a2c7
4
- data.tar.gz: 9e929da5ff5873ef3c044edebb4a96575cb47f4bfb9b2d36121082257ac03569
3
+ metadata.gz: 5aba663e3f7dca496c3c527adc1be8024353932ef806cf6f9eed9c70bd44f120
4
+ data.tar.gz: d4a031ffbb4e97f2d13eff2552760d3beae56413b1666e6e84edd1c182919c1e
5
5
  SHA512:
6
- metadata.gz: 58b77e64736847a4d5d6fedb3c67f7b76e6af6056e844926356269c052b8bfe5f29af0c1f76e916f2e26d1ddb5c5628d3d324f53c890ceeff55f289959409b8c
7
- data.tar.gz: 92e5f351f941d99103339f80fb05450e1393cb5929f584cd7d080759450f9cd31422a71d51ba847664b1217b88c761f1f132f346f280106580c8c806ba1c6bd9
6
+ metadata.gz: 71edec840ad30fb0e2852c0f6ad3928b961631f546d02060500001e4c8e6bc0ff9f5bb3f71f4e4261ed00d483d6d4c6c3e25fc7dca35cd970a386b77ba28df16
7
+ data.tar.gz: 1abf9451d0fdcaca5f5f4ec52a6ffb918391bfdc092a09c77778b76dcc137b41c0e17719d7141182a92f472f2505d56f2f9b93fc0547748f681a1f1ba226bd78
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- solargraph_test_coverage (0.2.3)
4
+ solargraph_test_coverage (0.3.0)
5
5
  solargraph (~> 0.40, > 0.40)
6
6
 
7
7
  GEM
@@ -19,7 +19,7 @@ GEM
19
19
  kramdown (~> 2.0)
20
20
  nokogiri (1.12.4-x86_64-darwin)
21
21
  racc (~> 1.4)
22
- parallel (1.20.1)
22
+ parallel (1.21.0)
23
23
  parser (3.0.2.0)
24
24
  ast (~> 2.4.1)
25
25
  racc (1.5.2)
@@ -29,7 +29,7 @@ GEM
29
29
  reverse_markdown (2.0.0)
30
30
  nokogiri
31
31
  rexml (3.2.5)
32
- rubocop (1.20.0)
32
+ rubocop (1.21.0)
33
33
  parallel (~> 1.10)
34
34
  parser (>= 3.0.0.0)
35
35
  rainbow (>= 2.2.2, < 4.0)
@@ -41,7 +41,7 @@ GEM
41
41
  rubocop-ast (1.11.0)
42
42
  parser (>= 3.0.1.1)
43
43
  ruby-progressbar (1.11.0)
44
- solargraph (0.43.0)
44
+ solargraph (0.43.2)
45
45
  backport (~> 1.2)
46
46
  benchmark
47
47
  bundler (>= 1.17.2)
@@ -58,7 +58,7 @@ GEM
58
58
  yard (~> 0.9, >= 0.9.24)
59
59
  thor (1.1.0)
60
60
  tilt (2.0.10)
61
- unicode-display_width (2.0.0)
61
+ unicode-display_width (2.1.0)
62
62
  yard (0.9.26)
63
63
 
64
64
  PLATFORMS
data/README.md CHANGED
@@ -33,13 +33,13 @@ test_coverage:
33
33
  preload_rails: true
34
34
  test_framework: rspec # or minitest
35
35
  coverage:
36
- - line
37
- - branch
38
- - test_failing
39
- - test_missing
36
+ - line
37
+ - branch
38
+ - test_failing
39
+ - test_missing
40
40
  exclude_paths:
41
- - 'app/controller'
42
- - 'concerns'
41
+ - 'app/controller'
42
+ - 'concerns'
43
43
  ```
44
44
 
45
45
 
@@ -51,6 +51,9 @@ And then execute:
51
51
  Or install it yourself as:
52
52
 
53
53
  $ gem install solargraph_test_coverage
54
+
55
+ A note on testing framework:
56
+ Since both Minitest and RSpec are supported, neither are direct dependencies of this gem. Therefore, you have to have them installed separately either via your bundle or via `gem install`.
54
57
 
55
58
  ## Usage
56
59
 
@@ -3,22 +3,15 @@
3
3
  module SolargraphTestCoverage
4
4
  # Adapted from SimpleCov - Small class that turns branch coverage data into something easier to work with
5
5
  class Branch
6
- class << self
7
- #
8
- # Builds an array of Branch objects for every branch in results hash.
9
- #
10
- # @return [Array]
11
- #
12
- def build_from(results)
13
- results.fetch(:branches, {}).flat_map do |condition, branches|
14
- _condition_type, _condition_id, condition_start_line, * = condition
6
+ def self.build_from(results)
7
+ results.fetch(:branches, {}).flat_map do |condition, branches|
8
+ _condition_type, _condition_id, condition_start_line, * = condition
15
9
 
16
- branches.map do |branch_data, hit_count|
17
- type, _id, start_line, _start_col, end_line, _end_col = branch_data
10
+ branches.map do |branch_data, hit_count|
11
+ type, _id, start_line, _start_col, end_line, _end_col = branch_data
18
12
 
19
- new(start_line: start_line, end_line: end_line, coverage: hit_count,
20
- inline: start_line == condition_start_line, type: type)
21
- end
13
+ new(start_line: start_line, end_line: end_line, coverage: hit_count,
14
+ inline: start_line == condition_start_line, type: type)
22
15
  end
23
16
  end
24
17
  end
@@ -33,44 +26,18 @@ module SolargraphTestCoverage
33
26
  @type = type
34
27
  end
35
28
 
36
- #
37
- # Predicate method for @inline instance variable
38
- #
39
- # @return [Boolean]
40
- #
41
29
  def inline?
42
30
  @inline
43
31
  end
44
32
 
45
- #
46
- # Return true if there is relevant count defined > 0
47
- #
48
- # @return [Boolean]
49
- #
50
33
  def covered?
51
34
  coverage.positive?
52
35
  end
53
36
 
54
- #
55
- # The line on which we want to report the coverage
56
- #
57
- # Usually we choose the line above the start of the branch (so that it shows up
58
- # at if/else) because that
59
- # * highlights the condition
60
- # * makes it distinguishable if the first line of the branch is an inline branch
61
- # (see the nested_branches fixture)
62
- #
63
- # @return [Integer]
64
- #
65
37
  def report_line
66
38
  inline? ? start_line : start_line - 1
67
39
  end
68
40
 
69
- #
70
- # Return array with coverage count and badge
71
- #
72
- # @return [Hash]
73
- #
74
41
  def report
75
42
  { type: type, line: report_line }
76
43
  end
@@ -1,82 +1,134 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # frozen_string_literal = true
4
-
5
3
  module SolargraphTestCoverage
6
- class Config
7
- class << self
8
- DEFAULTS = {
9
- 'preload_rails' => true, # can be true or false - performance optimization
10
- 'test_framework' => 'rspec', # can be 'rspec' or 'minitest'
11
- 'coverage' => [ # All diagnostics are enabled by default
12
- 'line', # Specifying an array with fewer diagnostics will overwrite this
13
- 'branch',
14
- 'test_failing',
15
- 'test_missing'
16
- ],
17
- 'exclude_paths' => [ # don't attempt to find/run a spec for files that match these paths
18
- 'app/controller',
19
- 'concerns'
20
- ]
21
- }.freeze
22
-
23
- def preload_rails?
24
- plugin_config['preload_rails']
25
- end
4
+ module Config
5
+ extend self
26
6
 
27
- def exclude_paths
28
- plugin_config['exclude_paths']
29
- end
7
+ DEFAULTS = {
8
+ 'preload_rails' => true, # can be true or false - performance optimization
9
+ 'debug' => false, # can be true or false - shows debug messages when ChildFailedError is raised
10
+ 'test_framework' => 'rspec', # can be 'rspec' or 'minitest'
11
+ 'coverage' => [ # All diagnostics are enabled by default
12
+ 'line', # Specifying an array with fewer diagnostics will overwrite this
13
+ 'branch',
14
+ 'test_failing',
15
+ 'test_missing',
16
+ 'example_failing'
17
+ ],
18
+ 'exclude_paths' => [ # don't attempt to find/run a spec for files that match these paths
19
+ 'app/controller',
20
+ 'concerns'
21
+ ]
22
+ }.freeze
30
23
 
31
- def line_coverage?
32
- plugin_config['coverage'].include? 'line'
33
- end
24
+ def debug?
25
+ plugin_config['debug']
26
+ end
34
27
 
35
- def branch_coverage?
36
- plugin_config['coverage'].include? 'branch'
37
- end
28
+ def preload_rails?
29
+ plugin_config['preload_rails']
30
+ end
38
31
 
39
- def test_failing_coverage?
40
- plugin_config['coverage'].include? 'test_failing'
41
- end
32
+ def exclude_paths
33
+ plugin_config['exclude_paths']
34
+ end
42
35
 
43
- def test_missing_coverage?
44
- plugin_config['coverage'].include? 'test_missing'
45
- end
36
+ def line_coverage?
37
+ plugin_config['coverage'].include? 'line'
38
+ end
39
+
40
+ def branch_coverage?
41
+ plugin_config['coverage'].include? 'branch'
42
+ end
43
+
44
+ def test_failing_coverage?
45
+ plugin_config['coverage'].include? 'test_failing'
46
+ end
47
+
48
+ def test_missing_coverage?
49
+ plugin_config['coverage'].include? 'test_missing'
50
+ end
46
51
 
47
- def test_framework
48
- plugin_config['test_framework']
52
+ def example_failing_coverage?
53
+ plugin_config['coverage'].include? 'example_failing'
54
+ end
55
+
56
+ def test_framework
57
+ plugin_config['test_framework']
58
+ end
59
+
60
+ def test_dir
61
+ case test_framework
62
+ when 'rspec'
63
+ 'spec'
64
+ when 'minitest'
65
+ 'test'
49
66
  end
67
+ end
50
68
 
51
- def test_dir
52
- case test_framework
53
- when 'rspec'
54
- 'spec'
55
- when 'minitest'
56
- 'test'
57
- end
69
+ def test_file_suffix
70
+ case test_framework
71
+ when 'rspec'
72
+ '_spec.rb'
73
+ when 'minitest'
74
+ '_test.rb'
58
75
  end
76
+ end
59
77
 
60
- def test_file_suffix
61
- case test_framework
62
- when 'rspec'
63
- '_spec.rb'
64
- when 'minitest'
65
- '_test.rb'
66
- end
78
+ def require_testing_framework!
79
+ case test_framework
80
+ when 'rspec'
81
+ require 'rspec/core'
82
+ when 'minitest'
83
+ require 'minitest/autorun'
84
+ else
85
+ raise UnknownTestingGem
67
86
  end
87
+ end
68
88
 
69
- private
89
+ # This gives us a nice speed-boost when running test in child process
90
+ #
91
+ # We rescue the LoadError since Solargraph would catch it otherwise,
92
+ # and not load the plugin at all.
93
+ #
94
+ # Adding the spec/ directory to the $LOAD_PATH lets 'require "spec_helper"'
95
+ # commonly found in rails_helper work.
96
+ #
97
+ # If Coverage was started in Rails/Spec helper by SimpleCov,
98
+ # calling Coverage.result after requiring stops and resets it.
99
+ #
100
+ # This is a bit experimental
101
+ #
102
+ def preload_rails!
103
+ return if defined?(Rails) || !File.file?('spec/rails_helper.rb')
70
104
 
71
- def plugin_config
72
- @plugin_config ||= workspace_config.tap do |config|
73
- DEFAULTS.each_key { |key| config[key] = DEFAULTS[key] unless config.key?(key) }
74
- end
75
- end
105
+ $LOAD_PATH.unshift(test_path) unless $LOAD_PATH.include?(test_path)
106
+
107
+ require File.join(test_path, 'rails_helper')
108
+ Coverage.result(stop: true, clear: true) if Coverage.running?
76
109
 
77
- def workspace_config
78
- Solargraph::Workspace::Config.new(Dir.pwd).raw_data.fetch('test_coverage', {})
110
+ true
111
+ rescue LoadError => e
112
+ Solargraph::Logging.logger.warn "LoadError when trying to require 'rails_helper'"
113
+ Solargraph::Logging.logger.warn "[#{e.class}] #{e.message}"
114
+
115
+ false
116
+ end
117
+
118
+ private
119
+
120
+ def plugin_config
121
+ @plugin_config ||= workspace_config.tap do |config|
122
+ DEFAULTS.each_key { |key| config[key] = DEFAULTS[key] unless config.key?(key) }
79
123
  end
80
124
  end
125
+
126
+ def workspace_config
127
+ Solargraph::Workspace::Config.new(Dir.pwd).raw_data.fetch('test_coverage', {})
128
+ end
129
+
130
+ def test_path
131
+ FileHelpers.test_path
132
+ end
81
133
  end
82
134
  end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolargraphTestCoverage
4
+ module DiagnosticMessages
5
+ def line_coverage_warning(source, line)
6
+ return unless Config.line_coverage?
7
+
8
+ {
9
+ range: range(line, 0, line, source.code.lines[line].length),
10
+ severity: Solargraph::Diagnostics::Severities::WARNING,
11
+ source: 'TestCoverage',
12
+ message: 'Line is missing test coverage'
13
+ }
14
+ end
15
+
16
+ def branch_coverage_warning(source, report)
17
+ return unless Config.branch_coverage?
18
+
19
+ {
20
+ range: range(report[:line] - 1, 0, report[:line] - 1, source.code.lines[report[:line] - 1].length),
21
+ severity: Solargraph::Diagnostics::Severities::WARNING,
22
+ source: 'TestCoverage',
23
+ message: "'#{report[:type].upcase}' branch is missing test coverage"
24
+ }
25
+ end
26
+
27
+ def test_failing_error(source)
28
+ return unless Config.test_failing_coverage?
29
+
30
+ {
31
+ range: range(0, 0, 0, source.code.lines[0].length),
32
+ severity: Solargraph::Diagnostics::Severities::ERROR,
33
+ source: 'TestCoverage',
34
+ message: 'Unit Test is currently failing.'
35
+ }
36
+ end
37
+
38
+ def test_missing_error(source)
39
+ return unless Config.test_missing_coverage?
40
+
41
+ {
42
+ range: range(0, 0, 0, source.code.lines[0].length),
43
+ severity: Solargraph::Diagnostics::Severities::HINT,
44
+ source: 'TestCoverage',
45
+ message: "No test file found at '#{FileHelpers.test_file(source).sub("#{Dir.pwd}/", '')}'"
46
+ }
47
+ end
48
+
49
+ def example_failing_error(example, source)
50
+ return unless Config.example_failing_coverage?
51
+
52
+ {
53
+ range: range(example[:line_number], 0, example[:line_number], source.code.lines[example[:line_number]].length),
54
+ severity: Solargraph::Diagnostics::Severities::ERROR,
55
+ source: 'ExampleStatus',
56
+ message: example[:message]
57
+ }
58
+ end
59
+
60
+ def debug_message(exception, source)
61
+ {
62
+ range: range(0, 0, 0, source.code.lines[0].length),
63
+ severity: Solargraph::Diagnostics::Severities::ERROR,
64
+ source: 'SolargraphTestCoverage Plugin',
65
+ message: "DEBUG: (ChildFailedError) #{exception.message}"
66
+ }
67
+ end
68
+
69
+ private
70
+
71
+ def range(start_line, start_column, end_line, end_column)
72
+ Solargraph::Range.from_to(start_line, start_column, end_line, end_column).to_hash
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # example_status reporter for Solargraph
4
+ module SolargraphTestCoverage
5
+ class ExampleStatusReporter < Solargraph::Diagnostics::Base
6
+ include ReporterHelpers
7
+ include ReporterGuards
8
+ include DiagnosticMessages
9
+
10
+ def diagnose(source, _api_map)
11
+ if source.code.empty? || using_debugger?(source) || !is_test_file?(source) || is_test_support_file?(source)
12
+ return []
13
+ end
14
+
15
+ example_failing_errors(source, run_test(source, source.location.filename))
16
+ rescue ChildFailedError => e
17
+ Config.debug? ? [debug_message(e, source)] : []
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolargraphTestCoverage
4
+ module FileHelpers
5
+ extend self
6
+
7
+ def test_file(source)
8
+ relative_filepath = source.location.filename.sub(Dir.pwd, '').split('/').reject(&:empty?)
9
+
10
+ if relative_filepath.first == Config.test_dir && relative_filepath.last.end_with?(Config.test_file_suffix)
11
+ return source.location.filename
12
+ end
13
+
14
+ relative_filepath[0] = Config.test_dir
15
+ File.join(Dir.pwd, relative_filepath.join('/')).sub('.rb', Config.test_file_suffix)
16
+ end
17
+
18
+ def test_path
19
+ File.join(Dir.pwd, Config.test_dir)
20
+ end
21
+ end
22
+ end
@@ -5,28 +5,39 @@ module SolargraphTestCoverage
5
5
  # When called with a block, runs the content of said block in a new (forked) process
6
6
  # the return value of the process/block can be captured and used in parent process
7
7
  class ForkProcess
8
- #
9
- # Executes block in forked process, and captures returned value of that block
10
- # Returns result of block
11
- #
12
- # @return [Object]
13
- #
14
- def self.run
15
- read, write = IO.pipe
8
+ def self.call(&block)
9
+ new.run(&block)
10
+ end
11
+
12
+ def initialize
13
+ @read, @write = IO.pipe
14
+ end
16
15
 
16
+ def run(&block)
17
17
  pid = fork do
18
- read.close
19
- result = yield
20
- Marshal.dump(result, write)
18
+ @read.close
19
+ Marshal.dump(run_block_with_timeout(&block), @write)
21
20
  exit!(0) # skips exit handlers.
22
21
  end
23
22
 
24
- write.close
25
- result = read.read
23
+ @write.close
24
+ result = @read.read
25
+
26
26
  Process.wait(pid)
27
- raise ChildFailedError if result.nil? || result.empty?
27
+ raise ChildFailedError, "Couldn't read pipe" if result.nil?
28
+
29
+ Marshal.load(result).tap do |r|
30
+ raise ChildFailedError, "Marshal.load(result) returned nil" if r.nil?
31
+ raise ChildFailedError, r.message if r.is_a? Exception
32
+ end
33
+ end
34
+
35
+ private
28
36
 
29
- Marshal.load(result)
37
+ def run_block_with_timeout(&block)
38
+ Timeout.timeout(30000, &block)
39
+ rescue StandardError => e
40
+ e
30
41
  end
31
42
  end
32
43
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolargraphTestCoverage
4
+ # Some guard functions for the diagnostics
5
+ module ReporterGuards
6
+ def has_test_file?(source)
7
+ File.file? FileHelpers.test_file(source)
8
+ end
9
+
10
+ def is_test_file?(source)
11
+ source.location.filename.start_with? FileHelpers.test_path
12
+ end
13
+
14
+ def is_test_support_file?(source)
15
+ is_test_file?(source) && !source.location.filename.end_with?(Config.test_file_suffix)
16
+ end
17
+
18
+ def exclude_file?(source)
19
+ Config.exclude_paths.any? { |path| source.location.filename.sub(Dir.pwd, '').include? path }
20
+ end
21
+
22
+ def using_debugger?(source)
23
+ source.code.include?('binding.pry') ||
24
+ source.code.include?('byebug') ||
25
+ source.code.include?('debugger')
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolargraphTestCoverage
4
+ # Some helper functions for the diagnostics
5
+ module ReporterHelpers
6
+ # @return [Hash]
7
+ def run_test(source, test_file)
8
+ ForkProcess.call do
9
+ Coverage.start(lines: true, branches: true)
10
+ runner = TestRunner.with(test_file).run!
11
+
12
+ Coverage.result
13
+ .fetch(source.location.filename, {})
14
+ .merge({ test_status: runner.passed?, failed_examples: runner.failed_examples })
15
+ end
16
+ end
17
+
18
+ def line_warnings(source, results)
19
+ uncovered_lines(results).map { |line| line_coverage_warning(source, line) }
20
+ end
21
+
22
+ def branch_warnings(source, results)
23
+ uncovered_branches(results).map { |branch| branch_coverage_warning(source, branch.report) }
24
+ end
25
+
26
+ def test_passing_error(source, results)
27
+ results[:test_status] ? [] : [test_failing_error(source)]
28
+ end
29
+
30
+ def example_failing_errors(source, results)
31
+ results.fetch(:failed_examples, [])
32
+ .map { |example| example_failing_error(example, source) }
33
+ end
34
+
35
+ # Adapted from SingleCov
36
+ # Coverage returns nil for untestable lines (like 'do', 'end', 'if' keywords)
37
+ # otherwise returns int showing how many times a line was called
38
+ #
39
+ # [nil, 1, 0, 1, 0] -> [3, 5]
40
+ # Returns array of line numbers with 0 coverage
41
+ def uncovered_lines(results)
42
+ return [] unless results[:lines]
43
+
44
+ results[:lines].each_with_index
45
+ .select { |c, _| c&.zero? }
46
+ .map { |_, i| i }
47
+ .compact
48
+ end
49
+
50
+ def uncovered_branches(results)
51
+ Branch.build_from(results).reject(&:covered?)
52
+ end
53
+ end
54
+ end
@@ -3,115 +3,23 @@
3
3
  # test_coverage reporter for Solargraph
4
4
  module SolargraphTestCoverage
5
5
  class TestCoverageReporter < Solargraph::Diagnostics::Base
6
- include Helpers
6
+ include ReporterHelpers
7
+ include ReporterGuards
8
+ include DiagnosticMessages
7
9
 
8
- # LSP Diagnostic method
9
- # @return [Array]
10
- #
11
10
  def diagnose(source, _api_map)
12
- return [] if source.code.empty? || exclude_file?(source.location.filename)
13
- return [test_missing_error(source)] unless File.file?(test_file(source))
11
+ return [] if source.code.empty? || using_debugger?(source) || exclude_file?(source) || is_test_file?(source)
12
+ return [test_missing_error(source)] unless has_test_file?(source)
14
13
 
15
- results = run_test(source)
16
- messages(source, results)
17
- rescue ChildFailedError
18
- []
19
- end
20
-
21
- private
14
+ results = run_test(source, FileHelpers.test_file(source))
22
15
 
23
- # Compiles all diagnostic messages for source file
24
- # @return [Array]
25
- #
26
- def messages(source, results)
27
- messages = [
16
+ [
28
17
  line_warnings(source, results),
29
18
  branch_warnings(source, results),
30
19
  test_passing_error(source, results)
31
- ]
32
-
33
- messages.flatten.compact
34
- end
35
-
36
- # Creates array of warnings for uncovered lines
37
- # @return [Array]
38
- #
39
- def line_warnings(source, results)
40
- uncovered_lines(results).map { |line| line_coverage_warning(source, line) }
41
- end
42
-
43
- # Creates array of warnings for uncovered branches
44
- # @return [Array]
45
- #
46
- def branch_warnings(source, results)
47
- uncovered_branches(results).map { |branch| branch_coverage_warning(source, branch.report) }
48
- end
49
-
50
- # Creates array containing error for failing spec
51
- # @return [Array]
52
- #
53
- def test_passing_error(source, results)
54
- results[:test_status] ? [] : [test_failing_error(source)]
55
- end
56
-
57
- # Creates LSP warning message for missing line coverage
58
- # @return [Hash]
59
- #
60
- def line_coverage_warning(source, line)
61
- return unless Config.line_coverage?
62
-
63
- {
64
- range: range(line, 0, line, source.code.lines[line].length),
65
- severity: Solargraph::Diagnostics::Severities::WARNING,
66
- source: 'TestCoverage',
67
- message: 'Line is missing test coverage'
68
- }
69
- end
70
-
71
- # Creates LSP warning message for missing branch coverage
72
- # Line numbers are off by 1, since Branch Coverage starts counting at 1, not 0
73
- #
74
- # @return [Hash]
75
- #
76
- def branch_coverage_warning(source, report)
77
- return unless Config.branch_coverage?
78
-
79
- {
80
- range: range(report[:line] - 1, 0, report[:line] - 1, source.code.lines[report[:line] - 1].length),
81
- severity: Solargraph::Diagnostics::Severities::WARNING,
82
- source: 'TestCoverage',
83
- message: "'#{report[:type].upcase}' branch is missing test coverage"
84
- }
85
- end
86
-
87
- # Creates LSP error message if test file is failing
88
- # @return [Hash]
89
- #
90
- def test_failing_error(source)
91
- return unless Config.test_failing_coverage?
92
-
93
- {
94
- range: range(0, 0, 0, source.code.lines[0].length),
95
- severity: Solargraph::Diagnostics::Severities::ERROR,
96
- source: 'TestCoverage',
97
- message: 'Unit Test is currently failing.'
98
- }
99
- end
100
-
101
- #
102
- # Creates LSP error message if no test file can be found
103
- #
104
- # @return [Hash]
105
- #
106
- def test_missing_error(source)
107
- return unless Config.test_missing_coverage?
108
-
109
- {
110
- range: range(0, 0, 0, source.code.lines[0].length),
111
- severity: Solargraph::Diagnostics::Severities::ERROR,
112
- source: 'TestCoverage',
113
- message: "No unit test file found at #{test_file(source)}"
114
- }
20
+ ].flatten.compact
21
+ rescue ChildFailedError => e
22
+ Config.debug? ? [debug_message(e, source)] : []
115
23
  end
116
24
  end
117
25
  end
@@ -15,14 +15,15 @@ module SolargraphTestCoverage
15
15
  def initialize(test_file)
16
16
  @test_file = test_file
17
17
  @result = nil
18
+ @output = StringIO.new
18
19
  end
19
20
 
20
21
  def run!
21
- @result = test_framework_runner.run(test_options)
22
+ @result = test_framework_runner.run(test_options, $stderr, @output)
22
23
  self
23
24
  end
24
25
 
25
- def test_options
26
+ def failed_examples
26
27
  raise NotImplementedError
27
28
  end
28
29
 
@@ -30,21 +31,43 @@ module SolargraphTestCoverage
30
31
  raise NotImplementedError
31
32
  end
32
33
 
34
+ private
35
+
36
+ def test_options
37
+ raise NotImplementedError
38
+ end
39
+
33
40
  def test_framework_runner
34
41
  raise NotImplementedError
35
42
  end
43
+
44
+ def output
45
+ return if @output.string.empty?
46
+
47
+ JSON.parse @output.string
48
+ end
36
49
  end
37
50
 
38
51
  # Test Runner Subclass for RSpec
39
52
  class RSpecRunner < TestRunner
40
- def test_options
41
- [@test_file, '-o', '/dev/null']
53
+ def failed_examples
54
+ return unless output
55
+
56
+ output['examples']
57
+ .select { |example| example['status'] == 'failed' }
58
+ .map { |example| { line_number: example['line_number'] - 1, message: example.dig('exception', 'message') } }
42
59
  end
43
60
 
44
61
  def passed?
45
62
  @result&.zero?
46
63
  end
47
64
 
65
+ private
66
+
67
+ def test_options
68
+ [@test_file, '--format', 'json']
69
+ end
70
+
48
71
  def test_framework_runner
49
72
  RSpec::Core::Runner
50
73
  end
@@ -52,14 +75,21 @@ module SolargraphTestCoverage
52
75
 
53
76
  # Test Runner Subclass for Minitest
54
77
  class MinitestRunner < TestRunner
55
- def test_options
56
- [@test_file]
78
+ # TODO
79
+ def failed_examples
80
+ []
57
81
  end
58
82
 
59
83
  def passed?
60
84
  @result
61
85
  end
62
86
 
87
+ private
88
+
89
+ def test_options
90
+ [@test_file]
91
+ end
92
+
63
93
  def test_framework_runner
64
94
  Minitest
65
95
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolargraphTestCoverage
4
- VERSION = '0.2.4'
4
+ VERSION = '0.3.0'
5
5
  end
@@ -3,21 +3,27 @@
3
3
  require 'solargraph_test_coverage/version'
4
4
  require 'solargraph_test_coverage/branch'
5
5
  require 'solargraph_test_coverage/fork_process'
6
- require 'solargraph_test_coverage/helpers'
6
+ require 'solargraph_test_coverage/reporter_helpers'
7
+ require 'solargraph_test_coverage/reporter_guards'
8
+ require 'solargraph_test_coverage/file_helpers'
7
9
  require 'solargraph_test_coverage/config'
8
10
  require 'solargraph_test_coverage/test_runner'
11
+ require 'solargraph_test_coverage/diagnostic_messages'
9
12
  require 'solargraph_test_coverage/test_coverage_reporter'
13
+ require 'solargraph_test_coverage/example_status_reporter'
10
14
 
11
15
  require 'solargraph'
12
16
  require 'coverage'
17
+ require 'timeout'
13
18
 
14
19
  module SolargraphTestCoverage
15
20
  class ChildFailedError < StandardError; end
16
21
 
17
22
  class UnknownTestingGem < StandardError; end
18
23
 
19
- Helpers.require_testing_framework!
20
- Helpers.preload_rails! if Config.preload_rails?
24
+ Config.require_testing_framework!
25
+ Config.preload_rails! if Config.preload_rails?
21
26
 
22
27
  Solargraph::Diagnostics.register 'test_coverage', TestCoverageReporter
28
+ Solargraph::Diagnostics.register 'example_status', ExampleStatusReporter
23
29
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solargraph_test_coverage
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cameron Kolkey
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-09-03 00:00:00.000000000 Z
11
+ date: 2021-09-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: solargraph
@@ -47,8 +47,12 @@ files:
47
47
  - lib/solargraph_test_coverage.rb
48
48
  - lib/solargraph_test_coverage/branch.rb
49
49
  - lib/solargraph_test_coverage/config.rb
50
+ - lib/solargraph_test_coverage/diagnostic_messages.rb
51
+ - lib/solargraph_test_coverage/example_status_reporter.rb
52
+ - lib/solargraph_test_coverage/file_helpers.rb
50
53
  - lib/solargraph_test_coverage/fork_process.rb
51
- - lib/solargraph_test_coverage/helpers.rb
54
+ - lib/solargraph_test_coverage/reporter_guards.rb
55
+ - lib/solargraph_test_coverage/reporter_helpers.rb
52
56
  - lib/solargraph_test_coverage/test_coverage_reporter.rb
53
57
  - lib/solargraph_test_coverage/test_runner.rb
54
58
  - lib/solargraph_test_coverage/version.rb
@@ -1,109 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SolargraphTestCoverage
4
- # Some helper functions for the diagnostics
5
- module Helpers
6
- # @return [Boolean]
7
- def exclude_file?(source_filename)
8
- return true if source_filename.start_with? Helpers.test_path
9
-
10
- Config.exclude_paths.each { |path| return true if source_filename.sub(Dir.pwd, '').include? path }
11
-
12
- false
13
- end
14
-
15
- # @return [String]
16
- def test_file(source)
17
- relative_filepath = source.location.filename.sub(Dir.pwd, '').split('/').reject(&:empty?)
18
- relative_filepath[0] = Config.test_dir
19
-
20
- File.join(Dir.pwd, relative_filepath.join('/'))
21
- .sub('.rb', Config.test_file_suffix)
22
- end
23
-
24
- # @return [Hash]
25
- def run_test(source)
26
- ForkProcess.run do
27
- Coverage.start(lines: true, branches: true)
28
- runner = TestRunner.with(test_file(source)).run!
29
- Coverage.result.fetch(source.location.filename, {}).merge({ test_status: runner.passed? })
30
- end
31
- end
32
-
33
- # Adapted from SingleCov
34
- # Coverage returns nil for untestable lines (like do, end, if keywords)
35
- # otherwise returns int showing how many times a line was called
36
- #
37
- # [nil, 1, 0, 1, 0] -> [3, 5]
38
- #
39
- # @return [Array]
40
- #
41
- def uncovered_lines(results)
42
- return [] unless results[:lines]
43
-
44
- results[:lines].each_with_index
45
- .select { |c, _| c&.zero? }
46
- .map { |_, i| i }
47
- .compact
48
- end
49
-
50
- # @return [Array]
51
- def uncovered_branches(results)
52
- Branch.build_from(results).reject(&:covered?)
53
- end
54
-
55
- # @return [Hash]
56
- def range(start_line, start_column, end_line, end_column)
57
- Solargraph::Range.from_to(start_line, start_column, end_line, end_column).to_hash
58
- end
59
-
60
- def self.require_testing_framework!
61
- case Config.test_framework
62
- when 'rspec'
63
- require 'rspec/core'
64
- when 'minitest'
65
- require 'minitest/autorun'
66
- else
67
- raise UnknownTestingGem
68
- end
69
- end
70
-
71
- # Only called once, when gem is loaded
72
- # Preloads rails via spec/rails_helper if Rails isn't already defined
73
- # This gives us a nice speed-boost when running test in child process
74
- #
75
- # We rescue the LoadError since Solargraph would catch it otherwise,
76
- # and not load the plugin at all.
77
- #
78
- # Adding the spec/ directory to the $LOAD_PATH lets 'require "spec_helper"'
79
- # commonly found in rails_helper work.
80
- #
81
- # If Coverage was started in Rails/Spec helper by SimpleCov,
82
- # calling Coverage.result after requiring stops and resets it.
83
- #
84
- # This is a bit experimental - I'm not sure if there will be downstream side-effects
85
- #
86
- # @return [Boolean]
87
- #
88
- def self.preload_rails!
89
- return if defined?(Rails) || !File.file?('spec/rails_helper.rb')
90
-
91
- $LOAD_PATH.unshift(test_path) unless $LOAD_PATH.include?(test_path)
92
-
93
- require File.join(test_path, 'rails_helper')
94
- Coverage.result(stop: true, clear: true) if Coverage.running?
95
-
96
- true
97
- rescue LoadError => e
98
- Solargraph::Logging.logger.warn "LoadError when trying to require 'rails_helper'"
99
- Solargraph::Logging.logger.warn "[#{e.class}] #{e.message}"
100
-
101
- false
102
- end
103
-
104
- # @return [String]
105
- def self.test_path
106
- File.join(Dir.pwd, Config.test_dir)
107
- end
108
- end
109
- end