# -*- 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()