Skip to content

largest-series-product: Rewrite tests to use hspec with fail-fast. #257

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 6, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion exercises/largest-series-product/package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ library:
dependencies:
# - foo # List here the packages you
# - bar # want to use in your solution.
- safe

tests:
test:
main: Tests.hs
source-dirs: test
dependencies:
- largest-series-product
- HUnit
- hspec
27 changes: 16 additions & 11 deletions exercises/largest-series-product/src/Example.hs
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
module Series (largestProduct) where
import Data.Char (digitToInt)
import Data.List (tails)

digits :: Integral a => String -> [a]
digits = map $ fromIntegral . digitToInt
import Control.Monad ((>=>))
import Data.Char (digitToInt, isDigit)
import Data.List (tails)
import Data.Maybe (mapMaybe)
import Safe (maximumMay)
import Safe.Exact (takeExactMay)

slices :: Integral a => Int -> String -> [[a]]
slices n = go . digits
where go xs = map (take n) $ take (length xs - pred n) (tails xs)
-- base >= 4.8 re-exports `Data.Traversable.traverse`.
import Data.Traversable -- This is only need for `traversable`.
import Prelude -- This trick avoids a warning if GHC >= 7.10.

largestProduct :: Integral a => Int -> String -> Maybe a
largestProduct n text = case map product (slices n text) of
[] -> Nothing
products -> Just $ maximum products
largestProduct :: (Integral a, Num b, Ord b) => a -> String -> Maybe b
largestProduct n = traverse charToNum >=> maximumMay . products
where
products = mapMaybe (fmap product . takeExactMay (fromIntegral n)) . tails
charToNum x
| isDigit x = Just . fromIntegral . digitToInt $ x
| otherwise = Nothing
139 changes: 101 additions & 38 deletions exercises/largest-series-product/test/Tests.hs
Original file line number Diff line number Diff line change
@@ -1,41 +1,104 @@
import Test.HUnit ((@=?), runTestTT, Test(..), Counts(..))
import System.Exit (ExitCode(..), exitWith)
import Series (largestProduct)
{-# OPTIONS_GHC -fno-warn-type-defaults #-}

import Test.Hspec (Spec, describe, it, shouldBe)
import Test.Hspec.Runner (configFastFail, defaultConfig, hspecWith)

exitProperly :: IO Counts -> IO ()
exitProperly m = do
counts <- m
exitWith $ if failures counts /= 0 || errors counts /= 0 then ExitFailure 1 else ExitSuccess
import Series (largestProduct)

main :: IO ()
main = exitProperly $ runTestTT $ TestList
[ TestList seriesTests ]

-- Allow implementations that work with Integral to compile without warning
int :: Int -> Maybe Int
int = Just

intNothing :: Maybe Int
intNothing = Nothing

seriesTests :: [Test]
seriesTests = map TestCase
[ int 72 @=? largestProduct 2 "0123456789"
, int 2 @=? largestProduct 2 "12"
, int 9 @=? largestProduct 2 "19"
, int 48 @=? largestProduct 2 "576802143"
, int 504 @=? largestProduct 3 ['0'..'9']
, int 270 @=? largestProduct 3 "1027839564"
, int 15120 @=? largestProduct 5 ['0'..'9']
, int 23520 @=?
largestProduct 6 "73167176531330624919225119674426574742355349194934"
, int 28350 @=?
largestProduct 6 "52677741234314237566414902593461595376319419139427"
, int 1 @=? largestProduct 0 ""
, int 1 @=? largestProduct 0 "123"
, intNothing @=? largestProduct 1 ""
, intNothing @=? largestProduct 4 "123"
-- if all spans contain zero, result is zero.
, int 0 @=? largestProduct 3 "99099"
, int 0 @=? largestProduct 2 "00"
]
main = hspecWith defaultConfig {configFastFail = True} specs

specs :: Spec
specs = describe "largest-series-product" $

-- Test cases adapted from `exercism/x-common/largest-series-product.json`
-- on 2016-07-27.

describe "largestProduct" $ do

it "can find the largest product of 2 with numbers in order" $
largestProduct 2 "0123456789"
`shouldBe` Just 72

it "can find the largest product of 2" $
largestProduct 2 "576802143"
`shouldBe` Just 48

it "finds the largest product if span equals length" $
largestProduct 2 "29"
`shouldBe` Just 18

it "can find the largest product of 3 with numbers in order" $
largestProduct 3 "0123456789"
`shouldBe` Just 504

it "can find the largest product of 3" $
largestProduct 3 "1027839564"
`shouldBe` Just 270

it "can find the largest product of 5 with numbers in order" $
largestProduct 5 "0123456789"
`shouldBe` Just 15120

it "can get the largest product of a big number" $
largestProduct 6 "73167176531330624919225119674426574742355349194934"
`shouldBe` Just 23520

it "can get the largest product of a big number II" $
largestProduct 6 "52677741234314237566414902593461595376319419139427"
`shouldBe` Just 28350

it "can get the largest product of a big number (Project Euler)" $
largestProduct 13 "73167176531330624919225119674426574742355349194934\
\96983520312774506326239578318016984801869478851843\
\85861560789112949495459501737958331952853208805511\
\12540698747158523863050715693290963295227443043557\
\66896648950445244523161731856403098711121722383113\
\62229893423380308135336276614282806444486645238749\
\30358907296290491560440772390713810515859307960866\
\70172427121883998797908792274921901699720888093776\
\65727333001053367881220235421809751254540594752243\
\52584907711670556013604839586446706324415722155397\
\53697817977846174064955149290862569321978468622482\
\83972241375657056057490261407972968652414535100474\
\82166370484403199890008895243450658541227588666881\
\16427171479924442928230863465674813919123162824586\
\17866458359124566529476545682848912883142607690042\
\24219022671055626321111109370544217506941658960408\
\07198403850962455444362981230987879927244284909188\
\84580156166097919133875499200524063689912560717606\
\05886116467109405077541002256983155200055935729725\
\71636269561882670428252483600823257530420752963450"
`shouldBe` Just 23514624000

it "reports zero if the only digits are zero" $
largestProduct 2 "0000"
`shouldBe` Just 0

it "reports zero if all spans include zero" $
largestProduct 3 "99099"
`shouldBe` Just 0

it "rejects span longer than string length" $
largestProduct 4 "123"
`shouldBe` Nothing

it "reports 1 for empty string and empty product (0 span)" $
largestProduct 0 ""
`shouldBe` Just 1

it "reports 1 for nonempty string and empty product (0 span)" $
largestProduct 0 "123"
`shouldBe` Just 1

it "rejects empty string and nonzero span" $
largestProduct 1 ""
`shouldBe` Nothing

it "rejects invalid character in digits" $
largestProduct 2 "1234a5"
`shouldBe` Nothing

it "rejects negative span" $
largestProduct (-1) "12345"
`shouldBe` Nothing