Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(adapter): Support spec=name URL and sharding. #243

Merged
merged 1 commit into from Jan 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 11 additions & 1 deletion README.md
Expand Up @@ -12,7 +12,6 @@

## Installation


```bash
$ npm install karma-jasmine --save-dev
$ npm install jasmine-core --save-dev
Expand Down Expand Up @@ -72,6 +71,17 @@ module.exports = function(config) {
}
```

## Debug by URL

Failing tests print a debug URL with `?spec=`. Use it with `--no_single_run`
and paste it into your browser to focus on a single failing test.

## Sharding

By setting `config.client.shardIndex` and `config.client.totalShards`, you can
run a subset of the full set of specs. Complete sharding support needs to be
done in the process that calls karma, and would need to support test result
integration across shards.
----

For more information on Karma see the [homepage].
Expand Down
164 changes: 143 additions & 21 deletions src/adapter.js
Expand Up @@ -101,6 +101,11 @@ function formatFailedStep (step) {
return relevantStackFrames.join('\n')
}

function debugUrl (description) {
// A link to re-run just one failed test case.
return window.location.origin + '/debug.html?spec=' + encodeURIComponent(description)
}

function SuiteNode (name, parent) {
this.name = name
this.parent = parent
Expand Down Expand Up @@ -215,7 +220,6 @@ function KarmaReporter (tc, jasmineEnv) {

this.specDone = function (specResult) {
var skipped = specResult.status === 'disabled' || specResult.status === 'pending' || specResult.status === 'excluded'

var result = {
fullName: specResult.fullName,
description: specResult.description,
Expand All @@ -242,6 +246,9 @@ function KarmaReporter (tc, jasmineEnv) {
for (var i = 0, l = steps.length; i < l; i++) {
result.log.push(formatFailedStep(steps[i]))
}

// Report the name of fhe failing spec so the reporter can emit a debug url.
result.debug_url = debugUrl(specResult.fullName)
}

// When failSpecWithNoExpectations is true, Jasmine will report specs without expectations as failed
Expand Down Expand Up @@ -300,15 +307,133 @@ var createRegExp = function (filter) {
return new RegExp(patternExpression, patternSwitches)
}

function getGrepSpecsToRun (clientConfig, specs) {
var grepOption = getGrepOption(clientConfig.args)
if (grepOption) {
var regExp = createRegExp(grepOption)
return filter(specs, function specFilter (spec) {
return regExp.test(spec.getFullName())
})
}
}

function parseQueryParams (location) {
johnjbarton marked this conversation as resolved.
Show resolved Hide resolved
var params = {}
if (location && Object.prototype.hasOwnProperty.call(location, 'search')) {
var pairs = location.search.substr(1).split('&')
for (var i = 0; i < pairs.length; i++) {
var keyValue = pairs[i].split('=')
params[decodeURIComponent(keyValue[0])] =
decodeURIComponent(keyValue[1])
}
}
return params
}

function getId (s) {
return s.id
}

function getSpecsByName (specs, name) {
specs = specs.filter(function (s) {
return s.name === name
})
if (specs.length === 0) {
throw new Error('No spec found with name: "' + name + '"')
}
return specs
}

function getDebugSpecToRun (location, specs) {
var queryParams = parseQueryParams(location)
var spec = queryParams.spec
if (spec) {
// A single spec has been requested by name for debugging.
return getSpecsByName(specs, spec)
}
}

function getSpecsToRunForCurrentShard (specs, shardIndex, totalShards) {
if (specs.length < totalShards) {
throw new Error(
'More shards (' + totalShards + ') than test specs (' + specs.length +
')')
}

// Just do a simple sharding strategy of dividing the number of specs
// equally.
var firstSpec = Math.floor(specs.length * shardIndex / totalShards)
var lastSpec = Math.floor(specs.length * (shardIndex + 1) / totalShards)
return specs.slice(firstSpec, lastSpec)
}

function getShardedSpecsToRun (specs, clientConfig) {
var shardIndex = clientConfig.shardIndex
var totalShards = clientConfig.totalShards
if (shardIndex != null && totalShards != null) {
// Sharded mode - Run only the subset of the specs corresponding to the
// current shard.
return getSpecsToRunForCurrentShard(
specs, Number(shardIndex), Number(totalShards))
}
}

/**
* Create jasmine spec filter
* @param {Object} options Spec filter options
* @param {Object} clientConfig karma config
* @param {!Object} jasmineEnv
*/
var KarmaSpecFilter = function (options) {
var filterPattern = createRegExp(options && options.filterString())
var KarmaSpecFilter = function (clientConfig, jasmineEnv) {
/**
* Walk the test suite tree depth first and collect all test specs
* @param {!Object} jasmineEnv
* @return {!Array<string>} All possible tests.
*/
function getAllSpecs (jasmineEnv) {
var specs = []
var stack = [jasmineEnv.topSuite()]
var currentNode
while ((currentNode = stack.pop())) {
if (currentNode.children) {
// jasmine.Suite
stack = stack.concat(currentNode.children)
} else if (currentNode.id) {
// jasmine.Spec
specs.unshift(currentNode)
}
}

return specs
}

/**
* Filter the specs with URL search params and config.
* @param {!Object} location property 'search' from URL.
* @param {!Object} clientConfig karma client config
* @param {!Object} jasmineEnv
* @return {!Array<string>}
*/
function getSpecsToRun (location, clientConfig, jasmineEnv) {
var specs = getAllSpecs(jasmineEnv).map(function (spec) {
spec.name = spec.getFullName()
return spec
})

if (!specs || !specs.length) {
return []
}

return getGrepSpecsToRun(clientConfig, specs) ||
getDebugSpecToRun(location, specs) ||
getShardedSpecsToRun(specs, clientConfig) ||
specs
}

this.specIdsToRun =
getSpecsToRun(window.location, clientConfig, jasmineEnv).map(getId)

this.matches = function (specName) {
return filterPattern.test(specName)
this.matches = function (spec) {
return this.specIdsToRun.indexOf(spec.id) !== -1
}
}

Expand All @@ -322,17 +447,13 @@ var KarmaSpecFilter = function (options) {
* @param {Object} jasmineEnv jasmine environment object
*/
var createSpecFilter = function (config, jasmineEnv) {
var karmaSpecFilter = new KarmaSpecFilter({
filterString: function () {
return getGrepOption(config.args)
}
})
var karmaSpecFilter = new KarmaSpecFilter(config, jasmineEnv)

var specFilter = function (spec) {
return karmaSpecFilter.matches(spec.getFullName())
return karmaSpecFilter.matches(spec)
}

jasmineEnv.configure({ specFilter: specFilter })
return specFilter
}

/**
Expand All @@ -346,18 +467,19 @@ var createSpecFilter = function (config, jasmineEnv) {
* @return {Function} Karma starter function.
*/
function createStartFn (karma, jasmineEnv) {
var clientConfig = karma.config || {}
var jasmineConfig = clientConfig.jasmine || {}
// This function will be assigned to `window.__karma__.start`:
return function () {
var clientConfig = karma.config || {}
var jasmineConfig = clientConfig.jasmine || {}

jasmineEnv = jasmineEnv || window.jasmine.getEnv()
jasmineEnv = jasmineEnv || window.jasmine.getEnv()

jasmineEnv.configure(jasmineConfig)
jasmineConfig.specFilter = createSpecFilter(clientConfig, jasmineEnv)

window.jasmine.DEFAULT_TIMEOUT_INTERVAL = jasmineConfig.timeoutInterval ||
window.jasmine.DEFAULT_TIMEOUT_INTERVAL
jasmineEnv.configure(jasmineConfig)

// This function will be assigned to `window.__karma__.start`:
return function () {
window.jasmine.DEFAULT_TIMEOUT_INTERVAL = jasmineConfig.timeoutInterval ||
window.jasmine.DEFAULT_TIMEOUT_INTERVAL
jasmineEnv.addReporter(new KarmaReporter(karma, jasmineEnv))
jasmineEnv.execute()
}
Expand Down
2 changes: 0 additions & 2 deletions src/adapter.wrapper
Expand Up @@ -2,8 +2,6 @@

%CONTENT%


createSpecFilter(window.__karma__.config, jasmine.getEnv())
window.__karma__.start = createStartFn(window.__karma__)

})(typeof window !== 'undefined' ? window : global);