packages feed

sydtest-mutation-driver-gen-0.0.0.0: test/Test/Syd/Mutation/Driver/ComponentsSpec.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}

module Test.Syd.Mutation.Driver.ComponentsSpec (spec) where

import qualified Control.Exception as Exception
import qualified Data.ByteString as SB
import Path
import Path.IO (createDirIfMissing, doesFileExist, withSystemTempDir)
import Test.Syd
import Test.Syd.Mutation.Driver.Components
  ( CabalFileLookupError (..),
    MissingBuiltComponent (..),
    findCabalFile,
    readComponentNames,
    runInstallComponentsWithBuildDir,
  )
import Test.Syd.Mutation.Driver.OptParse (ComponentKind (..))

spec :: Spec
spec = do
  describe "readComponentNames" $ do
    it "returns executable names in declaration order" $
      withSystemTempDir "components-exes" $ \dir -> do
        let cabalFile = dir </> [relfile|pkg.cabal|]
        SB.writeFile (fromAbsFile cabalFile) $
          mconcat
            [ "cabal-version: 2.0\n",
              "name: pkg\n",
              "version: 0.1.0.0\n",
              "build-type: Simple\n",
              "executable first-exe\n",
              "  main-is: Main.hs\n",
              "  build-depends: base\n",
              "executable second-exe\n",
              "  main-is: Main.hs\n",
              "  build-depends: base\n"
            ]
        names <- readComponentNames ComponentExecutables cabalFile
        names `shouldBe` ["first-exe", "second-exe"]

    it "returns test-suite names" $
      withSystemTempDir "components-tests" $ \dir -> do
        let cabalFile = dir </> [relfile|pkg.cabal|]
        SB.writeFile (fromAbsFile cabalFile) $
          mconcat
            [ "cabal-version: 2.0\n",
              "name: pkg\n",
              "version: 0.1.0.0\n",
              "build-type: Simple\n",
              "test-suite the-test\n",
              "  type: exitcode-stdio-1.0\n",
              "  main-is: Spec.hs\n",
              "  build-depends: base\n"
            ]
        names <- readComponentNames ComponentTestSuites cabalFile
        names `shouldBe` ["the-test"]

    it "returns an empty list when the package declares no components of that kind" $
      withSystemTempDir "components-empty" $ \dir -> do
        let cabalFile = dir </> [relfile|pkg.cabal|]
        SB.writeFile (fromAbsFile cabalFile) $
          mconcat
            [ "cabal-version: 2.0\n",
              "name: pkg\n",
              "version: 0.1.0.0\n",
              "build-type: Simple\n",
              "library\n",
              "  build-depends: base\n"
            ]
        execNames <- readComponentNames ComponentExecutables cabalFile
        testNames <- readComponentNames ComponentTestSuites cabalFile
        execNames `shouldBe` []
        testNames `shouldBe` []

  describe "runInstallComponentsWithBuildDir" $ do
    it "copies each declared executable from <buildDir>/<n>/<n> to <outDir>/<n>" $
      withSystemTempDir "install-ok" $ \dir -> do
        let cabalFile = dir </> [relfile|pkg.cabal|]
            buildDir = dir </> [reldir|dist/build|]
            outDir = dir </> [reldir|out|]
        SB.writeFile (fromAbsFile cabalFile) $
          mconcat
            [ "cabal-version: 2.0\n",
              "name: pkg\n",
              "version: 0.1.0.0\n",
              "build-type: Simple\n",
              "executable foo\n",
              "  main-is: Main.hs\n",
              "  build-depends: base\n",
              "executable bar\n",
              "  main-is: Main.hs\n",
              "  build-depends: base\n"
            ]
        createDirIfMissing True (buildDir </> [reldir|foo|])
        createDirIfMissing True (buildDir </> [reldir|bar|])
        SB.writeFile (fromAbsFile (buildDir </> [relfile|foo/foo|])) "FOO"
        SB.writeFile (fromAbsFile (buildDir </> [relfile|bar/bar|])) "BAR"
        runInstallComponentsWithBuildDir ComponentExecutables cabalFile buildDir outDir
        copiedFoo <- doesFileExist (outDir </> [relfile|foo|])
        copiedBar <- doesFileExist (outDir </> [relfile|bar|])
        copiedFoo `shouldBe` True
        copiedBar `shouldBe` True
        fooBytes <- SB.readFile (fromAbsFile (outDir </> [relfile|foo|]))
        fooBytes `shouldBe` "FOO"

    it "throws MissingBuiltComponent when a declared executable's binary is absent" $
      withSystemTempDir "install-missing" $ \dir -> do
        let cabalFile = dir </> [relfile|pkg.cabal|]
            buildDir = dir </> [reldir|dist/build|]
            outDir = dir </> [reldir|out|]
        SB.writeFile (fromAbsFile cabalFile) $
          mconcat
            [ "cabal-version: 2.0\n",
              "name: pkg\n",
              "version: 0.1.0.0\n",
              "build-type: Simple\n",
              "executable foo\n",
              "  main-is: Main.hs\n",
              "  build-depends: base\n"
            ]
        result <-
          Exception.try $
            runInstallComponentsWithBuildDir ComponentExecutables cabalFile buildDir outDir
        case result of
          Left (MissingBuiltComponent name _) -> name `shouldBe` "foo"
          Right () -> expectationFailure "expected MissingBuiltComponent to be thrown"

    it "creates the output directory when it does not exist" $
      withSystemTempDir "install-mkdir" $ \dir -> do
        let cabalFile = dir </> [relfile|pkg.cabal|]
            buildDir = dir </> [reldir|dist/build|]
            outDir = dir </> [reldir|nested/out|]
        SB.writeFile (fromAbsFile cabalFile) $
          mconcat
            [ "cabal-version: 2.0\n",
              "name: pkg\n",
              "version: 0.1.0.0\n",
              "build-type: Simple\n",
              "executable foo\n",
              "  main-is: Main.hs\n",
              "  build-depends: base\n"
            ]
        createDirIfMissing True (buildDir </> [reldir|foo|])
        SB.writeFile (fromAbsFile (buildDir </> [relfile|foo/foo|])) "FOO"
        runInstallComponentsWithBuildDir ComponentExecutables cabalFile buildDir outDir
        copied <- doesFileExist (outDir </> [relfile|foo|])
        copied `shouldBe` True

    it "is a no-op when the package declares no components of that kind" $
      withSystemTempDir "install-empty" $ \dir -> do
        let cabalFile = dir </> [relfile|pkg.cabal|]
            buildDir = dir </> [reldir|dist/build|]
            outDir = dir </> [reldir|out|]
        SB.writeFile (fromAbsFile cabalFile) $
          mconcat
            [ "cabal-version: 2.0\n",
              "name: pkg\n",
              "version: 0.1.0.0\n",
              "build-type: Simple\n",
              "library\n",
              "  build-depends: base\n"
            ]
        -- Should not throw and should not need <buildDir> to exist.
        runInstallComponentsWithBuildDir ComponentExecutables cabalFile buildDir outDir

  describe "findCabalFile" $ do
    it "returns <pname>.cabal when it exists" $
      withSystemTempDir "find-preferred" $ \dir -> do
        let expected = dir </> [relfile|foo.cabal|]
        SB.writeFile (fromAbsFile expected) "cabal-version: 2.0\nname: foo\n"
        actual <- findCabalFile "foo" dir
        actual `shouldBe` expected

    it "prefers <pname>.cabal over a differently-named .cabal also in the directory" $
      withSystemTempDir "find-prefer-over-other" $ \dir -> do
        let expected = dir </> [relfile|foo.cabal|]
        SB.writeFile (fromAbsFile expected) "cabal-version: 2.0\nname: foo\n"
        SB.writeFile (fromAbsFile (dir </> [relfile|other.cabal|])) "cabal-version: 2.0\nname: other\n"
        actual <- findCabalFile "foo" dir
        actual `shouldBe` expected

    it "falls back to the single other .cabal file when <pname>.cabal is absent" $
      withSystemTempDir "find-fallback" $ \dir -> do
        let expected = dir </> [relfile|differently-named.cabal|]
        SB.writeFile (fromAbsFile expected) "cabal-version: 2.0\nname: x\n"
        actual <- findCabalFile "foo" dir
        actual `shouldBe` expected

    it "throws NoCabalFileFound when the directory has no .cabal files" $
      withSystemTempDir "find-empty" $ \dir -> do
        result <-
          (Exception.try :: IO (Path Abs File) -> IO (Either CabalFileLookupError (Path Abs File))) $
            findCabalFile "foo" dir
        case result of
          Left (NoCabalFileFound d) -> d `shouldBe` dir
          Left e -> expectationFailure ("wrong error: " ++ show e)
          Right p -> expectationFailure ("expected NoCabalFileFound, got " ++ show p)

    it "throws AmbiguousCabalFile when <pname>.cabal is absent and multiple other .cabal files match" $
      withSystemTempDir "find-ambiguous" $ \dir -> do
        SB.writeFile (fromAbsFile (dir </> [relfile|a.cabal|])) "cabal-version: 2.0\nname: a\n"
        SB.writeFile (fromAbsFile (dir </> [relfile|b.cabal|])) "cabal-version: 2.0\nname: b\n"
        result <-
          (Exception.try :: IO (Path Abs File) -> IO (Either CabalFileLookupError (Path Abs File))) $
            findCabalFile "foo" dir
        case result of
          Left (AmbiguousCabalFile d pname _) -> do
            d `shouldBe` dir
            pname `shouldBe` "foo"
          Left e -> expectationFailure ("wrong error: " ++ show e)
          Right p -> expectationFailure ("expected AmbiguousCabalFile, got " ++ show p)