tadka-2.0.0.0: test/props/Phase1.hs
{-# LANGUAGE OverloadedStrings #-}
-- | Phase 1 properties: primitive types, smart constructors, width lookups.
module Phase1 (group) where
import Data.Char (isAsciiLower, isDigit)
import Data.Text (Text)
import qualified Data.Text as T
import Hedgehog
(Gen, Group (..), Property, assert, forAll, property,
withTests, (===))
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import Tadka
import Tadka.Internal.Types (LengthError (..), OffsetError (..), mkLength,
mkOffset, unLength, unOffset)
import Tadka.Internal.Width
(GBProp (..), charWidth, graphemeBreakProperty,
isExtendedPictographic, textWidth)
group :: Group
group = Group "Phase 1 - primitive types & width"
[ ("mkOffset rejects exactly negatives", prop_mkOffset)
, ("mkLength rejects exactly negatives", prop_mkLength)
, ("mkNamedSource: empty name rejected; else round-trip", prop_mkNamedSource)
, ("mkDiagnosticCode agrees with grammar oracle", prop_mkDiagnosticCode)
, ("mkDiagnosticId is total and round-trips", prop_mkDiagnosticId)
, ("mkUrl accepts absolute URIs only", prop_mkUrl)
, ("Tadka.Internal.Width point lookups", prop_width)
]
genInt :: Gen Int
genInt = Gen.int (Range.linearFrom 0 (-100000) 100000)
genText :: Gen Text
genText = Gen.text (Range.linear 0 30) Gen.unicode
genCodeish :: Gen Text
genCodeish = Gen.choice [genValidCode, genNoisyCode]
where
genValidCode = do
ns <- genNamespace
digs <- Gen.text (Range.linear 4 8) Gen.digit
pure (ns <> "::E" <> digs)
genNamespace = do
c0 <- Gen.lower
cs <- Gen.text (Range.linear 0 6)
(Gen.choice [Gen.lower, Gen.digit, Gen.constant '_'])
pure (T.cons c0 cs)
genNoisyCode =
Gen.text (Range.linear 0 16)
(Gen.choice (map Gen.constant "abcdeEZ0123:_# "))
prop_mkOffset :: Property
prop_mkOffset = property $ do
n <- forAll genInt
case mkOffset n of
Left (NegativeOffset m) -> do assert (n < 0); m === n
Right o -> do assert (n >= 0); unOffset o === n
prop_mkLength :: Property
prop_mkLength = property $ do
n <- forAll genInt
case mkLength n of
Left (NegativeLength m) -> do assert (n < 0); m === n
Right l -> do assert (n >= 0); unLength l === n
prop_mkNamedSource :: Property
prop_mkNamedSource = property $ do
name <- forAll genText
txt <- forAll genText
case mkNamedSource name txt of
Left EmptySourceName -> T.null name === True
Right ns -> do
assert (not (T.null name))
sourceName ns === name
sourceText ns === txt
oracleCode :: Text -> Bool
oracleCode t = case T.splitOn "::" t of
[ns, body] -> okNs (T.unpack ns) && okBody (T.unpack body)
_ -> False
where
okNs [] = False
okNs (c : cs) = isAsciiLower c
&& all (\x -> isAsciiLower x || isDigit x || x == '_') cs
okBody ('E' : ds) = length ds >= 4 && all isDigit ds
okBody _ = False
prop_mkDiagnosticCode :: Property
prop_mkDiagnosticCode = property $ do
t <- forAll genCodeish
case mkDiagnosticCode t of
Right c -> do oracleCode t === True; unDiagnosticCode c === t
Left EmptyCode -> T.null t === True
Left (MalformedCode m) -> do m === t
assert (not (T.null t))
oracleCode t === False
prop_mkDiagnosticId :: Property
prop_mkDiagnosticId = property $ do
t <- forAll genText
unDiagnosticId (mkDiagnosticId t) === t
urlCases :: [(Text, Bool)]
urlCases =
[ ("https://example.org/errors/E0001", True)
, ("http://a.example/", True)
, ("ftp://host/path", True)
, ("mailto:person@example.org", True)
, ("", False)
, ("example.org", False)
, ("/relative/path", False)
, ("not a url", False)
, ("http://x.example/#fragment", False)
]
prop_mkUrl :: Property
prop_mkUrl = property $ do
(input, valid) <- forAll (Gen.choice (map Gen.constant urlCases))
case mkUrl input of
Right u -> do assert valid; unUrl u === input
Left EmptyUrl -> T.null input === True
Left (NotAbsoluteUri m) -> do assert (not valid); m === input
prop_width :: Property
prop_width = withTests 1 . property $ do
charWidth 'A' === 1
charWidth ' ' === 1
charWidth '\x0301' === 0
charWidth '\xAC00' === 2
charWidth '\xD55C' === 2
charWidth '\x4E00' === 2
textWidth "AB" === 2
textWidth "a\x0301" === 1
textWidth "\xD55C\xAC00\xC5B4" === 6
graphemeBreakProperty '\r' === GBCR
graphemeBreakProperty '\n' === GBLF
graphemeBreakProperty '\x200D' === GBZWJ
graphemeBreakProperty 'A' === GBOther
assert (isExtendedPictographic '\x1F600')
assert (not (isExtendedPictographic 'A'))