diff --git a/.gitignore b/.gitignore index 647d3ed..fd8726b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,14 @@ pkg .conductor .mtn-ignore *.sw? +rspec-steps-*.gem +.bundle +corundum/ +.sass-cache/ +.yardoc/ +gh-pages/ +tmp/ +spec/examples.txt +.cadre/ +errors.err +spec_help/cadre.rb diff --git a/.rspec b/.rspec index 7dffdab..86bb78c 100644 --- a/.rspec +++ b/.rspec @@ -1,5 +1,6 @@ ---format documentation --I /home/judson/ruby/gems/rspec-steps/spec_help/interpose --I /home/judson/ruby/gems/rspec-steps/lib --I /home/judson/ruby/gems/rspec-steps/spec_help +-I ./spec_help/interpose +-I ./lib +-I ./spec_help --require spec_helper +--pattern *.rb +--color diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..cd5ac03 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.0 diff --git a/.simplecov b/.simplecov new file mode 100644 index 0000000..9a46f70 --- /dev/null +++ b/.simplecov @@ -0,0 +1,13 @@ +require 'simplecov-json' +require 'cadre/simplecov' + +SimpleCov.start do + coverage_dir "corundum/docs/coverage" + add_filter "./spec" + add_filter "vendor/bundle" + formatter SimpleCov::Formatter::MultiFormatter[ + SimpleCov::Formatter::HTMLFormatter, + SimpleCov::Formatter::JSONFormatter, + Cadre::SimpleCov::VimFormatter + ] +end diff --git a/.travis-support/cached-bundle b/.travis-support/cached-bundle new file mode 100755 index 0000000..01999b3 --- /dev/null +++ b/.travis-support/cached-bundle @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# Usage: cached-bundle install --deployment +# +# After running `bundle`, caches the `vendor/bundle` directory to S3. +# On the next run, restores the cached directory before running `bundle`. +# When `Gemfile.lock` changes, the cache gets rebuilt. +# +# Requirements: +# - Gemfile.lock +# - TRAVIS_REPO_SLUG +# - TRAVIS_RUBY_VERSION +# - AMAZON_S3_BUCKET +# - script/s3-put +# - bundle +# - curl +# +# Author: Mislav Marohnić + +set -e + +compute_md5() { + local output="$(openssl md5)" + echo "${output##* }" +} + +download() { + curl --tcp-nodelay -qfL "$1" -o "$2" +} + +bundle_path=${CUSTOM_BUNDLE_PATH:-vendor/bundle} +gemfile_hash="$(compute_md5 <"${BUNDLE_GEMFILE:-Gemfile}.lock")" +cache_name="${TRAVIS_RUBY_VERSION}-${gemfile_hash}.tgz" +fetch_url="http://${AMAZON_S3_BUCKET}.s3.amazonaws.com/${TRAVIS_REPO_SLUG}/${cache_name}" + +if download "$fetch_url" "$cache_name"; then + echo "Reusing cached bundle ${cache_name}" + ls -l $cache_name + tar xzf "$cache_name" +fi + +bundle "$@" + +if [ ! -f "$cache_name" ]; then + echo "Caching \`${bundle_path}' to S3" + tar czf "$cache_name" "$bundle_path" + echo "Putting $cache_name to ${AMAZON_S3_BUCKET}:${TRAVIS_REPO_SLUG}/${cache_name}" + .travis-support/s3-put "$cache_name" "${AMAZON_S3_BUCKET}:${TRAVIS_REPO_SLUG}/${cache_name}" +fi diff --git a/.travis-support/s3-put b/.travis-support/s3-put new file mode 100755 index 0000000..77b1cf5 --- /dev/null +++ b/.travis-support/s3-put @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# Usage: s3-put [:] [] +# +# Uploads a file to the Amazon S3 service. +# Outputs the URL for the newly uploaded file. +# +# Requirements: +# - AMAZON_ACCESS_KEY_ID +# - AMAZON_SECRET_ACCESS_KEY +# - openssl +# - curl +# +# Author: Mislav Marohnić + +set -e + +authorization() { + local signature="$(string_to_sign | hmac_sha1 | base64)" + echo "AWS ${AMAZON_ACCESS_KEY_ID?}:${signature}" +} + +hmac_sha1() { + openssl dgst -binary -sha1 -hmac "${AMAZON_SECRET_ACCESS_KEY?}" +} + +base64() { + openssl enc -base64 +} + +bin_md5() { + openssl dgst -binary -md5 +} + +string_to_sign() { + echo "$http_method" + echo "$content_md5" + echo "$content_type" + echo "$date" + printf "/$bucket/$remote_path" +} + +date_string() { + LC_TIME=C date "+%a, %d %h %Y %T %z" +} + +file="$1" +bucket="${2%%:*}" +remote_path="${2#*:}" +content_type="${3:-binary/octet-stream}" + +if [ -z "$remote_path" ] || [ "$remote_path" = "$bucket" ]; then + remote_path="${file##*/}" +fi + +http_method=PUT +acl="public-read" +content_md5="$(bin_md5 < "$file" | base64)" +date="$(date_string)" + +url="https://$bucket.s3.amazonaws.com/$remote_path" + +echo "Uploading $file to S3 bucket $bucket/$remote_path with ${AMAZON_ACCESS_KEY_ID}" + +curl -qSf -T "$file" \ + -H "Authorization: $(authorization)" \ + -H "Date: $date" \ + -H "Content-MD5: $content_md5" \ + -H "Content-Type: $content_type" \ + "$url" + +echo "$url" diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..9b69b63 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,37 @@ +sudo: false +language: ruby +script: bundle exec rake ci +matrix: + include: + - gemfile: gemfiles/3.0 + rvm: 1.9.3 + - gemfile: gemfiles/3.1 + rvm: 1.9.3 + - gemfile: gemfiles/3.2 + rvm: 1.9.3 + - gemfile: gemfiles/3.3 + rvm: 1.9.3 + + + - gemfile: gemfiles/3.0 + rvm: 2.0.0 + - gemfile: gemfiles/3.1 + rvm: 2.0.0 + - gemfile: gemfiles/3.2 + rvm: 2.0.0 + - gemfile: gemfiles/3.3 + rvm: 2.0.0 + + + - gemfile: gemfiles/3.0 + rvm: 2.1.2 + - gemfile: gemfiles/3.1 + rvm: 2.1.2 + - gemfile: gemfiles/3.2 + rvm: 2.1.2 + - gemfile: gemfiles/3.3 + rvm: 2.1.2 + +notifications: + flowdock: + secure: "iri0YRV17TZXSZ4IDGHd1e1k52u/Kz9EAqP4oGO1wI1OkIvD5t+2RvkSRoRNpQ080kzkjrlIQwpMaLMgUC9Y8TZ11JEq+uujmotRwOJIFtyAIbBwh3enQlOyPRU9kzdlmBHYtd7nLA92dd0PGfhoti2RkqUtqzgWAlZjqg/52zs=" diff --git a/Gemfile b/Gemfile index 86f571e..063e86f 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,7 @@ -source "http://judson:hEi4lOra@gems.lrdesign.com" -source "http://gemcutter.org" -source "http://gems.github.com" +source "https://rubygems.org" + +gem 'fuubar' +gem 'rspec', "~> 3.0.0" +gem 'cadre' gemspec diff --git a/Gemfile.lock b/Gemfile.lock index 267b8fa..0a19425 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,45 +1,196 @@ PATH remote: . specs: - rspec-steps (0.0.3) - rspec (>= 2.6) + rspec-steps (2.0.0) + rspec (>= 3.0, < 3.99) GEM - remote: http://judson:hEi4lOra@gems.lrdesign.com/ - remote: http://gemcutter.org/ - remote: http://gems.github.com/ + remote: https://rubygems.org/ specs: - diff-lcs (1.1.2) - haml (2.2.24) - hanna (0.1.12) - haml (~> 2.2.8) - rake (~> 0.8.2) - rdoc (~> 2.3.0) - mailfactory (1.4.0) - mime-types (>= 1.13.1) - mime-types (1.16) - rake (0.8.7) - rake-rubygems (0.2.0) - rake (>= 0.8.7) - rcov (0.9.9) - rdoc (2.3.0) - rspec (2.6.0) - rspec-core (~> 2.6.0) - rspec-expectations (~> 2.6.0) - rspec-mocks (~> 2.6.0) - rspec-core (2.6.4) - rspec-expectations (2.6.0) - diff-lcs (~> 1.1.2) - rspec-mocks (2.6.0) + abstract_type (0.0.7) + activesupport (4.2.4) + i18n (~> 0.7) + json (~> 1.7, >= 1.7.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + adamantium (0.2.0) + ice_nine (~> 0.11.0) + memoizable (~> 0.4.0) + addressable (2.3.8) + arrayfields (4.9.2) + ast (2.1.0) + cadre (1.0.1) + thor (>= 0.14, < 1.0) + tilt (> 1.0) + valise (~> 1.1.2) + calibrate (0.0.1) + caliph (0.3.1) + cane (2.6.2) + parallel + chronic (0.10.2) + churn (0.0.35) + chronic (>= 0.2.3) + hirb + json_pure + main + rest-client (>= 1.6.0) + ruby_parser (~> 3.0) + sexp_processor (~> 4.1) + code_analyzer (0.4.5) + sexp_processor + code_metrics (0.1.3) + coderay (1.1.0) + colored (1.2) + concord (0.1.5) + adamantium (~> 0.2.0) + equalizer (~> 0.0.9) + corundum (0.6.0) + bundler + caliph (~> 0.3) + mattock (~> 0.9) + paint (~> 0.8.7) + rspec (>= 2.0) + simplecov (>= 0.5.4) + simplecov-json (>= 0.2) + diff-lcs (1.2.5) + docile (1.1.5) + domain_name (0.5.24) + unf (>= 0.0.5, < 1.0.0) + equalizer (0.0.11) + erubis (2.7.0) + fattr (2.2.2) + flay (2.6.1) + ruby_parser (~> 3.0) + sexp_processor (~> 4.0) + flog (4.3.2) + ruby_parser (~> 3.1, > 3.1.0) + sexp_processor (~> 4.4) + fuubar (2.0.0) + rspec (~> 3.0) + ruby-progressbar (~> 1.4) + hirb (0.7.3) + http-cookie (1.0.2) + domain_name (~> 0.5) + i18n (0.7.0) + ice_nine (0.11.1) + json (1.8.3) + json_pure (1.8.2) + launchy (2.4.3) + addressable (~> 2.3) + main (6.1.0) + arrayfields (>= 4.7.4) + chronic (>= 0.6.2) + fattr (>= 2.2.0) + map (>= 5.1.0) + map (6.5.5) + mattock (0.10.0) + calibrate (~> 0.0.1) + caliph (~> 0.3.1) + rake (~> 10.0) + tilt (> 0) + valise (~> 1.1.1) + memoizable (0.4.2) + thread_safe (~> 0.3, >= 0.3.1) + metric_fu (4.11.4) + cane (~> 2.5, >= 2.5.2) + churn (~> 0.0.35) + code_metrics (~> 0.1) + coderay + flay (~> 2.1, >= 2.0.1) + flog (~> 4.1, >= 4.1.1) + launchy (~> 2.0) + metric_fu-Saikuro (~> 1.1, >= 1.1.3) + multi_json + rails_best_practices (~> 1.14, >= 1.14.3) + redcard + reek (~> 1.3, >= 1.3.4) + roodi (~> 3.1) + metric_fu-Saikuro (1.1.3) + mime-types (2.6.2) + minitest (5.8.0) + multi_json (1.11.2) + netrc (0.10.3) + paint (0.8.7) + parallel (1.6.1) + parser (2.2.2.6) + ast (>= 1.1, < 3.0) + procto (0.0.2) + rails_best_practices (1.15.7) + activesupport + code_analyzer (>= 0.4.3) + colored + erubis + i18n + json + require_all + ruby-progressbar + rainbow (2.0.0) + rake (10.4.2) + redcard (1.1.0) + reek (1.6.6) + parser (~> 2.2.0.pre.7) + rainbow (>= 1.99, < 3.0) + unparser (~> 0.2.2) + require_all (1.3.2) + rest-client (1.8.0) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 3.0) + netrc (~> 0.7) + roodi (3.3.1) + ruby_parser (~> 3.2, >= 3.2.2) + rspec (3.0.0) + rspec-core (~> 3.0.0) + rspec-expectations (~> 3.0.0) + rspec-mocks (~> 3.0.0) + rspec-core (3.0.4) + rspec-support (~> 3.0.0) + rspec-expectations (3.0.4) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.0.0) + rspec-mocks (3.0.4) + rspec-support (~> 3.0.0) + rspec-support (3.0.4) + ruby-progressbar (1.7.5) + ruby_parser (3.7.1) + sexp_processor (~> 4.1) + sexp_processor (4.6.0) + simplecov (0.10.0) + docile (~> 1.1.0) + json (~> 1.8) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.0) + simplecov-json (0.2) + json + simplecov + thor (0.19.1) + thread_safe (0.3.5) + tilt (2.0.1) + tzinfo (1.2.2) + thread_safe (~> 0.1) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.1) + unparser (0.2.4) + abstract_type (~> 0.0.7) + adamantium (~> 0.2.0) + concord (~> 0.1.5) + diff-lcs (~> 1.2.5) + equalizer (~> 0.0.9) + parser (~> 2.2.2) + procto (~> 0.0.2) + valise (1.1.4) PLATFORMS ruby DEPENDENCIES - bundler (~> 1.0.0) - hanna (~> 0.1.0) - mailfactory (~> 1.4.0) - rake-rubygems (>= 0.2.0) - rcov - rspec (>= 2.0) + cadre + corundum (>= 0.4.0) + fuubar + metric_fu (~> 4.11.1) + rspec (~> 3.0.0) rspec-steps! + +BUNDLED WITH + 1.10.6 diff --git a/README b/README.md similarity index 100% rename from README rename to README.md diff --git a/Rakefile b/Rakefile index 8b00218..f5f67d1 100644 --- a/Rakefile +++ b/Rakefile @@ -1,347 +1,45 @@ -require 'rubygems' -require 'rubygems/installer' -require 'rake/gempackagetask' -require 'rake/rubygems' -require 'hanna/rdoctask' -require 'rspec/core/rake_task' -require 'mailfactory' -require 'net/smtp' +require 'corundum/tasklibs' -begin - speclist = Dir[File.expand_path(__FILE__ +'/../*.gemspec')] - if speclist.length == 0 - puts "Found no *.gemspec files" - exit 1 - else if speclist.length > 1 - puts "Found too many *.gemspec files: #{speclist.inspect}" - exit 1 - end - - spec = Gem::Specification::load(speclist[0]) - RakeConfig = { - :gemspec => spec, - :gemspec_path => speclist[0], - :package_dir => "pkg", - :rcov_threshold => 80, - :email => { - :servers => [ { - :server => "ruby-lang.org", - :helo => "gmail.com" - } ], - :announce_to_email => "ruby-talk@ruby-lang.org", - }, - :files => { - :code => spec.files.grep(%r{^lib/}), - :test => spec.files.grep(%r{^spec/}), - :docs => spec.files.grep(%r{^doc/}) - }, - :rubyforge => { - :group_id => spec.rubyforge_project, - :package_id => spec.name.downcase, - :release_name => spec.full_name, - :home_page => spec.homepage, - :project_page => "http://rubyforge.org/project/#{spec.rubyforge_project}/" - } - } -end - -directory "doc" -directory RakeConfig[:package_dir] - -class SpecTask < RSpec::Core::RakeTask - def initialize(name=:spec) - super(name) do - @ruby_opts = [] - @rspec_opts= %w{-f d --out last_run --color} - @rcov_opts = %w{--exclude ^rcov/,[^/]*\.gemspec,^spec/,^spec_help/ --sort coverage --threshold 101 -o doc/coverage --xrefs --no-color} - @rcov = true - @warning = false #bundler raises lots of warnings :/ - @failure_message = "Spec examples failed." - @files_to_run = FileList['spec/**/*.rb'] - yield(self) if block_given? - end - task name => ".rspec" - end - - attr_accessor :files_to_run - - def spec_command - @spec_command ||= - begin - cmd_parts = [*ruby_opts] - cmd_parts << "-w" if warning? - cmd_parts << "-S" - cmd_parts << "bundle exec" if gemfile? unless skip_bundler - - if rcov - cmd_parts += [*rcov_path] - cmd_parts += ["-Ispec_help#{File::PATH_SEPARATOR}spec#{File::PATH_SEPARATOR}lib", *rcov_opts] - cmd_parts += ["spec_help/spec_helper.rb", *files_to_run ] - unless @rspec_opts.nil? or @rspec_opts.empty? - cmd_parts << "--" - cmd_parts += [*@rspec_opts] - end - else - cmd_parts += [*rspec_path] - cmd_parts += [*@rspec_opts] - cmd_parts += [*files_to_run] - end - - - cmd_parts.compact.join(" ").tap{|o| p o} - end - end -end - -task :needs_root do - unless Process::uid == 0 - fail "This task must be run as root" - - exit { - unless (user = ENV['SUDO_USER']).nil? - FileUtils::chown_R(user, ENV['SUDO_GID'].to_i, 'doc/coverage') - end - } - end -end - -desc "Run failing examples if any exist, otherwise, run the whole suite" -task :rspec => "rspec:quick" - -namespace :rspec do - file "doc/coverage/index.html" => FileList['spec/**/*.rb', 'lib/**/*.rb'] do - Rake::Task['rspec:doc'].invoke - end - - desc "Generate default .rspec file" - file ".rspec" => ["Rakefile", RakeConfig[:gemspec_path]] do |t| - options = [ - "--format documentation", - "--out last_run", - ] - [%w{spec_help interpose}, %w{lib}, %w{spec_help}].map do|dir| - options << "-I #{ File::join(File::dirname(__FILE__), *dir)}" - end - options << "--require spec_helper" - File.open(t.name, "w") do |rspec| - rspec.write(options.join("\n")) - end - end +module Corundum + register_project(__FILE__) - desc "Always run every spec" - SpecTask.new(:all) - - desc "Generate specifications documentation" - SpecTask.new(:doc) do |t| - t.rspec_opts = %w{-f s -o doc/Specifications} - t.failure_message = "Failed generating specification docs" - t.verbose = false + tk = Toolkit.new do |tk| + tk.file_lists.project = [__FILE__] + tk.file_lists.test << FileList["spec/**/*.rb"] end - desc "Run specs with Ruby profiling" - SpecTask.new(:profile) do |t| - t.ruby_opts += %w{-rprofile} - end - - desc "Run only failing examples" - SpecTask.new(:quick) do |t| - t.rspec_opts += %w{-f d --color} - examples = [] - begin - File.open("last_run", "r") do |fail_list| - fail_list.lines.grep(%r{^\s*\d+\)\s*(.*)}) do |line| - examples << $1.gsub(/'/){"[']"} - end + tk.in_namespace do + GemspecFiles.new(tk) + %w{debug profanity racism ableism sexism issues}.each do |type| + QuestionableContent.new(tk) do |qc| + qc.type = type end - rescue - end - unless examples.empty? - t.rspec_opts << "--example" - t.rspec_opts << "\"#{examples.join("|")}\"" end - t.rcov = false - t.failure_message = "Spec examples failed." - end - - desc "Run rspecs prior to a package publication" - SpecTask.new(:check) do |t| - t.rspec_opts = %w{--format p --out /dev/null} - t.failure_message = "Package does not conform to spec" - t.verbose = false - end - - desc "Open chromium to view RCov output" - task :view_coverage => "doc/coverage/index.html" do |t| - sh "/usr/bin/chromium doc/coverage/index.html" - end -end - -namespace :qa do - desc "Confirm code quality - e.g. before shipping" - task :sign_off => %w{verify_rcov compare:coverage_and_manifest} - desc "Confirm a minimum code coverage" - task :verify_rcov => "doc/coverage/index.html" do - require 'nokogiri' - - doc = Nokogiri::parse(File::read('doc/coverage/index.html')) - percentage = doc.xpath("//tt[@class='coverage_total']").first.content.to_f - raise "Coverage must be at least #{RakeConfig[:rcov_threshold]} but was #{percentage}" if percentage < RakeConfig[:rcov_threshold] - puts "Coverage is #{percentage}% (required: #{RakeConfig[:rcov_threshold]}%)" - end - - namespace :compare do - desc "Ensure that all code files being shipped are covered" - task :coverage_and_manifest => "doc/coverage/index.html" do - require 'nokogiri' - - doc = Nokogiri::parse(File::read('doc/coverage/index.html')) - covered_files = [] - doc.xpath("//table[@id='report_table']//td//a").each do |link| - covered_files << link.content - end - not_listed = covered_files - RakeConfig[:files][:code] - not_covered = RakeConfig[:files][:code] - covered_files - unless not_listed.empty? and not_covered.empty? - raise ["Covered files and gemspec manifest don't match:", - "Not in gemspec: #{not_listed.inspect}", - "Not covered: #{not_covered.inspect}"].join("\n") - end + rspec = RSpec.new(tk) do |rspec| + rspec.files_to_run = "spec" end - end -end - -Rake::Gemcutter::Tasks.new(RakeConfig[:gemspec]) -namespace :gem do - task :push => %w{qa:sign_off package} - task :install => [:needs_root, 'qa:sign_off'] - task :reinstall => [:needs_root, 'qa:sign_off'] - - package = Rake::GemPackageTask.new(RakeConfig[:gemspec]) {|t| - t.need_tar_gz = true - t.need_tar_bz2 = true - } - task(:package).prerequisites.each do |package_type| - file package_type => "rspec:check" - end - - Rake::RDocTask.new(:docs) do |rd| - rd.options += RakeConfig[:gemspec].rdoc_options - rd.rdoc_dir = 'rubydoc' - rd.rdoc_files.include(RakeConfig[:files][:code]) - rd.rdoc_files.include(RakeConfig[:files][:docs]) - rd.rdoc_files += (RakeConfig[:gemspec].extra_rdoc_files) - end - task :docs => ['rspec:doc'] -end - -task :gem => "gem:gem" - -desc "Publish the gem and its documentation to Rubyforge and Gemcutter" -task :publish => ['publish:docs', 'publish:rubyforge', 'gem:push'] - -namespace :publish do - desc 'Publish RDoc to RubyForge' - task :docs => 'gem:docs' do - config = YAML.load(File.read(File.expand_path("~/.rubyforge/user-config.yml"))) - host = "#{config["username"]}@rubyforge.org" - remote_dir = "/var/www/gforge-projects/#{RakeConfig[:rubyforge][:group_id]}" - local_dir = 'rubydoc' - sh %{rsync -av --delete #{local_dir}/ #{host}:#{remote_dir}} - end - - task :scrape_rubyforge do - require 'rubyforge' - forge = RubyForge.new - forge.configure - forge.scrape_project(RakeConfig[:rubyforge][:package_id]) - end - - desc "Publishes to RubyForge" - task :rubyforge => ['qa:sign_off', 'gem:package', :docs, :scrape_rubyforge] do |t| - require 'rubyforge' - forge = RubyForge.new - forge.configure - files = [".gem", ".tar.gz", ".tar.bz2"].map do |extension| - File::join(RakeConfig[:package_dir], RakeConfig[:gemspec].full_name) + extension + SimpleCov.new(tk, rspec) do |cov| + cov.threshold = 93 end - release = forge.lookup("release", RakeConfig[:rubyforge][:package_id])[RakeConfig[:rubyforge][:release_name]] rescue nil - if release.nil? - forge.add_release(RakeConfig[:rubyforge][:group_id], RakeConfig[:rubyforge][:package_id], RakeConfig[:rubyforge][:release_name], *files) - else - files.each do |file| - forge.add_file(RakeConfig[:rubyforge][:group_id], RakeConfig[:rubyforge][:package_id], RakeConfig[:rubyforge][:release_name], file) - end + gem = GemBuilding.new(tk) + GemCutter.new(tk,gem) + Git.new(tk) do |vc| + vc.branch = "master" end end end -def announcement - changes = "" - begin - File::open("./Changelog", "r") do |changelog| - changes = "Changes:\n\n" - changes += changelog.read +Dir['gemfiles/*'].delete_if{|path| path =~ /lock\z/ }.each do |gemfile| + gemfile_lock = gemfile + ".lock" + file gemfile_lock => [gemfile, "rspec-steps.gemspec"] do + Bundler.with_clean_env do + sh "bundle install --gemfile #{gemfile}" end - rescue Exception end - urls = "Project: #{RakeConfig[:rubyforge][:project_page]}\n" + - "Homepage: #{RakeConfig[:rubyforge][:home_page]}" - - subject = "#{RakeConfig[:gemspec].name} #{RakeConfig[:gemspec].version} Released" - title = "#{RakeConfig[:gemspec].name} version #{RakeConfig[:gemspec].version} has been released!" - body = "#{RakeConfig[:gemspec].description}\n\n#{changes}\n\n#{urls}" - - return subject, title, body + desc "Update all the bundler lockfiles for Travis" + task :travis_gemfiles => gemfile_lock end -desc 'Announce release on RubyForge and email' -task :press => ['press:rubyforge', 'press:email'] -namespace :press do - desc 'Post announcement to rubyforge.' - task :rubyforge do - require 'rubyforge' - subject, title, body = announcement - - forge = RubyForge.new - forge.configure - forge.post_news(RakeConfig[:rubyforge][:group_id], subject, "#{title}\n\n#{body}") - puts "Posted to rubyforge" - end - - file "email.txt" do |t| - subject, title, body= announcement - - mail = MailFactory.new - mail.To = RakeConfig[:announce_to_email] - mail.From = RakeConfig[:gemspec].email - mail.Subject = "[ANN] " + subject - mail.text = [title, body].join("\n\n") - - File.open(t.name, "w") do |mailfile| - mailfile.write mail.to_s - end - end - - desc 'Generate email announcement file.' - task :email => "email.txt" do - require 'rubyforge' - - RakeConfig[:email_servers].each do |server_config| - begin - File::open("email.txt", "r") do |email| - Net::SMTP.start(server_config[:server], 25, server_config[:helo], server_config[:username], server_config[:password]) do |smtp| - smtp.data do |mta| - mta.write(email.read) - end - end - end - break - rescue Object => ex - puts ex.message - end - end - end -end -end +task :default => [:release, :publish_docs] diff --git a/doc/README b/doc/README index e4a34b9..0287826 100644 --- a/doc/README +++ b/doc/README @@ -1,30 +1,132 @@ -== RSpec Steps -=== ( or: why would I want to relearn how to write specs? ) +# RSpec Steps +## ( or: why would I want to relearn how to write specs? ) -RSpec Steps allows you to chain examples into a series of steps without having -to go the whole 9 yards over to Cucumber. It's often incredibly useful to be -able to aseemble a series of tests that should all pass, but where completely -isolating them is less than sensible. +RSpec Steps allows you to chain examples into a series of steps that run +in sequence and which stop when a step fails. It's often incredibly +useful to be able to aseemble a series of tests that should all pass, +but where completely isolating them is less than sensible. -One excellent example is web site integration tests. With RSpec steps you can +( RSpec Steps has gone on with the tide of progress - it only supports RSpec +3.x. If you need Rspec 2 suport, check out two-step: +https://github.com/LRDesign/two-step ) + +One excellent example is web site integration tests. With RSpec steps you can do: - steps "Add a user" do - it do - visit root - page.should have_text "Login" - end - - it do - fill_in :name, "Johnny User" - click "Login" - page.should have_text "Welcome, Johnny!" - end - - ... +```ruby +RSpec::Steps.steps "Login and change password" do + it "should show the login form" do + visit root + page.should have_text "Login" + end + + it "should successfully log in" do + fill_in :name, "Johnny User" + click "Login" + page.should have_text "Welcome, Johnny!" + end + + it "should load the password change form" do + click "My Settings" + click "Update Password" + page.should have_selector("form#update_password") + end + + it "should change the user's password successfully" do + fill_in :password, "foobar" + fill_in :password_confirmation, "foobar" + click "Change Password" + page.should have_text "Password changed successfully!" + User.find_by_name("Johnny User").valid_password?("foobar").should be_true end +end +``` + +The examples above will be run in order. State is preserved between examples +inside a "steps" block: any DB transactions will not roll back until the entire +sequence has been complete. + +If any example inside the "steps" block fails, all remaining steps will be marked +pending and therefore skipped. + +## Rationale + +RSpec's philosophy is that all examples should be completely independent. This +is a great philosophy for most purposes, and we recommend you stick to it in +almost all cases. BUT, that complete separation of examples really sucks when +you're trying to write long stories involving many requests. You are usually +stuck with three choices: + +1. Write a sequence of examples, each of which repeats the behavior of all + previous examples. Downside: horrendously inefficient. +2. Write a single huge example which performs the entire story. Downside: only + one description, no independent reporting of the steps of the story. +3. Use Cucumber. Downside: We agree totally with this guy: http://bit.ly/dmXqnY + +RSpec-steps intentionally breaks RSpec's "independent" philosophy to let us get the +only thing we really want from Cucumber - the ability to execute some examples in sequence, +and skip subsequent steps after a failure. + +## Caveats and cautions + +Don't call "describe" inside of "steps". As of 2.0, this is an error. + +As of 2.0, Steps no longer automatically adds its DSL to the top level. If you +want that behavior (especially if you're updating an older project) add: + +``` +require 'rspec-steps/monkeypatching' +``` +to e.g. `spec_helper.rb.` + +If you're using RSpec-Steps with Rails (for instance, with Capybara), you will +absolutely need to make sure you have transactional fixtures off. Otherwise, +you'll experience problems where the tests and the application appear to see +completely different databases. + +While Steps 2.0 retains it's shift in lifecycle hooks (:each become :all, +there's a :step hook), this shift *no longer* applies to config.before _et al_ +-- you'll need to use config.before :each to run around each step. This is the +primary change to the Steps interface that called for a major version bump. + +## Advanced stuff: shared steps + +If you have (for example) two user stories that share the same first N steps but then +diverge, you can DRY your code out with shared_steps blocks, like so: + + shared_steps "For a logged-in user" do + it "should have login form" + visit root + page.should have_selector "form#login" + end + + it "should log the user in" do + fill_in :name, "Johnny User" + page.should have_text "Welcome, Johnny!" + end + end + + steps "updating password" do + perform_steps "For a logged-in user" + + it "should update the password" do + ... + end + end + + steps "uploading a profile picture" do + perform_steps "For a logged-in user" + + it "should upload a picture" do + ... + end + end + +## Versions and Dependencies + +The goal is to try to be compatible with as many versions +of RSpec 3.x as possible. -Add dozens of steps to a set. They get run in order, and the state of the -tests isn't reset between them. Better still, if one step fails, the rest are -all marked "pending" so they don't even try to run, which helps speed up -testing. +We make good use of Travis to check compatibility, however. You can check what +versions of RSpec and Ruby RSpec-Steps works with here: +https://travis-ci.org/LRDesign/rspec-steps diff --git a/gemfiles/3.0 b/gemfiles/3.0 new file mode 100644 index 0000000..4c062c5 --- /dev/null +++ b/gemfiles/3.0 @@ -0,0 +1,6 @@ +source "https://rubygems.org" + +gem "rspec", "~> 3.0.0" +gem 'cadre' + +gemspec :path => ".." diff --git a/gemfiles/3.0.lock b/gemfiles/3.0.lock new file mode 100644 index 0000000..217da9b --- /dev/null +++ b/gemfiles/3.0.lock @@ -0,0 +1,192 @@ +PATH + remote: .. + specs: + rspec-steps (2.0.1) + rspec (>= 3.0, < 3.99) + +GEM + remote: https://rubygems.org/ + specs: + abstract_type (0.0.7) + activesupport (4.2.4) + i18n (~> 0.7) + json (~> 1.7, >= 1.7.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + adamantium (0.2.0) + ice_nine (~> 0.11.0) + memoizable (~> 0.4.0) + addressable (2.3.8) + arrayfields (4.9.2) + ast (2.1.0) + cadre (1.0.1) + thor (>= 0.14, < 1.0) + tilt (> 1.0) + valise (~> 1.1.2) + calibrate (0.0.1) + caliph (0.3.1) + cane (2.6.2) + parallel + chronic (0.10.2) + churn (0.0.35) + chronic (>= 0.2.3) + hirb + json_pure + main + rest-client (>= 1.6.0) + ruby_parser (~> 3.0) + sexp_processor (~> 4.1) + code_analyzer (0.4.5) + sexp_processor + code_metrics (0.1.3) + coderay (1.1.0) + colored (1.2) + concord (0.1.5) + adamantium (~> 0.2.0) + equalizer (~> 0.0.9) + corundum (0.6.0) + bundler + caliph (~> 0.3) + mattock (~> 0.9) + paint (~> 0.8.7) + rspec (>= 2.0) + simplecov (>= 0.5.4) + simplecov-json (>= 0.2) + diff-lcs (1.2.5) + docile (1.1.5) + domain_name (0.5.24) + unf (>= 0.0.5, < 1.0.0) + equalizer (0.0.11) + erubis (2.7.0) + fattr (2.2.2) + flay (2.6.1) + ruby_parser (~> 3.0) + sexp_processor (~> 4.0) + flog (4.3.2) + ruby_parser (~> 3.1, > 3.1.0) + sexp_processor (~> 4.4) + hirb (0.7.3) + http-cookie (1.0.2) + domain_name (~> 0.5) + i18n (0.7.0) + ice_nine (0.11.1) + json (1.8.3) + json_pure (1.8.2) + launchy (2.4.3) + addressable (~> 2.3) + main (6.1.0) + arrayfields (>= 4.7.4) + chronic (>= 0.6.2) + fattr (>= 2.2.0) + map (>= 5.1.0) + map (6.5.5) + mattock (0.10.0) + calibrate (~> 0.0.1) + caliph (~> 0.3.1) + rake (~> 10.0) + tilt (> 0) + valise (~> 1.1.1) + memoizable (0.4.2) + thread_safe (~> 0.3, >= 0.3.1) + metric_fu (4.11.4) + cane (~> 2.5, >= 2.5.2) + churn (~> 0.0.35) + code_metrics (~> 0.1) + coderay + flay (~> 2.1, >= 2.0.1) + flog (~> 4.1, >= 4.1.1) + launchy (~> 2.0) + metric_fu-Saikuro (~> 1.1, >= 1.1.3) + multi_json + rails_best_practices (~> 1.14, >= 1.14.3) + redcard + reek (~> 1.3, >= 1.3.4) + roodi (~> 3.1) + metric_fu-Saikuro (1.1.3) + mime-types (2.6.2) + minitest (5.8.0) + multi_json (1.11.2) + netrc (0.10.3) + paint (0.8.7) + parallel (1.6.1) + parser (2.2.2.6) + ast (>= 1.1, < 3.0) + procto (0.0.2) + rails_best_practices (1.15.7) + activesupport + code_analyzer (>= 0.4.3) + colored + erubis + i18n + json + require_all + ruby-progressbar + rainbow (2.0.0) + rake (10.4.2) + redcard (1.1.0) + reek (1.6.6) + parser (~> 2.2.0.pre.7) + rainbow (>= 1.99, < 3.0) + unparser (~> 0.2.2) + require_all (1.3.2) + rest-client (1.8.0) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 3.0) + netrc (~> 0.7) + roodi (3.3.1) + ruby_parser (~> 3.2, >= 3.2.2) + rspec (3.0.0) + rspec-core (~> 3.0.0) + rspec-expectations (~> 3.0.0) + rspec-mocks (~> 3.0.0) + rspec-core (3.0.4) + rspec-support (~> 3.0.0) + rspec-expectations (3.0.4) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.0.0) + rspec-mocks (3.0.4) + rspec-support (~> 3.0.0) + rspec-support (3.0.4) + ruby-progressbar (1.7.5) + ruby_parser (3.7.1) + sexp_processor (~> 4.1) + sexp_processor (4.6.0) + simplecov (0.10.0) + docile (~> 1.1.0) + json (~> 1.8) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.0) + simplecov-json (0.2) + json + simplecov + thor (0.19.1) + thread_safe (0.3.5) + tilt (2.0.1) + tzinfo (1.2.2) + thread_safe (~> 0.1) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.1) + unparser (0.2.4) + abstract_type (~> 0.0.7) + adamantium (~> 0.2.0) + concord (~> 0.1.5) + diff-lcs (~> 1.2.5) + equalizer (~> 0.0.9) + parser (~> 2.2.2) + procto (~> 0.0.2) + valise (1.1.4) + +PLATFORMS + ruby + +DEPENDENCIES + cadre + corundum (>= 0.4.0) + metric_fu (~> 4.11.1) + rspec (~> 3.0.0) + rspec-steps! + +BUNDLED WITH + 1.10.6 diff --git a/gemfiles/3.1 b/gemfiles/3.1 new file mode 100644 index 0000000..59408f1 --- /dev/null +++ b/gemfiles/3.1 @@ -0,0 +1,6 @@ +source "https://rubygems.org" + +gem "rspec", "~> 3.1.0" +gem 'cadre' + +gemspec :path => ".." diff --git a/gemfiles/3.1.lock b/gemfiles/3.1.lock new file mode 100644 index 0000000..2ac81ed --- /dev/null +++ b/gemfiles/3.1.lock @@ -0,0 +1,192 @@ +PATH + remote: .. + specs: + rspec-steps (2.0.1) + rspec (>= 3.0, < 3.99) + +GEM + remote: https://rubygems.org/ + specs: + abstract_type (0.0.7) + activesupport (4.2.4) + i18n (~> 0.7) + json (~> 1.7, >= 1.7.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + adamantium (0.2.0) + ice_nine (~> 0.11.0) + memoizable (~> 0.4.0) + addressable (2.3.8) + arrayfields (4.9.2) + ast (2.1.0) + cadre (1.0.1) + thor (>= 0.14, < 1.0) + tilt (> 1.0) + valise (~> 1.1.2) + calibrate (0.0.1) + caliph (0.3.1) + cane (2.6.2) + parallel + chronic (0.10.2) + churn (0.0.35) + chronic (>= 0.2.3) + hirb + json_pure + main + rest-client (>= 1.6.0) + ruby_parser (~> 3.0) + sexp_processor (~> 4.1) + code_analyzer (0.4.5) + sexp_processor + code_metrics (0.1.3) + coderay (1.1.0) + colored (1.2) + concord (0.1.5) + adamantium (~> 0.2.0) + equalizer (~> 0.0.9) + corundum (0.6.0) + bundler + caliph (~> 0.3) + mattock (~> 0.9) + paint (~> 0.8.7) + rspec (>= 2.0) + simplecov (>= 0.5.4) + simplecov-json (>= 0.2) + diff-lcs (1.2.5) + docile (1.1.5) + domain_name (0.5.24) + unf (>= 0.0.5, < 1.0.0) + equalizer (0.0.11) + erubis (2.7.0) + fattr (2.2.2) + flay (2.6.1) + ruby_parser (~> 3.0) + sexp_processor (~> 4.0) + flog (4.3.2) + ruby_parser (~> 3.1, > 3.1.0) + sexp_processor (~> 4.4) + hirb (0.7.3) + http-cookie (1.0.2) + domain_name (~> 0.5) + i18n (0.7.0) + ice_nine (0.11.1) + json (1.8.3) + json_pure (1.8.2) + launchy (2.4.3) + addressable (~> 2.3) + main (6.1.0) + arrayfields (>= 4.7.4) + chronic (>= 0.6.2) + fattr (>= 2.2.0) + map (>= 5.1.0) + map (6.5.5) + mattock (0.10.0) + calibrate (~> 0.0.1) + caliph (~> 0.3.1) + rake (~> 10.0) + tilt (> 0) + valise (~> 1.1.1) + memoizable (0.4.2) + thread_safe (~> 0.3, >= 0.3.1) + metric_fu (4.11.4) + cane (~> 2.5, >= 2.5.2) + churn (~> 0.0.35) + code_metrics (~> 0.1) + coderay + flay (~> 2.1, >= 2.0.1) + flog (~> 4.1, >= 4.1.1) + launchy (~> 2.0) + metric_fu-Saikuro (~> 1.1, >= 1.1.3) + multi_json + rails_best_practices (~> 1.14, >= 1.14.3) + redcard + reek (~> 1.3, >= 1.3.4) + roodi (~> 3.1) + metric_fu-Saikuro (1.1.3) + mime-types (2.6.2) + minitest (5.8.0) + multi_json (1.11.2) + netrc (0.10.3) + paint (0.8.7) + parallel (1.6.1) + parser (2.2.2.6) + ast (>= 1.1, < 3.0) + procto (0.0.2) + rails_best_practices (1.15.7) + activesupport + code_analyzer (>= 0.4.3) + colored + erubis + i18n + json + require_all + ruby-progressbar + rainbow (2.0.0) + rake (10.4.2) + redcard (1.1.0) + reek (1.6.6) + parser (~> 2.2.0.pre.7) + rainbow (>= 1.99, < 3.0) + unparser (~> 0.2.2) + require_all (1.3.2) + rest-client (1.8.0) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 3.0) + netrc (~> 0.7) + roodi (3.3.1) + ruby_parser (~> 3.2, >= 3.2.2) + rspec (3.1.0) + rspec-core (~> 3.1.0) + rspec-expectations (~> 3.1.0) + rspec-mocks (~> 3.1.0) + rspec-core (3.1.7) + rspec-support (~> 3.1.0) + rspec-expectations (3.1.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.1.0) + rspec-mocks (3.1.3) + rspec-support (~> 3.1.0) + rspec-support (3.1.2) + ruby-progressbar (1.7.5) + ruby_parser (3.7.1) + sexp_processor (~> 4.1) + sexp_processor (4.6.0) + simplecov (0.10.0) + docile (~> 1.1.0) + json (~> 1.8) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.0) + simplecov-json (0.2) + json + simplecov + thor (0.19.1) + thread_safe (0.3.5) + tilt (2.0.1) + tzinfo (1.2.2) + thread_safe (~> 0.1) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.1) + unparser (0.2.4) + abstract_type (~> 0.0.7) + adamantium (~> 0.2.0) + concord (~> 0.1.5) + diff-lcs (~> 1.2.5) + equalizer (~> 0.0.9) + parser (~> 2.2.2) + procto (~> 0.0.2) + valise (1.1.4) + +PLATFORMS + ruby + +DEPENDENCIES + cadre + corundum (>= 0.4.0) + metric_fu (~> 4.11.1) + rspec (~> 3.1.0) + rspec-steps! + +BUNDLED WITH + 1.10.6 diff --git a/gemfiles/3.2 b/gemfiles/3.2 new file mode 100644 index 0000000..14bdc9b --- /dev/null +++ b/gemfiles/3.2 @@ -0,0 +1,6 @@ +source "https://rubygems.org" + +gem "rspec", "~> 3.2.0" +gem 'cadre' + +gemspec :path => ".." diff --git a/gemfiles/3.2.lock b/gemfiles/3.2.lock new file mode 100644 index 0000000..6590793 --- /dev/null +++ b/gemfiles/3.2.lock @@ -0,0 +1,193 @@ +PATH + remote: .. + specs: + rspec-steps (2.0.1) + rspec (>= 3.0, < 3.99) + +GEM + remote: https://rubygems.org/ + specs: + abstract_type (0.0.7) + activesupport (4.2.4) + i18n (~> 0.7) + json (~> 1.7, >= 1.7.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + adamantium (0.2.0) + ice_nine (~> 0.11.0) + memoizable (~> 0.4.0) + addressable (2.3.8) + arrayfields (4.9.2) + ast (2.1.0) + cadre (1.0.1) + thor (>= 0.14, < 1.0) + tilt (> 1.0) + valise (~> 1.1.2) + calibrate (0.0.1) + caliph (0.3.1) + cane (2.6.2) + parallel + chronic (0.10.2) + churn (0.0.35) + chronic (>= 0.2.3) + hirb + json_pure + main + rest-client (>= 1.6.0) + ruby_parser (~> 3.0) + sexp_processor (~> 4.1) + code_analyzer (0.4.5) + sexp_processor + code_metrics (0.1.3) + coderay (1.1.0) + colored (1.2) + concord (0.1.5) + adamantium (~> 0.2.0) + equalizer (~> 0.0.9) + corundum (0.6.0) + bundler + caliph (~> 0.3) + mattock (~> 0.9) + paint (~> 0.8.7) + rspec (>= 2.0) + simplecov (>= 0.5.4) + simplecov-json (>= 0.2) + diff-lcs (1.2.5) + docile (1.1.5) + domain_name (0.5.24) + unf (>= 0.0.5, < 1.0.0) + equalizer (0.0.11) + erubis (2.7.0) + fattr (2.2.2) + flay (2.6.1) + ruby_parser (~> 3.0) + sexp_processor (~> 4.0) + flog (4.3.2) + ruby_parser (~> 3.1, > 3.1.0) + sexp_processor (~> 4.4) + hirb (0.7.3) + http-cookie (1.0.2) + domain_name (~> 0.5) + i18n (0.7.0) + ice_nine (0.11.1) + json (1.8.3) + json_pure (1.8.2) + launchy (2.4.3) + addressable (~> 2.3) + main (6.1.0) + arrayfields (>= 4.7.4) + chronic (>= 0.6.2) + fattr (>= 2.2.0) + map (>= 5.1.0) + map (6.5.5) + mattock (0.10.0) + calibrate (~> 0.0.1) + caliph (~> 0.3.1) + rake (~> 10.0) + tilt (> 0) + valise (~> 1.1.1) + memoizable (0.4.2) + thread_safe (~> 0.3, >= 0.3.1) + metric_fu (4.11.4) + cane (~> 2.5, >= 2.5.2) + churn (~> 0.0.35) + code_metrics (~> 0.1) + coderay + flay (~> 2.1, >= 2.0.1) + flog (~> 4.1, >= 4.1.1) + launchy (~> 2.0) + metric_fu-Saikuro (~> 1.1, >= 1.1.3) + multi_json + rails_best_practices (~> 1.14, >= 1.14.3) + redcard + reek (~> 1.3, >= 1.3.4) + roodi (~> 3.1) + metric_fu-Saikuro (1.1.3) + mime-types (2.6.2) + minitest (5.8.0) + multi_json (1.11.2) + netrc (0.10.3) + paint (0.8.7) + parallel (1.6.1) + parser (2.2.2.6) + ast (>= 1.1, < 3.0) + procto (0.0.2) + rails_best_practices (1.15.7) + activesupport + code_analyzer (>= 0.4.3) + colored + erubis + i18n + json + require_all + ruby-progressbar + rainbow (2.0.0) + rake (10.4.2) + redcard (1.1.0) + reek (1.6.6) + parser (~> 2.2.0.pre.7) + rainbow (>= 1.99, < 3.0) + unparser (~> 0.2.2) + require_all (1.3.2) + rest-client (1.8.0) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 3.0) + netrc (~> 0.7) + roodi (3.3.1) + ruby_parser (~> 3.2, >= 3.2.2) + rspec (3.2.0) + rspec-core (~> 3.2.0) + rspec-expectations (~> 3.2.0) + rspec-mocks (~> 3.2.0) + rspec-core (3.2.3) + rspec-support (~> 3.2.0) + rspec-expectations (3.2.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.2.0) + rspec-mocks (3.2.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.2.0) + rspec-support (3.2.2) + ruby-progressbar (1.7.5) + ruby_parser (3.7.1) + sexp_processor (~> 4.1) + sexp_processor (4.6.0) + simplecov (0.10.0) + docile (~> 1.1.0) + json (~> 1.8) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.0) + simplecov-json (0.2) + json + simplecov + thor (0.19.1) + thread_safe (0.3.5) + tilt (2.0.1) + tzinfo (1.2.2) + thread_safe (~> 0.1) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.1) + unparser (0.2.4) + abstract_type (~> 0.0.7) + adamantium (~> 0.2.0) + concord (~> 0.1.5) + diff-lcs (~> 1.2.5) + equalizer (~> 0.0.9) + parser (~> 2.2.2) + procto (~> 0.0.2) + valise (1.1.4) + +PLATFORMS + ruby + +DEPENDENCIES + cadre + corundum (>= 0.4.0) + metric_fu (~> 4.11.1) + rspec (~> 3.2.0) + rspec-steps! + +BUNDLED WITH + 1.10.6 diff --git a/gemfiles/3.3 b/gemfiles/3.3 new file mode 100644 index 0000000..028bc33 --- /dev/null +++ b/gemfiles/3.3 @@ -0,0 +1,6 @@ +source "https://rubygems.org" + +gem "rspec", "~> 3.3.0" +gem 'cadre' + +gemspec :path => ".." diff --git a/gemfiles/3.3.lock b/gemfiles/3.3.lock new file mode 100644 index 0000000..0cdc254 --- /dev/null +++ b/gemfiles/3.3.lock @@ -0,0 +1,193 @@ +PATH + remote: .. + specs: + rspec-steps (2.0.1) + rspec (>= 3.0, < 3.99) + +GEM + remote: https://rubygems.org/ + specs: + abstract_type (0.0.7) + activesupport (4.2.4) + i18n (~> 0.7) + json (~> 1.7, >= 1.7.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + adamantium (0.2.0) + ice_nine (~> 0.11.0) + memoizable (~> 0.4.0) + addressable (2.3.8) + arrayfields (4.9.2) + ast (2.1.0) + cadre (1.0.1) + thor (>= 0.14, < 1.0) + tilt (> 1.0) + valise (~> 1.1.2) + calibrate (0.0.1) + caliph (0.3.1) + cane (2.6.2) + parallel + chronic (0.10.2) + churn (0.0.35) + chronic (>= 0.2.3) + hirb + json_pure + main + rest-client (>= 1.6.0) + ruby_parser (~> 3.0) + sexp_processor (~> 4.1) + code_analyzer (0.4.5) + sexp_processor + code_metrics (0.1.3) + coderay (1.1.0) + colored (1.2) + concord (0.1.5) + adamantium (~> 0.2.0) + equalizer (~> 0.0.9) + corundum (0.6.0) + bundler + caliph (~> 0.3) + mattock (~> 0.9) + paint (~> 0.8.7) + rspec (>= 2.0) + simplecov (>= 0.5.4) + simplecov-json (>= 0.2) + diff-lcs (1.2.5) + docile (1.1.5) + domain_name (0.5.24) + unf (>= 0.0.5, < 1.0.0) + equalizer (0.0.11) + erubis (2.7.0) + fattr (2.2.2) + flay (2.6.1) + ruby_parser (~> 3.0) + sexp_processor (~> 4.0) + flog (4.3.2) + ruby_parser (~> 3.1, > 3.1.0) + sexp_processor (~> 4.4) + hirb (0.7.3) + http-cookie (1.0.2) + domain_name (~> 0.5) + i18n (0.7.0) + ice_nine (0.11.1) + json (1.8.3) + json_pure (1.8.2) + launchy (2.4.3) + addressable (~> 2.3) + main (6.1.0) + arrayfields (>= 4.7.4) + chronic (>= 0.6.2) + fattr (>= 2.2.0) + map (>= 5.1.0) + map (6.5.5) + mattock (0.10.0) + calibrate (~> 0.0.1) + caliph (~> 0.3.1) + rake (~> 10.0) + tilt (> 0) + valise (~> 1.1.1) + memoizable (0.4.2) + thread_safe (~> 0.3, >= 0.3.1) + metric_fu (4.11.4) + cane (~> 2.5, >= 2.5.2) + churn (~> 0.0.35) + code_metrics (~> 0.1) + coderay + flay (~> 2.1, >= 2.0.1) + flog (~> 4.1, >= 4.1.1) + launchy (~> 2.0) + metric_fu-Saikuro (~> 1.1, >= 1.1.3) + multi_json + rails_best_practices (~> 1.14, >= 1.14.3) + redcard + reek (~> 1.3, >= 1.3.4) + roodi (~> 3.1) + metric_fu-Saikuro (1.1.3) + mime-types (2.6.2) + minitest (5.8.0) + multi_json (1.11.2) + netrc (0.10.3) + paint (0.8.7) + parallel (1.6.1) + parser (2.2.2.6) + ast (>= 1.1, < 3.0) + procto (0.0.2) + rails_best_practices (1.15.7) + activesupport + code_analyzer (>= 0.4.3) + colored + erubis + i18n + json + require_all + ruby-progressbar + rainbow (2.0.0) + rake (10.4.2) + redcard (1.1.0) + reek (1.6.6) + parser (~> 2.2.0.pre.7) + rainbow (>= 1.99, < 3.0) + unparser (~> 0.2.2) + require_all (1.3.2) + rest-client (1.8.0) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 3.0) + netrc (~> 0.7) + roodi (3.3.1) + ruby_parser (~> 3.2, >= 3.2.2) + rspec (3.3.0) + rspec-core (~> 3.3.0) + rspec-expectations (~> 3.3.0) + rspec-mocks (~> 3.3.0) + rspec-core (3.3.2) + rspec-support (~> 3.3.0) + rspec-expectations (3.3.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.3.0) + rspec-mocks (3.3.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.3.0) + rspec-support (3.3.0) + ruby-progressbar (1.7.5) + ruby_parser (3.7.1) + sexp_processor (~> 4.1) + sexp_processor (4.6.0) + simplecov (0.10.0) + docile (~> 1.1.0) + json (~> 1.8) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.0) + simplecov-json (0.2) + json + simplecov + thor (0.19.1) + thread_safe (0.3.5) + tilt (2.0.1) + tzinfo (1.2.2) + thread_safe (~> 0.1) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.1) + unparser (0.2.4) + abstract_type (~> 0.0.7) + adamantium (~> 0.2.0) + concord (~> 0.1.5) + diff-lcs (~> 1.2.5) + equalizer (~> 0.0.9) + parser (~> 2.2.2) + procto (~> 0.0.2) + valise (1.1.4) + +PLATFORMS + ruby + +DEPENDENCIES + cadre + corundum (>= 0.4.0) + metric_fu (~> 4.11.1) + rspec (~> 3.3.0) + rspec-steps! + +BUNDLED WITH + 1.10.6 diff --git a/lib/rspec-steps.rb b/lib/rspec-steps.rb index 54dafdd..3c80f67 100644 --- a/lib/rspec-steps.rb +++ b/lib/rspec-steps.rb @@ -1,2 +1,8 @@ -require 'rspec-steps/duckpunch/object-extensions' -require 'rspec-steps/duckpunch/example-group' +require 'rspec' +require 'rspec-steps/dsl' + +module RSpec + extend RSpec::Steps::DSL +end + +# open question: add steps to top level? diff --git a/lib/rspec-steps/builder.rb b/lib/rspec-steps/builder.rb new file mode 100644 index 0000000..17b015b --- /dev/null +++ b/lib/rspec-steps/builder.rb @@ -0,0 +1,23 @@ +module RSpec::Steps + class Builder + def initialize(describer) + @describer = describer + end + + def build_example_group + describer = @describer + + RSpec.describe(*describer.group_args, describer.metadata) do + describer.let_list.each do |letter| + letter.define_on(describer.step_list, self) + end + describer.hooks.each do |hook| + hook.define_on(self) + end + describer.step_list.each do |step| + step.define_on(describer.step_list, self) + end + end + end + end +end diff --git a/lib/rspec-steps/describer.rb b/lib/rspec-steps/describer.rb new file mode 100644 index 0000000..f021de2 --- /dev/null +++ b/lib/rspec-steps/describer.rb @@ -0,0 +1,78 @@ +require 'rspec-steps/dsl' +require 'rspec-steps/step' +require 'rspec-steps/hook' +require 'rspec-steps/step-list' +require 'rspec-steps/lets' + +module RSpec::Steps + + class Describer + def initialize(args, metadata, &block) + @group_args = args + @metadata = {} + if @group_args.last.is_a? Hash + @metadata = @group_args.pop + end + @metadata = metadata.merge(@metadata) + @step_list = StepList.new + @hooks = [] + @let_list = [] + instance_eval(&block) + end + attr_reader :group_args, :let_list, :step_list, :hooks, :metadata + + def step(*args, &action) + metadata = {} + if args.last.is_a? Hash + metadata = args.pop + end + + metadata = { + :caller => caller + }.merge(metadata) + + @step_list << Step.new(metadata, args, action) + end + alias when step + alias then step + alias next step + alias it step + + def shared_steps(*args, &block) + name = args.first + raise "shared step lists need a String for a name" unless name.is_a? String + raise "there is already a step list named #{name}" if SharedSteps.has_key?(name) + SharedSteps[name] = Describer.new(args, {:caller => caller}, &block) + end + + def perform_steps(name) + describer = SharedSteps.fetch(name) + @hooks += describer.hooks + @step_list += describer.step_list + end + + def let(name, &block) + @let_list << Let.new(name, block) + end + + def let!(name, &block) + @let_list << LetBang.new(name, block) + end + + def skip(*args) + #noop + end + + def before(kind = :all, &callback) + @hooks << Hook.new(:before, kind, callback) + end + + def after(kind = :all, &callback) + @hooks << Hook.new(:after, kind, callback) + end + + def around(kind = :all, &callback) + @hooks << Hook.new(:around, kind, callback) + end + end +end diff --git a/lib/rspec-steps/dsl.rb b/lib/rspec-steps/dsl.rb new file mode 100644 index 0000000..92cbe04 --- /dev/null +++ b/lib/rspec-steps/dsl.rb @@ -0,0 +1,30 @@ +require 'rspec-steps/builder' +require 'rspec-steps/describer' + +module RSpec::Steps + def self.warnings + @warnings ||= Hash.new do |h,warning| + puts warning #should be warn, but RSpec complains + h[warning] = true + end + end + + SharedSteps = {} + + module DSL + def steps(*args, &block) + describer = Describer.new(args, {:caller => caller}, &block) + builder = Builder.new(describer) + + builder.build_example_group + end + + def shared_steps(*args, &block) + name = args.first + raise "shared step lists need a String for a name" unless name.is_a? String + raise "there is already a step list named #{name}" if SharedSteps.has_key?(name) + SharedSteps[name] = Describer.new(*args, {:caller => caller}, &block) + end + end + extend DSL +end diff --git a/lib/rspec-steps/duckpunch/example-group.rb b/lib/rspec-steps/duckpunch/example-group.rb deleted file mode 100644 index 5283359..0000000 --- a/lib/rspec-steps/duckpunch/example-group.rb +++ /dev/null @@ -1,20 +0,0 @@ -require 'rspec/core/example_group' -require 'rspec-steps/stepwise' - -module RSpec::Steps - module ClassMethods - def steps(*args, &block) - options = if args.last.is_a?(Hash) then args.pop else {} end - options[:stepwise] = true - options[:caller] ||= caller - args.push(options) - - describe(*args, &block) - end - end -end - -RSpec::Core::ExampleGroup.extend RSpec::Steps::ClassMethods -include RSpec::Steps::ClassMethods - -RSpec::configuration.include(RSpecStepwise, :stepwise => true) diff --git a/lib/rspec-steps/duckpunch/object-extensions.rb b/lib/rspec-steps/duckpunch/object-extensions.rb deleted file mode 100644 index 3261c19..0000000 --- a/lib/rspec-steps/duckpunch/object-extensions.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'rspec-steps/duckpunch/example-group' -require 'rspec-steps/stepwise' -require 'rspec/core/shared_example_group' - -module RSpec::Core::SharedExampleGroup - alias :shared_steps :share_examples_for - alias :steps_shared_as :share_as -end -extend RSpec::Steps::ClassMethods diff --git a/lib/rspec-steps/hook.rb b/lib/rspec-steps/hook.rb new file mode 100644 index 0000000..34ad6e5 --- /dev/null +++ b/lib/rspec-steps/hook.rb @@ -0,0 +1,34 @@ +require 'rspec-steps/dsl' + +module RSpec::Steps + class Hook < Struct.new(:type, :kind, :action) + def rspec_kind + case kind + when :each + warn_about_promotion(type) + :all + when :step + :each + else + kind + end + end + + def warn_about_promotion(scope_name) + RSpec::Steps.warnings[ + "#{scope_name} :each blocks declared for steps are always treated as " + + ":all scope (it's possible you want #{scope_name} :step)"] + end + + def define_on(example_group) + case type + when :before + example_group.before rspec_kind, &action + when :after + example_group.after rspec_kind, &action + when :around + example_group.around rspec_kind, &action + end + end + end +end diff --git a/lib/rspec-steps/lets.rb b/lib/rspec-steps/lets.rb new file mode 100644 index 0000000..fd73424 --- /dev/null +++ b/lib/rspec-steps/lets.rb @@ -0,0 +1,20 @@ +module RSpec::Steps + class Let < Struct.new(:name, :block) + def define_on(step_list, group) + name = self.name + step_list.add_let(name, block) + + group.let(name) do + step_list.let_memo(name, self) + end + end + end + + class LetBang < Let + def define_on(step_list, group) + super + + step_list.add_let_bang(name) + end + end +end diff --git a/lib/rspec-steps/monkeypatching.rb b/lib/rspec-steps/monkeypatching.rb new file mode 100644 index 0000000..726a95a --- /dev/null +++ b/lib/rspec-steps/monkeypatching.rb @@ -0,0 +1,3 @@ +require 'rspec-steps/dsl' + +extend RSpec::Steps::DSL diff --git a/lib/rspec-steps/step-list.rb b/lib/rspec-steps/step-list.rb new file mode 100644 index 0000000..ba0524f --- /dev/null +++ b/lib/rspec-steps/step-list.rb @@ -0,0 +1,84 @@ +require 'rspec-steps/step-result' + +module RSpec::Steps + class StepList + include Enumerable + + def initialize + @steps = [] + @let_bangs = [] + @let_blocks = {} + @let_memos = Hash.new do |h,example| + h[example] = Hash.new do |h, let_name| + h[let_name] = example.instance_eval(&@let_blocks.fetch(let_name)) + end + end + @results = nil + end + attr_accessor :steps + + def add_let(name, block) + @let_blocks[name] = block + end + + # In this case, we scope the caching of a let block to an + # example - which since the whole step list runs in a single example is + # fine. It would be more correct to build a result-set and cache lets + # there. + def let_memo(name, example) + @let_memos[example][name] + end + + def add_let_bang(name) + @let_bangs << name + end + + def add(step) + @steps << step + end + alias << add + + def +(other) + result = StepList.new + result.steps = steps + other.steps + result + end + + def each(&block) + @steps.each(&block) + end + + def result_for(step) + @results[step] + end + + def run_only_once(context_example) + return unless @results.nil? + failed_step = nil + @let_bangs.each do |let_name| + context_example.__send__(let_name) + end + + @results = Hash[ @steps.map do |step| + [ + step, + if failed_step.nil? + result = capture_result(step, context_example) + if result.failed? + failed_step = result + end + result + else + StepResult.new(step, nil, nil, failed_step) + end + ] + end ] + end + + def capture_result(step, context_example) + StepResult.new(step, step.run_inside(context_example), nil, nil) + rescue BasicObject => ex + StepResult.new(step, nil, ex, nil) + end + end +end diff --git a/lib/rspec-steps/step-result.rb b/lib/rspec-steps/step-result.rb new file mode 100644 index 0000000..b918f7c --- /dev/null +++ b/lib/rspec-steps/step-result.rb @@ -0,0 +1,23 @@ +module RSpec::Steps + class StepResult < Struct.new(:step, :result, :exception, :failed_step) + def failed? + return (!exception.nil?) + end + + def has_executed_successfully? + if failed_step.nil? + if exception.nil? + true + else + raise exception + end + else + raise failed_step.exception + end + end + + def is_after_failed_step? + !!failed_step + end + end +end diff --git a/lib/rspec-steps/step.rb b/lib/rspec-steps/step.rb new file mode 100644 index 0000000..ed7f31d --- /dev/null +++ b/lib/rspec-steps/step.rb @@ -0,0 +1,24 @@ +module RSpec::Steps + class Step < Struct.new(:metadata, :args, :action) + def initialize(*whatever) + super + @failed_step = nil + end + attr_accessor :failed_step + + def define_on(step_list, example_group) + step = self + example_group.it(*args, metadata) do |example| + step_list.run_only_once(self) + result = step_list.result_for(step) + pending if result.is_after_failed_step? + expect(result).to have_executed_successfully + end + end + + def run_inside(example) + example.instance_eval(&action) + end + + end +end diff --git a/lib/rspec-steps/stepwise.rb b/lib/rspec-steps/stepwise.rb deleted file mode 100644 index 74b5a31..0000000 --- a/lib/rspec-steps/stepwise.rb +++ /dev/null @@ -1,136 +0,0 @@ -module RSpecStepwise - module ClassMethods - #TODO: This is hacky and needs a more general solution - #Something like cloning the current conf and having RSpec::Stepwise::config ? - def suspend_transactional_fixtures - if self.respond_to? :use_transactional_fixtures - old_val = self.use_transactional_fixtures - self.use_transactional_fixtures = false - - yield - - self.use_transactional_fixtures = old_val - else - yield - end - end - - def before(*args, &block) - if args.first == :each - puts "before blocks declared for steps are always treated as :all scope" - end - super - end - - def after(*args, &block) - if args.first == :each - puts "after blocks declared for steps are always treated as :all scope" - end - super - end - - def around(*args, &block) - if args.first == :each - puts "around :each blocks declared for steps are treated as :all scope" - end - super - end - - def eval_before_alls(example_group_instance) - super - example_group_instance.example = whole_list_example - world.run_hook_filtered(:before, :each, self, example_group_instance, whole_list_example) - ancestors.reverse.each { |ancestor| ancestor.run_hook(:before, :each, example_group_instance) } - end - - def eval_around_eachs(example) - end - - def eval_before_eachs(example) - end - - def eval_after_eachs(example) - end - - def eval_after_alls(example_group_instance) - example_group_instance.example = whole_list_example - ancestors.each { |ancestor| ancestor.run_hook(:after, :each, example_group_instance) } - world.run_hook_filtered(:after, :each, self, example_group_instance, whole_list_example) - super - end - - def whole_list_example - @whole_list_example ||= begin - RSpec::Core::Example.new(self, "step list", {}) - end - end - - def with_around_hooks(instance, &block) - hooks = around_hooks_for(self) - if hooks.empty? - yield - else - hooks.reverse.inject(Example.procsy(metadata)) do |procsy, around_hook| - Example.procsy(procsy.metadata) do - instance.instance_eval_with_args(procsy, &around_hook) - end - end.call - end - end - - def perform_steps(name, *args, &customization_block) - shared_block = world.shared_example_groups[name] - raise "Could not find shared example group named \#{name.inspect}" unless shared_block - - module_eval_with_args(*args, &shared_block) - module_eval(&customization_block) if customization_block - end - - def run_examples(reporter) - instance = new - - set_ivars(instance, before_all_ivars) - - instance.example = whole_list_example - - suspend_transactional_fixtures do - with_around_hooks(instance) do - filtered_examples.inject(true) do |success, example| - break if RSpec.wants_to_quit - unless success - reporter.example_started(example) - example.metadata[:pending] = true - example.metadata[:execution_result][:pending_message] = "Previous step failed" - example.metadata[:execution_result][:started_at] = Time.now - example.instance_eval{ record_finished :pending, :pending_message => "Previous step failed" } - reporter.example_pending(example) - next - end - succeeded = instance.with_indelible_ivars do - example.run(instance, reporter) - end - RSpec.wants_to_quit = true if fail_fast? && !succeeded - success && succeeded - end - end - end - end - end - - def with_indelible_ivars - old_value, @ivars_indelible = @ivars_indelible, true - result = yield - @ivars_indelible = old_value - result - end - - def instance_variable_set(name, value) - if !@ivars_indelible - super - end - end - - def self.included(base) - base.extend(ClassMethods) - end -end diff --git a/rspec-steps.gemspec b/rspec-steps.gemspec index 6d5ee42..d98f28c 100644 --- a/rspec-steps.gemspec +++ b/rspec-steps.gemspec @@ -1,33 +1,47 @@ Gem::Specification.new do |spec| spec.name = "rspec-steps" - spec.version = "0.0.4" + spec.version = "2.0.1" author_list = { - "Judson Lester" => "nyarly@gmail.com" + "Judson Lester" => "judson@lrdesign.com", + "Evan Dorn" => "evan@lrdesign.com" } spec.authors = author_list.keys spec.email = spec.authors.map {|name| author_list[name]} spec.summary = "I want steps in RSpec" spec.description = <<-EOD - I don't like Cucumber. I don't need plain text stories. My clients either - read code or don't read any test documents, so Cucumber is mostly useless. - But often, especially in full integration tests, it would be nice to have - steps in a test. + A minimal implementation of integration testing within RSpec. + Allows you to build sequential specs, each with a description, + but where state is maintained between tests and before/after actions are only + triggered at the beginning and end of the entire sequence. Cool things you + can do with this: + + * Build multi-step user stories in plain RSpec syntax. Locate the point of + failure quickly, and break up large integrations into sensible steps + * Speed up groups of related tests by running your factories only once before + the whole group. + EOD spec.rubyforge_project= spec.name.downcase - spec.homepage = "http://#{spec.rubyforge_project}.rubyforge.org/" + spec.homepage = "https://github.com/LRDesign/rspec-steps" spec.required_rubygems_version = Gem::Requirement.new(">= 0") if spec.respond_to? :required_rubygems_version= - # Do this: d$@" spec.files = %w[ lib/rspec-steps.rb - lib/rspec-steps/stepwise.rb - lib/rspec-steps/duckpunch/example-group.rb - lib/rspec-steps/duckpunch/object-extensions.rb + + lib/rspec-steps/step-list.rb + lib/rspec-steps/describer.rb + lib/rspec-steps/step.rb + lib/rspec-steps/dsl.rb + lib/rspec-steps/builder.rb + lib/rspec-steps/hook.rb + lib/rspec-steps/step-result.rb + lib/rspec-steps/lets.rb + lib/rspec-steps/monkeypatching.rb doc/README doc/Specifications - spec/example_group.rb + spec/example_group_spec.rb spec_help/spec_helper.rb spec_help/gem_test_suite.rb spec_help/rspec-sandbox.rb @@ -41,12 +55,8 @@ Gem::Specification.new do |spec| spec.rubygems_version = "1.3.5" dev_deps = [ - ["rake-rubygems", ">= 0.2.0"], - ["hanna", "~> 0.1.0"], - ["mailfactory", "~> 1.4.0"], - ["rspec", [">= 2.0"]], - ["bundler", ["~> 1.0.0"]], - ["rcov", [">= 0"]] + ["corundum", ">= 0.4.0"], + ["metric_fu", "~> 4.11.1"], ] if spec.respond_to? :specification_version then current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION @@ -73,9 +83,8 @@ Gem::Specification.new do |spec| spec.rdoc_options += %w{--main doc/README } spec.rdoc_options += ["--title", "#{spec.name}-#{spec.version} RDoc"] - spec.add_dependency("rspec", ">= 2.6") + spec.add_dependency("rspec", ">= 3.0", "< 3.99") - spec.post_install_message = "Another tidy package brought to you by Judson" + #spec.post_install_message = "Another tidy package brought to you by Judson + #Lester of Logical Reality Design" end - - diff --git a/spec/example_group.rb b/spec/example_group.rb deleted file mode 100644 index 0d58922..0000000 --- a/spec/example_group.rb +++ /dev/null @@ -1,75 +0,0 @@ -require 'rspec-steps' -require 'rspec-sandbox' - -describe RSpec::Core::ExampleGroup, "defined as stepwise" do - describe "::steps" do - it "should create an ExampleGroup that includes RSpec::Stepwise" do - group = nil - sandboxed do - group = steps "Test Steps" do - end - end - (class << group; self; end).included_modules.should include(RSpecStepwise::ClassMethods) - end - end - - describe "with Stepwise included" do - it "should retain instance variables between steps" do - group = nil - sandboxed do - group = steps "Test Steps" do - it("sets @a"){ @a = 1 } - it("reads @a"){ @a.should == 1} - end - group.run - end - - group.examples.each do |example| - example.metadata[:execution_result][:status].should == 'passed' - end - end - - it "should mark later examples as pending if one fails" do - group = nil - sandboxed do - group = steps "Test Steps" do - it { fail "All others fail" } - it { 1.should == 1 } - end - group.run - end - - group.examples[1].metadata[:pending].should be_true - end - - it "should allow nested steps", :pending => "Not really" do - group = nil - sandboxed do - group = steps "Test Steps" do - steps "Nested" do - it { @a = 1 } - it { @a.should == 1} - end - end - group.run - end - - group.children[0].examples.each do |example| - example.metadata[:execution_result][:status].should == 'passed' - end - group.children[0].should have(2).examples - end - - it "should not allow nested normal contexts" do - pending "A correct approach - in the meantime, this behavior is undefined" - expect { - sandboxed do - steps "Basic" do - describe "Not allowed" do - end - end - end - }.to raise_error - end - end -end diff --git a/spec/example_group_spec.rb b/spec/example_group_spec.rb new file mode 100644 index 0000000..fee2400 --- /dev/null +++ b/spec/example_group_spec.rb @@ -0,0 +1,174 @@ +require 'rspec-steps' +require 'rspec-sandbox' +require 'rspec-steps/monkeypatching' + +describe RSpec::Core::ExampleGroup do + describe "with Stepwise included" do + it "should retain instance variables between steps" do + group = nil + sandboxed do + group = RSpec.steps "Test Steps" do + it("sets @a"){ @a = 1 } + it("reads @a"){ @a.should == 1} + end + group.run + end + + group.examples.each do |example| + expect(example.metadata[:execution_result].status).to eq(:passed) + end + end + + it "should define let blocks correctly" do + group = nil + sandboxed do + group = RSpec.steps "Test Steps" do + let! :array do [] end + let :number do 17 end + it("adds number to array"){ array << number } + it("adds number to array twice"){ array << number } + it("checks array"){ expect(array).to eq([17,17])} + end + group.run + end + + group.examples.each do |example| + expect(example.metadata[:execution_result].status).to eq(:passed) + end + end + + it "should work with shared_steps/perform steps" do + group = nil + sandboxed do + group = RSpec.steps "Test Steps" do + shared_steps "add one" do + it("adds one to @a"){ @a += 1 } + end + it("sets @a"){ @a = 1 } + perform_steps "add one" + perform_steps "add one" + perform_steps "add one" + it("reads @a"){ @a.should == 4 } + end + group.run + end + + expect(group.examples.length).to eq(5) + + group.examples.each do |example| + expect(example.metadata[:execution_result].status).to eq(:passed) + end + end + + it "should run each_step hooks" do + group = nil + afters = [] + befores = [] + + sandboxed do + group = RSpec.steps "Test Each Step" do + before :each do + befores << :each + end + after :each do + afters << :each + end + + before :all do + befores << :all + end + after :all do + afters << :all + end + + before :step do + befores << :step + end + after :step do + afters << :step + end + + it "should 1" do + 1.should == 1 + end + it "should 2" do + 2.should == 2 + end + it "should 3" do + 3.should == 3 + end + end + group.run + end + + expect(befores.find_all{|item| item == :all}.length).to eq(1) + expect(befores.find_all{|item| item == :each}.length).to eq(1) + expect(befores.find_all{|item| item == :step}.length).to eq(3) + expect(afters.find_all{|item| item == :all}.length).to eq(1) + expect(afters.find_all{|item| item == :each}.length).to eq(1) + expect(afters.find_all{|item| item == :step}.length).to eq(3) + end + + it "should mark later examples as failed if a before hook fails" do + group = nil + exception = Exception.new "Testing Error" + + result = nil + sandboxed do + group = RSpec.steps "Test Steps" do + before { raise exception } + it { 1.should == 1 } + it { 1.should == 1 } + end + result = group.run + end + + expect(result).to eq(false) + end + + it "should mark later examples as pending if one fails" do + group = nil + result = nil + sandboxed do + group = RSpec.steps "Test Steps" do + it { fail "All others fail" } + it { 1.should == 1 } + end + result = group.run + end + + expect(result).to eq(false) + expect(group.examples[0].metadata[:execution_result].status).to eq(:failed) + expect(group.examples[1].metadata[:execution_result].status).to eq(:pending) + end + + it "should allow nested steps", :pending => "Not really" do + group = nil + sandboxed do + group = RSpec.steps "Test Steps" do + steps "Nested" do + it { @a = 1 } + it { @a.should == 1} + end + end + group.run + end + + group.children[0].examples.each do |example| + expect(example.metadata[:execution_result].status).to eq(:passed) + end + expect(group.children[0].size).to eq(2) + end + + it "should not allow nested normal contexts" do + expect { + sandboxed do + RSpec.steps "Basic" do + describe "Not allowed" do + end + end + end + }.to raise_error(NoMethodError) + end + end +end diff --git a/spec_help/file-sandbox.rb b/spec_help/file-sandbox.rb index 468e26d..3d9e326 100644 --- a/spec_help/file-sandbox.rb +++ b/spec_help/file-sandbox.rb @@ -34,7 +34,7 @@ def have_contents(expected) end attr_reader :sandbox - + def in_sandbox(&block) raise "I expected to create a sandbox as you passed in a block to me" if !block_given? @@ -84,7 +84,7 @@ def initialize(path = '__sandbox') clean_up FileUtils.mkdir_p @root end - + def [](name) SandboxFile.new(File.join(@root, name), name) end @@ -102,17 +102,17 @@ def new(options) file.content = (options.delete(:with_content) || options.delete(:with_contents) || '') end end - + raise "unexpected keys '#{options.keys.join(', ')}'" unless options.empty? - + dir || file end - + def remove(options) name = File.join(@root, options[:file]) FileUtils.remove_file name end - + def clean_up FileUtils.rm_rf @root if File.exists? @root @@ -124,7 +124,7 @@ def clean_up class SandboxFile attr_reader :path - + def initialize(path, sandbox_path) @path = path @sandbox_path = sandbox_path @@ -141,12 +141,12 @@ def exist? def content File.read path end - + def content=(content) FileUtils.mkdir_p File.dirname(@path) File.open(@path, "w") {|f| f << content} end - + def binary_content=(content) FileUtils.mkdir_p File.dirname(@path) File.open(@path, "wb") {|f| f << content} @@ -159,6 +159,6 @@ def create alias exists? exist? alias contents content alias contents= content= - alias binary_contents= binary_content= + alias binary_contents= binary_content= end end diff --git a/spec_help/rspec-sandbox.rb b/spec_help/rspec-sandbox.rb index 7254580..2490e85 100644 --- a/spec_help/rspec-sandbox.rb +++ b/spec_help/rspec-sandbox.rb @@ -1,88 +1,70 @@ +require 'rspec/support/spec' require 'rspec/core' -class NullObject - private - def method_missing(method, *args, &block) - # ignore - end +class << RSpec + attr_writer :configuration, :world end -def sandboxed(&block) - @orig_config = RSpec.configuration - @orig_world = RSpec.world - new_config = RSpec::Core::Configuration.new - new_world = RSpec::Core::World.new(new_config) - RSpec.instance_variable_set(:@configuration, new_config) - RSpec.instance_variable_set(:@world, new_world) - - load 'rspec-steps/duckpunch/example-group.rb' +$rspec_core_without_stderr_monkey_patch = RSpec::Core::Configuration.new - object = Object.new - object.extend(RSpec::Core::SharedExampleGroup) - - (class << RSpec::Core::ExampleGroup; self; end).class_eval do - alias_method :orig_run, :run - def run(reporter=nil) - @orig_mock_space = RSpec::Mocks::space - RSpec::Mocks::space = RSpec::Mocks::Space.new - orig_run(reporter || NullObject.new) - ensure - RSpec::Mocks::space = @orig_mock_space +class RSpec::Core::Configuration + def self.new(*args, &block) + super.tap do |config| + # We detect ruby warnings via $stderr, + # so direct our deprecations to $stdout instead. + config.deprecation_stream = $stdout end end - - object.instance_eval(&block) -ensure - (class << RSpec::Core::ExampleGroup; self; end).class_eval do - remove_method :run - alias_method :run, :orig_run - remove_method :orig_run - end - - RSpec.instance_variable_set(:@configuration, @orig_config) - RSpec.instance_variable_set(:@world, @orig_world) end -#Original from rspec-core -=begin -class << RSpec - alias_method :original_warn_about_deprecated_configure, :warn_about_deprecated_configure - - def warn_about_deprecated_configure - # no-op: in our specs we don't want to see the warning. - end - - alias_method :null_warn_about_deprecated_configure, :warn_about_deprecated_configure +module Sandboxing + def sandboxed(&block) + @orig_config = RSpec.configuration + @orig_world = RSpec.world + @orig_example = RSpec.current_example + new_config = RSpec::Core::Configuration.new + new_config.expose_dsl_globally = false + #new_config.expecting_with_rspec = true rescue nil + new_world = RSpec::Core::World.new(new_config) + RSpec.configuration = new_config + RSpec.world = new_world + object = Object.new + object.extend(RSpec::Core::SharedExampleGroup) - def allowing_configure_warning - (class << self; self; end).class_eval do - alias_method :warn_about_deprecated_configure, :original_warn_about_deprecated_configure - begin - yield - ensure - alias_method :warn_about_deprecated_configure, :null_warn_about_deprecated_configure + (class << RSpec::Core::ExampleGroup; self; end).class_exec do + alias_method :orig_run, :run + def run(reporter=nil) + RSpec.current_example = nil + orig_run(reporter || NullObject.new) end end + + RSpec::Mocks.with_temporary_scope do + object.instance_exec(&block) + end +# rescue BasicObject => ex +# puts "\n#{__FILE__}:#{__LINE__} => #{ex.inspect}" +# raise + ensure + (class << RSpec::Core::ExampleGroup; self; end).class_exec do + remove_method :run + alias_method :run, :orig_run + remove_method :orig_run + end + + RSpec.configuration = @orig_config + RSpec.world = @orig_world + RSpec.current_example = @orig_example end end -RSpec.configure do |c| - c.color_enabled = !in_editor? - c.filter_run :focus => true - c.run_all_when_everything_filtered = true - c.filter_run_excluding :ruby => lambda {|version| - case version.to_s - when "!jruby" - RUBY_ENGINE == "jruby" - when /^> (.*)/ - !(RUBY_VERSION.to_s > $1) - else - !(RUBY_VERSION.to_s =~ /^#{version.to_s}/) - end - } - c.around do |example| - sandboxed { example.run } +RSpec.configure do |config| + config.include Sandboxing +end + +class NullObject + private + def method_missing(method, *args, &block) + # ignore end end -=begin -=end diff --git a/spec_help/spec_helper.rb b/spec_help/spec_helper.rb index 8be9e12..fbf90a7 100644 --- a/spec_help/spec_helper.rb +++ b/spec_help/spec_helper.rb @@ -1,4 +1,8 @@ require 'rspec' -require 'spec_help/ungemmer' + +begin + require 'cadre' +rescue LoadError +end #Ungemmer::ungem_gemspec