Idiomatic way to run test-suites in stack - haskell

Haskell beginner here.
I am struggling to find a nice way to run my test-suites that I have defined in my .cabal file. Given that you add test-suite sections in the .cabal file I would expect that you can run all of them with a single command like stack runtests.
The best answer I found is this one:
Haskell Stack Ghci test-suite suggesting that you have to run
stack ghci --test module:test:libtests
However, there are two things that irritate me and I think that there must be a better way doing it.
It is cumbersome to call stack ghci --test module:test:libtests explicitly. I don't want to do that if I have more test-suites when projects get larger.
Even worse, I end up in an interactive session and have to call main myself. This does not scale.
Isn't there a better way to run your test-suites for a project in stack? Of course I could do some shell scripting, but hey stack should know how to run my tests, I specified everything in the .cabal file.
I tried stack runghc --test but that doesn't help.
Project setup:
.
├── app
│   └── Main.hs
├── LICENSE
├── README.md
├── Setup.hs
├── src
│   ├── Lib.hs
│   └── WordNumber.hs
├── stack.yaml
├── test
│   └── Spec.hs
└── WordNumber.cabal
WordNumber.cabal
name: WordNumber
version: 0.1.0.0
-- synopsis:
-- description:
homepage: https://github.com/githubuser/WordNumber#readme
license: BSD3
license-file: LICENSE
author: Author name here
maintainer: example#example.com
copyright: 2017 Author name here
category: Web
build-type: Simple
extra-source-files: README.md
cabal-version: >=1.10
library
hs-source-dirs: src
exposed-modules: Lib, WordNumber
build-depends: base >= 4.7 && < 5
default-language: Haskell2010
executable wordnumber
hs-source-dirs: app
main-is: Main.hs
ghc-options: -threaded -rtsopts -with-rtsopts=-N
build-depends: base
, WordNumber
default-language: Haskell2010
test-suite wordnumber-test
type: exitcode-stdio-1.0
hs-source-dirs: test
main-is: Spec.hs
build-depends: base
, WordNumber
, hspec
ghc-options: -threaded -rtsopts -with-rtsopts=-N
default-language: Haskell2010
Update:
Actually I feel a bit stupid for not finding the answer myself. It is either stack test or stack build --test and also documented.
However, for people following the HaskellBook it might not be that obvious. For some reason all tests in the testing chapter are executed in the cumbersome way and running tests with stack test is never mentioned.

stack test runs tests located in a .cabal file. It runs stack build if required, so you don't need to build manually before testing.
See further: https://docs.haskellstack.org/en/stable/GUIDE/#stack-test

Related

stack build works as expected but stack runghc can't find local modules

So I have a Haskell project (managed using stack) structured like this:
.
├── Main.hs
├── Other1.hs
├── subfolder
   └── Other2.hs
where the Main module imports both Other1 and Other2, as simply as
import Other1
import Other2
My .cabal file says:
name: (...)
executable Main
hs-source-dirs:
.,
subfolder
main-is: Main.hs
other-modules:
Other1
Other2
Now, if I run stack build everything works great, all modules are compiled and it looks like nothing can go wrong. But then if I try to execute my program with stack runghc Main, module Other2 (the one in the subfolder) is not found.
Why is that the case? How can I execute my code?

How can I have GHCi reload include changes in the local dependent library?

