packages feed

should-not-typecheck (empty) → 0.1.0.0

raw patch · 7 files changed

+207/−0 lines, 7 filesdep +HUnitdep +basedep +hspecsetup-changed

Dependencies added: HUnit, base, hspec, hspec-expectations, should-not-typecheck

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2015 Callum Rogers++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 Callum Rogers 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,61 @@+# should-not-typecheck [![Build Status](https://travis-ci.org/CRogers/should-not-typecheck.svg?branch=master)](https://travis-ci.org/CRogers/should-not-typecheck)++`should-not-typecheck` is a Haskell library which allows you to assert that an expression does not typecheck in your unit tests. It provides one function, `shouldNotTypecheck :: a -> Assertion`, which takes an expression and will fail the test if it typechecks. `shouldNotTypecheck` returns an HUnit `Assertion` (so it can be used with both `HUnit` and `hspec`).++## Example (hspec)++The secret sauce is the [Deferred Type Errors GHC extension](https://downloads.haskell.org/~ghc/7.10.1/docs/html/users_guide/defer-type-errors.html). This allows you to write a non-typechecking expression which will throw an exception at run time (rather than erroring out at compile time). `shouldNotTypecheck` tries to catch that exception and fails the test if no deferred type error is caught.++```haskell+{-# OPTIONS_GHC -fdefer-type-errors #-} -- Very important!++module Main where++import Test.Hspec (hspec, describe, it)+import Test.ShouldNotTypecheck (shouldNotTypecheck)++main :: IO ()+main = hspec $ do+  describe "Type Tests" $ do+    it "should not allow an Int to be a String" $+      shouldNotTypecheck (4 :: String)+```++It can be used similarly with HUnit.++## Motivation++Sometimes you want to ensure that it is impossible to type a particular expression. For example, imagine if we were making a typesafe Abstract Syntax Tree of mathematical expressions:++```haskell+{-# LANGUAGE GADTs #-}++data Expr t where+  IntVal :: Int -> Expr Int+  BoolVal :: Bool -> Expr Bool+  Add :: Expr Int -> Expr Int -> Expr Int+  -- ...+```++We might want to make sure that `Add (BoolVal True) (IntVal 4)` is not well typed. However, we can't even compile code like this to put in a unit test! This is where `should-not-typecheck` steps in.++## Limitations++Unfortunately, we can only turn on deferred type errors for the entire test file rather than just specific expressions. This means that any type error will compile but fail at runtime. For example:++```haskell+{-# OPTIONS_GHC -fdefer-type-errors #-}++-- ...++main :: IO ()+main = hspec $ do+  decsribe 4 $ do -- Oops!+   -- ...+```++Will create a warning at compile time but not an error. All of the ill-typed expressions we are testing will also produce warnings and it will hard to immediately see which ones matter. The upside is that the test-suite will still fail if there are errors.++### Workaround++You can separate out the ill-typed expressions we are testing and test boilerplate into separate files and only turn on deferred type errors for the expressions. This means that type errors in test code will still be found at compile time. The downside is your tests may now be harder to read.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ should-not-typecheck.cabal view
@@ -0,0 +1,39 @@+name:                should-not-typecheck+version:             0.1.0.0+synopsis:            A HUnit/hspec assertion library to verify that an expression does not typecheck+description:+  For examples and an introduction to the library please take a look at the <https://github.com/CRogers/should-not-typecheck#should-not-typecheck- README> on github.+homepage:            http://github.com/CRogers/should-not-typecheck+license:             BSD3+license-file:        LICENSE+author:              Callum Rogers+maintainer:          message.me.on@github.com+-- copyright:+category:            Testing+build-type:          Simple+extra-source-files:+    README.md+  , stack.yaml+cabal-version:       >=1.10++library+  hs-source-dirs:      src+  exposed-modules:     Test.ShouldNotTypecheck+  build-depends:       base >= 4.7 && < 5+                     , HUnit >= 1.2+  default-language:    Haskell2010++test-suite tests+  type:                exitcode-stdio-1.0+  hs-source-dirs:      test+  main-is:             ShouldNotTypecheckSpec.hs+  build-depends:       base+                     , should-not-typecheck+                     , HUnit >= 1.2+                     , hspec >= 2.1+                     , hspec-expectations >= 0.6+  default-language:    Haskell2010++source-repository head+  type: git+  location: git://github.com/CRogers/should-not-typecheck.git
+ src/Test/ShouldNotTypecheck.hs view
@@ -0,0 +1,21 @@+module Test.ShouldNotTypecheck (shouldNotTypecheck) where++import Control.Exception (evaluate, try, throw, ErrorCall(..))+import Data.List (isSuffixOf)+import Test.HUnit.Lang (Assertion, assertFailure)++{-|+  Takes one argument, an expression that should not typecheck.+  It will fail the test if the expression does typecheck.+  Requires Deferred Type Errors to be enabled for the file it is called in.+  See the <https://github.com/CRogers/should-not-typecheck#should-not-typecheck- README>+  for examples and more infomation.+-}+shouldNotTypecheck :: a -> Assertion+shouldNotTypecheck a = do+  result <- try (evaluate a)+  case result of+    Right _ -> assertFailure "Expected expression to not compile but it did compile"+    Left (ErrorCall msg) -> case isSuffixOf "(deferred type error)" msg of+      True -> return ()+      False -> throw (ErrorCall msg)
+ stack.yaml view
@@ -0,0 +1,5 @@+flags: {}+packages:+- '.'+extra-deps: []+resolver: lts-2.14
+ test/ShouldNotTypecheckSpec.hs view
@@ -0,0 +1,49 @@+{-# LANGUAGE PatternSynonyms #-}+{-# OPTIONS_GHC -fdefer-type-errors #-}++module Main where++import Control.Exception+import Test.Hspec+import Test.Hspec.Expectations (expectationFailure)+import Test.HUnit.Lang (performTestCase)+import Test.ShouldNotTypecheck++pattern TestSuccess = Nothing+pattern TestFailure msg = Just (True, msg)+pattern TestError msg = Just (False, msg)++shouldFailAssertion :: IO () -> IO ()+shouldFailAssertion value = do+  result <- performTestCase value+  case result of+    TestSuccess -> expectationFailure "Did not throw an assertion error"+    TestFailure _ -> return ()+    TestError msg -> expectationFailure $ "Raised an error " ++ msg++shouldThrowException :: Exception e => e -> IO () -> IO ()+shouldThrowException exception value = do+  result <- performTestCase value+  case result of+    TestSuccess -> expectationFailure "Did not throw exception: assertion succeeded"+    TestFailure msg -> expectationFailure "Did not throw exception: assertion failed"+    TestError msg -> case msg == show exception of+      True -> return ()+      False -> expectationFailure "Incorrect exception propagated"++main :: IO ()+main = hspec $ do+  describe "shouldNotCompile" $ do+    it "should not throw an assertion error when an expression is ill typed" $ do+      shouldNotTypecheck ("foo" :: Int)++    it "should throw an assertion error when an expression is well typed" $ do+      shouldFailAssertion (shouldNotTypecheck ("foo" :: String))++    it "should throw an actual exception and not fail the assertion if the expression contains an non-HUnitFailure exception" $ do+      let exception = NoMethodError "lol"+      shouldThrowException exception (shouldNotTypecheck (throw exception))++    it "should propagate an actual exception and not fail the assertion if the expression contains a non-deferred ErrorCall exception" $ do+      let exception = ErrorCall "yay"+      shouldThrowException exception (shouldNotTypecheck (throw exception))