## @file
# This file contain unit test for DecParser
#
# Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR>
#
# SPDX-License-Identifier: BSD-2-Clause-Patent

import os
import unittest
from edk2basetools.UPT.Logger.Log import FatalError

from edk2basetools.UPT.Parser.DecParser import \
    Dec, \
    _DecDefine, \
    _DecLibraryclass, \
    _DecPcd, \
    _DecGuid, \
    FileContent, \
    _DecBase, \
    CleanString

from edk2basetools.UPT.Object.Parser.DecObject import _DecComments

#
# Test CleanString
#
class CleanStringTestCase(unittest.TestCase):
    def test_clean_string(self):
        Line, Comment = CleanString('')
        self.assertEqual(Line, '')
        self.assertEqual(Comment, '')

        Line, Comment = CleanString('line without comment')
        self.assertEqual(Line, 'line without comment')
        self.assertEqual(Comment, '')

        Line, Comment = CleanString('# pure comment')
        self.assertEqual(Line, '')
        self.assertEqual(Comment, '# pure comment')

        Line, Comment = CleanString('line # and comment')
        self.assertEqual(Line, 'line')
        self.assertEqual(Comment, '# and comment')

    def test_clean_string_cpp(self):
        Line, Comment = CleanString('line // and comment', AllowCppStyleComment = True)
        self.assertEqual(Line, 'line')
        self.assertEqual(Comment, '# and comment')

#
# Test _DecBase._MacroParser function
#
class MacroParserTestCase(unittest.TestCase):
    def setUp(self):
        self.dec = _DecBase(FileContent('dummy', []))

    def testCorrectMacro(self):
        self.dec._MacroParser('DEFINE MACRO1 = test1')
        self.assertFalse('MACRO1' not in self.dec._LocalMacro)
        self.assertEqual(self.dec._LocalMacro['MACRO1'], 'test1')

    def testErrorMacro1(self):
        # Raise fatal error, macro name must be upper case letter
        self.assertRaises(FatalError, self.dec._MacroParser, 'DEFINE not_upper_case = test2')

    def testErrorMacro2(self):
        # No macro name given
        self.assertRaises(FatalError, self.dec._MacroParser, 'DEFINE ')

#
# Test _DecBase._TryBackSlash function
#
class TryBackSlashTestCase(unittest.TestCase):
    def setUp(self):
        Content = [
            # Right case
            'test no backslash',

            'test with backslash \\',
            'continue second line',

            # Do not precede with whitespace
            '\\',

            # Empty line after backlash is not allowed
            'line with backslash \\',
            ''
        ]
        self.dec = _DecBase(FileContent('dummy', Content))

    def testBackSlash(self):
        #
        # Right case, assert return values
        #
        ConcatLine, CommentList = self.dec._TryBackSlash(self.dec._RawData.GetNextLine(), [])
        self.assertEqual(ConcatLine, 'test no backslash')
        self.assertEqual(CommentList, [])

        ConcatLine, CommentList = self.dec._TryBackSlash(self.dec._RawData.GetNextLine(), [])
        self.assertEqual(CommentList, [])
        self.assertEqual(ConcatLine, 'test with backslash continue second line')

        #
        # Error cases, assert raise exception
        #
        self.assertRaises(FatalError, self.dec._TryBackSlash, self.dec._RawData.GetNextLine(), [])
        self.assertRaises(FatalError, self.dec._TryBackSlash, self.dec._RawData.GetNextLine(), [])

#
# Test _DecBase.Parse function
#
class DataItem(_DecComments):
    def __init__(self):
        _DecComments.__init__(self)
        self.String = ''

class Data(_DecComments):
    def __init__(self):
        _DecComments.__init__(self)
        # List of DataItem
        self.ItemList = []

class DecTestInner(_DecBase):
    def __init__(self, RawData):
        _DecBase.__init__(self, RawData)
        self.ItemObject = Data()

    def _StopCurrentParsing(self, Line):
        return Line == '[TOP]'

    def _ParseItem(self):
        Item = DataItem()
        Item.String = self._RawData.CurrentLine
        self.ItemObject.ItemList.append(Item)
        return Item

    def _TailCommentStrategy(self, Comment):
        return Comment.find('@comment') != -1

