packages feed

h3spec-0.1.15: h3spec.hs

{-# LANGUAGE OverloadedStrings #-}

module Main where

import Control.Monad (when)
import qualified Data.List as L
import Data.Maybe (fromMaybe)
import Data.Version (showVersion)
import qualified Network.HTTP3.Client as H3
import Network.QUIC.Internal
import System.Console.GetOpt
import System.Environment (getArgs, withArgs)
import System.Exit (exitFailure, exitSuccess)
import qualified Test.Hspec.Core.Runner as H
import Text.Read (readMaybe)

import HTTP3Error
import qualified Paths_h3spec as P
import TransportError

data Options = Options
    { optVersion :: Bool
    , optDebugLog :: Bool
    , optValidate :: Bool
    , optMatch :: [String]
    , optSkip :: [String]
    , optQLogDir :: Maybe FilePath
    , optKeyLogFile :: Maybe FilePath
    , optTimeout :: Int
    }
    deriving (Show)

defaultOptions :: Options
defaultOptions =
    Options
        { optVersion = False
        , optDebugLog = False
        , optValidate = True
        , optMatch = []
        , optSkip = []
        , optQLogDir = Nothing
        , optKeyLogFile = Nothing
        , optTimeout = 2000 -- 2 seconds
        }

options :: [OptDescr (Options -> Options)]
options =
    [ Option
        ['v']
        ["version"]
        (NoArg (\o -> o{optVersion = True}))
        "Print version"
    , Option
        ['d']
        ["debug"]
        (NoArg (\o -> o{optDebugLog = True}))
        "print debug info"
    , Option
        ['m']
        ["match"]
        (ReqArg (\m o -> o{optMatch = m : optMatch o}) "<test case description>")
        "Select test cases"
    , Option
        ['s']
        ["skip"]
        (ReqArg (\m o -> o{optSkip = m : optSkip o}) "<test case description>")
        "Skip test cases"
    , Option
        ['q']
        ["qlog-dir"]
        (ReqArg (\dir o -> o{optQLogDir = Just dir}) "<dir>")
        "directory to store qlog"
    , Option
        ['l']
        ["key-log-file"]
        (ReqArg (\file o -> o{optKeyLogFile = Just file}) "<file>")
        "a file to store negotiated secrets"
    , Option
        ['t']
        ["timeout"]
        (ReqArg setTimeout "<milliseconds>")
        "timeout for each test case (2000)"
    , Option
        ['n']
        ["no-validate"]
        (NoArg (\o -> o{optValidate = False}))
        "no validating server certificates"
    ]

-- An invalid value is reported by main after getOpt.
setTimeout :: String -> Options -> Options
setTimeout ms o = o{optTimeout = fromMaybe (-1) $ readMaybe ms}

showUsageAndExit :: String -> IO a
showUsageAndExit msg = do
    putStrLn msg
    putStrLn $ usageInfo usage options
    exitFailure

usage :: String
usage = "Usage: h3spec <host> <port>"

main :: IO ()
main = do
    args0 <- getArgs
    (opts, args) <- case getOpt Permute options args0 of
        (o, n, []) -> return (L.foldl' (flip id) defaultOptions o, n)
        (_, _, errs) -> showUsageAndExit $ concat errs
    when (optTimeout opts <= 0) $
        showUsageAndExit "timeout must be a positive integer\n"
    when (optVersion opts) $ do
        putStrLn $ "h3spec " ++ showVersion P.version
        exitSuccess
    (host, port) <- case args of
        [h, p] -> return (h, p)
        _ -> showUsageAndExit ""
    let cc =
            defaultClientConfig
                { ccServerName = host
                , ccPortName = port
                , ccALPN = \_ -> return $ Just ["h3", "h3-29", "hq-interop", "hq-29"]
                , ccDebugLog = optDebugLog opts
                , ccQLog = optQLogDir opts
                , ccKeyLog = getLogger $ optKeyLogFile opts
                , ccValidate = optValidate opts
                }
        qcArgs0
            | null (optMatch opts) = []
            | otherwise =
                "--match" : (L.intersperse "--match" $ reverse $ optMatch opts)
        qcArgs
            | null (optSkip opts) = qcArgs0
            | otherwise =
                qcArgs0 ++ "--skip" : (L.intersperse "--skip" $ reverse $ optSkip opts)
        h3cc = H3.defaultClientConfig{H3.authority = host}
        ms = optTimeout opts
    H.readConfig H.defaultConfig qcArgs
        >>= withArgs []
            . H.runSpec (transportErrorSpec cc ms >> h3ErrorSpec cc h3cc ms (const Nothing))
        >>= H.evaluateSummary

getLogger :: Maybe FilePath -> (String -> IO ())
getLogger Nothing = \_ -> return ()
getLogger (Just file) = \msg -> appendFile file (msg ++ "\n")