# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import os.path
import shutil
import zipfile
from StringIO import StringIO
import simplejson as json
import unittest
import cuddlefish
from cuddlefish import packaging, manifest

def up(path, generations=1):
    for i in range(generations):
        path = os.path.dirname(path)
    return path

ROOT = up(os.path.abspath(__file__), 4)
def get_linker_files_dir(name):
    return os.path.join(up(os.path.abspath(__file__)), "linker-files", name)

class Basic(unittest.TestCase):
    def get_pkg(self, name):
        d = get_linker_files_dir(name)
        return packaging.get_config_in_dir(d)

    def test_deps(self):
        target_cfg = self.get_pkg("one")
        pkg_cfg = packaging.build_config(ROOT, target_cfg)
        deps = packaging.get_deps_for_targets(pkg_cfg, ["one"])
        self.failUnlessEqual(deps, ["one"])
        deps = packaging.get_deps_for_targets(pkg_cfg,
                                              [target_cfg.name, "addon-sdk"])
        self.failUnlessEqual(deps, ["addon-sdk", "one"])

    def test_manifest(self):
        target_cfg = self.get_pkg("one")
        pkg_cfg = packaging.build_config(ROOT, target_cfg)
        deps = packaging.get_deps_for_targets(pkg_cfg,
                                              [target_cfg.name, "addon-sdk"])
        self.failUnlessEqual(deps, ["addon-sdk", "one"])
        # target_cfg.dependencies is not provided, so we'll search through
        # all known packages (everything in 'deps').
        m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=False)
        m = m.get_harness_options_manifest(False)

        def assertReqIs(modname, reqname, path):
            reqs = m["one/%s" % modname]["requirements"]
            self.failUnlessEqual(reqs[reqname], path)
        assertReqIs("main", "sdk/panel", "sdk/panel")
        assertReqIs("main", "two.js", "one/two")
        assertReqIs("main", "./two", "one/two")
        assertReqIs("main", "sdk/tabs.js", "sdk/tabs")
        assertReqIs("main", "./subdir/three", "one/subdir/three")
        assertReqIs("two", "main", "one/main")
        assertReqIs("subdir/three", "../main", "one/main")

        target_cfg.dependencies = []
        # now, because .dependencies *is* provided, we won't search 'deps',
        # so we'll get a link error
        self.assertRaises(manifest.ModuleNotFoundError,
                          manifest.build_manifest,
                          target_cfg, pkg_cfg, deps, scan_tests=False)

    def test_main_in_deps(self):
        target_cfg = self.get_pkg("three")
        package_path = [get_linker_files_dir("three-deps")]
        pkg_cfg = packaging.build_config(ROOT, target_cfg,
                                         packagepath=package_path)
        deps = packaging.get_deps_for_targets(pkg_cfg,
                                              [target_cfg.name, "addon-sdk"])
        self.failUnlessEqual(deps, ["addon-sdk", "three"])
        m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=False)
        m = m.get_harness_options_manifest(False)
        def assertReqIs(modname, reqname, path):
            reqs = m["three/%s" % modname]["requirements"]
            self.failUnlessEqual(reqs[reqname], path)
        assertReqIs("main", "three-a", "three-a/main")
        assertReqIs("main", "three-b", "three-b/main")
        assertReqIs("main", "three-c", "three-c/main")

    def test_relative_main_in_top(self):
        target_cfg = self.get_pkg("five")
        package_path = []
        pkg_cfg = packaging.build_config(ROOT, target_cfg,
                                         packagepath=package_path)
        deps = packaging.get_deps_for_targets(pkg_cfg,
                                              [target_cfg.name, "addon-sdk"])
        self.failUnlessEqual(deps, ["addon-sdk", "five"])
        # all we care about is that this next call doesn't raise an exception
        m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=False)
        m = m.get_harness_options_manifest(False)
        reqs = m["five/main"]["requirements"]
        self.failUnlessEqual(reqs, {});

    def test_unreachable_relative_main_in_top(self):
        target_cfg = self.get_pkg("six")
        package_path = []
        pkg_cfg = packaging.build_config(ROOT, target_cfg,
                                         packagepath=package_path)
        deps = packaging.get_deps_for_targets(pkg_cfg,
                                              [target_cfg.name, "addon-sdk"])
        self.failUnlessEqual(deps, ["addon-sdk", "six"])
        self.assertRaises(manifest.UnreachablePrefixError,
                          manifest.build_manifest,
                          target_cfg, pkg_cfg, deps, scan_tests=False)

    def test_unreachable_in_deps(self):
        target_cfg = self.get_pkg("four")
        package_path = [get_linker_files_dir("four-deps")]
        pkg_cfg = packaging.build_config(ROOT, target_cfg,
                                         packagepath=package_path)
        deps = packaging.get_deps_for_targets(pkg_cfg,
                                              [target_cfg.name, "addon-sdk"])
        self.failUnlessEqual(deps, ["addon-sdk", "four"])
        self.assertRaises(manifest.UnreachablePrefixError,
                          manifest.build_manifest,
                          target_cfg, pkg_cfg, deps, scan_tests=False)

