All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.jqwik.engine.properties.state.SequentialActionChain Maven / Gradle / Ivy

There is a newer version: 1.9.1
Show newest version
package net.jqwik.engine.properties.state;

import java.util.*;
import java.util.function.*;
import java.util.stream.*;

import org.jspecify.annotations.*;
import org.opentest4j.*;

import net.jqwik.api.*;
import net.jqwik.api.state.*;
import net.jqwik.api.support.*;
import net.jqwik.engine.support.*;

public class SequentialActionChain implements ActionChain {
	private final Chain chain;

	private volatile T currentValue = null;
	private volatile RunningState currentRunning = RunningState.NOT_RUN;
	private final List> peekers = new ArrayList<>();
	private final List>> invariants = new ArrayList<>();

	public SequentialActionChain(Chain chain) {
		this.chain = chain;
	}

	@Override
	public List transformations() {
		return chain.transformations();
	}

	@Override
	public List> transformers() {
		return chain.transformers();
	}

	@Override
	public synchronized T run() {
		currentRunning = RunningState.RUNNING;
		for (Iterator iterator = chain.iterator(); iterator.hasNext(); ) {
			nextAction(iterator);
		}
		currentRunning = RunningState.SUCCEEDED;
		return currentValue;
	}

	private void nextAction(Iterator iterator) {
		try {
			T state = iterator.next();
			currentValue = state;
			callPeekers();
			checkInvariants();
		} catch (InvariantFailedError ife) {
			currentRunning = RunningState.FAILED;
			throw ife;
		} catch (TestAbortedException testAbortedException) {
			throw testAbortedException;
		} catch (Throwable t) {
			currentRunning = RunningState.FAILED;
			AssertionFailedError assertionFailedError = new AssertionFailedError(createErrorMessage("Run", t.getMessage()), t);
			assertionFailedError.setStackTrace(t.getStackTrace());
			throw assertionFailedError;
		}
	}

	private void callPeekers() {
		for (Consumer peeker : peekers) {
			peeker.accept(currentValue);
		}
	}

	private void checkInvariants() {
		for (Tuple.Tuple2> tuple : invariants) {
			String label = tuple.get1();
			Consumer invariant = tuple.get2();
			try {
				invariant.accept(currentValue);
			} catch (Throwable t) {
				throw new InvariantFailedError(createErrorMessage(label, t.getMessage()), t);
			}
		}
	}

	private String createErrorMessage(String name, String causeMessage) {
		String actionsString = transformations()
								   .stream()
								   .map(transformation -> "    " + transformation)
								   .collect(Collectors.joining(System.lineSeparator()));
		return String.format(
			"%s failed after the following actions: [%s]%nfinal state: %s%n%s",
			name,
			actionsString.isEmpty() ? "" : String.format("%n%s  %n", actionsString),
			JqwikStringSupport.displayString(currentValue),
			causeMessage
		);
	}

	@Override
	public ActionChain withInvariant(@Nullable String label, Consumer invariant) {
		String invariantLabel = label == null ? "Invariant" : String.format("Invariant '%s'", label);
		invariants.add(Tuple.of(invariantLabel, invariant));
		return this;
	}

	@Override
	public synchronized Optional finalState() {
		return Optional.ofNullable(currentValue);
	}

	@Override
	public ActionChain.RunningState running() {
		return currentRunning;
	}

	@Override
	public synchronized ActionChain peek(Consumer peeker) {
		peekers.add(peeker);
		return this;
	}

	@Override
	public String toString() {
		if (running() == RunningState.NOT_RUN) {
			return String.format("ActionChain[%s]: %s max actions", running().name(), chain.maxTransformations());
		}
		String actionsString = JqwikStringSupport.displayString(transformations());
		return String.format("ActionChain[%s]: %s", running().name(), actionsString);
	}

	// This implementation is there to enable jqwik's after execution reporting
	@Override
	public boolean equals(Object o) {
		if (this == o) return true;
		if (o == null || getClass() != o.getClass()) return false;
		SequentialActionChain that = (SequentialActionChain) o;
		return currentRunning == that.currentRunning;
	}

	@Override
	public int hashCode() {
		return HashCodeSupport.hash(currentValue, currentRunning);
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy