blob: 49ebe69d6a3a47f83bb8b51e345ef8437a39945f [file] [log] [blame]
# This source file is part of the Swift.org open source project
#
# Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
# Licensed under Apache License v2.0 with Runtime Library Exception
#
# See https://swift.org/LICENSE.txt for license information
# See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
from __future__ import print_function
import os
import sys
import unittest
from contextlib import contextmanager
from io import StringIO
from build_swift import driver_arguments
from build_swift.tests import expected_options
from swift_build_support.swift_build_support import migration
from swift_build_support.swift_build_support.SwiftBuildSupport import (
get_all_preset_names,
get_preset_options,
)
FILE_PATH = os.path.abspath(__file__)
TESTS_PATH = os.path.abspath(os.path.join(FILE_PATH, os.pardir))
BUILD_SWIFT_PATH = os.path.abspath(os.path.join(TESTS_PATH, os.pardir))
UTILS_PATH = os.path.abspath(os.path.join(BUILD_SWIFT_PATH, os.pardir))
BUILD_SCRIPT_IMPL = os.path.join(UTILS_PATH, 'build-script-impl')
PRESETS_FILES = [
os.path.join(UTILS_PATH, 'build-presets.ini'),
]
class ParserError(Exception):
pass
@contextmanager
def redirect_stderr(stream=None):
stream = stream or StringIO()
old_stderr, sys.stderr = sys.stderr, stream
try:
yield stream
finally:
sys.stderr = old_stderr
@contextmanager
def redirect_stdout(stream=None):
stream = stream or StringIO()
old_stdout, sys.stdout = sys.stdout, stream
try:
yield stream
finally:
sys.stdout = old_stdout
def _load_all_presets(presets_files):
preset_names = get_all_preset_names(presets_files)
# Hack to filter out mixins which are not expected to be valid presets
preset_names = [n for n in preset_names if not n.startswith('mixin')]
substitutions = {
'install_destdir': '/tmp/install',
'install_symroot': '/tmp/symroot',
'installable_package': '/tmp/xcode-xyz-root.tar.gz',
}
presets = dict()
for name in preset_names:
try:
# Attempt to parse preset
presets[name] = get_preset_options(substitutions,
presets_files, name)
except SystemExit:
continue
return presets
class TestDriverArgumentParserMeta(type):
"""Metaclass used to dynamically generate test methods for each of the
individual options accepted by the parser and methods to validate all of
the presets.
"""
def __new__(cls, name, bases, attrs):
# Generate tests for each default value
for dest, value in expected_options.EXPECTED_DEFAULTS.items():
test_name = 'test_default_value_' + dest
attrs[test_name] = cls.generate_default_value_test(dest, value)
# Generate tests for each expected option
for option in expected_options.EXPECTED_OPTIONS:
test_name = 'test_option_' + option.sanitized_str()
attrs[test_name] = cls.generate_option_test(option)
# Generate tests for each preset
presets = _load_all_presets(PRESETS_FILES)
for name, args in presets.items():
test_name = 'test_preset_' + name
attrs[test_name] = cls.generate_preset_test(name, args)
return super(TestDriverArgumentParserMeta, cls).__new__(
cls, name, bases, attrs)
@classmethod
def generate_default_value_test(cls, dest, default_value):
def test(self):
with self.assertNotRaises(ParserError):
parsed_values = self.parse_args([])
parsed_value = getattr(parsed_values, dest)
if default_value.__class__ is str:
parsed_value = str(parsed_value)
self.assertEqual(default_value, parsed_value,
'Invalid default value for "{}": {} != {}'
.format(dest, default_value, parsed_value))
return test
@classmethod
def _generate_option_test(cls, option):
def test(self):
with self.assertNotRaises(ParserError):
args = self.parse_args([option.option_string])
self.assertEqual(getattr(args, option.dest), option.value)
with self.assertRaises(ParserError):
self.parse_args([option.option_string, 'foo'])
return test
@classmethod
def _generate_append_option_test(cls, option):
def test(self):
with self.assertNotRaises(ParserError):
# Range size is arbitrary, just needs to be more than once
for i in range(1, 4):
args = self.parse_args([option.option_string, 'ARG'] * i)
self.assertEqual(getattr(args, option.dest), ['ARG'] * i)
return test
@classmethod
def _generate_choices_option_test(cls, option):
def test(self):
with self.assertNotRaises(ParserError):
for choice in option.choices:
args = self.parse_args([option.option_string, str(choice)])
self.assertEqual(getattr(args, option.dest), choice)
with self.assertRaises(ParserError):
self.parse_args([option.option_string, 'INVALID'])
return test
@classmethod
def _generate_help_option_test(cls, option):
def test(self):
with redirect_stdout() as output, self.assertRaises(ParserError):
self.parse_args([option.option_string])
self.assertNotEmpty(output)
return test
@classmethod
def _generate_int_option_test(cls, option):
def test(self):
with self.assertNotRaises(ParserError):
for i in [0, 1, 42]:
args = self.parse_args([option.option_string, str(i)])
self.assertEqual(int(getattr(args, option.dest)), i)
# FIXME: int-type options should not accept non-int strings
# with self.assertRaises(ParserError):
# self.parse_args([option.option_string, str(0.0)])
# self.parse_args([option.option_string, str(1.0)])
# self.parse_args([option.option_string, str(3.14)])
# self.parse_args([option.option_string, 'NaN'])
return test
@classmethod
def _generate_path_option_test(cls, option):
def test(self):
with self.assertNotRaises(ParserError):
self.parse_args([option.option_string, sys.executable])
# FIXME: path-type options should not accept non-path inputs
# with self.assertRaises(ParserError):
# self.parse_args([option.option_string, 'foo'])
return test
@classmethod
def _generate_str_option_test(cls, option):
def test(self):
with self.assertNotRaises(ParserError):
self.parse_args([option.option_string, 'foo'])
return test
@classmethod
def _generate_toggle_option_test(cls, option):
def test(self):
with self.assertNotRaises(ParserError):
# Standalone argument
self.parse_args([option.option_string])
# True values
# self.parse_args([option.option_string, True])
# self.parse_args([option.option_string, 1])
self.parse_args([option.option_string, '1'])
self.parse_args([option.option_string, 'true'])
self.parse_args([option.option_string, 'True'])
# self.parse_args([option.option_string, 'TRUE'])
# False values
# self.parse_args([option.option_string, False])
# self.parse_args([option.option_string, 0])
self.parse_args([option.option_string, '0'])
self.parse_args([option.option_string, 'false'])
self.parse_args([option.option_string, 'False'])
# self.parse_args([option.option_string, 'FALSE'])
return test
@classmethod
def _generate_unsupported_option_test(cls, option):
def test(self):
with self.assertRaises(ParserError):
self.parse_args([option.option_string])
return test
@classmethod
def generate_option_test(cls, option):
if option.__class__ is expected_options.Option:
return cls._generate_option_test(option)
elif option.__class__ is expected_options.AppendOption:
return cls._generate_append_option_test(option)
elif option.__class__ is expected_options.ChoicesOption:
return cls._generate_choices_option_test(option)
elif option.__class__ is expected_options.HelpOption:
return cls._generate_help_option_test(option)
elif option.__class__ is expected_options.IntOption:
return cls._generate_int_option_test(option)
elif option.__class__ is expected_options.PathOption:
return cls._generate_path_option_test(option)
elif option.__class__ is expected_options.StrOption:
return cls._generate_str_option_test(option)
elif option.__class__ is expected_options.ToggleOption:
return cls._generate_toggle_option_test(option)
elif option.__class__ is expected_options.UnsupportedOption:
return cls._generate_unsupported_option_test(option)
# Ignore all IgnoreOption tests since they should be manually tested
elif option.__class__ is expected_options.IgnoreOption:
return lambda self: None
# Catch-all meaningless test
return lambda self: \
self.fail('Unexpected option "{}"'.format(option.option_string))
@classmethod
def generate_preset_test(cls, preset_name, preset_args):
def test(self):
try:
self.parse_args(preset_args, check_impl_args=True)
except ParserError as e:
self.fail('Failed to parse preset "{}": {}'.format(
preset_name, e))
return test
class TestDriverArgumentParser(unittest.TestCase):
__metaclass__ = TestDriverArgumentParserMeta
def parse_args(self, args, error_message=None, check_impl_args=False):
if error_message is None:
error_message = 'failed to parse arguments: ' + str(args)
with open(os.devnull, 'w') as devnull:
try:
with redirect_stderr(devnull), redirect_stdout(devnull):
namespace = migration.parse_args(self.parser, args)
except (SystemExit, ValueError) as e:
raise ParserError(error_message, e)
if not namespace.build_script_impl_args and not check_impl_args:
return namespace
try:
with redirect_stderr(devnull), redirect_stdout(devnull):
migration.check_impl_args(BUILD_SCRIPT_IMPL,
namespace.build_script_impl_args)
except (SystemExit, ValueError) as e:
raise ParserError(error_message, e)
return namespace
@contextmanager
def assertNotRaises(self, exception):
assert issubclass(exception, BaseException)
try:
yield
except exception as e:
self.fail(str(e))
def setUp(self):
self.parser = driver_arguments.create_argument_parser()
# -------------------------------------------------------------------------
# Manual option tests
def test_option_clang_compiler_version(self):
option_string = '--clang-compiler-version'
with self.assertNotRaises(ParserError):
self.parse_args([option_string, '5.0.0'])
self.parse_args([option_string, '5.0.1'])
self.parse_args([option_string, '5.0.0.1'])
with self.assertRaises(ParserError):
self.parse_args([option_string, '1'])
self.parse_args([option_string, '1.2'])
self.parse_args([option_string, '0.0.0.0.1'])
def test_option_clang_user_visible_version(self):
option_string = '--clang-user-visible-version'
with self.assertNotRaises(ParserError):
self.parse_args([option_string, '5.0.0'])
self.parse_args([option_string, '5.0.1'])
self.parse_args([option_string, '5.0.0.1'])
with self.assertRaises(ParserError):
self.parse_args([option_string, '1'])
self.parse_args([option_string, '1.2'])
self.parse_args([option_string, '0.0.0.0.1'])
def test_option_swift_compiler_version(self):
option_string = '--swift-compiler-version'
with self.assertNotRaises(ParserError):
self.parse_args([option_string, '4.1'])
self.parse_args([option_string, '4.0.1'])
self.parse_args([option_string, '200.99.1'])
with self.assertRaises(ParserError):
self.parse_args([option_string, '1'])
self.parse_args([option_string, '0.0.0.1'])
def test_option_swift_user_visible_version(self):
option_string = '--swift-user-visible-version'
with self.assertNotRaises(ParserError):
self.parse_args([option_string, '4.1'])
self.parse_args([option_string, '4.0.1'])
self.parse_args([option_string, '200.99.1'])
with self.assertRaises(ParserError):
self.parse_args([option_string, '1'])
self.parse_args([option_string, '0.0.0.1'])
# -------------------------------------------------------------------------
# Implied defaults tests
def test_implied_defaults_assertions(self):
with self.assertNotRaises(ParserError):
args = self.parse_args(['--assertions'])
self.assertTrue(args.cmark_assertions)
self.assertTrue(args.llvm_assertions)
self.assertTrue(args.swift_assertions)
self.assertTrue(args.swift_stdlib_assertions)
def test_implied_defaults_cmark_build_variant(self):
with self.assertNotRaises(ParserError):
args = self.parse_args(['--debug-cmark'])
self.assertTrue(args.build_cmark)
def test_implied_defaults_lldb_build_variant(self):
with self.assertNotRaises(ParserError):
args = self.parse_args(['--debug-lldb'])
self.assertTrue(args.build_lldb)
with self.assertNotRaises(ParserError):
args = self.parse_args(['--lldb-assertions'])
self.assertTrue(args.build_lldb)
def test_implied_defaults_build_variant(self):
with self.assertNotRaises(ParserError):
args = self.parse_args(['--debug'])
self.assertEquals(args.cmark_build_variant, 'Debug')
self.assertEquals(args.foundation_build_variant, 'Debug')
self.assertEquals(args.libdispatch_build_variant, 'Debug')
self.assertEquals(args.libicu_build_variant, 'Debug')
self.assertEquals(args.lldb_build_variant, 'Debug')
self.assertEquals(args.llvm_build_variant, 'Debug')
self.assertEquals(args.swift_build_variant, 'Debug')
self.assertEquals(args.swift_stdlib_build_variant, 'Debug')
def test_implied_defaults_skip_build(self):
with self.assertNotRaises(ParserError):
args = self.parse_args(['--skip-build'])
self.assertFalse(args.build_benchmarks)
self.assertFalse(args.build_linux)
self.assertFalse(args.build_android)
self.assertFalse(args.build_freebsd)
self.assertFalse(args.build_cygwin)
self.assertFalse(args.build_osx)
self.assertFalse(args.build_ios)
self.assertFalse(args.build_tvos)
self.assertFalse(args.build_watchos)
self.assertFalse(args.build_foundation)
self.assertFalse(args.build_libdispatch)
self.assertFalse(args.build_libicu)
self.assertFalse(args.build_lldb)
self.assertFalse(args.build_llbuild)
self.assertFalse(args.build_playgroundlogger)
self.assertFalse(args.build_playgroundsupport)
self.assertFalse(args.build_swiftpm)
self.assertFalse(args.build_xctest)
def test_implied_defaults_skip_build_ios(self):
with self.assertNotRaises(ParserError):
args = self.parse_args(['--skip-build-ios'])
self.assertFalse(args.build_ios_device)
self.assertFalse(args.build_ios_simulator)
# Also implies that the tests should be skipped
self.assertFalse(args.test_ios_host)
self.assertFalse(args.test_ios_simulator)
def test_implied_defaults_skip_build_tvos(self):
with self.assertNotRaises(ParserError):
args = self.parse_args(['--skip-build-tvos'])
self.assertFalse(args.build_tvos_device)
self.assertFalse(args.build_tvos_simulator)
# Also implies that the tests should be skipped
self.assertFalse(args.test_tvos_host)
self.assertFalse(args.test_tvos_simulator)
def test_implied_defaults_skip_build_watchos(self):
with self.assertNotRaises(ParserError):
args = self.parse_args(['--skip-build-watchos'])
self.assertFalse(args.build_watchos_device)
self.assertFalse(args.build_watchos_simulator)
# Also implies that the tests should be skipped
self.assertFalse(args.test_watchos_host)
self.assertFalse(args.test_watchos_simulator)
def test_implied_defaults_validation_test(self):
with self.assertNotRaises(ParserError):
args = self.parse_args(['--validation-test'])
self.assertTrue(args.test)
def test_implied_defaults_test_optimized(self):
with self.assertNotRaises(ParserError):
args = self.parse_args(['--test-optimized'])
self.assertTrue(args.test)
def test_implied_defaults_test_optimize_for_size(self):
with self.assertNotRaises(ParserError):
args = self.parse_args(['--test-optimize-for-size'])
self.assertTrue(args.test)
def test_implied_defaults_skip_all_tests(self):
with self.assertNotRaises(ParserError):
args = self.parse_args([
'--test', '0',
'--validation-test', '0',
'--long-test', '0',
])
self.assertFalse(args.test_linux)
self.assertFalse(args.test_freebsd)
self.assertFalse(args.test_cygwin)
self.assertFalse(args.test_osx)
self.assertFalse(args.test_ios)
self.assertFalse(args.test_tvos)
self.assertFalse(args.test_watchos)
def test_implied_defaults_skip_test_ios(self):
with self.assertNotRaises(ParserError):
args = self.parse_args(['--skip-test-ios'])
self.assertFalse(args.test_ios_host)
self.assertFalse(args.test_ios_simulator)
def test_implied_defaults_skip_test_tvos(self):
with self.assertNotRaises(ParserError):
args = self.parse_args(['--skip-test-tvos'])
self.assertFalse(args.test_tvos_host)
self.assertFalse(args.test_tvos_simulator)
def test_implied_defaults_skip_test_watchos(self):
with self.assertNotRaises(ParserError):
args = self.parse_args(['--skip-test-watchos'])
self.assertFalse(args.test_watchos_host)
self.assertFalse(args.test_watchos_simulator)
def test_implied_defaults_skip_build_android(self):
with self.assertNotRaises(ParserError):
args = self.parse_args(['--android', '0'])
self.assertFalse(args.test_android_host)
with self.assertNotRaises(ParserError):
args = self.parse_args(['--skip-build-android'])
self.assertFalse(args.test_android_host)
def test_implied_defaults_host_test(self):
with self.assertNotRaises(ParserError):
args = self.parse_args(['--host-test', '0'])
self.assertFalse(args.test_ios_host)
self.assertFalse(args.test_tvos_host)
self.assertFalse(args.test_watchos_host)
self.assertFalse(args.test_android_host)
if __name__ == '__main__':
unittest.main()