class DecTestTop(_DecBase):
    def __init__(self, RawData):
        _DecBase.__init__(self, RawData)
        # List of Data
        self.ItemObject = []

    # Top parser
    def _StopCurrentParsing(self, Line):
        return False

    def _ParseItem(self):
        TestParser = DecTestInner(self._RawData)
        TestParser.Parse()
        self.ItemObject.append(TestParser.ItemObject)
        return TestParser.ItemObject

class ParseTestCase(unittest.TestCase):
    def setUp(self):
        pass

    def testParse(self):
        Content = \
        '''# Top comment
        [TOP]
          # sub1 head comment
          (test item has both head and tail comment) # sub1 tail comment
          # sub2 head comment
          (test item has head and special tail comment)
          # @comment test TailCommentStrategy branch

          (test item has no comment)

        # test NextLine branch
        [TOP]
          sub-item
        '''
        dec = DecTestTop(FileContent('dummy', Content.splitlines()))
        dec.Parse()

        # Two sections
        self.assertEqual(len(dec.ItemObject), 2)

        data = dec.ItemObject[0]
        self.assertEqual(data._HeadComment[0][0], '# Top comment')
        self.assertEqual(data._HeadComment[0][1], 1)

        # 3 subitems
        self.assertEqual(len(data.ItemList), 3)

        dataitem = data.ItemList[0]
        self.assertEqual(dataitem.String, '(test item has both head and tail comment)')
        # Comment content
        self.assertEqual(dataitem._HeadComment[0][0], '# sub1 head comment')
        self.assertEqual(dataitem._TailComment[0][0], '# sub1 tail comment')
        # Comment line number
        self.assertEqual(dataitem._HeadComment[0][1], 3)
        self.assertEqual(dataitem._TailComment[0][1], 4)

        dataitem = data.ItemList[1]
        self.assertEqual(dataitem.String, '(test item has head and special tail comment)')
        # Comment content
        self.assertEqual(dataitem._HeadComment[0][0], '# sub2 head comment')
        self.assertEqual(dataitem._TailComment[0][0], '# @comment test TailCommentStrategy branch')
        # Comment line number
        self.assertEqual(dataitem._HeadComment[0][1], 5)
        self.assertEqual(dataitem._TailComment[0][1], 7)

        dataitem = data.ItemList[2]
        self.assertEqual(dataitem.String, '(test item has no comment)')
        # Comment content
        self.assertEqual(dataitem._HeadComment, [])
        self.assertEqual(dataitem._TailComment, [])

        data = dec.ItemObject[1]
        self.assertEqual(data._HeadComment[0][0], '# test NextLine branch')
        self.assertEqual(data._HeadComment[0][1], 11)

        # 1 subitems
        self.assertEqual(len(data.ItemList), 1)

        dataitem = data.ItemList[0]
        self.assertEqual(dataitem.String, 'sub-item')
        self.assertEqual(dataitem._HeadComment, [])
        self.assertEqual(dataitem._TailComment, [])

#
# Test _DecDefine._ParseItem
#
class DecDefineTestCase(unittest.TestCase):
    def GetObj(self, Content):
        Obj = _DecDefine(FileContent('dummy', Content.splitlines()))
        Obj._RawData.CurrentLine = Obj._RawData.GetNextLine()
        return Obj

    def testDecDefine(self):
        item = self.GetObj('PACKAGE_NAME = MdePkg')._ParseItem()
        self.assertEqual(item.Key, 'PACKAGE_NAME')
        self.assertEqual(item.Value, 'MdePkg')

    def testDecDefine1(self):
        obj = self.GetObj('PACKAGE_NAME')
        self.assertRaises(FatalError, obj._ParseItem)

    def testDecDefine2(self):
        obj = self.GetObj('unknown_key = ')
        self.assertRaises(FatalError, obj._ParseItem)

    def testDecDefine3(self):
        obj = self.GetObj('PACKAGE_NAME = ')
        self.assertRaises(FatalError, obj._ParseItem)

