summaryrefslogtreecommitdiff
path: root/source/Test/Unit/Provers.hs
diff options
context:
space:
mode:
Diffstat (limited to 'source/Test/Unit/Provers.hs')
-rw-r--r--source/Test/Unit/Provers.hs287
1 files changed, 242 insertions, 45 deletions
diff --git a/source/Test/Unit/Provers.hs b/source/Test/Unit/Provers.hs
index d852755..22a6b5b 100644
--- a/source/Test/Unit/Provers.hs
+++ b/source/Test/Unit/Provers.hs
@@ -2,24 +2,24 @@
module Test.Unit.Provers (unitTests) where
-import Base
+import Base hiding (Empty)
+import Checking.Backend.Problem
+import Checking.Core
import Provers
-import Report.Location (pattern Nowhere)
-import Syntax.Internal
- ( Directness(..)
- , Hypothesis(..)
- , Marker(..)
- , Task(..)
- , pattern Top
- )
import Control.Concurrent (threadDelay)
import Control.Exception (bracket)
import Control.Exception qualified as Exception
import Control.Monad.Logger (runNoLoggingT)
+import Data.IORef
+ ( newIORef
+ , readIORef
+ , writeIORef
+ )
import Data.Set qualified as Set
import Data.Text qualified as Text
import Data.Text.IO qualified as Text
+import Data.Vector qualified as Vector
import System.Directory qualified as Directory
import System.Exit (ExitCode(..))
import System.FilePath.Posix ((</>))
@@ -34,16 +34,162 @@ import Test.Tasty
import Test.Tasty.HUnit
import Text.Read (readMaybe)
import Text.Megaparsec (parseMaybe)
-import UnliftIO.Async (cancel, withAsync)
+import UnliftIO.Async (cancel, mapConcurrently, withAsync)
unitTests :: TestTree
unitTests =
testGroup "Provers"
[ vampireStatusParserTests
, vampireClassifierTests
+ , jobsSelectionTests
+ , vampireExecutorTests
, vampireProcessTests
]
+jobsSelectionTests :: TestTree
+jobsSelectionTests =
+ testGroup "effective jobs"
+ [ testCase "uses a positive override exactly" do
+ detectorCalled <- newIORef False
+ selected <- selectEffectiveJobs
+ (effectiveJobs 3)
+ (writeIORef detectorCalled True >> pure 99)
+ jobsSelectionEffectiveJobs selected
+ `shouldBe` positiveJobs 3
+ jobsSelectionDetectedProcessors selected `shouldBe` Nothing
+ jobsSelectionWasOverridden selected `shouldBe` True
+ readIORef detectorCalled >>= (`shouldBe` False)
+ , testCase "leaves one detected logical processor free" do
+ selected <- selectEffectiveJobs Nothing (pure 8)
+ jobsSelectionEffectiveJobs selected
+ `shouldBe` positiveJobs 7
+ jobsSelectionDetectedProcessors selected `shouldBe` Just 8
+ jobsSelectionWasOverridden selected `shouldBe` False
+ , testCase "falls back to one after bad detection" do
+ nonPositive <- selectEffectiveJobs Nothing (pure 0)
+ jobsSelectionEffectiveJobs nonPositive
+ `shouldBe` positiveJobs 1
+ failed <- selectEffectiveJobs Nothing
+ (Exception.throwIO (userError "processor detection failed"))
+ jobsSelectionEffectiveJobs failed
+ `shouldBe` positiveJobs 1
+ jobsSelectionDetectedProcessors failed `shouldBe` Nothing
+ ]
+
+vampireExecutorTests :: TestTree
+vampireExecutorTests =
+ testGroup "bounded Vampire executor"
+ [ testCase "bounds live one-core processes" do
+ prepared <- preparedTypedTask 0
+ withFakeVampire
+ [ "previous=''"
+ , "found=0"
+ , "for argument in \"$@\"; do"
+ , " if [ \"$previous\" = '--cores' ]; then"
+ , " [ \"$argument\" = '1' ] || exit 17"
+ , " found=1"
+ , " fi"
+ , " previous=$argument"
+ , "done"
+ , "[ \"$found\" = '1' ] || exit 18"
+ , "cat >/dev/null"
+ , "sleep 0.2"
+ , "printf '%s\n' '% SZS status Theorem for fake'"
+ ]
+ \vampireCommand ->
+ withVampireExecutor
+ (positiveJobs 2)
+ vampireCommand
+ (\_position _request -> pure ())
+ \executor -> do
+ answers <- mapConcurrently
+ (\ordinal ->
+ runNoLoggingT
+ (runPreparedTypedProverWithExecutor
+ executor
+ (workPosition 1 ordinal)
+ prepared))
+ [1..4]
+ traverse_ assertProved answers
+ observed <- vampireExecutorObservation executor
+ vampireExecutorRunCount observed `shouldBe` 4
+ vampireExecutorMaximumLiveCount observed
+ `shouldBe` 2
+ , testCase "propagates observer failure to the submitter" do
+ prepared <- preparedTypedTask 0
+ withFakeVampire
+ [ "cat >/dev/null"
+ , "printf '%s\n' '% SZS status Theorem for fake'"
+ ]
+ \vampireCommand ->
+ withVampireExecutor
+ (positiveJobs 1)
+ vampireCommand
+ (\_position _request ->
+ Exception.throwIO
+ (userError "observer failed"))
+ \executor -> do
+ result <- Exception.try
+ (runNoLoggingT
+ (runPreparedTypedProverWithExecutor
+ executor
+ (workPosition 1 1)
+ prepared))
+ case result of
+ Left (failure :: Exception.IOException) ->
+ assertBool
+ "observer exception"
+ ("observer failed"
+ `Text.isInfixOf`
+ Text.pack (show failure))
+ Right answer ->
+ assertFailure
+ ("observer failure was lost: "
+ <> show answer)
+ , testCase "cancels queued and running jobs independently" do
+ prepared <- preparedTypedTask 0
+ withProcessGroupFake
+ defaultTimeLimit
+ []
+ \pidFile vampireCommand ->
+ withVampireExecutor
+ (positiveJobs 1)
+ vampireCommand
+ (\_position _request -> pure ())
+ \executor ->
+ withAsync
+ (runNoLoggingT
+ (runPreparedTypedProverWithExecutor
+ executor
+ (workPosition 1 1)
+ prepared))
+ \running -> do
+ processIds <- waitForProcessIds pidFile
+ withAsync
+ (runNoLoggingT
+ (runPreparedTypedProverWithExecutor
+ executor
+ (workPosition 2 1)
+ prepared))
+ \queued -> do
+ waitForSubmittedCount executor 2
+ cancel queued
+ cancel running
+ observed <- vampireExecutorObservation
+ executor
+ vampireExecutorSubmittedCount observed
+ `shouldBe` 2
+ vampireExecutorRunCount observed
+ `shouldBe` 1
+ assertProcessesGone processIds
+ ]
+
+positiveJobs :: Int -> EffectiveJobs
+positiveJobs amount =
+ fromMaybe
+ (error "test requested a non-positive job count")
+ (effectiveJobs amount)
+
vampireStatusParserTests :: TestTree
vampireStatusParserTests =
testGroup "Vampire status parser"
@@ -165,12 +311,14 @@ vampireProcessTests =
assertFailure
("expected malformed stdout, got " <> show result)
, testCase "returns a broken stdin pipe" do
+ prepared <- preparedTypedTask 20000
result <- withFakeVampire
[ "exec 0<&-"
, "sleep 1"
]
\vampireCommand ->
- runVampireProcess vampireCommand largeTask
+ runNoLoggingT
+ (runPreparedTypedProver vampireCommand prepared)
case result of
Left (ProverCommunicationFailed _ ProverStdin _) ->
pure ()
@@ -179,6 +327,7 @@ vampireProcessTests =
("expected a communication failure, got "
<> show processResult)
, testCase "drains output while feeding prover input" do
+ prepared <- preparedTypedTask 20000
guardedAnswer <- Timeout.timeout
30000000
(withFakeVampire
@@ -192,21 +341,23 @@ vampireProcessTests =
, "exit 0"
]
\vampireCommand -> do
- (_location, _formula, answer) <-
- runNoLoggingT
- (runProver vampireCommand largeTask)
- pure answer)
+ runNoLoggingT
+ (runPreparedTypedProver
+ vampireCommand
+ prepared))
case guardedAnswer of
Nothing ->
assertFailure "prover communication did not finish"
Just answer ->
assertProved answer
, testCase "reports signal termination separately" do
+ prepared <- preparedTypedTask 0
result <- withFakeVampire
[ "kill -TERM $$"
]
\vampireCommand ->
- runVampireProcess vampireCommand directTask
+ runNoLoggingT
+ (runPreparedTypedProver vampireCommand prepared)
case result of
Left
(ProverTerminatedBySignal
@@ -222,27 +373,31 @@ vampireProcessTests =
("expected signal termination, got "
<> show processResult)
, testCase "deadline terminates and reaps the process group" do
+ prepared <- preparedTypedTask 0
withProcessGroupFake
(Seconds 0)
[]
\pidFile vampireCommand -> do
result <-
- runVampireProcess
- vampireCommand
- directTask
+ runNoLoggingT
+ (runPreparedTypedProver
+ vampireCommand
+ prepared)
assertTimedOut result
processIds <- readProcessIds pidFile
assertProcessesGone processIds
, testCase "output exhaustion terminates the process group" do
+ prepared <- preparedTypedTask 0
withProcessGroupFake
defaultTimeLimit
[ "head -c 33554432 /dev/zero"
]
\pidFile vampireCommand -> do
result <-
- runVampireProcess
- vampireCommand
- directTask
+ runNoLoggingT
+ (runPreparedTypedProver
+ vampireCommand
+ prepared)
case result of
Left
(ProverOutputLimitExceeded
@@ -257,14 +412,16 @@ vampireProcessTests =
processIds <- readProcessIds pidFile
assertProcessesGone processIds
, testCase "cancellation terminates and reaps the process group" do
+ prepared <- preparedTypedTask 0
withProcessGroupFake
defaultTimeLimit
[]
\pidFile vampireCommand ->
withAsync
- (runVampireProcess
- vampireCommand
- directTask)
+ (runNoLoggingT
+ (runPreparedTypedProver
+ vampireCommand
+ prepared))
\worker -> do
processIds <- waitForProcessIds pidFile
cancel worker
@@ -408,6 +565,24 @@ waitForProcessIds path = do
threadDelay 10000
loop
+waitForSubmittedCount :: VampireExecutor -> Int -> Assertion
+waitForSubmittedCount executor expected = do
+ guarded <- Timeout.timeout 10000000 loop
+ case guarded of
+ Just () ->
+ pure ()
+ Nothing ->
+ assertFailure
+ ("executor did not submit " <> show expected <> " requests")
+ where
+ loop = do
+ observed <- vampireExecutorObservation executor
+ if vampireExecutorSubmittedCount observed >= expected
+ then pure ()
+ else do
+ threadDelay 10000
+ loop
+
assertProcessesGone :: [ProcessID] -> Assertion
assertProcessesGone processIds = do
guarded <- Timeout.timeout 10000000 loop
@@ -442,9 +617,9 @@ runFakeVampire scriptLines =
withFakeVampire
(["cat >/dev/null"] <> scriptLines)
\vampireCommand -> do
- (_location, _formula, answer) <- runNoLoggingT
- (runProver vampireCommand directTask)
- pure answer
+ prepared <- preparedTypedTask 0
+ runNoLoggingT
+ (runPreparedTypedProver vampireCommand prepared)
withFakeVampire
:: [String]
@@ -496,21 +671,43 @@ shouldBe :: (Eq a, Show a, HasCallStack) => a -> a -> Assertion
shouldBe =
flip (assertEqual "")
-directTask :: Task
-directTask =
- Task
- { taskDirectness = Direct
- , taskHypotheses = []
- , taskConjectureLabel = Marker "dummy"
- , taskLocation = Nowhere
- , taskConjecture = Top
- }
+preparedTypedTask
+ :: Int
+ -> IO (PreparedTypedProverTask Int Void Void Void)
+preparedTypedTask factCount = do
+ checked <- expectRight
+ (checkScopedCanonicalCore
+ (const Nothing)
+ []
+ propositionTerm)
+ proposition <- expectRight
+ (supportedProposition Vector.empty checked)
+ capability <- expectRight
+ (classifySupportedProposition (const Nothing) proposition)
+ let facts =
+ Vector.generate
+ factCount
+ (\reference ->
+ typedBackendFact reference proposition capability)
+ problem <- expectRight
+ (planTypedProblem
+ (const Nothing)
+ facts
+ proposition
+ []
+ []
+ ExplicitGlobalPremises
+ FirstOrderLocals)
+ expectRight (prepareTypedProverTask DirectTask problem)
+ where
+ propositionTerm =
+ CEq TySet
+ (CIntrinsic Empty)
+ (CIntrinsic Empty)
-largeTask :: Task
-largeTask =
- directTask
- { taskHypotheses =
- replicate
- 20000
- (Hypothesis (Marker "large") Top)
- }
+expectRight :: Show error => Either error value -> IO value
+expectRight = \case
+ Left failure ->
+ assertFailure (show failure) >> fail "unreachable"
+ Right value ->
+ pure value