Skip to content

Commit 855cb9b

Browse files
cpiofile: Add unittest opening a symlink as fileobj
Signed-off-by: Bernhard Kaindl <[email protected]>
1 parent 2d200d8 commit 855cb9b

File tree

3 files changed

+61
-31
lines changed

3 files changed

+61
-31
lines changed

.vscode/ltex.dictionary.en-US.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ codecov
22
covcombine
33
coverallsapp
44
cpio
5+
cpiofile
6+
cpioinfo
57
euxv
68
ibft
79
ifrename
810
kname
911
lastboot
12+
linkname
1013
logbuf
1114
MACPCI
1215
nektos

tests/test_cpiofile.py

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import pytest
1515
from pyfakefs.fake_filesystem import FakeFileOpen, FakeFilesystem
1616

17-
from xcp.cpiofile import CpioFile
17+
from xcp.cpiofile import CpioFile, StreamError
1818

1919
binary_data = b"\x00\x1b\x5b\x95\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xcc\xdd\xee\xff"
2020

@@ -48,53 +48,79 @@ def test_cpiofile_modes(fs):
4848
if comp == "xz" and filetype == ":":
4949
continue # streaming xz is not implemented (supported only as file)
5050
check_archive_mode(filetype + comp, fs)
51+
if filetype == "|":
52+
check_archive_mode(filetype + comp, fs, filename="archive." + comp)
5153

5254

53-
def check_archive_mode(archive_mode, fs):
54-
# type: (str, FakeFilesystem) -> None
55+
def create_cpio_archive(fs, archive_mode, filename=None):
56+
# type: (FakeFilesystem, str, str | None) -> io.BytesIO | None
5557
"""
56-
Test CpioFile in the given archive mode with verification of the archive contents.
58+
Create a CpioFile archive with files and directories from a FakeFilesystem.
5759
60+
:param fs: `FakeFilesystem` fixture representing a simulated file system for testing
5861
:param archive_mode: The archive mode is a string parameter that specifies the mode
5962
in which the CpioFile object should be opened.
60-
:param fs: `FakeFilesystem` fixture representing a simulated file system for testing
63+
:param filename: The name of the file to create the cpio archive
6164
"""
62-
# Step 1: Create and populate a cpio archive in a BytesIO buffer
63-
bytesio = io.BytesIO()
64-
archive = CpioFile.open(fileobj=bytesio, mode="w" + archive_mode)
65-
pyfakefs_populate_archive(archive, fs)
65+
cpiofile = None if filename else io.BytesIO()
66+
fs.reset()
67+
cpio = CpioFile.open(name=filename, fileobj=cpiofile, mode="w" + archive_mode)
68+
pyfakefs_populate_archive(cpio, fs)
6669
if archive_mode == "|gz":
67-
archive.list(verbose=True)
68-
archive.close()
70+
cpio.list(verbose=True)
71+
cpio.close()
72+
if not cpiofile:
73+
cpio_data = FakeFileOpen(fs)(filename, "rb").read()
74+
fs.reset()
75+
fs.create_file(filename, contents=cast(str, cpio_data))
76+
return None
77+
fs.reset()
78+
cpiofile.seek(0)
79+
return cpiofile
80+
6981

82+
def check_archive_mode(archive_mode, fs, filename=None):
83+
# type: (str, FakeFilesystem, str | None) -> None
84+
"""
85+
Test CpioFile in the given archive mode with verification of the archive contents.
86+
87+
:param archive_mode: The archive mode is a string parameter that specifies the mode
88+
in which the CpioFile object should be opened.
89+
:param fs: `FakeFilesystem` fixture representing a simulated file system for testing
90+
"""
7091
# Step 2: Extract the archive in a clean filesystem and verify the extracted contents
71-
fs.reset()
72-
bytesio.seek(0)
73-
archive = CpioFile.open(fileobj=bytesio, mode="r" + archive_mode)
92+
cpiofile = create_cpio_archive(fs, archive_mode, filename)
93+
archive = CpioFile.open(name=filename, fileobj=cpiofile, mode="r" + archive_mode)
7494
archive.extractall()
7595
pyfakefs_verify_filesystem(fs)
76-
assert archive.getnames() == ["dirname", "dirname/filename", "dir2/symlink"]
96+
assert archive.getnames() == ["dirname", "dirname/filename", "symlink", "dir2/file_2"]
7797
dirs = [cpioinfo.name for cpioinfo in archive.getmembers() if cpioinfo.isdir()]
7898
files = [cpioinfo.name for cpioinfo in archive.getmembers() if cpioinfo.isreg()]
7999
symlinks = [cpioinfo.name for cpioinfo in archive.getmembers() if cpioinfo.issym()]
80100
assert dirs == ["dirname"]
81-
assert files == ["dirname/filename"]
82-
assert symlinks == ["dir2/symlink"]
83-
assert archive.getmember(symlinks[0]).linkname == "symlink_target"
101+
assert files == ["dirname/filename", "dir2/file_2"]
102+
assert symlinks == ["symlink"]
103+
assert archive.getmember(symlinks[0]).linkname == "dirname/filename"
104+
105+
# Test extracting a symlink to a file object:
106+
if archive_mode.startswith("|"): # Non-seekable streams raise StreamError
107+
with pytest.raises(StreamError):
108+
archive.extractfile("symlink")
109+
else: # Expect a seekable fileobj for this test (not a stream) to work:
110+
fileobj = archive.extractfile("symlink")
111+
assert fileobj and fileobj.read() == binary_data
84112
archive.close()
85113

