# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import unittest

import SCons.Executor


class MyEnvironment:
    def __init__(self, **kw):
        self._dict = {}
        self._dict.update(kw)
    def __getitem__(self, key):
        return self._dict[key]
    def Override(self, overrides):
        d = self._dict.copy()
        d.update(overrides)
        return MyEnvironment(**d)
    def _update(self, dict):
        self._dict.update(dict)

class MyAction:
    def __init__(self, actions=['action1', 'action2']):
        self.actions = actions
    def __call__(self, target, source, env, **kw):
        for action in self.actions:
            action(target, source, env, **kw)
    def genstring(self, target, source, env):
        return ' '.join(['GENSTRING'] + list(map(str, self.actions)) + target + source)
    def get_contents(self, target, source, env):
        return b' '.join(
            [SCons.Util.to_bytes(aa) for aa in self.actions] + 
            [SCons.Util.to_bytes(tt) for tt in target] +
            [SCons.Util.to_bytes(ss) for ss in source]
        )
    def get_implicit_deps(self, target, source, env):
        return []

class MyBuilder:
    def __init__(self, env, overrides):
        self.env = env
        self.overrides = overrides
        self.action = MyAction()

class MyNode:
    def __init__(self, name=None, pre=[], post=[]):
        self.name = name
        self.implicit = []
        self.pre_actions = pre
        self.post_actions = post
        self.missing_val = None
        self.always_build = False
        self.up_to_date = False

    def __str__(self):
        return self.name

    def build(self):
        executor = SCons.Executor.Executor(MyAction(self.pre_actions +
                                                    [self.builder.action] +
                                                    self.post_actions),
                                           self.builder.env,
                                           [],
                                           [self],
                                           ['s1', 's2'])
        executor(self)
    def get_env_scanner(self, env, kw):
        return MyScanner('dep-')
    def get_implicit_deps(self, env, scanner, path, kw={}):
        if not scanner:
            scanner = self.get_env_scanner(env, kw)
        return [scanner.prefix + str(self)]
    def add_to_implicit(self, deps):
        self.implicit.extend(deps)
    def missing(self):
        return self.missing_val
    def calc_signature(self, calc):
        return 'cs-'+calc+'-'+self.name
    def disambiguate(self):
        return self

    def is_up_to_date(self):
        return self.up_to_date

class MyScanner:
    def __init__(self, prefix):
        self.prefix = prefix
    def path(self, env, cwd, target, source):
        return ()
    def select(self, node):
        return self