#
# Test _DecLibraryclass._ParseItem
#
class DecLibraryTestCase(unittest.TestCase):
    def GetObj(self, Content):
        Obj = _DecLibraryclass(FileContent('dummy', Content.splitlines()))
        Obj._RawData.CurrentLine = Obj._RawData.GetNextLine()
        return Obj

    def testNoInc(self):
        obj = self.GetObj('UefiRuntimeLib')
        self.assertRaises(FatalError, obj._ParseItem)

    def testEmpty(self):
        obj = self.GetObj(' | ')
        self.assertRaises(FatalError, obj._ParseItem)

    def testLibclassNaming(self):
        obj = self.GetObj('lowercase_efiRuntimeLib|Include/Library/UefiRuntimeLib.h')
        self.assertRaises(FatalError, obj._ParseItem)

    def testLibclassExt(self):
        obj = self.GetObj('RuntimeLib|Include/Library/UefiRuntimeLib.no_h')
        self.assertRaises(FatalError, obj._ParseItem)

    def test_libclass_relative(self):
        obj = self.GetObj('RuntimeLib|Include/../UefiRuntimeLib.h')
        self.assertRaises(FatalError, obj._ParseItem)

#
# Test _DecPcd._ParseItem
#
class DecPcdTestCase(unittest.TestCase):
    def GetObj(self, Content):
        Obj = _DecPcd(FileContent('dummy', Content.splitlines()))
        Obj._RawData.CurrentLine = Obj._RawData.GetNextLine()
        Obj._RawData.CurrentScope = [('PcdsFeatureFlag'.upper(), 'COMMON')]
        return Obj

    def testOK(self):
        item = self.GetObj('gEfiMdePkgTokenSpaceGuid.PcdComponentNameDisable|FALSE|BOOLEAN|0x0000000d')._ParseItem()
        self.assertEqual(item.TokenSpaceGuidCName, 'gEfiMdePkgTokenSpaceGuid')
        self.assertEqual(item.TokenCName, 'PcdComponentNameDisable')
        self.assertEqual(item.DefaultValue, 'FALSE')
        self.assertEqual(item.DatumType, 'BOOLEAN')
        self.assertEqual(item.TokenValue, '0x0000000d')

    def testNoCvar(self):
        obj = self.GetObj('123ai.PcdComponentNameDisable|FALSE|BOOLEAN|0x0000000d')
        self.assertRaises(FatalError, obj._ParseItem)

    def testSplit(self):
        obj = self.GetObj('gEfiMdePkgTokenSpaceGuid.PcdComponentNameDisable FALSE|BOOLEAN|0x0000000d')
        self.assertRaises(FatalError, obj._ParseItem)

        obj = self.GetObj('gEfiMdePkgTokenSpaceGuid.PcdComponentNameDisable|FALSE|BOOLEAN|0x0000000d | abc')
        self.assertRaises(FatalError, obj._ParseItem)

    def testUnknownType(self):
        obj = self.GetObj('gEfiMdePkgTokenSpaceGuid.PcdComponentNameDisable|FALSE|unknown|0x0000000d')
        self.assertRaises(FatalError, obj._ParseItem)

    def testVoid(self):
        obj = self.GetObj('gEfiMdePkgTokenSpaceGuid.PcdComponentNameDisable|abc|VOID*|0x0000000d')
        self.assertRaises(FatalError, obj._ParseItem)

    def testUINT(self):
        obj = self.GetObj('gEfiMdePkgTokenSpaceGuid.PcdComponentNameDisable|0xabc|UINT8|0x0000000d')
        self.assertRaises(FatalError, obj._ParseItem)

#
# Test _DecInclude._ParseItem
#
class DecIncludeTestCase(unittest.TestCase):
    #
    # Test code to be added
    #
    pass

