diff --git a/src/main/java/com/github/protocolfuzzing/protocolstatefuzzer/components/learner/oracles/MembershipOracleWrapperRA.java b/src/main/java/com/github/protocolfuzzing/protocolstatefuzzer/components/learner/oracles/MembershipOracleWrapperRA.java new file mode 100644 index 00000000..a160c728 --- /dev/null +++ b/src/main/java/com/github/protocolfuzzing/protocolstatefuzzer/components/learner/oracles/MembershipOracleWrapperRA.java @@ -0,0 +1,57 @@ +package com.github.protocolfuzzing.protocolstatefuzzer.components.learner.oracles; + +import de.learnlib.oracle.MembershipOracle; +import de.learnlib.query.Query; +import de.learnlib.ralib.sul.SULOracle; +import de.learnlib.ralib.words.OutputSymbol; +import de.learnlib.ralib.words.PSymbolInstance; +import net.automatalib.word.Word; +import net.automatalib.word.WordBuilder; + +import java.util.Collection; + +/** + * A wrapper that allows the RALib SULOracle to be used where + * a MembershipOracle is required, since SULOracle does not + * implement this interface. + */ +public class MembershipOracleWrapperRA implements MembershipOracle> { + + /** The wrapped oracle */ + private SULOracle wrappedOracle; + + /** + * Constructs a wrapper around the provided instance + * @param wrappedOracle The SULOracle to wrap + */ + public MembershipOracleWrapperRA(SULOracle wrappedOracle) { + this.wrappedOracle = wrappedOracle; + } + + @Override + public Word answerQuery(Word inputWord) { + PSymbolInstance placeholderElement = new PSymbolInstance(new OutputSymbol("PLACEHOLDER ELEMENT")); + WordBuilder ioTraceBuilder = new WordBuilder(); + for (PSymbolInstance i: inputWord) { + ioTraceBuilder.add(i); + ioTraceBuilder.add(placeholderElement); + } + + Word ioTrace = wrappedOracle.trace(ioTraceBuilder.toWord()); + WordBuilder outputBuilder = new WordBuilder(); + for (PSymbolInstance symInstance : ioTrace) { + if (symInstance.getBaseSymbol() instanceof OutputSymbol) { + outputBuilder.add(symInstance); + } + } + + return outputBuilder.toWord(); + } + + @Override + public void processQueries(Collection>> queries) { + for (Query> query : queries) { + query.answer(answerQuery(query.getInput())); + } + } +} diff --git a/src/main/java/com/github/protocolfuzzing/protocolstatefuzzer/components/sul/core/sulwrappers/DataWordSULWrapper.java b/src/main/java/com/github/protocolfuzzing/protocolstatefuzzer/components/sul/core/sulwrappers/DataWordSULWrapper.java new file mode 100644 index 00000000..e041c489 --- /dev/null +++ b/src/main/java/com/github/protocolfuzzing/protocolstatefuzzer/components/sul/core/sulwrappers/DataWordSULWrapper.java @@ -0,0 +1,39 @@ +package com.github.protocolfuzzing.protocolstatefuzzer.components.sul.core.sulwrappers; + +import de.learnlib.ralib.sul.DataWordSUL; +import de.learnlib.ralib.words.PSymbolInstance; +import de.learnlib.sul.SUL; + +/** + * A wrapper that can be used as an {@code SUL} + * to DataWordSUL converter. Copied from StateFuzzerComposerRA. + */ +public class DataWordSULWrapper extends DataWordSUL { + + /** Stores the wrapped sul */ + protected SUL sul; + + /** + * Constructs a new instance from the given parameters. + * + * @param sul the wrapped sul + */ + public DataWordSULWrapper(SUL sul) { + this.sul = sul; + } + + @Override + public void pre() { + sul.pre(); + } + + @Override + public void post() { + sul.post(); + } + + @Override + public PSymbolInstance step(PSymbolInstance in) { + return sul.step(in); + } +} diff --git a/src/main/java/com/github/protocolfuzzing/protocolstatefuzzer/statefuzzer/core/StateFuzzerComposerRA.java b/src/main/java/com/github/protocolfuzzing/protocolstatefuzzer/statefuzzer/core/StateFuzzerComposerRA.java index cd06b9ba..61e391cd 100644 --- a/src/main/java/com/github/protocolfuzzing/protocolstatefuzzer/statefuzzer/core/StateFuzzerComposerRA.java +++ b/src/main/java/com/github/protocolfuzzing/protocolstatefuzzer/statefuzzer/core/StateFuzzerComposerRA.java @@ -9,6 +9,7 @@ import com.github.protocolfuzzing.protocolstatefuzzer.components.sul.core.AbstractSul; import com.github.protocolfuzzing.protocolstatefuzzer.components.sul.core.SulBuilder; import com.github.protocolfuzzing.protocolstatefuzzer.components.sul.core.SulWrapper; +import com.github.protocolfuzzing.protocolstatefuzzer.components.sul.core.sulwrappers.DataWordSULWrapper; import com.github.protocolfuzzing.protocolstatefuzzer.statefuzzer.core.config.StateFuzzerEnabler; import com.github.protocolfuzzing.protocolstatefuzzer.utils.CleanupTasks; import de.learnlib.query.DefaultQuery; @@ -18,7 +19,6 @@ import de.learnlib.ralib.learning.RaLearningAlgorithm; import de.learnlib.ralib.solver.ConstraintSolver; import de.learnlib.ralib.solver.simple.SimpleConstraintSolver; -import de.learnlib.ralib.sul.DataWordSUL; import de.learnlib.ralib.sul.SULOracle; import de.learnlib.ralib.theory.Theory; import de.learnlib.ralib.words.OutputSymbol; @@ -247,38 +247,4 @@ protected void composeEquivalenceOracle() { this.equivalenceOracle = LearningSetupFactory.createEquivalenceOracle(this.learnerConfig, this.sulOracle.getDataWordSUL(), this.alphabet, this.teachers, this.consts); } - - /** - * A wrapper that can be used as an {@code SUL} - * to DataWordSUL converter. - */ - protected static class DataWordSULWrapper extends DataWordSUL { - - /** Stores the wrapped sul */ - protected SUL sul; - - /** - * Constructs a new instance from the given parameters. - * - * @param sul the wrapped sul - */ - public DataWordSULWrapper(SUL sul) { - this.sul = sul; - } - - @Override - public void pre() { - sul.pre(); - } - - @Override - public void post() { - sul.post(); - } - - @Override - public PSymbolInstance step(PSymbolInstance in) { - return sul.step(in); - } - } } diff --git a/src/main/java/com/github/protocolfuzzing/protocolstatefuzzer/statefuzzer/testrunner/core/TestRunnerRA.java b/src/main/java/com/github/protocolfuzzing/protocolstatefuzzer/statefuzzer/testrunner/core/TestRunnerRA.java new file mode 100644 index 00000000..f2430f4f --- /dev/null +++ b/src/main/java/com/github/protocolfuzzing/protocolstatefuzzer/statefuzzer/testrunner/core/TestRunnerRA.java @@ -0,0 +1,240 @@ +package com.github.protocolfuzzing.protocolstatefuzzer.statefuzzer.testrunner.core; + +import com.github.protocolfuzzing.protocolstatefuzzer.components.learner.alphabet.AlphabetBuilder; +import com.github.protocolfuzzing.protocolstatefuzzer.components.learner.alphabet.AlphabetBuilderTransformer; +import com.github.protocolfuzzing.protocolstatefuzzer.components.learner.oracles.MembershipOracleWrapperRA; +import com.github.protocolfuzzing.protocolstatefuzzer.components.sul.core.AbstractSul; +import com.github.protocolfuzzing.protocolstatefuzzer.components.sul.core.SulBuilder; +import com.github.protocolfuzzing.protocolstatefuzzer.components.sul.core.SulWrapper; +import com.github.protocolfuzzing.protocolstatefuzzer.components.sul.core.config.SulConfig; +import com.github.protocolfuzzing.protocolstatefuzzer.components.sul.core.sulwrappers.DataWordSULWrapper; +import com.github.protocolfuzzing.protocolstatefuzzer.statefuzzer.testrunner.core.config.TestRunnerEnabler; +import com.github.protocolfuzzing.protocolstatefuzzer.utils.CleanupTasks; +import de.learnlib.ralib.sul.SULOracle; +import de.learnlib.ralib.words.InputSymbol; +import de.learnlib.ralib.words.OutputSymbol; +import de.learnlib.ralib.words.PSymbolInstance; +import de.learnlib.ralib.words.ParameterizedSymbol; +import de.learnlib.sul.SUL; +import net.automatalib.alphabet.Alphabet; +import net.automatalib.alphabet.impl.ListAlphabet; +import net.automatalib.exception.FormatException; +import net.automatalib.word.Word; +import net.automatalib.word.WordBuilder; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +/** + * The standard implementation of the TestRunner Interface. + * + * @param

the type of protocol messages + * @param the type of execution context + */ +public class TestRunnerRA implements TestRunner { + + private static final Logger LOGGER = LogManager.getLogger(); + + /** Stores the constructor parameter. */ + protected TestRunnerEnabler testRunnerEnabler; + + /** The built alphabet using the AlphabetBuilder constructor parameter. */ + protected Alphabet alphabet; + + + /** Transformer to convert mealy input symbols into Ralib input symbols */ + protected AlphabetBuilderTransformer inputTransformer; + + /** The Oracle that contains the sul built via SulBuilder and wrapped via SulWrapper constructor parameters. */ + protected SULOracle sulOracle; + + /** Stores the cleanup tasks of the TestRunner. */ + protected CleanupTasks cleanupTasks; + + /** + * Constructs a new instance from the given parameters. + *

+ * The {@link #sulOracle} contains the wrapped (and built) sul. + * Invoke {@link #initialize()} afterwards. + * + * @param testRunnerEnabler the configuration that enables the testing + * @param alphabetBuilder the builder of the alphabet + * @param alphabetBuilderTransformer the transformer used to translate inputs + * @param sulBuilder the builder of the sul + */ + public TestRunnerRA( + TestRunnerEnabler testRunnerEnabler, + AlphabetBuilder alphabetBuilder, + AlphabetBuilderTransformer alphabetBuilderTransformer, + SulBuilder sulBuilder + ) { + this.testRunnerEnabler = testRunnerEnabler; + this.alphabet = alphabetBuilder.build( + testRunnerEnabler.getLearnerConfig() + ); + this.inputTransformer = alphabetBuilderTransformer; + this.cleanupTasks = new CleanupTasks(); + + AbstractSul abstractSul = + sulBuilder.buildSul(testRunnerEnabler.getSulConfig(), cleanupTasks); + SulWrapper sulWrapper = sulBuilder.buildWrapper(); + SUL sul = sulWrapper.wrap(abstractSul).getWrappedSul(); + + this.sulOracle = new SULOracle( + new DataWordSULWrapper(sul), new OutputSymbol("_io_err") + ); + } + + /** + * Initializes the instance; to be run after the constructor. + *

+ * It checks if the TestRunnerConfig from the TestRunnerEnabler contains + * any test specification that needs to be built and used. + * + * @return the same instance + */ + public TestRunnerRA initialize() { + if ( + this.testRunnerEnabler.getTestRunnerConfig() + .getTestSpecification() != + null + ) { + throw new UnsupportedOperationException("Running with test spec is not implemented for RA learning."); + } + return this; + } + + /** + * Returns the alphabet to be used during testing. + * + * @return the alphabet to be used during testing + */ + public Alphabet getAlphabet() { + return alphabet; + } + + /** + * Returns the SulConfig of the {@link #testRunnerEnabler}. + * + * @return the SulConfig of the {@link #testRunnerEnabler} + */ + public SulConfig getSulConfig() { + return testRunnerEnabler.getSulConfig(); + } + + /** + * Runs the tests using {@link #runTests()} and cleans up using {@link #terminate()}. + */ + @Override + public void run() { + try { + List< + TestRunnerResult, Word> + > results = runTests(); + + for (TestRunnerResult< + Word, + Word + > result : results) { + LOGGER.info(result.toString()); + if ( + testRunnerEnabler + .getTestRunnerConfig() + .isShowTransitionSequence() + ) { + LOGGER.info( + "Displaying Transition Sequence\n{}", result + ); + } + } + } catch (IOException | FormatException e) { + LOGGER.error(e.getMessage()); + e.printStackTrace(); + } finally { + terminate(); + } + } + + /** + * Executes the {@link #cleanupTasks}; should be called only after all the + * desired tests have been executed. + */ + public void terminate() { + cleanupTasks.execute(); + } + + /** + * Reads the tests provided in the TestRunnerConfig of {@link #testRunnerEnabler}, + * executes each one of them using {@link #runTest(Word)} and collects the results. + * + * @return a list with the test results + * + * @throws IOException if an error during reading occurs + * @throws FormatException if an invalid format was encountered + */ + protected List, Word>> runTests() + throws IOException, FormatException { + TestParser testParser = new TestParser<>(); + List> tests; + String testFileOrTestString = testRunnerEnabler + .getTestRunnerConfig() + .getTest(); + + ListAlphabet inputAlphabet = new ListAlphabet<> (alphabet.stream() + .filter(i -> inputTransformer.toTransformedInput(i) instanceof InputSymbol).toList()); + + if (new File(testFileOrTestString).exists()) { + tests = testParser.readTests(inputAlphabet, testFileOrTestString); + } else { + LOGGER.info( + "File {} does not exist, interpreting argument as test", + testFileOrTestString + ); + String[] testStrings = testFileOrTestString.split("\\s+"); + tests = List.of( + testParser.readTest(inputAlphabet, Arrays.asList(testStrings)) + ); + } + + // net.automatalib.word.WordCollector exists but is not explicitly marked public. + // This is most likely unintended since it is a wrapper around WordBuilder which is public. + // Using that would allow us to skip using WordBuilder directly. + // TODO: Open an issue or otherwise notify about this. + List> convertedTests = new ArrayList<>(tests.size()); + for (Word test : tests) { + WordBuilder wordBuilder = new WordBuilder<>(); + for (I input : test) { + wordBuilder.append(new PSymbolInstance(inputTransformer.toTransformedInput(input))); + } + convertedTests.add(wordBuilder.toWord()); + } + List, Word>> results = new ArrayList<>(); + for (Word test : convertedTests) { + results.add(runTest(test)); + } + return results; + } + + /** + * Runs a single test and collects the result. + * + * @param test the test to be run against the stored {@link #sulOracle} + * @return the result of the test + */ + protected TestRunnerResult< + Word, + Word + > runTest(Word test) { + TestRunnerResult, Word> result = + TestRunner.runTest( + test, + testRunnerEnabler.getTestRunnerConfig().getTimes(), + new MembershipOracleWrapperRA(sulOracle) + ); + return result; + } +}