class GoldenChild::Scenario

Constants

Validation

@abstract

Attributes

command_history[R]
configuration[R]
name[R]

Public Class Methods

new(name:, configuration: ::GoldenChild.configuration) click to toggle source

@param [String] name The name of the scenario (e.g. RSpec example group)

# File lib/golden_child/scenario.rb, line 35
def initialize(name:, configuration: ::GoldenChild.configuration)
  @name            = name
  @command_history = []
  @configuration   = configuration
end

Public Instance Methods

actual_path() click to toggle source
# File lib/golden_child/scenario.rb, line 217
def actual_path
  actual_root + relative_path
end
Also aliased as: root
control_dir() click to toggle source
# File lib/golden_child/scenario.rb, line 235
def control_dir
  actual_path + ".golden_child"
end
current_actual_path() click to toggle source
# File lib/golden_child/scenario.rb, line 227
def current_actual_path
  actual_path + current_working_dir
end
current_master_path() click to toggle source
# File lib/golden_child/scenario.rb, line 231
def current_master_path
  master_path + current_working_dir
end
diff(master_file, actual_file) click to toggle source
# File lib/golden_child/scenario.rb, line 180
def diff(master_file, actual_file)
  differ      = RSpec::Support::Differ.new
  differences = differ.diff(filter_file(actual_file), filter_file(master_file))
  differences.empty? ? nil : differences
end
env() click to toggle source

