Add new file crystalball predictor to enqueue complete re-run

closes OUT-4922

Test-plan:
- Get a copy of crystalball_map.yml, copy it to canvas root
- Available via #crystalball-noisy
- Look at a file that has changes under a given spec
- make changes to it, and add it to a new commit
- verify via crystalball dry-run that specs are predicted and
  printed out to crystalball_spec_list.txt
- create a new .rb file
- run crystalball dry-run again and verify that the output
  warns about a "new file detected" and that the dry-run
  produces only a "." in the crystalball_spec_list.txt file

Change-Id: Ia2a5754c02814d681f1d375f5bc7357774b08926
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/281613
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Manoel Quirino <manoel.quirino@instructure.com>
Reviewed-by: Michael Hargiss <mhargiss@instructure.com>
QA-Review: Michael Hargiss <mhargiss@instructure.com>
Product-Review: Brian Watson <bwatson@instructure.com>
This commit is contained in:
Brian Watson 2021-12-16 16:15:10 -07:00
parent 2bdee31fa0
commit f87e9e352d
2 changed files with 71 additions and 1 deletions

View File

@ -5,7 +5,7 @@ execution_map_path: './crystalball_map.yml'
# examples_limit: 1
#
# Custom prediction builder class to use. Default: 'Crystalball::RSpec::StandardPredictionBuilder'
prediction_builder_class_name: 'Crystalball::RSpec::StandardPredictionBuilder'
prediction_builder_class_name: 'Crystalball::RSpec::CanvasPredictionBuilder'
# #
# Set of requires. Usually used to require custom predictor class file. Default: []
requires:

View File

@ -22,6 +22,9 @@ require "crystalball/rspec/prediction_builder"
require "crystalball/rspec/filtering"
require "crystalball/rspec/prediction_pruning"
require "crystalball/predictor/strategy"
require "crystalball/predictor/helpers/affected_example_groups_detector"
module Crystalball
module RSpec
# Our custom RSpec runner to generate and save predictions to a file, i.e. "dry-run"
@ -127,6 +130,73 @@ module Crystalball
@options.configure(@configuration)
end
end
class CanvasPredictionBuilder < Crystalball::RSpec::PredictionBuilder
def predictor
super do |p|
p.use Crystalball::Predictor::ModifiedSpecs.new
p.use Crystalball::Predictor::ModifiedExecutionPaths.new
p.use Crystalball::Predictor::NewFiles.new
end
end
end
end
class Predictor
# Queues a total re-run if any files are added. If no new files, don't add any predictions
# Possible git operation types for SourceDiff include: ['new', 'modified', 'moved', 'deleted]
class NewFiles
include Helpers::AffectedExampleGroupsDetector
include Strategy
# @param [Crystalball::SourceDiff] diff - the diff from which to predict
# which specs should run
# @param [Crystalball::ExampleGroupMap] map - the map with the relations of
# examples and used files
# @return [Array<String>] the spec paths associated with the changes
def call(diff, map)
super do
file_change_types = diff.map { |source_diff| [source_diff.relative_path, source_diff.type] }
# Create a map of git operations to files
# Hash["new"] = ["new_file1.rb", "new_file2.rb"]
# Hash["modified"] = ["modified_file1.rb", "modified_file2.rb"]
# etc...
new_files = file_change_types.each_with_object(Hash.new { |h, k| h[k] = [] }) do |arr, change_map|
change_path = arr[0]
change_type = arr[1]
change_map[change_type] << change_path
end
if new_files["new"].count.positive?
Crystalball.log :warn, "Crystalball detected new .git files: #{new_files["new"]}"
Crystalball.log :warn, "Crystalball requesting entire suite re-run"
["."]
else
[]
end
end
end
end
end
# Override prediction mechanism based on NewFiles predictor. If we requeue an entire suite re-run
# ENV["CRYSTALBALL_TEST_SUITE_ROOT"] should point to the root of selenium specs or whatever is deemed
# relevant for a "complete crystalball-predicted run"
class Predictor
# @return [Crystalball::Prediction] list of examples which may fail
def prediction
root_suite_path = ENV["CRYSTALBALL_TEST_SUITE_ROOT"] || "."
raw_prediction = raw_prediction(diff)
prediction_list = includes_root?(raw_prediction) ? [root_suite_path] : raw_prediction
Prediction.new(filter(prediction_list))
end
private
def includes_root?(prediction_list)
prediction_list.include?(".") ||
prediction_list.include?("./.") ||
prediction_list.include?(ENV["CRYSTALBALL_TEST_SUITE_ROOT"])
end
end
end