packages feed

bond-0.11.0.0: tests/Tests/Syntax.hs

-- Copyright (c) Microsoft. All rights reserved.
-- Licensed under the MIT license. See LICENSE file in the project root for full license information.

{-# LANGUAGE OverloadedStrings, RecordWildCards, TemplateHaskell, ScopedTypeVariables #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

module Tests.Syntax
    ( roundtripAST
    , compareAST
    , failBadSyntax
    , aliasResolution
    , verifySchemaDef
    ) where

import Control.Exception
import Data.Maybe
import Data.List
import Data.Aeson (encode, decode)
import Data.Aeson.Encode.Pretty (Config(..), encodePretty')
import Data.DeriveTH
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import System.FilePath
import Test.QuickCheck
import Test.HUnit
import Test.Tasty
import Test.Tasty.Golden
import Language.Bond.Syntax.Types
import Language.Bond.Syntax.Util
import Language.Bond.Syntax.SchemaDef
import IO

derive makeArbitrary ''Attribute
derive makeArbitrary ''Bond
derive makeArbitrary ''Constant
derive makeArbitrary ''Constraint
derive makeArbitrary ''Declaration
derive makeArbitrary ''Default
derive makeArbitrary ''Field
derive makeArbitrary ''Import
derive makeArbitrary ''Language
derive makeArbitrary ''Modifier
derive makeArbitrary ''Namespace
derive makeArbitrary ''Type
derive makeArbitrary ''TypeParam
derive makeArbitrary ''MethodType

instance Arbitrary Method where
  arbitrary = oneof
    [ Function <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
    , Event <$> arbitrary <*> arbitrary <*> eventInput]
    where
      -- events cannot have streaming input, so we need to customize the
      -- arbitrary input to omit Streaming
      eventInput = oneof [return Void,  Unary <$> arbitrary]

roundtripAST :: Bond -> Property
roundtripAST x = (decode . encode) x === Just x

assertException :: String -> IO a -> Assertion
assertException errMsg action = do
    e <- try action
    case e of
        Left (_ :: SomeException) -> return ()
        Right _ -> assertFailure errMsg

failBadSyntax :: String -> FilePath -> Assertion
failBadSyntax errMsg file = assertException errMsg (parseBondFile [] $ "tests" </> "schema" </> "error" </> file <.> "bond")

compareAST :: FilePath -> Assertion
compareAST file = do
    bond <- parseBondFile [] $ "tests" </> "schema" </> file <.> "bond"
    json <- parseASTFile $ "tests" </> "schema" </> file <.> "json"
    assertEqual "" bond json

aliasResolution :: Assertion
aliasResolution = do
    let p1 = TypeParam "T" Nothing
    let p2 = TypeParam "U" Nothing
    let a1 = Alias [] "" [] BT_Bool
    let a2 = Alias [] "" [p1] $ BT_TypeParam p1
    let a3 = Alias [] "" [p1] $ BT_List $ BT_TypeParam p1
    let a4 = Alias [] "" [p1, p2] $ BT_Nullable $ BT_Map (BT_TypeParam p2) (BT_Nullable $ BT_TypeParam p1)
    let a5 = Alias [] "" [p1] $ BT_List $ BT_UserDefined a3 [BT_TypeParam p1]
    let a6 = Alias [] "" [p1, p2] $ BT_Vector $ BT_Vector $ BT_TypeParam p2

    assertEqual "" BT_Bool $
        resolveAlias a1 []

    assertEqual "" BT_Int32 $
        resolveAlias a2 [BT_Int32]

    assertEqual "" (BT_Set BT_Int32) $
        resolveAlias a2 [BT_Set BT_Int32]

    assertEqual "" (BT_TypeParam p2) $
        resolveAlias a2 [BT_TypeParam p2]

    assertEqual "" (BT_TypeParam p1) $
        resolveAlias a2 [BT_TypeParam p1]

    assertEqual "" (BT_List BT_Int32) $
        resolveAlias a3 [BT_Int32]

    assertEqual "" (BT_List $ BT_UserDefined a1 []) $
        resolveAlias a3 [BT_UserDefined a1 []]

    assertEqual "" (BT_Nullable $ BT_Map BT_Bool $ BT_Nullable BT_String) $
        resolveAlias a4 [BT_String, BT_Bool]

    assertEqual "" (BT_List $ BT_UserDefined a3 [BT_Set BT_Bool]) $
        resolveAlias a5 [BT_Set BT_Bool]

    assertEqual "" (BT_Vector $ BT_Vector $ BT_List BT_Double) $
        resolveAlias a6 [BT_IntTypeArg 10, BT_List BT_Double]

verifySchemaDef :: FilePath -> String -> TestTree
verifySchemaDef baseName schemaName =
    goldenVsString
        schemaName
        ("tests" </> "generated" </> "schemadef" </> baseName <.> schemaName <.> "json")
        schemaDef
  where
    schemaDef = do
        (Bond _ _ declarations) <- parseBondFile [] $ "tests" </> "schema" </> baseName <.> "bond"
        let schema = fromJust $ find ((schemaName ==) . declName) declarations
        return $
            -- some versions aeson encode angle brackets
            BL.fromStrict $
            replace "\\u003c" "<" $
            replace "\\u003e" ">" $
            BL.toStrict $ prettyEncode $ makeSchemaDef $ BT_UserDefined schema []
      where
        prettyEncode = encodePretty' (Config indentSpaces compare)
          where
            indentSpaces = 2
        replace s r bs = if B.null t then h else
            B.append h (B.append r $ replace s r (B.drop (B.length s) t))
          where
            (h, t) = B.breakSubstring s bs