1 # Copyright (c) Twisted Matrix Laboratories.
2 # See LICENSE for details.
5 Tests for L{twisted.scripts.tap2rpm}.
9 from twisted.trial.unittest import TestCase, SkipTest
10 from twisted.python import procutils
11 from twisted.python import versions
12 from twisted.python import deprecate
13 from twisted.python.failure import Failure
14 from twisted.internet import utils
15 from twisted.scripts import tap2rpm
17 # When we query the RPM metadata, we get back a string we'll have to parse, so
18 # we'll use suitably rare delimiter characters to split on. Luckily, ASCII
19 # defines some for us!
20 RECORD_SEPARATOR = "\x1E"
21 UNIT_SEPARATOR = "\x1F"
25 def _makeRPMs(tapfile=None, maintainer=None, protocol=None, description=None,
26 longDescription=None, setVersion=None, rpmfile=None, type_=None):
28 Helper function to invoke tap2rpm with the given parameters.
33 tapfile = "dummy-tap-file"
34 handle = open(tapfile, "w")
35 handle.write("# Dummy TAP file\n")
38 args.extend(["--quiet", "--tapfile", tapfile])
41 args.extend(["--maintainer", maintainer])
43 args.extend(["--protocol", protocol])
45 args.extend(["--description", description])
47 args.extend(["--long_description", longDescription])
49 args.extend(["--set-version", setVersion])
51 args.extend(["--rpmfile", rpmfile])
53 args.extend(["--type", type_])
55 return tap2rpm.run(args)
59 def _queryRPMTags(rpmfile, taglist):
61 Helper function to read the given header tags from the given RPM file.
63 Returns a Deferred that fires with dictionary mapping a tag name to a list
64 of the associated values in the RPM header. If a tag has only a single
65 value in the header (like NAME or VERSION), it will be returned as a 1-item
68 Run "rpm --querytags" to see what tags can be queried.
71 # Build a query format string that will return appropriately delimited
72 # results. Every field is treated as an array field, so single-value tags
73 # like VERSION will be returned as 1-item lists.
74 queryFormat = RECORD_SEPARATOR.join([
75 "[%%{%s}%s]" % (tag, UNIT_SEPARATOR) for tag in taglist
78 def parseTagValues(output):
81 for tag, values in zip(taglist, output.split(RECORD_SEPARATOR)):
82 values = values.strip(UNIT_SEPARATOR).split(UNIT_SEPARATOR)
87 def checkErrorResult(failure):
88 # The current rpm packages on Debian and Ubuntu don't properly set up
89 # the RPM database, which causes rpm to print a harmless warning to
90 # stderr. Unfortunately, .getProcessOutput() assumes all warnings are
91 # catastrophic and panics whenever it sees one.
94 # http://twistedmatrix.com/trac/ticket/3292#comment:42
95 # http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=551669
96 # http://rpm.org/ticket/106
100 # Depending on kernel scheduling, we might read the whole error
101 # message, or only the first few bytes.
102 if str(failure.value).startswith("got stderr: 'error: "):
103 newFailure = Failure(SkipTest("rpm is missing its package "
104 "database. Run 'sudo rpm -qa > /dev/null' to create one."))
106 # Not the exception we were looking for; we should report the
110 # We don't want to raise the exception right away; we want to wait for
111 # the process to exit, otherwise we'll get extra useless errors
113 d = failure.value.processEnded
114 d.addBoth(lambda _: newFailure)
117 d = utils.getProcessOutput("rpm",
118 ("-q", "--queryformat", queryFormat, "-p", rpmfile))
119 d.addCallbacks(parseTagValues, checkErrorResult)
124 class TestTap2RPM(TestCase):
128 return self._checkForRpmbuild()
131 def _checkForRpmbuild(self):
133 tap2rpm requires rpmbuild; skip tests if rpmbuild is not present.
135 if not procutils.which("rpmbuild"):
136 raise SkipTest("rpmbuild must be present to test tap2rpm")
139 def _makeTapFile(self, basename="dummy"):
141 Make a temporary .tap file and returns the absolute path.
143 path = basename + ".tap"
144 handle = open(path, "w")
145 handle.write("# Dummy .tap file")
150 def _verifyRPMTags(self, rpmfile, **tags):
152 Check the given file has the given tags set to the given values.
155 d = _queryRPMTags(rpmfile, tags.keys())
156 d.addCallback(self.assertEqual, tags)
160 def test_optionDefaults(self):
162 Commandline options should default to sensible values.
164 "sensible" here is defined as "the same values that previous versions
167 config = tap2rpm.MyOptions()
168 config.parseOptions([])
170 self.assertEqual(config['tapfile'], 'twistd.tap')
171 self.assertEqual(config['maintainer'], 'tap2rpm')
172 self.assertEqual(config['protocol'], 'twistd')
173 self.assertEqual(config['description'], 'A TCP server for twistd')
174 self.assertEqual(config['long_description'],
175 'Automatically created by tap2rpm')
176 self.assertEqual(config['set-version'], '1.0')
177 self.assertEqual(config['rpmfile'], 'twisted-twistd')
178 self.assertEqual(config['type'], 'tap')
179 self.assertEqual(config['quiet'], False)
180 self.assertEqual(config['twistd_option'], 'file')
181 self.assertEqual(config['release-name'], 'twisted-twistd-1.0')
184 def test_protocolCalculatedFromTapFile(self):
186 The protocol name defaults to a value based on the tapfile value.
188 config = tap2rpm.MyOptions()
189 config.parseOptions(['--tapfile', 'pancakes.tap'])
191 self.assertEqual(config['tapfile'], 'pancakes.tap')
192 self.assertEqual(config['protocol'], 'pancakes')
195 def test_optionsDefaultToProtocolValue(self):
197 Many options default to a value calculated from the protocol name.
199 config = tap2rpm.MyOptions()
200 config.parseOptions([
201 '--tapfile', 'sausages.tap',
202 '--protocol', 'eggs',
205 self.assertEqual(config['tapfile'], 'sausages.tap')
206 self.assertEqual(config['maintainer'], 'tap2rpm')
207 self.assertEqual(config['protocol'], 'eggs')
208 self.assertEqual(config['description'], 'A TCP server for eggs')
209 self.assertEqual(config['long_description'],
210 'Automatically created by tap2rpm')
211 self.assertEqual(config['set-version'], '1.0')
212 self.assertEqual(config['rpmfile'], 'twisted-eggs')
213 self.assertEqual(config['type'], 'tap')
214 self.assertEqual(config['quiet'], False)
215 self.assertEqual(config['twistd_option'], 'file')
216 self.assertEqual(config['release-name'], 'twisted-eggs-1.0')
219 def test_releaseNameDefaultsToRpmfileValue(self):
221 The release-name option is calculated from rpmfile and set-version.
223 config = tap2rpm.MyOptions()
224 config.parseOptions([
225 "--rpmfile", "beans",
226 "--set-version", "1.2.3",
229 self.assertEqual(config['release-name'], 'beans-1.2.3')
232 def test_basicOperation(self):
234 Calling tap2rpm should produce an RPM and SRPM with default metadata.
236 basename = "frenchtoast"
238 # Create RPMs based on a TAP file with this name.
239 rpm, srpm = _makeRPMs(tapfile=self._makeTapFile(basename))
241 # Verify the resulting RPMs have the correct tags.
242 d = self._verifyRPMTags(rpm,
243 NAME=["twisted-%s" % (basename,)],
246 SUMMARY=["A TCP server for %s" % (basename,)],
247 DESCRIPTION=["Automatically created by tap2rpm"],
249 d.addCallback(lambda _: self._verifyRPMTags(srpm,
250 NAME=["twisted-%s" % (basename,)],
253 SUMMARY=["A TCP server for %s" % (basename,)],
254 DESCRIPTION=["Automatically created by tap2rpm"],
260 def test_protocolOverride(self):
262 Setting 'protocol' should change the name of the resulting package.
267 # Create RPMs based on a TAP file with this name.
268 rpm, srpm = _makeRPMs(tapfile=self._makeTapFile(basename),
271 # Verify the resulting RPMs have the correct tags.
272 d = self._verifyRPMTags(rpm,
273 NAME=["twisted-%s" % (protocol,)],
274 SUMMARY=["A TCP server for %s" % (protocol,)],
276 d.addCallback(lambda _: self._verifyRPMTags(srpm,
277 NAME=["twisted-%s" % (protocol,)],
278 SUMMARY=["A TCP server for %s" % (protocol,)],
284 def test_rpmfileOverride(self):
286 Setting 'rpmfile' should change the name of the resulting package.
291 # Create RPMs based on a TAP file with this name.
292 rpm, srpm = _makeRPMs(tapfile=self._makeTapFile(basename),
295 # Verify the resulting RPMs have the correct tags.
296 d = self._verifyRPMTags(rpm,
298 SUMMARY=["A TCP server for %s" % (basename,)],
300 d.addCallback(lambda _: self._verifyRPMTags(srpm,
302 SUMMARY=["A TCP server for %s" % (basename,)],
308 def test_descriptionOverride(self):
310 Setting 'description' should change the SUMMARY tag.
312 description = "eggplant"
314 # Create RPMs based on a TAP file with this name.
315 rpm, srpm = _makeRPMs(tapfile=self._makeTapFile(),
316 description=description)
318 # Verify the resulting RPMs have the correct tags.
319 d = self._verifyRPMTags(rpm,
320 SUMMARY=[description],
322 d.addCallback(lambda _: self._verifyRPMTags(srpm,
323 SUMMARY=[description],
329 def test_longDescriptionOverride(self):
331 Setting 'longDescription' should change the DESCRIPTION tag.
333 longDescription = "fig"
335 # Create RPMs based on a TAP file with this name.
336 rpm, srpm = _makeRPMs(tapfile=self._makeTapFile(),
337 longDescription=longDescription)
339 # Verify the resulting RPMs have the correct tags.
340 d = self._verifyRPMTags(rpm,
341 DESCRIPTION=[longDescription],
343 d.addCallback(lambda _: self._verifyRPMTags(srpm,
344 DESCRIPTION=[longDescription],
350 def test_setVersionOverride(self):
352 Setting 'setVersion' should change the RPM's version info.
356 # Create RPMs based on a TAP file with this name.
357 rpm, srpm = _makeRPMs(tapfile=self._makeTapFile(),
360 # Verify the resulting RPMs have the correct tags.
361 d = self._verifyRPMTags(rpm,
365 d.addCallback(lambda _: self._verifyRPMTags(srpm,
373 def test_tapInOtherDirectory(self):
375 tap2rpm handles tapfiles outside the current directory.
377 # Make a tapfile outside the current directory.
378 tempdir = self.mktemp()
380 tapfile = self._makeTapFile(os.path.join(tempdir, "bacon"))
382 # Try and make an RPM from that tapfile.
383 _makeRPMs(tapfile=tapfile)
386 def test_unsignedFlagDeprecationWarning(self):
388 The 'unsigned' flag in tap2rpm should be deprecated, and its use
389 should raise a warning as such.
391 config = tap2rpm.MyOptions()
392 config.parseOptions(['--unsigned'])
393 warnings = self.flushWarnings()
394 self.assertEqual(DeprecationWarning, warnings[0]['category'])
396 deprecate.getDeprecationWarningString(
397 config.opt_unsigned, versions.Version("Twisted", 12, 1, 0)),
398 warnings[0]['message'])
399 self.assertEqual(1, len(warnings))