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
@@ -13,6 +13,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
13
13
 
14
14
  require "cri"
15
15
  require 'fileutils'
16
+ require 'rspec_starter/helpers'
16
17
 
17
18
  module Cri
18
19
  # A small monkey patch to force the cri gem to stop coloring output.
@@ -23,6 +24,7 @@ module Cri
23
24
  end
24
25
  end
25
26
 
27
+ # rubocop:disable Metrics/BlockLength
26
28
  command = Cri::Command.define do
27
29
  usage 'rspec_starter [options]'
28
30
  # aliases :ds, :stuff
@@ -35,12 +37,22 @@ command = Cri::Command.define do
35
37
  end
36
38
 
37
39
  flag :i, :init, "(re)initialize your project bin/start_rspec file" do |_value, _cmd|
40
+ helpers = RspecStarter::Helpers.new
41
+
38
42
  if File.file?("bin/start_rspec")
39
43
  FileUtils.mv("bin/start_rspec", "bin/start_rspec.bak")
40
44
  puts "bin/start_rspec renamed to bin/start_rspec.bak"
41
45
  end
42
46
 
43
- template_path = File.expand_path('../lib/templates/start_rspec', __dir__)
47
+ template_path =
48
+ if helpers.project_is_rails_app?
49
+ File.expand_path('../lib/templates/rails_start_rspec', __dir__)
50
+ elsif helpers.project_is_rails_app?
51
+ File.expand_path('../lib/templates/rails_engine_start_rspec', __dir__)
52
+ else
53
+ File.expand_path('../lib/templates/start_rspec', __dir__)
54
+ end
55
+
44
56
  FileUtils.cp(template_path, "bin/start_rspec")
45
57
  File.chmod(0o775, "bin/start_rspec")
46
58
  puts "bin/start_rspec created"
@@ -56,6 +68,7 @@ command = Cri::Command.define do
56
68
  run do |options, args, cmd|
57
69
  end
58
70
  end
71
+ # rubocop:enable Metrics/BlockLength
59
72
 
60
73
  begin
61
74
  command.run(ARGV)
@@ -1,7 +1,37 @@
1
+ require 'active_support/core_ext/string/inflections'
1
2
  require 'English'
2
3
  require 'colorize'
4
+ require 'open3'
5
+ require 'rbconfig'
6
+
7
+ require 'rspec_starter/core_ext/string'
8
+
9
+ require 'rspec_starter/help'
10
+ require 'rspec_starter/helpers'
11
+ require 'rspec_starter/helpers/which'
12
+ require 'rspec_starter/option'
13
+ require 'rspec_starter/options'
14
+ require 'rspec_starter/step_options'
15
+ require 'rspec_starter/environment'
16
+
3
17
  require "rspec_starter/version"
4
- require_relative 'rspec_starter/runner'
18
+ require "rspec_starter/errors/step_error"
19
+ require "rspec_starter/errors/step_stopper"
20
+
21
+ require 'rspec_starter/runner'
22
+ require 'rspec_starter/step_context'
23
+ require 'rspec_starter/step'
24
+ require 'rspec_starter/task_context'
25
+ require 'rspec_starter/rspec_starter_task'
26
+ require 'rspec_starter/command_context'
27
+ require 'rspec_starter/command'
28
+
29
+ require 'rspec_starter/tasks/rebuild_rails_app_database'
30
+ require 'rspec_starter/tasks/remove_tmp_folder'
31
+ require 'rspec_starter/tasks/start_rspec'
32
+ require 'rspec_starter/tasks/verify_display_server'
33
+
34
+ require 'rspec_starter/legacy'
5
35
 
6
36
  # Setup pry for development when running "rake console". Guard against load
7
37
  # errors in production (since pry is only loaded as a DEVELOPMENT dependency
@@ -13,29 +43,44 @@ rescue LoadError
13
43
  end
14
44
  # rubocop:enable Lint/HandleExceptions
15
45
 
16
- # Entry point for the RspecStarter gem.
46
+ # The main entry point for the RspecStarter gem. The 'bin/start_rspec' file contains a block of code like this:
47
+ #
48
+ # RspecStarter.start do
49
+ # command "echo 'something'"
50
+ # task :some_task_name
51
+ # ... more tasks and commands ...
52
+ # end
53
+ #
54
+ # The start method that is executed is show below. The call to 'instance_eval(&block)' below is the special sauce that
55
+ # makes 'self' equal the RspecStarter module inside the block.
17
56
  module RspecStarter
