unison/unison-src/EasyTest.u

265 lines
7.6 KiB
Plaintext
Raw Normal View History

2019-05-23 00:50:08 +03:00
use Test Success Status Report Test Scope
use Test.Status Failed Expected Unexpected Pending
use Test.Success Passed Proved
use Test.Report Report
use Test.Test Test
use Test passed proved failed expected unexpected pending finished label
use Test.Scope Scope
2019-06-27 21:30:50 +03:00
use List flatMap
2019-05-23 00:50:08 +03:00
2019-05-14 20:43:21 +03:00
type Test.Success = Passed Nat | Proved
2019-05-13 23:25:29 +03:00
2019-05-14 20:43:21 +03:00
type Test.Status = Failed
| Expected Test.Success
| Unexpected Test.Success
| Pending
2019-05-22 18:47:18 +03:00
-- Current scope together with accumulated test report.
2019-06-28 07:15:45 +03:00
type Test.Report = Report (Trie Text Test.Status)
2019-05-14 20:43:21 +03:00
2019-05-22 18:47:18 +03:00
type Test.Test = Test (Test.Scope -> Test.Report)
2019-05-14 20:43:21 +03:00
2019-05-22 18:47:18 +03:00
unique type Test.Scope = Scope [Text]
2019-05-14 20:43:21 +03:00
2019-05-22 18:47:18 +03:00
foldSuccess : (Nat -> r) -> r -> Success -> r
foldSuccess passed proved s = case s of
Passed n -> passed n
Proved -> proved
foldStatus : r -> (Success -> r) -> (Success -> r) -> r -> Status -> r
foldStatus failed expected unexpected pending status = case status of
Failed -> failed
Expected s -> expected s
Unexpected s -> unexpected s
Pending -> pending
2019-06-28 07:15:45 +03:00
foldReport : (Trie Text Test.Status -> r) -> Report -> r
2019-05-22 18:47:18 +03:00
foldReport k r = case r of Report t -> k t
foldScope : ([Text] -> r) -> Scope -> r
foldScope k s = case s of Scope ss -> k ss
Scope.cons : Text -> Scope -> Scope
2019-06-27 21:30:50 +03:00
Scope.cons n = foldScope (Scope . List.cons n)
2019-05-22 18:47:18 +03:00
-- Basic building blocks of tests
Test.finished : Status -> Test
Test.finished st =
2019-06-28 07:15:45 +03:00
Test (Report . foldScope (sc -> Trie.singleton sc st))
2019-05-22 18:47:18 +03:00
Test.failed : Test
Test.failed = finished Failed
Test.proved : Test
Test.proved = finished <| Expected Proved
Test.passed : Test
Test.passed = finished . Expected <| Passed 1
Test.passedUnexpectedly : Test
Test.passedUnexpectedly = finished . Unexpected <| Passed 1
Test.provedUnexpectedly : Test
Test.provedUnexpectedly = finished <| Unexpected Proved
-- Basic test combinators
Test.modifyStatus : (Status -> Status) -> Test -> Test
Test.modifyStatus f t =
2019-06-28 07:15:45 +03:00
case t of Test k -> Test (foldReport (Report . map f) . k)
2019-05-22 18:47:18 +03:00
Test.label : Text -> Test -> Test
Test.label n t = case t of
2019-05-23 00:50:08 +03:00
Test.Test.Test k -> Test (scope -> k <| Scope.cons n scope)
use Test.Report combine
2019-05-22 18:47:18 +03:00
(Test.&&) : Test -> Test -> Test
(Test.&&) a b = case (a,b) of
(Test k1, Test k2) ->
Test (
scope ->
let r1 = k1 scope
r2 = k2 scope
combine r1 r2)
Test.passedWith : Text -> Test
2019-05-23 17:24:44 +03:00
Test.passedWith m = label m passed
2019-05-22 18:47:18 +03:00
Test.provedWith : Text -> Test
2019-05-23 17:24:44 +03:00
Test.provedWith m = label m proved
2019-05-22 18:47:18 +03:00
Test.failedWith : Text -> Test
2019-05-23 17:24:44 +03:00
Test.failedWith m = Test.label m Test.failed
2019-05-22 18:47:18 +03:00
-- Report combinators
Test.Report.combine : Report -> Report -> Report
Test.Report.combine r1 r2 = case (r1, r2) of
(Test.Report.Report t1, Test.Report.Report t2) ->
2019-06-28 07:15:45 +03:00
Report <| Trie.unionWith Status.combine t1 t2
2019-05-22 18:47:18 +03:00
Test.Report.empty : Report
Test.Report.empty = Report empty
Test.Report.toCLIResult : Report -> [Test.Result]
Test.Report.toCLIResult r =
descend scope p = case p of (k, t) ->
go ((if scope != "" then (scope ++ ".") else "") ++ k) t
convert : Text -> Test.Status -> Test.Result
convert scope s = case s of
Test.Status.Failed -> Test.Result.Fail scope
Test.Status.Expected (Test.Success.Passed n) ->
Test.Result.Ok (scope ++ " : Passed " ++ Nat.toText n ++ " tests.")
Test.Status.Expected (Test.Success.Proved) ->
Test.Result.Ok (scope ++ " : Proved.")
2019-06-28 07:15:45 +03:00
go : Text -> Trie Text Test.Status -> [Test.Result]
2019-05-22 18:47:18 +03:00
go scope t =
2019-06-27 21:30:50 +03:00
rest = flatMap (descend scope) (Map.toList (tail t))
2019-05-22 18:47:18 +03:00
case head t of
2019-06-28 07:15:45 +03:00
Optional.Some status ->
cons (convert scope status) rest
2019-05-22 18:47:18 +03:00
Optional.None -> rest
case r of Test.Report.Report t -> go "" t
Test.report : Test -> Report
Test.report t = case t of Test k -> k (Scope [])
2019-05-23 00:50:08 +03:00
-- Running tests
2019-05-22 18:47:18 +03:00
Test.run : Test -> [Test.Result]
2019-05-23 00:50:08 +03:00
Test.run = Test.Report.toCLIResult . Test.report
2019-05-13 23:25:29 +03:00
2019-05-22 18:47:18 +03:00
Test.runAll : [Test] -> [Test.Result]
Test.runAll = flatMap Test.run
-- Status combinators
2019-05-13 23:25:29 +03:00
2019-05-14 20:43:21 +03:00
Status.combine : Test.Status -> Test.Status -> Test.Status
2019-05-13 23:25:29 +03:00
Status.combine s1 s2 = case (s1, s2) of
(_, Pending) -> Pending
(Pending, _) -> Pending
(Failed, _) -> Failed
(_, Failed) -> Failed
2019-06-28 07:15:45 +03:00
(Unexpected a, Unexpected b) -> Unexpected (Success.combine a b)
(Unexpected a, _) -> Unexpected a
(_, Unexpected b) -> Unexpected b
2019-05-22 18:47:18 +03:00
(Expected a, Expected b) -> Expected (Success.combine a b)
2019-05-13 23:25:29 +03:00
2019-06-28 07:15:45 +03:00
2019-05-14 20:43:21 +03:00
Status.pending : Test.Status -> Test.Status
2019-05-13 23:25:29 +03:00
Status.pending s = case s of
Failed -> Pending
Expected s -> Unexpected s
Unexpected s -> Pending
Pending -> Pending
2019-05-22 18:47:18 +03:00
-- Make a test pending
Test.pending : Test -> Test
Test.pending = modifyStatus Status.pending
Test.modifyScope : (Scope -> Scope) -> Test -> Test
Test.modifyScope f t = case t of Test k -> Test (k . f)
2019-05-14 20:43:21 +03:00
2019-05-13 23:25:29 +03:00
Success.combine s1 s2 = case (s1, s2) of
(Passed n, Passed m) -> Passed (n + m)
(Passed n, Proved) -> Passed n
(Proved, Passed n) -> Passed n
(Proved, Proved) -> Proved
2019-05-29 00:44:56 +03:00
-- Test case generation
-- A domain is either small, in which case we can exhaustively list all the
-- values in the domain, or it's large, in which case we can ask for a value
-- of a particular size.
2019-06-28 07:15:45 +03:00
type Domain a = Small [a] | Large (Weighted a)
2019-05-29 00:44:56 +03:00
-- The domain of natural numbers is large.
Domain.nats : Domain Nat
2019-06-28 07:15:45 +03:00
Domain.nats = Large Weighted.nats
2019-05-29 00:44:56 +03:00
2019-06-28 17:00:54 +03:00
-- The domain of all integers
2019-05-29 00:44:56 +03:00
Domain.ints : Domain Int
2019-06-28 07:15:45 +03:00
Domain.ints = let
2019-06-28 17:00:54 +03:00
go n = yield n <|> weight 1
'(go (if n > +0 then negate n else increment (negate n)))
Large (List.foldl (a n -> a <|> yield n)
Weighted.Fail
[+0, +1, -1, maxInt, minInt] <|> go +2)
2019-06-28 07:15:45 +03:00
use Universal == < >
namespace Domain where
-- The threshold of "small" domains.
smallSize = 10000
-- The Boolean domain is small
boolean : Domain Boolean
boolean = Small [false, true]
-- The domain of lists of arbitrary data is large
listsOf : Domain a -> Domain [a]
listsOf d =
Large (Weighted.lists case d of
Domain.Small as -> Weighted.fromList as
Domain.Large w -> w)
lists : Domain [()]
lists = Domain.listsOf (Small [()])
sample : Nat -> Domain a -> [a]
sample n d =
case d of
Domain.Large w -> Weighted.sample n w
Domain.Small xs -> take n xs
map : (a -> b) -> Domain a -> Domain b
map f d = case d of
Domain.Large w -> Domain.Large (Weighted.map f w)
Domain.Small as -> Domain.Small (List.map f as)
pairs : Domain a -> Domain (a,a)
pairs d = lift2 (a b -> (a,b)) d d
tuples : Domain a -> Domain b -> Domain (Pair a b)
tuples = lift2 (a b -> Pair a b)
lift2 : (a -> b -> c) -> Domain a -> Domain b -> Domain c
lift2 f da db = let
wa = weighted da
wb = weighted db
wc = mergeWith (a1 a2 -> f a1 a2) wa wb
case (da, db) of
(Domain.Small as, Domain.Small bs) | size as + size bs < smallSize ->
Small (Weighted.sample smallSize wc)
_ -> Large wc
weighted : Domain a -> Weighted a
weighted d = case d of
Domain.Small as -> Weighted.fromList as
Domain.Large w -> w
2019-05-29 00:44:56 +03:00
-- Test a property for a given domain up to a maximum size
2019-06-28 17:00:54 +03:00
Test.forAll' : Nat -> Domain a -> (a -> Boolean) -> Test
Test.forAll' maxSize domain property =
2019-05-29 00:44:56 +03:00
check xs s =
2019-06-27 21:30:50 +03:00
List.map (
a -> case a of (c, i) ->
2019-05-29 00:44:56 +03:00
if property c then finished (Expected s)
2019-06-27 21:30:50 +03:00
else label ("test case " ++ Nat.toText i) (finished Failed)
2019-05-29 00:44:56 +03:00
) (indexed xs)
2019-06-27 21:30:50 +03:00
List.foldb id (Test.&&) proved <|
2019-05-29 00:44:56 +03:00
case domain of
Domain.Small xs -> check (take maxSize xs) Proved
Domain.Large _ -> check (sample maxSize domain) (Passed 1)
2019-05-23 00:50:08 +03:00
2019-06-28 07:15:45 +03:00
Test.check' : Boolean -> Test
Test.check' b = if b then Test.proved else Test.failed
2019-06-28 17:00:54 +03:00
Test.forAll : Nat -> Domain a -> (a -> Boolean) -> [Test.Result]
Test.forAll n d p = Test.run (Test.forAll' n d p)
2019-06-28 07:15:45 +03:00
Test.check : Boolean -> [Test.Result]
Test.check = Test.run . Test.check'