|
2 | 2 | {-# LANGUAGE RecordWildCards #-} |
3 | 3 |
|
4 | 4 | -- | Tests for the project file parser |
5 | | -module Tests.ParserTests (parserTests) where |
| 5 | +module Tests.ParserTests (parserTests, packageTestsProjectTests) where |
6 | 6 |
|
| 7 | +import Control.Exception (try) |
7 | 8 | import Control.Monad.IO.Class |
8 | 9 | ( MonadIO (liftIO) |
9 | 10 | ) |
10 | 11 | import Data.Either (fromRight) |
| 12 | +import Data.List (isInfixOf) |
11 | 13 | import qualified Data.Map as Map |
12 | 14 | import Data.Maybe (fromJust) |
13 | 15 | import qualified Data.Set as Set |
@@ -60,8 +62,8 @@ import Distribution.Utils.NubList |
60 | 62 | import Distribution.Verbosity |
61 | 63 | import GHC.Stack (HasCallStack) |
62 | 64 | import Network.URI (parseURI) |
63 | | -import System.Directory (canonicalizePath, doesFileExist) |
64 | | -import System.FilePath (normalise, (</>)) |
| 65 | +import System.Directory (canonicalizePath, doesDirectoryExist, doesFileExist, listDirectory) |
| 66 | +import System.FilePath (makeRelative, normalise, takeDirectory, takeExtension, takeFileName, (</>)) |
65 | 67 | import Prelude () |
66 | 68 |
|
67 | 69 | import Test.Tasty (TestTree, testGroup) |
@@ -607,6 +609,61 @@ readConfig testSubDir projectFileName = do |
607 | 609 | assertEdiffEqual "Parsec parser disagrees with the legacy parser" legacy parsec |
608 | 610 | return (parsec, legacy) |
609 | 611 |
|
| 612 | +-- | The project files of the cabal-testsuite package tests, each read with |
| 613 | +-- both parsers, which must agree. These are real project files exercising |
| 614 | +-- far more of the grammar than the fixtures here, so with the legacy parser |
| 615 | +-- as the oracle they are the widest check that the parsec parser has of what |
| 616 | +-- a field means. |
| 617 | +-- |
| 618 | +-- Files with an import over HTTP are skipped so that the test needs no |
| 619 | +-- network. Files that both parsers reject are accepted without comparing the |
| 620 | +-- errors, but a file that only one parser rejects is a failure. |
| 621 | +-- |
| 622 | +-- The tests are empty when the cabal-testsuite directory is not there, as in |
| 623 | +-- an sdist of cabal-install. |
| 624 | +packageTestsProjectTests :: IO TestTree |
| 625 | +packageTestsProjectTests = do |
| 626 | + exists <- doesDirectoryExist packageTestsDir |
| 627 | + files <- if exists then filterM isOffline =<< findProjectFiles packageTestsDir else pure [] |
| 628 | + httpTransport <- configureTransport silentVerbosity [] Nothing |
| 629 | + pure $ |
| 630 | + testGroup |
| 631 | + "cabal-testsuite project files" |
| 632 | + [ testCase (makeRelative packageTestsDir file) (assertParsersAgree httpTransport file) |
| 633 | + | file <- files |
| 634 | + ] |
| 635 | + where |
| 636 | + packageTestsDir = ".." </> "cabal-testsuite" </> "PackageTests" |
| 637 | + isOffline file = not . any importsHttp . lines <$> readFile file |
| 638 | + importsHttp l = "import:" `isPrefixOf` dropWhile isSpace l && "http" `isInfixOf` l |
| 639 | + |
| 640 | +-- | Every @.project@ file under a directory. |
| 641 | +findProjectFiles :: FilePath -> IO [FilePath] |
| 642 | +findProjectFiles dir = do |
| 643 | + entries <- map (dir </>) <$> listDirectory dir |
| 644 | + dirs <- filterM doesDirectoryExist entries |
| 645 | + let files = [e | e <- entries, takeExtension e == ".project"] |
| 646 | + (sort files ++) . concat <$> traverse findProjectFiles (sort dirs) |
| 647 | + |
| 648 | +-- | Reads one project file with both parsers and checks that they agree. |
| 649 | +assertParsersAgree :: HttpTransport -> FilePath -> Assertion |
| 650 | +assertParsersAgree httpTransport file = do |
| 651 | + projectRootDir <- canonicalizePath (takeDirectory file) |
| 652 | + let projectRoot = ProjectRootExplicit projectRootDir (takeFileName file) |
| 653 | + distDirLayout = defaultDistDirLayout projectRoot Nothing Nothing |
| 654 | + readWith reader = try . runRebuild projectRootDir $ reader silentVerbosity httpTransport distDirLayout ProjectFileKeyMain |
| 655 | + parsec <- readWith readProjectFileSkeletonParsec |
| 656 | + legacy <- readWith readProjectFileSkeletonLegacy |
| 657 | + case (legacy, parsec) of |
| 658 | + (Right l, Right p) -> assertEdiffEqual "Parsec parser disagrees with the legacy parser" l p |
| 659 | + (Left _, Left _) -> pure () |
| 660 | + (Left e, Right _) -> assertFailure $ "Legacy parser failed where the parsec parser succeeded:\n" ++ displayException (e :: SomeException) |
| 661 | + (Right _, Left e) -> assertFailure $ "Parsec parser failed where the legacy parser succeeded:\n" ++ displayException (e :: SomeException) |
| 662 | + |
| 663 | +-- | Neither parser's warnings are of interest when comparing them. |
| 664 | +silentVerbosity :: Verbosity |
| 665 | +silentVerbosity = mkVerbosity defaultVerbosityHandles silent |
| 666 | + |
610 | 667 | -- | Like 'assertEqual' but the failure shows a tree diff of the two values |
611 | 668 | -- instead of two 'show' dumps. |
612 | 669 | assertEdiffEqual :: (Eq a, ToExpr a, HasCallStack) => String -> a -> a -> Assertion |
|
0 commit comments