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