xp_datetime,
xp_early_line_date,
xp_earnings,
+ xp_fracpart_only_double,
xp_gamedate,
xp_tba_time,
xp_time,
import System.Locale ( TimeLocale( wDays, months ), defaultTimeLocale )
import Test.Tasty ( TestTree, testGroup )
import Test.Tasty.HUnit ( (@?=), testCase )
+import Text.Read ( readMaybe )
import Text.XML.HXT.Arrow.Pickle (
xpText,
xpWrap,
reverse (intercalate "," $ chunksOf 3 $ reverse $ show x)
+
-- | Parse \<Earnings\> from an 'AutoRaceResultsListing'. These are
-- essentially 'Int's, but they look like,
--
+-- | Pickle a 'Double' that can be missing its leading zero (for
+-- values less than one). For example, we've seen,
+--
+-- <TrackLength KPH=".805">0.5</TrackLength>
+--
+-- Which 'xpPrim' can't handle without the leading
+-- zero. Unfortunately there's no way pickle/unpickle can be
+-- inverses of each other here, since \"0.5\" and \".5\" should
+-- unpickle to the same 'Double'.
+--
+-- Examples:
+--
+-- >>> let tn = text_node "0.5"
+-- >>> unpickleDoc xp_fracpart_only_double tn
+-- Just 0.5
+--
+-- >>> let tn = text_node ".5"
+-- >>> unpickleDoc xp_fracpart_only_double tn
+-- Just 0.5
+--
+-- >>> let tn = text_node "foo"
+-- >>> unpickleDoc xp_fracpart_only_double tn
+-- Nothing
+--
+xp_fracpart_only_double :: PU Double
+xp_fracpart_only_double =
+ (to_double, from_double) `xpWrapMaybe` xpText
+ where
+ -- | Convert a 'String' to a 'Double', maybe. We always prepend a
+ -- zero, since it will fix the fraction-only values, and not hurt
+ -- the ones that already have a leading integer.
+ to_double :: String -> Maybe Double
+ to_double s = readMaybe ("0" ++ s)
+
+ from_double :: Double -> String
+ from_double = show
+
+
+
-- | (Un)pickle an unpadded 'UTCTime'. Used for example on the
-- \<RaceDate\> elements in an 'AutoRaceResults' message.
--
-- Local imports.
import TSN.Codegen ( tsn_codegen_config )
import TSN.DbImport ( DbImport(..), ImportResult(..), run_dbmigrate )
-import TSN.Picklers ( xp_earnings, xp_datetime, xp_time_stamp )
+import TSN.Picklers (
+ xp_earnings,
+ xp_fracpart_only_double,
+ xp_datetime,
+ xp_time_stamp )
import TSN.XmlImport ( XmlImport(..), XmlImportFk(..) )
import Xml (
Child(..),
xp11Tuple (-- I can't think of another way to get both the
-- TrackLength and its KPH attribute. So we shove them
-- both in a 2-tuple. This should probably be an embedded type!
- xpElem "TrackLength" $ xpPair xpText (xpAttr "KPH" xpPrim) )
+ xpElem "TrackLength" $
+ xpPair xpText
+ (xpAttr "KPH" xp_fracpart_only_double) )
(xpElem "Laps" xpInt)
(xpOption $ xpElem "AverageSpeedMPH" xpPrim)
(xpOption $ xpElem "AverageSpeedKPH" xpPrim)
-- test does not mean that unpickling succeeded.
--
test_pickle_of_unpickle_is_identity :: TestTree
-test_pickle_of_unpickle_is_identity =
- testCase "pickle composed with unpickle is the identity" $ do
- let path = "test/xml/AutoRacingResultsXML.xml"
- (expected, actual) <- pickle_unpickle pickle_message path
- actual @?= expected
+test_pickle_of_unpickle_is_identity = testGroup "pickle-unpickle tests" $
+ [ check "pickle composed with unpickle is the identity"
+ "test/xml/AutoRacingResultsXML.xml",
+
+ check "pickle composed with unpickle is the identity (fractional KPH)"
+ "test/xml/AutoRacingResultsXML-fractional-kph.xml" ]
+ where
+ check desc path = testCase desc $ do
+ (expected, actual) <- pickle_unpickle pickle_message path
+ actual @?= expected
-- | Make sure we can actually unpickle these things.
--
test_unpickle_succeeds :: TestTree
-test_unpickle_succeeds =
- testCase "unpickling succeeds" $ do
- let path = "test/xml/AutoRacingResultsXML.xml"
- actual <- unpickleable path pickle_message
+test_unpickle_succeeds = testGroup "unpickle tests" $
+ [ check "unpickling succeeds"
+ "test/xml/AutoRacingResultsXML.xml",
- let expected = True
- actual @?= expected
+ check "unpickling succeeds (fractional KPH)"
+ "test/xml/AutoRacingResultsXML-fractional-kph.xml" ]
+ where
+ check desc path = testCase desc $ do
+ actual <- unpickleable path pickle_message
+ let expected = True
+ actual @?= expected
-- record.
--
test_on_delete_cascade :: TestTree
-test_on_delete_cascade =
- testCase "deleting auto_racing_results deletes its children" $ do
- let path = "test/xml/AutoRacingResultsXML.xml"
- results <- unsafe_unpickle path pickle_message
- let a = undefined :: AutoRacingResults
- let b = undefined :: AutoRacingResultsListing
- let c = undefined :: AutoRacingResultsRaceInformation
-
- actual <- withSqliteConn ":memory:" $ runDbConn $ do
- runMigration silentMigrationLogger $ do
- migrate a
- migrate b
- migrate c
- _ <- dbimport results
- deleteAll a
- count_a <- countAll a
- count_b <- countAll b
- count_c <- countAll c
- return $ sum [count_a, count_b, count_c]
- let expected = 0
- actual @?= expected
+test_on_delete_cascade = testGroup "cascading delete tests" $
+ [ check "deleting auto_racing_results deletes its children"
+ "test/xml/AutoRacingResultsXML.xml",
+
+ check "deleting auto_racing_results deletes its children (fractional KPH)"
+ "test/xml/AutoRacingResultsXML-fractional-kph.xml" ]
+ where
+ check desc path = testCase desc $ do
+ results <- unsafe_unpickle path pickle_message
+ let a = undefined :: AutoRacingResults
+ let b = undefined :: AutoRacingResultsListing
+ let c = undefined :: AutoRacingResultsRaceInformation
+
+ actual <- withSqliteConn ":memory:" $ runDbConn $ do
+ runMigration silentMigrationLogger $ do
+ migrate a
+ migrate b
+ migrate c
+ _ <- dbimport results
+ deleteAll a
+ count_a <- countAll a
+ count_b <- countAll b
+ count_c <- countAll c
+ return $ sum [count_a, count_b, count_c]
+ let expected = 0
+ actual @?= expected