{-# LANGUAGE BlockArguments #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} module ReduceCSpec where import Control.Monad import qualified Control.Monad.IRTree as IRTree import qualified Control.Monad.RTree as RTree import Data.Bits import Data.Function import Data.Functor import Data.RPath import Data.String import qualified Data.Text.Lazy as LazyText import qualified Data.Text.Lazy.Encoding as LazyText import qualified Language.C as C import qualified Language.C.System.GCC as C import ReduceC import System.Directory import System.Directory.Internal.Prelude (tryIOError) import System.FilePath import System.Process.Typed import Test.Hspec import qualified Test.Hspec.Expectations.Pretty as EP import Test.Hspec.Glitter import qualified Text.PrettyPrint as P spec :: Spec spec = do specSmallCases specLargeCases specSmallCases :: Spec specSmallCases = do let testcases = "test" </> "cases" </> "small" cases <- runIO (listDirectory testcases) forM_ cases \cname -> do let cfrom = testcases </> cname describe cfrom do c <- runIO $ parse cfrom let expected = "test" </> "expected" </> dropExtensions cname -- onGlitter (expected </> "main.hs") (\f -> LazyText.writeFile f (PS.pShowNoColor (void c))) do -- pure () onGlitter (expected </> "main.c") (`render` c) do it "should be valid" . foldMap $ \cf -> do validate cf it "should be parsed equally" . foldMap $ \cf -> do C.parseCFilePre cf >>= \case Left err -> fail (show err) Right c' -> void c' `EP.shouldBe` void c describe "reduction" do it "should extract itself" do IRTree.extract (defaultReduceC c) $> () `EP.shouldBe` c $> () onGlitterEach (expected </> "reduction") ( \a -> do _ <- tryIOError (removeDirectoryRecursive a) createDirectoryIfMissing True a let examples = RTree.iinputs (defaultReduceC c) forM_ examples \(i, _) -> do let rfile = expected </> "reduction" </> "r" <> debugShow i <.> "c" renderWithChoices rfile (IRTree.probe (defaultReduceC c) i) when (length examples > 50) $ fail "too many examples - simplify test case" ) do it "should validate all reductions" $ \a -> do when (takeExtension a == ".c") do validate a specLargeCases :: Spec specLargeCases = focus do cases <- runIO (listDirectory "test/cases/large") let cases' = filter (\c -> c == "gcc-71626.c") cases forM_ cases' \cname -> do let cfrom = "test" </> "cases" </> "large" </> cname let expected = "test" </> "expected" </> dropExtensions cname describe cfrom do c <- runIO $ parse cfrom onGlitter (expected </> "main.c") (`render` c) do it "should be valid" . foldMap $ \cf -> do simplevalidate cf it "should be parsed equally" . foldMap $ \cf -> do C.parseCFilePre cf >>= \case Left err -> fail (show err) Right c' -> c' $> () `shouldBe` c $> () onGlitterEach (expected </> "extract.c") (\fp -> render fp (IRTree.extract (defaultReduceC c))) do it "should be parsed equally" $ \cf -> do C.parseCFilePre cf >>= \case Left err -> fail (show err) Right c' -> when (void c' /= void c) do expectationFailure "did not parse as we extracted" onGlitterEach (expected </> "reduction") ( \fp -> do _ <- tryIOError (removeDirectoryRecursive fp) createDirectoryIfMissing True fp 0 & fix \rec n -> do let idx = fromString (replicate (1 `shiftL` n) '1') let (c', t, _) = IRTree.probe (defaultReduceC c) idx render (fp </> "x" <> show n <.> "c") c' renderChoices (fp </> "p" <> show n <.> "path") t unless (all ((/= Undecided) . choice) t) do rec (n + 1) ) do it "should validate all reductions" $ \a -> do when (takeExtension a == ".c") do simplevalidate a validate :: FilePath -> IO () validate fp = do (ec, _, stderr_) <- readProcess ( proc "clang" [ "-Werror" , "-Wno-error=int-conversion" , "-Wno-error=unused-value" , "-Wno-error=return-type" , "-Wno-error=incompatible-library-redeclaration" , "-std=gnu89" , "-o" , "/dev/null" , fp ] ) case ec of ExitFailure _ -> expectationFailure $ "could not validate " <> show fp <> "\n" <> ( LazyText.unpack . LazyText.unlines . filter (LazyText.isInfixOf "error") . LazyText.lines $ LazyText.decodeUtf8 stderr_ ) ExitSuccess -> pure () simplevalidate :: FilePath -> IO () simplevalidate fp = do (ec, _, stderr_) <- readProcess ( proc "clang" [ -- "-Wno-error=int-conversion" "-std=gnu89" , "-o" , "/dev/null" , fp ] ) case ec of ExitFailure _ -> expectationFailure $ "could not simple validate " <> show fp <> "\n" <> ( LazyText.unpack . LazyText.unlines . filter (LazyText.isInfixOf "error") . LazyText.lines $ LazyText.decodeUtf8 stderr_ ) ExitSuccess -> pure () render :: FilePath -> C.CTranslUnit -> IO () render cto c = do createDirectoryIfMissing True (takeDirectory cto) writeFile cto (P.render (C.pretty c) <> "\n") renderChoices :: FilePath -> [AnnotatedChoice (String, C.Position)] -> IO () renderChoices file as = writeFile file ( ( unlines . map ( \(AnnotatedChoice cs (reason, pos)) -> [debugShowChoice cs] <> " " <> reason <> " at " <> show pos ) $ as ) <> "\n" ) renderWithChoices :: FilePath -> (C.CTranslUnit, [AnnotatedChoice (String, C.Position)], Int) -> IO () renderWithChoices file (c, a, _) = do createDirectoryIfMissing True (takeDirectory file) writeFile file ( ( unlines . map ( \(AnnotatedChoice cs (reason, pos)) -> "// " <> [debugShowChoice cs] <> " " <> reason <> " at " <> show pos ) $ a ) <> "\n" ) appendFile file (P.render (C.pretty c) <> "\n") parse :: FilePath -> IO C.CTranslUnit parse cfrom = do cf <- C.parseCFile (C.newGCC "clang") Nothing [] cfrom case cf of Left err -> fail (show err) Right cf' -> pure cf'