#! /usr/bin/env python3
# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Unit tests for remote_link.
#
# Usage:
#
# tools/clang/scripts/remote_link_unit_tests.py
#
# A coverage report combining these tests with the integration tests
# in remote_link_integration_tests.py can be generated by running:
#
# env COVERAGE_FILE=.coverage.unit python3 third_party/pycoverage run \
# tools/clang/scripts/remote_link_unit_tests.py
# env COVERAGE_FILE=.coverage.integration python3 third_party/pycoverage \
# run tools/clang/scripts/remote_link_integration_tests.py
# python3 third_party/pycoverage combine
# python3 third_party/pycoverage html
#
# The report will be available as htmlcov/index.html
import remote_ld
import remote_link
import os
import unittest
from unittest import mock
from remote_link_test_utils import named_directory, working_directory
class FakeFs(object):
"""
Context manager that mocks the functions through which remote_link
interacts with the filesystem.
"""
def __init__(self, bitcode_files=None, other_files=None):
self.bitcode_files = set(bitcode_files or [])
self.other_files = set(other_files or [])
def ensure_file(path):
self.other_files.add(path)
def exists(path):
return path in self.bitcode_files or path in self.other_files
def is_bitcode_file(path):
return path in self.bitcode_files
self.mock_ensure_file = mock.patch('remote_link.ensure_file', ensure_file)
self.mock_exists = mock.patch('os.path.exists', exists)
self.mock_is_bitcode_file = mock.patch('remote_link.is_bitcode_file',
is_bitcode_file)
def __enter__(self):
self.mock_ensure_file.start()
self.mock_exists.start()
self.mock_is_bitcode_file.start()
return self
def __exit__(self, exnty, *args, **kwargs):
self.mock_is_bitcode_file.stop()
self.mock_exists.stop()
self.mock_ensure_file.stop()
return exnty is None
class RemoteLinkUnitTest(unittest.TestCase):
"""
Unit tests for remote_link.
"""
def test_analyze_expanded_args_nocodegen(self):
with FakeFs(other_files=['foo.o', 'bar.o']):
self.assertIsNone(remote_ld.RemoteLinkUnix().analyze_expanded_args(
['clang', 'foo.o', 'bar.o', '-o', 'foo'], 'foo', 'clang', 'lto.foo',
'common', False))
def test_analyze_expanded_args_one_codegen(self):
with FakeFs(bitcode_files=['foo.o'], other_files=['bar.o']):
result = remote_ld.RemoteLinkUnix().analyze_expanded_args(
['clang', 'foo.o', 'bar.o', '-o', 'foo'], 'foo', 'clang', 'lto.foo',
'common', False)
self.assertIsNotNone(result)
self.assertNotEqual(len(result.codegen), 0)
self.assertEqual(result.codegen[0][1], 'foo.o')
self.assertEqual(len(result.codegen), 1)
self.assertIn('foo.o', result.index_params)
self.assertIn('bar.o', result.index_params)
self.assertIn('bar.o', result.final_params)
# foo.o should not be in final_params because it will be added via
# the used object file.
self.assertNotIn('foo.o', result.final_params)
def test_analyze_expanded_args_params(self):
with FakeFs(bitcode_files=['foo.o']):
result = remote_ld.RemoteLinkUnix().analyze_expanded_args([
'clang', '-O2', '--target=arm-none-eabi', '-march=armv7-a',
'-flto=thin', '-fsplit-lto-unit', '-fwhole-program-vtables',
'-fsanitize=cfi', '-g', '-gsplit-dwarf', '-mllvm',
'-generate-type-units', 'foo.o', '-o', 'foo'
], 'foo', 'clang', 'lto.foo', 'common', False)
self.assertIsNotNone(result)
self.assertIn('-Wl,-plugin-opt=obj-path=lto.foo/foo.split.o',
result.index_params)
self.assertIn('-O2', result.index_params)
self.assertIn('--target=arm-none-eabi', result.codegen_params)
self.assertIn('-march=armv7-a', result.codegen_params)
self.assertIn('-g', result.index_params)
self.assertIn('-gsplit-dwarf', result.index_params)
self.assertIn('-mllvm -generate-type-units',
' '.join(result.index_params))
self.assertIn('-flto=thin', result.index_params)
self.assertIn('-fwhole-program-vtables', result.index_params)
self.assertIn('-fsanitize=cfi', result.index_params)
self.assertIn('-O2', result.codegen_params)
self.assertIn('--target=arm-none-eabi', result.codegen_params)
self.assertIn('-march=armv7-a', result.codegen_params)
self.assertIn('-gsplit-dwarf', result.codegen_params)
self.assertIn('-mllvm -generate-type-units',
' '.join(result.codegen_params))
self.assertNotIn('-flto=thin', result.codegen_params)
self.assertNotIn('-fwhole-program-vtables', result.codegen_params)
self.assertNotIn('-fsanitize=cfi', result.codegen_params)
self.assertIn('-flto=thin', result.final_params)
def test_codegen_params_default(self):
with FakeFs(bitcode_files=['foo.o'], other_files=['bar.o']):
result = remote_ld.RemoteLinkUnix().analyze_expanded_args(
['clang', 'foo.o', 'bar.o', '-o', 'foo'], 'foo', 'clang', 'lto.foo',
'common', False)
# Codegen optimization level should default to 2.
self.assertIn('-O2', result.codegen_params)
# -fdata-sections and -ffunction-sections default to on to match the
# behavior of local linking.
self.assertIn('-fdata-sections', result.codegen_params)
self.assertIn('-ffunction-sections', result.codegen_params)
def test_codegen_params_default_cl(self):
with FakeFs(bitcode_files=['foo.obj'], other_files=['bar.obj']):
result = remote_link.RemoteLinkWindows().analyze_expanded_args(
['clang-cl', 'foo.obj', 'bar.obj', '-Fefoo.exe'], 'foo.exe',
'clang-cl', 'lto.foo', 'common', False)
# Codegen optimization level should default to 2.
self.assertIn('-O2', result.codegen_params)
# -Gw and -Gy default to on to match the behavior of local linking.
self.assertIn('-Gw', result.codegen_params)
self.assertIn('-Gy', result.codegen_params)
def test_codegen_params_no_data_sections(self):
with FakeFs(bitcode_files=['foo.o'], other_files=['bar.o']):
result = remote_ld.RemoteLinkUnix().analyze_expanded_args(
['clang', '-fno-data-sections', 'foo.o', 'bar.o', '-o', 'foo'], 'foo',
'clang', 'lto.foo', 'common', False)
self.assertNotIn('-fdata-sections', result.codegen_params)
self.assertIn('-ffunction-sections', result.codegen_params)
def test_codegen_params_no_function_sections(self):
with FakeFs(bitcode_files=['foo.o'], other_files=['bar.o']):
result = remote_ld.RemoteLinkUnix().analyze_expanded_args(
['clang', '-fno-function-sections', 'foo.o', 'bar.o', '-o', 'foo'],
'foo', 'clang', 'lto.foo', 'common', False)
self.assertIn('-fdata-sections', result.codegen_params)
self.assertNotIn('-ffunction-sections', result.codegen_params)
def test_codegen_params_no_data_sections_cl(self):
with FakeFs(bitcode_files=['foo.obj'], other_files=['bar.obj']):
result = remote_link.RemoteLinkWindows().analyze_expanded_args(
['clang-cl', '/Gw-', 'foo.obj', 'bar.obj', '/Fefoo.exe'], 'foo.exe',
'clang-cl', 'lto.foo', 'common', False)
self.assertNotIn('-fdata-sections', result.codegen_params)
self.assertNotIn('-Gw', result.codegen_params)
self.assertNotIn('/Gw', result.codegen_params)
self.assertIn('-Gy', result.codegen_params)
def test_codegen_params_no_function_sections_cl(self):
with FakeFs(bitcode_files=['foo.obj'], other_files=['bar.obj']):
result = remote_link.RemoteLinkWindows().analyze_expanded_args(
['clang-cl', '/Gy-', 'foo.obj', 'bar.obj', '/Fefoo.exe'], 'foo.exe',
'clang-cl', 'lto.foo', 'common', False)
self.assertIn('-Gw', result.codegen_params)
self.assertNotIn('-ffunction-sections', result.codegen_params)
self.assertNotIn('-Gy', result.codegen_params)
self.assertNotIn('/Gy', result.codegen_params)
def test_codegen_params_explicit_data_and_function_sections(self):
with FakeFs(bitcode_files=['foo.o'], other_files=['bar.o']):
result = remote_ld.RemoteLinkUnix().analyze_expanded_args([
'clang', '-ffunction-sections', '-fdata-sections', 'foo.o', 'bar.o',
'-o', 'foo'
], 'foo', 'clang', 'lto.foo', 'common', False)
self.assertIn('-fdata-sections', result.codegen_params)
self.assertIn('-ffunction-sections', result.codegen_params)
def test_codegen_params_explicit_data_and_function_sections_cl(self):
with FakeFs(bitcode_files=['foo.obj'], other_files=['bar.obj']):
result = remote_link.RemoteLinkWindows().analyze_expanded_args(
['clang-cl', '/Gy', '-Gw', 'foo.obj', 'bar.obj', '/Fefoo.exe'],
'foo.exe', 'clang-cl', 'lto.foo', 'common', False)
self.assertIn('-Gw', result.codegen_params)
self.assertIn('/Gy', result.codegen_params)
self.assertNotIn('-fdata-sections', result.codegen_params)
self.assertNotIn('-ffunction-sections', result.codegen_params)
def test_ensure_file_no_dir(self):
with named_directory() as d, working_directory(d):
self.assertFalse(os.path.exists('test'))
remote_link.ensure_file('test')
self.assertTrue(os.path.exists('test'))
def test_ensure_file_existing(self):
with named_directory() as d, working_directory(d):
self.assertFalse(os.path.exists('foo/test'))
remote_link.ensure_file('foo/test')
self.assertTrue(os.path.exists('foo/test'))
os.utime('foo/test', (0, 0))
statresult = os.stat('foo/test')
remote_link.ensure_file('foo/test')
self.assertTrue(os.path.exists('foo/test'))
newstatresult = os.stat('foo/test')
self.assertEqual(newstatresult.st_mtime, statresult.st_mtime)
def test_ensure_file_error(self):
with named_directory() as d, working_directory(d):
self.assertFalse(os.path.exists('test'))
remote_link.ensure_file('test')
self.assertTrue(os.path.exists('test'))
self.assertRaises(OSError, remote_link.ensure_file, 'test/impossible')
def test_transform_codegen_param_on_mllvm(self):
# Regression test for crbug.com/1135234
link = remote_ld.RemoteLinkUnix()
self.assertEqual(
link.transform_codegen_param_common('-mllvm,-import-instr-limit=20'),
['-mllvm', '-import-instr-limit=20'])
if __name__ == '__main__':
unittest.main()