#
# Test _DecGuid._ParseItem
#
class DecGuidTestCase(unittest.TestCase):
    def GetObj(self, Content):
        Obj = _DecGuid(FileContent('dummy', Content.splitlines()))
        Obj._RawData.CurrentLine = Obj._RawData.GetNextLine()
        Obj._RawData.CurrentScope = [('guids'.upper(), 'COMMON')]
        return Obj

    def testCValue(self):
        item = self.GetObj('gEfiIpSecProtocolGuid={ 0xdfb386f7, 0xe100, 0x43ad,'
                           ' {0x9c, 0x9a, 0xed, 0x90, 0xd0, 0x8a, 0x5e, 0x12 }}')._ParseItem()
        self.assertEqual(item.GuidCName, 'gEfiIpSecProtocolGuid')
        self.assertEqual(item.GuidCValue, '{ 0xdfb386f7, 0xe100, 0x43ad, {0x9c, 0x9a, 0xed, 0x90, 0xd0, 0x8a, 0x5e, 0x12 }}')

    def testGuidString(self):
        item = self.GetObj('gEfiIpSecProtocolGuid=1E73767F-8F52-4603-AEB4-F29B510B6766')._ParseItem()
        self.assertEqual(item.GuidCName, 'gEfiIpSecProtocolGuid')
        self.assertEqual(item.GuidCValue, '1E73767F-8F52-4603-AEB4-F29B510B6766')

    def testNoValue1(self):
        obj = self.GetObj('gEfiIpSecProtocolGuid')
        self.assertRaises(FatalError, obj._ParseItem)

    def testNoValue2(self):
        obj = self.GetObj('gEfiIpSecProtocolGuid=')
        self.assertRaises(FatalError, obj._ParseItem)

    def testNoName(self):
        obj = self.GetObj('=')
        self.assertRaises(FatalError, obj._ParseItem)

#
# Test Dec.__init__
#
class DecDecInitTestCase(unittest.TestCase):
    def testNoDecFile(self):
        self.assertRaises(FatalError, Dec, 'No_Such_File')

class TmpFile:
    def __init__(self, File):
        self.File = File

    def Write(self, Content):
        try:
            FileObj = open(self.File, 'w')
            FileObj.write(Content)
            FileObj.close()
        except:
            pass

    def Remove(self):
        try:
            os.remove(self.File)
        except:
            pass

#
# Test Dec._UserExtentionSectionParser
#
class DecUESectionTestCase(unittest.TestCase):
    def setUp(self):
        self.File = TmpFile('test.dec')
        self.File.Write(
'''[userextensions.intel."myid"]
[userextensions.intel."myid".IA32]
[userextensions.intel."myid".IA32,]
[userextensions.intel."myid]
'''
        )

    def tearDown(self):
        self.File.Remove()

    def testUserExtentionHeader(self):
        dec = Dec('test.dec', False)

        # OK: [userextensions.intel."myid"]
        dec._RawData.CurrentLine = CleanString(dec._RawData.GetNextLine())[0]
        dec._UserExtentionSectionParser()
        self.assertEqual(len(dec._RawData.CurrentScope), 1)
        self.assertEqual(dec._RawData.CurrentScope[0][0], 'userextensions'.upper())
        self.assertEqual(dec._RawData.CurrentScope[0][1], 'intel')
        self.assertEqual(dec._RawData.CurrentScope[0][2], '"myid"')
        self.assertEqual(dec._RawData.CurrentScope[0][3], 'COMMON')

        # OK: [userextensions.intel."myid".IA32]
        dec._RawData.CurrentLine = CleanString(dec._RawData.GetNextLine())[0]
        dec._UserExtentionSectionParser()
        self.assertEqual(len(dec._RawData.CurrentScope), 1)
        self.assertEqual(dec._RawData.CurrentScope[0][0], 'userextensions'.upper())
        self.assertEqual(dec._RawData.CurrentScope[0][1], 'intel')
        self.assertEqual(dec._RawData.CurrentScope[0][2], '"myid"')
        self.assertEqual(dec._RawData.CurrentScope[0][3], 'IA32')

        # Fail: [userextensions.intel."myid".IA32,]
        dec._RawData.CurrentLine = CleanString(dec._RawData.GetNextLine())[0]
        self.assertRaises(FatalError, dec._UserExtentionSectionParser)

        # Fail: [userextensions.intel."myid]
        dec._RawData.CurrentLine = CleanString(dec._RawData.GetNextLine())[0]
        self.assertRaises(FatalError, dec._UserExtentionSectionParser)

