diff --git a/getdents/__init__.py b/getdents/__init__.py index 84b6a07..7532e8d 100644 --- a/getdents/__init__.py +++ b/getdents/__init__.py @@ -15,16 +15,30 @@ ) -def getdents(path, buff_size=32768, close_fd=False): - if hasattr(path, 'fileno'): - fd = path.fileno() - elif isinstance(path, str): - fd = os.open(path, O_GETDENTS) - close_fd = True - elif isinstance(path, int): - fd = path - else: - raise TypeError('Unsupported type: %s', type(path)) +def getdents(path, buff_size=32768): + """Get directory entries. + + Wrapper around getdents_raw(), simulates ls behaviour: ignores deleted + files, skips . and .. entries. + + Note: + Default buffer size is 32k, it's a default allocation size of glibc's + readdir() implementation. + + Note: + Larger buffer will result in a fewer syscalls, so for really large + dirs you should pick larger value. + + Note: + For better performance, set buffer size to be multiple of your block + size for filesystem I/O. + + Args: + path (str): Location of the directory. + buff_size (int): Buffer size in bytes for getdents64 syscall. + """ + + fd = os.open(path, O_GETDENTS) try: yield from ( @@ -33,5 +47,4 @@ def getdents(path, buff_size=32768, close_fd=False): if not(type == DT_UNKNOWN or inode == 0 or name in ('.', '..')) ) finally: - if close_fd: - os.close(fd) + os.close(fd) diff --git a/setup.py b/setup.py index 3b19f54..8ac2a7a 100755 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name='getdents', - version='0.1', + version='0.2', description='Python binding to linux syscall getdents64.', long_description=open('README.rst').read(), classifiers=[ diff --git a/tests/test___init__.py b/tests/test___init__.py index a85bf22..e259afb 100644 --- a/tests/test___init__.py +++ b/tests/test___init__.py @@ -1,54 +1,36 @@ -from unittest.mock import Mock, patch, sentinel +from unittest.mock import patch, sentinel -from pytest import mark, raises +from pytest import raises from getdents import DT_DIR, DT_REG, DT_UNKNOWN, O_GETDENTS, getdents -@patch('getdents.os') -@patch('getdents.getdents_raw') -def test_file_like(mock_getdents_raw, mock_os): - mock_file = Mock(spec_set=['fileno']) - mock_file.fileno.return_value = sentinel.fd - - list(getdents(mock_file, sentinel.size)) - - assert mock_file.fileno.called - assert mock_getdents_raw.called_once_with(sentinel.fd, sentinel.size) - - @patch('getdents.os') @patch('getdents.getdents_raw') def test_path(mock_getdents_raw, mock_os): - mock_path = Mock(spec='/tmp') mock_os.open.return_value = sentinel.fd - list(getdents(mock_path, sentinel.size)) + list(getdents('/tmp', sentinel.size)) - assert mock_os.open.called_once_with(mock_path, O_GETDENTS) - assert mock_getdents_raw.called_once_with(sentinel.fd, sentinel.size) + mock_os.open.assert_called_once_with('/tmp', O_GETDENTS) + mock_getdents_raw.assert_called_once_with(sentinel.fd, sentinel.size) + mock_os.close.assert_called_once_with(sentinel.fd) -@mark.parametrize(['close_fd'], [(True,), (False,)]) @patch('getdents.os') -@patch('getdents.getdents_raw') -def test_fd(mock_getdents_raw, mock_os, close_fd): - mock_fd = Mock(spec=4) - - list(getdents(mock_fd, sentinel.size, close_fd)) - - assert mock_getdents_raw.called_once_with(mock_fd, sentinel.size) - assert mock_os.close.called is close_fd - +@patch('getdents.getdents_raw', side_effect=[Exception]) +def test_path_err(mock_getdents_raw, mock_os): + mock_os.open.return_value = sentinel.fd -@patch('getdents.getdents_raw') -def test_alien(mock_getdents_raw): - with raises(TypeError): - list(getdents(object())) + with raises(Exception): + list(getdents('/tmp', sentinel.size)) - assert not mock_getdents_raw.called + mock_os.open.assert_called_once_with('/tmp', O_GETDENTS) + mock_getdents_raw.assert_called_once_with(sentinel.fd, sentinel.size) + mock_os.close.assert_called_once_with(sentinel.fd) +@patch('getdents.os') @patch('getdents.getdents_raw', return_value=iter([ (1, DT_DIR, '.'), (2, DT_DIR, '..'), @@ -57,7 +39,7 @@ def test_alien(mock_getdents_raw): (5, DT_UNKNOWN, '???'), (0, DT_REG, 'deleted'), ])) -def test_filtering(mock_getdents_raw): +def test_filtering(mock_getdents_raw, mock_os): assert list(getdents(0)) == [ (3, DT_DIR, 'dir'), (4, DT_REG, 'file'), diff --git a/tests/test__getdents.py b/tests/test__getdents.py index 2449efc..7666c0e 100644 --- a/tests/test__getdents.py +++ b/tests/test__getdents.py @@ -2,7 +2,7 @@ from unittest.mock import ANY -from pytest import fixture, mark, raises +from pytest import fixture, raises from getdents._getdents import ( DT_DIR, @@ -45,10 +45,9 @@ def test_small_buffer(fixt_dir): getdents_raw(fixt_dir, MIN_GETDENTS_BUFF_SIZE - 1) -@mark.skip(reason='No idea how to test this properly') def test_malloc_fail(fixt_dir): with raises(MemoryError): - getdents_raw(fixt_dir, 2**63) + getdents_raw(fixt_dir, 1 << 62) def test_getdents_raw(fixt_dir):