{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE OverloadedStrings #-}

-- |
-- Module      : Test.Hspec.BenchGolden.Lenses
-- Description : Lens-based expectation combinators for benchmark comparison
-- Copyright   : (c) 2026
-- License     : MIT
-- Maintainer  : @ocramz
--
-- This module provides van Laarhoven lenses for 'GoldenStats' fields and
-- expectation combinators for building custom performance assertions.
--
-- = Quick Start
--
-- @
-- import Test.Hspec
-- import Test.Hspec.BenchGolden
-- import Test.Hspec.BenchGolden.Lenses
--
-- main :: IO ()
-- main = hspec $ do
--   describe \"Custom Expectations\" $ do
--     -- Expect median within 10% tolerance
--     benchGoldenWithExpectation \"median-based\" defaultBenchConfig
--       [expect _statsMedian (Percent 10.0)]
--       myAction
--
--     -- Expect IQR within absolute 0.5ms
--     benchGoldenWithExpectation \"low variance\" defaultBenchConfig
--       [expect _statsIQR (Absolute 0.5)]
--       myAction
--
--     -- Compose multiple expectations (both must pass)
--     benchGoldenWithExpectation \"composed\" defaultBenchConfig
--       [ expect _statsMean (Percent 15.0) &&~
--         expect _statsMAD (Percent 50.0)
--       ]
--       myAction
-- @
--
-- = Lenses
--
-- Simple van Laarhoven lenses provide access to 'GoldenStats' fields:
--
-- * '_statsMean', '_statsMedian', '_statsTrimmedMean' - Central tendency metrics
-- * '_statsStddev', '_statsMAD', '_statsIQR' - Dispersion metrics  
-- * '_statsMin', '_statsMax' - Range metrics
--
-- = Smart Selectors
--
-- 'metricFor' and 'varianceFor' automatically select the appropriate lens
-- based on 'BenchConfig' settings:
--
-- @
-- let lens = metricFor config  -- Returns _statsTrimmedMean if useRobustStatistics
--     baseline = golden ^. lens
--     current = actual ^. lens
-- @
--
-- = Expectation Combinators
--
-- Build expectations with 'expect' and compose them:
--
-- * 'Percent' tolerance - e.g., @Percent 15.0@ for ±15%
-- * 'Absolute' tolerance - e.g., @Absolute 0.01@ for ±0.01ms
-- * 'Hybrid' tolerance - e.g., @Hybrid 15.0 0.01@ (pass if either satisfied)
--
-- Boolean composition operators:
--
-- * '(&&~)' - AND (both expectations must pass)
-- * '(||~)' - OR (either expectation can pass)
--
-- = Infix Operators
--
-- For concise tolerance checking:
--
-- * '(@~)' - Within percentage: @baseline \@~ 15.0 $ actual@
-- * '(@<)' - Within absolute: @baseline \@< 0.01 $ actual@
-- * '(@<<)' - Must be faster (negative tolerance): @baseline \@<< 5.0 $ actual@
-- * '(@>>)' - Must be slower (positive tolerance): @baseline \@>> 5.0 $ actual@

module Test.Hspec.BenchGolden.Lenses
  ( -- * Lenses for GoldenStats
    _statsMean
  , _statsStddev
  , _statsMedian
  , _statsMin
  , _statsMax
  , _statsTrimmedMean
  , _statsMAD
  , _statsIQR

    -- * Smart Metric Selectors
  , metricFor
  , varianceFor

    -- * Expectation Types
  , Expectation(..)
  , Tolerance(..)

    -- * Expectation Combinators
  , expect
  , expectStat
  , checkExpectation

    -- * Tolerance Checking Functions
  , withinPercent
  , withinAbsolute
  , withinHybrid
  , mustImprove
  , mustRegress

    -- * Infix Operators
  , (@~)
  , (@<)
  , (@<<)
  , (@>>)

    -- * Boolean Composition
  , (&&~)
  , (||~)

    -- * Utilities
  , percentDiff
  , absDiff
  , toleranceFromExpectation
  , toleranceValues
  ) where

import Lens.Micro
import Test.Hspec.BenchGolden.Types

-- -----------------------------------------------------------------------------
-- Lenses for GoldenStats fields
-- -----------------------------------------------------------------------------

-- | Lens for mean execution time in milliseconds.
_statsMean :: Lens' GoldenStats Double
_statsMean :: Lens' GoldenStats Double
_statsMean Double -> f Double
f GoldenStats
s = (Double -> GoldenStats) -> f Double -> f GoldenStats
forall a b. (a -> b) -> f a -> f b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\Double
x -> GoldenStats
s { statsMean = x }) (Double -> f Double
f (GoldenStats -> Double
statsMean GoldenStats
s))

