1

TLDR: Is it possible to use module re-exports to avoid having "expose" all testable modules?

I've used something similar to the Chris Done template for my Haskell project. My ventureforth.cabal file has the following sections:

library
  hs-source-dirs:      src
  exposed-modules:     VForth,
                       VForth.Location
  build-depends:       base >= 4.7 && < 5
  ghc-options:         -Wall -Werror
  default-language:    Haskell2010

executable ventureforth
  hs-source-dirs:      app
  main-is:             Main.hs
  build-depends:       base >= 4.7 && < 5,
                       ventureforth -any
  ghc-options:         -Wall -Werror -threaded -rtsopts -with-rtsopts=-N
  default-language:    Haskell2010

test-suite ventureforth-test
  type:                exitcode-stdio-1.0
  hs-source-dirs:      test
  main-is:             Spec.hs
  build-depends:       base >= 4.7 && < 5,
                       ventureforth -any,
                       doctest >= 0.9 && < 0.11,
                       hspec -any
  ghc-options:         -Wall -Werror -threaded -rtsopts -with-rtsopts=-N
  default-language:    Haskell2010

My code is laid out as

ventureforth/
 |
 +- ventureforth.cabal
 +- app/
 |   |
 |   +- Main.hs
 |
 +- src/
 |   |
 |   +- VForth.hs
 |   +- VForth/
 |       |
 |       +- Location.hs
 |
 +- test/
 |   |
 |   +- Spec.hs
 |   +- VForth
 |       |
 |       +- LocationSpec.hs

I've set up VForth.hs to re-export VForth.Location

module VForth (
    module VForth.Location
) where

import VForth.Location

And in the VForth.LocationSpec unit-test I need only import VForth to test the Location type.

However unless I add add VForth.Location to the list of "exposed modules" I encounter linker errors when trying to run cabal test.

I had thought exposing a single module, VForth, which re-exported all other modules, would have sufficed. Am I really stuck in the situation of having to list every single source file in cabal?

Feenaboccles
  • 412
  • 3
  • 12
  • 1
    If you don't want to expose a module, you still need to include it in the `other-modules` section. Besides that, yes, you are stuck listing every module in the cabal file. Relevant line from the user guide: ["Every module in the package must be listed in one of other-modules, exposed-modules or main-is fields."](https://downloads.haskell.org/~ghc/7.0.4/docs/html/Cabal/authors.html#buildinfo) – user2407038 Nov 10 '15 at 00:07
  • That seems really clunky. Am I right in thinking that my project layout follows Haskell best practice? Will I really just be listing every source file in Cabal. – Feenaboccles Nov 10 '15 at 15:29

1 Answers1

2

So in the end this is unavoidable. However, on reflection, it is also good behaviour. Since I'm creating a library, and the Haskell language can't control module visibility, I have to specify which modules are to be exported in Cabal.

In order to function as tests, my tests should not have any additional access to an application that my library client does. Therefore it's acceptable that my tests can't access any modules that are also inaccessible to clients of my library.

In practice however, there are some internal elements I will want to test which are not exposed. Therefore, I've split my test-suite in two, one internal, and the other external, which test these different aspects.

test-suite ventureforth-test-external
  type:                exitcode-stdio-1.0
  hs-source-dirs:      test
  main-is:             Spec.hs
  build-depends:       base >= 4.7 && < 5,
                       ventureforth -any,
                       hspec -any
  ghc-options:         -Wall -Werror -threaded -rtsopts -with-rtsopts=-N
  default-language:    Haskell2010

test-suite ventureforth-test-internal
  type:                exitcode-stdio-1.0
  hs-source-dirs:      test,src
  main-is:             Spec.hs
  build-depends:       base >= 4.7 && < 5,
                       hspec -any
  ghc-options:         -Wall -Werror -threaded -rtsopts -with-rtsopts=-N
  default-language:    Haskell2010

The internal test has access to the source-code directly, and so doesn't import the library.

Lastly as a second source of "internal" testing, I'm also using doctests, as described in the Haskeleton project setup guide

Finally, for anyone else structuring a Haskell project, note that you can use the Haskell Init tool to create a skeleton project called my-project-name in a directory of the same name with all the testing bits and bobs set up and ready to go

hi my-project-name \
    --repository git://github.com/tfausak/haskeleton.git \
    --package-name my-project-name \
    --module-name MyProjectName \
    --author Bryan Feeney \
    --email bryan.feeney@mailserver.com

The repository flag gives a path to a template project. The Haskeleton template features unit-tests, documentation tests and benchmarks. Other project tempaltes, such as web-apps, can be found on the HI templates page. To install the Haskell Init tool, just type

cabal install hi
Feenaboccles
  • 412
  • 3
  • 12