packages feed

bond-0.13.0.0: tests/Tests/Codegen.hs

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

{-# LANGUAGE RecordWildCards #-}
{-# OPTIONS_GHC -fno-warn-incomplete-patterns #-}

module Tests.Codegen
    ( verifyCodegen
    , verifyCodegenVariation
    , verifyCppCodegen
    , verifyApplyCodegen
    , verifyExportsCodegen
    , verifyCsCodegen
    , verifyJavaCodegen
    ) where

import System.FilePath
import Control.Monad
import Data.Monoid
import Data.Maybe
import Prelude
import Data.Algorithm.DiffContext
import Data.Text.Lazy (Text, unpack)
import qualified Data.Text.Lazy as LT
import qualified Data.ByteString.Char8 as BS
import Text.PrettyPrint (render, text)
import Test.Tasty
import Test.Tasty.Golden.Advanced
import Language.Bond.Codegen.Templates
import Language.Bond.Codegen.TypeMapping
import Language.Bond.Syntax.Types (Bond(..), Import, Declaration(..))
import Options
import IO

type Template = MappingContext -> String -> [Import] -> [Declaration] -> (String, Text)

verifyCppCodegen :: FilePath -> TestTree
verifyCppCodegen = verifyCodegen ["c++"]

verifyCsCodegen :: FilePath -> TestTree
verifyCsCodegen = verifyCodegen ["c#"]

verifyJavaCodegen :: FilePath -> TestTree
verifyJavaCodegen = verifyCodegen ["java"]

verifyCodegen :: [String] -> FilePath -> TestTree
verifyCodegen args baseName =
    testGroup baseName $
        verifyFiles (processOptions args) baseName ""

verifyCodegenVariation :: [String] -> FilePath -> FilePath -> TestTree
verifyCodegenVariation args baseName variation =
    testGroup baseName $
        verifyFiles (processOptions args) baseName variation

verifyApplyCodegen :: [String] -> FilePath -> TestTree
verifyApplyCodegen args baseName =
    testGroup baseName $
        map (verifyFile options baseName cppTypeMapping "apply") templates
  where
    options = processOptions args
    templates =
        [ apply_h protocols (export_attribute options)
        , apply_cpp protocols
        ]
    protocols =
        [ ProtocolReader "bond::CompactBinaryReader<bond::InputBuffer>"
        , ProtocolWriter "bond::CompactBinaryWriter<bond::OutputBuffer>"
        , ProtocolWriter "bond::CompactBinaryWriter<bond::OutputCounter>"
        , ProtocolReader "bond::FastBinaryReader<bond::InputBuffer>"
        , ProtocolWriter "bond::FastBinaryWriter<bond::OutputBuffer>"
        , ProtocolReader "bond::SimpleBinaryReader<bond::InputBuffer>"
        , ProtocolWriter "bond::SimpleBinaryWriter<bond::OutputBuffer>"
        ]

verifyExportsCodegen :: [String] -> FilePath -> TestTree
verifyExportsCodegen args baseName =
    testGroup baseName $
        map (verifyFile options baseName (cppExpandAliases (type_aliases_enabled options) cppTypeMapping) "exports") (templates options)
  where
    options = processOptions args
    templates Cpp {..} =
        [ reflection_h export_attribute
        , types_h export_attribute header enum_header allocator alloc_ctors_enabled type_aliases_enabled scoped_alloc_enabled
        ]

verifyFiles :: Options -> FilePath -> FilePath -> [TestTree]
verifyFiles options baseName variation =
    map (verify (typeMapping options) variation) (templates options)
    <>
    extra options
  where
    verify = verifyFile options baseName
    fieldMapping Cs {..} = if readonly_properties
        then ReadOnlyProperties
        else if fields
             then PublicFields
             else Properties
    constructorOptions Cs {..} = if constructor_parameters
        then ConstructorParameters
        else DefaultWithProtectedBase
    typeMapping Cpp {..} = cppExpandAliases type_aliases_enabled $ maybe cppTypeMapping (cppCustomAllocTypeMapping scoped_alloc_enabled) allocator
    typeMapping Cs {} = csTypeMapping
    typeMapping Java {} = javaTypeMapping
    templates Cpp {..} =
        [ (reflection_h export_attribute)
        , types_cpp
        , types_h export_attribute header enum_header allocator alloc_ctors_enabled type_aliases_enabled scoped_alloc_enabled
        ] <>
        [ enum_h | enum_header]
    templates Cs {..} =
        [ types_cs Class (fieldMapping options) (constructorOptions options)
        ]
    templates Java {} =
        [ javaCatTemplate
        ]
    extra Cs {} =
        [ testGroup "collection interfaces" $
            map (verify csCollectionInterfacesTypeMapping (variation </> "collection-interfaces")) (templates options)
        ]
    extra Cpp {..} =
        [ testGroup "custom allocator" $
            map (verify (cppExpandAliasesTypeMapping $ cppCustomAllocTypeMapping False "arena") (variation </> "allocator"))
                (templates $ options { allocator = Just "arena" })
            | isNothing allocator
        ] ++
        [ testGroup "constructors with allocator argument" $
            map (verify (cppExpandAliasesTypeMapping $ cppCustomAllocTypeMapping False "arena") (variation </> "alloc_ctors"))
                (templates $ options { allocator = Just "arena", alloc_ctors_enabled = True })
            | isNothing allocator
        ] ++
        [ testGroup "type aliases" $
            map (verify (cppCustomAllocTypeMapping False "arena") (variation </> "type_aliases"))
                (templates $ options { allocator = Just "arena", type_aliases_enabled = True })
        ] ++
        [ testGroup "scoped allocator" $
            map (verify (cppExpandAliasesTypeMapping $ cppCustomAllocTypeMapping True "arena") (variation </> "scoped_allocator"))
                (templates $ options { allocator = Just "arena", scoped_alloc_enabled = True })
            | isNothing allocator
        ]
    extra Java {} =
        [
        ]

verifyFile :: Options -> FilePath -> TypeMapping -> FilePath -> Template -> TestTree
verifyFile options baseName typeMapping subfolder template =
    goldenTest suffix readGolden codegen cmp updateGolden
  where
    (suffix, _) = template (MappingContext typeMapping [] [] []) "" [] []
    golden = "tests" </> "generated" </> subfolder </> baseName ++ suffix
    readGolden = BS.readFile golden
    updateGolden = BS.writeFile golden
    codegen = do
        aliasMapping <- parseAliasMappings $ using options
        namespaceMapping <- parseNamespaceMappings $ namespace options
        (Bond imports namespaces declarations) <- parseBondFile (import_dir options) $ "tests" </> "schema" </> baseName <.> "bond"
        let mappingContext = MappingContext typeMapping aliasMapping namespaceMapping namespaces
        let (_, code) = template mappingContext baseName imports declarations
        return $ BS.pack $ unpack code
    cmp x y = return $ if x == y then Nothing else Just $ diff x y
    diff x y = render $ prettyContextDiff
                            (text golden)
                            (text "test output")
                            (text . BS.unpack)
                            (getContextDiff 3 (BS.lines x) (BS.lines y))

javaCatTemplate :: MappingContext -> String -> [Import] -> [Declaration] -> (String, Text)
javaCatTemplate mappingContext _ imports declarations =
  (suffix, LT.concat $ mapMaybe codegenDecl declarations)
    where
      suffix = "_concatenated.java"
      codegenDecl declaration =
        case declaration of
          Struct {} -> Just $ class_java mappingContext imports declaration
          Enum {}   -> Just $ enum_java mappingContext declaration
          _         -> Nothing

cppExpandAliases :: Bool -> TypeMapping -> TypeMapping
cppExpandAliases type_aliases_enabled = if type_aliases_enabled
    then id
    else cppExpandAliasesTypeMapping