18
- # The 'start' method takes arguments that can be used to control the steps that are executed when running Rspec. These
19
- # arguments are specified by the developer when configuring how the app prefers to run RSpec. In addition to the arguments,
20
- # the end user can pass command line options to the script/executable that is executing 'start'. The command line options
21
- # allow the end user to customize execution on a per-run basis. In general, the arguments have the ability to turn features
22
- # on and off while the command line options allow users to turn features off. For example, a developer could probably
23
- # configure his aplication to always prepare the database before running RSpec. When the command is run, the developer might
24
- # want to bypass preparing the database for a specific run because he already knows the database is clean (this will save some
25
- # startup time). On the other hand, if the developer has configured 'start' to never prepare the database, he cannot
26
- # enable it via the command line on a specific run.
27
- #
28
- # Arguments List
29
- # defaults(Hash)
30
- # :prepare_db => (true/false) Should the database be rebuilt?
31
- # :remove_tmp => (true/false) Should the tmp folder inside the application be removed before starting RSpec?
32
- # :allow_xvfb => (true/false) Should XVFB be allowed on systems that need it (like Linux)
33
- #
34
- # Command Line Options
35
- # --no-xvfb Do not attempt to start XVFB on Linux. On Macs, this doesn't do anything since XVFB doesn't exist.
36
- # --no-prep-db Do not try to rebuild the database. This is useful when the db is already clean and want to save time.
37
- # --no-remove-tmp Do not attempt to remove the tmp folder.
38
- def self.start(defaults={})
39
- Runner.new(defaults).run
57
+ def self.start(defaults={}, &block)
58
+ # If a block is missing, then the user is using the old method based starter.
59
+ return invoke_legacy_starter(defaults) unless block_given?
60
+
61
+ # Loads the information from the bin/start_rspec file and loads/parses the options. Provides info to the @runner.
62
+ @environment = Environment.new(ARGV, &block)
63
+
64
+ # Holds the step objects and executes them.
65
+ @runner = Runner.new(@environment)
66
+
67
+ # The bin/start_rspec file in the host application sets the APP_ROOT to the root folder of the host application.
68
+ Dir.chdir APP_ROOT do
69
+ # If we show help, exit and don't do anything else. We need to run the help in the context of the app root
70
+ # so the help output can show file paths relative to where the user is running (and not relative to this gem).
71
+ return show_help if should_show_help?
72
+
73
+ begin
74
+ @runner.run
75
+ rescue StepError
76
+ # The runner executes steps (which are tasks and commands). If a step reports a problem and the 'stop_on_problem' option
77
+ # is set to 'true' (for a given step), the step will raise a StepError error. That is the signal that execution should
78
+ # terminate immediately.
79
+ exit(@runner.largest_exit_status)
80
+ end
81
+
82
+ # If we get here RSpec has been started and the rspec_starter's job is done.
83
+ exit(0)
84
+ end
40
85
  end
41
86
  end
@@ -0,0 +1,59 @@
1
+ module RspecStarter
2
+ # Commands are steps that execute shell scripts or shell commands. They can be created using the command method
3
+ # inside the RspecStarter.start block. Commands execute quietly unless you specifically set 'quiet: false'.
4
+ #
5
+ # RspecStarter.start do
6
+ # command "echo 'Done'"
7
+ # command "echo 'This command shows the echo'", quiet: false
8
+ # end
9
+ #
10
+ class Command < RspecStarterStep
11
+ attr_accessor :status, :stderr, :stdout
12
+
13
+ def initialize(id, runner, command_string, options)
14
+ super(id, runner, options)
15
+ @command_string = command_string
16
+ end
17
+
18
+ def self.default_quiet
19
+ true
20
+ end
21
+
22
+ def print_starting_message
23
+ print "Executing #{colored_command_string} ..." + (quiet? ? "" : "\n")
24
+ end
25
+
26
+ def execute
27
+ if quiet?
28
+ @stdout, @stderr, @status = Open3.capture3(@command_string)
29
+ else
30
+ @verbose_command_passed = system(@command_string)
31
+ @status = $CHILD_STATUS
32
+ print "Executed #{colored_command_string} -"
33
+ end
34
+
35
+ problem(exit_status: @status.exitstatus) if command_failed?
36
+ end
37
+
38
+ private
39
+
40
+ def initialize_name
41
+ @name = :command
42
+ end
43
+
44
+ def write_error_info
45
+ puts @stdout
46
+ puts @stderr
47
+ puts "There was an error running '#{@command_string}'. It returned with exit status #{@status.exitstatus}.\n" \
48
+ "See the output above for details or manually run the command for more information.".colorize(:red)
49
+ end
50
+
51
+ def colored_command_string
52
+ @command_string.colorize(:light_blue)
53
+ end
54
+
55
+ def command_failed?
56
+ @status.exitstatus > 0 || @verbose_command_passed == false
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,38 @@
1
+ module RspecStarter
2
+ # CommandContext's are created to when parsing the RspecStater.start block. They hold the args for Commands. They are
3
+ # asked to create instances of Command from this information when it is time to execute. The also resolve the options
4
+ # that each Command is allowed to access.
5
+ class CommandContext < StepContext
6
+ attr_reader :command_string
7
+
8
+ def initialize(environment:, id:, command_string:, requested_args:)
9
+ super(environment: environment, id: id, requested_args: requested_args)
10
+
11
+ @command_string = command_string
12
+ end
13
+
14
+ def instantiate(runner)
15
+ Command.new(@id, runner, @command_string, build_options)
16
+ end
17
+
18
+ def step_class
19
+ Command
20
+ end
21
+
22
+ def is_task?
23
+ false
24
+ end
25
+
26
+ def is_command?
27
+ true
28
+ end
29
+
30
+ private
31
+
32
+ def build_options
33
+ options = super
34
+ apply_args_to_options(options)
35
+ options
36
+ end
37
+ end
38
+ end
@@ -1,7 +1,10 @@
1
1
  # Add some ome simple colorization methods for formatting console output.
