#!/usr/bin/python
# pkgbinarymangler automatic tests
# (C) 2010 Canonical Ltd.
# Author: Martin Pitt <martin.pitt@ubuntu.com>
# License: GPL 2 or later

import unittest
import subprocess
import tempfile
import shutil
import os.path
import sys
import tarfile
import re
from glob import glob

class T(unittest.TestCase):
    def setUp(self):
        self.srcdir = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0])))
        
        self.workdir = tempfile.mkdtemp()
        self.pkgdir = os.path.join(self.workdir, 'icecream')
        shutil.copytree(os.path.join(self.srcdir, 'test', 'icecream'),
                self.pkgdir)

        self.buildinfo = None

        os.environ['PKGBINARYMANGLER_COMMON_PATH'] = self.srcdir
        # locally fake the dpkg diversion
        if os.path.exists('/usr/bin/dpkg-deb.pkgbinarymangler'):
            os.symlink('/usr/bin/dpkg-deb.pkgbinarymangler', os.path.join(self.srcdir, 'dpkg-deb.pkgbinarymangler'))
        else:
            os.symlink('/usr/bin/dpkg-deb', os.path.join(self.srcdir, 'dpkg-deb.pkgbinarymangler'))

        # copy our default configuration files, and enable them
        for conf in glob(os.path.join(self.srcdir, '*.conf')):
            f = open(os.path.join(self.workdir, os.path.basename(conf)), 'w')
            f.write(open(conf).read().replace('enable: false', 'enable: true'))
            f.close()
        os.environ['PKGBINARYMANGLER_CONF_DIR'] = self.workdir

        os.environ['PKMAINTAINERGMANGLER_OVERRIDES'] = os.path.join(
                self.srcdir, 'maintainermangler.overrides')

    def tearDown(self):
        shutil.rmtree(self.workdir)
        os.unlink(os.path.join(self.srcdir, 'dpkg-deb.pkgbinarymangler'))

    def build(self, use_local_mangler=True, extra_env=None, srcname='icecream'):
        env = os.environ.copy()
        if use_local_mangler:
            env['PATH'] = self.srcdir + ':' + env.get('PATH', '')

            bi = os.path.join(self.workdir, 'CurrentlyBuilding')
            env['CURRENTLY_BUILDING_PATH'] = bi
            # ignore system apt sources, otherwise we'll break some tests if we
            # run them in an OEM buildd environment
            env['PKGBINARYMANGLER_APT_CONF_DIR'] = self.workdir
            if self.buildinfo:
                f = open(bi, 'w')
                f.write(self.buildinfo)
                f.close()
        else:
            # disable a system-installed pkgbinarymangler
            if os.path.exists('/usr/bin/dpkg-deb.pkgbinarymangler'):
                os.symlink('/usr/bin/dpkg-deb.pkgbinarymangler',
                        os.path.join(self.workdir, 'dpkg-deb'))
                env['PATH'] = self.workdir + ':' + env.get('PATH', '')

        if extra_env:
            env.update(extra_env)

        build = subprocess.Popen(['dpkg-buildpackage', '-us', '-uc', '-b'],
                stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                cwd=self.pkgdir, env=env)
        out = build.communicate()[0]
        self.assertEqual(build.returncode, 0)

        self.changes = open(glob(os.path.join(self.workdir, '%s_1_*.changes' % srcname))[0]).read()

        self.translations_tar = glob(os.path.join(self.workdir, '%s_1_*_translations.tar.gz' % srcname))
        if len(self.translations_tar) == 0:
            self.translations_tar = None
        else:
            assert len(self.translations_tar) == 1
            self.translations_tar = self.translations_tar[0]

    def check_deb_mo(self, expect_mo):
        '''Verify the built .deb contents for translations'''

        for pkg in ('vanilla', 'chocolate'):
            deb = glob(os.path.join(self.workdir, '%s_1_*.deb' % pkg))[0]
            dpkg = subprocess.Popen(['dpkg', '-c', deb],
                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            (out, err) = dpkg.communicate()
            if expect_mo:
                self.assert_('./usr/share/locale/fr/LC_MESSAGES/%s.mo' % pkg in out)
                self.assert_('./usr/share/locale/de/LC_MESSAGES/%s.mo' % pkg in out)
            else:
                self.failIf('/usr/share/locale/' in out)
            self.assert_('./usr/share/doc/%s/copyright' % pkg in out)

    def check_maintainer(self, expect_mangle):
        '''Verify the built .deb for mangled maintainer'''

        for deb in glob(os.path.join(self.workdir, '*_1_*.deb')):
            dpkg = subprocess.Popen(['dpkg', '-I', deb],
                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            (out, err) = dpkg.communicate()
            self.assertEqual(err, '')
            if expect_mangle:
                self.assert_('Maintainer: Ubuntu' in out)
                self.assert_('Original-Maintainer: Joe User <joe@example.com>' in out)
            else:
                self.assert_('Maintainer: Joe User <joe@example.com>' in out)
                self.failIf('Original-Maintainer:' in out)

    def check_translation_tarball(self):
        '''Verify _translations.tar.gz integrity'''

        self.assert_('raw-translations - ' + os.path.basename(self.translations_tar) in self.changes)

        tar = tarfile.open(self.translations_tar)
        self.assert_('./vanilla/usr/share/locale/fr/LC_MESSAGES/vanilla.mo' 
                in tar.getnames())
        self.assert_('./chocolate/usr/share/locale/de/LC_MESSAGES/chocolate.mo' 
                in tar.getnames())
        self.assert_('./source/po-vanilla/vanilla.pot'
                in tar.getnames())
        self.assert_('./source/po-chocolate/de.po'
                in tar.getnames())

    def check_deb_integrity(self):
        '''Check that we can properly unpack the generated .debs'''

        debs = glob(os.path.join(self.workdir, '*.deb'))
        if not debs:
            self.fail('No .debs produced')

        for deb in debs:
            extractdir = os.path.join(self.workdir, 'deb-' +
                    os.path.splitext(os.path.basename(deb))[0])

            env = os.environ.copy()
            env['PATH'] = self.srcdir + ':' + env.get('PATH', '')
            dpkg = subprocess.Popen(['dpkg-deb', '-x', deb, extractdir],
                    stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
            (out, err) = dpkg.communicate()

            # dpkg-deb must not print anything, otherwise apt falls over
            self.assertEqual(out, '')
            self.assertEqual(err, '')
            self.assertEqual(dpkg.returncode, 0)

    def test_no_mangler(self):
        '''No pkgbinarymangler'''

        self.build(False)
        self.check_deb_mo(True)
        self.check_maintainer(False)

    def test_no_pkg_mangle(self):
        '''$NO_PKG_MANGLE disables pkgbinarymangler'''

        self.build(True, {'NO_PKG_MANGLE': '1'})
        self.check_deb_mo(True)
        self.check_maintainer(False)
        self.assertEqual(self.translations_tar, None)
        self.check_deb_integrity()

    def test_no_buildinfo(self):
        '''No build info'''

        # this should always strip
        self.build()
        self.check_deb_mo(False)
        self.check_maintainer(True)
        self.check_translation_tarball()
        self.check_deb_integrity()

    def test_main(self):
        '''Component: main'''

        self.buildinfo = '''Package: icecream
Component: main
Suite: lucid
Purpose: PRIMARY
'''

        # as configured by default, universe packages are not stripped, but
        # produce a tarball
        self.build()
        self.check_deb_mo(False)
        self.check_maintainer(True)
        self.check_translation_tarball()
        self.check_deb_integrity()

    def test_universe(self):
        '''Component: universe'''

        self.buildinfo = '''Package: icecream
Component: universe
Suite: lucid
Purpose: PRIMARY
'''

        # as configured by default, universe packages are not stripped, but
        # produce a tarball
        self.build()
        self.check_deb_mo(True)
        self.check_maintainer(True)
        self.check_translation_tarball()

    def test_ppa(self):
        '''Purpose: PPA'''

        self.buildinfo = '''Package: icecream
Component: main
Suite: lucid
Purpose: PPA
'''

        # PPA builds are never touched by default
        self.build()
        self.check_deb_mo(True)
        self.check_maintainer(False)
        self.assertEqual(self.translations_tar, None)
        self.check_deb_integrity()

    def test_partner(self):
        '''Section: partner'''

        cpath = os.path.join(self.pkgdir, 'debian', 'control')
        contents = open(cpath).readlines()
        for l in range(len(contents)):
            if contents[l].startswith('Section:'):
                contents[l] = 'Section: partner\n'
                break
        f = open(cpath, 'w')
        f.write(''.join(contents))
        f.close()

        # partner builds are not touched
        self.build()
        self.check_deb_mo(True)
        self.check_maintainer(False)
        self.assertEqual(self.translations_tar, None)

    def test_langpack(self):
        '''language packs are not stripped'''

        self.buildinfo = '''Package: language-pack-de
Component: main
Suite: lucid
Purpose: PRIMARY
'''

        # rename source to langpack
        assert subprocess.call(['sed', '-i', 's/icecream/language-pack-de/g', 
            os.path.join(self.pkgdir, 'debian', 'control'), 
            os.path.join(self.pkgdir, 'debian', 'changelog')]) == 0
        self.build(True, srcname='language-pack-de')

        # should not strip translations, since partner is blacklisted
        self.check_deb_mo(True)
        self.assertEqual(self.translations_tar, None)

    def test_ppa_oem_non_main(self):
        '''OEM PPA for non-main package'''

        # these currently look like normal PPAs, we can't yet determine the PPA
        # name from buildinfo; hack around with checking apt sources
        self.buildinfo = '''Package: icecream
Component: main
Suite: lucid
Purpose: PPA
'''
        apt_sources = open(os.path.join(self.workdir, 'sources.list'), 'w')
        apt_sources.write('''deb http://private-ppa.buildd/oem-archive/myoem/ubuntu lucid main
deb https://cesg.canonical.com/canonical myoem public private
deb https://cesg.canonical.com/canonical myoem-devel public private
deb http://ftpmaster.internal/ubuntu lucid main restricted universe multiverse
''')
        apt_sources.close()

        # those should not strip translations, since it's not in main
        self.build(True, {'PKGBINARYMANGLER_APT_CONF_DIR': self.workdir})
        self.check_deb_mo(True)
        self.check_translation_tarball()

        # no maintainer mangling in PPAs, as usual
        self.check_maintainer(False)

    def test_ppa_oem_main(self):
        '''OEM PPA for main package'''

        # these currently look like normal PPAs, we can't yet determine the PPA
        # name from buildinfo; hack around with checking apt sources
        self.buildinfo = '''Package: icecream
Component: main
Suite: lucid
Purpose: PPA
'''
        apt_sources = open(os.path.join(self.workdir, 'sources.list'), 'w')
        apt_sources.write('''deb http://private-ppa.buildd/oem-archive/myoem/ubuntu lucid main
deb https://cesg.canonical.com/canonical myoem public private
deb https://cesg.canonical.com/canonical myoem-devel public private
deb http://ftpmaster.internal/ubuntu lucid main restricted universe multiverse
''')
        apt_sources.close()

        # rename binaries to two in main
        assert subprocess.call(['sed', '-i', 's/vanilla/coreutils/g; s/chocolate/bash/g', 
            os.path.join(self.pkgdir, 'debian', 'control')]) == 0
        os.rename(os.path.join(self.pkgdir, 'debian', 'vanilla.install'),
                os.path.join(self.pkgdir, 'debian', 'coreutils.install'))
        os.rename(os.path.join(self.pkgdir, 'debian', 'chocolate.install'),
                os.path.join(self.pkgdir, 'debian', 'bash.install'))

        self.build(True, {'PKGBINARYMANGLER_APT_CONF_DIR': self.workdir})

        # should strip translations, since the are in Ubuntu main
        for pkg in ('coreutils', 'bash'):
            deb = glob(os.path.join(self.workdir, '%s_1_*.deb' % pkg))[0]
            dpkg = subprocess.Popen(['dpkg', '-c', deb],
                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            (out, err) = dpkg.communicate()
            self.failIf('/usr/share/locale/' in out)
            self.assert_('./usr/share/doc/%s/copyright' % pkg in out)

        tar = tarfile.open(self.translations_tar)
        self.assert_('./coreutils/usr/share/locale/fr/LC_MESSAGES/vanilla.mo' 
                in tar.getnames())
        self.assert_('./bash/usr/share/locale/de/LC_MESSAGES/chocolate.mo' 
                in tar.getnames())
        self.assert_('./source/po-vanilla/vanilla.pot'
                in tar.getnames())
        self.assert_('./source/po-chocolate/de.po'
                in tar.getnames())

        # no maintainer mangling in PPAs, as usual
        self.check_maintainer(False)

    def test_ppa_oem_main_blacklisted(self):
        '''OEM PPA for main package for blacklisted project'''

        # these currently look like normal PPAs, we can't yet determine the PPA
        # name from buildinfo; hack around with checking apt sources
        self.buildinfo = '''Package: icecream
Component: main
Suite: lucid
Purpose: PPA
'''
        apt_sources = open(os.path.join(self.workdir, 'sources.list'), 'w')
        apt_sources.write('''deb http://private-ppa.buildd/oem-archive/partner/ubuntu lucid main
deb https://cesg.canonical.com/canonical partner public private
deb https://cesg.canonical.com/canonical partner-devel public private
deb http://ftpmaster.internal/ubuntu lucid main restricted universe multiverse
''')
        apt_sources.close()

        # rename binaries to two in main
        assert subprocess.call(['sed', '-i', 's/vanilla/coreutils/g; s/chocolate/bash/g', 
            os.path.join(self.pkgdir, 'debian', 'control')]) == 0
        os.rename(os.path.join(self.pkgdir, 'debian', 'vanilla.install'),
                os.path.join(self.pkgdir, 'debian', 'coreutils.install'))
        os.rename(os.path.join(self.pkgdir, 'debian', 'chocolate.install'),
                os.path.join(self.pkgdir, 'debian', 'bash.install'))

        self.build(True, {'PKGBINARYMANGLER_APT_CONF_DIR': self.workdir})

        # should not strip translations, since partner is blacklisted
        for pkg in ('coreutils', 'bash'):
            deb = glob(os.path.join(self.workdir, '%s_1_*.deb' % pkg))[0]
            dpkg = subprocess.Popen(['dpkg', '-c', deb],
                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            (out, err) = dpkg.communicate()
            if pkg == 'coreutils':
                self.assert_('./usr/share/locale/fr/LC_MESSAGES/vanilla.mo' in out)
            else:
                self.assert_('./usr/share/locale/de/LC_MESSAGES/chocolate.mo' in out)
            self.assert_('./usr/share/doc/%s/copyright' % pkg in out)

        self.assertEqual(self.translations_tar, None)

        # no maintainer mangling in PPAs, as usual
        self.check_maintainer(False)

    def test_installed_size(self):
        '''Installed-Size gets updated'''

        # add some bloat to the vanilla po to get a recognizable size increase
        f = open(os.path.join(self.pkgdir, 'po-vanilla', 'de.po'), 'a')
        for i in xrange(10000):
            f.write('\nmsgid "%i"\nmsgstr"%i"\n' % (i, i))
        f.close()

        self.build()

        dpkg = subprocess.Popen(['dpkg', '-I', os.path.join(self.workdir, 'vanilla_1_all.deb')],
                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        try:
            for l in dpkg.stdout:
                l = l.strip()
                if l.startswith('Installed-Size:'):
                    self.assert_(re.match('^Installed-Size: \d+$', l))
                    self.assert_(int(l.split()[1]) < 100)
                    break
            else:
                self.fail('No Installed-Size: header')
        finally:
            self.assertEqual(dpkg.wait(), 0)

        self.check_deb_integrity()

#
# main
#

unittest.main()
