diff --git a/.gitignore b/.gitignore index f6dcb60..0ff1dcc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ Session.vim +cabal.project.local dist-newstyle/ diff --git a/bench/Main.hs b/bench/Main.hs index 7fb590d..a3aa271 100644 --- a/bench/Main.hs +++ b/bench/Main.hs @@ -1,10 +1,10 @@ module Main (main) where -import Control.DeepSeq (deepseq, force) +import Control.DeepSeq (force) import Control.Exception (evaluate) import List.Shuffle qualified as List import System.Random qualified as Random -import Test.Tasty.Bench (bench, defaultMain, nf, whnf) +import Test.Tasty.Bench (bench, defaultMain, whnf) main :: IO () main = do diff --git a/list-shuffle.cabal b/list-shuffle.cabal index a59d527..c8c2734 100644 --- a/list-shuffle.cabal +++ b/list-shuffle.cabal @@ -2,28 +2,45 @@ cabal-version: 2.4 name: list-shuffle version: 0 -library - build-depends: - base, - primitive, - random, +common component default-extensions: BlockArguments LambdaCase + OverloadedStrings PatternSynonyms default-language: GHC2021 - hs-source-dirs: src ghc-options: -Wall + +library + import: component + build-depends: + base, + primitive, + random, + hs-source-dirs: src exposed-modules: List.Shuffle +test-suite test + import: component + build-depends: + base, + containers, + hedgehog ^>= 1.4, + list-shuffle, + random, + ghc-options: -rtsopts -threaded "-with-rtsopts=-N4" + hs-source-dirs: test + main-is: Main.hs + type: exitcode-stdio-1.0 + benchmark bench + import: component build-depends: base, deepseq, list-shuffle, random, tasty-bench, - default-language: GHC2021 ghc-options: -O "-with-rtsopts=-A32m -T" hs-source-dirs: bench main-is: Main.hs diff --git a/test/Main.hs b/test/Main.hs new file mode 100644 index 0000000..0cf609b --- /dev/null +++ b/test/Main.hs @@ -0,0 +1,57 @@ +module Main (main) where + +import Data.List qualified as List +import Data.Ord (clamp) +import Data.Word +import Hedgehog +import Hedgehog.Gen qualified as Gen +import Hedgehog.Main +import Hedgehog.Range qualified as Range +import List.Shuffle qualified as List +import System.Random qualified as Random + +main :: IO () +main = do + defaultMain [checkParallel (Group "tests" (map (\(name, prop) -> (name, withTests 10000 (property prop))) tests))] + +tests :: [(PropertyName, PropertyT IO ())] +tests = + [ ( "shuffle preserves list elements", + do + list <- generateList + gen <- generateGen + List.sort (List.shuffle_ list gen) === List.sort list + ), + ( "sample returns the requested number of elements", + do + list <- generateList + gen <- generateGen + n <- forAll (Gen.int (Range.linearFrom 0 (-30) (30))) + length (List.sample_ n list gen) === clamp (0, length list) n + ), + ( "sample returns a subset of list elements", + do + list <- generateList + gen <- generateGen + n <- forAll (Gen.int (Range.linearFrom 0 (-30) (30))) + assert (List.sort (List.sample_ n list gen) `isSubsetOf` List.sort list) + ) + ] + +generateList :: PropertyT IO [Word8] +generateList = + forAll (Gen.list (Range.linear 0 200) (Gen.word8 Range.linearBounded)) + +generateGen :: PropertyT IO Random.StdGen +generateGen = + Random.mkStdGen <$> forAll (Gen.int Range.constantBounded) + +-- precondition: lists are sorted +isSubsetOf :: (Ord a) => [a] -> [a] -> Bool +isSubsetOf [] _ = True +isSubsetOf (_ : _) [] = False +isSubsetOf (x : xs) (y : ys) = + case compare x y of + LT -> False + EQ -> isSubsetOf xs ys + GT -> isSubsetOf (x : xs) ys