packages feed

tasty-discover-5.2.0: README.md

[![CircleCI](https://circleci.com/gh/haskell-works/tasty-discover/tree/master.svg?style=svg)](https://circleci.com/gh/haskell-works/tasty-discover/tree/master)
[![tasty-discover-nightly](http://stackage.org/package/tasty-discover/badge/nightly)](http://stackage.org/nightly/package/tasty-discover)
[![tasty-discover-lts](http://stackage.org/package/tasty-discover/badge/lts)](http://stackage.org/lts/package/tasty-discover)
[![Hackage Status](https://img.shields.io/hackage/v/tasty-discover.svg)](http://hackage.haskell.org/package/tasty-discover)
[![GitHub license](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://raw.githubusercontent.com/haskell-works/tasty-discover/main/LICENSE)

# tasty-discover

- [Getting Started](#getting-started)
  * [Create Test Driver File](#create-test-driver-file)
  * [Configure Cabal or Hpack Test Suite](#configure-cabal-or-hpack-test-suite)
- [Write Tests](#write-tests)
  * [Test Transformations (Flavored, skip, platform)](#test-transformations-flavored-skip-platform)
    - [Flavored (test transformations)](#flavored-test-transformations)
    - [Skipping Tests](#skipping-tests)
    - [Platform-Specific Tests](#platform-specific-tests)
    - [Combining skip and platform](#combining-skip-and-platform)
    - [Using skip and platform (guidelines)](#using-skip-and-platform-guidelines)
  * [Comment Handling](#comment-handling)
- [Customise Discovery](#customise-discovery)
  * [No Arguments](#no-arguments)
  * [With Arguments](#with-arguments)
  * [Custom Main Function](#custom-main-function)
- [Example Project](#example-project)
- [Change Log](#change-log)
- [Deprecation Policy](#deprecation-policy)
- [Contributing](#contributing)
- [FAQ](#frequently-asked-questions)
- [Maintenance](#maintenance)
- [Releasing](#releasing)
- [Acknowledgements](#acknowledgements)
- [AI Guidelines](AI_GUIDELINES.md)
- [Coding Style](CODING_STYLE.md)

Haskell auto-magic test discovery and runner for the [tasty test framework].

[tasty test framework]: https://github.com/feuerbach/tasty

Prefix your test case names and `tasty-discover` will discover, collect and run
them. All popular Haskell test libraries are covered. Configure once then just
write your tests. Remember to add your test modules to your Cabal/Hpack
files. Tasty ingredients are included along with various configuration options
for different use cases.

**Recent improvements include:**
- New `--no-main` option for custom test runners
- **Platform-specific test filtering** with `platform` function and logical expressions
- **Skip test functionality** with `skip` function and yellow `[SKIPPED]` output
- **`Flavored` type** for general-purpose test transformations with extensible design
- Enhanced support for custom test types with `Tasty` instances
- Better handling of backup files and directories in test discovery
- Intelligent block comment handling to prevent false test discovery
- Comprehensive documentation with more test examples

See below for full documentation and examples.

# Getting Started

There are 4 simple steps:

  1. [Create a test driver file in the test directory](#create-test-driver-file)
  2. [Mark the driver file as the `main-is` in the test suite](#configure-cabal-or-hpack-test-suite)
  3. [Mark tests with the correct prefixes](#write-tests)
  4. [Customise test discovery as needed](#customise-discovery)

Check out the [example project](#example-project) to get moving quickly.

## Create Test Driver File

You can name this file anything you want but it must contain the correct
preprocessor definition for `tasty-discover` to run and to detect the
configuration. It should be at the top level of the test directory.

For example (in `test/Driver.hs`):

```
{-# OPTIONS_GHC -F -pgmF tasty-discover #-}
```
following example, the test driver file is called `Driver.hs`:

```
test-suite test
  type: exitcode-stdio-1.0
  main-is: Driver.hs
  hs-source-dirs: test
  build-depends: base
```

If you use [hpack], that might look like:

[hpack]: https://github.com/sol/hpack

``` yaml
tests:
  test:
    main: "Driver.hs"
    source-dirs: "test"
    dependencies:
    - "base"
```

To ensure that `tasty-discover` is available even without installation, add this
to the test suite in your cabal file:

```
  build-tool-depends:
    tasty-discover:tasty-discover
```

See [`hpack` documentation](https://github.com/sol/hpack) for `stack` equivalent.

# Write Tests

Create test modules and prefix the test function name with an identifier that
corresponds to the testing library you wish to run the test with:

  - **prop_**: [QuickCheck](http://hackage.haskell.org/package/tasty-quickcheck) properties.
  - **scprop_**: [SmallCheck](http://hackage.haskell.org/package/tasty-smallcheck) properties.
  - **hprop_**: [Hedgehog](http://hackage.haskell.org/package/tasty-hedgehog) properties.
  - **unit_**: [HUnit](http://hackage.haskell.org/package/tasty-hunit) test cases.
  - **spec_**: [Hspec](http://hackage.haskell.org/package/tasty-hspec) specifications.
  - **test_**: [Tasty](http://hackage.haskell.org/package/tasty) TestTrees.
  - **tasty_**: Custom tests with [Tasty](http://hackage.haskell.org/package/tasty) instances.

Here is an example test module with a bunch of different tests:

``` haskell
{-# LANGUAGE ScopedTypeVariables #-}

module ExampleTest where

import Data.List
import Test.Tasty
import Test.Tasty.Discover
import Test.Tasty.HUnit
import Test.Tasty.Hspec
import Test.Tasty.QuickCheck
import Test.Tasty.SmallCheck
import qualified Test.Tasty.Hedgehog as TH
import qualified Hedgehog as H
import qualified Hedgehog.Gen as G
import qualified Hedgehog.Range as R

-- HUnit test case
unit_listCompare :: IO ()
unit_listCompare = [1, 2, 3] `compare` [1,2] @?= GT

-- HUnit test case with additional info
unit_listInfo :: IO String
unit_listInfo = return "This test provides info"

-- HUnit test case with steps
unit_listSteps :: (String -> IO ()) -> IO ()
unit_listSteps step = do
  step "Setting up test data"
  step "Running the test"
  [1, 2, 3] `compare` [1,2] @?= GT

-- QuickCheck property
prop_additionCommutative :: Int -> Int -> Bool
prop_additionCommutative a b = a + b == b + a

-- SmallCheck property
scprop_sortReverse :: [Int] -> Bool
scprop_sortReverse list = sort list == sort (reverse list)

-- Hedgehog property
hprop_reverseReverse :: H.Property
hprop_reverseReverse = H.property $ do
  xs <- H.forAll $ G.list (R.linear 0 100) G.alpha
  reverse (reverse xs) H.=== xs

-- Hspec specification
spec_prelude :: Spec
spec_prelude = describe "Prelude.head" $ do
  it "returns the first element of a list" $ do
    head [23 ..] `shouldBe` (23 :: Int)

-- Simple Tasty TestTree
test_addition :: TestTree
test_addition = testProperty "Addition commutes" $ \(a :: Int) (b :: Int) -> a + b == b + a

-- List of Tasty TestTrees
test_multiplication :: [TestTree]
test_multiplication =
  [ testProperty "Multiplication commutes" $ \(a :: Int) (b :: Int) -> a * b == b * a
  , testProperty "One is identity" $ \(a :: Int) -> a * 1 == a
  ]

-- IO Tasty TestTree
test_generateTree :: IO TestTree
test_generateTree = do
  input <- pure "Some input"
  pure $ testCase input $ pure ()

-- IO List of Tasty TestTrees
test_generateTrees :: IO [TestTree]
test_generateTrees = do
  inputs <- pure ["First input", "Second input"]
  pure $ map (\s -> testCase s $ pure ()) inputs

-- Custom test with Tasty instance
--
-- Write a test for anything with a Tasty instance
-- In order to use Flavored with tasty_ functions, add tasty-discover as a library
-- dependency to your test component in the cabal file.

data CustomTest = CustomTest String Assertion

instance Tasty CustomTest where
  tasty info (CustomTest prefix act) =
    pure $ testCase (prefix ++ descriptionOf info) act

tasty_myTest :: CustomTest
tasty_myTest = CustomTest "Custom: " $ pure ()

-- Custom Tasty TestTree (can be any TestTree)
tasty_customGroup :: TestTree
tasty_customGroup = testGroup "Custom Test Group"
  [ testCase "nested test 1" $ return ()
  , testCase "nested test 2" $ (1 + 1) @?= (2 :: Int)
  ]
```

## Test Transformations (Flavored, skip, platform)

This section covers how to transform tests using the Flavored pattern and how to apply skip and platform filters effectively.

### Flavored (test transformations)

The `Flavored` type provides a general-purpose mechanism for transforming `TestTree`s generated by `tasty_` functions before they are added to the test suite. This allows you to apply various options and modifications to your tests.

You can create flavored tests using the `flavored` constructor:

```haskell
flavored :: (TestTree -> TestTree) -> a -> Flavored a
```

#### Examples

```haskell
import Test.Tasty.Discover (Flavored, flavored, skip)

-- Skip a custom property test
tasty_skipProperty :: Flavored Property
tasty_skipProperty = flavored skip $ property $ do
  -- This test will be skipped and show as [SKIPPED] in yellow
  H.failure
```

When tests are skipped, they will show as `[SKIPPED]` in yellow in the test output and won't actually execute.

### Skipping Tests

You can skip tests using the skip functionality provided by tasty-discover. There are multiple ways to skip tests:

For a high-level overview of when to use flavored vs direct application, see
[Using skip and platform (guidelines)](#using-skip-and-platform-guidelines).

#### Using the `skip` function

You can use the `skip` function to skip any TestTree:

```haskell
import Test.Tasty.Discover (skip)

-- Skip a simple test
test_skipThis :: TestTree
test_skipThis = skip $ testCase "this will be skipped" $ pure ()

-- Skip a property test
prop_skipThis :: Property -> TestTree
prop_skipThis p = skip $ testProperty "skipped property" p
```

**Potential future uses:**
The `Flavored` mechanism is designed to be extensible and could be used for other `TestTree` transformations such as:
- Setting test timeouts
- Adding test metadata or descriptions
- Grouping tests under custom names
- Setting resource dependencies
- Applying multiple transformations in sequence

#### Important: How to skip a tasty_ test

Guideline (TL;DR): To skip a `tasty_` test so it shows `[SKIPPED]` and doesn’t run, wrap it with `flavored skip`:

```haskell
import Test.Tasty.Discover (Flavored, flavored, skip)

-- Skips at the TestTree level (preferred for tasty_ tests)
tasty_mySkipped :: Flavored TestTree
tasty_mySkipped = flavored skip $ testCase "will be skipped" $ pure ()
```

Details (why this matters): Applying `skip` directly to an already-constructed
`TestTree` marks only that subtree as skipped. The test body can detect it via
`askOption`, but the outer `Tasty` instance (used by `tasty_` functions) may not
render a top-level `[SKIPPED]` placeholder because the option is applied after
the instance decides how to wrap the node. Using `flavored skip` applies the
transformation early so the instance can short-circuit and substitute a skipped
placeholder.

For completeness, a direct `skip` example that observes the option inside the test:

```haskell
-- Direct skip on a TestTree: the test can read SkipTest via askOption, but the
-- outer instance may not show a top-level [SKIPPED] marker for this node.
test_directSkip :: TestTree
test_directSkip = skip $ askOption $ \(TD.SkipTest shouldSkip) ->
  testCase "observes SkipTest inside" $ assertBool "expected SkipTest" shouldSkip
```

Using `Flavored` ensures the `skip` is visible to the `Tasty` instance early enough to short-circuit with a skipped placeholder.

```

### Platform-Specific Tests

You can conditionally run tests based on the current operating platform using the `platform` function provided by tasty-discover. This is useful for tests that only work on specific operating systems or need to be excluded from certain platforms.

For general guidance and composition patterns with `skip`, see
[Using skip and platform (guidelines)](#using-skip-and-platform-guidelines).

The `platform` function takes a platform expression string and a `TestTree`, returning a `TestTree` that will only run if the expression evaluates to true for the current platform:

```haskell
import Test.Tasty.Discover (platform)

-- Run only on Linux
tasty_linuxOnly :: TestTree
tasty_linuxOnly = platform "linux" $ testCase "Linux-specific functionality" $ do
  -- This test only runs on Linux
  pure ()

-- Run on all platforms except Windows  
tasty_notWindows :: TestTree  
tasty_notWindows = platform "!windows" $ testCase "Non-Windows functionality" $ do
  -- This test runs on all platforms except Windows
  pure ()

-- Run on Unix-like systems (Linux or Darwin)
tasty_unixLike :: TestTree
tasty_unixLike = platform "unix" $ testCase "Unix-like systems" $ do
  -- This test runs on Linux and Darwin (Unix-like systems)
  pure ()
```

#### Platform Expression Syntax

Platform expressions support the following syntax:

**Platform Names:**
- `"linux"` - Linux systems
- `"darwin"` - macOS systems 
- `"windows"` - Windows systems (mapped to "mingw32" internally)
- `"mingw32"` - Windows systems (actual System.Info.os value)
- `"unix"` - Unix-like systems (matches both "linux" and "darwin")

**Logical Operators:**
- `"!"` (NOT) - Negation, e.g., `"!windows"` means "not Windows"
- `"&"` (AND) - Conjunction, e.g., `"!windows & !darwin"` means "neither Windows nor Darwin"
- `"|"` (OR) - Disjunction, e.g., `"linux | darwin"` means "Linux or Darwin"

**Complex Examples:**
```haskell
-- Run on platforms that are neither Windows nor Darwin (e.g., Linux)
tasty_complexPlatform1 :: TestTree
tasty_complexPlatform1 = platform "!windows & !darwin" $ testCase "Neither Windows nor Darwin" $ do
  pure ()

-- Run on either Linux or Darwin, but not Windows
tasty_complexPlatform2 :: TestTree
tasty_complexPlatform2 = platform "linux | darwin" $ testCase "Linux or Darwin only" $ do
  pure ()
```

#### Using `Flavored` with Platform Filtering

You can combine platform filtering with other test transformations using the `Flavored` type:

```haskell
import Test.Tasty.Discover (Flavored, flavored, platform)

-- Apply platform filtering to custom test types
tasty_platformFlavored :: Flavored TestTree
tasty_platformFlavored = flavored (platform "!windows") $ testCase "Advanced platform test" $ do
  pure ()

-- Platform-specific property test
tasty_platformProperty :: Flavored Property  
tasty_platformProperty = flavored (platform "unix") $ property $ do
  -- This hedgehog property only runs on Unix-like systems
  x <- H.forAll $ G.int (R.linear 1 100)
  x H.=== x
```

#### Combining skip and platform

Platform filtering can be combined with other tasty-discover features:

```haskell
-- Platform filtering with test skipping
tasty_platformAndSkip :: TestTree
tasty_platformAndSkip = platform "linux" $ skip $ testCase "Linux test that's also skipped" $ do
  -- This would only run on Linux, but it's also skipped
  pure ()

-- Platform filtering with test groups
tasty_platformGroup :: TestTree
tasty_platformGroup = platform "unix" $ testGroup "Unix-only tests"
  [ testCase "Unix test 1" $ pure ()
  , testCase "Unix test 2" $ pure ()
  , testProperty "Unix property" $ \(x :: Int) -> x >= 0 || x < 0
  ]
```

Platform filtering works by checking the current platform against the expression at runtime. If the expression evaluates to `False`, the test is automatically skipped using the same mechanism as the `skip` function.

#### Skipping entire test trees with `applySkips`

When you want to skip an entire test tree (such as a group of tests) and have each individual test show as `[SKIPPED]` in the output, use the `applySkips` function:

```haskell
import Test.Tasty.Discover (Flavored, flavored, platform, applySkips)

-- Skip an entire test group on Darwin
tasty_testTree_no_darwin :: Flavored (IO TestTree)
tasty_testTree_no_darwin =
  flavored (platform "!darwin") $ pure $
    applySkips $ testGroup "Non-Darwin group"
      [ testProperty "Test 1" $ \(x :: Int) -> x == x
      , testCase "Test 2" $ pure ()
      , testProperty "Test 3" $ \(x :: Int) -> x >= 0 || x < 0
      ]
```

On Darwin, this will display all tests as skipped in yellow:

```
Non-Darwin group
  Test 1 [SKIPPED]: OK
  Test 2 [SKIPPED]: OK
  Test 3 [SKIPPED]: OK
```

The `applySkips` function:
- Checks the `SkipTest` option (set by functions like `skip` or `platform`)
- Traverses the entire test tree and replaces each individual test with a skipped placeholder
- Preserves the test group structure
- Shows `[SKIPPED]` in yellow for each test

This is particularly useful for platform-specific test suites where you want to see which tests would run on other platforms, rather than hiding the entire group.

### Using skip and platform (guidelines)

TL;DR:
- For tests exposed via `tasty_` functions, prefer the `Flavored` pattern to apply
  transformations like `skip` and `platform` so they take effect at the TestTree level and can short-circuit execution.
- Applying `skip` directly to an already-constructed `TestTree` marks the subtree as skipped. The test can observe this via `askOption`, but the outer `Tasty` instance may not show a top-level `[SKIPPED]` placeholder.

Examples:

```haskell
import Test.Tasty.Discover (Flavored, flavored, skip, platform)
import Test.Tasty (TestTree, testCase)

-- Skip (preferred for tasty_ tests):
tasty_mySkipped :: Flavored TestTree
tasty_mySkipped = flavored skip $ testCase "will be skipped" $ pure ()

-- Platform filter with Flavored:
tasty_linuxOnly :: Flavored TestTree
tasty_linuxOnly = flavored (platform "linux") $ testCase "Linux only" $ pure ()

-- Compose platform and skip:
tasty_linuxButSkipped :: Flavored TestTree
tasty_linuxButSkipped = flavored (platform "linux") $ flavored skip $ testCase "won't run" $ pure ()

-- Direct skip on a TestTree (observed inside the test):
test_directSkip :: TestTree
test_directSkip = skip $ askOption $ \(TD.SkipTest shouldSkip) ->
  testCase "observes SkipTest inside" $ assertBool "expected SkipTest" shouldSkip
```

Platform expressions:
- Names: "linux", "darwin", "windows" (mapped to "mingw32"), "mingw32", and "unix" (matches both linux and darwin)
- Operators: `!` (NOT), `&` (AND), `|` (OR)
- Examples:
  - `platform "!windows & !darwin"` — neither Windows nor Darwin
  - `platform "linux | darwin"` — Linux or Darwin
  - `platform "unix"` — Linux or Darwin

## Test Type Variations

### HUnit Tests (`unit_` prefix)

The `unit_` prefix supports three different function signatures:
  ### Tasty TestTrees (`test_` prefix)
- `unit_testName :: IO ()` - Basic test case
- `unit_testName :: IO String` - Test case that provides additional info
- `unit_testName :: (String -> IO ()) -> IO ()` - Test case with steps

### Tasty TestTrees (`test_` prefix)

The `test_` prefix supports four different function signatures:

- `test_testName :: TestTree` - A single test tree
- `test_testName :: [TestTree]` - A list of test trees (automatically grouped)
- `test_testName :: IO TestTree` - A test tree generated in IO
- `test_testName :: IO [TestTree]` - A list of test trees generated in IO

### Custom Tests (`tasty_` prefix)

The `tasty_` prefix works with any type that has a `Tasty` instance:

- Built-in instances for `TestTree`, `[TestTree]`, `IO TestTree`, `IO [TestTree]`
- Custom instances for your own data types
- Provides access to test metadata through `TastyInfo`

### Organizing Tests with `testGroup`

The `testGroup` function is Tasty's way of organizing tests into hierarchical groups. You can use it in several ways:

**In `test_` functions:**
```haskell
test_arithmeticTests :: TestTree
test_arithmeticTests = testGroup "Arithmetic Operations"
  [ testCase "addition" $ 2 + 2 @?= 4
  , testCase "multiplication" $ 3 * 4 @?= 12
  , testProperty "commutativity" $ \a b -> a + b == b + (a :: Int)
  ]
```

**In `tasty_` functions:**
```haskell
tasty_myTestSuite :: TestTree
tasty_myTestSuite = testGroup "My Test Suite"
  [ testGroup "Unit Tests"
      [ testCase "test 1" $ pure ()
      , testCase "test 2" $ pure ()
      ]
  , testGroup "Properties"
      [ testProperty "prop 1" $ \x -> x == (x :: Int)
      ]
  ]
```

**In list form with `test_` functions:**
```haskell
test_groupedTests :: [TestTree]
test_groupedTests =
  [ testGroup "Group 1" [testCase "test A" $ pure ()]
  , testGroup "Group 2" [testCase "test B" $ pure ()]
  ]
```

This creates nested test hierarchies that make test output more organized and easier to navigate.

## Comment Handling

`tasty-discover` intelligently handles Haskell comments during test discovery to prevent false positives:

### Block Comments

Tests inside multiline block comments are automatically ignored:

```haskell
module MyTest where

-- This test will be discovered
unit_validTest :: IO ()
unit_validTest = pure ()

{- This test will be ignored
unit_commentedOut :: IO ()
unit_commentedOut = pure ()
-}

{- Nested comments are also handled correctly
{- Even deeply nested ones
unit_deeplyNested :: IO ()
unit_deeplyNested = pure ()
-}
unit_alsoIgnored :: IO ()
unit_alsoIgnored = pure ()
-}
```

### Line Comments

Line comments (starting with `--`) are handled by the Haskell lexer and don't interfere with test discovery:

```haskell
-- unit_thisIsIgnored :: IO ()
unit_thisIsFound :: IO ()  -- This test will be discovered
unit_thisIsFound = pure ()
```

This feature prevents compilation errors that would occur if `tasty-discover` tried to reference tests that are commented out, making it easier to temporarily disable tests during development.

# Customise Discovery

You configure `tasty-discover` by passing options to the test driver file.

## No Arguments

Example: `{-# OPTIONS_GHC -F -pgmF tasty-discover -optF --debug #-}`

  - **--debug**: Output the contents of the generated module while testing.
  - **--tree-display**: Display the test output results hierarchically.
  - **--no-main**: Generate a module without a main function, exporting `tests` and `ingredients` instead.

## With Arguments

Example: `{-# OPTIONS_GHC -F -pgmF tasty-discover -optF --modules="*CustomTest.hs" #-}`

  - **--modules**: Which test modules to discover (with glob pattern).
  - **--search-dir**: Where to look for tests. This is a directory relative
    to the location of the source file. By default, this is the directory
    of the source file."
  - **--ignores**: Which test modules to ignore (with glob pattern).
  - **--generated-module**: The name of the generated test module.
  - **--ingredient**: Tasty ingredients to add to your test runner.
  - **--inplace**: Has the generated code written to the source file.

It is also possible to override [tasty test options] with `-optF`:

[tasty test options]: https://github.com/feuerbach/tasty#options

``` bash
{-# OPTIONS_GHC -F -pgmF tasty-discover -optF --hide-successes #-}
```

## Custom Main Function

The `--no-main` option allows you to write your own custom main function while still using tasty-discover for test discovery. This is useful when you need to:

- Apply custom test transformations or wrappers
- Add custom logging or output formatting
- Integrate with custom test runners or CI systems
- Control exactly how tests are executed

### Example Usage

Create your test discovery file (e.g., `test/Tests.hs`):

```haskell
{-# OPTIONS_GHC -F -pgmF tasty-discover -optF --no-main -optF --generated-module -optF Tests #-}
```

Then create your custom main file (e.g., `test/Main.hs`):

```haskell
module Main where

import qualified Tests
import qualified Test.Tasty as T

main :: IO ()
main = do
  putStrLn "=== Custom Test Runner ==="

  -- Get discovered tests and ingredients
  discoveredTests <- Tests.tests

  -- Apply custom transformations
  let wrappedTests = T.testGroup "My Custom Tests" [discoveredTests]

  -- Run with custom configuration
  T.defaultMainWithIngredients Tests.ingredients wrappedTests
```

Configure your cabal test suite to use the custom main:

```
test-suite my-tests
  type: exitcode-stdio-1.0
  main-is: Main.hs
  other-modules: Tests
  hs-source-dirs: test
  build-depends: base, tasty
  build-tool-depends: tasty-discover:tasty-discover
```

# Example Project

See the [testing for this package] for a fully configured example.

[testing for this package]: https://github.com/haskell-works/tasty-discover/tree/main/test

# Change Log

Please see the [CHANGELOG.md] for the latest changes.

We try to keep [tagged releases] in our release process, if you care about that.

[CHANGELOG.md]: https://github.com/haskell-works/tasty-discover/blob/main/CHANGELOG.md
[tagged releases]: https://github.com/haskell-works/tasty-discover/releases

# Releasing

This project's release flow is automated via GitHub Actions and triggered by pushing version tags.

Release checklist:

1) Prepare notes
- Update `CHANGELOG.md`: move items from "Unreleased" to a new version section with the current date.

2) Bump version
- Edit `tasty-discover.cabal` and set `version:` to the new version (e.g., `5.x.y`).

3) Commit changes
- Commit the changes: `git commit -m "Release X.Y.Z"`
- Push the commit: `git push origin main`

4) Create and push tag
- Create a git tag: `git tag -a vX.Y.Z -m "Release version X.Y.Z"`
- Push the tag: `git push origin vX.Y.Z`

5) CI does the rest (automated)
- When the tag is pushed, GitHub Actions automatically:
  - Runs the full test suite
  - Validates the cabal project with `cabal check`
  - Builds source distributions (`cabal v2-sdist`)
  - Uploads to Hackage (requires repo secrets `HACKAGE_USER`/`HACKAGE_PASS`)
  - Creates a draft GitHub Release for the tag

6) Publish release
- Go to https://github.com/haskell-works/tasty-discover/releases
- Edit the draft GitHub Release notes if needed and publish

Notes:
- The workflow is defined in `.github/workflows/haskell.yml`.
- The release workflow only triggers on tags matching `v[0-9]+.[0-9]+.[0-9]+` (e.g., v5.2.0).
- Keep `tested-with` in the cabal file up to date with CI's GHC matrix.

# Deprecation Policy

If a breaking change is implemented, you'll see a major version increase, an
entry in the [change log] and a compile-time error with a deprecation warning
and clear instructions on how to upgrade. Please do complain if we're doing
this too much.

[change log]: https://github.com/haskell-works/tasty-discover/blob/main/CHANGELOG.md

# Contributing

All contributions welcome! The continuous integration suite is pretty
comprehensive, so just get hacking and add a test case - there are *plenty* of
examples, so this should be simple - and I'll get to review your change ASAP.

Please follow the guidelines in [CODING_STYLE.md](CODING_STYLE.md) for consistent code formatting and patterns.

For AI assistants and detailed development guidelines, see [AI_GUIDELINES.md](AI_GUIDELINES.md).

# Frequently Asked Questions

## Deleting Tests Breaks The Test Run

This is a known limitation and has been reported. No fix is planned unless you have time.

Please see [#145](https://github.com/haskell-works/tasty-discover/issues/145) for more information.

# Maintenance

If you're interested in helping maintain this package, please let [@newhoggy] know!

It doesn't take much time (max ~3 hours a month) and all we need to do is:

  * Triage issues that are raised.
  * Review pull requests from contributors.
  * Fix bugs when present.
  * Make releases.
  * Manage bounds issues on Stackage.

You can [create an issue] or drop him a line at **lukewm AT riseup DOT NET**.

[@newhoggy]: https://twitter.com/newhoggy
[create an issue]: https://github.com/haskell-works/tasty-discover/issues/new

# Acknowledgements

Thanks to [hspec-discover] and [tasty-auto] for making this possible.

A huge thanks to the growing list of contributors.

[hspec-discover]: https://hspec.github.io/hspec-discover.html
[tasty-auto]: https://github.com/minad/tasty-auto