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(os.getcwd()), 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.env, 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 if PATH_TO_COMPILED_TEST_HEADERS_LIB.endswith('.dll'): 204 path_to_test_headers_lib = PATH_TO_COMPILED_TEST_HEADERS_LIB[:-4] + '.lib' 205 else: 206 path_to_test_headers_lib = PATH_TO_COMPILED_TEST_HEADERS_LIB 207 fruit_tests_linker_flags = [path_to_fruit_lib, path_to_test_headers_lib] 208 fruit_error_message_extraction_regex = 'error C2338: (.*)' 209else: 210 compiler = PosixCompiler() 211 fruit_tests_linker_flags = [ 212 '-lfruit', 213 '-ltest_headers_copy', 214 '-L' + PATH_TO_COMPILED_FRUIT, 215 '-Wl,-rpath,' + PATH_TO_COMPILED_FRUIT, 216 '-L' + PATH_TO_COMPILED_TEST_HEADERS, 217 '-Wl,-rpath,' + PATH_TO_COMPILED_TEST_HEADERS, 218 ] 219 fruit_error_message_extraction_regex = 'static.assert(.*)' 220 221fruit_tests_include_dirs = ADDITIONAL_INCLUDE_DIRS.splitlines() + [ 222 PATH_TO_FRUIT_TEST_HEADERS, 223 PATH_TO_FRUIT_STATIC_HEADERS, 224 PATH_TO_FRUIT_GENERATED_HEADERS, 225] 226 227_assert_helper = unittest.TestCase() 228 229def modify_env_for_compiled_executables(env): 230 env = env.copy() 231 path_to_fruit_lib_dir = os.path.dirname(PATH_TO_COMPILED_FRUIT_LIB) 232 path_to_fruit_test_headers_dir = os.path.dirname(PATH_TO_COMPILED_TEST_HEADERS_LIB) 233 print('PATH_TO_COMPILED_FRUIT_LIB:', PATH_TO_COMPILED_FRUIT_LIB) 234 print('PATH_TO_COMPILED_TEST_HEADERS_LIB:', PATH_TO_COMPILED_TEST_HEADERS_LIB) 235 print('Adding directory to PATH:', path_to_fruit_lib_dir) 236 print('Adding directory to PATH:', path_to_fruit_test_headers_dir) 237 env["PATH"] += os.pathsep + path_to_fruit_lib_dir + os.pathsep + path_to_fruit_test_headers_dir 238 return env 239 240def _create_temporary_file(file_content, file_name_suffix=''): 241 file_descriptor, file_name = tempfile.mkstemp(text=True, suffix=file_name_suffix) 242 file = os.fdopen(file_descriptor, mode='w') 243 file.write(file_content) 244 file.close() 245 return file_name 246 247def _cap_to_lines(s, n): 248 lines = s.splitlines() 249 if len(lines) <= n: 250 return s 251 else: 252 return '\n'.join(lines[0:n] + ['...']) 253 254def _replace_using_test_params(s, test_params): 255 for var_name, value in test_params.items(): 256 if isinstance(value, str): 257 s = re.sub(r'\b%s\b' % var_name, value, s) 258 return s 259 260def _construct_final_source_code(setup_source_code, source_code, test_params): 261 setup_source_code = textwrap.dedent(setup_source_code) 262 source_code = textwrap.dedent(source_code) 263 source_code = _replace_using_test_params(source_code, test_params) 264 return setup_source_code + source_code 265 266def try_remove_temporary_file(filename): 267 try: 268 os.remove(filename) 269 except: 270 # When running Fruit tests on Windows using Appveyor, the remove command fails for temporary files sometimes. 271 # This shouldn't cause the tests to fail, so we ignore the exception and go ahead. 272 pass 273 274def normalize_error_message_lines(lines): 275 # Different compilers output a different number of spaces when pretty-printing types. 276 # When using libc++, sometimes std::foo identifiers are reported as std::__1::foo. 277 return [line.replace(' ', '').replace('std::__1::', 'std::') for line in lines] 278 279def expect_compile_error_helper( 280 check_error_fun, 281 setup_source_code, 282 source_code, 283 test_params={}, 284 ignore_deprecation_warnings=False, 285 ignore_warnings=False): 286 source_code = _construct_final_source_code(setup_source_code, source_code, test_params) 287 288 source_file_name = _create_temporary_file(source_code, file_name_suffix='.cpp') 289 290 try: 291 args = [] 292 if ignore_deprecation_warnings: 293 args += compiler.get_disable_deprecation_warning_flags() 294 if ignore_warnings: 295 args += compiler.get_disable_all_warnings_flags() 296 if ENABLE_COVERAGE: 297 # When collecting coverage these arguments are enabled by default; however we must disable them in tests 298 # expected to fail at compile-time because GCC would otherwise fail with an error like: 299 # /tmp/tmp4m22cey7.cpp:1:0: error: cannot open /dev/null.gcno 300 args += ['-fno-profile-arcs', '-fno-test-coverage'] 301 compiler.compile_discarding_output( 302 source=source_file_name, 303 include_dirs=fruit_tests_include_dirs, 304 args=args) 305 raise Exception('The test should have failed to compile, but it compiled successfully') 306 except CompilationFailedException as e1: 307 e = e1 308 309 error_message = e.error_message 310 error_message_lines = error_message.splitlines() 311 error_message_head = _cap_to_lines(error_message, 40) 312 313 check_error_fun(e, error_message_lines, error_message_head) 314 315 try_remove_temporary_file(source_file_name) 316 317def apply_any_error_context_replacements(error_string, following_lines): 318 if CXX_COMPILER_NAME == 'MSVC': 319 # MSVC errors are of the form: 320 # 321 # C:\Path\To\header\foo.h(59): note: see reference to class template instantiation 'fruit::impl::NoBindingFoundError<fruit::Annotated<Annotation,U>>' being compiled 322 # with 323 # [ 324 # Annotation=Annotation1, 325 # U=std::function<std::unique_ptr<ScalerImpl,std::default_delete<ScalerImpl>> (double)> 326 # ] 327 # 328 # So we need to parse the following few lines and use them to replace the placeholder types in the Fruit error type. 329 replacement_lines = [] 330 if len(following_lines) >= 4 and following_lines[0].strip() == 'with': 331 assert following_lines[1].strip() == '[', 'Line was: ' + following_lines[1] 332 for line in itertools.islice(following_lines, 2, None): 333 line = line.strip() 334 if line == ']': 335 break 336 if line.endswith(','): 337 line = line[:-1] 338 replacement_lines.append(line) 339 340 for replacement_line in replacement_lines: 341 match = re.search('([A-Za-z0-9_-]*)=(.*)', replacement_line) 342 if not match: 343 raise Exception('Failed to parse replacement line: %s' % replacement_line) 344 (type_variable, type_expression) = match.groups() 345 error_string = re.sub(r'\b' + type_variable + r'\b', type_expression, error_string) 346 return error_string 347 348def expect_generic_compile_error(expected_error_regex, setup_source_code, source_code, test_params={}): 349 """ 350 Tests that the given source produces the expected error during compilation. 351 352 :param expected_fruit_error_regex: A regex used to match the Fruit error type, 353 e.g. 'NoBindingFoundForAbstractClassError<ScalerImpl>'. 354 Any identifiers contained in the regex will be replaced using test_params (where a replacement is defined). 355 :param expected_fruit_error_desc_regex: A regex used to match the Fruit error description, 356 e.g. 'No explicit binding was found for C, and C is an abstract class'. 357 :param setup_source_code: The first part of the source code. This is dedented separately from source_code and it's 358 *not* subject to test_params, unlike source_code. 359 :param source_code: The second part of the source code. Any identifiers will be replaced using test_params 360 (where a replacement is defined). This will be dedented. 361 :param test_params: A dict containing the definition of some identifiers. Each identifier in 362 expected_fruit_error_regex and source_code will be replaced (textually) with its definition (if a definition 363 was provided). 364 """ 365 366 expected_error_regex = _replace_using_test_params(expected_error_regex, test_params) 367 expected_error_regex = expected_error_regex.replace(' ', '') 368 369 def check_error(e, error_message_lines, error_message_head): 370 error_message_lines_with_replacements = [ 371 apply_any_error_context_replacements(line, error_message_lines[line_number + 1:]) 372 for line_number, line in enumerate(error_message_lines)] 373 374 normalized_error_message_lines = normalize_error_message_lines(error_message_lines_with_replacements) 375 376 for line in normalized_error_message_lines: 377 if re.search(expected_error_regex, line): 378 return 379 raise Exception(textwrap.dedent('''\ 380 Expected error {expected_error} but the compiler output did not contain that. 381 Compiler command line: {compiler_command} 382 Error message was: 383 {error_message} 384 ''').format(expected_error = expected_error_regex, compiler_command=e.command, error_message = error_message_head)) 385 386 expect_compile_error_helper(check_error, setup_source_code, source_code, test_params) 387 388def expect_compile_error( 389 expected_fruit_error_regex, 390 expected_fruit_error_desc_regex, 391 setup_source_code, 392 source_code, 393 test_params={}, 394 ignore_deprecation_warnings=False, 395 ignore_warnings=False, 396 disable_error_line_number_check=False): 397 """ 398 Tests that the given source produces the expected error during compilation. 399 400 :param expected_fruit_error_regex: A regex used to match the Fruit error type, 401 e.g. 'NoBindingFoundForAbstractClassError<ScalerImpl>'. 402 Any identifiers contained in the regex will be replaced using test_params (where a replacement is defined). 403 :param expected_fruit_error_desc_regex: A regex used to match the Fruit error description, 404 e.g. 'No explicit binding was found for C, and C is an abstract class'. 405 :param setup_source_code: The first part of the source code. This is dedented separately from source_code and it's 406 *not* subject to test_params, unlike source_code. 407 :param source_code: The second part of the source code. Any identifiers will be replaced using test_params 408 (where a replacement is defined). This will be dedented. 409 :param test_params: A dict containing the definition of some identifiers. Each identifier in 410 expected_fruit_error_regex and source_code will be replaced (textually) with its definition (if a definition 411 was provided). 412 :param ignore_deprecation_warnings: A boolean. If True, deprecation warnings will be ignored. 413 :param ignore_warnings: A boolean. If True, all warnings will be ignored. 414 :param disable_error_line_number_check: A boolean. If True, the test will not fail if there are other diagnostic 415 lines before the expected error. 416 """ 417 if '\n' in expected_fruit_error_regex: 418 raise Exception('expected_fruit_error_regex should not contain newlines') 419 if '\n' in expected_fruit_error_desc_regex: 420 raise Exception('expected_fruit_error_desc_regex should not contain newlines') 421 422 expected_fruit_error_regex = _replace_using_test_params(expected_fruit_error_regex, test_params) 423 expected_fruit_error_regex = expected_fruit_error_regex.replace(' ', '') 424 425 def check_error(e, error_message_lines, error_message_head): 426 normalized_error_message_lines = normalize_error_message_lines(error_message_lines) 427 428 for line_number, line in enumerate(normalized_error_message_lines): 429 match = re.search('fruit::impl::(.*Error<.*>)', line) 430 if match: 431 actual_fruit_error_line_number = line_number 432 actual_fruit_error = match.groups()[0] 433 actual_fruit_error = apply_any_error_context_replacements(actual_fruit_error, normalized_error_message_lines[line_number + 1:]) 434 break 435 else: 436 raise Exception(textwrap.dedent('''\ 437 Expected error {expected_error} but the compiler output did not contain user-facing Fruit errors. 438 Compiler command line: {compiler_command} 439 Error message was: 440 {error_message} 441 ''').format(expected_error = expected_fruit_error_regex, compiler_command = e.command, error_message = error_message_head)) 442 443 for line_number, line in enumerate(error_message_lines): 444 match = re.search(fruit_error_message_extraction_regex, line) 445 if match: 446 actual_static_assert_error_line_number = line_number 447 actual_static_assert_error = match.groups()[0] 448 break 449 else: 450 raise Exception(textwrap.dedent('''\ 451 Expected error {expected_error} but the compiler output did not contain static_assert errors. 452 Compiler command line: {compiler_command} 453 Error message was: 454 {error_message} 455 ''').format(expected_error = expected_fruit_error_regex, compiler_command=e.command, error_message = error_message_head)) 456 457 try: 458 regex_search_result = re.search(expected_fruit_error_regex, actual_fruit_error) 459 except Exception as e: 460 raise Exception('re.search() failed for regex \'%s\'' % expected_fruit_error_regex) from e 461 if not regex_search_result: 462 raise Exception(textwrap.dedent('''\ 463 The compilation failed as expected, but with a different error type. 464 Expected Fruit error type: {expected_fruit_error_regex} 465 Error type was: {actual_fruit_error} 466 Expected static assert error: {expected_fruit_error_desc_regex} 467 Static assert was: {actual_static_assert_error} 468 Error message was: 469 {error_message} 470 '''.format( 471 expected_fruit_error_regex = expected_fruit_error_regex, 472 actual_fruit_error = actual_fruit_error, 473 expected_fruit_error_desc_regex = expected_fruit_error_desc_regex, 474 actual_static_assert_error = actual_static_assert_error, 475 error_message = error_message_head))) 476 try: 477 regex_search_result = re.search(expected_fruit_error_desc_regex, actual_static_assert_error) 478 except Exception as e: 479 raise Exception('re.search() failed for regex \'%s\'' % expected_fruit_error_desc_regex) from e 480 if not regex_search_result: 481 raise Exception(textwrap.dedent('''\ 482 The compilation failed as expected, but with a different error message. 483 Expected Fruit error type: {expected_fruit_error_regex} 484 Error type was: {actual_fruit_error} 485 Expected static assert error: {expected_fruit_error_desc_regex} 486 Static assert was: {actual_static_assert_error} 487 Error message: 488 {error_message} 489 '''.format( 490 expected_fruit_error_regex = expected_fruit_error_regex, 491 actual_fruit_error = actual_fruit_error, 492 expected_fruit_error_desc_regex = expected_fruit_error_desc_regex, 493 actual_static_assert_error = actual_static_assert_error, 494 error_message = error_message_head))) 495 496 # 6 is just a constant that works for both g++ (<=6.0.0 at least) and clang++ (<=4.0.0 at least). 497 # It might need to be changed. 498 if not disable_error_line_number_check and (actual_fruit_error_line_number > 6 or actual_static_assert_error_line_number > 6): 499 raise Exception(textwrap.dedent('''\ 500 The compilation failed with the expected message, but the error message contained too many lines before the relevant ones. 501 The error type was reported on line {actual_fruit_error_line_number} of the message (should be <=6). 502 The static assert was reported on line {actual_static_assert_error_line_number} of the message (should be <=6). 503 Error message: 504 {error_message} 505 '''.format( 506 actual_fruit_error_line_number = actual_fruit_error_line_number, 507 actual_static_assert_error_line_number = actual_static_assert_error_line_number, 508 error_message = error_message_head))) 509 510 for line in error_message_lines[:max(actual_fruit_error_line_number, actual_static_assert_error_line_number)]: 511 if re.search('fruit::impl::meta', line): 512 raise Exception( 513 '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) 514 515 expect_compile_error_helper(check_error, setup_source_code, source_code, test_params, ignore_deprecation_warnings, ignore_warnings) 516 517 518def expect_runtime_error( 519 expected_error_regex, 520 setup_source_code, 521 source_code, 522 test_params={}, 523 ignore_deprecation_warnings=False): 524 """ 525 Tests that the given source (compiles successfully and) produces the expected error at runtime. 526 527 :param expected_error_regex: A regex used to match the content of stderr. 528 Any identifiers contained in the regex will be replaced using test_params (where a replacement is defined). 529 :param setup_source_code: The first part of the source code. This is dedented separately from source_code and it's 530 *not* subject to test_params, unlike source_code. 531 :param source_code: The second part of the source code. Any identifiers will be replaced using test_params 532 (where a replacement is defined). This will be dedented. 533 :param test_params: A dict containing the definition of some identifiers. Each identifier in 534 expected_error_regex and source_code will be replaced (textually) with its definition (if a definition 535 was provided). 536 """ 537 expected_error_regex = _replace_using_test_params(expected_error_regex, test_params) 538 source_code = _construct_final_source_code(setup_source_code, source_code, test_params) 539 540 source_file_name = _create_temporary_file(source_code, file_name_suffix='.cpp') 541 executable_suffix = {'posix': '', 'nt': '.exe'}[os.name] 542 output_file_name = _create_temporary_file('', executable_suffix) 543 544 args = fruit_tests_linker_flags.copy() 545 if ignore_deprecation_warnings: 546 args += compiler.get_disable_deprecation_warning_flags() 547 compiler.compile_and_link( 548 source=source_file_name, 549 include_dirs=fruit_tests_include_dirs, 550 output_file_name=output_file_name, 551 args=args) 552 553 try: 554 run_compiled_executable(output_file_name) 555 raise Exception('The test should have failed at runtime, but it ran successfully') 556 except CommandFailedException as e1: 557 e = e1 558 559 stderr = e.stderr 560 stderr_head = _cap_to_lines(stderr, 40) 561 562 if '\n' in expected_error_regex: 563 regex_flags = re.MULTILINE 564 else: 565 regex_flags = 0 566 567 try: 568 regex_search_result = re.search(expected_error_regex, stderr, flags=regex_flags) 569 except Exception as e: 570 raise Exception('re.search() failed for regex \'%s\'' % expected_error_regex) from e 571 if not regex_search_result: 572 raise Exception(textwrap.dedent('''\ 573 The test failed as expected, but with a different message. 574 Expected: {expected_error_regex} 575 Was: 576 {stderr} 577 '''.format(expected_error_regex = expected_error_regex, stderr = stderr_head))) 578 579 # Note that we don't delete the temporary files if the test failed. This is intentional, keeping them around helps debugging the failure. 580 if not ENABLE_COVERAGE: 581 try_remove_temporary_file(source_file_name) 582 try_remove_temporary_file(output_file_name) 583 584 585def expect_success(setup_source_code, source_code, test_params={}, ignore_deprecation_warnings=False): 586 """ 587 Tests that the given source compiles and runs successfully. 588 589 :param setup_source_code: The first part of the source code. This is dedented separately from source_code and it's 590 *not* subject to test_params, unlike source_code. 591 :param source_code: The second part of the source code. Any identifiers will be replaced using test_params 592 (where a replacement is defined). This will be dedented. 593 :param test_params: A dict containing the definition of some identifiers. Each identifier in 594 source_code will be replaced (textually) with its definition (if a definition was provided). 595 """ 596 source_code = _construct_final_source_code(setup_source_code, source_code, test_params) 597 598 if 'main(' not in source_code: 599 source_code += textwrap.dedent(''' 600 int main() { 601 } 602 ''') 603 604 source_file_name = _create_temporary_file(source_code, file_name_suffix='.cpp') 605 executable_suffix = {'posix': '', 'nt': '.exe'}[os.name] 606 output_file_name = _create_temporary_file('', executable_suffix) 607 608 args = fruit_tests_linker_flags.copy() 609 if ignore_deprecation_warnings: 610 args += compiler.get_disable_deprecation_warning_flags() 611 compiler.compile_and_link( 612 source=source_file_name, 613 include_dirs=fruit_tests_include_dirs, 614 output_file_name=output_file_name, 615 args=args) 616 617 run_compiled_executable(output_file_name) 618 619 # Note that we don't delete the temporary files if the test failed. This is intentional, keeping them around helps debugging the failure. 620 if not ENABLE_COVERAGE: 621 try_remove_temporary_file(source_file_name) 622 try_remove_temporary_file(output_file_name) 623 624 625# Note: this is not the main function of this file, it's meant to be used as main function from test_*.py files. 626def main(): 627 absltest.main(*sys.argv) 628