rspec_starter 1.5.0 → 1.6.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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +9 -0
  3. data/.travis.yml +5 -5
  4. data/CHANGELOG.md +29 -18
  5. data/README.md +145 -115
  6. data/exe/rspec_starter +14 -1
  7. data/lib/rspec_starter.rb +69 -24
  8. data/lib/rspec_starter/command.rb +59 -0
  9. data/lib/rspec_starter/command_context.rb +38 -0
  10. data/lib/rspec_starter/core_ext/string.rb +6 -3
  11. data/lib/rspec_starter/environment.rb +57 -0
  12. data/lib/rspec_starter/errors/step_error.rb +9 -0
  13. data/lib/rspec_starter/errors/step_stopper.rb +9 -0
  14. data/lib/rspec_starter/help.rb +79 -32
  15. data/lib/rspec_starter/helpers.rb +74 -0
  16. data/lib/rspec_starter/{which.rb → helpers/which.rb} +0 -0
  17. data/lib/rspec_starter/legacy.rb +16 -0
  18. data/lib/rspec_starter/legacy/help.rb +40 -0
  19. data/lib/rspec_starter/legacy/legacy_runner.rb +90 -0
  20. data/lib/rspec_starter/{steps → legacy/steps}/invoke_rspec_step.rb +2 -0
  21. data/lib/rspec_starter/{steps → legacy/steps}/prepare_database_step.rb +3 -1
  22. data/lib/rspec_starter/{steps → legacy/steps}/remove_tmp_folder_step.rb +3 -0
  23. data/lib/rspec_starter/{steps → legacy/steps}/step.rb +0 -0
  24. data/lib/rspec_starter/{steps → legacy/steps}/verify_xvfb_step.rb +2 -0
  25. data/lib/rspec_starter/option.rb +84 -0
  26. data/lib/rspec_starter/options.rb +96 -0
  27. data/lib/rspec_starter/rspec_starter_task.rb +35 -0
  28. data/lib/rspec_starter/runner.rb +15 -74
  29. data/lib/rspec_starter/step.rb +181 -0
  30. data/lib/rspec_starter/step_context.rb +28 -0
  31. data/lib/rspec_starter/step_options.rb +20 -0
  32. data/lib/rspec_starter/task_context.rb +63 -0
  33. data/lib/rspec_starter/tasks/rebuild_rails_app_database.rb +50 -0
  34. data/lib/rspec_starter/tasks/remove_tmp_folder.rb +28 -0
  35. data/lib/rspec_starter/tasks/start_rspec.rb +68 -0
  36. data/lib/rspec_starter/tasks/verify_display_server.rb +43 -0
  37. data/lib/rspec_starter/version.rb +1 -1
  38. data/lib/templates/rails_engine_start_rspec +38 -0
  39. data/lib/templates/rails_start_rspec +38 -0
  40. data/lib/templates/start_rspec +20 -5
  41. data/rspec_starter.gemspec +4 -2
  42. metadata +63 -14
