Add file check to crystalball smoke test and move to rspecq
flag=none [skip-stages=Flakey Spec Catcher] Test-plan: - verify that minimal files are displayed for missing in the map - ensure that Test Plan junit displays ~45,000 specs run Change-Id: I516103e3d3348b4f7deaf4124044e8276dfc707e Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/288539 Reviewed-by: James Butters <jbutters@instructure.com> Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> QA-Review: Brian Watson <bwatson@instructure.com> Product-Review: Brian Watson <bwatson@instructure.com>
This commit is contained in:
parent
3a20fdd258
commit
a87301eaf7
|
@ -135,7 +135,7 @@ pipeline {
|
|||
|
||||
extendedStage("${RUN_MIGRATIONS_STAGE} (Waiting for Dependencies)").obeysAllowStages(false).waitsFor(RUN_MIGRATIONS_STAGE, 'Builder').queue(rootStages) { stageConfig, buildConfig ->
|
||||
def nestedStages = [:]
|
||||
rspecStage.createLegacyDistribution(nestedStages)
|
||||
rspecStage.createDistribution(nestedStages)
|
||||
|
||||
parallel(nestedStages)
|
||||
}
|
||||
|
@ -176,22 +176,25 @@ pipeline {
|
|||
def message = "<$env.BUILD_URL/testReport|Latest Crystalball Map Generated> - <${getResultsHTMLUrl()}|Map>\n"
|
||||
try {
|
||||
def mapSpecInfo = sh(script: """
|
||||
docker-compose run --rm \
|
||||
docker-compose run --rm \
|
||||
-v \$(pwd)/\$LOCAL_WORKDIR/crystalball_map.yml/:/usr/src/app/crystalball_map.yml \
|
||||
-v \$(pwd)/\$LOCAL_WORKDIR/build:/usr/src/app/build \
|
||||
-v \$(pwd)/\$LOCAL_WORKDIR/gems/plugins/:/usr/src/app/gems/plugins \
|
||||
-v \$(pwd)/\$LOCAL_WORKDIR/spec:/usr/src/app/spec \
|
||||
web bash -c 'ruby build/new-jenkins/crystalball_map_smoke_test.rb'
|
||||
""", returnStdout: true)
|
||||
"""
|
||||
, returnStdout: true)
|
||||
message = message + "\n" + mapSpecInfo
|
||||
// Only alert and push to s3 on periodic jobs, not ones resulting from manual tests
|
||||
if (env.CRYSTALBALL_MAP_PUSH_TO_S3 == '1' && env.GERRIT_EVENT_TYPE != 'comment-added') {
|
||||
sh 'aws s3 cp crystalball_map.yml s3://instructure-canvas-ci/'
|
||||
}
|
||||
} catch(e) {
|
||||
message = message + "\n" + 'Map Invalid!'
|
||||
message = message + "\nMap Invalid!"
|
||||
} finally {
|
||||
echo message
|
||||
slackSend channel: '#crystalball-noisy', message: message
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
cleanup {
|
||||
|
|
|
@ -19,12 +19,25 @@
|
|||
#
|
||||
|
||||
require "yaml"
|
||||
SPEC_THRESHOLD = 35_000
|
||||
|
||||
SPEC_THRESHOLD = 40_000
|
||||
|
||||
spec_count = YAML.load_file("crystalball_map.yml")[:version].split[0].to_i
|
||||
spec_files_in_map = File.read("crystalball_map.yml").split("\n").grep(/spec\.rb\[\d*\]/).map { |file| file.split("[").first.gsub(%r{^"./}, "") }.uniq
|
||||
spec_files_in_code = (Dir.glob("/usr/src/app/spec/**/*spec.rb") + Dir.glob("/usr/src/app/gems/plugins/**/spec_canvas/**/*spec.rb")).uniq.map { |file| file.gsub("/usr/src/app/", "") }
|
||||
|
||||
# Remove filtered out specs
|
||||
spec_files_in_code.reject! { |file| file.match?("(selenium/performance|instfs/selenium|contracts|force_failure)") }
|
||||
|
||||
delta_spec_files = spec_files_in_code - spec_files_in_map
|
||||
|
||||
unless delta_spec_files.empty?
|
||||
puts "*#{delta_spec_files.count} Missing Spec Files in crystalball_map.yml*"
|
||||
puts(delta_spec_files.map { |file| " - #{file}" })
|
||||
end
|
||||
|
||||
if spec_count >= SPEC_THRESHOLD
|
||||
puts "Map Contains #{spec_count} specs"
|
||||
puts "*Map Contains #{spec_count} specs*"
|
||||
else
|
||||
raise "Map Only Contains #{spec_count} Specs, but #{SPEC_THRESHOLD} required to push map"
|
||||
raise "*Map Only Contains #{spec_count} Specs, but #{SPEC_THRESHOLD} required to push map*"
|
||||
end
|
||||
|
|
|
@ -71,4 +71,6 @@ File.open("crystalball_map.yml", "w") do |file|
|
|||
end
|
||||
end
|
||||
|
||||
puts "Crystalball Map Created for #{map_body.keys.count} tests!"
|
||||
map_files = map_body.keys.map { |spec_file| spec_file.split("[").first.gsub(%r{^"./}, "") }.uniq
|
||||
|
||||
puts "Crystalball Map Created for #{map_body.keys.count} tests in #{map_files.count} files"
|
||||
|
|
|
@ -38,11 +38,16 @@ def createDistribution(nestedStages) {
|
|||
"FORCE_FAILURE=${configuration.isForceFailureSelenium() ? '1' : ''}",
|
||||
"RERUNS_RETRY=${configuration.getInteger('rspecq-max-requeues')}",
|
||||
"RSPEC_PROCESSES=${configuration.getInteger('rspecq-processes')}",
|
||||
"RSPECQ_FILE_SPLIT_THRESHOLD=${configuration.fileSplitThreshold()}",
|
||||
"RSPECQ_MAX_REQUEUES=${configuration.getInteger('rspecq-max-requeues')}",
|
||||
"RSPECQ_UPDATE_TIMINGS=${env.GERRIT_EVENT_TYPE == 'change-merged' ? '1' : '0'}",
|
||||
]
|
||||
|
||||
if(env.CRYSTALBALL_MAP == '1') {
|
||||
rspecqEnvVars = rspecqEnvVars + ['RSPECQ_FILE_SPLIT_THRESHOLD=9999', 'CRYSTALBALL_MAP=1']
|
||||
} else {
|
||||
rspecqEnvVars = rspecqEnvVars + ["RSPECQ_FILE_SPLIT_THRESHOLD=${configuration.fileSplitThreshold()}"]
|
||||
}
|
||||
|
||||
if(env.ENABLE_AXE_SELENIUM == '1') {
|
||||
rspecqEnvVars = rspecqEnvVars + ['TEST_PATTERN=^./(spec|gems/plugins/.*/spec_canvas)/selenium']
|
||||
} else {
|
||||
|
@ -66,35 +71,6 @@ def createDistribution(nestedStages) {
|
|||
}
|
||||
}
|
||||
|
||||
def createLegacyDistribution(nestedStages) {
|
||||
def setupNodeHook = this.&setupNode
|
||||
def baseEnvVars = [
|
||||
'POSTGRES_PASSWORD=sekret'
|
||||
]
|
||||
|
||||
// Used only for crystalball map generation
|
||||
def legacyNodeTotal = configuration.getInteger('selenium-ci-node-total')
|
||||
def legacyEnvVars = baseEnvVars + [
|
||||
"CI_NODE_TOTAL=$legacyNodeTotal",
|
||||
'COMPOSE_FILE=docker-compose.new-jenkins.yml:docker-compose.new-jenkins-selenium.yml',
|
||||
'EXCLUDE_TESTS=.*/(selenium/performance|instfs/selenium)',
|
||||
"FORCE_FAILURE=${configuration.isForceFailureSelenium() ? '1' : ''}",
|
||||
"RERUNS_RETRY=${configuration.getInteger('selenium-rerun-retry')}",
|
||||
"RSPEC_PROCESSES=${configuration.getInteger('selenium-processes')}",
|
||||
'TEST_PATTERN=^./(spec|gems/plugins/.*/spec_canvas)/', // Crystalball map needs to run all specs
|
||||
'CRYSTALBALL_MAP=1'
|
||||
]
|
||||
|
||||
legacyNodeTotal.times { index ->
|
||||
extendedStage("Legacy Test Set ${(index + 1).toString().padLeft(2, '0')}")
|
||||
.envVars(legacyEnvVars + ["CI_NODE_INDEX=$index"])
|
||||
.hooks([onNodeAcquired: setupNodeHook, onNodeReleasing: { tearDownNode('selenium') }])
|
||||
.nodeRequirements(RSPEC_NODE_REQUIREMENTS)
|
||||
.timeout(45)
|
||||
.queue(nestedStages, this.&runLegacySuite)
|
||||
}
|
||||
}
|
||||
|
||||
def setupNode() {
|
||||
try {
|
||||
env.AUTO_CANCELLED = env.AUTO_CANCELLED ?: ''
|
||||
|
@ -192,6 +168,7 @@ def runRspecqSuite() {
|
|||
-e COVERAGE \
|
||||
-e BUILD_NAME \
|
||||
-e BUILD_NUMBER \
|
||||
-e CRYSTALBALL_MAP \
|
||||
-e CRYSTAL_BALL_SPECS canvas bash -c \'build/new-jenkins/rspecq-tests.sh\'', label: 'Run RspecQ Tests')
|
||||
} catch (org.jenkinsci.plugins.workflow.steps.FlowInterruptedException e) {
|
||||
if (e.causes[0] instanceof org.jenkinsci.plugins.workflow.steps.TimeoutStepExecution.ExceededTimeout) {
|
||||
|
@ -218,25 +195,6 @@ def runRspecqSuite() {
|
|||
}
|
||||
}
|
||||
|
||||
def runLegacySuite() {
|
||||
try {
|
||||
sh(script: 'docker-compose exec -T -e RSPEC_PROCESSES -e ENABLE_AXE_SELENIUM -e CRYSTALBALL_MAP canvas bash -c \'build/new-jenkins/rspec-with-retries.sh\'', label: 'Run Tests')
|
||||
} catch (org.jenkinsci.plugins.workflow.steps.FlowInterruptedException e) {
|
||||
if (e.causes[0] instanceof org.jenkinsci.plugins.workflow.steps.TimeoutStepExecution.ExceededTimeout) {
|
||||
/* groovylint-disable-next-line GStringExpressionWithinString */
|
||||
sh '''#!/bin/bash
|
||||
ids=( $(docker ps -aq --filter "name=canvas_") )
|
||||
for i in "${ids[@]}"
|
||||
do
|
||||
docker exec $i bash -c "cat /usr/src/app/log/cmd_output/*.log"
|
||||
done
|
||||
'''
|
||||
}
|
||||
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
def runReporter() {
|
||||
try {
|
||||
sh(script: "docker-compose exec -e SENTRY_DSN -T canvas bundle exec rspecq \
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
#
|
||||
# Copyright (C) 2013 Instructure, Inc.
|
||||
# Copyright (C) 2022 - present Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
|
@ -19,7 +19,7 @@
|
|||
#
|
||||
|
||||
require_relative "../api_spec_helper"
|
||||
require_relative "../locked_spec"
|
||||
require_relative "../locked_examples"
|
||||
require_relative "../../lti_spec_helper"
|
||||
require_relative "../../lti2_spec_helper"
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
#
|
||||
|
||||
require_relative "../api_spec_helper"
|
||||
require_relative "../locked_spec"
|
||||
require_relative "../locked_examples"
|
||||
|
||||
require "nokogiri"
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
#
|
||||
|
||||
require_relative "../api_spec_helper"
|
||||
require_relative "../locked_spec"
|
||||
require_relative "../locked_examples"
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.include ApplicationHelper
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
require_relative "../api_spec_helper"
|
||||
require_relative "../locked_spec"
|
||||
require_relative "../locked_examples"
|
||||
|
||||
describe "Pages API", type: :request do
|
||||
include Api::V1::User
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#
|
||||
|
||||
require_relative "../../api_spec_helper"
|
||||
require_relative "../../locked_spec"
|
||||
require_relative "../../locked_examples"
|
||||
|
||||
describe Quizzes::QuizGroupsController, type: :request do
|
||||
before :once do
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
#
|
||||
|
||||
require_relative "../../api_spec_helper"
|
||||
require_relative "../../locked_spec"
|
||||
require_relative "../../locked_examples"
|
||||
require_relative "../../../file_upload_helper"
|
||||
|
||||
describe Quizzes::QuizzesApiController, type: :request do
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
require_relative "../../api_spec_helper"
|
||||
require_relative "../../locked_spec"
|
||||
require_relative "../../locked_examples"
|
||||
require_relative "../../../file_upload_helper"
|
||||
|
||||
describe QuizzesNext::QuizzesApiController, type: :request do
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
#
|
||||
|
||||
require_relative "../api_spec_helper"
|
||||
require_relative "../locked_spec"
|
||||
require_relative "../locked_examples"
|
||||
require_relative "../../lti_spec_helper"
|
||||
|
||||
describe WikiPagesApiController, type: :request do
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
require_dependency "quizzes/quiz_question/base"
|
||||
|
||||
describe Quizzes::QuizQuestion::FillInMultipleBlanksQuestion do
|
||||
let(:answer1) { { id: 1, blank_id: "blank1", text: "First", weight: 100 } }
|
||||
let(:answer2) { { id: 2, blank_id: "blank2", text: "Second", weight: 100 } }
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
require_relative "../../lib/canvas/draft_state_validations_spec"
|
||||
require_relative "../../lib/canvas/draft_state_validations_examples"
|
||||
|
||||
describe Quizzes::Quiz do
|
||||
before :once do
|
||||
|
|
|
@ -46,6 +46,7 @@ describe "taking a quiz" do
|
|||
let(:quiz) { quiz_create(course: @course) }
|
||||
|
||||
it 'automatically submits the quiz once the quiz is locked, and does not mark it "late"', priority: "1" do
|
||||
skip "Failing Crystalball DEMO-212"
|
||||
auto_submit_quiz(quiz)
|
||||
|
||||
verify_quiz_is_locked
|
||||
|
|
|
@ -305,6 +305,7 @@ describe "quizzes" do
|
|||
it "should mark dropdown questions as answered", priority: "2"
|
||||
|
||||
it "gives a student extra time if the time limit is extended", priority: "2" do
|
||||
skip "Failing Crystalball DEMO-212"
|
||||
@context = @course
|
||||
bank = @course.assessment_question_banks.create!(title: "Test Bank")
|
||||
q = quiz_model
|
||||
|
|
|
@ -52,6 +52,7 @@ if ENV["CRYSTALBALL_MAP"] == "1"
|
|||
Crystalball::MapGenerator.start! do |config|
|
||||
config.register Crystalball::MapGenerator::CoverageStrategy.new
|
||||
config.map_storage_path = "log/results/crystalball_results/#{SecureRandom.uuid}_#{ENV.fetch("PARALLEL_INDEX", "0")}_map.yml"
|
||||
config.dump_threshold = 50_000
|
||||
end
|
||||
|
||||
module Crystalball
|
||||
|
|
|
@ -262,6 +262,29 @@ module Crystalball
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
class MapGenerator
|
||||
def start!
|
||||
self.map = nil
|
||||
configuration.reset_map_storage!
|
||||
map_storage.clear!
|
||||
map_storage.dump(map.metadata.to_h)
|
||||
|
||||
strategies.reverse.each(&:after_start)
|
||||
self.started = true
|
||||
end
|
||||
|
||||
class Configuration
|
||||
def generate_unique_map_filename
|
||||
"log/results/crystalball_results/#{SecureRandom.uuid}_#{ENV.fetch("PARALLEL_INDEX", "0")}_map.yml"
|
||||
end
|
||||
|
||||
def reset_map_storage!
|
||||
self.map_storage_path = generate_unique_map_filename
|
||||
@map_storage = MapStorage::YAMLStorage.new(map_storage_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require "crystalball/rspec/runner/configuration"
|
||||
|
|
Loading…
Reference in New Issue