Repository: dask/dask

Issue:
Test failures when parallel testing
This is on Linux. To reproduce: install the xdist plugin with pip (see https://pytest.org/dev/xdist.html#installation-of-xdist-plugin) then run something like `PYTHONHASHSEED=1 py.test -n=auto --tb=short dask`.

These are the failures I get here:

```
_______________________________________________________________ test_read_text[xz-None-ascii1] ________________________________________________________________
[gw0] linux -- Python 3.5.2 /home/antoine/miniconda3/envs/dask35/bin/python
dask/bag/tests/test_text.py:41: in test_read_text
    L, = compute(b)
dask/base.py:171: in compute
    results = get(dsk, keys, **kwargs)
dask/async.py:549: in get_sync
    raise_on_exception=True, **kwargs)
dask/async.py:478: in get_async
    fire_task()
dask/async.py:474: in fire_task
    callback=queue.put)
dask/async.py:535: in apply_sync
    res = func(*args, **kwds)
dask/async.py:266: in execute_task
    result = _execute_task(task, data)
dask/async.py:247: in _execute_task
    return func(*args2)
../miniconda3/envs/dask35/lib/python3.5/lzma.py:210: in read1
    return self._buffer.read1(size)
../miniconda3/envs/dask35/lib/python3.5/_compression.py:68: in readinto
    data = self.read(len(byte_view))
../miniconda3/envs/dask35/lib/python3.5/_compression.py:103: in read
    data = self._decompressor.decompress(rawblock, size)
E   _lzma.LZMAError: Input format not supported by decoder
_______________________________________________________________________ test_read_bytes _______________________________________________________________________
[gw0] linux -- Python 3.5.2 /home/antoine/miniconda3/envs/dask35/bin/python
dask/bytes/tests/test_local.py:40: in test_read_bytes
    results = compute(*concat(values))
dask/base.py:171: in compute
    results = get(dsk, keys, **kwargs)
dask/async.py:549: in get_sync
    raise_on_exception=True, **kwargs)
dask/async.py:478: in get_async
    fire_task()
dask/async.py:474: in fire_task
    callback=queue.put)
dask/async.py:535: in apply_sync
    res = func(*args, **kwds)
dask/async.py:266: in execute_task
    result = _execute_task(task, data)
dask/async.py:247: in _execute_task
    return func(*args2)
dask/bytes/local.py:74: in read_block_from_file
    with open(path, 'rb') as f:
E   FileNotFoundError: [Errno 2] No such file or directory: '/home/antoine/dask/.test.accounts.2.json'
```



Discussion:
The `dask/bytes/tests/test_local.py` tests all use the same filenames in the local directory for testing.  Multiple tests are likely rewriting and deleting each others' files.  This can probably be avoided by changing the `filetexts` context manager to write the files to a temporary directory rather than the local path as is done in most of the other context managers like `filetext`.

It looks like the first error, `test_read_text` uses the same filenames as well


Resolving patch:
diff --git a/dask/utils.py b/dask/utils.py
--- a/dask/utils.py
+++ b/dask/utils.py
@@ -118,6 +118,28 @@ def filetext(text, extension='', open=open, mode='w'):
         yield filename
 
 
+@contextmanager
+def changed_cwd(new_cwd):
+    old_cwd = os.getcwd()
+    os.chdir(new_cwd)
+    try:
+        yield
+    finally:
+        os.chdir(old_cwd)
+
+
+@contextmanager
+def tmp_cwd(dir=None):
+    with tmpdir(dir) as dirname:
+        with changed_cwd(dirname):
+            yield dirname
+
+
+@contextmanager
+def noop_context():
+    yield
+
+
 def repr_long_list(seq):
     """
 
@@ -150,27 +172,32 @@ def __getitem__(self, key):
 
 
 @contextmanager
-def filetexts(d, open=open, mode='t'):
+def filetexts(d, open=open, mode='t', use_tmpdir=True):
     """ Dumps a number of textfiles to disk
 
     d - dict
         a mapping from filename to text like {'a.csv': '1,1\n2,2'}
+
+    Since this is meant for use in tests, this context manager will
+    automatically switch to a temporary current directory, to avoid
+    race conditions when running tests in parallel.
     """
-    for filename, text in d.items():
-        f = open(filename, 'w' + mode)
-        try:
-            f.write(text)
-        finally:
+    with (tmp_cwd() if use_tmpdir else noop_context()):
+        for filename, text in d.items():
+            f = open(filename, 'w' + mode)
             try:
-                f.close()
-            except AttributeError:
-                pass
-
-    yield list(d)
-
-    for filename in d:
-        if os.path.exists(filename):
-            os.remove(filename)
+                f.write(text)
+            finally:
+                try:
+                    f.close()
+                except AttributeError:
+                    pass
+
+        yield list(d)
+
+        for filename in d:
+            if os.path.exists(filename):
+                os.remove(filename)
 
 
 compressions = {'gz': 'gzip', 'bz2': 'bz2', 'xz': 'xz'}


Test patch: