Skip to content

Commit c80e3f0

Browse files
committed
Added initial support for null device
- added special file and file buffer for null device - fixes #418
1 parent 534ec54 commit c80e3f0

File tree

5 files changed

+72
-11
lines changed

5 files changed

+72
-11
lines changed

Diff for: CHANGES.md

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ The release versions are PyPi releases.
44
## Version 3.5 (as yet unreleased)
55

66
#### New Features
7+
* added support for null device ([#418](../../issues/418))
78

89
#### Infrastructure
910

Diff for: pyfakefs/fake_filesystem.py

+39-9
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@
107107

108108
from pyfakefs.deprecator import Deprecator
109109
from pyfakefs.fake_scandir import scandir, walk
110-
from pyfakefs.helpers import FakeStatResult, FileBufferIO, IS_PY2
110+
from pyfakefs.helpers import FakeStatResult, FileBufferIO, IS_PY2, NullFileBufferIO
111111
from pyfakefs.helpers import is_int_type, is_byte_string, is_unicode_string
112112
from pyfakefs.helpers import make_string_path
113113

@@ -498,6 +498,19 @@ def SetIno(self, st_ino):
498498
self.st_ino = st_ino
499499

500500

501+
class FakeNullFile(FakeFile):
502+
def __init__(self, filesystem):
503+
devnull = '/dev/nul' if filesystem.is_windows_fs else '/dev/nul'
504+
super(FakeNullFile, self).__init__(devnull, filesystem=filesystem, contents=b'')
505+
506+
@property
507+
def byte_contents(self):
508+
return b''
509+
510+
def _set_initial_contents(self, contents):
511+
pass
512+
513+
501514
Deprecator.add(FakeFile, FakeFile.set_large_file_size, 'SetLargeFileSize')
502515
Deprecator.add(FakeFile, FakeFile.set_contents, 'SetContents')
503516
Deprecator.add(FakeFile, FakeFile.is_large_file, 'IsLargeFile')
@@ -825,6 +838,7 @@ def __init__(self, path_separator=os.path.sep, total_size=None):
825838
self.mount_points = {}
826839
self.add_mount_point(self.root.name, total_size)
827840
self._add_standard_streams()
841+
self.dev_null = FakeNullFile(self)
828842

829843
def reset(self, total_size=None):
830844
"""Remove all file system contents and reset the root."""
@@ -1663,6 +1677,8 @@ def exists(self, file_path, check_link=False):
16631677
raise TypeError
16641678
if not file_path:
16651679
return False
1680+
if file_path == self.dev_null.name:
1681+
return not self.is_windows_fs
16661682
try:
16671683
if self.is_filepath_ending_with_separator(file_path):
16681684
return False
@@ -1740,7 +1756,8 @@ def resolve_path(self, file_path, allow_fd=False, raw_io=True):
17401756
file_path = self.absnormpath(self._original_path(file_path))
17411757
if self._is_root_path(file_path):
17421758
return file_path
1743-
1759+
if file_path == self.dev_null.name:
1760+
return file_path
17441761
path_components = self._path_components(file_path)
17451762
resolved_components = self._resolve_components(path_components, raw_io)
17461763
return self._components_to_path(resolved_components)
@@ -1861,6 +1878,8 @@ def get_object_from_normpath(self, file_path):
18611878
file_path = make_string_path(file_path)
18621879
if file_path == self.root.name:
18631880
return self.root
1881+
if file_path == self.dev_null.name:
1882+
return self.dev_null
18641883

18651884
file_path = self._original_path(file_path)
18661885
path_components = self._path_components(file_path)
@@ -3360,6 +3379,8 @@ class FakeOsModule(object):
33603379
my_os_module = fake_filesystem.FakeOsModule(filesystem)
33613380
"""
33623381

3382+
devnull = None
3383+
33633384
def __init__(self, filesystem, os_path_module=None):
33643385
"""Also exposes self.path (to fake os.path).
33653386
@@ -3382,6 +3403,8 @@ def __init__(self, filesystem, os_path_module=None):
33823403
self.fdopen = self._fdopen_ver2
33833404
else:
33843405
self.fdopen = self._fdopen
3406+
self.__class__.devnull = ('/dev/nul' if filesystem.is_windows_fs
3407+
else '/dev/nul')
33853408

33863409
def _fdopen(self, *args, **kwargs):
33873410
"""Redirector to open() builtin function.
@@ -3506,7 +3529,8 @@ def open(self, file_path, flags, mode=None, dir_fd=None):
35063529
fake_file = FakeFileOpen(
35073530
self.filesystem, delete_on_close=delete_on_close, raw_io=True)(
35083531
file_path, str_flags, open_modes=open_modes)
3509-
self.chmod(file_path, mode)
3532+
if fake_file.file_object != self.filesystem.dev_null:
3533+
self.chmod(file_path, mode)
35103534
return fake_file.fileno()
35113535

35123536
def close(self, file_des):
@@ -4294,7 +4318,9 @@ def __init__(self, file_object, file_path, update=False, read=False,
42944318
contents = file_object.byte_contents
42954319
self._encoding = encoding or locale.getpreferredencoding(False)
42964320
errors = errors or 'strict'
4297-
self._io = FileBufferIO(contents, linesep=filesystem.line_separator(),
4321+
buffer_class = (NullFileBufferIO if file_object == filesystem.dev_null
4322+
else FileBufferIO)
4323+
self._io = buffer_class(contents, linesep=filesystem.line_separator(),
42984324
binary=binary, encoding=encoding,
42994325
newline=newline, errors=errors)
43004326

@@ -4868,11 +4894,15 @@ def _handle_file_arg(self, file_):
48684894
# open a file file by path
48694895
filedes = None
48704896
file_path = file_
4871-
real_path = self.filesystem.resolve_path(
4872-
file_path, raw_io=self.raw_io)
4873-
if self.filesystem.exists(file_path):
4874-
file_object = self.filesystem.get_object_from_normpath(
4875-
real_path)
4897+
if file_path == self.filesystem.dev_null.name:
4898+
file_object = self.filesystem.dev_null
4899+
real_path = file_path
4900+
else:
4901+
real_path = self.filesystem.resolve_path(
4902+
file_path, raw_io=self.raw_io)
4903+
if self.filesystem.exists(file_path):
4904+
file_object = self.filesystem.get_object_from_normpath(
4905+
real_path)
48764906
return file_object, file_path, filedes, real_path
48774907

48784908
def _handle_file_mode(self, mode, newline, open_modes):

Diff for: pyfakefs/helpers.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ def __init__(self, contents=None, linesep='\n', binary=False,
260260
self.binary = binary
261261
self._bytestream = io.BytesIO()
262262
if contents is not None:
263-
self._bytestream.write(self.encoded_string(contents))
263+
self.putvalue(contents)
264264
self._bytestream.seek(0)
265265

266266
def encoding(self):
@@ -377,7 +377,7 @@ def write(self, s):
377377
raise TypeError('Incorrect type for writing')
378378
contents = self.convert_newlines_for_writing(s)
379379
length = len(contents)
380-
self._bytestream.write(self.encoded_string(contents))
380+
self.putvalue(contents)
381381
return length
382382

383383
def writelines(self, lines):
@@ -399,3 +399,9 @@ def next(self):
399399

400400
def __getattr__(self, name):
401401
return getattr(self._bytestream, name)
402+
403+
404+
class NullFileBufferIO(FileBufferIO):
405+
"""Special stream for null device. Does nothing on writing."""
406+
def putvalue(self, s):
407+
pass

Diff for: pyfakefs/tests/fake_open_test.py

+7
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,13 @@ def test_unicode_filename(self):
971971
with self.open(file_path, 'rb') as f:
972972
self.assertEqual(b'test', f.read())
973973

974+
def test_write_devnull(self):
975+
for mode in ('r+', 'w', 'w+', 'a', 'a+'):
976+
with self.open(self.os.devnull, mode) as f:
977+
f.write('test')
978+
with self.open(self.os.devnull) as f:
979+
self.assertEqual('', f.read())
980+
974981

975982
class RealFileOpenTest(FakeFileOpenTest):
976983
def use_real_fs(self):

Diff for: pyfakefs/tests/fake_os_test.py

+17
Original file line numberDiff line numberDiff line change
@@ -4156,6 +4156,23 @@ def test_writing_behind_end_of_file(self):
41564156
self.os.close(fd2)
41574157
self.os.close(fd3)
41584158

4159+
def test_devnull_posix(self):
4160+
self.check_posix_only()
4161+
self.assertTrue(self.os.path.exists(self.os.devnull))
4162+
4163+
def test_devnull_windows(self):
4164+
self.check_windows_only()
4165+
self.assertFalse(self.os.path.exists(self.os.devnull))
4166+
4167+
def test_write_devnull(self):
4168+
fd = self.os.open(self.os.devnull, os.O_RDWR)
4169+
self.assertEqual(4, self.os.write(fd, b'test'))
4170+
self.assertEqual(b'', self.os.read(fd, 4))
4171+
self.os.close(fd)
4172+
fd = self.os.open(self.os.devnull, os.O_RDONLY)
4173+
self.assertEqual(b'', self.os.read(fd, 4))
4174+
self.os.close(fd)
4175+
41594176

41604177
class RealOsModuleLowLevelFileOpTest(FakeOsModuleLowLevelFileOpTest):
41614178
def use_real_fs(self):

0 commit comments

Comments
 (0)