diff options
author | Marco Poletti <poletti.marco@gmail.com> | 2017-03-12 15:54:31 +0000 |
---|---|---|
committer | Marco Poletti <poletti.marco@gmail.com> | 2017-03-12 15:54:31 +0000 |
commit | 905dc09804c6f14bdaa063d37d71346a62bc37ea (patch) | |
tree | 02ec37f14cf428cc5bb299b4d4de36e728fb9d8e /tests/fruit_test_common.py | |
parent | 81c74e3b8a2d21509af3453bb0cf11337478ca60 (diff) | |
download | google-fruit-905dc09804c6f14bdaa063d37d71346a62bc37ea.tar.gz |
Support building Fruit with MSVC.
Current limitations:
* Tests must be run from the command-line, using the "Native Tools Command Prompt" that comes with MSVC. Running tests from within MSVC doesn't work.
* Tests must be run serially (-j 1), otherwise there might be test failures with errors like "fatal error C1033: cannot open program database 'C:\Some\Path\vc140.pdb'"
* Building Fruit as a shared library (.dll) is not yet supported, Fruit can only be built as a static library (.lib).
Diffstat (limited to 'tests/fruit_test_common.py')
-rw-r--r-- | tests/fruit_test_common.py | 163 |
1 files changed, 125 insertions, 38 deletions
diff --git a/tests/fruit_test_common.py b/tests/fruit_test_common.py index dfb18ee..6265c13 100644 --- a/tests/fruit_test_common.py +++ b/tests/fruit_test_common.py @@ -52,20 +52,72 @@ def run_command(executable, args=[]): raise CommandFailedException(command, stdout, stderr, p.returncode) return (stdout, stderr) +class CompilationFailedException(Exception): + def __init__(self, command, error_message): + self.command = command + self.error_message = error_message + + def __str__(self): + return textwrap.dedent('''\ + Ran command: {command} + Error message: + {error_message} + ''').format(command=self.command, error_message=self.error_message) + class PosixCompiler: def __init__(self): self.executable = CXX self.name = CXX_COMPILER_NAME def compile_discarding_output(self, source, include_dirs, args=[]): + try: + self._compile( + include_dirs, + args = ( + args + + ['-c', source] + + ['-o', os.path.devnull] + )) + except CommandFailedException as e: + raise CompilationFailedException(e.command, e.stderr) + + def compile_and_link(self, source, include_dirs, output_file_name, args=[]): self._compile( include_dirs, args = ( - args - + ['-c', source] - + ['-o', os.path.devnull] + [source] + + ADDITIONAL_LINKER_FLAGS.split() + + args + + ['-o', output_file_name] )) + def _compile(self, include_dirs, args): + include_flags = ['-I%s' % include_dir for include_dir in include_dirs] + args = ( + FRUIT_COMPILE_FLAGS.split() + + include_flags + + ['-g0'] + + args + ) + run_command(self.executable, args) + +class MsvcCompiler: + def __init__(self): + self.executable = CXX + self.name = CXX_COMPILER_NAME + + def compile_discarding_output(self, source, include_dirs, args=[]): + try: + self._compile( + include_dirs, + args = ( + args + + ['/c', source] + )) + except CommandFailedException as e: + # Note that we use stdout here, unlike above. MSVC reports compilation warnings and errors on stdout. + raise CompilationFailedException(e.command, e.stdout) + def compile_and_link(self, source, include_dirs, output_file_name, args=[]): self._compile( include_dirs, @@ -73,7 +125,7 @@ class PosixCompiler: [source] + ADDITIONAL_LINKER_FLAGS.split() + args - + ['-o', output_file_name] + + ['/Fe' + output_file_name] )) def _compile(self, include_dirs, args): @@ -81,12 +133,24 @@ class PosixCompiler: args = ( FRUIT_COMPILE_FLAGS.split() + include_flags - + ['-g0'] + args ) run_command(self.executable, args) -compiler = PosixCompiler() +if CXX_COMPILER_NAME == 'MSVC': + compiler = MsvcCompiler() + fruit_tests_linker_flags = [ + PATH_TO_COMPILED_FRUIT_LIB, + ] + fruit_error_message_extraction_regex = 'error C2338: (.*)' +else: + compiler = PosixCompiler() + fruit_tests_linker_flags = [ + '-lfruit', + '-L' + PATH_TO_COMPILED_FRUIT, + '-Wl,-rpath,' + PATH_TO_COMPILED_FRUIT, + ] + fruit_error_message_extraction_regex = 'static.assert(.*)' fruit_tests_include_dirs=[ PATH_TO_FRUIT_TEST_HEADERS, @@ -94,12 +158,6 @@ fruit_tests_include_dirs=[ PATH_TO_FRUIT_GENERATED_HEADERS, ] -fruit_tests_linker_flags=[ - '-lfruit', - '-L' + PATH_TO_COMPILED_FRUIT, - '-Wl,-rpath,' + PATH_TO_COMPILED_FRUIT, -] - _assert_helper = unittest.TestCase() def _create_temporary_file(file_content, file_name_suffix=''): @@ -159,33 +217,62 @@ def expect_compile_error(expected_fruit_error_regex, expected_fruit_error_desc_r try: compiler.compile_discarding_output(source=source_file_name, include_dirs=fruit_tests_include_dirs) raise Exception('The test should have failed to compile, but it compiled successfully') - except CommandFailedException as e1: + except CompilationFailedException as e1: e = e1 - stderr = e.stderr - stderr_lines = stderr.splitlines() + error_message = e.error_message + error_message_lines = error_message.splitlines() # Different compilers output a different number of spaces when pretty-printing types. # When using libc++, sometimes std::foo identifiers are reported as std::__1::foo. - normalized_stderr = stderr.replace(' ', '').replace('std::__1::', 'std::') - normalized_stderr_lines = normalized_stderr.splitlines() - stderr_head = _cap_to_lines(stderr, 40) + normalized_error_message = error_message.replace(' ', '').replace('std::__1::', 'std::') + normalized_error_message_lines = normalized_error_message.splitlines() + error_message_head = _cap_to_lines(error_message, 40) - for line_number, line in enumerate(normalized_stderr_lines): + for line_number, line in enumerate(normalized_error_message_lines): match = re.search('fruit::impl::(.*Error<.*>)', line) if match: actual_fruit_error_line_number = line_number actual_fruit_error = match.groups()[0] + if CXX_COMPILER_NAME == 'MSVC': + # MSVC errors are of the form: + # + # C:\Path\To\header\foo.h(59): note: see reference to class template instantiation 'fruit::impl::NoBindingFoundError<fruit::Annotated<Annotation,U>>' being compiled + # with + # [ + # Annotation=Annotation1, + # U=std::function<std::unique_ptr<ScalerImpl,std::default_delete<ScalerImpl>> (double)> + # ] + # + # So we need to parse the following few lines and use them to replace the placeholder types in the Fruit error type. + try: + replacement_lines = [] + if normalized_error_message_lines[line_number + 1].strip() == 'with': + for line in itertools.islice(normalized_error_message_lines, line_number + 3, None): + line = line.strip() + if line == ']': + break + if line.endswith(','): + line = line[:-1] + replacement_lines.append(line) + for replacement_line in replacement_lines: + match = re.search('([A-Za-z0-9_-]*)=(.*)', replacement_line) + if not match: + raise Exception('Failed to parse replacement line: %s' % replacement_line) from e + (type_variable, type_expression) = match.groups() + actual_fruit_error = re.sub(r'\b' + type_variable + r'\b', type_expression, actual_fruit_error) + except Exception: + raise Exception('Failed to parse MSVC template type arguments') break else: raise Exception(textwrap.dedent('''\ Expected error {expected_error} but the compiler output did not contain user-facing Fruit errors. Compiler command line: {compiler_command} - Stderr was: - {stderr} - ''').format(expected_error = expected_fruit_error_regex, compiler_command = e.command, stderr = stderr_head)) + Error message was: + {error_message} + ''').format(expected_error = expected_fruit_error_regex, compiler_command = e.command, error_message = error_message_head)) - for line_number, line in enumerate(stderr_lines): - match = re.search('static.assert(.*)', line) + for line_number, line in enumerate(error_message_lines): + match = re.search(fruit_error_message_extraction_regex, line) if match: actual_static_assert_error_line_number = line_number actual_static_assert_error = match.groups()[0] @@ -194,9 +281,9 @@ def expect_compile_error(expected_fruit_error_regex, expected_fruit_error_desc_r raise Exception(textwrap.dedent('''\ Expected error {expected_error} but the compiler output did not contain static_assert errors. Compiler command line: {compiler_command} - Stderr was: - {stderr} - ''').format(expected_error = expected_fruit_error_regex, compiler_command=e.command, stderr = stderr_head)) + Error message was: + {error_message} + ''').format(expected_error = expected_fruit_error_regex, compiler_command=e.command, error_message = error_message_head)) try: regex_search_result = re.search(expected_fruit_error_regex, actual_fruit_error) @@ -209,14 +296,14 @@ def expect_compile_error(expected_fruit_error_regex, expected_fruit_error_desc_r Error type was: {actual_fruit_error} Expected static assert error: {expected_fruit_error_desc_regex} Static assert was: {actual_static_assert_error} - Stderr: - {stderr} + Error message was: + {error_message} '''.format( expected_fruit_error_regex = expected_fruit_error_regex, actual_fruit_error = actual_fruit_error, expected_fruit_error_desc_regex = expected_fruit_error_desc_regex, actual_static_assert_error = actual_static_assert_error, - stderr = stderr_head))) + error_message = error_message_head))) try: regex_search_result = re.search(expected_fruit_error_desc_regex, actual_static_assert_error) except Exception as e: @@ -228,14 +315,14 @@ def expect_compile_error(expected_fruit_error_regex, expected_fruit_error_desc_r Error type was: {actual_fruit_error} Expected static assert error: {expected_fruit_error_desc_regex} Static assert was: {actual_static_assert_error} - Stderr: - {stderr} + Error message: + {error_message} '''.format( expected_fruit_error_regex = expected_fruit_error_regex, actual_fruit_error = actual_fruit_error, expected_fruit_error_desc_regex = expected_fruit_error_desc_regex, actual_static_assert_error = actual_static_assert_error, - stderr = stderr_head))) + error_message = error_message_head))) # 6 is just a constant that works for both g++ (<=4.8.3) and clang++ (<=3.5.0). It might need to be changed. if actual_fruit_error_line_number > 6 or actual_static_assert_error_line_number > 6: @@ -243,17 +330,17 @@ def expect_compile_error(expected_fruit_error_regex, expected_fruit_error_desc_r The compilation failed with the expected message, but the error message contained too many lines before the relevant ones. The error type was reported on line {actual_fruit_error_line_number} of the message (should be <=6). The static assert was reported on line {actual_static_assert_error_line_number} of the message (should be <=6). - Stderr: - {stderr} + Error message: + {error_message} '''.format( actual_fruit_error_line_number = actual_fruit_error_line_number, actual_static_assert_error_line_number = actual_static_assert_error_line_number, - stderr = stderr_head))) + error_message = error_message_head))) - for line in stderr_lines[:max(actual_fruit_error_line_number, actual_static_assert_error_line_number)]: + for line in error_message_lines[:max(actual_fruit_error_line_number, actual_static_assert_error_line_number)]: if re.search('fruit::impl::meta', line): raise Exception( - 'The compilation failed with the expected message, but the error message contained some metaprogramming types in the output (besides Error). Stderr:\n%s' + stderr_head) + '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) # Note that we don't delete the temporary file if the test failed. This is intentional, keeping the file around helps debugging the failure. os.remove(source_file_name) |