From 6ea1c4573445f19b5b0df5ba34b85dcaf931772e Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Tue, 16 Apr 2019 16:39:03 -0700 Subject: [PATCH] Add rpmfluff temporarily There is a problem with rpmfluff and the current version of rpm in rawhide. Changes are upstream, but no new build has been done yet. --- Makefile | 2 +- tests/rpmfluff/rpmfluff.py | 2164 ++++++++++++++++++++++++++++++++++++ 2 files changed, 2165 insertions(+), 1 deletion(-) create mode 100644 tests/rpmfluff/rpmfluff.py diff --git a/Makefile b/Makefile index 1d9edc48..dbb020ef 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ check: test: @echo "*** Running tests ***" - PYTHONPATH=$(PYTHONPATH):./src/ $(PYTHON) -m nose -v --with-coverage --cover-erase --cover-branches \ + PYTHONPATH=$(PYTHONPATH):./tests/rpmfluff/:./src/ $(PYTHON) -m nose -v --with-coverage --cover-erase --cover-branches \ --cover-package=pylorax --cover-inclusive \ ./tests/pylorax/ ./tests/composer/ diff --git a/tests/rpmfluff/rpmfluff.py b/tests/rpmfluff/rpmfluff.py new file mode 100644 index 00000000..e1e440a1 --- /dev/null +++ b/tests/rpmfluff/rpmfluff.py @@ -0,0 +1,2164 @@ +# -*- coding: UTF-8 -*- +# +# Copyright (c) 2006-2016 Red Hat, Inc. All rights reserved. This copyrighted material +# is made available to anyone wishing to use, modify, copy, or +# redistribute it subject to the terms and conditions of the GNU General +# Public License v.2. +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# Author: David Malcolm +""" +rpmfluff is a lightweight way of building RPMs, and sabotaging them so they +are broken in controlled ways. + +It is intended for use when testing RPM-testers e.g. rpmlint +and writing test cases for RPM tools e.g. yum +""" + +from __future__ import print_function + +import unittest +import os +import os.path +import shutil +import sys +import rpm +import subprocess + +UTF8ENCODE = None + +def _which(cmd): + """Hacked Python 3.3+ shutil.which() with limited functionality.""" + path = os.environ.get("PATH", os.defpath) + for p in path.split(os.pathsep): + p = os.path.join(p, cmd) + if os.path.exists(p) and os.access(p, os.F_OK | os.X_OK): + return p + +if sys.version_info < (3, 3): + shutil.which = _which + +def _utf8_encode(s): + """ + RPM now returns all string data as surrogate-escaped utf-8 strings + so we need to introduce backwards compatible method to deal with that + """ + global UTF8ENCODE + + if UTF8ENCODE is None: + h = rpm.hdr() + test = 'test' + h['name'] = test + UTF8ENCODE = (test != h['name']) + + if UTF8ENCODE: + return s.encode('utf-8') + else: + return s + +def get_rpm_header(path): + assert(os.path.isfile(path)) + ts = rpm.TransactionSet() + ts.setVSFlags(-1) # disable all verifications + fd = os.open(path, os.O_RDONLY) + try: + h = ts.hdrFromFdno(fd) + return h + finally: + os.close(fd) + +def expand_macros(expr): + # If the expression contains RPM macros, return the expanded string + if '%' in expr: + return subprocess.check_output(['rpm', '-E', expr], universal_newlines=True).strip() + else: + return expr + +class Check: + """ + Something that ought to hold for the built RPMs + and can be checked automatically, and has a name via __str__ + """ + def check(self, build): + raise NotImplementedError + + def get_failure_message(self): + raise NotImplementedError + +class FailedCheck(Exception): + """ + Exception class representing a failed L{Check} + """ + def __init__(self, check, extraInfo=None): + self.check = check + self.extraInfo = extraInfo + super(FailedCheck, self).__init__() + + def __str__(self): + s = self.check.get_failure_message() + if self.extraInfo: + return "%s (%s)"%(s, self.extraInfo) + else: + return s + +class CheckPayloadFile(Check): + """Check that a built package contains a specified payload file or directory""" + def __init__(self, packageName, arch, fullPath): + self.packageName = packageName + self.arch = arch + self.fullPath = fullPath + + def __str__(self): + return 'Checking that %s RPM on %s contains payload file "%s"'%(self.packageName, self.arch, self.fullPath) + + def get_failure_message(self): + return '%s RPM on %s does not contain expected payload file "%s"'%(self.packageName, self.arch, self.fullPath) + + def check(self, build): + rpmHdr = build.get_built_rpm_header(self.arch, self.packageName) + if _utf8_encode(self.fullPath) not in rpmHdr[rpm.RPMTAG_FILENAMES]: + raise FailedCheck(self) + +class CheckSourceFile(Check): + """Check that an SRPM contains the given source file""" + def __init__(self, name): + self.name = name + + def __str__(self): + return 'Checking that SRPM contains source file "%s"'%self.name + + def get_failure_message(self): + return 'SRPM does not contain expected source file "%s"'%(self.name) + + def check(self, build): + srpmHdr = build.get_built_srpm_header() + # The values in srpmHdr are binary strings, and self.name + # may not be a binary string, so encode self.name. + if _utf8_encode(self.name) not in srpmHdr[rpm.RPMTAG_FILENAMES]: + raise FailedCheck(self) + +class CheckTrigger(Check): + """Check that a built package contains a specified L{Trigger}""" + def __init__(self, packageName, arch, trigger): + self.packageName = packageName + self.arch = arch + self.trigger = trigger + + def __str__(self): + return 'Checking that %s RPM on %s has trigger: %s'%(self.packageName, self.arch, self.trigger) + + def get_failure_message(self): + return '%s RPM on %s does not contain expected trigger "%s"'%(self.packageName, self.arch, self.trigger) + + def check(self, build): + rpmHdr = build.get_built_rpm_header(self.arch, self.packageName) + # Search by event type and trigger condition: + index = 0 + for t in rpmHdr[rpm.RPMTAG_TRIGGERTYPE]: + # print(t) + # print(rpmHdr[rpm.RPMTAG_TRIGGERCONDS][index]) + if t==_utf8_encode(self.trigger.event) and rpmHdr[rpm.RPMTAG_TRIGGERCONDS][index]==_utf8_encode(self.trigger.triggerConds): + if rpmHdr[rpm.RPMTAG_TRIGGERSCRIPTS][index]!=_utf8_encode(self.trigger.script): + raise FailedCheck(self, 'script "%s" did not match expected "%s"'%(rpmHdr[rpm.RPMTAG_TRIGGERSCRIPTS][index],self.trigger.script)) + expectedProgram = self.trigger.program + if expectedProgram is None: + expectedProgram = "/bin/sh" + if rpmHdr[rpm.RPMTAG_TRIGGERSCRIPTPROG][index]!=_utf8_encode(expectedProgram): + raise FailedCheck(self, 'executable "%s" did not match expected "%s"'%(rpmHdr[rpm.RPMTAG_TRIGGERSCRIPTPROG][index],expectedProgram)) + + # We have a match: + return + # No match: try next one: + index += 1 + # We din't find the trigger: + raise FailedCheck(self, 'trigger for event "%s" on "%s" not found within RPM'%(self.trigger.event, self.trigger.triggerConds)) + +class CheckRequires(Check): + def __init__(self, packageName, arch, requires): + self.packageName = packageName + self.arch = arch + self.requires = requires + + def __str__(self): + return 'Checking that %s RPM on %s has "Requires: %s"'%(self.packageName, self.arch, self.requires) + + def get_failure_message(self): + return '%s RPM on %s does not contain expected "Requires: %s"'%(self.packageName, self.arch, self.requires) + + def check(self, build): + rpmHdr = build.get_built_rpm_header(self.arch, self.packageName) + # Search by event type and trigger condition: + for t in rpmHdr[rpm.RPMTAG_REQUIRES]: + if t == self.requires: + return # found a match + + # We didn't find the requires: + raise FailedCheck(self) + +class CheckProvides(Check): + def __init__(self, packageName, arch, provides): + self.packageName = packageName + self.arch = arch + self.provides = provides + + def __str__(self): + return 'Checking that %s RPM on %s has "Provides: %s"'%(self.packageName, self.arch, self.provides) + + def get_failure_message(self): + return '%s RPM on %s does not contain expected "Provides %s"'%(self.packageName, self.arch, self.provides) + + def check(self, build): + rpmHdr = build.get_built_rpm_header(self.arch, self.packageName) + # Search by event type and trigger condition: + for t in rpmHdr[rpm.RPMTAG_PROVIDES]: + if t == self.provides: + return # found a match + + # We didn't find the provides: + raise FailedCheck(self) + +# Should scrap these in favour of strings, for base64 encoded files +class FileConstraint: + """ + Abstract base class for describing innards of a file + """ + def affect_file(self, dstFile): + raise NotImplementedError + +class BytesAt(FileConstraint): + """ + Class representing byte values at an offset in a file + """ + def __init__(self, offset, values): + self.offset = offset + self.values = values + + def affect_file(self, dstFile): + dstFile.seek(self.offset) + dstFile.write(self.values) + +def make_png(): + return [BytesAt(0, b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52" + b"\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4" + b"\x89\x00\x00\x00\x0a\x49\x44\x41\x54\x78\x9c\x63\x00\x01\x00\x00" + b"\x05\x00\x01\x0d\x0a\x2d\xb4\x00\x00\x00\x00\x49\x45\x4e\x44\xae" + b"\x42\x60\x82")] + +def make_gif(): + return [BytesAt(0, b"GIF89a\x01\x00\x01\x00\x00\x00\x00\x3b")] + +def make_elf(bit_format=64): + """ + See https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header + """ + if bit_format == 64: + return [BytesAt(0, b"\177ELF\002")] + elif bit_format == 32: + return [BytesAt(0, b"\177ELF\001")] + else: + raise Exception("make_elf: unknown bit format") + +class Buildable: + def is_up_to_date(self): + raise NotImplementedError + + def make(self): + # print("considering building %s"%self) + if not self.is_up_to_date(): + # print("doing it!") + self.do_make() + + def clean(self): + raise NotImplementedError + + def do_make(self): + raise NotImplementedError + +class RpmBuild(Buildable): + """ + Wrapper for an invocation of rpmbuild + """ + def __init__(self, buildArchs=None): + """ + buildArchs: + if None, the build will happen on the current arch + if non-None, should be a list of strings: the archs to build on + """ + self.buildArchs = buildArchs + + def is_up_to_date(self): + # FIXME: crude check for now: does the build dir exist? + if os.path.isdir(self.get_base_dir()): + return True + return False + + def get_base_dir(self): + """Determine the name of the base directory of the rpmbuild hierarchy""" + raise NotImplementedError + + def clean(self): + os.system('rm -rf %s'%self.get_base_dir()) + + def __create_directories(self): + """Sets up the directory hierarchy for the build""" + os.mkdir(self.get_base_dir()) + + # Make fake rpmbuild directories + for subDir in ['BUILD', 'SOURCES', 'SRPMS', 'RPMS']: + os.mkdir(os.path.join(self.get_base_dir(), subDir)) + + def do_make(self): + """ + Hook to actually perform the rpmbuild, gathering the necessary source files first + """ + self.clean() + + self.__create_directories() + + specFileName = self.gather_spec_file(self.get_base_dir()) + + sourcesDir = self.get_sources_dir() + self.gather_sources(sourcesDir) + + absBaseDir = os.path.abspath(self.get_base_dir()) + + buildArchs = () + if self.buildArchs: + buildArchs = self.buildArchs + else: + buildArchs = (expectedArch,) + for arch in buildArchs: + command = ["rpmbuild", "--define", "_topdir %s" % absBaseDir, + "--define", "_rpmfilename %%{ARCH}/%%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm", + "-ba", "--target", arch, specFileName] + try: + log = subprocess.check_output(command, stderr=subprocess.STDOUT).splitlines(True) + except subprocess.CalledProcessError as e: + raise RuntimeError('rpmbuild command failed with exit status %s: %s\n%s' + % (e.returncode, e.cmd, e.output)) + self.__write_log(log, arch) + + self.check_results() + + def __write_log(self, log, arch): + log_dir = self.get_build_log_dir(arch) + if not os.path.exists(log_dir): + os.makedirs(log_dir) + filename = self.get_build_log_path(arch) + f = open(filename, "wb") + for line in log: + f.write(line) + f.close() + + def get_build_log_dir(self, arch): + """For the sake of standardization, write build logs to $basedir/LOGS/$arch/build.log""" + return os.path.join(self.get_base_dir(), "LOGS", arch) + + def get_build_log_path(self, arch): + """For the sake of standardization, write build logs to $basedir/LOGS/$arch/build.log""" + return os.path.join(self.get_build_log_dir(arch), "build.log") + + def get_build_dir(self): + return os.path.join(self.get_base_dir(), "BUILD") + + def get_sources_dir(self): + return os.path.join(self.get_base_dir(), "SOURCES") + + def get_srpms_dir(self): + return os.path.join(self.get_base_dir(), "SRPMS") + + def get_rpms_dir(self): + return os.path.join(self.get_base_dir(), "RPMS") + + def gather_sources(self, sourcesDir): + """ + Pure virtual hook for gathering source for the build to the given location + """ + raise NotImplementedError + + def gather_spec_file(self, tmpDir): + """ + Pure virtual hook for gathering specfile for the build to the appropriate location + + @return: full path/name of specfile + """ + raise NotImplementedError + + def check_results(self): + """ + Pure virtual hook for performing checks upon the results of the build + """ + raise NotImplementedError + +class SourceFile: + def __init__(self, sourceName, content, encoding = 'utf8'): + self.sourceName = sourceName + self.content = content + self.encoding = encoding + + def _get_dst_file(self, sourcesDir): + import codecs + dstFileName = os.path.join(sourcesDir, self.sourceName) + if isinstance(self.content, bytes): + dstFile = open(dstFileName, "wb") + else: + dstFile = codecs.open(dstFileName, "wb", self.encoding) + return dstFile + + def write_file(self, sourcesDir): + dstFile = self._get_dst_file(sourcesDir) + dstFile.write(self.content) + dstFile.close() + +class GeneratedSourceFile: + def __init__(self, sourceName, fileConstraints): + self.sourceName = sourceName + self.fileConstraints = fileConstraints + + def _get_dst_file(self, sourcesDir): + dstFileName = os.path.join(sourcesDir, self.sourceName) + dstFile = open(dstFileName, 'wb') + return dstFile + + def write_file(self, sourcesDir): + dstFile = self._get_dst_file(sourcesDir) + for c in self.fileConstraints: + c.affect_file(dstFile) + dstFile.close() + +class ExternalSourceFile: + def __init__(self, sourceName, path): + self.sourceName = sourceName + self.path = path + + def _get_dst_file(self, sourcesDir): + dstFileName = os.path.join(sourcesDir, self.sourceName) + dstFile = open(dstFileName, 'wb') + return dstFile + + def write_file(self, sourcesDir): + dstFile = self._get_dst_file(sourcesDir) + for line in open(self.path): + dstFile.write(line) + +class GeneratedTarball: + def __init__(self, sourceName, internalPath, contents): + self.sourceName = sourceName + self.internalPath = internalPath + self.contents = contents + + def write_file(self, sourcesDir): + shutil.rmtree(self.internalPath, ignore_errors=True) + os.mkdir(self.internalPath) + for content in self.contents: + content.write_file(self.internalPath) + + compressionOption = '--gzip' + cmd = ["tar", "--create", compressionOption, + "--file", os.path.join(sourcesDir, self.sourceName), self.internalPath] + subprocess.check_call(cmd) + shutil.rmtree(self.internalPath) + + +hello_world = """#include + +int +main (int argc, char **argv) +{ + printf ("Hello world\\n"); + + return 0; +} +""" + +hello_world_patch = r"""--- main.c.old 2007-04-09 13:23:51.000000000 -0400 ++++ main.c 2007-04-09 13:24:12.000000000 -0400 +@@ -3,7 +3,7 @@ + int + main (int argc, char **argv) + { +- printf ("Hello world\n"); ++ printf ("Foo\n"); + + return 0; + } +""" + +simple_library_source = """#include + +void greet(const char *message) +{ + printf ("%s\\n", message); +} +""" + + +defaultChangelogFormat = """* Sun Jul 22 2018 John Doe - %s-%s +- Initial version +""" + +sample_man_page = u""".TH FOO "1" "May 2009" "foo 1.00" "User Commands" +.SH NAME +foo \\- Frobnicates the doohickey +.SH SYNOPSIS +.B foo +[\\fIOPTION\\fR]... + +.SH DESCRIPTION +A sample manpage +""" + +def get_expected_arch(): + # FIXME: do this by directly querying rpm python bindings: + evalArch = subprocess.check_output(['rpm', '--eval', '%{_arch}']) + + # first line of output, losing trailing carriage return + # convert to a unicode type for python3 + return evalArch.strip().decode('ascii') + +expectedArch = get_expected_arch() + +def can_compile_m32(): + # 64-bit hosts can compile 32-bit binaries by using -m32, but only if the + # necessary bits are installed (they are often not). + return os.path.exists('/usr/include/gnu/stubs-32.h') and os.path.exists('/lib/libgcc_s.so.1') + +def can_use_rpm_weak_deps(): + return int(rpm.__version_info__[0]) >= 4 and int(rpm.__version_info__[1]) >= 12 + +class Trigger: + def __init__(self, event, triggerConds, script, program=None): + """For documentation on RPM triggers, see + U{http://www.rpm.org/support/RPM-Changes-6.html} + + @param event: can be: + - "un" + - "in" + - "postun" + @type event: string + + @param triggerConds: the name of the target package, potentially with a conditional, e.g.: + "sendmail" + "fileutils > 3.0, perl < 1.2" + @type triggerConds: string + + @param script: textual content of the script to execute + @type script: string + + @param program: the progam used to execute the script + @type program: string + """ + self.event = event + self.triggerConds = triggerConds + self.script = script + self.program = program + + def output(self, specFile, subpackageName=""): + # Write trigger line: + specFile.write("%%trigger%s %s"%(self.event, subpackageName)) + if self.program: + specFile.write("-p %s"%self.program) + specFile.write(" -- %s\n"%self.triggerConds) + + # Write script: + specFile.write("%s\n"%self.script) + + def __str__(self): + result = "%%trigger%s "%(self.event) + if self.program: + result += "-p %s"%self.program + result += " -- %s\n"%self.triggerConds + result += "%s\n"%self.script + return result + +class Subpackage: + def __init__(self, suffix): + """ + @param suffix: the suffix part of the name. For example, a + "foo-devel" subpackage of "foo" has name "devel" + """ + self.suffix = suffix + + # Provide some sane defaults which rpmlint won't complain about: + self.group = "Applications/Productivity" + self.summary = "Dummy summary" + self.description = "This is a dummy description." + + self.section_requires = "" + self.section_recommends = "" + self.section_suggests = "" + self.section_supplements = "" + self.section_enhances = "" + self.section_provides = "" + self.section_obsoletes = "" + self.section_conflicts = "" + self.section_files = "" + + self.section_pre = "" + self.section_post = "" + self.section_preun = "" + self.section_postun = "" + + self.triggers = [] + + def add_group(self, groupName): + "Add a group name to the .spec file" + self.group = groupName + + def add_description(self, descriptiveText): + "Change the default description for the rpm" + self.description = descriptiveText + + def add_summary(self, summaryText): + "Change the default summary text for the rpm. You can describe the test, or ways in which the rpm is intentionally defective." + self.summary = summaryText + + def add_requires(self, requirement): + "Add a Requires: line" + self.section_requires += "Requires: %s\n"%requirement + + def add_suggests(self, suggestion): + "Add a Suggests: line" + self.section_suggests += "Suggests: %s\n"%suggestion + + def add_supplements(self, supplement): + "Add a Supplements: line" + self.section_supplements += "Supplements: %s\n"%supplement + + def add_enhances(self, enhancement): + "Add a Requires: line" + self.section_enhances += "Enhances: %s\n"%enhancement + + def add_recommends(self, recommendation): + "Add a Recommends: line" + self.section_recommends += "Recommends: %s\n"%recommendation + + def add_provides(self, capability): + "Add a Provides: line" + self.section_provides += "Provides: %s\n"%capability + + def add_obsoletes(self, obsoletes): + "Add a Obsoletes: line" + self.section_obsoletes += "Obsoletes: %s\n"%obsoletes + + def add_conflicts(self, conflicts): + "Add a Conflicts: line" + self.section_conflicts += "Conflicts: %s\n"%conflicts + + def add_pre(self, preLine): + self.section_pre += preLine + + def add_post(self, postLine): + self.section_post += postLine + + def add_preun(self, preunLine): + self.section_preun += preunLine + + def add_postun(self, postunLine): + self.section_postun += postunLine + + def add_trigger(self, trigger): + "Add a trigger" + self.triggers.append(trigger) + + def write_triggers(self, specFile): + for trigger in self.triggers: + trigger.output(specFile, self.suffix) + +class SimpleRpmBuild(RpmBuild): + """A wrapper for rpmbuild that also provides a canned way of generating a + specfile and the source files.""" + def __init__(self, name, version, release, buildArchs=None): + RpmBuild.__init__(self, buildArchs) + + self.specfileEncoding = 'utf-8' + + self.checks = [] + + self.header = "# autogenerated specfile\n" + self.name = name + self.epoch = None + self.version = version + self.release = release + # Provide sane default which rpmlint won't complain about: + self.license = "GPL" + self.vendor = "" + self.packager = "" + self.url = "" + + self.basePackage = Subpackage('') + self.subPackages = [] + + self.makeDebugInfo = False + + self.sources = {} + self.patches = {} + + self.section_sources = "" + self.section_patches = "" + self.section_prep = "" + self.section_build = "" + self.section_clean = "rm -rf $$RPM_BUILD_ROOT" + self.section_install = "" + + self.section_pre = "" + self.section_post = "" + self.section_preun = "" + self.section_postun = "" + + self.section_changelog = defaultChangelogFormat%(version, release) + + def get_base_dir(self): + return "test-rpmbuild-%s-%s-%s"%(self.name, self.version, expand_macros(self.release)) + + def get_subpackage_names(self): + """ + @return: generates a list of subpackage names: e.g. + ['foo', 'foo-devel', 'foo-debuginfo'] + """ + result = [self.name] + for sub in self.subPackages: + result.append("%s-%s"%(self.name, sub.suffix)) + if self.makeDebugInfo: + result.append("%s-debuginfo"%self.name) + return result + + def get_subpackage_name(self, suffix): + if suffix is not None: + return '%s-%s' % (self.name, suffix) + else: + return self.name + + def get_subpackage(self, suffix): + """ + @return: get a subpackage by suffix (e.g. "devel"), or None/"" for the base package + """ + if suffix==None or suffix=='': + return self.basePackage + + for sub in self.subPackages: + if suffix == sub.suffix: + return sub + # Not found: + return None + + def gather_sources(self, sourcesDir): + #print(self.sources) + for source in self.sources.values(): + source.write_file(sourcesDir) + for patch in self.patches.values(): + patch.write_file(sourcesDir) + + def add_summary(self, summaryText): + "Change the default summary text for this package" + self.basePackage.add_summary(summaryText) + + def add_group(self, groupText): + "Change the default group text for this package" + self.basePackage.add_group(groupText) + + def add_description(self, descriptiveText): + "Change the default description text for this package" + self.basePackage.add_description(descriptiveText) + + def addLicense(self, licenseName): + "Set License" + self.license = licenseName + + def addVendor(self, vendorName): + "Set Vendor name" + self.vendor = vendorName + + def addPackager(self, packagerName): + "Set Packager name" + self.packager = packagerName + + def addUrl(self, urlName): + "Set URL" + self.url = urlName + + def add_pre(self, preLine): + "Append a line to the %pre script section of this package" + self.section_pre += preLine + + def add_post(self, postLine): + "Append a line to the %post script section of this package" + self.section_post += postLine + + def add_preun(self, preunLine): + "Append a line to the %preun script section of this package" + self.section_preun += preunLine + + def add_postun(self, postunLine): + "Append a line to the %postun script section of this package" + self.section_postun += postunLine + + def gather_spec_file(self, tmpDir): + import codecs + specFileName = os.path.join(tmpDir, "%s.spec"%self.name) + specFile = codecs.open(specFileName, "wb", self.specfileEncoding) + specFile.write(self.header) + specFile.write("Summary: %s\n"%self.basePackage.summary) + specFile.write("Name: %s\n"%self.name) + if self.epoch: + specFile.write("Epoch: %s\n"%self.epoch) + specFile.write("Version: %s\n"%self.version) + specFile.write("Release: %s\n"%self.release) + specFile.write("License: %s\n"%self.license) + specFile.write("Group: %s\n"%self.basePackage.group) + if self.vendor: + specFile.write("Vendor: %s\n"%self.vendor) + if self.packager: + specFile.write("Packager: %s\n"%self.packager) + if self.url: + specFile.write("URL: %s\n"%self.url) + specFile.write("\n") + specFile.write("BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX)\n") + + # FIXME: ExclusiveArch + + specFile.write(self.section_sources) + specFile.write(self.section_patches) + + specFile.write(self.basePackage.section_requires) + specFile.write(self.basePackage.section_recommends) + specFile.write(self.basePackage.section_suggests) + specFile.write(self.basePackage.section_supplements) + specFile.write(self.basePackage.section_enhances) + specFile.write(self.basePackage.section_provides) + specFile.write(self.basePackage.section_obsoletes) + specFile.write(self.basePackage.section_conflicts) + + specFile.write("\n") + + specFile.write("%description\n") + specFile.write("%s\n"%self.basePackage.description) + specFile.write("\n") + + for sub in self.subPackages: + specFile.write("%%package %s\n"%sub.suffix) + specFile.write("Group: %s\n"%sub.group) + specFile.write("Summary: %s\n"%sub.summary) + specFile.write(sub.section_requires) + specFile.write(sub.section_provides) + specFile.write(sub.section_obsoletes) + specFile.write(sub.section_conflicts) + specFile.write("\n") + specFile.write("%%description %s\n"%sub.suffix) + specFile.write("%s\n"%sub.description) + specFile.write("\n") + + specFile.write("%prep\n") + specFile.write(self.section_prep) + specFile.write("\n") + + specFile.write("%build\n") + specFile.write(self.section_build) + specFile.write("\n") + + specFile.write("%clean\n") + specFile.write(self.section_clean) + specFile.write("\n") + + specFile.write("%install\n") + specFile.write("rm -rf $RPM_BUILD_ROOT\n") + specFile.write("mkdir $RPM_BUILD_ROOT\n") + specFile.write(self.section_install) + specFile.write("\n") + + if self.section_pre != '': + specFile.write("%pre\n") + specFile.write(self.section_pre) + specFile.write("\n") + + if self.section_post != '': + specFile.write("%post\n") + specFile.write(self.section_post) + specFile.write("\n") + + if self.section_preun != '': + specFile.write("%preun\n") + specFile.write(self.section_preun) + specFile.write("\n") + + if self.section_postun != '': + specFile.write("%postun\n") + specFile.write(self.section_postun) + specFile.write("\n") + + self.basePackage.write_triggers(specFile) + for sub in self.subPackages: + if sub.section_pre != '': + specFile.write("%%pre %s\n"%sub.suffix) + specFile.write(sub.section_pre) + specFile.write("\n") + + if sub.section_post != '': + specFile.write("%%post %s\n"%sub.suffix) + specFile.write(sub.section_post) + specFile.write("\n") + + if sub.section_preun != '': + specFile.write("%%preun %s\n"%sub.suffix) + specFile.write(sub.section_preun) + specFile.write("\n") + + if sub.section_postun != '': + specFile.write("%%postun %s\n"%sub.suffix) + specFile.write(sub.section_postun) + specFile.write("\n") + + sub.write_triggers(specFile) + + specFile.write("%files\n") + specFile.write(self.basePackage.section_files) + specFile.write("\n") + + if self.makeDebugInfo: + specFile.write("%debug_package\n") + + for sub in self.subPackages: + specFile.write("%%files %s\n"%sub.suffix) + specFile.write(sub.section_files) + specFile.write("\n") + + if self.section_changelog: + specFile.write("%changelog\n") + specFile.write(self.section_changelog) + specFile.write("\n") + specFile.close() + + return specFileName + + def check_results(self): + for check in self.checks: + check.check(self) + + def get_built_srpm(self): + return self.get_built_rpm('SRPMS') + + def get_built_rpm(self, arch, name=None): + # name can be given separately to allow for subpackages + if not name: + name = self.name + + if arch=="SRPMS": + archSuffix="src" + else: + archSuffix=arch + + builtRpmName="%s-%s-%s.%s.rpm"%(name, self.version, expand_macros(self.release), archSuffix) + if arch=="SRPMS": + builtRpmDir = self.get_srpms_dir() + else: + builtRpmDir = os.path.join(self.get_rpms_dir(), arch) + builtRpmPath = os.path.join(builtRpmDir, builtRpmName) + #print(builtRpmDir) + #print(builtRpmPath) + return builtRpmPath + + def get_built_srpm_header(self): + return self.get_built_rpm_header('SRPMS') + + def get_built_rpm_header(self, arch, name=None): + rpmFilename = self.get_built_rpm(arch, name) + return get_rpm_header(rpmFilename) + + def expected_archs(self): + """Get all arch subdirs we expect, including SRPMS""" + if self.buildArchs: + return self.buildArchs + ['SRPMS'] + else: + return [expectedArch, "SRPMS"] + + def get_build_archs(self): + """Get all archs we are building on (i.e. not including SRPMS)""" + if self.buildArchs: + return self.buildArchs + else: + return [expectedArch] + + def add_check(self, check): + self.checks.append(check) + + def add_payload_check(self, fullPath, subpackageSuffix=None): + absPath = os.path.join('/', fullPath) + for arch in self.get_build_archs(): + name = self.get_subpackage_name(subpackageSuffix) + self.add_check(CheckPayloadFile(name, arch, absPath)) + + def escape_path(self, path): + result = "" + for char in path: + if char in " $": + result += "\\" + result += char + return result + + # Various methods for adding things to the build: + + def add_devel_subpackage(self): + sub = self.add_subpackage('devel') + sub.group = "Development/Libraries" + sub.add_requires("%{name} = %{version}") + return sub + + def add_subpackage(self, name): + sub = Subpackage(name) + self.subPackages.append(sub) + return sub + + def add_requires(self, requirement): + "Add a Requires: line" + self.basePackage.add_requires(requirement) + + def add_recommends(self, recommendation): + "Add a Recommends: line" + self.basePackage.add_recommends(recommendation) + + def add_suggests(self, suggestion): + "Add a Suggests: line" + self.basePackage.add_suggests(suggestion) + + def add_supplements(self, supplement): + "Add a Supplements: line" + self.basePackage.add_supplements(supplement) + + def add_enhances(self, enhancement): + "Add a Requires: line" + self.basePackage.add_enhances(enhancement) + + def add_provides(self, capability): + "Add a Provides: line" + self.basePackage.add_provides(capability) + + def add_obsoletes(self, obsoletes): + "Add an Obsoletes: line" + self.basePackage.add_obsoletes(obsoletes) + + def add_conflicts(self, conflicts): + "Add an Conflicts: line" + self.basePackage.add_conflicts(conflicts) + + def add_build_requires(self, requirement): + self.basePackage.section_requires += "BuildRequires: %s\n"%requirement + + def add_trigger(self, trigger): + "Add a trigger" + self.basePackage.add_trigger(trigger) + for arch in self.get_build_archs(): + self.add_check(CheckTrigger(self.name, arch, trigger)) + + def add_source(self, source): + "Add source; returning index" + # add source to dict so it can be copied up: + sourceIndex = len(self.sources) + self.sources[sourceIndex] = source + + # add to section: + self.section_sources += "Source%i: %s\n"%(sourceIndex, source.sourceName) + + # add a copyup to BUILD from SOURCES to prep: + self.section_prep += "cp %%{SOURCE%i} .\n"%(sourceIndex) + + self.add_check(CheckSourceFile(source.sourceName)) + + return sourceIndex + + def add_patch(self, patch, applyPatch, patchUrl=None): + "Add patch; returning index" + # add patch to dict so it can be copied up: + patchIndex = len(self.patches) + self.patches[patchIndex] = patch + + if patchUrl: + patchName = patchUrl + else: + patchName = patch.sourceName + + # add to section: + self.section_patches += "Patch%i: %s\n"%(patchIndex, patchName) + self.add_check(CheckSourceFile(patch.sourceName)) + + if applyPatch: + self.section_prep += "%%patch%i\n"%patchIndex + + return patchIndex + + def add_compressed_file(self, sourceFile, installPath, createParentDirs=True, subpackageSuffix=None): + self.add_source(sourceFile) + + if createParentDirs: + self.create_parent_dirs(installPath) + self.section_install += "gzip -c %s > $RPM_BUILD_ROOT/%s\n"%(sourceFile.sourceName, installPath) + + sub = self.get_subpackage(subpackageSuffix) + sub.section_files += "/%s\n"%installPath + self.add_payload_check(installPath, subpackageSuffix) + + def create_parent_dirs(self, installPath): + """ + Given a file at installPath, add commands to installation to ensure + the directory holding it exists. + """ + (head, _tail) = os.path.split(installPath) + self.section_install += "mkdir -p $RPM_BUILD_ROOT/%s\n"%head + + def add_mode(self, + installPath, + mode): + self.section_install += "chmod %s $RPM_BUILD_ROOT/%s\n"%(mode, self.escape_path(installPath)) + + def add_installed_file(self, + installPath, + sourceFile, + mode=None, + createParentDirs=True, + subpackageSuffix=None, + isConfig=False, + isDoc=False, + isGhost=False, + owner=None, + group=None): + """Add a simple source file to the sources, and set it up to be copied up directly at %install, potentially with certain permissions""" + sourceId = self.add_source(sourceFile) + + if createParentDirs: + self.create_parent_dirs(installPath) + self.section_install += "cp %%{SOURCE%i} $RPM_BUILD_ROOT/%s\n"%(sourceId, self.escape_path(installPath)) + if mode: + self.section_install += "chmod %s $RPM_BUILD_ROOT/%s\n"%(mode, self.escape_path(installPath)) + + sub = self.get_subpackage(subpackageSuffix) + tag="" + if owner or group: + tag += '%%attr(-,%s,%s) ' % (owner or '-', group or '-') + if isConfig: + tag+="%config " + if isDoc: + tag+="%doc " + if isGhost: + tag+="%ghost " + sub.section_files += '%s"/%s"\n'%(tag, installPath) + + def add_installed_directory(self, + installPath, + mode=None, + subpackageSuffix=None): + """Add a simple creation of the directory into the %install phase, and pick it up in the %files list""" + if installPath[-1] == '/': + installPath = installPath[:-1] + self.section_install += "mkdir -p $RPM_BUILD_ROOT/%s\n"%installPath + if mode: + self.section_install += "chmod %s $RPM_BUILD_ROOT/%s\n"%(mode, self.escape_path(installPath)) + sub = self.get_subpackage(subpackageSuffix) + sub.section_files += "/%s\n"%installPath + self.add_payload_check(installPath, subpackageSuffix) + + def add_installed_symlink(self, installedPath, target, subpackageSuffix=None, isConfig=False, isDoc=False, isGhost=False): + """Add a simple symlinking into the %install phase, and pick it up in the %files list""" + self.create_parent_dirs(installedPath) + self.section_install += "ln -s %s $RPM_BUILD_ROOT/%s\n"%(target, installedPath) + sub = self.get_subpackage(subpackageSuffix) + tag = "" + if isConfig: + tag+="%config " + if isDoc: + tag+="%doc " + if isGhost: + tag+="%ghost " + sub.section_files += '%s"/%s"\n'%(tag, installedPath) + self.add_payload_check(installedPath, subpackageSuffix) + + def add_simple_payload_file(self): + """Trivial hook for adding a simple file to payload, hardcoding all params""" + self.add_installed_file(installPath = 'usr/share/doc/hello-world.txt', + sourceFile = SourceFile('hello-world.txt', 'hello world\n'), + isDoc=True) + + def add_simple_payload_file_random(self, size=100): + """Trivial hook for adding a simple file to payload, random (ASCII printable chars) content of specified size (default is 100 bytes), name based on the packages ENVRA and count of the source files (to be unique)""" + import random + random.seed() + content = '' + for _ in range(size): + content = content + chr(random.randrange(32, 127)) + name = "%s-%s-%s-%s-%s-%s.txt" % (self.epoch, self.name, self.version, expand_macros(self.release), self.get_build_archs()[0], len(self.sources)) + self.add_installed_file(installPath = 'usr/share/doc/%s' % name, + sourceFile = SourceFile(name, content), + isDoc=True) + + def add_simple_compilation(self, + sourceFileName="main.c", + sourceContent=hello_world, + compileFlags = "", + installPath="usr/bin/hello-world", + createParentDirs=True, + subpackageSuffix=None): + """Add a simple source file to the sources, build it, and install it somewhere, using the given compilation flags""" + _sourceId = self.add_source(SourceFile(sourceFileName, sourceContent)) + self.section_build += "%if 0%{?__isa_bits} == 32\n%define mopt -m32\n%endif\n" + self.section_build += "gcc %%{?mopt} %s %s\n"%(compileFlags, sourceFileName) + if createParentDirs: + self.create_parent_dirs(installPath) + self.section_install += "cp a.out $RPM_BUILD_ROOT/%s\n"%installPath + sub = self.get_subpackage(subpackageSuffix) + sub.section_files += "/%s\n"%installPath + self.add_payload_check(installPath, subpackageSuffix) + + def add_simple_library(self, + sourceFileName="foo.c", + sourceContent=simple_library_source, + compileFlags = "", + libraryName = 'libfoo.so', + installPath="usr/lib/libfoo.so", + createParentDirs=True, + subpackageSuffix=None): + """Add a simple source file to the sources, build it as a library, and + install it somewhere, using the given compilation flags""" + _sourceId = self.add_source(SourceFile(sourceFileName, sourceContent)) + self.section_build += "gcc --shared -fPIC -o %s %s %s\n"%(libraryName, compileFlags, sourceFileName) + if createParentDirs: + self.create_parent_dirs(installPath) + self.section_install += "cp %s $RPM_BUILD_ROOT/%s\n" % (libraryName, installPath) + sub = self.get_subpackage(subpackageSuffix) + sub.section_files += "/%s\n"%installPath + self.add_payload_check(installPath, subpackageSuffix) + + def add_fake_virus(self, installPath, sourceName, mode=None, subpackageSuffix=None): + """ + Generate an anti-virus test file. Not a real virus, but intended by + convention to generate a positive when tested by virus scanners. + + See U{http://www.eicar.org/anti_virus_test_file.htm} + """ + eicarTestContent = r"X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" + self.add_installed_file(installPath, SourceFile(sourceName, eicarTestContent), mode, subpackageSuffix=subpackageSuffix) + + def add_multilib_conflict(self, installPath="/usr/share/bogusly-arch-specific-data.txt", createParentDirs=True, subpackageSuffix=None): + """ + Add an architecture-specific file in a location that shouldn't be + architecture-specific, so that it would be a conflict if you tried + to install both 32-bit and 64-bit versions of the generated package. + """ + if createParentDirs: + self.create_parent_dirs(installPath) + self.section_install += 'echo "The value of RPM_OPT_FLAGS during the build is: $RPM_OPT_FLAGS" > $RPM_BUILD_ROOT/%s\n' % (installPath) + self.section_install += 'echo "The value of RPM_ARCH during the build is: $RPM_ARCH" >> $RPM_BUILD_ROOT/%s\n' % (installPath) + + sub = self.get_subpackage(subpackageSuffix) + sub.section_files += "/%s\n"%installPath + self.add_payload_check(installPath, subpackageSuffix) + + def add_build_warning(self, message): + """Add a message to stderr during the build, so that we can simulate + e.g. testsuite failures""" + # Want to generate stderr, but avoid stdout having similar content + # (which would lead to duplicates in the merged log). + # So we rot13 the desired message, and echo that through a shell + # rot13 (using tr), getting the desired output to stderr + import codecs + rot13Message = codecs.getencoder('rot-13')(message)[0] + self.section_build += "echo '%s' | tr 'a-zA-Z' 'n-za-mN-ZA-N' 1>&2\n" % rot13Message + + def add_changelog_entry(self, + message, + version, + release, + dateStr='Sun Jul 22 2018', + nameStr='John Doe '): + """Prepend a changelog entry""" + newEntry = "* %s %s - %s-%s\n- %s\n"%(dateStr, nameStr, version, release, message) + self.section_changelog = newEntry + "\n" + self.section_changelog + + def add_generated_tarball(self, + tarballName, + internalPath, + contents, + extract=True, + createParentDirs=True, + installPath='/usr/share', + subpackageSuffix=None): + _sourceIndex = self.add_source(GeneratedTarball(tarballName, internalPath, contents)) + if extract: + self.section_build += "tar -zxvf %s\n" % tarballName + if createParentDirs: + self.create_parent_dirs(os.path.join(installPath, internalPath)) + self.section_install += "cp -r %s $RPM_BUILD_ROOT/%s\n" % (internalPath, installPath) + sub = self.get_subpackage(subpackageSuffix) + for file in contents: + sub.section_files += '/%s/%s/%s\n' % (installPath, internalPath, file.sourceName) + + def add_manpage(self, + sourceFileName='foo.1', + sourceFileContent=sample_man_page, + installPath='usr/share/man/man1/foo.1', + createParentDirs=True, + subpackageSuffix=None): + sourceIndex = self.add_source(SourceFile(sourceFileName, sourceFileContent)) + if createParentDirs: + self.create_parent_dirs(installPath) + self.section_install += 'cp %%{SOURCE%i} $RPM_BUILD_ROOT/%s\n' % (sourceIndex, self.escape_path(installPath)) + + # brp-compress will compress all man pages. If the man page is already + # compressed, it will decompress the page and recompress it. + (installBase, installExt) = os.path.splitext(installPath) + if installExt in ('.gz', '.Z', '.bz2', '.xz', '.lzma'): + finalPath = installBase + '.gz' + else: + finalPath = installPath + '.gz' + + sub = self.get_subpackage(subpackageSuffix) + sub.section_files += '/%s\n' % finalPath + self.add_payload_check(finalPath, subpackageSuffix) + +class YumRepoBuild: + """Class for easily creating a yum repo from a collection of RpmBuild instances""" + def __init__(self, rpmBuilds): + """@type rpmBuilds: list of L{RpmBuild} instances""" + import tempfile + self.repoDir = tempfile.mkdtemp() + self.rpmBuilds = rpmBuilds + + def make(self, *arches): + # Build all the packages + for pkg in self.rpmBuilds: + pkg.make() + + # Now assemble into a yum repo: + for pkg in self.rpmBuilds: + for arch in arches: + if arch in pkg.get_build_archs(): + for subpackage in pkg.get_subpackage_names(): + try: + shutil.copy(pkg.get_built_rpm(arch, name=subpackage), self.repoDir) + except IOError: + pass # possibly repo arch set to noarch+x86_64 but RpmBuild + # built for noarch only? + + try: + subprocess.check_output(["createrepo_c", self.repoDir], stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as e: + raise RuntimeError('createrepo_c command failed with exit status %s: %s\n%s' + % (e.returncode, e.cmd, e.output)) + +testTrigger='print "This is the trigger!' + +class TestSimpleRpmBuild(unittest.TestCase): + def assert_header_has_item(self, rpmFilename, tagId, item, msg=None): + # Check that the header tag contains the specified item + h = get_rpm_header(rpmFilename) + self.assertIn(_utf8_encode(item), h[tagId], msg) + + def assert_requires(self, rpmFilename, requirement): + self.assert_header_has_item(rpmFilename, rpm.RPMTAG_REQUIRENAME, requirement, + "%s does not require %s" % (rpmFilename, requirement)) + + def assert_provides(self, rpmFilename, capability): + self.assert_header_has_item(rpmFilename, rpm.RPMTAG_PROVIDENAME, capability, + "%s does not provide %s" % (rpmFilename, capability)) + + def assert_obsoletes(self, rpmFilename, obsoletes): + self.assert_header_has_item(rpmFilename, rpm.RPMTAG_OBSOLETENAME, obsoletes, + "%s does not obsolete %s" % (rpmFilename, obsoletes)) + + def assert_conflicts(self, rpmFilename, conflicts): + self.assert_header_has_item(rpmFilename, rpm.RPMTAG_CONFLICTNAME, conflicts, + "%s does not conflict with %s" % (rpmFilename, conflicts)) + + def assert_recommends(self, rpmFilename, recommendation): + self.assert_header_has_item(rpmFilename, rpm.RPMTAG_RECOMMENDNAME, recommendation, + "%s does not recommend %s" % (rpmFilename, recommendation)) + + def assert_suggests(self, rpmFilename, suggestion): + self.assert_header_has_item(rpmFilename, rpm.RPMTAG_SUGGESTNAME, suggestion, + "%s does not suggest %s" % (rpmFilename, suggestion)) + + def assert_supplements(self, rpmFilename, supplement): + self.assert_header_has_item(rpmFilename, rpm.RPMTAG_SUPPLEMENTNAME, supplement, + "%s does not supplement %s" % (rpmFilename, supplement)) + + def assert_enhances(self, rpmFilename, enhancement): + self.assert_header_has_item(rpmFilename, rpm.RPMTAG_ENHANCENAME, enhancement, + "%s does not enhance %s" % (rpmFilename, enhancement)) + + def assert_header_contains(self, rpmFilename, tagId, text): + # Check that the header tag contains the specified piece of text + ts = rpm.TransactionSet() + ts.setVSFlags(-1) # disable all verifications + fd = os.open(rpmFilename, os.O_RDONLY) + h = ts.hdrFromFdno(fd) + os.close(fd) + self.assertIn(text, str(h[tagId])) + + def assert_is_dir(self, dirname): + self.assertTrue(os.path.isdir(dirname), "%s is not a directory" % dirname) + + def assert_is_file(self, filename): + self.assertTrue(os.path.isfile(filename), "%s is not a file" % filename) + + def setUp(self): + # Take the last element of the id (e.g., __main__.TestSimpleRpmBuild.test_add_buildrequires) + # and replace _ with - to make it look nicer + pkgname = self.id().split('.')[-1].replace('_', '-') + + self.rpmbuild = SimpleRpmBuild(pkgname, "0.1", "1") + + # If the build directory already exists, go ahead and fail + self.assertFalse(os.path.isdir(self.rpmbuild.get_base_dir()), + "build directory %s already exists" % self.rpmbuild.get_base_dir()) + + def tearDown(self): + shutil.rmtree(self.rpmbuild.get_base_dir(), ignore_errors=True) + + def test_build(self): + self.rpmbuild.make() + tmpDir = self.rpmbuild.get_base_dir() + + buildDir = os.path.join(tmpDir, "BUILD") + self.assert_is_dir(buildDir) + + _sourcesDir = os.path.join(tmpDir, "SOURCES") + self.assert_is_dir(buildDir) + + srpmsDir = os.path.join(tmpDir, "SRPMS") + self.assert_is_dir(srpmsDir) + _srpmFile = os.path.join(srpmsDir, "test-build-0.1-1.src.rpm") + self.assert_is_file(os.path.join(srpmsDir, "test-build-0.1-1.src.rpm")) + + rpmsDir = self.rpmbuild.get_rpms_dir() + self.assert_is_dir(rpmsDir) + + for arch in [expectedArch]: + # FIXME: sort out architecture properly + rpmFile = os.path.join(rpmsDir, arch, "test-build-0.1-1.%s.rpm"%arch) + self.assert_is_file(rpmFile) + h = get_rpm_header(rpmFile) + self.assertEqual(h['name'], b'test-build') + self.assertEqual(h['version'], b'0.1') + self.assertEqual(h['release'], b'1') + + def test_add_requires(self): + self.rpmbuild.add_requires("test-requirement") + self.rpmbuild.make() + # FIXME: sort out architecture properly + for arch in [expectedArch]: + rpmFile = self.rpmbuild.get_built_rpm(arch) + self.assert_is_file(rpmFile) + + self.assert_requires(rpmFile, 'test-requirement') + + def test_add_provides(self): + self.rpmbuild.add_provides("test-capability = 2.0") + self.rpmbuild.make() + # FIXME: sort out architecture properly + for arch in [expectedArch]: + rpmFile = self.rpmbuild.get_built_rpm(arch) + self.assert_is_file(rpmFile) + + self.assert_provides(rpmFile, 'test-capability') + + def test_add_obsoletes(self): + self.rpmbuild.add_obsoletes("test-obsoletes") + self.rpmbuild.make() + # FIXME: sort out architecture properly + for arch in [expectedArch]: + rpmFile = self.rpmbuild.get_built_rpm(arch) + self.assert_is_file(rpmFile) + + self.assert_obsoletes(rpmFile, 'test-obsoletes') + + def test_add_conflicts(self): + self.rpmbuild.add_conflicts('test-conflicts') + self.rpmbuild.make() + # FIXME: sort out architecture properly + for arch in [expectedArch]: + rpmFile = self.rpmbuild.get_built_rpm(arch) + self.assert_is_file(rpmFile) + + self.assert_conflicts(rpmFile, 'test-conflicts') + + @unittest.skipIf(not can_use_rpm_weak_deps(), 'RPM weak deps are not supported') + def test_add_recommends(self): + self.rpmbuild.add_recommends('test-recommendation') + self.rpmbuild.make() + # FIXME: sort out architecture properly + for arch in [expectedArch]: + rpmFile = self.rpmbuild.get_built_rpm(arch) + self.assert_is_file(rpmFile) + + self.assert_recommends(rpmFile, 'test-recommendation') + + @unittest.skipIf(not can_use_rpm_weak_deps(), 'RPM weak deps are not supported') + def test_add_suggests(self): + self.rpmbuild.add_suggests('test-suggestion') + self.rpmbuild.make() + # FIXME: sort out architecture properly + for arch in [expectedArch]: + rpmFile = self.rpmbuild.get_built_rpm(arch) + self.assert_is_file(rpmFile) + + self.assert_suggests(rpmFile, 'test-suggestion') + + @unittest.skipIf(not can_use_rpm_weak_deps(), 'RPM weak deps are not supported') + def test_add_supplements(self): + self.rpmbuild.add_supplements('test-supplement') + self.rpmbuild.make() + # FIXME: sort out architecture properly + for arch in [expectedArch]: + rpmFile = self.rpmbuild.get_built_rpm(arch) + self.assert_is_file(rpmFile) + + self.assert_supplements(rpmFile, 'test-supplement') + + @unittest.skipIf(not can_use_rpm_weak_deps(), 'RPM weak deps are not supported') + def test_add_enhances(self): + self.rpmbuild.add_enhances('test-enhancement') + self.rpmbuild.make() + # FIXME: sort out architecture properly + for arch in [expectedArch]: + rpmFile = self.rpmbuild.get_built_rpm(arch) + self.assert_is_file(rpmFile) + + self.assert_enhances(rpmFile, 'test-enhancement') + + def test_add_buildrequires(self): + self.rpmbuild.add_build_requires("gcc") + self.rpmbuild.make() + srpmFile = self.rpmbuild.get_built_srpm() + self.assert_is_file(srpmFile) + + self.assert_requires(srpmFile, 'gcc') + + def test_add_vendor(self): + vendor = 'My own RPM Lab' + self.rpmbuild.addVendor(vendor) + self.rpmbuild.make() + # FIXME: sort out architecture properly + for arch in [expectedArch]: + rpmFile = self.rpmbuild.get_built_rpm(arch) + self.assert_is_file(rpmFile) + + self.assert_header_has_item(rpmFile, rpm.RPMTAG_VENDOR, vendor) + + def test_add_group_default(self): + self.rpmbuild.make() + # FIXME: sort out architecture properly + for arch in [expectedArch]: + rpmFile = self.rpmbuild.get_built_rpm(arch) + self.assert_is_file(rpmFile) + + self.assert_header_has_item(rpmFile, rpm.RPMTAG_GROUP, 'Applications/Productivity') + + def test_add_group(self): + group = 'Some/Test/Group' + self.rpmbuild.add_group(group) + self.rpmbuild.make() + # FIXME: sort out architecture properly + for arch in [expectedArch]: + rpmFile = self.rpmbuild.get_built_rpm(arch) + self.assert_is_file(rpmFile) + + self.assert_header_has_item(rpmFile, rpm.RPMTAG_GROUP, group) + + def test_add_packager(self): + packager = 'Some Packager ' + self.rpmbuild.addPackager(packager) + self.rpmbuild.make() + # FIXME: sort out architecture properly + for arch in [expectedArch]: + rpmFile = self.rpmbuild.get_built_rpm(arch) + self.assert_is_file(rpmFile) + + self.assert_header_has_item(rpmFile, rpm.RPMTAG_PACKAGER, packager) + + def test_add_license(self): + licenseName = 'SomeLicense' + self.rpmbuild.addLicense(licenseName) + self.rpmbuild.make() + # FIXME: sort out architecture properly + for arch in [expectedArch]: + rpmFile = self.rpmbuild.get_built_rpm(arch) + self.assert_is_file(rpmFile) + + self.assert_header_has_item(rpmFile, rpm.RPMTAG_LICENSE, licenseName) + + def test_archs_build(self): + archs = ('i386', 'x86_64', 'ppc') + # Override the object created by setUp + self.rpmbuild = SimpleRpmBuild(self.rpmbuild.name, self.rpmbuild.version, + self.rpmbuild.release, archs) + self.rpmbuild.make() + for arch in archs: + rpmFile = self.rpmbuild.get_built_rpm(arch) + self.assert_is_file(rpmFile) + + def test_add_commiter(self): + commiter = 'Some Commiter ' + message = 'Fixed bug #123456' + self.rpmbuild.add_changelog_entry(message, '0.1', '1', 'Sun Jul 22 2018', commiter) + self.rpmbuild.make() + # FIXME: sort out architecture properly + for arch in [expectedArch]: + rpmFile = self.rpmbuild.get_built_rpm(arch) + self.assert_is_file(rpmFile) + + self.assert_header_contains(rpmFile, rpm.RPMTAG_CHANGELOGNAME, commiter) + self.assert_header_contains(rpmFile, rpm.RPMTAG_CHANGELOGTEXT, message) + + def test_add_url(self): + url = 'http://www.example.com/myproject/' + self.rpmbuild.addUrl(url) + self.rpmbuild.make() + # FIXME: sort out architecture properly + for arch in [expectedArch]: + rpmFile = self.rpmbuild.get_built_rpm(arch) + self.assert_is_file(rpmFile) + + self.assert_header_has_item(rpmFile, rpm.RPMTAG_URL, url) + + def test_add_pre(self): + script = 'echo "Hello World!"' + self.rpmbuild.add_pre(script) + self.rpmbuild.make() + # FIXME: sort out architecture properly + for arch in [expectedArch]: + rpmFile = self.rpmbuild.get_built_rpm(arch) + self.assert_is_file(rpmFile) + + self.assert_header_has_item(rpmFile, rpm.RPMTAG_PREIN, script) + + def test_add_post(self): + script = 'echo "Hello World!"' + self.rpmbuild.add_post(script) + self.rpmbuild.make() + # FIXME: sort out architecture properly + for arch in [expectedArch]: + rpmFile = self.rpmbuild.get_built_rpm(arch) + self.assert_is_file(rpmFile) + + self.assert_header_has_item(rpmFile, rpm.RPMTAG_POSTIN, script) + + def test_add_preun(self): + script = 'echo "Hello World!"' + self.rpmbuild.add_preun(script) + self.rpmbuild.make() + # FIXME: sort out architecture properly + for arch in [expectedArch]: + rpmFile = self.rpmbuild.get_built_rpm(arch) + self.assert_is_file(rpmFile) + + self.assert_header_has_item(rpmFile, rpm.RPMTAG_PREUN, script) + + def test_add_postun(self): + script = 'echo "Hello World!"' + self.rpmbuild.add_postun(script) + self.rpmbuild.make() + # FIXME: sort out architecture properly + for arch in [expectedArch]: + rpmFile = self.rpmbuild.get_built_rpm(arch) + self.assert_is_file(rpmFile) + + self.assert_header_has_item(rpmFile, rpm.RPMTAG_POSTUN, script) + + def test_add_sub_pre(self): + script = 'echo "Hello World!"' + self.rpmbuild.add_subpackage('subpackage-pre-test') + sub = self.rpmbuild.get_subpackage('subpackage-pre-test') + sub.add_pre(script) + self.rpmbuild.make() + # FIXME: sort out architecture properly + for arch in [expectedArch]: + rpmFile = self.rpmbuild.get_built_rpm(arch, "%s-%s" % (self.rpmbuild.name, sub.suffix)) + self.assert_is_file(rpmFile) + + self.assert_header_has_item(rpmFile, rpm.RPMTAG_PREIN, script) + + def test_add_sub_post(self): + script = 'echo "Hello World!"' + self.rpmbuild.add_subpackage('subpackage-post-test') + sub = self.rpmbuild.get_subpackage('subpackage-post-test') + sub.add_post(script) + self.rpmbuild.make() + # FIXME: sort out architecture properly + for arch in [expectedArch]: + rpmFile = self.rpmbuild.get_built_rpm(arch, "%s-%s" % (self.rpmbuild.name, sub.suffix)) + self.assert_is_file(rpmFile) + + self.assert_header_has_item(rpmFile, rpm.RPMTAG_POSTIN, script) + + def test_add_sub_preun(self): + script = 'echo "Hello World!"' + self.rpmbuild.add_subpackage('subpackage-preun-test') + sub = self.rpmbuild.get_subpackage('subpackage-preun-test') + sub.add_preun(script) + self.rpmbuild.make() + # FIXME: sort out architecture properly + for arch in [expectedArch]: + rpmFile = self.rpmbuild.get_built_rpm(arch, "%s-%s" % (self.rpmbuild.name, sub.suffix)) + self.assert_is_file(rpmFile) + + self.assert_header_has_item(rpmFile, rpm.RPMTAG_PREUN, script) + + def test_add_sub_postun(self): + script = 'echo "Hello World!"' + self.rpmbuild.add_subpackage('subpackage-postun-test') + sub = self.rpmbuild.get_subpackage('subpackage-postun-test') + sub.add_postun(script) + self.rpmbuild.make() + # FIXME: sort out architecture properly + for arch in [expectedArch]: + rpmFile = self.rpmbuild.get_built_rpm(arch, "%s-%s" % (self.rpmbuild.name, sub.suffix)) + self.assert_is_file(rpmFile) + + self.assert_header_has_item(rpmFile, rpm.RPMTAG_POSTUN, script) + + def test_subpackage_names_A(self): + self.assertEqual(self.rpmbuild.get_subpackage_names(), ["test-subpackage-names-A"]) + + def test_subpackage_names_B(self): + self.rpmbuild.add_devel_subpackage() + self.rpmbuild.add_subpackage('ssl') + self.rpmbuild.makeDebugInfo=True + self.assertEqual(self.rpmbuild.get_subpackage_names(), ['test-subpackage-names-B', + 'test-subpackage-names-B-devel', + 'test-subpackage-names-B-ssl', + 'test-subpackage-names-B-debuginfo']) + + def test_png(self): + self.rpmbuild.add_installed_file("/foo.png", GeneratedSourceFile("foo.png", make_png())) + self.rpmbuild.make() + + def test_gif(self): + self.rpmbuild.add_installed_file("/foo.gif", GeneratedSourceFile("foo.gif", make_gif())) + self.rpmbuild.make() + + def test_elf(self): + self.rpmbuild.add_installed_file("/foo.so", GeneratedSourceFile("foo.so", make_elf())) + self.rpmbuild.make() + + def test_elf_32(self): + self.rpmbuild.add_installed_file("/foo.so", + GeneratedSourceFile("foo.so", make_elf(bit_format=32))) + self.rpmbuild.make() + + def test_elf_64(self): + self.rpmbuild.add_installed_file("/foo.so", + GeneratedSourceFile("foo.so", make_elf(bit_format=64))) + self.rpmbuild.make() + + def test_elf_executable(self): + self.rpmbuild.add_installed_file("/foo.so", + GeneratedSourceFile("foo.so", make_elf()), mode="0755") + self.rpmbuild.make() + rpm_name = self.rpmbuild.get_built_rpm(self.rpmbuild.get_build_archs()[0]) + files = subprocess.check_output(["rpm", "-qp", "--qf", + "[%{FILENAMES} %{FILEMODES:perms}\n]", rpm_name]) + assert files.split(b"\n")[0].strip().decode() == '/foo.so -rwxr-xr-x' + + def test_escape_path(self): + self.assertEqual(self.rpmbuild.escape_path("Hello World.txt"), "Hello\\ World.txt") + + def test_add_installed_file_with_space(self): + # see http://www.redhat.com/archives/rpm-list/2006-October/msg00115.html + self.rpmbuild.add_installed_file("/this filename has a space in it.txt", GeneratedSourceFile("foo.so", make_elf())) + self.rpmbuild.make() + + def test_add_simple_payload_file(self): + self.rpmbuild.add_simple_payload_file() + self.rpmbuild.make() + + def test_add_simple_payload_file_random(self): + self.rpmbuild.add_simple_payload_file_random() + self.rpmbuild.make() + + def test_add_simple_payload_file_random_multi(self): + self.rpmbuild.add_simple_payload_file_random() + self.rpmbuild.add_simple_payload_file_random() + self.rpmbuild.add_simple_payload_file_random() + self.rpmbuild.make() + + def test_add_simple_payload_file_random_size(self): + self.rpmbuild.add_simple_payload_file_random(100) + self.rpmbuild.make() + + def test_multiple_sources(self): + self.rpmbuild.add_installed_file("/test-1", GeneratedSourceFile("test-1", make_elf())) + self.rpmbuild.add_installed_file("/test-2", GeneratedSourceFile("test-2", make_png())) + self.rpmbuild.add_installed_file("/test-3", GeneratedSourceFile("test-3", make_gif())) + + self.rpmbuild.make() + tmpDir = self.rpmbuild.get_base_dir() + + rpmsDir = os.path.join(tmpDir, "RPMS") + self.assert_is_dir(rpmsDir) + + for arch in [expectedArch]: + # FIXME: sort out architecture properly + rpmFile = self.rpmbuild.get_built_rpm(arch) + h = get_rpm_header(rpmFile) + self.assertEqual(h['name'], b'test-multiple-sources') + self.assertEqual(h['version'], b'0.1') + self.assertEqual(h['release'], b'1') + + def test_generated_tarball(self): + pkgName = b'test-generated-tarball' + self.rpmbuild.add_generated_tarball('test-tarball-0.1.tar.gz', + 'test-tarball-0.1', + [GeneratedSourceFile("test-1", make_elf()), + GeneratedSourceFile("test-2", make_png()), + GeneratedSourceFile("test-3", make_gif())]) + + self.rpmbuild.make() + tmpDir = self.rpmbuild.get_base_dir() + + rpmsDir = os.path.join(tmpDir, "RPMS") + self.assert_is_dir(rpmsDir) + + for arch in [expectedArch]: + # FIXME: sort out architecture properly + rpmFile = self.rpmbuild.get_built_rpm(arch) + h = get_rpm_header(rpmFile) + self.assertEqual(h['name'], pkgName) + self.assertEqual(h['version'], b'0.1') + self.assertEqual(h['release'], b'1') + + + def test_simple_compilation(self): + """Ensure that adding a compiled file works as expected""" + self.rpmbuild.add_simple_compilation() + self.rpmbuild.make() + + def test_installed_directory(self): + """Ensure that adding a directory with specific permissions works as + expected""" + self.rpmbuild.add_installed_directory("/var/spool/foo", mode="1777") + self.rpmbuild.make() + + rpmsDir = self.rpmbuild.get_rpms_dir() + self.assert_is_dir(rpmsDir) + + for arch in [expectedArch]: + # FIXME: sort out architecture properly + rpmFile = self.rpmbuild.get_built_rpm(arch) + h = get_rpm_header(rpmFile) + files = list(h.fiFromHeader()) + self.assertEqual(1, len(files)) + (filename, _size, mode, _mtime, _flags, _rdev, _inode, _FNlink, _Fstate, _vflags, _user, _group, _md5sum) = files[0] + self.assertEqual("/var/spool/foo", filename) + self.assertEqual(0o041777, mode) + + def test_installed_symlink(self): + self.rpmbuild.add_installed_symlink("foo", "bar") + self.rpmbuild.make() + + rpmsDir = self.rpmbuild.get_rpms_dir() + self.assert_is_dir(rpmsDir) + + for arch in [expectedArch]: + # FIXME: sort out architecture properly + rpmFile = self.rpmbuild.get_built_rpm(arch) + h = get_rpm_header(rpmFile) + files = list(h.fiFromHeader()) + self.assertEqual(1, len(files)) + self.assertEqual("/foo", files[0][0]) + + def test_config_symlink(self): + self.rpmbuild.add_installed_symlink("foo", "bar", isConfig=True) + self.rpmbuild.make() + + rpmsDir = self.rpmbuild.get_rpms_dir() + self.assert_is_dir(rpmsDir) + + for arch in [expectedArch]: + # FIXME: sort out architecture properly + rpmFile = self.rpmbuild.get_built_rpm(arch) + h = get_rpm_header(rpmFile) + files = list(h.fiFromHeader()) + self.assertEqual(1, len(files)) + self.assertEqual("/foo", files[0][0]) + + def test_doc_symlink(self): + self.rpmbuild.add_installed_symlink("foo", "bar", isDoc=True) + self.rpmbuild.make() + + rpmsDir = self.rpmbuild.get_rpms_dir() + self.assert_is_dir(rpmsDir) + + for arch in [expectedArch]: + # FIXME: sort out architecture properly + rpmFile = self.rpmbuild.get_built_rpm(arch) + h = get_rpm_header(rpmFile) + files = list(h.fiFromHeader()) + self.assertEqual(1, len(files)) + self.assertEqual("/foo", files[0][0]) + + def test_ghost_symlink(self): + self.rpmbuild.add_installed_symlink("foo", "bar", isGhost=True) + self.rpmbuild.make() + + rpmsDir = self.rpmbuild.get_rpms_dir() + self.assert_is_dir(rpmsDir) + + for arch in [expectedArch]: + # FIXME: sort out architecture properly + rpmFile = self.rpmbuild.get_built_rpm(arch) + h = get_rpm_header(rpmFile) + files = list(h.fiFromHeader()) + self.assertEqual(1, len(files)) + self.assertEqual("/foo", files[0][0]) + + def test_fake_virus(self): + """Ensure that adding a fake virus works as expected""" + self.rpmbuild.add_fake_virus('fake-virus-infectee.exe', 'fake-virus-infectee.exe') + self.rpmbuild.make() + + def test_debuginfo_generation(self): + self.rpmbuild.add_simple_compilation(compileFlags="-g") + self.rpmbuild.basePackage.section_files += "%debug_package\n" + self.rpmbuild.make() + for arch in [expectedArch]: + # FIXME: sort out architecture properly + rpmFile = self.rpmbuild.get_built_rpm(arch, name="test-debuginfo-generation-debuginfo") + self.assert_is_file(rpmFile) + + def test_devel_generation(self): + self.rpmbuild.add_devel_subpackage() + self.rpmbuild.make() + for arch in [expectedArch]: + # FIXME: sort out architecture properly + rpmFile = self.rpmbuild.get_built_rpm(arch, name="test-devel-generation-devel") + self.assert_is_file(rpmFile) + + self.assert_requires(rpmFile, 'test-devel-generation') + + def test_triggers(self): + """Ensure that adding a trigger works as expected""" + self.rpmbuild.add_trigger(Trigger("in", + "fileutils > 3.0", + testTrigger, + "/usr/bin/perl")) + self.rpmbuild.make() + + @unittest.skipIf(expectedArch != 'x86_64' or not can_compile_m32(), + 'host arch is not x86_64 or 32-bit support is missing') + def test_multiarch_compilation(self): + """Ensure that building on multiple archs works as expected""" + self.rpmbuild.buildArchs = ['i386', 'x86_64'] + self.rpmbuild.add_simple_compilation(installPath='/usr/bin/program') + self.rpmbuild.make() + hdr = self.rpmbuild.get_built_rpm_header('i386') + fi = hdr.fiFromHeader() + fi.next() + self.assertEqual('/usr/bin/program', fi.FN()) + self.assertEqual(1, fi.FColor()) + hdr = self.rpmbuild.get_built_rpm_header('x86_64') + fi = hdr.fiFromHeader() + fi.next() + self.assertEqual('/usr/bin/program', fi.FN()) + self.assertEqual(2, fi.FColor()) + + def test_multilib_conflict(self): + """Ensure that the hooks to create a multilib conflict work as expected""" + self.rpmbuild.add_multilib_conflict() + self.rpmbuild.make() + + def test_build_warning(self): + """Ensure that the hooks to simulate build warnings work as expected""" + self.rpmbuild.add_build_warning('# of unexpected failures 15') + self.rpmbuild.make() + + def test_add_patch(self): + """Ensure that adding a patch works as expected""" + self.rpmbuild.add_simple_compilation() + self.rpmbuild.add_patch(SourceFile(sourceName="change-greeting.patch", + content=hello_world_patch), + applyPatch=True) + self.rpmbuild.make() + + def test_add_compressed_file(self): + """Ensure that adding a compressed file works as expected""" + self.rpmbuild.add_compressed_file(SourceFile(sourceName="hello-world.txt", + content="Hello world"), + installPath='usr/share/hello-world.txt.gz') + self.rpmbuild.make() + + def test_add_config_file(self): + """Ensure that adding a file marked as config works as expected""" + self.rpmbuild.add_installed_file("/etc/foo.conf", + SourceFile("foo.conf", + "someOption=True"), + isConfig=True) + self.rpmbuild.make() + + def test_add_doc_file(self): + """Ensure that adding a file marked as documentation works as expected""" + self.rpmbuild.add_installed_file("/usr/share/foo/README", + SourceFile("README", + "Another useless file telling you to use 'info' rather than being helpful"), + isDoc=True) + self.rpmbuild.make() + + def test_add_ghost_file(self): + """Ensure that adding a file marked as a ghost works as expected""" + self.rpmbuild.add_installed_file("/var/cache/foo.txt", + SourceFile("foo.txt", + "Dummy file"), + isGhost=True) + self.rpmbuild.make() + + def test_add_file_with_owner_and_group(self): + self.rpmbuild.add_installed_file('/var/www/html/index.html', + SourceFile('index.html', '

