packages feed

golds-gym-0.3.0.0: example/Spec.hs

-- | Example benchmark golden tests demonstrating golds-gym usage.
module Main (main) where

import Control.Exception (evaluate)
import Data.List (sort)
import Test.Hspec
import Test.Hspec.BenchGolden
import Test.Hspec.BenchGolden.Lenses

main :: IO ()
main = hspec spec

spec :: Spec
spec = do
  describe "List Operations" $ do
    -- Simple benchmark with default configuration
    benchGolden "list append (1000 elements)" $ do
      _ <- evaluate $ [1..1000 :: Int] ++ [1..1000]
      return ()

    -- Benchmark with more iterations for stability
    benchGoldenWith defaultBenchConfig { iterations = 200 }
      "list reverse (5000 elements)" $ do
      _ <- evaluate $ reverse [1..5000 :: Int]
      return ()

  describe "Sorting Algorithms" $ do
    -- Benchmark with tighter tolerance for critical code
    benchGoldenWith defaultBenchConfig
      { iterations = 150
      , tolerancePercent = 10.0
      , warmupIterations = 10
      }
      "sort 1000 elements" $ do
      _ <- evaluate $ sort [1000, 999..1 :: Int]
      return ()

    -- Benchmark with robust statistics for operations prone to outliers
    benchGoldenWith defaultBenchConfig
      { useRobustStatistics = True
      , trimPercent = 10.0
      , outlierThreshold = 3.0
      , warnOnVarianceChange = False
      }
      "sort already sorted" $ do
      _ <- evaluate $ sort [1..1000 :: Int]
      return ()

  describe "Numeric Operations" $ do
    benchGolden "fibonacci 30" $ do
      _ <- evaluate $ fib 30
      return ()

    benchGoldenWith defaultBenchConfig
      { iterations = 500
      , tolerancePercent = 20.0  -- Higher tolerance for fast operations
      }
      "sum of list" $ do
      _ <- evaluate $ sum [1..10000 :: Int]
      return ()

  describe "Robust Statistics Mode" $ do
    -- Benchmark using robust statistics (trimmed mean, MAD)
    benchGoldenWith defaultBenchConfig
      { useRobustStatistics = True
      , iterations = 100
      , trimPercent = 10.0      -- Trim 10% from each tail
      , outlierThreshold = 3.0  -- 3 MADs for outlier detection
      }
      "robust mode - list reverse" $ do
      _ <- evaluate $ reverse [1..5000 :: Int]
      return ()

    -- Benchmark with robust statistics and high outlier sensitivity
    benchGoldenWith defaultBenchConfig
      { useRobustStatistics = True
      , iterations = 200
      , trimPercent = 5.0       -- Minimal trimming
      , outlierThreshold = 2.5  -- More sensitive outlier detection
      , tolerancePercent = 10.0
      }
      "robust mode - sorting" $ do
      _ <- evaluate $ sort [1000, 999..1 :: Int]
      return ()

    -- Demonstrate outlier detection with intentionally noisy operation
    benchGoldenWith defaultBenchConfig
      { useRobustStatistics = True
      , iterations = 50
      , outlierThreshold = 2.0
      }
      "robust mode - with potential outliers" $ do
      _ <- evaluate $ fib 25
      return ()

  describe "Tolerance Configuration" $ do
    -- Hybrid tolerance (default): pass if within ±15% OR ±0.01ms
    benchGolden "hybrid tolerance - fast operation" $ do
      _ <- evaluate $ sum [1..100 :: Int]
      return ()

    -- Percentage-only tolerance: disable absolute tolerance
    -- Note: This test may fail occasionally due to measurement noise,
    -- demonstrating why hybrid tolerance is the recommended default
    benchGoldenWith defaultBenchConfig
      { absoluteToleranceMs = Nothing
      , tolerancePercent = 30.0  -- Increased to reduce false failures
      }
      "percentage-only tolerance" $ do
      _ <- evaluate $ sum [1..500 :: Int]
      return ()

    -- Strict absolute tolerance: 1 microsecond
    benchGoldenWith defaultBenchConfig
      { absoluteToleranceMs = Just 0.001  -- 1 microsecond
      , tolerancePercent = 10.0
      }
      "strict absolute tolerance" $ do
      _ <- evaluate $ fib 20
      return ()

    -- Relaxed absolute tolerance for CI environments
    benchGoldenWith defaultBenchConfig
      { absoluteToleranceMs = Just 0.1  -- 100 microseconds
      , tolerancePercent = 25.0
      }
      "relaxed tolerance for CI" $ do
      _ <- evaluate $ reverse [1..1000 :: Int]
      return ()

  describe "Lens-Based Expectations (Advanced)" $ do
    -- Median-based comparison instead of mean
    benchGoldenWithExpectation "median-based comparison" defaultBenchConfig
      [expect _statsMedian (Percent 10.0)]
      $ do
        _ <- evaluate $ sort [1000, 999..1 :: Int]
        return ()

    -- Compose multiple expectations with AND
    benchGoldenWithExpectation "composed expectations (AND)" defaultBenchConfig
      [ expect _statsMean (Percent 15.0) &&~
        expect _statsMAD (Percent 50.0)
      ]
      $ do
        _ <- evaluate $ fib 25
        return ()

    -- Compose multiple expectations with OR
    benchGoldenWithExpectation "composed expectations (OR)" defaultBenchConfig
      [ expect _statsMedian (Percent 10.0) ||~
        expect _statsMin (Absolute 0.01)
      ]
      $ do
        _ <- evaluate $ sum [1..500 :: Int]
        return ()

    -- Expect reasonable performance (will pass with normal variance)
    benchGoldenWithExpectation "flexible tolerance" defaultBenchConfig
      [expect _statsMean (Percent 20.0)]  -- Wide tolerance for example
      $ do
        _ <- evaluate $ [1..1000 :: Int]  -- Trivial operation
        return ()

    -- Hybrid tolerance with custom absolute threshold
    benchGoldenWithExpectation "hybrid tolerance custom" defaultBenchConfig
      [expect _statsMean (Hybrid 20.0 0.005)]  -- ±20% OR ±5μs
      $ do
        _ <- evaluate $ sort [100, 99..1 :: Int]
        return ()

    -- Use robust statistics lens (trimmed mean)
    benchGoldenWithExpectation "trimmed mean comparison" 
      (defaultBenchConfig { useRobustStatistics = True })
      [expect _statsTrimmedMean (Percent 20.0)]
      $ do
        _ <- evaluate $ fib 26
        return ()

-- | Naive Fibonacci for benchmarking purposes.
fib :: Int -> Int
fib 0 = 0
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)