Repository: gorakhargosh/watchdog

Issue:
Constructor for OSError used incorrectly ( inotify_c.py line 406 )
OS in Windows10 with "Windows Subsystem for Linux" enabled, and from inside the "Bash" shell, although it still appears to be cmd.exe.

>   File "/usr/local/lib/python2.7/dist-packages/watchdog/observers/api.py", line 255, in start
>     emitter.start()
>   File "/usr/local/lib/python2.7/dist-packages/watchdog/utils/**init**.py", line 111, in start
>     self.on_thread_start()
>   File "/usr/local/lib/python2.7/dist-packages/watchdog/observers/inotify.py", line 121, in on_thread_start
>     self._inotify = InotifyBuffer(path, self.watch.is_recursive)
>   File "/usr/local/lib/python2.7/dist-packages/watchdog/observers/inotify_buffer.py", line 35, in __init__
>     self._inotify = Inotify(path, recursive)
>   File "/usr/local/lib/python2.7/dist-packages/watchdog/observers/inotify_c.py", line 187, in __init__
>     self._add_dir_watch(path, recursive, event_mask)
>   File "/usr/local/lib/python2.7/dist-packages/watchdog/observers/inotify_c.py", line 364, in _add_dir_watch
>     self._add_watch(path, mask)
>   File "/usr/local/lib/python2.7/dist-packages/watchdog/observers/inotify_c.py", line 385, in _add_watch
>     Inotify._raise_error()
>   File "/usr/local/lib/python2.7/dist-packages/watchdog/observers/inotify_c.py", line 406, in _raise_error
>     raise OSError(os.strerror(err))
> OSError: Invalid argument


Discussion:

Patch:
diff --git a/src/watchdog/observers/inotify_c.py b/src/watchdog/observers/inotify_c.py
index ba99bd5..19b2c01 100644
--- a/src/watchdog/observers/inotify_c.py
+++ b/src/watchdog/observers/inotify_c.py
@@ -376,7 +376,7 @@ def _add_dir_watch(self, path, recursive, mask):
             Event bit mask.
         """
         if not os.path.isdir(path):
-            raise OSError('Path is not a directory')
+            raise OSError(errno.ENOTDIR, os.strerror(errno.ENOTDIR), path)
         self._add_watch(path, mask)
         if recursive:
             for root, dirnames, _ in os.walk(path):
@@ -410,11 +410,11 @@ def _raise_error():
         """
         err = ctypes.get_errno()
         if err == errno.ENOSPC:
-            raise OSError("inotify watch limit reached")
+            raise OSError(errno.ENOSPC, "inotify watch limit reached")
         elif err == errno.EMFILE:
-            raise OSError("inotify instance limit reached")
+            raise OSError(errno.EMFILE, "inotify instance limit reached")
         else:
-            raise OSError(os.strerror(err))
+            raise OSError(err, os.strerror(err))
 
     @staticmethod
     def _parse_event_buffer(event_buffer):


Test patch:
diff --git a/tests/shell.py b/tests/shell.py
index ff4635d..3989158 100644
--- a/tests/shell.py
+++ b/tests/shell.py
@@ -24,6 +24,7 @@
 
 from __future__ import with_statement
 
+import os
 import os.path
 import tempfile
 import shutil
@@ -71,7 +72,7 @@ def rm(path, recursive=False):
         # else:
         #    os.rmdir(path)
         else:
-            raise OSError("rm: %s: is a directory." % path)
+            raise OSError(errno.EISDIR, os.strerror(errno.EISDIR), path)
     else:
         os.remove(path)
 
diff --git a/tests/test_inotify_c.py b/tests/test_inotify_c.py
index 0b5a887..1e8092c 100644
--- a/tests/test_inotify_c.py
+++ b/tests/test_inotify_c.py
@@ -1,17 +1,26 @@
 from __future__ import unicode_literals
-import os
+
 import pytest
+
+from watchdog.utils import platform
+
+if not platform.is_linux():
+    pytest.skip("GNU/Linux only.", allow_module_level=True)
+
+import ctypes
+import errno
+import os
 import logging
 import contextlib
-from tests import Queue
 from functools import partial
-from .shell import rm, mkdtemp
-from watchdog.utils import platform
+
 from watchdog.events import DirCreatedEvent, DirDeletedEvent, DirModifiedEvent
 from watchdog.observers.api import ObservedWatch
+from watchdog.observers.inotify import InotifyFullEmitter, InotifyEmitter
+from watchdog.observers.inotify_c import Inotify
 
-if platform.is_linux():
-    from watchdog.observers.inotify import InotifyFullEmitter, InotifyEmitter
+from . import Queue
+from .shell import mkdtemp, rm
 
 
 logging.basicConfig(level=logging.DEBUG)
@@ -39,11 +48,12 @@ def watching(path=None, use_full_emitter=False):
 
 def teardown_function(function):
     rm(p(''), recursive=True)
-    assert not emitter.is_alive()
+    try:
+        assert not emitter.is_alive()
+    except NameError:
+        pass
 
 
-@pytest.mark.skipif(not platform.is_linux(),
-                    reason="Testing with inotify messages (Linux only)")
 def test_late_double_deletion(monkeypatch):
     inotify_fd = type(str("FD"), (object,), {})() # Empty object
     inotify_fd.last = 0
@@ -69,6 +79,7 @@ def test_late_double_deletion(monkeypatch):
     )
 
     os_read_bkp = os.read
+
     def fakeread(fd, length):
         if fd is inotify_fd:
             result, fd.buf = fd.buf[:length], fd.buf[length:]
@@ -76,6 +87,7 @@ def fakeread(fd, length):
         return os_read_bkp(fd, length)
 
     os_close_bkp = os.close
+
     def fakeclose(fd):
         if fd is not inotify_fd:
             os_close_bkp(fd)
@@ -112,6 +124,34 @@ def inotify_rm_watch(fd, wd):
             assert isinstance(event, DirModifiedEvent)
             assert event.src_path == p('').rstrip(os.path.sep)
 
-    assert inotify_fd.last == 3 # Number of directories
-    assert inotify_fd.buf == b"" # Didn't miss any event
-    assert inotify_fd.wds == [2, 3] # Only 1 is removed explicitly
+    assert inotify_fd.last == 3  # Number of directories
+    assert inotify_fd.buf == b""  # Didn't miss any event
+    assert inotify_fd.wds == [2, 3]  # Only 1 is removed explicitly
+
+
+def test_raise_error(monkeypatch):
+    func = Inotify._raise_error
+
+    monkeypatch.setattr(ctypes, "get_errno", lambda: errno.ENOSPC)
+    with pytest.raises(OSError) as exc:
+        func()
+    assert exc.value.errno == errno.ENOSPC
+    assert "inotify watch limit reached" in str(exc.value)
+
+    monkeypatch.setattr(ctypes, "get_errno", lambda: errno.EMFILE)
+    with pytest.raises(OSError) as exc:
+        func()
+    assert exc.value.errno == errno.EMFILE
+    assert "inotify instance limit reached" in str(exc.value)
+
+    monkeypatch.setattr(ctypes, "get_errno", lambda: errno.ENOENT)
+    with pytest.raises(OSError) as exc:
+        func()
+    assert exc.value.errno == errno.ENOENT
+    assert "No such file or directory" in str(exc.value)
+
+    monkeypatch.setattr(ctypes, "get_errno", lambda: -1)
+    with pytest.raises(OSError) as exc:
+        func()
+    assert exc.value.errno == -1
+    assert "Unknown error -1" in str(exc.value)