Full reproducible project here: https://github.com/chrissound/215
I have the following simple cabal file which defines:
a library (source under src-lib)
executable (source under src) in the same project (which depends on the above local library)
cabal-version: 1.12
name: HaskellNixCabalStarter
version: 0.1.0.0
author: HaskellNixCabalStarter
maintainer: HaskellNixCabalStarter
license: MIT
build-type: Simple
library
exposed-modules:
Hello
other-modules:
Paths_HaskellNixCabalStarter
hs-source-dirs:
src-lib
build-depends:
base >=4.12 && <4.13
default-language: Haskell2010
executable app
main-is: Main.hs
other-modules:
Paths_HaskellNixCabalStarter
hs-source-dirs:
src
build-depends:
HaskellNixCabalStarter
, base >=4.12 && <4.13
default-language: Haskell2010
I can open a GHCi repl with:
cabal v2-repl app
However, upon GHCi reloading (:r), it will only reload changes in the app executable, and disregard any changes in the library.
This seems like very limiting / incorrect behavior. How can I fix this / workaround this?
There is a workaround, you either
run cabal repl and then :load src/Main.hs, or
with cabal repl app you'd need to :load src/Main.hs src-lib/Hello.hs.
Now :reload also reloads changes from dependencies.
In the first case it's the :load that somehow also starts loading/following the dependencies. (Not sure why cabal repl app isn't doing exactly the same.)
On the second case you need to explicitly name the modules you want to follow. Also, you need to have the module in who's namespace you want to be in, first. So :load src/Main.hs ..others...
See this on reddit. It appears that cabal can only have one "unit" loaded, but loading other sources with :load seems to subvert that.
I don't think it can be done (yet?). Evidence:
jeff#jbb-dell:cabal-experim$ tree
.
├── cabal.project
├── P1
│   ├── app
│   │   ├── Lib.hs
│   │   └── Main.hs
│   └── P1.cabal
└── P2
├── P2.cabal
└── src
└── MyLib.hs
jeff#jbb-dell:cabal-experim$ cabal repl P1 P2
cabal: Cannot open a repl for multiple components at once. The targets 'P1'
and 'P2' refer to different components.
The reason for this limitation is that current versions of ghci do not support
loading multiple components as source. Load just one component and when you
make changes to a dependent component then quit and reload.

Could not find module ‘Test.HUnit’