#
# Test Dec._SectionHeaderParser
#
class DecSectionTestCase(unittest.TestCase):
    def setUp(self):
        self.File = TmpFile('test.dec')
        self.File.Write(
'''[no section start or end
[,] # empty sub-section
[unknow_section_name]
[Includes.IA32.other] # no third one
[PcdsFeatureFlag, PcdsFixedAtBuild] # feature flag PCD must not be in the same section of other types of PCD
[Includes.IA32, Includes.IA32]
[Includes, Includes.IA32] # common cannot be with other arch
[Includes.IA32, PcdsFeatureFlag] # different section name
'''     )

    def tearDown(self):
        self.File.Remove()

    def testSectionHeader(self):
        dec = Dec('test.dec', False)
        # [no section start or end
        dec._RawData.CurrentLine = CleanString(dec._RawData.GetNextLine())[0]
        self.assertRaises(FatalError, dec._SectionHeaderParser)

        #[,] # empty sub-section
        dec._RawData.CurrentLine = CleanString(dec._RawData.GetNextLine())[0]
        self.assertRaises(FatalError, dec._SectionHeaderParser)

        # [unknow_section_name]
        dec._RawData.CurrentLine = CleanString(dec._RawData.GetNextLine())[0]
        self.assertRaises(FatalError, dec._SectionHeaderParser)

        # [Includes.IA32.other] # no third one
        dec._RawData.CurrentLine = CleanString(dec._RawData.GetNextLine())[0]
        self.assertRaises(FatalError, dec._SectionHeaderParser)

        # [PcdsFeatureFlag, PcdsFixedAtBuild]
        dec._RawData.CurrentLine = CleanString(dec._RawData.GetNextLine())[0]
        self.assertRaises(FatalError, dec._SectionHeaderParser)

        # [Includes.IA32, Includes.IA32]
        dec._RawData.CurrentLine = CleanString(dec._RawData.GetNextLine())[0]
        dec._SectionHeaderParser()
        self.assertEqual(len(dec._RawData.CurrentScope), 1)
        self.assertEqual(dec._RawData.CurrentScope[0][0], 'Includes'.upper())
        self.assertEqual(dec._RawData.CurrentScope[0][1], 'IA32')

        # [Includes, Includes.IA32] # common cannot be with other arch
        dec._RawData.CurrentLine = CleanString(dec._RawData.GetNextLine())[0]
        self.assertRaises(FatalError, dec._SectionHeaderParser)

        # [Includes.IA32, PcdsFeatureFlag] # different section name not allowed
        dec._RawData.CurrentLine = CleanString(dec._RawData.GetNextLine())[0]
        self.assertRaises(FatalError, dec._SectionHeaderParser)

#
# Test Dec._ParseDecComment
#
class DecDecCommentTestCase(unittest.TestCase):
    def testDecHeadComment(self):
        File = TmpFile('test.dec')
        File.Write(
       '''## @file abc
          ##''')
        dec = Dec('test.dec', False)
        dec.ParseDecComment()
        self.assertEqual(len(dec._HeadComment), 2)
        self.assertEqual(dec._HeadComment[0][0], '## @file abc')
        self.assertEqual(dec._HeadComment[0][1], 1)
        self.assertEqual(dec._HeadComment[1][0], '##')
        self.assertEqual(dec._HeadComment[1][1], 2)
        File.Remove()

    def testNoDoubleComment(self):
        File = TmpFile('test.dec')
        File.Write(
       '''## @file abc
          #
          [section_start]''')
        dec = Dec('test.dec', False)
        dec.ParseDecComment()
        self.assertEqual(len(dec._HeadComment), 2)
        self.assertEqual(dec._HeadComment[0][0], '## @file abc')
        self.assertEqual(dec._HeadComment[0][1], 1)
        self.assertEqual(dec._HeadComment[1][0], '#')
        self.assertEqual(dec._HeadComment[1][1], 2)
        File.Remove()

if __name__ == '__main__':
    import Logger.Logger
    Logger.Logger.Initialize()
    unittest.main()

