packages feed

hgom-0.5.1: Gom/UnitTests.hs

------------------------------------------------------------------
-- |
-- Module      : Gom.UnitTests
-- Copyright   : (c) Paul Brauner 2009
--               (c) Emilie Balland 2009
--               (c) INRIA 2009
-- Licence     : GPL (see COPYING)
--
-- Maintainer  : paul.brauner@inria.fr
-- Stability   : provisional
-- Portability : non-portable (requires generalized newtype deriving)
--
-- Run all unit tests
--------------------------------------------------------------------

module Gom.UnitTests (testSuite) where

import Gom.Pretty ()
import Gom.Random ()
import Gom.Sig
import Gom.Parser
import Gom.Checker

-- imported test suites
import qualified Gom.SymbolTable

-- for t*.gom testing
import Test.Framework (Test,testGroup)
import Test.Framework.Providers.QuickCheck2 (testProperty)
import Test.Framework.Providers.HUnit (testCase)
import Test.HUnit.Base((@?=))
import System.FilePath((</>))

-- generated by cabal
import Paths_hgom (getDataFileName)

-- for generated parser tesing
import System.Process(rawSystem)
import Data.Char(toLower)
import System.Directory
import Test.QuickCheck
import Test.QuickCheck.Monadic
import System.Exit

-- for generated java testing
import System.FilePath.Glob
import Data.List(intercalate)
import Control.Monad(liftM2)
import Data.Maybe(isNothing)
import System.Process(readProcessWithExitCode)

-- | models at which step of the chain a module failed
data FailsDuring = Parsing | Checking | Never deriving (Show,Eq)

-- | checks how far in the chain a module gets without 
-- raising a failure of some sort
failsDuring :: String -> FailsDuring
failsDuring s = 
  case parseModule s of
    Left  _ -> Parsing
    Right m -> case checkEverything m of
      Nothing -> Never
      Just _  -> Checking

-- | run failsDuring on some relative filePath
fileFailsDuring :: FilePath -> IO FailsDuring
fileFailsDuring f = do af <- getDataFileName f
                       failsDuring `fmap` readFile af

-- | regression suite
regressionSuite :: Test
regressionSuite = testGroup "regression tests" $
  concat [map (cook Never   ) (files 1 1 6),
          map (cook Checking) (files 2 0 9), 
          map (cook Parsing ) (files 3 1 7)]
  where files :: Int -> Int -> Int -> [FilePath]
        files n l h = map fname [l..h]
          where fname i = "test" </> "data" </> 
                          "t" ++ show n ++ 
                          "_" ++ show i ++ ".gom"
        cook f s = testCase (msg s f) (test f s)
        test f s = fileFailsDuring s >>= (@?= f)
        msg s Never    = s ++ " is valid"
        msg s Parsing  = s ++ " fails parsing"
        msg s Checking = s ++ " fails checking"
        

-- | checks that @parse . pretty = id@
propParsePretty :: Module -> Bool
propParsePretty m = 
  case parseModule (show m) of
    Left  _  -> False
    Right m' -> m == m'

getTempDir :: IO FilePath
getTempDir = getTemporaryDirectory >>= getTemp 0
  where getTemp :: Integer -> FilePath -> IO FilePath
        getTemp n tmp = do
          b <- doesDirectoryExist htmp
          if b then getTemp (n+1) tmp
               else do createDirectory htmp
                       return htmp
          where htmp = tmp </> "hgom-tmp" ++ show n
    

-- | @doInTempDir a@ creates a directory, change working directory 
-- to it, perform @a@ and comes back to original directory
doInTempDir :: IO Bool -> IO Bool
doInTempDir a = do
  tmp <- getTempDir
  cur <- getCurrentDirectory
  setCurrentDirectory tmp
  r <- a
  setCurrentDirectory cur
  if r then removeDirectoryRecursive tmp
       else putStrLn $ "guilty files kept in " ++ tmp
  return r

-- | True iff the module passes the check phase
checks :: Module -> Bool
checks = isNothing . checkEverything

-- | test that the generated parser is correct w.r.t. 
-- the generated pretty printer (@fromString(x.toString()) == x@)
propGenParsePretty :: Property
propGenParsePretty = monadicIO $ do
  sig <- pick (arbitrary `suchThat` liftM2 (&&) hasSort checks)
  case sig of
    Module m _ (SortDef s _ _:_) ->
      let pack = map toLower m 
      in assert =<< (run . doInTempDir $ do 
        writeFile "Test.gom" $ show sig
        _ <- rawSystem "hgom" ["-r","Test.gom"]
        writeFile "Test.java" $ template pack (show s)
        cp <- getDataFileName $ "test" </> "data" </> "tom-runtime-full.jar:"
        (st,_,_) <- readProcessWithExitCode "javac" ["-cp",cp,"Test.java"] ""
        let res = (st == ExitSuccess)
        return res)
    _ -> error "never happens"

  where hasSort = not . null . sortDefs
        template pack s = unlines
          ["import " ++ pack ++ ".types.*;",
           "public class Test {",
           "  public static void main(String[] args) {",
           "    for(int i=0; i<10; i++) {",
           "      " ++ s ++ " t = " ++ s ++ ".makeRandom(i);",
           "      if (!" ++ s ++ ".fromString(t.toString()).equals(t))",
           "        System.exit(1);",
           "    }",
           "  }",
           "}"]

-- | @testChecker opts@ generates a random module that passes the checker
-- phase, and return True iff javac successes on the generated files
testChecker :: [String] -> Property
testChecker opts = monadicIO $ do
  sig <- pick (arbitrary `suchThat` checks)
  case sig of 
    Module m _ _ ->
      let pack = map toLower m
      in assert =<< (run . doInTempDir $ do
        writeFile "Test.gom" $ show sig
        _ <- rawSystem "hgom" ("Test.gom":opts)
        jfs <- globDir1 (compile $ "**" </> "*.java") pack
        cp <- getDataFileName $ "test" </> "data" </> "tom-runtime-full.jar:"
        (st,_,_) <- readProcessWithExitCode "javac" (["-cp",cp]++jfs) ""
        let res = (st == ExitSuccess)
        return res)

-- | cross modules quickcheck tests
crossModuleSuite :: Test
crossModuleSuite = testGroup "cross module properties" 
  [testProperty "parse . pretty = id" propParsePretty,
   check flags1, check flags2, check flags3, 
   testProperty "generated parse . generated pretty = id" propGenParsePretty]
  where check fs = testProperty (mes fs) (testChecker fs)
        flags1 = ["-r","-d","-s","-h","-c","same"]
        flags2 = ["--noSharing"]
        flags3 = ["-j"]
        mes fs = "check ok => compilable java (" ++ intercalate " " fs ++ ")"

-- | all tests
testSuite :: [Test]
testSuite = [regressionSuite,
             Gom.SymbolTable.testSuite,
             crossModuleSuite]