2
- # Prefix methods with rs_ so they don't collide with other gems.
3
2
  class String
4
- def rs_colorize(color_code)
5
- "\e[#{color_code}m#{self}\e[0m"
3
+ def highlight
4
+ colorize(:light_blue)
5
+ end
6
+
7
+ def warning
8
+ colorize(:yellow)
6
9
  end
7
10
  end
@@ -0,0 +1,57 @@
1
+ module RspecStarter
2
+ # Load the environment for RspecStarter.
3
+ class Environment
4
+ attr_reader :options
5
+
6
+ # &start_block is the start block from the host application's bin/start_rspec file. It looks like this:
7
+ #
8
+ # RspecStarter.start do
9
+ # command "something"
10
+ # task :task_one
11
+ # command "something"
12
+ # task :task two
13
+ # ... more commands and tasks...
14
+ # end
15
+ def initialize(args, &start_block)
16
+ @next_id = 0
17
+ @step_info = {}
18
+
19
+ load_step_info(&start_block)
20
+
21
+ @options = Options.new(self, args, &start_block)
22
+ # This will lazily load once execution starts.
23
+ end
24
+
25
+ def step_contexts
26
+ @step_info.values
27
+ end
28
+
29
+ def unique_task_classes
30
+ @step_info.values.select(&:is_task?).collect(&:step_class).uniq
31
+ end
32
+
33
+ def command(string, **args)
34
+ step_id = next_id
35
+ @step_info[step_id] = CommandContext.new(environment: self, id: step_id, command_string: string, requested_args: args)
36
+ end
37
+
38
+ def task(task_name, **args)
39
+ step_id = next_id
40
+ klass = RspecStarter.helpers.class_for_task_name(task_name)
41
+ @step_info[step_id] = TaskContext.new(environment: self, id: step_id, step_class: klass, requested_args: args)
42
+ end
43
+
44
+ private
45
+
46
+ def next_id
47
+ @next_id += 1
48
+ end
49
+
50
+ def load_step_info(&start_block)
51
+ # This evaluates the start block and sets 'self' to an instance of Environment. The 'command' and 'task' methods
52
+ # in this class capture the 'command' and 'task' calls in the block. From there, we can determine the Steps classes that the
53
+ # user is trying to load.
54
+ instance_eval(&start_block)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,9 @@
1
+ module RspecStarter
2
+ # This error is raised when execution should completely stop. The current step should be finalized before this
3
+ # error is raised. Once this error is raised, RspecStarter will halt and report a non-zero exit status to the user.
4
+ class StepError < StandardError
5
+ def initialize(msg="")
6
+ super
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module RspecStarter
2
+ # This error is raised when a step wants to gracefully stop execution. It does not prevent later steps from running.
3
+ # It only ends on the current step execution.
4
+ class StepStopper < StandardError
5
+ def initialize(msg="")
6
+ super
7
+ end
8
+ end
9
+ end
@@ -1,40 +1,87 @@
1
+ # Code that shows output when 'bin/start_rspec --help' or 'bin/start_rspec -h' is executed.
1
2
  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" }
