-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathSandbox.hs
105 lines (85 loc) · 3.54 KB
/
Sandbox.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
module Sandbox (tryProblem) where
import Problems (problems, Problem(..), Test)
import System.Process
import System.Timeout
import System.IO
import Control.Monad (void)
import Control.Concurrent.Async
import Data.Unique
import System.Directory
import Data.List
import Data.Aeson hiding (json)
import Data.Text (pack)
data TestRes = TestRes { test :: Test
, testSucc :: Bool
, testOut :: String
} deriving (Show)
data Mark = Mark { markSucc :: Bool
, markTests :: [TestRes]
} deriving (Show)
instance ToJSON TestRes where
toJSON (TestRes t s o) = object [ pack "test" .= t
, pack "success" .= s
, pack "output" .= o ]
instance ToJSON Mark where
toJSON (Mark s t) = object [pack "success" .= s, pack "tests" .= t]
trim :: Char -> String -> String
trim v = x . x where x = reverse . dropWhile (\x -> x == v)
hGetContentsEager :: Handle -> IO String
hGetContentsEager h = do
readable <- hIsReadable h
end <- hIsEOF h
if readable && not end then do
x <- hGetLine h
y <- hGetContentsEager h
return . trim '\n' $ x ++ "\n" ++ y
else
return ""
stripError :: String -> String
stripError [] = ""
stripError e | "/mnt/" `isPrefixOf` e = stripError $ drop 5 e
| "<interactive>:" `isPrefixOf` e = stripError $ drop 19 e
| "<interactive>" `isPrefixOf` e = stripError $ drop 13 e
| "*** Exception" `isPrefixOf` e = stripError $ drop 4 e
stripError (x:xs) = case take 13 xs == "<interactive>" of
True -> [x]
False -> x : stripError xs
makeTestRes :: Test -> Bool -> String -> String -> TestRes
makeTestRes t s r e | length e > 0 = TestRes t s $ stripError e
| otherwise = TestRes t s r
runTest :: Int -> String -> Test -> IO TestRes
runTest to c t = do
unique <- newUnique
let code = 'a':(show $ hashUnique unique) -- docker requires letter initial
let dir = "/tmp/99haskell/" ++ code
createDirectoryIfMissing True dir
writeFile (dir ++ "/Main.hs") c
(Just hin, Just hout, Just herr, hproc) <-
createProcess
(proc "docker" [ "run"
, "--name=" ++ code
, "--interactive=true"
, "--volume=" ++ dir ++ ":/mnt"
, "haskell"
, "/bin/bash" ])
{ std_in = CreatePipe, std_out = CreatePipe, std_err = CreatePipe }
hPutStrLn hin "ghci -v0 /mnt/Main.hs"
hPutStrLn hin $ fst t
hClose hin
maybeOutput <- timeout (to * 1000000) (return =<< (hGetContentsEager hout))
let output = case maybeOutput of Nothing -> ""
Just a -> a
error <- case maybeOutput of Nothing -> return $ "99Haskell: Timeout Error\
\. Program did not exit after "
++ show to ++ " seconds."
Just _ -> hGetContentsEager herr
hClose hout
hClose herr
async $ do
readProcess "docker" ["rm", "-f", code] ""
removeDirectoryRecursive dir
return $ makeTestRes t (output == snd t) output error
tryProblem :: Int -> String -> IO Value
tryProblem i c = do
allTests <- mapConcurrently (runTest 5 c) (tests $ problems !! (i - 1))
return . toJSON $ Mark (all (==True) [testSucc x | x <- allTests]) allTests