Running RSpec in dry mode and RSpec formatters

Outline:

  1. Cloning of repository and building app
  2. RSpec in dry-run mode
  3. RSpec formatters
  4. Custom formatters

Cloning and building the app

repo_url = "github.com/#{repo.owner}/#{repo.name}.git"
system "git clone https://#{access_token}@#{repo_url}"

Cloning and building the app

Run bundle inside the cloned application directory as below?

./bin/bundle install

Where gems are extracted?

We can not interfere in the main app environment !!!

Cloning and building the app

Are you sure bundle uses the correct Gemfile and installs gems for appropriate applications?

./bin/bundle install --path path/to/extracted/gems

NOT !!!

Bundle uses Gemfile from the main app

Bundle installs gems for the main app

Cloning and building the app

Bundle uses correct Gemfile


Bundle installs gems for the main app

./bin/bundle install --path path/to/extracted/gems 
   --gemfile path/to/Gemfile

Cloning and building the app

SOLUTION:

Bundler.with_clean_env do
  FileUtils.cd("#{Rails.root}/path/to/gh_repository") do
    system "./bin/bundle install --path path/to/extracted/gems"
  end
end

RSpec in dry-run mode

RSpec in dry-run mode

Use the --dry-run option to have RSpec print your suite's formatter output without running any examples or hooks."

RSpec in dry-run mode

Bundler.with_clean_env do
  FileUtils.cd("#{Rails.root}/path/to/gh_repository") do
    System "./bin/bundle exec rspec spec --dry-run"
  end
end

RSpec requires a current database also in dry-run mode, but it does not use its !!!

Migrations are pending. To resolve this issue, run:

   bin/rake db:migrate RAILS_ENV=test

Is the creation of a database specifically for this reason it is reasonable?

Of course
NOT !!!


 

SOLUTION:

# support_for_rspec.rb

module ActiveRecord
  class Migration
    def self.maintain_test_schema!
      true
    end
  end
end
./bin/bundle exec rspec spec --dry-run 
  --require path/to/support_for_rspec.rb

Result:

  •  RSpec runs the tests and displays it in the default format

Problems to be solved:

  •  create a formatter, which adapts the RSpec output to our needs
  • intercept output stream

Formatters in RSpec

--format option

  • progress - default
    output: ....F.....*....
  • documentation (group and example names)
    output:
      something
          does something that passes
          does something that fails (FAILED - 1)
          does something that is pending (PENDING: Not Yet Implemented
  • html
  • json
    output:

     
 { "examples": [
    {
      "description": "should require title to be set",
      "full_description": "Article validations should require title to be set",
      "status": "passed",
      "file_path": "./spec/models/article_spec.rb",
      "line_number": 5,
      "run_time": 0.117204642
    },{
      "description": "should require content to be set",
      "full_description": "Article validations should require content to be set",
      "status": "passed",
      "file_path": "./spec/models/article_spec.rb",
      "line_number": 6,
      "run_time": 0.010326601
    }]
summary": {
    "duration": 1.444473286,
    "example_count": 2,
    "failure_count": 0,
    "pending_count": 0
  },
  "summary_line": "2 examples, 0 failures"
}

Custom formatters

# RSpec::Core::Formatters::JsonFormatter
class JsonFormatter < BaseFormatter

  attr_reader :output_hash

  def initialize(output)
    super
    @output_hash = {}
  end

  def message(message)
    (@output_hash[:messages] ||= []) << message
  end

  def dump_summary(duration, example_count, failure_count, pending_count)
    super(duration, example_count, failure_count, pending_count)
    @output_hash[:summary] = {
      :duration => duration,
      :example_count => example_count,
      :failure_count => failure_count,
      :pending_count => pending_count
    }
    @output_hash[:summary_line] = summary_line(example_count, failure_count, pending_count)
  end

  def summary_line(example_count, failure_count, pending_count)
    summary = pluralize(example_count, "example")
    summary << ", " << pluralize(failure_count, "failure")
    summary << ", #{pending_count} pending" if pending_count > 0
    summary
  end

  def stop
    super
    @output_hash[:examples] = examples.map do |example|
      {
         :description => example.description,
         :full_description => example.full_description,
         :status => example.execution_result[:status],
         # :example_group,
         # :execution_result,
         :file_path => example.metadata[:file_path],
         :line_number  => example.metadata[:line_number],
       }.tap do |hash|
         if e=example.exception
           hash[:exception] =  {
             :class => e.class.name,
             :message => e.message,
             :backtrace => e.backtrace,
           }
         end
       end
    end
  end

 def close
   output.write @output_hash.to_json
   output.close if IO === output && output != $stdout
 end

end
class PercentFormatter < RSpec::Core::Formatters::JsonFormatter
  
  def dump_summary(duration, example_count, failure_count, pending_count)
    super(duration, example_count, failure_count, pending_count)
    @output_hash[:summary] = {
      :percent_of_success => “#{(example_count - failure_count) / example_count * 100}%”
      :duration => duration,
      :example_count => example_count,
      :failure_count => failure_count,
      :pending_count => pending_count
     }
     @output_hash[:summary_line] = summary_line(example_count, failure_count, pending_count)
  end
end
./bin/bundle exec rspec spec --dry-run 
  --require path/to/support_for_rspec.rb path/to/percent_formater.rb
  --format PercentFormatter
class JsonFormatterWithTreeStructure < RSpec::Core::Formatters::JsonFormatter
  RSpec::Core::Formatters.register self, :message, :dump_summary, :dump_profile, :stop, :close

  def initialize(output)
    super(output)
    @example_nodes = []
  end

  private
    def format_example(example)
      @example_nodes = []
      set_example_nodes(example.metadata)
      build_tree_structure
    end

    def set_example_nodes(example)
      @example_nodes <<  {
        description: example[:description],
        full_description: example[:full_description],
        file_path: example[:file_path],
        line_number: example[:line_number],
        example_type: example[:type]
      }
      if example[:parent_example_group]
        set_example_nodes example[:parent_example_group]
      elsif example[:example_group] && !(example[:example_group].to_h == example)
        set_example_nodes example[:example_group]
      end
    end

    def build_tree_structure
      @example_nodes.map.with_index do |node, index|
        @example_nodes[index+1] ? @example_nodes[index+1][:child] = node : node
      end.last
    end
end

https://www.relishapp.com/rspec/rspec-core/docs/formatters/custom-formatters

Bundler.with_clean_env do
  FileUtils.cd("#{Rails.root}/path/to/gh_repository") do
    required = "--require #{required_support_file} --require #{formatter}"
    cmd = "./bin/bundle exec rspec spec --dry-run #{required} --format JsonFormatterWithTreeStructure"
    stdin, stdout, stderr = Open3.popen3(cmd)
    @output_tests_lists = JSON.parse(stdout.read.partition('{').last(2).join)
  end
end

Running RSpec in dry-run mode and RSpec formatters

By Przemek Sienkowski

Running RSpec in dry-run mode and RSpec formatters

  • 996