packages feed

large-anon-0.2: test/Test/Sanity/Discovery.hs

{-# LANGUAGE DataKinds         #-}
{-# LANGUAGE OverloadedLabels  #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications  #-}
{-# LANGUAGE TypeOperators     #-}

{-# OPTIONS_GHC -fplugin=Data.Record.Anon.Plugin #-}

module Test.Sanity.Discovery (tests) where

import Data.Either (fromRight)
import Data.Record.Generic

import Data.Record.Anon

import qualified Data.Record.Anon.Simple   as S
import qualified Data.Record.Anon.Advanced as A

import Test.Tasty
import Test.Tasty.HUnit

import Test.Infra.Discovery
import Test.Infra.DynRecord
import Test.Infra.MarkStrictness

import qualified Test.Infra.DynRecord.Simple   as Dyn.S
import qualified Test.Infra.DynRecord.Advanced as Dyn.A

{-------------------------------------------------------------------------------
  Tests proper
-------------------------------------------------------------------------------}

tests :: TestTree
tests = testGroup "Test.Sanity.Discovery" [
      testGroup "Simple" [
          testCase "inferType" test_simple_inferType
        , testCase "toLens"    test_simple_toLens
        ]
    , testGroup "Advanced" [
          testCase "inferType" test_advanced_inferType
        , testCase "toLens"    test_advanced_toLens
        ]
    ]

{-------------------------------------------------------------------------------
  Tests for the simple API (kind Type)
-------------------------------------------------------------------------------}

type ExpectedSimple = [ "a" := Int, "c" := Char ]

test_simple_inferType :: Assertion
test_simple_inferType =
    case Dyn.S.inferType example1 of
      Dyn.S.SomeRecord r -> do
        assertEqual "show" expected $
          show r

        -- The comparison test implies that we are parsing one record, then
        -- using the result to parse another /in the same shape/
        assertEqual "compare" (Right r) $
          Dyn.S.toRecord r example2
  where
    expected :: String
    expected = "ANON {a = 1, b = True, c = 'a'}"

test_simple_toLens :: Assertion
test_simple_toLens = do
    assertEqual "get" expectedGet $
      show get
    assertEqual "set" example1' $
      set (S.set #c 'b' get)
    assertEqual "missingField" expectedMissingField . fmap (const ()) $
      Dyn.S.toLens (Proxy @ExpectedSimple) exampleMissingField
    assertEqual "wrongType" expectedWrongType  . fmap (const ()) $
      Dyn.S.toLens (Proxy @ExpectedSimple) exampleWrongType
  where
    get :: S.Record ExpectedSimple
    set :: S.Record ExpectedSimple -> DynRecord
    (get, set) =
        fromRight (error "unexpected error") $
          Dyn.S.toLens (Proxy @ExpectedSimple) example1

    expectedGet :: String
    expectedGet = "ANON {a = 1, c = 'a'}"

    expectedMissingField :: Either NotSubRow ()
    expectedMissingField = Left ["c"]

    -- TODO: For now we don't get more information in case of a type error
    expectedWrongType :: Either NotSubRow ()
    expectedWrongType = Left ["c"]

{-------------------------------------------------------------------------------
  Tests for the advanced API (kind other than Type)

  These follow the same structure as the tests for the simple API. We don't
  explicitly test the error cases again here ('NotSubRow'), since the
  projection and parsing machinery is the same for the simple and the advanced
  case.
-------------------------------------------------------------------------------}

type ExpectedAdvanced = [ "a" := Strict Int, "c" := Lazy Char ]

test_advanced_inferType :: Assertion
test_advanced_inferType =
    case Dyn.A.inferType example1 :: Dyn.A.SomeRecord Boxed of
      Dyn.A.SomeRecord r -> do
        assertEqual "show" expected $
          show r

        -- The comparison test implies that we are parsing one record, then
        -- using the result to parse another /in the same shape/
        assertEqual "compare" (Right r) $
          Dyn.A.toRecord r example2
  where
    expected :: String
    expected = "ANON_F {a = 1, b = True, c = 'a'}"

test_advanced_toLens :: Assertion
test_advanced_toLens = do
    assertEqual "get" expectedGet $
      show get
    assertEqual "set" example1' $
      set (A.set #c (BoxLazy 'b') get)
  where
    get :: A.Record Boxed ExpectedAdvanced
    set :: A.Record Boxed ExpectedAdvanced -> DynRecord
    (get, set) =
       fromRight (error "unexpected error") $
         Dyn.A.toLens (Proxy @ExpectedAdvanced) example1

    expectedGet :: String
    expectedGet = "ANON_F {a = 1, c = 'a'}"

{-------------------------------------------------------------------------------
  Example 'DynRecord' values

  These are used both for the simple and the advanced tests (which of course
  interpret them differently).

  TODO: We should have a test with some shadowing.
  TODO: We should not have all fields alphabetical.
-------------------------------------------------------------------------------}

-- | Main running example
example1 :: DynRecord
example1 = DynRecord [
      ("a", VI 1)
    , ("b", VB True)
    , ("c", VC 'a')
    ]

-- | Like 'example1', but one field updated
example1' :: DynRecord
example1' = DynRecord [
      ("a", VI 1)
    , ("b", VB True)
    , ("c", VC 'b')
    ]

-- | Equal to 'example1', but with some additional fields
--
-- We should be able to parse this according to the type inferred for
-- 'example1'.
example2 :: DynRecord
example2 = DynRecord [
      ("a", VI 1)
    , ("b", VB True)
    , ("c", VC 'a')
    , ("d", VI 2)
    ]

-- | Example that does not conform to 'Expected': field missing
exampleMissingField :: DynRecord
exampleMissingField = DynRecord [
      ("a", VI 1)
    ]

-- | Example that does not conform to 'Expected': wrong field type
exampleWrongType :: DynRecord
exampleWrongType = DynRecord [
      ("a", VI 1)
    , ("b", VB True)
    , ("c", VB False)
    ]