tests: add Test.Hspec.Nix.roundtrip

This commit is contained in:
Richard Marko 2023-11-19 21:29:26 +01:00
parent 49dc678060
commit ee40d47a67
9 changed files with 172 additions and 182 deletions

View File

@ -112,19 +112,18 @@ test-suite remote
ghc-options: -Wall
other-modules:
SerializeSpec
build-tool-depends:
hspec-discover:hspec-discover
build-depends:
base >=4.12 && <5
, hnix-store-core
, hnix-store-remote
, hnix-store-tests
, bytestring
, cereal
, text
, time
, hspec
, tasty
, tasty-hspec
, tasty-quickcheck
, QuickCheck
, quickcheck-instances
, unordered-containers
@ -142,6 +141,8 @@ test-suite remote-io
other-modules:
NixDaemon
, Spec
build-tool-depends:
tasty-discover:tasty-discover
build-depends:
base >=4.12 && <5
, hnix-store-core

View File

@ -1 +1 @@
{-# OPTIONS_GHC -F -pgmF tasty-discover #-}
{-# OPTIONS_GHC -F -pgmF hspec-discover #-}

View File

@ -1,18 +1,18 @@
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE OverloadedStrings #-}
module SerializeSpec where
module SerializeSpec (spec) where
import Data.ByteString (ByteString)
import Data.Fixed (Uni)
import Data.HashSet (HashSet)
import Data.Serialize (Serialize(..))
import Data.Serialize.Get (Get, runGet)
import Data.Serialize.Put (Putter, runPut)
import Data.Text (Text)
import Data.Time (NominalDiffTime)
import Test.Hspec (Spec, describe, it, shouldBe)
import Test.Tasty.QuickCheck
import Test.Hspec (Expectation, Spec, describe, it, shouldBe)
import Test.Hspec.QuickCheck (prop)
import Test.Hspec.Nix (roundtrips)
import Test.QuickCheck (arbitrary, forAll, suchThat)
import Test.QuickCheck.Instances ()
import qualified Data.Either
@ -23,139 +23,110 @@ import qualified System.Nix.Build
import System.Nix.Arbitrary ()
import System.Nix.Build (BuildMode, BuildStatus)
import System.Nix.Derivation (Derivation(..))
import System.Nix.StorePath (StoreDir, StorePath)
import System.Nix.Store.Remote.Serialize (getDerivation, putDerivation)
import System.Nix.Store.Remote.Serialize.Prim
roundTrip :: (Eq a, Show a) => Putter a -> Get a -> a -> Property
roundTrip p g a = res === Right a
where res = runGet g (runPut (p a))
-- | Test for roundtrip using @Putter@ and @Get@ functions
roundtrips2
:: ( Eq a
, Show a
)
=> Putter a
-> Get a
-> a
-> Expectation
roundtrips2 putter getter =
roundtrips
(runPut . putter)
(runGet getter)
-- * Prim
-- ** Int
-- | Test for roundtrip using @Serialize@ instance
roundtripS
:: ( Eq a
, Serialize a
, Show a
)
=> a
-> Expectation
roundtripS =
roundtrips
(runPut . put)
(runGet get)
prop_int :: Int -> Property
prop_int = roundTrip putInt getInt
spec :: Spec
spec = do
describe "Prim" $ do
prop "Int" $ roundtrips2 putInt getInt
prop "Bool" $ roundtrips2 putBool getBool
-- ** Bool
prop "UTCTime" $ do
let
-- scale to seconds and back
toSeconds :: Int -> NominalDiffTime
toSeconds n = realToFrac (toEnum n :: Uni)
fromSeconds :: NominalDiffTime -> Int
fromSeconds = (fromEnum :: Uni -> Int) . realToFrac
prop_bool :: Bool -> Property
prop_bool = roundTrip putBool getBool
roundtrips2
(putTime . Data.Time.Clock.POSIX.posixSecondsToUTCTime . toSeconds)
(fromSeconds . Data.Time.Clock.POSIX.utcTimeToPOSIXSeconds <$> getTime)
-- ** UTCTime
describe "Combinators" $ do
prop "Many" $ roundtrips2 (putMany putInt) (getMany getInt)
prop "ByteString" $ roundtrips2 putByteString getByteString
prop "[ByteString]" $ roundtrips2 putByteStrings getByteStrings
prop "Text" $ roundtrips2 putText getText
prop "[Text]" $ roundtrips2 putTexts getTexts
prop_time :: Int -> Property
prop_time =
roundTrip
(putTime . Data.Time.Clock.POSIX.posixSecondsToUTCTime . toSeconds)
(fromSeconds . Data.Time.Clock.POSIX.utcTimeToPOSIXSeconds <$> getTime)
where
-- scale to seconds and back
toSeconds :: Int -> NominalDiffTime
toSeconds n = realToFrac (toEnum n :: Uni)
fromSeconds :: NominalDiffTime -> Int
fromSeconds = (fromEnum :: Uni -> Int) . realToFrac
prop "StorePath" $ \sd ->
roundtrips2
(putPath sd)
(Data.Either.fromRight undefined <$> getPath sd)
-- ** Combinators
prop "HashSet StorePath" $ \sd ->
roundtrips2
(putPaths sd)
(Data.HashSet.map (Data.Either.fromRight undefined) <$> getPaths sd)
prop_many :: [Int] -> Property
prop_many = roundTrip (putMany putInt) (getMany getInt)
describe "Serialize instances" $ do
prop "Text" $ roundtripS @Text
prop "BuildMode" $ roundtripS @BuildMode
prop "BuildStatus" $ roundtripS @BuildStatus
it "BuildResult" $
forAll (arbitrary `suchThat` ((/= Just "") . System.Nix.Build.errorMessage))
$ \br ->
roundtripS
-- fix time to 0 as we test UTCTime above
$ br { System.Nix.Build.startTime = Data.Time.Clock.POSIX.posixSecondsToUTCTime 0
, System.Nix.Build.stopTime = Data.Time.Clock.POSIX.posixSecondsToUTCTime 0
}
-- ** ByteString
prop "Derivation StorePath Text" $ \sd ->
roundtrips2
(putDerivation sd)
(getDerivation sd)
-- inputDrvs is not used in remote protocol serialization
. (\drv -> drv { inputDrvs = mempty })
prop_bytestring :: ByteString -> Property
prop_bytestring = roundTrip putByteString getByteString
prop_bytestrings :: [ByteString] -> Property
prop_bytestrings = roundTrip putByteStrings getByteStrings
-- ** Text
prop_text :: Text -> Property
prop_text = roundTrip putText getText
prop_texts :: [Text] -> Property
prop_texts = roundTrip putTexts getTexts
-- ** StorePath
prop_path :: StoreDir -> StorePath -> Property
prop_path = \sd ->
roundTrip
(putPath sd)
(Data.Either.fromRight undefined <$> getPath sd)
prop_paths :: StoreDir -> HashSet StorePath -> Property
prop_paths = \sd ->
roundTrip
(putPaths sd)
(Data.HashSet.map (Data.Either.fromRight undefined) <$> getPaths sd)
-- * Serialize
roundTripS :: (Eq a, Serialize a, Show a) => a -> Property
roundTripS a = res === Right a
where res = runGet get (runPut (put a))
-- ** Text
prop_Text :: Text -> Property
prop_Text = roundTripS
-- ** BuildMode
prop_buildMode :: BuildMode -> Property
prop_buildMode = roundTripS
-- ** BuildStatus
prop_buildStatus :: BuildStatus -> Property
prop_buildStatus = roundTripS
-- ** BuildResult
prop_buildResult :: Property
prop_buildResult =
forAll (arbitrary `suchThat` ((/= Just "") . System.Nix.Build.errorMessage))
$ \br ->
roundTripS
$ br { System.Nix.Build.startTime = Data.Time.Clock.POSIX.posixSecondsToUTCTime 0
, System.Nix.Build.stopTime = Data.Time.Clock.POSIX.posixSecondsToUTCTime 0
}
-- ** Enums
spec_buildEnums :: Spec
spec_buildEnums =
let it' name constr value = it name $ runPut (put constr) `shouldBe` runPut (putInt value)
in do
describe "Build enum order matches Nix" $ do
it' "Normal" System.Nix.Build.Normal 0
it' "Repair" System.Nix.Build.Repair 1
it' "Check" System.Nix.Build.Check 2
describe "Build enum order matches Nix" $ do
it' "Normal" System.Nix.Build.Normal 0
it' "Repair" System.Nix.Build.Repair 1
it' "Check" System.Nix.Build.Check 2
describe "BuildStatus enum order matches Nix" $ do
it' "Built" System.Nix.Build.Built 0
it' "Substituted" System.Nix.Build.Substituted 1
it' "AlreadyValid" System.Nix.Build.AlreadyValid 2
it' "PermanentFailure" System.Nix.Build.PermanentFailure 3
it' "InputRejected" System.Nix.Build.InputRejected 4
it' "OutputRejected" System.Nix.Build.OutputRejected 5
it' "TransientFailure" System.Nix.Build.TransientFailure 6
it' "CachedFailure" System.Nix.Build.CachedFailure 7
it' "TimedOut" System.Nix.Build.TimedOut 8
it' "MiscFailure" System.Nix.Build.MiscFailure 9
it' "DependencyFailed" System.Nix.Build.DependencyFailed 10
it' "LogLimitExceeded" System.Nix.Build.LogLimitExceeded 11
it' "NotDeterministic" System.Nix.Build.NotDeterministic 12
it' "ResolvesToAlreadyValid" System.Nix.Build.ResolvesToAlreadyValid 13
it' "NoSubstituters" System.Nix.Build.NoSubstituters 14
-- ** Derivation
prop_derivation :: StoreDir -> Derivation StorePath Text -> Property
prop_derivation sd drv =
roundTrip
(putDerivation sd)
(getDerivation sd)
-- inputDrvs is not used in remote protocol serialization
(drv { inputDrvs = mempty })
describe "BuildStatus enum order matches Nix" $ do
it' "Built" System.Nix.Build.Built 0
it' "Substituted" System.Nix.Build.Substituted 1
it' "AlreadyValid" System.Nix.Build.AlreadyValid 2
it' "PermanentFailure" System.Nix.Build.PermanentFailure 3
it' "InputRejected" System.Nix.Build.InputRejected 4
it' "OutputRejected" System.Nix.Build.OutputRejected 5
it' "TransientFailure" System.Nix.Build.TransientFailure 6
it' "CachedFailure" System.Nix.Build.CachedFailure 7
it' "TimedOut" System.Nix.Build.TimedOut 8
it' "MiscFailure" System.Nix.Build.MiscFailure 9
it' "DependencyFailed" System.Nix.Build.DependencyFailed 10
it' "LogLimitExceeded" System.Nix.Build.LogLimitExceeded 11
it' "NotDeterministic" System.Nix.Build.NotDeterministic 12
it' "ResolvesToAlreadyValid" System.Nix.Build.ResolvesToAlreadyValid 13
it' "NoSubstituters" System.Nix.Build.NoSubstituters 14

View File

@ -42,6 +42,7 @@ library
, System.Nix.Arbitrary.DerivedPath
, System.Nix.Arbitrary.Hash
, System.Nix.Arbitrary.StorePath
, Test.Hspec.Nix
build-depends:
base >=4.12 && <5
, hnix-store-core >= 0.8
@ -49,6 +50,7 @@ library
, cryptonite
, dependent-sum > 0.7
, generic-arbitrary < 1.1
, hspec
, QuickCheck
, quickcheck-instances
, text
@ -73,7 +75,6 @@ test-suite props
, hnix-store-core
, hnix-store-tests
, attoparsec
, bytestring
, containers
, data-default-class
, QuickCheck

View File

@ -0,0 +1,20 @@
module Test.Hspec.Nix
( roundtrips
) where
import Test.Hspec (Expectation, shouldBe)
roundtrips
:: forall a b f
. ( Applicative f
, Eq (f a)
, Show a
, Show b
, Show (f a)
)
=> (a -> b) -- ^ Encode
-> (b -> f a) -- ^ Decode
-> a
-> Expectation
roundtrips encode decode x =
decode (encode x) `shouldBe` pure x

View File

@ -1,33 +1,30 @@
module BaseEncodingSpec where
import Test.Hspec (Spec, describe, shouldBe)
import Test.Hspec (Spec, describe)
import Test.Hspec.QuickCheck (prop)
import Test.QuickCheck (Gen, choose, listOf1, forAllShrink, genericShrink)
import Test.Hspec.Nix (roundtrips)
import System.Nix.Base
import System.Nix.Arbitrary ()
import System.Nix.StorePath (StorePathHashPart(..))
import qualified Data.ByteString.Char8
import qualified System.Nix.Base32
spec :: Spec
spec = do
describe "Hash" $ do
prop "Nix-like Base32 roundtrips" $
-- TODO(srk): use decodeWith
forAllShrink nonEmptyString genericShrink $ \x ->
(System.Nix.Base32.decode
. System.Nix.Base32.encode
. Data.ByteString.Char8.pack $ x)
`shouldBe`
pure (Data.ByteString.Char8.pack x)
prop "Base16 roundtrips" $ \x ->
decodeWith Base16 (encodeWith Base16 $ unStorePathHashPart x)
`shouldBe`
pure (unStorePathHashPart x)
where
nonEmptyString :: Gen String
nonEmptyString = listOf1 genSafeChar
prop "Base16 roundtrips" $
roundtrips
(encodeWith Base16)
(decodeWith Base16)
. unStorePathHashPart
genSafeChar :: Gen Char
genSafeChar = choose ('\1', '\127') -- ASCII without \NUL
prop "Nix-like Base32 roundtrips" $
roundtrips
(encodeWith NixBase32)
(decodeWith NixBase32)
. unStorePathHashPart
prop "Base64 roundtrips" $
roundtrips
(encodeWith Base64)
(decodeWith Base64)
. unStorePathHashPart

View File

@ -1,21 +1,16 @@
module ContentAddressSpec where
import Test.Hspec (Spec, describe, shouldBe)
import Test.Hspec (Spec, describe)
import Test.Hspec.QuickCheck (prop)
import Test.Hspec.Nix (roundtrips)
import System.Nix.Arbitrary ()
import qualified Data.Attoparsec.Text.Lazy
import qualified Data.Text.Lazy.Builder
import qualified System.Nix.ContentAddress
spec :: Spec
spec = do
describe "ContentAddress" $ do
prop "roundtrips" $ \caAddr ->
Data.Attoparsec.Text.Lazy.parseOnly
System.Nix.ContentAddress.contentAddressParser
(Data.Text.Lazy.Builder.toLazyText
(System.Nix.ContentAddress.contentAddressBuilder caAddr))
`shouldBe` pure caAddr
prop "roundtrips" $
roundtrips
System.Nix.ContentAddress.buildContentAddress
System.Nix.ContentAddress.parseContentAddress

View File

@ -1,7 +1,8 @@
module DerivationSpec where
import Test.Hspec (Spec, describe, shouldBe)
import Test.Hspec (Spec, describe)
import Test.Hspec.QuickCheck (xprop)
import Test.Hspec.Nix (roundtrips)
import System.Nix.Arbitrary ()
import System.Nix.Derivation (parseDerivation, buildDerivation)
@ -17,11 +18,10 @@ import qualified Data.Text.Lazy.Builder
spec :: Spec
spec = do
describe "Derivation" $ do
xprop "roundtrips via Text" $ \sd drv ->
Data.Attoparsec.Text.parseOnly (parseDerivation sd)
( Data.Text.Lazy.toStrict
$ Data.Text.Lazy.Builder.toLazyText
$ buildDerivation sd drv
)
`shouldBe` pure drv
xprop "roundtrips via Text" $ \sd ->
roundtrips
( Data.Text.Lazy.toStrict
. Data.Text.Lazy.Builder.toLazyText
. buildDerivation sd
)
(Data.Attoparsec.Text.parseOnly (parseDerivation sd))

View File

@ -1,7 +1,8 @@
module StorePathSpec where
import Test.Hspec (Spec, describe, shouldBe)
import Test.Hspec (Spec, describe)
import Test.Hspec.QuickCheck (prop)
import Test.Hspec.Nix (roundtrips)
import System.Nix.Arbitrary ()
import System.Nix.StorePath
@ -12,15 +13,19 @@ spec :: Spec
spec = do
describe "StorePath" $ do
prop "roundtrips using parsePath . storePathToRawFilePath" $
\storeDir x ->
parsePath storeDir (storePathToRawFilePath storeDir x) `shouldBe` pure x
\storeDir ->
roundtrips
(storePathToRawFilePath storeDir)
(parsePath storeDir)
prop "roundtrips using parsePathFromText . storePathToText" $
\storeDir x ->
parsePathFromText storeDir (storePathToText storeDir x) `shouldBe` pure x
\storeDir ->
roundtrips
(storePathToText storeDir)
(parsePathFromText storeDir)
prop "roundtrips using pathParser . storePathToText" $
\storeDir x ->
Data.Attoparsec.Text.parseOnly
(pathParser storeDir)
(storePathToText storeDir x) `shouldBe` pure x
\storeDir ->
roundtrips
(storePathToText storeDir)
(Data.Attoparsec.Text.parseOnly $ pathParser storeDir)