# test_cmake.py - Unit tests for swift_build_support.cmake -*- python -*-
#
# 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


import os
import platform
import unittest
from argparse import Namespace

from build_swift.build_swift.versions import Version

from swift_build_support.cmake import CMake, CMakeOptions
from swift_build_support.toolchain import host_toolchain


class CMakeTestCase(unittest.TestCase):

    def mock_distcc_path(self):
        """Return a path string of mock distcc executable
        """
        if platform.system() == 'Windows':
            executable = 'mock-distcc.cmd'
        else:
            executable = 'mock-distcc'
        return os.path.join(os.path.dirname(__file__), executable)

    def mock_sccache_path(self):
        """Return a path string of a mock sccache executable
        """
        if platform.system() == 'Windows':
            executable = 'sccache.cmd'
        else:
            executable = 'sccache'
        return os.path.join(os.path.dirname(__file__), executable)

    def default_args(self):
        """Return new args object with default values
        """
        return Namespace(host_cc="/path/to/clang",
                         host_cxx="/path/to/clang++",
                         host_swiftc="/path/to/swiftc",
                         host_libtool="/path/to/libtool",
                         host_ar="/path/to/ar",
                         host_ranlib="/path/to/ranlib",
                         enable_asan=False,
                         enable_ubsan=False,
                         enable_tsan=False,
                         enable_lsan=False,
                         enable_sanitize_coverage=False,
                         export_compile_commands=False,
                         distcc=False,
                         sccache=False,
                         cmake_generator="Ninja",
                         cmake_c_launcher=None,
                         cmake_cxx_launcher=None,
                         clang_compiler_version=None,
                         clang_user_visible_version=None,
                         build_jobs=8,
                         build_args=[],
                         verbose_build=False,
                         build_ninja=False)

    def which_ninja(self, args):
        # Maybe we'll build a ninja, maybe we wont.
        # Fake it anyway for the tests.
        return '/path/to/built/ninja'

    def cmake(self, args):
        """Return new CMake object initialized with given args
        """
        toolchain = host_toolchain()
        toolchain.cc = args.host_cc
        toolchain.cxx = args.host_cxx
        toolchain.swiftc = args.host_swiftc
        toolchain.libtool = args.host_libtool
        toolchain.ar = args.host_ar
        toolchain.ranlib = args.host_ranlib
        if args.distcc:
            toolchain.distcc = self.mock_distcc_path()
        if args.sccache:
            toolchain.sccache = self.mock_sccache_path()
        toolchain.ninja = self.which_ninja(args)
        return CMake(args=args, toolchain=toolchain)

    def test_common_options_defaults(self):
        args = self.default_args()
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.common_options()),
            ["-G", "Ninja",
             "-DCMAKE_C_COMPILER:PATH=/path/to/clang",
             "-DCMAKE_CXX_COMPILER:PATH=/path/to/clang++",
             "-DCMAKE_Swift_COMPILER:PATH=/path/to/swiftc",
             "-DCMAKE_LIBTOOL:PATH=/path/to/libtool",
             "-DCMAKE_AR:PATH=/path/to/ar",
             "-DCMAKE_RANLIB:PATH=/path/to/ranlib",
             "-DCMAKE_MAKE_PROGRAM=" + self.which_ninja(args)])

    def test_common_options_asan(self):
        args = self.default_args()
        args.enable_asan = True
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.common_options()),
            ["-G", "Ninja",
             "-DLLVM_USE_SANITIZER=Address",
             "-DCMAKE_C_COMPILER:PATH=/path/to/clang",
             "-DCMAKE_CXX_COMPILER:PATH=/path/to/clang++",
             "-DCMAKE_Swift_COMPILER:PATH=/path/to/swiftc",
             "-DCMAKE_LIBTOOL:PATH=/path/to/libtool",
             "-DCMAKE_AR:PATH=/path/to/ar",
             "-DCMAKE_RANLIB:PATH=/path/to/ranlib",
             "-DCMAKE_MAKE_PROGRAM=" + self.which_ninja(args)])

    def test_common_options_ubsan(self):
        args = self.default_args()
        args.enable_ubsan = True
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.common_options()),
            ["-G", "Ninja",
             "-DLLVM_USE_SANITIZER=Undefined",
             "-DCMAKE_C_COMPILER:PATH=/path/to/clang",
             "-DCMAKE_CXX_COMPILER:PATH=/path/to/clang++",
             "-DCMAKE_Swift_COMPILER:PATH=/path/to/swiftc",
             "-DCMAKE_LIBTOOL:PATH=/path/to/libtool",
             "-DCMAKE_AR:PATH=/path/to/ar",
             "-DCMAKE_RANLIB:PATH=/path/to/ranlib",
             "-DCMAKE_MAKE_PROGRAM=" + self.which_ninja(args)])

    def test_common_options_tsan(self):
        args = self.default_args()
        args.enable_tsan = True
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.common_options()),
            ["-G", "Ninja",
             "-DLLVM_USE_SANITIZER=Thread",
             "-DCMAKE_C_COMPILER:PATH=/path/to/clang",
             "-DCMAKE_CXX_COMPILER:PATH=/path/to/clang++",
             "-DCMAKE_Swift_COMPILER:PATH=/path/to/swiftc",
             "-DCMAKE_LIBTOOL:PATH=/path/to/libtool",
             "-DCMAKE_AR:PATH=/path/to/ar",
             "-DCMAKE_RANLIB:PATH=/path/to/ranlib",
             "-DCMAKE_MAKE_PROGRAM=" + self.which_ninja(args)])

    def test_common_options_asan_ubsan(self):
        args = self.default_args()
        args.enable_asan = True
        args.enable_ubsan = True
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.common_options()),
            ["-G", "Ninja",
             "-DLLVM_USE_SANITIZER=Address;Undefined",
             "-DCMAKE_C_COMPILER:PATH=/path/to/clang",
             "-DCMAKE_CXX_COMPILER:PATH=/path/to/clang++",
             "-DCMAKE_Swift_COMPILER:PATH=/path/to/swiftc",
             "-DCMAKE_LIBTOOL:PATH=/path/to/libtool",
             "-DCMAKE_AR:PATH=/path/to/ar",
             "-DCMAKE_RANLIB:PATH=/path/to/ranlib",
             "-DCMAKE_MAKE_PROGRAM=" + self.which_ninja(args)])

    def test_common_options_ubsan_tsan(self):
        args = self.default_args()
        args.enable_ubsan = True
        args.enable_tsan = True
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.common_options()),
            ["-G", "Ninja",
             "-DLLVM_USE_SANITIZER=Undefined;Thread",
             "-DCMAKE_C_COMPILER:PATH=/path/to/clang",
             "-DCMAKE_CXX_COMPILER:PATH=/path/to/clang++",
             "-DCMAKE_Swift_COMPILER:PATH=/path/to/swiftc",
             "-DCMAKE_LIBTOOL:PATH=/path/to/libtool",
             "-DCMAKE_AR:PATH=/path/to/ar",
             "-DCMAKE_RANLIB:PATH=/path/to/ranlib",
             "-DCMAKE_MAKE_PROGRAM=" + self.which_ninja(args)])

    def test_common_options_asan_ubsan_tsan(self):
        args = self.default_args()
        args.enable_asan = True
        args.enable_ubsan = True
        args.enable_tsan = True
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.common_options()),
            ["-G", "Ninja",
             "-DLLVM_USE_SANITIZER=Address;Undefined;Thread",
             "-DCMAKE_C_COMPILER:PATH=/path/to/clang",
             "-DCMAKE_CXX_COMPILER:PATH=/path/to/clang++",
             "-DCMAKE_Swift_COMPILER:PATH=/path/to/swiftc",
             "-DCMAKE_LIBTOOL:PATH=/path/to/libtool",
             "-DCMAKE_AR:PATH=/path/to/ar",
             "-DCMAKE_RANLIB:PATH=/path/to/ranlib",
             "-DCMAKE_MAKE_PROGRAM=" + self.which_ninja(args)])

    def test_common_options_lsan(self):
        args = self.default_args()
        args.enable_lsan = True
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.common_options()),
            ["-G", "Ninja",
             "-DLLVM_USE_SANITIZER=Leaks",
             "-DCMAKE_C_COMPILER:PATH=/path/to/clang",
             "-DCMAKE_CXX_COMPILER:PATH=/path/to/clang++",
             "-DCMAKE_Swift_COMPILER:PATH=/path/to/swiftc",
             "-DCMAKE_LIBTOOL:PATH=/path/to/libtool",
             "-DCMAKE_AR:PATH=/path/to/ar",
             "-DCMAKE_RANLIB:PATH=/path/to/ranlib",
             "-DCMAKE_MAKE_PROGRAM=" + self.which_ninja(args)])

    def test_common_options_coverage_sanitizer(self):
        args = self.default_args()
        args.enable_sanitize_coverage = True
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.common_options()),
            ["-G", "Ninja",
             "-DLLVM_USE_SANITIZE_COVERAGE=ON",
             "-DCMAKE_C_COMPILER:PATH=/path/to/clang",
             "-DCMAKE_CXX_COMPILER:PATH=/path/to/clang++",
             "-DCMAKE_Swift_COMPILER:PATH=/path/to/swiftc",
             "-DCMAKE_LIBTOOL:PATH=/path/to/libtool",
             "-DCMAKE_AR:PATH=/path/to/ar",
             "-DCMAKE_RANLIB:PATH=/path/to/ranlib",
             "-DCMAKE_MAKE_PROGRAM=" + self.which_ninja(args)])

    def test_common_options_export_compile_commands(self):
        args = self.default_args()
        args.export_compile_commands = True
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.common_options()),
            ["-G", "Ninja",
             "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
             "-DCMAKE_C_COMPILER:PATH=/path/to/clang",
             "-DCMAKE_CXX_COMPILER:PATH=/path/to/clang++",
             "-DCMAKE_Swift_COMPILER:PATH=/path/to/swiftc",
             "-DCMAKE_LIBTOOL:PATH=/path/to/libtool",
             "-DCMAKE_AR:PATH=/path/to/ar",
             "-DCMAKE_RANLIB:PATH=/path/to/ranlib",
             "-DCMAKE_MAKE_PROGRAM=" + self.which_ninja(args)])

    def test_common_options_distcc(self):
        args = self.default_args()
        args.distcc = True
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.common_options()),
            ["-G", "Ninja",
             "-DCMAKE_C_COMPILER_LAUNCHER:PATH=" + self.mock_distcc_path(),
             "-DCMAKE_CXX_COMPILER_LAUNCHER:PATH=" + self.mock_distcc_path(),
             "-DCMAKE_C_COMPILER:PATH=/path/to/clang",
             "-DCMAKE_CXX_COMPILER:PATH=/path/to/clang++",
             "-DCMAKE_Swift_COMPILER:PATH=/path/to/swiftc",
             "-DCMAKE_LIBTOOL:PATH=/path/to/libtool",
             "-DCMAKE_AR:PATH=/path/to/ar",
             "-DCMAKE_RANLIB:PATH=/path/to/ranlib",
             "-DCMAKE_MAKE_PROGRAM=" + self.which_ninja(args)])

    def test_common_options_sccache(self):
        args = self.default_args()
        args.sccache = True
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.common_options()),
            ["-G", "Ninja",
             "-DCMAKE_C_COMPILER_LAUNCHER:PATH=" + self.mock_sccache_path(),
             "-DCMAKE_CXX_COMPILER_LAUNCHER:PATH=" + self.mock_sccache_path(),
             "-DCMAKE_C_COMPILER:PATH=/path/to/clang",
             "-DCMAKE_CXX_COMPILER:PATH=/path/to/clang++",
             "-DCMAKE_Swift_COMPILER:PATH=/path/to/swiftc",
             "-DCMAKE_LIBTOOL:PATH=/path/to/libtool",
             "-DCMAKE_AR:PATH=/path/to/ar",
             "-DCMAKE_RANLIB:PATH=/path/to/ranlib",
             "-DCMAKE_MAKE_PROGRAM=" + self.which_ninja(args)])

    def test_common_options_launcher(self):
        args = self.default_args()
        cmake_c_launcher = "/path/to/c_launcher"
        cmake_cxx_launcher = "/path/to/cxx_launcher"
        args.cmake_c_launcher = cmake_c_launcher
        args.cmake_cxx_launcher = cmake_cxx_launcher
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.common_options()),
            ["-G", "Ninja",
             "-DCMAKE_C_COMPILER_LAUNCHER:PATH=" + cmake_c_launcher,
             "-DCMAKE_CXX_COMPILER_LAUNCHER:PATH=" + cmake_cxx_launcher,
             "-DCMAKE_C_COMPILER:PATH=/path/to/clang",
             "-DCMAKE_CXX_COMPILER:PATH=/path/to/clang++",
             "-DCMAKE_Swift_COMPILER:PATH=/path/to/swiftc",
             "-DCMAKE_LIBTOOL:PATH=/path/to/libtool",
             "-DCMAKE_AR:PATH=/path/to/ar",
             "-DCMAKE_RANLIB:PATH=/path/to/ranlib",
             "-DCMAKE_MAKE_PROGRAM=" + self.which_ninja(args)])

    def test_common_options_xcode(self):
        args = self.default_args()
        args.cmake_generator = 'Xcode'
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.common_options()),
            ["-G", "Xcode",
             "-DCMAKE_C_COMPILER:PATH=/path/to/clang",
             "-DCMAKE_CXX_COMPILER:PATH=/path/to/clang++",
             "-DCMAKE_Swift_COMPILER:PATH=/path/to/swiftc",
             "-DCMAKE_LIBTOOL:PATH=/path/to/libtool",
             "-DCMAKE_AR:PATH=/path/to/ar",
             "-DCMAKE_RANLIB:PATH=/path/to/ranlib",
             "-DCMAKE_CONFIGURATION_TYPES=" +
             "Debug;Release;MinSizeRel;RelWithDebInfo"])

    def test_common_options_clang_compiler_version(self):
        args = self.default_args()
        args.clang_compiler_version = Version("999.0.999")
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.common_options()),
            ["-G", "Ninja",
             "-DCMAKE_C_COMPILER:PATH=/path/to/clang",
             "-DCMAKE_CXX_COMPILER:PATH=/path/to/clang++",
             "-DCMAKE_Swift_COMPILER:PATH=/path/to/swiftc",
             "-DCMAKE_LIBTOOL:PATH=/path/to/libtool",
             "-DCMAKE_AR:PATH=/path/to/ar",
             "-DCMAKE_RANLIB:PATH=/path/to/ranlib",
             "-DCMAKE_MAKE_PROGRAM=" + self.which_ninja(args)])

    def test_common_options_clang_user_visible_version(self):
        args = self.default_args()
        args.clang_user_visible_version = Version("9.0.0")
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.common_options()),
            ["-G", "Ninja",
             "-DCMAKE_C_COMPILER:PATH=/path/to/clang",
             "-DCMAKE_CXX_COMPILER:PATH=/path/to/clang++",
             "-DCMAKE_Swift_COMPILER:PATH=/path/to/swiftc",
             "-DCMAKE_LIBTOOL:PATH=/path/to/libtool",
             "-DCMAKE_AR:PATH=/path/to/ar",
             "-DCMAKE_RANLIB:PATH=/path/to/ranlib",
             "-DLLVM_VERSION_MAJOR:STRING=9",
             "-DLLVM_VERSION_MINOR:STRING=0",
             "-DLLVM_VERSION_PATCH:STRING=0",
             "-DCLANG_VERSION_MAJOR:STRING=9",
             "-DCLANG_VERSION_MINOR:STRING=0",
             "-DCLANG_VERSION_PATCH:STRING=0",
             "-DCMAKE_MAKE_PROGRAM=" + self.which_ninja(args)])

    def test_common_options_build_ninja(self):
        args = self.default_args()
        args.build_ninja = True
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.common_options()),
            ["-G", "Ninja",
             "-DCMAKE_C_COMPILER:PATH=/path/to/clang",
             "-DCMAKE_CXX_COMPILER:PATH=/path/to/clang++",
             "-DCMAKE_Swift_COMPILER:PATH=/path/to/swiftc",
             "-DCMAKE_LIBTOOL:PATH=/path/to/libtool",
             "-DCMAKE_AR:PATH=/path/to/ar",
             "-DCMAKE_RANLIB:PATH=/path/to/ranlib",
             "-DCMAKE_MAKE_PROGRAM=" + self.which_ninja(args)])

    def test_common_options_full(self):
        args = self.default_args()
        args.enable_asan = True
        args.enable_ubsan = True
        args.export_compile_commands = True
        args.distcc = True
        args.cmake_generator = 'Xcode'
        args.clang_user_visible_version = Version("9.0.0")
        args.clang_compiler_version = Version("999.0.900")
        args.build_ninja = True
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.common_options()),
            ["-G", "Xcode",
             "-DLLVM_USE_SANITIZER=Address;Undefined",
             "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
             "-DCMAKE_C_COMPILER_LAUNCHER:PATH=" + self.mock_distcc_path(),
             "-DCMAKE_CXX_COMPILER_LAUNCHER:PATH=" + self.mock_distcc_path(),
             "-DCMAKE_C_COMPILER:PATH=/path/to/clang",
             "-DCMAKE_CXX_COMPILER:PATH=/path/to/clang++",
             "-DCMAKE_Swift_COMPILER:PATH=/path/to/swiftc",
             "-DCMAKE_LIBTOOL:PATH=/path/to/libtool",
             "-DCMAKE_AR:PATH=/path/to/ar",
             "-DCMAKE_RANLIB:PATH=/path/to/ranlib",
             "-DCMAKE_CONFIGURATION_TYPES=" +
             "Debug;Release;MinSizeRel;RelWithDebInfo",
             "-DLLVM_VERSION_MAJOR:STRING=9",
             "-DLLVM_VERSION_MINOR:STRING=0",
             "-DLLVM_VERSION_PATCH:STRING=0",
             "-DCLANG_VERSION_MAJOR:STRING=9",
             "-DCLANG_VERSION_MINOR:STRING=0",
             "-DCLANG_VERSION_PATCH:STRING=0"])
        # NOTE: No "-DCMAKE_MAKE_PROGRAM=/path/to/built/ninja" because
        #       cmake_generator is 'Xcode'

    def test_build_args_ninja(self):
        args = self.default_args()
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.build_args()),
            ["-j8"])

        args.verbose_build = True
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.build_args()),
            ["-j8", "-v"])

    def test_build_args_makefile(self):
        args = self.default_args()
        args.cmake_generator = "Unix Makefiles"
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.build_args()),
            ["-j8"])

        args.verbose_build = True
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.build_args()),
            ["-j8", "VERBOSE=1"])

    def test_build_args_xcode(self):
        args = self.default_args()
        args.cmake_generator = "Xcode"
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.build_args()),
            ["-parallelizeTargets", "-jobs", "8"])

        # NOTE: Xcode generator DOES NOT take 'verbose-build' into account.
        args.verbose_build = True
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.build_args()),
            ["-parallelizeTargets", "-jobs", "8"])

    def test_build_args_eclipse_ninja(self):
        # NOTE: Eclipse generator DOES NOT take 'build-jobs' into account,
        #       nor 'verbose-build'.
        args = self.default_args()
        args.cmake_generator = "Eclipse CDT4 - Ninja"
        args.verbose_build = True
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.build_args()), [])

    def test_build_args_custom_build_args(self):
        args = self.default_args()
        args.build_args = ["-foo", "bar baz"]
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.build_args()),
            ["-foo", "bar baz", "-j8"])

    def test_build_args_distcc(self):
        args = self.default_args()
        args.distcc = True
        cmake = self.cmake(args)
        self.assertEqual(
            list(cmake.build_args()),
            ["-j6"])


class CMakeOptionsTestCase(unittest.TestCase):
    def test_define(self):
        options = CMakeOptions()

        options.define('OPT1:STRING', 'foo')

        options.define('OPT2:BOOL', True)
        options.define('OPT3:BOOL', 1)
        options.define('OPT4:BOOL', 'True')
        options.define('OPT5:BOOL', 'true')
        options.define('OPT6:BOOL', 'YES')
        options.define('OPT7:BOOL', '1')

        options.define('OPT8:BOOL', False)
        options.define('OPT9:BOOL', 0)
        options.define('OPT10:BOOL', 'false')
        options.define('OPT11:BOOL', 'False')
        options.define('OPT12:BOOL', 'No')
        options.define('OPT13:BOOL', '0')

        options.define('OPT14', 12)
        options.define('OPT15', '')
        options.define('OPT16', None)
        options.define('OPT17:PATH', 'foo')

        self.assertRaises(ValueError, options.define, 'ERR', ["FOO"])
        self.assertRaises(ValueError, options.define, 'ERR', {"FOO": 1})

        self.assertRaises(ValueError, options.define, 'ERR:BOOL', None)
        self.assertRaises(ValueError, options.define, 'ERR:BOOL', 3)
        self.assertRaises(ValueError, options.define, 'ERR:BOOL', 'foo')
        self.assertRaises(ValueError, options.define, 'ERR:BOOL', [1])

        self.assertEqual(list(options), [
            '-DOPT1:STRING=foo',
            '-DOPT2:BOOL=TRUE',
            '-DOPT3:BOOL=TRUE',
            '-DOPT4:BOOL=TRUE',
            '-DOPT5:BOOL=TRUE',
            '-DOPT6:BOOL=TRUE',
            '-DOPT7:BOOL=TRUE',
            '-DOPT8:BOOL=FALSE',
            '-DOPT9:BOOL=FALSE',
            '-DOPT10:BOOL=FALSE',
            '-DOPT11:BOOL=FALSE',
            '-DOPT12:BOOL=FALSE',
            '-DOPT13:BOOL=FALSE',
            '-DOPT14=12',
            '-DOPT15=',
            '-DOPT16=',
            '-DOPT17:PATH=foo'])

    def test_operations(self):

        options1 = CMakeOptions()
        options1.define("OPT1_1", 'VAL1')
        options1.define("OPT1_2", 'VAL2')

        options2 = CMakeOptions()
        options2.define("OPT2_1", 'VAL3')

        options = options1 + options2
        self.assertIsInstance(options, CMakeOptions)
        self.assertEqual(list(options), [
            "-DOPT1_1=VAL1",
            "-DOPT1_2=VAL2",
            "-DOPT2_1=VAL3"])

        options_added = options + ["-CUSTOM", "12"]
        self.assertIsInstance(options_added, CMakeOptions)
        self.assertEqual(list(options_added), [
            "-DOPT1_1=VAL1",
            "-DOPT1_2=VAL2",
            "-DOPT2_1=VAL3",
            "-CUSTOM", "12"])

        options += options2
        self.assertIsInstance(options, CMakeOptions)
        self.assertEqual(list(options), [
            "-DOPT1_1=VAL1",
            "-DOPT1_2=VAL2",
            "-DOPT2_1=VAL3",
            "-DOPT2_1=VAL3"])

        options += ["-G", "Ninja"]
        self.assertIsInstance(options, CMakeOptions)
        self.assertEqual(list(options), [
            "-DOPT1_1=VAL1",
            "-DOPT1_2=VAL2",
            "-DOPT2_1=VAL3",
            "-DOPT2_1=VAL3",
            "-G", "Ninja"])

        list_options = ["-G", "Ninja"]
        list_options += options1
        self.assertIsInstance(list_options, list)
        self.assertEqual(list_options, [
            "-G", "Ninja",
            "-DOPT1_1=VAL1",
            "-DOPT1_2=VAL2"])

    def test_initial_options_with_tuples(self):
        options = CMakeOptions([('FOO', 'foo'), ('BAR', True)])
        self.assertIn('-DFOO=foo', options)
        self.assertIn('-DBAR=TRUE', options)

    def test_initial_options_with_other_options(self):
        options = CMakeOptions()
        options.define('FOO', 'foo')
        options.define('BAR', True)
        derived = CMakeOptions(options)
        self.assertIn('-DFOO=foo', derived)
        self.assertIn('-DBAR=TRUE', derived)

    def test_booleans_are_translated(self):
        options = CMakeOptions()
        options.define('A_BOOLEAN_OPTION', True)
        options.define('ANOTHER_BOOLEAN_OPTION', False)
        self.assertIn('-DA_BOOLEAN_OPTION=TRUE', options)
        self.assertIn('-DANOTHER_BOOLEAN_OPTION=FALSE', options)

    def test_extend_with_other_options(self):
        options = CMakeOptions()
        options.define('FOO', 'foo')
        options.define('BAR', True)
        derived = CMakeOptions()
        derived.extend(options)
        self.assertIn('-DFOO=foo', derived)
        self.assertIn('-DBAR=TRUE', derived)

    def test_extend_with_tuples(self):
        options = CMakeOptions()
        options.extend([('FOO', 'foo'), ('BAR', True)])
        self.assertIn('-DFOO=foo', options)
        self.assertIn('-DBAR=TRUE', options)

    def test_contains(self):
        options = CMakeOptions()
        self.assertTrue('-DFOO=foo' not in options)
        options.define('FOO', 'foo')
        self.assertTrue('-DFOO=foo' in options)


if __name__ == '__main__':
    unittest.main()
