# 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
import unittest
from shutil import rmtree
from tempfile import mkdtemp

import buildconfig
import mozpack.path as mozpath
from mozunit import main

from mozbuild.backend.configenvironment import PartialConfigEnvironment

config = {
    "defines": {
        "MOZ_FOO": "1",
        "MOZ_BAR": "2",
    },
    "substs": {
        "MOZ_SUBST_1": "1",
        "MOZ_SUBST_2": "2",
        "CPP": "cpp",
    },
}


class TestPartial(unittest.TestCase):
    def setUp(self):
        self._old_env = dict(os.environ)

    def tearDown(self):
        os.environ.clear()
        os.environ.update(self._old_env)

    def _objdir(self):
        objdir = mkdtemp(dir=buildconfig.topsrcdir)
        self.addCleanup(rmtree, objdir)
        return objdir

    def test_auto_substs(self):
        """Test the automatically set values of ACDEFINES, and ALLDEFINES"""
        env = PartialConfigEnvironment(self._objdir())
        env.write_vars(config)
        self.assertEqual(env.substs["ACDEFINES"], "-DMOZ_BAR=2 -DMOZ_FOO=1")
        self.assertEqual(
            env.defines["ALLDEFINES"],
            {
                "MOZ_BAR": "2",
                "MOZ_FOO": "1",
            },
        )

    def test_remove_subst(self):
        """Test removing a subst from the config. The file should be overwritten with 'None'"""
        env = PartialConfigEnvironment(self._objdir())
        path = mozpath.join(env.topobjdir, "config.statusd", "substs", "MYSUBST")
        myconfig = config.copy()
        env.write_vars(myconfig)
        with self.assertRaises(KeyError):
            _ = env.substs["MYSUBST"]
        self.assertFalse(os.path.exists(path))

        myconfig["substs"]["MYSUBST"] = "new"
        env.write_vars(myconfig)

        self.assertEqual(env.substs["MYSUBST"], "new")
        self.assertTrue(os.path.exists(path))

        del myconfig["substs"]["MYSUBST"]
        env.write_vars(myconfig)
        with self.assertRaises(KeyError):
            _ = env.substs["MYSUBST"]
        # Now that the subst is gone, the file still needs to be present so that
        # make can update dependencies correctly. Overwriting the file with
        # 'None' is the same as deleting it as far as the
        # PartialConfigEnvironment is concerned, but make can't track a
        # dependency on a file that doesn't exist.
        self.assertTrue(os.path.exists(path))

    def _assert_deps(self, env, deps):
        deps = sorted(
            [
                "$(wildcard %s)" % (mozpath.join(env.topobjdir, "config.statusd", d))
                for d in deps
            ]
        )
        self.assertEqual(sorted(env.get_dependencies()), deps)

    def test_dependencies(self):
        """Test getting dependencies on defines and substs."""
        env = PartialConfigEnvironment(self._objdir())
        env.write_vars(config)
        self._assert_deps(env, [])

        self.assertEqual(env.defines["MOZ_FOO"], "1")
        self._assert_deps(env, ["defines/MOZ_FOO"])

        self.assertEqual(env.defines["MOZ_BAR"], "2")
        self._assert_deps(env, ["defines/MOZ_FOO", "defines/MOZ_BAR"])

        # Getting a define again shouldn't add a redundant dependency
        self.assertEqual(env.defines["MOZ_FOO"], "1")
        self._assert_deps(env, ["defines/MOZ_FOO", "defines/MOZ_BAR"])

        self.assertEqual(env.substs["MOZ_SUBST_1"], "1")
        self._assert_deps(
            env, ["defines/MOZ_FOO", "defines/MOZ_BAR", "substs/MOZ_SUBST_1"]
        )

        with self.assertRaises(KeyError):
            _ = env.substs["NON_EXISTENT"]
        self._assert_deps(
            env,
            [
                "defines/MOZ_FOO",
                "defines/MOZ_BAR",
                "substs/MOZ_SUBST_1",
                "substs/NON_EXISTENT",
            ],
        )
        self.assertEqual(env.substs.get("NON_EXISTENT"), None)

    def test_set_subst(self):
        """Test setting a subst"""
        env = PartialConfigEnvironment(self._objdir())
        env.write_vars(config)

        self.assertEqual(env.substs["MOZ_SUBST_1"], "1")
        env.substs["MOZ_SUBST_1"] = "updated"
        self.assertEqual(env.substs["MOZ_SUBST_1"], "updated")

        # A new environment should pull the result from the file again.
        newenv = PartialConfigEnvironment(env.topobjdir)
        self.assertEqual(newenv.substs["MOZ_SUBST_1"], "1")

    def test_env_override(self):
        """Test overriding a subst with an environment variable"""
        env = PartialConfigEnvironment(self._objdir())
        env.write_vars(config)

        self.assertEqual(env.substs["MOZ_SUBST_1"], "1")
        self.assertEqual(env.substs["CPP"], "cpp")

        # Reset the environment and set some environment variables.
        env = PartialConfigEnvironment(env.topobjdir)
        os.environ["MOZ_SUBST_1"] = "subst 1 environ"
        os.environ["CPP"] = "cpp environ"

        # The MOZ_SUBST_1 should be overridden by the environment, while CPP is
        # a special variable and should not.
        self.assertEqual(env.substs["MOZ_SUBST_1"], "subst 1 environ")
        self.assertEqual(env.substs["CPP"], "cpp")

    def test_update(self):
        """Test calling update on the substs or defines pseudo dicts"""
        env = PartialConfigEnvironment(self._objdir())
        env.write_vars(config)

        mysubsts = {"NEW": "new"}
        mysubsts.update(env.substs.iteritems())
        self.assertEqual(mysubsts["NEW"], "new")
        self.assertEqual(mysubsts["CPP"], "cpp")

        mydefines = {"DEBUG": "1"}
        mydefines.update(env.defines.iteritems())
        self.assertEqual(mydefines["DEBUG"], "1")
        self.assertEqual(mydefines["MOZ_FOO"], "1")


if __name__ == "__main__":
    main()
