packages feed

checkmate-0.4.0: test/Checkmate/DiscoverSpec.hs

{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
module Checkmate.DiscoverSpec (spec) where

import Control.Monad

import Data.ByteString
import Data.FileEmbed
import Data.Text
import System.Directory
import System.FilePath
import System.IO.Temp
import Test.Hspec
import Text.Diff.Parse (parseDiff)
import Text.Diff.Parse.Types

import Checkmate.Check
import Checkmate.Discover

fixtures :: [(FilePath, ByteString)]
fixtures = $(embedDir "test/fixtures/")

diffTextFixture :: Text
diffTextFixture = $(embedStringFile "test/fixtures/patch.diff")

diffFixture :: FileDeltas
Right diffFixture = parseDiff diffTextFixture

rootChecklistFixture :: FilePath -> Checklist
rootChecklistFixture dirPath =
    [ Check { checkScope = dir
            , checkOrderIndex = 1
            , checkText = "Sample check A"
            }
    , Check { checkScope = dir
            , checkOrderIndex = 2
            , checkText = "sample check B"
            }
    , Check { checkScope = dir
            , checkOrderIndex = 3
            , checkText = "Sample check C"
            }
    ]
  where
    dir :: Scope
    dir = Directory dirPath

subdirChecklistFixture :: FilePath -> Checklist
subdirChecklistFixture dirPath =
    [ Check { checkScope = dir
            , checkOrderIndex = 1
            , checkText = "sub-dir check A"
            }
    , Check { checkScope = dir
            , checkOrderIndex = 2
            , checkText = "sub-dir check B"
            }
    ] `union` rootChecklistFixture dirPath
  where
    dir :: Scope
    dir = Directory $ dirPath </> "subdir"

pyChecklistFixture :: FilePath -> Checklist
pyChecklistFixture d =
    [ Check { checkScope = FileBlock { scopePath = pyPath
                                     , scopeRange = Range 2 24
                                     }
            , checkOrderIndex = 1
            , checkText = "module-level check"
            }
    , Check { checkScope = FileBlock { scopePath = pyPath
                                     , scopeRange = Range 11 5
                                     }
            , checkOrderIndex = 3
            , checkText = "function-level check 2"
            }
    , Check { checkScope = FileBlock { scopePath = pyPath
                                     , scopeRange = Range 14 2
                                     }
            , checkOrderIndex = 4
            , checkText = "nested function-level check"
            }
    ]
  where
    pyPath :: FilePath
    pyPath = d </> "sample.py"

jsChecklistFixture :: FilePath -> Checklist
jsChecklistFixture d =
    [ Check { checkScope = FileBlock { scopePath = jsPath
                                     , scopeRange = Range 2 20
                                     }
            , checkOrderIndex = 1
            , checkText = "global check"
            }
    , Check { checkScope = FileBlock { scopePath = jsPath
                                     , scopeRange = Range 10 11
                                     }
            , checkOrderIndex = 3
            , checkText = "function-level check 2"
            }
    , Check { checkScope = FileBlock { scopePath = jsPath
                                     , scopeRange = Range 13 5
                                     }
            , checkOrderIndex = 4
            , checkText = "closure check.\nIt can be multiline."
            }
    ]
  where
    jsPath :: FilePath
    jsPath = d </> "subdir" </> "inner_sample.js"

withTempDir :: (FilePath -> IO a) -> IO a
withTempDir = withSystemTempDirectory "checkmate-test"

withFixtureDir :: (FilePath -> IO a) -> IO a
withFixtureDir action =
    withTempDir $ \ dirPath -> do
        forM_ fixtures $ \ (filePath, contents) -> do
            let path = dirPath </> filePath
                parent = takeDirectory path
                -- `parent` can differ from `dirPath` when `filePath` has '/'
            createDirectoryIfMissing True parent
            Data.ByteString.writeFile path contents
        action dirPath

spec :: Spec
spec = do
    describe "discover" $ do
        it "scans all relative checks" $
            withFixtureDir $ \ dirPath -> do
                checklist <- discover dirPath diffFixture
                let expected = subdirChecklistFixture dirPath
                        `union` pyChecklistFixture dirPath
                        `union` jsChecklistFixture dirPath
                checklist `shouldBe` expected
        it "ignores files/directories that do not exist" $
            withTempDir $ \ dirPath -> do
                checklist <- discover dirPath diffFixture
                checklist `shouldBe` []
    describe "discoverDirectory" $ do
        it "lists files in the given path" $
            withFixtureDir $ \ dirPath -> do
                subdirChecks <- discoverDirectory dirPath "subdir"
                subdirChecks `shouldBe` subdirChecklistFixture dirPath
                rootChecks <- discoverDirectory dirPath "."
                rootChecks `shouldBe` rootChecklistFixture dirPath
        it "ignores files/directories that do not exist" $
            withTempDir $ \ dirPath -> do
                checks <- discoverDirectory
                    (dirPath </> "dir-do-not-exist")
                    "dir-do-not-exist"
                checks `shouldBe` []
    describe "discoverFile" $ do
        it "scans all relative checks" $
            withFixtureDir $ \ dirPath -> do
                pyChecks <- discoverFile dirPath $ Prelude.head diffFixture
                pyChecks `shouldBe` pyChecklistFixture dirPath
                jsChecks <- discoverFile dirPath $ Prelude.last diffFixture
                jsChecks `shouldBe` jsChecklistFixture dirPath
        it "ignores files/directories that do not exist" $
            withTempDir $ \ dirPath -> do
                checklist <- discoverFile dirPath $ Prelude.head diffFixture
                checklist `shouldBe` []