Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dry-run option #129

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/crystalball.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
require 'crystalball/predictor/modified_specs'
require 'crystalball/predictor/modified_support_specs'
require 'crystalball/predictor/associated_specs'
require 'crystalball/predictor/regex_specs'
require 'crystalball/example_group_map'
require 'crystalball/execution_map'
require 'crystalball/map_generator'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def apply!
# Overrides `FactoryBot::FactoryRunner#run`. Pushes factory name to
# `FactoryBotStrategy.used_factories` and calls original `run`
def run(*)
factory = FactoryBotStrategy.factory_bot_constant.factory_by_name(@name)
factory = FactoryBotStrategy.factory_bot_constant.factories.find(@name)
FactoryBotStrategy.used_factories << factory.name.to_s
super
end
Expand Down
44 changes: 44 additions & 0 deletions lib/crystalball/predictor/regex_specs.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

require 'crystalball/predictor/strategy'

module Crystalball
class Predictor
# This strategy is almost the same as associated_specs.rb, but the only difference is that the `to` parameter also accept regex.
# Used with `predictor.use Crystalball::Predictor::FilenamePatternSpecs.new(from: %r{models/(.*).rb}, to: "./spec/models/%s_spec.rb")`.
# When used will look for files matched to `from` regex and use captures to fill `to` regex to
# get paths of proper specs
class RegexSpecs
include Strategy

# @param [file glob] scope - to find all the spec files scope to work with
# @param [Regexp] from - regular expression to match specific files and get proper captures
# @param [Regexp] to - regex in sprintf format to get proper files using captures of regexp
def initialize(scope:, from:, to:)
@scope = scope
@from = from
@to = to
end

def call(diff, _map)
super do
regex_string = diff.map(&:relative_path).grep(from).map { |source_file_path| to % captures(source_file_path) }
regex_string.flat_map { |regex| Dir[scope].grep(Regexp.new regex)}
end
end

private

attr_reader :scope, :from, :to

def captures(file_path)
match = file_path.match(from)
if match.names.any?
match.names.map(&:to_sym).zip(match.captures).to_h
else
match.captures
end
end
end
end
end
11 changes: 10 additions & 1 deletion lib/crystalball/rspec/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,22 @@ def run(args, err = $stderr, out = $stdout)

Crystalball.log :info, "Crystalball starts to glow..."
prediction = build_prediction

dry_run?(prediction)

Crystalball.log :debug, "Prediction: #{prediction.first(5).join(' ')}#{'...' if prediction.size > 5}"
Crystalball.log :info, "Starting RSpec."

super(args + prediction, err, out)
end

def dry_run?(prediction)
args = Hash[ ARGV.flat_map{|s| s.scan(/--?([^=\s]+)(?:=(\S+))?/) } ]
if args.key?('dry-run')
puts prediction.to_a
exit
end
end

def reset!
self.prediction_builder = nil
self.config = nil
Expand Down
Empty file added spec/data/file1/spec1_spec.rb
Empty file.
Empty file added spec/data/file1/spec2_spec.rb
Empty file.
Empty file added spec/data/file2/spec1_spec.rb
Empty file.
Empty file added spec/data/file2/spec2_spec.rb
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def run(*args, &block)
end

before do
class_double('FactoryBotConstant', factory_by_name: nil).as_stubbed_const
class_double('FactoryBotConstant').as_stubbed_const
allow(Crystalball::MapGenerator::FactoryBotStrategy).to receive(:factory_bot_constant).and_return(FactoryBotConstant)
end

Expand Down Expand Up @@ -43,7 +43,7 @@ def run(*); end

before do
allow(Crystalball::MapGenerator::FactoryBotStrategy).to receive(:used_factories).and_return(used_factories)
allow(FactoryBotConstant).to receive(:factory_by_name).with(:bad_dummy) { double(name: :dummy) }
allow(FactoryBotConstant).to receive_message_chain(:factories, :find).with(:bad_dummy) { double(name: :dummy) }
instance.instance_variable_set(:@name, :bad_dummy)
end

Expand Down
45 changes: 45 additions & 0 deletions spec/predictor/regex_specs_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# frozen_string_literal: true

require 'spec_helper'

describe Crystalball::Predictor::RegexSpecs do
subject(:predictor) { described_class.new scope: 'spec/data/**/*_spec.rb', from: %r{models/(?<file>.*).rb}, to: 'spec/data/%<file>s/(.*).rb' }
let(:path1) { 'models/file1.rb' }
let(:spec_file1_spec1) { 'spec/data/file1/spec1_spec.rb' }
let(:spec_file1_spec2) { 'spec/data/file1/spec2_spec.rb' }
let(:spec_file2_spec1) { 'spec/data/file2/spec1_spec.rb' }
let(:spec_file2_spec2) { 'spec/data/file2/spec1_spec.rb' }
let(:diff) { [double(relative_path: path1)] }

describe '#call' do
subject { predictor.call(diff, {}) }

it { is_expected.to eq(["./#{spec_file1_spec1}", "./#{spec_file1_spec2}"]) }

context 'when path does not contain specs' do
let(:path1) { 'models/file3.rb' }

it { is_expected.to eq([]) }
end

context 'when path does not match "FROM" pattern' do
let(:path1) { 'lib/file3.rb' }

it { is_expected.to eq([]) }
end

context 'when path is out of scope' do
let(:predictor) { described_class.new scope: 'spec/data/file1/*_spec.rb', from: %r{models/(?<file>.*).rb}, to: 'spec/data/%<file>s/(.*).rb' }
let(:path1) { 'models/file2.rb' }

it { is_expected.to eq([]) }
end

context 'without named captures' do
let(:predictor) { described_class.new scope: 'spec', from: /Gemfile/, to: 'spec' }
let(:path1) { 'Gemfile' }

it { is_expected.to eq ["./spec"] }
end
end
end