312 lines
10 KiB
Python
312 lines
10 KiB
Python
# unit tests for driver disk utilities (utils/dd)
|
|
|
|
import os
|
|
import shutil
|
|
import unittest
|
|
import tempfile
|
|
import subprocess
|
|
|
|
from abc import ABC
|
|
|
|
from contextlib import contextmanager
|
|
from collections import namedtuple
|
|
from rpmfluff import SourceFile, GeneratedSourceFile, SimpleRpmBuild
|
|
from rpmfluff.utils import expectedArch
|
|
from rpmfluff.make import make_elf
|
|
from shutup import shutup
|
|
|
|
TOP_SRCDIR = os.environ.get("top_builddir", "../..")
|
|
UTILDIR = os.path.join(TOP_SRCDIR, "utils/dd")
|
|
|
|
# helpers for calling the utilities
|
|
Driver = namedtuple("Driver", "source name flags description")
|
|
|
|
|
|
def dd_list(dd_path, kernel_ver, anaconda_ver):
|
|
out = subprocess.check_output([os.path.join(UTILDIR, "dd_list"),
|
|
'-d', dd_path,
|
|
'-k', kernel_ver,
|
|
'-a', anaconda_ver],
|
|
stderr=open('/dev/null')).decode('utf-8')
|
|
return [Driver(*d.split('\n', 3)) for d in out.split('\n---\n')[:-1]]
|
|
|
|
|
|
def dd_extract(rpm_path, outdir, kernel_ver, flags='-blmf'):
|
|
out = subprocess.check_output([os.path.join(UTILDIR, "dd_extract"),
|
|
flags,
|
|
'-r', rpm_path,
|
|
'-d', outdir,
|
|
'-k', kernel_ver],
|
|
stderr=subprocess.STDOUT).decode('utf-8')
|
|
return out
|
|
|
|
|
|
def listfiles(dirname):
|
|
return set(os.path.join(root, f)
|
|
for root, dirs, files in os.walk(dirname)
|
|
for f in files)
|
|
|
|
|
|
# helpers for creating RPMs to test with
|
|
@contextmanager
|
|
def in_tempdir(prefix='tmp'):
|
|
oldcwd = os.getcwd()
|
|
tmpdir = tempfile.mkdtemp(prefix=prefix)
|
|
os.chdir(tmpdir)
|
|
yield
|
|
os.chdir(oldcwd)
|
|
shutil.rmtree(tmpdir)
|
|
|
|
|
|
def make_rpm(outdir, name='test', version='1.0', release='1', arch=None,
|
|
for_anaconda_ver=None, for_kernel_ver=None,
|
|
payload=None):
|
|
"""Create RPM for tests by using rpmfluff library."""
|
|
p = SimpleRpmBuild(name, version, release)
|
|
if for_anaconda_ver:
|
|
p.add_provides('installer-enhancement = %s' % for_anaconda_ver)
|
|
if for_kernel_ver:
|
|
p.add_provides('kernel-modules >= %s' % for_kernel_ver)
|
|
if payload is None:
|
|
payload = []
|
|
for item in payload:
|
|
p.add_installed_file(item.path,
|
|
item.create_source_file(),
|
|
**item.kwargs)
|
|
with in_tempdir("anaconda-test-dd."):
|
|
with shutup():
|
|
p.make()
|
|
rpmfile = p.get_built_rpm(arch or expectedArch)
|
|
outfile = os.path.join(outdir, os.path.basename(rpmfile))
|
|
shutil.move(rpmfile, outfile)
|
|
return p
|
|
|
|
|
|
class RPMFile(ABC):
|
|
"""Base class for simple container object for information about RPM payloads"""
|
|
def __init__(self, path, srcpath=None, mode=None):
|
|
self.path = path
|
|
self.srcpath = srcpath or os.path.basename(path)
|
|
self.kwargs = {}
|
|
|
|
if mode:
|
|
self.kwargs["mode"] = mode
|
|
|
|
def create_source_file(self):
|
|
"""Create source file for RPM fluff."""
|
|
pass
|
|
|
|
|
|
class TextRPMFile(RPMFile):
|
|
"""RPM file with text content"""
|
|
def __init__(self, contents='', **kwargs):
|
|
super().__init__(**kwargs)
|
|
self.contents = contents
|
|
|
|
def create_source_file(self):
|
|
return SourceFile(self.srcpath, self.contents)
|
|
|
|
|
|
class BinRPMFile(RPMFile):
|
|
"""RPM file with binary content"""
|
|
def create_source_file(self):
|
|
return GeneratedSourceFile(self.srcpath, make_elf())
|
|
|
|
|
|
binfile = BinRPMFile(
|
|
path="/usr/bin/fun",
|
|
mode="0755"
|
|
)
|
|
libfile = BinRPMFile(
|
|
path="/usr/lib/fun.so",
|
|
mode="0755"
|
|
)
|
|
fwfile = TextRPMFile(
|
|
path="/lib/firmware/fun.fw",
|
|
contents="HELLO I AM FIRMWARE"
|
|
)
|
|
kofile = TextRPMFile(
|
|
path="/lib/modules/KERNELVER/extra/net/fun.ko",
|
|
contents="KERNEL MODULE??? YOU BETCHA"
|
|
)
|
|
koxzfile = TextRPMFile(
|
|
path="/lib/modules/KERNELVER/extra/net/fun.ko.xz",
|
|
contents="XZ COMPRESSED KERNEL MODULE??? YOU BETCHA"
|
|
)
|
|
kozstfile = TextRPMFile(
|
|
path="/lib/modules/KERNELVER/extra/net/fun.ko.zst",
|
|
contents="ZSTD COMPRESSED KERNEL MODULE??? YOU BETCHA"
|
|
)
|
|
|
|
|
|
# Finally, the actual test cases
|
|
class ASelfTestCase(unittest.TestCase):
|
|
def setUp(self):
|
|
self.tmpdir = tempfile.mkdtemp(prefix="dd_tests.")
|
|
|
|
def tearDown(self):
|
|
shutil.rmtree(self.tmpdir)
|
|
|
|
def test_rpmfluff_simple(self):
|
|
"""check if rpmfluff is working"""
|
|
p = make_rpm(outdir=self.tmpdir)
|
|
rpmfile = os.path.basename(p.get_built_rpm(expectedArch))
|
|
assert rpmfile in os.listdir(self.tmpdir)
|
|
|
|
def test_rpmfluff_payload(self):
|
|
"""check if rpmfluff can add files to built RPMs"""
|
|
p = make_rpm(outdir=self.tmpdir, payload=(binfile, kofile))
|
|
rpmfile = os.path.basename(p.get_built_rpm(expectedArch))
|
|
assert rpmfile in os.listdir(self.tmpdir)
|
|
|
|
def test_utils_exist(self):
|
|
"""check that the dd utilities exist"""
|
|
assert "dd_list" in os.listdir(UTILDIR)
|
|
assert "dd_extract" in os.listdir(UTILDIR)
|
|
|
|
|
|
class DD_List_TestCase(unittest.TestCase):
|
|
def setUp(self):
|
|
self.tmpdir = tempfile.mkdtemp(prefix="dd_tests.")
|
|
self.k_ver = "4.1.4-333"
|
|
self.a_ver = "22.0"
|
|
|
|
def dd_list(self, dd_dir=None, kernel_ver=None, anaconda_ver=None):
|
|
return dd_list(dd_dir or self.tmpdir,
|
|
kernel_ver or self.k_ver,
|
|
anaconda_ver or self.a_ver)
|
|
|
|
def tearDown(self):
|
|
shutil.rmtree(self.tmpdir)
|
|
|
|
def test_dd_list(self):
|
|
"""dd_list: check output format"""
|
|
rpm = make_rpm(self.tmpdir, for_kernel_ver=self.k_ver)
|
|
drivers = self.dd_list()
|
|
assert len(drivers) == 1
|
|
d = drivers[0]
|
|
assert d.name == rpm.name
|
|
assert d.description == rpm.basePackage.description
|
|
assert d.description
|
|
assert os.path.exists(d.source)
|
|
assert "modules" in d.flags
|
|
assert "firmwares" in d.flags
|
|
assert "binaries" not in d.flags
|
|
assert "libraries" not in d.flags
|
|
|
|
def test_dd_list_multiple(self):
|
|
"""dd_list: multiple outputs for multiple packages"""
|
|
names = ['fun', 'even_more_fun', 'too_much_fun']
|
|
for name in names:
|
|
make_rpm(self.tmpdir, name=name, for_kernel_ver=self.k_ver)
|
|
drivers = self.dd_list()
|
|
assert len(drivers) == len(names)
|
|
assert set(d.name for d in drivers) == set(names)
|
|
|
|
def test_dd_list_binaries(self):
|
|
"""dd_list: 'Provides:installer-enhancement' implies bins/libs"""
|
|
make_rpm(self.tmpdir, for_anaconda_ver=self.a_ver)
|
|
drivers = self.dd_list()
|
|
assert not (drivers == [])
|
|
d = drivers[0]
|
|
assert "binaries" in d.flags
|
|
assert "libraries" in d.flags
|
|
assert "modules" not in d.flags
|
|
assert "firmwares" not in d.flags
|
|
|
|
def test_dd_list_old_kmods(self):
|
|
"""dd_list: ignore kmods if our kernel is too old"""
|
|
make_rpm(self.tmpdir, for_kernel_ver="5.0.1-555")
|
|
assert self.dd_list() == []
|
|
|
|
def test_dd_list_z_stream_kmods(self):
|
|
"""dd_list: accept kmods for z-stream kernels (#1207831)"""
|
|
make_rpm(self.tmpdir, for_kernel_ver=self.k_ver)
|
|
drivers = self.dd_list(kernel_ver=self.k_ver+".3")
|
|
assert drivers != []
|
|
d = drivers[0]
|
|
assert "modules" in d.flags
|
|
|
|
def test_dd_list_anaconda_old(self):
|
|
"""dd_list: ignore installer-enhancements if version doesn't match"""
|
|
make_rpm(self.tmpdir, for_anaconda_ver="23.0")
|
|
assert self.dd_list() == []
|
|
|
|
def test_dd_list_no_rpms(self):
|
|
"""dd_list: empty directory returns no results"""
|
|
assert self.dd_list() == []
|
|
|
|
def test_dd_list_missing_dir(self):
|
|
"""dd_list: missing directory returns no results"""
|
|
assert self.dd_list(dd_dir="/non/existent/path") == []
|
|
|
|
|
|
class DD_Extract_TestCase(unittest.TestCase):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.k_ver = "4.1.4-333"
|
|
cls.a_ver = "22.0"
|
|
cls.tmpdir = tempfile.mkdtemp(prefix="dd_tests.")
|
|
cls.rpmpayload = (binfile, kofile, koxzfile, kozstfile, fwfile, libfile)
|
|
make_rpm(cls.tmpdir, payload=cls.rpmpayload)
|
|
(cls.rpmfile,) = listfiles(cls.tmpdir)
|
|
|
|
def setUp(self):
|
|
self.outdir = os.path.join(self.tmpdir, "outdir")
|
|
os.mkdir(self.outdir)
|
|
|
|
def dd_extract(self, flags='-bmlf'):
|
|
dd_extract(self.rpmfile, self.outdir, self.k_ver, flags=flags)
|
|
return listfiles(self.outdir)
|
|
|
|
def tearDown(self):
|
|
shutil.rmtree(self.outdir)
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
shutil.rmtree(cls.tmpdir)
|
|
|
|
def test_dd_extract(self):
|
|
"""dd_extract: files are extracted correctly"""
|
|
self.dd_extract()
|
|
for item in self.rpmpayload:
|
|
out_path = self.outdir+item.path
|
|
if isinstance(item, TextRPMFile):
|
|
with open(out_path) as f:
|
|
assert item.contents == f.read()
|
|
elif isinstance(item, BinRPMFile):
|
|
assert os.path.exists(out_path)
|
|
# check that file have some generated content
|
|
assert os.stat(out_path).st_size > 1
|
|
|
|
def test_dd_extract_chmod(self):
|
|
"""dd_extract: files get correct mode (#1222056)"""
|
|
self.dd_extract()
|
|
for f in self.rpmpayload:
|
|
# test only files which have file mode explicitly set
|
|
if 'mode' in f.kwargs:
|
|
binmode = os.stat(self.outdir+f.path).st_mode
|
|
expectmode = int(f.kwargs['mode'], 8)
|
|
print("testing", f.path)
|
|
assert binmode & expectmode == expectmode
|
|
|
|
def test_dd_extract_modules(self):
|
|
"""dd_extract: using --modules extracts only .ko, .ko.bz2, .ko.gz, .ko.xz and .ko.zst files"""
|
|
outfiles = self.dd_extract(flags='--modules')
|
|
assert outfiles == set([self.outdir+kofile.path, self.outdir+koxzfile.path, self.outdir+kozstfile.path])
|
|
|
|
def test_dd_extract_binaries(self):
|
|
"""dd_extract: using --binaries extracts only /bin, /sbin, etc."""
|
|
outfiles = self.dd_extract(flags='--binaries')
|
|
assert outfiles == set([self.outdir+binfile.path])
|
|
|
|
def test_dd_extract_libs(self):
|
|
"""dd_extract: using --libraries extracts only /lib etc."""
|
|
outfiles = self.dd_extract(flags='--libraries')
|
|
assert outfiles == set([self.outdir+libfile.path])
|
|
|
|
def test_dd_extract_firmware(self):
|
|
"""dd_extract: using --firmwares extracts only /lib/firmware"""
|
|
outfiles = self.dd_extract(flags='--firmwares')
|
|
assert outfiles == set([self.outdir+fwfile.path])
|