packages feed

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 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)