packages feed

tasty-autocollect 0.3.2.0 → 0.4.0

raw patch · 27 files changed

+654/−685 lines, 27 filesdep ~ghcPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: ghc

API changes (from Hackage documentation)

- Test.Tasty.AutoCollect.Config: data AutoCollectConfig
- Test.Tasty.AutoCollect.Config: defaultConfig :: AutoCollectConfig
+ Test.Tasty.AutoCollect.Config: [cfgCustomMain] :: AutoCollectConfig' f -> Apply f Bool
+ Test.Tasty.AutoCollect.Config: [cfgImports] :: AutoCollectConfig' f -> Apply f [FilePath]
+ Test.Tasty.AutoCollect.Config: data AutoCollectConfig' f
+ Test.Tasty.AutoCollect.Config: instance GHC.Base.Monoid Test.Tasty.AutoCollect.Config.AutoCollectConfigPartial
+ Test.Tasty.AutoCollect.Config: instance GHC.Base.Semigroup Test.Tasty.AutoCollect.Config.AutoCollectConfigPartial
+ Test.Tasty.AutoCollect.Config: instance GHC.Classes.Eq Test.Tasty.AutoCollect.Config.AutoCollectConfigPartial
+ Test.Tasty.AutoCollect.Config: instance GHC.Show.Show Test.Tasty.AutoCollect.Config.AutoCollectConfigPartial
+ Test.Tasty.AutoCollect.Config: resolveConfig :: FilePath -> AutoCollectConfigPartial -> IO AutoCollectConfig
+ Test.Tasty.AutoCollect.Config: type AutoCollectConfig = AutoCollectConfig' Identity
+ Test.Tasty.AutoCollect.Config: type AutoCollectConfigPartial = AutoCollectConfig' Maybe
- Test.Tasty.AutoCollect.Config: AutoCollectConfig :: Maybe Text -> AutoCollectGroupType -> Text -> [Text] -> Bool -> AutoCollectConfig
+ Test.Tasty.AutoCollect.Config: AutoCollectConfig :: Apply f [FilePath] -> Apply f (Maybe Text) -> Apply f AutoCollectGroupType -> Apply f Text -> Apply f [Text] -> Apply f Bool -> Apply f Bool -> AutoCollectConfig' f
- Test.Tasty.AutoCollect.Config: [cfgGroupType] :: AutoCollectConfig -> AutoCollectGroupType
+ Test.Tasty.AutoCollect.Config: [cfgGroupType] :: AutoCollectConfig' f -> Apply f AutoCollectGroupType
- Test.Tasty.AutoCollect.Config: [cfgIngredientsOverride] :: AutoCollectConfig -> Bool
+ Test.Tasty.AutoCollect.Config: [cfgIngredientsOverride] :: AutoCollectConfig' f -> Apply f Bool
- Test.Tasty.AutoCollect.Config: [cfgIngredients] :: AutoCollectConfig -> [Text]
+ Test.Tasty.AutoCollect.Config: [cfgIngredients] :: AutoCollectConfig' f -> Apply f [Text]
- Test.Tasty.AutoCollect.Config: [cfgStripSuffix] :: AutoCollectConfig -> Text
+ Test.Tasty.AutoCollect.Config: [cfgStripSuffix] :: AutoCollectConfig' f -> Apply f Text
- Test.Tasty.AutoCollect.Config: [cfgSuiteName] :: AutoCollectConfig -> Maybe Text
+ Test.Tasty.AutoCollect.Config: [cfgSuiteName] :: AutoCollectConfig' f -> Apply f (Maybe Text)
- Test.Tasty.AutoCollect.Config: parseConfig :: Text -> Either Text AutoCollectConfig
+ Test.Tasty.AutoCollect.Config: parseConfig :: Text -> Either Text AutoCollectConfigPartial
- Test.Tasty.AutoCollect.GenerateMain: generateMainModule :: AutoCollectConfig -> FilePath -> IO Text
+ Test.Tasty.AutoCollect.GenerateMain: generateMainModule :: AutoCollectConfig -> FilePath -> Text -> IO Text
- Test.Tasty.AutoCollect.ModuleType: ModuleMain :: AutoCollectConfig -> ModuleType
+ Test.Tasty.AutoCollect.ModuleType: ModuleMain :: AutoCollectConfigPartial -> ModuleType

Files

