summaryrefslogtreecommitdiff
path: root/src/main/java/au/id/zancanaro/javacheck/state
diff options
context:
space:
mode:
authorCarlo Zancanaro <carlo@zancanaro.id.au>2015-06-05 17:30:46 +1000
committerCarlo Zancanaro <carlo@zancanaro.id.au>2015-06-05 17:30:46 +1000
commit05ec409ce96da92d430c4a8e58b08d46f42d667a (patch)
tree2c3d8925fa9dd86cc265dcfbcb1fe5d8ee97cd0b /src/main/java/au/id/zancanaro/javacheck/state
parent20b1226b4eb10e85497862bd73fe9e9a2f05191d (diff)
More work on the stateful checker; still not perfect, but it's getting better
Diffstat (limited to 'src/main/java/au/id/zancanaro/javacheck/state')
-rw-r--r--src/main/java/au/id/zancanaro/javacheck/state/Command.java23
-rw-r--r--src/main/java/au/id/zancanaro/javacheck/state/CommandList.java91
-rw-r--r--src/main/java/au/id/zancanaro/javacheck/state/CommandListGenerator.java50
-rw-r--r--src/main/java/au/id/zancanaro/javacheck/state/CommandResult.java31
-rw-r--r--src/main/java/au/id/zancanaro/javacheck/state/CommandValue.java47
-rw-r--r--src/main/java/au/id/zancanaro/javacheck/state/GeneratedCommand.java52
6 files changed, 294 insertions, 0 deletions
diff --git a/src/main/java/au/id/zancanaro/javacheck/state/Command.java b/src/main/java/au/id/zancanaro/javacheck/state/Command.java
new file mode 100644
index 0000000..afa3957
--- /dev/null
+++ b/src/main/java/au/id/zancanaro/javacheck/state/Command.java
@@ -0,0 +1,23 @@
+package au.id.zancanaro.javacheck.state;
+
+import au.id.zancanaro.javacheck.Generator;
+
+public abstract class Command<State,Args,Result> {
+ public Generator<Args> argsGenerator(State state) {
+ return Generator.pure(null);
+ }
+
+ public boolean preCondition(State state, Args args) {
+ return true;
+ }
+
+ public abstract Result runCommand(Args args);
+
+ public State nextState(State state, Args args, CommandValue<Result> result) {
+ return state;
+ }
+
+ public boolean postCondition(State oldState, State newState, Args args, Result result) {
+ return true;
+ }
+}
diff --git a/src/main/java/au/id/zancanaro/javacheck/state/CommandList.java b/src/main/java/au/id/zancanaro/javacheck/state/CommandList.java
new file mode 100644
index 0000000..0426fd3
--- /dev/null
+++ b/src/main/java/au/id/zancanaro/javacheck/state/CommandList.java
@@ -0,0 +1,91 @@
+package au.id.zancanaro.javacheck.state;
+
+import java.util.*;
+
+public class CommandList<State> {
+ private final List<GeneratedCommand<State, ?, ?>> commands;
+
+ public CommandList(List<GeneratedCommand<State, ?, ?>> commands) {
+ this.commands = new ArrayList<>(commands);
+ }
+
+ public CommandResult<State> run(State initialState) {
+ Map<Integer, Object> values = new HashMap<>();
+ CommandResult<State> result = CommandResult.success(initialState);
+ for (GeneratedCommand<State, ?, ?> generated : commands) {
+ result = runRealCommand(generated, result.getState(), values);
+ if (result.isFailed()) {
+ break;
+ }
+ }
+ return result;
+ }
+
+ private static <State, Args, Result> CommandResult<State> runRealCommand(
+ GeneratedCommand<State, Args, Result> generated,
+ State state,
+ Map<Integer, Object> values) {
+ int id = generated.getId();
+ Command<State, Args, Result> command = generated.getCommand();
+ Args args = generated.getArgs();
+ try {
+ if (!command.preCondition(state, args)) {
+ return CommandResult.fail(state, new Error("Precondition failed"));
+ }
+ Result result = CommandValue.withValues(values, () ->
+ command.runCommand(args));
+ values.put(id, result);
+ final State oldState = state;
+ final State newState = CommandValue.withValues(values, () ->
+ command.nextState(oldState, args, new CommandValue<>(id)));
+ state = newState;
+ if (!CommandValue.withValues(values, () -> command.postCondition(oldState, newState, args, result))) {
+ return CommandResult.fail(state, new Error("Postcondition failed"));
+ }
+ return CommandResult.success(state);
+ } catch (Throwable ex) {
+ return CommandResult.fail(state, ex);
+ }
+ }
+
+ private static <State, Args, Result> CommandResult<State> runAbstractCommand(
+ GeneratedCommand<State, Args, Result> generated,
+ State state) {
+ int id = generated.getId();
+ Command<State, Args, Result> command = generated.getCommand();
+ Args args = generated.getArgs();
+ try {
+ if (!command.preCondition(state, args)) {
+ return CommandResult.fail(state, new Error("Precondition failed"));
+ }
+ state = command.nextState(state, args, new CommandValue<>(id));
+ return CommandResult.success(state);
+ } catch (Throwable ex) {
+ return CommandResult.fail(state, ex);
+ }
+ }
+
+ public boolean isValid() {
+ CommandResult<State> result = CommandResult.success(null);
+ for (GeneratedCommand<State, ?, ?> generated : commands) {
+ result = runAbstractCommand(generated, result.getState());
+ if (result.isFailed()) {
+ break;
+ }
+ }
+ return !result.isFailed();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ Iterator<GeneratedCommand<State, ?, ?>> iterator = commands.iterator();
+ while (iterator.hasNext()) {
+ builder.append(iterator.next());
+ if (iterator.hasNext()) {
+ builder.append(", ");
+ }
+ }
+ return builder.toString();
+ }
+}
diff --git a/src/main/java/au/id/zancanaro/javacheck/state/CommandListGenerator.java b/src/main/java/au/id/zancanaro/javacheck/state/CommandListGenerator.java
new file mode 100644
index 0000000..4c2c47f
--- /dev/null
+++ b/src/main/java/au/id/zancanaro/javacheck/state/CommandListGenerator.java
@@ -0,0 +1,50 @@
+package au.id.zancanaro.javacheck.state;
+
+import au.id.zancanaro.javacheck.Generator;
+import au.id.zancanaro.javacheck.ShrinkTree;
+
+import java.util.Random;
+import java.util.function.Function;
+
+import static au.id.zancanaro.javacheck.Generator.pure;
+import static au.id.zancanaro.javacheck.Generators.noShrink;
+
+public class CommandListGenerator<State> implements Generator<CommandList<State>> {
+ private final Function<State, Generator<Command<State, ?, ?>>> generateCommand;
+
+ public CommandListGenerator(Function<State, Generator<Command<State, ?, ?>>> generateCommand) {
+ this.generateCommand = generateCommand;
+ }
+
+ public Generator<GeneratedCommand<State, ?, ?>> commandGenerator(int id, State state) {
+ return noShrink(generateCommand.apply(state))
+ .flatMap(command -> generateSingleCommand(id, command, state));
+ }
+
+ public <Args, Result> Generator<GeneratedCommand<State, ?, ?>> generateSingleCommand(int id, Command<State, Args, Result> command, State state) {
+ return command.argsGenerator(state).flatMap(generatedArgs ->
+ command.preCondition(state, generatedArgs) ?
+ pure(new GeneratedCommand<>(id, command, generatedArgs)) :
+ commandGenerator(id, state));
+ }
+
+ public <Args, Result> State nextState(int id, GeneratedCommand<State, Args, Result> generatedCommand, State state) {
+ return generatedCommand.getCommand().nextState(state, generatedCommand.getArgs(), new CommandValue<>(id));
+ }
+
+ @Override
+ public ShrinkTree<CommandList<State>> generate(Random random, int size) {
+ int count = random.nextInt(size);
+ @SuppressWarnings("unchecked")
+ ShrinkTree<GeneratedCommand<State, ?, ?>>[] commandTrees = (ShrinkTree<GeneratedCommand<State, ?, ?>>[]) new ShrinkTree[count];
+ State state = null;
+ for (int i = 0; i < count; ++i) {
+ commandTrees[i] = commandGenerator(i, state).generate(random, size);
+ GeneratedCommand<State, ?, ?> generatedCommand = commandTrees[i].getValue();
+ state = nextState(i, generatedCommand, state);
+ }
+ return ShrinkTree.combine(commandTrees, ShrinkTree::removeAndPromoteChildren)
+ .map(list -> new CommandList<>(list))
+ .filter(CommandList::isValid);
+ }
+}
diff --git a/src/main/java/au/id/zancanaro/javacheck/state/CommandResult.java b/src/main/java/au/id/zancanaro/javacheck/state/CommandResult.java
new file mode 100644
index 0000000..12f650d
--- /dev/null
+++ b/src/main/java/au/id/zancanaro/javacheck/state/CommandResult.java
@@ -0,0 +1,31 @@
+package au.id.zancanaro.javacheck.state;
+
+public class CommandResult<State> {
+ private final State state;
+ private final Throwable thrown;
+
+ private CommandResult(State state, Throwable thrown) {
+ this.state = state;
+ this.thrown = thrown;
+ }
+
+ public State getState() {
+ return state;
+ }
+
+ public boolean isFailed() {
+ return thrown != null;
+ }
+
+ public Throwable getThrown() {
+ return thrown;
+ }
+
+ public static <State> CommandResult<State> success(State state) {
+ return new CommandResult<>(state, null);
+ }
+
+ public static <State> CommandResult<State> fail(State state, Throwable ex) {
+ return new CommandResult<>(state, ex);
+ }
+}
diff --git a/src/main/java/au/id/zancanaro/javacheck/state/CommandValue.java b/src/main/java/au/id/zancanaro/javacheck/state/CommandValue.java
new file mode 100644
index 0000000..8d3f272
--- /dev/null
+++ b/src/main/java/au/id/zancanaro/javacheck/state/CommandValue.java
@@ -0,0 +1,47 @@
+package au.id.zancanaro.javacheck.state;
+
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.function.Supplier;
+
+public class CommandValue<T> {
+ private static Map<Integer, Object> values = null;
+
+ public static <T> T withValues(Map<Integer, Object> newValues, Supplier<T> action) {
+ Map<Integer,Object> oldValues = values;
+ try {
+ values = newValues;
+ return action.get();
+ } finally {
+ values = oldValues;
+ }
+ }
+
+ private final int id;
+
+ public CommandValue(int id) {
+ this.id = id;
+ }
+
+ public boolean isAbstract() {
+ return values == null;
+ }
+
+ @SuppressWarnings("unchecked")
+ public T get() {
+ if (values.containsKey(getId())) {
+ return (T) values.get(getId());
+ } else {
+ throw new NoSuchElementException("Concrete values cannot be supplied prior to being calculated");
+ }
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ @Override
+ public String toString() {
+ return "#{" + id + "}";
+ }
+}
diff --git a/src/main/java/au/id/zancanaro/javacheck/state/GeneratedCommand.java b/src/main/java/au/id/zancanaro/javacheck/state/GeneratedCommand.java
new file mode 100644
index 0000000..90d9a47
--- /dev/null
+++ b/src/main/java/au/id/zancanaro/javacheck/state/GeneratedCommand.java
@@ -0,0 +1,52 @@
+package au.id.zancanaro.javacheck.state;
+
+public class GeneratedCommand<State, Args, Result> {
+ private final int id;
+ private final Command<State, Args, Result> command;
+ private final Args args;
+
+ public GeneratedCommand(int id, Command<State, Args, Result> command, Args args) {
+ this.id = id;
+ this.command = command;
+ this.args = args;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public Command<State, Args, Result> getCommand() {
+ return command;
+ }
+
+ public Args getArgs() {
+ return args;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ GeneratedCommand that = (GeneratedCommand) o;
+
+ if (args != null ? !args.equals(that.args) : that.args != null)
+ return false;
+ if (command != null ? !command.equals(that.command) : that.command != null)
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = command != null ? command.hashCode() : 0;
+ result = 31 * result + (args != null ? args.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "#{" + id + "} = " + command + " <- " + args;
+ }
+}