{-# LANGUAGE ViewPatterns #-} module Main (main) where import Prelude () import Prelude.Compat import HaskellCI hiding (main) import HaskellCI.Diagnostics (runDiagnosticsT) import Control.Exception (ErrorCall (..), throwIO) import Data.Algorithm.Diff (PolyDiff (..), getGroupedDiff) import Data.List (stripPrefix) import Data.Maybe (mapMaybe) import System.Directory (doesFileExist, setCurrentDirectory) import System.FilePath (addExtension) import Test.Tasty (TestName, TestTree, defaultMain, testGroup) import Test.Tasty.Golden.Advanced (goldenTest) import Text.Read (readMaybe) import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as BS8 import qualified System.Console.ANSI as ANSI main :: IO () main = do setCurrentDirectory "fixtures/" defaultMain $ testGroup "fixtures" [ fixtureGoldenTest "cabal.project.empty-line" , fixtureGoldenTest "cabal.project.fail-versions" , fixtureGoldenTest "cabal.project.messy" , fixtureGoldenTest "cabal.project.travis-patch" , testGroup "copy-fields" [ fixtureGoldenTest "cabal.project.copy-fields.all" , fixtureGoldenTest "cabal.project.copy-fields.some" , fixtureGoldenTest "cabal.project.copy-fields.none" ] ] linesToArgv :: String -> Maybe [String] linesToArgv txt = case mapMaybe lineToArgv (lines txt) of [argv] -> Just argv _ -> Nothing where lineToArgv line | Just rest <- "# REGENDATA " `stripPrefix` line = readMaybe rest | otherwise = Nothing -- | -- @ -- travisFromConfigFile :: -- ... => ([String],Options) -> FilePath -> [String] -> Writer [String] m () -- @ fixtureGoldenTest :: FilePath -> TestTree fixtureGoldenTest fp = cabalGoldenTest fp outputRef errorRef $ do (argv, opts) <- makeTravisFlags let genConfig = travisFromConfigFile argv opts fp runDiagnosticsT genConfig where outputRef = addExtension fp "travis.yml" errorRef = addExtension fp "stderr" referenceArgv :: Bool -> IO (Maybe [String]) referenceArgv refExists | refExists = (linesToArgv . BS8.unpack) `fmap` BS.readFile outputRef | otherwise = return $ Just [fp] makeTravisFlags :: IO ([String], Options) makeTravisFlags = do result <- doesFileExist outputRef >>= referenceArgv case result of Nothing -> throwIO (ErrorCall "No REGENDATA in result file.") Just argv -> do (opts, _fp) <- parseOpts argv return (argv, opts) parseOpts :: [String] -> IO (Options, FilePath) parseOpts argv = do (path, opts) <- parseTravis argv return (opts, path) data Result = Success [String] [String] | Failure [String] deriving Eq cabalGoldenTest :: TestName -> FilePath -> FilePath -> IO (Maybe [String], [String]) -> TestTree cabalGoldenTest name outRef errRef act = goldenTest name readGolden act' cmp upd where readData :: FilePath -> IO [String] readData fp = lines . BS8.unpack <$> BS.readFile fp act' = flip fmap act $ \r -> case r of (Nothing, diags) -> Failure diags (Just x, diags) -> Success diags x readGolden = do refExists <- doesFileExist outRef if refExists then Success <$> readData errRef <*> readData outRef else Failure <$> readData errRef packData :: [String] -> BS.ByteString packData = BS8.pack . unlines upd (Failure (packData -> errs)) = BS.writeFile errRef errs upd (Success (packData -> warnings) (packData -> contents)) = do BS.writeFile outRef contents BS.writeFile errRef warnings cmp x y | x == y = return Nothing cmp (Failure err1) (Failure err2) = return . Just $ diff err1 err2 cmp (Success warn1 out1) (Success warn2 out2) = return . Just . mconcat $ [ diff warn1 warn2 , "\n\n" , diff out1 out2 ] cmp (Failure err) (Success warnings _) = return . Just . unlines $ [ "Expected failure:" ] ++ err ++ ["\n\nFound success:"] ++ warnings cmp (Success warnings _) (Failure err) = return . Just . unlines $ [ "Expected success:" ] ++ warnings ++ ["\n\nFound failure:"] ++ err diff x y = ansiReset -- reset tasty's red color ++ unlines (concatMap f (getGroupedDiff x y)) f (First xs) = map (withAnsiRed . cons3 '-') xs f (Second ys) = map (withAnsiGreen . cons3 '+') ys -- we print unchanged lines too. -- we trim the contents a little. f (Both xs _) = map (cons3 ' ') (shorten xs) -- we add three characters, so the changed lines are easier to spot cons3 c cs = c : c : c : ' ' : cs ansiReset = ANSI.setSGRCode [ ANSI.SetColor ANSI.Foreground ANSI.Dull ANSI.White] -- default color: gray ansiRed = ANSI.setSGRCode [ ANSI.SetColor ANSI.Foreground ANSI.Vivid ANSI.Red] ansiGreen = ANSI.setSGRCode [ ANSI.SetColor ANSI.Foreground ANSI.Vivid ANSI.Green] withAnsiRed s = ansiRed ++ s ++ ansiReset withAnsiGreen s = ansiGreen ++ s ++ ansiReset -- for large thunks make ellipsis in between shorten xs | l < 15 = xs | otherwise = as ++ ["..."] ++ drop (l - 10) bs where l = length xs (as, bs) = splitAt 5 xs