guard-rspec 4.3.1 → 4.4.1

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.hound.yml +262 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +2 -0
  5. data/.rubocop_todo.yml +10 -0
  6. data/.travis.yml +2 -3
  7. data/CONTRIBUTING.md +1 -1
  8. data/Gemfile +9 -2
  9. data/Guardfile +11 -4
  10. data/README.md +8 -4
  11. data/Rakefile +23 -2
  12. data/gemfiles/Gemfile.rspec-2.14 +1 -0
  13. data/gemfiles/Gemfile.rspec-2.99 +14 -0
  14. data/gemfiles/Gemfile.rspec-3.0 +1 -0
  15. data/guard-rspec.gemspec +18 -16
  16. data/lib/guard/rspec.rb +13 -8
  17. data/lib/guard/rspec/command.rb +30 -12
  18. data/lib/guard/rspec/deprecator.rb +32 -11
  19. data/lib/guard/rspec/inspectors/base_inspector.rb +36 -15
  20. data/lib/guard/rspec/inspectors/factory.rb +7 -8
  21. data/lib/guard/rspec/inspectors/focused_inspector.rb +2 -2
  22. data/lib/guard/rspec/inspectors/keeping_inspector.rb +7 -6
  23. data/lib/guard/rspec/inspectors/simple_inspector.rb +3 -3
  24. data/lib/guard/rspec/notifier.rb +9 -5
  25. data/lib/guard/rspec/options.rb +12 -10
  26. data/lib/guard/rspec/runner.rb +41 -25
  27. data/lib/guard/rspec/templates/Guardfile +43 -15
  28. data/lib/guard/rspec/version.rb +1 -1
  29. data/lib/guard/rspec_formatter.rb +111 -0
  30. data/spec/lib/guard/rspec/command_spec.rb +48 -12
  31. data/spec/lib/guard/rspec/deprecator_spec.rb +37 -18
  32. data/spec/lib/guard/rspec/inspectors/base_inspector_spec.rb +129 -18
  33. data/spec/lib/guard/rspec/inspectors/factory_spec.rb +20 -15
  34. data/spec/lib/guard/rspec/inspectors/focused_inspector_spec.rb +79 -13
  35. data/spec/lib/guard/rspec/inspectors/keeping_inspector_spec.rb +103 -33
  36. data/spec/lib/guard/rspec/inspectors/shared_examples.rb +93 -31
  37. data/spec/lib/guard/rspec/inspectors/simple_inspector_spec.rb +52 -16
  38. data/spec/lib/guard/rspec/notifier_spec.rb +49 -27
  39. data/spec/lib/guard/rspec/runner_spec.rb +120 -77
  40. data/spec/lib/guard/rspec_formatter_spec.rb +144 -0
  41. data/spec/lib/guard/rspec_spec.rb +23 -18
  42. data/spec/spec_helper.rb +99 -14
  43. metadata +12 -7
  44. data/lib/guard/rspec/formatter.rb +0 -99
  45. data/spec/lib/guard/rspec/formatter_spec.rb +0 -122
@@ -1,5 +1,5 @@
1
1
  module Guard
2
- class RSpec
2
+ class RSpec < Plugin
3
3
  class Notifier
4
4
  attr_accessor :options
5
5
 
@@ -7,19 +7,23 @@ module Guard
7
7
  @options = options
8
8
  end
9
9
 
10
- TITLE = 'RSpec results'
11
-
12
10
  def notify(summary)
13
11
  return unless options[:notification]
14
12
  failure_count, pending_count = _parse_summary(summary)
15
13
  image = _image(failure_count, pending_count)
16
14
  priority = _priority(image)
17
- ::Guard::Notifier.notify(summary, title: TITLE, image: image, priority: priority)
15
+ ::Guard::Notifier.notify(summary,
16
+ title: @options[:title],
17
+ image: image,
18
+ priority: priority)
18
19
  end
19
20
 
20
21
  def notify_failure
21
22
  return unless options[:notification]
22
- ::Guard::Notifier.notify('Failed', title: TITLE, image: :failed, priority: 2)
23
+ ::Guard::Notifier.notify("Failed",
24
+ title: @options[:title],
25
+ image: :failed,
26
+ priority: 2)
23
27
  end
24
28
 
25
29
  private
@@ -1,15 +1,17 @@
1
1
  module Guard
