packages feed

hstox-0.0.1: src/testsuite/Network/Tox/RPCTest.hs

{-# LANGUAGE LambdaCase        #-}
{-# LANGUAGE OverloadedStrings #-}
-- | This module provides helper functions to make writing RPC tests slightly
-- easier.
module Network.Tox.RPCTest where

import           Control.Exception          (catch)
import           Control.Monad.IO.Class     (liftIO)
import qualified Data.Text                  as Text
import           Test.Hspec
import           Test.QuickCheck            (Arbitrary, Testable, property)

import           Data.MessagePack           (Object (..))
import           Network.MessagePack.Client (Client, RpcError (..))
import qualified Network.MessagePack.Client as Client
import qualified Network.Tox.Testing        as Testing


runClient :: Client a -> IO a
runClient = Client.runClient "localhost" Testing.defaultPort


-- | Run a 'Client' using 'runClient' and catch "Pending" errors from the
-- system under test (SUT) to turn them into Hspec 'pending' calls. Other
-- errors are turned into calls to 'expectationFailure'.
runTest :: Client () -> IO ()
runTest a3 =
  runClient a3 `catch` \case
    RemoteError (ObjectStr msg) | msg == Text.pack "Pending" -> pending
    e -> expectationFailure $ show e


-- | Common code for the equivN helper functions below. Each equiv function
-- ('equiv1', 'equiv2', ...) evaluates a pure library function and executes an
-- equivalent RPC method and compares their results. This is used to check
-- whether the model implementation and the SUT produce the same output for any
-- given input. It uses 'shouldBe' from Hspec to compare the results.
--
-- Not all functions require equivalence. E.g. @Nonce.newNonce@ is
-- non-deterministic, as it generates a random nonce using the system's random
-- source. Therefore, no 'equiv'-like function exists for functions in the 'IO'
-- monad.
equiv :: (Eq r, Show r)
      => r -> Client r -> Client ()
equiv expected actualM = do
  actual <- actualM
  liftIO $ actual `shouldBe` expected


-- | 'equivProp' and its @equivPropN@ variants produce equivalence property
-- tests. 'equivProp' is @it "msg" . property@ with an appropriate message.
equivProp :: Testable prop => prop -> Spec
equivProp = it "is equivalent to its RPC method" . property


equiv1 :: (Eq r, Show r)
       => (a1 -> r)
       -> (a1 -> Client r)
       -> a1 -> Client ()
equiv1 f1 f2 a1 =
  equiv (f1 a1)
        (f2 a1)

equivProp1 :: ( Eq r, Show r
              , Show a1, Arbitrary a1)
           => (a1 -> r)
           -> (a1 -> Client r)
           -> Spec
equivProp1 f m =
  equivProp $ \a1 -> runTest $ equiv1 f m a1


equiv2 :: (Eq r, Show r)
       => (a1 -> a2 -> r)
       -> (a1 -> a2 -> Client r)
       -> a1 -> a2 -> Client ()
equiv2 f1 f2 a1 a2 =
  equiv (f1 a1 a2)
        (f2 a1 a2)

equivProp2 :: ( Eq r, Show r
              , Show a1, Arbitrary a1
              , Show a2, Arbitrary a2)
           => (a1 -> a2 -> r)
           -> (a1 -> a2 -> Client r)
           -> Spec
equivProp2 f m =
  equivProp $ \a1 a2 -> runTest $ equiv2 f m a1 a2


equiv3 :: (Eq r, Show r)
       => (a1 -> a2 -> a3 -> r)
       -> (a1 -> a2 -> a3 -> Client r)
       -> a1 -> a2 -> a3 -> Client ()
equiv3 f1 f2 a1 a2 a3 =
  equiv (f1 a1 a2 a3)
        (f2 a1 a2 a3)

equivProp3 :: ( Eq r, Show r
              , Show a1, Arbitrary a1
              , Show a2, Arbitrary a2
              , Show a3, Arbitrary a3)
           => (a1 -> a2 -> a3 -> r)
           -> (a1 -> a2 -> a3 -> Client r)
           -> Spec
equivProp3 f m =
  equivProp $ \a1 a2 a3 -> runTest $ equiv3 f m a1 a2 a3


equiv4 :: (Eq r, Show r)
       => (a1 -> a2 -> a3 -> a4 -> r)
       -> (a1 -> a2 -> a3 -> a4 -> Client r)
       -> a1 -> a2 -> a3 -> a4 -> Client ()
equiv4 f1 f2 a1 a2 a3 a4 =
  equiv (f1 a1 a2 a3 a4)
        (f2 a1 a2 a3 a4)

equivProp4 :: ( Eq r, Show r
              , Show a1, Arbitrary a1
              , Show a2, Arbitrary a2
              , Show a3, Arbitrary a3
              , Show a4, Arbitrary a4)
           => (a1 -> a2 -> a3 -> a4 -> r)
           -> (a1 -> a2 -> a3 -> a4 -> Client r)
           -> Spec
equivProp4 f m =
  equivProp $ \a1 a2 a3 a4 -> runTest $ equiv4 f m a1 a2 a3 a4