From 6380f3a8fec79c561b06c45cf66d44661eb73a99 Mon Sep 17 00:00:00 2001 From: Feng Sha Date: Thu, 27 Aug 2020 16:38:56 -0400 Subject: [PATCH 1/4] prototype commit --- lib/crystalball/rspec/runner.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/crystalball/rspec/runner.rb b/lib/crystalball/rspec/runner.rb index e124f735..60d2d154 100644 --- a/lib/crystalball/rspec/runner.rb +++ b/lib/crystalball/rspec/runner.rb @@ -17,13 +17,23 @@ def run(args, err = $stderr, out = $stdout) Crystalball.log :info, "Crystalball starts to glow..." prediction = build_prediction - + dry_prediction(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_prediction (prediction) + args = Hash[ ARGV.flat_map{|s| s.scan(/--?([^=\s]+)(?:=(\S+))?/) } ] + if args.key?('dry-prediction') + array = prediction.to_a + File.write('./crystalball_spec_list.txt', array.join(",")) + exit + end + end + def reset! self.prediction_builder = nil self.config = nil From 7996245354cd6b275e2414c9835fb95442efb966 Mon Sep 17 00:00:00 2001 From: Feng Sha Date: Wed, 16 Sep 2020 11:48:38 -0400 Subject: [PATCH 2/4] WIP --- lib/crystalball/rspec/runner.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/crystalball/rspec/runner.rb b/lib/crystalball/rspec/runner.rb index 60d2d154..90d9165e 100644 --- a/lib/crystalball/rspec/runner.rb +++ b/lib/crystalball/rspec/runner.rb @@ -17,7 +17,7 @@ def run(args, err = $stderr, out = $stdout) Crystalball.log :info, "Crystalball starts to glow..." prediction = build_prediction - dry_prediction(prediction) + dry_run?(prediction) Crystalball.log :debug, "Prediction: #{prediction.first(5).join(' ')}#{'...' if prediction.size > 5}" Crystalball.log :info, "Starting RSpec." @@ -25,11 +25,11 @@ def run(args, err = $stderr, out = $stdout) super(args + prediction, err, out) end - def dry_prediction (prediction) + def dry_run?(prediction) + puts "Testing lib import" args = Hash[ ARGV.flat_map{|s| s.scan(/--?([^=\s]+)(?:=(\S+))?/) } ] - if args.key?('dry-prediction') - array = prediction.to_a - File.write('./crystalball_spec_list.txt', array.join(",")) + if args.key?('dry-run') + puts prediction.to_a exit end end From 0d9682ef5b251b43281244a0f2772591894c5c7e Mon Sep 17 00:00:00 2001 From: Feng Sha Date: Wed, 16 Sep 2020 12:13:28 -0400 Subject: [PATCH 3/4] remove testing code --- lib/crystalball/rspec/runner.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/crystalball/rspec/runner.rb b/lib/crystalball/rspec/runner.rb index 90d9165e..d16e44e0 100644 --- a/lib/crystalball/rspec/runner.rb +++ b/lib/crystalball/rspec/runner.rb @@ -26,7 +26,6 @@ def run(args, err = $stderr, out = $stdout) end def dry_run?(prediction) - puts "Testing lib import" args = Hash[ ARGV.flat_map{|s| s.scan(/--?([^=\s]+)(?:=(\S+))?/) } ] if args.key?('dry-run') puts prediction.to_a From 932d0f31299a639925b579e140d942fd1ac790dd Mon Sep 17 00:00:00 2001 From: Feng Sha Date: Tue, 22 Jun 2021 15:46:34 +0000 Subject: [PATCH 4/4] crystalball changes --- lib/crystalball.rb | 1 + .../factory_runner_patch.rb | 2 +- lib/crystalball/predictor/regex_specs.rb | 44 ++++++++++++++++++ spec/data/file1/spec1_spec.rb | 0 spec/data/file1/spec2_spec.rb | 0 spec/data/file2/spec1_spec.rb | 0 spec/data/file2/spec2_spec.rb | 0 .../factory_runner_patch_spec.rb | 4 +- spec/predictor/regex_specs_spec.rb | 45 +++++++++++++++++++ 9 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 lib/crystalball/predictor/regex_specs.rb create mode 100644 spec/data/file1/spec1_spec.rb create mode 100644 spec/data/file1/spec2_spec.rb create mode 100644 spec/data/file2/spec1_spec.rb create mode 100644 spec/data/file2/spec2_spec.rb create mode 100644 spec/predictor/regex_specs_spec.rb diff --git a/lib/crystalball.rb b/lib/crystalball.rb index 502afd97..e71c25a3 100644 --- a/lib/crystalball.rb +++ b/lib/crystalball.rb @@ -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' diff --git a/lib/crystalball/map_generator/factory_bot_strategy/factory_runner_patch.rb b/lib/crystalball/map_generator/factory_bot_strategy/factory_runner_patch.rb index cc7c18ed..ced75879 100644 --- a/lib/crystalball/map_generator/factory_bot_strategy/factory_runner_patch.rb +++ b/lib/crystalball/map_generator/factory_bot_strategy/factory_runner_patch.rb @@ -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 diff --git a/lib/crystalball/predictor/regex_specs.rb b/lib/crystalball/predictor/regex_specs.rb new file mode 100644 index 00000000..6718cc00 --- /dev/null +++ b/lib/crystalball/predictor/regex_specs.rb @@ -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 diff --git a/spec/data/file1/spec1_spec.rb b/spec/data/file1/spec1_spec.rb new file mode 100644 index 00000000..e69de29b diff --git a/spec/data/file1/spec2_spec.rb b/spec/data/file1/spec2_spec.rb new file mode 100644 index 00000000..e69de29b diff --git a/spec/data/file2/spec1_spec.rb b/spec/data/file2/spec1_spec.rb new file mode 100644 index 00000000..e69de29b diff --git a/spec/data/file2/spec2_spec.rb b/spec/data/file2/spec2_spec.rb new file mode 100644 index 00000000..e69de29b diff --git a/spec/map_generator/factory_bot_strategy/factory_runner_patch_spec.rb b/spec/map_generator/factory_bot_strategy/factory_runner_patch_spec.rb index 57f5e722..43b696d2 100644 --- a/spec/map_generator/factory_bot_strategy/factory_runner_patch_spec.rb +++ b/spec/map_generator/factory_bot_strategy/factory_runner_patch_spec.rb @@ -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 @@ -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 diff --git a/spec/predictor/regex_specs_spec.rb b/spec/predictor/regex_specs_spec.rb new file mode 100644 index 00000000..107da94b --- /dev/null +++ b/spec/predictor/regex_specs_spec.rb @@ -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/(?.*).rb}, to: 'spec/data/%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/(?.*).rb}, to: 'spec/data/%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