-- -- QuickCheck tests for Megaparsec's parse errors. -- -- Copyright © 2015–2016 Megaparsec contributors -- -- Redistribution and use in source and binary forms, with or without -- modification, are permitted provided that the following conditions are -- met: -- -- * Redistributions of source code must retain the above copyright notice, -- this list of conditions and the following disclaimer. -- -- * Redistributions in binary form must reproduce the above copyright -- notice, this list of conditions and the following disclaimer in the -- documentation and/or other materials provided with the distribution. -- -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS “AS IS” AND ANY -- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY -- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -- STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -- ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -- POSSIBILITY OF SUCH DAMAGE. {-# LANGUAGE CPP #-} {-# OPTIONS -fno-warn-orphans #-} module Error (tests) where import Data.Function (on) import Data.List (isInfixOf) import Data.List.NonEmpty (NonEmpty (..)) import Data.Monoid ((<>)) import Data.Set (Set) import qualified Data.List.NonEmpty as NE import qualified Data.Set as E import Test.Framework import Test.Framework.Providers.HUnit (testCase) import Test.Framework.Providers.QuickCheck2 (testProperty) import Test.HUnit (Assertion, (@?=)) import Test.QuickCheck import Text.Megaparsec.Error import Text.Megaparsec.Pos import Util () #if !MIN_VERSION_base(4,8,0) import Data.Foldable (Foldable, all) import Data.Monoid (mempty) import Prelude hiding (all) #endif tests :: Test tests = testGroup "Parse errors" [ testProperty "monoid left identity" prop_monoid_left_id , testProperty "monoid right identity" prop_monoid_right_id , testProperty "monoid associativity" prop_monoid_assoc , testProperty "consistency of Show/Read" prop_showReadConsistency , testProperty "position of merged error" prop_mergeErrorPos , testProperty "unexpected items in merged error" prop_mergeErrorUnexpected , testProperty "expected items in merged error" prop_mergeErrorExpected , testProperty "custom items in merged error" prop_mergeErrorCustom , testCase "showTokens (String instance)" case_showTokens , testCase "rendering of unknown parse error" case_ppUnknownError , testProperty "source position in rendered error" prop_ppSourcePos , testProperty "unexpected items in rendered error" prop_ppUnexpected , testProperty "expected items in rendered error" prop_ppExpected , testProperty "custom data in rendered error" prop_ppCustom ] type PE = ParseError Char Dec prop_monoid_left_id :: PE -> Property prop_monoid_left_id x = mempty <> x === x .&&. mempty { errorPos = errorPos x } <> x === x prop_monoid_right_id :: PE -> Property prop_monoid_right_id x = x <> mempty === x .&&. mempty { errorPos = errorPos x } <> x === x prop_monoid_assoc :: PE -> PE -> PE -> Property prop_monoid_assoc x y z = (x <> y) <> z === x <> (y <> z) prop_showReadConsistency :: PE -> Property prop_showReadConsistency x = read (show x) === x prop_mergeErrorPos :: PE -> PE -> Property prop_mergeErrorPos e1 e2 = errorPos (e1 <> e2) === max (errorPos e1) (errorPos e2) prop_mergeErrorUnexpected :: PE -> PE -> Property prop_mergeErrorUnexpected = checkMergedItems errorUnexpected prop_mergeErrorExpected :: PE -> PE -> Property prop_mergeErrorExpected = checkMergedItems errorExpected prop_mergeErrorCustom :: PE -> PE -> Property prop_mergeErrorCustom = checkMergedItems errorCustom checkMergedItems :: (Ord a, Show a) => (PE -> Set a) -> PE -> PE -> Property checkMergedItems f e1 e2 = f (e1 <> e2) === r where r = case (compare `on` errorPos) e1 e2 of LT -> f e2 EQ -> (E.union `on` f) e1 e2 GT -> f e1 case_showTokens :: Assertion case_showTokens = mapM_ (\(x,y) -> showTokens (NE.fromList x) @?= y) [ ("\r\n", "crlf newline") , ("\0", "null") , ("\a", "bell") , ("\b", "backspace") , ("\t", "tab") , ("\n", "newline") , ("\v", "vertical tab") , ("\f", "form feed") , ("\r", "carriage return") , (" ", "space") , ("a", "'a'") , ("foo", "\"foo\"") ] case_ppUnknownError :: Assertion case_ppUnknownError = parseErrorPretty (err :: PE) @?= "1:1:\nunknown parse error\n" where err = ParseError { errorPos = initialPos "" :| [] , errorUnexpected = E.empty , errorExpected = E.empty , errorCustom = E.empty } prop_ppSourcePos :: PE -> Property prop_ppSourcePos = checkPresence errorPos sourcePosPretty prop_ppUnexpected :: PE -> Property prop_ppUnexpected = checkPresence errorUnexpected showErrorComponent prop_ppExpected :: PE -> Property prop_ppExpected = checkPresence errorExpected showErrorComponent prop_ppCustom :: PE -> Property prop_ppCustom = checkPresence errorCustom showErrorComponent checkPresence :: Foldable t => (PE -> t a) -> (a -> String) -> PE -> Property checkPresence g r e = property (all f (g e)) where rendered = parseErrorPretty e f x = r x `isInfixOf` rendered