From 3c9f159fb8937641541caa27e65b95251e0aa2be Mon Sep 17 00:00:00 2001 From: Jordon Bedwell Date: Sun, 10 Jan 2016 14:28:28 -0600 Subject: [PATCH] Move Cucumber to using RSpec-Expections and furthering JRuby support. * Removes posix-spawn in favor of Open3#popen3 * Encapsulates all the paths into a single easy class. * Moves to %r{} to avoid ambiguious warnings per-Cucumber suggestion. * Starts passing around Pathname to make some actions faster. * Clean's up some methods to make them easier to read. * AUTOMATIC: Add "#" between each method. --- .travis.yml | 4 - Gemfile | 1 + features/step_definitions/jekyll_steps.rb | 303 +++++++++++----------- features/support/env.rb | 155 ++++++----- 4 files changed, 252 insertions(+), 211 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7777656d..4bba7a8d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,10 +16,6 @@ matrix: allow_failures: - rvm: jruby-9.0.3.0 - rvm: ruby-head - exclude: - - rvm: jruby-9.0.3.0 - env: TEST_SUITE=cucumber - env: matrix: - TEST_SUITE=test diff --git a/Gemfile b/Gemfile index 04bd73bd..2c0d7e8a 100644 --- a/Gemfile +++ b/Gemfile @@ -11,6 +11,7 @@ group :development do end group :test do + gem 'rspec-expectations' gem 'redgreen', '~> 1.2' gem 'shoulda', '~> 3.5' gem 'cucumber', '~> 2.1' diff --git a/features/step_definitions/jekyll_steps.rb b/features/step_definitions/jekyll_steps.rb index c9ae0567..64213ea4 100644 --- a/features/step_definitions/jekyll_steps.rb +++ b/features/step_definitions/jekyll_steps.rb @@ -1,131 +1,115 @@ -def file_content_from_hash(input_hash) - matter_hash = input_hash.reject { |k, v| k == "content" } - matter = matter_hash.map { |k, v| "#{k}: #{v}\n" }.join.chomp - - content = if input_hash['input'] && input_hash['filter'] - "{{ #{input_hash['input']} | #{input_hash['filter']} }}" - else - input_hash['content'] - end - - <<-EOF ---- -#{matter} ---- -#{content} -EOF -end - Before do - FileUtils.mkdir_p(TEST_DIR) unless File.exist?(TEST_DIR) - Dir.chdir(TEST_DIR) + FileUtils.mkdir_p(Paths.test_dir) unless Paths.test_dir.directory? + Dir.chdir(Paths.test_dir) end +# + After do - FileUtils.rm_rf(TEST_DIR) if File.exist?(TEST_DIR) - FileUtils.rm(JEKYLL_COMMAND_OUTPUT_FILE) if File.exist?(JEKYLL_COMMAND_OUTPUT_FILE) - FileUtils.rm(JEKYLL_COMMAND_STATUS_FILE) if File.exist?(JEKYLL_COMMAND_STATUS_FILE) - Dir.chdir(File.dirname(TEST_DIR)) + Paths.test_dir.rmtree if Paths.test_dir.exist? + Paths.output_file.delete if Paths.output_file.exist? + Paths.status_file.delete if Paths.status_file.exist? + Dir.chdir(Paths.test_dir.parent) end -World do - MinitestWorld.new -end +# -Given /^I have a blank site in "(.*)"$/ do |path| - FileUtils.mkdir_p(path) unless File.exist?(path) -end - -Given /^I do not have a "(.*)" directory$/ do |path| - File.directory?("#{TEST_DIR}/#{path}") -end - -# Like "I have a foo file" but gives a yaml front matter so jekyll actually processes it -Given /^I have an? "(.*)" page(?: with (.*) "(.*)")? that contains "(.*)"$/ do |file, key, value, text| - File.open(file, 'w') do |f| - f.write <<-EOF ---- -#{key || 'layout'}: #{value || 'nil'} ---- -#{text} -EOF +Given %r{^I have a blank site in "(.*)"$} do |path| + if !File.exist?(path) + then FileUtils.mkdir_p(path) end end -Given /^I have an? "(.*)" file that contains "(.*)"$/ do |file, text| - File.open(file, 'w') do |f| - f.write(text) +# + +Given %r{^I do not have a "(.*)" directory$} do |path| + Paths.test_dir.join(path).directory? +end + +# + +Given %r{^I have an? "(.*)" page(?: with (.*) "(.*)")? that contains "(.*)"$} do |file, key, value, text| + File.write(file, Jekyll::Utils.strip_heredoc(<<-DATA)) + --- + #{key || 'layout'}: #{value || 'nil'} + --- + + #{text} + DATA +end + +# + +Given %r{^I have an? "(.*)" file that contains "(.*)"$} do |file, text| + File.write(file, text) +end + +# + +Given %r{^I have an? (.*) (layout|theme) that contains "(.*)"$} do |name, type, text| + folder = type == "layout" ? "_layouts" : "_theme" + + destination_file = Pathname.new(File.join(folder, "#{name}.html")) + FileUtils.mkdir_p(destination_file.parent) unless destination_file.parent.directory? + File.write(destination_file, text) +end + +# + +Given %r{^I have an? "(.*)" file with content:$} do |file, text| + File.write(file, text) +end + +# + +Given %r{^I have an? (.*) directory$} do |dir| + if !File.directory?(dir) + then FileUtils.mkdir_p(dir) end end -Given /^I have an? (.*) (layout|theme) that contains "(.*)"$/ do |name, type, text| - folder = if type == 'layout' - '_layouts' - else - '_theme' - end - destination_file = File.join(folder, name + '.html') - destination_path = File.dirname(destination_file) - unless File.exist?(destination_path) - FileUtils.mkdir_p(destination_path) - end - File.open(destination_file, 'w') do |f| - f.write(text) - end -end +# -Given /^I have an? "(.*)" file with content:$/ do |file, text| - File.open(file, 'w') do |f| - f.write(text) - end -end - -Given /^I have an? (.*) directory$/ do |dir| - FileUtils.mkdir_p(dir) -end - -Given /^I have the following (draft|page|post)s?(?: (in|under) "([^"]+)")?:$/ do |status, direction, folder, table| +Given %r{^I have the following (draft|page|post)s?(?: (in|under) "([^"]+)")?:$} do |status, direction, folder, table| table.hashes.each do |input_hash| - title = slug(input_hash['title']) - ext = input_hash['type'] || 'markdown' + title = slug(input_hash["title"]) + ext = input_hash["type"] || "markdown" + filename = filename = "#{title}.#{ext}" if %w(draft page).include?(status) before, after = location(folder, direction) + dest_folder = "_drafts" if status == "draft" + dest_folder = "_posts" if status == "post" + dest_folder = "" if status == "page" - case status - when "draft" - dest_folder = '_drafts' - filename = "#{title}.#{ext}" - when "page" - dest_folder = '' - filename = "#{title}.#{ext}" - when "post" + if status == "post" parsed_date = Time.xmlschema(input_hash['date']) rescue Time.parse(input_hash['date']) - dest_folder = '_posts' filename = "#{parsed_date.strftime('%Y-%m-%d')}-#{title}.#{ext}" end path = File.join(before, dest_folder, after, filename) - File.open(path, 'w') do |f| - f.write file_content_from_hash(input_hash) - end + File.write(path, file_content_from_hash(input_hash)) end end -Given /^I have a configuration file with "(.*)" set to "(.*)"$/ do |key, value| - File.open('_config.yml', 'w') do |f| - f.write("#{key}: #{value}\n") - end +# + +Given %r{^I have a configuration file with "(.*)" set to "(.*)"$} do |key, value| + File.write("_config.yml", "#{key}: #{value}\n") end -Given /^I have a configuration file with:$/ do |table| - File.open('_config.yml', 'w') do |f| +# + +Given %r{^I have a configuration file with:$} do |table| + File.open("_config.yml", "w") do |f| table.hashes.each do |row| f.write("#{row["key"]}: #{row["value"]}\n") end end end -Given /^I have a configuration file with "([^\"]*)" set to:$/ do |key, table| - File.open('_config.yml', 'w') do |f| +# + +Given %r{^I have a configuration file with "([^\"]*)" set to:$} do |key, table| + File.open("_config.yml", "w") do |f| f.write("#{key}:\n") table.hashes.each do |row| f.write("- #{row["value"]}\n") @@ -133,102 +117,123 @@ Given /^I have a configuration file with "([^\"]*)" set to:$/ do |key, table| end end -Given /^I have fixture collections$/ do - FileUtils.cp_r File.join(JEKYLL_SOURCE_DIR, "test", "source", "_methods"), source_dir +# + +Given %r{^I have fixture collections$} do + FileUtils.cp_r Paths.source_dir.join("test", "source", "_methods"), source_dir end -Given /^I wait (\d+) second(s?)$/ do |time, plural| +# + +Given %r{^I wait (\d+) second(s?)$} do |time, plural| sleep(time.to_f) end -################## # -# Changing stuff -# -################## -When /^I run jekyll(.*)$/ do |args| - status = run_jekyll(args) +When %r{^I run jekyll(.*)$} do |args| + run_jekyll(args) + if args.include?("--verbose") || ENV["DEBUG"] + $stderr.puts "\n#{jekyll_run_output}\n" + end +end + +# + +When %r{^I run bundle(.*)$} do |args| + run_bundle(args) if args.include?("--verbose") || ENV['DEBUG'] $stderr.puts "\n#{jekyll_run_output}\n" end end -When /^I run bundle(.*)$/ do |args| - status = run_bundle(args) - if args.include?("--verbose") || ENV['DEBUG'] - $stderr.puts "\n#{jekyll_run_output}\n" - end -end +# -When /^I change "(.*)" to contain "(.*)"$/ do |file, text| - File.open(file, 'a') do |f| +When %r{^I change "(.*)" to contain "(.*)"$} do |file, text| + File.open(file, "a") do |f| f.write(text) end end -When /^I delete the file "(.*)"$/ do |file| +# + +When %r{^I delete the file "(.*)"$} do |file| File.delete(file) end -################## # -# Checking stuff + +Then %r{^the (.*) directory should +exist$} do |dir| + expect(Pathname.new(dir)).to exist +end + # -################## -Then /^the (.*) directory should +exist$/ do |dir| - assert File.directory?(dir), "The directory \"#{dir}\" does not exist" +Then %r{^the (.*) directory should not exist$} do |dir| + expect(Pathname.new(dir)).not_to exist end -Then /^the (.*) directory should not exist$/ do |dir| - assert !File.directory?(dir), "The directory \"#{dir}\" exists" +# +Then %r{^I should see "(.*)" in "(.*)"$} do |text, file| + regexp = Regexp.new(text, Regexp::MULTILINE) + expect(file_contents(file)).to match regexp end -Then /^I should see "(.*)" in "(.*)"$/ do |text, file| - assert_match Regexp.new(text, Regexp::MULTILINE), file_contents(file) +# + +Then %r{^I should see exactly "(.*)" in "(.*)"$} do |text, file| + expect(file_contents(file).strip).to eq text end -Then /^I should see exactly "(.*)" in "(.*)"$/ do |text, file| - assert_equal text, file_contents(file).strip +# + +Then %r{^I should not see "(.*)" in "(.*)"$} do |text, file| + regexp = Regexp.new(text, Regexp::MULTILINE) + expect(file_contents(file)).not_to match regexp end -Then /^I should not see "(.*)" in "(.*)"$/ do |text, file| - refute_match Regexp.new(text, Regexp::MULTILINE), file_contents(file) +# + +Then %r{^I should see escaped "(.*)" in "(.*)"$} do |text, file| + regexp = Regexp.new(Regexp.escape(text)) + expect(file_contents(file)).to match regexp end -Then /^I should see escaped "(.*)" in "(.*)"$/ do |text, file| - assert_match Regexp.new(Regexp.escape(text)), file_contents(file) +# + +Then %r{^the "(.*)" file should +exist$} do |file| + expect(Pathname.new(file)).to exist end -Then /^the "(.*)" file should +exist$/ do |file| - file_does_exist = File.file?(file) - unless file_does_exist - all_steps_to_path(file).each do |dir| - STDERR.puts "" - STDERR.puts "Dir #{dir}:" - STDERR.puts Dir["#{dir}/**/*"] - end - end - assert file_does_exist, "The file \"#{file}\" does not exist.\n" +# + +Then %r{^the "(.*)" file should not exist$} do |file| + expect(Pathname.new(file)).to_not exist end -Then /^the "(.*)" file should not exist$/ do |file| - assert !File.exist?(file), "The file \"#{file}\" exists" +# + +Then %r{^I should see today's time in "(.*)"$} do |file| + seconds = seconds_agnostic_time(Time.now) + expect(file_contents(file)).to match Regexp.new(seconds) end -Then /^I should see today's time in "(.*)"$/ do |file| - assert_match Regexp.new(seconds_agnostic_time(Time.now)), file_contents(file) +# + +Then %r{^I should see today's date in "(.*)"$} do |file| + regexp = Regexp.new(Date.today.to_s) + expect(file_contents(file)).to match regexp end -Then /^I should see today's date in "(.*)"$/ do |file| - assert_match Regexp.new(Date.today.to_s), file_contents(file) +# + +Then %r{^I should see "(.*)" in the build output$} do |text| + regexp = Regexp.new(text) + expect(jekyll_run_output).to match regexp end -Then /^I should see "(.*)" in the build output$/ do |text| - assert_match Regexp.new(text), jekyll_run_output -end +# -Then /^I should get a non-zero exit(?:\-| )status$/ do - assert jekyll_run_status > 0 +Then %r{^I should get a non-zero exit(?:\-| )status$} do + expect(jekyll_run_status.to_i).to be > 0 end diff --git a/features/support/env.rb b/features/support/env.rb index 76a3e707..62892612 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -1,116 +1,155 @@ -require 'fileutils' -require 'posix-spawn' -require 'minitest/spec' -require 'time' +require "fileutils" +require "jekyll/utils" +require "open3" +require "time" -class MinitestWorld - extend Minitest::Assertions - attr_accessor :assertions - - def initialize - self.assertions = 0 - end +class Paths + SOURCE_DIR = Pathname.new(File.expand_path("../..", __dir__)) + def self.test_dir; source_dir.join("tmp", "jekyll"); end + def self.output_file; test_dir.join("jekyll_output.txt"); end + def self.status_file; test_dir.join("jekyll_status.txt"); end + def self.jekyll_bin; source_dir.join("bin", "jekyll"); end + def self.source_dir; SOURCE_DIR; end end -JEKYLL_SOURCE_DIR = File.dirname(File.dirname(File.dirname(__FILE__))) -TEST_DIR = File.expand_path(File.join('..', '..', 'tmp', 'jekyll'), File.dirname(__FILE__)) -JEKYLL_PATH = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'bin', 'jekyll')) -JEKYLL_COMMAND_OUTPUT_FILE = File.join(File.dirname(TEST_DIR), 'jekyll_output.txt') -JEKYLL_COMMAND_STATUS_FILE = File.join(File.dirname(TEST_DIR), 'jekyll_status.txt') +# + +def file_content_from_hash(input_hash) + matter_hash = input_hash.reject { |k, v| k == "content" } + matter = matter_hash.map do |k, v| "#{k}: #{v}\n" + end + + matter = matter.join.chomp + content = \ + if !input_hash['input'] || !input_hash['filter'] + then input_hash['content'] + else "{{ #{input_hash['input']} | " \ + "#{input_hash['filter']} }}" + end + + Jekyll::Utils.strip_heredoc(<<-EOF) + --- + #{matter.gsub( + /\n/, "\n " + )} + --- + #{content} + EOF +end + +# def source_dir(*files) - File.join(TEST_DIR, *files) + return Paths.test_dir(*files) end +# + def all_steps_to_path(path) - source = Pathname.new(source_dir('_site')).expand_path - dest = Pathname.new(path).expand_path + source = source_dir + dest = Pathname.new(path).expand_path paths = [] + dest.ascend do |f| - break if f.eql? source + break if f == source paths.unshift f.to_s end + paths end -def jekyll_output_file - JEKYLL_COMMAND_OUTPUT_FILE -end - -def jekyll_status_file - JEKYLL_COMMAND_STATUS_FILE -end +# def jekyll_run_output - File.read(jekyll_output_file) if File.file?(jekyll_output_file) + if Paths.output_file.file? + then return Paths.output_file.read + end end +# + def jekyll_run_status - (File.read(jekyll_status_file) rescue 0).to_i + if Paths.status_file.file? + then return Paths.status_file.read + end end +# + def run_bundle(args) - run_in_shell('bundle', *args.strip.split(' ')) + run_in_shell("bundle", *args.strip.split(' ')) end +# + def run_jekyll(args) - child = run_in_shell(JEKYLL_PATH, *args.strip.split(' '), "--trace") - child.status.exitstatus == 0 + args = args.strip.split(" ") # Shellwords? + process = run_in_shell(Paths.jekyll_bin.to_s, *args, "--trace") + process.exitstatus == 0 end -# ----------------------------------------------------------------------------- -# XXX: POSIX::Spawn::Child does not write output when the exit status is > 0 -# for example when doing [:out, :err] => [file, "w"] it will skip -# writing the file entirely, we sould switch to Open. -# ----------------------------------------------------------------------------- +# def run_in_shell(*args) - spawned = POSIX::Spawn::Child.new(*args) - status = spawned.status.exitstatus - File.write(JEKYLL_COMMAND_STATUS_FILE, status) - File.open(JEKYLL_COMMAND_OUTPUT_FILE, "w+") do |file| - status == 0 ? file.write(spawned.out) : file.write(spawned.err) + i, o, e, p = Open3.popen3(*args) + out = o.read.strip + err = e.read.strip + + [i, o, e].each do |m| + m.close end - spawned + File.write(Paths.status_file, p.value.exitstatus) + File.write(Paths.output_file, out) if p.value.exitstatus == 0 + File.write(Paths.output_file, err) if p.value.exitstatus != 0 + p.value end -def slug(title) - if title - title.downcase.gsub(/[^\w]/, " ").strip.gsub(/\s+/, '-') - else - Time.now.strftime("%s%9N") # nanoseconds since the Epoch +# + +def slug(title = nil) + if !title + then Time.now.strftime("%s%9N") # nanoseconds since the Epoch + else title.downcase.gsub(/[^\w]/, " ").strip.gsub(/\s+/, '-') end end +# + def location(folder, direction) if folder - before = folder if direction == "in" - after = folder if direction == "under" + before = folder if direction == "in" + after = folder if direction == "under" end - [before || '.', after || '.'] + + [before || '.', + after || '.'] end +# + def file_contents(path) - File.open(path) do |file| - file.readlines.join # avoid differences with \n and \r\n line endings - end + return Pathname.new(path).read end +# + def seconds_agnostic_datetime(datetime = Time.now) date, time, zone = datetime.to_s.split(" ") time = seconds_agnostic_time(time) + [ Regexp.escape(date), "#{time}:\\d{2}", Regexp.escape(zone) - ].join("\\ ") + ] \ + .join("\\ ") end +# + def seconds_agnostic_time(time) - if time.is_a? Time - time = time.strftime("%H:%M:%S") - end + time = time.strftime("%H:%M:%S") if time.is_a?(Time) hour, minutes, _ = time.split(":") "#{hour}:#{minutes}" end