solargraph_test_coverage 0.2.3 → 0.2.7

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: 69e0c33725c04d5fe0ccb51767c639d8e864996edf5396e05c16e3aa72bc18da
4
- data.tar.gz: 30ed55d17273d8ec327f9f83cf69573a33bcc1a77ec036e7267136da2aff17a1
3
+ metadata.gz: b7c7bd9d2a4a3b6184942c28ce619023f098a515ca356e8534788adb93f9cb96
4
+ data.tar.gz: 11850e7e7184658995b6e15479e8c3c87527f4b945c8b8bc9ab446242c5dba32
5
5
  SHA512:
6
- metadata.gz: f3b2c1f49dec3b91a85495ecbeeeb381d6948fdbcdd0bf10ed843be0b02d33c1a84545a902cd220051e52f272ad0bb4d651d4e5c7043f2d97612cb48f3cabdae
7
- data.tar.gz: 440383a6909e68f20d726c5fce67d6b8f195e772be6702161e4421c04cf1720f451ba0c23637b906732e2a0a9895612901cbc2b5c9198a30070393d392d59ce7
6
+ metadata.gz: d4901017237a8e5cfc49e8a0228776c48929304ebb043bdd9c89500e56db2c4d7349b01838c911a3080900ca1d657b07f2d29a921d0cc39b2da96efe517d1443
7
+ data.tar.gz: c00ecc7e6c928c42a93abda3f976e3d0811934f21f21362a40711dbc449244a3e443d683a6c6204955ea3ec28989226f41d5b371344238594d78c86b3fb98987
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- solargraph_test_coverage (0.2.1)
4
+ solargraph_test_coverage (0.2.6)
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)
data/README.md CHANGED
@@ -33,12 +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'
41
+ - 'app/controller'
42
+ - 'concerns'
42
43
  ```
43
44
 
44
45
 
@@ -50,6 +51,9 @@ And then execute:
50
51
  Or install it yourself as:
51
52
 
52
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`.
53
57
 
54
58
  ## Usage
55
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,124 @@
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
+ 'test_framework' => 'rspec', # can be 'rspec' or 'minitest'
10
+ 'coverage' => [ # All diagnostics are enabled by default
11
+ 'line', # Specifying an array with fewer diagnostics will overwrite this
12
+ 'branch',
13
+ 'test_failing',
14
+ 'test_missing'
15
+ ],
16
+ 'exclude_paths' => [ # don't attempt to find/run a spec for files that match these paths
17
+ 'app/controller',
18
+ 'concerns'
19
+ ]
20
+ }.freeze
30
21
 
31
- def line_coverage?
32
- plugin_config['coverage'].include? 'line'
33
- end
22
+ def preload_rails?
23
+ plugin_config['preload_rails']
24
+ end
34
25
 
35
- def branch_coverage?
36
- plugin_config['coverage'].include? 'branch'
37
- end
26
+ def exclude_paths
27
+ plugin_config['exclude_paths']
28
+ end
38
29
 
39
- def test_failing_coverage?
40
- plugin_config['coverage'].include? 'test_failing'
41
- end
30
+ def line_coverage?
31
+ plugin_config['coverage'].include? 'line'
32
+ end
42
33
 
43
- def test_missing_coverage?
44
- plugin_config['coverage'].include? 'test_missing'
45
- end
34
+ def branch_coverage?
35
+ plugin_config['coverage'].include? 'branch'
36
+ end
37
+
38
+ def test_failing_coverage?
39
+ plugin_config['coverage'].include? 'test_failing'
40
+ end
41
+
42
+ def test_missing_coverage?
43
+ plugin_config['coverage'].include? 'test_missing'
44
+ end
46
45
 
47
- def test_framework
48
- plugin_config['test_framework']
46
+ def test_framework
47
+ plugin_config['test_framework']
48
+ end
49
+
50
+ def test_dir
51
+ case test_framework
52
+ when 'rspec'
53
+ 'spec'
54
+ when 'minitest'
55
+ 'test'
49
56
  end