-- | Lens for standard deviation in milliseconds.
_statsStddev :: Lens' GoldenStats Double
_statsStddev :: Lens' GoldenStats Double
_statsStddev Double -> f Double
f GoldenStats
s = (Double -> GoldenStats) -> f Double -> f GoldenStats
forall a b. (a -> b) -> f a -> f b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\Double
x -> GoldenStats
s { statsStddev = x }) (Double -> f Double
f (GoldenStats -> Double
statsStddev GoldenStats
s))

-- | Lens for median execution time in milliseconds.
_statsMedian :: Lens' GoldenStats Double
_statsMedian :: Lens' GoldenStats Double
_statsMedian Double -> f Double
f GoldenStats
s = (Double -> GoldenStats) -> f Double -> f GoldenStats
forall a b. (a -> b) -> f a -> f b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\Double
x -> GoldenStats
s { statsMedian = x }) (Double -> f Double
f (GoldenStats -> Double
statsMedian GoldenStats
s))

-- | Lens for minimum execution time in milliseconds.
_statsMin :: Lens' GoldenStats Double
_statsMin :: Lens' GoldenStats Double
_statsMin Double -> f Double
f GoldenStats
s = (Double -> GoldenStats) -> f Double -> f GoldenStats
forall a b. (a -> b) -> f a -> f b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\Double
x -> GoldenStats
s { statsMin = x }) (Double -> f Double
f (GoldenStats -> Double
statsMin GoldenStats
s))

-- | Lens for maximum execution time in milliseconds.
_statsMax :: Lens' GoldenStats Double
_statsMax :: Lens' GoldenStats Double
_statsMax Double -> f Double
f GoldenStats
s = (Double -> GoldenStats) -> f Double -> f GoldenStats
forall a b. (a -> b) -> f a -> f b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\Double
x -> GoldenStats
s { statsMax = x }) (Double -> f Double
f (GoldenStats -> Double
statsMax GoldenStats
s))

-- | Lens for trimmed mean (with tails removed) in milliseconds.
_statsTrimmedMean :: Lens' GoldenStats Double
_statsTrimmedMean :: Lens' GoldenStats Double
_statsTrimmedMean Double -> f Double
f GoldenStats
s = (Double -> GoldenStats) -> f Double -> f GoldenStats
forall a b. (a -> b) -> f a -> f b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\Double
x -> GoldenStats
s { statsTrimmedMean = x }) (Double -> f Double
f (GoldenStats -> Double
statsTrimmedMean GoldenStats
s))

-- | Lens for median absolute deviation (MAD) in milliseconds.
_statsMAD :: Lens' GoldenStats Double
_statsMAD :: Lens' GoldenStats Double
_statsMAD Double -> f Double
f GoldenStats
s = (Double -> GoldenStats) -> f Double -> f GoldenStats
forall a b. (a -> b) -> f a -> f b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\Double
x -> GoldenStats
s { statsMAD = x }) (Double -> f Double
f (GoldenStats -> Double
statsMAD GoldenStats
s))

