From ee40d47a67da5a4213c0a452ac53471a650ebb8a Mon Sep 17 00:00:00 2001 From: Richard Marko Date: Sun, 19 Nov 2023 21:29:26 +0100 Subject: [PATCH] tests: add Test.Hspec.Nix.roundtrip --- hnix-store-remote/hnix-store-remote.cabal | 9 +- hnix-store-remote/tests/Driver.hs | 2 +- hnix-store-remote/tests/SerializeSpec.hs | 223 ++++++++----------- hnix-store-tests/hnix-store-tests.cabal | 3 +- hnix-store-tests/src/Test/Hspec/Nix.hs | 20 ++ hnix-store-tests/tests/BaseEncodingSpec.hs | 39 ++-- hnix-store-tests/tests/ContentAddressSpec.hs | 17 +- hnix-store-tests/tests/DerivationSpec.hs | 18 +- hnix-store-tests/tests/StorePathSpec.hs | 23 +- 9 files changed, 172 insertions(+), 182 deletions(-) create mode 100644 hnix-store-tests/src/Test/Hspec/Nix.hs diff --git a/hnix-store-remote/hnix-store-remote.cabal b/hnix-store-remote/hnix-store-remote.cabal index 5a4dbf0..3822852 100644 --- a/hnix-store-remote/hnix-store-remote.cabal +++ b/hnix-store-remote/hnix-store-remote.cabal @@ -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 diff --git a/hnix-store-remote/tests/Driver.hs b/hnix-store-remote/tests/Driver.hs index 70c55f5..a824f8c 100644 --- a/hnix-store-remote/tests/Driver.hs +++ b/hnix-store-remote/tests/Driver.hs @@ -1 +1 @@ -{-# OPTIONS_GHC -F -pgmF tasty-discover #-} +{-# OPTIONS_GHC -F -pgmF hspec-discover #-} diff --git a/hnix-store-remote/tests/SerializeSpec.hs b/hnix-store-remote/tests/SerializeSpec.hs index d9a2d99..8c51027 100644 --- a/hnix-store-remote/tests/SerializeSpec.hs +++ b/hnix-store-remote/tests/SerializeSpec.hs @@ -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 diff --git a/hnix-store-tests/hnix-store-tests.cabal b/hnix-store-tests/hnix-store-tests.cabal index 8017907..17eb25a 100644 --- a/hnix-store-tests/hnix-store-tests.cabal +++ b/hnix-store-tests/hnix-store-tests.cabal @@ -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 diff --git a/hnix-store-tests/src/Test/Hspec/Nix.hs b/hnix-store-tests/src/Test/Hspec/Nix.hs new file mode 100644 index 0000000..01416d9 --- /dev/null +++ b/hnix-store-tests/src/Test/Hspec/Nix.hs @@ -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 diff --git a/hnix-store-tests/tests/BaseEncodingSpec.hs b/hnix-store-tests/tests/BaseEncodingSpec.hs index a15cc30..ad696b1 100644 --- a/hnix-store-tests/tests/BaseEncodingSpec.hs +++ b/hnix-store-tests/tests/BaseEncodingSpec.hs @@ -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 diff --git a/hnix-store-tests/tests/ContentAddressSpec.hs b/hnix-store-tests/tests/ContentAddressSpec.hs index ec4123e..7acbd6c 100644 --- a/hnix-store-tests/tests/ContentAddressSpec.hs +++ b/hnix-store-tests/tests/ContentAddressSpec.hs @@ -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 diff --git a/hnix-store-tests/tests/DerivationSpec.hs b/hnix-store-tests/tests/DerivationSpec.hs index 0622bc7..650de75 100644 --- a/hnix-store-tests/tests/DerivationSpec.hs +++ b/hnix-store-tests/tests/DerivationSpec.hs @@ -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)) diff --git a/hnix-store-tests/tests/StorePathSpec.hs b/hnix-store-tests/tests/StorePathSpec.hs index 8282f86..7c83f35 100644 --- a/hnix-store-tests/tests/StorePathSpec.hs +++ b/hnix-store-tests/tests/StorePathSpec.hs @@ -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)