Repository: bokeh/bokeh

Issue:
SVG export test occasionally fails on Travis 
Fails with `OSError`, restarting once is usually enough to get it passing:

```
__________________ test_basic_script_with_multiple_svg_plots ___________________
capsys = <_pytest.capture.CaptureFixture object at 0x7f443e983950>
    @pytest.mark.unit
    @pytest.mark.selenium
    def test_basic_script_with_multiple_svg_plots(capsys):
        def run(dirname):
            with WorkingDir(dirname):
                main(["bokeh", "svg", "scatter.py"])
            out, err = capsys.readouterr()
            assert err == ""
            assert out == ""
    
            assert set(["scatter.svg", "scatter_1.svg", "scatter.py"]) == set(os.listdir(dirname))
    
        with_directory_contents({ 'scatter.py' : multi_svg_scatter_script },
>                               run)
bokeh/command/subcommands/tests/test_svg.py:160: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
bokeh/util/testing.py:190: in with_directory_contents
    return func(os.path.realpath(dirname))
bokeh/command/subcommands/tests/test_svg.py:152: in run
    main(["bokeh", "svg", "scatter.py"])
bokeh/command/bootstrap.py:72: in main
    die("ERROR:" + str(e))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
message = 'ERROR:[Errno 9] Bad file descriptor', status = 1
    def die(message, status=1):
        ''' Print an error message and exit.
    
        This function will call ``sys.exit`` with the given ``status`` and the
        process will terminate.
    
        Args:
            message (str) : error message to print
    
            status (int) : the exit status to pass to ``sys.exit``
    
        '''
        print(message, file=sys.stderr)
>       sys.exit(status)
E       SystemExit: 1
bokeh/command/util.py:33: SystemExit
----------------------------- Captured stderr call -----------------------------
ERROR:[Errno 9] Bad file descriptor
________________________ test_get_svgs_with_svg_present ________________________
    @pytest.mark.unit
    @pytest.mark.selenium
    def test_get_svgs_with_svg_present():
    
        def fix_ids(svg):
            svg = re.sub(r'id="\w{12}"', 'id="X"', svg)
            svg = re.sub(r'url\(#\w{12}\)', 'url(#X)', svg)
            return svg
    
        layout = Plot(x_range=Range1d(), y_range=Range1d(),
                      plot_height=20, plot_width=20, toolbar_location=None,
                      outline_line_color=None, border_fill_color=None,
                      background_fill_color="red", output_backend="svg")
    
        svg0 = fix_ids(bie.get_svgs(layout)[0])
    
        driver = webdriver.PhantomJS(service_log_path=os.path.devnull)
        svg1 = fix_ids(bie.get_svgs(layout)[0])
>       terminate_web_driver(driver) # Have to manually clean up the driver session
bokeh/io/tests/test_export.py:157: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
bokeh/util/api.py:190: in wrapper
    return obj(*args, **kw)
bokeh/io/export.py:276: in terminate_web_driver
    driver.quit()
../../../miniconda/lib/python2.7/site-packages/selenium/webdriver/phantomjs/webdriver.py:80: in quit
    self.service.stop()
../../../miniconda/lib/python2.7/site-packages/selenium/webdriver/common/service.py:151: in stop
    self.send_remote_shutdown_command()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
self = <selenium.webdriver.phantomjs.service.Service object at 0x7f443df4b0d0>
    def send_remote_shutdown_command(self):
        if self._cookie_temp_file:
>           os.close(self._cookie_temp_file_handle)
E           OSError: [Errno 9] Bad file descriptor
../../../miniconda/lib/python2.7/site-packages/selenium/webdriver/phantomjs/service.py:67: OSError
```
xx @mattpap 


Discussion:
This build will not pass no matter how many times I restart it:

https://travis-ci.org/bokeh/bokeh/jobs/361916517

Resolving patch:
diff --git a/bokeh/io/export.py b/bokeh/io/export.py
--- a/bokeh/io/export.py
+++ b/bokeh/io/export.py
@@ -25,6 +25,7 @@
 #-----------------------------------------------------------------------------
 
 # Standard library imports
+import os
 import io
 import signal
 from os.path import abspath, devnull
@@ -162,13 +163,11 @@ def get_screenshot_as_png(obj, driver=None, **kwargs):
                             'To use bokeh.io.export_png you need pillow ' +
                             '("conda install pillow" or "pip install pillow")')
 
-    with _tmp_file() as tmp:
-        html_path = tmp.name
-
-        save_layout_html(obj, html_path, **kwargs)
+    with _tmp_html() as tmp:
+        save_layout_html(obj, tmp.name, **kwargs)
 
         web_driver = driver if driver is not None else _create_default_webdriver()
-        web_driver.get("file:///" + html_path)
+        web_driver.get("file:///" + tmp.name)
         web_driver.maximize_window()
 
         ## resize for PhantomJS compat
@@ -193,13 +192,11 @@ def get_svgs(obj, driver=None, **kwargs):
     '''
 
     '''
-    with _tmp_file() as tmp:
-        html_path = tmp.name
-
-        save_layout_html(obj, html_path, **kwargs)
+    with _tmp_html() as tmp:
+        save_layout_html(obj, tmp.name, **kwargs)
 
         web_driver = driver if driver is not None else _create_default_webdriver()
-        web_driver.get("file:///" + html_path)
+        web_driver.get("file:///" + tmp.name)
 
         wait_until_render_complete(web_driver)
 
@@ -322,8 +319,8 @@ def _create_default_webdriver():
     phantomjs_path = detect_phantomjs()
     return webdriver.PhantomJS(executable_path=phantomjs_path, service_log_path=devnull)
 
-def _tmp_file():
-    return NamedTemporaryFile(suffix=".html")
+def _tmp_html():
+    return NamedTemporaryFile(prefix=".bokeh", suffix=".html", dir=os.getcwd())
 
 #-----------------------------------------------------------------------------
 # Code


Test patch: