#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2018 Apple Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1.  Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
# 2.  Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import atexit
import lldb
import lldb_webkit
import os
import sys
import unittest

from webkitpy.common.system.systemhost import SystemHost

# Run just the tests in this file with ./Tools/Scripts/test-webkitpy lldb_webkit_unittest

# We cache the lldb debug session state so that we don't create it again for each call to a serial_test_ method.
# We store it in a global variable so that we can delete this cached state on exit(3).
# FIXME: Remove this once test-webkitpy supports class and module fixtures (i.e. setUpClass()/setUpModule()
# are called exactly once per class/module).
cached_debug_session = None


# FIXME: Remove this once test-webkitpy supports class and module fixtures (i.e. setUpClass()/setUpModule()
# are called exactly once per class/module).
@atexit.register
def destroy_cached_debug_session():
    if cached_debug_session:
        cached_debug_session.tearDown()


class LLDBDebugSession(object):
    @classmethod
    def setup(cls):
        BREAK_FOR_TESTING_FUNCTION_NAME = 'breakForTestingSummaryProviders'

        cls.sbDebugger = lldb.SBDebugger.Create()
        cls.sbDebugger.SetAsync(False)

        cls.lldbWebKitTesterExecutable = str(os.environ['LLDB_WEBKIT_TESTER_EXECUTABLE'])

        cls.sbTarget = cls.sbDebugger.CreateTarget(cls.lldbWebKitTesterExecutable)
        assert cls.sbTarget
        cls.sbTarget.BreakpointCreateByName(BREAK_FOR_TESTING_FUNCTION_NAME, cls.sbTarget.GetExecutable().GetFilename())

        argv = None
        envp = None
        cls.sbProcess = cls.sbTarget.LaunchSimple(argv, envp, os.getcwd())
        assert cls.sbProcess
        assert cls.sbProcess.GetState() == lldb.eStateStopped

        cls.sbThread = cls.sbProcess.GetThreadAtIndex(0)
        assert cls.sbThread

        # Frame 0 is the function with name BREAK_FOR_TESTING_FUNCTION_NAME. We want the frame of the caller of
        # BREAK_FOR_TESTING_FUNCTION_NAME because it has all the interesting local variables we want to test.
        cls.sbFrame = cls.sbThread.GetFrameAtIndex(1)
        assert cls.sbFrame

    @classmethod
    def tearDown(cls):
        cls.sbProcess.Kill()


class TestSummaryProviders(unittest.TestCase):
    @classmethod
    def shouldSkip(cls):
        return not SystemHost.get_default().platform.is_mac()

    @classmethod
    def setUpClass(cls):
        global cached_debug_session
        if not cached_debug_session:
            cached_debug_session = LLDBDebugSession()
            cached_debug_session.setup()

    @property
    def _sbFrame(self):
        return cached_debug_session.sbFrame

    # The LLDB Python module does not work with Python multiprocessing and causes errors of the form:
    #     objc[76794]: +[__MDQuery initialize] may have been in progress in another thread when fork() was called.
    #                  We cannot safely call it or ignore it in the fork() child process. Crashing instead.
    # So, we run the following tests serially.

    # MARK: WTFStringImpl_SummaryProvider test cases

    def serial_test_WTFStringImpl_SummaryProvider_null_string(self):
        summary = lldb_webkit.WTFStringImpl_SummaryProvider(self._sbFrame.FindVariable('aNullStringImpl'), {})
        self.assertEqual(summary, "{ length = 0, is8bit = 1, contents = '' }")

    def serial_test_WTFStringImpl_SummaryProvider_empty_string(self):
        summary = lldb_webkit.WTFStringImpl_SummaryProvider(self._sbFrame.FindVariable('anEmptyStringImpl'), {})
        self.assertEqual(summary, "{ length = 0, is8bit = 1, contents = '' }")

    def serial_test_WTFStringImpl_SummaryProvider_8bit_string(self):
        summary = lldb_webkit.WTFStringImpl_SummaryProvider(self._sbFrame.FindVariable('an8BitStringImpl'), {})
        self.assertEqual(summary, u"{ length = 8, is8bit = 1, contents = 'r\xe9sum\xe9' }")

    def serial_test_WTFStringImpl_SummaryProvider_16bit_string(self):
        summary = lldb_webkit.WTFStringImpl_SummaryProvider(self._sbFrame.FindVariable('a16BitStringImpl'), {})
        self.assertEqual(summary, u"{ length = 13, is8bit = 0, contents = '\u1680Cappuccino\u1680\x00' }")

    # MARK: WTFString_SummaryProvider test cases

    def serial_test_WTFString_SummaryProvider_null_string(self):
        summary = lldb_webkit.WTFString_SummaryProvider(self._sbFrame.FindVariable('aNullString'), {})
        self.assertEqual(summary, "{ length = 0, contents = '' }")

    def serial_test_WTFString_SummaryProvider_empty_string(self):
        summary = lldb_webkit.WTFString_SummaryProvider(self._sbFrame.FindVariable('anEmptyString'), {})
        self.assertEqual(summary, "{ length = 0, contents = '' }")

    def serial_test_WTFString_SummaryProvider_8bit_string(self):
        summary = lldb_webkit.WTFString_SummaryProvider(self._sbFrame.FindVariable('an8BitString'), {})
        self.assertEqual(summary, u"{ length = 8, contents = 'r\xe9sum\xe9' }")

    def serial_test_WTFString_SummaryProvider_16bit_string(self):
        summary = lldb_webkit.WTFString_SummaryProvider(self._sbFrame.FindVariable('a16BitString'), {})
        self.assertEqual(summary, u"{ length = 13, contents = '\u1680Cappuccino\u1680\x00' }")

    # MARK: WTFVector_SummaryProvider test cases

    def serial_test_WTFVectorProvider_empty_vector(self):
        variable = self._sbFrame.FindVariable('anEmptyVector');
        summary = lldb_webkit.WTFVector_SummaryProvider(variable, {})
        self.assertEqual(summary, "{ size = 0, capacity = 0 }")

    def serial_test_WTFVectorProvider_vector_size_and_capacity(self):
        variable = self._sbFrame.FindVariable('aVectorWithOneItem');
        summary = lldb_webkit.WTFVector_SummaryProvider(variable, {})
        self.assertEqual(summary, "{ size = 1, capacity = 16 }")

    # MARK: WTFHashMap_SummaryProvider and WTFHashSet_SummaryProvider test cases

    def serial_test_WTFHashMap_tablesize_and_size(self):
        variable = self._sbFrame.FindVariable('hashMapOfInts')
        summary = lldb_webkit.WTFHashMap_SummaryProvider(variable, {})
        self.assertEqual(summary, "{ tableSize = 8, keyCount = 2 }")

    def serial_test_WTFHashMap_of_vectors_tablesize_and_size(self):
        variable = self._sbFrame.FindVariable('hashMapOfVectors')
        summary = lldb_webkit.WTFHashMap_SummaryProvider(variable, {})
        self.assertEqual(summary, "{ tableSize = 8, keyCount = 1 }")

    def serial_test_WTFHashSet_tablesize_and_size(self):
        variable = self._sbFrame.FindVariable('hashSetOfInts')
        summary = lldb_webkit.WTFHashSet_SummaryProvider(variable, {})
        self.assertEqual(summary, "{ tableSize = 8, keyCount = 1 }")

    # MARK: WTFOptionSet_SummaryProvider test cases

    def serial_test_WTFOptionSet_SummaryProvider_empty(self):
        variable = self._sbFrame.FindVariable('exampleFlagsEmpty')
        summary = lldb_webkit.WTFOptionSet_SummaryProvider(variable, {})
        self.assertEqual(summary, "{ size = 0 }")

    def serial_test_WTFOptionSet_SummaryProvider_simple(self):
        variable = self._sbFrame.FindVariable('exampleFlagsSimple')
        summary = lldb_webkit.WTFOptionSet_SummaryProvider(variable, {})
        self.assertEqual(summary, "{ size = 3 }")

    # MARK: WTFOptionSetProvider test cases

    def serial_test_WTFOptionSetProvider_empty(self):
        variable = self._sbFrame.FindVariable('exampleFlagsEmpty')
        provider = lldb_webkit.WTFOptionSetProvider(variable, {})
        self.assertEqual(provider.get_child_at_index(0).GetName(), 'm_storage')

    def serial_test_WTFOptionSetProvider_simple(self):
        variable = self._sbFrame.FindVariable('exampleFlagsSimple')
        provider = lldb_webkit.WTFOptionSetProvider(variable, {})
        self.assertEqual(provider.get_child_at_index(0).GetName(), 'A')
        self.assertEqual(provider.get_child_at_index(0).GetValue(), '1')
        self.assertEqual(provider.get_child_at_index(1).GetName(), 'C')
        self.assertEqual(provider.get_child_at_index(1).GetValue(), '4')
        self.assertEqual(provider.get_child_at_index(2).GetName(), 'D')
        self.assertEqual(provider.get_child_at_index(2).GetValue(), '8')

    def serial_test_WTFOptionSetProvider_aliased_flag(self):
        variable = self._sbFrame.FindVariable('exampleFlagsAliasedFlag')
        provider = lldb_webkit.WTFOptionSetProvider(variable, {})
        self.assertEqual(provider.get_child_at_index(0).GetName(), 'A')
        self.assertEqual(provider.get_child_at_index(0).GetValue(), '1')
        self.assertEqual(provider.get_child_at_index(1).GetName(), 'D')
        self.assertEqual(provider.get_child_at_index(1).GetValue(), '8')

    # MARK: WTFURL_SummaryProvider test cases

    def serial_test_WTFURL_SummaryProvider(self):
        variable = lldb_webkit.WTFURL_SummaryProvider(self._sbFrame.FindVariable('aURL'), {})
        self.assertEqual(variable, '{ https://www.example.com/ }')

    # MARK: WTFCompactPointerTuple test cases

    def serial_test_WTFCompactPointerTuple_SummaryProvider_empty(self):
        variable = self._sbFrame.FindVariable('exampleCompactPointerTupleEmpty')
        summary = lldb_webkit.WTFCompactPointerTuple_SummaryProvider(variable, {})
        self.assertEqual(summary, "{ type = 0 }")

    def serial_test_WTFCompactPointerTuple_SummaryProvider_simple(self):
        variable = self._sbFrame.FindVariable('exampleCompactPointerTupleSimple')
        summary = lldb_webkit.WTFCompactPointerTuple_SummaryProvider(variable, {})
        self.assertEqual(summary, "{ type = 7 }")

    def serial_test_WTFCompactPointerTuple_SummaryProvider_max_type_value(self):
        variable = self._sbFrame.FindVariable('exampleCompactPointerTupleMaxTypeValue')
        summary = lldb_webkit.WTFCompactPointerTuple_SummaryProvider(variable, {})
        self.assertEqual(summary, "{ type = 255 }")

    def serial_test_WTFCompactPointerTuple_SummaryProvider_bool(self):
        variable = self._sbFrame.FindVariable('exampleCompactPointerTupleSimpleBool')
        summary = lldb_webkit.WTFCompactPointerTuple_SummaryProvider(variable, {})
        self.assertEqual(summary, "{ type = true }")

    def serial_test_WTFCompactPointerTupleProvider_empty(self):
        variable = self._sbFrame.FindVariable('exampleCompactPointerTupleEmpty')
        provider = lldb_webkit.WTFCompactPointerTupleProvider(variable, {})
        self.assertEqual(provider.get_child_at_index(0).GetName(), '[0]')
        self.assertEqual(provider.get_child_at_index(0).GetValueAsUnsigned(), 0)
        self.assertEqual(provider.get_child_at_index(1).GetName(), '[1]')
        self.assertEqual(provider.get_child_at_index(1).GetValueAsUnsigned(), 0)

    def serial_test_WTFCompactPointerTupleProvider_simple(self):
        variable = self._sbFrame.FindVariable('exampleCompactPointerTupleSimple')
        provider = lldb_webkit.WTFCompactPointerTupleProvider(variable, {})
        self.assertEqual(provider.get_child_at_index(0).GetName(), '[0]')
        self.assertNotEqual(provider.get_child_at_index(0).GetValueAsUnsigned(), 0)
        self.assertEqual(provider.get_child_at_index(1).GetName(), '[1]')
        self.assertEqual(provider.get_child_at_index(1).GetValueAsUnsigned(), 7)

    def serial_test_WTFCompactPointerTupleProvider_max_type_value(self):
        variable = self._sbFrame.FindVariable('exampleCompactPointerTupleMaxTypeValue')
        provider = lldb_webkit.WTFCompactPointerTupleProvider(variable, {})
        self.assertEqual(provider.get_child_at_index(0).GetName(), '[0]')
        self.assertNotEqual(provider.get_child_at_index(0).GetValueAsUnsigned(), 0)
        self.assertEqual(provider.get_child_at_index(1).GetName(), '[1]')
        self.assertEqual(provider.get_child_at_index(1).GetValueAsUnsigned(), 255)

    def serial_test_WTFCompactPointerTupleProvider_simple_bool(self):
        variable = self._sbFrame.FindVariable('exampleCompactPointerTupleSimpleBool')
        provider = lldb_webkit.WTFCompactPointerTupleProvider(variable, {})
        self.assertEqual(provider.get_child_at_index(0).GetName(), '[0]')
        self.assertNotEqual(provider.get_child_at_index(0).GetValueAsUnsigned(), 0)
        self.assertEqual(provider.get_child_at_index(1).GetName(), '[1]')
        self.assertEqual(provider.get_child_at_index(1).GetValue(), 'true')
