{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} -- | This test suite for the `python-pickle` package uses Python to dump -- various pickled objects to a temporary file, and for each dump checks -- that `Language.Python.Pickle` can unpickle the object, and repickle it -- exactly as the original dump. module Main (main) where import Control.Arrow ((&&&)) import Control.Monad (when) import qualified Data.ByteString as S import qualified Data.ByteString.Char8 as C import Data.String (IsString) import qualified Data.Map as M import System.Directory (removeFile) import System.Process (rawSystem) import Test.HUnit (assertEqual, assertFailure) import Test.Framework (defaultMain, testGroup, Test) import Test.Framework.Providers.HUnit (testCase) import Language.Python.Pickle main :: IO () main = defaultMain tests tests :: [Test] tests = [ testGroup "PickledValues protocol 0" $ map (\(a, b) -> testCase a $ testAgainstPython 0 b a) expressions , testGroup "PickledValues protocol 1" $ map (\(a, b) -> testCase a $ testAgainstPython 1 b a) expressions , testGroup "PickledValues protocol 2" $ map (\(a, b) -> testCase a $ testAgainstPython 2 b a) expressions ] -- The round-tripping (unpickling/pickling and comparing the original -- with the result) is not enough. For instance incorrectly casting ints to -- ints can be hidden (the unpickled value is incorrect but when pickled again, it -- is the same as the original). -- So we can provide an expected value. testAgainstPython :: Int -> Value -> String -> IO () testAgainstPython protocol expected s = do let filename = "python-pickle-test-pickled-values.pickle" _ <- rawSystem "./tests/pickle-dump.py" ["--output", filename, "--protocol", show protocol, "--", s] content <- S.readFile filename let value = unpickle content case value of Left err -> assertFailure $ "Can't unpickle " ++ s ++ show content ++ ".\nUnpickling error:\n " ++ err Right v -> do assertEqual "Unpickled value against expected value" expected v when (protocol == 2) $ assertEqual "Pickled value against original file content" content (pickle v) removeFile filename expressions :: [(String, Value)] expressions = [ ("{}", Dict M.empty) , ("{'type': 'cache-query'}", Dict $ M.fromList [(BinString "type", BinString "cache-query")]) , ("{'type': 'cache-query', 'metric': 'some.metric.name'}", Dict $ M.fromList [(BinString "type", BinString "cache-query"), (BinString "metric", BinString "some.metric.name")]) , ("[]", List []) , ("[1]", List [BinInt 1]) , ("[1, 2]", List [BinInt 1, BinInt 2]) , ("()", Tuple []) , ("(1,)", Tuple [BinInt 1]) , ("(1, 2)", Tuple [BinInt 1, BinInt 2]) , ("(1, 2, 3)", Tuple [BinInt 1, BinInt 2, BinInt 3]) , ("(1, 2, 3, 4)", Tuple [BinInt 1, BinInt 2, BinInt 3, BinInt 4]) , ("((), [], [3, 4], {})", Tuple [Tuple [], List [], List [BinInt 3, BinInt 4], Dict M.empty]) , ("None", None) , ("True", Bool True) , ("False", Bool False) , ("{'datapoints': [(1, 2)]}", Dict $ M.fromList [(BinString "datapoints", List [Tuple [BinInt 1, BinInt 2]])]) , ("{'datapoints': [(1, 2)], (2,): 'foo'}", Dict $ M.fromList [ ( BinString "datapoints" , List [Tuple [BinInt 1, BinInt 2]]) , ( Tuple [BinInt 2] , BinString "foo") ]) , ("[(1, 2)]", List [Tuple [BinInt 1, BinInt 2]]) , ("('twice', 'twice')", Tuple [BinString "twice", BinString "twice"]) ] ++ map (show &&& BinInt) ints ++ map (show &&& BinFloat) doubles ++ map (quote . C.unpack &&& BinString) strings ++ map ((++ "L") . show &&& BinLong) ints ints :: [Int] ints = [0, 10..100] ++ [100, 150..1000] ++ [1000, 1500..10000] ++ [10000, 50000..1000000] ++ map negate ([1,126,127,128,129] ++ [10000, 50000..1000000]) doubles :: [Double] doubles = [0.1, 10.1..100] ++ map negate [0.1, 10.1..100] strings :: [C.ByteString] strings = ["cache-query"] quote :: IsString [a] => [a] -> [a] quote s = concat ["'", s, "'"]