Skip to content
Snippets Groups Projects
ReduceCSpec.hs 4.99 KiB
Newer Older
  • Learn to ignore specific revisions
  • {-# LANGUAGE BlockArguments #-}
    {-# LANGUAGE LambdaCase #-}
    
    chrg's avatar
    chrg committed
    {-# LANGUAGE OverloadedStrings #-}
    
    module ReduceCSpec where
    
    
    import Control.Monad
    
    chrg's avatar
    chrg committed
    import Data.Function
    
    
    import System.Directory
    import System.FilePath
    import Test.Hspec
    
    
    import qualified Data.Text.Lazy as LazyText
    import qualified Data.Text.Lazy.Encoding as LazyText
    
    
    import Test.Hspec.Glitter
    
    import qualified Language.C as C
    import qualified Text.PrettyPrint as P
    
    
    import qualified Test.Hspec.Expectations.Pretty as EP
    
    
    chrg's avatar
    chrg committed
    import qualified Control.Monad.IRTree as IRTree
    
    chrg's avatar
    chrg committed
    import qualified Control.Monad.RTree as RTree
    
    import Data.Bits
    
    import Data.Functor
    import Data.RPath
    
    chrg's avatar
    chrg committed
    import Data.String
    
    import qualified Language.C.System.GCC as C
    import ReduceC
    
    chrg's avatar
    chrg committed
    import System.Directory.Internal.Prelude (tryIOError)
    
    import System.Process.Typed
    
    spec :: Spec
    spec = do
    
    chrg's avatar
    chrg committed
      specSmallCases
      specLargeCases
    
    specLargeCases :: Spec
    specLargeCases = do
      cases <- runIO (listDirectory "test/cases/large")
    
    
      forM_ cases \cname -> do
    
    chrg's avatar
    chrg committed
        let cfrom = "test" </> "cases" </> "large" </> cname
    
        let expected = "test" </> "expected" </> dropExtensions cname
    
    chrg's avatar
    chrg committed
    
    
    chrg's avatar
    chrg committed
        describe cfrom do
          c <- runIO $ parse cfrom
    
    
    chrg's avatar
    chrg committed
          onGlitter (expected </> "main.c") (`render` c) do
    
    chrg's avatar
    chrg committed
            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' -> c' $> () `shouldBe` c $> ()
    
    
    chrg's avatar
    chrg committed
          onGlitter (expected </> "extract.c") (\fp -> render fp (IRTree.extract (defaultReduceC c))) do
    
            it "should be parsed equally" . foldMap $ \cf -> do
              C.parseCFilePre cf >>= \case
                Left err -> fail (show err)
                Right c' -> c' $> () `shouldBe` c $> ()
    
    chrg's avatar
    chrg committed
    
    
    chrg's avatar
    chrg committed
          onGlitterEach
            (expected </> "reduction")
    
    chrg's avatar
    chrg committed
            ( \a -> do
    
                _ <- tryIOError (removeDirectoryRecursive a)
    
    chrg's avatar
    chrg committed
                createDirectoryIfMissing True a
    
                0 & fix \rec n -> do
                  let idx = fromString (replicate (1 `shiftL` n) '1')
    
    chrg's avatar
    chrg committed
                  let (c', t, _) = IRTree.probe (defaultReduceC c) idx
    
                  render (expected </> "reduction" </> "x" <> show n <.> "c") c'
    
    chrg's avatar
    chrg committed
                  unless (all ((/= Undecided) . choice) t) do
    
    chrg's avatar
    chrg committed
            )
            do
    
    chrg's avatar
    chrg committed
              it "should validate all reductions" $ \a -> do
    
    chrg's avatar
    chrg committed
                when (takeExtension a == ".c") do
                  validate a
    
    specSmallCases :: Spec
    specSmallCases = do
    
    chrg's avatar
    chrg committed
      let testcases = "test" </> "cases" </> "small"
      cases <- runIO (listDirectory testcases)
    
    chrg's avatar
    chrg committed
    
      forM_ cases \cname -> do
    
    chrg's avatar
    chrg committed
        let cfrom = testcases </> cname
    
    chrg's avatar
    chrg committed
    
        describe cfrom do
          c <- runIO $ parse cfrom
    
    
    chrg's avatar
    chrg committed
          let expected = "test" </> "expected" </> dropExtensions cname
    
    chrg's avatar
    chrg committed
          onGlitter (expected </> "main.c") (\fp -> render fp c) do
    
    chrg's avatar
    chrg committed
            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' -> c' $> () `EP.shouldBe` c $> ()
    
    chrg's avatar
    chrg committed
    
          describe "reduction" do
            it "should extract itself" do
    
              IRTree.extract (defaultReduceC c) $> () `EP.shouldBe` c $> ()
    
    chrg's avatar
    chrg committed
    
    
    chrg's avatar
    chrg committed
          onGlitterEach
            (expected </> "reduction")
    
    chrg's avatar
    chrg committed
            ( \a -> do
    
    chrg's avatar
    chrg committed
                _ <- tryIOError (removeDirectoryRecursive a)
                createDirectoryIfMissing True a
    
    chrg's avatar
    chrg committed
                forM_ (RTree.iinputs (defaultReduceC c)) \(i, _) -> do
    
    chrg's avatar
    chrg committed
                  let rfile = expected </> "reduction" </> "r" <> debugShow i <.> "c"
    
    chrg's avatar
    chrg committed
                  renderWithChoices rfile (IRTree.probe (defaultReduceC c) i)
    
    chrg's avatar
    chrg committed
            )
            do
    
    chrg's avatar
    chrg committed
              it "should validate all reductions" $ \a -> do
    
    chrg's avatar
    chrg committed
                when (takeExtension a == ".c") do
                  validate a
    
    
    validate :: FilePath -> IO ()
    validate fp = do
    
    chrg's avatar
    chrg committed
      (ec, _, stderr) <- readProcess (proc "clang" ["-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 ()
    
    render :: FilePath -> C.CTranslUnit -> IO ()
    render cto c = do
      createDirectoryIfMissing True (takeDirectory cto)
      writeFile cto (P.render (C.pretty c) <> "\n")
    
    
    chrg's avatar
    chrg committed
    renderWithChoices :: FilePath -> (C.CTranslUnit, [AnnotatedChoice (String, C.Position)], Int) -> IO ()
    renderWithChoices file (c, a, _) = do
    
    chrg's avatar
    chrg committed
      createDirectoryIfMissing True (takeDirectory file)
      writeFile
        file
        ( ( unlines
              . map
    
    chrg's avatar
    chrg committed
                ( \(AnnotatedChoice cs (reason, pos)) ->
                    "// " <> [debugShowChoice cs] <> " " <> reason <> " at " <> show pos
    
    chrg's avatar
    chrg committed
      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'