xref: /aosp_15_r20/external/cronet/third_party/libc++/src/test/libcxx/selftest/dsl/dsl.sh.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1# ===----------------------------------------------------------------------===##
2#
3# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4# See https://llvm.org/LICENSE.txt for license information.
5# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6#
7# ===----------------------------------------------------------------------===##
8
9# With picolibc, test_program_stderr_is_not_conflated_with_stdout fails
10# because stdout & stderr are treated as the same.
11# XFAIL: LIBCXX-PICOLIBC-FIXME
12
13# Note: We prepend arguments with 'x' to avoid thinking there are too few
14#       arguments in case an argument is an empty string.
15# RUN: %{python} %s x%S x%T x%{substitutions}
16
17import base64
18import copy
19import os
20import pickle
21import platform
22import subprocess
23import sys
24import unittest
25from os.path import dirname
26
27# Allow importing 'lit' and the 'libcxx' module. Make sure we put the lit
28# path first so we don't find any system-installed version.
29monorepoRoot = dirname(dirname(dirname(dirname(dirname(dirname(__file__))))))
30sys.path = [
31    os.path.join(monorepoRoot, "libcxx", "utils"),
32    os.path.join(monorepoRoot, "llvm", "utils", "lit"),
33] + sys.path
34import libcxx.test.dsl as dsl
35import lit.LitConfig
36import lit.util
37
38# Steal some parameters from the config running this test so that we can
39# bootstrap our own TestingConfig.
40args = list(map(lambda s: s[1:], sys.argv[1:8]))  # Remove the leading 'x'
41SOURCE_ROOT, EXEC_PATH, SUBSTITUTIONS = args
42sys.argv[1:8] = []
43
44# Decode the substitutions.
45SUBSTITUTIONS = pickle.loads(base64.b64decode(SUBSTITUTIONS))
46for s, sub in SUBSTITUTIONS:
47    print("Substitution '{}' is '{}'".format(s, sub))
48
49
50class SetupConfigs(unittest.TestCase):
51    """
52    Base class for the tests below -- it creates a fake TestingConfig.
53    """
54
55    def setUp(self):
56        """
57        Create a fake TestingConfig that can be populated however we wish for
58        the purpose of running unit tests below. We pre-populate it with the
59        minimum required substitutions.
60        """
61        self.litConfig = lit.LitConfig.LitConfig(
62            progname="lit",
63            path=[],
64            quiet=False,
65            useValgrind=False,
66            valgrindLeakCheck=False,
67            valgrindArgs=[],
68            noExecute=False,
69            debug=False,
70            isWindows=platform.system() == "Windows",
71            order="smart",
72            params={},
73        )
74
75        self.config = lit.TestingConfig.TestingConfig.fromdefaults(self.litConfig)
76        self.config.environment = dict(os.environ)
77        self.config.test_source_root = SOURCE_ROOT
78        self.config.test_exec_root = EXEC_PATH
79        self.config.recursiveExpansionLimit = 10
80        self.config.substitutions = copy.deepcopy(SUBSTITUTIONS)
81
82    def getSubstitution(self, substitution):
83        """
84        Return a given substitution from the TestingConfig. It is an error if
85        there is no such substitution.
86        """
87        found = [x for (s, x) in self.config.substitutions if s == substitution]
88        assert len(found) == 1
89        return found[0]
90
91
92def findIndex(list, pred):
93    """Finds the index of the first element satisfying 'pred' in a list, or
94    'len(list)' if there is no such element."""
95    index = 0
96    for x in list:
97        if pred(x):
98            break
99        else:
100            index += 1
101    return index
102
103
104class TestHasCompileFlag(SetupConfigs):
105    """
106    Tests for libcxx.test.dsl.hasCompileFlag
107    """
108
109    def test_no_flag_should_work(self):
110        self.assertTrue(dsl.hasCompileFlag(self.config, ""))
111
112    def test_flag_exists(self):
113        self.assertTrue(dsl.hasCompileFlag(self.config, "-O1"))
114
115    def test_nonexistent_flag(self):
116        self.assertFalse(
117            dsl.hasCompileFlag(self.config, "-this_is_not_a_flag_any_compiler_has")
118        )
119
120    def test_multiple_flags(self):
121        self.assertTrue(dsl.hasCompileFlag(self.config, "-O1 -Dhello"))
122
123
124class TestSourceBuilds(SetupConfigs):
125    """
126    Tests for libcxx.test.dsl.sourceBuilds
127    """
128
129    def test_valid_program_builds(self):
130        source = """int main(int, char**) { return 0; }"""
131        self.assertTrue(dsl.sourceBuilds(self.config, source))
132
133    def test_compilation_error_fails(self):
134        source = """int main(int, char**) { this does not compile }"""
135        self.assertFalse(dsl.sourceBuilds(self.config, source))
136
137    def test_link_error_fails(self):
138        source = """extern void this_isnt_defined_anywhere();
139                    int main(int, char**) { this_isnt_defined_anywhere(); return 0; }"""
140        self.assertFalse(dsl.sourceBuilds(self.config, source))
141
142
143class TestProgramOutput(SetupConfigs):
144    """
145    Tests for libcxx.test.dsl.programOutput
146    """
147
148    def test_valid_program_returns_output(self):
149        source = """
150        #include <cstdio>
151        int main(int, char**) { std::printf("FOOBAR"); return 0; }
152        """
153        self.assertEqual(dsl.programOutput(self.config, source), "FOOBAR")
154
155    def test_valid_program_returns_output_newline_handling(self):
156        source = """
157        #include <cstdio>
158        int main(int, char**) { std::printf("FOOBAR\\n"); return 0; }
159        """
160        self.assertEqual(dsl.programOutput(self.config, source), "FOOBAR\n")
161
162    def test_valid_program_returns_no_output(self):
163        source = """
164        int main(int, char**) { return 0; }
165        """
166        self.assertEqual(dsl.programOutput(self.config, source), "")
167
168    def test_program_that_fails_to_run_raises_runtime_error(self):
169        # The program compiles, but exits with an error
170        source = """
171        int main(int, char**) { return 1; }
172        """
173        self.assertRaises(
174            dsl.ConfigurationRuntimeError,
175            lambda: dsl.programOutput(self.config, source),
176        )
177
178    def test_program_that_fails_to_compile_raises_compilation_error(self):
179        # The program doesn't compile
180        source = """
181        int main(int, char**) { this doesnt compile }
182        """
183        self.assertRaises(
184            dsl.ConfigurationCompilationError,
185            lambda: dsl.programOutput(self.config, source),
186        )
187
188    def test_pass_arguments_to_program(self):
189        source = """
190        #include <cassert>
191        #include <string>
192        int main(int argc, char** argv) {
193            assert(argc == 3);
194            assert(argv[1] == std::string("first-argument"));
195            assert(argv[2] == std::string("second-argument"));
196            return 0;
197        }
198        """
199        args = ["first-argument", "second-argument"]
200        self.assertEqual(dsl.programOutput(self.config, source, args=args), "")
201
202    def test_caching_is_not_too_aggressive(self):
203        # Run a program, then change the substitutions and run it again.
204        # Make sure the program is run the second time and the right result
205        # is given, to ensure we're not incorrectly caching the result of the
206        # first program run.
207        source = """
208        #include <cstdio>
209        int main(int, char**) {
210            std::printf("MACRO=%u\\n", MACRO);
211            return 0;
212        }
213        """
214        compileFlagsIndex = findIndex(
215            self.config.substitutions, lambda x: x[0] == "%{compile_flags}"
216        )
217        compileFlags = self.config.substitutions[compileFlagsIndex][1]
218
219        self.config.substitutions[compileFlagsIndex] = (
220            "%{compile_flags}",
221            compileFlags + " -DMACRO=1",
222        )
223        output1 = dsl.programOutput(self.config, source)
224        self.assertEqual(output1, "MACRO=1\n")
225
226        self.config.substitutions[compileFlagsIndex] = (
227            "%{compile_flags}",
228            compileFlags + " -DMACRO=2",
229        )
230        output2 = dsl.programOutput(self.config, source)
231        self.assertEqual(output2, "MACRO=2\n")
232
233    def test_program_stderr_is_not_conflated_with_stdout(self):
234        # Run a program that produces stdout output and stderr output too, making
235        # sure the stderr output does not pollute the stdout output.
236        source = """
237        #include <cstdio>
238        int main(int, char**) {
239            std::fprintf(stdout, "STDOUT-OUTPUT");
240            std::fprintf(stderr, "STDERR-OUTPUT");
241            return 0;
242        }
243        """
244        self.assertEqual(dsl.programOutput(self.config, source), "STDOUT-OUTPUT")
245
246
247class TestProgramSucceeds(SetupConfigs):
248    """
249    Tests for libcxx.test.dsl.programSucceeds
250    """
251
252    def test_success(self):
253        source = """
254        int main(int, char**) { return 0; }
255        """
256        self.assertTrue(dsl.programSucceeds(self.config, source))
257
258    def test_failure(self):
259        source = """
260        int main(int, char**) { return 1; }
261        """
262        self.assertFalse(dsl.programSucceeds(self.config, source))
263
264    def test_compile_failure(self):
265        source = """
266        this does not compile
267        """
268        self.assertRaises(
269            dsl.ConfigurationCompilationError,
270            lambda: dsl.programSucceeds(self.config, source),
271        )
272
273
274class TestHasLocale(SetupConfigs):
275    """
276    Tests for libcxx.test.dsl.hasLocale
277    """
278
279    def test_doesnt_explode(self):
280        # It's really hard to test that a system has a given locale, so at least
281        # make sure we don't explode when we try to check it.
282        try:
283            dsl.hasAnyLocale(self.config, ["en_US.UTF-8"])
284        except subprocess.CalledProcessError:
285            self.fail("checking for hasLocale should not explode")
286
287    def test_nonexistent_locale(self):
288        self.assertFalse(
289            dsl.hasAnyLocale(self.config, ["forsurethisisnotanexistinglocale"])
290        )
291
292    def test_localization_program_doesnt_compile(self):
293        compilerIndex = findIndex(self.config.substitutions, lambda x: x[0] == "%{cxx}")
294        self.config.substitutions[compilerIndex] = (
295            "%{cxx}",
296            "this-is-certainly-not-a-valid-compiler!!",
297        )
298        self.assertRaises(
299            dsl.ConfigurationCompilationError,
300            lambda: dsl.hasAnyLocale(self.config, ["en_US.UTF-8"]),
301        )
302
303
304class TestCompilerMacros(SetupConfigs):
305    """
306    Tests for libcxx.test.dsl.compilerMacros
307    """
308
309    def test_basic(self):
310        macros = dsl.compilerMacros(self.config)
311        self.assertIsInstance(macros, dict)
312        self.assertGreater(len(macros), 0)
313        for (k, v) in macros.items():
314            self.assertIsInstance(k, str)
315            self.assertIsInstance(v, str)
316
317    def test_no_flag(self):
318        macros = dsl.compilerMacros(self.config)
319        self.assertIn("__cplusplus", macros.keys())
320
321    def test_empty_flag(self):
322        macros = dsl.compilerMacros(self.config, "")
323        self.assertIn("__cplusplus", macros.keys())
324
325    def test_with_flag(self):
326        macros = dsl.compilerMacros(self.config, "-DFOO=3")
327        self.assertIn("__cplusplus", macros.keys())
328        self.assertEqual(macros["FOO"], "3")
329
330    def test_with_flags(self):
331        macros = dsl.compilerMacros(self.config, "-DFOO=3 -DBAR=hello")
332        self.assertIn("__cplusplus", macros.keys())
333        self.assertEqual(macros["FOO"], "3")
334        self.assertEqual(macros["BAR"], "hello")
335
336
337class TestFeatureTestMacros(SetupConfigs):
338    """
339    Tests for libcxx.test.dsl.featureTestMacros
340    """
341
342    def test_basic(self):
343        macros = dsl.featureTestMacros(self.config)
344        self.assertIsInstance(macros, dict)
345        self.assertGreater(len(macros), 0)
346        for (k, v) in macros.items():
347            self.assertIsInstance(k, str)
348            self.assertIsInstance(v, int)
349
350
351class TestFeature(SetupConfigs):
352    """
353    Tests for libcxx.test.dsl.Feature
354    """
355
356    def test_trivial(self):
357        feature = dsl.Feature(name="name")
358        origSubstitutions = copy.deepcopy(self.config.substitutions)
359        actions = feature.getActions(self.config)
360        self.assertTrue(len(actions) == 1)
361        for a in actions:
362            a.applyTo(self.config)
363        self.assertEqual(origSubstitutions, self.config.substitutions)
364        self.assertIn("name", self.config.available_features)
365
366    def test_name_can_be_a_callable(self):
367        feature = dsl.Feature(name=lambda cfg: "name")
368        for a in feature.getActions(self.config):
369            a.applyTo(self.config)
370        self.assertIn("name", self.config.available_features)
371
372    def test_name_is_not_a_string_1(self):
373        feature = dsl.Feature(name=None)
374        self.assertRaises(ValueError, lambda: feature.getActions(self.config))
375        self.assertRaises(ValueError, lambda: feature.pretty(self.config))
376
377    def test_name_is_not_a_string_2(self):
378        feature = dsl.Feature(name=lambda cfg: None)
379        self.assertRaises(ValueError, lambda: feature.getActions(self.config))
380        self.assertRaises(ValueError, lambda: feature.pretty(self.config))
381
382    def test_adding_action(self):
383        feature = dsl.Feature(name="name", actions=[dsl.AddCompileFlag("-std=c++03")])
384        origLinkFlags = copy.deepcopy(self.getSubstitution("%{link_flags}"))
385        for a in feature.getActions(self.config):
386            a.applyTo(self.config)
387        self.assertIn("name", self.config.available_features)
388        self.assertIn("-std=c++03", self.getSubstitution("%{compile_flags}"))
389        self.assertEqual(origLinkFlags, self.getSubstitution("%{link_flags}"))
390
391    def test_actions_can_be_a_callable(self):
392        feature = dsl.Feature(
393            name="name",
394            actions=lambda cfg: (
395                self.assertIs(self.config, cfg),
396                [dsl.AddCompileFlag("-std=c++03")],
397            )[1],
398        )
399        for a in feature.getActions(self.config):
400            a.applyTo(self.config)
401        self.assertIn("-std=c++03", self.getSubstitution("%{compile_flags}"))
402
403    def test_unsupported_feature(self):
404        feature = dsl.Feature(name="name", when=lambda _: False)
405        self.assertEqual(feature.getActions(self.config), [])
406
407    def test_is_supported_gets_passed_the_config(self):
408        feature = dsl.Feature(
409            name="name", when=lambda cfg: (self.assertIs(self.config, cfg), True)[1]
410        )
411        self.assertEqual(len(feature.getActions(self.config)), 1)
412
413
414def _throw():
415    raise ValueError()
416
417
418class TestParameter(SetupConfigs):
419    """
420    Tests for libcxx.test.dsl.Parameter
421    """
422
423    def test_empty_name_should_blow_up(self):
424        self.assertRaises(
425            ValueError,
426            lambda: dsl.Parameter(
427                name="", choices=["c++03"], type=str, help="", actions=lambda _: []
428            ),
429        )
430
431    def test_empty_choices_should_blow_up(self):
432        self.assertRaises(
433            ValueError,
434            lambda: dsl.Parameter(
435                name="std", choices=[], type=str, help="", actions=lambda _: []
436            ),
437        )
438
439    def test_no_choices_is_ok(self):
440        param = dsl.Parameter(name="triple", type=str, help="", actions=lambda _: [])
441        self.assertEqual(param.name, "triple")
442
443    def test_name_is_set_correctly(self):
444        param = dsl.Parameter(
445            name="std", choices=["c++03"], type=str, help="", actions=lambda _: []
446        )
447        self.assertEqual(param.name, "std")
448
449    def test_no_value_provided_and_no_default_value(self):
450        param = dsl.Parameter(
451            name="std", choices=["c++03"], type=str, help="", actions=lambda _: []
452        )
453        self.assertRaises(
454            ValueError, lambda: param.getActions(self.config, self.litConfig.params)
455        )
456
457    def test_no_value_provided_and_default_value(self):
458        param = dsl.Parameter(
459            name="std",
460            choices=["c++03"],
461            type=str,
462            help="",
463            default="c++03",
464            actions=lambda std: [dsl.AddFeature(std)],
465        )
466        for a in param.getActions(self.config, self.litConfig.params):
467            a.applyTo(self.config)
468        self.assertIn("c++03", self.config.available_features)
469
470    def test_value_provided_on_command_line_and_no_default_value(self):
471        self.litConfig.params["std"] = "c++03"
472        param = dsl.Parameter(
473            name="std",
474            choices=["c++03"],
475            type=str,
476            help="",
477            actions=lambda std: [dsl.AddFeature(std)],
478        )
479        for a in param.getActions(self.config, self.litConfig.params):
480            a.applyTo(self.config)
481        self.assertIn("c++03", self.config.available_features)
482
483    def test_value_provided_on_command_line_and_default_value(self):
484        """The value provided on the command line should override the default value"""
485        self.litConfig.params["std"] = "c++11"
486        param = dsl.Parameter(
487            name="std",
488            choices=["c++03", "c++11"],
489            type=str,
490            default="c++03",
491            help="",
492            actions=lambda std: [dsl.AddFeature(std)],
493        )
494        for a in param.getActions(self.config, self.litConfig.params):
495            a.applyTo(self.config)
496        self.assertIn("c++11", self.config.available_features)
497        self.assertNotIn("c++03", self.config.available_features)
498
499    def test_value_provided_in_config_and_default_value(self):
500        """The value provided in the config should override the default value"""
501        self.config.std = "c++11"
502        param = dsl.Parameter(
503            name="std",
504            choices=["c++03", "c++11"],
505            type=str,
506            default="c++03",
507            help="",
508            actions=lambda std: [dsl.AddFeature(std)],
509        )
510        for a in param.getActions(self.config, self.litConfig.params):
511            a.applyTo(self.config)
512        self.assertIn("c++11", self.config.available_features)
513        self.assertNotIn("c++03", self.config.available_features)
514
515    def test_value_provided_in_config_and_on_command_line(self):
516        """The value on the command line should override the one in the config"""
517        self.config.std = "c++11"
518        self.litConfig.params["std"] = "c++03"
519        param = dsl.Parameter(
520            name="std",
521            choices=["c++03", "c++11"],
522            type=str,
523            help="",
524            actions=lambda std: [dsl.AddFeature(std)],
525        )
526        for a in param.getActions(self.config, self.litConfig.params):
527            a.applyTo(self.config)
528        self.assertIn("c++03", self.config.available_features)
529        self.assertNotIn("c++11", self.config.available_features)
530
531    def test_no_actions(self):
532        self.litConfig.params["std"] = "c++03"
533        param = dsl.Parameter(
534            name="std", choices=["c++03"], type=str, help="", actions=lambda _: []
535        )
536        actions = param.getActions(self.config, self.litConfig.params)
537        self.assertEqual(actions, [])
538
539    def test_boolean_value_parsed_from_trueish_string_parameter(self):
540        self.litConfig.params["enable_exceptions"] = "True"
541        param = dsl.Parameter(
542            name="enable_exceptions",
543            choices=[True, False],
544            type=bool,
545            help="",
546            actions=lambda exceptions: [] if exceptions else _throw(),
547        )
548        self.assertEqual(param.getActions(self.config, self.litConfig.params), [])
549
550    def test_boolean_value_from_true_boolean_parameter(self):
551        self.litConfig.params["enable_exceptions"] = True
552        param = dsl.Parameter(
553            name="enable_exceptions",
554            choices=[True, False],
555            type=bool,
556            help="",
557            actions=lambda exceptions: [] if exceptions else _throw(),
558        )
559        self.assertEqual(param.getActions(self.config, self.litConfig.params), [])
560
561    def test_boolean_value_parsed_from_falseish_string_parameter(self):
562        self.litConfig.params["enable_exceptions"] = "False"
563        param = dsl.Parameter(
564            name="enable_exceptions",
565            choices=[True, False],
566            type=bool,
567            help="",
568            actions=lambda exceptions: []
569            if exceptions
570            else [dsl.AddFeature("-fno-exceptions")],
571        )
572        for a in param.getActions(self.config, self.litConfig.params):
573            a.applyTo(self.config)
574        self.assertIn("-fno-exceptions", self.config.available_features)
575
576    def test_boolean_value_from_false_boolean_parameter(self):
577        self.litConfig.params["enable_exceptions"] = False
578        param = dsl.Parameter(
579            name="enable_exceptions",
580            choices=[True, False],
581            type=bool,
582            help="",
583            actions=lambda exceptions: []
584            if exceptions
585            else [dsl.AddFeature("-fno-exceptions")],
586        )
587        for a in param.getActions(self.config, self.litConfig.params):
588            a.applyTo(self.config)
589        self.assertIn("-fno-exceptions", self.config.available_features)
590
591    def test_list_parsed_from_comma_delimited_string_empty(self):
592        self.litConfig.params["additional_features"] = ""
593        param = dsl.Parameter(
594            name="additional_features", type=list, help="", actions=lambda f: f
595        )
596        self.assertEqual(param.getActions(self.config, self.litConfig.params), [])
597
598    def test_list_parsed_from_comma_delimited_string_1(self):
599        self.litConfig.params["additional_features"] = "feature1"
600        param = dsl.Parameter(
601            name="additional_features", type=list, help="", actions=lambda f: f
602        )
603        self.assertEqual(
604            param.getActions(self.config, self.litConfig.params), ["feature1"]
605        )
606
607    def test_list_parsed_from_comma_delimited_string_2(self):
608        self.litConfig.params["additional_features"] = "feature1,feature2"
609        param = dsl.Parameter(
610            name="additional_features", type=list, help="", actions=lambda f: f
611        )
612        self.assertEqual(
613            param.getActions(self.config, self.litConfig.params),
614            ["feature1", "feature2"],
615        )
616
617    def test_list_parsed_from_comma_delimited_string_3(self):
618        self.litConfig.params["additional_features"] = "feature1,feature2, feature3"
619        param = dsl.Parameter(
620            name="additional_features", type=list, help="", actions=lambda f: f
621        )
622        self.assertEqual(
623            param.getActions(self.config, self.litConfig.params),
624            ["feature1", "feature2", "feature3"],
625        )
626
627
628if __name__ == "__main__":
629    unittest.main(verbosity=2)
630