tricorder-1.0.0.0: test/Unit/Tricorder/Daemon/WatchSpec.hs
module Unit.Tricorder.Daemon.WatchSpec (test_Watch) where
import Atelier.Effects.FileWatcher (matchesAny)
import Atelier.Effects.FileWatcher.Event (Event (..))
import Effectful (runEff)
import Effectful.Writer.Static.Shared (execWriter, runWriter)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (testCase, (@?=))
import Text.Regex.TDFA.ReadRegex (parseRegex)
import Atelier.Effects.PubSub.Pub qualified as Pub
import Tricorder.Build.Changes (CabalChangeDetected (..), SourceChangeDetected (..))
import Tricorder.Daemon.Watch (WatchedFile (..))
import Tricorder.Runtime (ProjectRoot (..))
import Tricorder.Session.WatchDirs (WatchDirs (..))
import Tricorder.Session.WatchExclusionPatterns (WatchExclusionPatterns (..))
import Tricorder.Daemon.Watch qualified as Watch
test_Watch :: TestTree
test_Watch =
testGroup
"Watch"
[ testGroup "publishChange" testPublishChange
, testGroup "specs" testSpecs
]
testPublishChange :: [TestTree]
testPublishChange =
[ testGroup
"with non-cabal file change"
[ testCase "should publish SourceChangeDetected" do
(_, sourceChanges) <- runTest "foo"
sourceChanges @?= [SourceChangeDetected "foo" Modified]
]
, testGroup
"with cabal file change"
[ testCase "should publish CabalChangeDetected" do
(cabalChanges, _) <- runTest "foo.cabal"
cabalChanges @?= [CabalChangeDetected "foo.cabal" Modified]
]
]
where
runTest =
runEff
. runWriter
. Pub.toWriter @SourceChangeDetected
. execWriter
. Pub.toWriter @CabalChangeDetected
. Watch.publishChange
. (`WatchedFile` Modified)
testSpecs :: [TestTree]
testSpecs =
[ testGroup
"source watches"
[ testCase "matches .hs files in configured watch dirs" do
let watches =
Watch.specs
(ProjectRoot "/proj")
(WatchExclusionPatterns [])
(WatchDirs ["/proj/src"])
matchesAny watches "/proj/src/Foo.hs" @?= True
, testCase "does not match non-.hs files" do
let watches =
Watch.specs
(ProjectRoot "/proj")
(WatchExclusionPatterns [])
(WatchDirs ["/proj/src"])
matchesAny watches "/proj/src/Foo.txt" @?= False
, testCase "excludes paths containing dist-newstyle" do
let watches =
Watch.specs
(ProjectRoot "/proj")
(WatchExclusionPatterns [])
(WatchDirs ["/proj/src"])
matchesAny watches "/proj/src/dist-newstyle/Foo.hs" @?= False
, testCase "excludes paths matching an exclusion pattern" do
let pat = parsePattern "vendor"
watches =
Watch.specs
(ProjectRoot "/proj")
(WatchExclusionPatterns [pat])
(WatchDirs ["/proj/src"])
matchesAny watches "/proj/src/vendor/Foo.hs" @?= False
matchesAny watches "/proj/src/Foo.hs" @?= True
, testCase "matches .hs files across multiple watch dirs" do
let watches =
Watch.specs
(ProjectRoot "/proj")
(WatchExclusionPatterns [])
(WatchDirs ["/proj/src", "/proj/test"])
matchesAny watches "/proj/src/Foo.hs" @?= True
matchesAny watches "/proj/test/FooSpec.hs" @?= True
, -- Second line of defense: 'cabalWatches' registers the whole project
-- root, and 'deduplicateDirs' collapses the narrow source dirs into it,
-- so the OS watches the entire repo recursively. 'matchesAny' is what
-- re-scopes events back to the configured dirs — a .hs file in a sibling
-- package must not match.
testCase "does not match a .hs file in a sibling package outside the watch dirs" do
let watches =
Watch.specs
(ProjectRoot "/proj")
(WatchExclusionPatterns [])
(WatchDirs ["/proj/pkg-a/src"])
matchesAny watches "/proj/pkg-a/src/Foo.hs" @?= True
matchesAny watches "/proj/pkg-b/src/Foo.hs" @?= False
]
, testGroup
"cabal watches"
[ testCase "matches .cabal files under project root" do
let watches =
Watch.specs
(ProjectRoot "/proj")
(WatchExclusionPatterns [])
(WatchDirs [])
matchesAny watches "/proj/foo.cabal" @?= True
, testCase "matches cabal.project under project root" do
let watches =
Watch.specs
(ProjectRoot "/proj")
(WatchExclusionPatterns [])
(WatchDirs [])
matchesAny watches "/proj/cabal.project" @?= True
, testCase "matches package.yaml under project root" do
let watches =
Watch.specs
(ProjectRoot "/proj")
(WatchExclusionPatterns [])
(WatchDirs [])
matchesAny watches "/proj/package.yaml" @?= True
, testCase "does not match non-cabal files" do
let watches =
Watch.specs
(ProjectRoot "/proj")
(WatchExclusionPatterns [])
(WatchDirs [])
matchesAny watches "/proj/README.md" @?= False
, testCase "excludes cabal files under dist-newstyle" do
let watches =
Watch.specs
(ProjectRoot "/proj")
(WatchExclusionPatterns [])
(WatchDirs [])
matchesAny watches "/proj/dist-newstyle/foo.cabal" @?= False
]
]
where
parsePattern p = fromRight (error . toText $ "bad test pattern: " <> p) (parseRegex p)