packages feed

hedgehog-extras-0.6.5.0: src/Hedgehog/Extras/Test/Network.hs

{-# LANGUAGE ScopedTypeVariables #-}

{- HLINT ignore "Redundant flip" -}

module Hedgehog.Extras.Test.Network
  ( doesFileExists
  , isPortOpen
  , doesSocketExist
  , assertPortOpen
  , assertSocketExists
  , doesSprocketExist
  , downloadToFile
  , downloadAndExtractGithubCommitToTemp
  ) where

import           Control.Exception (IOException, try)
import           Control.Monad
import           Control.Monad.IO.Class (MonadIO)
import           Data.Bool
import           Data.Either
import           Data.Function
import           Data.Functor
import           Data.Int
import           Data.Semigroup
import           GHC.Stack (HasCallStack)
import           Hedgehog (MonadTest)
import           Hedgehog.Extras.Stock.IO.Network.Sprocket (Sprocket, sprocketSystemName)
import           Prelude (String)
import           System.FilePath ((</>))
import           System.IO (FilePath)
import           Text.Show

import qualified Codec.Archive.Tar as TAR
import qualified Codec.Archive.Tar.Check as TAR
import qualified Codec.Compression.GZip as GZ
import qualified Data.ByteString.Lazy as LBS
import qualified Data.List as List
import qualified GHC.Stack as GHC
import qualified Hedgehog as H
import qualified Hedgehog.Extras.Stock.IO.Network.NamedPipe as IO
import qualified Hedgehog.Extras.Stock.IO.Network.Socket as IO
import qualified Hedgehog.Extras.Stock.OS as OS
import qualified Hedgehog.Extras.Test.Base as H
import qualified Network.HTTP.Conduit as HTTP
import qualified System.Directory as H
import qualified System.Directory as IO
import qualified System.FilePath as FP

-- | Test if a file exists
doesFileExists :: (MonadTest m, MonadIO m, HasCallStack) => FilePath -> m Bool
doesFileExists = GHC.withFrozenCallStack . H.evalIO . IO.doesFileExist

-- | Test if a port is open
isPortOpen :: (MonadTest m, MonadIO m, HasCallStack) => Int -> m Bool
isPortOpen port = GHC.withFrozenCallStack $ do
  H.note_ $ "Port: " <> show port
  H.evalIO $ IO.isPortOpen port

-- | Test if a socket file exists
doesSocketExist :: (MonadTest m, MonadIO m, HasCallStack) => FilePath -> m Bool
doesSocketExist = GHC.withFrozenCallStack . H.evalIO . IO.doesSocketExist

-- | Assert that a port is open
assertPortOpen :: (MonadTest m, MonadIO m, HasCallStack) => Int -> m ()
assertPortOpen = GHC.withFrozenCallStack . H.assertM . isPortOpen

-- | Assert that a socket file exists is open
assertSocketExists :: (MonadTest m, MonadIO m, HasCallStack) => FilePath -> m ()
assertSocketExists = GHC.withFrozenCallStack . H.assertM . doesSocketExist

-- | Test if the sprocket exists
doesSprocketExist :: (MonadTest m, MonadIO m, HasCallStack) => Sprocket -> m Bool
doesSprocketExist socket = GHC.withFrozenCallStack $ do
  waitResult <- H.evalIO . try $ if OS.isWin32
    then IO.doesNamedPipeExist (sprocketSystemName socket)
    else IO.doesSocketExist (sprocketSystemName socket)
  case waitResult of
    Right result -> return result
    Left (e :: IOException) -> do
      H.annotate $ "Error: " <> show e
      return False

-- | Download from a URl to a file
downloadToFile :: (MonadTest m, MonadIO m, HasCallStack) => String -> FilePath -> m ()
downloadToFile url path = GHC.withFrozenCallStack $ do
  H.note_ $ "Downloading " <> url <> " to " <> path
  H.evalIO $ HTTP.simpleHttp url >>= LBS.writeFile path

tarErrors :: TAR.GenEntries tarPath linkTarget e -> [e]
tarErrors entries = TAR.foldEntries (flip const) id (:) entries []

-- | Download a github commit to a temporary directory, extract it and return the path to the extracted directory.
--
-- If the file is already downloaded, it will not be downloaded again.
-- If the file is already extracted, it will not be extracted again.
downloadAndExtractGithubCommitToTemp :: (MonadTest m, MonadIO m, HasCallStack) => FilePath -> String -> String -> m FilePath
downloadAndExtractGithubCommitToTemp dir repository commit = GHC.withFrozenCallStack $ do
  let url = "https://github.com/" <> repository <> "/archive/" <> commit <> ".tar.gz"
  let topDir = FP.takeFileName repository <> "-" <> commit
  let tarPath = dir </> topDir <> ".tar.gz"
  let dest = dir </> topDir

  tarFileExists <- H.evalIO $ IO.doesFileExist tarPath
  if tarFileExists
    then H.note_ $ "Already downloaded " <> url <> " to " <> tarPath
    else do
      H.note_ $ "Downloading " <> url <> " to " <> tarPath
      H.evalIO $ HTTP.simpleHttp url >>= LBS.writeFile tarPath

  destExists <- H.evalIO $ IO.doesDirectoryExist dest
  if destExists
    then H.note_ $ "Already extracted " <> tarPath <> " to " <> dest
    else do
      H.note_ $ "Extracting " <> tarPath <> " to " <> dest
      errors <- H.evalIO $ tarErrors . TAR.checkTarbomb topDir . TAR.read . GZ.decompress <$> LBS.readFile tarPath

      unless (List.null errors) $ do
        H.annotate $ "Errors: " <> show errors
        H.failure

      H.evalIO $ TAR.unpack dir . TAR.read . GZ.decompress =<< LBS.readFile tarPath

      void . H.assertIO $ H.doesDirectoryExist dest

  H.note dest