canvas-lms/webpack.test.config.js

287 lines
9.3 KiB
JavaScript

/*
* Copyright (C) 2018 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* 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/>.
*/
process.env.NODE_ENV = 'test'
const assert = require('assert')
const fs = require('fs')
const path = require('path')
const webpack = require('webpack')
const glob = require('glob')
const {promisify} = require('util')
const testWebpackConfig = require('./ui-build/webpack')
const CONTEXT_COFFEESCRIPT_SPEC = 'spec/coffeescripts'
const CONTEXT_EMBER_GRADEBOOK_SPEC = 'ui/features/screenreader_gradebook/ember'
const CONTEXT_JSX_SPEC = 'spec/javascripts/jsx'
const RESOURCE_COFFEESCRIPT_SPEC = /Spec$/
const RESOURCE_EMBER_GRADEBOOK_SPEC = /\.spec$/
const RESOURCE_JSX_SPEC = /Spec$/
const RESOURCE_JSA_SPLIT_SPEC = /^\.\/[a-f].*Spec$/
const RESOURCE_JSG_SPLIT_SPEC = /^\.\/g.*Spec$/
const RESOURCE_JSH_SPLIT_SPEC = /^\.\/[h-z].*Spec$/
const WEBPACK_PLUGIN_SPECS = path.resolve(__dirname, 'tmp/webpack-plugin-specs.js')
testWebpackConfig.entry = undefined
testWebpackConfig.plugins.push(
new webpack.DefinePlugin({
CONTEXT_COFFEESCRIPT_SPEC: JSON.stringify(CONTEXT_COFFEESCRIPT_SPEC),
CONTEXT_EMBER_GRADEBOOK_SPEC: JSON.stringify(CONTEXT_EMBER_GRADEBOOK_SPEC),
CONTEXT_JSX_SPEC: JSON.stringify(CONTEXT_JSX_SPEC),
RESOURCE_COFFEESCRIPT_SPEC,
RESOURCE_EMBER_GRADEBOOK_SPEC,
RESOURCE_JSX_SPEC,
WEBPACK_PLUGIN_SPECS: JSON.stringify(WEBPACK_PLUGIN_SPECS)
})
)
testWebpackConfig.plugins.push(
new webpack.EnvironmentPlugin({
JSPEC_PATH: null,
JSPEC_GROUP: null,
JSPEC_RECURSE: '1',
JSPEC_VERBOSE: '0',
A11Y_REPORT: false,
SENTRY_DSN: 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.ceil(sortedArrLength / partitionCount)
const R = []
for (let i = 0; i < sortedArrLength; i += chunkSize) {
R.push(sortedArr.slice(i, i + chunkSize))
}
assert(R.length <= partitionCount)
return R
}
if (process.env.JSPEC_GROUP) {
const nodeIndex = +process.env.CI_NODE_INDEX
const nodeTotal = +process.env.CI_NODE_TOTAL
let ignoreResource = () => {
throw new Error(`Unknown JSPEC_GROUP ${process.env.JSPEC_GROUP}`)
}
if (process.env.JSPEC_GROUP === 'coffee') {
let partitions = null
if (!isNaN(nodeIndex) && !isNaN(nodeTotal)) {
const allFiles = []
getAllFiles(CONTEXT_COFFEESCRIPT_SPEC, allFiles, filePath => {
const relativePath = filePath
.replace(CONTEXT_COFFEESCRIPT_SPEC, '.')
.replace(/\.(coffee|js)$/, '')
return RESOURCE_COFFEESCRIPT_SPEC.test(relativePath) ? relativePath : null
})
getAllFiles(CONTEXT_EMBER_GRADEBOOK_SPEC, allFiles, filePath => {
const relativePath = filePath
.replace(CONTEXT_EMBER_GRADEBOOK_SPEC, '.')
.replace(/\.(coffee|js)$/, '')
return RESOURCE_EMBER_GRADEBOOK_SPEC.test(relativePath) ? relativePath : null
})
partitions = makeSortedPartitions(allFiles, nodeTotal)
}
ignoreResource = (resource, context) => {
return (
(context.endsWith(CONTEXT_JSX_SPEC) && RESOURCE_JSX_SPEC.test(resource)) ||
(partitions &&
context.endsWith(CONTEXT_COFFEESCRIPT_SPEC) &&
RESOURCE_COFFEESCRIPT_SPEC.test(resource) &&
!isPartitionMatch(resource, partitions, nodeIndex)) ||
(partitions &&
context.endsWith(CONTEXT_EMBER_GRADEBOOK_SPEC) &&
// FIXME: Unlike the other specs, webpack is including the suffix
(resource = resource.replace(/\.(coffee|js)$/, '')) &&
RESOURCE_EMBER_GRADEBOOK_SPEC.test(resource) &&
!isPartitionMatch(resource, partitions, nodeIndex))
)
}
} else if (process.env.JSPEC_GROUP === 'jsa') {
ignoreResource = (resource, context) => {
return (
context.endsWith(CONTEXT_COFFEESCRIPT_SPEC) ||
context.endsWith(CONTEXT_EMBER_GRADEBOOK_SPEC) ||
(context.endsWith(CONTEXT_JSX_SPEC) &&
RESOURCE_JSX_SPEC.test(resource) &&
!RESOURCE_JSA_SPLIT_SPEC.test(resource))
)
}
} 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) => {
return (
context.endsWith(CONTEXT_COFFEESCRIPT_SPEC) ||
context.endsWith(CONTEXT_EMBER_GRADEBOOK_SPEC) ||
(context.endsWith(CONTEXT_JSX_SPEC) &&
RESOURCE_JSX_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') {
ignoreResource = (resource, context) => {
return (
context.endsWith(CONTEXT_COFFEESCRIPT_SPEC) ||
context.endsWith(CONTEXT_EMBER_GRADEBOOK_SPEC) ||
(context.endsWith(CONTEXT_JSX_SPEC) &&
RESOURCE_JSX_SPEC.test(resource) &&
!RESOURCE_JSH_SPLIT_SPEC.test(resource))
)
}
}
testWebpackConfig.plugins.push(
new webpack.IgnorePlugin({
checkResource: ignoreResource
})
)
}
if (process.env.SENTRY_DSN) {
const SentryCliPlugin = require('@sentry/webpack-plugin')
testWebpackConfig.plugins.push(
new SentryCliPlugin({
release: process.env.GIT_COMMIT,
include: [
path.resolve(__dirname, 'ui'),
path.resolve(__dirname, 'spec/javascripts/jsx'),
path.resolve(__dirname, 'spec/coffeescripts')
],
ignore: [path.resolve(__dirname, 'public/javascripts/translations'), /bower\//]
})
)
}
class PluginSpecsRunner {
constructor({pattern, outfile}) {
this.pattern = pattern
this.outfile = outfile
}
apply(compiler) {
compiler.hooks.beforeCompile.tapAsync('PluginSpecsRunner', (_, callback) => {
glob(this.pattern, {absolute: true}, (e, files) => {
if (e) {
return callback(e)
}
fs.writeFile(this.outfile, files.map(x => `require("${x}")`).join('\n'), 'utf8', callback)
})
})
}
}
testWebpackConfig.plugins.push(
new PluginSpecsRunner({
pattern: 'gems/plugins/*/spec_canvas/coffeescripts/**/*Spec.js',
outfile: WEBPACK_PLUGIN_SPECS
})
)
testWebpackConfig.resolve.alias[CONTEXT_EMBER_GRADEBOOK_SPEC] = path.resolve(
__dirname,
CONTEXT_EMBER_GRADEBOOK_SPEC
)
testWebpackConfig.resolve.alias[CONTEXT_COFFEESCRIPT_SPEC] = path.resolve(
__dirname,
CONTEXT_COFFEESCRIPT_SPEC
)
testWebpackConfig.resolve.alias[CONTEXT_JSX_SPEC] = path.resolve(__dirname, CONTEXT_JSX_SPEC)
testWebpackConfig.resolve.alias['spec/jsx'] = path.resolve(__dirname, 'spec/javascripts/jsx')
testWebpackConfig.resolve.alias['ui/features'] = path.resolve(__dirname, 'ui/features')
testWebpackConfig.resolve.alias['ui/ext'] = path.resolve(__dirname, 'ui/ext')
testWebpackConfig.resolve.alias['ui/boot/initializers'] = path.resolve(
__dirname,
'ui/boot/initializers'
)
testWebpackConfig.resolve.extensions.push('.coffee')
testWebpackConfig.resolve.modules.push(path.resolve(__dirname, 'spec/coffeescripts'))
testWebpackConfig.mode = 'development'
testWebpackConfig.module.rules.unshift({
test: [
/\/spec\/coffeescripts\//,
/\/spec_canvas\/coffeescripts\//,
// Some plugins use a special spec_canvas path for their specs
/\/spec\/javascripts\/jsx\//,
/\/ember\/.*\/tests\//
],
// Our spec files expect qunit's global `test`, `module`, `asyncTest` and `start` variables.
// These imports loaders make it so they are avalable as local variables
// inside of a closure, without truly making them globals.
// We should get rid of this and just change our actual source to s/test/qunit.test/ and s/module/qunit.module/
loaders: ['imports-loader?test=>QUnit.test']
})
module.exports = testWebpackConfig