My Main.hs file takes in commandline arguments:
module Main (toLowerStr, Result(..), grade, main) where
...
grade :: [String] -> String -> String -> (Bool, Result, [Highlight])
grade dictWords correctAnswer studentAnswer =
...
...
main :: IO ()
main = do
args <- getArgs
dict <- readFile $ args !! 0
...
Which works fine, except when I try to test. My testing file is
module Testing where
import Main
import Test.Hspec
main :: IO ()
main = do
dict <- readFile "dict.txt"
let dictWords = map toLowerStr $ lines dict
hspec $ do
describe "Validate passing answers" $ do
it "Match should be verified exactly" $ do
grade dictWords "house" "house"
`shouldBe` (True, Perfect, [])
Yet when I run cabal test it still gives me
Preprocessing test suite 'tests' for grade-0.1.0.0...
[2 of 2] Compiling Testing ( src/HSpecTests.hs, dist/build/tests/tests-tmp/Testing.o )
Linking dist/build/tests/tests ...
Running 1 test suites...
Test suite tests: RUNNING...
tests: Prelude.(!!): index too large
Test suite tests: FAIL
I'm pretty sure it's failing because of the calls to args in Main.main, because the executable itself works fine, and I don't see !! being used anywhere else.
How do I get tests to run?
EDIT: Used pattern matching in Main.hs:
main :: IO ()
main = do
[filename, correctString, studentString] <- getArgs
...
and the error is now
[1 of 2] Compiling Main ( src/Main.hs, dist/build/tests/tests-tmp/Main.o )
Linking dist/build/tests/tests ...
Running 1 test suites...
Test suite tests: RUNNING...
tests: user error (Pattern match failure in do expression at src/Main.hs:141:9-48)
Test suite tests: FAIL
EDIT 2: My entire grade.cabal file
-- Initial grade.cabal generated by cabal init. For further documentation,
-- see http://haskell.org/cabal/users-guide/
name: grade
version: 0.1.0.0
-- synopsis:
-- description:
license-file: LICENSE
author: Amos Ng <amosng#cmu.edu>
maintainer: Amos Ng <amosng#cmu.edu>
-- copyright:
category: Language
build-type: Simple
cabal-version: >=1.8
executable grade
main-is: Main.hs
-- other-modules:
build-depends: base, split ==0.2.*
hs-source-dirs: src
test-suite tests
ghc-options: -Wall
type: exitcode-stdio-1.0
main-is: HSpecTests.hs
other-modules: Main
build-depends: base, split ==0.2.*, hspec ==1.11.*
hs-source-dirs: src
GHC will always use as the entry point the function named main out of the module named Main. Since HSpecTests.hs is the Testing module, not the Main module, its main is completely ignored, instead favoring the main from the Main module. You should break your logic out of the Main module, leaving the Main module as a command-line stub. Then you’ll want to exclude that from the test build, and change module Testing to module Main in HSpecTests.hs.
Related
Background
Using stack, and its preset file Spec.hs, as far as I know you need to import the following test framework modules in order to execute a proper test:
import qualified Test.Framework as TF
import qualified Test.Framework.Providers.HUnit as FHU
import qualified Test.Framework.Providers.QuickCheck2 as QC2
import qualified Test.HUnit as HU
import qualified Test.QuickCheck as QC
Hence, you also need to add the add dependecies into the package.yaml file, as follows:
tests:
XYZ-test:
main: Spec.hs
source-dirs: test
ghc-options:
- -threaded
- -rtsopts
- -with-rtsopts=-N
dependencies:
- Test4
- test-framework
- test-framework-hunit
- test-framework-quickcheck2
- HUnit
- QuickCheck
If you import the subject to test (calling it MyModule) and implement test cases in Spec.hs for that module then you cannot test the functions that are internally used in the module (MyModule).
To test the internal functions you could implement the tests inside the module (MyModule) and export the tests.
module MyModule
(
...
testCases, -- exported test cases
-- fun1 -- internal function not exported
) where
...
import qualified Test.Framework as TF
import qualified Test.Framework.Providers.HUnit as FHU
import qualified Test.HUnit as HU
fun1 :: [Bool] -> Integer -- internal function not exported
fun1 ...
testCases =
(FHU.testCase "MyModule.fun1 #1" ((fun1 []) HU.#?= 0)) :
(FHU.testCase "MyModule.fun1 #2" ((fun1 [True]) HU.#?= 0)) :
(FHU.testCase "MyModule.fun1 #2" ((fun1 [True, True]) HU.#?= 2)) :
[]
But then you also need to import the test framework (at least, Test.Framework, Test.Framework.Providers.HUnit, and Test.HUnit) and need to add additional dependencies also to the library of (MyModule). Hence, package.yaml would look like this:
...
dependencies:
- ...
- test-framework
- test-framework-hunit
- HUnit
library:
source-dirs: src
...
Question
Is there a more lean aproach to export the unit test of module MyModule?
Add a data type to wrap the bindings for each test case. Ideally, in a module (e.g. TestCaseWrap) to reuse that for other modules with internal functions that are to be tested.
{-| wraps a test case
* prefix: tcw, TCW
* for an assertion
* to hold/evaluate the test case name
* to hold/evaluate the actual value of test case
* to hold/evaluate the expected value of test case
-}
data TestCaseWrap a =
TestAssertion {
-- | name of the test case, needed to reference and find the test case if failed
rsName :: String,
-- | function and actual value evaluated by test case, respectively
rxActual :: a,
-- | expected value evaluated by test case, respectively
rxExpected :: a }
NOTE: The data structure TestCaseWrap supports assertions and may be extended for proballistic quick tests.
In module MyModule import TestCaseWrap to define the data type TestCaseWrap. Fill an array with all the test cases (e.g. testCasesWrap).
module MyModule
(
...
lTestCasesWrap
) where
import qualified TestCaseWrap as TCW
...
fun1 :: [Bool] -> Integer
fun1 ...
testCasesWrap :: [TCW.TestCaseWrap Integer]
testCasesWrap =
(TCW.TestAssertion "MyModule.fun1 #1" (fun1 []) 0) :
(TCW.TestAssertion "MyModule.fun1 #2" (fun1 [True]) 0) :
(TCW.TestAssertion "MyModule.fun1 #3" (fun1 [True, True]) 2) :
[]
Implement a function to convert the wrapped test information into a Test. Again, ideally, in a module (e.g. TestCaseUnwrap).
module TestCaseUnwrap
(
testCaseFromTestCaseWrap,
testCaseFromTestCaseWrapList
) where
import qualified TestCaseWrap as TCW
import qualified Test.Framework as TF
import qualified Test.Framework.Providers.HUnit as FHU
import qualified Test.HUnit as HU
testCaseFromTestCaseWrap :: (Eq a, Show a) => (TCW.TestCaseWrap a) -> TF.Test
testCaseFromTestCaseWrap (TCW.TestAssertion sName xActual xExpected) = FHU.testCase sName (xActual HU.#?= xExpected)
testCaseFromTestCaseWrapList :: (Eq a, Show a) => [TCW.TestCaseWrap a] -> [TF.Test]
testCaseFromTestCaseWrapList ltcwA = fmap testCaseFromTestCaseWrap ltcwA
Implement Spec.hs like this:
import qualified Test.Framework as TF
import qualified Test.Framework.Providers.HUnit as FHU
import qualified Test.Framework.Providers.QuickCheck2 as QC2
import qualified Test.HUnit as HU
import qualified Test.QuickCheck as QC
import qualified MyModule
import qualified TestCaseUnwrap as TCU
main :: IO ()
main =
TF.defaultMainWithOpts
(
(TCU.testCaseFromTestCaseWrapList MyModule.testCasesWrap) ++
[
... -- other tests
]
)
mempty
... -- other tests
... and it will execute like this:
XYZ> test (suite: XYZ-test)
MyModule.fun1 #1: [OK]
MyModule.fun1 #2: [OK]
...
... : [OK]
... : [OK]
... : [OK]
Properties Test Cases Total
Passed 1 71 72
Failed 0 0 0
Total 1 71 72
XYZ> Test suite XYZ-test passed
Completed 2 action(s).
I've noticed some really strange behaviour:
I'm trying to get the haskell z3 bindings to work on windows. But if I insert code from the library into the Main module putStrLn stops working. How can that be?
The Setup
I used the precompiled x64 binary of the z3 solver (z3.4.8.4) for windows (and put it in C:\mylibs\z3-4.8.4_x64).
Then I created a stack project haskellZ3Test
The relevant parts from package.yaml:
dependencies:
- base >= 4.7 && < 5
- z3 <= 408.0
The relevant parts from stack.yaml:
resolver: lts-13.28
packages:
- .
- location:
git: https://github.com/IagoAbal/haskell-z3.git
commit: b10e09b8a809fb5bbbb1ef86aeb62109ece99cae
extra-dep: true
extra-include-dirs:
- "C:\\mylibs\\z3-4.8.4_x64\\include"
extra-lib-dirs:
- "C:\\mylibs\\z3-4.8.4_x64\\bin"
The Problem
Here is Main version 1:
module Main where
import Z3.Monad
main :: IO ()
main = putStrLn "hello world"
If I stack build and then stack exec haskellZ3Test-exe I get hello world on the command line as expected.
However - here is Main version 2:
module Main where
import Z3.Monad
ast :: Z3 AST
ast = do
a <- mkFreshBoolVar "A"
b <- mkFreshBoolVar "B"
mkImplies a b
main :: IO ()
main = putStrLn "hello world"
If I stack build and then stack exec haskellZ3Test-exe now I get nothing ...
Honestly I'm pretty puzzled - compiling and linking seems to work fine.
Any help is highly appreciated. Many thanks in advance.
I have two haskell files named SimpleJSON.hs and another is Main.hs
--File: SimpleJSON.hs
module SimpleJSON
(
JValue (..)
,getString
,getInt
,getDouble
,getBool
,getObject
,getArray
,isNull
) where
data JValue = JString String
| JNumber Double
| JBool Bool
| JNull
| JObject [(String, JValue)]
| JArray [JValue]
deriving (Eq, Ord, Show)
And
--File: Main.hs
module Main () where
import SimpleJSON
main = print (JObject [("foo", JNumber 1), ("bar", JBool False)])
So while compiling
I am doing
ghc -c SimpleJSON.hs
and
ghc simple Main.hs SimpleJSON.o
Then I am getting error as
Main.hs:1:1: error:
The IO action ‘main’ is not exported by module ‘Main’
|
1 | module Main () where
| ^
How to resolve this compilation error?
Should be
module Main where
or
module Main (main) where
This answer for specifically for reader of Real World Haskell. It seems that the book uses an older version of Haskell where main need not be explicitly mentioned in the list of exports. In the latest version, this is no longer the case.
There are two aspects that need to be mentioned:
you need to write module Main (main) where so that main is exported
you do not need to make an object file of SimpleJSON.hs and mention it while compiling Main.hs i.e simply running ghc -o simple Main.hs will suffice
As a general note, a couple of reviews on the Amazon page for the book mention that some aspects covered in the book are out of date. So, if you are following this book, make it a point to use another source to catch up on the differences.
I've found that using
me$ stack install --resolver lts-11.0 on the snippet
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
module Main where
import ClassyPrelude
import Network.HTTP.Conduit (host)
hostnameFromUrl :: MonadThrow m => Text -> m Text
hostnameFromUrl url = do
return $ decodeUtf8 $ host "helloworld.com"
fails with message
/Users/me/.../temp/Main.hs:8:20: error:
Not in scope: type constructor or class ‘MonadThrow’
|
8 | hostnameFromUrl :: MonadThrow m => Text -> m Text
| ^^^^^^^^^^
when any lts >= 11.0 is used. No error is raised for lts <= 10.10.
This seems strange, as the relevant package (exceptions-0.8.3) does not change between these two LTSs.
I have stack.yaml file
resolver: lts-11.0
packages:
- '.'
flags: {}
extra-package-dbs: []
and temp.cabal file
name: temp
version: 0.0
build-type: Simple
-- extra-source-files:
cabal-version: >=1.10
library
hs-source-dirs: .
exposed-modules: Main
ghc-options: -Wall
build-depends: classy-prelude >= 1.0.0.2
, http-conduit >= 2.1
default-language: Haskell2010
Pardon the specific setup; I've tried to make this as easily replicable as possible.
Any insight would be greatly appreciated! Thank you.
It's no longer exported from ClassyPrelude, you'd need to import it from Control.Monad.Catch.
I'm switching (or trying to) from the brilliant tup to haskell shake as my build system..
Only I can't figure out how to get shake to rebuild files on changes.
I could of course use inotify or a wrapper like filewatcher or even watchman.
Since I'm using shake though, I was wondering how to integrate with twitch which shares the do syntax, but otherwise doesn't provide much in way of documentation..
The ultimate goal is to use pandoc for multi format documents.
The only reason tup was inadequate was because it doesn't support targets.
First of all, you should to write your own shake build rules. Then, when some source file will be changed, you should to run your build rules to produce your targets.
Like this:
main = defaultMain $ do
"src/*.md" |> const build
build = shakeArgs shake{shakeFiles="out"} $ do
want ["out/foo.html", "out/foo.pdf"]
"out/*.html" %> \out -> do
let src = "src" </> dropDirectory1 out -<.> "md"
cmd_ "pandoc -o" [out] src
"out/*.pdf" %> \out -> do
let src = "src" </> dropDirectory1 out -<.> "md"
cmd_ "pandoc -o" [out] src
When a markdown file in src directory will be changed, then out/foo.html and out/foo.pdf will be updated.
If you want to optimize work of shake then you can do like this:
main = defaultMain $ do
"src/*.md" |> build . dependentTargets
build targets = shakeArgs shake{shakeFiles="out"} $ do
want targets
...
dependentTargets src
| "*.md" ?== src = ["out/foo.html", "out/foo.pdf"]
| otherwise = []
The package twitch recommends to use extension OverloadedStrings for compile code like this:
"src/*.md" |> ...
But this leads to ambiguous code in other parts of the program. For fix that, you can explicitly converting String to Dep like this:
import Data.String
fromString "src/*.md" |> ...
You can improve this code by redefining the (|>) operator:
import Data.String
import Twitch hiding ((|>))
pattern |> callback = addModify callback $ fromString pattern
"src/*.md" |> ...
I use shake for building a web site and have wrapped it into twitch to rerun the shake build when some files change. The main call for the watching functions (it uses forkIO to watches in two directories, and each can run shake) is bracketed; it also starts the web server.
mainWatch :: SiteLayout -> Port -> Path Abs Dir -> IO ()
mainWatch layout bakedPort bakedPath = bracketIO
(do -- first
shake layout
watchDough <- forkIO (mainWatchDough layout) -- calls shake
watchTemplates <- forkIO (mainWatchThemes layout) -- calls shake
scotty bakedPort (site bakedPath)
return (watchDough,watchTemplates) )
(\(watchDough,watchTemplates) -> do -- last
putIOwords ["main2 end"]
killThread watchDough
killThread watchTemplates
return ()
)
(\watch -> do -- during
return ()
)
Hope this can be adapted to your case!