1

I'm new to Haskell. I'm playing with QuickCheck tests, trying to test a simple function calculateStrengthSingle (see the source of testee below)

# Fighter.hs
module Fighter
( Quantity(Quantity)
, Fighter(TeamPlayer, LoneWolf, Polymorph)
, Strength(Strength)
, calculateStrengthSingle)
where

import System.Random

data Strength = Strength Double

instance Eq Strength where
    Strength s1 == Strength s2 =
        s1 == s2

instance Ord Strength where
    Strength s1 < Strength s2 =
        s1 < s2

data Quantity = Quantity Int deriving (Show)

instance Random Quantity where
    randomR (Quantity lo, Quantity hi) g =
        let rand = randomR (lo, hi) g
            (r, g1) = rand
        in (Quantity r, g1)
    random g =
        let rand = random g
            (r, g1) = rand
        in (Quantity r, g1)

data Fighter = TeamPlayer
             | LoneWolf
             | Polymorph
    deriving (Show)


calculateStrengthSingle :: Quantity -> Fighter -> Strength
calculateStrengthSingle (Quantity number) TeamPlayer =
    Strength(log (fromIntegral number))
calculateStrengthSingle _ _ = Strength 0.0

The test looks like this

# TestFighter.hs
import qualified Test.QuickCheck as QuickCheck
import Fighter

prop_StrengthPositive quantity fighter =
    Fighter.calculateStrengthSingle quantity fighter >= Strength 0.0

instance QuickCheck.Arbitrary Fighter.Fighter where
    arbitrary = QuickCheck.oneof([return Fighter.TeamPlayer, return Fighter.LoneWolf, return Fighter.Polymorph])

instance QuickCheck.Arbitrary Fighter.Quantity where
    arbitrary = QuickCheck.choose(Fighter.Quantity 1, Fighter.Quantity 10)

main :: IO()
main = do
    QuickCheck.quickCheck prop_StrengthPositive

When I do runhaskell TestFighter.hs there's output (1 test) (the number is changing, sometimes it's 0 other times it's 4) and CPU is 100% loaded. Nothing happens for a minute or so. When I interrupt the program by Ctrl+C, it spits out something like

^C*** Failed! Exception: 'user interrupt' (after 1 test):  
Quantity 2
TeamPlayer

Questions:

  1. Where did I mess up?
  2. How can I debug cases of infinite calculation, like this one?
Michael Pankov
  • 3,581
  • 2
  • 23
  • 31

2 Answers2

7

You haven't defined the Ord instance for Strength correctly. You need to define <= and not <.

With only < defined the function <= enters into an infinite loop as it is defined in terms of compare and compare is defined in terms of <=. Minimal definition needs to define either compare or <=.

Here is the fixed code for Ord instance

instance Ord Strength where
    Strength s1 <= Strength s2 =
        s1 <= s2
Satvik
  • 11,238
  • 1
  • 38
  • 46
  • What about debugging such stuff? How can I figure out what do I miss? – Michael Pankov Aug 13 '13 at 05:20
  • 2
    There is no general way of doing that. What I usually do is to use `verboseCheck` instead of `quickCheck` to see what are the tests it is failing on and then try to go through the test manually. That is how I found the `Ord` instance was the culprit in your case. – Satvik Aug 13 '13 at 05:48
1

You could try running your test with -o 1 as a parameter, which causes a timeout after one second. It can then display the input that caused your program to hang. Its doesn't tell you exactly where the problem is, but its a good start.

EDIT: this is how I use test framework with cabal

test-Suite runTests
   hs-source-dirs:      src, test
   type:                exitcode-stdio-1.0
   main-is:             RunTests.hs
   other-modules:
                       <...>

  build-depends:       base >= 3 && < 5,
                       HUnit,
                       test-framework,
                       test-framework-th,
                       test-framework-hunit,
                       test-framework-quickcheck2,
                       QuickCheck,

To be complete in my description, I build using:

cabal-dev install --enable-tests
OllieB
  • 1,431
  • 9
  • 14
  • In my GHC, `-o` is setting filename, not timeout – Michael Pankov Aug 13 '13 at 05:23
  • Sorry, I should have been more specific. I use -o 1 as a runtime parameter, but I also use quickcheck wrapped with test-framework, so there may be a discrepancy between options here. http://hackage.haskell.org/package/test-framework – OllieB Aug 13 '13 at 07:26
  • if you'd like to go down the test-framework road, look at test-framework-th, this minimises typing. – OllieB Aug 13 '13 at 07:28
  • Maybe you can also link me a way to integrate the `test-framework` with `cabal test`? It seems like `cabal` only supports HUnit and QuickCheck by themselves, not the unifying `test-framework`. – Michael Pankov Aug 14 '13 at 10:20