#!/usr/bin/python3
import unittest
import subprocess
import tempfile
import shutil
import os
from glob import glob
import json

class T(unittest.TestCase):
    def setUp(self):
        '''Call ubuntu-defaults-template to build a test project.

        This will build defaults-test (self.pkgdir) in self.workdir (a
        temporary directory), and leave the files untouched. Call
        self.enable_example() to enable the example content of a customization
        file, or change the files manually.
        '''
        self.srcdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
        self.workdir = tempfile.mkdtemp()
        os.environ['UBUNTU_DEFAULTS_TEMPLATE_DIR'] = os.path.join(self.srcdir, 'template')

        builder = subprocess.Popen([os.path.join(self.srcdir, 'bin', 'ubuntu-defaults-template'),
            '--quiet', 'defaults-test'], cwd=self.workdir)
        builder.communicate()
        self.assertEqual(builder.returncode, 0)
        self.pkgdir = os.path.join(self.workdir, 'defaults-test')
        self.assertTrue(os.path.isdir(self.pkgdir))

    def tearDown(self):
        shutil.rmtree(self.workdir)

    def build(self, expect_fail=False):
        '''Build the defaults package and run lintian check.
        
        Return the result of dpkg -I. After running this, self.debdir contains
        the unpacked .deb.
        '''
        env = os.environ.copy()
        env['PATH'] = os.path.join(self.srcdir, 'bin') + ':' + env.get('PATH', '')
        env['DH_AUTOSCRIPTDIR'] = os.path.join(self.srcdir, 'debhelper-scripts')
        dpkg = subprocess.Popen(['dpkg-buildpackage', '-us', '-uc', '-d'],
                cwd=self.pkgdir, stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT, env=env)
        out = dpkg.communicate()[0].decode()
        if expect_fail:
            self.assertNotEqual(dpkg.returncode, 0, 'dpkg-buildpackage unexpectedly succeeded:\n' + out)
            return
        else:
            self.assertEqual(dpkg.returncode, 0, 'dpkg-buildpackage failed:\n' + out)

        changes = glob(os.path.join(self.workdir, '*defaults-test_*.changes'))
        self.assertEqual(len(changes), 1)
        changes = changes[0]

        (code, lintian) = subprocess.getstatusoutput('lintian ' + changes)
        # filter out warnings we don't care about
        for line in lintian.splitlines():
            if 'unknown-section metapackages' in line or \
               'desktop-command-not-in-package' in line or \
               'empty-binary-package' in line:
                   continue
            self.fail('lintian errors:\n' + lintian)
        self.assertEqual(code, 0, 'lintian failed')

        deb = glob(os.path.join(self.workdir, '*defaults-test_*.deb'))
        self.assertEqual(len(deb), 1)
        deb = deb[0]

        self.debdir = os.path.join(self.workdir, 'deb')
        subprocess.check_call(['dpkg', '-x', deb, self.debdir],
                cwd=self.workdir)

        return subprocess.check_output(['dpkg', '-I', deb], cwd=self.workdir).decode()

    def enable_example(self, file):
        file = os.path.join(self.pkgdir, file)
        lines = []
        line = ''
        with open(file) as f:
            for line in f:
                if line.startswith('# Example:'):
                    break
            for line in f:
                assert line.startswith('# ')
                lines.append(line[2:])
        with open(file, 'w') as f:
            for line in lines:
                f.write(line)

    def set_file(self, file, contents):
        with open(os.path.join(self.pkgdir, file), 'w') as f:
            f.write(contents)

    def rename_package(self, name):
        '''Change the built package name'''

        for file in ['debian/control', 'debian/changelog']:
            with open(os.path.join(self.pkgdir, file)) as f:
                contents = f.read()
            contents = contents.replace('defaults-test', name)
            with open(os.path.join(self.pkgdir, file), 'w') as f:
                f.write(contents)

    def test_template(self):
        '''Pure template without any customizations.'''

        control = self.build()

        files = subprocess.check_output(['find', '-type', 'f', '!', '-path',
            './usr/share/doc/defaults-test*'], cwd=self.debdir).decode()
        self.assertTrue(files == '', 'unexpected files in package:\n' + files)

        self.assertTrue('Package: defaults-test' in control)
        self.assertFalse('Depends:' in control)
        self.assertFalse('Recommends:' in control)

    def test_empty(self):
        '''Empty defaults package with no example files'''

        # only keep the Debianization
        for f in os.listdir(self.pkgdir):
            if f != 'debian':
                path = os.path.join(self.pkgdir, f)
                if os.path.isdir(path):
                    shutil.rmtree(path)
                else:
                    os.unlink(path)

        control = self.build()

        self.assertTrue('Package: defaults-test' in control)
        self.assertFalse('Depends:' in control)
        self.assertFalse('Recommends:' in control)

    def test_dependencies(self):
        '''depends.txt, recommends.txt'''

        self.enable_example('depends.txt')
        self.enable_example('recommends.txt')

        control = self.build()
        self.assertTrue('Depends: manpages-de\n' in control, control)
        self.assertTrue('Recommends: fortunes-de\n' in control)

    def test_desktop_background(self):
        '''desktop/background.jpg'''

        test_jpg = os.path.join('test', 'test.jpg')
        shutil.copy(test_jpg, os.path.join(self.pkgdir,
            'desktop', 'background.jpg'))
        self.build()
        with open(os.path.join(self.debdir, 'usr/share/backgrounds/defaults-test.jpg'), 'rb') as dest:
            with open(test_jpg, 'rb') as src:
                self.assertEqual(src.read(), dest.read())
        with open(os.path.join(self.debdir,
            'usr/share/glib-2.0/schemas/defaults-test.gschema.override')) as f:
            self.assertTrue('usr/share/backgrounds/defaults-test.jpg' in f.read())

    def test_desktop_files(self):
        '''desktop/*.desktop'''

        path = os.path.join(self.pkgdir, 'desktop', 'googlemail.desktop')
        os.rename(path + '.example', path)
        self.build()

        with open(os.path.join(self.debdir,
            'usr/share/applications/googlemail.desktop')) as f:
            self.assertTrue('http://googlemail.com' in f.read())

    def test_desktop_default_session(self):
        '''desktop/default-session.txt'''

        self.enable_example('desktop/default-session.txt')
        self.build()

        with open(os.path.join(self.debdir,
            'usr/share/defaults-test/default-session.txt')) as f:
            self.assertEqual(f.read(), 'ubuntu-2d')

    def test_default_applications(self):
        '''desktop/default-applications.txt'''

        self.enable_example('desktop/default-applications.txt')
        control = self.build()

        # check default applications settings
        with open(os.path.join(self.debdir,
            'usr/share/applications/defaults.list')) as f:
            contents = f.read()
        self.assertTrue("[Default Applications]" in contents, 'has correct header')
        self.assertTrue("application/ogg=banshee.desktop" in contents, 'has overriden default applications')
        self.assertTrue("x-scheme-handler/mailto=thunderbird.desktop" in contents, 'has overriden default application')
        self.assertTrue("x-foo/bar=toto.desktop" in contents, 'have non exiting custom mimetype')
        self.assertTrue(contents.count("application/ogg") == 1, 'have only one occurence by overriden mimetype')
        self.assertTrue(contents.count("\n") > 3, 'have not only overriden values')

    def test_unity(self):
        '''unity/launchers.txt'''

        self.enable_example('unity/launchers.txt')
        control = self.build()

        # check Unity gsettings
        with open(os.path.join(self.debdir,
            'usr/share/glib-2.0/schemas/defaults-test.gschema.override')) as f:
            contents = f.read()
        self.assertTrue("favorites=[" in contents, 'has correct array formatting')
        self.assertTrue("'application://firefox.desktop'," in contents, 'has standard launchers: ' + contents)
        self.assertTrue("'application://gnome-sudoku.desktop'," in contents, 'has custom launchers:' + contents)
        self.assertTrue("'application://sol.desktop'" in contents, 'has custom launchers:' + contents)
        # picks up the extra dependency
        self.assertTrue('aisleriot' in control, control)
        
    def test_unity_clear(self):
        '''unity/launchers.txt with @clear'''

        with open(os.path.join(self.pkgdir, 'unity', 'launchers.txt'), 'w') as f:
            f.write('@clear\ngnome-sudoku\nyelp');
        self.build()

        # check Unity gsettings
        with open(os.path.join(self.debdir,
            'usr/share/glib-2.0/schemas/defaults-test.gschema.override')) as f:
            contents = f.read()
        self.assertTrue("favorites=[" in contents, 'has correct array formatting')
        self.assertFalse('firefox.desktop' in contents, 'does not have standard launchers: ' + contents)
        self.assertTrue("'application://gnome-sudoku.desktop'," in contents, 'has custom launchers:' + contents)
        self.assertTrue("'application://yelp.desktop'" in contents, 'has custom launchers:' + contents)
        self.assertFalse('clear' in contents, 'does not have @clear control command')

    def test_multimedia(self):
        '''multimedia/radiostations.txt'''

        self.enable_example('multimedia/radiostations.txt')
        with open(os.path.join(self.pkgdir, 'multimedia', 'radiostations.txt'), 'a') as f:
            f.write('invalid;line\n');
        self.build()

        with open(os.path.join(self.debdir,
            'usr/share/rhythmbox/plugins/iradio/iradio-initial.xspf')) as f:
            contents = f.read()

        self.assertTrue('Paradise' in contents)
        self.assertTrue('SWR3' in contents)
        self.assertFalse('invalid' in contents)
        self.assertTrue('&amp;channel' in contents)

        self.assertNotEqual(glob(os.path.join(self.debdir,
            'usr/share/banshee/stations/*.xspf')), [])

    def test_webbrowser(self):
        '''webbrowser/'''

        self.enable_example('webbrowser/startpage.txt')
        self.enable_example('webbrowser/searchengine.txt')
        self.enable_example('webbrowser/bookmarks-toolbar.txt')
        self.enable_example('webbrowser/bookmarks-menu.txt')
        self.build()

        with open(glob(os.path.join(self.debdir,
            'usr/lib/firefox*/distribution/distribution.ini'))[0]) as f:
            contents = f.read()

        rel = subprocess.check_output(['lsb_release', '-sr']).decode().strip()
        self.assertTrue('http://www.ubuntuforums.org?release=' + rel in contents)
        self.assertFalse('${' in contents)
        self.assertTrue('"Wikipedia (de)"' in contents)
        self.assertTrue('www.zeit.de' in contents)
        self.assertTrue('www.ubuntu.de' in contents)
        self.assertTrue('heise.de' in contents)

    def test_i18n(self):
        '''i18n/'''

        self.enable_example('i18n/language.txt')
        self.enable_example('i18n/langpacks.txt')
        self.enable_example('i18n/keyboard.txt')
        self.build()
        with open(os.path.join(self.debdir,
            'usr/share/defaults-test/language.txt')) as f:
            self.assertEqual(f.read(), 'de\n')
        with open(os.path.join(self.debdir,
            'usr/share/defaults-test/langpacks.txt')) as f:
            self.assertEqual(f.read(), 'de complete\nes\nzh-hans')
        with open(os.path.join(self.debdir,
            'usr/share/defaults-test/keyboard.txt')) as f:
            self.assertEqual(f.read(), 'de_nodeadkeys')

    def test_i18n_keyboard_no_variant(self):
        '''i18n/keyboard.txt without variant'''

        with open(os.path.join(self.pkgdir, 'i18n', 'keyboard.txt'), 'w') as f:
            f.write('de\n');
        self.build()
        with open(os.path.join(self.debdir,
            'usr/share/defaults-test/keyboard.txt')) as f:
            self.assertEqual(f.read(), 'de')

    def test_i18n_keyboard_invalid_fields(self):
        '''i18n/keyboard.txt with invalid number of fields'''

        with open(os.path.join(self.pkgdir, 'i18n', 'keyboard.txt'), 'w') as f:
            f.write('de nodeadkeys foo\n');
        self.build(expect_fail=True)

    def test_i18n_keyboard_invalid_layout(self):
        '''i18n/keyboard.txt with invalid layout'''

        with open(os.path.join(self.pkgdir, 'i18n', 'keyboard.txt'), 'w') as f:
            f.write('yy\n');
        self.build(expect_fail=True)

    def test_i18n_keyboard_invalid_variant(self):
        '''i18n/keyboard.txt with invalid variant'''

        with open(os.path.join(self.pkgdir, 'i18n', 'keyboard.txt'), 'w') as f:
            f.write('de dvorak-classic\n');
        self.build(expect_fail=True)

    def test_macros(self):
        '''macros'''

        # this gets copied verbatim, so abuse this for some more complicated
        # test cases
        with open(os.path.join(self.pkgdir, 'i18n', 'language.txt'), 'w') as f:
            f.write('-${distro_release_name}-$-${distro_release_number}-${distro_release_name}');
        self.build()
        rel = subprocess.check_output(['lsb_release', '-sr']).decode().strip()
        relname = subprocess.check_output(['lsb_release', '-sc']).decode().strip()
        with open(glob(os.path.join(self.debdir,
            'usr/share/defaults-test/language.txt'))[0]) as f:
            self.assertEqual(f.read(), '-%s-$-%s-%s\n' % (relname, rel, relname))

        with open(os.path.join(self.pkgdir, 'webbrowser', 'startpage.txt'), 'w') as f:
            f.write('http://${unknown}.start.com\n');
        self.build(expect_fail=True)

    def test_icons(self):
        '''icons/'''

        test_jpg = os.path.join('test', 'test.jpg')
        shutil.copy(test_jpg, os.path.join(self.pkgdir,
            'icons', '48x48', 'apps', 'myapp.jpg'))
        self.build()
        self.assertEqual(os.listdir(os.path.join(self.debdir, 'usr/share/icons/hicolor')), 
                ['48x48'])
        with open(os.path.join(self.debdir, 'usr/share/icons/hicolor/48x48/apps/myapp.jpg'), 'rb') as dest:
            with open(test_jpg, 'rb') as src:
                self.assertEqual(src.read(), dest.read())
        self.assertFalse(os.path.exists(os.path.join(self.debdir, 'usr/share/icons/hicolor/README')))

    def test_ubiquity_slideshow(self):
        '''ubiquity-slideshow/'''

        slides_path = os.path.join(self.pkgdir, 'ubiquity-slideshow', 'slides')
        os.makedirs(os.path.join(slides_path, 'C', 'screenshots'))
        test_jpg = os.path.join('test', 'test.jpg')
        shutil.copy(test_jpg, os.path.join(slides_path, 'C', 'screenshots', 'welcome.jpg'))
        test_slide = open(os.path.join(slides_path, 'C', 'welcome.html'), 'w')
        test_slide.close()

        self.build()

        install_dir = os.path.join(self.debdir, 'usr/share/ubiquity-slideshow/slides/extra')
        self.assertEqual(sorted(os.listdir(install_dir)),
            ['C', 'directory.jsonp'])
        self.assertEqual(sorted(os.listdir(os.path.join(install_dir, 'C'))),
            ['screenshots', 'welcome.html'])
        self.assertEqual(os.listdir(os.path.join(install_dir, 'C', 'screenshots')),
            ['welcome.jpg'])
        self.assertFalse(os.path.exists(
            os.path.join(install_dir, 'README')))

        directory = {}
        with open(os.path.join(install_dir, 'directory.jsonp'), 'r') as f:
            f_jsonp = f.read()
            f_json = f_jsonp[f_jsonp.index('(')+1 : f_jsonp.rindex(')')]
            directory = json.loads(f_json)
        self.assertEqual(directory['C']['slides'], ['welcome.html'])
        self.assertEqual(directory['C']['media'], ['screenshots/welcome.jpg'])

    def test_hooks(self):
        '''hooks/'''

        with open(os.path.join(self.pkgdir, 'hooks', 'chroot'), 'w') as f:
            f.write('#!/bin/sh -e\ntouch /tmp/pwned\n')
        self.build()
        self.assertTrue(os.access(os.path.join(self.debdir, 
            'usr/share/defaults-test/hooks/chroot'), os.X_OK))

    def test_ubuntu_restrictions_allowed(self):
        '''Some settings are allowed in ubuntu-* package'''

        self.rename_package('ubuntu-defaults-test')
        self.enable_example('depends.txt')
        self.enable_example('recommends.txt')
        self.enable_example('webbrowser/bookmarks-menu.txt')
        self.enable_example('i18n/language.txt')
        self.enable_example('i18n/langpacks.txt')
        self.enable_example('i18n/keyboard.txt')
        self.build()

    def test_ubuntu_restrictions_session(self):
        '''Default session must not be changed for an ubuntu-* package'''

        self.rename_package('ubuntu-defaults-test')
        self.enable_example('desktop/default-session.txt')
        self.build(expect_fail=True)

    def test_ubuntu_restrictions_background(self):
        '''Background must not be changed for an ubuntu-* package'''

        self.rename_package('ubuntu-defaults-test')
        test_jpg = os.path.join('test', 'test.jpg')
        shutil.copy(test_jpg, os.path.join(self.pkgdir,
            'desktop', 'background.jpg'))
        self.build(expect_fail=True)

    def test_ubuntu_restrictions_disable(self):
        '''Disable ubuntu-* restrictions'''

        self.rename_package('ubuntu-defaults-test')
        self.enable_example('desktop/default-session.txt')

        rules_path = os.path.join(self.pkgdir, 'debian', 'rules')
        with open(rules_path) as f:
            rules = f.read()
        rules = rules.replace('dh_ubuntu_defaults', 'dh_ubuntu_defaults --disable-restrictions')
        with open(rules_path, 'w') as f:
            f.write(rules)

        self.build(expect_fail=False)

        with open(os.path.join(self.debdir,
            'usr/share/ubuntu-defaults-test/default-session.txt')) as f:
            self.assertEqual(f.read(), 'ubuntu-2d')

    def test_image_syntax(self):
        '''syntax check of ubuntu-defaults-image'''

        self.assertEqual(subprocess.call(['sh', '-n', 'bin/ubuntu-defaults-image']), 0)

    def test_image_ppa_spec(self):
        '''ubuntu-defaults-image fails early on invalid PPA spec'''

        for spec in ['~user/foo', 'user', 'user foo']:
            img = subprocess.Popen(['bin/ubuntu-defaults-image', '--ppa',
                spec], stderr=subprocess.PIPE)
            err = img.communicate()[1].decode()
            self.assertNotEqual(img.returncode, 0)
            self.assertTrue('Invalid PPA' in err)

unittest.main()
