packages feed

dhall-json-1.7.0: tasty/Main.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE OverloadedLists   #-}
{-# LANGUAGE RecordWildCards   #-}

module Main where

import Data.Monoid ((<>))
import Data.Void   (Void)
import Test.Tasty  (TestTree)

import qualified Data.Aeson           as Aeson
import qualified Data.ByteString.Lazy
import qualified Data.Text
import qualified Data.Text.IO
import qualified Dhall.Core           as Core
import qualified Dhall.Import
import qualified Dhall.JSON
import qualified Dhall.JSONToDhall    as JSONToDhall
import qualified Dhall.Parser
import qualified Dhall.TypeCheck
import qualified GHC.IO.Encoding
import qualified Test.Tasty
import qualified Test.Tasty.HUnit
import qualified Test.Tasty.Silver

main :: IO ()
main = do
    GHC.IO.Encoding.setLocaleEncoding GHC.IO.Encoding.utf8

    Test.Tasty.defaultMain testTree

testTree :: TestTree
testTree =
    Test.Tasty.testGroup "dhall-json"
        [ testDhallToJSON "./tasty/data/issue48"
        , testDhallToJSON "./tasty/data/emptyObjectStrongType"
        , testDhallToJSON "./tasty/data/toArbitraryJSON_12_0_0"
        , testDhallToJSON "./tasty/data/toArbitraryJSON_13_0_0"
        , testJSONToDhall "./tasty/data/emptyAlternative"
        , testJSONToDhall "./tasty/data/emptyObject"
        , testJSONToDhall "./tasty/data/emptyList"
        , testJSONToDhall "./tasty/data/emptyObjectStrongType"
        , testJSONToDhall "./tasty/data/emptyListStrongType"
        , testJSONToDhall "./tasty/data/fromArbitraryJSON_12_0_0"
        , testJSONToDhall "./tasty/data/fromArbitraryJSON_13_0_0"
        , inferJSONToDhall "./tasty/data/potpourri"
        , testCustomConversionJSONToDhall False omissibleLists "./tasty/data/missingList"
        , Test.Tasty.testGroup "Errors"
            [ testJSONToDhallErrorMessage "./tasty/data/error/mismatchMessage0" defaultConversion
            , testJSONToDhallErrorMessage "./tasty/data/error/mismatchMessage1" defaultConversion
            , testJSONToDhallErrorMessage "./tasty/data/error/mismatchMessage2" defaultConversion
            , testJSONToDhallErrorMessage "./tasty/data/error/unhandledKeys" strictRecs
            , testJSONToDhallErrorMessage "./tasty/data/error/missingKey" strictRecs
            ]
        , Test.Tasty.testGroup "Nesting"
            [ testDhallToJSON "./tasty/data/nesting0"
            , testDhallToJSON "./tasty/data/nesting1"
            , testDhallToJSON "./tasty/data/nesting2"
            , testDhallToJSON "./tasty/data/nesting3"
            , testDhallToJSON "./tasty/data/nestingLegacy0"
            , testDhallToJSON "./tasty/data/nestingLegacy1"
            ]
        , Test.Tasty.testGroup "Union keys"
            [ testJSONToDhall "./tasty/data/unionKeys"
            , testDhallToJSON "./tasty/data/unionKeys"
            ]
        ]
    where
        defaultConversion = JSONToDhall.defaultConversion
        omissibleLists = defaultConversion{JSONToDhall.omissibleLists = True}
        strictRecs = defaultConversion{JSONToDhall.strictRecs = True}

testDhallToJSON :: String -> TestTree
testDhallToJSON prefix = Test.Tasty.HUnit.testCase prefix $ do
    let inputFile = prefix <> ".dhall"
    let outputFile = prefix <> ".json"

    text <- Data.Text.IO.readFile inputFile

    parsedExpression <- do
        Core.throws (Dhall.Parser.exprFromText inputFile text)

    resolvedExpression <- Dhall.Import.load parsedExpression

    _ <- Core.throws (Dhall.TypeCheck.typeOf resolvedExpression)

    let convertedExpression =
            Dhall.JSON.convertToHomogeneousMaps Dhall.JSON.defaultConversion resolvedExpression

    actualValue <- do
        Core.throws (Dhall.JSON.dhallToJSON convertedExpression)

    bytes <- Data.ByteString.Lazy.readFile outputFile

    expectedValue <- case Aeson.eitherDecode bytes of
        Left  string        -> fail string
        Right expectedValue -> return expectedValue

    let message = "Conversion to JSON did not generate the expected output"

    Test.Tasty.HUnit.assertEqual message expectedValue actualValue

testCustomConversionJSONToDhall
    :: Bool -> JSONToDhall.Conversion -> String -> TestTree
testCustomConversionJSONToDhall infer conv prefix =
  Test.Tasty.HUnit.testCase prefix $ do
    let inputFile = prefix <> ".json"
    let outputFile = prefix <> ".dhall"

    bytes <- Data.ByteString.Lazy.readFile inputFile

    value <-
        case Aeson.eitherDecode bytes of
            Left string -> fail string
            Right value -> return value

    schema <-
        if infer
            then return (JSONToDhall.schemaToDhallType (JSONToDhall.inferSchema value))
            else let schemaFile = prefix <> "Schema.dhall" in loadSchemaFromFile schemaFile

    _ <- Core.throws (Dhall.TypeCheck.typeOf schema)

    actualExpression <- do
        Core.throws (JSONToDhall.dhallFromJSON conv schema value)

    outputText <- Data.Text.IO.readFile outputFile

    parsedExpression <- do
        Core.throws (Dhall.Parser.exprFromText outputFile outputText)

    resolvedExpression <- Dhall.Import.load parsedExpression

    _ <- Core.throws (Dhall.TypeCheck.typeOf resolvedExpression)

    let expectedExpression = Core.denote resolvedExpression

    let message =
            "Conversion to Dhall did not generate the expected output"

    Test.Tasty.HUnit.assertEqual message expectedExpression actualExpression

testJSONToDhall :: String -> TestTree
testJSONToDhall =
    testCustomConversionJSONToDhall False JSONToDhall.defaultConversion

inferJSONToDhall :: String -> TestTree
inferJSONToDhall =
    testCustomConversionJSONToDhall True JSONToDhall.defaultConversion

loadSchemaFromFile :: FilePath -> IO (Core.Expr Dhall.Parser.Src Void)
loadSchemaFromFile schemaFile = do
    schemaText <- Data.Text.IO.readFile schemaFile

    parsedSchema <- Core.throws (Dhall.Parser.exprFromText schemaFile schemaText)

    Dhall.Import.load parsedSchema

testJSONToDhallErrorMessage :: String -> JSONToDhall.Conversion -> TestTree
testJSONToDhallErrorMessage prefix conv =
    Test.Tasty.Silver.goldenVsAction prefix goldenFile action converter
    where
        goldenFile = prefix <> ".golden"
        schemaFile = prefix <> "Schema.dhall"
        inputFile  = prefix <> ".json"

        action = do
            bytes <- Data.ByteString.Lazy.readFile inputFile

            value <-
                case Aeson.eitherDecode bytes of
                    Left string -> fail string
                    Right value -> return value

            schema <- loadSchemaFromFile schemaFile

            _ <- Core.throws (Dhall.TypeCheck.typeOf schema)

            case JSONToDhall.dhallFromJSON conv schema value of
                Right _ -> fail $ prefix <> " should fail"
                Left compileError ->
                    return (Data.Text.pack $ show compileError)

        converter = id