hspec-need-env (empty) → 0.1.0.0
raw patch · 9 files changed
+399/−0 lines, 9 filesdep +basedep +hspecdep +hspec-coresetup-changed
Dependencies added: base, hspec, hspec-core, hspec-expectations, hspec-need-env, setenv, transformers
Files
- ChangeLog.md +5/−0
- LICENSE +30/−0
- README.md +10/−0
- Setup.hs +2/−0
- hspec-need-env.cabal +55/−0
- src/Test/Hspec/NeedEnv.hs +118/−0
- test/Spec.hs +1/−0
- test/Synopsis.hs +30/−0
- test/Test/Hspec/NeedEnvSpec.hs +148/−0
+ ChangeLog.md view
@@ -0,0 +1,5 @@+# Revision history for hspec-need-env++## 0.1.0.0 -- 2018-07-18++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2018, Toshio Ito++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * 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.++ * Neither the name of Toshio Ito nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT+OWNER 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.md view
@@ -0,0 +1,10 @@+# hspec-need-env++A simple tool to read environment variables for hspec tests.++See [Test.Hspec.NeedEnv](https://hackage.haskell.org/package/hspec-need-env/docs/Test-Hspec-NeedEnv.html) for detail.+++## Author++Toshio Ito <debug.ito@gmail.com>
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ hspec-need-env.cabal view
@@ -0,0 +1,55 @@+name: hspec-need-env+version: 0.1.0.0+author: Toshio Ito <debug.ito@gmail.com>+maintainer: Toshio Ito <debug.ito@gmail.com>+license: BSD3+license-file: LICENSE+synopsis: Read environment variables for hspec tests+description: Read environment variables for hspec tests. See 'Test.Hspec.NeedEnv'.+category: Test+cabal-version: >= 1.10+build-type: Simple+extra-source-files: README.md, ChangeLog.md+homepage: https://github.com/debug-ito/hspec-need-env+bug-reports: https://github.com/debug-ito/hspec-need-env/issues++library+ default-language: Haskell2010+ hs-source-dirs: src+ ghc-options: -Wall -fno-warn-unused-imports+ -- default-extensions: + -- other-extensions: + exposed-modules: Test.Hspec.NeedEnv+ -- other-modules: + build-depends: base >=4.6.0.0 && <4.12,+ hspec-expectations >=0.7.2 && <0.9,+ hspec-core >=2.2.4 && <2.6++-- executable hspec-need-env+-- default-language: Haskell2010+-- hs-source-dirs: app+-- main-is: Main.hs+-- ghc-options: -Wall -fno-warn-unused-imports+-- -- other-modules: +-- -- default-extensions: +-- -- other-extensions: +-- build-depends: base++test-suite spec+ type: exitcode-stdio-1.0+ default-language: Haskell2010+ hs-source-dirs: test+ ghc-options: -Wall -fno-warn-unused-imports "-with-rtsopts=-M512m"+ main-is: Spec.hs+ -- default-extensions: + other-extensions: CPP+ other-modules: Test.Hspec.NeedEnvSpec,+ Synopsis+ build-depends: base, hspec-need-env, hspec-core,+ hspec >=2.2.4,+ setenv >=0.1.1.3 && <0.2,+ transformers >=0.5.2.0 && <0.6++source-repository head+ type: git+ location: https://github.com/debug-ito/hspec-need-env.git
+ src/Test/Hspec/NeedEnv.hs view
@@ -0,0 +1,118 @@+-- |+-- Module: Test.Hspec.NeedEnv+-- Description: Read environment variables for hspec tests+-- Maintainer: Toshio Ito <debug.ito@gmail.com>+-- +-- <https://github.com/debug-ito/hspec-need-env/tree/master/test/Synopsis.hs Synopsis>:+--+-- > module Synopsis (main,spec) where+-- > +-- > import Control.Applicative ((<$>), (<*>))+-- > import Test.Hspec (Spec, SpecWith, hspec, before, describe, it, shouldBe)+-- > import Test.Hspec.NeedEnv (EnvMode(Need), needEnv, needEnvRead)+-- > +-- > main :: IO ()+-- > main = hspec spec+-- > +-- > -- | Read environment variables for parameters necessary for testing.+-- > getEnvs :: IO (String, Int)+-- > getEnvs = (,)+-- > <$> needEnv mode "TEST_USER_NAME"+-- > <*> needEnvRead mode "TEST_SEED"+-- > where+-- > mode = Need+-- > +-- > spec :: Spec+-- > spec = before getEnvs $ specWithUserAndSeed+-- > -- ^ Use 'before' and similar functions to write 'SpecWith'+-- > -- that takes parameters.+-- > +-- > -- | Test spec that depends on the environment variables.+-- > specWithUserAndSeed :: SpecWith (String, Int)+-- > specWithUserAndSeed = describe "funcUnderTest" $ do+-- > it "should do something" $ \(user_name, seed) -> do+-- > funcUnderTest user_name seed `shouldBe` "SOMETHING"+-- > +-- > funcUnderTest :: String -> Int -> String+-- > funcUnderTest = undefined+--+-- This module exports 'needEnv' and other similar functions that read+-- environment variables in hspec tests. They are useful to write+-- tests that depend on some external entities, e.g. Web servers,+-- database servers and random number generators.+module Test.Hspec.NeedEnv+ ( -- * Basics+ EnvMode(..),+ needEnv,+ needEnvParse,+ needEnvRead,+ -- * Utilities+ needEnvHostPort+ ) where++import Control.Applicative ((<$>), (<*>))+import Data.Monoid ((<>))+import System.Environment (lookupEnv)+import Test.Hspec.Core.Spec (pendingWith)+import Test.Hspec.Expectations (expectationFailure)+import Text.Read (readEither)++-- | How to treat missing environment variable.+data EnvMode = Need+ -- ^ If the environment variable is not set, the test+ -- fails.+ | Want+ -- ^ If the environment variable is not set, the test+ -- gets pending.+ deriving (Show,Eq,Ord,Enum,Bounded)++-- | Get value of the specified environment variable. If the+-- environment variable is not set, it executes the action specified+-- by the 'EnvMode'.+needEnv :: EnvMode+ -> String -- ^ name of the environment variable+ -> IO String -- ^ value of the environment variable+needEnv mode envkey = do+ mval <- lookupEnv envkey+ case mval of+ Nothing -> do+ signalMsg ("Environment variable " <> envkey <> " is not set.")+ return ""+ Just str -> return str+ where+ signalMsg = case mode of+ Need -> expectationFailure+ Want -> pendingWith++-- | Get environment variable by 'needEnv', and parse the value. If it+-- fails to parse, the test fails.+needEnvParse :: EnvMode+ -> (String -> Either String a) -- ^ the parser of the environment variable+ -> String+ -> IO a+needEnvParse mode parseEnvVal envkey = do+ val_str <- needEnv mode envkey+ case parseEnvVal val_str of+ Right val -> return val+ Left e -> do+ let error_msg = "Fail to parse environment variable " <> envkey <> ": " <> e+ expectationFailure error_msg+ error error_msg++-- | Parse the environment variable with 'Read' class.+needEnvRead :: (Read a)+ => EnvMode -> String -> IO a+needEnvRead mode = needEnvParse mode readEither++-- | Get the pair of hostname and port number from environment+-- variables.+--+-- It reads environment variables @(prefix ++ \"_HOST\")@ and+-- @(prefix ++ \"_PORT\")@.+needEnvHostPort :: EnvMode+ -> String -- ^ prefix of environment variables+ -> IO (String,Int)+needEnvHostPort mode prefix = (,) <$> needStr "_HOST" <*> needInt "_PORT"+ where+ needStr suffix = needEnv mode (prefix ++ suffix)+ needInt suffix = needEnvRead mode (prefix ++ suffix)
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ test/Synopsis.hs view
@@ -0,0 +1,30 @@+module Synopsis (main,spec) where++import Control.Applicative ((<$>), (<*>))+import Test.Hspec (Spec, SpecWith, hspec, before, describe, it, shouldBe)+import Test.Hspec.NeedEnv (EnvMode(Need), needEnv, needEnvRead)++main :: IO ()+main = hspec spec++-- | Read environment variables for parameters necessary for testing.+getEnvs :: IO (String, Int)+getEnvs = (,)+ <$> needEnv mode "TEST_USER_NAME"+ <*> needEnvRead mode "TEST_SEED"+ where+ mode = Need++spec :: Spec+spec = before getEnvs $ specWithUserAndSeed+ -- ^ Use 'before' and similar functions to write 'SpecWith'+ -- that takes parameters.++-- | Test spec that depends on the environment variables.+specWithUserAndSeed :: SpecWith (String, Int)+specWithUserAndSeed = describe "funcUnderTest" $ do+ it "should do something" $ \(user_name, seed) -> do+ funcUnderTest user_name seed `shouldBe` "SOMETHING"++funcUnderTest :: String -> Int -> String+funcUnderTest = undefined
+ test/Test/Hspec/NeedEnvSpec.hs view
@@ -0,0 +1,148 @@+{-# LANGUAGE CPP #-}+module Test.Hspec.NeedEnvSpec (main, spec) where++import Control.Monad.IO.Class (liftIO)+import Data.List (isInfixOf)+import Data.IORef (IORef, newIORef, modifyIORef, readIORef)+import System.SetEnv (setEnv, unsetEnv)+import Test.Hspec+import Test.Hspec.Core.Runner (hspecWithResult, Summary(..), Config(..), defaultConfig)+import Test.Hspec.Core.Formatters (Formatter)+import qualified Test.Hspec.Core.Formatters as F+import Test.Hspec.Core.Util (Path)++import Test.Hspec.NeedEnv+ ( EnvMode(..), needEnv, needEnvRead, needEnvHostPort+ )++main :: IO ()+main = hspec spec++spec :: Spec+spec = do+ describe "needEnv Need" $ do+ it "should get the specified env" $ do+ setEnv "HOGE" "hoge"+ successCase (needEnv Need) "HOGE" "hoge"+ it "should fail when the specified env is not present" $ do+ unsetEnv "HOGE"+ failCase (needEnv Need) "HOGE" (\msg -> "not set" `isInfixOf` msg)+ describe "needEnvRead Need" $ do+ it "should get and parse the specified env" $ do+ setEnv "FOOBAR" "100"+ successCase (needEnvRead Need) "FOOBAR" (100 :: Int)+ it "should fail when it fails to parse" $ do+ let needEnvInt :: String -> IO Int+ needEnvInt = needEnvRead Need+ setEnv "FOOBAR" "hoge"+ failCase needEnvInt "FOOBAR" (\msg -> "parse" `isInfixOf` msg)+ describe "needEnv Want" $ do+ it "should get the specified env" $ do+ setEnv "FOOBAR" "foo_bar"+ successCase (needEnv Want) "FOOBAR" "foo_bar"+ it "should make the test pending when the specified env is not present" $ do+ unsetEnv "FOOBAR"+ pendingCase (needEnv Want) "FOOBAR" (\msg -> "not set" `isInfixOf` msg)+ describe "needEnvRead Want" $ do+ it "should fail (even if it's Want) when it fails to parse the env" $ do+ let needEnvInt :: String -> IO Int+ needEnvInt = needEnvRead Want+ setEnv "FOOBAR" "hoge"+ failCase needEnvInt "FOOBAR" (\msg -> "parse" `isInfixOf` msg)+ describe "needEnvHostPort Need" $ do+ it "should get host and port from environment variables the common name prefix" $ do+ (conf, ref_results) <- configForTest+ setEnv "PREFIX_HOGE_HOST" "192.168.17.223"+ setEnv "PREFIX_HOGE_PORT" "1123"+ got_summary <- hspecWithResult conf $ before (needEnvHostPort Need "PREFIX_HOGE") $ do+ specify "dummny" $ \(host, port) -> do+ host `shouldBe` "192.168.17.223"+ port `shouldBe` 1123+ got <- readIORef ref_results+ map resultBody got `shouldBe` [ExampleSuccess]+ got_summary `shouldBe` Summary { summaryExamples = 1, summaryFailures = 0 }++successCase :: (Read a, Show a, Eq a) => (String -> IO a) -> String -> a -> IO ()+successCase envGet envkey envval = do+ (conf, ref_results) <- configForTest+ got_summary <- hspecWithResult conf (before (envGet envkey) $ sampleSpec envval)+ got <- readIORef ref_results+ map resultBody got `shouldBe` [ExampleSuccess]+ got_summary `shouldBe` Summary { summaryExamples = 1, summaryFailures = 0 }++failOrPendingCase :: (ExampleResultBody -> Maybe String) -> (String -> IO a) -> String -> (String -> Bool) -> Int -> IO ()+failOrPendingCase extractMsg envGet envkey andExpect exp_sum_failures = do+ let expect [body] = case extractMsg body of+ Nothing -> False+ Just msg -> (envkey `isInfixOf` msg) && andExpect msg+ expect _ = False+ (conf, ref_results) <- configForTest+ got_summary <- hspecWithResult conf (before (envGet envkey) sampleSuccess)+ got <- fmap (map resultBody) $ readIORef ref_results+ got `shouldSatisfy` expect+ got_summary `shouldBe` Summary { summaryExamples = 1, summaryFailures = exp_sum_failures }+++failCase :: (String -> IO a) -> String -> (String -> Bool) -> IO ()+failCase envGet envkey andExpect = failOrPendingCase extract envGet envkey andExpect 1+ where+ extract (ExampleFailure mmsg) = mmsg+ extract _ = Nothing++pendingCase :: (String -> IO a) -> String -> (String -> Bool) -> IO ()+pendingCase envGet envkey andExpect = failOrPendingCase extract envGet envkey andExpect 0+ where+ extract (ExamplePending mmsg) = mmsg+ extract _ = Nothing++sampleSpec :: (Eq a, Show a) => a -> SpecWith a+sampleSpec expected = specify "dummy" $ \got -> got `shouldBe` expected++sampleSuccess :: SpecWith a+sampleSuccess = specify "dummy" $ const (True `shouldBe` True)++data ExampleResultBody = ExampleSuccess+ | ExampleFailure (Maybe String)+ | ExamplePending (Maybe String)+ deriving (Show,Eq,Ord)++type ExampleResult = (Path, ExampleResultBody)++resultBody :: ExampleResult -> ExampleResultBody+resultBody (_, b) = b++#if MIN_VERSION_hspec_core(2,4,0)+failureReasonToString :: Either e F.FailureReason -> Maybe String+failureReasonToString (Right (F.Reason s)) = Just s+failureReasonToString _ = Nothing+#else+failureReasonToString :: Either e String -> Maybe String+failureReasonToString (Right s) = Just s+failureReasonToString _ = Nothing+#endif++formatterForTest :: IO (Formatter, IORef [ExampleResult])+formatterForTest = do+ ref_results <- newIORef []+ let putRet r = liftIO $ modifyIORef ref_results (\rs -> rs ++ [r])+#if MIN_VERSION_hspec_core(2,5,0)+ f = F.silent { F.exampleSucceeded = \p _ -> putRet (p, ExampleSuccess),+ F.exampleFailed = \p _ fr -> putRet (p, ExampleFailure (failureReasonToString $ Right fr)),+ F.examplePending = \p _ ms -> putRet (p, ExamplePending ms)+ }+#else+ f = F.silent { F.exampleSucceeded = \p -> putRet (p, ExampleSuccess),+ F.exampleFailed = \p efr -> putRet (p, ExampleFailure (failureReasonToString efr)),+ F.examplePending = \p ms -> putRet (p, ExamplePending ms)+ }+#endif+ return (f, ref_results)++configForTest :: IO (Config, IORef [ExampleResult])+configForTest = do+ (fmt, ref_results) <- formatterForTest+ let conf = defaultConfig { -- configIgnoreConfigFile = True, -- not in hspec-core-2.3.2+ configDryRun = False,+ configFormatter = Just fmt+ }+ return (conf, ref_results)