sbv-14.8: SBVTestSuite/TestSuite/CodeGeneration/PseudoBoolean.hs
-----------------------------------------------------------------------------
-- |
-- Module : TestSuite.CodeGeneration.PseudoBoolean
-- Copyright : (c) Levent Erkok
-- License : BSD3
-- Maintainer: erkokl@gmail.com
-- Stability : experimental
--
-- Executed pseudo-Boolean comparisons at native overflow boundaries.
-----------------------------------------------------------------------------
{-# OPTIONS_GHC -Wall -Werror #-}
module TestSuite.CodeGeneration.PseudoBoolean (tests, testsWith) where
import Control.Monad (replicateM, void)
import Data.List (intercalate)
import System.Environment (lookupEnv)
import System.Exit (ExitCode(..))
import System.FilePath ((</>))
import System.IO.Temp (withSystemTempDirectory)
import System.Process (readProcessWithExitCode)
import Test.Tasty.HUnit (assertEqual)
import Data.SBV.Tools.CodeGen
import qualified Data.SBV.Tools.CodeGen.Legacy as Legacy
import Utils.SBVTestFramework
-- | Exercise both backends through their public standalone and library APIs.
tests :: TestTree
tests = testGroup "CodeGeneration.PseudoBoolean"
[ testsWith "current" $ \dir library functionName program ->
if library
then void $ compileToCLib (Just dir) "pbLibrary" [(functionName, program)]
else compileToC (Just dir) functionName program
, testsWith "legacy" $ \dir library functionName program ->
if library
then void $ Legacy.compileToCLib (Just dir) "pbLibrary" [(functionName, program)]
else Legacy.compileToC (Just dir) functionName program
]
-- | Reuse the execution matrix for a compatibility backend without changing its
-- import facade. Every truth assignment is checked against unbounded Haskell
-- arithmetic, at both optimization levels and with undefined-behavior checking.
testsWith :: String -> (FilePath -> Bool -> String -> SBVCodeGen () -> IO ()) -> TestTree
testsWith groupName generate = testGroup groupName
[ testCase (form ++ " mapped" ++ show integerWidth ++ " " ++ optimization) $ withSystemTempDirectory "sbv-c-pseudo-boolean" $ \dir -> do
let functionName = "pbChecks"
artifact = if library then "pbLibrary.a" else functionName ++ ".o"
header = if library then "pbLibrary.h" else functionName ++ ".h"
generate dir library functionName $ do
cgOverwriteFiles True
cgGenerateDriver False
cgIntegerSize integerWidth
inputs <- cgInputArr 5 "inputs"
cgOutputArr "checks" (comparisons inputs)
extraFlags <- maybe [] words <$> lookupEnv "SBV_C_TEST_FLAGS"
let flags = ["-std=c11", "-Wall", "-Wextra", "-Werror"] ++ words optimization ++ extraFlags
(makeExit, _, makeError) <- readProcessWithExitCode "make"
["-C", dir, artifact, "CCFLAGS=" ++ unwords flags] ""
assertEqual makeError ExitSuccess makeExit
writeFile (dir </> "caller.c") (caller header functionName)
let executablePath = dir </> "caller"
(ccExit, _, ccError) <- readProcessWithExitCode "cc"
(flags ++ [dir </> "caller.c", dir </> artifact, "-o", executablePath]) ""
assertEqual ccError ExitSuccess ccExit
(runExit, _, runError) <- readProcessWithExitCode executablePath [] ""
assertEqual runError ExitSuccess runExit
mapM_ (\values -> assertEqual "Concrete SBV comparisons must agree with mathematical sums"
(map Just (expected values)) (map unliteral (comparisons (map literal values)))) assignments
| (form, library) <- [("standalone", False), ("library", True)]
, integerWidth <- [8, 64]
, optimization <- ["-O0", "-O2", "-O1 -fsanitize=undefined -fno-sanitize-recover=all"]
]
-- | Coefficients and bounds covering signed 32-bit overflow, signed and unsigned
-- 64-bit overflow, zero weights, exact ties, and crossing a bound late in a sum.
scenarios :: [([Int], Int)]
scenarios = [ ([2000000000, 2000000000, 0, 0, 0], 2100000000)
, ([maxBound, maxBound, 0, 0, 0], maxBound)
, (replicate 5 maxBound, maxBound)
, ([maxBound, 1, maxBound, 1, 0], maxBound)
, ([0, 0, 0, 0, 0], 0)
, ([0, 1, 0, 1, 0], 0)
, ([1, 2, 3, 4, 5], 7)
, ([1, 2, 3, 4, 5], maxBound)
, ([maxBound, maxBound, maxBound, 0, 0], 0)
, ([maxBound - 1, 1, 1, maxBound - 1, 1], maxBound)
]
-- | All assignments to the five independent input Booleans.
assignments :: [[Bool]]
assignments = replicateM 5 [False, True]
-- | Exercise all six pseudo-Boolean operators, plus their empty-list semantics.
comparisons :: [SBool] -> [SBool]
comparisons inputs = concat [let weighted = zip coefficients inputs
in [pbLe weighted bound, pbGe weighted bound, pbEq weighted bound]
| (coefficients, bound) <- scenarios]
++ [pbAtMost inputs 2, pbAtLeast inputs 2, pbExactly inputs 2]
++ [pbAtMost [] 0, pbAtLeast [] 1, pbExactly [] 0]
-- | Compute expected answers with mathematical integers, never machine sums.
expected :: [Bool] -> [Bool]
expected inputs = concat [compareSum (sum [toInteger coefficient | (coefficient, True) <- zip coefficients inputs]) (toInteger bound)
| (coefficients, bound) <- scenarios]
++ compareSum (toInteger (length (filter id inputs))) 2
++ [True, False, True]
where compareSum total bound = [total <= bound, total >= bound, total == bound]
-- | An independent C caller supplies all input combinations and exact expected
-- outputs. Its arrays are shared across calls to check that temporary sums reset.
caller :: String -> String -> String
caller header functionName = unlines
[ "#include <assert.h>"
, "#include \"" ++ header ++ "\""
, "int main(void)"
, "{"
, " static const SBool inputs[][5] = {" ++ intercalate ", " (map row assignments) ++ "};"
, " static const SBool expected[][" ++ show count ++ "] = {" ++ intercalate ", " (map (row . expected) assignments) ++ "};"
, " SBool output[" ++ show count ++ "];"
, " for (size_t i = 0; i < sizeof inputs / sizeof inputs[0]; ++i) {"
, " " ++ functionName ++ "(inputs[i], output);"
, " for (size_t j = 0; j < " ++ show count ++ "; ++j) assert(output[j] == expected[i][j]);"
, " }"
, " return 0;"
, "}"
]
where count = length (expected (replicate 5 False))
row values = "{" ++ intercalate ", " [if value then "true" else "false" | value <- values] ++ "}"