diff options
Diffstat (limited to 'src/main/java/au/id/zancanaro/javacheck')
6 files changed, 305 insertions, 0 deletions
| diff --git a/src/main/java/au/id/zancanaro/javacheck/statem/Command.java b/src/main/java/au/id/zancanaro/javacheck/statem/Command.java new file mode 100644 index 0000000..a741f0a --- /dev/null +++ b/src/main/java/au/id/zancanaro/javacheck/statem/Command.java @@ -0,0 +1,23 @@ +package au.id.zancanaro.javacheck.statem; + +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/statem/CommandList.java b/src/main/java/au/id/zancanaro/javacheck/statem/CommandList.java new file mode 100644 index 0000000..9e8948a --- /dev/null +++ b/src/main/java/au/id/zancanaro/javacheck/statem/CommandList.java @@ -0,0 +1,85 @@ +package au.id.zancanaro.javacheck.statem; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +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 = command.runCommand(args); +            values.put(id, result); +            State oldState = state; +            state = command.nextState(state, args, new CommandValue.ConcreteValue<>(id, values)); +            if (!command.postCondition(oldState, state, 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.AbstractValue<>(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() { +        return "CommandList{" + +                "commands=" + commands + +                '}'; +    } +} diff --git a/src/main/java/au/id/zancanaro/javacheck/statem/CommandListGenerator.java b/src/main/java/au/id/zancanaro/javacheck/statem/CommandListGenerator.java new file mode 100644 index 0000000..a0df66f --- /dev/null +++ b/src/main/java/au/id/zancanaro/javacheck/statem/CommandListGenerator.java @@ -0,0 +1,50 @@ +package au.id.zancanaro.javacheck.statem; + +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.AbstractValue<>(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/statem/CommandResult.java b/src/main/java/au/id/zancanaro/javacheck/statem/CommandResult.java new file mode 100644 index 0000000..dc5b085 --- /dev/null +++ b/src/main/java/au/id/zancanaro/javacheck/statem/CommandResult.java @@ -0,0 +1,31 @@ +package au.id.zancanaro.javacheck.statem; + +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/statem/CommandValue.java b/src/main/java/au/id/zancanaro/javacheck/statem/CommandValue.java new file mode 100644 index 0000000..0a1fb61 --- /dev/null +++ b/src/main/java/au/id/zancanaro/javacheck/statem/CommandValue.java @@ -0,0 +1,60 @@ +package au.id.zancanaro.javacheck.statem; + +import java.util.Map; +import java.util.NoSuchElementException; + +public abstract class CommandValue<T> { +    private final int id; + +    public CommandValue(int id) { +        this.id = id; +    } + +    public abstract boolean isAbstract(); + +    public abstract T get(); + +    public int getId() { +        return id; +    } + +    static class AbstractValue<T> extends CommandValue<T> { +        public AbstractValue(int id) { +            super(id); +        } + +        @Override +        public boolean isAbstract() { +            return true; +        } + +        @Override +        public T get() { +            throw new NoSuchElementException("Abstract values cannot be supplied"); +        } +    } + +    static class ConcreteValue<T> extends CommandValue<T> { +        private final Map<Integer, Object> values; + +        public ConcreteValue(int id, Map<Integer, Object> values) { +            super(id); +            this.values = values; +        } + +        @Override +        public boolean isAbstract() { +            return true; +        } + +        @Override +        @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"); +            } +        } +    } +} diff --git a/src/main/java/au/id/zancanaro/javacheck/statem/GeneratedCommand.java b/src/main/java/au/id/zancanaro/javacheck/statem/GeneratedCommand.java new file mode 100644 index 0000000..5e576b4 --- /dev/null +++ b/src/main/java/au/id/zancanaro/javacheck/statem/GeneratedCommand.java @@ -0,0 +1,56 @@ +package au.id.zancanaro.javacheck.statem; + +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 "GeneratedCommand{" + +                "id=" + id + +                ", command=" + command + +                ", args=" + args + +                '}'; +    } +} | 