-- | Lens for interquartile range (IQR = Q3 - Q1) in milliseconds.
_statsIQR :: Lens' GoldenStats Double
_statsIQR :: Lens' GoldenStats Double
_statsIQR Double -> f Double
f GoldenStats
s = (Double -> GoldenStats) -> f Double -> f GoldenStats
forall a b. (a -> b) -> f a -> f b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\Double
x -> GoldenStats
s { statsIQR = x }) (Double -> f Double
f (GoldenStats -> Double
statsIQR GoldenStats
s))

-- -----------------------------------------------------------------------------
-- Smart Metric Selectors
-- -----------------------------------------------------------------------------

-- | Select the appropriate central tendency metric based on configuration.
--
-- Returns:
--
-- * '_statsTrimmedMean' if 'useRobustStatistics' is 'True'
-- * '_statsMean' otherwise
--
-- Example:
--
-- @
-- let lens = metricFor config
--     baseline = golden ^. lens
--     current = actual ^. lens
-- @
metricFor :: BenchConfig -> Lens' GoldenStats Double
metricFor :: BenchConfig -> Lens' GoldenStats Double
metricFor BenchConfig
cfg = if BenchConfig -> Bool
useRobustStatistics BenchConfig
cfg 
                then (Double -> f Double) -> GoldenStats -> f GoldenStats
Lens' GoldenStats Double
_statsTrimmedMean 
                else (Double -> f Double) -> GoldenStats -> f GoldenStats
Lens' GoldenStats Double
_statsMean

-- | Select the appropriate dispersion metric based on configuration.
--
-- Returns:
--
-- * '_statsMAD' if 'useRobustStatistics' is 'True'
-- * '_statsStddev' otherwise
--
-- Example:
--
-- @
-- let vLens = varianceFor config
--     goldenVar = golden ^. vLens
--     actualVar = actual ^. vLens
-- @
varianceFor :: BenchConfig -> Lens' GoldenStats Double
varianceFor :: BenchConfig -> Lens' GoldenStats Double
varianceFor BenchConfig
cfg = if BenchConfig -> Bool
useRobustStatistics BenchConfig
cfg
                  then (Double -> f Double) -> GoldenStats -> f GoldenStats
Lens' GoldenStats Double
_statsMAD
                  else (Double -> f Double) -> GoldenStats -> f GoldenStats
Lens' GoldenStats Double
_statsStddev

-- -----------------------------------------------------------------------------
-- Expectation Types
-- -----------------------------------------------------------------------------

-- | Tolerance specification for performance comparison.
data Tolerance
  = Percent !Double
    -- ^ Percentage tolerance (e.g., @Percent 15.0@ = ±15%)
  | Absolute !Double
    -- ^ Absolute tolerance in milliseconds (e.g., @Absolute 0.01@ = ±0.01ms)
  | Hybrid !Double !Double
    -- ^ Hybrid tolerance: pass if EITHER percentage OR absolute is satisfied
    --   (e.g., @Hybrid 15.0 0.01@ = pass if within ±15% OR ±0.01ms)
  | MustImprove !Double
    -- ^ Must be faster by at least this percentage (e.g., @MustImprove 10.0@ = must be ≥10% faster)
  | MustRegress !Double
    -- ^ Must be slower by at least this percentage (e.g., @MustRegress 5.0@ = must be ≥5% slower)
  deriving (Int -> Tolerance -> ShowS
[Tolerance] -> ShowS
Tolerance -> String
(Int -> Tolerance -> ShowS)
-> (Tolerance -> String)
-> ([Tolerance] -> ShowS)
-> Show Tolerance
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Tolerance -> ShowS
showsPrec :: Int -> Tolerance -> ShowS
$cshow :: Tolerance -> String
show :: Tolerance -> String
$cshowList :: [Tolerance] -> ShowS
showList :: [Tolerance] -> ShowS
Show, Tolerance -> Tolerance -> Bool
(Tolerance -> Tolerance -> Bool)
-> (Tolerance -> Tolerance -> Bool) -> Eq Tolerance
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Tolerance -> Tolerance -> Bool
== :: Tolerance -> Tolerance -> Bool
$c/= :: Tolerance -> Tolerance -> Bool
/= :: Tolerance -> Tolerance -> Bool
Eq)

