packages feed

ideas-0.6: src/Documentation/SelfCheck.hs

-----------------------------------------------------------------------------
-- Copyright 2010, Open Universiteit Nederland. This file is distributed 
-- under the terms of the GNU General Public License. For more information, 
-- see the file "LICENSE.txt", which is included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer  :  bastiaan.heeren@ou.nl
-- Stability   :  provisional
-- Portability :  portable (depends on ghc)
--
-----------------------------------------------------------------------------
module Documentation.SelfCheck (performSelfCheck) where

import Control.Monad.Trans
import System.Directory
import Common.Utils (reportTest, useFixedStdGen, Some(..), snd3)
import Common.Exercise
import Service.ExercisePackage
import qualified Common.Strategy.Grammar as Grammar
import Control.Monad
import Service.Request
import Service.DomainReasoner

import qualified Domain.LinearAlgebra.Checks as LA
import Service.ModeJSON
import Service.ModeXML

import qualified Domain.Math.Numeric.Tests as MathNum
import qualified Domain.Math.Polynomial.Tests as MathPoly
import qualified Domain.Math.SquareRoot.Tests as MathSqrt
import qualified Domain.Math.Data.Interval as MathInterval

import qualified Text.UTF8 as UTF8
import qualified Text.JSON as JSON
import Data.List
import System.Time

performSelfCheck :: String -> DomainReasoner ()
performSelfCheck dir = totalDiff $ do
   timeDiff $ liftIO $ do
      putStrLn "* 1. Domain checks"
      Grammar.checks
      MathNum.main
      MathPoly.tests
      MathSqrt.tests
      MathInterval.testMe
      LA.checks
      UTF8.testEncoding
      JSON.testMe

   liftIO $ putStrLn "* 2. Exercise checks"
   pkgs <- getPackages
   forM_ pkgs $ \(Some pkg) ->
      timeDiff $ liftIO $ checkExercise (exercise pkg)

   timeDiff $ do
      liftIO $ putStrLn "* 3. Unit tests"
      n <- unitTests dir
      liftIO $ putStrLn $ "** Number of unit tests: " ++ show n
   
-- Returns the number of tests performed
unitTests :: String -> DomainReasoner Int
unitTests = visit 0
 where
   visit i path = do
      valid <- liftIO $ doesDirectoryExist path
      if not valid then return 0 else do
         -- analyse content
         xs <- liftIO $ getDirectoryContents path
         let xml  = filter (".xml"  `isSuffixOf`) xs
             json = filter (".json" `isSuffixOf`) xs
         liftIO $ putStrLn $ replicate (i+1) '*' ++ " " ++ simplerDirectory path
         -- perform tests
         forM json $ \x -> 
            performUnitTest JSON (path ++ "/" ++ x)
         forM xml $ \x -> 
            performUnitTest XML (path ++ "/" ++ x)
         -- recursively visit subdirectories
         is <- forM (filter ((/= ".") . take 1) xs) $ \x -> 
                  visit (i+1) (path ++ "/" ++ x)
         return (length (xml ++ json) + sum is)

performUnitTest :: DataFormat -> FilePath -> DomainReasoner ()
performUnitTest format path = do
   liftIO useFixedStdGen -- fix the random number generator
   txt <- liftIO $ readFile path
   exp <- liftIO $ readFile expPath
   out <- case format of 
             JSON -> liftM snd3 (processJSON txt)
             XML  -> liftM snd3 (processXML txt) 
                        `catchError` \_ -> return "Error"
   liftIO $ reportTest (stripDirectoryPart path) (out ~= exp)
 where
   expPath = baseOf path ++ ".exp"
   baseOf  = reverse . drop 1 . dropWhile (/= '.') . reverse
   x ~= y  = filterVersion x == filterVersion y
   
   filterVersion = 
      let p s = not (null s || "version" `isInfixOf` s)
      in unlines . filter p . lines

simplerDirectory :: String -> String
simplerDirectory s
   | "../"   `isPrefixOf` s = simplerDirectory (drop 3 s)
   | "test/" `isPrefixOf` s = simplerDirectory (drop 5 s)
   | otherwise = s

stripDirectoryPart :: String -> String
stripDirectoryPart = reverse . takeWhile (/= '/') . reverse

{-
logicConfluence :: IO ()
logicConfluence = reportTest "logic rules" (isConfluent f rs)
 where
   f    = normalizeWith ops . normalFormWith ops rs
   ops  = map makeCommutative Logic.logicOperators
   rwrs = Logic.logicRules \\ [Logic.ruleOrOverAnd, Logic.ruleCommOr, Logic.ruleCommAnd]
   rs   = [ r | RewriteRule r <- concatMap transformations rwrs ]
   -- eqs  = bothWays [ r | RewriteRule r <- concatMap transformations Logic.logicRules ]
-}

-- Helper functions
showDiffWith :: MonadIO m => (TimeDiff -> IO ()) -> m a -> m a
showDiffWith f action = do
   t0 <- liftIO getClockTime
   a  <- action
   t1 <- liftIO getClockTime
   liftIO (f (diffClockTimes t1 t0))
   return a

totalDiff :: MonadIO m => m a -> m a
totalDiff = showDiffWith (putStrLn . ("*** Total time: "++) . formatDiff)
   
timeDiff :: MonadIO m => m a -> m a
timeDiff = showDiffWith (putStrLn . ("+++ Time: "++) . formatDiff) 

formatDiff :: TimeDiff -> String
formatDiff d@(TimeDiff z1 z2 z3 h m s p)
   | any (/=0) [z1,z2,z3] = timeDiffToString d
   | s >= 60      = formatDiff (timeDiff ((h*60+m)*60+s) p)
   | h==0 && m==0 = show inSec ++ " secs"
   | otherwise    = show (60*h+m) ++ ":" ++ digSec ++ " mins" 
 where
   milSec = 1000*toInteger s + p `div` 1000000000
   inSec  = fromIntegral milSec / 1000
   digSec = (if s < 10 then ('0' :) else id) (show s)
   timeDiff n p = 
      let (rest, s) = n `divMod` 60
          (h, m)    = rest `divMod` 60
      in TimeDiff 0 0 0 h m s p