2
- class RSpec
2
+ class RSpec < Plugin
3
3
  module Options
4
4
  DEFAULTS = {
5
- all_on_start: false,
6
- all_after_pass: false,
7
- run_all: { message: 'Running all specs' },
8
- failed_mode: :none, # :keep and :focus are other posibilities
9
- spec_paths: %w[spec],
10
- cmd: nil,
11
- launchy: nil,
12
- notification: true
5
+ all_on_start: false,
6
+ all_after_pass: false,
7
+ run_all: { message: "Running all specs" },
8
+ failed_mode: :none, # :keep and :focus are other posibilities
9
+ spec_paths: %w(spec),
10
+ cmd: nil,
11
+ cmd_additional_args: nil,
12
+ launchy: nil,
13
+ notification: true,
14
+ title: "RSpec results"
13
15
  }
14
16
 
15
17
  class << self
@@ -20,7 +22,7 @@ module Guard
20
22
  private
21
23
 
22
24
  def _deep_merge(hash1, hash2)
23
- hash1.merge(hash2) do |key, oldval, newval|
25
+ hash1.merge(hash2) do |_key, oldval, newval|
24
26
  if oldval.instance_of?(Hash) && newval.instance_of?(Hash)
25
27
  _deep_merge(oldval, newval)
26
28
  else
@@ -1,11 +1,13 @@
1
- require 'guard/rspec/inspectors/factory'
2
- require 'guard/rspec/command'
3
- require 'guard/rspec/formatter'
4
- require 'guard/rspec/notifier'
1
+ require "guard/rspec/inspectors/factory"
2
+ require "guard/rspec/command"
3
+ require "guard/rspec/notifier"
5
4
 
6
5
  module Guard
7
- class RSpec
6
+ class RSpec < Plugin
8
7
  class Runner
8
+ # NOTE: must match with const in RspecFormatter!
9
+ TEMPORARY_FILE_PATH ||= "tmp/rspec_guard_result"
10
+
9
11
  attr_accessor :options, :inspector, :notifier
10
12
 
11
13
  def initialize(options = {})
@@ -25,7 +27,7 @@ module Guard
25
27
  def run(paths)
26
28
  paths = inspector.paths(paths)
27
29
  return true if paths.empty?
28
- ::Guard::UI.info("Running: #{paths.join(' ')}", reset: true)
30
+ ::Guard::UI.info("Running: #{paths.join(" ")}", reset: true)
29
31
  _run(false, paths, options)
30
32
  end
31
33
 
@@ -38,20 +40,9 @@ module Guard
38
40
  def _run(all, paths, options)
39
41
  return unless _cmd_option_present(options)
40
42
  command = Command.new(paths, options)
43
+
41
44
  _without_bundler_env { Kernel.system(command) }.tap do |success|
42
- if _command_success?(success)
43
- summary, failed_paths = _command_output
44
- if summary && failed_paths
45
- inspector.failed(failed_paths)
46
- notifier.notify(summary)
47
- _open_launchy
48
- _run_all_after_pass if !all && success
49
- else
50
- notifier.notify_failure
51
- end
52
- else
53
- notifier.notify_failure
54
- end
45
+ _process_run_result(success, all)
55
46
  end
56
47
  end
57
48
 
@@ -65,29 +56,32 @@ module Guard
65
56
 
66
57
  def _cmd_option_present(options)
67
58
  return true if options[:cmd]
68
- ::Guard::UI.error('No cmd option specified, unable to run specs!')
59
+ ::Guard::UI.error("No cmd option specified, unable to run specs!")
69
60
  notifier.notify_failure
70
61
  false
71
62
  end
72
63
 
73
64
  def _command_success?(success)
74
65
  return false if success.nil?
75
- [Command::FAILURE_EXIT_CODE, 0].include?($?.exitstatus)
66
+ [Command::FAILURE_EXIT_CODE, 0].include?($CHILD_STATUS.exitstatus)
76
67
  end
77
68
 
78
69
  def _command_output
79
- formatter_tmp_file = Formatter::TEMPORARY_FILE_PATH
70
+ formatter_tmp_file = _tmp_file(options[:chdir])
80
71
  lines = File.readlines(formatter_tmp_file)