57
+ end
50
58
 
51
- def test_dir
52
- case test_framework
53
- when 'rspec'
54
- 'spec'
55
- when 'minitest'
56
- 'test'
57
- end
59
+ def test_file_suffix
60
+ case test_framework
61
+ when 'rspec'
62
+ '_spec.rb'
63
+ when 'minitest'
64
+ '_test.rb'
58
65
  end
66
+ end
59
67
 
60
- def test_file_suffix
61
- case test_framework
62
- when 'rspec'
63
- '_spec.rb'
64
- when 'minitest'
65
- '_test.rb'
66
- end
68
+ def require_testing_framework!
69
+ case test_framework
70
+ when 'rspec'
71
+ require 'rspec/core'
72
+ when 'minitest'
73
+ require 'minitest/autorun'
74
+ else
75
+ raise UnknownTestingGem
67
76
  end
77
+ end
68
78
 
69
- private
79
+ # This gives us a nice speed-boost when running test in child process
80
+ #
81
+ # We rescue the LoadError since Solargraph would catch it otherwise,
82
+ # and not load the plugin at all.
83
+ #
84
+ # Adding the spec/ directory to the $LOAD_PATH lets 'require "spec_helper"'
85
+ # commonly found in rails_helper work.
86
+ #
87
+ # If Coverage was started in Rails/Spec helper by SimpleCov,
88
+ # calling Coverage.result after requiring stops and resets it.
89
+ #
90
+ # This is a bit experimental
91
+ #
92
+ def preload_rails!
93
+ return if defined?(Rails) || !File.file?('spec/rails_helper.rb')
70
94
 
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
95
+ $LOAD_PATH.unshift(test_path) unless $LOAD_PATH.include?(test_path)
96
+
97
+ require File.join(test_path, 'rails_helper')
98
+ Coverage.result(stop: true, clear: true) if Coverage.running?
99
+
100
+ true
101
+ rescue LoadError => e
102
+ Solargraph::Logging.logger.warn "LoadError when trying to require 'rails_helper'"
103
+ Solargraph::Logging.logger.warn "[#{e.class}] #{e.message}"
76
104
 
77
- def workspace_config
78
- Solargraph::Workspace::Config.new(Dir.pwd).raw_data.fetch('test_coverage', {})
105
+ false
106
+ end
107
+
108
+ private
109
+
110
+ def plugin_config
111
+ @plugin_config ||= workspace_config.tap do |config|
112
+ DEFAULTS.each_key { |key| config[key] = DEFAULTS[key] unless config.key?(key) }
79
113
  end
80
114
  end
115
+
116
+ def workspace_config
117
+ Solargraph::Workspace::Config.new(Dir.pwd).raw_data.fetch('test_coverage', {})
118
+ end
119
+
120
+ def test_path
121
+ ReporterHelpers.test_path
122
+ end
81
123
  end
82
124
  end
@@ -5,28 +5,36 @@ 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 if result.nil?
28
+
29
+ Marshal.load(result).tap { |r| raise ChildFailedError if r.nil? }
30
+ end
31
+
32
+ private
28
33
 
29
- Marshal.load(result)
34
+ def run_block_with_timeout(&block)
35
+ Timeout.timeout(30, &block)
36
+ rescue StandardError
37
+ nil
30
38
  end
31
39
  end
32
40
  end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolargraphTestCoverage
