Skip to content

Commit 27825d2

Browse files
committed
manually merge PR48 with minimal adjustments. Fixes potential leftover open file handles on error. Enable corresponding unit test again. Thanks @albu-diku.
git-svn-id: svn+ssh://svn.code.sf.net/p/migrid/code/trunk@6037 b75ad72c-e7d7-11dd-a971-7dbc132099af
1 parent 5a25d84 commit 27825d2

File tree

3 files changed

+99
-48
lines changed

3 files changed

+99
-48
lines changed

mig/shared/fileio.py

Lines changed: 45 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,9 +70,11 @@
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")
@@ -81,66 +83,70 @@ def write_chunk(path, chunk, offset, logger, mode='r+b'):
8183
# create dir and file if it does not exists
8284

8385
(head, _) = os.path.split(path)
84-
if not os.path.isdir(head):
86+
if not os.path.isdir(head) and make_parent:
8587
try:
8688
os.mkdir(head)
8789
except Exception as err:
8890
logger.error("could not create parent dir %r: %s" % (head, err))
89-
if not os.path.isfile(path):
91+
return False
92+
93+
# ensure a file is present
94+
95+
if not os.path.isfile(path) and create_file:
9096
try:
9197
open(path, "w").close()
9298
except Exception as err:
9399
logger.error("could not create file %r: %s" % (path, err))
100+
return False
101+
94102
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
103+
with open(path, mode) as filehandle:
104+
if offset > 0:
105+
# Make sure we can write at requested position, filling if needed
106+
try:
107+
filehandle.seek(offset)
108+
except:
109+
filehandle.seek(0, 2)
110+
file_size = filehandle.tell()
111+
for _ in xrange(offset - file_size):
112+
filehandle.write('\0')
113+
# logger.debug("write %r chunk of size %d at position %d" %
114+
# (path, len(chunk), filehandle.tell()))
115+
filehandle.write(chunk)
116+
# logger.debug("file %r chunk written at %d" % (path, offset))
117+
return True
110118
except Exception as err:
111119
logger.error("could not write %r chunk at %d: %s" %
112120
(path, offset, err))
113121
return False
114122

115123

124+
def write_chunk(path, chunk, offset, logger, mode='r+b'):
125+
"""Wrapper to handle writing of chunks with offset to path.
126+
Creates file first if it doesn't already exist.
127+
"""
128+
return _write_chunk(path, chunk, offset, logger, mode)
129+
130+
116131
def write_file(content, path, logger, mode='w', make_parent=True, umask=None):
117132
"""Wrapper to handle writing of contents to path"""
118133
if not logger:
119134
logger = null_logger("dummy")
120135
# logger.debug("writing %r file" % path)
121136

122-
# create dir if it does not exists
123-
124-
(head, _) = os.path.split(path)
125137
if umask is not None:
126138
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
139+
140+
# NOTE: detect byte writes and handle explicitly in a portable way
141+
if isinstance(content, bytes) and 'b' not in mode:
142+
mode = "%sb" % mode # appended to avoid mode ordering error on PY2
143+
144+
retval = _write_chunk(path, content, offset=0, logger=logger, mode=mode,
145+
make_parent=make_parent, create_file=False)
146+
142147
if umask is not None:
143148
os.umask(old_umask)
149+
144150
return retval
145151

146152

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: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,35 +13,41 @@
1313
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+
class MigSharedFileio__write_chunk(MigTestCase):
2021
def setUp(self):
21-
super(TestFileioWriteChunk, self).setUp()
22+
super(MigSharedFileio__write_chunk, self).setUp()
2223
self.tmp_path = temppath(DUMMY_FILE_WRITECHUNK, self, skip_clean=True)
2324
cleanpath(os.path.dirname(DUMMY_FILE_WRITECHUNK), self)
2425

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)
26+
def test_return_false_on_invalid_data(self):
27+
did_succeed = fileio.write_chunk(self.tmp_path, 1234, 0, self.logger)
28+
self.assertFalse(did_succeed)
2929

30-
def test_write_chunk_creates_directory(self):
30+
def test_return_false_on_invalid_dir(self):
31+
os.makedirs(self.tmp_path)
32+
33+
did_succeed = fileio.write_chunk(self.tmp_path, 1234, 0, self.logger)
34+
self.assertFalse(did_succeed)
35+
36+
def test_creates_directory(self):
3137
fileio.write_chunk(self.tmp_path, DUMMY_BYTES, 0, self.logger)
3238

3339
path_kind = self.assertPathExists(DUMMY_FILE_WRITECHUNK)
3440
self.assertEqual(path_kind, "file")
3541

36-
def test_write_chunk_store_bytes(self):
42+
def test_store_bytes(self):
3743
fileio.write_chunk(self.tmp_path, DUMMY_BYTES, 0, self.logger)
3844

3945
with open(self.tmp_path, 'rb') as file:
4046
content = file.read(1024)
4147
self.assertEqual(len(content), DUMMY_BYTES_LENGTH)
4248
self.assertEqual(content[:], DUMMY_BYTES)
4349

44-
def test_write_chunk_store_bytes_at_offset(self):
50+
def test_store_bytes_at_offset(self):
4551
offset = 3
4652

4753
fileio.write_chunk(self.tmp_path, DUMMY_BYTES, offset, self.logger)
@@ -52,5 +58,43 @@ def test_write_chunk_store_bytes_at_offset(self):
5258
self.assertEqual(content[0:3], bytearray([0, 0, 0]), "expected a hole was left")
5359
self.assertEqual(content[3:], DUMMY_BYTES)
5460

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

0 commit comments

Comments
 (0)