81
- [lines.first.strip, lines[1..11].map(&:strip).compact]
72
+ summary = lines.first.strip
73
+ failed_paths = lines[1..11].map(&:strip).compact
74
+
75
+ [summary, failed_paths]
82
76
  rescue
83
77
  [nil, nil]
84
78
  ensure
85
- File.exist?(formatter_tmp_file) && File.delete(formatter_tmp_file)
79
+ File.delete(formatter_tmp_file) if File.exists?(formatter_tmp_file)
86
80
  end
87
81
 
88
82
  def _open_launchy
89
83
  return unless options[:launchy]
90
- require 'launchy'
84
+ require "launchy"
91
85
  pn = Pathname.new(options[:launchy])
92
86
  ::Launchy.open(options[:launchy]) if pn.exist?
93
87
  end
@@ -96,6 +90,28 @@ module Guard
96
90
  return unless options[:all_after_pass]
97
91
  run_all
98
92
  end
93
+
94
+ def _process_run_result(result, all)
95
+ unless _command_success?(result)
96
+ notifier.notify_failure
97
+ return
98
+ end
99
+
100
+ summary, failed_paths = _command_output
101
+ unless summary && failed_paths
102
+ notifier.notify_failure
103
+ end
104
+
105
+ inspector.failed(failed_paths)
106
+ notifier.notify(summary)
107
+ _open_launchy
108
+
109
+ _run_all_after_pass if !all && result
110
+ end
111
+
112
+ def _tmp_file(chdir)
113
+ chdir ? File.join(chdir, TEMPORARY_FILE_PATH) : TEMPORARY_FILE_PATH
114
+ end
99
115
  end
100
116
  end
101
117
  end
@@ -2,29 +2,57 @@
2
2
  # rspec may be run, below are examples of the most common uses.
3
3
  # * bundler: 'bundle exec rspec'
4
4
  # * bundler binstubs: 'bin/rspec'
5
- # * spring: 'bin/rsspec' (This will use spring if running and you have
5
+ # * spring: 'bin/rspec' (This will use spring if running and you have
6
6
  # installed the spring binstubs per the docs)
7
- # * zeus: 'zeus rspec' (requires the server to be started separetly)
7
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
8
8
  # * 'just' rspec: 'rspec'
9
- guard :rspec, cmd: 'bundle exec rspec' do
9
+
10
+ guard :rspec, cmd: "bundle exec rspec" do
11
+ require "ostruct"
12
+
13
+ # Generic Ruby apps
14
+ rspec = OpenStruct.new
15
+ rspec.spec = ->(m) { "spec/#{m}_spec.rb" }
16
+ rspec.spec_dir = "spec"
17
+ rspec.spec_helper = "spec/spec_helper.rb"
18
+
10
19
  watch(%r{^spec/.+_spec\.rb$})
11
- watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
12
- watch('spec/spec_helper.rb') { "spec" }
20
+ watch(%r{^lib/(.+)\.rb$}) { |m| rspec.spec.("lib/#{m[1]}") }
21
+ watch(rspec.spec_helper) { rspec.spec_dir }
13
22
 
14
23
  # Rails example