4
+ # Some helper functions for the diagnostics
5
+ module ReporterHelpers
6
+ def exclude_file?(source_filename)
7
+ return true if source_filename.start_with? test_path
8
+
9
+ Config.exclude_paths.any? { |path| source_filename.sub(Dir.pwd, '').include? path }
10
+ end
11
+
12
+ def using_debugger?(source)
13
+ source.code.include?('binding.pry') ||
14
+ source.code.include?('byebug') ||
15
+ source.code.include?('debugger')
16
+ end
17
+
18
+ def test_file(source)
19
+ relative_filepath = source.location.filename.sub(Dir.pwd, '').split('/').reject(&:empty?)
20
+ relative_filepath[0] = Config.test_dir
21
+
22
+ File.join(Dir.pwd, relative_filepath.join('/')).sub('.rb', Config.test_file_suffix)
23
+ end
24
+
25
+ # @return [Hash]
26
+ def run_test(source)
27
+ ForkProcess.call do
28
+ Coverage.start(lines: true, branches: true)
29
+ runner = TestRunner.with(test_file(source)).run!
30
+ Coverage.result.fetch(source.location.filename, {}).merge({ test_status: runner.passed? })
31
+ end
32
+ end
33
+
34
+ def messages(source, results)
35
+ messages = [
36
+ line_warnings(source, results),
37
+ branch_warnings(source, results),
38
+ test_passing_error(source, results)
39
+ ]
40
+
41
+ messages.flatten.compact
42
+ end
43
+
44
+ def line_warnings(source, results)
45
+ uncovered_lines(results).map { |line| line_coverage_warning(source, line) }
46
+ end
47
+
48
+ def branch_warnings(source, results)
49
+ uncovered_branches(results).map { |branch| branch_coverage_warning(source, branch.report) }
50
+ end
51
+
52
+ def test_passing_error(source, results)
53
+ results[:test_status] ? [] : [test_failing_error(source)]
54
+ end
55
+
56
+ # Adapted from SingleCov
57
+ # Coverage returns nil for untestable lines (like 'do', 'end', 'if' keywords)
58
+ # otherwise returns int showing how many times a line was called
59
+ #
60
+ # [nil, 1, 0, 1, 0] -> [3, 5]
61
+ # Returns array of line numbers with 0 coverage
62
+ def uncovered_lines(results)
63
+ return [] unless results[:lines]
64
+
65
+ results[:lines].each_with_index
66
+ .select { |c, _| c&.zero? }
67
+ .map { |_, i| i }
68
+ .compact
69
+ end
70
+
71
+ def uncovered_branches(results)
72
+ Branch.build_from(results).reject(&:covered?)
73
+ end
74
+
75
+ def range(start_line, start_column, end_line, end_column)
76
+ Solargraph::Range.from_to(start_line, start_column, end_line, end_column).to_hash
77
+ end
78
+
79
+ def self.test_path
80
+ File.join(Dir.pwd, Config.test_dir)
81
+ end
82
+
83
+ def test_path
84
+ ReporterHelpers.test_path
85
+ end
86
+ end
87
+ end
@@ -3,13 +3,10 @@
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
7
 
8
- # LSP Diagnostic method
9
- # @return [Array]
10
- #
11
8
  def diagnose(source, _api_map)
12
- return [] if source.code.empty? || exclude_file?(source.location.filename)
9
+ return [] if source.code.empty? || using_debugger?(source) || exclude_file?(source.location.filename)
13
10
  return [test_missing_error(source)] unless File.file?(test_file(source))
14
11
 
15
12
  results = run_test(source)
@@ -20,43 +17,6 @@ module SolargraphTestCoverage
20
17
 
21
18
  private
22
19
 
23
- # Compiles all diagnostic messages for source file
24
- # @return [Array]
25
- #
26
- def messages(source, results)
27
- messages = [
28
- line_warnings(source, results),
29
- branch_warnings(source, results),
30
- 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
20
  def line_coverage_warning(source, line)
61
21
  return unless Config.line_coverage?
62
22
 
@@ -68,11 +28,6 @@ module SolargraphTestCoverage
68
28
  }
69
29
  end
70
30
 
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
31
  def branch_coverage_warning(source, report)
77
32
  return unless Config.branch_coverage?
78
33
 
@@ -84,9 +39,6 @@ module SolargraphTestCoverage
84
39
  }
85
40
  end
