Skip to content

Commit

Permalink
Simplify api, improve documentation and tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
ZipFile committed Oct 22, 2017
1 parent 161aec5 commit 8be130d
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 50 deletions.
37 changes: 25 additions & 12 deletions getdents/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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)
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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=[
Expand Down
50 changes: 16 additions & 34 deletions tests/test___init__.py
Original file line number Diff line number Diff line change
@@ -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, '..'),
Expand All @@ -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'),
Expand Down
5 changes: 2 additions & 3 deletions tests/test__getdents.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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):
Expand Down

0 comments on commit 8be130d

Please sign in to comment.