packages feed

pure-borrow-0.1.0.0: test/PureBorrow/Internal/Bench/Worklist/ResumeSpec.hs

{-# LANGUAGE BlockArguments #-}

module PureBorrow.Internal.Bench.Worklist.ResumeSpec (
  module PureBorrow.Internal.Bench.Worklist.ResumeSpec,
) where

import Data.Vector.Unboxed qualified as U
import PureBorrow.Internal.Bench.Worklist.Resume
import Test.Tasty (TestTree)
import Test.Tasty.HUnit

test_worklistOpenOnceEvidence :: TestTree
test_worklistOpenOnceEvidence =
  testCase "open-once traversal preserves the frozen trace and direct output" do
    mapM_
      ( \target -> do
          let direct = worklistDirectOpenOnceRoot target
          summary direct @?= expectedSummary target 4 0 0 0
          assertFrozenOutput target direct
          worklistPureBorrowOpenOnceRoot target @?= direct
      )
      [minBound .. maxBound]

test_worklistCheckedSurfaceEvidence :: TestTree
test_worklistCheckedSurfaceEvidence =
  testCase "checked-surface reads reproduce the unchecked traversal exactly" do
    mapM_
      ( \target -> do
          let unchecked = worklistPureBorrowOpenOnceRoot target
          assertFrozenOutput target unchecked
          worklistPureBorrowCheckedOpenOnceRoot target @?= unchecked
      )
      [minBound .. maxBound]
    mapM_
      ( \seed ->
          mapM_
            ( \target ->
                worklistPureBorrowCheckedOpenOnceRootWithSeed seed target
                  @?= worklistPureBorrowOpenOnceRootWithSeed seed target
            )
            [minBound .. maxBound]
      )
      [1, 37]

test_worklistReopenEvidence :: TestTree
test_worklistReopenEvidence =
  testCase "flat and nested reopen shapes preserve every frozen growth trace" do
    mapM_
      ( \(growth, target, flatOpens, nestedOpens, resumes, updates, growths) -> do
          let directFlat =
                worklistDirectReopenRoot FlatReopen growth target
              directNested =
                worklistDirectReopenRoot NestedReopen growth target
          summary directFlat
            @?= expectedSummary
              target
              flatOpens
              resumes
              updates
              growths
          summary directNested
            @?= expectedSummary
              target
              nestedOpens
              resumes
              updates
              growths
          assertFrozenOutput target directFlat
          assertSameTraversalOutput directFlat directNested
          worklistPureBorrowFlatReopenRoot growth target
            @?= directFlat
          worklistPureBorrowNestedReopenRoot growth target
            @?= directNested
      )
      reopenCases
    mapM_
      ( \(target, noGrowth, growth) ->
          assertSameTraversalOutput
            (worklistDirectReopenRoot NestedReopen noGrowth target)
            (worklistDirectReopenRoot NestedReopen growth target)
      )
      [ (target, noGrowth, growth)
      | target <- [minBound .. maxBound]
      , (noGrowth, growth) <-
          [ (NoGrowthBatch64, SparseGrowth)
          , (NoGrowthBatch8, DenseGrowth)
          ]
      ]

test_worklistSeededEvidence :: TestTree
test_worklistSeededEvidence =
  testCase "fresh-run seeds preserve direct and Pure Borrow equivalence" do
    mapM_
      ( \seed ->
          mapM_
            ( \target ->
                worklistPureBorrowOpenOnceRootWithSeed seed target
                  @?= worklistDirectOpenOnceRootWithSeed seed target
            )
            [minBound .. maxBound]
      )
      [1, 37]
    mapM_
      ( \(label, root) -> do
          let seed1 = root 1
              seed37 = root 37
          assertBool
            (label <> " must include the seed in its digest")
            (finalDigest (summary seed1) /= finalDigest (summary seed37))
          root 1 @?= seed1
          root 37 @?= seed37
          root 1 @?= seed1
      )
      [ ("open-once", \seed -> worklistPureBorrowOpenOnceRootWithSeed seed Drain)
      ,
        ( "dense nested reopen"
        , \seed ->
            worklistPureBorrowNestedReopenRootWithSeed
              seed
              DenseGrowth
              Drain
        )
      ]
    mapM_
      ( \seed ->
          mapM_
            ( \(growth, target) -> do
                worklistPureBorrowFlatReopenRootWithSeed seed growth target
                  @?= worklistDirectReopenRootWithSeed
                    seed
                    FlatReopen
                    growth
                    target
                worklistPureBorrowNestedReopenRootWithSeed seed growth target
                  @?= worklistDirectReopenRootWithSeed
                    seed
                    NestedReopen
                    growth
                    target
            )
            [ (growth, target)
            | growth <- [minBound .. maxBound]
            , target <- [minBound .. maxBound]
            ]
      )
      [1, 37]

reopenCases ::
  [ ( WorklistGrowth
    , WorklistTarget
    , Int
    , Int
    , Int
    , Int
    , Int
    )
  ]
reopenCases =
  [ (NoGrowth, Drain, 84, 44, 20, 41, 0)
  , (NoGrowth, StopEarly, 44, 24, 10, 22, 0)
  , (NoGrowthBatch64, Drain, 272, 138, 67, 131, 0)
  , (NoGrowthBatch64, StopEarly, 100, 52, 24, 50, 0)
  , (NoGrowthBatch8, Drain, 2056, 1030, 513, 934, 0)
  , (NoGrowthBatch8, StopEarly, 692, 348, 172, 346, 0)
  , (SparseGrowth, Drain, 272, 138, 67, 131, 8)
  , (SparseGrowth, StopEarly, 100, 52, 24, 50, 7)
  , (DenseGrowth, Drain, 2056, 1030, 513, 934, 21)
  , (DenseGrowth, StopEarly, 692, 348, 172, 346, 19)
  ]

expectedSummary ::
  WorklistTarget ->
  Int ->
  Int ->
  Int ->
  Int ->
  WorklistSummary
expectedSummary target opens resumes updates growths =
  case target of
    Drain ->
      WorklistSummary
        { outcome = Drained
        , visitedNodes = 4096
        , enqueueTransitions = 4095
        , offsetReads = 8192
        , adjacencyReads = 12288
        , payloadReads = 12288
        , markReads = 12288
        , markWrites = 4095
        , queueReads = 4096
        , queueWrites = 4095
        , logWrites = 4096
        , contentOpens = opens
        , resumeBoundaries = resumes
        , headerUpdates = updates
        , bufferGrowths = growths
        , finalDigest = 2728622868939553119
        }
    StopEarly ->
      WorklistSummary
        { outcome = Stopped
        , visitedNodes = 1365
        , enqueueTransitions = 2888
        , offsetReads = 2730
        , adjacencyReads = 4095
        , payloadReads = 4095
        , markReads = 4095
        , markWrites = 2888
        , queueReads = 1365
        , queueWrites = 2888
        , logWrites = 1365
        , contentOpens = opens
        , resumeBoundaries = resumes
        , headerUpdates = updates
        , bufferGrowths = growths
        , finalDigest = 5952155574826728904
        }

assertFrozenOutput :: WorklistTarget -> WorklistOutput -> Assertion
assertFrozenOutput target result = do
  let resultSummary = summary result
      visits = visitedNodes resultSummary
      enqueues = enqueueTransitions resultSummary
  U.sum (finalMarks result) @?= enqueues + 1
  finalState result
    @?= U.fromList
      [ visits
      , enqueues + 1
      , visits
      , enqueues
      , visits
      ]
  U.length (finalQueue result) @?= enqueues + 1
  U.length (finalLog result) @?= visits
  outcome resultSummary
    @?= case target of
      Drain -> Drained
      StopEarly -> Stopped

assertSameTraversalOutput ::
  WorklistOutput ->
  WorklistOutput ->
  Assertion
assertSameTraversalOutput left right = do
  finalMarks left @?= finalMarks right
  finalState left @?= finalState right
  finalQueue left @?= finalQueue right
  finalLog left @?= finalLog right