Repository: xonsh/xonsh

Issue:
#1041 broke xonsh if broken symlink on Path. 
see comments I'm keeping in #1041. 



Discussion:
see https://github.com/scopatz/xonsh/pull/1041#issuecomment-223103928


Resolving patch:
diff --git a/xonsh/tools.py b/xonsh/tools.py
--- a/xonsh/tools.py
+++ b/xonsh/tools.py
@@ -317,6 +317,18 @@ class redirect_stderr(_RedirectStream):
     _stream = "stderr"
 
 
+def _yield_accessible_unix_file_names(path):
+    "yield file names of executablel files in `path`"
+
+    for file_ in scandir(path):
+        try:
+            if file_.is_file() and os.access(file_.path, os.X_OK):
+                yield file_.name
+        except NotADirectoryError:
+            # broken Symlink are neither dir not files
+            pass
+
+
 def _executables_in_posix(path):
     if PYTHON_VERSION_INFO < (3, 5, 0):
         for fname in os.listdir(path):
@@ -325,8 +337,7 @@ def _executables_in_posix(path):
                                     (not os.path.isdir(fpath))):
                 yield fname
     else:
-        yield from (x.name for x in scandir(path)
-                    if x.is_file() and os.access(x.path, os.X_OK))
+        yield from _yield_accessible_unix_file_names(path)
 
 
 def _executables_in_windows(path):


Test patch:
diff --git a/tests/test_tools.py b/tests/test_tools.py
--- a/tests/test_tools.py
+++ b/tests/test_tools.py
@@ -591,29 +591,37 @@ def test_partial_string():
 
 
 def test_executables_in():
+
+
     expected = set()
-    types = ('none', 'file', 'file', 'directory')
-    executables = (True, True, False)
+    types = ('file', 'directory', 'brokensymlink')
+    executables = (True, False)
     with TemporaryDirectory() as test_path:
-        for i in range(64):
-            _type = types[i%len(types)]
-            if _type == 'none':
-                continue
-            executable = executables[i%len(executables)]
-            if _type == 'file' and executable:
-                ext = '.exe' if ON_WINDOWS else ''
-                expected.add(str(i) + ext)
-            else:
-                ext = ''
-            path = os.path.join(test_path, str(i) + ext)
-            if _type == 'file':
-                with open(path, 'w') as f:
-                    f.write(str(i))
-            elif _type == 'directory':
-                os.mkdir(path)
-            if executable:
-                os.chmod(path, stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR)
-        result = set(executables_in(test_path))
+        for _type in types:
+            for executable in executables:
+                fname = '%s_%s' % (_type, executable)
+                if _type == 'none':
+                    continue
+                if _type == 'file' and executable:
+                    ext = '.exe' if ON_WINDOWS else ''
+                    expected.add(fname + ext)
+                else:
+                    ext = ''
+                path = os.path.join(test_path, fname + ext)
+                if _type == 'file':
+                    with open(path, 'w') as f:
+                        f.write(fname)
+                elif _type == 'directory':
+                    os.mkdir(path)
+                elif _type == 'brokensymlink':
+                    tmp_path = os.path.join(test_path, 'i_wont_exist')
+                    with open(tmp_path,'w') as f:
+                        f.write('deleteme')
+                        os.symlink(tmp_path, path)
+                    os.remove(tmp_path)
+                if executable and not _type == 'brokensymlink' :
+                    os.chmod(path, stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR)
+            result = set(executables_in(test_path))
     assert_equal(expected, result)