diff --git a/IPython/html/tests/util.js b/IPython/html/tests/util.js index dad860936..22287ce0a 100644 --- a/IPython/html/tests/util.js +++ b/IPython/html/tests/util.js @@ -1,7 +1,6 @@ // // Utility functions for the HTML notebook's CasperJS tests. // - casper.get_notebook_server = function () { // Get the URL of a notebook server on which to run tests. port = casper.cli.get("port"); @@ -32,6 +31,15 @@ casper.open_new_notebook = function () { IPython._status = 'busy'; }); }); + + // Because of the asynchronous nature of SlimerJS (Gecko), we need to make + // sure the notebook has actually been loaded into the IPython namespace + // before running any tests. + this.waitFor(function() { + return this.evaluate(function () { + return IPython.notebook; + }); + }); }; casper.kernel_running = function kernel_running() { @@ -411,9 +419,30 @@ casper.cell_has_class = function(index, classes) { casper.notebook_test = function(test) { // Wrap a notebook test to reduce boilerplate. + // + // If you want to read parameters from the commandline, use the following + // (i.e. value=): + // if (casper.cli.options.value) { + // casper.exit(1); + // } this.open_new_notebook(); - this.then(test); + // Echo whether or not we are running this test using SlimerJS + if (this.evaluate(function(){ + return typeof InstallTrigger !== 'undefined'; // Firefox 1.0+ + })) { console.log('This test is running in SlimerJS.'); } + + // Make sure to remove the onbeforeunload callback. This callback is + // responsable for the "Are you sure you want to quit?" type messages. + // PhantomJS ignores these prompts, SlimerJS does not which causes hangs. + this.then(function(){ + this.evaluate(function(){ + window.onbeforeunload = function(){}; + }); + }); + + this.then(test); + // Kill the kernel and delete the notebook. this.shutdown_current_kernel(); // This is still broken but shouldn't be a problem for now. diff --git a/IPython/html/tests/widgets/widget.js b/IPython/html/tests/widgets/widget.js index 3067ab606..5eb8d5b76 100644 --- a/IPython/html/tests/widgets/widget.js +++ b/IPython/html/tests/widgets/widget.js @@ -1,9 +1,15 @@ var xor = function (a, b) {return !a ^ !b;}; -var isArray = function (a) {return toString.call(a) === "[object Array]" || toString.call(a) === "[object RuntimeArray]";}; +var isArray = function (a) { + try { + return Object.toString.call(a) === "[object Array]" || Object.toString.call(a) === "[object RuntimeArray]"; + } catch (e) { + return Array.isArray(a); + } +}; var recursive_compare = function(a, b) { // Recursively compare two objects. var same = true; - same = same && !xor(a instanceof Object, b instanceof Object); + same = same && !xor(a instanceof Object || typeof a == 'object', b instanceof Object || typeof b == 'object'); same = same && !xor(isArray(a), isArray(b)); if (same) { diff --git a/IPython/testing/iptest.py b/IPython/testing/iptest.py index 957b8a395..bb03e3884 100644 --- a/IPython/testing/iptest.py +++ b/IPython/testing/iptest.py @@ -148,6 +148,8 @@ have['jinja2'] = test_for('jinja2') have['requests'] = test_for('requests') have['sphinx'] = test_for('sphinx') have['casperjs'] = is_cmd_found('casperjs') +have['phantomjs'] = is_cmd_found('phantomjs') +have['slimerjs'] = is_cmd_found('slimerjs') min_zmq = (2,1,11) diff --git a/IPython/testing/iptestcontroller.py b/IPython/testing/iptestcontroller.py index ee3e0e730..5b48b0a05 100644 --- a/IPython/testing/iptestcontroller.py +++ b/IPython/testing/iptestcontroller.py @@ -20,12 +20,14 @@ import signal import sys import subprocess import time +import re from .iptest import have, test_group_names as py_test_group_names, test_sections, StreamCapturer from IPython.utils.path import compress_user from IPython.utils.py3compat import bytes_to_str from IPython.utils.sysinfo import get_sys_info from IPython.utils.tempdir import TemporaryDirectory +from IPython.nbconvert.filters.ansi import strip_ansi try: # Python >= 3.3 @@ -214,15 +216,17 @@ def all_js_groups(): class JSController(TestController): """Run CasperJS tests """ requirements = ['zmq', 'tornado', 'jinja2', 'casperjs', 'sqlite3'] - def __init__(self, section, enabled=True): + def __init__(self, section, enabled=True, engine='phantomjs'): """Create new test runner.""" TestController.__init__(self) + self.engine = engine self.section = section self.enabled = enabled + self.slimer_failure = re.compile('^FAIL.*', flags=re.MULTILINE) js_test_dir = get_js_test_dir() includes = '--includes=' + os.path.join(js_test_dir,'util.js') test_cases = os.path.join(js_test_dir, self.section[len(js_prefix):]) - self.cmd = ['casperjs', 'test', includes, test_cases] + self.cmd = ['casperjs', 'test', includes, test_cases, '--engine=%s' % self.engine] def setup(self): self.ipydir = TemporaryDirectory() @@ -240,13 +244,31 @@ class JSController(TestController): else: # don't launch tests if the server didn't start self.cmd = [sys.executable, '-c', 'raise SystemExit(1)'] + + def launch(self, buffer_output): + # If the engine is SlimerJS, we need to buffer the output because + # SlimerJS does not support exit codes, therefor CasperJS always returns + # 0 which is a false positive. + buffer_output = (self.engine == 'slimerjs') or buffer_output + super(JSController, self).launch(buffer_output=buffer_output) + + def wait(self, *pargs, **kwargs): + """Wait for the JSController to finish""" + ret = super(JSController, self).wait(*pargs, **kwargs) + # If this is a SlimerJS controller, echo the captured output. + if self.engine == 'slimerjs': + print(self.stdout) + # Return True if a failure occured. + return self.slimer_failure.search(strip_ansi(self.stdout)) + else: + return ret def print_extra_info(self): print("Running tests with notebook directory %r" % self.nbdir.name) @property def will_run(self): - return self.enabled and all(have[a] for a in self.requirements) + return self.enabled and all(have[a] for a in self.requirements + [self.engine]) def _init_server(self): "Start the notebook server in a separate process" @@ -281,7 +303,7 @@ class JSController(TestController): # get invalid JSON; it should be ready next iteration. pass else: - return + return time.sleep(0.1) print("Notebook server-info file never arrived: %s" % self.server_info_file, file=sys.stderr @@ -350,7 +372,8 @@ def prepare_controllers(options): else: js_testgroups = all_js_groups() - c_js = [JSController(name) for name in js_testgroups] + engine = 'phantomjs' if have['phantomjs'] and not options.slimerjs else 'slimerjs' + c_js = [JSController(name, engine=engine) for name in js_testgroups] c_py = [PyTestController(name, options) for name in py_testgroups] controllers = c_py + c_js @@ -455,6 +478,9 @@ def run_iptestall(options): Include slow tests, like IPython.parallel. By default, these tests aren't run. + slimerjs : bool + Use slimerjs if it's installed instead of phantomjs for casperjs tests. + xunit : bool Produce Xunit XML output. This is written to multiple foo.xunit.xml files. @@ -581,6 +607,8 @@ argparser.add_argument('testgroups', nargs='*', 'all tests.') argparser.add_argument('--all', action='store_true', help='Include slow tests not run by default.') +argparser.add_argument('--slimerjs', action='store_true', + help="Use slimerjs if it's installed instead of phantomjs for casperjs tests.") argparser.add_argument('-j', '--fast', nargs='?', const=None, default=1, type=int, help='Run test sections in parallel. This starts as many ' 'processes as you have cores, or you can specify a number.')