Add istanbul-instrumenter-loader for crystalball map
Note: Ensure that istanbul is only enabled for crystalball before this is merged closes OUT-4918 flag=none Test-plan: - crystalball map should include JS files - ensure that CRYSTALBALL_MAP isn't set to 1 in standard pre-merge Change-Id: I5ae2f32177640e3caeb77871918644890eb4ae30 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/280813 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: James Butters <jbutters@instructure.com> QA-Review: Brian Watson <bwatson@instructure.com> Product-Review: Brian Watson <bwatson@instructure.com>
This commit is contained in:
parent
00583725d3
commit
4a49800443
|
@ -3,12 +3,14 @@ COPY --chown=docker:docker --from=local/cache-helper-collect-webpack /tmp/dst ${
|
|||
|
||||
ARG JS_BUILD_NO_UGLIFY=0
|
||||
ARG RAILS_LOAD_ALL_LOCALES=0
|
||||
ARG CRYSTALBALL_MAP=0
|
||||
RUN COMPILE_ASSETS_API_DOCS=0 \
|
||||
COMPILE_ASSETS_BRAND_CONFIGS=0 \
|
||||
COMPILE_ASSETS_NPM_INSTALL=0 \
|
||||
COMPILE_ASSETS_STYLEGUIDE=0 \
|
||||
JS_BUILD_NO_UGLIFY="$JS_BUILD_NO_UGLIFY" \
|
||||
RAILS_LOAD_ALL_LOCALES="$RAILS_LOAD_ALL_LOCALES" \
|
||||
CRYSTALBALL_MAP="$CRYSTALBALL_MAP" \
|
||||
bundle exec rails canvas:compile_assets
|
||||
|
||||
FROM local/ruby-runner AS webpack-cache
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
library 'canvas-builds-library'
|
||||
loadLocalLibrary('local-lib', 'build/new-jenkins/library')
|
||||
final static RUN_MIGRATIONS_STAGE = 'Run Migrations'
|
||||
|
||||
// if the build never starts or gets into a node block, then we
|
||||
// can never load a file. and a very noisy/confusing error is thrown.
|
||||
|
@ -64,9 +65,17 @@ pipeline {
|
|||
// e.g. canvas-lms:01.123456.78-postgres-12-ruby-2.6
|
||||
PATCHSET_TAG = getPatchsetTag()
|
||||
|
||||
BASE_RUNNER_PREFIX = configuration.buildRegistryPath('base-runner')
|
||||
CASSANDRA_PREFIX = configuration.buildRegistryPath('cassandra-migrations')
|
||||
DYNAMODB_PREFIX = configuration.buildRegistryPath('dynamodb-migrations')
|
||||
KARMA_BUILDER_PREFIX = configuration.buildRegistryPath('karma-builder')
|
||||
KARMA_RUNNER_PREFIX = configuration.buildRegistryPath('karma-runner')
|
||||
LINTERS_RUNNER_PREFIX = configuration.buildRegistryPath('linters-runner')
|
||||
POSTGRES_PREFIX = configuration.buildRegistryPath('postgres-migrations')
|
||||
RUBY_RUNNER_PREFIX = configuration.buildRegistryPath('ruby-runner')
|
||||
YARN_RUNNER_PREFIX = configuration.buildRegistryPath('yarn-runner')
|
||||
WEBPACK_BUILDER_PREFIX = configuration.buildRegistryPath('webpack-builder')
|
||||
WEBPACK_CACHE_PREFIX = configuration.buildRegistryPath('webpack-cache')
|
||||
|
||||
IMAGE_CACHE_MERGE_SCOPE = configuration.gerritBranchSanitized()
|
||||
RSPEC_PROCESSES = 6
|
||||
|
@ -74,24 +83,65 @@ pipeline {
|
|||
CASSANDRA_IMAGE_TAG = "$CASSANDRA_PREFIX:$IMAGE_CACHE_MERGE_SCOPE-$RSPEC_PROCESSES"
|
||||
DYNAMODB_IMAGE_TAG = "$DYNAMODB_PREFIX:$IMAGE_CACHE_MERGE_SCOPE-$RSPEC_PROCESSES"
|
||||
POSTGRES_IMAGE_TAG = "$POSTGRES_PREFIX:$IMAGE_CACHE_MERGE_SCOPE-$RSPEC_PROCESSES"
|
||||
POSTGRES_CLIENT = configuration.postgresClient()
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Setup') {
|
||||
stage('Pre-Setup') {
|
||||
steps {
|
||||
cleanAndSetup()
|
||||
}
|
||||
}
|
||||
|
||||
stage('Parallel Run Tests') {
|
||||
stage('Crystalball Map') {
|
||||
steps {
|
||||
script {
|
||||
def stages = [:]
|
||||
def postBuildHandler = [
|
||||
onStageEnded: { stageName, stageConfig, result ->
|
||||
buildSummaryReport.addFailureRun('Main Build', currentBuild)
|
||||
}
|
||||
]
|
||||
|
||||
distribution.stashBuildScripts()
|
||||
rspecStage.createDistribution(stages)
|
||||
extendedStage('Root').hooks(postBuildHandler).obeysAllowStages(false).reportTimings(false).execute {
|
||||
def rootStages = [:]
|
||||
|
||||
parallel(stages)
|
||||
extendedStage('Builder').nodeRequirements(label: 'canvas-docker', podTemplate: null).obeysAllowStages(false).reportTimings(false).queue(rootStages) {
|
||||
extendedStage('Setup')
|
||||
.hooks(buildSummaryReportHooks.call())
|
||||
.obeysAllowStages(false)
|
||||
.timeout(2)
|
||||
.execute { setupStage() }
|
||||
|
||||
extendedStage('Build Docker Image (Pre-Merge)')
|
||||
.hooks(buildSummaryReportHooks.call())
|
||||
.obeysAllowStages(false)
|
||||
.required(configuration.isChangeMerged())
|
||||
.timeout(20)
|
||||
.execute(buildDockerImageStage.&premergeCacheImage)
|
||||
|
||||
extendedStage('Build Docker Image')
|
||||
.hooks(buildSummaryReportHooks.call())
|
||||
.obeysAllowStages(false)
|
||||
.timeout(20)
|
||||
.execute(buildDockerImageStage.&patchsetImage)
|
||||
|
||||
extendedStage(RUN_MIGRATIONS_STAGE)
|
||||
.hooks(buildSummaryReportHooks.call())
|
||||
.obeysAllowStages(false)
|
||||
.timeout(10)
|
||||
.execute { runMigrationsStage() }
|
||||
}
|
||||
|
||||
extendedStage("${RUN_MIGRATIONS_STAGE} (Waiting for Dependencies)").obeysAllowStages(false).waitsFor(RUN_MIGRATIONS_STAGE, 'Builder').queue(rootStages) { stageConfig, buildConfig ->
|
||||
def nestedStages = [:]
|
||||
rspecStage.createLegacyDistribution(nestedStages)
|
||||
|
||||
parallel(nestedStages)
|
||||
}
|
||||
|
||||
parallel(rootStages)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +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/>.
|
||||
#
|
||||
|
||||
path = "/tmp/crystalball"
|
||||
map_header = nil
|
||||
map_body = {}
|
||||
|
@ -34,7 +33,18 @@ Dir.glob("#{path}/**/*_map.yml") do |filename|
|
|||
|
||||
raise "#{spec} already has entries: #{map_body[spec]}" unless map_body[spec].nil?
|
||||
|
||||
map_body[spec] = changed_files
|
||||
# JS files will be added to the map based on the parent directory of the file only
|
||||
# TODO: we should have a flag to filter JS at this level
|
||||
changed_files.map! do |file|
|
||||
if /(\.js|\.ts|\.tsx)/.match?(file)
|
||||
# Wrap in File.dirname if we want to filter by directories
|
||||
file.gsub(%r{("|/usr/src/app/)}, "")
|
||||
else
|
||||
file
|
||||
end
|
||||
end
|
||||
|
||||
map_body[spec] = changed_files.uniq
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -71,6 +71,7 @@ BASE_RUNNER_BUILD_ARGS=(
|
|||
WEBPACK_CACHE_BUILD_ARGS=(
|
||||
--build-arg JS_BUILD_NO_UGLIFY="$JS_BUILD_NO_UGLIFY"
|
||||
--build-arg RAILS_LOAD_ALL_LOCALES="$RAILS_LOAD_ALL_LOCALES"
|
||||
--build-arg CRYSTALBALL_MAP="$CRYSTALBALL_MAP"
|
||||
)
|
||||
BASE_RUNNER_PARTS=(
|
||||
$BASE_IMAGE_ID
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
set -o errexit -o errtrace -o nounset -o pipefail -o xtrace
|
||||
|
||||
WORKSPACE=${WORKSPACE:-$(pwd)}
|
||||
CRYSTALBALL_MAP=${CRYSTALBALL_MAP:-0}
|
||||
|
||||
echo "CRYSTALBALL_MAP VALUE ${CRYSTALBALL_MAP}"
|
||||
|
||||
export CACHE_VERSION="2020-02-02.1"
|
||||
|
||||
|
|
|
@ -95,6 +95,7 @@ def jsImage() {
|
|||
"PATCHSET_TAG=${env.PATCHSET_TAG}",
|
||||
"RAILS_LOAD_ALL_LOCALES=${getRailsLoadAllLocales()}",
|
||||
"WEBPACK_BUILDER_IMAGE=${env.WEBPACK_BUILDER_IMAGE}",
|
||||
"CRYSTALBALL_MAP=${env.CRYSTALBALL_MAP}"
|
||||
]) {
|
||||
sh "./build/new-jenkins/js/docker-build.sh $KARMA_RUNNER_IMAGE"
|
||||
}
|
||||
|
@ -124,6 +125,7 @@ def premergeCacheImage() {
|
|||
"CACHE_LOAD_FALLBACK_SCOPE=${env.IMAGE_CACHE_BUILD_SCOPE}",
|
||||
"CACHE_SAVE_SCOPE=${env.IMAGE_CACHE_MERGE_SCOPE}",
|
||||
'COMPILE_ADDITIONAL_ASSETS=0',
|
||||
"CRYSTALBALL_MAP=${env.CRYSTALBALL_MAP}",
|
||||
'JS_BUILD_NO_UGLIFY=1',
|
||||
'RAILS_LOAD_ALL_LOCALES=0',
|
||||
"RUBY_RUNNER_PREFIX=${env.RUBY_RUNNER_PREFIX}",
|
||||
|
@ -164,6 +166,7 @@ def patchsetImage() {
|
|||
"CACHE_SAVE_SCOPE=${cacheScope}",
|
||||
"CACHE_UNIQUE_SCOPE=${env.IMAGE_CACHE_UNIQUE_SCOPE}",
|
||||
"COMPILE_ADDITIONAL_ASSETS=${configuration.isChangeMerged() ? 1 : 0}",
|
||||
"CRYSTALBALL_MAP=${env.CRYSTALBALL_MAP}",
|
||||
"JS_BUILD_NO_UGLIFY=${configuration.isChangeMerged() ? 0 : 1}",
|
||||
"RAILS_LOAD_ALL_LOCALES=${getRailsLoadAllLocales()}",
|
||||
"RUBY_RUNNER_PREFIX=${env.RUBY_RUNNER_PREFIX}",
|
||||
|
|
|
@ -21,6 +21,8 @@ import org.jenkinsci.plugins.workflow.steps.FlowInterruptedException
|
|||
|
||||
@Field static final SUCCESS_NOT_BUILT = [buildResult: 'SUCCESS', stageResult: 'NOT_BUILT']
|
||||
@Field static final SUCCESS_UNSTABLE = [buildResult: 'SUCCESS', stageResult: 'UNSTABLE']
|
||||
@Field static final RSPEC_NODE_REQUIREMENTS = [label: 'canvas-docker']
|
||||
|
||||
|
||||
def createDistribution(nestedStages) {
|
||||
def rspecqNodeTotal = configuration.getInteger('rspecq-ci-node-total')
|
||||
|
@ -45,6 +47,32 @@ def createDistribution(nestedStages) {
|
|||
"RSPECQ_UPDATE_TIMINGS=${env.GERRIT_EVENT_TYPE == 'change-merged' ? '1' : '0'}",
|
||||
]
|
||||
|
||||
extendedStage('RSpecQ Reporter for Rspec')
|
||||
.envVars(rspecqEnvVars)
|
||||
.hooks(buildSummaryReportHooks.call() + [onNodeAcquired: setupNodeHook])
|
||||
.nodeRequirements(RSPEC_NODE_REQUIREMENTS)
|
||||
.timeout(15)
|
||||
.queue(nestedStages, this.&runReporter)
|
||||
|
||||
rspecqNodeTotal.times { index ->
|
||||
extendedStage("RSpecQ Test Set ${(index + 1).toString().padLeft(2, '0')}")
|
||||
.envVars(rspecqEnvVars + ["CI_NODE_INDEX=$index"])
|
||||
.hooks(buildSummaryReportHooks.call() + [onNodeAcquired: setupNodeHook, onNodeReleasing: { tearDownNode('spec') }])
|
||||
.nodeRequirements(RSPEC_NODE_REQUIREMENTS)
|
||||
.timeout(15)
|
||||
.queue(nestedStages, this.&runRspecqSuite)
|
||||
}
|
||||
}
|
||||
|
||||
def createLegacyDistribution(nestedStages) {
|
||||
def setupNodeHook = this.&setupNode
|
||||
def baseEnvVars = [
|
||||
"ENABLE_AXE_SELENIUM=${env.ENABLE_AXE_SELENIUM}",
|
||||
"ENABLE_CRYSTALBALL=${env.ENABLE_CRYSTALBALL}",
|
||||
'POSTGRES_PASSWORD=sekret',
|
||||
'SELENIUM_VERSION=3.141.59-20210929'
|
||||
]
|
||||
|
||||
// Used only for crystalball map generation
|
||||
def seleniumNodeTotal = configuration.getInteger('selenium-ci-node-total')
|
||||
def seleniumEnvVars = baseEnvVars + [
|
||||
|
@ -55,35 +83,16 @@ def createDistribution(nestedStages) {
|
|||
"RERUNS_RETRY=${configuration.getInteger('selenium-rerun-retry')}",
|
||||
"RSPEC_PROCESSES=${configuration.getInteger('selenium-processes')}",
|
||||
'TEST_PATTERN=^./(spec|gems/plugins/.*/spec_canvas)/selenium',
|
||||
'CRYSTALBALL_MAP=1'
|
||||
]
|
||||
|
||||
def rspecNodeRequirements = [label: 'canvas-docker']
|
||||
|
||||
if (env.ENABLE_CRYSTALBALL != '1') {
|
||||
extendedStage('RSpecQ Reporter for Rspec')
|
||||
.envVars(rspecqEnvVars)
|
||||
.hooks(buildSummaryReportHooks.call() + [onNodeAcquired: setupNodeHook])
|
||||
.nodeRequirements(rspecNodeRequirements)
|
||||
seleniumNodeTotal.times { index ->
|
||||
extendedStage("Selenium Test Set ${(index + 1).toString().padLeft(2, '0')}")
|
||||
.envVars(seleniumEnvVars + ["CI_NODE_INDEX=$index"])
|
||||
.hooks([onNodeAcquired: setupNodeHook, onNodeReleasing: { tearDownNode('selenium') }])
|
||||
.nodeRequirements(RSPEC_NODE_REQUIREMENTS)
|
||||
.timeout(15)
|
||||
.queue(nestedStages, this.&runReporter)
|
||||
|
||||
rspecqNodeTotal.times { index ->
|
||||
extendedStage("RSpecQ Test Set ${(index + 1).toString().padLeft(2, '0')}")
|
||||
.envVars(rspecqEnvVars + ["CI_NODE_INDEX=$index"])
|
||||
.hooks(buildSummaryReportHooks.call() + [onNodeAcquired: setupNodeHook, onNodeReleasing: { tearDownNode('spec') }])
|
||||
.nodeRequirements(rspecNodeRequirements)
|
||||
.timeout(15)
|
||||
.queue(nestedStages, this.&runRspecqSuite)
|
||||
}
|
||||
} else {
|
||||
seleniumNodeTotal.times { index ->
|
||||
extendedStage("Selenium Test Set ${(index + 1).toString().padLeft(2, '0')}")
|
||||
.envVars(seleniumEnvVars + ["CI_NODE_INDEX=$index"])
|
||||
.hooks([onNodeAcquired: setupNodeHook, onNodeReleasing: { tearDownNode('selenium') }])
|
||||
.nodeRequirements(rspecNodeRequirements)
|
||||
.timeout(15)
|
||||
.queue(nestedStages, this.&runLegacySuite)
|
||||
}
|
||||
.queue(nestedStages, this.&runLegacySuite)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,7 +137,7 @@ def tearDownNode(prefix) {
|
|||
archiveArtifacts allowEmptyArchive: true, artifacts: 'tmp/coverage/**/*'
|
||||
}
|
||||
|
||||
if (env.ENABLE_CRYSTALBALL == '1') {
|
||||
if (env.CRYSTALBALL_MAP == '1') {
|
||||
sh 'build/new-jenkins/docker-copy-files.sh /usr/src/app/log/results/crystalball_results tmp/crystalball canvas_ --allow-error --clean-dir'
|
||||
sh 'ls tmp/crystalball'
|
||||
sh 'ls -R'
|
||||
|
@ -215,7 +224,7 @@ def runRspecqSuite() {
|
|||
|
||||
def runLegacySuite() {
|
||||
try {
|
||||
sh(script: 'docker-compose exec -T -e RSPEC_PROCESSES -e ENABLE_AXE_SELENIUM -e ENABLE_CRYSTALBALL canvas bash -c \'build/new-jenkins/rspec-with-retries.sh\'', label: 'Run Tests')
|
||||
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 */
|
||||
|
|
|
@ -255,7 +255,8 @@ shared_context "in-process server selenium tests" do
|
|||
browser_errors_we_dont_care_about.none? { |s| e.message.include?(s) }
|
||||
end
|
||||
|
||||
if javascript_errors.present?
|
||||
# Crystalball is going to get a few JS errors when using istanbul-instrumenter
|
||||
if javascript_errors.present? && ENV["CRYSTALBALL_MAP"] != "1"
|
||||
raise javascript_errors.map(&:message).join("\n\n")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -162,7 +162,7 @@ if ENV["ENABLE_AXE_SELENIUM"] == "1"
|
|||
end
|
||||
end
|
||||
|
||||
if ENV["ENABLE_CRYSTALBALL"] == "1"
|
||||
if ENV["CRYSTALBALL_MAP"] == "1"
|
||||
Crystalball::MapGenerator.start! do |config|
|
||||
config.register Crystalball::MapGenerator::CoverageStrategy.new
|
||||
config.map_storage_path = "log/results/crystalball_results/#{ENV.fetch("PARALLEL_INDEX", "0")}_map.yml"
|
||||
|
@ -177,6 +177,11 @@ if ENV["ENABLE_CRYSTALBALL"] == "1"
|
|||
yield example_map, example
|
||||
after = Coverage.peek_result
|
||||
example_map.push(*execution_detector.detect(before, after))
|
||||
|
||||
# rubocop:disable Specs/NoExecuteScript
|
||||
js_coverage = SeleniumDriverSetup.driver.execute_script("return window.__coverage__")&.keys&.uniq
|
||||
# rubocop:enable Specs/NoExecuteScript
|
||||
example_map.used_files.concat(js_coverage) if js_coverage
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,4 +16,36 @@
|
|||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
module.exports = require('./ui-build/webpack')
|
||||
const webpack = require('./ui-build/webpack')
|
||||
|
||||
// since istanbul-instrumenter-loader adds so much overhead, only use it when generating crystalball map
|
||||
if (process.env.CRYSTALBALL_MAP === '1') {
|
||||
const path = require('path')
|
||||
const {canvasDir} = require('./ui-build/params')
|
||||
|
||||
webpack.module.rules.unshift({
|
||||
test: /\.(js|ts|tsx)$/,
|
||||
include: [
|
||||
path.resolve(canvasDir, 'ui'),
|
||||
path.resolve(canvasDir, 'packages/jquery-kyle-menu'),
|
||||
path.resolve(canvasDir, 'packages/jquery-sticky'),
|
||||
path.resolve(canvasDir, 'packages/jquery-popover'),
|
||||
path.resolve(canvasDir, 'packages/jquery-selectmenu'),
|
||||
path.resolve(canvasDir, 'packages/mathml'),
|
||||
path.resolve(canvasDir, 'packages/persistent-array'),
|
||||
path.resolve(canvasDir, 'packages/slickgrid'),
|
||||
path.resolve(canvasDir, 'packages/with-breakpoints'),
|
||||
path.resolve(canvasDir, 'spec/javascripts/jsx'),
|
||||
path.resolve(canvasDir, 'spec/coffeescripts'),
|
||||
/gems\/plugins\/.*\/app\/(jsx|coffeescripts)\//
|
||||
],
|
||||
exclude: [/test\//, /spec/],
|
||||
use: {
|
||||
loader: 'istanbul-instrumenter-loader',
|
||||
options: {esModules: true, produceSourceMap: true}
|
||||
},
|
||||
enforce: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = webpack
|
||||
|
|
Loading…
Reference in New Issue