class ExecutorTestCase(unittest.TestCase):

    def test__init__(self):
        """Test creating an Executor"""
        source_list = ['s1', 's2']
        x = SCons.Executor.Executor('a', 'e', ['o'], 't', source_list)
        assert x.action_list == ['a'], x.action_list
        assert x.env == 'e', x.env
        assert x.overridelist == ['o'], x.overridelist
        targets = x.get_all_targets()
        assert targets == ['t'], targets
        source_list.append('s3')
        sources = x.get_all_sources()
        assert sources == ['s1', 's2'], sources
        try:
            x = SCons.Executor.Executor(None, 'e', ['o'], 't', source_list)
        except SCons.Errors.UserError:
            pass
        else:
            raise Exception("Did not catch expected UserError")

    def test__action_list(self):
        """Test the {get,set}_action_list() methods"""
        x = SCons.Executor.Executor('a', 'e', 'o', 't', ['s1', 's2'])

        l = x.get_action_list()
        assert l == ['a'], l

        x.add_pre_action('pre')
        x.add_post_action('post')
        l = x.get_action_list()
        assert l == ['pre', 'a', 'post'], l

        x.set_action_list('b')
        l = x.get_action_list()
        assert l == ['pre', 'b', 'post'], l

        x.set_action_list(['c'])
        l = x.get_action_list()
        assert l == ['pre', 'c', 'post'], l

    def test_get_build_env(self):
        """Test fetching and generating a build environment"""
        x = SCons.Executor.Executor(MyAction(), MyEnvironment(e=1), [],
                                    't', ['s1', 's2'])
        x.env = MyEnvironment(eee=1)
        be = x.get_build_env()
        assert be['eee'] == 1, be

        env = MyEnvironment(X='xxx')
        x = SCons.Executor.Executor(MyAction(),
                                    env,
                                    [{'O':'o2'}],
                                    't',
                                    ['s1', 's2'])
        be = x.get_build_env()
        assert be['O'] == 'o2', be['O']
        assert be['X'] == 'xxx', be['X']

        env = MyEnvironment(Y='yyy')
        overrides = [{'O':'ob3'}, {'O':'oo3'}]
        x = SCons.Executor.Executor(MyAction(), env, overrides, ['t'], ['s'])
        be = x.get_build_env()
        assert be['O'] == 'oo3', be['O']
        assert be['Y'] == 'yyy', be['Y']
        overrides = [{'O':'ob3'}]
        x = SCons.Executor.Executor(MyAction(), env, overrides, ['t'], ['s'])
        be = x.get_build_env()
        assert be['O'] == 'ob3', be['O']
        assert be['Y'] == 'yyy', be['Y']

    def test_get_build_scanner_path(self):
        """Test fetching the path for the specified scanner."""
        t = MyNode('t')
        t.cwd = 'here'
        x = SCons.Executor.Executor(MyAction(),
                                    MyEnvironment(SCANNERVAL='sss'),
                                    [],
                                    [t],
                                    ['s1', 's2'])

        class LocalScanner:
            def path(self, env, dir, target, source):
                target = list(map(str, target))
                source = list(map(str, source))
                return "scanner: %s, %s, %s, %s" % (env['SCANNERVAL'], dir, target, source)
        s = LocalScanner()

        p = x.get_build_scanner_path(s)
        assert p == "scanner: sss, here, ['t'], ['s1', 's2']", p

    def test_get_kw(self):
        """Test the get_kw() method"""
        t = MyNode('t')
        x = SCons.Executor.Executor(MyAction(),
                                    MyEnvironment(),
                                    [],
                                    [t],
                                    ['s1', 's2'],
                                    builder_kw={'X':1, 'Y':2})
        kw = x.get_kw()
        assert kw == {'X':1, 'Y':2, 'executor':x}, kw
        kw = x.get_kw({'Z':3})
        assert kw == {'X':1, 'Y':2, 'Z':3, 'executor':x}, kw
        kw = x.get_kw({'X':4})
        assert kw == {'X':4, 'Y':2, 'executor':x}, kw

    def test__call__(self):
        """Test calling an Executor"""
        result = []
        def pre(target, source, env, result=result, **kw):
            result.append('pre')
        def action1(target, source, env, result=result, **kw):
            result.append('action1')
        def action2(target, source, env, result=result, **kw):
            result.append('action2')
        def post(target, source, env, result=result, **kw):
            result.append('post')

        env = MyEnvironment()
        a = MyAction([action1, action2])
        t = MyNode('t')

        x = SCons.Executor.Executor(a, env, [], [t], ['s1', 's2'])
        x.add_pre_action(pre)
        x.add_post_action(post)
        x(t)
        assert result == ['pre', 'action1', 'action2', 'post'], result
        del result[:]

        def pre_err(target, source, env, result=result, **kw):
            result.append('pre_err')
            return 1

        x = SCons.Executor.Executor(a, env, [], [t], ['s1', 's2'])
        x.add_pre_action(pre_err)
        x.add_post_action(post)
        try:
            x(t)
        except SCons.Errors.BuildError:
            pass
        else:
            raise Exception("Did not catch expected BuildError")
        assert result == ['pre_err'], result
        del result[:]

    def test_cleanup(self):
        """Test cleaning up an Executor"""
        orig_env = MyEnvironment(e=1)
        x = SCons.Executor.Executor('b', orig_env, [{'o':1}],
                                    't', ['s1', 's2'])

        be = x.get_build_env()
        assert be['e'] == 1, be['e']
        
        x.cleanup()

        x.env = MyEnvironment(eee=1)
        be = x.get_build_env()
        assert be['eee'] == 1, be['eee']

        x.cleanup()

        be = x.get_build_env()
        assert be['eee'] == 1, be['eee']

    def test_add_sources(self):
        """Test adding sources to an Executor"""
        x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2'])
        sources = x.get_all_sources()
        assert sources == ['s1', 's2'], sources

        x.add_sources(['s1', 's2'])
        sources = x.get_all_sources()
        assert sources == ['s1', 's2'], sources

        x.add_sources(['s3', 's1', 's4'])
        sources = x.get_all_sources()
        assert sources == ['s1', 's2', 's3', 's4'], sources

    def test_get_sources(self):
        """Test getting sources from an Executor"""
        x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2'])
        sources = x.get_sources()
        assert sources == ['s1', 's2'], sources

        x.add_sources(['s1', 's2'])
        sources = x.get_sources()
        assert sources == ['s1', 's2'], sources

        x.add_sources(['s3', 's1', 's4'])
        sources = x.get_sources()
        assert sources == ['s1', 's2', 's3', 's4'], sources

    def test_prepare(self):
        """Test the Executor's prepare() method"""
        env = MyEnvironment()
        t1 = MyNode('t1')
        s1 = MyNode('s1')
        s2 = MyNode('s2')
        s3 = MyNode('s3')
        x = SCons.Executor.Executor('b', env, [{}], [t1], [s1, s2, s3])

        s2.missing_val = True

        try:
            r = x.prepare()
        except SCons.Errors.StopError as e:
            assert str(e) == "Source `s2' not found, needed by target `t1'.", e
        else:
            raise AssertionError("did not catch expected StopError: %s" % r)

    def test_add_pre_action(self):
        """Test adding pre-actions to an Executor"""
        x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2'])
        x.add_pre_action('a1')
        assert x.pre_actions == ['a1']
        x.add_pre_action('a2')
        assert x.pre_actions == ['a1', 'a2']

    def test_add_post_action(self):
        """Test adding post-actions to an Executor"""
        x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2'])
        x.add_post_action('a1')
        assert x.post_actions == ['a1']
        x.add_post_action('a2')
        assert x.post_actions == ['a1', 'a2']

    def test___str__(self):
        """Test the __str__() method"""
        env = MyEnvironment(S='string')

        x = SCons.Executor.Executor(MyAction(), env, [], ['t'], ['s'])
        c = str(x)
        assert c == 'GENSTRING action1 action2 t s', c

        x = SCons.Executor.Executor(MyAction(), env, [], ['t'], ['s'])
        x.add_pre_action(MyAction(['pre']))
        x.add_post_action(MyAction(['post']))
        c = str(x)
        expect = 'GENSTRING pre t s\n' + \
                 'GENSTRING action1 action2 t s\n' + \
                 'GENSTRING post t s'
        assert c == expect, c

    def test_nullify(self):
        """Test the nullify() method"""
        env = MyEnvironment(S='string')

        result = []
        def action1(target, source, env, result=result, **kw):
            result.append('action1')

        env = MyEnvironment()
        a = MyAction([action1])
        x = SCons.Executor.Executor(a, env, [], ['t1', 't2'], ['s1', 's2'])

        x(MyNode('', [], []))
        assert result == ['action1'], result
        s = str(x)
        assert s[:10] == 'GENSTRING ', s

        del result[:]
        x.nullify()

        assert result == [], result
        x(MyNode('', [], []))
        assert result == [], result
        s = str(x)
        assert s == '', s

    def test_get_contents(self):
        """Test fetching the signatures contents"""
        env = MyEnvironment(C='contents')

        x = SCons.Executor.Executor(MyAction(), env, [], ['t'], ['s'])
        c = x.get_contents()
        assert c == b'action1 action2 t s', c

        x = SCons.Executor.Executor(MyAction(actions=['grow']), env, [],
                                    ['t'], ['s'])
        x.add_pre_action(MyAction(['pre']))
        x.add_post_action(MyAction(['post']))
        c = x.get_contents()
        assert c == b'pre t sgrow t spost t s', c

    def test_get_timestamp(self):
        """Test fetching the "timestamp" """
        x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2'])
        ts = x.get_timestamp()
        assert ts == 0, ts

    def test_scan_targets(self):
        """Test scanning the targets for implicit dependencies"""
        env = MyEnvironment(S='string')
        t1 = MyNode('t1')
        t2 = MyNode('t2')
        sources = [MyNode('s1'), MyNode('s2')]
        x = SCons.Executor.Executor(MyAction(), env, [{}], [t1, t2], sources)

        deps = x.scan_targets(None)
        assert t1.implicit == ['dep-t1', 'dep-t2'], t1.implicit
        assert t2.implicit == ['dep-t1', 'dep-t2'], t2.implicit

        t1.implicit = []
        t2.implicit = []

        deps = x.scan_targets(MyScanner('scanner-'))
        assert t1.implicit == ['scanner-t1', 'scanner-t2'], t1.implicit
        assert t2.implicit == ['scanner-t1', 'scanner-t2'], t2.implicit

    def test_scan_sources(self):
        """Test scanning the sources for implicit dependencies"""
        env = MyEnvironment(S='string')
        t1 = MyNode('t1')
        t2 = MyNode('t2')
        sources = [MyNode('s1'), MyNode('s2')]
        x = SCons.Executor.Executor(MyAction(), env, [{}], [t1, t2], sources)

        deps = x.scan_sources(None)
        assert t1.implicit == ['dep-s1', 'dep-s2'], t1.implicit
        assert t2.implicit == ['dep-s1', 'dep-s2'], t2.implicit

        t1.implicit = []
        t2.implicit = []

        deps = x.scan_sources(MyScanner('scanner-'))
        assert t1.implicit == ['scanner-s1', 'scanner-s2'], t1.implicit
        assert t2.implicit == ['scanner-s1', 'scanner-s2'], t2.implicit

    def test_get_unignored_sources(self):
        """Test fetching the unignored source list"""
        env = MyEnvironment()
        s1 = MyNode('s1')
        s2 = MyNode('s2')
        s3 = MyNode('s3')
        x = SCons.Executor.Executor('b', env, [{}], [], [s1, s2, s3])

        r = x.get_unignored_sources(None, [])
        assert r == [s1, s2, s3], list(map(str, r))

        r = x.get_unignored_sources(None, [s2])
        assert r == [s1, s3], list(map(str, r))

        r = x.get_unignored_sources(None, [s1, s3])
        assert r == [s2], list(map(str, r))

    def test_changed_sources_for_alwaysBuild(self):
        """
        Ensure if a target is marked always build that the sources are always marked changed sources
        :return:
        """
        env = MyEnvironment()
        s1 = MyNode('s1')
        s2 = MyNode('s2')
        t1 = MyNode('t1')
        t1.up_to_date = True
        t1.always_build = True

        x = SCons.Executor.Executor('b', env, [{}], [t1], [s1, s2])

        changed_sources = x._get_changed_sources()
        assert changed_sources == [s1, s2], "If target marked AlwaysBuild sources should always be marked changed"




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

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:
