1#!/usr/bin/python 2 3# test DistUtilsExtra.auto 4 5import unittest 6import shutil 7import tempfile 8import os 9import subprocess 10 11class T(unittest.TestCase): 12 def setUp(self): 13 self.maxDiff = None 14 self.src = tempfile.mkdtemp() 15 16 self._mksrc('setup.py', ''' 17# ignore warning about import from local path 18import warnings 19warnings.filterwarnings('ignore', 'Module DistUtilsExtra was already imported from.*') 20warnings.filterwarnings('ignore', 'pipe2 set errno ENOSYS.*') 21 22from DistUtilsExtra.auto import setup 23 24setup( 25 name='foo', 26 version='0.1', 27 description='Test suite package', 28 url='https://foo.example.com', 29 license='GPL v2 or later', 30 author='Martin Pitt', 31 author_email='martin.pitt@example.com', 32) 33''') 34 self.snapshot = None 35 self.install_tree = None 36 37 def tearDown(self): 38 try: 39 # check that setup.py clean removes everything 40 (o, e, s) = self.setup_py(['clean', '-a']) 41 self.assertEqual(s, 0, o+e) 42 cruft = self.diff_snapshot() 43 self.assertEqual(cruft, '', 'no cruft after cleaning:\n' + cruft) 44 finally: 45 shutil.rmtree(self.src) 46 if self.snapshot: 47 shutil.rmtree(self.snapshot) 48 if self.install_tree: 49 shutil.rmtree(self.install_tree) 50 self.src = None 51 self.snapshot = None 52 self.install_tree = None 53 54 # 55 # actual tests come here 56 # 57 58 def test_empty(self): 59 '''empty source tree (just setup.py)''' 60 61 (o, e, s) = self.do_install() 62 self.assertEqual(e, '') 63 self.assertEqual(s, 0) 64 self.assertNotIn('following files are not recognized', o) 65 66 f = self.installed_files() 67 # just installs the .egg_info 68 self.assertEqual(len(f), 1) 69 self.assertTrue(f[0].endswith('.egg-info')) 70 71 def test_vcs(self): 72 '''Ignores revision control files''' 73 74 self._mksrc('.shelf/1') 75 self._mksrc('.bzr/revs') 76 self._mksrc('.git/config') 77 self._mksrc('.svn/revs') 78 79 (o, e, s) = self.do_install() 80 self.assertEqual(e, '') 81 self.assertEqual(s, 0) 82 self.assertNotIn('following files are not recognized', o) 83 84 f = self.installed_files() 85 # just installs the .egg_info 86 self.assertEqual(len(f), 1) 87 self.assertTrue(f[0].endswith('.egg-info')) 88 89 def test_modules(self): 90 '''Python modules''' 91 92 self._mksrc('yesme.py', b'x ="a\xc3\xa4b\xe2\x99\xa5"'.decode('UTF-8')) 93 self._mksrc('stuff/notme.py', b'x ="a\xc3\xa4b\xe2\x99\xa5"'.decode('UTF-8')) 94 self._mksrc('stuff/withencoding.py', b'# -*- Mode: Python; coding: utf-8; -*- \nfoo = 1'.decode('UTF-8')) 95 96 (o, e, s) = self.do_install() 97 self.assertEqual(e, '') 98 self.assertEqual(s, 0) 99 self.assertIn('following files are not recognized', o) 100 self.assertIn('\n stuff/notme.py\n', o) 101 102 f = '\n'.join(self.installed_files()) 103 self.assertIn('-packages/yesme.py', f) 104 self.assertNotIn('notme', f) 105 106 def test_packages(self): 107 '''Python packages''' 108 109 self._mksrc('foopkg/__init__.py', '') 110 self._mksrc('foopkg/bar.py') 111 self._mksrc('foopkg/baz.py') 112 self._mksrc('noinit/notme.py') 113 114 (o, e, s) = self.do_install() 115 self.assertEqual(e, '') 116 self.assertEqual(s, 0) 117 self.assertIn('following files are not recognized', o) 118 self.assertIn('\n noinit/notme.py\n', o) 119 120 f = '\n'.join(self.installed_files()) 121 self.assertIn('foopkg/__init__.py', f) 122 self.assertIn('foopkg/bar.py', f) 123 self.assertNotIn('noinit', f) 124 125 def test_dbus(self): 126 '''D-BUS configuration and service files''' 127 128 # D-BUS ACL configuration file 129 self._mksrc('daemon/com.example.foo.conf', '''<!DOCTYPE busconfig PUBLIC 130 "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" 131 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> 132<busconfig> 133</busconfig>''') 134 135 # non-D-BUS configuration file 136 self._mksrc('daemon/defaults.conf', 'start = True\nlog = syslog') 137 138 # D-BUS system service 139 self._mksrc('daemon/com.example.foo.service', '''[D-BUS Service] 140Name=com.example.Foo 141Exec=/usr/lib/foo/foo_daemon 142User=root''') 143 144 # D-BUS session service 145 self._mksrc('gui/com.example.foo.gui.service', '''[D-BUS Service] 146Name=com.example.Foo.GUI 147Exec=/usr/bin/foo-gtk 148''') 149 150 # non-D-BUS .service file 151 self._mksrc('stuff/super.service', 'I am a file') 152 153 (o, e, s) = self.do_install() 154 self.assertEqual(e, '') 155 self.assertEqual(s, 0) 156 self.assertIn('following files are not recognized', o) 157 self.assertIn('\n stuff/super.service\n', o) 158 159 f = self.installed_files() 160 self.assertEqual(len(f), 4) # 3 D-BUS files plus .egg-info 161 self.assertIn('/etc/dbus-1/system.d/com.example.foo.conf', f) 162 self.assertIn('/usr/share/dbus-1/system-services/com.example.foo.service', f) 163 self.assertIn('/usr/share/dbus-1/services/com.example.foo.gui.service', f) 164 self.assertNotIn('super.service', '\n'.join(f)) 165 166 def test_gsettings(self): 167 '''GSettings schema files''' 168 169 # schema files in dedicated directory 170 self._mksrc('data/glib-2.0/schemas/org.test.myapp.gschema.xml') 171 self._mksrc('data/glib-2.0/schemas/gschemas.compiled') 172 # schema files in data directory 173 self._mksrc('data/org.test.myapp2.gschema.xml') 174 self._mksrc('data/gschemas.compiled') 175 176 (o, e, s) = self.do_install() 177 self.assertEqual(e, '') 178 self.assertEqual(s, 0) 179 180 f = self.installed_files() 181 self.assertEqual(len(f), 3) # 2 schema files plus .egg-info 182 self.assertIn('/usr/share/glib-2.0/schemas/org.test.myapp.gschema.xml', f) 183 self.assertNotIn('gschemas.compiled', '\n'.join(f)) 184 185 def test_apport_hook(self): 186 '''Apport hooks''' 187 188 self._mksrc('apport/foo.py', '''import os 189def add_info(report): 190 pass 191''') 192 193 self._mksrc('apport/source_foo.py', '''import os 194def add_info(report): 195 pass 196''') 197 198 (o, e, s) = self.do_install() 199 self.assertEqual(e, '') 200 self.assertEqual(s, 0) 201 self.assertNotIn('following files are not recognized', o) 202 203 f = self.installed_files() 204 self.assertEqual(len(f), 3, f) # 2 hook files plus .egg-info 205 self.assertIn('/usr/share/apport/package-hooks/foo.py', f) 206 self.assertIn('/usr/share/apport/package-hooks/source_foo.py', f) 207 208 def test_po(self): 209 '''gettext *.po files''' 210 211 self._mkpo() 212 213 (o, e, s) = self.do_install() 214 self.assertEqual(e, '') 215 self.assertEqual(s, 0) 216 self.assertNotIn('following files are not recognized', o) 217 f = self.installed_files() 218 self.assertIn('/usr/share/locale/de/LC_MESSAGES/foo.mo', f) 219 self.assertIn('/usr/share/locale/fr/LC_MESSAGES/foo.mo', f) 220 self.assertNotIn('junk', '\n'.join(f)) 221 222 msgunfmt = subprocess.Popen(['msgunfmt', 223 os.path.join(self.install_tree, 224 'usr/share/locale/de/LC_MESSAGES/foo.mo')], 225 stdout=subprocess.PIPE) 226 out = msgunfmt.communicate()[0].decode() 227 self.assertEqual(out, self._src_contents('po/de.po')) 228 229 def test_policykit(self): 230 '''*.policy.in PolicyKit files''' 231 232 self._mksrc('daemon/com.example.foo.policy.in', '''<?xml version="1.0" encoding="UTF-8"?> 233<!DOCTYPE policyconfig PUBLIC 234 "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" 235 "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd"> 236<policyconfig> 237 <vendor>Foo project</vendor> 238 <vendor_url>https://foo.example.com</vendor_url> 239 240 <action id="com.example.foo.greet"> 241 <_description>Good morning</_description> 242 <_message>Hello</_message> 243 <defaults> 244 <allow_active>yes</allow_active> 245 </defaults> 246 </action> 247</policyconfig>''') 248 249 self._mkpo() 250 (o, e, s) = self.do_install() 251 self.assertEqual(e, '') 252 self.assertEqual(s, 0) 253 self.assertNotIn('following files are not recognized', o) 254 255 f = self.installed_files() 256 self.assertIn('/usr/share/polkit-1/actions/com.example.foo.policy', f) 257 p = self._installed_contents('usr/share/polkit-1/actions/com.example.foo.policy') 258 self.assertIn('<description>Good morning</description>', p) 259 self.assertIn('<description xml:lang="de">Guten Morgen</description>', p) 260 self.assertIn('<message>Hello</message>', p) 261 self.assertIn('<message xml:lang="de">Hallo</message>', p) 262 263 def test_desktop(self): 264 '''*.desktop.in files''' 265 266 self._mksrc('gui/foogtk.desktop.in', '''[Desktop Entry] 267_Name=Hello 268_Comment=Good morning 269Exec=/bin/foo''') 270 self._mksrc('gui/autostart/fooapplet.desktop.in', '''[Desktop Entry] 271_Name=Hello 272_Comment=Good morning 273Exec=/usr/bin/fooapplet''') 274 self._mkpo() 275 self._mksrc('data/foosettings.desktop.in', '''[Desktop Entry] 276_Name=Hello 277_Comment=Good morning 278Exec=/bin/foosettings''') 279 280 (o, e, s) = self.do_install() 281 self.assertEqual(e, '') 282 self.assertEqual(s, 0) 283 self.assertNotIn('following files are not recognized', o) 284 285 f = self.installed_files() 286 self.assertIn('/usr/share/autostart/fooapplet.desktop', f) 287 self.assertIn('/usr/share/applications/foogtk.desktop', f) 288 self.assertIn('/usr/share/applications/foosettings.desktop', f) 289 # data/*.desktop.in shouldn't go to data dir 290 self.assertNotIn('/usr/share/foo/', f) 291 292 p = self._installed_contents('usr/share/autostart/fooapplet.desktop') 293 self.assertIn('\nName=Hello\n', p) 294 self.assertIn('\nName[de]=Hallo\n', p) 295 self.assertIn('\nComment[fr]=Bonjour\n', p) 296 297 def test_icons(self): 298 '''data/icons/''' 299 300 self._mksrc('data/icons/scalable/actions/press.png') 301 self._mksrc('data/icons/48x48/apps/foo.png') 302 scalable_icon_path = os.path.join(self.src, 'data', 'icons', 'scalable') 303 os.symlink(os.path.join(scalable_icon_path, 'actions', 'press.png'), 304 os.path.join(scalable_icon_path, 'actions', 'crunch.png')) 305 306 # test broken symlink, too 307 os.mkdir(os.path.join(scalable_icon_path, 'mimetypes')) 308 os.symlink('../apps/foo.svg', 309 os.path.join(scalable_icon_path, 'mimetypes', 'text-x-foo.svg')) 310 311 (o, e, s) = self.do_install() 312 self.assertEqual(e, '') 313 self.assertEqual(s, 0) 314 self.assertNotIn('following files are not recognized', o) 315 316 f = self.installed_files() 317 self.assertIn('/usr/share/icons/hicolor/scalable/actions/press.png', f) 318 self.assertIn('/usr/share/icons/hicolor/scalable/actions/crunch.png', f) 319 self.assertIn('/usr/share/icons/hicolor/48x48/apps/foo.png', f) 320 self.assertTrue(os.path.islink(os.path.join(self.install_tree, 321 'usr/share/icons/hicolor/scalable/actions/crunch.png'))) 322 self.assertTrue(os.path.islink(os.path.join(self.install_tree, 323 'usr/share/icons/hicolor/scalable/mimetypes/text-x-foo.svg'))) 324 325 def test_data(self): 326 '''Auxiliary files in data/''' 327 328 # have some explicitly covered files, to check that they don't get 329 # installed into prefix/share/foo/ again 330 self._mksrc('setup.py', ''' 331from DistUtilsExtra.auto import setup 332from glob import glob 333 334setup( 335 name='foo', 336 version='0.1', 337 description='Test suite package', 338 url='https://foo.example.com', 339 license='GPL v2 or later', 340 author='Martin Pitt', 341 author_email='martin.pitt@example.com', 342 343 data_files = [ 344 ('/lib/udev/rules.d', ['data/40-foo.rules']), 345 ('/etc/foo', glob('data/*.conf')), 346 ] 347) 348''') 349 350 self._mksrc('data/stuff') 351 self._mksrc('data/handlers/red.py', 'import sys\nprint ("RED")') 352 self._mksrc('data/handlers/blue.py', 'import sys\nprint ("BLUE")') 353 self._mksrc('data/40-foo.rules') 354 self._mksrc('data/blob1.conf') 355 self._mksrc('data/blob2.conf') 356 os.symlink('stuff', os.path.join(self.src, 'data', 'stufflink')) 357 358 (o, e, s) = self.do_install() 359 self.assertEqual(e, '') 360 self.assertEqual(s, 0) 361 self.assertNotIn('following files are not recognized', o) 362 363 f = self.installed_files() 364 self.assertIn('/usr/share/foo/stuff', f) 365 self.assertIn('/usr/share/foo/stufflink', f) 366 self.assertTrue(os.path.islink(os.path.join(self.install_tree, 'usr', 367 'share', 'foo', 'stufflink'))) 368 self.assertIn('/usr/share/foo/handlers/red.py', f) 369 self.assertIn('/usr/share/foo/handlers/blue.py', f) 370 self.assertIn('/lib/udev/rules.d/40-foo.rules', f) 371 self.assertIn('/etc/foo/blob1.conf', f) 372 self.assertIn('/etc/foo/blob2.conf', f) 373 self.assertNotIn('/usr/share/foo/blob1.conf', f) 374 self.assertNotIn('/usr/share/foo/40-foo.rules', f) 375 376 def test_scripts(self): 377 '''scripts''' 378 379 # these should get autoinstalled 380 self._mksrc('bin/yell', '#!/bin/sh', True) 381 self._mksrc('bin/shout', '#!/bin/sh', True) 382 self._mksrc('bin/foo', b'#!/usr/bin/python\n# \xc2\xa9 copyright'.decode('UTF-8'), True) 383 os.symlink('shout', os.path.join(self.src, 'bin', 'shoutlink')) 384 385 # these shouldn't 386 self._mksrc('daemon/food', '#!/bin/sh', True) # not in bin/ 387 self._mksrc('foob', '#!/bin/sh', True) # not named like project 388 # not executable 389 self._mksrc('bin/whisper', b'#!/usr/bin/python\n# \xc2\xa9 copyright'.decode('UTF-8')) 390 391 (o, e, s) = self.do_install() 392 self.assertEqual(e, '') 393 self.assertEqual(s, 0) 394 self.assertIn('following files are not recognized', o) 395 self.assertIn('\n foob', o) 396 self.assertIn('\n bin/whisper', o) 397 self.assertIn('\n daemon/food', o) 398 399 f = self.installed_files() 400 self.assertIn('/usr/bin/yell', f) 401 self.assertIn('/usr/bin/shout', f) 402 self.assertIn('/usr/bin/shoutlink', f) 403 self.assertTrue(os.path.islink(os.path.join(self.install_tree, 'usr', 404 'bin', 'shoutlink'))) 405 self.assertIn('/usr/bin/foo', f) 406 ftext = '\n'.join(f) 407 self.assertNotIn('food', ftext) 408 self.assertNotIn('foob', ftext) 409 self.assertNotIn('whisper', ftext) 410 411 # verify that they are executable 412 binpath = os.path.join(self.install_tree, 'usr', 'bin') 413 self.assertTrue(os.access(os.path.join(binpath, 'yell'), os.X_OK)) 414 self.assertTrue(os.access(os.path.join(binpath, 'shout'), os.X_OK)) 415 self.assertTrue(os.access(os.path.join(binpath, 'foo'), os.X_OK)) 416 417 def test_pot_manual(self): 418 '''PO template creation with manual POTFILES.in''' 419 420 self._mk_i18n_source() 421 self._mksrc('po/foo.pot', '') 422 # only do a subset here 423 self._mksrc('po/POTFILES.in', ''' 424gtk/main.py 425gui/foo.desktop.in 426[type: gettext/glade]gtk/test.ui''') 427 428 (o, e, s) = self.setup_py(['build']) 429 self.assertEqual(e, '') 430 self.assertEqual(s, 0) 431 # POT file should not be shown as not recognized 432 self.assertNotIn('\n po/foo.pot\n', o) 433 434 pot = self._src_contents('po/foo.pot') 435 436 self.assertNotIn('msgid "no"', pot) 437 self.assertIn('msgid "yes1"', pot) 438 self.assertIn('msgid "yes2 %s"', pot) 439 self.assertNotIn('msgid "yes5"', pot) # we didn't add helpers.py 440 self.assertIn('msgid "yes7"', pot) # we did include the desktop file 441 self.assertNotIn('msgid "yes5"', pot) # we didn't add helpers.py 442 self.assertIn('msgid "yes11"', pot) # we added one GTKBuilder file 443 self.assertNotIn('msgid "yes12"', pot) # ... but not the other 444 445 def test_pot_auto(self): 446 '''PO template creation with automatic POTFILES.in''' 447 448 self._mk_i18n_source() 449 450 (o, e, s) = self.setup_py(['build']) 451 self.assertEqual(e, '') 452 self.assertEqual(s, 0) 453 # POT file should not be shown as not recognized 454 self.assertNotIn('\n po/foo.pot\n', o) 455 456 pot = self._src_contents('po/foo.pot') 457 458 self.assertNotIn('msgid "no"', pot) 459 for i in range(2, 15): 460 self.assertTrue('msgid "yes%i' % i in pot or 461 'msgid ""\n"yes%i' % i in pot, 462 'yes%i' % i) 463 # above loop would match yes11 to yes1 as well, so test it explicitly 464 self.assertIn('msgid "yes1"', pot) 465 466 def test_pot_auto_explicit(self): 467 '''PO template creation with automatic POTFILES.in and explicit scripts''' 468 469 self._mk_i18n_source() 470 471 # add some additional binaries here which aren't caught by default 472 self._mksrc('cli/client-cli', "#!/usr/bin/python\nprint (_('yes15'))", True) 473 self._mksrc('gtk/client-gtk', '#!/usr/bin/python\nprint (_("yes16"))', True) 474 # this is the most tricky case: intltool doesn't consider them Python 475 # files by default and thus just looks for _(""): 476 self._mksrc('kde/client-kde', "#!/usr/bin/python\nprint (_('yes17'))", True) 477 self._mksrc('po/POTFILES.in.in', 'gtk/client-gtk\nkde/client-kde') 478 self._mksrc('setup.py', ''' 479from DistUtilsExtra.auto import setup 480 481import warnings 482warnings.filterwarnings('ignore', 'pipe2 set errno ENOSYS.*') 483 484setup( 485 name='foo', 486 version='0.1', 487 data_files=[('share/foo', ['gtk/client-gtk', 'kde/client-kde'])], 488 scripts=['cli/client-cli'], 489) 490''') 491 492 (o, e, s) = self.setup_py(['build']) 493 self.assertEqual(e, '') 494 self.assertEqual(s, 0) 495 # POT file should not be shown as not recognized 496 self.assertNotIn('\n po/foo.pot\n', o) 497 498 pot = self._src_contents('po/foo.pot') 499 500 self.assertNotIn('msgid "no"', pot) 501 for i in range(2, 18): 502 self.assertTrue('msgid "yes%i' % i in pot or 503 'msgid ""\n"yes%i' % i in pot, 504 'yes%i' % i) 505 # above loop would match yes11 to yes1 as well, so test it explicitly 506 self.assertIn('msgid "yes1"', pot) 507 508 def test_standard_files(self): 509 '''Standard files (MANIFEST.in, COPYING, etc.)''' 510 511 self._mksrc('AUTHORS') 512 self._mksrc('COPYING') 513 self._mksrc('LICENSE') 514 self._mksrc('COPYING.LIB') 515 self._mksrc('README.txt') 516 self._mksrc('MANIFEST.in') 517 self._mksrc('MANIFEST') 518 self._mksrc('NEWS') 519 self._mksrc('TODO') 520 521 (o, e, s) = self.do_install() 522 self.assertEqual(e, '') 523 self.assertEqual(s, 0) 524 self.assertNotIn('following files are not recognized', o) 525 526 f = self.installed_files() 527 self.assertIn('/usr/share/doc/foo/README.txt', f) 528 self.assertIn('/usr/share/doc/foo/NEWS', f) 529 ftext = '\n'.join(f) 530 self.assertNotIn('MANIFEST', ftext) 531 self.assertNotIn('COPYING', ftext) 532 self.assertNotIn('COPYING', ftext) 533 self.assertNotIn('AUTHORS', ftext) 534 self.assertNotIn('TODO', ftext) 535 536 # sub-dir READMEs shouldn't be installed by default 537 self.snapshot = None 538 self._mksrc('extra/README') 539 (o, e, s) = self.do_install() 540 self.assertEqual(e, '') 541 self.assertEqual(s, 0) 542 self.assertIn('following files are not recognized', o) 543 self.assertIn('\n extra/README\n', o) 544 545 def test_sdist(self): 546 '''default MANIFEST''' 547 548 good = ['AUTHORS', 'README.txt', 'COPYING', 'helpers.py', 549 'foo/__init__.py', 'foo/bar.py', 'tests/all.py', 550 'gui/x.desktop.in', 'backend/foo.policy.in', 551 'daemon/backend.conf', 'x/y', 'po/de.po', 'po/foo.pot', 552 '.quickly', 'data/icons/16x16/apps/foo.png', 'bin/foo', 553 'backend/food', 'backend/com.example.foo.service', 554 'gtk/main.glade', 'dist/extra.tar.gz'] 555 bad = ['po/de.mo', '.helpers.py.swp', '.bzr/index', '.svn/index', 556 '.git/index', 'bin/foo~', 'backend/foo.pyc', 557 'dist/foo-0.1.tar.gz', '.shelf/1', '.bzr/revs', '.git/config'] 558 559 for f in good + bad: 560 self._mksrc(f) 561 562 (o, e, s) = self.setup_py(['sdist', '-o']) 563 self.assertIn("'MANIFEST.in' does not exist", e) 564 self.assertEqual(s, 0) 565 566 manifest = self._src_contents('MANIFEST').splitlines() 567 568 for f in good: 569 self.assertIn(f, manifest) 570 for f in bad: 571 self.assertNotIn(f, manifest) 572 os.unlink(os.path.join(self.src, 'MANIFEST')) 573 574 def test_ui(self): 575 '''GtkBuilder/Qt *.ui''' 576 577 self._mksrc('gtk/test.ui', b'''<?xml version="1.0"?> 578<interface> 579 <requires lib="gtk+" version="2.16"/> 580 <object class="GtkWindow" id="window1"> 581 <property name="title" translatable="yes">my\xe2\x99\xa5</property> 582 <child><placeholder/></child> 583 </object> 584</interface>'''.decode('UTF-8')) 585 586 self._mksrc('gtk/settings.ui', '''<?xml version="1.0"?> 587<!-- Generated with glade 3.18.3 --> 588<interface domain="foobar"> 589 <requires lib="gtk+" version="2.16"/> 590 <object class="GtkWindow" id="window2"> 591 <property name="title" translatable="yes">yes12</property> 592 <child><placeholder/></child> 593 </object> 594</interface>''') 595 596 self._mksrc('kde/mainwindow.ui', '''<?xml version="1.0"?> 597<ui version="4.0"> 598 <class>CrashDialog</class> 599 <widget class="QDialog" name="CrashDialog"> 600 </widget> 601</ui> 602''') 603 604 self._mksrc('someweird.ui') 605 606 (o, e, s) = self.do_install() 607 self.assertEqual(e, '') 608 self.assertEqual(s, 0) 609 self.assertIn('following files are not recognized', o) 610 self.assertIn('\n someweird.ui\n', o) 611 612 f = self.installed_files() 613 self.assertIn('/usr/share/foo/test.ui', f) 614 self.assertIn('/usr/share/foo/settings.ui', f) 615 self.assertIn('/usr/share/foo/mainwindow.ui', f) 616 ftext = '\n'.join(f) 617 self.assertNotIn('someweird', ftext) 618 619 def test_manpages(self): 620 '''manpages''' 621 622 self._mksrc('man/foo.1', '.TH foo 1 "Jan 01, 1900" "Joe Developer"') 623 self._mksrc('daemon/food.8', '.\" some comment\n.TH food 8 "Jan 01, 1900" "Joe Developer"') 624 self._mksrc('cruft/food.1', '') 625 self._mksrc('daemon/notme.s', '.TH food 8 "Jan 01, 1900" "Joe Developer"') 626 627 (o, e, s) = self.do_install() 628 self.assertEqual(e, '') 629 self.assertEqual(s, 0) 630 self.assertIn('following files are not recognized', o) 631 self.assertIn('\n cruft/food.1\n', o) 632 self.assertIn('\n daemon/notme.s\n', o) 633 634 f = self.installed_files() 635 self.assertIn('/usr/share/man/man1/foo.1', f) 636 self.assertIn('/usr/share/man/man8/food.8', f) 637 ftext = '\n'.join(f) 638 self.assertNotIn('food.1', ftext) 639 self.assertNotIn('notme', ftext) 640 641 def test_etc(self): 642 '''etc/*''' 643 644 self._mksrc('etc/cron.daily/foo') 645 self._mksrc('etc/foo.conf') 646 self._mksrc('etc/init.d/foo', executable=True) 647 d = os.path.join(self.src, 'etc', 'cron.weekly') 648 os.mkdir(d) 649 os.symlink(os.path.join('..', 'cron.daily', 'foo'), 650 os.path.join(d, 'foo')) 651 652 (o, e, s) = self.do_install() 653 self.assertEqual(e, '') 654 self.assertEqual(s, 0) 655 self.assertNotIn('following files are not recognized', o) 656 657 f = self.installed_files() 658 self.assertIn('/etc/cron.daily/foo', f) 659 self.assertIn('/etc/cron.weekly/foo', f) 660 self.assertIn('/etc/init.d/foo', f) 661 self.assertIn('/etc/foo.conf', f) 662 663 # verify that init script is executable 664 self.assertTrue(os.access(os.path.join(self.install_tree, 'etc', 'init.d', 665 'foo'), os.X_OK)) 666 # verify that symlinks get preserved 667 self.assertTrue(os.path.islink(os.path.join(self.install_tree, 'etc', 668 'cron.weekly', 'foo'))) 669 670 # check that we can install again into the same source tree 671 (o, e, s) = self.setup_py(['install', '--no-compile', '--prefix=/usr', 672 '--root=' + self.install_tree]) 673 self.assertEqual(e, '') 674 self.assertEqual(s, 0) 675 self.assertNotIn('following files are not recognized', o) 676 677 def test_requires_provides(self): 678 '''automatic requires/provides''' 679 680 for needed_pkg in ['pkg_resources','httplib2','gi.repository.GLib']: 681 try: 682 __import__(needed_pkg) 683 except ImportError: 684 self.fail('You need to have %s installed for this test suite to work' % needed_pkg) 685 686 self._mksrc('foo/__init__.py', '') 687 self._mksrc('foo/stuff.py', '''import xml.parsers.expat 688import os, os.path, email.mime, distutils.command.register 689from email import header as h 690import httplib2.iri2uri, unknown 691from . bar import poke 692from bar.poke import x 693import grab_cli 694import broken 695''') 696 697 self._mksrc('foo/bar/__init__.py', '') 698 self._mksrc('foo/bar/poke.py', 'from . import broken\ndef x(): pass') 699 self._mksrc('foo/bar/broken.py', 'raise RuntimeError("cannot initialize system")') 700 701 self._mksrc('mymod.py', 'import foo\nfrom foo.bar.poke import x') 702 # trying to import this will cause setup.py to not process any args any more 703 self._mksrc('grab_cli.py', 'from optparse import OptionParser\nOptionParser().parse_args()') 704 # trying to import this will break setup.py 705 self._mksrc('broken.py', 'raise SystemError("cannot initialize system")') 706 self._mksrc('pygi.py', 'from gi.repository import GLib\nimport gi.repository.GObject') 707 708 self._mksrc('bin/foo-cli', '''#!/usr/bin/python 709import sys 710import pkg_resources 711import foo.bar 712from httplib2 import iri2uri 713 714print ('import iamnota.module') 715''', executable=True) 716 717 # this shouldn't be treated specially 718 self._mksrc('data/example-code/template.py', 'import example.module') 719 self._mksrc('data/example-code/mymod/__init__.py', '') 720 self._mksrc('data/example-code/mymod/shiny.py', 'import example.othermod') 721 722 (o, e, s) = self.do_install() 723 self.assertEqual(s, 0, e) 724 self.assertEqual(e, 'ERROR: Python module unknown not found\n') 725 self.assertNotIn('following files are not recognized', o) 726 727 inst = self.installed_files() 728 self.assertIn('/usr/share/foo/example-code/template.py', inst) 729 self.assertIn('/usr/share/foo/example-code/mymod/shiny.py', inst) 730 for f in inst: 731 if 'template.py' in f or 'shiny' in f: 732 self.assertNotIn('packages', f) 733 734 # parse .egg-info 735 (o, e, s) = self.setup_py(['install_egg_info', '-d', self.install_tree]) 736 self.assertEqual(e, 'ERROR: Python module unknown not found\n') 737 egg_paths = [x for x in inst if x.endswith('.egg-info')] 738 self.assertEqual(len(egg_paths), 1) 739 egg = self._installed_contents(egg_paths[0].strip(os.path.sep)).splitlines() 740 self.assertIn('Name: foo', egg) 741 742 # check provides 743 prov = [prop.split(' ', 1)[1] for prop in egg if prop.startswith('Provides: ')] 744 self.assertEqual(set(prov), set(['foo', 'mymod', 'broken', 'grab_cli', 'pygi'])) 745 746 # check requires 747 req = [prop.split(' ', 1)[1] for prop in egg if prop.startswith('Requires: ')] 748 self.assertEqual(set(req), set(['httplib2', 'pkg_resources', 749 'gi.repository.GLib', 'gi.repository.GObject'])) 750 751 def test_help_docbook(self): 752 '''Docbook XML help''' 753 754 self._mksrc('help/C/index.docbook') 755 self._mksrc('help/C/legal.xml') 756 self._mksrc('help/C/figures/mainscreen.png') 757 self._mksrc('help/de/index.docbook') 758 self._mksrc('help/de/legal.xml') 759 self._mksrc('help/de/figures/mainscreen.png') 760 761 self._mksrc('help/weird.xml') 762 self._mksrc('help/notme.png') 763 764 (o, e, s) = self.do_install() 765 self.assertEqual(e, '') 766 self.assertEqual(s, 0) 767 self.assertIn('following files are not recognized', o) 768 self.assertIn('\n help/weird.xml\n', o) 769 self.assertIn('\n help/notme.png\n', o) 770 771 f = self.installed_files() 772 self.assertIn('/usr/share/help/C/foo/index.docbook', f) 773 self.assertIn('/usr/share/help/C/foo/legal.xml', f) 774 self.assertIn('/usr/share/help/C/foo/figures/mainscreen.png', f) 775 self.assertIn('/usr/share/help/de/foo/index.docbook', f) 776 self.assertIn('/usr/share/help/de/foo/legal.xml', f) 777 self.assertIn('/usr/share/help/de/foo/figures/mainscreen.png', f) 778 779 def test_help_mallard(self): 780 '''Mallard XML help''' 781 782 self._mksrc('help/C/index.page') 783 self._mksrc('help/C/legal.page') 784 self._mksrc('help/C/figures/mainscreen.png') 785 self._mksrc('help/de/index.page') 786 self._mksrc('help/de/legal.page') 787 self._mksrc('help/de/figures/mainscreen.png') 788 789 self._mksrc('help/weird.page') 790 self._mksrc('help/notme.png') 791 792 (o, e, s) = self.do_install() 793 self.assertEqual(e, '') 794 self.assertEqual(s, 0) 795 self.assertIn('following files are not recognized', o) 796 self.assertIn('\n help/weird.page\n', o) 797 self.assertIn('\n help/notme.png\n', o) 798 799 f = self.installed_files() 800 self.assertIn('/usr/share/help/C/foo/index.page', f) 801 self.assertIn('/usr/share/help/C/foo/legal.page', f) 802 self.assertIn('/usr/share/help/C/foo/figures/mainscreen.png', f) 803 self.assertIn('/usr/share/help/de/foo/index.page', f) 804 self.assertIn('/usr/share/help/de/foo/legal.page', f) 805 self.assertIn('/usr/share/help/de/foo/figures/mainscreen.png', f) 806 807 def test_binary_files(self): 808 '''Binary files are ignored''' 809 810 with open(os.path.join(self.src, 'binary_trap'), 'wb') as f: 811 f.write(b'\x00\x01abc\xFF\xFE') 812 (o, e, s) = self.do_install() 813 self.assertEqual(e, '') 814 self.assertEqual(s, 0) 815 self.assertIn('following files are not recognized', o) 816 self.assertIn('\n binary_trap\n', o) 817 818 f = self.installed_files() 819 self.assertEqual(len(f), 1, f) 820 self.assertIn('egg-info', f[0]) 821 822 def test_utf8_filenames(self): 823 '''UTF-8 file names''' 824 825 bin_fname = b'a\xc3\xa4b.bin'.decode('UTF-8') 826 with open(os.path.join(self.src, bin_fname).encode('UTF-8'), 'wb') as f: 827 f.write(b'\x00\x01abc\xFF\xFE') 828 829 (o, e, s) = self.do_install() 830 self.assertEqual(e, '') 831 self.assertEqual(s, 0) 832 833 f = self.installed_files() 834 self.assertEqual(len(f), 1, f) 835 self.assertIn('egg-info', f[0]) 836 837 self.assertIn('following files are not recognized', o) 838 # this might not be the correct file name when the locale is e. g. C 839 self.assertIn('b.bin\n', o) 840 841 # 842 # helper methods 843 # 844 845 def setup_py(self, args): 846 '''Run setup.py with given arguments. 847 848 For convenience, this snapshots the tree if no snapshot exists yet. 849 850 Return (out, err, exitcode) triple. 851 ''' 852 if not self.snapshot: 853 self.do_snapshot() 854 855 env = os.environ.copy() 856 oldcwd = os.getcwd() 857 if 'PYTHONPATH' in env: 858 env['PYTHONPATH'] = oldcwd + os.pathsep + env['PYTHONPATH'] 859 else: 860 env['PYTHONPATH'] = oldcwd 861 # unset envvars that alter results 862 env.pop('LINGUAS', '') 863 env.pop('PYTHONDONTWRITEBYTECODE', '') 864 os.chdir(self.src) 865 s = subprocess.Popen(['/proc/self/exe', 'setup.py'] + args, env=env, 866 stdout=subprocess.PIPE, stderr=subprocess.PIPE) 867 (out, err) = s.communicate() 868 out = out.decode() 869 err = err.decode() 870 os.chdir(oldcwd) 871 872 return (out, err, s.returncode) 873 874 def do_install(self): 875 '''Run setup.py install into temporary tree. 876 877 Return (out, err, exitcode) triple. 878 ''' 879 self.install_tree = tempfile.mkdtemp() 880 881 self.setup_py(['build']) 882 return self.setup_py(['install', '--no-compile', '--skip-build', 883 '--prefix=/usr', '--root=' + self.install_tree]) 884 885 def installed_files(self): 886 '''Return list of file paths in install tree.''' 887 888 result = [] 889 for root, _, files in os.walk(self.install_tree): 890 assert root.startswith(self.install_tree) 891 r = root[len(self.install_tree):] 892 for f in files: 893 result.append(os.path.join(r, f)) 894 return result 895 896 def _mksrc(self, path, content=None, executable=False): 897 '''Create a file in the test source tree.''' 898 899 path = os.path.join(self.src, path) 900 dir = os.path.dirname(path) 901 if not os.path.isdir(dir): 902 os.makedirs(dir) 903 with open(path, 'wb') as f: 904 if content is None: 905 # default content, to spot with diff 906 f.write(b'dummy') 907 else: 908 f.write((content + '\n').encode('UTF-8')) 909 910 if executable: 911 os.chmod(path, 0o755) 912 913 def do_snapshot(self): 914 '''Snapshot source tree. 915 916 This should be called after a test set up all source files. 917 ''' 918 assert self.snapshot is None, 'snapshot already taken' 919 920 self.snapshot = tempfile.mkdtemp() 921 shutil.copytree(self.src, os.path.join(self.snapshot, 's'), symlinks=True) 922 923 def diff_snapshot(self): 924 '''Compare source tree to snapshot. 925 926 Return diff -Nur output. 927 ''' 928 assert self.snapshot, 'no snapshot taken' 929 diff = subprocess.Popen(['diff', '-x', 'foo.pot', '-x', '*.pyc', 930 '-Nur', os.path.join(self.snapshot, 's'), self.src], 931 stdout=subprocess.PIPE, stderr=subprocess.PIPE) 932 (out, err) = diff.communicate() 933 out = out.decode('UTF-8') 934 return out 935 936 def _mkpo(self): 937 '''Create some example po files.''' 938 939 self._mksrc('po/POTFILES.in', '') 940 self._mksrc('po/de.po', '''msgid "" 941msgstr "Content-Type: text/plain; charset=UTF-8\\n" 942 943msgid "Good morning" 944msgstr "Guten Morgen" 945 946msgid "Hello" 947msgstr "Hallo"''') 948 self._mksrc('po/fr.po', '''msgid "" 949msgstr "Content-Type: text/plain; charset=UTF-8\\n" 950 951msgid "Good morning" 952msgstr "Bonjour"''') 953 954 def _mk_i18n_source(self): 955 '''Create some example source files with gettext calls''' 956 957 self._mksrc('gtk/main.py', '''print (_("yes1")) 958print ("no1") 959print (__("no2")) 960x = _('yes2 %s') % y 961 962def f(): 963 print (_("yes3")) 964 return _('yes6')''') 965 966 self._mksrc('helpers.py', ''' 967print (f(_("yes4"))) 968print (_(\'\'\'yes5 969even more 970lines\'\'\')) 971print (_("""yes6 972more lines""")) 973print (\'\'\'no3 974boo\'\'\') 975print ("""no4 976more""")''') 977 978 self._mksrc('gui/foo.desktop.in', '''[Desktop Entry] 979_Name=yes7 980_Comment=yes8 981Icon=no5 982Exec=/usr/bin/foo''') 983 984 self._mksrc('daemon/com.example.foo.policy.in', '''<?xml version="1.0" encoding="UTF-8"?> 985<!DOCTYPE policyconfig PUBLIC 986 "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" 987 "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd"> 988<policyconfig> 989 <action id="com.example.foo.greet"> 990 <_description>yes9</_description> 991 <_message>yes10</_message> 992 <defaults> 993 <allow_active>no6</allow_active> 994 </defaults> 995 </action> 996</policyconfig>''') 997 998 self._mksrc('gtk/test.ui', '''<?xml version="1.0"?> 999<interface> 1000 <requires lib="gtk+" version="2.16"/> 1001 <object class="GtkWindow" id="window1"> 1002 <property name="title" translatable="yes">yes11</property> 1003 <child><placeholder/></child> 1004 </object> 1005</interface>''') 1006 1007 self._mksrc('data/settings.ui', '''<?xml version="1.0"?> 1008<interface domain="foobar"> 1009 <requires lib="gtk+" version="2.16"/> 1010 <object class="GtkWindow" id="window1"> 1011 <property name="title" translatable="yes">yes12</property> 1012 <child><placeholder/></child> 1013 </object> 1014</interface>''') 1015 1016 self._mksrc('Makefile', 'echo _("no7")') 1017 1018 # Executables without *.py extension 1019 self._mksrc('gtk/foo-gtk', '#!/usr/bin/python\nprint (_("yes13"))', 1020 executable=True) 1021 self._mksrc('cli/foo-cli', '#!/usr/bin/env python\nprint (_(\'yes14\'))', 1022 executable=True) 1023 self._mksrc('daemon/foobarize', '#!/usr/bin/flex\np _("no8")', 1024 executable=True) 1025 1026 def _src_contents(self, path): 1027 f = open(os.path.join(self.src, path)) 1028 contents = f.read() 1029 f.close() 1030 return contents 1031 1032 def _installed_contents(self, path): 1033 f = open(os.path.join(self.install_tree, path)) 1034 contents = f.read() 1035 f.close() 1036 return contents 1037 1038 1039if __name__ == '__main__': 1040 unittest.main() 1041