1import unittest
2from os import sys, path
3
4is_standalone = __name__ == '__main__' and __package__ is None
5if is_standalone:
6    sys.path.append(path.abspath(path.join(path.dirname(__file__), "..")))
7    from ccompiler_opt import CCompilerOpt
8else:
9    from numpy.distutils.ccompiler_opt import CCompilerOpt
10
11arch_compilers = dict(
12    x86 = ("gcc", "clang", "icc", "iccw", "msvc"),
13    x64 = ("gcc", "clang", "icc", "iccw", "msvc"),
14    ppc64 = ("gcc", "clang"),
15    ppc64le = ("gcc", "clang"),
16    armhf = ("gcc", "clang"),
17    aarch64 = ("gcc", "clang"),
18    narch = ("gcc",)
19)
20
21class FakeCCompilerOpt(CCompilerOpt):
22    fake_info = ("arch", "compiler", "extra_args")
23    def __init__(self, *args, **kwargs):
24        CCompilerOpt.__init__(self, None, **kwargs)
25    def dist_compile(self, sources, flags, **kwargs):
26        return sources
27    def dist_info(self):
28        return FakeCCompilerOpt.fake_info
29    @staticmethod
30    def dist_log(*args, stderr=False):
31        pass
32
33class _TestConfFeatures(FakeCCompilerOpt):
34    """A hook to check the sanity of configured features
35-   before it called by the abstract class '_Feature'
36    """
37
38    def conf_features_partial(self):
39        conf_all = self.conf_features
40        for feature_name, feature in conf_all.items():
41            self.test_feature(
42                "attribute conf_features",
43                conf_all, feature_name, feature
44            )
45
46        conf_partial = FakeCCompilerOpt.conf_features_partial(self)
47        for feature_name, feature in conf_partial.items():
48            self.test_feature(
49                "conf_features_partial()",
50                conf_partial, feature_name, feature
51            )
52        return conf_partial
53
54    def test_feature(self, log, search_in, feature_name, feature_dict):
55        error_msg = (
56            "during validate '{}' within feature '{}', "
57            "march '{}' and compiler '{}'\n>> "
58        ).format(log, feature_name, self.cc_march, self.cc_name)
59
60        if not feature_name.isupper():
61            raise AssertionError(error_msg + "feature name must be in uppercase")
62
63        for option, val in feature_dict.items():
64            self.test_option_types(error_msg, option, val)
65            self.test_duplicates(error_msg, option, val)
66
67        self.test_implies(error_msg, search_in, feature_name, feature_dict)
68        self.test_group(error_msg, search_in, feature_name, feature_dict)
69        self.test_extra_checks(error_msg, search_in, feature_name, feature_dict)
70
71    def test_option_types(self, error_msg, option, val):
72        for tp, available in (
73            ((str, list), (
74                "implies", "headers", "flags", "group", "detect", "extra_checks"
75            )),
76            ((str,),  ("disable",)),
77            ((int,),  ("interest",)),
78            ((bool,), ("implies_detect",)),
79            ((bool, type(None)), ("autovec",)),
80        ) :
81            found_it = option in available
82            if not found_it:
83                continue
84            if not isinstance(val, tp):
85                error_tp = [t.__name__ for t in (*tp,)]
86                error_tp = ' or '.join(error_tp)
87                raise AssertionError(error_msg +
88                    "expected '%s' type for option '%s' not '%s'" % (
89                     error_tp, option, type(val).__name__
90                ))
91            break
92
93        if not found_it:
94            raise AssertionError(error_msg + "invalid option name '%s'" % option)
95
96    def test_duplicates(self, error_msg, option, val):
97        if option not in (
98            "implies", "headers", "flags", "group", "detect", "extra_checks"
99        ) : return
100
101        if isinstance(val, str):
102            val = val.split()
103
104        if len(val) != len(set(val)):
105            raise AssertionError(error_msg + "duplicated values in option '%s'" % option)
106
107    def test_implies(self, error_msg, search_in, feature_name, feature_dict):
108        if feature_dict.get("disabled") is not None:
109            return
110        implies = feature_dict.get("implies", "")
111        if not implies:
112            return
113        if isinstance(implies, str):
114            implies = implies.split()
115
116        if feature_name in implies:
117            raise AssertionError(error_msg + "feature implies itself")
118
119        for impl in implies:
120            impl_dict = search_in.get(impl)
121            if impl_dict is not None:
122                if "disable" in impl_dict:
123                    raise AssertionError(error_msg + "implies disabled feature '%s'" % impl)
124                continue
125            raise AssertionError(error_msg + "implies non-exist feature '%s'" % impl)
126
127    def test_group(self, error_msg, search_in, feature_name, feature_dict):
128        if feature_dict.get("disabled") is not None:
129            return
130        group = feature_dict.get("group", "")
131        if not group:
132            return
133        if isinstance(group, str):
134            group = group.split()
135
136        for f in group:
137            impl_dict = search_in.get(f)
138            if not impl_dict or "disable" in impl_dict:
139                continue
140            raise AssertionError(error_msg +
141                "in option 'group', '%s' already exists as a feature name" % f
142            )
143
144    def test_extra_checks(self, error_msg, search_in, feature_name, feature_dict):
145        if feature_dict.get("disabled") is not None:
146            return
147        extra_checks = feature_dict.get("extra_checks", "")
148        if not extra_checks:
149            return
150        if isinstance(extra_checks, str):
151            extra_checks = extra_checks.split()
152
153        for f in extra_checks:
154            impl_dict = search_in.get(f)
155            if not impl_dict or "disable" in impl_dict:
156                continue
157            raise AssertionError(error_msg +
158                "in option 'extra_checks', extra test case '%s' already exists as a feature name" % f
159            )
160
161class TestConfFeatures(unittest.TestCase):
162    def __init__(self, methodName="runTest"):
163        unittest.TestCase.__init__(self, methodName)
164        self.setup()
165
166    def setup(self):
167        FakeCCompilerOpt.conf_nocache = True
168
169    def test_features(self):
170        for arch, compilers in arch_compilers.items():
171            for cc in compilers:
172                FakeCCompilerOpt.fake_info = (arch, cc, "")
173                _TestConfFeatures()
174
175if is_standalone:
176    unittest.main()
177