@@ -0,0 +1,74 @@
1
+ # Helper methods that can be used anywhere in RspecStarter.
2
+ module RspecStarter
3
+ def self.helpers
4
+ @helpers ||= Helpers.new
5
+ end
6
+
7
+ # A class that provides helper methods that can be used anywhere in RspecStarter.
8
+ class Helpers
9
+ def class_for_task_name(string_or_symbol)
10
+ string_or_symbol.to_s.camelize.constantize
11
+ end
12
+
13
+ def project_is_rails_app?
14
+ @project_is_rails_app ||= File.file?(File.join(Dir.pwd, 'config', 'application.rb'))
15
+ end
16
+
17
+ def project_is_rails_engine?
18
+ return false unless project_has_lib_dir?
19
+
20
+ Dir["#{Dir.pwd}/lib/**/*.rb"].each do |file|
21
+ return true if File.readlines(file).detect { |line| line.match(/\s*class\s+.*<\s+::Rails::Engine/) }
22
+ end
23
+ false
24
+ end
25
+
26
+ def project_has_lib_dir?
27
+ @project_has_lib_dir ||= Dir.exist?("#{Dir.pwd}/lib")
28
+ end
29
+
30
+ # Taken from https://stackoverflow.com/questions/11784109/detecting-operating-systems-in-ruby/13586108
31
+ def operating_system_name
32
+ @operating_system_name ||= begin
33
+ host_os = RbConfig::CONFIG['host_os']
34
+ case host_os
35
+ when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
36
+ :windows
37
+ when /darwin|mac os/
38
+ :macosx
39
+ when /linux/
40
+ :linux
41
+ when /solaris|bsd/
42
+ :unix
43
+ else
44
+ :unknown
45
+ end
46
+ end
47
+ end
48
+
49
+ def is_linux?
50
+ operating_system_name == :linux
51
+ end
52
+
53
+ def is_mac?
54
+ operating_system_name == :maxosx
55
+ end
56
+
57
+ def xvfb_installed?
58
+ @xvfb_installed ||= RspecStarter.which("xvfb-run")
59
+ end
60
+
61
+ def xvfb_not_installed?
62
+ !xvfb_installed?
63
+ end
64
+
65
+ # This is ugly, but it gives us some added flexibility. Users can invoke the rspec_starter method from any script or
66
+ # executable. This method attempts to find out the file name of the script/exectuable.
67
+ #
68
+ # This method may not return a pretty result in all cases, but it's decent if the user has defined a script in their project
69
+ # (possibly in the bin folder, or root of the project).
70
+ def starter_script_file_name
71
+ caller.last.split(":")[0]
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,16 @@
1
+ # This module loads the legacy RspecStarter code that uses the non-block DSL. It will be removed in 2.0.0.
2
+ # The file is loaded when RspecStarter starts, but most of the legacy code is not loaded until the
3
+ # 'invoke_legacy_starter' method is called.
4
+ module RspecStarter
5
+ def self.invoke_legacy_starter(defaults)
6
+ require 'rspec_starter/legacy/legacy_runner'
7
+ # puts "[DEPRECATION NOTICE] Your #{helpers.starter_script_file_name} file uses an old method for starting RSpec.\n" \
8
+ # "RspecStarter 1.6.0 introduced a new interface that is faster and more flexible.\n" \
9
+ # "To upgrade:\n" \
10
+ # " 1. Run 'rspec_starter --init' to install a new bin/start_rspec file.\n" \
11
+ # " 2. Your old bin/start_rspec will be renamed to bin/start_rspec.bak. If you customized\n" \
12
+ # " this file, open it up and consider moving the changes to the new bin/start_rspec file.\n" \
13
+ # " See https://github.com/roberts1000/rspec_starter for instructions on the new interface.\n".colorize(:yellow)
14
+ LegacyRunner.new(defaults).run
15
+ end
16
+ end
@@ -0,0 +1,40 @@
1
+ module RspecStarter
2
+ # Method that support the help option on the bin/start_rspec script.
3
+ module Help
4
+ def should_show_help?
5
+ ARGV.any? { |option| option.include? "--help" }
6
+ end
7
+
8
+ def show_help
9
+ # Figure out the file name that invoked the rspec_starter helper. This is the name of the script; it be called anything.
10
+ script_name = calling_file_name
11
+ puts "Usage: #{script_name.colorize(:light_blue)} #{'[options] [options for RSpec]'.colorize(:light_blue)}\n"
12
+ puts " #{script_name} will look for its own options first then pass any remaining options to rspec"
13
+
14
+ # rubocop:disable Metrics/LineLength
15
+ puts "\nOptions: (run 'rspec --help' to see RSpec's options)"
16
+ puts " #{'--no-xvfb'.colorize(:light_blue)} DO NOT run XVFB (this can speed up RSpec when running tests that don't need XVFB)"
17
+ puts " #{'--no-prep'.colorize(:light_blue)} DO NOT prepare the test database (can speed up testing if you know the DB is clean)"
18
+
19
+ puts "\nExamples:"
20
+ puts " #{script_name.colorize(:light_blue)} #{'spec/features'.colorize(:light_blue)} (only run specs in the specs/features folder)"
21
+ puts " #{script_name.colorize(:light_blue)} #{'spec/features/some_spec:53'.colorize(:light_blue)} (run the spec on line 53 of the spec/features_some_spec.rb file)"
22
+ puts " #{script_name.colorize(:light_blue)} #{'--no-xvfb'.colorize(:light_blue)} #{'spec/requests/some_spec'.colorize(:light_blue)} (don't start XVFB since it's not needed for request specs)"
23
+ puts " #{'SIMPLECOV_FORMATTER=rcov'.colorize(:light_blue)} #{script_name.colorize(:light_blue)} (use with environment variables)\n"
24
+ # rubocop:enable Metrics/LineLength
25
+ end
26
+
27
+ # This is ugly, but it gives us some added flexibility. Users can invoke the rspec_starter method from any script or
28
+ # executable. This method attempts to find out the name of the script/exectuable.
29
+ # "caller" returns the method stack, and because of the location of this file in the gem, we happen to be the 4th item in the
30
+ # the array (hence "caller[3]" below).
31
+ #
32
+ # This method may not return a pretty result in all cases, but it's decent if the user has defined a script in their project
33
+ # (possibly in the bin folder, or root of the project).
34
+ def calling_file_name
35
+ # rubocop:disable Performance/Caller
36
+ caller[3].split(":")[0]
37
+ # rubocop:enable Performance/Caller
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,90 @@
1
+ require 'pathname'
2
+ require 'open3'
3
+
4
+ require_relative 'help'
5
+ require_relative 'steps/step'
6
+ require_relative 'steps/verify_xvfb_step'
7
+ require_relative 'steps/prepare_database_step'
8
+ require_relative 'steps/remove_tmp_folder_step'
9
+ require_relative 'steps/invoke_rspec_step'
10
+
11
+ module RspecStarter
12
+ # This is a simple class that encapulates the process of running RSpec. When a Runner is created, it creates a set of
13
+ # steps that will be executed, in order, when the 'run' method is invoked. Each step encapsulates an action that can be
14
+ # taken to help invoke Rspec. Steps are typically independent do not depend on information from other steps. However
15
+ # this is not a hard rule. If more complex steps are needed, feel free to create them. Each steps knows about the main
16
+ # runner object, so the runner object is a good place to store shared info.
17
+ class LegacyRunner
18
+ include Help
19
+ attr_reader :xvfb_installed, :step_num, :steps
20
+
21
+ def initialize(defaults)
22
+ @steps = []
23
+ @step_num = 1
24
+ @xvfb_installed = RspecStarter.which("xvfb-run")
25
+ @prep_db_step = PrepareDatabaseStep.new(defaults, self)
26
+ @run_rspec_step = InvokeRspecStep.new(defaults, self)
27
+ @steps << VerifyXvfbStep.new(defaults, self)
28
+ @steps << @prep_db_step
29
+ @steps << RemoveTmpFolderStep.new(defaults, self)
30
+ @steps << @run_rspec_step
31
+ end
32
+
33
+ def run
34
+ return show_help if should_show_help? # If we show help, exit and don't do anything else.
35
+
36
+ @steps.each do |step|
37
+ next unless step.should_execute?
38
+
39
+ step.execute
40
+ @step_num += 1
41
+ break if step.failed?
42
+ end
43
+
44
+ finalize_exit
45
+ end
46
+
47
+ def project_is_rails_app?
48
+ File.file?(File.join(Dir.pwd, 'config', 'application.rb'))
49
+ end
50
+
51
+ def project_is_rails_engine?
52
+ return false unless project_has_lib_dir?
53
+
54
+ Dir["#{Dir.pwd}/lib/**/*.rb"].each do |file|
55
+ return true if File.readlines(file).detect { |line| line.match(/\s*class\s+.*<\s+::Rails::Engine/) }
56
+ end
57
+ false
58
+ end
59
+
60
+ def project_has_lib_dir?
61
+ Dir.exist?("#{Dir.pwd}/lib")
62
+ end
63
+
64
+ def operating_system_name
65
+ result = `uname`
66
+ return 'Linux' if result.include?('Linux')
67
+ return 'MacOS' if result.include?('Darwin')
68
+
69
+ 'Unknown'
70
+ end
71
+
72
+ def is_linux?
73
+ operating_system_name == 'Linux'
74
+ end
75
+
76
+ def is_mac?
77
+ operating_system_name == 'MacOS'
78
+ end
79
+
80
+ def xvfb_installed?
81
+ @xvfb_installed
82
+ end
83
+
84
+ def finalize_exit
85
+ exit(1) if @run_rspec_step.rspec_exit_status.nonzero?
86
+ exit(1) if @prep_db_step.exit_status.nonzero?
87
+ exit(0)
88
+ end
89
+ end
90
+ end
@@ -5,6 +5,7 @@ module RspecStarter
5
5
 