I have a fresh installation of stack and ghci:
$ stack --version
Version 1.6.3, Git revision b27e629b8c4ce369e3b8273f04db193b060000db (5454 commits) x86_64 hpack-0.20.0
$ ghci --version
The Glorious Glasgow Haskell Compilation System, version 8.2.1
I make a new project:
$ stack new so-mve
Downloading template "new-template" to create project "so-mve" in so-mve/
... blah blah blah ...
Looking for .cabal or package.yaml files to use to init the project.
Using cabal packages:
- so-mve/
Selecting the best among 12 snapshots...
* Matches lts-10.3
Selected resolver: lts-10.3
Initialising configuration using resolver: lts-10.3
Total number of user packages considered: 1
Writing configuration to file: so-mve/stack.yaml
All done.
Looks pretty good:
$ tree so-mve
so-mve
├── ChangeLog.md
├── LICENSE
├── README.md
├── Setup.hs
├── app
│   └── Main.hs
├── package.yaml
├── so-mve.cabal
├── src
│   └── Lib.hs
├── stack.yaml
└── test
└── Spec.hs
It builds and runs:
$ cd so-mve
$ stack build
so-mve-0.1.0.0: build (lib + exe)
Preprocessing library for so-mve-0.1.0.0..
Building library for so-mve-0.1.0.0..
Preprocessing executable 'so-mve-exe' for so-mve-0.1.0.0..
Building executable 'so-mve-exe' for so-mve-0.1.0.0..
so-mve-0.1.0.0: copy/register
Installing library in /...blah-blah.../so-mve/.stack-work/install/x86_64-osx/lts-10.3/8.2.2/lib/x86_64-osx-ghc-8.2.2/so-mve-0.1.0.0-5kG2WnHWwo99IiYYGoxrcC
Installing executable so-mve-exe in /...blah-blah.../so-mve/.stack-work/install/x86_64-osx/lts-10.3/8.2.2/bin
Registering library for so-mve-0.1.0.0..
$ stack exec so-mve-exe
someFunc
Tests run:
$ stack test
blah blah blah
[2 of 2] Compiling Main ( test/Spec.hs, .stack-work/dist/x86_64-osx/Cabal-2.0.1.0/build/so-mve-test/so-mve-test-tmp/Main.o )
...blah-blah-blah...
Progress: 1/2Test suite not yet implemented
so-mve-0.1.0.0: Test suite so-mve-test passed
Completed 2 action(s).
I triple check that HUnit is installed
$ stack install HUnit
Populated index cache.
I add one line to test/Spec.hs
$ cat test/Spec.hs
import Test.HUnit
main :: IO ()
main = putStrLn "Test suite not yet implemented"
Doesn't work:
$ stack test
so-mve-0.1.0.0: unregistering (components added: test:so-mve-test)
so-mve-0.1.0.0: build (lib + exe + test)
Preprocessing library for so-mve-0.1.0.0..
Building library for so-mve-0.1.0.0..
Preprocessing executable 'so-mve-exe' for so-mve-0.1.0.0..
Building executable 'so-mve-exe' for so-mve-0.1.0.0..
Preprocessing test suite 'so-mve-test' for so-mve-0.1.0.0..
Building test suite 'so-mve-test' for so-mve-0.1.0.0..
[2 of 2] Compiling Main ( test/Spec.hs, .stack-work/dist/x86_64-osx/Cabal-2.0.1.0/build/so-mve-test/so-mve-test-tmp/Main.o )
/...blah-blah.../so-mve/test/Spec.hs:1:1: error:
Could not find module ‘Test.HUnit’
Use -v to see a list of the files searched for.
|
1 | import Test.HUnit
| ^^^^^^^^^^^^^^^^^
Progress: 1/2
-- While building custom Setup.hs for package so-mve-0.1.0.0 using:
/Users/XXXXXXXX/.stack/setup-exe-cache/x86_64-osx/Cabal-simple_mPHDZzAJ_2.0.1.0_ghc-8.2.2 --builddir=.stack-work/dist/x86_64-osx/Cabal-2.0.1.0 build lib:so-mve exe:so-mve-exe test:so-mve-test --ghc-options " -ddump-hi -ddump-to-file -fdiagnostics-color=always"
Process exited with code: ExitFailure 1
I don't have trouble importing other libraries, like Text.Read and Text.Printf.
I googled around a bunch, but didn't find an answer. Any ideas for me?
You just need to add HUnit to the dependencies for your test project. When using stack, you should edit the package.yaml file to specify dependencies. In particular, your test configuration should look something like:
tests:
so-mve-test:
main: Spec.hs
source-dirs: test
ghc-options:
- ...
dependencies:
- HUnit
This is documented in the latest Stack Guide, under the section Adding Dependencies.
You installed HUnit globally with stack, but that doesn't mean it is specified for your project.
Your cabal file for the project needs to specify a dependency on HUnit:
--so-mve.cabal
...
test-suite so-mve
type: exitcode-stdio-1.0
hs-source-dirs: test
main-is: Spec.hs
build-depends: base
, HUnit
...
Text.Read and Text.Printf are both included in base, so you don't need to specify an additional dependency.
As pointed out in the comments, since you're using a package.yaml (as opposed to a stack.yaml) configuration with the newer version of stack, you'll need to specify the dependency there instead of the .cabal file:
tests:
so-mve-test:
main: Spec.hs
source-dirs: test
ghc-options:
- -threaded
dependencies:
- HUnit

How am I meant to split code between src/Lib.hs and app/Main.hs in a new stack project?

