pungi/tests/test_linker.py
Lubomír Sedlář e765db157f linker: Drop ability to link dirs recursively
Nothing in the code base uses this functionality, and the semantins are
not well defined anyway when it comes to symlinks.

Now the tests are failing in Python 3.14 rebuild when hardlinking
symlinks. Rather than trying to fix the unused code, we could just drop
it.

Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=2367780
Signed-off-by: Lubomír Sedlář <lsedlar@redhat.com>
(cherry picked from commit ab11e0e4a9e0b70e1b78399931d357f92f3a27aa)
2025-09-29 18:28:03 +03:00

283 lines
10 KiB
Python

# -*- coding: utf-8 -*-
import errno
import os
import stat
from unittest import mock
from pungi import linker
from tests import helpers
class TestLinkerBase(helpers.PungiTestCase):
def setUp(self):
super(TestLinkerBase, self).setUp()
self.logger = mock.Mock()
self.linker = linker.Linker(logger=self.logger)
self.path_src = self.touch("file", "asdf")
def touch(self, path, contents=None):
path = os.path.join(self.topdir, path)
helpers.touch(path, contents)
return path
def mkdir(self, path):
path = os.path.join(self.topdir, path.lstrip("/"))
try:
os.makedirs(path)
except OSError as ex:
if ex.errno != errno.EEXIST:
raise
return path
def same_inode(self, path1, path2):
st1 = os.stat(path1)
st2 = os.stat(path2)
return (st1.st_dev, st1.st_ino) == (st2.st_dev, st2.st_ino)
def same_content(self, path1, path2):
if self.same_inode(path1, path2):
return True
data1 = open(path1, "r").read()
data2 = open(path2, "r").read()
return data1 == data2
def same_stat(self, path1, path2):
st1 = os.stat(path1)
st2 = os.stat(path2)
if not stat.S_ISDIR(st1.st_mode) and int(st1.st_mtime) != int(st2.st_mtime):
return False
if st1.st_size != st2.st_size:
return False
if st1.st_mode != st2.st_mode:
return False
return True
def assertSameStat(self, a, b):
self.assertTrue(self.same_stat(a, b))
def assertSameFile(self, a, b):
self.assertTrue(os.path.samefile(a, b))
def assertDifferentFile(self, a, b):
self.assertFalse(os.path.samefile(a, b))
class TestLinkerSymlink(TestLinkerBase):
def test_symlink(self):
path_dst = os.path.join(self.topdir, "symlink")
# symlink 'symlink' -> 'file'
self.linker.symlink(self.path_src, path_dst)
self.assertTrue(os.path.islink(path_dst))
self.assertEqual(os.readlink(path_dst), "file")
self.assertSameFile(self.path_src, path_dst)
# linking existing file must pass
self.linker.symlink(self.path_src, path_dst)
# linking existing file with different target must fail
self.assertRaises(
OSError, self.linker.symlink, self.path_src, path_dst, relative=False
)
def test_symlink_different_type(self):
# try to symlink 'symlink' -> 'another-file' ('symlink' already exists
# and points to 'file')
path_dst = os.path.join(self.topdir, "symlink")
os.symlink(self.path_src, path_dst)
path_src = self.touch("another-file")
self.assertRaises(OSError, self.linker.symlink, path_src, path_dst)
def test_relative_symlink(self):
# symlink bar -> ../a
dir_dst = os.path.join(self.topdir, "foo")
os.makedirs(dir_dst)
path_dst = os.path.join(dir_dst, "bar")
self.linker.symlink(self.path_src, path_dst)
self.assertTrue(os.path.islink(path_dst))
self.assertEqual(os.readlink(path_dst), "../file")
self.assertSameFile(self.path_src, path_dst)
def test_symlink_to_symlink(self):
path_dst = os.path.join(self.topdir, "symlink")
path_dst2 = os.path.join(self.topdir, "symlink-to-symlink")
# symlink 'symlink' -> 'file'
self.linker.symlink(self.path_src, path_dst, relative=True)
self.linker.symlink(path_dst, path_dst2)
class TestLinkerHardlink(TestLinkerBase):
def test_hardlink(self):
path_dst = os.path.join(self.topdir, "hardlink")
# hardlink 'file' to 'hardlink'
self.linker.hardlink(self.path_src, path_dst)
self.assertTrue(os.path.isfile(path_dst))
self.assertSameFile(self.path_src, path_dst)
# hardlink 'file' to 'foo/hardlink'
dir_dst = os.path.join(self.topdir, "foo")
os.makedirs(dir_dst)
path_dst = os.path.join(dir_dst, "hardlink")
self.linker.hardlink(self.path_src, path_dst)
self.assertTrue(os.path.isfile(path_dst))
self.assertSameFile(self.path_src, path_dst)
def test_hardlink_different_type(self):
# try to hardlink a file to existing dst with incompatible type
path_dst = os.path.join(self.topdir, "symlink")
os.symlink(self.path_src, path_dst)
self.assertRaises(OSError, self.linker.hardlink, self.path_src, path_dst)
class TestLinkerCopy(TestLinkerBase):
def test_copy(self):
path_dst = os.path.join(self.topdir, "b")
# copy 'file' to 'b'
self.linker.copy(self.path_src, path_dst)
self.assertTrue(os.path.isfile(path_dst))
self.assertDifferentFile(self.path_src, path_dst)
def test_copy_to_existing_file_with_different_content(self):
path_dst = os.path.join(self.topdir, "b")
helpers.touch(path_dst, "xxx")
self.assertRaises(Exception, self.linker.copy, self.path_src, path_dst)
def test_copy_to_directory(self):
dir_dst = os.path.join(self.topdir, "foo")
os.makedirs(dir_dst)
path_dst = os.path.join(dir_dst, "bar")
self.linker.copy(self.path_src, path_dst)
self.assertTrue(os.path.isfile(path_dst))
self.assertDifferentFile(self.path_src, path_dst)
def test_copy_different_type(self):
# try to copy a file to existing dst with incompatible type
path_dst = os.path.join(self.topdir, "symlink")
os.symlink(self.path_src, path_dst)
self.assertRaises(OSError, self.linker.copy, self.path_src, path_dst)
class TestLinkerLink(TestLinkerBase):
def setUp(self):
# This will create following structure as a source.
#
# + src/
# + file1
# + file2
# + symlink2 (-> 'subdir')
# + symlink3 (-> 'does-not-exist')
# + hardlink1 (same file as 'file1')
# + subdir/
# + file3
# + symlink1 (-> '../file1')
#
# The destination paths are are similar, but in dst/ top-level dir.
super(TestLinkerLink, self).setUp()
self.src_dir = self.mkdir("src")
self.file1 = self.touch("src/file1", "file1")
self.file2 = self.touch("src/file2", "file2")
os.utime(self.file2, (-1, 2))
self.file3 = self.touch("src/subdir/file3", "file3")
os.utime(self.file3, (-1, 3))
os.utime(os.path.dirname(self.file3), (-1, 31))
self.symlink1 = os.path.join(self.topdir, "src/subdir/symlink1")
os.symlink("../file1", self.symlink1)
self.symlink2 = os.path.join(self.topdir, "src/symlink2")
os.symlink("subdir", self.symlink2)
self.symlink3 = os.path.join(self.topdir, "src/symlink3")
os.symlink("does-not-exist", self.symlink3)
self.hardlink1 = os.path.join(self.topdir, "src/hardlink1")
os.link(self.file1, self.hardlink1)
self.dst_dir = os.path.join(self.topdir, "dst")
self.dst_file1 = os.path.join(self.dst_dir, "file1")
self.dst_file2 = os.path.join(self.dst_dir, "file2")
self.dst_file3 = os.path.join(self.dst_dir, "subdir", "file3")
self.dst_symlink1 = os.path.join(self.dst_dir, "subdir", "symlink1")
self.dst_symlink2 = os.path.join(self.dst_dir, "symlink2")
self.dst_symlink3 = os.path.join(self.dst_dir, "symlink3")
self.dst_hardlink1 = os.path.join(self.dst_dir, "hardlink1")
def test_link_file(self):
dst = os.path.join(self.topdir, "hardlink")
self.linker.link(self.path_src, dst, link_type="hardlink")
self.assertTrue(self.same_inode(self.path_src, dst))
self.assertFalse(os.path.islink(dst))
def test_symlink_file(self):
dst = os.path.join(self.topdir, "symlink")
self.linker.link(self.path_src, dst, link_type="symlink")
self.assertEqual(os.readlink(dst), "file")
self.assertTrue(os.path.islink(dst))
def test_copy_file(self):
dst = os.path.join(self.topdir, "copy")
self.linker.link(self.path_src, dst, link_type="copy")
self.assertFalse(os.path.islink(dst))
self.assertFalse(self.same_inode(self.path_src, dst))
self.assertTrue(self.same_content(self.path_src, dst))
def test_hardlink_or_copy_file(self):
dst = os.path.join(self.topdir, "hardlink-or-copy")
self.linker.link(self.path_src, dst, link_type="hardlink-or-copy")
self.assertTrue(self.same_inode(self.path_src, dst))
self.assertFalse(os.path.islink(dst))
def test_link_file_test_mode(self):
self.linker = linker.Linker(logger=self.logger, test=True)
dst = os.path.join(self.topdir, "hardlink")
self.linker.link(self.path_src, dst, link_type="hardlink")
self.assertFalse(os.path.isdir(self.dst_dir))
self.assertEqual(len(self.logger.mock_calls), 1)
def test_symlink_file_test_mode(self):
self.linker = linker.Linker(logger=self.logger, test=True)
dst = os.path.join(self.topdir, "symlink")
self.linker.link(self.path_src, dst, link_type="symlink")
self.assertFalse(os.path.isdir(self.dst_dir))
self.assertEqual(len(self.logger.mock_calls), 1)
def test_copy_file_test_mode(self):
self.linker = linker.Linker(logger=self.logger, test=True)
dst = os.path.join(self.topdir, "copy")
self.linker.link(self.path_src, dst, link_type="copy")
self.assertFalse(os.path.isdir(self.dst_dir))
self.assertEqual(len(self.logger.mock_calls), 1)
def test_hardlink_or_copy_file_test_mode(self):
self.linker = linker.Linker(logger=self.logger, test=True)
dst = os.path.join(self.topdir, "hardlink-or-copy")
self.linker.link(self.path_src, dst, link_type="hardlink-or-copy")
self.assertFalse(os.path.isdir(self.dst_dir))
self.assertEqual(len(self.logger.mock_calls), 1)
def test_link_file_to_existing_destination(self):
self.assertRaises(
OSError, self.linker.link, self.file1, self.file2, link_type="hardlink"
)
def test_symlink_file_to_existing_destination(self):
self.assertRaises(
OSError, self.linker.link, self.file1, self.file2, link_type="symlink"
)
def test_copy_file_to_existing_destination(self):
self.assertRaises(
OSError, self.linker.link, self.file1, self.file2, link_type="copy"
)
def test_hardlink_or_copy_file_to_existing_destination(self):
self.assertRaises(
OSError,
self.linker.link,
self.file1,
self.file2,
link_type="hardlink-or-copy",
)