{-# 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"