6
6
  def initialize(defaults, runner)
7
7
  super(runner)
8
+
8
9
  @allow_xvfb = defaults.fetch(:allow_xvfb, true)
9
10
  @relevant_options = ["--no-xvfb"]
10
11
  @success_or_skipped = nil # Will be updated once step executes
@@ -42,6 +43,7 @@ module RspecStarter
42
43
  return base if @runner.is_mac?
43
44
  return base unless @allow_xvfb
44
45
  return base if @user_wants_to_skip_xvfb
46
+
45
47
  @runner.xvfb_installed? ? "xvfb-run #{base}" : base
46
48
  end
47
49
  end
@@ -5,6 +5,7 @@ module RspecStarter
5
5
 
6
6
  def initialize(defaults, runner)
7
7
  super(runner)
8
+
8
9
  @prepare_database = defaults.fetch(:prepare_db, true)
9
10
  @relevant_options << '--no-prep-db'
10
11
  @user_wants_to_skip = ARGV.any? { |option| option.include?("--no-prep-db") }
@@ -19,6 +20,7 @@ module RspecStarter
19
20
  def should_execute?
20
21
  return false if @user_wants_to_skip
21
22
  return false unless @prepare_database
23
+
22
24
  @runner.project_is_rails_app? || @runner.project_is_rails_engine?