Hello

'), + owner='apache', group='apache') + self.rpmbuild.make() + hdr = self.rpmbuild.get_built_rpm_header(expectedArch) + files = list(hdr.fiFromHeader()) + self.assertEqual(1, len(files)) + (filename, _size, _mode, _mtime, _flags, _rdev, _inode, _FNlink, _Fstate, _vflags, user, group, _md5sum) = files[0] + self.assertEqual('/var/www/html/index.html', filename) + self.assertEqual('apache', user) + self.assertEqual('apache', group) + + def test_specfile_encoding_utf8(self): + self.rpmbuild.section_changelog = u"* Fri Mar 30 2001 Trond Eivind Glomsr\u00F8d \nDo something" + self.rpmbuild.make() + + def test_specfile_encoding_iso8859(self): + self.rpmbuild.specfileEncoding = 'iso8859_10' + self.rpmbuild.section_changelog = u"* Fri Mar 30 2001 Trond Eivind Glomsr\u00F8d \nDo something" + self.rpmbuild.make() + + def test_epoch(self): + """Ensuring that setting the epoch works""" + self.rpmbuild.epoch = 3 + self.rpmbuild.make() + + srpmHdr = self.rpmbuild.get_built_srpm_header() + self.assertEqual(3, srpmHdr[rpm.RPMTAG_EPOCH]) + + def test_add_manpage(self): + self.rpmbuild.add_manpage() + self.rpmbuild.make() + + def test_add_compressed_manpage(self): + """Ensuring that adding an already compressed manpage works correctly""" + import zlib + compressedPage = zlib.compress(sample_man_page.encode('ascii')) + self.rpmbuild.add_manpage(sourceFileName='foo.1.gz', + sourceFileContent=compressedPage, + installPath='usr/share/man/man1/foo.1.gz') + self.rpmbuild.make() + + def test_add_differently_compressed_manpage(self): + """Ensuring that a non-gzip compressed manpage is re-compressed""" + import bz2 + compressedPage = bz2.compress(sample_man_page.encode('ascii')) + self.rpmbuild.add_manpage(sourceFileName='foo.1.bz2', + sourceFileContent=compressedPage, + installPath='usr/share/man/man1/foo.1.bz2') + self.rpmbuild.make() + + def test_dist_tag(self): + """Ensuring that macros in the release tag work""" + self.rpmbuild.release = '1%{?dist}' + self.rpmbuild.make() + + self.assert_is_file(self.rpmbuild.get_built_rpm(expectedArch)) + +class YumRepoBuildTests(unittest.TestCase): + def assert_is_dir(self, dirname): + self.assertTrue(os.path.isdir(dirname), "%s is not a directory" % dirname) + + def assert_is_file(self, filename): + self.assertTrue(os.path.isfile(filename), "%s is not a file" % filename) + + @unittest.skipIf(not shutil.which("createrepo_c"), "createrepo_c not found in PATH") + def test_small_repo(self): + """Assemble a small yum repo of 3 packages""" + pkgs = [] + names = ['foo', 'bar', 'baz'] + for name in names: + pkgs.append(SimpleRpmBuild("test-package-%s"%name, "0.1", "1")) + repo = YumRepoBuild(pkgs) + + try: + repo.make(expectedArch) + + # Check that the expected files were created: + for name in names: + rpmFile = os.path.join(repo.repoDir, "test-package-%s-0.1-1.%s.rpm"%(name, expectedArch)) + self.assert_is_file(rpmFile) + repodataDir = os.path.join(repo.repoDir, "repodata") + self.assert_is_dir(repodataDir) + repomd = os.path.join(repodataDir, "repomd.xml") + self.assert_is_file(repomd) + + # Parse the repomd and look for the expected data + import xml.etree.ElementTree as ET + tree = ET.parse(repomd) + for mdtype in ("filelists", "other", "primary"): + element = tree.findall(".//{http://linux.duke.edu/metadata/repo}data[@type='%s']/{http://linux.duke.edu/metadata/repo}location" % mdtype) + self.assertTrue(len(element) == 1, "Could not find data for type %s" % mdtype) + self.assert_is_file(os.path.join(repo.repoDir, element[0].get('href'))) + finally: + shutil.rmtree(repo.repoDir, ignore_errors=True) + for pkg in repo.rpmBuilds: + shutil.rmtree(pkg.get_base_dir()) + + @unittest.skipIf(not shutil.which('createrepo_c'), 'createrepo_c not found in PATH') + def test_includes_subpackages(self): + package = SimpleRpmBuild('test-package', '0.1', '1') + package.add_devel_subpackage() + package.add_subpackage('python') + repo = YumRepoBuild([package]) + self.addCleanup(shutil.rmtree, package.get_base_dir()) + self.addCleanup(shutil.rmtree, repo.repoDir) + + repo.make(expectedArch) + + self.assert_is_dir(os.path.join(repo.repoDir, 'repodata')) + self.assert_is_file(os.path.join(repo.repoDir, 'test-package-0.1-1.%s.rpm' % expectedArch)) + self.assert_is_file(os.path.join(repo.repoDir, 'test-package-devel-0.1-1.%s.rpm' % expectedArch)) + self.assert_is_file(os.path.join(repo.repoDir, 'test-package-python-0.1-1.%s.rpm' % expectedArch)) + + @unittest.skipIf(expectedArch != 'x86_64' or not can_compile_m32() or not shutil.which("createrepo_c"), + 'host arch is not x86_64 or 32-bit support is missing or createrepo_c not found in PATH') + def test_multiple_arches(self): + package = SimpleRpmBuild('test-multilib-package', '0.1', '1', ['i386', 'x86_64']) + repo = YumRepoBuild([package]) + self.addCleanup(shutil.rmtree, package.get_base_dir()) + self.addCleanup(shutil.rmtree, repo.repoDir) + + repo.make('i386', 'x86_64') + + # Check that the repo was built with both the i386 and x86_64 packages + self.assert_is_dir(os.path.join(repo.repoDir, 'repodata')) + self.assert_is_file(os.path.join(repo.repoDir, 'test-multilib-package-0.1-1.i386.rpm')) + self.assert_is_file(os.path.join(repo.repoDir, 'test-multilib-package-0.1-1.x86_64.rpm')) + + @unittest.skipIf(not shutil.which('createrepo_c'), 'createrepo_c not found in PATH') + def test_arch_with_noarch(self): + archful_package = SimpleRpmBuild('test-package', '0.1', '1') + noarch_package = SimpleRpmBuild('python-package', '0.1', '1', ['noarch']) + repo = YumRepoBuild([archful_package, noarch_package]) + self.addCleanup(shutil.rmtree, archful_package.get_base_dir()) + self.addCleanup(shutil.rmtree, noarch_package.get_base_dir()) + self.addCleanup(shutil.rmtree, repo.repoDir) + + repo.make(expectedArch, 'noarch') + + self.assert_is_dir(os.path.join(repo.repoDir, 'repodata')) + self.assert_is_file(os.path.join(repo.repoDir, 'test-package-0.1-1.%s.rpm' % expectedArch)) + self.assert_is_file(os.path.join(repo.repoDir, 'python-package-0.1-1.noarch.rpm')) + +if __name__ == "__main__": + unittest.main() +