Merge pull request #6586 from jhamrick/kernel-session-js

Kernel and session javascript cleanup
Matthias Bussonnier 12 years ago
commit fa11da9162

@ -106,7 +106,11 @@ class SessionHandler(IPythonHandler):
def delete(self, session_id):
# Deletes the session with given session_id
sm = self.session_manager
sm.delete_session(session_id)
try:
sm.delete_session(session_id)
except KeyError:
# the kernel was deleted but the session wasn't!
raise web.HTTPError(410, "Kernel deleted before session")
self.set_status(204)
self.finish()

@ -90,6 +90,15 @@ define([
return modal.modal(options);
};
var kernel_modal = function (options) {
// only one kernel dialog should be open at a time -- but
// other modal dialogs can still be open
$('.kernel-modal').modal('hide');
var dialog = modal(options);
dialog.addClass('kernel-modal');
return dialog;
};
var edit_metadata = function (options) {
options.name = options.name || "Cell";
var error_div = $('<div/>').css('color', 'red');
@ -153,6 +162,7 @@ define([
var dialog = {
modal : modal,
kernel_modal : kernel_modal,
edit_metadata : edit_metadata,
};

@ -523,12 +523,14 @@ define([
var ajax_error_msg = function (jqXHR) {
// Return a JSON error message if there is one,
// otherwise the basic HTTP status text.
if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
if (jqXHR.responseJSON && jqXHR.responseJSON.traceback) {
return jqXHR.responseJSON.traceback;
} else if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
return jqXHR.responseJSON.message;
} else {
return jqXHR.statusText;
}
}
};
var log_ajax_error = function (jqXHR, status, error) {
// log ajax failures with informative messages
var msg = "API request failed (" + jqXHR.status + "): ";

@ -82,10 +82,10 @@ define([
this.cell = cell;
this.editor = cell.code_mirror;
var that = this;
events.on('status_busy.Kernel', function () {
events.on('kernel_busy.Kernel', function () {
that.skip_kernel_completion = true;
});
events.on('status_idle.Kernel', function () {
events.on('kernel_idle.Kernel', function () {
that.skip_kernel_completion = false;
});
};

@ -117,7 +117,7 @@ define([
label : 'Interrupt',
icon : 'fa-stop',
callback : function () {
that.notebook.session.interrupt_kernel();
that.notebook.kernel.interrupt();
}
},
{

@ -287,7 +287,7 @@ define([
// Kernel
this.element.find('#int_kernel').click(function () {
that.notebook.session.interrupt_kernel();
that.notebook.kernel.interrupt();
});
this.element.find('#restart_kernel').click(function () {
that.notebook.restart_kernel();

@ -205,20 +205,6 @@ define([
this.events.on('command_mode.Cell', function (event, data) {
that.handle_command_mode(data.cell);
});
this.events.on('status_autorestarting.Kernel', function () {
dialog.modal({
notebook: that,
keyboard_manager: that.keyboard_manager,
title: "Kernel Restarting",
body: "The kernel appears to have died. It will restart automatically.",
buttons: {
OK : {
class : "btn-primary"
}
}
});
});
this.events.on('spec_changed.Kernel', function(event, data) {
that.set_kernelspec_metadata(data);
@ -258,7 +244,7 @@ define([
// TODO: Make killing the kernel configurable.
var kill_kernel = false;
if (kill_kernel) {
that.session.kill_kernel();
that.session.delete();
}
// if we are autosaving, trigger an autosave on nav-away.
// still warn, because if we don't the autosave may fail.
@ -1572,45 +1558,25 @@ define([
throw new session.SessionAlreadyStarting();
}
this._session_starting = true;
if (this.session !== null) {
var s = this.session;
this.session = null;
// need to start the new session in a callback after delete,
// because javascript does not guarantee the ordering of AJAX requests (?!)
s.delete(function () {
// on successful delete, start new session
that._session_starting = false;
that.start_session(kernel_name);
}, function (jqXHR, status, error) {
// log the failed delete, but still create a new session
// 404 just means it was already deleted by someone else,
// but other errors are possible.
utils.log_ajax_error(jqXHR, status, error);
that._session_starting = false;
that.start_session(kernel_name);
}
);
return;
}
this.session = new session.Session({
var options = {
base_url: this.base_url,
ws_url: this.ws_url,
notebook_path: this.notebook_path,
notebook_name: this.notebook_name,
// For now, create all sessions with the 'python' kernel, which is the
// default. Later, the user will be able to select kernels. This is
// overridden if KernelManager.kernel_cmd is specified for the server.
kernel_name: kernel_name,
notebook: this});
notebook: this
};
this.session.start(
$.proxy(this._session_started, this),
$.proxy(this._session_start_failed, this)
);
var success = $.proxy(this._session_started, this);
var failure = $.proxy(this._session_start_failed, this);
if (this.session !== null) {
this.session.restart(options, success, failure);
} else {
this.session = new session.Session(options);
this.session.start(success, failure);
}
};
@ -1654,7 +1620,7 @@ define([
"Restart" : {
"class" : "btn-danger",
"click" : function() {
that.session.restart_kernel();
that.kernel.restart();
}
}
}

@ -108,12 +108,12 @@ define([
var $modal_ind_icon = $("#modal_indicator_icon");
// Command/Edit mode
this.events.on('edit_mode.Notebook',function () {
this.events.on('edit_mode.Notebook', function () {
that.save_widget.update_document_title();
$modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode');
});
this.events.on('command_mode.Notebook',function () {
this.events.on('command_mode.Notebook', function () {
that.save_widget.update_document_title();
$modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
});
@ -121,77 +121,136 @@ define([
// Implicitly start off in Command mode, switching to Edit mode will trigger event
$modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
// Kernel events
this.events.on('status_idle.Kernel',function () {
that.save_widget.update_document_title();
$kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
// Kernel events
// this can be either kernel_created.Kernel or kernel_created.Session
this.events.on('kernel_created.Kernel kernel_created.Session', function () {
knw.info("Kernel Created", 500);
});
this.events.on('status_busy.Kernel',function () {
window.document.title='(Busy) '+window.document.title;
$kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
this.events.on('kernel_reconnecting.Kernel', function () {
knw.warning("Connecting to kernel");
});
this.events.on('status_restarting.Kernel',function () {
this.events.on('kernel_connected.Kernel', function () {
knw.info("Connected", 500);
});
this.events.on('kernel_restarting.Kernel', function () {
that.save_widget.update_document_title();
knw.set_message("Restarting kernel", 2000);
});
this.events.on('status_dead.Kernel',function () {
this.events.on('kernel_autorestarting.Kernel', function (evt, info) {
// Only show the dialog on the first restart attempt. This
// number gets tracked by the `Kernel` object and passed
// along here, because we don't want to show the user 5
// dialogs saying the same thing (which is the number of
// times it tries restarting).
if (info.attempt === 1) {
dialog.kernel_modal({
notebook: that.notebook,
keyboard_manager: that.keyboard_manager,
title: "Kernel Restarting",
body: "The kernel appears to have died. It will restart automatically.",
buttons: {
OK : {
class : "btn-primary"
}
}
});
};
that.save_widget.update_document_title();
knw.danger("Dead kernel");
$kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
});
this.events.on('status_interrupting.Kernel',function () {
this.events.on('kernel_interrupting.Kernel', function () {
knw.set_message("Interrupting kernel", 2000);
});
// Start the kernel indicator in the busy state, and send a kernel_info request.
// When the kernel_info reply arrives, the kernel is idle.
$kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
this.events.on('status_started.Kernel', function (evt, data) {
knw.info("Websockets Connected", 500);
that.events.trigger('status_busy.Kernel');
data.kernel.kernel_info(function () {
that.events.trigger('status_idle.Kernel');
});
this.events.on('kernel_disconnected.Kernel', function () {
$kernel_ind_icon
.attr('class', 'kernel_disconnected_icon')
.attr('title', 'No Connection to Kernel');
});
this.events.on('status_restart_failed.Kernel',function () {
var msg = 'The kernel has died, and the automatic restart has failed.' +
' It is possible the kernel cannot be restarted.' +
' If you are not able to restart the kernel, you will still be able to save' +
' the notebook, but running code will no longer work until the notebook' +
' is reopened.';
dialog.modal({
title: "Dead kernel",
body : msg,
keyboard_manager: that.keyboard_manager,
notebook: that.notebook,
buttons : {
"Manual Restart": {
class: "btn-danger",
click: function () {
that.events.trigger('status_restarting.Kernel');
that.notebook.start_kernel();
}
},
this.events.on('kernel_connection_failed.Kernel', function (evt, info) {
// only show the dialog if this is the first failed
// connect attempt, because the kernel will continue
// trying to reconnect and we don't want to spam the user
// with messages
if (info.attempt === 1) {
var msg = "A connection to the notebook server could not be established." +
" The notebook will continue trying to reconnect, but" +
" until it does, you will NOT be able to run code. Check your" +
" network connection or notebook server configuration.";
dialog.kernel_modal({
title: "Connection failed",
body: msg,
keyboard_manager: that.keyboard_manager,
notebook: that.notebook,
buttons : {
"OK": {}
}
});
}
});
this.events.on('kernel_killed.Kernel kernel_killed.Session', function () {
that.save_widget.update_document_title();
knw.danger("Dead kernel");
$kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
});
this.events.on('kernel_dead.Kernel', function () {
var showMsg = function () {
var msg = 'The kernel has died, and the automatic restart has failed.' +
' It is possible the kernel cannot be restarted.' +
' If you are not able to restart the kernel, you will still be able to save' +
' the notebook, but running code will no longer work until the notebook' +
' is reopened.';
dialog.kernel_modal({
title: "Dead kernel",
body : msg,
keyboard_manager: that.keyboard_manager,
notebook: that.notebook,
buttons : {
"Manual Restart": {
class: "btn-danger",
click: function () {
that.notebook.start_session();
}
},
"Don't restart": {}
}
});
}
});
return false;
};
that.save_widget.update_document_title();
knw.danger("Dead kernel", undefined, showMsg);
$kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
showMsg();
});
this.events.on('start_failed.Session',function (session, xhr, status, error) {
var full = status.responseJSON.message;
var short = status.responseJSON.short_message || 'Kernel error';
var traceback = status.responseJSON.traceback;
this.events.on('kernel_dead.Session', function (evt, info) {
var full = info.xhr.responseJSON.message;
var short = info.xhr.responseJSON.short_message || 'Kernel error';
var traceback = info.xhr.responseJSON.traceback;
var showMsg = function () {
var msg = $('<div/>').append($('<p/>').text(full));
var cm, cm_elem;
var cm, cm_elem, cm_open;
if (traceback) {
cm_elem = $('<div/>')
@ -204,14 +263,15 @@ define([
readOnly : true
});
cm.setValue(traceback);
cm_open = $.proxy(cm.refresh, cm);
}
dialog.modal({
dialog.kernel_modal({
title: "Failed to start the kernel",
body : msg,
keyboard_manager: that.keyboard_manager,
notebook: that.notebook,
open: $.proxy(cm.refresh, cm),
open: cm_open,
buttons : {
"Ok": { class: 'btn-primary' }
}
@ -225,45 +285,31 @@ define([
knw.danger(short, undefined, showMsg);
});
this.events.on('websocket_closed.Kernel', function (event, data) {
var kernel = data.kernel;
var ws_url = data.ws_url;
var early = data.early;
var msg;
this.events.on('kernel_starting.Kernel', function () {
window.document.title='(Starting) '+window.document.title;
$kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
knw.set_message("Kernel starting, please wait...");
});
$kernel_ind_icon
.attr('class', 'kernel_disconnected_icon')
.attr('title', 'No Connection to Kernel');
if (!early) {
knw.warning('Reconnecting');
setTimeout(function () {
kernel.start_channels();
}, 5000);
return;
}
console.log('WebSocket connection failed: ', ws_url);
msg = "A WebSocket connection could not be established." +
" You will NOT be able to run code. Check your" +
" network connection or notebook server configuration.";
dialog.modal({
title: "WebSocket connection failed",
body: msg,
keyboard_manager: that.keyboard_manager,
notebook: that.notebook,
buttons : {
"OK": {},
"Reconnect": {
click: function () {
knw.warning('Reconnecting');
setTimeout(function () {
kernel.start_channels();
}, 5000);
}
}
}
});
this.events.on('kernel_ready.Kernel', function () {
that.save_widget.update_document_title();
$kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
knw.info("Kernel ready", 500);
});
this.events.on('kernel_idle.Kernel', function () {
that.save_widget.update_document_title();
$kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
});
this.events.on('kernel_busy.Kernel', function () {
window.document.title='(Busy) '+window.document.title;
$kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
});
// Start the kernel indicator in the busy state, and send a kernel_info request.
// When the kernel_info reply arrives, the kernel is idle.
$kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
};
/**

@ -91,19 +91,19 @@ define([
element: "#kernel_indicator",
title: "Kernel Indicator",
placement: 'bottom',
onShow: function(tour) { events.trigger('status_idle.Kernel');},
onShow: function(tour) { events.trigger('kernel_idle.Kernel');},
content: "This is the Kernel indicator. It looks like this when the Kernel is idle."
}, {
element: "#kernel_indicator",
title: "Kernel Indicator",
placement: 'bottom',
onShow: function(tour) { events.trigger('status_busy.Kernel'); },
onShow: function(tour) { events.trigger('kernel_busy.Kernel'); },
content: "The Kernel indicator looks like this when the Kernel is busy."
}, {
element: ".fa-stop",
placement: 'bottom',
title: "Interrupting the Kernel",
onHide: function(tour) { events.trigger('status_idle.Kernel'); },
onHide: function(tour) { events.trigger('kernel_idle.Kernel'); },
content: "To cancel a computation in progress, you can click here."
}, {
element: "#notification_kernel",

File diff suppressed because it is too large Load Diff

@ -9,134 +9,313 @@ define([
], function(IPython, $, utils, kernel) {
"use strict";
var Session = function(options){
this.kernel = null;
/**
* Session object for accessing the session REST api. The session
* should be used to start kernels and then shut them down -- for
* all other operations, the kernel object should be used.
*
* Options should include:
* - notebook_name: the notebook name
* - notebook_path: the path (not including name) to the notebook
* - kernel_name: the type of kernel (e.g. python3)
* - base_url: the root url of the notebook server
* - ws_url: the url to access websockets
* - notebook: Notebook object
*
* @class Session
* @param {Object} options
*/
var Session = function (options) {
this.id = null;
this.notebook = options.notebook;
this.events = options.notebook.events;
this.name = options.notebook_name;
this.path = options.notebook_path;
this.kernel_name = options.kernel_name;
this.notebook_model = {
name: options.notebook_name,
path: options.notebook_path
};
this.kernel_model = {
id: null,
name: options.kernel_name
};
this.base_url = options.base_url;
this.ws_url = options.ws_url;
this.session_service_url = utils.url_join_encode(this.base_url, 'api/sessions');
this.session_url = null;
this.notebook = options.notebook;
this.kernel = null;
this.events = options.notebook.events;
this.bind_events();
};
Session.prototype.bind_events = function () {
var that = this;
var record_status = function (evt, info) {
console.log('Session: ' + evt.type + ' (' + info.session.id + ')');
};
this.events.on('kernel_created.Session', record_status);
this.events.on('kernel_dead.Session', record_status);
this.events.on('kernel_killed.Session', record_status);
// if the kernel dies, then also remove the session
this.events.on('kernel_dead.Kernel', function () {
that.delete();
});
};
// Public REST api functions
/**
* GET /api/sessions
*
* Get a list of the current sessions.
*
* @function list
* @param {function} [success] - function executed on ajax success
* @param {function} [error] - functon executed on ajax error
*/
Session.prototype.list = function (success, error) {
$.ajax(this.session_service_url, {
processData: false,
cache: false,
type: "GET",
dataType: "json",
success: success,
error: this._on_error(error)
});
};
/**
* POST /api/sessions
*
* Start a new session. This function can only executed once.
*
* @function start
* @param {function} [success] - function executed on ajax success
* @param {function} [error] - functon executed on ajax error
*/
Session.prototype.start = function (success, error) {
var that = this;
var model = {
notebook : {
name : this.name,
path : this.path
},
kernel : {
name : this.kernel_name
var on_success = function (data, status, xhr) {
if (!that.kernel) {
var kernel_service_url = utils.url_path_join(that.base_url, "api/kernels");
that.kernel = new kernel.Kernel(kernel_service_url, that.ws_url, that.notebook, that.kernel_model.name);
}
};
var settings = {
processData : false,
cache : false,
type : "POST",
data: JSON.stringify(model),
dataType : "json",
success : function (data, status, xhr) {
that._handle_start_success(data);
if (success) {
success(data, status, xhr);
}
},
error : function (xhr, status, err) {
that._handle_start_failure(xhr, status, err);
if (error !== undefined) {
error(xhr, status, err);
}
utils.log_ajax_error(xhr, status, err);
that.events.trigger('kernel_created.Session', {session: that, kernel: that.kernel});
that.kernel._kernel_created(data.kernel);
if (success) {
success(data, status, xhr);
}
};
var url = utils.url_join_encode(this.base_url, 'api/sessions');
$.ajax(url, settings);
};
Session.prototype.rename_notebook = function (name, path) {
this.name = name;
this.path = path;
var model = {
notebook : {
name : this.name,
path : this.path
var on_error = function (xhr, status, err) {
that.events.trigger('kernel_dead.Session', {session: that, xhr: xhr, status: status, error: err});
if (error) {
error(xhr, status, err);
}
};
var settings = {
processData : false,
cache : false,
type : "PATCH",
data: JSON.stringify(model),
dataType : "json",
error : utils.log_ajax_error,
};
var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
$.ajax(url, settings);
$.ajax(this.session_service_url, {
processData: false,
cache: false,
type: "POST",
data: JSON.stringify(this._get_model()),
dataType: "json",
success: this._on_success(on_success),
error: this._on_error(on_error)
});
};
/**
* GET /api/sessions/[:session_id]
*
* Get information about a session.
*
* @function get_info
* @param {function} [success] - function executed on ajax success
* @param {function} [error] - functon executed on ajax error
*/
Session.prototype.get_info = function (success, error) {
$.ajax(this.session_url, {
processData: false,
cache: false,
type: "GET",
dataType: "json",
success: this._on_success(success),
error: this._on_error(error)
});
};
/**
* PATCH /api/sessions/[:session_id]
*
* Rename or move a notebook. If the given name or path are
* undefined, then they will not be changed.
*
* @function rename_notebook
* @param {string} [name] - new notebook name
* @param {string} [path] - new path to notebook
* @param {function} [success] - function executed on ajax success
* @param {function} [error] - functon executed on ajax error
*/
Session.prototype.rename_notebook = function (name, path, success, error) {
if (name !== undefined) {
this.notebook_model.name = name;
}
if (path !== undefined) {
this.notebook_model.path = path;
}
$.ajax(this.session_url, {
processData: false,
cache: false,
type: "PATCH",
data: JSON.stringify(this._get_model()),
dataType: "json",
success: this._on_success(success),
error: this._on_error(error)
});
};
/**
* DELETE /api/sessions/[:session_id]
*
* Kill the kernel and shutdown the session.
*
* @function delete
* @param {function} [success] - function executed on ajax success
* @param {function} [error] - functon executed on ajax error
*/
Session.prototype.delete = function (success, error) {
var settings = {
processData : false,
cache : false,
type : "DELETE",
dataType : "json",
success : success,
error : error || utils.log_ajax_error,
};
if (this.kernel) {
this.kernel.running = false;
this.kernel.stop_channels();
this.events.trigger('kernel_killed.Session', {session: this, kernel: this.kernel});
this.kernel._kernel_dead();
}
var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
$.ajax(url, settings);
$.ajax(this.session_url, {
processData: false,
cache: false,
type: "DELETE",
dataType: "json",
success: this._on_success(success),
error: this._on_error(error)
});
};
// Kernel related things
/**
* Create the Kernel object associated with this Session.
*
* @method _handle_start_success
* Restart the session by deleting it and the starting it
* fresh. If options are given, they can include any of the
* following:
*
* - notebook_name - the name of the notebook
* - notebook_path - the path to the notebook
* - kernel_name - the name (type) of the kernel
*
* @function restart
* @param {Object} [options] - options for the new kernel
* @param {function} [success] - function executed on ajax success
* @param {function} [error] - functon executed on ajax error
*/
Session.prototype._handle_start_success = function (data, status, xhr) {
this.id = data.id;
// If we asked for 'python', the response will have 'python3' or 'python2'.
this.kernel_name = data.kernel.name;
this.events.trigger('started.Session', this);
var kernel_service_url = utils.url_path_join(this.base_url, "api/kernels");
this.kernel = new kernel.Kernel(kernel_service_url, this.ws_url, this.notebook, this.kernel_name);
this.kernel._kernel_started(data.kernel);
Session.prototype.restart = function (options, success, error) {
var that = this;
var start = function () {
if (options && options.notebook_name) {
that.notebook_model.name = options.notebook_name;
}
if (options && options.notebook_path) {
that.notebook_model.path = options.notebook_path;
}
if (options && options.kernel_name) {
that.kernel_model.name = options.kernel_name;
}
that.kernel_model.id = null;
that.start(success, error);
};
this.delete(start, start);
};
Session.prototype._handle_start_failure = function (xhr, status, error) {
this.events.trigger('start_failed.Session', [this, xhr, status, error]);
// Helper functions
/**
* Get the data model for the session, which includes the notebook
* (name and path) and kernel (name and id).
*
* @function _get_model
* @returns {Object} - the data model
*/
Session.prototype._get_model = function () {
return {
notebook: this.notebook_model,
kernel: this.kernel_model
};
};
/**
* Prompt the user to restart the IPython kernel.
*
* @method restart_kernel
* Update the data model from the given JSON object, which should
* have attributes of `id`, `notebook`, and/or `kernel`. If
* provided, the notebook data must include name and path, and the
* kernel data must include name and id.
*
* @function _update_model
* @param {Object} data - updated data model
*/
Session.prototype.restart_kernel = function () {
this.kernel.restart();
Session.prototype._update_model = function (data) {
if (data && data.id) {
this.id = data.id;
this.session_url = utils.url_join_encode(this.session_service_url, this.id);
}
if (data && data.notebook) {
this.notebook_model.name = data.notebook.name;
this.notebook_model.path = data.notebook.path;
}
if (data && data.kernel) {
this.kernel_model.name = data.kernel.name;
this.kernel_model.id = data.kernel.id;
}
};
Session.prototype.interrupt_kernel = function() {
this.kernel.interrupt();
/**
* Handle a successful AJAX request by updating the session data
* model with the response, and then optionally calling a provided
* callback.
*
* @function _on_success
* @param {function} success - callback
*/
Session.prototype._on_success = function (success) {
var that = this;
return function (data, status, xhr) {
that._update_model(data);
if (success) {
success(data, status, xhr);
}
};
};
Session.prototype.kill_kernel = function() {
this.kernel.kill();
/**
* Handle a failed AJAX request by logging the error message, and
* then optionally calling a provided callback.
*
* @function _on_error
* @param {function} error - callback
*/
Session.prototype._on_error = function (error) {
return function (xhr, status, err) {
utils.log_ajax_error(xhr, status, err);
if (error) {
error(xhr, status, err);
}
};
};
/**
* Error type indicating that the session is already starting.
*/
var SessionAlreadyStarting = function (message) {
this.name = "SessionAlreadyStarting";
this.message = (message || "");
};
SessionAlreadyStarting.prototype = Error.prototype;
// For backwards compatability.
@ -144,6 +323,6 @@ define([
return {
Session: Session,
SessionAlreadyStarting: SessionAlreadyStarting,
SessionAlreadyStarting: SessionAlreadyStarting
};
});

@ -3,20 +3,111 @@
// Kernel tests
//
casper.notebook_test(function () {
// test that the kernel is running
this.then(function () {
this.test.assert(this.kernel_running(), 'kernel is running');
});
// test list
this.thenEvaluate(function () {
IPython._kernels = null;
IPython.notebook.kernel.list(function (data) {
IPython._kernels = data;
});
});
this.waitFor(function () {
return this.evaluate(function () {
return IPython._kernels !== null;
});
});
this.then(function () {
var num_kernels = this.evaluate(function () {
return IPython._kernels.length;
});
this.test.assertEquals(num_kernels, 1, 'one kernel running');
});
// test get_info
var kernel_info = this.evaluate(function () {
return {
name: IPython.notebook.kernel.name,
id: IPython.notebook.kernel.id
};
});
this.thenEvaluate(function () {
IPython._kernel_info = null;
IPython.notebook.kernel.get_info(function (data) {
IPython._kernel_info = data;
});
});
this.waitFor(function () {
return this.evaluate(function () {
return IPython._kernel_info !== null;
});
});
this.then(function () {
var new_kernel_info = this.evaluate(function () {
return IPython._kernel_info;
});
this.test.assertEquals(kernel_info.name, new_kernel_info.name, 'kernel: name correct');
this.test.assertEquals(kernel_info.id, new_kernel_info.id, 'kernel: id correct');
});
// test interrupt
this.thenEvaluate(function () {
IPython._interrupted = false;
IPython.notebook.kernel.interrupt(function () {
IPython._interrupted = true;
});
});
this.waitFor(function () {
return this.evaluate(function () {
return IPython._interrupted;
});
});
this.then(function () {
var interrupted = this.evaluate(function () {
return IPython._interrupted;
});
this.test.assert(interrupted, 'kernel was interrupted');
});
// test restart
this.thenEvaluate(function () {
IPython.notebook.kernel.restart();
});
this.waitFor(this.kernel_disconnected);
this.wait_for_kernel_ready();
this.then(function () {
this.test.assert(this.kernel_running(), 'kernel restarted');
});
// test reconnect
this.thenEvaluate(function () {
IPython.notebook.kernel.stop_channels();
});
this.waitFor(this.kernel_disconnected);
this.thenEvaluate(function () {
IPython.notebook.kernel.reconnect();
});
this.wait_for_kernel_ready();
this.then(function () {
this.test.assert(this.kernel_running(), 'kernel reconnected');
});
// test kernel_info_request
this.evaluate(function () {
IPython.notebook.kernel.kernel_info(
function(msg){
IPython._kernel_info_response = msg;
})
});
});
this.waitFor(
function () {
return this.evaluate(function(){
return IPython._kernel_info_response;
});
});
this.then(function () {
var kernel_info_response = this.evaluate(function(){
return IPython._kernel_info_response;
@ -24,41 +115,203 @@ casper.notebook_test(function () {
this.test.assertTrue( kernel_info_response.msg_type === 'kernel_info_reply', 'Kernel info request return kernel_info_reply');
this.test.assertTrue( kernel_info_response.content !== undefined, 'Kernel_info_reply is not undefined');
});
// test kill
this.thenEvaluate(function () {
var kernel = IPython.notebook.session.kernel;
IPython._channels = [
kernel.shell_channel,
kernel.iopub_channel,
kernel.stdin_channel
];
kernel.kill();
IPython.notebook.kernel.kill();
});
this.waitFor(function () {
return this.evaluate(function(){
for (var i=0; i < IPython._channels.length; i++) {
var ws = IPython._channels[i];
if (ws.readyState !== ws.CLOSED) {
return false;
}
}
return true;
this.waitFor(this.kernel_disconnected);
this.then(function () {
this.test.assert(!this.kernel_running(), 'kernel is not running');
});
// test start
var url;
this.then(function () {
url = this.evaluate(function () {
return IPython.notebook.kernel.start();
});
});
this.then(function () {
var states = this.evaluate(function() {
var states = [];
for (var i = 0; i < IPython._channels.length; i++) {
states.push(IPython._channels[i].readyState);
}
return states;
this.test.assertEquals(url, "/api/kernels", "start url is correct");
});
this.wait_for_kernel_ready();
this.then(function () {
this.test.assert(this.kernel_running(), 'kernel is running');
});
// test start with parameters
this.thenEvaluate(function () {
IPython.notebook.kernel.kill();
});
this.waitFor(this.kernel_disconnected);
this.then(function () {
url = this.evaluate(function () {
return IPython.notebook.kernel.start({foo: "bar"});
});
for (var i = 0; i < states.length; i++) {
this.test.assertEquals(states[i], WebSocket.CLOSED,
"Kernel.kill closes websockets[" + i + "]");
});
this.then(function () {
this.test.assertEquals(url, "/api/kernels?foo=bar", "start url with params is correct");
});
this.wait_for_kernel_ready();
this.then(function () {
this.test.assert(this.kernel_running(), 'kernel is running');
});
// check for events in kill/start cycle
this.event_test(
'kill/start',
[
'kernel_killed.Kernel',
'kernel_created.Kernel',
'kernel_connected.Kernel',
'kernel_starting.Kernel',
'kernel_ready.Kernel'
],
function () {
this.thenEvaluate(function () {
IPython.notebook.kernel.kill();
});
this.waitFor(this.kernel_disconnected);
this.thenEvaluate(function () {
IPython.notebook.kernel.start();
});
}
);
// wait for any last idle/busy messages to be handled
this.wait_for_kernel_ready();
// check for events in disconnect/connect cycle
this.event_test(
'reconnect',
[
'kernel_reconnecting.Kernel',
'kernel_connected.Kernel',
],
function () {
this.thenEvaluate(function () {
IPython.notebook.kernel.stop_channels();
IPython.notebook.kernel.reconnect(1);
});
}
);
// wait for any last idle/busy messages to be handled
this.wait_for_kernel_ready();
// check for events in the restart cycle
this.event_test(
'restart',
[
'kernel_restarting.Kernel',
'kernel_created.Kernel',
'kernel_connected.Kernel',
'kernel_starting.Kernel',
'kernel_ready.Kernel'
],
function () {
this.thenEvaluate(function () {
IPython.notebook.kernel.restart();
});
}
);
// wait for any last idle/busy messages to be handled
this.wait_for_kernel_ready();
// check for events in the interrupt cycle
this.event_test(
'interrupt',
[
'kernel_interrupting.Kernel',
'kernel_busy.Kernel',
'kernel_idle.Kernel'
],
function () {
this.thenEvaluate(function () {
IPython.notebook.kernel.interrupt();
});
}
);
this.wait_for_kernel_ready();
// check for events after ws close
this.event_test(
'ws_closed_ok',
[
'kernel_disconnected.Kernel',
'kernel_reconnecting.Kernel',
'kernel_connected.Kernel',
'kernel_busy.Kernel',
'kernel_idle.Kernel'
],
function () {
this.thenEvaluate(function () {
IPython.notebook.kernel._ws_closed("", false);
});
}
);
// wait for any last idle/busy messages to be handled
this.wait_for_kernel_ready();
// check for events after ws close (error)
this.event_test(
'ws_closed_error',
[
'kernel_disconnected.Kernel',
'kernel_connection_failed.Kernel'
],
function () {
this.thenEvaluate(function () {
IPython.notebook.kernel._ws_closed("", true);
});
}
);
// start the kernel back up
this.thenEvaluate(function () {
IPython.notebook.kernel.restart();
});
this.waitFor(this.kernel_running);
this.wait_for_kernel_ready();
// test handling of autorestarting messages
this.event_test(
'autorestarting',
[
'kernel_restarting.Kernel',
'kernel_autorestarting.Kernel',
],
function () {
this.thenEvaluate(function () {
var cell = IPython.notebook.get_cell(0);
cell.set_text('import os\n' + 'os._exit(1)');
cell.execute();
});
}
);
this.wait_for_kernel_ready();
// test handling of failed restart
this.event_test(
'failed_restart',
[
'kernel_restarting.Kernel',
'kernel_autorestarting.Kernel',
'kernel_dead.Kernel'
],
function () {
this.thenEvaluate(function () {
var cell = IPython.notebook.get_cell(0);
cell.set_text("import os\n" +
"from IPython.kernel.connect import get_connection_file\n" +
"with open(get_connection_file(), 'w') as f:\n" +
" f.write('garbage')\n" +
"os._exit(1)");
cell.execute();
});
},
// need an extra-long timeout, because it needs to try
// restarting the kernel 5 times!
20000
);
});

@ -4,40 +4,179 @@
//
casper.notebook_test(function () {
this.evaluate(function () {
var kernel = IPython.notebook.session.kernel;
IPython._channels = [
kernel.shell_channel,
kernel.iopub_channel,
kernel.stdin_channel
];
IPython.notebook.session.delete();
var that = this;
var get_info = function () {
return that.evaluate(function () {
return JSON.parse(JSON.stringify(IPython.notebook.session._get_model()));
});
};
// test that the kernel is running
this.then(function () {
this.test.assert(this.kernel_running(), 'session: kernel is running');
});
// test list
this.thenEvaluate(function () {
IPython._sessions = null;
IPython.notebook.session.list(function (data) {
IPython._sessions = data;
});
});
this.waitFor(function () {
return this.evaluate(function(){
for (var i=0; i < IPython._channels.length; i++) {
var ws = IPython._channels[i];
if (ws.readyState !== ws.CLOSED) {
return false;
}
}
return true;
return this.evaluate(function () {
return IPython._sessions !== null;
});
});
this.then(function () {
var num_sessions = this.evaluate(function () {
return IPython._sessions.length;
});
this.test.assertEquals(num_sessions, 1, 'one session running');
});
// test get_info
var session_info = get_info();
this.thenEvaluate(function () {
IPython._session_info = null;
IPython.notebook.session.get_info(function (data) {
IPython._session_info = data;
});
});
this.waitFor(function () {
return this.evaluate(function () {
return IPython._session_info !== null;
});
});
this.then(function () {
var states = this.evaluate(function() {
var states = [];
for (var i = 0; i < IPython._channels.length; i++) {
states.push(IPython._channels[i].readyState);
}
return states;
var new_session_info = this.evaluate(function () {
return IPython._session_info;
});
for (var i = 0; i < states.length; i++) {
this.test.assertEquals(states[i], WebSocket.CLOSED,
"Session.delete closes websockets[" + i + "]");
}
this.test.assertEquals(session_info.notebook.name, new_session_info.notebook.name, 'session: notebook name correct');
this.test.assertEquals(session_info.notebook.path, new_session_info.notebook.path, 'session: notebook path correct');
this.test.assertEquals(session_info.kernel.name, new_session_info.kernel.name, 'session: kernel name correct');
this.test.assertEquals(session_info.kernel.id, new_session_info.kernel.id, 'session: kernel id correct');
});
// test rename_notebook
//
// TODO: the PATCH request isn't supported by phantom, so this test always
// fails, see https://github.com/ariya/phantomjs/issues/11384
// when this is fixed we can properly run this test
//
// this.thenEvaluate(function () {
// IPython._renamed = false;
// IPython.notebook.session.rename_notebook(
// "foo",
// "bar",
// function (data) {
// IPython._renamed = true;
// }
// );
// });
// this.waitFor(function () {
// return this.evaluate(function () {
// return IPython._renamed;
// });
// });
// this.then(function () {
// var info = get_info();
// this.test.assertEquals(info.notebook.name, "foo", "notebook was renamed");
// this.test.assertEquals(info.notebook.path, "bar", "notebook path was changed");
// });
// test delete
this.thenEvaluate(function () {
IPython.notebook.session.delete();
});
this.waitFor(this.kernel_disconnected);
this.then(function () {
this.test.assert(!this.kernel_running(), 'session deletes kernel');
});
// check for events when starting the session
this.event_test(
'start_session',
[
'kernel_created.Session',
'kernel_connected.Kernel',
'kernel_starting.Kernel',
'kernel_ready.Kernel'
],
function () {
this.thenEvaluate(function () {
IPython.notebook.session.start();
});
}
);
this.wait_for_kernel_ready();
// check for events when killing the session
this.event_test(
'delete_session',
['kernel_killed.Session'],
function () {
this.thenEvaluate(function () {
IPython.notebook.session.delete();
});
}
);
// check for events when restarting the session
this.event_test(
'restart_session',
[
'kernel_killed.Session',
'kernel_created.Session',
'kernel_connected.Kernel',
'kernel_starting.Kernel',
'kernel_ready.Kernel'
],
function () {
this.thenEvaluate(function () {
IPython.notebook.session.restart();
});
}
);
this.wait_for_kernel_ready();
// test handling of failed restart
this.event_test(
'failed_restart',
[
'kernel_restarting.Kernel',
'kernel_autorestarting.Kernel',
'kernel_killed.Session',
'kernel_dead.Kernel',
],
function () {
this.thenEvaluate(function () {
var cell = IPython.notebook.get_cell(0);
cell.set_text("import os\n" +
"from IPython.kernel.connect import get_connection_file\n" +
"with open(get_connection_file(), 'w') as f:\n" +
" f.write('garbage')\n" +
"os._exit(1)");
cell.execute();
});
},
// need an extra-long timeout, because it needs to try
// restarting the kernel 5 times!
20000
);
// check for events when starting a nonexistant kernel
this.event_test(
'bad_start_session',
[
'kernel_killed.Session',
'kernel_dead.Session'
],
function () {
this.thenEvaluate(function () {
IPython.notebook.session.restart({kernel_name: 'foo'});
});
}
);
});

@ -28,10 +28,10 @@ casper.open_new_notebook = function () {
this.thenEvaluate(function () {
require(['base/js/namespace', 'base/js/events'], function (IPython, events) {
events.on('status_idle.Kernel',function () {
events.on('kernel_idle.Kernel',function () {
IPython._status = 'idle';
});
events.on('status_busy.Kernel',function () {
events.on('kernel_busy.Kernel',function () {
IPython._status = 'busy';
});
});
@ -58,7 +58,29 @@ casper.page_loaded = function() {
casper.kernel_running = function() {
// Return whether or not the kernel is running.
return this.evaluate(function() {
return IPython.notebook.kernel.running;
return IPython.notebook.kernel.is_connected();
});
};
casper.kernel_disconnected = function() {
return this.evaluate(function() {
return IPython.notebook.kernel.is_fully_disconnected();
});
};
casper.wait_for_kernel_ready = function () {
this.waitFor(this.kernel_running);
this.thenEvaluate(function () {
IPython._kernel_ready = false;
IPython.notebook.kernel.kernel_info(
function () {
IPython._kernel_ready = true;
});
});
this.waitFor(function () {
return this.evaluate(function () {
return IPython._kernel_ready;
});
});
};
@ -560,6 +582,61 @@ casper.dashboard_test = function (test) {
});
};
// note that this will only work for UNIQUE events -- if you want to
// listen for the same event twice, this will not work!
casper.event_test = function (name, events, action, timeout) {
// set up handlers to listen for each of the events
this.thenEvaluate(function (events) {
var make_handler = function (event) {
return function () {
IPython._events_triggered.push(event);
IPython.notebook.events.off(event, null, IPython._event_handlers[event]);
delete IPython._event_handlers[event];
};
};
IPython._event_handlers = {};
IPython._events_triggered = [];
for (var i=0; i < events.length; i++) {
IPython._event_handlers[events[i]] = make_handler(events[i]);
IPython.notebook.events.on(events[i], IPython._event_handlers[events[i]]);
}
}, [events]);
// execute the requested action
this.then(action);
// wait for all the events to be triggered
this.waitFor(function () {
return this.evaluate(function (events) {
return IPython._events_triggered.length >= events.length;
}, [events]);
}, undefined, undefined, timeout);
// test that the events were triggered in the proper order
this.then(function () {
var triggered = this.evaluate(function () {
return IPython._events_triggered;
});
var handlers = this.evaluate(function () {
return Object.keys(IPython._event_handlers);
});
this.test.assertEquals(triggered.length, events.length, name + ': ' + events.length + ' events were triggered');
this.test.assertEquals(handlers.length, 0, name + ': all handlers triggered');
for (var i=0; i < events.length; i++) {
this.test.assertEquals(triggered[i], events[i], name + ': ' + events[i] + ' was triggered');
}
});
// turn off any remaining event listeners
this.thenEvaluate(function () {
for (var event in IPython._event_handlers) {
IPython.notebook.events.off(event, null, IPython._event_handlers[event]);
delete IPython._event_handlers[event];
}
});
};
casper.options.waitTimeout=10000;
casper.on('waitFor.timeout', function onWaitForTimeout(timeout) {
this.echo("Timeout for " + casper.get_notebook_server());

Loading…
Cancel
Save