packages feed

haal-0.7.0.0: test/EquivalenceOracleSpec.hs

module EquivalenceOracleSpec (
    spec,
) where

import Control.Monad.Reader
import qualified Data.Set as Set
import Haal.Automaton.MealyAutomaton (MealyAutomaton, mkMealyAutomaton)
import Haal.BlackBox (states)
import Haal.EquivalenceOracle.WMethod (
    RandomWMethodConfig (..),
    WMethodConfig (..),
    mkRandomWMethod,
    mkWMethod,
    wmethodSuiteSize,
 )
import Haal.EquivalenceOracle.WpMethod (
    RandomWpMethodConfig (..),
    WpMethodConfig (..),
    mkRandomWpMethod,
    mkWpMethod,
 )
import Haal.Experiment
import Haal.Learning.LMstar (LMstarConfig (..), mkLMstar)
import System.Random (mkStdGen)
import Test.Hspec (Spec, context, describe, it, shouldBe, shouldNotBe, shouldSatisfy)
import Test.QuickCheck (Property, property, (==>))
import Utils

-- Generic identity and difference properties
prop_identity :: (OracleWrapper w oracle) => Mealy Input Output -> w -> Bool
prop_identity (Mealy aut) w = ([], []) == snd (runReader (findCex (unwrap w) aut) aut)

prop_difference :: (OracleWrapper w oracle) => Mealy Input Output -> Mealy Input Output -> w -> Property
prop_difference (Mealy aut1) (Mealy aut2) w =
    aut1 /= aut2 ==> ([], []) /= snd (runReader (findCex (unwrap w) aut1) aut2)

-- WMethod-specific cardinality law
prop_WMethodCardinality :: ArbWMethod -> Mealy Input Output -> Bool
prop_WMethodCardinality (ArbWMethod wm) (Mealy aut) =
    length (snd (testSuite wm aut)) == wmethodSuiteSize wm aut

{- | A counter modulo 5 that outputs 'Y' when input 'A' makes it wrap around and
'X' otherwise; every other input resets it. No single input tells its states
apart, so the first hypothesis of LM* has one state.
-}
counter :: MealyAutomaton Int Input Output
counter = mkMealyAutomaton delta lambda (Set.fromList [0 .. 4]) 0
  where
    delta s A = (s + 1) `mod` 5
    delta _ _ = 0
    lambda 4 A = Y
    lambda _ _ = X

-- | A hypothesis with a single state, which always outputs 'X'.
oneState :: MealyAutomaton Int Input Output
oneState = mkMealyAutomaton (\_ _ -> 0) (\_ _ -> X) (Set.fromList [0]) 0

-- | The test suite an oracle generates for the one-state hypothesis.
suiteForOneState :: (EquivalenceOracle oracle) => Either String oracle -> [[Input]]
suiteForOneState = either error (\o -> snd (testSuite o oneState))

-- | The counterexample an oracle finds for the one-state hypothesis of 'counter'.
cexForOneState :: (EquivalenceOracle oracle) => Either String oracle -> [Input]
cexForOneState = either error (\o -> fst (snd (runReader (findCex o oneState) counter)))

spec :: Spec
spec = do
    describe "A hypothesis with a single state" $ do
        -- Its characterizing set is empty. The oracles used to build no test
        -- words from it (W, Wp) or crash on it (random Wp).
        it "gets a non-empty W-method test suite" $
            suiteForOneState (mkWMethod (WMethodConfig 1)) `shouldNotBe` []
        it "gets a non-empty Wp-method test suite" $
            suiteForOneState (mkWpMethod (WpMethodConfig 1)) `shouldNotBe` []
        -- Summing the lengths generates every test word, which is where the
        -- random Wp-method crashed.
        it "gets a non-empty random W-method test suite" $
            sum (map length (suiteForOneState (mkRandomWMethod (RandomWMethodConfig (mkStdGen 1) 20 6))))
                `shouldSatisfy` (> 0)
        it "gets a non-empty random Wp-method test suite" $
            sum (map length (suiteForOneState (mkRandomWpMethod (RandomWpMethodConfig (mkStdGen 1) 4 3 20))))
                `shouldSatisfy` (> 0)
        it "is refuted by the W-method with enough extra states" $
            cexForOneState (mkWMethod (WMethodConfig 4)) `shouldNotBe` []
        it "is refuted by the Wp-method with enough extra states" $
            cexForOneState (mkWpMethod (WpMethodConfig 4)) `shouldNotBe` []
        it "does not stop LM* from learning all states of the counter" $ do
            let oracle = either error id (mkWMethod (WMethodConfig 4))
                model = runExperiment (experiment (mkLMstar Star) oracle) counter
            Set.size (states model) `shouldBe` 5

    describe "WMethod Equivalence Oracle" $ do
        context "when two automatons differ" $
            it "WMethod returns Just" $
                property (prop_difference :: Mealy Input Output -> Mealy Input Output -> ArbWMethod -> Property)

        context "when two automatons are the same" $
            it "WMethod returns Nothing" $
                property (prop_identity :: Mealy Input Output -> ArbWMethod -> Bool)

        it "computes the correct WMethod test suite size" $
            property prop_WMethodCardinality

    describe "WpMethod Equivalence Oracle" $ do
        context "when two automatons differ" $
            it "WpMethod returns Just" $
                property (prop_difference :: Mealy Input Output -> Mealy Input Output -> ArbWpMethod -> Property)

        context "when two automatons are the same" $
            it "WpMethod returns Nothing" $
                property (prop_identity :: Mealy Input Output -> ArbWpMethod -> Bool)

    describe "RandomWords Equivalence Oracle" $ do
        context "when two automatons differ" $
            it "RandomWords returns Just" $
                property (prop_difference :: Mealy Input Output -> Mealy Input Output -> ArbRandomWords -> Property)

        context "when two automatons are the same" $
            it "RandomWords returns Nothing" $
                property (prop_identity :: Mealy Input Output -> ArbRandomWords -> Bool)

    describe "RandomWalk Equivalence Oracle" $ do
        context "when two automatons differ" $
            it "RandomWalk returns Just" $
                property (prop_difference :: Mealy Input Output -> Mealy Input Output -> ArbRandomWalk -> Property)

        context "when two automatons are the same" $
            it "RandomWalk returns Nothing" $
                property (prop_identity :: Mealy Input Output -> ArbRandomWalk -> Bool)

    describe "RandomWMethod Equivalence Oracle" $ do
        context "when two automatons differ" $
            it "RandomWMethod returns Just" $
                property (prop_difference :: Mealy Input Output -> Mealy Input Output -> ArbRandomWMethod -> Property)

        context "when two automatons are the same" $
            it "RandomWMethod returns Nothing" $
                property (prop_identity :: Mealy Input Output -> ArbRandomWMethod -> Bool)

    describe "RandomWpMethod Equivalence Oracle" $ do
        context "when two automatons differ" $
            it "RandomWpMethod returns Just" $
                property (prop_difference :: Mealy Input Output -> Mealy Input Output -> ArbRandomWpMethod -> Property)

        context "when two automatons are the same" $
            it "RandomWpMethod returns Nothing" $
                property (prop_identity :: Mealy Input Output -> ArbRandomWpMethod -> Bool)