86
41
 
87
- # Creates LSP error message if test file is failing
88
- # @return [Hash]
89
- #
90
42
  def test_failing_error(source)
91
43
  return unless Config.test_failing_coverage?
92
44
 
@@ -98,19 +50,14 @@ module SolargraphTestCoverage
98
50
  }
99
51
  end
100
52
 
101
- #
102
- # Creates LSP error message if no test file can be found
103
- #
104
- # @return [Hash]
105
- #
106
53
  def test_missing_error(source)
107
54
  return unless Config.test_missing_coverage?
108
55
 
109
56
  {
110
57
  range: range(0, 0, 0, source.code.lines[0].length),
111
- severity: Solargraph::Diagnostics::Severities::ERROR,
58
+ severity: Solargraph::Diagnostics::Severities::HINT,
112
59
  source: 'TestCoverage',
113
- message: "No unit test file found at #{test_file(source)}"
60
+ message: "No test file found at '#{test_file(source).sub("#{Dir.pwd}/", '')}'"
114
61
  }
115
62
  end
116
63
  end
@@ -18,10 +18,14 @@ module SolargraphTestCoverage
18
18
  end
19
19
 
20
20
  def run!
21
- @result = test_framework_runner.run([@test_file])
21
+ @result = test_framework_runner.run(test_options)
22
22
  self
23
23
  end
24
24
 
25
+ def test_options
26
+ raise NotImplementedError
27
+ end
28
+
25
29
  def passed?
26
30
  raise NotImplementedError
27
31
  end
@@ -33,6 +37,10 @@ module SolargraphTestCoverage
33
37
 
34
38
  # Test Runner Subclass for RSpec
35
39
  class RSpecRunner < TestRunner
40
+ def test_options
41
+ [@test_file, '-o', '/dev/null']
42
+ end
43
+
36
44
  def passed?
37
45
  @result&.zero?
38
46
  end
@@ -44,6 +52,10 @@ module SolargraphTestCoverage
44
52
 
45
53
  # Test Runner Subclass for Minitest
46
54
  class MinitestRunner < TestRunner
55
+ def test_options
56
+ [@test_file]
57
+ end
58
+
47
59
  def passed?
48
60
  @result
49
61
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolargraphTestCoverage
4
- VERSION = '0.2.3'
4
+ VERSION = '0.2.7'
5
5
  end
@@ -3,21 +3,22 @@
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
7
  require 'solargraph_test_coverage/config'
8
8
  require 'solargraph_test_coverage/test_runner'
9
9
  require 'solargraph_test_coverage/test_coverage_reporter'
10
10
 
11
11
  require 'solargraph'
12
12
  require 'coverage'
13
+ require 'timeout'
13
14
 
14
15
  module SolargraphTestCoverage
15
16
  class ChildFailedError < StandardError; end
16
17
 
17
18
  class UnknownTestingGem < StandardError; end
18
19
 
19
- Helpers.require_testing_framework!
20
- Helpers.preload_rails! if Config.preload_rails?
20
+ Config.require_testing_framework!
21
+ Config.preload_rails! if Config.preload_rails?
21
22
 
22
23
  Solargraph::Diagnostics.register 'test_coverage', TestCoverageReporter
23
24
  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.3
4
+ version: 0.2.7
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-01 00:00:00.000000000 Z
11
+ date: 2021-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: solargraph
@@ -48,7 +48,7 @@ files:
48
48
  - lib/solargraph_test_coverage/branch.rb
49
49
  - lib/solargraph_test_coverage/config.rb
50
50
  - lib/solargraph_test_coverage/fork_process.rb
51
- - lib/solargraph_test_coverage/helpers.rb
51
+ - lib/solargraph_test_coverage/reporter_helpers.rb
52
52
  - lib/solargraph_test_coverage/test_coverage_reporter.rb
53
53
  - lib/solargraph_test_coverage/test_runner.rb
54
54
  - lib/solargraph_test_coverage/version.rb
