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

fix(client): prevent race condition in clear context #3425

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 43 additions & 23 deletions client/karma.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ function Karma (socket, iframe, opener, navigator, location, document) {
childWindow.close()
}
childWindow = opener(url)
// run context on parent element (client_with_context)
// using window.__karma__.scriptUrls to get the html element strings and load them dynamically
// run context on parent element (client_with_context)
// using window.__karma__.scriptUrls to get the html element strings and load them dynamically
} else if (url !== 'about:blank') {
var loadScript = function (idx) {
if (idx < window.__karma__.scriptUrls.length) {
Expand Down Expand Up @@ -120,14 +120,33 @@ function Karma (socket, iframe, opener, navigator, location, document) {
}
loadScript(0)
}
// run in iframe
// run in iframe
} else {
iframe.src = policy.createURL(url)
}
}

/**
* Schedules te next execution after current context is cleared
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the

* (or is directly started if context is currently not being cleared)
* @param execution {() => void}
* @see https://github.com/karma-runner/karma/issues/3424
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add

Fixes #3424 

to the PR description.

*/
this.scheduleExecution = function (execution) {
if (reloadingContext) {
// A context reload is in progress. Wait for it to complete before executing.
this.scheduledExecution = execution
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this one character member name difference is hard to read, could we use eg executionScheduled?

} else {
execution()
}
}

this.onbeforeunload = function () {
if (!reloadingContext) {
if (reloadingContext) {
reloadingContext = false
self.scheduledExecution && self.scheduledExecution()
self.scheduledExecution = undefined
} else {
// TODO(vojta): show what test (with explanation about jasmine.UPDATE_INTERVAL)
self.error('Some of your tests did a full page reload!')
}
Expand All @@ -146,8 +165,6 @@ function Karma (socket, iframe, opener, navigator, location, document) {
this.stringify = stringify

function clearContext () {
reloadingContext = true

navigateContextTo('about:blank')
}

Expand Down Expand Up @@ -234,6 +251,8 @@ function Karma (socket, iframe, opener, navigator, location, document) {
}

if (self.config.clearContext) {
reloadingContext = true

// give the browser some time to breath, there could be a page reload, but because a bunch of
// tests could run in the same event loop, we wouldn't notice.
setTimeout(function () {
Expand All @@ -259,24 +278,25 @@ function Karma (socket, iframe, opener, navigator, location, document) {
}

socket.on('execute', function (cfg) {
// reset startEmitted and reload the iframe
startEmitted = false
self.config = cfg
// if not clearing context, reloadingContext always true to prevent beforeUnload error
reloadingContext = !self.config.clearContext
navigateContextTo(constant.CONTEXT_URL)

if (self.config.clientDisplayNone) {
[].forEach.call(document.querySelectorAll('#banner, #browsers'), function (el) {
el.style.display = 'none'
})
}
self.scheduleExecution(() => {
// reset startEmitted and reload the iframe
startEmitted = false
self.config = cfg

// clear the console before run
// works only on FF (Safari, Chrome do not allow to clear console from js source)
if (window.console && window.console.clear) {
window.console.clear()
}
navigateContextTo(constant.CONTEXT_URL)

if (self.config.clientDisplayNone) {
[].forEach.call(document.querySelectorAll('#banner, #browsers'), function (el) {
el.style.display = 'none'
})
}

// clear the console before run
// works only on FF (Safari, Chrome do not allow to clear console from js source)
if (window.console && window.console.clear) {
window.console.clear()
}
})
})
socket.on('stop', function () {
this.complete()
Expand Down
73 changes: 59 additions & 14 deletions test/client/karma.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ describe('Karma', function () {
})

it('should remove reference to start even after syntax error', function () {
function ADAPTER_START_FN () {}
function ADAPTER_START_FN () { }

ck.start = ADAPTER_START_FN
ck.error('syntax error', '/some/file.js', 11)
Expand Down Expand Up @@ -129,23 +129,68 @@ describe('Karma', function () {
assert(mockWindow.onerror != null)
})

it('should error out if a script attempted to reload the browser after setup', function () {
// Perform setup
var config = ck.config = {
it('should schedule execution if clearContext is busy', function () {
// Arrange
ck.config = k.config = {
useIframe: true,
clearContext: true
}
socket.emit('execute', config)
var mockWindow = {}
const runConfig = {
useIFrame: true
}
const mockWindow = {}
ck.setupContext(mockWindow)
k.complete() // set reloading contexts

// Act
socket.emit('execute', runConfig)
ck.loaded()

// Assert
assert(!startSpy.calledWith(runConfig))
assert(!!k.scheduledExecution)
})

describe('onbeforeunload', function () {
it('should error out if a script attempted to reload the browser after setup', function () {
// Perform setup
var config = ck.config = {
clearContext: true
}

socket.emit('execute', config)
var mockWindow = {}
ck.setupContext(mockWindow)

// Spy on our error handler
sinon.spy(k, 'error')
// Spy on our error handler
sinon.spy(k, 'error')

// Emulate an unload event
mockWindow.onbeforeunload()
// Emulate an unload event
mockWindow.onbeforeunload()

// Assert our spy was called
assert(k.error.calledWith('Some of your tests did a full page reload!'))
})

// Assert our spy was called
assert(k.error.calledWith('Some of your tests did a full page reload!'))
it('should execute if an earlier execution is scheduled', function () {
// Arrange
const config = ck.config = k.config = {
useIframe: true,
clearContext: true
}
const mockWindow = {}
ck.setupContext(mockWindow)
k.complete() // set reloading contexts
socket.emit('execute', config)

// Act
mockWindow.onbeforeunload()
ck.loaded()

// Assert
assert(startSpy.calledWith(config))
assert(!k.scheduledExecution)
})
})

it('should report navigator name', function () {
Expand Down Expand Up @@ -318,7 +363,7 @@ describe('Karma', function () {

var mockWindow = {
console: {
log: function () {}
log: function () { }
}
}

Expand All @@ -334,7 +379,7 @@ describe('Karma', function () {

var mockWindow = {
console: {
log: function () {}
log: function () { }
}
}

Expand Down