86114
# Step 3: Extract the archive a second time using another method
87-
fs.reset()
88-
bytesio.seek(0)
89-
archive = CpioFile.open(fileobj=bytesio, mode="r" + archive_mode)
115+
cpiofile = create_cpio_archive(fs, archive_mode, filename)
116+
archive = CpioFile.open(name=filename, fileobj=cpiofile, mode="r" + archive_mode)
90117
if archive_mode[0] != "|":
91118
for cpioinfo in archive:
92119
archive.extract(cpioinfo)
93120
pyfakefs_verify_filesystem(fs)
94121
if archive_mode == "|xz":
95122
archive.list(verbose=True)
96123
archive.close()
97-
bytesio.close()
98124

99125

100126
def pyfakefs_populate_archive(archive, fs):
@@ -105,11 +131,12 @@ def pyfakefs_populate_archive(archive, fs):
105131
:param archive: Instance of the CpioFile class to create a new cpio archive
106132
:param fs: `FakeFilesystem` fixture representing a simulated file system for testing
107133
"""
108-
fs.reset()
109134

110135
fs.create_file("dirname/filename", contents=cast(str, binary_data))
111136
archive.add("dirname", recursive=True)
112-
fs.create_symlink("directory/symlink", "symlink_target")
137+
fs.create_file("directory/file_2", contents=cast(str, binary_data))
138+
fs.create_symlink("symlink", "dirname/filename")
139+
archive.add("symlink")
113140

114141
# Test special code path of archive.add(".", ...):
115142
os.chdir("directory")
@@ -129,7 +156,10 @@ def pyfakefs_verify_filesystem(fs):
129156
130157
:param fs: `FakeFilesystem` fixture representing a simulated file system for testing
131158
"""
132-
assert fs.islink("dir2/symlink")
159+
assert fs.islink("symlink")
133160
assert fs.isfile("dirname/filename")
161+
assert fs.isfile("dir2/file_2")
134162
with FakeFileOpen(fs)("dirname/filename", "rb") as contents:
135163
assert contents.read() == binary_data
164+
with FakeFileOpen(fs)("dir2/file_2", "rb") as contents:
165+
assert contents.read() == binary_data

xcp/cpiofile.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1463,7 +1463,7 @@ def extract(self, member, path=""):
14631463
self._dbg(1, "cpiofile: %s" % e)
14641464

14651465
def extractfile(self, member):
1466-
# type:(CpioInfo) -> ExFileObject | None
1466+
# type:(CpioInfo | str | bytes) -> ExFileObject | None
14671467
"""Extract a member from the archive as a file object. `member' may be
14681468
a filename or a CpioInfo object. If `member' is a regular file, a
14691469
file-like object is returned. If `member' is a link, a file-like
@@ -1489,11 +1489,8 @@ def extractfile(self, member):
14891489
# A small but ugly workaround for the case that someone tries
14901490
# to extract a symlink as a file-object from a non-seekable
14911491
# stream of cpio blocks.
1492-
raise StreamError("cannot extract symlink as file object")
1493-
else:
1494-
# A symlink's file object is its target's file object.
1495-
return self.extractfile(self._getmember(cpioinfo.linkname,
1496-
cpioinfo)) # type: ignore
1492+
raise StreamError("Need a seekable stream to open() a symlink target!")
1493+
return self.extractfile(cpioinfo.linkname)
14971494
else:
14981495
# If there's no data associated with the member (directory, chrdev,
14991496
# blkdev, etc.), return None instead of a file object.

0 commit comments

Comments
 (0)