-- | An expectation for comparing golden and actual statistics.
--
-- Expectations can be composed using boolean operators:
--
-- @
-- expect _statsMean (Percent 15.0) &&~ expect _statsMAD (Percent 50.0)
-- @
data Expectation
  = ExpectStat !(Lens' GoldenStats Double) !Tolerance
    -- ^ Expect a specific field to be within tolerance
  | And !Expectation !Expectation
    -- ^ Both expectations must pass
  | Or !Expectation !Expectation
    -- ^ Either expectation can pass

-- Manual Eq instance (lenses can't be compared, so we only compare structure)
instance Eq Expectation where
  ExpectStat Lens' GoldenStats Double
_ Tolerance
tol1 == :: Expectation -> Expectation -> Bool
== ExpectStat Lens' GoldenStats Double
_ Tolerance
tol2 = Tolerance
tol1 Tolerance -> Tolerance -> Bool
forall a. Eq a => a -> a -> Bool
== Tolerance
tol2
  And Expectation
e1 Expectation
e2 == And Expectation
e3 Expectation
e4 = Expectation
e1 Expectation -> Expectation -> Bool
forall a. Eq a => a -> a -> Bool
== Expectation
e3 Bool -> Bool -> Bool
&& Expectation
e2 Expectation -> Expectation -> Bool
forall a. Eq a => a -> a -> Bool
== Expectation
e4
  Or Expectation
e1 Expectation
e2 == Or Expectation
e3 Expectation
e4 = Expectation
e1 Expectation -> Expectation -> Bool
forall a. Eq a => a -> a -> Bool
== Expectation
e3 Bool -> Bool -> Bool
&& Expectation
e2 Expectation -> Expectation -> Bool
forall a. Eq a => a -> a -> Bool
== Expectation
e4
  Expectation
_ == Expectation
_ = Bool
False

instance Show Expectation where
  show :: Expectation -> String
show (ExpectStat Lens' GoldenStats Double
_ Tolerance
tol) = String
"expect <field> " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Tolerance -> String
forall a. Show a => a -> String
show Tolerance
tol
  show (And Expectation
e1 Expectation
e2) = String
"(" String -> ShowS
forall a. [a] -> [a] -> [a]
++ Expectation -> String
forall a. Show a => a -> String
show Expectation
e1 String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" &&~ " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Expectation -> String
forall a. Show a => a -> String
show Expectation
e2 String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
")"
  show (Or Expectation
e1 Expectation
e2) = String
"(" String -> ShowS
forall a. [a] -> [a] -> [a]
++ Expectation -> String
forall a. Show a => a -> String
show Expectation
e1 String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" ||~ " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Expectation -> String
forall a. Show a => a -> String
show Expectation
e2 String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
")"

-- -----------------------------------------------------------------------------
-- Expectation Combinators
-- -----------------------------------------------------------------------------

-- | Create an expectation for a specific statistic field.
--
-- Example:
--
-- @
-- expect _statsMedian (Percent 10.0)
-- expect _statsIQR (Absolute 0.5)
-- expect _statsMean (Hybrid 15.0 0.01)
-- expect _statsMean (MustImprove 10.0)
-- @
expect :: Lens' GoldenStats Double -> Tolerance -> Expectation
expect :: Lens' GoldenStats Double -> Tolerance -> Expectation
expect = Lens' GoldenStats Double -> Tolerance -> Expectation
ExpectStat

-- | Create an expectation using a custom lens.
--
-- This is an alias for 'expect' for compatibility.
expectStat :: Lens' GoldenStats Double -> Tolerance -> Expectation
expectStat :: Lens' GoldenStats Double -> Tolerance -> Expectation
expectStat = Lens' GoldenStats Double -> Tolerance -> Expectation
expect

-- | Check if an expectation is satisfied for the given golden and actual stats.
--
-- Returns 'True' if the expectation passes, 'False' otherwise.
checkExpectation :: Expectation -> GoldenStats -> GoldenStats -> Bool
checkExpectation :: Expectation -> GoldenStats -> GoldenStats -> Bool
checkExpectation (ExpectStat Lens' GoldenStats Double
lns Tolerance
tol) GoldenStats
golden GoldenStats
actual =
  let baseline :: Double
baseline = GoldenStats
golden GoldenStats -> Getting Double GoldenStats Double -> Double
forall s a. s -> Getting a s a -> a
^. Getting Double GoldenStats Double
Lens' GoldenStats Double
lns
      current :: Double
current = GoldenStats
actual GoldenStats -> Getting Double GoldenStats Double -> Double
forall s a. s -> Getting a s a -> a
^. Getting Double GoldenStats Double
Lens' GoldenStats Double
lns
  in Tolerance -> Double -> Double -> Bool
checkTolerance Tolerance
tol Double
baseline Double
current
checkExpectation (And Expectation
e1 Expectation
e2) GoldenStats
golden GoldenStats
actual =
  Expectation -> GoldenStats -> GoldenStats -> Bool
checkExpectation Expectation
e1 GoldenStats
golden GoldenStats
actual Bool -> Bool -> Bool
&& Expectation -> GoldenStats -> GoldenStats -> Bool
checkExpectation Expectation
e2 GoldenStats
golden GoldenStats
actual
checkExpectation (Or Expectation
e1 Expectation
e2) GoldenStats
golden GoldenStats
actual =
  Expectation -> GoldenStats -> GoldenStats -> Bool
checkExpectation Expectation
e1 GoldenStats
golden GoldenStats
actual Bool -> Bool -> Bool
|| Expectation -> GoldenStats -> GoldenStats -> Bool
checkExpectation Expectation
e2 GoldenStats
golden GoldenStats
actual

-- | Check tolerance between baseline and current values.
checkTolerance :: Tolerance -> Double -> Double -> Bool
checkTolerance :: Tolerance -> Double -> Double -> Bool
checkTolerance (Percent Double
pct) Double
baseline Double
current =
  Double -> Double -> Double -> Bool
withinPercent Double
pct Double
baseline Double
current
checkTolerance (Absolute Double
absThreshold) Double
baseline Double
current =
  Double -> Double -> Double -> Bool
withinAbsolute Double
absThreshold Double
baseline Double
current
checkTolerance (Hybrid Double
pct Double
absThreshold) Double
baseline Double
current =
  Double -> Double -> Double -> Double -> Bool
withinHybrid Double
pct Double
absThreshold Double
baseline Double
current
checkTolerance (MustImprove Double
minPct) Double
baseline Double
current =
  Double -> Double -> Double -> Bool
mustImprove Double
minPct Double
baseline Double
current
checkTolerance (MustRegress Double
minPct) Double
baseline Double
current =
  Double -> Double -> Double -> Bool
mustRegress Double
minPct Double
baseline Double
current

-- -----------------------------------------------------------------------------
-- Tolerance Checking Functions
-- -----------------------------------------------------------------------------

-- | Check if value is within percentage tolerance.
--
-- @
-- withinPercent 15.0 baseline actual  -- within ±15%
-- @
withinPercent :: Double -> Double -> Double -> Bool
withinPercent :: Double -> Double -> Double -> Bool
withinPercent Double
tolerance Double
baseline Double
actual =
  let pct :: Double
pct = Double -> Double -> Double
percentDiff Double
baseline Double
actual
  in Double -> Double
forall a. Num a => a -> a
abs Double
pct Double -> Double -> Bool
forall a. Ord a => a -> a -> Bool
<= Double
tolerance

-- | Check if value is within absolute tolerance (milliseconds).
--
-- @
-- withinAbsolute 0.01 baseline actual  -- within ±0.01ms
-- @
withinAbsolute :: Double -> Double -> Double -> Bool
withinAbsolute :: Double -> Double -> Double -> Bool
withinAbsolute Double
threshold Double
baseline Double
actual =
  Double -> Double -> Double
absDiff Double
baseline Double
actual Double -> Double -> Bool
forall a. Ord a => a -> a -> Bool
<= Double
threshold

-- | Check if value satisfies hybrid tolerance (percentage OR absolute).
--
-- @
-- withinHybrid 15.0 0.01 baseline actual  -- within ±15% OR ±0.01ms
-- @
withinHybrid :: Double -> Double -> Double -> Double -> Bool
withinHybrid :: Double -> Double -> Double -> Double -> Bool
withinHybrid Double
pctTolerance Double
absThreshold Double
baseline Double
actual =
  Double -> Double -> Double -> Bool
withinPercent Double
pctTolerance Double
baseline Double
actual Bool -> Bool -> Bool
||
  Double -> Double -> Double -> Bool
withinAbsolute Double
absThreshold Double
baseline Double
actual

-- | Check if actual is faster than baseline by at least the given percentage.
--
-- @
-- mustImprove 10.0 baseline actual  -- must be ≥10% faster
-- @
mustImprove :: Double -> Double -> Double -> Bool
mustImprove :: Double -> Double -> Double -> Bool
mustImprove Double
minPercent Double
baseline Double
actual =
  let pct :: Double
pct = Double -> Double -> Double
percentDiff Double
baseline Double
actual
  in Double
pct Double -> Double -> Bool
forall a. Ord a => a -> a -> Bool
<= Double -> Double
forall a. Num a => a -> a
negate Double
minPercent  -- Negative percentage = improvement

-- | Check if actual is slower than baseline by at least the given percentage.
--
-- @
-- mustRegress 5.0 baseline actual  -- must be ≥5% slower
-- @
mustRegress :: Double -> Double -> Double -> Bool
mustRegress :: Double -> Double -> Double -> Bool
mustRegress Double
minPercent Double
baseline Double
actual =
  let pct :: Double
pct = Double -> Double -> Double
percentDiff Double
baseline Double
actual
  in Double
pct Double -> Double -> Bool
forall a. Ord a => a -> a -> Bool
>= Double
minPercent  -- Positive percentage = regression

-- -----------------------------------------------------------------------------
-- Infix Operators
-- -----------------------------------------------------------------------------

-- | Infix operator for percentage tolerance check.
--
-- @
-- baseline \@~ 15.0 $ actual  -- within ±15%
-- @
(@~) :: Double -> Double -> Double -> Bool
@~ :: Double -> Double -> Double -> Bool
(@~) Double
baseline Double
tolerance Double
actual = Double -> Double -> Double -> Bool
withinPercent Double
tolerance Double
baseline Double
actual

infixl 4 @~

-- | Infix operator for absolute tolerance check.
--
-- @
-- baseline \@< 0.01 $ actual  -- within ±0.01ms
-- @
(@<) :: Double -> Double -> Double -> Bool
@< :: Double -> Double -> Double -> Bool
(@<) Double
baseline Double
threshold Double
actual = Double -> Double -> Double -> Bool
withinAbsolute Double
threshold Double
baseline Double
actual

infixl 4 @<

-- | Infix operator for "must improve" check.
--
-- @
-- baseline \@<< 10.0 $ actual  -- must be ≥10% faster
-- @
(@<<) :: Double -> Double -> Double -> Bool
@<< :: Double -> Double -> Double -> Bool
(@<<) Double
baseline Double
minPercent Double
actual = Double -> Double -> Double -> Bool
mustImprove Double
minPercent Double
baseline Double
actual

infixl 4 @<<

-- | Infix operator for "must regress" check.
--
-- @
-- baseline \@>> 5.0 $ actual  -- must be ≥5% slower  
-- @
(@>>) :: Double -> Double -> Double -> Bool
@>> :: Double -> Double -> Double -> Bool
(@>>) Double
baseline Double
minPercent Double
actual = Double -> Double -> Double -> Bool
mustRegress Double
minPercent Double
baseline Double
actual

infixl 4 @>>

-- -----------------------------------------------------------------------------
-- Boolean Composition
-- -----------------------------------------------------------------------------

-- | AND composition of expectations (both must pass).
--
-- @
-- expect _statsMean (Percent 15.0) &&~ expect _statsMAD (Percent 50.0)
-- @
(&&~) :: Expectation -> Expectation -> Expectation
&&~ :: Expectation -> Expectation -> Expectation
(&&~) = Expectation -> Expectation -> Expectation
And

infixr 3 &&~

-- | OR composition of expectations (either can pass).
--
-- @
-- expect _statsMedian (Percent 10.0) ||~ expect _statsMin (Absolute 0.01)
-- @
(||~) :: Expectation -> Expectation -> Expectation
||~ :: Expectation -> Expectation -> Expectation
(||~) = Expectation -> Expectation -> Expectation
Or

infixr 2 ||~

-- -----------------------------------------------------------------------------
-- Utilities
-- -----------------------------------------------------------------------------

-- | Calculate percentage difference between baseline and actual.
--
-- Returns: @((actual - baseline) / baseline) * 100@
--
-- * Positive = regression (slower)
-- * Negative = improvement (faster)
-- * Zero = no change
percentDiff :: Double -> Double -> Double
percentDiff :: Double -> Double -> Double
percentDiff Double
baseline Double
actual
  | Double
baseline Double -> Double -> Bool
forall a. Eq a => a -> a -> Bool
== Double
0 = if Double
actual Double -> Double -> Bool
forall a. Eq a => a -> a -> Bool
== Double
0 then Double
0 else Double
100
  | Bool
otherwise = ((Double
actual Double -> Double -> Double
forall a. Num a => a -> a -> a
- Double
baseline) Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ Double
baseline) Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
100

-- | Calculate absolute difference between baseline and actual.
--
-- Returns: @abs(actual - baseline)@
absDiff :: Double -> Double -> Double
absDiff :: Double -> Double -> Double
absDiff Double
baseline Double
actual = Double -> Double
forall a. Num a => a -> a
abs (Double
actual Double -> Double -> Double
forall a. Num a => a -> a -> a
- Double
baseline)

-- | Extract tolerance description from an expectation for error messages.
-- For compound expectations (And/Or), returns the first tolerance found.
toleranceFromExpectation :: Expectation -> (Double, Maybe Double)
toleranceFromExpectation :: Expectation -> (Double, Maybe Double)
toleranceFromExpectation (ExpectStat Lens' GoldenStats Double
_ Tolerance
tol) = Tolerance -> (Double, Maybe Double)
toleranceValues Tolerance
tol
toleranceFromExpectation (And Expectation
e1 Expectation
_) = Expectation -> (Double, Maybe Double)
toleranceFromExpectation Expectation
e1
toleranceFromExpectation (Or Expectation
e1 Expectation
_) = Expectation -> (Double, Maybe Double)
toleranceFromExpectation Expectation
e1

-- | Extract percentage and optional absolute tolerance from a Tolerance.
toleranceValues :: Tolerance -> (Double, Maybe Double)
toleranceValues :: Tolerance -> (Double, Maybe Double)
toleranceValues (Percent Double
pct) = (Double
pct, Maybe Double
forall a. Maybe a
Nothing)
toleranceValues (Absolute Double
abs_) = (Double
0, Double -> Maybe Double
forall a. a -> Maybe a
Just Double
abs_)
toleranceValues (Hybrid Double
pct Double
abs_) = (Double
pct, Double -> Maybe Double
forall a. a -> Maybe a
Just Double
abs_)
toleranceValues (MustImprove Double
pct) = (Double
pct, Maybe Double
forall a. Maybe a
Nothing)
toleranceValues (MustRegress Double
pct) = (Double
pct, Maybe Double
forall a. Maybe a
Nothing)