23
25
  end
24
26
 
@@ -41,7 +43,6 @@ module RspecStarter
41
43
  end
42
44
  end
43
45
 
44
-
45
46
  private
46
47
 
47
48
  def rebuild_command
@@ -52,6 +53,7 @@ module RspecStarter
52
53
  # return a zero exit status. We need to see if 'rake aborted!' has been written to the output.
53
54
  def successful?(stderr)
54
55
  return false if @exit_status.nonzero?
56
+
55
57
  !stderr.include?("rake aborted!")
56
58
  end
57
59
  end
@@ -3,6 +3,7 @@ module RspecStarter
3
3
  class RemoveTmpFolderStep < RspecStarter::Step
4
4
  def initialize(defaults, runner)
5
5
  super(runner)
6
+
6
7
  @remove_tmp_folder = defaults.fetch(:remove_tmp, true)
7
8
  @runner = runner
8
9
  @relevant_options << "--no-remove-tmp"
@@ -16,11 +17,13 @@ module RspecStarter
16
17
 
17
18
  def should_execute?
18
19
  return false if @user_wants_to_skip_removal
20
+
19
21
  @remove_tmp_folder
20
22
  end
21
23
 
22
24
  def execute
23
25
  return @success_or_skipped = true unless should_execute?
26
+
24
27
  existed_before = tmp_folder_exists?