@@ -1,127 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SolargraphTestCoverage
4
- # Some helper functions for the diagnostics
5
- module Helpers
6
- # Determines if a file should be excluded from running diagnostics
7
- # @return [Boolean]
8
- #
9
- def exclude_file?(source_filename)
10
- return true if source_filename.start_with? Helpers.test_path
11
-
12
- Config.exclude_paths.each { |path| return true if source_filename.sub(Dir.pwd, '').include? path }
13
-
14
- false
15
- end
16
-
17
- # Attempts to find the corresponding unit test file
18
- # @return [String]
19
- #
20
- def test_file(source)
21
- relative_filepath = source.location.filename.sub(Dir.pwd, '').split('/').reject(&:empty?)
22
- relative_filepath[0] = Config.test_dir
23
-
24
- File.join(Dir.pwd, relative_filepath.join('/'))
25
- .sub('.rb', Config.test_file_suffix)
26
- end
27
-
28
- # Runs test file in a child process with specified testing framework
29
- # Returns coverage results for current working file
30
- #
31
- # @return [Hash]
32
- #
33
- def run_test(source)
34
- ForkProcess.run do
35
- Coverage.start(lines: true, branches: true)
36
- runner = TestRunner.with(test_file(source)).run!
37
- Coverage.result.fetch(source.location.filename, {}).merge({ test_status: runner.passed? })
38
- end
39
- end
40
-
41
- # Adapted from SingleCov
42
- # Coverage returns nil for untestable lines (like do, end, if keywords)
43
- # otherwise returns int showing how many times a line was called
44
- #
45
- # [nil, 1, 0, 1, 0] -> [3, 5]
46
- #
47
- # @return [Array]
48
- #
49
- def uncovered_lines(results)
50
- results.fetch(:lines)
51
- .each_with_index
52
- .select { |c, _| c&.zero? }
53
- .map { |_, i| i }
54
- .compact
55
- end
56
-
57
- # Builds a new Branch object for each branch tested from results hash
58
- # Then removes branches which have test coverage
59
- #
60
- # @return [Array]
61
- #
62
- def uncovered_branches(results)
63
- Branch.build_from(results).reject(&:covered?)
64
- end
65
-
66
- # Builds a range for warnings/errors
67
- # @return [Hash]
68
- #
69
- def range(start_line, start_column, end_line, end_column)
70
- Solargraph::Range.from_to(start_line, start_column, end_line, end_column).to_hash
71
- end
72
-
73
- # requires the specified testing framework
74
- # @return [Boolean]
75
- #
76
- def self.require_testing_framework!
77
- case Config.test_framework
78
- when 'rspec'
79
- require 'rspec/core'
80
- when 'minitest'
81
- require 'minitest/autorun'
82
- else
83
- raise UnknownTestingGem
84
- end
85
- end
86
-
87
- # Only called once, when gem is loaded
88
- # Preloads rails via spec/rails_helper if Rails isn't already defined
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 - I'm not sure if there will be downstream side-effects
101
- #
102
- # @return [Boolean]
103
- #
104
- def self.preload_rails!
105
- return if defined?(Rails) || !File.file?('spec/rails_helper.rb')
106
-
107
- $LOAD_PATH.unshift(test_path) unless $LOAD_PATH.include?(test_path)
108
-
109
- require File.join(test_path, 'rails_helper')
110
- Coverage.result(stop: true, clear: true) if Coverage.running?
111
-
112
- true
113
- rescue LoadError => e
114
- Solargraph::Logging.logger.warn "LoadError when trying to require 'rails_helper'"
115
- Solargraph::Logging.logger.warn "[#{e.class}] #{e.message}"
116
-
117
- false
118
- end
119
-
120
- # Returns absolute path for test folder
121
- # @return [String]
122
- #
123
- def self.test_path
124
- File.join(Dir.pwd, Config.test_dir)
125
- end
126
- end
127
- end