{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE RankNTypes #-}

-- | Tests for versioned serialisation.
--
-- Some of our serialization code supports some limited migration capability.
-- This module contains a few unit tests that check that the migration
-- infrastructure we provide works as expected.
--
module Test.Consensus.Util.Versioned (tests) where

import           Codec.CBOR.Read (deserialiseFromBytes)
import           Codec.CBOR.Write (toLazyByteString)
import           Codec.Serialise (DeserialiseFailure (..), Serialise (..))
import           GHC.Generics (Generic)
import           Ouroboros.Consensus.Util.Versioned
import           Test.Tasty
import           Test.Tasty.HUnit

tests :: TestTree
tests :: TestTree
tests = String -> [TestTree] -> TestTree
testGroup String
"Versioned"
    [ String -> Assertion -> TestTree
testCase String
"version0" Assertion
test_version0
    , String -> Assertion -> TestTree
testCase String
"version1" Assertion
test_version1
    , String -> Assertion -> TestTree
testCase String
"version2" Assertion
test_version2
    , String -> Assertion -> TestTree
testCase String
"unknown"  Assertion
test_unknown
    ]

{-------------------------------------------------------------------------------
  Setup
-------------------------------------------------------------------------------}

-- Plan:
--
-- Version2 is the current version, i.e., the type we'll try to decode. We
-- test how we handle decoding encodings of previous versions.
--
-- Version0 is missing 'field2', which is a field that cannot be
-- reconstructed, so we should fail with 'IncompatibleVersion'.
--
-- Version1 has 'field2' but misses 'field3', but we can reconstruct 'field3'
-- by adding 'field1' to 'field2'. So we should be able to decode a 'Version2'
-- from an encoding of 'Version1' using 'Migrate'.

data Version0 = Version0
    { Version0 -> Int
field1 :: Int
    }
  deriving (Version0 -> Version0 -> Bool
(Version0 -> Version0 -> Bool)
-> (Version0 -> Version0 -> Bool) -> Eq Version0
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Version0 -> Version0 -> Bool
== :: Version0 -> Version0 -> Bool
$c/= :: Version0 -> Version0 -> Bool
/= :: Version0 -> Version0 -> Bool
Eq, Int -> Version0 -> ShowS
[Version0] -> ShowS
Version0 -> String
(Int -> Version0 -> ShowS)
-> (Version0 -> String) -> ([Version0] -> ShowS) -> Show Version0
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Version0 -> ShowS
showsPrec :: Int -> Version0 -> ShowS
$cshow :: Version0 -> String
show :: Version0 -> String
$cshowList :: [Version0] -> ShowS
showList :: [Version0] -> ShowS
Show, (forall x. Version0 -> Rep Version0 x)
-> (forall x. Rep Version0 x -> Version0) -> Generic Version0
forall x. Rep Version0 x -> Version0
forall x. Version0 -> Rep Version0 x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. Version0 -> Rep Version0 x
from :: forall x. Version0 -> Rep Version0 x
$cto :: forall x. Rep Version0 x -> Version0
to :: forall x. Rep Version0 x -> Version0
Generic, [Version0] -> Encoding
Version0 -> Encoding
(Version0 -> Encoding)
-> (forall s. Decoder s Version0)
-> ([Version0] -> Encoding)
-> (forall s. Decoder s [Version0])
-> Serialise Version0
forall s. Decoder s [Version0]
forall s. Decoder s Version0
forall a.
(a -> Encoding)
-> (forall s. Decoder s a)
-> ([a] -> Encoding)
-> (forall s. Decoder s [a])
-> Serialise a
$cencode :: Version0 -> Encoding
encode :: Version0 -> Encoding
$cdecode :: forall s. Decoder s Version0
decode :: forall s. Decoder s Version0
$cencodeList :: [Version0] -> Encoding
encodeList :: [Version0] -> Encoding
$cdecodeList :: forall s. Decoder s [Version0]
decodeList :: forall s. Decoder s [Version0]
Serialise)

data Version1 = Version1
    { Version1 -> Int
field1 :: Int
    , Version1 -> Int
field2 :: Int
      -- ^ Let's say this field cannot be reconstructed from nothing
    }
  deriving (Version1 -> Version1 -> Bool
(Version1 -> Version1 -> Bool)
-> (Version1 -> Version1 -> Bool) -> Eq Version1
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Version1 -> Version1 -> Bool
== :: Version1 -> Version1 -> Bool
$c/= :: Version1 -> Version1 -> Bool
/= :: Version1 -> Version1 -> Bool
Eq, Int -> Version1 -> ShowS
[Version1] -> ShowS
Version1 -> String
(Int -> Version1 -> ShowS)
-> (Version1 -> String) -> ([Version1] -> ShowS) -> Show Version1
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Version1 -> ShowS
showsPrec :: Int -> Version1 -> ShowS
$cshow :: Version1 -> String
show :: Version1 -> String
$cshowList :: [Version1] -> ShowS
showList :: [Version1] -> ShowS
Show, (forall x. Version1 -> Rep Version1 x)
-> (forall x. Rep Version1 x -> Version1) -> Generic Version1
forall x. Rep Version1 x -> Version1
forall x. Version1 -> Rep Version1 x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. Version1 -> Rep Version1 x
from :: forall x. Version1 -> Rep Version1 x
$cto :: forall x. Rep Version1 x -> Version1
to :: forall x. Rep Version1 x -> Version1
Generic, [Version1] -> Encoding
Version1 -> Encoding
(Version1 -> Encoding)
-> (forall s. Decoder s Version1)
-> ([Version1] -> Encoding)
-> (forall s. Decoder s [Version1])
-> Serialise Version1
forall s. Decoder s [Version1]
forall s. Decoder s Version1
forall a.
(a -> Encoding)
-> (forall s. Decoder s a)
-> ([a] -> Encoding)
-> (forall s. Decoder s [a])
-> Serialise a
$cencode :: Version1 -> Encoding
encode :: Version1 -> Encoding
$cdecode :: forall s. Decoder s Version1
decode :: forall s. Decoder s Version1
$cencodeList :: [Version1] -> Encoding
encodeList :: [Version1] -> Encoding
$cdecodeList :: forall s. Decoder s [Version1]
decodeList :: forall s. Decoder s [Version1]
Serialise)

data Version2 = Version2
    { Version2 -> Int
field1 :: Int
    , Version2 -> Int
field2 :: Int
    , Version2 -> Int
field3 :: Int
      -- ^ This field is the sum of 'field1' and 'field2'
    }
  deriving (Version2 -> Version2 -> Bool
(Version2 -> Version2 -> Bool)
-> (Version2 -> Version2 -> Bool) -> Eq Version2
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Version2 -> Version2 -> Bool
== :: Version2 -> Version2 -> Bool
$c/= :: Version2 -> Version2 -> Bool
/= :: Version2 -> Version2 -> Bool
Eq, Int -> Version2 -> ShowS
[Version2] -> ShowS
Version2 -> String
(Int -> Version2 -> ShowS)
-> (Version2 -> String) -> ([Version2] -> ShowS) -> Show Version2
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Version2 -> ShowS
showsPrec :: Int -> Version2 -> ShowS
$cshow :: Version2 -> String
show :: Version2 -> String
$cshowList :: [Version2] -> ShowS
showList :: [Version2] -> ShowS
Show, (forall x. Version2 -> Rep Version2 x)
-> (forall x. Rep Version2 x -> Version2) -> Generic Version2
forall x. Rep Version2 x -> Version2
forall x. Version2 -> Rep Version2 x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. Version2 -> Rep Version2 x
from :: forall x. Version2 -> Rep Version2 x
$cto :: forall x. Rep Version2 x -> Version2
to :: forall x. Rep Version2 x -> Version2
Generic, [Version2] -> Encoding
Version2 -> Encoding
(Version2 -> Encoding)
-> (forall s. Decoder s Version2)
-> ([Version2] -> Encoding)
-> (forall s. Decoder s [Version2])
-> Serialise Version2
forall s. Decoder s [Version2]
forall s. Decoder s Version2
forall a.
(a -> Encoding)
-> (forall s. Decoder s a)
-> ([a] -> Encoding)
-> (forall s. Decoder s [a])
-> Serialise a
$cencode :: Version2 -> Encoding
encode :: Version2 -> Encoding
$cdecode :: forall s. Decoder s Version2
decode :: forall s. Decoder s Version2
$cencodeList :: [Version2] -> Encoding
encodeList :: [Version2] -> Encoding
$cdecodeList :: forall s. Decoder s [Version2]
decodeList :: forall s. Decoder s [Version2]
Serialise)

version0 :: Version0
version0 :: Version0
version0 = Int -> Version0
Version0 Int
1

version1 :: Version1
version1 :: Version1
version1 = Int -> Int -> Version1
Version1 Int
1 Int
100

version2 :: Version2
version2 :: Version2
version2 = Int -> Int -> Int -> Version2
Version2 Int
1 Int
100 Int
101

decodeLatestVersion ::
     [(VersionNumber, VersionDecoder Version2)]
decodeLatestVersion :: [(VersionNumber, VersionDecoder Version2)]
decodeLatestVersion =
    [ (VersionNumber
0, String -> VersionDecoder Version2
forall a. String -> VersionDecoder a
Incompatible String
"missing field 1")
    , (VersionNumber
1, VersionDecoder Version1
-> (Version1 -> Either String Version2) -> VersionDecoder Version2
forall from a.
VersionDecoder from
-> (from -> Either String a) -> VersionDecoder a
Migrate ((forall s. Decoder s Version1) -> VersionDecoder Version1
forall a. (forall s. Decoder s a) -> VersionDecoder a
Decode Decoder s Version1
forall s. Decoder s Version1
forall a s. Serialise a => Decoder s a
decode) ((Version1 -> Either String Version2) -> VersionDecoder Version2)
-> (Version1 -> Either String Version2) -> VersionDecoder Version2
forall a b. (a -> b) -> a -> b
$ \(Version1 Int
a Int
b) ->
        Version2 -> Either String Version2
forall a. a -> Either String a
forall (m :: * -> *) a. Monad m => a -> m a
return (Version2 -> Either String Version2)
-> Version2 -> Either String Version2
forall a b. (a -> b) -> a -> b
$ Int -> Int -> Int -> Version2
Version2 Int
a Int
b (Int
a Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
b))
    , (VersionNumber
2, (forall s. Decoder s Version2) -> VersionDecoder Version2
forall a. (forall s. Decoder s a) -> VersionDecoder a
Decode Decoder s Version2
forall s. Decoder s Version2
forall a s. Serialise a => Decoder s a
decode)
    ]

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

test_decodeVersioned
  :: Serialise a
  => [(VersionNumber, VersionDecoder b)]
  -> VersionNumber
  -> a
  -> Either String (Versioned b)
test_decodeVersioned :: forall a b.
Serialise a =>
[(VersionNumber, VersionDecoder b)]
-> VersionNumber -> a -> Either String (Versioned b)
test_decodeVersioned [(VersionNumber, VersionDecoder b)]
decs VersionNumber
vn a
a =
    case (forall s. Decoder s (Versioned b))
-> ByteString
-> Either DeserialiseFailure (ByteString, Versioned b)
forall a.
(forall s. Decoder s a)
-> ByteString -> Either DeserialiseFailure (ByteString, a)
deserialiseFromBytes
           ([(VersionNumber, VersionDecoder b)]
-> forall s. Decoder s (Versioned b)
forall a.
[(VersionNumber, VersionDecoder a)]
-> forall s. Decoder s (Versioned a)
decodeVersioned [(VersionNumber, VersionDecoder b)]
decs)
           (Encoding -> ByteString
toLazyByteString (VersionNumber -> Encoding -> Encoding
encodeVersion VersionNumber
vn (a -> Encoding
forall a. Serialise a => a -> Encoding
encode a
a))) of
      Left  (DeserialiseFailure ByteOffset
_offset String
msg) -> String -> Either String (Versioned b)
forall a b. a -> Either a b
Left  String
msg
      Right (ByteString
_unconsumed, Versioned b
b)                 -> Versioned b -> Either String (Versioned b)
forall a b. b -> Either a b
Right Versioned b
b

test_version0 :: Assertion
test_version0 :: Assertion
test_version0 =
    [(VersionNumber, VersionDecoder Version2)]
-> VersionNumber -> Version0 -> Either String (Versioned Version2)
forall a b.
Serialise a =>
[(VersionNumber, VersionDecoder b)]
-> VersionNumber -> a -> Either String (Versioned b)
test_decodeVersioned [(VersionNumber, VersionDecoder Version2)]
decodeLatestVersion VersionNumber
0 Version0
version0
      Either String (Versioned Version2)
-> Either String (Versioned Version2) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= String -> Either String (Versioned Version2)
forall a b. a -> Either a b
Left String
"IncompatibleVersion 0 \"missing field 1\""

test_version1 :: Assertion
test_version1 :: Assertion
test_version1 =
    [(VersionNumber, VersionDecoder Version2)]
-> VersionNumber -> Version1 -> Either String (Versioned Version2)
forall a b.
Serialise a =>
[(VersionNumber, VersionDecoder b)]
-> VersionNumber -> a -> Either String (Versioned b)
test_decodeVersioned [(VersionNumber, VersionDecoder Version2)]
decodeLatestVersion VersionNumber
1 Version1
version1
      Either String (Versioned Version2)
-> Either String (Versioned Version2) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Versioned Version2 -> Either String (Versioned Version2)
forall a b. b -> Either a b
Right (VersionNumber -> Version2 -> Versioned Version2
forall a. VersionNumber -> a -> Versioned a
Versioned VersionNumber
1 Version2
version2)

test_version2 :: Assertion
test_version2 :: Assertion
test_version2 =
    [(VersionNumber, VersionDecoder Version2)]
-> VersionNumber -> Version2 -> Either String (Versioned Version2)
forall a b.
Serialise a =>
[(VersionNumber, VersionDecoder b)]
-> VersionNumber -> a -> Either String (Versioned b)
test_decodeVersioned [(VersionNumber, VersionDecoder Version2)]
decodeLatestVersion VersionNumber
2 Version2
version2
      Either String (Versioned Version2)
-> Either String (Versioned Version2) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Versioned Version2 -> Either String (Versioned Version2)
forall a b. b -> Either a b
Right (VersionNumber -> Version2 -> Versioned Version2
forall a. VersionNumber -> a -> Versioned a
Versioned VersionNumber
2 Version2
version2)

test_unknown :: Assertion
test_unknown :: Assertion
test_unknown =
    [(VersionNumber, VersionDecoder Version2)]
-> VersionNumber -> Bool -> Either String (Versioned Version2)
forall a b.
Serialise a =>
[(VersionNumber, VersionDecoder b)]
-> VersionNumber -> a -> Either String (Versioned b)
test_decodeVersioned [(VersionNumber, VersionDecoder Version2)]
decodeLatestVersion VersionNumber
12 Bool
True
      Either String (Versioned Version2)
-> Either String (Versioned Version2) -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= String -> Either String (Versioned Version2)
forall a b. a -> Either a b
Left String
"UnknownVersion 12"