class Contents(unittest.TestCase):

    def run_in_subdir(self, dirname, f, *args, **kwargs):
        top = os.path.abspath(os.getcwd())
        basedir = os.path.abspath(os.path.join(".test_tmp",self.id(),dirname))
        if os.path.isdir(basedir):
            assert basedir.startswith(top)
            shutil.rmtree(basedir)
        os.makedirs(basedir)
        try:
            os.chdir(basedir)
            return f(basedir, *args, **kwargs)
        finally:
            os.chdir(top)

    def assertIn(self, what, inside_what):
        self.failUnless(what in inside_what, inside_what)

    def test_jetpackID(self):
        # this uses "id": "jid7", to which a @jetpack should be appended
        seven = get_linker_files_dir("seven")
        def _test(basedir):
            stdout = StringIO()
            shutil.copytree(seven, "seven")
            os.chdir("seven")
            try:
                # regrettably, run() always finishes with sys.exit()
                cuddlefish.run(["xpi", "--no-strip-xpi"],
                               stdout=stdout)
            except SystemExit, e:
                self.failUnlessEqual(e.args[0], 0)
            zf = zipfile.ZipFile("seven.xpi", "r")
            hopts = json.loads(zf.read("harness-options.json"))
            self.failUnlessEqual(hopts["jetpackID"], "jid7@jetpack")
        self.run_in_subdir("x", _test)

    def test_jetpackID_suffix(self):
        # this uses "id": "jid1@jetpack", so no suffix should be appended
        one = get_linker_files_dir("one")
        def _test(basedir):
            stdout = StringIO()
            shutil.copytree(one, "one")
            os.chdir("one")
            try:
                # regrettably, run() always finishes with sys.exit()
                cuddlefish.run(["xpi", "--no-strip-xpi"],
                               stdout=stdout)
            except SystemExit, e:
                self.failUnlessEqual(e.args[0], 0)
            zf = zipfile.ZipFile("one.xpi", "r")
            hopts = json.loads(zf.read("harness-options.json"))
            self.failUnlessEqual(hopts["jetpackID"], "jid1@jetpack")
        self.run_in_subdir("x", _test)

    def test_strip_default(self):
        seven = get_linker_files_dir("seven")
        # now run 'cfx xpi' in that directory, except put the generated .xpi
        # elsewhere
        def _test(basedir):
            stdout = StringIO()
            shutil.copytree(seven, "seven")
            os.chdir("seven")
            try:
                # regrettably, run() always finishes with sys.exit()
                cuddlefish.run(["xpi"], # --strip-xpi is now the default
                               stdout=stdout)
            except SystemExit, e:
                self.failUnlessEqual(e.args[0], 0)
            zf = zipfile.ZipFile("seven.xpi", "r")
            names = zf.namelist()
            # problem found in bug 664840 was that an addon
            # without an explicit tests/ directory would copy all files from
            # the package into a bogus JID-PKGNAME-tests/ directory, so check
            # for that
            testfiles = [fn for fn in names if "seven/tests" in fn]
            self.failUnlessEqual([], testfiles)
            # another problem was that data files were being stripped from
            # the XPI. Note that data/ is only supposed to be included if a
            # module that actually gets used does a require("self") .
            self.assertIn("resources/seven/data/text.data",
                          names)
            self.failIf("seven/lib/unused.js"
                        in names, names)
        self.run_in_subdir("x", _test)

    def test_no_strip(self):
        seven = get_linker_files_dir("seven")
        def _test(basedir):
            stdout = StringIO()
            shutil.copytree(seven, "seven")
            os.chdir("seven")
            try:
                # regrettably, run() always finishes with sys.exit()
                cuddlefish.run(["xpi", "--no-strip-xpi"],
                               stdout=stdout)
            except SystemExit, e:
                self.failUnlessEqual(e.args[0], 0)
            zf = zipfile.ZipFile("seven.xpi", "r")
            names = zf.namelist()
            self.assertIn("resources/addon-sdk/lib/sdk/loader/cuddlefish.js", names)
            testfiles = [fn for fn in names if "seven/tests" in fn]
            self.failUnlessEqual([], testfiles)
            self.assertIn("resources/seven/data/text.data",
                          names)
            self.failUnless("resources/seven/lib/unused.js"
                            in names, names)
        self.run_in_subdir("x", _test)


if __name__ == '__main__':
    unittest.main()