15
- watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
16
- watch(%r{^app/(.*)(\.erb|\.haml|\.slim)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
17
- watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
18
- watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
19
- watch('config/routes.rb') { "spec/routing" }
20
- watch('app/controllers/application_controller.rb') { "spec/controllers" }
21
- watch('spec/rails_helper.rb') { "spec" }
24
+ rails = OpenStruct.new
25
+ rails.app = %r{^app/(.+)\.rb$}
26
+ rails.views_n_layouts = %r{^app/(.*)(\.erb|\.haml|\.slim)$}
27
+ rails.controllers = %r{^app/controllers/(.+)_controller\.rb$}
28
+ rails.routes = "config/routes.rb"
29
+ rails.app_controller = "app/controllers/application_controller.rb"
30
+ rails.spec_helper = "spec/rails_helper.rb"
31
+ rails.spec_support = %r{^spec/support/(.+)\.rb$}
32
+ rails.views = %r{^app/views/(.+)/.*\.(erb|haml|slim)$}
33
+
34
+ watch(rails.app) { |m| rspec.spec.(m[1]) }
35
+ watch(rails.views_n_layouts) { |m| rspec.spec.("#{m[1]}#{m[2]}") }
36
+ watch(rails.controllers) do |m|
37
+ [
38
+ rspec.spec.("routing/#{m[1]}_routing"),
39
+ rspec.spec.("controllers/#{m[1]}_controller"),
40
+ rspec.spec.("acceptance/#{m[1]}")
41
+ ]
42
+ end
43
+
44
+ watch(rails.spec_support) { rspec.spec_dir }
45
+ watch(rails.spec_helper) { rspec.spec_dir }
46
+ watch(rails.routes) { "spec/routing" }
47
+ watch(rails.app_controller) { "spec/controllers" }
22
48
 
23
49
  # Capybara features specs
24
- watch(%r{^app/views/(.+)/.*\.(erb|haml|slim)$}) { |m| "spec/features/#{m[1]}_spec.rb" }
50
+ watch(rails.views) { |m| rspec.spec.("features/#{m[1]}") }
25
51
 
26
52
  # Turnip features and steps
27
53
  watch(%r{^spec/acceptance/(.+)\.feature$})
28
- watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
29
- end
54
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
55
+ Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
56
+ end
30
57
 
58
+ end
@@ -1,5 +1,5 @@
1
1
  module Guard
2
2
  module RSpecVersion
3
- VERSION = '4.3.1'
3
+ VERSION = "4.4.1"
4
4
  end
5
5
  end
@@ -0,0 +1,111 @@
1
+ # NOTE: This class only exists for RSpec and should not be used by
2
+ # other classes in this project!
3
+
4
+ require "pathname"
5
+
6
+ require "rspec"
7
+ require "rspec/core/formatters/base_formatter"
8
+
9
+ module Guard
10
+ class RSpecFormatter < ::RSpec::Core::Formatters::BaseFormatter
11
+ TEMPORARY_FILE_PATH ||= "tmp/rspec_guard_result"
12
+
13
+ def self.rspec_3?
14
+ ::RSpec::Core::Version::STRING.split(".").first == "3"
15
+ end
16
+
17
+ if rspec_3?
18
+ ::RSpec::Core::Formatters.register self, :dump_summary, :example_failed
19
+
20
+ def example_failed(failure)
21
+ examples.push failure.example
22
+ end
23
+
24
+ def examples
25
+ @examples ||= []
26
+ end
27
+ end
28
+
29
+ # rspec issue https://github.com/rspec/rspec-core/issues/793
30
+ def self.extract_spec_location(metadata)
31
+ root_metadata = metadata
32
+ location = metadata[:location]
33
+
34
+ until spec_path?(location)
35
+ metadata = metadata[:example_group]
36
+
37
+ unless metadata
38
+ STDERR.puts "no spec file found for #{root_metadata[:location]}"
39
+ return root_metadata[:location]
40
+ end
41
+
42
+ # rspec issue https://github.com/rspec/rspec-core/issues/1243
43
+ location = (metadata[:location] || "").split(":").first
44
+ end
45
+
46
+ location
47
+ end
48
+
49
+ def self.spec_path?(path)
50
+ path ||= ""
51
+ flags = File::FNM_PATHNAME | File::FNM_DOTMATCH
52
+ if File.const_defined?(:FNM_EXTGLOB) # ruby >= 2
53
+ flags |= File::FNM_EXTGLOB
54
+ end
55
+ pattern = ::RSpec.configuration.pattern
56
+ path = path.sub(/:\d+\z/, "")
57
+ path = Pathname.new(path).cleanpath.to_s
58
+ File.fnmatch(pattern, path, flags)
59
+ end
60
+
61
+ def dump_summary(*args)
62
+ if self.class.rspec_3?
63
+ notification = args[0]
64
+ write_summary(
65
+ notification.duration,
66
+ notification.example_count,
67
+ notification.failure_count,
68
+ notification.pending_count
69
+ )
70
+ else
71
+ write_summary(*args)
72
+ end
73
+ rescue
74
+ # nothing really we can do, at least don"t kill the test runner
75
+ end
76
+
77
+ # Write summary to temporary file for runner
78
+ def write_summary(duration, total, failures, pending)
79
+ _write do |f|
80
+ f.puts _message(total, failures, pending, duration)
81
+ f.puts _failed_paths.join("\n") if failures > 0
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ def _write(&block)
88
+ file = File.expand_path(TEMPORARY_FILE_PATH)
89
+ FileUtils.mkdir_p(File.dirname(file))
90
+ File.open(file, "w", &block)
91
+ end
92
+
93
+ def _failed_paths
94
+ failed = examples.select do |e|
95
+ e.execution_result[:status].to_s == "failed"
96
+ end
97
+
98
+ klass = self.class
99
+ failed.map { |e| klass.extract_spec_location(e.metadata) }.sort.uniq
100
+ end
101
+
102
+ def _message(example_count, failure_count, pending_count, duration)
103
+ message = "#{example_count} examples, #{failure_count} failures"
104
+ if pending_count > 0
105
+ message << " (#{pending_count} pending)"
106
+ end
107
+ message << " in #{duration.round(4)} seconds"
108
+ message
109
+ end
110
+ end
111
+ end
@@ -1,12 +1,14 @@
1
- require 'spec_helper'
2
- require 'launchy'
1
+ require "launchy"
3
2
 
4
- describe Guard::RSpec::Command do
5
- let(:options) { { } }
6
- let(:paths) { %w[path1 path2] }
3
+ require "guard/compat/test/helper"
4
+ require "guard/rspec/command"
5
+
6
+ RSpec.describe Guard::RSpec::Command do
7
+ let(:options) { {} }
8
+ let(:paths) { %w(path1 path2) }
7
9
  let(:command) { Guard::RSpec::Command.new(paths, options) }
8
10
 
9
- describe '.initialize' do
11
+ describe ".initialize" do
10
12
 
11
13
  it "sets paths at the end" do
12
14
  expect(command).to match /path1 path2$/
@@ -17,20 +19,26 @@ describe Guard::RSpec::Command do
17
19
  end
18
20
 
19
21
  it "sets formatter" do
20
- expect(command).to match %r{-r .*/lib/guard/rspec/formatter.rb -f Guard::RSpec::Formatter}
22
+ regexp = %r{-r .*/lib/guard/rspec_formatter.rb -f Guard::RSpecFormatter}
23
+ expect(command).to match(regexp)
21
24
  end
22
25
 
23
26
  context "with custom cmd" do
24
- let(:options) { { cmd: 'rspec -t ~slow' } }
27
+ let(:options) { { cmd: "rspec -t ~slow" } }
25
28
 
26
29
  it "uses custom cmd" do
27
- expect(command).to match /^rspec -t ~slow/
30
+ expect(command).to match /^rspec -t ~slow/
28
31
  end
29
32
  end
30
33
 
31
34
  context "with RSpec defined formatter" do
32
- let(:formatters) { [['doc','output']] }
33
- before { allow(RSpec::Core::ConfigurationOptions).to receive(:new) { double(options: { formatters: formatters }) } }
35
+ let(:formatters) { [%w(doc output)] }
36
+
37
+ before do
38
+ allow(RSpec::Core::ConfigurationOptions).to receive(:new) do
39
+ double(options: { formatters: formatters })
40
+ end
41
+ end
34
42
 
35
43
  it "uses them" do
36
44
  expect(command).to match %r{-f doc -o output}
@@ -44,12 +52,40 @@ describe Guard::RSpec::Command do
44
52
  end
45
53
 
46
54
  context "with formatter in cmd" do
47
- let(:options) { { cmd: 'rspec -f doc' } }
55
+ let(:options) { { cmd: "rspec -f doc" } }
48
56
 
49
57
  it "sets no other formatters" do
50
58
  expect(command).to match %r{-f doc}
51
59
  end
52
60
  end
61
+
62
+ context "with cmd_additional_args" do
63
+ let(:options) { { cmd: "rspec", cmd_additional_args: "-f progress" } }
64
+
65
+ it "uses them" do
66
+ expect(command).to match %r{-f progress}
67
+ end
68
+ end
69
+
70
+ context ":chdir option present" do
71
+ let(:chdir) { "moduleA" }
72
+ let(:paths) do
73
+ %w[path1 path2].map { |p| "#{chdir}#{File::Separator}#{p}" }
74
+ end
75
+
76
+ let(:options) do
77
+ {
78
+ cmd: "cd #{chdir} && rspec",
79
+ chdir: chdir
80
+ }
81
+ end
82
+
83
+ it "removes chdir part from the path
84
+ as it should be present in the cmd" do
85
+
86
+ expect(command).to match %r{path1 path2}
87
+ end
88
+ end
53
89
  end
54
90
 
55
91
  end