I was following the stack guide and I got a new project setup (yay!).
It generated the following file layout:
.
├── app
│   ├── Main.hs
├── .gitignore
├── LICENSE
├── helloworld.cabal
├── Setup.hs
├── src
│   └── Lib.hs
├── stack.yaml
└── test
└── Spec.hs
According to the "Files in helloworld" section of the guide:
The app/Main.hs, src/Lib.hs, and test/Spec.hs files are all Haskell source files that compose the actual functionality of our project (we won't dwell on them here).
I really wish they had dwelled on that for a second, because I have no idea what the distinction between app/Main.hs and src/Lib.hs should be. Which code should I put where?
In what ways am I supposed to divide code between app/, src/, app/Main.hs and src/Lib.hs?
If I'm just writing an application or just writing a library, do I need both files/directories?
This separation of modules into folders can be any way you want. The naive idea is that you put almost all logic into the Lib folder. Main.hs then just
imports required parts from Lib,
reads command-line arguments, and
runs stuff.
You can rename app into executables and change the corresponding lines in .cabal file. Actually, you can come up with an arbitrary file hierarchy.
In our company project, we use another but also very popular approach. And our file hierarchy looks like this:
.
|-- bench
|-- src
|-- exec1
|-- Main.hs
|-- exec2
|-- Main.hs
|-- SuperCoolLibrary
|-- LibModule1.hs
|-- LibModule2.hs
|-- test
|-- Setup.hs
Other stack.yaml, .cabal, etc. files are not shown here.
Actually, if you are writing an application, you can just create one Main.hs file and put all logic inside the main function. You won't believe it but as a Haskell lecturer I saw such code from my students :(
Though I don't suggest you write code that way.
If you are writing a library then you don't need Main.hs files and the main function at all. You can look at a simple example like this library (it allows you to automatically generate command-line options from data types): optparse-generic
I hope I helped clearing up your confusion.
The main reason it's typically set up like this even for an application is for writing tests. Say you create a default stack project called foo, the test suite foo-test will depend on the foo library, as will the foo-exe. If you were to put all your functions into app/Main.hs, then those functions cannot be tested from the foo-test test suite.
If you're just playing around and don't care about having a test suite, you could base your stack project on the simple template:
$ stack new foo simple
If you'd like to set up testing, I like tasty. You'd modify your .cabal file something like this:
test-suite foo-test
type: exitcode-stdio-1.0
hs-source-dirs: test
main-is: Spec.hs
build-depends: base
, foo
, tasty
, tasty-hunit
, tasty-quickcheck
ghc-options: -threaded -rtsopts -with-rtsopts=-N
default-language: Haskell2010
Then take a look at the example.

Using HSpec with Stack

I have the following architecture :
backend
├── Chat.hs
├── Main.hs
└── Message.hs
test
├── backendSpec
│ └── MessageSpec.hs
└── Spec.hs
My .cabal file contains the following
test-suite spec
build-depends: base, hspec == 2.*,
snap >= 0.14.0.6,
containers,
aeson,
text,
transformers,
stm,
snap-core,
snap-server,
socket-io,
engine-io-snap,
snap-cors,
bytestring
hs-source-dirs: test
main-is: Spec.hs
Type: exitcode-stdio-1.0
but when I do
stack test
HSpec cannot find my test int MessageSpec.hs.
Finished in 0.0002 seconds
0 examples, 0 failures
Spec.hs is the correct input : {-# OPTIONS_GHC -F -pgmF hspec-discover #-}
and my MessageSpec module is exposing : module MessageSpec (main, spec).
Could you help me find a way to make my stack project doing all my tests.
Thank you,
Your path to your spec must follow the module name convention. backendSpec.MessageSpec is not a valid module name, since it starts with a lowercase letter.
Furthermore, the module name of your spec should only differ by the additional suffix Spec from your original module. Your modules in backendSpec wouldn't follow this:
module Message where ...
-- vs
module BackendSpec.MessageSpec where ...
So to fix this, make sure that all directories in your test directory start with an uppercase letter. But even better, make sure that the test directory has the same structure as your src directory, as this will result in better module names during your tests:
-- If file is test/BackendSpec/MessageSpec.hs
BackendSpec.Message:
<someDescription>
<some assertion>
<some assertion>
<some assertion>
vs
-- If file is test/MessageSpec.hs
Message:
<someDescription>
<some assertion>
<some assertion>
<some assertion>
(The relevant code for this behaviour can be found in hspec/Run.hs of hspec-discover)

Resources