3
# test DistUtilsExtra.auto
5
import sys, unittest, shutil, tempfile, os, os.path, subprocess
7
class T(unittest.TestCase):
9
self.src = tempfile.mkdtemp()
11
self._mksrc('setup.py', '''
12
from DistUtilsExtra.auto import setup
17
description='Test suite package',
18
url='https://foo.example.com',
19
license='GPL v2 or later',
21
author_email='martin.pitt@example.com',
25
self.install_tree = None
29
# check that setup.py clean removes everything
30
(o, e, s) = self.setup_py(['clean', '-a'])
31
self.assertEqual(e, '')
32
self.assertEqual(s, 0)
33
cruft = self.diff_snapshot()
34
self.assertEqual(cruft, '', 'no cruft after cleaning:\n' + cruft)
36
shutil.rmtree(self.src)
38
shutil.rmtree(self.snapshot)
40
shutil.rmtree(self.install_tree)
43
self.install_tree = None
46
# actual tests come here
50
'''empty source tree (just setup.py)'''
52
(o, e, s) = self.do_install()
53
self.assertEqual(e, '')
54
self.assertEqual(s, 0)
55
self.failIf('following files are not recognized' in o)
57
f = self.installed_files()
58
# just installs the .egg_info
59
self.assertEqual(len(f), 1)
60
self.assert_(f[0].endswith('.egg-info'))
62
def test_modules(self):
65
self._mksrc('yesme.py')
66
self._mksrc('stuff/notme.py')
68
(o, e, s) = self.do_install()
69
self.assertEqual(e, '')
70
self.assertEqual(s, 0)
71
self.assert_('following files are not recognized' in o)
72
self.assert_('\n stuff/notme.py\n' in o)
74
f = '\n'.join(self.installed_files())
75
self.assert_('-packages/yesme.py' in f)
76
self.failIf('notme' in f)
78
def test_packages(self):
81
self._mksrc('foopkg/__init__.py', '')
82
self._mksrc('foopkg/bar.py')
83
self._mksrc('foopkg/baz.py')
84
self._mksrc('noinit/notme.py')
86
(o, e, s) = self.do_install()
87
self.assertEqual(e, '')
88
self.assertEqual(s, 0)
89
self.assert_('following files are not recognized' in o)
90
self.assert_('\n noinit/notme.py\n' in o)
92
f = '\n'.join(self.installed_files())
93
self.assert_('foopkg/__init__.py' in f)
94
self.assert_('foopkg/bar.py' in f)
95
self.failIf('noinit' in f)
98
'''D-BUS configuration and service files'''
100
# D-BUS ACL configuration file
101
self._mksrc('daemon/com.example.foo.conf', '''<!DOCTYPE busconfig PUBLIC
102
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
103
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
107
# non-D-BUS configuration file
108
self._mksrc('daemon/defaults.conf', 'start = True\nlog = syslog')
110
# D-BUS system service
111
self._mksrc('daemon/com.example.foo.service', '''[D-BUS Service]
113
Exec=/usr/lib/foo/foo_daemon
116
# D-BUS session service
117
self._mksrc('gui/com.example.foo.gui.service', '''[D-BUS Service]
118
Name=com.example.Foo.GUI
119
Exec=/usr/bin/foo-gtk
122
# non-D-BUS .service file
123
self._mksrc('stuff/super.service', 'I am a file')
125
(o, e, s) = self.do_install()
126
self.assertEqual(e, '')
127
self.assertEqual(s, 0)
128
self.assert_('following files are not recognized' in o)
129
self.assert_('\n stuff/super.service\n' in o)
131
f = self.installed_files()
132
self.assertEqual(len(f), 4) # 3 D-BUS files plus .egg-info
133
self.assert_('/etc/dbus-1/system.d/com.example.foo.conf' in f)
134
self.assert_('/usr/share/dbus-1/system-services/com.example.foo.service' in f)
135
self.assert_('/usr/share/dbus-1/services/com.example.foo.gui.service' in f)
136
self.failIf('super.service' in '\n'.join(f))
139
'''gettext *.po files'''
143
(o, e, s) = self.do_install()
144
self.assertEqual(e, '')
145
self.assertEqual(s, 0)
146
self.failIf('following files are not recognized' in o)
147
f = self.installed_files()
148
self.assert_('/usr/share/locale/de/LC_MESSAGES/foo.mo' in f)
149
self.assert_('/usr/share/locale/fr/LC_MESSAGES/foo.mo' in f)
150
self.failIf('junk' in '\n'.join(f))
152
msgunfmt = subprocess.Popen(['msgunfmt',
153
os.path.join(self.install_tree,
154
'usr/share/locale/de/LC_MESSAGES/foo.mo')],
155
stdout=subprocess.PIPE)
156
out = msgunfmt.communicate()[0]
157
self.assertEqual(out, open(os.path.join(self.src, 'po/de.po')).read())
159
def test_policykit(self):
160
'''*.policy.in PolicyKit files'''
162
self._mksrc('daemon/com.example.foo.policy.in', '''<?xml version="1.0" encoding="UTF-8"?>
163
<!DOCTYPE policyconfig PUBLIC
164
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
165
"http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
167
<vendor>Foo project</vendor>
168
<vendor_url>https://foo.example.com</vendor_url>
170
<action id="com.example.foo.greet">
171
<_description>Good morning</_description>
172
<_message>Hello</_message>
174
<allow_active>yes</allow_active>
180
(o, e, s) = self.do_install()
181
self.assertEqual(e, '')
182
self.assertEqual(s, 0)
183
self.failIf('following files are not recognized' in o)
185
f = self.installed_files()
186
self.assert_('/usr/share/PolicyKit/policy/com.example.foo.policy' in f)
187
p = open(os.path.join(self.install_tree,
188
'usr/share/PolicyKit/policy/com.example.foo.policy')).read()
189
self.assert_('<description>Good morning</description>' in p)
190
self.assert_('<description xml:lang="de">Guten Morgen</description>' in p)
191
self.assert_('<message>Hello</message>' in p)
192
self.assert_('<message xml:lang="de">Hallo</message>' in p)
194
def test_desktop(self):
195
'''*.desktop.in files'''
197
self._mksrc('gui/foogtk.desktop.in', '''[Desktop Entry]
199
_Comment=Good morning
201
self._mksrc('gui/autostart/fooapplet.desktop.in', '''[Desktop Entry]
203
_Comment=Good morning
204
Exec=/usr/bin/fooapplet''')
207
(o, e, s) = self.do_install()
208
self.assertEqual(e, '')
209
self.assertEqual(s, 0)
210
self.failIf('following files are not recognized' in o)
212
f = self.installed_files()
213
self.assert_('/usr/share/autostart/fooapplet.desktop' in f)
214
self.assert_('/usr/share/applications/foogtk.desktop' in f)
216
p = open(os.path.join(self.install_tree,
217
'usr/share/autostart/fooapplet.desktop')).read()
218
self.assert_('\nName=Hello\n' in p)
219
self.assert_('\nName[de]=Hallo\n' in p)
220
self.assert_('\nComment[fr]=Bonjour\n' in p)
222
def test_icons(self):
225
self._mksrc('data/icons/scalable/actions/press.png')
226
self._mksrc('data/icons/48x48/apps/foo.png')
227
action_icon_path = os.path.join(self.src, 'data', 'icons', 'scalable',
229
os.symlink(os.path.join(action_icon_path, 'press.png'),
230
os.path.join(action_icon_path, 'crunch.png'))
232
(o, e, s) = self.do_install()
233
self.assertEqual(e, '')
234
self.assertEqual(s, 0)
235
self.failIf('following files are not recognized' in o)
237
f = self.installed_files()
238
self.assert_('/usr/share/icons/hicolor/scalable/actions/press.png' in f)
239
self.assert_('/usr/share/icons/hicolor/scalable/actions/crunch.png' in f)
240
self.assert_('/usr/share/icons/hicolor/48x48/apps/foo.png' in f)
241
# TODO: known to fail right now
242
#self.assert_(os.path.islink(os.path.join(self.install_tree,
243
# '/usr/share/icons/hicolor/scalable/actions/crunch.png'))
246
'''Auxiliary files in data/'''
248
# have some explicitly covered files, to check that they don't get
249
# installed into prefix/share/foo/ again
250
self._mksrc('setup.py', '''
251
from DistUtilsExtra.auto import setup
252
from glob import glob
257
description='Test suite package',
258
url='https://foo.example.com',
259
license='GPL v2 or later',
260
author='Martin Pitt',
261
author_email='martin.pitt@example.com',
264
('/lib/udev/rules.d', ['data/40-foo.rules']),
265
('/etc/foo', glob('data/*.conf')),
270
self._mksrc('data/stuff')
271
self._mksrc('data/handlers/red.py', 'import sys\nprint "RED"')
272
self._mksrc('data/handlers/blue.py', 'import sys\nprint "BLUE"')
273
self._mksrc('data/40-foo.rules')
274
self._mksrc('data/blob1.conf')
275
self._mksrc('data/blob2.conf')
277
(o, e, s) = self.do_install()
278
self.assertEqual(e, '')
279
self.assertEqual(s, 0)
280
self.failIf('following files are not recognized' in o)
282
f = self.installed_files()
283
self.assert_('/usr/share/foo/stuff' in f)
284
self.assert_('/usr/share/foo/handlers/red.py' in f)
285
self.assert_('/usr/share/foo/handlers/blue.py' in f)
286
self.assert_('/lib/udev/rules.d/40-foo.rules' in f)
287
self.assert_('/etc/foo/blob1.conf' in f)
288
self.assert_('/etc/foo/blob2.conf' in f)
289
self.failIf('/usr/share/foo/blob1.conf' in f)
290
self.failIf('/usr/share/foo/40-foo.rules' in f)
292
def test_scripts(self):
295
# these should get autoinstalled
296
self._mksrc('bin/yell', '#!/bin/sh', True)
297
self._mksrc('bin/shout', '#!/bin/sh', True)
298
self._mksrc('bin/foo', '#!/bin/sh', True)
301
self._mksrc('daemon/food', '#!/bin/sh', True) # not in bin/
302
self._mksrc('foob', '#!/bin/sh', True) # not named like project
303
self._mksrc('bin/whisper', '#!/bin/sh') # not executable
305
(o, e, s) = self.do_install()
306
self.assertEqual(e, '')
307
self.assertEqual(s, 0)
308
self.assert_('following files are not recognized' in o)
309
self.assert_('\n foob' in o)
310
self.assert_('\n bin/whisper' in o)
311
self.assert_('\n daemon/food' in o)
313
f = self.installed_files()
314
self.assert_('/usr/bin/yell' in f)
315
self.assert_('/usr/bin/shout' in f)
316
self.assert_('/usr/bin/foo' in f)
318
self.failIf('food' in ftext)
319
self.failIf('foob' in ftext)
320
self.failIf('whisper' in ftext)
322
# verify that they are executable
323
binpath = os.path.join(self.install_tree, 'usr', 'bin')
324
self.assert_(os.access(os.path.join(binpath, 'yell'), os.X_OK))
325
self.assert_(os.access(os.path.join(binpath, 'shout'), os.X_OK))
326
self.assert_(os.access(os.path.join(binpath, 'foo'), os.X_OK))
328
def test_pot_manual(self):
329
'''PO template creation with manual POTFILES.in'''
331
self._mk_i18n_source()
332
self._mksrc('po/foo.pot', '')
333
# only do a subset here
334
self._mksrc('po/POTFILES.in', '''
337
[type: gettext/glade]gtk/test.ui''')
339
(o, e, s) = self.setup_py(['build'])
340
self.assertEqual(e, '')
341
self.assertEqual(s, 0)
342
# POT file should not be shown as not recognized
343
self.failIf('\n po/foo.pot\n' in o)
345
pot_path = os.path.join(self.src, 'po', 'foo.pot')
346
self.assert_(os.path.exists(pot_path))
347
pot = open(pot_path).read()
349
self.failIf('msgid "no"' in pot)
350
self.assert_('msgid "yes1"' in pot)
351
self.assert_('msgid "yes2 %s"' in pot)
352
self.failIf('msgid "yes5"' in pot) # we didn't add helpers.py
353
self.assert_('msgid "yes7"' in pot) # we did include the desktop file
354
self.failIf('msgid "yes5"' in pot) # we didn't add helpers.py
355
self.assert_('msgid "yes11"' in pot) # we added the GTKBuilder file
357
def test_pot_auto(self):
358
'''PO template creation with automatic POTFILES.in'''
360
self._mk_i18n_source()
362
(o, e, s) = self.setup_py(['build'])
363
self.assertEqual(e, '')
364
self.assertEqual(s, 0)
365
# POT file should not be shown as not recognized
366
self.failIf('\n po/foo.pot\n' in o)
368
pot_path = os.path.join(self.src, 'po', 'foo.pot')
369
self.assert_(os.path.exists(pot_path))
370
pot = open(pot_path).read()
372
self.failIf('msgid "no"' in pot)
373
for i in range(2, 14):
374
self.assert_('msgid "yes%i' % i in pot or
375
'msgid ""\n"yes%i' % i in pot,
377
# above loop would match yes11 to yes1 as well, so test it explicitly
378
self.assert_('msgid "yes1"' in pot)
380
def test_standard_files(self):
381
'''Standard files (MANIFEST.in, COPYING, etc.)'''
383
self._mksrc('AUTHORS')
384
self._mksrc('COPYING')
385
self._mksrc('LICENSE')
386
self._mksrc('COPYING.LIB')
387
self._mksrc('README.txt')
388
self._mksrc('MANIFEST.in')
389
self._mksrc('MANIFEST')
393
(o, e, s) = self.do_install()
394
self.assertEqual(e, '')
395
self.assertEqual(s, 0)
396
self.failIf('following files are not recognized' in o, o)
398
f = self.installed_files()
399
self.assert_('/usr/share/doc/foo/README.txt' in f)
400
self.assert_('/usr/share/doc/foo/NEWS' in f)
402
self.failIf('MANIFEST' in ftext)
403
self.failIf('COPYING' in ftext)
404
self.failIf('COPYING' in ftext)
405
self.failIf('AUTHORS' in ftext)
406
self.failIf('TODO' in ftext)
408
# sub-dir READMEs shouldn't be installed by default
410
self._mksrc('extra/README')
411
(o, e, s) = self.do_install()
412
self.assertEqual(e, '')
413
self.assertEqual(s, 0)
414
self.assert_('following files are not recognized' in o)
415
self.assert_('\n extra/README\n' in o)
417
def test_sdist(self):
418
'''default MANIFEST'''
420
good = ['AUTHORS', 'README.txt', 'COPYING', 'helpers.py',
421
'foo/__init__.py', 'foo/bar.py', 'tests/all.py',
422
'gui/x.desktop.in', 'backend/foo.policy.in',
423
'daemon/backend.conf', 'x/y', 'po/de.po', 'po/foo.pot',
424
'.quickly', 'data/icons/16x16/apps/foo.png', 'bin/foo',
425
'backend/food', 'backend/com.example.foo.service',
426
'gtk/main.glade', 'dist/extra.tar.gz']
427
bad = ['po/de.mo', '.helpers.py.swp', '.bzr/index', '.svn/index',
428
'.git/index', 'bin/foo~', 'backend/foo.pyc',
429
'dist/foo-0.1.tar.gz']
434
(o, e, s) = self.setup_py(['sdist', '-o'])
435
self.assert_("'MANIFEST.in' does not exist" in e)
436
self.assertEqual(s, 0)
438
manifest = open(os.path.join(self.src, 'MANIFEST')).read().splitlines()
441
self.assert_(f in manifest, '%s in manifest' % f)
443
self.failIf(f in manifest, '%s not in manifest' % f)
444
os.unlink(os.path.join(self.src, 'MANIFEST'))
446
def test_gtkbuilder(self):
447
'''GtkBuilder *.ui'''
449
self._mksrc('gtk/test.ui', '''<?xml version="1.0"?>
451
<requires lib="gtk+" version="2.16"/>
452
<object class="GtkWindow" id="window1">
453
<property name="title" translatable="yes">yes11</property>
454
<child><placeholder/></child>
457
self._mksrc('someweird.ui')
459
(o, e, s) = self.do_install()
460
self.assertEqual(e, '')
461
self.assertEqual(s, 0)
462
self.assert_('following files are not recognized' in o)
463
self.assert_('\n someweird.ui\n' in o)
465
f = self.installed_files()
466
self.assert_('/usr/share/foo/test.ui' in f)
468
self.failIf('someweird' in ftext)
470
def test_manpages(self):
473
self._mksrc('man/foo.1', '.TH foo 1 "Jan 01, 1900" "Joe Developer"')
474
self._mksrc('daemon/food.8', '.TH food 8 "Jan 01, 1900" "Joe Developer"')
475
self._mksrc('cruft/food.1', '')
476
self._mksrc('daemon/notme.s', '.TH food 8 "Jan 01, 1900" "Joe Developer"')
478
(o, e, s) = self.do_install()
479
self.assertEqual(e, '')
480
self.assertEqual(s, 0)
481
self.assert_('following files are not recognized' in o)
482
self.assert_('\n cruft/food.1\n' in o)
483
self.assert_('\n daemon/notme.s\n' in o)
485
f = self.installed_files()
486
self.assert_('/usr/share/man/man1/foo.1' in f)
487
self.assert_('/usr/share/man/man8/food.8' in f)
489
self.failIf('food.1' in ftext)
490
self.failIf('notme' in ftext)
495
self._mksrc('etc/cron.daily/foo')
496
self._mksrc('etc/foo.conf')
497
self._mksrc('etc/init.d/foo', executable=True)
498
d = os.path.join(self.src, 'etc', 'cron.weekly')
500
os.symlink(os.path.join('..', 'cron.daily', 'foo'),
501
os.path.join(d, 'foo'))
503
(o, e, s) = self.do_install()
504
self.assertEqual(e, '')
505
self.assertEqual(s, 0)
506
self.failIf('following files are not recognized' in o, o)
508
f = self.installed_files()
509
self.assert_('/etc/cron.daily/foo' in f)
510
self.assert_('/etc/cron.weekly/foo' in f)
511
self.assert_('/etc/init.d/foo' in f)
512
self.assert_('/etc/foo.conf' in f)
514
# verify that init script is executable
515
self.assert_(os.access(os.path.join(self.install_tree, 'etc', 'init.d',
517
# verify that symlinks get preserved
518
self.assert_(os.path.islink(os.path.join(self.install_tree, 'etc',
519
'cron.weekly', 'foo')))
521
# check that we can install again into the same source tree
522
(o, e, s) = self.setup_py(['install', '--no-compile', '--prefix=/usr',
523
'--root=' + self.install_tree])
524
self.assertEqual(e, '')
525
self.assertEqual(s, 0)
526
self.failIf('following files are not recognized' in o, o)
528
def test_requires_provides(self):
529
'''automatic requires/provides'''
533
__import__('dateutil')
535
self.fail('You need to have Crypto and dateutil installed for this test suite to work')
537
self._mksrc('foo/__init__.py', '')
538
self._mksrc('foo/stuff.py', '''import xml.parsers.expat
539
import os, os.path, email.mime, distutils.command.register
540
from email import header as h
541
import Crypto.PublicKey.DSA, unknown
544
self._mksrc('foo/bar/__init__.py', '')
545
self._mksrc('foo/bar/poke.py', 'def x(): pass')
547
self._mksrc('mymod.py', 'import foo\nfrom foo.bar.poke import x')
549
self._mksrc('bin/foo-cli', '''#!/usr/bin/python
551
from dateutil import tz
553
from Crypto import Cipher
555
print 'import iamnota.module'
556
''', executable=True)
558
self.install_tree = tempfile.mkdtemp()
559
(o, e, s) = self.setup_py(['install_egg_info', '-d', self.install_tree])
560
self.assertEqual(e, 'ERROR: Python module unknown not found\n')
561
self.assertEqual(s, 0)
562
self.failIf('following files are not recognized' in o, o)
565
egg = open(os.path.join(self.install_tree,
566
'foo-0.1.egg-info')).read().splitlines()
567
self.assert_('Name: foo' in egg)
570
prov = [prop.split(' ', 1)[1] for prop in egg if prop.startswith('Provides: ')]
571
self.assertEqual(set(prov), set(['foo', 'mymod']))
574
req = [prop.split(' ', 1)[1] for prop in egg if prop.startswith('Requires: ')]
575
self.assertEqual(set(req), set(['DistUtilsExtra.auto', 'Crypto', 'dateutil']))
581
def setup_py(self, args):
582
'''Run setup.py with given arguments.
584
For convenience, this snapshots the tree if no snapshot exists yet.
586
Return (out, err, exitcode) triple.
588
if not self.snapshot:
593
env['PYTHONPATH'] = oldcwd
595
s = subprocess.Popen(['python', 'setup.py'] + args, env=env,
596
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
597
(out, err) = s.communicate()
599
return (out, err, s.returncode)
601
def do_install(self):
602
'''Run setup.py install into temporary tree.
604
Return (out, err, exitcode) triple.
606
self.install_tree = tempfile.mkdtemp()
608
return self.setup_py(['install', '--no-compile', '--prefix=/usr',
609
'--root=' + self.install_tree])
611
def installed_files(self):
612
'''Return list of file paths in install tree.'''
615
for root, _, files in os.walk(self.install_tree):
616
assert root.startswith(self.install_tree)
617
r = root[len(self.install_tree):]
619
result.append(os.path.join(r, f))
622
def _mksrc(self, path, content=None, executable=False):
623
'''Create a file in the test source tree.'''
625
path = os.path.join(self.src, path)
626
dir = os.path.dirname(path)
627
if not os.path.isdir(dir):
631
# default content, to spot with diff
640
def do_snapshot(self):
641
'''Snapshot source tree.
643
This should be called after a test set up all source files.
645
assert self.snapshot is None, 'snapshot already taken'
647
self.snapshot = tempfile.mkdtemp()
648
shutil.copytree(self.src, os.path.join(self.snapshot, 's'))
650
def diff_snapshot(self):
651
'''Compare source tree to snapshot.
653
Return diff -Nur output.
655
assert self.snapshot, 'no snapshot taken'
656
diff = subprocess.Popen(['diff', '-x', 'foo.pot', '-Nur', os.path.join(self.snapshot, 's'),
657
self.src], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
658
(out, err) = diff.communicate()
659
self.assertEqual(err, '', 'diff error messages')
663
'''Create some example po files.'''
665
self._mksrc('po/POTFILES.in', '')
666
self._mksrc('po/de.po', '''msgid ""
667
msgstr "Content-Type: text/plain; charset=UTF-8\\n"
670
msgstr "Guten Morgen"
674
self._mksrc('po/fr.po', '''msgid ""
675
msgstr "Content-Type: text/plain; charset=UTF-8\\n"
680
def _mk_i18n_source(self):
681
'''Create some example source files with gettext calls'''
683
self._mksrc('gtk/main.py', '''print _("yes1")
690
return _(u'yes6')''')
692
self._mksrc('helpers.py', '''
704
self._mksrc('gui/foo.desktop.in', '''[Desktop Entry]
708
Exec=/usr/bin/foo''')
710
self._mksrc('daemon/com.example.foo.policy.in', '''<?xml version="1.0" encoding="UTF-8"?>
711
<!DOCTYPE policyconfig PUBLIC
712
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
713
"http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
715
<action id="com.example.foo.greet">
716
<_description>yes9</_description>
717
<_message>yes10</_message>
719
<allow_active>no6</allow_active>
724
self._mksrc('gtk/test.ui', '''<?xml version="1.0"?>
726
<requires lib="gtk+" version="2.16"/>
727
<object class="GtkWindow" id="window1">
728
<property name="title" translatable="yes">yes11</property>
729
<child><placeholder/></child>
733
self._mksrc('Makefile', 'echo _("no7")')
735
# Executables without *.py extension
736
self._mksrc('gtk/foo-gtk', '#!/usr/bin/python\nprint _("yes12")',
738
self._mksrc('cli/foo-cli', '#!/usr/bin/env python\nprint _("yes13")',
740
self._mksrc('daemon/foobarize', '#!/usr/bin/flex\np _("no8")',