packages feed

darcs-benchmark (empty) → 0.1

raw patch · 9 files changed

+676/−0 lines, 9 filesdep +HTTPdep +basedep +bytestringbuild-type:Customsetup-changed

Dependencies added: HTTP, base, bytestring, containers, directory, filepath, html, mtl, network, process, regex-posix, tabular, tar, time, zlib

Files

+ Benchmark.hs view
@@ -0,0 +1,198 @@+module Benchmark where++import Shellish hiding ( run )+import Data.Char+import Data.List+import Data.Maybe+import System.Directory+import System.FilePath( (</>), (<.>) )+import System.IO+import qualified Text.Tabular          as Tab+import qualified Text.Tabular.AsciiArt as TA+import System.Exit+import Text.Printf+import Text.Regex.Posix( (=~) )+import Data.Time.Clock+import Control.Monad.Error+import Control.Monad.State( liftIO )+import Control.Exception( throw )+import System.Process( runInteractiveProcess, runInteractiveCommand,+                       waitForProcess )++precision, iterations :: Int+precision = 1+iterations = 2+combine :: Ord a => [a] -> a+combine = minimum++data MemTime = MemTime Rational Float+type Darcs = [String] -> Command String++newtype TestRepo = TestRepo String deriving Eq+data TestBinary = TestBinary String++type BenchmarkCmd a = Darcs -> Command a+data Benchmark a = Idempotent String (BenchmarkCmd a)+                 | Destructive String (BenchmarkCmd a)++data Test a = Test (Benchmark a) TestRepo TestBinary++copyTree :: FilePath -> FilePath -> IO ()+copyTree from to =+    do subs <- (\\ [".", ".."]) `fmap` getDirectoryContents from+       createDirectory to+       forM_ subs $ \item -> do+         is_dir <- doesDirectoryExist (from </> item)+         is_file <- doesFileExist (from </> item)+         when is_dir $ copyTree (from </> item) (to </> item)+         when is_file $ copyFile (from </> item) (to </> item)++reset :: Command ()+reset = do+  resetMemoryUsed+  resetTimeUsed++description :: Benchmark a -> String+description (Idempotent d _) = d+description (Destructive d _) = d++exec :: Benchmark a -> FilePath -> Command a+exec (Idempotent _ cmd) darcs_path = do+  cd "_playground"+  cmd (darcs darcs_path)+exec (Destructive _ cmd) darcs_path = do+  cd "_playground"+  let cleanup = cd ".." >> rm_rf "_playground"+  res <- cmd (darcs darcs_path) `catchError` \e -> (cleanup >> throw e)+  cleanup+  return res++defaultrepo :: FilePath -> FilePath+defaultrepo path = (path </> "_darcs" </> "prefs" </> "defaultrepo")++prepare :: String -> Command ()+prepare repo = do+  echo_n "!"+  rm_rf "_playground"+  echo_n "."+  liftIO $ createDirectory "_playground"+  let playrepo = "_playground" </> "repo"+      origrepo = "repo" <.> repo+  isrepo <- liftIO $ doesDirectoryExist (origrepo </> "_darcs")+  unless isrepo $ fail $ origrepo ++ ": Not a darcs repository!"+  liftIO $ copyTree origrepo playrepo+  echo_n "."+  wd <- pwd+  liftIO $ writeFile (defaultrepo playrepo) (wd </> origrepo)++prepareIfDifferent :: String -> Command ()+prepareIfDifferent repo = do+  let playrepo = "_playground" </> "repo"+      origrepo = "repo" <.> repo+  exist <- test_e "_playground"+  current' <- if exist then liftIO $ readFile (defaultrepo playrepo) else return ""+  let current = reverse (dropWhile (=='\n') $ reverse current')+  wd <- pwd+  if (exist && current == wd </> origrepo) then echo_n "..."+                                           else prepare repo++run :: Test a -> Command (Maybe MemTime)+run (Test benchmark (TestRepo testrepo) (TestBinary bin)) = do+  (Just `fmap` run') `catchError` \e ->+      do echo $ " error: " ++ show e+         return Nothing+  where run' = do+          echo_n $ bin ++ " " ++ description benchmark ++ " [" ++ testrepo ++ "]: "+          exe <- which $ bin+          darcs_path <- case exe of+                          Nothing -> canonize bin+                          Just p -> return p+          times <- sequence [+                    do echo_n $ show i+                       sub $ do prepareIfDifferent testrepo+                                timed (exec benchmark darcs_path)+                           | i <- [1 .. iterations] ]+          let time = combine [ t | MemTime _ t <- times ]+              mem = combine [ m | MemTime m _ <- times ]+              spaces = 45 - (length bin + length (description benchmark) + length testrepo)+              result = MemTime mem time+          echo $ (replicate spaces ' ') ++ (concat $ intersperse ", " $ formatResult result)+          return result++formatNumber :: (PrintfArg a, Fractional a) => a -> String+formatNumber = printf $ "%."++(show precision)++"f"++formatResult :: MemTime -> [String]+formatResult (MemTime mem time) =+  [ formatNumber time ++ "s"+  ,  formatNumber ((realToFrac (mem / (1024*1024))) :: Float) ++ "M" ]++tabulateRepo :: String -> [(Test a, Maybe MemTime)] -> Tab.Table String String String+tabulateRepo repo results = Tab.Table rowhdrs colhdrs rows+ where+  rowhdrs = Tab.Group Tab.NoLine $ map Tab.Header rownames+  colhdrs = Tab.Group Tab.SingleLine $ map colgrp colnames+  colgrp x = Tab.Group Tab.NoLine [Tab.Header x, Tab.Header ""]+  colnames = nub [ label | (Test _ _ (TestBinary label), _) <- interesting ]+  rownames = nub [ description bench | (Test bench _ _, _) <- interesting ]+  interesting = [ test | test@(Test _ (TestRepo r) _, _) <- results, r == repo ]+  rows = [ concat [ fmt $ find (match row column) interesting | column <- colnames ]+           | row <- rownames ]+  match bench binary (Test bench' _ (TestBinary binary'), _) =+      bench == description bench' && binary == binary'+  fmt (Just (_, Just x)) = formatResult x+  fmt _ = [ "-", "-" ]++tabulate :: [(Test a, Maybe MemTime)] -> [(String, Tab.Table String String String)]+tabulate results = zip repos $ map (flip tabulateRepo results) repos+ where repos = nub [ repo | (Test _ (TestRepo repo) _, _) <- results ]++timed :: Command a -> Command MemTime+timed a = do+  resetMemoryUsed+  t1 <- liftIO $ getCurrentTime+  a+  t2 <- liftIO $ getCurrentTime+  mem <- memoryUsed+  resetMemoryUsed+  return $ MemTime (fromIntegral mem) (realToFrac $ diffUTCTime t2 t1)++check_darcs :: String -> IO ()+check_darcs cmd = do+       (_,outH,_,procH) <- runInteractiveCommand $ cmd ++ " --version"+       out <- strictGetContents outH+       waitForProcess procH+       case out of+         '2':'.':_ -> return ()+         _ -> fail $ cmd ++ ": Not darcs 2.x binary."++darcs :: String -> [String] -> Command String+darcs cmd args' = do+    (res, _, stats) <- liftIO $ do+       let args = args' ++ ["+RTS", "-sdarcs-stats", "-RTS"]+       (_,outH,errH,procH) <- runInteractiveProcess cmd args Nothing Nothing+       res <- strictGetContents outH+       errs <- strictGetContents errH+       ex <- waitForProcess procH+       stats <- readFile "darcs-stats" `catch` \_ -> return ""+       case ex of+         ExitSuccess -> return ()+         ExitFailure n -> fail $ "darcs failed with error code "+                            ++ show n ++ "\nsaying: " ++ errs+       return (res, errs, stats)+    let bytes = (stats =~ "([0-9, ]+) M[bB] total memory in use") :: String+        mem = (read (filter (`elem` "0123456789") bytes) :: Int)+    recordMemoryUsed $ mem * 1024 * 1024+    return res++benchMany :: [TestRepo] -> [TestBinary] -> [Benchmark a] -> Command [(Test a, Maybe MemTime)]+benchMany repos bins benches =+    sequence [ do let test = Test bench repo bin+                  memtime <- run test+                  return (test, memtime)+               | repo <- repos, bin <- bins, bench <- benches ]++renderMany :: [(Test a, Maybe MemTime)] -> Command ()+renderMany t = sequence_ [ do echo $ "\n=== " ++ r ++ " ===\n"+                              echo_n $ TA.render id id id tab+                           | (r, tab) <- tabulate t ]
+ Download.hs view
@@ -0,0 +1,34 @@+module Download where++import Network.URI+import Network.HTTP+import Network.HTTP.Base+import qualified Codec.Archive.Tar as Tar ( read, unpack )+import System.FilePath+import System.Directory+import Codec.Compression.Zlib( decompress )+import Control.Monad++baseurl :: String+baseurl = "http://repos.mornfall.net/darcs/benchmark-repos/"++download :: String -> IO ()+download repo = do+  let Just url = parseURI $ baseurl  ++ repo ++ ".tgz"+  putStrLn $ "downloading and extracting: " ++ show url+  exist_dir <- doesDirectoryExist $ "repo" <.> repo+  exist_file <- doesFileExist $ "repo" <.> repo+  let go = do rsp' <- simpleHTTP (mkRequest GET url)+              rsp <- case rsp' of+                       Left e -> fail (show e)+                       Right x -> return x+              when (rspCode rsp /= (2, 0, 0)) $+                   fail ("download failed: " ++ rspReason rsp)+              createDirectory $ "repo" <.> repo+              bits <- getResponseBody rsp'+              let entries = Tar.read $ decompress bits+              Tar.unpack ("repo" <.> repo) entries++  if (exist_dir || exist_file)+       then putStrLn $ "repo" <.> repo ++ " already exists, skipping!"+       else go
+ LICENSE view
@@ -0,0 +1,26 @@+Copyright (c) Eric Kow 2008.+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:+1. Redistributions of source code must retain the above copyright+   notice, this list of conditions and the following disclaimer.+2. Redistributions in binary form must reproduce the above copyright+   notice, this list of conditions and the following disclaimer in the+   documentation and/or other materials provided with the distribution.+3. Neither the name of the author nor the names of his contributors+   may be used to endorse or promote products derived from this software+   without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE+ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF+SUCH DAMAGE.
+ README view
@@ -0,0 +1,39 @@+Darcs benchmarking bits+=======================++Running+-------++Say `cabal install` to build and install the darcs-benchmark binary. When you+have one, run it to obtain further instructions. (Hint: --get will download the+benchmark repositories.)++Writing Benchmarks+------------------++Please take a look at Standard.hs: this is a module with a bunch of "standard+darcs benchmarks", currently get, annotate, check, repair and some other. It+should be fairly clear how to write your own from these examples. Timing is the+overall time spent in the benchmark, memory is the peak memory usage reported+by RTS, from darcs invocations only (this also means that the darcs executables+supplied need to accept +RTS ... options).++Adding repositories+-------------------++To add a new repository:++- cd my-bench-repo && tar czf ../my-bench-repo.tgz .+- upload the result somewhere and tell mornfall (me-at-mornfall-dot-org)++To test locally, just darcs get /path/to/your/repository repo.your-repo and run+darcs-benchmark.++Obtaining Profiles+-----------------++Not yet implemented, but shouldn't be too hard. Probably we need to:++- detect whether the binaries are compiled with profiling enabled,+- pass in the right RTS flags to produce some profiling output,+- stash away this output after each darcs invocation, to a predictable place.
+ Setup.hs view
@@ -0,0 +1,11 @@+import Distribution.Simple+         ( defaultMainWithHooks, UserHooks(..), simpleUserHooks )+import Distribution.Simple.LocalBuildInfo( LocalBuildInfo(..) )+import System( system, exitWith )+import System.FilePath( (</>) )++main :: IO ()+main = defaultMainWithHooks simpleUserHooks {+  runTests = \ _ _ _ lbi -> do+               exitWith =<< system (buildDir lbi </> "darcs-benchmark" </> "darcs-benchmark")+}
+ Shellish.hs view
@@ -0,0 +1,162 @@+module Shellish( cd, pwd, run, (#), withCurrentDirectory, strictGetContents,+                 shellish, silently, verbosely, Command, sub, echo, echo_n,+                 which, canonize, resetMemoryUsed, recordMemoryUsed,+                 memoryUsed, rm_rf, whenM, test_e, test_f, test_d,+                 resetTimeUsed, timeUsed, mv, ls )+    where++import Data.List+import System.IO+import System.Directory+import System.Exit+import Control.Monad.State+import Control.Monad.Error+import Data.Time.Clock( getCurrentTime, diffUTCTime, UTCTime(..) )+import Control.Exception ( bracket, evaluate )+import Control.Monad ( when )+import qualified Data.ByteString.Char8 as B+import System.Process( runInteractiveProcess, waitForProcess )++-- TODO maxMem is sort of a layering violation here... but who cares.+data St = St { sCode :: Int, sStderr :: B.ByteString , sStdout :: B.ByteString,+               sVerbose :: Bool, maxMem :: Int, startTime :: UTCTime }++type Command a = StateT St IO a++cd :: String -> Command ()+cd = liftIO . setCurrentDirectory++mv :: String -> String -> Command ()+mv a b = liftIO $ renameFile a b++ls :: String -> Command [String]+ls dir = liftIO $ (\\ [".", ".."]) `fmap` getDirectoryContents dir++pwd :: Command String+pwd = liftIO $ getCurrentDirectory++echo :: String -> Command ()+echo = liftIO . putStrLn++echo_n :: String -> Command ()+echo_n = liftIO . (>> hFlush System.IO.stdout) . putStr++which :: String -> Command (Maybe String)+which = liftIO . findExecutable++canonize :: String -> Command String+canonize = liftIO . canonicalizePath++whenM :: Monad m => m Bool -> m () -> m ()+whenM c a = do res <- c+               when res a++test_e :: String -> Command Bool+test_e f = liftIO $ do+             dir <- doesDirectoryExist f+             file <- doesFileExist f+             return $ file || dir++test_f :: String -> Command Bool+test_f = liftIO . doesFileExist++test_d :: String -> Command Bool+test_d = liftIO . doesDirectoryExist++rm_rf :: String -> Command ()+rm_rf f = whenM (test_e f) $ liftIO $ removeDirectoryRecursive f++run :: String -> [String] -> Command String+run cmd args = do+    (_,outH,errH,procH) <- liftIO $ runInteractiveProcess cmd args Nothing Nothing+    st <- get+    res <- liftIO $ B.hGetContents outH+    errs <- liftIO $ B.hGetContents errH+    ex <- liftIO $ waitForProcess procH+    when (sVerbose st) $ do+                 liftIO $ B.putStr res+                 liftIO $ B.putStr errs+    case ex of+      ExitSuccess -> return ()+      ExitFailure n -> fail $ "command " ++ cmd ++ " " ++ show args+                         ++ " failed with exit code " ++ show n+    put $ st { sCode = 0, sStderr = errs, sStdout = res }+    return $ B.unpack res++(#) :: String -> [String] -> Command String+cmd # args = run cmd args++silently :: Command a -> Command a+silently a = do+  x <- get+  put $ x { sVerbose = False }+  r <- a+  put x+  return r++verbosely :: Command a -> Command a+verbosely a = do+  x <- get+  put $ x { sVerbose = True }+  r <- a+  put x+  return r++sub :: Command a -> Command a+sub a = do+  -- TODO save environment as well?+  dir <- liftIO $ getCurrentDirectory+  r <- a `catchError` (\e -> (liftIO $ setCurrentDirectory dir) >> throwError e)+  liftIO $ setCurrentDirectory dir+  return r++shellish :: MonadIO m => Command a -> m a+shellish a = do+  dir <- liftIO $ getCurrentDirectory+  time <- liftIO getCurrentTime+  r <- liftIO $ evalStateT a $ empty time+  liftIO $ setCurrentDirectory dir+  return r+      where empty t = St { sCode = 0, sStderr = B.empty,+                           sStdout = B.empty, sVerbose = True,+                           maxMem = 0, startTime = t }++strictGetContents :: Handle -> IO String+strictGetContents h =+    do res <- hGetContents h+       evaluate (length res)+       return res++withCurrentDirectory :: FilePath -> IO a -> IO a+withCurrentDirectory name m =+    bracket+        (do cwd <- getCurrentDirectory+            when (name /= "") (setCurrentDirectory name)+            return cwd)+        (\oldwd -> setCurrentDirectory oldwd `catch` (\_ -> return ()))+        (const m)++recordMemoryUsed :: Int -> Command ()+recordMemoryUsed u = do+  x <- get+  put x { maxMem = maximum [maxMem x, u] }++resetMemoryUsed :: Command ()+resetMemoryUsed = do+  x <- get+  put x { maxMem = 0 }++resetTimeUsed :: Command ()+resetTimeUsed = do+  x <- get+  t <- liftIO getCurrentTime+  put x { startTime = t }++memoryUsed :: Command Int+memoryUsed = do x <- get+                return $ maxMem x++timeUsed :: Command Float+timeUsed = do x <- get+              t <- liftIO getCurrentTime+              return $ realToFrac $ diffUTCTime t (startTime x)
+ Standard.hs view
@@ -0,0 +1,87 @@+module Standard( standard, fast ) where+import System.FilePath+import Shellish+import Benchmark hiding ( darcs )+import Control.Monad( forM, filterM, when )+import Control.Monad.Trans( liftIO )+import qualified Control.Monad.State as MS++check :: BenchmarkCmd ()+check darcs = do+  cd "repo"+  darcs [ "check", "--no-test" ]+  return ()++repair :: BenchmarkCmd ()+repair darcs = do+  cd "repo"+  darcs [ "repair" ]+  return ()++annotate :: BenchmarkCmd ()+annotate darcs = do+  cd "repo"+  whenM ((not . or) `fmap` mapM test_e files) $ fail "no files to annotate"+  sequence [ whenM (test_e f) $ (darcs [ "annotate", f ] >> return ())+             | f <- files ]+  return ()+    where files = [ "Setup.hs", "Setup.lhs" ]++get :: Int -> [String] -> BenchmarkCmd ()+get n param darcs = do+  forM [1..n] $ \x -> darcs $ "get" : param ++ ["repo", "get" ++ show x]+  return ()++pull :: Int -> BenchmarkCmd ()+pull n darcs = do+  cd "repo"+  darcs [ "unpull", "--last", show n, "--all" ]+  reset -- the benchmark starts here+  darcs [ "pull", "--all" ]+  return ()++-- Oh my eyes! Oh noes! Horrible!+darcs_wh :: [String] -> BenchmarkCmd ()+darcs_wh param darcs = do+  state <- MS.get+  newstate <- liftIO $ catch (MS.execStateT (darcs $ "whatsnew" : param) state)+                             (\_ -> return state)+  MS.put newstate++wh :: Int -> BenchmarkCmd ()+wh n darcs = do+  cd "repo"+  forM [1..n] $ \_ -> darcs_wh [] darcs+  return ()++wh_mod :: Int -> BenchmarkCmd ()+wh_mod n darcs = do+  cd "repo"+  files <- filterM test_f =<< ls "."+  when (null files) $ fail "no files to modify in repo root!"+  forM files $ \f -> mv f $ f <.> "__foo__"+  forM [1..n] $ \_ -> darcs_wh [] darcs+  forM files $ \f -> mv (f <.> "__foo__") f+  return ()++wh_l :: Int -> BenchmarkCmd ()+wh_l n darcs = do+  cd "repo"+  forM [1..n] $ \_ -> darcs_wh [ "--look-for-adds" ] darcs+  return ()++fast :: [ Benchmark () ]+fast = [ Destructive "get (full)" $ get 1 []+       , Destructive "get (lazy, x10)" $ get 10 ["--lazy"]+       , Idempotent "pull 100" $ pull 100+       , Idempotent "annotate" annotate+       , Idempotent "wh x50" $ wh 50+       , Idempotent "wh mod x50" $ wh_mod 50+       , Idempotent "wh -l x20" $ wh_l 20 ]++standard :: [ Benchmark () ]+standard = fast +++           [ Idempotent "check" check+           , Idempotent "repair" repair+           , Idempotent "pull 1000" $ pull 1000 ]+
+ darcs-benchmark.cabal view
@@ -0,0 +1,40 @@+name:          darcs-benchmark+version:       0.1+synopsis:      Comparative benchmark suite for darcs.++description: A simple tool to compare performance of different Darcs 2.x+             instances.  The program can download a set of test repositories,+             or you can provide your own. Run the program without parameters to+             get help.++license:       BSD3+license-file:  LICENSE+copyright:     2009 Petr Rockai <me@mornfall.net>+author:        Eric Kow <kowey@darcs.net>, Simon Michael <simon@joyful.com>+               and Petr Rockai <me@mornfall.net>+maintainer:    Petr Rockai <me@mornfall.net>+homepage:      http://wiki.darcs.net/Development/Benchmarks+category:      Testing+build-type:    Custom+cabal-version: >= 1.6+extra-source-files: README++executable darcs-benchmark+    if impl(ghc >= 6.8)+      ghc-options: -fwarn-tabs+    ghc-options:   -Wall+    ghc-prof-options: -prof -auto-all++    build-depends: base < 5, process, mtl, tabular >= 0.2, time,+                   regex-posix, html, filepath, directory,+                   containers, bytestring, network, HTTP >= 4000, tar, zlib++    main-is: main.hs+    other-modules: Shellish+                   Benchmark+                   Standard+                   Download++source-repository head+  type:     darcs+  location: http://repos.mornfall.net/darcs/benchmark
+ main.hs view
@@ -0,0 +1,79 @@+#!/usr/bin/env runhaskell+import Shellish( shellish, rm_rf )+import System.Exit+import System.Environment+import System.Directory+import Control.Monad+import Benchmark+import Standard+import Download+import Data.List++help :: String+help = unlines [ "darcs-benchmark: run standard darcs benchmarks"+               , ""+               , "Please either specify the repositories and binaries to run on like this:"+               , "$ darcs-benchmark binary binary -- repository repository"+               , ""+               , "or alternatively, to run on all available repos:"+               , "$ darcs-benchmark binary binary"+               , ""+               , "You can also create a file called 'config' in the working directory."+               , "Put two lines in it, one with list of binaries, one with list of repos:"+               , ""+               , "binary binary binary"+               , "repo repo repo"+               , ""+               , "(again, if the second line is not there, we run on all available repos)"+               , ""+               , "NOTE: To obtain test repositories, you can use 'darcs-benchmark --get',"+               , "optionally supplying the names of the test repos you are interested in."+               , ""+               , "Thank you for benchmarking darcs!" ]++known_repos :: [String]+known_repos = [ "ghc-hashed", "darcs" ]++nonopt :: [String] -> [String]+nonopt args = [ r | r <- args, not $ "--" `isPrefixOf` r ]++download_repos :: [String] -> IO ()+download_repos r = do forM_ (if null r then known_repos else r) $ download+                      exitWith ExitSuccess++config :: [TestRepo] -> [String] -> IO ([TestRepo], [TestBinary], [Benchmark ()])+config allrepos args = do+  when ("--get" `elem` args) $ do download_repos (nonopt args)+  haveConf <- doesFileExist "config"+  when (not haveConf && null args) $ do+               putStrLn help+               exitWith $ ExitFailure 1+  conf <- if haveConf then lines `fmap` readFile "config"+                      else return []+  let confrepos = if length conf > 1 then (words $ conf !! 1) else []+      confbins = if length conf > 0 then words $ conf !! 0 else []+      bins = nonopt $ takeWhile (/= "--") args+      repos = nonopt $ dropWhile (/= "--") args+      userepos = map TestRepo $ if null repos then confrepos else repos+      usebins = map TestBinary $ if null bins then confbins else bins+      usetests = if "--fast" `elem` args then fast else standard+  return (if null userepos then allrepos else userepos, usebins, usetests)++main :: IO ()+main = do+    allrepos <- do listing <- getDirectoryContents "."+                   return $ [ TestRepo $ drop 5 repo+                              | repo <- listing, repo `notElem` [".", ".."]+                                               , "repo." `isPrefixOf` repo ]+    (repos, binaries, tests) <- config allrepos =<< getArgs+    unless (null $ repos \\ allrepos) $ do+         let name r = intercalate ", " $ map (\(TestRepo x) -> x) r+         putStrLn $ "Missing repositories: " ++ name (repos \\ allrepos)+         exitWith $ ExitFailure 2+    forM binaries $ \(TestBinary bin) -> check_darcs bin+    when (null repos) $ do+         putStrLn $ "Oops, no repositories! Consider doing a --get."+         putStrLn $ "(Alternatively, check that you are in the right directory.)"+         exitWith $ ExitFailure 3+    shellish $ do rm_rf "_playground"+                  benchMany repos binaries tests >>= renderMany