25
28
 
26
29
  print "[#{@runner.step_num}] Removing #{'tmp'.colorize(:light_blue)} folder ... "
@@ -5,6 +5,7 @@ module RspecStarter
5
5
  class VerifyXvfbStep < RspecStarter::Step
6
6
  def initialize(defaults, runner)
7
7
  super(runner)
8
+
8
9
  @relevant_options << '--no-xvfb'
9
10
  @use_xvfb = defaults.fetch(:use_xvfb, true)
10
11
  @user_wants_to_skip_xvfb = ARGV.any? { |option| option.include?("--no-xvfb") }
@@ -21,6 +22,7 @@ module RspecStarter
21
22
 
22
23
  def should_execute?
23
24
  return false if @user_wants_to_skip_xvfb
25
+
24
26
  @use_xvfb
25
27
  end
26
28
 
@@ -0,0 +1,84 @@
1
+ module RspecStarter
2
+ # Option objects hold the information that Step subclasses want to register as options.
3
+ class Option
4
+ attr_reader :name, :default, :description, :key, :owner, :switch, :switch_description
5
+
6
+ def initialize(name:, default:, description: "", owner:, switch:, switch_description: "")
7
+ @owner = owner
8
+ @switch = switch
9
+ @switch_description = switch_description
10
+ @simplified_switch = switch.nil? ? nil : self.class.simplified_switch_name(switch)
11
+ @description = description
12
+ @default = default
13
+ self.name = name
14
+ @key = name || @simplified_switch
15
+ validate
16
+ end
17
+
18
+ def name=(value)
19
+ @name = value.nil? ? nil : value.to_s
20
+ end
21
+
22
+ # Remove leading hyphens
23
+ # Convert remaining hyphens to underscores
24
+ # Undercase
25
+ def self.simplified_switch_name(switch)
26
+ switch.sub(/^-*/, "").sub("-", "_").underscore
27
+ end
28
+
29
+ # Does this option apply to the DSL inside the RspecStarter.start block. If so, users can use it like this:
30
+ # RspecStarter.start do
31
+ # task :foo, option_name_here: "some_value"
32
+ # end
33
+ def is_dsl_option?
34
+ !name.nil?
35
+ end
36
+
37
+ private
38
+
39
+ def validate
40
+ validate_name_has_no_hyphens
41
+ validate_switch_starts_with_hyphens
42
+ validate_name_or_switch_present
43
+ validate_default_is_true_false_for_switch
44
+ validate_switch_is_like_name
45
+ end
46
+
47
+ def validate_name_has_no_hyphens
48
+ return unless name
49
+ return unless name.include?("-")
50
+
51
+ raise "#{owner.name}#register_options is trying to register #{name}. Option names should not include hyphens"
52
+ end
53
+
54
+ def validate_switch_starts_with_hyphens
55
+ return unless switch
56
+ return unless switch[0] != "-" || switch[2] == "-"
57
+
58
+ raise "#{owner.name}#register_options is trying to register switch #{switch}. The switch name must start with '--' or '-'"
59
+ end
60
+
61
+ def validate_name_or_switch_present
62
+ return unless switch.nil? && name.nil?
63
+
64
+ raise "#{owner.name}#register_options is trying to create an option but 'name:' or 'switch:' are not specified. " \
65
+ "At least one must be specified."
66
+ end
67
+
68
+ def validate_default_is_true_false_for_switch
69
+ return unless switch && ![true, false].include?(default)
70
+
71
+ raise "#{owner.name}#register_options is trying to create an option for switch #{switch}. Options with switches " \
72
+ "can only return true/false. Set the 'default' argument to true or false for the option."
73
+ end
74
+
75
+ def validate_switch_is_like_name
76
+ return unless !switch.nil? && !name.nil?
77
+ return if name == @simplified_switch
78
+
79
+ raise "#{owner.name}#register_options is trying to create an option with name #{name} and switch #{switch}. " \
80
+ "The switch must be the same as the name when hyphens are converted to underscores and leading hyphens " \
81
+ "are removed."
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,96 @@
1
+ module RspecStarter
2
+ # Manages the option registration process for the Step subclasses.
3
+ # Holds raw option information so it can organized when steps run.
4
+ class Options
5
+ attr_reader :present_switches, :registered_task_options, :rspec_args_string
6
+
7
+ def initialize(environment, args)
8
+ @environment = environment
9
+ # These are the ARGV args that are passed to the command line.
10
+ @command_line_args = args
11
+ # Task classes that are used in the RspecStarter.start block: VerifyDisplayServer, StartRspec, etc...
12
+ @task_classes = environment.unique_task_classes
13
+ # The options that each Task wants to use.
14
+ @registered_task_options = {}
15
+
16
+ # Tasks register the command line switches they care about and RspecStarter has some global switches that it uses. This
17
+ # holds the list of switches that the user provided that match a task switch or a global switch. If the user doesn't
18
+ # supply a switch, or supplies a switch that nobody cares about, this will be an empty list.
19
+ @present_switches = nil
20
+
21
+ load_registered_task_options
22
+ initialize_present_switches
23
+
24
+ # The args that should be passed to rspec when it starts.
25
+ @rspec_args_string = nil
26
+ initialize_rspec_args_string
27
+ end
28
+
29
+ # If 'switch' is given, the option can only return 'true' or 'false'. 'default' must be set to 'true' or 'false'. When
30
+ # the switch isn't specified the user, the default (which must be true/false) is return. When the user specifies the default
31
+ # !default is returned.
32
+
33
+ # If switch is not given, default an be set to anything.
34
+
35
+ # If name isn't specified, this option is only a commandline switch.
36
+ # If name is specified, this option can be used inside the start block on a task.
37
+ def register_task_option(klass, name: nil, default: nil, description: "", switch: nil, switch_description: "")
38
+ new_option = RspecStarter::Option.new(name: name, default: default, description: description, owner: klass, switch: switch,
39
+ switch_description: switch_description)
40
+ @registered_task_options[klass] << new_option
41
+ end
42
+
43
+ def all_task_options
44
+ hash = {}
45
+ @registered_task_options.each do |_task_class, options|
46
+ options.each do |option|
47
+ hash[option.switch] = option unless hash.has_key?(option.switch)
48
+ end
49
+ end
50
+
51
+ list = []
52
+ hash.each { |_switch, option| list << option }
53
+ list
54
+ end
55
+
56
+ def all_switches
57
+ (global_switches + all_task_switches).sort
58
+ end
59
+
60
+ def global_switches
61
+ ['--help', '-h']
62
+ end
63
+
64
+ def all_task_switches
65
+ list = Set.new
66
+ @registered_task_options.each do |_task_class, options|
67
+ options.each { |option| list << option.switch unless option.switch.nil? }
68
+ end
69
+ list.to_a
70
+ end
71
+
72
+ def registered_task_option(task_class)
73
+ @registered_task_options[task_class]
74
+ end
75
+
76
+ private
77
+
78
+ def initialize_rspec_args_string
79
+ list = @command_line_args.dup
80
+ all_switches.each { |switch| list.delete(switch) }
81
+ @rspec_args_string = list.join(" ")
82
+ end
83
+
84
+ def load_registered_task_options
85
+ # Give each step class the ability to register options that we should know about.
86
+ @task_classes.each do |klass|
87
+ @registered_task_options[klass] = []
88
+ klass.provide_options_to(self)
89
+ end
90
+ end
91
+
92
+ def initialize_present_switches
93
+ @present_switches = all_switches.select { |switch| @command_line_args.include?(switch) }.to_a
94
+ end
95
+ end
96
+ end