CHANGELOG.md view
@@ -1,3 +1,10 @@+# v0.4.0++* Drop support for GHC 8.10+* Added support for where clauses in `test_prop`, where the where clause may reference the generated arguments in `test_prop`+* Don't autocollect invalid module names+* Enable importing configuration from other files+ # v0.3.2.0  * Added support for GHC 9.4
README.md view
@@ -1,6 +1,6 @@ # tasty-autocollect -[![](https://img.shields.io/github/workflow/status/brandonchinn178/tasty-autocollect/CI/main)](https://github.com/brandonchinn178/tasty-autocollect/actions)+[![](https://img.shields.io/github/actions/workflow/status/brandonchinn178/tasty-autocollect/ci.yml?branch=main)](https://github.com/brandonchinn178/tasty-autocollect/actions) [![](https://img.shields.io/codecov/c/gh/brandonchinn178/tasty-autocollect)](https://app.codecov.io/gh/brandonchinn178/tasty-autocollect) [![](https://img.shields.io/hackage/v/tasty-autocollect)](https://hackage.haskell.org/package/tasty-autocollect) @@ -8,13 +8,12 @@  Design goals: * Don't use any weird syntax so that syntax highlighters, linters, and formatters still work-* Support test functions with multiple arguments like `tasty-golden`'s API (which `tasty-discover` doesn't easily support) * Avoid universally exporting the whole test module, so that GHC can warn about unused test helpers * Support arbitrary test functions (e.g. user-defined test helpers or third-party tasty libraries)--## Usage+* Minimal dependencies+  * Only uses boot libraries, with two exceptions: `tasty` + `tasty-expected-failure` -### Quickstart+## Quickstart  1. Add the following to your `package.yaml` or `.cabal` file: @@ -76,79 +75,11 @@      test =       testGroup "manually defining a test group"-        [ testCase "some test" $ return ()-        , testCase "some other test" $ return ()+        [ testCase "some test" $ pure ()+        , testCase "some other test" $ pure ()         ]     ``` -### How it works--The `package.yaml`/`.cabal` snippet registers `tasty-autocollect` as a preprocessor, which does one of three things at the very beginning of compilation:--1. If the file contains `{- AUTOCOLLECT.MAIN -}`, find all test modules and generate a main module.-2. If the file contains `{- AUTOCOLLECT.TEST -}`, register the `tasty-autocollect` GHC plugin to rewrite tests (see below).-3. Otherwise, do nothing--In a test file, the plugin will search for any functions named `test`. It will then rename the function to `tasty_test_N`, where `N` is an autoincrementing, unique number. Then it will collect all the tests into a `tasty_tests :: [TestTree]` binding, which is exported at the location of the `{- AUTOCOLLECT.TEST.export -}` comment.--### Configuration--`tasty-autocollect` can be configured by adding `k = v` lines to the same block comment as `AUTOCOLLECT.MAIN`; e.g.--```hs-{- AUTOCOLLECT.MAIN-suite_name = foo--}-```--* `suite_name`: The name to use in the `testGroup` at the root of the test suite `TestTree` (defaults to the path of the main file)--* `group_type`: How the tests should be grouped (defaults to `modules`)-    * `flat`: All the tests are in the same namespace-        ```-        Main.hs-          test 1: OK-          test 2: OK-          test 3: OK-        ```--    * `modules`: Tests are grouped by their module-        ```-        Main.hs-          Test.Module1-            test1: OK-            test2: OK-          Test.Module2-            test3: OK-        ```--    * `tree`: Tests are grouped by their module, which is broken out into a tree-        ```-        Main.hs-          Test-            Module1-              test1: OK-              test2: OK-            Module2-              test3: OK-        ```--* `strip_suffix`: The suffix to strip from a test module, e.g. `strip_suffix = Test` will relabel a `Foo.BarTest` module to `Foo.Bar`--* `ingredients`: A comma-separated list of extra tasty ingredients to include, e.g.--    ```-    ingredients = SomeLibrary.ingredient1, SomeLibrary.ingredient2-    ```--* `ingredients_override`: By default, `ingredients` will add the ingredients in front of the default `tasty` ingredients. When `true`, does not automatically include the default `tasty` ingredients, for complete control over the ingredient order.--### Notes--* If you're using a formatter like Ormolu/Fourmolu, use `-- $AUTOCOLLECT.TEST.export$` instead; otherwise, the formatter will move it out of the export list.-    * This works around the issue by reusing Haddock's named section syntax, but it shouldn't be an issue because you shouldn't be building Haddocks for test modules. If this becomes a problem for you, please open an issue.-    * Upstream ticket: https://github.com/tweag/ormolu/issues/906- ## Features  In addition to automatically collecting tests, this library also provides some additional functionality out-of-the-box, to make writing + managing tests a seamless experience.@@ -189,7 +120,7 @@  ```hs test_batch =-  [ testCase ("test #" ++ show x) $ return ()+  [ testCase ("test #" ++ show x) $ pure ()   | x <- [1, 5, 10 :: Int]   ] ```@@ -197,11 +128,11 @@ is equivalent to writing:  ```hs-test = testCase "test #1" $ return ()+test = testCase "test #1" $ pure () -test = testCase "test #5" $ return ()+test = testCase "test #5" $ pure () -test = testCase "test #10" $ return ()+test = testCase "test #10" $ pure () ```  ### Integration with `tasty-expected-failures`@@ -239,6 +170,72 @@ test_prop_expectFailBecause "Issue #123" "some property" x = x === x ``` +## Configuration++`tasty-autocollect` can be configured by adding `k = v` lines to the same block comment as `AUTOCOLLECT.MAIN`; e.g.++```hs+{- AUTOCOLLECT.MAIN+suite_name = foo++# comments can start with a hash symbol+group_type = flat+-}+```++* `import`: A comma separated list of files (relative to the Main file) containing configuration to import+    * Recommended file extension: `.conf`+    * Configuration in files later in the list override configuration in files earlier in the list+    * Configuration in the Main file override imported configuration++* `suite_name`: The name to use in the `testGroup` at the root of the test suite `TestTree` (defaults to the path of the main file)++* `group_type`: How the tests should be grouped (defaults to `modules`)+    * `flat`: All the tests are in the same namespace+        ```+        Main.hs+          test 1: OK+          test 2: OK+          test 3: OK+        ```++    * `modules`: Tests are grouped by their module+        ```+        Main.hs+          Test.Module1+            test1: OK+            test2: OK+          Test.Module2+            test3: OK+        ```++    * `tree`: Tests are grouped by their module, which is broken out into a tree+        ```+        Main.hs+          Test+            Module1+              test1: OK+              test2: OK+            Module2+              test3: OK+        ```++* `strip_suffix`: The suffix to strip from a test module, e.g. `strip_suffix = Test` will relabel a `Foo.BarTest` module to `Foo.Bar`++* `ingredients`: A comma-separated list of extra tasty ingredients to include, e.g.++    ```+    ingredients = SomeLibrary.ingredient1, SomeLibrary.ingredient2+    ```++* `ingredients_override`: By default, `ingredients` will add the ingredients in front of the default `tasty` ingredients. When `true`, does not automatically include the default `tasty` ingredients, for complete control over the ingredient order.++* `custom_main`: If you'd like fine-grained control over how the `Main` module is generated (e.g. if you're using `NoImplicitPrelude` or custom preludes), set this to `true`. When set, it will do the following replacements:+    * `{- AUTOCOLLECT.MAIN.imports -}`: replaced with the import lines needed for the tests.+    * `{- AUTOCOLLECT.MAIN.tests -}`: replaced with the `[TestTree]` list, all on one line. If you're using a formatter or linter, it might be helpful to do `tests = id {- AUTOCOLLECT.MAIN.tests -}` so that the code still parses.++    Due to current limitations, the above comments need to be matched exactly (e.g. not with `--` comments or extra whitespace).+ ## Comparison with `tasty-discover`  Advantages:@@ -255,3 +252,29 @@ Disadvantages: * Uses both a preprocessor and a plugin (`tasty-discover` only uses a preprocessor)     * Haven't tested performance yet, but I wouldn't be surprised if there's a non-negligible performance cost++## Appendix++### Debugging ++To inspect the generated Main module, build with `-keep-tmp-files`, and look in `$TMPDIR` for a `ghc_1.hspp` file. (Upstream tickets: [GHC](https://gitlab.haskell.org/ghc/ghc/-/issues/22258), [Stack](https://github.com/commercialhaskell/stack/issues/5892))++To inspect the converted test modules, build with `-ddump-rn -ddump-to-file` and look for the `.dump-rn` files in `.stack-work/` or `dist-newstyle/`.++### Note for Ormolu/Fourmolu++If you're using Ormolu or Fourmolu, use `-- $AUTOCOLLECT.TEST.export$` instead; otherwise, the comment will be moved out of the export list.++This works around the issue by reusing Haddock's named section syntax, but it shouldn't be an issue because you shouldn't be building Haddocks for test modules. If this becomes a problem for you, please open an issue.++Upstream ticket: https://github.com/tweag/ormolu/issues/906++### How it works++The `package.yaml`/`.cabal` snippet registers `tasty-autocollect` as a preprocessor, which does one of three things at the very beginning of compilation:++1. If the file contains `{- AUTOCOLLECT.MAIN -}`, find all test modules and generate a main module.+2. If the file contains `{- AUTOCOLLECT.TEST -}`, register the `tasty-autocollect` GHC plugin to rewrite tests (see below).+3. Otherwise, do nothing++In a test file, the plugin will search for any functions named `test`. It will then rename the function to `tasty_test_N`, where `N` is an autoincrementing, unique number. Then it will collect all the tests into a `tasty_tests :: [TestTree]` binding, which is exported at the location of the `{- AUTOCOLLECT.TEST.export -}` comment.
exe/Preprocessor.hs view
@@ -1,6 +1,6 @@ {-# LANGUAGE LambdaCase #-} -{- |+{-| A preprocessor that registers tasty-autocollect in a test suite.  We need to use a preprocessor for Main.hs because GHC plugins don't
src/Test/Tasty/AutoCollect.hs view
@@ -7,6 +7,7 @@ import Data.Text (Text) import qualified Data.Text as Text +import Test.Tasty.AutoCollect.Config import Test.Tasty.AutoCollect.GenerateMain import Test.Tasty.AutoCollect.ModuleType import Test.Tasty.AutoCollect.Utils.Text@@ -15,7 +16,9 @@ processFile :: FilePath -> Text -> IO Text processFile path file =   case parseModuleType file of-    Just (ModuleMain cfg) -> addLinePragma <$> generateMainModule cfg path+    Just (ModuleMain cfg) -> do+      cfg' <- resolveConfig path cfg+      addLinePragma <$> generateMainModule cfg' path file     Just ModuleTest ->       pure         . addLine "{-# OPTIONS_GHC -fplugin=Test.Tasty.AutoCollect.ConvertTest #-}"
src/Test/Tasty/AutoCollect/Config.hs view
@@ -1,36 +1,63 @@+{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE ViewPatterns #-}  module Test.Tasty.AutoCollect.Config (-  AutoCollectConfig (..),+  AutoCollectConfig' (..),+  AutoCollectConfig,+  AutoCollectConfigPartial,   AutoCollectGroupType (..),-  defaultConfig,   parseConfig,+  resolveConfig, ) where +import Control.Applicative ((<|>))+import Control.Monad (forM)+import Data.Functor.Identity (Identity)+import Data.Maybe (fromMaybe) import Data.Text (Text) import qualified Data.Text as Text+import qualified Data.Text.IO as Text+import System.FilePath (takeDirectory, (</>))  {----- Configuration -----} +type family Apply f a where+  Apply Maybe a = Maybe a+  Apply Identity a = a+ -- | Configuration for generating the Main module, specified as a block comment.-data AutoCollectConfig = AutoCollectConfig-  { cfgSuiteName :: Maybe Text+data AutoCollectConfig' f = AutoCollectConfig+  { cfgImports :: Apply f [FilePath]+  -- ^ Files to import+  , cfgSuiteName :: Apply f (Maybe Text)   -- ^ The name of the entire test suite-  , cfgGroupType :: AutoCollectGroupType+  , cfgGroupType :: Apply f AutoCollectGroupType   -- ^ How tests should be grouped (defaults to "modules")-  , cfgStripSuffix :: Text+  , cfgStripSuffix :: Apply f Text   -- ^ The suffix to strip from a test, e.g. @strip_suffix = Test@ will relabel   -- a module @Foo.BarTest@ to @Foo.Bar@.-  , cfgIngredients :: [Text]+  , cfgIngredients :: Apply f [Text]   -- ^ A comma-separated list of extra tasty ingredients to include-  , cfgIngredientsOverride :: Bool+  , cfgIngredientsOverride :: Apply f Bool   -- ^ If true, 'cfgIngredients' overrides the default tasty ingredients;   -- otherwise, they're prepended to the list of default ingredients (defaults to false)+  , cfgCustomMain :: Apply f Bool   }-  deriving (Show, Eq) +type AutoCollectConfigPartial = AutoCollectConfig' Maybe+deriving instance Show AutoCollectConfigPartial+deriving instance Eq AutoCollectConfigPartial++type AutoCollectConfig = AutoCollectConfig' Identity+deriving instance Show AutoCollectConfig+deriving instance Eq AutoCollectConfig+ data AutoCollectGroupType   = -- | All tests will be flattened like     --@@ -63,22 +90,38 @@     AutoCollectGroupTree   deriving (Show, Eq) -defaultConfig :: AutoCollectConfig-defaultConfig =-  AutoCollectConfig-    { cfgSuiteName = Nothing-    , cfgGroupType = AutoCollectGroupModules-    , cfgIngredients = []-    , cfgIngredientsOverride = False-    , cfgStripSuffix = ""-    }+-- | Config on RHS overrides config on LHS.+instance Semigroup AutoCollectConfigPartial where+  cfg1 <> cfg2 =+    AutoCollectConfig+      { cfgImports = cfgImports cfg2 <|> cfgImports cfg1+      , cfgSuiteName = cfgSuiteName cfg2 <|> cfgSuiteName cfg1+      , cfgGroupType = cfgGroupType cfg2 <|> cfgGroupType cfg1+      , cfgIngredients = cfgIngredients cfg2 <|> cfgIngredients cfg1+      , cfgIngredientsOverride = cfgIngredientsOverride cfg2 <|> cfgIngredientsOverride cfg1+      , cfgStripSuffix = cfgStripSuffix cfg2 <|> cfgStripSuffix cfg1+      , cfgCustomMain = cfgCustomMain cfg2 <|> cfgCustomMain cfg1+      } -parseConfig :: Text -> Either Text AutoCollectConfig-parseConfig = fmap resolve . mapM parseLine . filter (not . isIgnoredLine) . Text.lines+instance Monoid AutoCollectConfigPartial where+  mempty =+    AutoCollectConfig+      Nothing+      Nothing+      Nothing+      Nothing+      Nothing+      Nothing+      Nothing++{----- Parsing -----}++parseConfig :: Text -> Either Text AutoCollectConfigPartial+parseConfig = fmap mconcat . mapM parseLine . filter (not . isIgnoredLine) . Text.lines   where     isIgnoredLine s = Text.null (Text.strip s) || ("#" `Text.isPrefixOf` s) -    parseLine :: Text -> Either Text (AutoCollectConfig -> AutoCollectConfig)+    parseLine :: Text -> Either Text AutoCollectConfigPartial     parseLine s = do       (k, v) <-         case Text.splitOn "=" s of@@ -89,23 +132,25 @@           _ -> Left $ "Invalid configuration line: " <> Text.pack (show s)        case k of+        "import" ->+          pure mempty{cfgImports = Just $ map Text.unpack $ parseCSV v}         "suite_name" ->-          pure $ \cfg -> cfg{cfgSuiteName = Just v}+          pure mempty{cfgSuiteName = Just (Just v)}         "group_type" -> do           groupType <- parseGroupType v-          pure $ \cfg -> cfg{cfgGroupType = groupType}+          pure mempty{cfgGroupType = Just groupType}         "strip_suffix" ->-          pure $ \cfg -> cfg{cfgStripSuffix = v}-        "ingredients" -> do-          let ingredients = map Text.strip . Text.splitOn "," $ v-          pure $ \cfg -> cfg{cfgIngredients = ingredients}+          pure mempty{cfgStripSuffix = Just v}+        "ingredients" ->+          pure mempty{cfgIngredients = Just $ parseCSV v}         "ingredients_override" -> do           override <- parseBool v-          pure $ \cfg -> cfg{cfgIngredientsOverride = override}+          pure mempty{cfgIngredientsOverride = Just override}+        "custom_main" -> do+          customMain <- parseBool v+          pure mempty{cfgCustomMain = Just customMain}         _ -> Left $ "Invalid configuration key: " <> Text.pack (show k) -    resolve fs = compose fs defaultConfig- parseGroupType :: Text -> Either Text AutoCollectGroupType parseGroupType = \case   "flat" -> pure AutoCollectGroupFlat@@ -113,6 +158,9 @@   "tree" -> pure AutoCollectGroupTree   ty -> Left $ "Invalid group_type: " <> Text.pack (show ty) +parseCSV :: Text -> [Text]+parseCSV = map Text.strip . Text.splitOn ","+ parseBool :: Text -> Either Text Bool parseBool s =   case Text.toLower s of@@ -120,8 +168,30 @@     "false" -> pure False     _ -> Left $ "Invalid bool: " <> Text.pack (show s) -{----- Utilities -----}+{----- Resolving -----} --- | [f, g, h] => (h . g . f)-compose :: [a -> a] -> a -> a-compose fs = foldr (\f acc -> acc . f) id fs+resolveConfig :: FilePath -> AutoCollectConfigPartial -> IO AutoCollectConfig+resolveConfig path0 cfg0 = resolve <$> resolveImports path0 cfg0+  where+    resolveImports path cfg = do+      let imports = fromMaybe [] $ cfgImports cfg+      fmap (mergeConfigs cfg) . forM imports $ \imp -> do+        let fp = takeDirectory path </> imp+        file <- Text.readFile fp+        case parseConfig file of+          Right cfg' -> resolveImports fp cfg'+          Left e -> errorWithoutStackTrace $ "Could not parse imported config (" <> fp <> "): " <> Text.unpack e++    mergeConfigs cfg importedCfgs = mconcat importedCfgs <> cfg++    resolve :: AutoCollectConfigPartial -> AutoCollectConfig+    resolve AutoCollectConfig{..} =+      AutoCollectConfig+        { cfgImports = []+        , cfgSuiteName = fromMaybe Nothing cfgSuiteName+        , cfgGroupType = fromMaybe AutoCollectGroupModules cfgGroupType+        , cfgIngredients = fromMaybe [] cfgIngredients+        , cfgIngredientsOverride = fromMaybe False cfgIngredientsOverride+        , cfgStripSuffix = fromMaybe "" cfgStripSuffix+        , cfgCustomMain = fromMaybe False cfgCustomMain+        }
src/Test/Tasty/AutoCollect/ConvertTest.hs view
@@ -36,41 +36,39 @@           pure $ withParsedResultModule result (transformTestModule names)       } -{- |-Transforms a test module of the form--@-{\- AUTOCOLLECT.TEST -\}-module MyTest (-  foo,-  {\- AUTOCOLLECT.TEST.export -\}-  bar,-) where--test = ...-@--to the equivalent of--@-module MyTest (-  foo,-  tasty_tests,-  bar,-) where--tasty_tests :: [TestTree]-tasty_tests = [tasty_test_1]--tasty_test_1 :: TestTree-tasty_test_1 = ...-@--}+-- | Transforms a test module of the form+--+-- @+-- {\- AUTOCOLLECT.TEST -\}+-- module MyTest (+--   foo,+--   {\- AUTOCOLLECT.TEST.export -\}+--   bar,+-- ) where+--+-- test = ...+-- @+--+-- to the equivalent of+--+-- @+-- module MyTest (+--   foo,+--   tasty_tests,+--   bar,+-- ) where+--+-- tasty_tests :: [TestTree]+-- tasty_tests = [tasty_test_1]+--+-- tasty_test_1 :: TestTree+-- tasty_test_1 = ...+-- @ transformTestModule :: ExternalNames -> HsParsedModule -> HsParsedModule transformTestModule names parsedModl = parsedModl{hpm_module = updateModule <$> hpm_module parsedModl}   where     updateModule modl =-      let (decls, testNames) = runConvertTestM $ concatMapM (convertTest names) $ hsmodDecls modl+      let (decls, testNames) = runConvertTestModuleM $ concatMapM (convertTest names) $ hsmodDecls modl        in modl             { hsmodExports = updateExports <$> hsmodExports modl             , hsmodDecls = mkTestsList testNames ++ decls@@ -101,11 +99,9 @@         mkExprTypeSig testsList . genLoc $           HsListTy noAnn (getListOfTestTreeType names) -{- |-If the given declaration is a test, return the converted test, or otherwise-return it unmodified--}-convertTest :: ExternalNames -> LHsDecl GhcPs -> ConvertTestM [LHsDecl GhcPs]+-- | If the given declaration is a test, return the converted test, or otherwise+-- return it unmodified+convertTest :: ExternalNames -> LHsDecl GhcPs -> ConvertTestModuleM [LHsDecl GhcPs] convertTest names ldecl =   case parseDecl ldecl of     Just (FuncSig [funcName] ty)@@ -142,9 +138,21 @@             | testType == testTypeFromSig -> pure (testName, Just signatureType)             | otherwise -> autocollectError $ "Found test with different type of signature: " ++ show (testType, testTypeFromSig) -      testBody <-+      (testBody, ConvertTestState{mWhereClause}) <-         case funcDefGuards of-          [FuncGuardedBody [] body] -> convertSingleTestBody testType mSigType funcDefArgs body+          [FuncGuardedBody [] body] -> do+            let state =+                  ConvertTestState+                    { mSigType+                    , testArgs = funcDefArgs+                    , mWhereClause = Just funcDefWhereClause+                    }+            pure . runConvertTestM state $ do+              testBody <- convertSingleTestBody testType body+              State.gets testArgs >>= \case+                [] -> pure ()+                _ -> autocollectError $ "Found extraneous arguments at " ++ getSpanLine loc+              pure testBody           _ ->             autocollectError . unlines $               [ "Test should have no guards."@@ -155,53 +163,57 @@         [ if isNothing mSigInfo             then [genLoc $ genFuncSig testName (getListOfTestTreeType names)]             else []-        , [genFuncDecl testName [] testBody (Just funcDefWhereClause) <$ ldecl]+        , [genFuncDecl testName [] testBody mWhereClause <$ ldecl]         ] -    convertSingleTestBody testType mSigType args body =+    convertSingleTestBody testType body =       case testType of-        TestNormal -> do-          checkNoArgs testType args+        TestNormal ->           pure $ singleExpr body         TestProp -> do+          -- test_prop :: <type>+          -- test_prop "name" arg1 arg2 = <body> where <defs>+          -- ====>+          -- test = testProperty "name" ((\arg1 arg2 -> let <defs> in <body>) :: <type>)++          state@ConvertTestState{mSigType, mWhereClause} <- State.get+          State.put state{mSigType = Nothing, mWhereClause = Nothing}+           (name, remainingPats) <--            case args of-              arg : rest | Just s <- parseLitStrPat arg -> return (s, rest)+            popRemainingArgs >>= \case+              arg : rest | Just s <- parseLitStrPat arg -> pure (s, rest)               [] -> autocollectError "test_prop requires at least the name of the test"               arg : _ ->                 autocollectError . unlines $                   [ "test_prop expected a String for the name of the test."                   , "Got: " ++ showPpr arg                   ]-          let propBody = mkHsLam remainingPats body++          let propBody =+                mkHsLam remainingPats $+                  case mWhereClause of+                    Just defs -> genLoc $ mkLet defs body+                    Nothing -> body+           pure . singleExpr $             mkHsApps               (lhsvar $ mkLRdrName "testProperty")               [ mkHsLitString name               , maybe propBody (genLoc . ExprWithTySig noAnn propBody) mSigType               ]-        TestTodo -> do-          checkNoArgs testType args+        TestTodo ->           pure . singleExpr $             mkHsApp               (mkHsVar $ name_testTreeTodo names)               (mkExprTypeSig body $ mkHsTyVar (name_String names))-        TestBatch -> do-          checkNoArgs testType args+        TestBatch ->           pure body         TestModify modifier testType' ->-          withTestModifier names modifier loc args $ \args' ->-            convertSingleTestBody testType' mSigType args' body+          withTestModifier names modifier loc $+            convertSingleTestBody testType' body      singleExpr = genLoc . mkExplicitList . (: []) -    checkNoArgs testType args =-      unless (null args) $-        autocollectError . unwords $-          [ showTestType testType ++ " should not be used with arguments"-          , "(at " ++ getSpanLine loc ++ ")"-          ]- -- | Identifier for the generated `tests` list. testListName :: LocatedN RdrName testListName = mkLRdrName testListIdentifier@@ -243,20 +255,6 @@      unsnoc = fmap (NonEmpty.init &&& NonEmpty.last) . NonEmpty.nonEmpty -showTestType :: TestType -> String-showTestType = \case-  TestNormal -> "test"-  TestProp -> "test_prop"-  TestTodo -> "test_todo"-  TestBatch -> "test_batch"-  TestModify modifier tt -> showTestType tt ++ showModifier modifier-  where-    showModifier = \case-      ExpectFail -> "_expectFail"-      ExpectFailBecause -> "_expectFailBecause"-      IgnoreTest -> "_ignoreTest"-      IgnoreTestBecause -> "_ignoreTestBecause"- isValidForTestType :: ExternalNames -> TestType -> LHsSigWcType GhcPs -> Bool isValidForTestType names = \case   TestNormal -> parsedTypeMatches isTestTreeTypeVar@@ -296,36 +294,34 @@   _ -> False  withTestModifier ::-  Monad m =>-  ExternalNames ->-  TestModifier ->-  SrcSpan ->-  [LPat GhcPs] ->-  ([LPat GhcPs] -> m (LHsExpr GhcPs)) ->-  m (LHsExpr GhcPs)-withTestModifier names modifier loc args f =+  ExternalNames+  -> TestModifier+  -> SrcSpan+  -> ConvertTestM (LHsExpr GhcPs)+  -> ConvertTestM (LHsExpr GhcPs)+withTestModifier names modifier loc m =   case modifier of-    ExpectFail -> mapAllTests (mkHsVar $ name_expectFail names) <$> f args+    ExpectFail -> mapAllTests (mkHsVar $ name_expectFail names) <$> m     ExpectFailBecause ->-      case args of-        arg : rest+      popArg >>= \case+        Just arg           | Just s <- parseLitStrPat arg ->-              mapAllTests (applyName (name_expectFailBecause names) [mkHsLitString s]) <$> f rest-        _ -> needsStrArg "_expectFailBecause"-    IgnoreTest -> mapAllTests (mkHsVar $ name_ignoreTest names) <$> f args+              mapAllTests (applyName (name_expectFailBecause names) [mkHsLitString s]) <$> m+        mArg -> needsStrArg mArg "_expectFailBecause"+    IgnoreTest -> mapAllTests (mkHsVar $ name_ignoreTest names) <$> m     IgnoreTestBecause ->-      case args of-        arg : rest+      popArg >>= \case+        Just arg           | Just s <- parseLitStrPat arg ->-              mapAllTests (applyName (name_ignoreTestBecause names) [mkHsLitString s]) <$> f rest-        _ -> needsStrArg "_ignoreTestBecause"+              mapAllTests (applyName (name_ignoreTestBecause names) [mkHsLitString s]) <$> m+        mArg -> needsStrArg mArg "_ignoreTestBecause"   where-    needsStrArg label =+    needsStrArg mArg label =       autocollectError . unlines . concat $         [ [label ++ " requires a String argument."]-        , case args of-            [] -> []-            arg : _ -> ["Got: " ++ showPpr arg]+        , case mArg of+            Nothing -> []+            Just arg -> ["Got: " ++ showPpr arg]         , ["At: " ++ getSpanLine loc]         ] @@ -334,11 +330,40 @@     -- mapAllTests f e = [| map $f $e |]     mapAllTests func expr = applyName (name_map names) [func, expr] -{----- Test converter monad -----}+{----- Test function converter monad -----}  type ConvertTestM = State ConvertTestState  data ConvertTestState = ConvertTestState+  { mSigType :: Maybe (LHsSigWcType GhcPs)+  , mWhereClause :: Maybe (HsLocalBinds GhcPs)+  , testArgs :: [LPat GhcPs]+  }++runConvertTestM :: ConvertTestState -> ConvertTestM a -> (a, ConvertTestState)+runConvertTestM = flip State.runState++popArg :: ConvertTestM (Maybe (LPat GhcPs))+popArg = do+  state <- State.get+  let (mArg, rest) =+        case testArgs state of+          [] -> (Nothing, [])+          arg : args -> (Just arg, args)+  State.put state{testArgs = rest}+  pure mArg++popRemainingArgs :: ConvertTestM [LPat GhcPs]+popRemainingArgs = do+  state@ConvertTestState{testArgs} <- State.get+  State.put state{testArgs = []}+  pure testArgs++{----- Test module converter monad -----}++type ConvertTestModuleM = State ConvertTestModuleState++data ConvertTestModuleState = ConvertTestModuleState   { lastSeenSig :: Maybe SigInfo   , allTests :: Seq (LocatedN RdrName)   }@@ -352,26 +377,26 @@   -- ^ The type captured in the signature   } -runConvertTestM :: ConvertTestM a -> (a, [LocatedN RdrName])-runConvertTestM m =+runConvertTestModuleM :: ConvertTestModuleM a -> (a, [LocatedN RdrName])+runConvertTestModuleM m =   fmap (toList . allTests) . State.runState m $-    ConvertTestState+    ConvertTestModuleState       { lastSeenSig = Nothing       , allTests = Seq.Empty       } -getLastSeenSig :: ConvertTestM (Maybe SigInfo)+getLastSeenSig :: ConvertTestModuleM (Maybe SigInfo) getLastSeenSig = do-  state@ConvertTestState{lastSeenSig} <- State.get+  state@ConvertTestModuleState{lastSeenSig} <- State.get   State.put state{lastSeenSig = Nothing}   pure lastSeenSig -setLastSeenSig :: SigInfo -> ConvertTestM ()+setLastSeenSig :: SigInfo -> ConvertTestModuleM () setLastSeenSig info = State.modify' $ \state -> state{lastSeenSig = Just info} -getNextTestName :: ConvertTestM (LocatedN RdrName)+getNextTestName :: ConvertTestModuleM (LocatedN RdrName) getNextTestName = do-  state@ConvertTestState{allTests} <- State.get+  state@ConvertTestModuleState{allTests} <- State.get   let nextTestName = mkLRdrName $ testIdentifier (length allTests)   State.put state{allTests = allTests Seq.|> nextTestName}   pure nextTestName
src/Test/Tasty/AutoCollect/ExternalNames.hs view
@@ -40,4 +40,4 @@   where     loadName name =       thNameToGhcNameIO env (hsc_NC env) name-        >>= maybe (autocollectError $ "Could not get Name for " ++ show name) return+        >>= maybe (autocollectError $ "Could not get Name for " ++ show name) pure
src/Test/Tasty/AutoCollect/GHC.hs view
@@ -36,6 +36,7 @@ import Data.Foldable (foldl') import Data.List (sortOn) import Data.Maybe (fromMaybe, listToMaybe, mapMaybe)+import qualified GHC.Types.Name.Occurrence as NameSpace (tcName, varName)  import Test.Tasty.AutoCollect.GHC.Shim @@ -104,19 +105,19 @@ getSpanLine :: SrcSpan -> String getSpanLine loc =   case srcSpanStart loc of-    Right srcLoc -> "line " ++ show (srcLocLine srcLoc)-    Left s -> s+    RealSrcLoc srcLoc _ -> "line " ++ show (srcLocLine srcLoc)+    UnhelpfulLoc s -> unpackFS s  {----- Name utilities -----}  mkRdrName :: String -> RdrName-mkRdrName = mkRdrUnqual . mkOccNameVar+mkRdrName = mkRdrUnqual . mkOccName NameSpace.varName  mkLRdrName :: String -> LocatedN RdrName mkLRdrName = genLoc . mkRdrName  mkRdrNameType :: String -> RdrName-mkRdrNameType = mkRdrUnqual . mkOccNameTC+mkRdrNameType = mkRdrUnqual . mkOccName NameSpace.tcName  mkLRdrNameType :: String -> LocatedN RdrName mkLRdrNameType = genLoc . mkRdrNameType
src/Test/Tasty/AutoCollect/GHC/Shim.hs view
@@ -4,9 +4,7 @@  -- GHC-specific shims import Test.Tasty.AutoCollect.GHC.Shim_Common as X-#if __GLASGOW_HASKELL__ == 810-import Test.Tasty.AutoCollect.GHC.Shim_8_10 as X-#elif __GLASGOW_HASKELL__ == 900+#if __GLASGOW_HASKELL__ == 900 import Test.Tasty.AutoCollect.GHC.Shim_9_0 as X #elif __GLASGOW_HASKELL__ == 902 import Test.Tasty.AutoCollect.GHC.Shim_9_2 as X
− src/Test/Tasty/AutoCollect/GHC/Shim_8_10.hs
@@ -1,229 +0,0 @@-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE NamedFieldPuns #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}--module Test.Tasty.AutoCollect.GHC.Shim_8_10 (-  -- * Re-exports-  module X,--  -- * Compat--  -- ** Plugin-  setKeepRawTokenStream,-  withParsedResultModule,--  -- ** Annotations-  getExportComments,-  generatedSrcAnn,-  toSrcAnnA,--  -- ** SrcSpan-  srcSpanStart,--  -- ** OccName-  mkOccNameVar,-  mkOccNameTC,--  -- ** Decl-  parseDecl,--  -- ** Type-  parseSigWcType,-  parseType,--  -- ** Expr-  mkExplicitList,-  mkExplicitTuple,-  xAppTypeE,--  -- * Backports-  SrcAnn,-  SrcSpanAnn',-  LocatedN,-  unLoc,-  getLoc,-  getLocA,-  mkHsApps,-  mkMatch,-  noAnn,-  hsTypeToHsSigType,-  hsTypeToHsSigWcType,-  thNameToGhcNameIO,-) where---- Re-exports-import ApiAnnotation as X (AnnotationComment (..))-import GHC.Hs as X hiding (mkHsAppType, mkHsAppTypes, mkMatch)-import GhcPlugins as X hiding (getHscEnv, getLoc, showPpr, srcSpanStart, unLoc)-import HscMain as X (getHscEnv)-import NameCache as X (NameCache)--import ApiAnnotation (getAnnotationComments)-import Data.Foldable (foldl')-import Data.IORef (IORef)-import Data.Maybe (mapMaybe)-import qualified Data.Text as Text-import qualified GHC.Hs.Utils as GHC (mkMatch)-import qualified Language.Haskell.TH as TH-import qualified OccName as NameSpace (tcName, varName)-import qualified SrcLoc as GHC (srcSpanStart)--import Test.Tasty.AutoCollect.GHC.Shim_Common-import Test.Tasty.AutoCollect.Utils.Text--{----- Compat / Plugin -----}--setKeepRawTokenStream :: Plugin -> Plugin-setKeepRawTokenStream plugin =-  plugin-    { dynflagsPlugin = \_ df ->-        pure $ df `gopt_set` Opt_KeepRawTokenStream-    }--withParsedResultModule :: HsParsedModule -> (HsParsedModule -> HsParsedModule) -> HsParsedModule-withParsedResultModule = flip ($)--{----- Compat / Annotations -----}---- | Get the contents of all comments in the given hsmodExports list.-getExportComments :: HsParsedModule -> Located [LIE GhcPs] -> [RealLocated String]-getExportComments parsedModl = map fromRLAnnotationComment . getCommentsAt . getLoc-  where-    getCommentsAt = mapMaybe toRealLocated . getAnnotationComments (hpm_annotations parsedModl)-    toRealLocated = \case-      L (RealSrcSpan l) e -> Just (L l e)-      L (UnhelpfulSpan _) _ -> Nothing-    fromRLAnnotationComment (L rss comment) =-      L rss $ (Text.unpack . Text.strip . unwrap) comment-    unwrap = \case-      AnnDocCommentNext s -> withoutPrefix "-- |" $ Text.pack s-      AnnDocCommentPrev s -> withoutPrefix "-- ^" $ Text.pack s-      AnnDocCommentNamed s -> withoutPrefix "-- $" $ Text.pack s-      AnnDocSection _ s -> Text.pack s-      AnnDocOptions s -> Text.pack s-      AnnLineComment s -> withoutPrefix "--" $ Text.pack s-      AnnBlockComment s -> withoutPrefix "{-" . withoutSuffix "-}" $ Text.pack s--generatedSrcAnn :: SrcSpan-generatedSrcAnn = UnhelpfulSpan (fsLit "<generated>")--toSrcAnnA :: RealSrcSpan -> SrcSpan-toSrcAnnA = RealSrcSpan--{----- Compat / SrcSpan -----}--srcSpanStart :: SrcSpan -> Either String RealSrcLoc-srcSpanStart ss =-  case GHC.srcSpanStart ss of-    RealSrcLoc srcLoc -> Right srcLoc-    UnhelpfulLoc s -> Left $ unpackFS s--{----- Compat / OccName -----}--mkOccNameVar :: String -> OccName-mkOccNameVar = mkOccName NameSpace.varName--mkOccNameTC :: String -> OccName-mkOccNameTC = mkOccName NameSpace.tcName--{----- Compat / Decl -----}--parseDecl :: LHsDecl GhcPs -> Maybe ParsedDecl-parseDecl (L _ decl) =-  case decl of-    SigD _ (TypeSig _ names ty) -> Just $ FuncSig names ty-    ValD _ (FunBind _ name matchGroup _ _) ->-      Just . FuncDef name $-        case matchGroup of-          MG{mg_alts = L _ matches} -> map (fmap parseFuncSingleDef) matches-          XMatchGroup x -> noExtCon x-    _ -> Nothing-  where-    parseFuncSingleDef = \case-      Match{m_pats, m_grhss = GRHSs _ bodys whereClause} ->-        FuncSingleDef-          { funcDefArgs = m_pats-          , funcDefGuards = map (parseFuncGuardedBody . unLoc) bodys-          , funcDefWhereClause = unLoc whereClause-          }-      Match{m_grhss = XGRHSs x} -> noExtCon x-      XMatch x -> noExtCon x-    parseFuncGuardedBody = \case-      GRHS _ guards body -> FuncGuardedBody guards body-      XGRHS x -> noExtCon x--{----- Compat / Type -----}--parseSigWcType :: LHsSigWcType GhcPs -> Maybe ParsedType-parseSigWcType = \case-  HsWC _ (HsIB _ ltype) -> parseType ltype-  HsWC _ (XHsImplicitBndrs x) -> noExtCon x-  XHsWildCardBndrs x -> noExtCon x--parseType :: LHsType GhcPs -> Maybe ParsedType-parseType (L _ ty) =-  case ty of-    HsTyVar _ flag name -> Just $ TypeVar flag name-    HsListTy _ t -> TypeList <$> parseType t-    _ -> Nothing--{----- Compat / Expr -----}--mkExplicitList :: [LHsExpr GhcPs] -> HsExpr GhcPs-mkExplicitList = ExplicitList noExtField Nothing--mkExplicitTuple :: [HsTupArg GhcPs] -> Boxity -> HsExpr GhcPs-mkExplicitTuple = ExplicitTuple noAnn . map (L generatedSrcAnn)--xAppTypeE :: XAppTypeE GhcPs-xAppTypeE = noExtField--{----- Backports -----}--type SrcAnn ann = SrcSpan-type SrcSpanAnn' a = SrcSpan-type LocatedN = Located--unLoc :: GenLocated l e -> e-unLoc (L _ e) = e--getLoc :: GenLocated l e -> l-getLoc (L l _) = l--getLocA :: Located e -> SrcSpan-getLocA = getLoc--mkHsApps :: LHsExpr GhcPs -> [LHsExpr GhcPs] -> LHsExpr GhcPs-mkHsApps = foldl' mkHsApp--mkMatch :: HsMatchContext RdrName -> [LPat GhcPs] -> LHsExpr GhcPs -> HsLocalBinds GhcPs -> LMatch GhcPs (LHsExpr GhcPs)-mkMatch ctxt pats expr lbinds = GHC.mkMatch ctxt pats expr (L generatedSrcAnn lbinds)--noAnn :: NoExtField-noAnn = NoExtField--hsTypeToHsSigType :: LHsType GhcPs -> LHsSigType GhcPs-hsTypeToHsSigType = mkLHsSigType--hsTypeToHsSigWcType :: LHsType GhcPs -> LHsSigWcType GhcPs-hsTypeToHsSigWcType = mkLHsSigWcType---- https://gitlab.haskell.org/ghc/ghc/-/merge_requests/8492-thNameToGhcNameIO :: HscEnv -> IORef NameCache -> TH.Name -> IO (Maybe Name)-thNameToGhcNameIO hscEnv cache name =-  fmap fst-    . runCoreM-      hscEnv{hsc_NC = cache}-      (unused "cr_rule_base")-      (strict '.')-      (unused "cr_module")-      (strict mempty)-      (unused "cr_print_unqual")-      (unused "cr_loc")-    $ thNameToGhcName name-  where-    unused msg = error $ "unexpectedly used: " ++ msg--    -- marks fields that are strict, so we can't use `unused`-    strict = id
src/Test/Tasty/AutoCollect/GHC/Shim_9_0.hs view
@@ -17,13 +17,6 @@   generatedSrcAnn,   toSrcAnnA, -  -- ** SrcSpan-  srcSpanStart,--  -- ** OccName-  mkOccNameVar,-  mkOccNameTC,-   -- ** Decl   parseDecl, @@ -34,6 +27,7 @@   -- ** Expr   mkExplicitList,   mkExplicitTuple,+  mkLet,   xAppTypeE,    -- * Backports@@ -52,15 +46,13 @@ import GHC.Driver.Main as X (getHscEnv) import GHC.Hs as X hiding (mkHsAppType, mkHsAppTypes, mkMatch) import GHC.Parser.Annotation as X (AnnotationComment (..))-import GHC.Plugins as X hiding (getHscEnv, showPpr, srcSpanStart, varName)+import GHC.Plugins as X hiding (getHscEnv, mkLet, showPpr, varName) import GHC.Types.Name.Cache as X (NameCache)  import Data.IORef (IORef) import qualified Data.Text as Text import qualified GHC.Hs.Utils as GHC (mkMatch) import GHC.Parser.Annotation (getAnnotationComments)-import qualified GHC.Types.Name.Occurrence as NameSpace (tcName, varName)-import qualified GHC.Types.SrcLoc as GHC (srcSpanStart) import qualified Language.Haskell.TH as TH  import Test.Tasty.AutoCollect.GHC.Shim_Common@@ -104,22 +96,6 @@ toSrcAnnA :: RealSrcSpan -> SrcSpan toSrcAnnA x = RealSrcSpan x Nothing -{----- Compat / SrcSpan -----}--srcSpanStart :: SrcSpan -> Either String RealSrcLoc-srcSpanStart ss =-  case GHC.srcSpanStart ss of-    RealSrcLoc srcLoc _ -> Right srcLoc-    UnhelpfulLoc s -> Left $ unpackFS s--{----- Compat / OccName -----}--mkOccNameVar :: String -> OccName-mkOccNameVar = mkOccName NameSpace.varName--mkOccNameTC :: String -> OccName-mkOccNameTC = mkOccName NameSpace.tcName- {----- Compat / Decl -----}  parseDecl :: LHsDecl GhcPs -> Maybe ParsedDecl@@ -158,6 +134,9 @@  mkExplicitTuple :: [HsTupArg GhcPs] -> Boxity -> HsExpr GhcPs mkExplicitTuple = ExplicitTuple noAnn . map (L generatedSrcAnn)++mkLet :: HsLocalBinds GhcPs -> LHsExpr GhcPs -> HsExpr GhcPs+mkLet binds expr = HsLet noExtField (L generatedSrcAnn binds) expr  xAppTypeE :: XAppTypeE GhcPs xAppTypeE = noExtField
src/Test/Tasty/AutoCollect/GHC/Shim_9_2.hs view
@@ -18,13 +18,6 @@   getExportComments,   toSrcAnnA, -  -- ** SrcSpan-  srcSpanStart,--  -- ** OccName-  mkOccNameVar,-  mkOccNameTC,-   -- ** Decl   parseDecl, @@ -35,6 +28,7 @@   -- ** Expr   mkExplicitList,   mkExplicitTuple,+  mkLet,   xAppTypeE,    -- * Backports@@ -44,13 +38,18 @@ -- Re-exports import GHC.Driver.Main as X (getHscEnv) import GHC.Hs as X hiding (comment, mkHsAppType, mkHsAppTypes)-import GHC.Plugins as X hiding (AnnBind (..), AnnExpr' (..), getHscEnv, showPpr, srcSpanStart, varName)+import GHC.Plugins as X hiding (+  AnnBind (..),+  AnnExpr' (..),+  getHscEnv,+  mkLet,+  showPpr,+  varName,+ ) import GHC.Types.Name.Cache as X (NameCache)  import Data.IORef (IORef) import qualified Data.Text as Text-import qualified GHC.Types.Name.Occurrence as NameSpace (tcName, varName)-import qualified GHC.Types.SrcLoc as GHC (srcSpanStart) import qualified Language.Haskell.TH as TH  import Test.Tasty.AutoCollect.GHC.Shim_Common@@ -95,22 +94,6 @@ toSrcAnnA :: RealSrcSpan -> SrcSpanAnnA toSrcAnnA rss = SrcSpanAnn noAnn (RealSrcSpan rss Nothing) -{----- Compat / SrcSpan -----}--srcSpanStart :: SrcSpan -> Either String RealSrcLoc-srcSpanStart ss =-  case GHC.srcSpanStart ss of-    RealSrcLoc srcLoc _ -> Right srcLoc-    UnhelpfulLoc s -> Left $ unpackFS s--{----- Compat / OccName -----}--mkOccNameVar :: String -> OccName-mkOccNameVar = mkOccName NameSpace.varName--mkOccNameTC :: String -> OccName-mkOccNameTC = mkOccName NameSpace.tcName- {----- Compat / Decl -----}  parseDecl :: LHsDecl GhcPs -> Maybe ParsedDecl@@ -149,6 +132,9 @@  mkExplicitTuple :: [HsTupArg GhcPs] -> Boxity -> HsExpr GhcPs mkExplicitTuple = ExplicitTuple noAnn++mkLet :: HsLocalBinds GhcPs -> LHsExpr GhcPs -> HsExpr GhcPs+mkLet binds expr = HsLet noAnn binds expr  xAppTypeE :: XAppTypeE GhcPs xAppTypeE = generatedSrcSpan
src/Test/Tasty/AutoCollect/GHC/Shim_9_4.hs view
@@ -18,13 +18,6 @@   getExportComments,   toSrcAnnA, -  -- ** SrcSpan-  srcSpanStart,--  -- ** OccName-  mkOccNameVar,-  mkOccNameTC,-   -- ** Decl   parseDecl, @@ -35,6 +28,7 @@   -- ** Expr   mkExplicitList,   mkExplicitTuple,+  mkLet,   xAppTypeE,    -- * Backports@@ -48,9 +42,9 @@   AnnBind (..),   AnnExpr' (..),   getHscEnv,+  mkLet,   msg,   showPpr,-  srcSpanStart,   thNameToGhcNameIO,   varName,  )@@ -59,8 +53,6 @@ import qualified Data.Text as Text import qualified GHC.Data.Strict as Strict import qualified GHC.Plugins as GHC (thNameToGhcNameIO)-import qualified GHC.Types.Name.Occurrence as NameSpace (tcName, varName)-import qualified GHC.Types.SrcLoc as GHC (srcSpanStart) import qualified Language.Haskell.TH as TH  import Test.Tasty.AutoCollect.GHC.Shim_Common@@ -102,22 +94,6 @@ toSrcAnnA :: RealSrcSpan -> SrcSpanAnnA toSrcAnnA rss = SrcSpanAnn noAnn (RealSrcSpan rss Strict.Nothing) -{----- Compat / SrcSpan -----}--srcSpanStart :: SrcSpan -> Either String RealSrcLoc-srcSpanStart ss =-  case GHC.srcSpanStart ss of-    RealSrcLoc srcLoc _ -> Right srcLoc-    UnhelpfulLoc s -> Left $ unpackFS s--{----- Compat / OccName -----}--mkOccNameVar :: String -> OccName-mkOccNameVar = mkOccName NameSpace.varName--mkOccNameTC :: String -> OccName-mkOccNameTC = mkOccName NameSpace.tcName- {----- Compat / Decl -----}  parseDecl :: LHsDecl GhcPs -> Maybe ParsedDecl@@ -156,6 +132,9 @@  mkExplicitTuple :: [HsTupArg GhcPs] -> Boxity -> HsExpr GhcPs mkExplicitTuple = ExplicitTuple noAnn++mkLet :: HsLocalBinds GhcPs -> LHsExpr GhcPs -> HsExpr GhcPs+mkLet binds expr = HsLet noAnn (L NoTokenLoc HsTok) binds (L NoTokenLoc HsTok) expr  xAppTypeE :: XAppTypeE GhcPs xAppTypeE = generatedSrcSpan
src/Test/Tasty/AutoCollect/GenerateMain.hs view
@@ -6,7 +6,9 @@   generateMainModule, ) where +import Control.Monad (guard) import qualified Data.ByteString as ByteString+import Data.Char (isDigit, isLower, isUpper) import Data.List (sortOn) import qualified Data.Map.Strict as Map import Data.Maybe (catMaybes, fromMaybe)@@ -23,23 +25,37 @@ import Test.Tasty.AutoCollect.Utils.Text import qualified Test.Tasty.AutoCollect.Utils.TreeMap as TreeMap -generateMainModule :: AutoCollectConfig -> FilePath -> IO Text-generateMainModule cfg@AutoCollectConfig{..} path = do+generateMainModule :: AutoCollectConfig -> FilePath -> Text -> IO Text+generateMainModule cfg path originalMain = do   testModules <- sortOn displayName <$> findTestModules cfg path-  pure . Text.unlines $+  let importLines = map ("import qualified " <>) $ map moduleName testModules+      tests = generateTests cfg testModules+  pure $+    if cfgCustomMain cfg+      then rewriteMain importLines tests originalMain+      else mkMainModule cfg path importLines tests++rewriteMain :: [Text] -> Text -> Text -> Text+rewriteMain importLines tests =+  Text.replace "{- AUTOCOLLECT.MAIN.imports -}" (Text.unlines importLines)+    . Text.replace "{- AUTOCOLLECT.MAIN.tests -}" tests++mkMainModule :: AutoCollectConfig -> FilePath -> [Text] -> Text -> Text+mkMainModule AutoCollectConfig{..} path importLines tests =+  Text.unlines     [ "{-# OPTIONS_GHC -w #-}"     , ""     , "module Main (main) where"     , ""     , "import Test.Tasty"-    , Text.unlines . map ("import qualified " <>) $ map moduleName testModules ++ ingredientsModules+    , Text.unlines $ importLines ++ map ("import qualified " <>) ingredientsModules     , ""     , "main :: IO ()"     , "main = defaultMainWithIngredients ingredients (testGroup suiteName tests)"     , "  where"     , "    ingredients = " <> ingredients     , "    suiteName = " <> suiteName-    , "    tests = " <> generateTests cfg testModules+    , "    tests = " <> tests     ]   where     ingredients =@@ -65,12 +81,10 @@   -- ^ The module name to display   } -{- |-Find all test modules using the given path to the Main module.-->>> findTestModules "test/Main.hs"-["My.Module.Test1", "My.Module.Test2", ...]--}+-- | Find all test modules using the given path to the Main module.+--+-- >>> findTestModules "test/Main.hs"+-- ["My.Module.Test1", "My.Module.Test2", ...] findTestModules :: AutoCollectConfig -> FilePath -> IO [TestModule] findTestModules cfg path = listDirectoryRecursive testDir >>= mapMaybeM toTestModule   where@@ -78,16 +92,26 @@      toTestModule fp = do       fileContentsBS <- ByteString.readFile fp-      return $-        case (splitExtensions fp, parseModuleType <$> Text.decodeUtf8' fileContentsBS) of-          ((fpNoExt, ".hs"), Right (Just ModuleTest)) ->-            let moduleName = Text.replace "/" "." . Text.pack . makeRelative testDir $ fpNoExt-             in Just+      pure $+        case splitExtensions fp of+          (fpNoExt, ".hs")+            | Right (Just ModuleTest) <- parseModuleType <$> Text.decodeUtf8' fileContentsBS+            , Just moduleName <- toModuleName $ Text.pack (makeRelative testDir fpNoExt) ->+                Just                   TestModule                     { moduleName                     , displayName = withoutSuffix (cfgStripSuffix cfg) moduleName                     }           _ -> Nothing++    toModuleName = fmap (Text.intercalate ".") . mapM validateModuleName . Text.splitOn "/"+    -- https://www.haskell.org/onlinereport/syntax-iso.html+    -- large { small | large | digit | ' }+    validateModuleName name = do+      (first, rest) <- Text.uncons name+      guard $ isUpper first+      guard $ Text.all (\c -> isUpper c || isLower c || isDigit c || c == '\'') rest+      Just name      mapMaybeM :: Monad m => (a -> m (Maybe b)) -> [a] -> m [b]     mapMaybeM f = fmap catMaybes . mapM f
src/Test/Tasty/AutoCollect/ModuleType.hs view
@@ -13,7 +13,7 @@ import Test.Tasty.AutoCollect.Constants  data ModuleType-  = ModuleMain AutoCollectConfig+  = ModuleMain AutoCollectConfigPartial   | ModuleTest   deriving (Show, Eq) @@ -29,11 +29,9 @@       | isTestComment (Text.unpack x) = Just ModuleTest     go (_ : rest) = go rest -{- |-Group consecutive whitespace characters.-->>> groupWhitespace " a  bb  c "-[" ", "a", "  ", "bb", "  ", "c", " "]--}+-- | Group consecutive whitespace characters.+--+-- >>> groupWhitespace " a  bb  c "+-- [" ", "a", "  ", "bb", "  ", "c", " "] groupWhitespace :: Text -> [Text] groupWhitespace = Text.groupBy (\c1 c2 -> isSpace c1 == isSpace c2)
src/Test/Tasty/AutoCollect/Utils/TreeMap.hs view
@@ -16,49 +16,47 @@   }   deriving (Show, Eq) -{- |-Convert the given list of values into a 'TreeMap'.--For example,-@-fromList [[A, B, C], [A, B], [A, C, D], [Z]]-@-would become-@-TreeMap-  { value = Nothing-  , children = Map.fromList-      [ ("A", TreeMap-          { value = Nothing-          , children = Map.fromList-              [ ("B", TreeMap-                  { value = Just ...-                  , children = Map.fromList-                      ("C", [ TreeMap-                          { value = Just ...-                          , children = Map.empty-                          }-                      ])-                  })-              , ("C", TreeMap-                  { value = Nothing-                  , children = Map.fromList-                      [ ("D", TreeMap-                          { value = Just ...-                          , children = Map.empty-                          })-                      ]-                  })-              ]-          })-    , ("Z", TreeMap-        { value = Just ...-        , children = Map.empty-        })-    ]-  }-@--}+-- | Convert the given list of values into a 'TreeMap'.+--+-- For example,+-- @+-- fromList [[A, B, C], [A, B], [A, C, D], [Z]]+-- @+-- would become+-- @+-- TreeMap+--   { value = Nothing+--   , children = Map.fromList+--       [ ("A", TreeMap+--           { value = Nothing+--           , children = Map.fromList+--               [ ("B", TreeMap+--                   { value = Just ...+--                   , children = Map.fromList+--                       ("C", [ TreeMap+--                           { value = Just ...+--                           , children = Map.empty+--                           }+--                       ])+--                   })+--               , ("C", TreeMap+--                   { value = Nothing+--                   , children = Map.fromList+--                       [ ("D", TreeMap+--                           { value = Just ...+--                           , children = Map.empty+--                           })+--                       ]+--                   })+--               ]+--           })+--     , ("Z", TreeMap+--         { value = Just ...+--         , children = Map.empty+--         })+--     ]+--   }+-- @ fromList :: Ord k => [([k], v)] -> TreeMap k v fromList = foldr (uncurry insert) empty 
tasty-autocollect.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           tasty-autocollect-version:        0.3.2.0+version:        0.4.0 synopsis:       Autocollection of tasty tests. description:    Autocollection of tasty tests. See README.md for more details. category:       Testing@@ -61,24 +61,20 @@       Test.Tasty.AutoCollect.GHC.Shim_Common   hs-source-dirs:       src-  ghc-options: -Wall+  ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wnoncanonical-monad-instances   build-depends:       base >=4.14 && <5     , bytestring >=0.10 && <0.12     , containers >=0.6.2.1 && <0.7     , directory >=1.3.6.0 && <2     , filepath >=1.4.2.1 && <2-    , ghc >=8.10 && <9.5+    , ghc >=9.0 && <9.5     , tasty >=1.4.2.1 && <2     , tasty-expected-failure >=0.11 && <1     , template-haskell >=2.16 && <2.20     , text >=1.2.3.2 && <3     , transformers >=0.5.6.2 && <1   default-language: Haskell2010-  if impl(ghc >= 8.0)-    ghc-options: -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wnoncanonical-monad-instances-  if impl(ghc < 8.8)-    ghc-options: -Wnoncanonical-monadfail-instances   if impl(ghc >= 9.4) && impl(ghc < 9.6)     other-modules:         Test.Tasty.AutoCollect.GHC.Shim_9_4@@ -88,9 +84,6 @@   if impl(ghc >= 9.0) && impl(ghc < 9.2)     other-modules:         Test.Tasty.AutoCollect.GHC.Shim_9_0-  if impl(ghc >= 8.10) && impl(ghc < 9.0)-    other-modules:-        Test.Tasty.AutoCollect.GHC.Shim_8_10  executable tasty-autocollect   main-is: Preprocessor.hs@@ -98,16 +91,12 @@       Paths_tasty_autocollect   hs-source-dirs:       exe-  ghc-options: -Wall+  ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wnoncanonical-monad-instances   build-depends:       base >=4.14 && <5     , tasty-autocollect     , text >=1.2.3.2 && <3   default-language: Haskell2010-  if impl(ghc >= 8.0)-    ghc-options: -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wnoncanonical-monad-instances-  if impl(ghc < 8.8)-    ghc-options: -Wnoncanonical-monadfail-instances  test-suite tasty-autocollect-tests   type: exitcode-stdio-1.0@@ -127,7 +116,7 @@       Paths_tasty_autocollect   hs-source-dirs:       test-  ghc-options: -Wall -F -pgmF=tasty-autocollect+  ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wnoncanonical-monad-instances -F -pgmF=tasty-autocollect   build-depends:       base     , bytestring@@ -144,8 +133,4 @@     , text     , typed-process   default-language: Haskell2010-  if impl(ghc >= 8.0)-    ghc-options: -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wnoncanonical-monad-instances-  if impl(ghc < 8.8)-    ghc-options: -Wnoncanonical-monadfail-instances   build-tool-depends: tasty-autocollect:tasty-autocollect
test/Test/Tasty/AutoCollect/ConfigTest.hs view
@@ -9,10 +9,15 @@   -- $AUTOCOLLECT.TEST.export$ ) where +import Control.Monad (forM_) import Data.Bifunctor (first) import Data.Char (isSpace) import Data.Text (Text) import qualified Data.Text as Text+import qualified Data.Text.IO as Text+import System.Directory (createDirectoryIfMissing)+import System.FilePath (takeDirectory, (</>))+import System.IO.Temp (withSystemTempDirectory) import Test.Predicates import Test.Predicates.HUnit import Test.Predicates.QuickCheck@@ -66,16 +71,21 @@  {----- Configuration options -----} +test =+  testCase "parseConfig parses import" $+    parseConfig "import = foo.conf, ../bar/baz.conf"+      @?~ right (cfgImports `with` just (eq ["foo.conf", "../bar/baz.conf"]))+ test_prop :: ConfigPiece -> Property test_prop "parseConfig parses suite_name" (ConfigPiece v) =   parseConfig ("suite_name = " <> v)-    `satisfies` right (cfgSuiteName `with` just (eq v))+    `satisfies` right (cfgSuiteName `with` just (just (eq v)))  test_prop :: Property test_prop "parseConfig parses group_type" =   forAll (elements groupTypeOptions) $ \(groupTypeName, groupType) ->     parseConfig ("group_type = " <> groupTypeName)-      `satisfies` right (cfgGroupType `with` eq groupType)+      `satisfies` right (cfgGroupType `with` just (eq groupType))  test_prop :: ConfigPiece -> Property test_prop "parseConfig errors on invalid group_type" (ConfigPiece v) =@@ -92,24 +102,24 @@ test_prop :: ConfigPiece -> Property test_prop "parseConfig parses strip_suffix" (ConfigPiece v) =   parseConfig ("strip_suffix = " <> v)-    `satisfies` right (cfgStripSuffix `with` eq v)+    `satisfies` right (cfgStripSuffix `with` just (eq v))  test_prop :: NonEmptyList HsIdentifier -> Property test_prop "parseConfig parses ingredients" (NonEmpty (map getHsIdentifier -> ingredients)) =   parseConfig ("ingredients = " <> Text.intercalate "," ingredients)-    `satisfies` right (cfgIngredients `with` eq ingredients)+    `satisfies` right (cfgIngredients `with` just (eq ingredients))  test_prop :: NonEmptyList (HsIdentifier, Spaces) -> Property test_prop "parseConfig strips whitespace when parsing ingredients" (NonEmpty (map (first getHsIdentifier) -> identifiers)) =   let ingredientsVal = Text.intercalate "," . map (\(s, spaces) -> wrapSpaces spaces s) $ identifiers       ingredients = map fst identifiers    in parseConfig ("ingredients = " <> ingredientsVal)-        `satisfies` right (cfgIngredients `with` eq ingredients)+        `satisfies` right (cfgIngredients `with` just (eq ingredients))  test_prop :: BoolOption -> Property test_prop "parseConfig parses ingredients_override (case insensitive)" option =   parseConfig ("ingredients_override = " <> getText option)-    `satisfies` right (cfgIngredientsOverride `with` eq (getBool option))+    `satisfies` right (cfgIngredientsOverride `with` just (eq (getBool option)))  test_prop :: ConfigPiece -> Property test_prop "parseConfig errors on invalid ingredients_override" (ConfigPiece v) =@@ -129,14 +139,42 @@       , "strip_suffix"       ] -{----- Helpers -----}+{----- Configuration resolution -----} -{- |-A Text suitable for use as a key or value in the configuration.+test =+  testCase "resolveConfig imports config recursively" $+    withSystemTempDirectory "tasty-autocollect-resolveConfig" $ \tmpdir -> do+      let+        files =+          [+            ( "foo/autocollect.conf"+            ,+              [ "import = ../base/autocollect.conf"+              , "suite_name = foo"+              ]+            )+          ,+            ( "base/autocollect.conf"+            ,+              [ "suite_name = base"+              , "ingredients = baseIngredients"+              ]+            )+          ]+      forM_ files $ \(fpRel, fileLines) -> do+        let fp = tmpdir </> fpRel+        createDirectoryIfMissing True (takeDirectory fp)+        Text.writeFile fp (Text.unlines fileLines)+      cfg <- resolveConfig (tmpdir </> "Main.hs") mempty{cfgImports = Just ["foo/autocollect.conf"]}+      cfgSuiteName cfg @?= Just "foo"+      cfgIngredients cfg @?= ["baseIngredients"] -Specifically, will be a non-empty string that does not contain '=',-newlines, or trailing/leading spaces.--}+{----- Helpers -----}++-- | A Text suitable for use as a key or value in the configuration.+--+-- Specifically, will be a non-empty string that does not contain '=',+-- newlines, or trailing/leading spaces. newtype ConfigPiece = ConfigPiece {getConfigPiece :: Text}   deriving (Show) 
test/Test/Tasty/AutoCollect/ConvertTestTest.hs view
@@ -81,7 +81,7 @@ test = testCase "tests fail when omitting export comment" $ do   (_, stderr) <-     assertAnyFailure . runTestWith (modifyFile "Test.hs" (map removeExports)) $-      [ "test = testCase \"a test\" $ return ()"+      [ "test = testCase \"a test\" $ pure ()"       ]   getTestLines stderr @?~ containsStripped (startsWith messagePreGHC94 `orP` startsWith messagePostGHC94)   where@@ -94,7 +94,7 @@ test = testCase "test file can omit an explicit export list" $ do   (stdout, _) <-     assertSuccess . runTestWith (modifyFile "Test.hs" (map removeExports)) $-      [ "test = testCase \"a test\" $ return ()"+      [ "test = testCase \"a test\" $ pure ()"       ]   getTestLines stdout @?~ containsStripped (eq "a test: OK")   where@@ -127,30 +127,30 @@   testCase "test may specify type" $     assertSuccess_ . runTest $       [ "test :: TestTree"-      , "test = testCase \"a test\" $ return ()"+      , "test = testCase \"a test\" $ pure ()"       ]  test = testGolden "test fails when given arguments" "test_args.golden" $ do   (_, stderr) <-     assertAnyFailure . runTest $-      [ "test \"some name\" = testCase \"test\" $ return ()"+      [ "test \"some name\" = testCase \"test\" $ pure ()"       ]-  return stderr+  pure stderr  test = testGolden "test fails when specifying wrong type" "test_type.golden" $ do   (_, stderr) <-     assertAnyFailure . runTest $       [ "test :: Int"-      , "test = testCase \"test\" $ return ()"+      , "test = testCase \"test\" $ pure ()"       ]-  return stderr+  pure stderr  test = testCase "tests can omit type signatures" $ do   (stdout, _) <-     assertSuccess . runTest $-      [ "test = testCase \"test 1\" $ return ()"+      [ "test = testCase \"test 1\" $ pure ()"       , ""-      , "test = testCase \"test 2\" $ return ()"+      , "test = testCase \"test 2\" $ pure ()"       ]   getTestLines stdout @?~ containsStripped (eq "test 1: OK")   getTestLines stdout @?~ containsStripped (eq "test 2: OK")@@ -159,9 +159,9 @@   testCase "tests may omit type after specifying a type prior" $     assertSuccess_ . runQCTest $       [ "test :: TestTree"-      , "test = testCase \"test 1\" $ return ()"+      , "test = testCase \"test 1\" $ pure ()"       , ""-      , "test = testCase \"test 2\" $ return ()"+      , "test = testCase \"test 2\" $ pure ()"       ]  {----- test_batch -----}@@ -170,7 +170,7 @@   (stdout, _) <-     assertSuccess . runTest $       [ "test_batch ="-      , "  [ testCase (\"test #\" ++ show x) $ return ()"+      , "  [ testCase (\"test #\" ++ show x) $ pure ()"       , "  | x <- [1 .. 5]"       , "  ]"       ]@@ -181,7 +181,7 @@   (stdout, _) <-     assertSuccess . runTest $       [ "test_batch ="-      , "  [ testCase (label x) $ return ()"+      , "  [ testCase (label x) $ pure ()"       , "  | x <- [1 .. 5]"       , "  ]"       , "  where"@@ -202,7 +202,7 @@     assertAnyFailure . runTest $       [ "test_batch \"some name\" = []"       ]-  return stderr+  pure stderr  test = testGolden "test_batch fails when specifying wrong type" "test_batch_type.golden" $ do   (_, stderr) <-@@ -210,7 +210,7 @@       [ "test_batch :: TestTree"       , "test_batch = []"       ]-  return stderr+  pure stderr  {----- test_prop -----} @@ -225,6 +225,15 @@     stdout @?~ hasSubstr "passed 100 tests"  test =+  testCase "test_prop where clause has args in scope" $+    assertSuccess_ . runQCTest $+      [ "test_prop :: Positive Int -> Bool"+      , "test_prop \"test\" (Positive n) = n > zero"+      , "  where"+      , "    zero = n - n"+      ]++test =   testCase "test_prop may omit type" $     assertSuccess_ . runQCTest $       [ "test_prop \"test\" x = (x :: Int) === x"@@ -254,12 +263,12 @@ test =   testGolden "test_prop fails when no arguments provided" "test_prop_no_args.golden" $ do     (_, stderr) <- assertAnyFailure $ runTest ["test_prop = 1 === 1"]-    return stderr+    pure stderr  test =   testGolden "test_prop fails when non-string argument provided" "test_prop_bad_arg.golden" $ do     (_, stderr) <- assertAnyFailure $ runTest ["test_prop 11 = True"]-    return stderr+    pure stderr  test =   testCase "test_prop works when -XOverloadedStrings is enabled" $@@ -285,7 +294,7 @@       assertSuccess . runTest $         [ "test_expectFail = testCase \"failing test\" $ 1 @?= 2"         ]-    return (normalizeTestOutput stdout)+    pure (normalizeTestOutput stdout)  test =   testGolden "expectFailBecause succeeds when test fails" "test_expectFailBecause_output.golden" $ do@@ -293,7 +302,7 @@       assertSuccess . runTest $         [ "test_expectFailBecause \"some reason\" = testCase \"failing test\" $ 1 @?= 2"         ]-    return (normalizeTestOutput stdout)+    pure (normalizeTestOutput stdout)  test =   testGolden "ignoreTest succeeds when test fails" "test_ignoreTest_output.golden" $ do@@ -301,7 +310,7 @@       assertSuccess . runTest $         [ "test_ignoreTest = testCase \"failing test\" $ 1 @?= 2"         ]-    return (normalizeTestOutput stdout)+    pure (normalizeTestOutput stdout)  test =   testGolden "ignoreTestBecause succeeds when test fails" "test_ignoreTestBecause_output.golden" $ do@@ -309,7 +318,7 @@       assertSuccess . runTest $         [ "test_ignoreTestBecause \"some reason\" = testCase \"failing test\" $ 1 @?= 2"         ]-    return (normalizeTestOutput stdout)+    pure (normalizeTestOutput stdout)  test =   testGolden "expected-failure modifiers work on test_batch" "test_batch_expectFailBecause_output.golden" $ do@@ -320,7 +329,7 @@         , "  | x <- [1 .. 3 :: Int]"         , "  ]"         ]-    return (normalizeTestOutput stdout)+    pure (normalizeTestOutput stdout)  test =   testCase "expected-failure modifiers work on test_prop" $ do
test/Test/Tasty/AutoCollect/GenerateMainTest.hs view
@@ -35,7 +35,7 @@       [ "{- AUTOCOLLECT.TEST -}"       , "module A.B.C.X.Y.Z where"       , "import Test.Tasty.HUnit"-      , "test = testCase \"test\" $ return ()"+      , "test = testCase \"test\" $ pure ()"       ]  test =@@ -52,6 +52,16 @@         )         ["{- AUTOCOLLECT.MAIN -}"] +test =+  testCase "ignores directories with invalid module name" $+    assertSuccess_ . runMainWith (addFiles [("A/&Bad/Foo.hs", ["{- AUTOCOLLECT.TEST -}"])]) $+      ["{- AUTOCOLLECT.MAIN -}"]++test =+  testCase "ignores files with invalid module name" $+    assertSuccess_ . runMainWith (addFiles [("A/#Foo#.hs", ["{- AUTOCOLLECT.TEST -}"])]) $+      ["{- AUTOCOLLECT.MAIN -}"]+ test_batch =   [ testGolden     ("output for group_type = " <> groupType <> " is as expected")@@ -77,8 +87,8 @@       [ "{- AUTOCOLLECT.TEST -}"       , "module " <> moduleName <> " where"       , "import Test.Tasty.HUnit"-      , "test = testCase \"test #1 for " <> ident <> "\" $ return ()"-      , "test = testCase \"test #2 for " <> ident <> "\" $ return ()"+      , "test = testCase \"test #1 for " <> ident <> "\" $ pure ()"+      , "test = testCase \"test #2 for " <> ident <> "\" $ pure ()"       ]  test = testCase "generateMain orders test modules alphabetically" $ do@@ -114,7 +124,7 @@       [ "{- AUTOCOLLECT.TEST -}"       , "module " <> moduleName <> " where"       , "import Test.Tasty.HUnit"-      , "test = testCase \"test\" $ return ()"+      , "test = testCase \"test\" $ pure ()"       ]  test = testCase "allows stripping suffix from test modules" $ do@@ -142,7 +152,7 @@       [ "{- AUTOCOLLECT.TEST -}"       , "module " <> moduleName <> " where"       , "import Test.Tasty.HUnit"-      , "test = testCase \"test\" $ return ()"+      , "test = testCase \"test\" $ pure ()"       ]  test = testCase "suffix is stripped before building module tree" $ do@@ -171,7 +181,7 @@           [ "{- AUTOCOLLECT.TEST -}"           , "module A.B.CTest where"           , "import Test.Tasty.HUnit"-          , "test = testCase \"test1\" $ return ()"+          , "test = testCase \"test1\" $ pure ()"           ]         )       ,@@ -180,7 +190,7 @@           [ "{- AUTOCOLLECT.TEST -}"           , "module A.B.C.DTest where"           , "import Test.Tasty.HUnit"-          , "test = testCase \"test2\" $ return ()"+          , "test = testCase \"test2\" $ pure ()"           ]         )       ]@@ -199,7 +209,7 @@       , "import Test.Tasty.Ingredients"       , "sayHelloAndExit :: Ingredient"       , "sayHelloAndExit = TestManager [] $ \\_ _ -> Just $"-      , "  putStrLn \"Hello!\" >> return True"+      , "  putStrLn \"Hello!\" >> pure True"       ]  test = testCase "gives informative error when ingredient lacks module" $ do@@ -229,6 +239,75 @@       ]   stdout @?~ startsWith "my-test-suite" +test = testCase "allows importing configuration" $ do+  (stdout, _) <-+    assertSuccess . runMainWith (addFiles [("autocollect.conf", config), testFile]) $+      [ "{- AUTOCOLLECT.MAIN"+      , "import = autocollect.conf"+      , "suite_name = my-actual-test-suite"+      , "-}"+      ]+  stdout @?~ startsWith "my-actual-test-suite"+  getTestLines stdout @?~ containsStripped (eq "MyLibrary")+  where+    config =+      [ "# this is a comment in imported config"+      , "suite_name = my-test-suite"+      , "strip_suffix = Test"+      ]+    testFile =+      ( "MyLibraryTest.hs"+      ,+        [ "{- AUTOCOLLECT.TEST -}"+        , "module MyLibraryTest where"+        , "import Test.Tasty.HUnit"+        , "test = testCase \"my test\" $ pure ()"+        ]+      )++test = testCase "allows customizing main module" $ do+  (stdout, _) <-+    assertSuccess . runMainWith (setTestFiles testFiles) $+      [ "{- AUTOCOLLECT.MAIN"+      , "custom_main = true"+      , "group_type = modules"+      , "strip_suffix = Test"+      , "-}"+      , ""+      , "{- AUTOCOLLECT.MAIN.imports -}"+      , "import Test.Tasty"+      , ""+      , "main :: IO ()"+      , "main = do"+      , "  putStrLn \"hello world!\""+      , "  defaultMain $ testGroup \"my tests\" tests"+      , "  where"+      , "    tests = id {- AUTOCOLLECT.MAIN.tests -}"+      ]+  getTestLines stdout+    @?~ startsWith+      [ "hello world!"+      , "my tests"+      , "  A"+      , "    test: OK"+      , "  A.A"+      , "    test: OK"+      , "  B"+      , "    test: OK"+      ]+  where+    testFiles =+      [ ("A.hs", testFile "A")+      , ("A/ATest.hs", testFile "A.ATest")+      , ("BTest.hs", testFile "BTest")+      ]+    testFile moduleName =+      [ "{- AUTOCOLLECT.TEST -}"+      , "module " <> moduleName <> " where"+      , "import Test.Tasty.HUnit"+      , "test = testCase \"test\" $ pure ()"+      ]+ {----- Helpers -----}  setTestFiles :: [(FilePath, FileContents)] -> GHCProject -> GHCProject@@ -264,6 +343,6 @@         , "import Test.Tasty"         , "import Test.Tasty.HUnit"         , ""-        , "test = testCase \"a test in " <> moduleName <> "\" $ return ()"+        , "test = testCase \"a test in " <> moduleName <> "\" $ pure ()"         ]       )
test/Test/Tasty/AutoCollect/ModuleTypeTest.hs view
@@ -36,6 +36,6 @@  test = testCase "parseModuleType parses configuration for main modules" $ do   parseModuleType "{- AUTOCOLLECT.MAIN suite_name = foo -}"-    @?~ just ($(qADT 'ModuleMain) $ cfgSuiteName `with` eq (Just "foo"))+    @?~ just ($(qADT 'ModuleMain) $ cfgSuiteName `with` eq (Just $ Just "foo"))   parseModuleType "{- AUTOCOLLECT.MAIN\nsuite_name = foo\n-}"-    @?~ just ($(qADT 'ModuleMain) $ cfgSuiteName `with` eq (Just "foo"))+    @?~ just ($(qADT 'ModuleMain) $ cfgSuiteName `with` eq (Just $ Just "foo"))
test/Test/Tasty/Ext/TodoTest.hs view
@@ -29,7 +29,7 @@     assertAnyFailure . runTest $       [ "test_todo \"some name\" = \"a pending test\""       ]-  return stderr+  pure stderr  test = testGolden "test_todo fails when specifying wrong type" "test_todo_type.golden" $ do   (_, stderr) <-@@ -37,7 +37,7 @@       [ "test_todo :: Int"       , "test_todo = \"a pending test\""       ]-  return stderr+  pure stderr  test =   testGolden "--fail-todos makes TODO tests fail" "fail_todos.golden" $ do@@ -46,4 +46,4 @@         runTestWith           (\proj -> proj{runArgs = ["--fail-todos"]})           ["test_todo = \"a pending test\""]-    return $ normalizeTestOutput stdout+    pure $ normalizeTestOutput stdout
test/TestUtils/Integration.hs view
@@ -49,7 +49,7 @@ assertStatus isExpected testResult = do   (code, stdout, stderr) <- testResult   if isExpected code-    then return (stdout, stderr)+    then pure (stdout, stderr)     else       errorWithoutStackTrace . unlines $         [ "Got: " ++ show code@@ -123,17 +123,15 @@           , "--" : entrypoint : map Text.unpack runArgs           ] -    return (code, decode stdout, decode stderr)+    pure (code, decode stdout, decode stderr)   where     decode = TextL.toStrict . TextL.decodeUtf8  {----- Helpers -----} -{- |-Run a test file with tasty-autocollect.--Automatically imports Test.Tasty and Test.Tasty.HUnit.--}+-- | Run a test file with tasty-autocollect.+--+-- Automatically imports Test.Tasty and Test.Tasty.HUnit. runTest :: FileContents -> IO (ExitCode, Text, Text) runTest = runTestWith id 
test/TestUtils/Predicates.hs view
@@ -9,14 +9,12 @@ import qualified Data.Text as Text import Test.Predicates -{- |-A predicate for checking that the given lines of text-contain a line that, when stripped of whitespace, satisfies-the given predicate.--Writing our own because explainable-predicate's `contain`-function doesn't provide a good output on failure.--}+-- | A predicate for checking that the given lines of text+-- contain a line that, when stripped of whitespace, satisfies+-- the given predicate.+--+-- Writing our own because explainable-predicate's `contain`+-- function doesn't provide a good output on failure. containsStripped :: Predicate Text -> Predicate [Text] containsStripped p =   Predicate
test/golden/test_args.golden view
@@ -2,5 +2,5 @@ <no location info>: error:      ******************** tasty-autocollect failure ********************-test should not be used with arguments (at line 5)+Found extraneous arguments at line 5 
test/golden/test_batch_args.golden view
@@ -2,5 +2,5 @@ <no location info>: error:      ******************** tasty-autocollect failure ********************-test_batch should not be used with arguments (at line 5)+Found extraneous arguments at line 5 
test/golden/test_todo_args.golden view
@@ -2,5 +2,5 @@ <no location info>: error:      ******************** tasty-autocollect failure ********************-test_todo should not be used with arguments (at line 5)+Found extraneous arguments at line 5