@return [Hash] editable env var hash, defaults to {#configuration}

# File lib/golden_child/scenario.rb, line 248
def env
  @env ||= configuration.env.dup
end
filter_file(filename) click to toggle source
# File lib/golden_child/scenario.rb, line 186
def filter_file(filename)
  @filtered_files ||= {} # memoization
  @filtered_files.fetch(filename) {
    @filtered_files[filename] = content_filters.reduce(filename.read) {
        |content, filter|
      filter.call(content)
    }
  }
end
filtered_files_differ?(master_file, actual_file) click to toggle source
# File lib/golden_child/scenario.rb, line 176
def filtered_files_differ?(master_file, actual_file)
  filter_file(master_file) != filter_file(actual_file)
end
get_shortcode_for(actual_file) click to toggle source
# File lib/golden_child/scenario.rb, line 196
def get_shortcode_for(actual_file)
  code = state_transaction do |store|
    shortcode_map = (store[:shortcode_map] ||= {})
    shortcode_map.fetch(actual_file.to_s) {
      new_code                        = shortcode_map.values.max.to_i + 1
      shortcode_map[actual_file.to_s] = new_code
    }
  end
  "@#{code}"
end
master_path() click to toggle source
# File lib/golden_child/scenario.rb, line 223
def master_path
  golden_path + "master" + relative_path
end
populate_from(source_dir, caller=caller) click to toggle source

Recursively populate the current scenario with copies of files from ‘source_dir`

@param [String, Pathname] source_dir @param [Array] caller

# File lib/golden_child/scenario.rb, line 46
def populate_from(source_dir, caller=caller)
  Dir.chdir(project_root) do
    raise "Scenario has not been set up" unless actual_path.exist?
    source_dir = Pathname(source_dir)
    unless source_dir.directory?
      fail RuntimeError, "No such directory #{source_dir}", caller
    end

    Dir.foreach(source_dir) do |entry|
      next if %w[. ..].include?(entry)
      copy_entry source_dir + entry, actual_path + entry
    end
  end
end
relative_path() click to toggle source
# File lib/golden_child/scenario.rb, line 239
def relative_path
  slug
end
root()
Alias for: actual_path
run(*args, allow_fail: false, env: self.env, caller: caller, ** options) click to toggle source

Run a command in the context of the current scenario.

@param [Array] args The command. See {Process.spawn} for the various

forms supported. Note that any environment should be passed via the
`env` option below.

@param [true, false] allow_fail (false) Whether to raise an exception

if the command fails.

@param [Hash] env Environment variables for the command @param [Array] caller @param [Hash] options

# File lib/golden_child/scenario.rb, line 71
def run(*args, allow_fail: false, env: self.env, caller: caller, ** options)
  options[:chdir]        ||= actual_path.to_s
  env                    = env.map { |k, v| [k.to_s, v.to_s] }.to_h
  stdout, stderr, status = Open3.capture3(env, *args, ** options)
  command_history.push(
      command: args, status: status, stdout: stdout, stderr: stderr)
  command_log = ""
  command_log << "\nCommand: #{args}"
  command_log << "\nEnvironment:"
  env.each_pair do |key, value|
    command_log << "\n  #{key}=#{value}"
  end
  command_log << "\nExited with status #{status.exitstatus}"
  command_log << "\n========== Command STDOUT ==========\n"
  command_log << stdout
  command_log << "\n========== End STDOUT ==========\n"
  command_log << "\n========== Command STDERR ==========\n"
  command_log << stderr
  command_log << "\n========== End STDERR ==========\n"
  (control_dir + "commands.log").open("a") do |f|
    f.write(command_log)
  end
  unless status.success? || allow_fail
    fail RuntimeError, command_log, caller
  end
end
setup() click to toggle source
# File lib/golden_child/scenario.rb, line 207
def setup
  mkpath master_path.parent
  rmtree actual_path
  mkpath actual_path
  mkpath control_dir
end
slug() click to toggle source
# File lib/golden_child/scenario.rb, line 243
def slug
  name.downcase.tr_s("^a-z0-9", "-")[0..255]
end
teardown() click to toggle source
# File lib/golden_child/scenario.rb, line 214
def teardown
end
unzip_dir_for(relative_filename) click to toggle source

@return [Pathname]

# File lib/golden_child/scenario.rb, line 119
def unzip_dir_for(relative_filename)
  current_actual_path + (relative_filename.to_s + ".golden_child_unzip")
end
validate(*files, ** options) click to toggle source

Verify that ‘files` are identical to their corresponding gold master files.

# File lib/golden_child/scenario.rb, line 125
def validate(*files, ** options)
  paths   = Rake::FileList[*files.map(&:to_s)]
  pass    = true
  message = "No files to validate"
  Dir.chdir(project_root) do
    paths.each do |path|
      master_file  = current_master_path + path
      actual_file  = current_actual_path + path
      shortcode    = get_shortcode_for(actual_file)
      approval_cmd = "golden accept #{shortcode}"
      message      = ""
      file_pass    = false
      if !actual_file.exist?
        message << "Expected file: #{actual_file}"
        message << "\nto be created, but it was not."
      elsif !actual_file.file?
        message << "Expected: #{actual_file}"
        message << "\n to be a file, but it is a #{actual_file.ftype}."
      elsif !master_file.exist?
        message << "Master: #{master_file}"
        message << "\ndoes not yet exist."
        message << "\nActual file: #{actual_file}"
        message << "\nhas the following content:\n\n"
        message << filter_file(actual_file)
        message << "\n\nIf this looks correct, run `#{approval_cmd}`"
      elsif !master_file.file?
        message << "Master: #{master_file}"
        message << "must be a file, but it is a #{master_file.ftype}."
      elsif filtered_files_differ?(master_file, actual_file)
        message << "Actual: #{actual_file}"
        message << "\ndiffers from master: #{master_file}"
        message << "\n"
        message << diff(master_file, actual_file)
        message << "\n\nIf the changes look correct, run `#{approval_cmd}`"
      else
        message << "Actual file #{actual_file} matches master #{master_file}"
        file_pass = true
      end
      unless file_pass
        pass = false
        break
      end
    end
  end
  if pass
    PassedValidation.new(message)
  else
    FailedValidation.new(message)
  end
end
within_zip(relative_filename) { |unzip_dir| ... } click to toggle source

Unzip a zip file, and execute commands in the context of the unzipped directory.

You must have the ‘unzip(1)` program installed for this to work.helper

@param [String, Pathname] relative_filename The path of the zip file

# File lib/golden_child/scenario.rb, line 104
def within_zip(relative_filename)
  relative_filename = Pathname(relative_filename)
  filename          = actual_path + relative_filename
  raise "Zip file not found: #{filename}" unless filename.exist?
  unzip_dir = unzip_dir_for(relative_filename)
  mkpath unzip_dir
  unzip_succeeded =
      system(*%W[unzip -qq #{filename} -d #{unzip_dir}])
  raise "Could not unzip #{filename}" unless unzip_succeeded
  push_working_dir(unzip_dir.relative_path_from(current_actual_path)) do
    yield(unzip_dir)
  end
end

Private Instance Methods

current_working_dir() click to toggle source
# File lib/golden_child/scenario.rb, line 268
def current_working_dir
  last_dir = working_dir_stack.last
  last_dir || "."
end
push_working_dir(new_dir) { || ... } click to toggle source
# File lib/golden_child/scenario.rb, line 256
def push_working_dir(new_dir)
  dir_stack = working_dir_stack
  dir_stack.push(new_dir)
  yield
ensure
  dir_stack.pop
end
working_dir_stack() click to toggle source
# File lib/golden_child/scenario.rb, line 264
def working_dir_stack
  Thread.current[:golden_child_working_dir] ||= []
end