packages feed

hpack-0.5.1: test/Hpack/ConfigSpec.hs

{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
module Hpack.ConfigSpec (
  spec

, package
, executable
, library
, section
) where

import           Helper

import           Data.Aeson.Types
import           Data.Aeson.QQ
import           Data.String.Interpolate

import           Hpack.Config

package :: Package
package = Package "foo" "0.0.0" Nothing Nothing Nothing Nothing Nothing Nothing [] [] [] Nothing Nothing [] [] Nothing Nothing [] []

executable :: String -> String -> Executable
executable name main_ = Executable name main_ []

library :: Library
library = Library [] []

section :: a -> Section a
section a = Section a [] [] [] [] []

spec :: Spec
spec = do
  describe "parseJSON" $ do
    context "when parsing a Dependency" $ do
      it "accepts simple dependencies" $ do
        parseEither parseJSON "hpack" `shouldBe` Right (Dependency "hpack" Nothing)

      it "accepts git dependencies" $ do
        let value = [aesonQQ|{
              name: "hpack",
              git: "https://github.com/sol/hpack",
              ref: "master"
            }|]
            git = GitRef "https://github.com/sol/hpack" "master"
        parseEither parseJSON value `shouldBe` Right (Dependency "hpack" (Just git))

      it "accepts github dependencies" $ do
        let value = [aesonQQ|{
              name: "hpack",
              github: "sol/hpack",
              ref: "master"
            }|]
            git = GitRef "https://github.com/sol/hpack" "master"
        parseEither parseJSON value `shouldBe` Right (Dependency "hpack" (Just git))

      context "when parsing fails" $ do
        it "returns an error message" $ do
          let value = Number 23
          parseEither parseJSON value `shouldBe` (Left "when expecting a String or an Object, encountered Number instead" :: Either String Dependency)

        context "when ref is missing" $ do
          it "produces accurate error messages" $ do
            let value = [aesonQQ|{
                  name: "hpack",
                  git: "sol/hpack",
                  ef: "master"
                }|]
            parseEither parseJSON value `shouldBe` (Left "key \"ref\" not present" :: Either String Dependency)

        context "when both git and github are missing" $ do
          it "produces accurate error messages" $ do
            let value = [aesonQQ|{
                  name: "hpack",
                  gi: "sol/hpack",
                  ref: "master"
                }|]
            parseEither parseJSON value `shouldBe` (Left "neither key \"git\" nor key \"github\" present" :: Either String Dependency)

  describe "readPackageConfig" $ around_ (inTempDirectoryNamed "foo") $ do
    it "warns on unknown fields" $ do
      writeFile "package.yaml" [i|
        bar: 23
        baz: 42
        |]
      fmap fst <$> readPackageConfig "package.yaml" `shouldReturn` Right [
          "Ignoring unknown field \"bar\" in package description"
        , "Ignoring unknown field \"baz\" in package description"
        ]

    it "accepts name" $ do
      writeFile "package.yaml" [i|
        name: bar
        |]
      Right (_, c) <- readPackageConfig "package.yaml"
      c `shouldBe` package {packageName = "bar"}

    it "accepts version" $ do
      writeFile "package.yaml" [i|
        version: 0.1.0
        |]
      Right (_, c) <- readPackageConfig "package.yaml"
      c `shouldBe` package {packageVersion = "0.1.0"}

    it "accepts synopsis" $ do
      writeFile "package.yaml" [i|
        synopsis: some synopsis
        |]
      Right (_, c) <- readPackageConfig "package.yaml"
      c `shouldBe` package {packageSynopsis = Just "some synopsis"}

    it "accepts description" $ do
      writeFile "package.yaml" [i|
        description: some description
        |]
      Right (_, c) <- readPackageConfig "package.yaml"
      c `shouldBe` package {packageDescription = Just "some description"}

    it "accepts category" $ do
      writeFile "package.yaml" [i|
        category: Data
        |]
      Right (_, c) <- readPackageConfig "package.yaml"
      c `shouldBe` package {packageCategory = Just "Data"}

    it "accepts author" $ do
      writeFile "package.yaml" [i|
        author: John Doe
        |]
      Right (_, c) <- readPackageConfig "package.yaml"
      c `shouldBe` package {packageAuthor = ["John Doe"]}

    it "accepts maintainer" $ do
      writeFile "package.yaml" [i|
        maintainer: John Doe <john.doe@example.com>
        |]
      Right (_, c) <- readPackageConfig "package.yaml"
      c `shouldBe` package {packageMaintainer = ["John Doe <john.doe@example.com>"]}

    it "accepts copyright" $ do
      writeFile "package.yaml" [i|
        copyright: (c) 2015 John Doe
        |]
      Right (_, c) <- readPackageConfig "package.yaml"
      c `shouldBe` package {packageCopyright = ["(c) 2015 John Doe"]}

    it "accepts stability" $ do
      writeFile "package.yaml" [i|
        stability: experimental
        |]
      Right (_, c) <- readPackageConfig "package.yaml"
      packageStability c `shouldBe` Just "experimental"

    it "accepts homepage URL" $ do
      writeFile "package.yaml" [i|
        github: hspec/hspec
        homepage: https://example.com/
        |]
      Right (_, c) <- readPackageConfig "package.yaml"
      packageHomepage c `shouldBe` Just "https://example.com/"

    it "infers homepage URL from github" $ do
      writeFile "package.yaml" [i|
        github: hspec/hspec
        |]
      Right (_, c) <- readPackageConfig "package.yaml"
      packageHomepage c `shouldBe` Just "https://github.com/hspec/hspec#readme"

    it "omits homepage URL if it is null" $ do
      writeFile "package.yaml" [i|
        github: hspec/hspec
        homepage: null
        |]
      Right (_, c) <- readPackageConfig "package.yaml"
      packageHomepage c `shouldBe` Nothing

    it "accepts bug-reports URL" $ do
      writeFile "package.yaml" [i|
        github: hspec/hspec
        bug-reports: https://example.com/issues
        |]
      Right (_, c) <- readPackageConfig "package.yaml"
      packageBugReports c `shouldBe` Just "https://example.com/issues"

    it "infers bug-reports URL from github" $ do
      writeFile "package.yaml" [i|
        github: hspec/hspec
        |]
      Right (_, c) <- readPackageConfig "package.yaml"
      packageBugReports c `shouldBe` Just "https://github.com/hspec/hspec/issues"

    it "omits bug-reports URL if it is null" $ do
      writeFile "package.yaml" [i|
        github: hspec/hspec
        bug-reports: null
        |]
      Right (_, c) <- readPackageConfig "package.yaml"
      packageBugReports c `shouldBe` Nothing

    it "accepts license" $ do
      writeFile "package.yaml" [i|
        license: MIT
        |]
      Right (_, c) <- readPackageConfig "package.yaml"
      c `shouldBe` package {packageLicense = Just "MIT"}

    it "infers license file" $ do
      writeFile "package.yaml" [i|
        name: foo
        |]
      touch "LICENSE"
      Right (_, c) <- readPackageConfig "package.yaml"
      c `shouldBe` package {packageLicenseFile = Just "LICENSE"}

    it "accepts extra-source-files" $ do
      writeFile "package.yaml" [i|
        extra-source-files:
          - CHANGES.markdown
          - README.markdown
        |]
      touch "CHANGES.markdown"
      touch "README.markdown"
      Right (_, c) <- readPackageConfig "package.yaml"
      packageExtraSourceFiles c `shouldBe` ["CHANGES.markdown", "README.markdown"]

    it "accepts data-files" $ do
      writeFile "package.yaml" [i|
        data-files:
          - data/**/*.html
        |]
      touch "data/foo/index.html"
      touch "data/bar/index.html"
      Right (_, c) <- readPackageConfig "package.yaml"
      packageDataFiles c `shouldMatchList` ["data/foo/index.html", "data/bar/index.html"]

    it "accepts github" $ do
      writeFile "package.yaml" [i|
        github: hspec/hspec
        |]
      Right (_, c) <- readPackageConfig "package.yaml"
      packageSourceRepository c `shouldBe`
        Just (SourceRepository "https://github.com/hspec/hspec" Nothing)

    it "accepts third part of github URL as subdir" $ do
      writeFile "package.yaml" [i|
        github: hspec/hspec/hspec-core
        |]
      Right (_, c) <- readPackageConfig "package.yaml"
      packageSourceRepository c `shouldBe`
        Just (SourceRepository "https://github.com/hspec/hspec" (Just "hspec-core"))

    it "accepts CPP options" $ do
      writeFile "package.yaml" [i|
        cpp-options: -DFOO
        library:
          cpp-options: -DLIB

        executables:
          foo:
            main: Main.hs
            cpp-options: -DFOO


        tests:
          spec:
            main: Spec.hs
            cpp-options: -DTEST
        |]
      Right (_, c) <- readPackageConfig "package.yaml"
      c `shouldBe` package {
          packageLibrary = Just (section library) {sectionCppOptions = ["-DFOO", "-DLIB"]}
        , packageExecutables = [(section $ executable "foo" "Main.hs") {sectionCppOptions = ["-DFOO", "-DFOO"]}]
        , packageTests = [(section $ executable "spec" "Spec.hs") {sectionCppOptions = ["-DFOO", "-DTEST"]}]
        }

    context "when reading library section" $ do
      it "warns on unknown fields" $ do
        writeFile "package.yaml" [i|
          library:
            bar: 23
            baz: 42
          |]

        fmap fst <$> readPackageConfig "package.yaml" `shouldReturn` Right [
            "Ignoring unknown field \"bar\" in library section"
          , "Ignoring unknown field \"baz\" in library section"
          ]

      it "accepts source-dirs" $ do
        writeFile "package.yaml" [i|
          library:
            source-dirs:
              - foo
              - bar
          |]
        Right (_, c) <- readPackageConfig "package.yaml"
        packageLibrary c `shouldBe` Just (section library) {sectionSourceDirs = ["foo", "bar"]}

      it "accepts default-extensions" $ do
        writeFile "package.yaml" [i|
          library:
            default-extensions:
              - Foo
              - Bar
          |]
        Right (_, c) <- readPackageConfig "package.yaml"
        packageLibrary c `shouldBe` Just (section library) {sectionDefaultExtensions = ["Foo", "Bar"]}

      it "accepts global default-extensions" $ do
        writeFile "package.yaml" [i|
          default-extensions:
            - Foo
            - Bar
          library: {}
          |]
        Right (_, c) <- readPackageConfig "package.yaml"
        packageLibrary c `shouldBe` Just (section library) {sectionDefaultExtensions = ["Foo", "Bar"]}

      it "accepts global source-dirs" $ do
        writeFile "package.yaml" [i|
          source-dirs:
            - foo
            - bar
          library: {}
          |]
        Right (_, c) <- readPackageConfig "package.yaml"
        packageLibrary c `shouldBe` Just (section library) {sectionSourceDirs = ["foo", "bar"]}

      it "allows to specify exposed-modules" $ do
        writeFile "package.yaml" [i|
          library:
            source-dirs: src
            exposed-modules: Foo
          |]
        touch "src/Foo.hs"
        touch "src/Bar.hs"
        Right (_, c) <- readPackageConfig "package.yaml"
        packageLibrary c `shouldBe` Just (section library{libraryExposedModules = ["Foo"], libraryOtherModules = ["Bar"]}) {sectionSourceDirs = ["src"]}

      it "allows to specify other-modules" $ do
        writeFile "package.yaml" [i|
          library:
            source-dirs: src
            other-modules: Bar
          |]
        touch "src/Foo.hs"
        touch "src/Bar.hs"
        Right (_, c) <- readPackageConfig "package.yaml"
        packageLibrary c `shouldBe` Just (section library{libraryExposedModules = ["Foo"], libraryOtherModules = ["Bar"]}) {sectionSourceDirs = ["src"]}

      it "allows to specify both exposed-modules and other-modules" $ do
        writeFile "package.yaml" [i|
          library:
            source-dirs: src
            exposed-modules: Foo
            other-modules: Bar
          |]
        touch "src/Baz.hs"
        Right (_, c) <- readPackageConfig "package.yaml"
        packageLibrary c `shouldBe` Just (section library{libraryExposedModules = ["Foo"], libraryOtherModules = ["Bar"]}) {sectionSourceDirs = ["src"]}

      context "when neither exposed-module nor other-module are specified" $ do
        it "exposes all modules" $ do
          writeFile "package.yaml" [i|
            library:
              source-dirs: src
            |]
          touch "src/Foo.hs"
          touch "src/Bar.hs"
          Right (_, c) <- readPackageConfig "package.yaml"
          packageLibrary c `shouldBe` Just (section library{libraryExposedModules = ["Bar", "Foo"]}) {sectionSourceDirs = ["src"]}

    context "when reading executable section" $ do
      it "warns on unknown fields" $ do
        writeFile "package.yaml" [i|
          executables:
            foo:
              main: Main.hs
              bar: 42
              baz: 23
          |]

        fmap fst <$> readPackageConfig "package.yaml" `shouldReturn` Right [
            "Ignoring unknown field \"bar\" in executable section \"foo\""
          , "Ignoring unknown field \"baz\" in executable section \"foo\""
          ]

      it "reads executable section" $ do
        writeFile "package.yaml" [i|
          executables:
            foo:
              main: driver/Main.hs
          |]
        Right (_, c) <- readPackageConfig "package.yaml"
        packageExecutables c `shouldBe` [section $ executable "foo" "driver/Main.hs"]

      it "accepts source-dirs" $ do
        writeFile "package.yaml" [i|
          executables:
            foo:
              main: Main.hs
              source-dirs:
                - foo
                - bar
          |]
        Right (_, c) <- readPackageConfig "package.yaml"
        packageExecutables c `shouldBe` [(section $ executable "foo" "Main.hs") {sectionSourceDirs = ["foo", "bar"]}]

      it "accepts global source-dirs" $ do
        writeFile "package.yaml" [i|
          source-dirs:
            - foo
            - bar
          executables:
            foo:
              main: Main.hs
          |]
        Right (_, c) <- readPackageConfig "package.yaml"
        packageExecutables c `shouldBe` [(section $ executable "foo" "Main.hs") {sectionSourceDirs = ["foo", "bar"]}]

      it "infers other-modules" $ do
        touch "src/Main.hs"
        touch "src/Foo.hs"
        touch "src/Bar.hs"
        touch "src/Baz.lhs"
        writeFile "package.yaml" [i|
          executables:
            foo:
              main: Main.hs
              source-dirs: src
          |]
        Right (_, [r]) <- (fmap . fmap) packageExecutables <$> readPackageConfig "package.yaml"
        executableOtherModules (sectionData r) `shouldBe` ["Bar", "Baz", "Foo"]

      it "allows to specify other-modules" $ do
        touch "src/Foo.hs"
        touch "src/Bar.hs"
        writeFile "package.yaml" [i|
          executables:
            foo:
              main: Main.hs
              source-dirs: src
              other-modules: Baz
          |]
        Right (_, [r]) <- (fmap . fmap) packageExecutables <$> readPackageConfig "package.yaml"
        executableOtherModules (sectionData r) `shouldBe` ["Baz"]

      it "accepts default-extensions" $ do
        writeFile "package.yaml" [i|
          executables:
            foo:
              main: driver/Main.hs
              default-extensions:
                - Foo
                - Bar
          |]
        Right (_, c) <- readPackageConfig "package.yaml"
        packageExecutables c `shouldBe` [(section $ executable "foo" "driver/Main.hs") {sectionDefaultExtensions = ["Foo", "Bar"]}]

      it "accepts global default-extensions" $ do
        writeFile "package.yaml" [i|
          default-extensions:
            - Foo
            - Bar
          executables:
            foo:
              main: driver/Main.hs
          |]
        Right (_, c) <- readPackageConfig "package.yaml"
        packageExecutables c `shouldBe` [(section $ executable "foo" "driver/Main.hs") {sectionDefaultExtensions = ["Foo", "Bar"]}]

      it "accepts GHC options" $ do
        writeFile "package.yaml" [i|
          executables:
            foo:
              main: driver/Main.hs
              ghc-options: -Wall
          |]
        Right (_, c) <- readPackageConfig "package.yaml"
        c `shouldBe` package {packageExecutables = [(section $ executable "foo" "driver/Main.hs") {sectionGhcOptions = ["-Wall"]}]}

      it "accepts global GHC options" $ do
        writeFile "package.yaml" [i|
          ghc-options: -Wall
          executables:
            foo:
              main: driver/Main.hs
          |]
        Right (_, c) <- readPackageConfig "package.yaml"
        c `shouldBe` package {packageExecutables = [(section $ executable "foo" "driver/Main.hs") {sectionGhcOptions = ["-Wall"]}]}

    context "when reading test section" $ do
      it "warns on unknown fields" $ do
        writeFile "package.yaml" [i|
          tests:
            foo:
              main: Main.hs
              bar: 42
              baz: 23
          |]

        fmap fst <$> readPackageConfig "package.yaml" `shouldReturn` Right [
            "Ignoring unknown field \"bar\" in test section \"foo\""
          , "Ignoring unknown field \"baz\" in test section \"foo\""
          ]

      it "reads test section" $ do
        writeFile "package.yaml" [i|
          tests:
            spec:
              main: test/Spec.hs
          |]
        Right (_, c) <- readPackageConfig "package.yaml"
        c `shouldBe` package {packageTests = [section $ executable "spec" "test/Spec.hs"]}

      it "accepts single dependency" $ do
        writeFile "package.yaml" [i|
          tests:
            spec:
              main: test/Spec.hs
              dependencies: hspec
          |]
        Right (_, c) <- readPackageConfig "package.yaml"
        c `shouldBe` package {packageTests = [(section $ executable "spec" "test/Spec.hs") {sectionDependencies = ["hspec"]}]}

      it "accepts list of dependencies" $ do
        writeFile "package.yaml" [i|
          tests:
            spec:
              main: test/Spec.hs
              dependencies:
                - hspec
                - QuickCheck
          |]
        Right (_, c) <- readPackageConfig "package.yaml"
        c `shouldBe` package {packageTests = [(section $ executable "spec" "test/Spec.hs") {sectionDependencies = ["hspec", "QuickCheck"]}]}

      context "when both global and section specific dependencies are specified" $ do
        it "combines dependencies" $ do
          writeFile "package.yaml" [i|
            dependencies:
              - base

            tests:
              spec:
                main: test/Spec.hs
                dependencies: hspec
            |]
          Right (_, c) <- readPackageConfig "package.yaml"
          c `shouldBe` package {packageTests = [(section $ executable "spec" "test/Spec.hs") {sectionDependencies = ["base", "hspec"]}]}

    context "when a specified source directory does not exist" $ do
      it "warns" $ do
        writeFile "package.yaml" [i|
          source-dirs:
            - some-dir
            - some-existing-dir
          library:
            source-dirs: some-lib-dir
          executables:
            main:
              main: Main.hs
              source-dirs: some-exec-dir
          tests:
            spec:
              main: Main.hs
              source-dirs: some-test-dir
          |]
        touch "some-existing-dir/foo"
        fmap fst <$> readPackageConfig "package.yaml" `shouldReturn` Right [
            "Specified source-dir \"some-dir\" does not exist"
          , "Specified source-dir \"some-exec-dir\" does not exist"
          , "Specified source-dir \"some-lib-dir\" does not exist"
          , "Specified source-dir \"some-test-dir\" does not exist"
          ]

    context "when package.yaml can not be parsed" $ do
      it "returns an error" $ do
        writeFile "package.yaml" [i|
          foo: bar
          foo baz
          |]
        readPackageConfig "package.yaml" `shouldReturn` Left "package.yaml:3:10: could not find expected ':' while scanning a simple key"

    context "when package.yaml is invalid" $ do
      it "returns an error" $ do
        writeFile "package.yaml" [i|
          executables:
            foo:
              ain: driver/Main.hs
          |]
        readPackageConfig "package.yaml" `shouldReturn` Left "package.yaml: The key \"main\" was not found"

    context "when package.yaml does not exist" $ do
      it "returns an error" $
        readPackageConfig "package.yaml" `shouldReturn` Left "package.yaml: Yaml file not found: package.yaml"