• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#  Copyright 2016 Google Inc. All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS-IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import os
17import tempfile
18import unittest
19import textwrap
20import re
21import sys
22import shlex
23
24import itertools
25
26import subprocess
27
28from absl.testing import parameterized
29
30from fruit_test_config import *
31
32from absl.testing import absltest
33
34run_under_valgrind = RUN_TESTS_UNDER_VALGRIND.lower() not in ('false', 'off', 'no', '0', '')
35
36def pretty_print_command(command, env):
37    return 'cd %s; env -i %s %s' % (
38        shlex.quote(env['PWD']),
39        ' '.join('%s=%s' % (var_name, shlex.quote(value)) for var_name, value in env.items() if var_name != 'PWD'),
40        ' '.join(shlex.quote(x) for x in command))
41
42def multiple_parameters(*param_lists):
43    param_lists = [[params if isinstance(params, tuple) else (params,)
44                    for params in param_list]
45                   for param_list in param_lists]
46    result = param_lists[0]
47    for param_list in param_lists[1:]:
48        result = [(*args1, *args2)
49                  for args1 in result
50                  for args2 in param_list]
51    return parameterized.parameters(*result)
52
53def multiple_named_parameters(*param_lists):
54    result = param_lists[0]
55    for param_list in param_lists[1:]:
56        result = [(name1 + ', ' + name2, *args1, *args2)
57                  for name1, *args1 in result
58                  for name2, *args2 in param_list]
59    return parameterized.named_parameters(*result)
60
61class CommandFailedException(Exception):
62    def __init__(self, command, env, stdout, stderr, error_code):
63        self.command = command
64        self.env = env
65        self.stdout = stdout
66        self.stderr = stderr
67        self.error_code = error_code
68
69    def __str__(self):
70        return textwrap.dedent('''\
71        Ran command: {command}
72        Exit code {error_code}
73        Stdout:
74        {stdout}
75
76        Stderr:
77        {stderr}
78        ''').format(command=pretty_print_command(self.command, self.env), error_code=self.error_code, stdout=self.stdout, stderr=self.stderr)
79
80def run_command(executable, args=[], modify_env=lambda env: env):
81    command = [executable] + args
82    modified_env = modify_env(os.environ)
83    print('Executing command:', pretty_print_command(command, modified_env))
84    try:
85        p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env=modified_env)
86        (stdout, stderr) = p.communicate()
87    except Exception as e:
88        raise Exception("While executing: %s" % command)
89    if p.returncode != 0:
90        raise CommandFailedException(command, modified_env, stdout, stderr, p.returncode)
91    print('Execution successful.')
92    print('stdout:')
93    print(stdout)
94    print('')
95    print('stderr:')
96    print(stderr)
97    print('')
98    return (stdout, stderr)
99
100def run_compiled_executable(executable):
101    if run_under_valgrind:
102        args = VALGRIND_FLAGS.split() + [executable]
103        run_command('valgrind', args = args, modify_env = modify_env_for_compiled_executables)
104    else:
105        run_command(executable, modify_env = modify_env_for_compiled_executables)
106
107class CompilationFailedException(Exception):
108    def __init__(self, command, env, error_message):
109        self.command = command
110        self.env = env
111        self.error_message = error_message
112
113    def __str__(self):
114        return textwrap.dedent('''\
115        Ran command: {command}
116        Error message:
117        {error_message}
118        ''').format(command=pretty_print_command(self.command, self.env), error_message=self.error_message)
119
120class PosixCompiler:
121    def __init__(self):
122        self.executable = CXX
123        self.name = CXX_COMPILER_NAME
124
125    def compile_discarding_output(self, source, include_dirs, args=[]):
126        try:
127            args = args + ['-c', source, '-o', os.path.devnull]
128            self._compile(include_dirs, args=args)
129        except CommandFailedException as e:
130            raise CompilationFailedException(e.command, e.env, e.stderr)
131
132    def compile_and_link(self, source, include_dirs, output_file_name, args=[]):
133        self._compile(
134            include_dirs,
135            args = (
136                [source]
137                + ADDITIONAL_LINKER_FLAGS.split()
138                + args
139                + ['-o', output_file_name]
140            ))
141
142    def _compile(self, include_dirs, args):
143        include_flags = ['-I%s' % include_dir for include_dir in include_dirs]
144        args = (
145            FRUIT_COMPILE_FLAGS.split()
146            + include_flags
147            + ['-g0', '-Werror']
148            + args
149        )
150        run_command(self.executable, args)
151
152    def get_disable_deprecation_warning_flags(self):
153        return ['-Wno-deprecated-declarations']
154
155    def get_disable_all_warnings_flags(self):
156        return ['-Wno-error']
157
158class MsvcCompiler:
159    def __init__(self):
160        self.executable = CXX
161        self.name = CXX_COMPILER_NAME
162
163    def compile_discarding_output(self, source, include_dirs, args=[]):
164        try:
165            args = args + ['/c', source]
166            self._compile(include_dirs, args = args)
167        except CommandFailedException as e:
168            # Note that we use stdout here, unlike above. MSVC reports compilation warnings and errors on stdout.
169            raise CompilationFailedException(e.command, e.stdout)
170
171    def compile_and_link(self, source, include_dirs, output_file_name, args=[]):
172        self._compile(
173            include_dirs,
174            args = (
175                [source]
176                + ADDITIONAL_LINKER_FLAGS.split()
177                + args
178                + ['/Fe' + output_file_name]
179            ))
180
181    def _compile(self, include_dirs, args):
182        include_flags = ['-I%s' % include_dir for include_dir in include_dirs]
183        args = (
184            FRUIT_COMPILE_FLAGS.split()
185            + include_flags
186            + ['/WX']
187            + args
188        )
189        run_command(self.executable, args)
190
191    def get_disable_deprecation_warning_flags(self):
192        return ['/wd4996']
193
194    def get_disable_all_warnings_flags(self):
195        return ['/WX:NO']
196
197if CXX_COMPILER_NAME == 'MSVC':
198    compiler = MsvcCompiler()
199    if PATH_TO_COMPILED_FRUIT_LIB.endswith('.dll'):
200        path_to_fruit_lib = PATH_TO_COMPILED_FRUIT_LIB[:-4] + '.lib'
201    else:
202        path_to_fruit_lib = PATH_TO_COMPILED_FRUIT_LIB
203    fruit_tests_linker_flags = [path_to_fruit_lib]
204    fruit_error_message_extraction_regex = 'error C2338: (.*)'
205else:
206    compiler = PosixCompiler()
207    fruit_tests_linker_flags = [
208        '-lfruit',
209        '-L' + PATH_TO_COMPILED_FRUIT,
210        '-Wl,-rpath,' + PATH_TO_COMPILED_FRUIT,
211    ]
212    fruit_error_message_extraction_regex = 'static.assert(.*)'
213
214fruit_tests_include_dirs = ADDITIONAL_INCLUDE_DIRS.splitlines() + [
215    PATH_TO_FRUIT_TEST_HEADERS,
216    PATH_TO_FRUIT_STATIC_HEADERS,
217    PATH_TO_FRUIT_GENERATED_HEADERS,
218]
219
220_assert_helper = unittest.TestCase()
221
222def modify_env_for_compiled_executables(env):
223    env = env.copy()
224    path_to_fruit_lib_dir = os.path.dirname(PATH_TO_COMPILED_FRUIT_LIB)
225    print('PATH_TO_COMPILED_FRUIT_LIB:', PATH_TO_COMPILED_FRUIT_LIB)
226    print('Adding directory to PATH:', path_to_fruit_lib_dir)
227    env["PATH"] += os.pathsep + path_to_fruit_lib_dir
228    return env
229
230def _create_temporary_file(file_content, file_name_suffix=''):
231    file_descriptor, file_name = tempfile.mkstemp(text=True, suffix=file_name_suffix)
232    file = os.fdopen(file_descriptor, mode='w')
233    file.write(file_content)
234    file.close()
235    return file_name
236
237def _cap_to_lines(s, n):
238    lines = s.splitlines()
239    if len(lines) <= n:
240        return s
241    else:
242        return '\n'.join(lines[0:n] + ['...'])
243
244def _replace_using_test_params(s, test_params):
245    for var_name, value in test_params.items():
246        if isinstance(value, str):
247            s = re.sub(r'\b%s\b' % var_name, value, s)
248    return s
249
250def _construct_final_source_code(setup_source_code, source_code, test_params):
251    setup_source_code = textwrap.dedent(setup_source_code)
252    source_code = textwrap.dedent(source_code)
253    source_code = _replace_using_test_params(source_code, test_params)
254    return setup_source_code + source_code
255
256def try_remove_temporary_file(filename):
257    try:
258        os.remove(filename)
259    except:
260        # When running Fruit tests on Windows using Appveyor, the remove command fails for temporary files sometimes.
261        # This shouldn't cause the tests to fail, so we ignore the exception and go ahead.
262        pass
263
264def normalize_error_message_lines(lines):
265    # Different compilers output a different number of spaces when pretty-printing types.
266    # When using libc++, sometimes std::foo identifiers are reported as std::__1::foo.
267    return [line.replace(' ', '').replace('std::__1::', 'std::') for line in lines]
268
269def expect_compile_error_helper(
270        check_error_fun,
271        setup_source_code,
272        source_code,
273        test_params={},
274        ignore_deprecation_warnings=False,
275        ignore_warnings=False):
276    source_code = _construct_final_source_code(setup_source_code, source_code, test_params)
277
278    source_file_name = _create_temporary_file(source_code, file_name_suffix='.cpp')
279
280    try:
281        args = []
282        if ignore_deprecation_warnings:
283            args += compiler.get_disable_deprecation_warning_flags()
284        if ignore_warnings:
285            args += compiler.get_disable_all_warnings_flags()
286        if ENABLE_COVERAGE:
287            # When collecting coverage these arguments are enabled by default; however we must disable them in tests
288            # expected to fail at compile-time because GCC would otherwise fail with an error like:
289            # /tmp/tmp4m22cey7.cpp:1:0: error: cannot open /dev/null.gcno
290            args += ['-fno-profile-arcs', '-fno-test-coverage']
291        compiler.compile_discarding_output(
292            source=source_file_name,
293            include_dirs=fruit_tests_include_dirs,
294            args=args)
295        raise Exception('The test should have failed to compile, but it compiled successfully')
296    except CompilationFailedException as e1:
297        e = e1
298
299    error_message = e.error_message
300    error_message_lines = error_message.splitlines()
301    error_message_lines = error_message.splitlines()
302    error_message_head = _cap_to_lines(error_message, 40)
303
304    check_error_fun(e, error_message_lines, error_message_head)
305
306    try_remove_temporary_file(source_file_name)
307
308def apply_any_error_context_replacements(error_string, following_lines):
309    if CXX_COMPILER_NAME == 'MSVC':
310        # MSVC errors are of the form:
311        #
312        # C:\Path\To\header\foo.h(59): note: see reference to class template instantiation 'fruit::impl::NoBindingFoundError<fruit::Annotated<Annotation,U>>' being compiled
313        #         with
314        #         [
315        #              Annotation=Annotation1,
316        #              U=std::function<std::unique_ptr<ScalerImpl,std::default_delete<ScalerImpl>> (double)>
317        #         ]
318        #
319        # So we need to parse the following few lines and use them to replace the placeholder types in the Fruit error type.
320        replacement_lines = []
321        if len(following_lines) >= 4 and following_lines[0].strip() == 'with':
322            assert following_lines[1].strip() == '[', 'Line was: ' + following_lines[1]
323            for line in itertools.islice(following_lines, 2, None):
324                line = line.strip()
325                if line == ']':
326                    break
327                if line.endswith(','):
328                    line = line[:-1]
329                replacement_lines.append(line)
330
331        for replacement_line in replacement_lines:
332            match = re.search('([A-Za-z0-9_-]*)=(.*)', replacement_line)
333            if not match:
334                raise Exception('Failed to parse replacement line: %s' % replacement_line)
335            (type_variable, type_expression) = match.groups()
336            error_string = re.sub(r'\b' + type_variable + r'\b', type_expression, error_string)
337    return error_string
338
339def expect_generic_compile_error(expected_error_regex, setup_source_code, source_code, test_params={}):
340    """
341    Tests that the given source produces the expected error during compilation.
342
343    :param expected_fruit_error_regex: A regex used to match the Fruit error type,
344           e.g. 'NoBindingFoundForAbstractClassError<ScalerImpl>'.
345           Any identifiers contained in the regex will be replaced using test_params (where a replacement is defined).
346    :param expected_fruit_error_desc_regex: A regex used to match the Fruit error description,
347           e.g. 'No explicit binding was found for C, and C is an abstract class'.
348    :param setup_source_code: The first part of the source code. This is dedented separately from source_code and it's
349           *not* subject to test_params, unlike source_code.
350    :param source_code: The second part of the source code. Any identifiers will be replaced using test_params
351           (where a replacement is defined). This will be dedented.
352    :param test_params: A dict containing the definition of some identifiers. Each identifier in
353           expected_fruit_error_regex and source_code will be replaced (textually) with its definition (if a definition
354           was provided).
355    """
356
357    expected_error_regex = _replace_using_test_params(expected_error_regex, test_params)
358    expected_error_regex = expected_error_regex.replace(' ', '')
359
360    def check_error(e, error_message_lines, error_message_head):
361        error_message_lines_with_replacements = [
362            apply_any_error_context_replacements(line, error_message_lines[line_number + 1:])
363            for line_number, line in enumerate(error_message_lines)]
364
365        normalized_error_message_lines = normalize_error_message_lines(error_message_lines_with_replacements)
366
367        for line in normalized_error_message_lines:
368            if re.search(expected_error_regex, line):
369                return
370        raise Exception(textwrap.dedent('''\
371            Expected error {expected_error} but the compiler output did not contain that.
372            Compiler command line: {compiler_command}
373            Error message was:
374            {error_message}
375            ''').format(expected_error = expected_error_regex, compiler_command=e.command, error_message = error_message_head))
376
377    expect_compile_error_helper(check_error, setup_source_code, source_code, test_params)
378
379def expect_compile_error(
380        expected_fruit_error_regex,
381        expected_fruit_error_desc_regex,
382        setup_source_code,
383        source_code,
384        test_params={},
385        ignore_deprecation_warnings=False,
386        ignore_warnings=False,
387        disable_error_line_number_check=False):
388    """
389    Tests that the given source produces the expected error during compilation.
390
391    :param expected_fruit_error_regex: A regex used to match the Fruit error type,
392           e.g. 'NoBindingFoundForAbstractClassError<ScalerImpl>'.
393           Any identifiers contained in the regex will be replaced using test_params (where a replacement is defined).
394    :param expected_fruit_error_desc_regex: A regex used to match the Fruit error description,
395           e.g. 'No explicit binding was found for C, and C is an abstract class'.
396    :param setup_source_code: The first part of the source code. This is dedented separately from source_code and it's
397           *not* subject to test_params, unlike source_code.
398    :param source_code: The second part of the source code. Any identifiers will be replaced using test_params
399           (where a replacement is defined). This will be dedented.
400    :param test_params: A dict containing the definition of some identifiers. Each identifier in
401           expected_fruit_error_regex and source_code will be replaced (textually) with its definition (if a definition
402           was provided).
403    :param ignore_deprecation_warnings: A boolean. If True, deprecation warnings will be ignored.
404    :param ignore_warnings: A boolean. If True, all warnings will be ignored.
405    :param disable_error_line_number_check: A boolean. If True, the test will not fail if there are other diagnostic
406           lines before the expected error.
407    """
408    if '\n' in expected_fruit_error_regex:
409        raise Exception('expected_fruit_error_regex should not contain newlines')
410    if '\n' in expected_fruit_error_desc_regex:
411        raise Exception('expected_fruit_error_desc_regex should not contain newlines')
412
413    expected_fruit_error_regex = _replace_using_test_params(expected_fruit_error_regex, test_params)
414    expected_fruit_error_regex = expected_fruit_error_regex.replace(' ', '')
415
416    def check_error(e, error_message_lines, error_message_head):
417        normalized_error_message_lines = normalize_error_message_lines(error_message_lines)
418
419        for line_number, line in enumerate(normalized_error_message_lines):
420            match = re.search('fruit::impl::(.*Error<.*>)', line)
421            if match:
422                actual_fruit_error_line_number = line_number
423                actual_fruit_error = match.groups()[0]
424                actual_fruit_error = apply_any_error_context_replacements(actual_fruit_error, normalized_error_message_lines[line_number + 1:])
425                break
426        else:
427            raise Exception(textwrap.dedent('''\
428                Expected error {expected_error} but the compiler output did not contain user-facing Fruit errors.
429                Compiler command line: {compiler_command}
430                Error message was:
431                {error_message}
432                ''').format(expected_error = expected_fruit_error_regex, compiler_command = e.command, error_message = error_message_head))
433
434        for line_number, line in enumerate(error_message_lines):
435            match = re.search(fruit_error_message_extraction_regex, line)
436            if match:
437                actual_static_assert_error_line_number = line_number
438                actual_static_assert_error = match.groups()[0]
439                break
440        else:
441            raise Exception(textwrap.dedent('''\
442                Expected error {expected_error} but the compiler output did not contain static_assert errors.
443                Compiler command line: {compiler_command}
444                Error message was:
445                {error_message}
446                ''').format(expected_error = expected_fruit_error_regex, compiler_command=e.command, error_message = error_message_head))
447
448        try:
449            regex_search_result = re.search(expected_fruit_error_regex, actual_fruit_error)
450        except Exception as e:
451            raise Exception('re.search() failed for regex \'%s\'' % expected_fruit_error_regex) from e
452        if not regex_search_result:
453            raise Exception(textwrap.dedent('''\
454                The compilation failed as expected, but with a different error type.
455                Expected Fruit error type:    {expected_fruit_error_regex}
456                Error type was:               {actual_fruit_error}
457                Expected static assert error: {expected_fruit_error_desc_regex}
458                Static assert was:            {actual_static_assert_error}
459                Error message was:
460                {error_message}
461                '''.format(
462                expected_fruit_error_regex = expected_fruit_error_regex,
463                actual_fruit_error = actual_fruit_error,
464                expected_fruit_error_desc_regex = expected_fruit_error_desc_regex,
465                actual_static_assert_error = actual_static_assert_error,
466                error_message = error_message_head)))
467        try:
468            regex_search_result = re.search(expected_fruit_error_desc_regex, actual_static_assert_error)
469        except Exception as e:
470            raise Exception('re.search() failed for regex \'%s\'' % expected_fruit_error_desc_regex) from e
471        if not regex_search_result:
472            raise Exception(textwrap.dedent('''\
473                The compilation failed as expected, but with a different error message.
474                Expected Fruit error type:    {expected_fruit_error_regex}
475                Error type was:               {actual_fruit_error}
476                Expected static assert error: {expected_fruit_error_desc_regex}
477                Static assert was:            {actual_static_assert_error}
478                Error message:
479                {error_message}
480                '''.format(
481                expected_fruit_error_regex = expected_fruit_error_regex,
482                actual_fruit_error = actual_fruit_error,
483                expected_fruit_error_desc_regex = expected_fruit_error_desc_regex,
484                actual_static_assert_error = actual_static_assert_error,
485                error_message = error_message_head)))
486
487        # 6 is just a constant that works for both g++ (<=6.0.0 at least) and clang++ (<=4.0.0 at least).
488        # It might need to be changed.
489        if not disable_error_line_number_check and (actual_fruit_error_line_number > 6 or actual_static_assert_error_line_number > 6):
490            raise Exception(textwrap.dedent('''\
491                The compilation failed with the expected message, but the error message contained too many lines before the relevant ones.
492                The error type was reported on line {actual_fruit_error_line_number} of the message (should be <=6).
493                The static assert was reported on line {actual_static_assert_error_line_number} of the message (should be <=6).
494                Error message:
495                {error_message}
496                '''.format(
497                actual_fruit_error_line_number = actual_fruit_error_line_number,
498                actual_static_assert_error_line_number = actual_static_assert_error_line_number,
499                error_message = error_message_head)))
500
501        for line in error_message_lines[:max(actual_fruit_error_line_number, actual_static_assert_error_line_number)]:
502            if re.search('fruit::impl::meta', line):
503                raise Exception(
504                    'The compilation failed with the expected message, but the error message contained some metaprogramming types in the output (besides Error). Error message:\n%s' + error_message_head)
505
506    expect_compile_error_helper(check_error, setup_source_code, source_code, test_params, ignore_deprecation_warnings, ignore_warnings)
507
508
509def expect_runtime_error(
510        expected_error_regex,
511        setup_source_code,
512        source_code,
513        test_params={},
514        ignore_deprecation_warnings=False):
515    """
516    Tests that the given source (compiles successfully and) produces the expected error at runtime.
517
518    :param expected_error_regex: A regex used to match the content of stderr.
519           Any identifiers contained in the regex will be replaced using test_params (where a replacement is defined).
520    :param setup_source_code: The first part of the source code. This is dedented separately from source_code and it's
521           *not* subject to test_params, unlike source_code.
522    :param source_code: The second part of the source code. Any identifiers will be replaced using test_params
523           (where a replacement is defined). This will be dedented.
524    :param test_params: A dict containing the definition of some identifiers. Each identifier in
525           expected_error_regex and source_code will be replaced (textually) with its definition (if a definition
526           was provided).
527    """
528    expected_error_regex = _replace_using_test_params(expected_error_regex, test_params)
529    source_code = _construct_final_source_code(setup_source_code, source_code, test_params)
530
531    source_file_name = _create_temporary_file(source_code, file_name_suffix='.cpp')
532    executable_suffix = {'posix': '', 'nt': '.exe'}[os.name]
533    output_file_name = _create_temporary_file('', executable_suffix)
534
535    args = fruit_tests_linker_flags.copy()
536    if ignore_deprecation_warnings:
537        args += compiler.get_disable_deprecation_warning_flags()
538    compiler.compile_and_link(
539        source=source_file_name,
540        include_dirs=fruit_tests_include_dirs,
541        output_file_name=output_file_name,
542        args=args)
543
544    try:
545        run_compiled_executable(output_file_name)
546        raise Exception('The test should have failed at runtime, but it ran successfully')
547    except CommandFailedException as e1:
548        e = e1
549
550    stderr = e.stderr
551    stderr_head = _cap_to_lines(stderr, 40)
552
553    if '\n' in expected_error_regex:
554        regex_flags = re.MULTILINE
555    else:
556        regex_flags = 0
557
558    try:
559        regex_search_result = re.search(expected_error_regex, stderr, flags=regex_flags)
560    except Exception as e:
561        raise Exception('re.search() failed for regex \'%s\'' % expected_error_regex) from e
562    if not regex_search_result:
563        raise Exception(textwrap.dedent('''\
564            The test failed as expected, but with a different message.
565            Expected: {expected_error_regex}
566            Was:
567            {stderr}
568            '''.format(expected_error_regex = expected_error_regex, stderr = stderr_head)))
569
570    # Note that we don't delete the temporary files if the test failed. This is intentional, keeping them around helps debugging the failure.
571    if not ENABLE_COVERAGE:
572        try_remove_temporary_file(source_file_name)
573        try_remove_temporary_file(output_file_name)
574
575
576def expect_success(setup_source_code, source_code, test_params={}, ignore_deprecation_warnings=False):
577    """
578    Tests that the given source compiles and runs successfully.
579
580    :param setup_source_code: The first part of the source code. This is dedented separately from source_code and it's
581           *not* subject to test_params, unlike source_code.
582    :param source_code: The second part of the source code. Any identifiers will be replaced using test_params
583           (where a replacement is defined). This will be dedented.
584    :param test_params: A dict containing the definition of some identifiers. Each identifier in
585           source_code will be replaced (textually) with its definition (if a definition was provided).
586    """
587    source_code = _construct_final_source_code(setup_source_code, source_code, test_params)
588
589    if 'main(' not in source_code:
590        source_code += textwrap.dedent('''
591            int main() {
592            }
593            ''')
594
595    source_file_name = _create_temporary_file(source_code, file_name_suffix='.cpp')
596    executable_suffix = {'posix': '', 'nt': '.exe'}[os.name]
597    output_file_name = _create_temporary_file('', executable_suffix)
598
599    args = fruit_tests_linker_flags.copy()
600    if ignore_deprecation_warnings:
601        args += compiler.get_disable_deprecation_warning_flags()
602    compiler.compile_and_link(
603        source=source_file_name,
604        include_dirs=fruit_tests_include_dirs,
605        output_file_name=output_file_name,
606        args=args)
607
608    run_compiled_executable(output_file_name)
609
610    # Note that we don't delete the temporary files if the test failed. This is intentional, keeping them around helps debugging the failure.
611    if not ENABLE_COVERAGE:
612        try_remove_temporary_file(source_file_name)
613        try_remove_temporary_file(output_file_name)
614
615
616# Note: this is not the main function of this file, it's meant to be used as main function from test_*.py files.
617def main():
618    absltest.main(*sys.argv)
619