Skip to content

Commit f525f9d

Browse files
committed
Merge remote-tracking branch 'origin/master' into edge
2 parents ca10f18 + 82ea540 commit f525f9d

File tree

3 files changed

+114
-50
lines changed

3 files changed

+114
-50
lines changed

mig/shared/fileio.py

Lines changed: 50 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# --- BEGIN_HEADER ---
55
#
66
# fileio - wrappers to keep file I/O in a single replaceable module
7-
# Copyright (C) 2003-2023 The MiG Project lead by Brian Vinter
7+
# Copyright (C) 2003-2024 The MiG Project by the Science HPC Center at UCPH
88
#
99
# This file is part of MiG.
1010
#
@@ -70,77 +70,88 @@
7070
exit(1)
7171

7272

73-
def write_chunk(path, chunk, offset, logger, mode='r+b'):
74-
"""Wrapper to handle writing of chunks with offset to path.
75-
Creates file first if it doesn't already exist.
73+
def _write_chunk(path, chunk, offset, logger=None, mode='r+b',
74+
make_parent=True, create_file=True):
75+
"""Internal helper to wrap writing of chunks with offset to path.
76+
The optional make_parent and create_file are used to decide if the parent
77+
directory and the file should be created if it doesn't already exist.
7678
"""
7779
if not logger:
7880
logger = null_logger("dummy")
7981
# logger.debug("writing chunk to %r at offset %d" % (path, offset))
8082

83+
if offset < 0:
84+
logger.error("cannot write to negative offset %d in %r" %
85+
(offset, path))
86+
return False
87+
8188
# create dir and file if it does not exists
8289

8390
(head, _) = os.path.split(path)
84-
if not os.path.isdir(head):
91+
if not os.path.isdir(head) and make_parent:
8592
try:
8693
os.mkdir(head)
8794
except Exception as err:
8895
logger.error("could not create parent dir %r: %s" % (head, err))
89-
if not os.path.isfile(path):
96+
return False
97+
98+
# ensure a file is present
99+
100+
if not os.path.isfile(path) and create_file:
90101
try:
91102
open(path, "w").close()
92103
except Exception as err:
93104
logger.error("could not create file %r: %s" % (path, err))
105+
return False
106+
94107
try:
95-
filehandle = open(path, mode)
96-
# Make sure we can write at requested position, filling if needed
97-
try:
98-
filehandle.seek(offset)
99-
except:
100-
filehandle.seek(0, 2)
101-
file_size = filehandle.tell()
102-
for _ in xrange(offset - file_size):
103-
filehandle.write('\0')
104-
# logger.debug("write %r chunk of size %d at position %d" %
105-
# (path, len(chunk), filehandle.tell()))
106-
filehandle.write(chunk)
107-
filehandle.close()
108-
# logger.debug("file %r chunk written at %d" % (path, offset))
109-
return True
108+
with open(path, mode) as filehandle:
109+
if offset > 0:
110+
# Make sure we can write at requested position, filling if needed
111+
try:
112+
filehandle.seek(offset)
113+
except:
114+
filehandle.seek(0, 2)
115+
file_size = filehandle.tell()
116+
for _ in xrange(offset - file_size):
117+
filehandle.write('\0')
118+
# logger.debug("write %r chunk of size %d at position %d" %
119+
# (path, len(chunk), filehandle.tell()))
120+
filehandle.write(chunk)
121+
# logger.debug("file %r chunk written at %d" % (path, offset))
122+
return True
110123
except Exception as err:
111124
logger.error("could not write %r chunk at %d: %s" %
112125
(path, offset, err))
113126
return False
114127

115128

129+
def write_chunk(path, chunk, offset, logger, mode='r+b'):
130+
"""Wrapper to handle writing of chunks with offset to path.
131+
Creates file first if it doesn't already exist.
132+
"""
133+
return _write_chunk(path, chunk, offset, logger, mode)
134+
135+
116136
def write_file(content, path, logger, mode='w', make_parent=True, umask=None):
117137
"""Wrapper to handle writing of contents to path"""
118138
if not logger:
119139
logger = null_logger("dummy")
120140
# logger.debug("writing %r file" % path)
121141

122-
# create dir if it does not exists
123-
124-
(head, _) = os.path.split(path)
125142
if umask is not None:
126143
old_umask = os.umask(umask)
127-
if not os.path.isdir(head) and make_parent:
128-
try:
129-
# logger.debug("making parent directory %r" % head)
130-
os.mkdir(head)
131-
except Exception as err:
132-
logger.error("could not create parent dir %r: %s" % (head, err))
133-
try:
134-
filehandle = open(path, mode)
135-
filehandle.write(content)
136-
filehandle.close()
137-
# logger.debug("file %r written" % path)
138-
retval = True
139-
except Exception as err:
140-
logger.error("could not write file %r: %s" % (path, err))
141-
retval = False
144+
145+
# NOTE: detect byte writes and handle explicitly in a portable way
146+
if isinstance(content, bytes) and 'b' not in mode:
147+
mode = "%sb" % mode # appended to avoid mode ordering error on PY2
148+
149+
retval = _write_chunk(path, content, offset=0, logger=logger, mode=mode,
150+
make_parent=make_parent, create_file=False)
151+
142152
if umask is not None:
143153
os.umask(old_umask)
154+
144155
return retval
145156

146157

tests/support.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ def cleanpath(relative_path, test_case):
208208
assert isinstance(test_case, MigTestCase)
209209
tmp_path = os.path.join(TEST_OUTPUT_DIR, relative_path)
210210
test_case._cleanup_paths.add(tmp_path)
211+
return tmp_path
211212

212213

213214
def temppath(relative_path, test_case, skip_clean=False):

tests/test_mig_shared_fileio.py

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,47 +10,99 @@
1010

1111
import mig.shared.fileio as fileio
1212

13-
DUMMY_BYTES = binascii.unhexlify('DEADBEEF') # 4 bytes
13+
DUMMY_BYTES = binascii.unhexlify('DEADBEEF') # 4 bytes
1414
DUMMY_BYTES_LENGTH = 4
1515
DUMMY_FILE_WRITECHUNK = 'fileio/write_chunk'
16+
DUMMY_FILE_WRITEFILE = 'fileio/write_file'
1617

1718
assert isinstance(DUMMY_BYTES, bytes)
1819

19-
class TestFileioWriteChunk(MigTestCase):
20+
21+
class MigSharedFileio__write_chunk(MigTestCase):
2022
def setUp(self):
21-
super(TestFileioWriteChunk, self).setUp()
23+
super(MigSharedFileio__write_chunk, self).setUp()
2224
self.tmp_path = temppath(DUMMY_FILE_WRITECHUNK, self, skip_clean=True)
2325
cleanpath(os.path.dirname(DUMMY_FILE_WRITECHUNK), self)
2426

25-
# TODO: enable next test again once the bug is fixed (see PR48)
26-
#def test_write_chunk_error_on_invalid_data(self):
27-
# did_succeed = fileio.write_chunk(self.tmp_path, 1234, 0, self.logger)
28-
# self.assertFalse(did_succeed)
27+
def test_return_false_on_invalid_data(self):
28+
did_succeed = fileio.write_chunk(self.tmp_path, 1234, 0, self.logger)
29+
self.assertFalse(did_succeed)
30+
31+
def test_return_false_on_invalid_offset(self):
32+
did_succeed = fileio.write_chunk(self.tmp_path, DUMMY_BYTES, -42,
33+
self.logger)
34+
self.assertFalse(did_succeed)
35+
36+
def test_return_false_on_invalid_dir(self):
37+
os.makedirs(self.tmp_path)
2938

30-
def test_write_chunk_creates_directory(self):
39+
did_succeed = fileio.write_chunk(self.tmp_path, 1234, 0, self.logger)
40+
self.assertFalse(did_succeed)
41+
42+
def test_creates_directory(self):
3143
fileio.write_chunk(self.tmp_path, DUMMY_BYTES, 0, self.logger)
3244

3345
path_kind = self.assertPathExists(DUMMY_FILE_WRITECHUNK)
3446
self.assertEqual(path_kind, "file")
3547

36-
def test_write_chunk_store_bytes(self):
48+
def test_store_bytes(self):
3749
fileio.write_chunk(self.tmp_path, DUMMY_BYTES, 0, self.logger)
3850

3951
with open(self.tmp_path, 'rb') as file:
4052
content = file.read(1024)
4153
self.assertEqual(len(content), DUMMY_BYTES_LENGTH)
4254
self.assertEqual(content[:], DUMMY_BYTES)
4355

44-
def test_write_chunk_store_bytes_at_offset(self):
56+
def test_store_bytes_at_offset(self):
4557
offset = 3
4658

4759
fileio.write_chunk(self.tmp_path, DUMMY_BYTES, offset, self.logger)
4860

4961
with open(self.tmp_path, 'rb') as file:
5062
content = file.read(1024)
5163
self.assertEqual(len(content), DUMMY_BYTES_LENGTH + offset)
52-
self.assertEqual(content[0:3], bytearray([0, 0, 0]), "expected a hole was left")
64+
self.assertEqual(content[0:3], bytearray([0, 0, 0]),
65+
"expected a hole was left")
5366
self.assertEqual(content[3:], DUMMY_BYTES)
5467

68+
69+
class MigSharedFileio__write_file(MigTestCase):
70+
def setUp(self):
71+
super(MigSharedFileio__write_file, self).setUp()
72+
self.tmp_path = temppath(DUMMY_FILE_WRITEFILE, self, skip_clean=True)
73+
cleanpath(os.path.dirname(DUMMY_FILE_WRITEFILE), self)
74+
75+
def test_return_false_on_invalid_data(self):
76+
did_succeed = fileio.write_file(1234, self.tmp_path, self.logger)
77+
self.assertFalse(did_succeed)
78+
79+
def test_return_false_on_invalid_dir(self):
80+
os.makedirs(self.tmp_path)
81+
82+
did_succeed = fileio.write_file(DUMMY_BYTES, self.tmp_path, self.logger)
83+
self.assertFalse(did_succeed)
84+
85+
def test_return_false_on_missing_dir(self):
86+
did_succeed = fileio.write_file(DUMMY_BYTES, self.tmp_path, self.logger,
87+
make_parent=False)
88+
self.assertFalse(did_succeed)
89+
90+
def test_creates_directory(self):
91+
did_succeed = fileio.write_file(DUMMY_BYTES, self.tmp_path, self.logger)
92+
self.assertTrue(did_succeed)
93+
94+
path_kind = self.assertPathExists(DUMMY_FILE_WRITEFILE)
95+
self.assertEqual(path_kind, "file")
96+
97+
def test_store_bytes(self):
98+
did_succeed = fileio.write_file(DUMMY_BYTES, self.tmp_path, self.logger)
99+
self.assertTrue(did_succeed)
100+
101+
with open(self.tmp_path, 'rb') as file:
102+
content = file.read(1024)
103+
self.assertEqual(len(content), DUMMY_BYTES_LENGTH)
104+
self.assertEqual(content[:], DUMMY_BYTES)
105+
106+
55107
if __name__ == '__main__':
56108
testmain()

0 commit comments

Comments
 (0)