automatically split jsg nodes
refs DE-123 Test Plan: 1. Ensure that the number of tests run per JS job is the same 2. Ensure that test reporting works correctly flag = none Change-Id: If25f6f4fe6af6d8982b5aed62c61c65b1d4daca9 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/242341 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Ryan Norton <rnorton@instructure.com> QA-Review: Aaron Ogata <aogata@instructure.com> Product-Review: Aaron Ogata <aogata@instructure.com>
This commit is contained in:
parent
321238cfb3
commit
ff78f2bf0b
|
@ -20,11 +20,33 @@
|
||||||
|
|
||||||
library "canvas-builds-library"
|
library "canvas-builds-library"
|
||||||
|
|
||||||
|
def DEFAULT_NODE_COUNT = 1
|
||||||
|
def JSG_NODE_COUNT = 3
|
||||||
|
|
||||||
def copyFiles(dockerName, dockerPath, hostPath) {
|
def copyFiles(dockerName, dockerPath, hostPath) {
|
||||||
sh "mkdir -vp ./$hostPath"
|
sh "mkdir -vp ./$hostPath"
|
||||||
sh "docker cp \$(docker ps -qa -f name=$dockerName):/usr/src/app/$dockerPath ./$hostPath"
|
sh "docker cp \$(docker ps -qa -f name=$dockerName):/usr/src/app/$dockerPath ./$hostPath"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def makeKarmaStage(group, ciNode, ciTotal) {
|
||||||
|
return {
|
||||||
|
withEnv([
|
||||||
|
"CI_NODE_INDEX=${ciNode}",
|
||||||
|
"CI_NODE_TOTAL=${ciTotal}",
|
||||||
|
"CONTAINER_NAME=tests-karma-${group}-${ciNode}",
|
||||||
|
"JSPEC_GROUP=${group}"
|
||||||
|
]) {
|
||||||
|
try {
|
||||||
|
credentials.withSentryCredentials {
|
||||||
|
sh 'build/new-jenkins/js/tests-karma.sh'
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
copyFiles(env.CONTAINER_NAME, 'coverage-js', "./tmp/${env.CONTAINER_NAME}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pipeline {
|
pipeline {
|
||||||
agent { label 'canvas-docker' }
|
agent { label 'canvas-docker' }
|
||||||
options { ansiColor('xterm') }
|
options { ansiColor('xterm') }
|
||||||
|
@ -86,18 +108,12 @@ pipeline {
|
||||||
sh 'build/new-jenkins/js/tests-quizzes.sh'
|
sh 'build/new-jenkins/js/tests-quizzes.sh'
|
||||||
}
|
}
|
||||||
|
|
||||||
['coffee', 'jsa', 'jsg', 'jsh'].each { group ->
|
for(int i = 0; i < JSG_NODE_COUNT; i++) {
|
||||||
tests["Karma - Spec Group - ${group}"] = {
|
tests["Karma - Spec Group - jsg${i}"] = makeKarmaStage('jsg', i, JSG_NODE_COUNT)
|
||||||
withEnv(["CONTAINER_NAME=tests-karma-${group}", "JSPEC_GROUP=${group}"]) {
|
}
|
||||||
try {
|
|
||||||
credentials.withSentryCredentials {
|
['coffee', 'jsa', 'jsh'].each { group ->
|
||||||
sh 'build/new-jenkins/js/tests-karma.sh'
|
tests["Karma - Spec Group - ${group}"] = makeKarmaStage(group, 0, DEFAULT_NODE_COUNT)
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
copyFiles(env.CONTAINER_NAME, 'coverage-js', "./tmp/${env.CONTAINER_NAME}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,4 +8,4 @@ if [[ "${COVERAGE:-}" == "1" ]]; then
|
||||||
sentry="-e SENTRY_URL -e SENTRY_DSN -e SENTRY_ORG -e SENTRY_PROJECT -e SENTRY_AUTH_TOKEN -e DEPRECATION_SENTRY_DSN"
|
sentry="-e SENTRY_URL -e SENTRY_DSN -e SENTRY_ORG -e SENTRY_PROJECT -e SENTRY_AUTH_TOKEN -e DEPRECATION_SENTRY_DSN"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
docker-compose run --name $CONTAINER_NAME -e COVERAGE -e FORCE_FAILURE $sentry karma yarn test:karma:headless
|
docker-compose run --name $CONTAINER_NAME -e CI_NODE_INDEX -e CI_NODE_TOTAL -e COVERAGE -e FORCE_FAILURE $sentry karma yarn test:karma:headless
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
process.env.NODE_ENV = 'test'
|
process.env.NODE_ENV = 'test'
|
||||||
|
|
||||||
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const webpack = require('webpack')
|
const webpack = require('webpack')
|
||||||
const testWebpackConfig = require('./frontend_build/baseWebpackConfig')
|
const testWebpackConfig = require('./frontend_build/baseWebpackConfig')
|
||||||
|
@ -55,7 +56,45 @@ testWebpackConfig.plugins.push(new webpack.EnvironmentPlugin({
|
||||||
GIT_COMMIT: null
|
GIT_COMMIT: null
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
const getAllFiles = (dirPath, arrayOfFiles, callback) => {
|
||||||
|
const files = fs.readdirSync(dirPath)
|
||||||
|
|
||||||
|
files.forEach(file => {
|
||||||
|
if (fs.statSync(dirPath + '/' + file).isDirectory()) {
|
||||||
|
arrayOfFiles = getAllFiles(dirPath + '/' + file, arrayOfFiles, callback)
|
||||||
|
} else {
|
||||||
|
const filePath = callback(dirPath + '/' + file)
|
||||||
|
|
||||||
|
if (filePath) {
|
||||||
|
arrayOfFiles.push(filePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return arrayOfFiles
|
||||||
|
}
|
||||||
|
|
||||||
|
const isPartitionMatch = (resource, partitions, partitionIndex) => {
|
||||||
|
return isNaN(partitionIndex) || partitions[partitionIndex].indexOf(resource) >= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const makeSortedPartitions = (arr, partitionCount) => {
|
||||||
|
const sortedArr = arr.sort()
|
||||||
|
const sortedArrLength = sortedArr.length
|
||||||
|
const chunkSize = Math.floor(sortedArrLength / partitionCount)
|
||||||
|
const R = []
|
||||||
|
|
||||||
|
for (let i = 0; i < sortedArrLength; i += chunkSize) {
|
||||||
|
R.push(sortedArr.slice(i, i + chunkSize))
|
||||||
|
}
|
||||||
|
|
||||||
|
return R
|
||||||
|
}
|
||||||
|
|
||||||
if (process.env.JSPEC_GROUP) {
|
if (process.env.JSPEC_GROUP) {
|
||||||
|
const nodeIndex = +process.env.CI_NODE_INDEX
|
||||||
|
const nodeTotal = +process.env.CI_NODE_TOTAL
|
||||||
|
|
||||||
let ignoreResource = () => {
|
let ignoreResource = () => {
|
||||||
throw new Error(`Unknown JSPEC_GROUP ${process.env.JSPEC_GROUP}`)
|
throw new Error(`Unknown JSPEC_GROUP ${process.env.JSPEC_GROUP}`)
|
||||||
}
|
}
|
||||||
|
@ -75,13 +114,30 @@ if (process.env.JSPEC_GROUP) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (process.env.JSPEC_GROUP === 'jsg') {
|
} else if (process.env.JSPEC_GROUP === 'jsg') {
|
||||||
|
let partitions = null
|
||||||
|
|
||||||
|
if (!isNaN(nodeIndex) && !isNaN(nodeTotal)) {
|
||||||
|
const allFiles = getAllFiles(CONTEXT_JSX_SPEC, [], filePath => {
|
||||||
|
const relativePath = filePath.replace(CONTEXT_JSX_SPEC, '.').replace(/\.js$/, '')
|
||||||
|
|
||||||
|
return RESOURCE_JSG_SPLIT_SPEC.test(relativePath) ? relativePath : null
|
||||||
|
})
|
||||||
|
|
||||||
|
partitions = makeSortedPartitions(allFiles, nodeTotal)
|
||||||
|
}
|
||||||
|
|
||||||
ignoreResource = (resource, context) => {
|
ignoreResource = (resource, context) => {
|
||||||
return (
|
return (
|
||||||
context.endsWith(CONTEXT_COFFEESCRIPT_SPEC) ||
|
context.endsWith(CONTEXT_COFFEESCRIPT_SPEC) ||
|
||||||
context.endsWith(CONTEXT_EMBER_GRADEBOOK_SPEC) ||
|
context.endsWith(CONTEXT_EMBER_GRADEBOOK_SPEC) ||
|
||||||
(context.endsWith(CONTEXT_JSX_SPEC) &&
|
(context.endsWith(CONTEXT_JSX_SPEC) &&
|
||||||
RESOURCE_JSX_SPEC.test(resource) &&
|
RESOURCE_JSX_SPEC.test(resource) &&
|
||||||
!RESOURCE_JSG_SPLIT_SPEC.test(resource))
|
!RESOURCE_JSG_SPLIT_SPEC.test(resource)) ||
|
||||||
|
(partitions &&
|
||||||
|
context.endsWith(CONTEXT_JSX_SPEC) &&
|
||||||
|
RESOURCE_JSX_SPEC.test(resource) &&
|
||||||
|
RESOURCE_JSG_SPLIT_SPEC.test(resource) &&
|
||||||
|
!isPartitionMatch(resource, partitions, nodeIndex))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (process.env.JSPEC_GROUP === 'jsh') {
|
} else if (process.env.JSPEC_GROUP === 'jsh') {
|
||||||
|
|
Loading…
Reference in New Issue