Repository: zoofIO/flexx
Pull request #36 (2015-09-20 21:05:24)

Removed a few instances of asserts.
I changed a few of the assertions to use explicit errors in case of invalid input / checks.

I was using [Bandit](https://github.com/openstack/bandit) to check for potential vulnerabilities in a few popular Python projects and this came up as a warning:

```
>> Issue: Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
   Severity: Low   Confidence: High
   Location: flexx/webruntime/common.py:23
19	    """ Base class for all runtimes.
20	    """
21
22	    def __init__(self, **kwargs):
23	        assert 'url' in kwargs
24	        self._kwargs = kwargs
25	        self._proc = None
26	        self._streamreader = None

>> Issue: Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
   Severity: Low   Confidence: High
   Location: flexx/webruntime/xul.py:495
491	        # Get app of firefox
492	        if not 'Contents/MacOS' in xul_path:
493	            raise NotImplementedError('Cannot deal with real xulrunner yet')
494	        xul_app = op.dirname(op.dirname(op.dirname(xul_path)))
495	        assert xul_app.endswith('.app')
496
497	        # Clear destination
497	        # Clear destination
498	        if op.isdir(path):
498	        if op.isdir(path):
```

As per the warning, `assert` statements are stripped out if compiling with optimized bytecode (-O option) in Python. Here's a [StackOverflow question](http://stackoverflow.com/questions/2830358/what-are-the-implications-of-running-python-with-the-optimize-flag) with a little discussion and the original [Python docs](https://docs.python.org/2/reference/simple_stmts.html#the-assert-statement).

In these cases, they're mostly forward-looking sanity checks on the input. Still, it's best to replace asserts with other things, especially if there's a possibility that it would be compiled with the -O option. I found a number of other instances where asserts were used but these felt like the most prudent. I'll try to come back and swap the rest at a later time.

This commit also strips trailing whitespace in a few files. Apologies for the noise that adds to the PR.


Patch:
diff --git a/flexx/webruntime/common.py b/flexx/webruntime/common.py
index e4a78a5..4f1fd84 100644
--- a/flexx/webruntime/common.py
+++ b/flexx/webruntime/common.py
@@ -18,17 +18,19 @@ from ..util.icon import Icon
 class BaseRuntime(object):
     """ Base class for all runtimes.
     """
-    
+
     def __init__(self, **kwargs):
-        assert 'url' in kwargs
+        if not 'url' in kwargs:
+            raise KeyError('No url provided for runtime.')
+
         self._kwargs = kwargs
         self._proc = None
         self._streamreader = None
         atexit.register(self.close)
-        
+
         logging.info('launching %s' % self.__class__.__name__)
         self._launch()
-    
+
     def close(self):
         """ Close the runtime, or kill it if the process does not
         respond. Note that closing does not work when the runtime is a
@@ -51,7 +53,7 @@ class BaseRuntime(object):
                 self._proc.kill()
         # Discart process
         self._proc = None
-    
+
     def _start_subprocess(self, cmd, **env):
         """ For subclasses to easily launch the subprocess.
         """
@@ -59,21 +61,21 @@ class BaseRuntime(object):
         environ.update(env)
         try:
             self._proc = subprocess.Popen(cmd, env=environ,
-                                          stdout=subprocess.PIPE, 
+                                          stdout=subprocess.PIPE,
                                           stderr=subprocess.STDOUT)
         except OSError as err:  # pragma: no cover
             raise RuntimeError('Could not start runtime with command %r:\n%s' %
                                (cmd[0], str(err)))
         self._streamreader = StreamReader(self._proc)
         self._streamreader.start()
-    
+
     def _launch(self):
         raise NotImplementedError()
 
 
 class DesktopRuntime(BaseRuntime):
     """ A base class for runtimes that launch a desktop-like app.
-    
+
     Arguments:
         title (str): Text shown in title bar.
         size (tuple of ints): The size in pixels of the window.
@@ -83,9 +85,9 @@ class DesktopRuntime(BaseRuntime):
             png/ico/icns, depending on what's needed by the runtime and
             platform.
     """
-    
+
     def __init__(self, **kwargs):
-        
+
         icon = kwargs.get('icon', None)
         kwargs['icon'] = iconize(icon)
         BaseRuntime.__init__(self, **kwargs)
@@ -93,23 +95,23 @@ class DesktopRuntime(BaseRuntime):
 
 class StreamReader(threading.Thread):
     """ Reads stdout of process and log
-    
+
     This needs to be done in a separate thread because reading from a
     PYPE blocks.
     """
     def __init__(self, process):
         threading.Thread.__init__(self)
-        
+
         self._process = process
         self._exit = False
         self.setDaemon(True)
         atexit.register(self.stop)
-    
+
     def stop(self, wait=None):  # pragma: no cover
         self._exit = True
         if wait is not None:
             self.join(wait)
-    
+
     def run(self):  # pragma: no cover
         msgs = []
         while not self._exit:
@@ -127,15 +129,15 @@ class StreamReader(threading.Thread):
             msgs.append(msg)
             msgs[:-32] = []
             logging.debug('webruntime: ' + msg)
-        
+
         if self._exit:
             return  # might be interpreter shutdown, don't print
-        
+
         # Poll to get return code. Polling also helps to really
         # clean the process up
         while self._process.poll() is None:
             time.sleep(0.05)
-        
+
         # Notify
         code = self._process.poll()
         if hasattr(self._process, 'we_closed_it') and self._process.we_closed_it:
@@ -143,26 +145,26 @@ class StreamReader(threading.Thread):
         elif not code:
             logging.info('runtime process stopped')
         else:
-            logging.error('runtime process stopped (%i), stdout:\n%s' % 
+            logging.error('runtime process stopped (%i), stdout:\n%s' %
                         (code, '\n'.join(msgs)))
 
 
 def create_temp_app_dir(prefix, suffix='', cleanup=60):
     """ Create a temporary direrctory and return path
-    
+
     The directory will be named "<prefix>_<timestamp>_<pid>_<suffix>".
     Will clean up directories with the same prefix which are older than
     cleanup seconds.
     """
-    
+
     # Select main dir
     maindir = os.path.join(appdata_dir('flexx'), 'temp_apps')
     if not os.path.isdir(maindir):  # pragma: no cover
         os.mkdir(maindir)
-    
+
     prefix = prefix.strip(' _-') + '_'
     suffix = '' if not suffix else '_' + suffix.strip(' _-')
-    
+
     # Clear any old files
     for dname in os.listdir(maindir):
         if dname.startswith(prefix):
@@ -177,7 +179,7 @@ def create_temp_app_dir(prefix, suffix='', cleanup=60):
                         shutil.rmtree(dirname)
                     except (OSError, IOError):
                         pass
-    
+
     # Return new dir
     id = '%i_%i' % (time.time(), os.getpid())
     path = os.path.join(maindir, prefix + id + suffix)
@@ -191,14 +193,14 @@ def appdata_dir(appname=None, roaming=False, macAsLinux=False):
     Get the path to the application directory, where applications are allowed
     to write user specific files (e.g. configurations). For non-user specific
     data, consider using common_appdata_dir().
-    If appname is given, a subdir is appended (and created if necessary). 
+    If appname is given, a subdir is appended (and created if necessary).
     If roaming is True, will prefer a roaming directory (Windows Vista/7).
     If macAsLinux is True, will return the Linux-like location on Mac.
     """
-    
+
     # Define default user directory
     userDir = os.path.expanduser('~')
-    
+
     # Get system app data dir
     path = None
     if sys.platform.startswith('win'):
@@ -209,8 +211,8 @@ def appdata_dir(appname=None, roaming=False, macAsLinux=False):
     # On Linux and as fallback
     if not (path and os.path.isdir(path)):
         path = userDir
-    
-    # Maybe we should store things local to the executable (in case of a 
+
+    # Maybe we should store things local to the executable (in case of a
     # portable distro or a frozen application that wants to be portable)
     prefix = sys.prefix
     if getattr(sys, 'frozen', None): # See application_dir() function
@@ -226,7 +228,7 @@ def appdata_dir(appname=None, roaming=False, macAsLinux=False):
             else:
                 path = localpath
                 break
-    
+
     # Get path specific for this app
     if appname:
         if path == userDir:
@@ -234,7 +236,7 @@ def appdata_dir(appname=None, roaming=False, macAsLinux=False):
         path = os.path.join(path, appname)
         if not os.path.isdir(path):  # pragma: no cover
             os.mkdir(path)
-    
+
     # Done
     return path
 
@@ -248,7 +250,7 @@ x  xx xx x x x x
 
  xx   xxx   xxx
 x  x  x  x  x  x
-xxxx  xxx   xxx 
+xxxx  xxx   xxx
 x  x  x     x
 """
 
@@ -264,7 +266,7 @@ def default_icon():
                 if c == 'x':
                     i = (y * 16 + x) * 4
                     im[i:i+4] = [0, 0, 150, 255]
-        
+
     icon = Icon()
     icon.add(im)
     return icon
@@ -280,6 +282,6 @@ def iconize(icon):
     elif isinstance(icon, str):
         icon = Icon(icon)
     else:
-        raise ValueError('Icon must be an Icon, a filename or None, not %r' % 
+        raise ValueError('Icon must be an Icon, a filename or None, not %r' %
                          type(icon))
     return icon
diff --git a/flexx/webruntime/xul.py b/flexx/webruntime/xul.py
index 7d2ee33..3acb217 100644
--- a/flexx/webruntime/xul.py
+++ b/flexx/webruntime/xul.py
@@ -62,7 +62,7 @@ MAIN_XUL = """
 <?xml version="1.0"?>
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
 
-<window 
+<window
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
     id="{windowid}"
     title="{title}"
@@ -91,7 +91,7 @@ var thebrowser;
 function startup() {
     //thebrowser = document.createElement("browser");
     //window.document.body.appendChild(thebrowser);
-    
+
     document.getElementById("content").open = function() {
     window.alert('haha open ' + arguments[0]);
         //window.open(arguments[0], arguments[1], "chrome," + arguments[2]);
@@ -153,12 +153,12 @@ INFO_PLIST = """
 
 def get_firefox_exe():
     """ Get the path of the Firefox executable
-    
+
     If the path could not be found, returns None. You may still be able
     to launch using "firefox" though.
     """
     paths = []
-    
+
     # Collect possible locations
     if sys.platform.startswith('win'):
         for basepath in ('C:\\Program Files\\', 'C:\\Program Files (x86)\\'):
@@ -172,7 +172,7 @@ def get_firefox_exe():
         paths.append('/usr/lib64/iceweasel/iceweasel')
     elif sys.platform.startswith('darwin'):
         paths.append('/Applications/Firefox.app/Contents/MacOS/firefox')
-    
+
     # Try location until we find one that exists
     for path in paths:
         if op.isfile(path):
@@ -186,7 +186,7 @@ def has_firefox():
     """
     if get_firefox_exe() is not None:
         return True
-    
+
     try:
         version = subprocess.check_output(['firefox', '--version'])
         return True
@@ -204,7 +204,7 @@ def copy_xul_runtime(dir1, dir2):
     t0 = time.time()
     # Get extension
     ext = '.exe' if sys.platform.startswith('win') else ''
-    # On Rasberry Pi, the xul runtime is in a (linked) subdir 
+    # On Rasberry Pi, the xul runtime is in a (linked) subdir
     if op.isdir(op.join(dir1, 'xulrunner')):
         dir1 = op.join(dir1, 'xulrunner')
     # Clear
@@ -212,7 +212,7 @@ def copy_xul_runtime(dir1, dir2):
         shutil.rmtree(dir2)
     os.mkdir(dir2)
     try:
-        # Copy all files except dirs 
+        # Copy all files except dirs
         for fname in os.listdir(dir1):
             filename1 = op.join(dir1, fname)
             filename2 = op.join(dir2, fname)
@@ -235,23 +235,23 @@ class XulRuntime(DesktopRuntime):
     """ Desktop runtime based on Mozilla's XUL framework. Xul is
     available wherever Firefox is installed, and uses same engine (Gecko).
     Requires Firefox or the Xul binaries to be installed.
-    
+
     This runtime is currently the best supported way to create a desktop
     app, with full support for title, window position, icon and custom
     process name.
     """
-    
+
     _app_count = 0
-    
+
     def _launch(self):
         XulRuntime._app_count += 1
-        
+
         self._check_compat()
-        
+
         # Temp folder to store app files
         app_path = create_temp_app_dir('xul', str(XulRuntime._app_count))
         id = op.basename(app_path).split('_', 1)[1]
-        
+
         # Set size and position
         size = self._kwargs.get('size', (640, 480))
         pos = self._kwargs.get('pos', None)
@@ -259,17 +259,17 @@ class XulRuntime(DesktopRuntime):
         windowfeatures += 'width=%i, height=%i' % (size[0], size[1])
         if pos:
             windowfeatures += ', top=%i, left=%i' % (pos[0], pos[1])
-        
+
         # More preparing
         self._kwargs['title'] = self._kwargs.get('title', 'XUL runtime')
-        
+
         # Create files for app
-        self._create_xul_app(app_path, id, windowfeatures=windowfeatures, 
+        self._create_xul_app(app_path, id, windowfeatures=windowfeatures,
                              **self._kwargs)
-        
+
         # Get executable for xul runtime (may be None)
         xul_exe = self._get_xul_runtime()
-        
+
         # Get the command to execute
         exe = None
         if xul_exe and op.isfile(op.realpath(xul_exe)):
@@ -282,26 +282,26 @@ class XulRuntime(DesktopRuntime):
                 exe = 'firefox'
             except Exception:
                 pass
-        
+
         # Final check
         if exe is None:
             raise RuntimeError('Could not find XUL runtime. '
                                'Please install firefox.')
-        
+
         # Launch
         cmd = [exe, '-app', op.join(app_path, 'application.ini')]
         #cmd.append('-jsconsole')  # for debugging
         self._start_subprocess(cmd)
-    
+
     def _check_compat(self):
         if any([name+'.QtCore' in sys.modules for name in ('PySide', 'PyQt4', 'PyQt5')]):
             logging.warn("Using Flexx' Xul runtime and Qt (PySide/PyQt4/PyQt5) "
                          "together may cause problems.")
-    
+
     def _create_xul_app(self, path, id, **kwargs):
         """ Create the files that determine the XUL app to launch.
         """
-        
+
         # Dict with all values that are injected in the file templates
         # All values can be overriden via kwargs
         D = dict(vendor='None',
@@ -310,16 +310,16 @@ class XulRuntime(DesktopRuntime):
                  buildid='1',
                  id='some.app@flexx.io',
                  windowid='xx',
-                 title='XUL app runtime', 
-                 url='http://example.com', 
+                 title='XUL app runtime',
+                 url='http://example.com',
                  windowfeatures='', )
         D.update(kwargs)
-        
+
         # Create values that need to be unique
         D['windowid'] = 'W' + id
         D['name'] = 'app_' + id
         D['id'] = 'app_' + id + '@flexx.io'
-        
+
         # Fill in arguments in file contents
         manifest_link = 'manifest chrome/chrome.manifest'
         manifest = 'content {name} content/'.format(**D)
@@ -327,19 +327,19 @@ class XulRuntime(DesktopRuntime):
         main_xul = MAIN_XUL.format(**D)
         main_js = MAIN_JS  # No format (also problematic due to braces)
         prefs_js = PREFS_JS.format(**D)
-        
+
         # Clear
         if op.isdir(path):
             shutil.rmtree(path)
-        
+
         # Create directory structure
         for subdir in ('',
-                    'chrome', 'chrome/content', 
+                    'chrome', 'chrome/content',
                     'chrome/icons', 'chrome/icons/default',
                     'defaults', 'defaults/preferences',
                     ):
             os.mkdir(op.join(path, subdir))
-        
+
         # Create files
         for fname, text in [('chrome.manifest', manifest_link),
                             ('chrome/chrome.manifest', manifest),
@@ -349,7 +349,7 @@ class XulRuntime(DesktopRuntime):
                             ('chrome/content/main.xul', main_xul),
                             ]:
             open(op.join(path, fname), 'wb').write(text.encode('utf-8'))
-        
+
         # Icon - use Icon class to write a png (Unix) and an ico (Windows)
         # The launch function ensures that there always is an icon
         if kwargs.get('icon'):
@@ -357,24 +357,24 @@ class XulRuntime(DesktopRuntime):
             icon_name = op.join(path, 'chrome/icons/default/' + D['windowid'])
             icon.write(icon_name + '.ico')
             icon.write(icon_name + '.png')
-    
+
     def _get_xul_runtime(self):
         """ Get path to executable of a xul runtime. The returned path
         is in a writable directory. On Unix/OSX it may be a symlink,
         on Windows it is a real file. Returns None if no XUL runtime
         could be found.
         """
-        
+
         # Define name of xul executable
         exe_name = 'xulrunner'
         exe_name += '.exe' if sys.platform.startswith('win') else ''
-        
+
         # Get location and version of firefox
         ff_exe = get_firefox_exe()
         ff_version = ''
         if ff_exe:
             if 'Contents/MacOS/' in ff_exe:
-                inifile = op.join(op.dirname(ff_exe), 
+                inifile = op.join(op.dirname(ff_exe),
                                   '../Resources/application.ini')
             else:
                 inifile = op.join(op.dirname(ff_exe), 'application.ini')
@@ -382,18 +382,18 @@ class XulRuntime(DesktopRuntime):
                 if line.lower().startswith('version'):
                     ff_version = 'xul_' + line.split('=')[1].strip()
                     break
-        
+
         # Get dir with runtimes and list of subdirs (that represent versions)
         xuldir = op.join(appdata_dir('flexx'), 'webruntimes')
         if not op.isdir(xuldir):
             os.mkdir(xuldir)
         dnames = [d for d in sorted(os.listdir(xuldir)) if d.startswith('xul')]
-        
+
         # Get obsolete versions, and remove them
-        obsolete = [d for d in dnames if not 
+        obsolete = [d for d in dnames if not
                     op.isfile(op.realpath(op.join(xuldir, d, exe_name)))]
         dnames = [d for d in dnames if d not in obsolete]
-        
+
         # Clean up old runtimes (do before installing new ff, because
         # we may be "updating" it)
         for dname in (obsolete + dnames[:-1]):
@@ -402,34 +402,34 @@ class XulRuntime(DesktopRuntime):
                 shutil.rmtree(op.join(xuldir, dname))
             except (OSError, IOError):
                 pass
-        
+
         # Must we install ff?
         if ff_version:
             if (not dnames) or (dnames[-1] < ff_version):
                 self._install_ff_locally(op.join(xuldir, ff_version), ff_exe)
                 dnames.append(ff_version)
-        
+
         # Return highest version or None
         if dnames:
             return op.join(xuldir, dnames[-1], exe_name)
-    
-    
+
+
     def _install_ff_locally(self, path, ff_exe):
         """ Make a local "copy" of the firefox runtime. This should put
         a 'xulrunner' executable in the given path.
-        
+
         On Windows we must make a copy, so that we can make a copy of
         the runtime exe with the name of our choice, to thereby change
         the process name and avoid taskbar grouping with firefox. On
         other systems we use symlinks. On Windows, when symlinks are
         supported (Python 3.2+, Vista+) they hardly ever work because
         the process has no sufficient privileges.
-        
+
         On OSX we will create an application "X.app", that symlinks all
         files in the firefox runtime. The symlink that we create here
         is only for reference, and does not actually work.
         """
-        
+
         if sys.platform.startswith('win'):
             # Windows: copy the whole tuntime
             copy_xul_runtime(op.dirname(ff_exe), path)
@@ -439,23 +439,23 @@ class XulRuntime(DesktopRuntime):
             stub_exe = op.join(path, 'xulrunner')
             os.symlink(ff_exe, stub_exe)
             return stub_exe
-    
-    
+
+
     def _get_app_exe(self, xul_exe, app_path):
         """ Get the executable to run our app. This should take care
         that the runtime process shows up in the task manager with the
         correct exe_name.
-        
+
         * xul_exe: the location of the xul executbale (can be a symlink)
         * app_path: the location of the xul app (the application.ini etc.)
-        
+
         """
-        
+
         if sys.platform.startswith('darwin'):
             # OSX: create an app, the name of the exe does not matter
             # much but the name to give the application does. We set
             # the latter to the title, because title and process name
-            # seem the same thing in osx. 
+            # seem the same thing in osx.
             exe = op.join(app_path, 'xulrunner.app')
             title = self._kwargs['title']
             self._osx_create_app(op.realpath(xul_exe), exe, title)
@@ -467,7 +467,7 @@ class XulRuntime(DesktopRuntime):
             exe_name, ext = op.splitext(op.basename(sys.executable))
             exe_name = exe_name + '-ui' + ext
             if sys.platform.startswith('win'):
-                # Windows: make a copy of the xulrunner executable 
+                # Windows: make a copy of the xulrunner executable
                 exe = op.join(op.dirname(xul_exe), exe_name)
                 if not op.isfile(exe):
                     shutil.copy2(xul_exe, exe)
@@ -476,34 +476,35 @@ class XulRuntime(DesktopRuntime):
                 exe = op.join(app_path, exe_name)
                 if not op.isfile(exe):
                     os.symlink(op.realpath(xul_exe), exe)
-        
+
         return exe
-    
-   
+
+
     def _osx_create_app(self, xul_path, path, title):
         """ Create osx app
-        
+
         * xul_path: path to executable of xulrunner (not the symlink)
         * path: location of the .app directory to create.
         * title: the title of the window *and* the process name
         """
-        
+
         # Get app of firefox
         if not 'Contents/MacOS' in xul_path:
             raise NotImplementedError('Cannot deal with real xulrunner yet')
         xul_app = op.dirname(op.dirname(op.dirname(xul_path)))
-        assert xul_app.endswith('.app')
-        
+        if not xul_app.endswith('.app'):
+            raise TypeError('The xulrunner application must end in .app.')
+
         # Clear destination
         if op.isdir(path):
             shutil.rmtree(path)
         os.mkdir(path)
-        
+
         # Make dir structure
         os.mkdir(op.join(path, 'Contents'))
         os.mkdir(op.join(path, 'Contents', 'MacOS'))
         os.mkdir(op.join(path, 'Contents', 'Resources'))
-        
+
         # Make a link for all the files
         for dirpath, dirnames, filenames in os.walk(xul_app):
             relpath = op.relpath(dirpath, xul_app)


Test patch:
diff --git a/flexx/webruntime/tests/test_runtimes.py b/flexx/webruntime/tests/test_runtimes.py
index 5ade2f3..d81d857 100644
--- a/flexx/webruntime/tests/test_runtimes.py
+++ b/flexx/webruntime/tests/test_runtimes.py
@@ -9,6 +9,7 @@ from flexx.util import icon
 import pytest
 from flexx.util.testing import run_tests_if_main
 
+from flexx.webruntime.common import BaseRuntime
 from flexx.webruntime import launch
 from flexx import webruntime
 
@@ -50,21 +51,21 @@ def has_qt():
 ## Misc
 
 def test_iconize():
-    
+
     # Default icon
     icn = webruntime.common.iconize(None)
     assert isinstance(icn, icon.Icon)
-    
+
     fname = os.path.join(tempfile.gettempdir(), 'flexx_testicon.ico')
     icn.write(fname)
-    
+
     # Load from file
     icn = webruntime.common.iconize(fname)
     assert isinstance(icn, icon.Icon)
-    
+
     # Load from icon (noop)
     assert webruntime.common.iconize(icn) is icn
-    
+
     # Error
     pytest.raises(ValueError, webruntime.common.iconize, [])
 
@@ -82,10 +83,10 @@ def test_qtwebkit():
 def test_xul():
     p = launch(URL, 'xul')
     assert p._proc
-    
+
     p.close()
     p.close()  # should do no harm
-    
+
 
 def test_nwjs():
     p = launch(URL, 'nwjs')
@@ -109,7 +110,7 @@ def test_nodejs():
 def test_browser():
     p = launch(URL, 'browser')
     assert p._proc is None
-    
+
 
 def test_browser_ff():
     p = launch(URL, 'browser-firefox')
@@ -126,13 +127,13 @@ def test_selenium():
     assert p._proc is None
     assert p.driver
     p.close()
-    
-    pytest.raises(ValueError, launch, URL, 'selenium') 
+
+    pytest.raises(ValueError, launch, URL, 'selenium')
 
 
 def test_unknown():
     pytest.raises(ValueError, launch, URL, 'foo')
-    
+
 
 def test_default():
     p = launch(URL)
@@ -140,4 +141,11 @@ def test_default():
     p.close()
 
 
+def test_base_runtime_must_have_url_in_kwargs():
+    with pytest.raises(KeyError) as excinfo:
+        BaseRuntime()
+
+    assert 'url' in str(excinfo.value)
+
+
 run_tests_if_main()