3
+ def self.should_show_help?
4
+ ARGV.any? { |option| option.include?("--help") || option.include?("-h") }
5
+ end
6
+
7
+ def self.show_help
8
+ # Figure out the file name that invoked the rspec_starter helper and treat it as the name of the
9
+ # script. We install a bin/rspec_starter file, but the user could call it anything.
10
+ script_name = helpers.starter_script_file_name
11
+ colored_script_name = script_name.colorize(:light_blue)
12
+
13
+ write_help_usage_section(script_name, colored_script_name)
14
+ write_help_command_line_options_section(colored_script_name)
15
+ write_help_examples_section(colored_script_name)
16
+ write_available_tasks
17
+ write_task_info(colored_script_name)
18
+
19
+ puts ""
20
+
21
+ exit(0)
22
+ end
23
+
24
+ def self.write_help_usage_section(script_name, colored_script_name)
25
+ puts "Usage: #{colored_script_name} #{'[options] [options for RSpec]'.colorize(:light_blue)}\n"
26
+ puts " #{script_name} will look for its own options first then pass any remaining options to RSpec."
27
+ end
28
+
29
+ def self.write_help_command_line_options_section(colored_script_name)
30
+ puts "\nCommand Line Options: (run #{'rspec --help'.colorize(:light_blue)} to see RSpec's options)"
31
+ puts " This list is computed dynamically based on the tasks that are enabled in the #{colored_script_name} file."
32
+
33
+ max_length = @environment.options.all_task_switches.max_by(&:length).size
34
+
35
+ @environment.options.all_task_options.each do |option|
36
+ next unless option.switch
37
+
38
+ length = option.switch.length
39
+ padding = max_length - length + 5 # 5 is the number of spaces past the longest swtich to start the description column
40
+ puts " #{option.switch.colorize(:light_blue)}#{' ' * padding}#{option.switch_description}"
6
41
  end
42
+ end
43
+
44
+ def self.write_help_examples_section(colored_script_name)
45
+ # rubocop:disable Metrics/LineLength
46
+ puts "\nExamples:"
47
+ puts " #{colored_script_name} #{'spec/features'.colorize(:light_blue)} (only run specs in the specs/features folder)"
48
+ puts " #{colored_script_name} #{'spec/features/some_spec:53'.colorize(:light_blue)} (run the spec on line 53 of the spec/features_some_spec.rb file)"
49
+ puts " #{colored_script_name} #{'--no-xvfb'.colorize(:light_blue)} #{'spec/requests/some_spec'.colorize(:light_blue)} (don't start XVFB since it's not needed for request specs)"
50
+ puts " #{'SIMPLECOV_FORMATTER=rcov'.colorize(:light_blue)} #{colored_script_name} (use with environment variables)\n"
51
+ # rubocop:enable Metrics/LineLength
52
+ end
7
53
 
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
54
+ def self.write_available_tasks
55
+ subclasses = ObjectSpace.each_object(Class).select { |klass| klass < RspecStarterTask }.sort { |a, b| a.name <=> b.name }
56
+
57
+ puts "\nAvailable Tasks:"
58
+ subclasses.each do |klass|
59
+ task_name = RspecStarterTask.name_for_class(klass).to_s
60
+ puts " #{task_name.colorize(:light_blue)} - #{klass.description}"
25
61
  end
62
+ end
63
+
64
+ def self.write_task_info(colored_script_name)
65
+ puts "\nTask Options:"
66
+ puts " These options can be used inside the #{colored_script_name} file on #{"task".colorize(:light_blue)} lines."
67
+ puts " This list is computed dynamically based on the tasks that are enabled in the #{colored_script_name} file."
68
+ puts " Every task accepts the following options:"
69
+ puts " #{"quiet".colorize(:light_blue)} (true/false - Tell the task to be verbose. Some tasks may disregard at times.)"
70
+ puts " #{"stop_on_problem".colorize(:light_blue)} (true/false - Tell the task to stop startup if it encounters a problem.)"
71
+ puts ""
72
+
73
+ sorted_task_options = Hash[@environment.options.registered_task_options.sort_by do |klass, options|
74
+ RspecStarterTask.name_for_class(klass)
75
+ end]
26
76
 
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
77
+ sorted_task_options.each do |klass, options|
78
+ dsl_options = options.select(&:is_dsl_option?).sort {|a,b| a.name <=> b.name }
79
+ unless dsl_options.empty?
80
+ puts " :#{RspecStarterTask.name_for_class(klass).to_s.colorize(:light_blue)}"
81
+ dsl_options.each do |option|
82
+ puts " #{option.name.colorize(:light_blue)} (#{option.description})"
83
+ end
84
+ end
38
85
  end
39
86
  end
40
87
  end