| {-# LANGUAGE TemplateHaskell #-} |
| {-# OPTIONS_GHC -fno-warn-orphans #-} |
| |
| {-| Unittests for @xm list --long@ parser -} |
| |
| {- |
| |
| Copyright (C) 2013 Google Inc. |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program; if not, write to the Free Software |
| Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301, USA. |
| |
| -} |
| |
| module Test.Ganeti.Hypervisor.Xen.XmParser |
| ( testHypervisor_Xen_XmParser |
| ) where |
| |
| import Test.HUnit |
| import Test.QuickCheck as QuickCheck hiding (Result) |
| |
| import Test.Ganeti.TestHelper |
| import Test.Ganeti.TestCommon |
| |
| import Control.Monad (liftM) |
| import qualified Data.Attoparsec.Text as A |
| import Data.Text (pack) |
| import Data.Char |
| import qualified Data.Map as Map |
| import Text.Printf |
| |
| import Ganeti.Hypervisor.Xen.Types |
| import Ganeti.Hypervisor.Xen.XmParser |
| |
| {-# ANN module "HLint: ignore Use camelCase" #-} |
| |
| -- * Arbitraries |
| |
| -- | Generator for 'ListConfig'. |
| -- |
| -- A completely arbitrary configuration would contain too many lists |
| -- and its size would be to big to be actually parsable in reasonable |
| -- time. This generator builds a random Config that is still of a |
| -- reasonable size, and it also Avoids generating strings that might |
| -- be interpreted as numbers. |
| genConfig :: Int -> Gen LispConfig |
| genConfig 0 = |
| -- only terminal values for size 0 |
| frequency [ (5, liftM LCString (genName `suchThat` (not . canBeNumber))) |
| , (5, liftM LCDouble arbitrary) |
| ] |
| genConfig n = |
| -- for size greater than 0, allow "some" lists |
| frequency [ (5, liftM LCString (resize n genName `suchThat` |
| (not . canBeNumber))) |
| , (5, liftM LCDouble arbitrary) |
| , (1, liftM LCList (choose (1, n) >>= |
| (\n' -> vectorOf n' (genConfig $ n `div` n')))) |
| ] |
| |
| -- | Arbitrary instance for 'LispConfig' using 'genConfig'. |
| instance Arbitrary LispConfig where |
| arbitrary = sized genConfig |
| |
| -- | Determines conservatively whether a string could be a number. |
| canBeNumber :: String -> Bool |
| canBeNumber [] = False |
| canBeNumber [c] = canBeNumberChar c |
| canBeNumber (c:xs) = canBeNumberChar c && canBeNumber xs |
| |
| -- | Determines whether a char can be part of the string representation of a |
| -- number (even in scientific notation). |
| canBeNumberChar :: Char -> Bool |
| canBeNumberChar c = isDigit c || (c `elem` "eE-") |
| |
| -- | Generates an arbitrary @xm uptime@ output line. |
| instance Arbitrary UptimeInfo where |
| arbitrary = do |
| name <- genFQDN |
| NonNegative idNum <- arbitrary :: Gen (NonNegative Int) |
| NonNegative days <- arbitrary :: Gen (NonNegative Int) |
| hours <- choose (0, 23) :: Gen Int |
| mins <- choose (0, 59) :: Gen Int |
| secs <- choose (0, 59) :: Gen Int |
| let uptime :: String |
| uptime = |
| if days /= 0 |
| then printf "%d days, %d:%d:%d" days hours mins secs |
| else printf "%d:%d:%d" hours mins secs |
| return $ UptimeInfo name idNum uptime |
| |
| -- * Helper functions for tests |
| |
| -- | Function for testing whether a domain configuration is parsed correctly. |
| testDomain :: String -> Map.Map String Domain -> Assertion |
| testDomain fileName expectedContent = do |
| fileContent <- readTestData fileName |
| case A.parseOnly xmListParser $ pack fileContent of |
| Left msg -> assertFailure $ "Parsing failed: " ++ msg |
| Right obtained -> assertEqual fileName expectedContent obtained |
| |
| -- | Function for testing whether a @xm uptime@ output (stored in a file) |
| -- is parsed correctly. |
| testUptimeInfo :: String -> Map.Map Int UptimeInfo -> Assertion |
| testUptimeInfo fileName expectedContent = do |
| fileContent <- readTestData fileName |
| case A.parseOnly xmUptimeParser $ pack fileContent of |
| Left msg -> assertFailure $ "Parsing failed: " ++ msg |
| Right obtained -> assertEqual fileName expectedContent obtained |
| |
| -- | Computes the relative error of two 'Double' numbers. |
| -- |
| -- This is the \"relative error\" algorithm in |
| -- http:\/\/randomascii.wordpress.com\/2012\/02\/25\/ |
| -- comparing-floating-point-numbers-2012-edition (URL split due to too |
| -- long line). |
| relativeError :: Double -> Double -> Double |
| relativeError d1 d2 = |
| let delta = abs $ d1 - d2 |
| a1 = abs d1 |
| a2 = abs d2 |
| greatest = max a1 a2 |
| in if delta == 0 |
| then 0 |
| else delta / greatest |
| |
| -- | Determines whether two LispConfig are equal, with the exception of Double |
| -- values, that just need to be \"almost equal\". |
| -- |
| -- Meant mainly for testing purposes, given that Double values may be slightly |
| -- rounded during parsing. |
| isAlmostEqual :: LispConfig -> LispConfig -> Property |
| isAlmostEqual (LCList c1) (LCList c2) = |
| (length c1 ==? length c2) .&&. |
| conjoin (zipWith isAlmostEqual c1 c2) |
| isAlmostEqual (LCString s1) (LCString s2) = s1 ==? s2 |
| isAlmostEqual (LCDouble d1) (LCDouble d2) = printTestCase msg $ rel <= 1e-12 |
| where rel = relativeError d1 d2 |
| msg = "Relative error " ++ show rel ++ " not smaller than 1e-12\n" ++ |
| "expected: " ++ show d2 ++ "\n but got: " ++ show d1 |
| isAlmostEqual a b = |
| failTest $ "Comparing different types: '" ++ show a ++ "' with '" ++ |
| show b ++ "'" |
| |
| -- | Function to serialize LispConfigs in such a way that they can be rebuilt |
| -- again by the lispConfigParser. |
| serializeConf :: LispConfig -> String |
| serializeConf (LCList c) = "(" ++ unwords (map serializeConf c) ++ ")" |
| serializeConf (LCString s) = s |
| serializeConf (LCDouble d) = show d |
| |
| -- | Function to serialize UptimeInfos in such a way that they can be rebuilt |
| -- againg by the uptimeLineParser. |
| serializeUptime :: UptimeInfo -> String |
| serializeUptime (UptimeInfo name idNum uptime) = |
| printf "%s\t%d\t%s" name idNum uptime |
| |
| -- | Test whether a randomly generated config can be parsed. |
| -- Implicitly, this also tests that the Show instance of Config is correct. |
| prop_config :: LispConfig -> Property |
| prop_config conf = |
| case A.parseOnly lispConfigParser . pack . serializeConf $ conf of |
| Left msg -> failTest $ "Parsing failed: " ++ msg |
| Right obtained -> printTestCase "Failing almost equal check" $ |
| isAlmostEqual obtained conf |
| |
| -- | Test whether a randomly generated UptimeInfo text line can be parsed. |
| prop_uptimeInfo :: UptimeInfo -> Property |
| prop_uptimeInfo uInfo = |
| case A.parseOnly uptimeLineParser . pack . serializeUptime $ uInfo of |
| Left msg -> failTest $ "Parsing failed: " ++ msg |
| Right obtained -> obtained ==? uInfo |
| |
| -- | Test a Xen 4.0.1 @xm list --long@ output. |
| case_xen401list :: Assertion |
| case_xen401list = testDomain "xen-xm-list-long-4.0.1.txt" $ |
| Map.fromList |
| [ ("Domain-0", Domain 0 "Domain-0" 184000.41332 ActualRunning Nothing) |
| , ("instance1.example.com", Domain 119 "instance1.example.com" 24.116146647 |
| ActualBlocked Nothing) |
| ] |
| |
| -- | Test a Xen 4.0.1 @xm uptime@ output. |
| case_xen401uptime :: Assertion |
| case_xen401uptime = testUptimeInfo "xen-xm-uptime-4.0.1.txt" $ |
| Map.fromList |
| [ (0, UptimeInfo "Domain-0" 0 "98 days, 2:27:44") |
| , (119, UptimeInfo "instance1.example.com" 119 "15 days, 20:57:07") |
| ] |
| |
| testSuite "Hypervisor/Xen/XmParser" |
| [ 'prop_config |
| , 'prop_uptimeInfo |
| , 'case_xen401list |
| , 'case_xen401uptime |
| ] |