From f3ebe117679fc0fe76acfdbbfeb04cae66a01e78 2014-06-04 19:10:29
From: Jonathan Frederic <jdfreder@calpoly.edu>
Date: 2014-06-04 19:10:29
Subject: [PATCH] Adde slimerjs support to JS tests

This commit contains 16 more, squashed commits:

Added slimerjs flag

Fixed some bugs with flag code

Figured out how to add arg correctly

Cleanup

Improvements to the test framework for FF

Log to see if slimmer is actually running

Added print in controller to debug

Added full logging to casperjs

Remove the special logging logic since it was caussing a failure

remove notebook.dirty = false line in favor of
overwritting onbeforeunload.

Capture output for slimerjs

Fixed iptestcontroller rebase issues

Fixed rebase issues.

Wait for notebook to load completely before testing

Fixed stdout capture for slimerjs tests.

Clean-up comments in util.js

Added slimerjs to Travis

Fixed indent .travis.yml

Comment out slimerjslauncher env var.

Removed zmq flag which doesn't work there anyways,
it was added by me in a recent commit

Fixed travis yaml,

broken because of rebase

---

diff --git a/IPython/html/tests/util.js b/IPython/html/tests/util.js
index dad8609..22287ce 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 3067ab6..5eb8d5b 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 957b8a3..bb03e38 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 ee3e0e7..5b48b0a 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.')