packages feed

flat-0.6: test/DocTest.hs

-- Execute doctest tests with no dependencies on doctest
-- TODO: move in a different doctest-static package
{-# LANGUAGE OverloadedStrings ,DeriveFunctor#-}
{-# LANGUAGE FlexibleInstances,UndecidableInstances #-}
-- move to doctest
module DocTest
  ( asPrint
  , test
  , testProp
  , LineChunk(..)
  , ExpectedLine(..)
  )
where
import qualified Data.Text                     as T
import           Test.Tasty
import           Test.Tasty.HUnit

import           Test.Tasty.QuickCheck
-- import           Runner.Example
import           Data.Char
import           Data.List
import           Data.String

class Print f where
    asPrint :: f -> IO String

instance Show a => Print (IO a) where
  asPrint io = io >>= return . show

instance {-# OVERLAPPABLE #-} Show a => Print a where
  asPrint a = return (show a)

{-

-}
-- test :: TestName -> ExpectedResult -> IO String -> IO TestTree
test :: TestName -> String -> IO String -> IO TestTree
test loc expectedS valIO = do
  let expected = read expectedS
  actual <- lines <$> valIO
  return $ testCase loc (mkResult expected actual @=? Equal)
  -- return $ testCase loc (unlines exp @=? unlines actual)

testProp :: Testable t => TestName -> t -> IO TestTree
testProp loc = return . testProperty loc


-- Code copied and adapted by doctest

data Result = Equal | NotEqual [String]
  deriving (Eq, Show,Read)

-- | Remove trailing white space from a string.
--
-- >>> stripEnd "foo   "
-- "foo"
stripEnd :: String -> String
stripEnd = reverse . dropWhile isSpace . reverse

mkResult :: ExpectedResult -> [String] -> Result
mkResult expected actual | expected `matches` actual = Equal
                         | otherwise = NotEqual (formatNotEqual expected actual)
 where
  chunksMatch :: [LineChunk] -> String -> Bool
  chunksMatch []             "" = True
  chunksMatch [LineChunk xs] ys = stripEnd xs == stripEnd ys
  chunksMatch (LineChunk x : xs) ys =
    x `isPrefixOf` ys && xs `chunksMatch` drop (length x) ys
  chunksMatch zs@(WildCardChunk : xs) (_ : ys) =
    xs `chunksMatch` ys || zs `chunksMatch` ys
  chunksMatch _ _ = False

  matches :: ExpectedResult -> [String] -> Bool
  matches (ExpectedLine x : xs) (y : ys) = x `chunksMatch` y && xs `matches` ys
  matches (WildCardLine : xs) ys | xs `matches` ys = True
  matches zs@(WildCardLine : _) (_ : ys)           = zs `matches` ys
  matches []                    []                 = True
  matches []                    _                  = False
  matches _                     []                 = False


formatNotEqual :: ExpectedResult -> [String] -> [String]
formatNotEqual expected_ actual =
  formatLines "expected: " expected ++ formatLines " but got: " actual
 where
  expected :: [String]
  expected = map
    (\x -> case x of
      ExpectedLine str -> concatMap lineChunkToString str
      WildCardLine     -> "..."
    )
    expected_

  -- use show to escape special characters in output lines if any output line
  -- contains any unsafe character
  escapeOutput | any (not . isSafe) (concat $ expected ++ actual) = map show
               | otherwise = id

  isSafe :: Char -> Bool
  isSafe c = c == ' ' || (isPrint c && (not . isSpace) c)

  formatLines :: String -> [String] -> [String]
  formatLines message xs = case escapeOutput xs of
    y : ys -> (message ++ y) : map (padding ++) ys
    []     -> [message]
    where padding = replicate (length message) ' '

lineChunkToString :: LineChunk -> String
lineChunkToString WildCardChunk   = "..."
lineChunkToString (LineChunk str) = str

-- import           Control.DeepSeq (deepseq, NFData(rnf))

-- | A thing with a location attached.
data Located a = Located Location a
  deriving (Eq, Show, Functor)

-- instance NFData a => NFData (Located a) where
--   rnf (Located loc a) = loc `deepseq` a `deepseq` ()

-- | Discard location information.
unLoc :: Located a -> a
unLoc (Located _ a) = a

-- | Add dummy location information.
noLocation :: a -> Located a
noLocation = Located (UnhelpfulLocation "<no location info>")

-- | A line number.
type Line = Int

-- | A combination of file name and line number.
data Location = UnhelpfulLocation String | Location FilePath Line
  deriving Eq

instance Show Location where
  show (UnhelpfulLocation s) = s
  show (Location file line ) = file ++ ":" ++ show line

-- instance NFData Location where
--   rnf (UnhelpfulLocation str) = str `deepseq` ()
--   rnf (Location file line   ) = file `deepseq` line `deepseq` ()

-- |
-- Create a list from a location, by repeatedly increasing the line number by
-- one.
enumerate :: Location -> [Location]
enumerate loc = case loc of
  UnhelpfulLocation _ -> repeat loc
  Location file line  -> map (Location file) [line ..]

data DocTest = Example Expression ExpectedResult | Property Expression
  deriving (Eq, Show,Read)

data LineChunk = LineChunk String | WildCardChunk
  deriving (Show, Eq,Read)

instance IsString LineChunk where
  fromString = LineChunk

data ExpectedLine = ExpectedLine [LineChunk] | WildCardLine
  deriving (Show, Eq,Read)

instance IsString ExpectedLine where
  fromString = ExpectedLine . return . LineChunk

type Expression = String
type ExpectedResult = [ExpectedLine]