1

Let's say I have a src file like so:

{-# LANGUAGE CPP #-}
module Alphabet (
#ifdef TEST
  alphabet
#endif
) where
  alphabet :: [Char]
  alphabet = "abcdefghijklmnopqrstuvwxyz"

a .cabal file like so:

name:                Alphabet
version:             0.1.0.0
library
  build-depends:       base >=4.8 && <4.9, containers >=0.5 && <0.6, split >=0.2 && <0.3
  hs-source-dirs:      src
  Exposed-modules:     Alphabet
  default-language:    Haskell2010
test-suite alphabet-test
  ghc-options:         -Wall -Werror
  cpp-options:         -DTEST
  default-extensions:  OverloadedStrings
  type:                exitcode-stdio-1.0
  main-is:             Spec.hs
  hs-source-dirs:      tests
  build-depends:       Alphabet, base >= 4.8 && < 4.9, containers >= 0.5 && <0.6, split >= 0.2 && < 0.3, hspec, QuickCheck
  default-language:    Haskell2010

A master test file like so:

 {-# OPTIONS_GHC -F -pgmF hspec-discover #-}

and a test file like:

module AphabetSpec (spec) where

  import Test.Hspec
  import Alphabet (alphabet)

  spec :: Spec
  spec = do
    describe "Alphabet.alphabet" $ do
      it "returns the alphabet" $ do
        alphabet `shouldBe` "abcdefghijklmnopqrstuvwxyz"

now running `cabal test`:
cabal test
Preprocessing library Alphabet-0.1.0.0...
In-place registering Alphabet-0.1.0.0...
Preprocessing test suite 'alphabet-test' for Alphabet-0.1.0.0...
[1 of 1] Compiling Main             ( tests/AlphabetSpec.hs, dist/build/alphabet-test/alphabet-test-tmp/AlphabetSpec.o )

tests/AlphabetSpec.hs:4:27:
Module ‘Alphabet’ does not export ‘alphabet’

Why is my CPP not working as expected? How can I fix it?

Abraham P
  • 15,029
  • 13
  • 58
  • 126
  • 1
    The suggested way seems to be to use flags (like [this question asker](http://stackoverflow.com/questions/31821952/flags-in-cabal-files)) – Nadir Sampaoli Jan 16 '16 at 11:22

2 Answers2

3

Why is my CPP not working as expected?

Two-step building. Since your tests depend on the library, it gets build first. The library doesn't have any CPP options set, therefore alphabet doesn't get exported.

When the tests get built, your library is already compiled, and alphabet doesn't get exported. It's a separation of concerns.

How can I fix it?

There are several tricks to work with "hidden" (e.g. non-exported) functions. For one, you can put them all into a .Internal module. That way, users that want to use the hidden bits can do so rather easily, but the casual user doesn't have too many tools at hand.

Another way to handle this is to drop the dependency in the test and instead add the src directory to the tests:

 hs-source-dirs:      tests, src

However, this also means that you have to rebuild the whole library for tests, but it will enable using CPP.

A third option is to not test alphabet, but instead the observable behaviour of exported functions that depend on it. So instead of testing alphabet, you would test filterAlpha:

filterAlpha :: String -> String
filterAlpha = filter (`elem` alphabet)

You have to test filterAlpha anyway. If there are many functions that use alphabet, it is likely that you will have some test that will notice a regression if you accidentally change it.

Zeta
  • 103,620
  • 13
  • 194
  • 236
  • You're a rockstar.... I swear you've done more for my Haskell in the last 24 hours than hours of blogs/tinkering did in over the past few weeks :) Thank you – Abraham P Jan 16 '16 at 23:43
0

The problem was a simply syntax error. #ifdef is wrong, #ifndef is right

Abraham P
  • 15,029
  • 13
  • 58
  • 126
  • 1
    Aren't you doing the opposite of what you wanted, though? `#ifndef` means "if it's not defined" so you're making the function `alphabet` always public, except when `TEST` is defined. – Nadir Sampaoli Jan 16 '16 at 11:20