Commit 03abc842 authored by Martti Käärik's avatar Martti Käärik
Browse files

Generic time constraint processing + time constraint logging + use correct...

Generic time constraint processing + time constraint logging + use correct time units with mapped/unmapped data #135
parent 76e3bf2a
Loading
Loading
Loading
Loading
Loading
+193 −68
Original line number Diff line number Diff line
@@ -162,6 +162,7 @@ public class JUnitTestGenerator extends Renderer {
		return PROPERTIES_CLASS + "." + RuntimeApi.OF + "(" + String.join(", ", keyValues) + ")";
	}


	protected Map<MappableDataElement, Set<DataElementMapping>> elementMappings = new Hashtable<MappableDataElement, Set<DataElementMapping>>();

	protected Map<Element, String> classNames = new Hashtable<Element, String>();
@@ -962,6 +963,8 @@ public class JUnitTestGenerator extends Renderer {
		public int altTriggerIndex;
		// For alternatives: the name of the callables list variable (signals batch mode)
		public String altCallablesListName;
		// For TimeoutResult futures: the reason passed to terminate() when it wins
		public String terminationMessage = "Timeout";

		public FutureInfo(String varName, String kind, Behaviour b) {
			this.varName = varName;
@@ -1040,7 +1043,7 @@ public class JUnitTestGenerator extends Renderer {

				// Store period value before thread creation (must be effectively final)
				append("long " + periodVar + " = ");
				write(periodValue, dataUseVariables);
				writeMillis(periodValue, dataUseVariables);
				line(";");

				line("Throwable[] " + errorName + " = new Throwable[1];");
@@ -1122,19 +1125,26 @@ public class JUnitTestGenerator extends Renderer {

			// Atomic behaviours

			// Timing
			TimeLabel timeLabel = ((AtomicBehaviour) b).getTimeLabel();
			if (timeLabel != null) {
				String labelName = getTimeLabelName(timeLabel);
				line(labelName + ".timestamp();");
			}
			for (TimeConstraint tc : ((AtomicBehaviour) b).getTimeConstraint()) {
				FutureInfo futureInfo = declareFuture(b, tc, dataUseVariables);
			// Timing. The timestamp of a behaviour is the time its event occurs.
			AtomicBehaviour ab = (AtomicBehaviour) b;
			if (!isTesterInput(b)) {
					line(futureInfo.varName + ".get();");
				// Execution is blocked until the time constraints hold, then the
				// behaviour is timestamped and executed
				String labelName = ab.getTimeLabel() != null ? getTimeLabelName(ab.getTimeLabel()) : null;
				if (!ab.getTimeConstraint().isEmpty()) {
					// The wait records the behaviour's timestamp itself (provisionally
					// while waiting, finally when the constraints hold)
					String constraintName = declareTimeConstraint(ab, dataUseVariables);
					line(getTimeConstraintEvaluationVariable(ab) + " = " + COMPONENT_FIELD + "." + RuntimeApi.AWAIT_TIME_CONSTRAINT + "("
							+ constraintName + ", " + (labelName != null ? labelName : "null") + ", \"" + escape(getMessage(ab)) + "\");");
					thrownExceptions.add(INTERRUPTED_EXCEPTION);
				} else if (labelName != null)
					line(labelName + "." + RuntimeApi.TIMESTAMP + "();");

				} else
					myFutures.add(futureInfo);
			} else if (!ab.getTimeConstraint().isEmpty()) {
				// Timestamp and time constraint check happen once the event has
				// occurred (see writeResultHandling())
				declareTimeConstraint(ab, dataUseVariables);
			}

			// Actions
@@ -1264,16 +1274,13 @@ public class JUnitTestGenerator extends Renderer {
			// Timer operations
			else if (b instanceof TimerStart) {
				DataUse period = ((TimerStart) b).getPeriod();
				Time timeType = resolveTimeType(period);
				DataElementMapping mapping = getMappingChecked(timeType);

				initializeDataUse(period, dataUseVariables);

				append(COMPONENT_FIELD + "." + getTimerName(((TimerOperation) b).getTimer()) + ".start(");
				write(period, dataUseVariables);
				if (timeType != null)
					append(", TimeUnit." + mapping.getElementURI());
				line(");");
				// The period is in the uniform runtime unit ms (see write())
				append(COMPONENT_FIELD + "." + getTimerName(((TimerOperation) b).getTimer()) + "." + RuntimeApi.TIMER_START + "(");
				writeMillis(period, dataUseVariables);
				line(", TimeUnit.MilliSecond);");

			} else if (b instanceof TimerStop) {
				append(COMPONENT_FIELD + "." + getTimerName(((TimerOperation) b).getTimer()) + "." + RuntimeApi.TIMER_STOP + "();");
@@ -1433,9 +1440,12 @@ public class JUnitTestGenerator extends Renderer {
					callable.altTriggerIndex = triggerIndex;
					callable.altCallablesListName = altsListName;
					line(altsListName + ".add(" + callable.varName + ");");
					newLine();

					myFutures.add(callable);

					// Time constraints of the trigger, checked once its event has occurred
					if (!((AtomicBehaviour) triggerBehaviour).getTimeConstraint().isEmpty())
						declareTimeConstraint((AtomicBehaviour) triggerBehaviour, dataUseVariables);
					newLine();
				}

			} else if (b instanceof ConditionalBehaviour) {
@@ -1565,16 +1575,16 @@ public class JUnitTestGenerator extends Renderer {
					// Result handling depends on single tester-input vs alternative
					if (f.altBlock != null) {
						// Alternative: value assignments from trigger + remaining block behaviours
						writeResultHandling(f.b, dataUseVariables);
						writeResultHandling(f.b, dataUseVariables, thrownExceptions);
						for (int j = f.altTriggerIndex + 1; j < f.altBlock.getBehaviour().size(); j++) {
							write(f.altBlock.getBehaviour().get(j), dataUseVariables, null, thrownExceptions);
							newLine();
						}
					} else {
						// Single tester-input: value assignments + timeout handling
						writeResultHandling(f.b, dataUseVariables);
						writeResultHandling(f.b, dataUseVariables, thrownExceptions);
						if (f.kind.equals("TimeoutResult")) {
							line(COMPONENT_FIELD + "." + RuntimeApi.TERMINATE + "(\"Timeout\");");
							line(COMPONENT_FIELD + "." + RuntimeApi.TERMINATE + "(\"" + escape(f.terminationMessage) + "\");");
							thrownExceptions.add(STOP_EXCEPTION);
						}
					}
@@ -1664,7 +1674,10 @@ public class JUnitTestGenerator extends Renderer {
	 * Writes value assignment code for a tester-input result.
	 * Assumes a local variable "result" of type InteractionResult is in scope.
	 */
	private void writeResultHandling(Behaviour trigger, Map<DataUse, String> dataUseVariables) {
	private void writeResultHandling(Behaviour trigger, Map<DataUse, String> dataUseVariables,
			Set<String> thrownExceptions) {
		if (trigger == null)
			return;
		if (trigger instanceof Interaction) {
			Optional<Target> localTargetOpt = ((Interaction) trigger).getTarget().stream()
					.filter(t -> isCurrentComponentInstance(t.getTargetGate().getComponent())).findFirst();
@@ -1679,9 +1692,27 @@ public class JUnitTestGenerator extends Renderer {
				}
			}
		}
		if (trigger instanceof AtomicBehaviour) {
			AtomicBehaviour ab = (AtomicBehaviour) trigger;
			if (ab.getTimeLabel() != null)
				line(getTimeLabelName(ab.getTimeLabel()) + "." + RuntimeApi.TIMESTAMP + "(result." + RuntimeApi.GET_TIMESTAMP + "());");
			if (!ab.getTimeConstraint().isEmpty()) {
				String evaluation = getTimeConstraintEvaluationVariable(ab);
				line(evaluation + " = " + getTimeConstraintName(ab) + "." + RuntimeApi.EVALUATE + "();");
				line(COMPONENT_FIELD + "." + RuntimeApi.CHECK_TIME_CONSTRAINT + "(" + getTimeConstraintName(ab) + ", " + evaluation
						+ ", \"" + escape(getMessage(ab)) + "\");");
				thrownExceptions.add(STOP_EXCEPTION);
			}
		}
	}

	private String getTimeConstraintName(AtomicBehaviour b) {
		return "timeConstraint_" + getElementName(b);
	}

	/**
	 * Declares the variables holding the outcome of a behaviour and registers them as
	 * Declares the variables holding the outcome of a behaviour (assertion result
	 * and operands, time constraint result and operands) and registers them as
	 * outcome properties. Emitted before the try block of the behaviour, or for an
	 * alternative trigger before its callable is created.
	 */
@@ -1691,6 +1722,93 @@ public class JUnitTestGenerator extends Renderer {
			if (!getAssertionOperands(((Assertion) b).getCondition()).isEmpty())
				line("Object[] " + getAssertionOperandsVariable(b) + " = null;");
		}
		if (b instanceof AtomicBehaviour && !((AtomicBehaviour) b).getTimeConstraint().isEmpty()) {
			AtomicBehaviour ab = (AtomicBehaviour) b;
			String evaluation = getTimeConstraintEvaluationVariable(ab);
			line(CORE_PACKAGE + ".Constraint.Evaluation " + evaluation + " = null;");
			addOutcomeProperty(ab, RuntimeApi.PROPERTY_TIME_CONSTRAINT_RESULT, evaluation + " != null && " + evaluation + "." + RuntimeApi.HOLDS);
			if (getTimeConstraintOperator(ab) != null) {
				addOutcomeProperty(ab, RuntimeApi.PROPERTY_TIME_CONSTRAINT_OPERATOR, "\"" + escape(getTimeConstraintOperator(ab)) + "\"");
				addOutcomeProperty(ab, RuntimeApi.PROPERTY_TIME_CONSTRAINT_OPERANDS,
						evaluation + " != null ? " + evaluation + "." + RuntimeApi.OPERANDS + " : null");
			}
		}
	}

	private String getTimeConstraintEvaluationVariable(AtomicBehaviour b) {
		return getTimeConstraintName(b) + "_evaluation";
	}

	/**
	 * The top-level operator of the time constraint of a behaviour, if it has a
	 * single constraint whose expression is an operator application with several
	 * operands; <b>null</b> otherwise.
	 */
	private String getTimeConstraintOperator(AtomicBehaviour b) {
		if (b.getTimeConstraint().size() != 1)
			return null;
		DataUse top = b.getTimeConstraint().get(0).getTimeConstraintExpression();
		if (top instanceof CastDataUse)
			top = ((CastDataUse) top).getDataUse();
		if (top instanceof PredefinedFunctionCall && getDataUseArgumentValues(top).size() > 1)
			return ((PredefinedFunctionCall) top).getFunction().getName();
		return null;
	}

	private String declareTimeConstraint(AtomicBehaviour b, Map<DataUse, String> dataUseVariables) {
		String constraintName = getTimeConstraintName(b);
		append(CORE_PACKAGE + ".Constraint " + constraintName + " = ");
		writeTimeConstraint(b, dataUseVariables);
		line(";");
		return constraintName;
	}

	/**
	 * Emits the time constraints of a behaviour as one {@code Constraint}: a
	 * conjunction of all constraint expressions. Emits operator and operands only
	 * if there's a single constraint attached.
	 */
	private void writeTimeConstraint(AtomicBehaviour b, Map<DataUse, String> dataUseVariables) {
		String operator = getTimeConstraintOperator(b);
		append("new " + CORE_PACKAGE + ".Constraint(() -> ");
		blockOpen();
		{
			// Data uses of the constraint expressions live inside the lambda
			Map<DataUse, String> localDataUseVariables = new Hashtable<DataUse, String>();
			for (TimeConstraint tc : b.getTimeConstraint()) {
				declareDataUses(tc, localDataUseVariables, "");
				initializeDataUse(tc.getTimeConstraintExpression(), localDataUseVariables);
			}
			append("return new " + CORE_PACKAGE + ".Constraint.Evaluation(");
			boolean first = true;
			for (TimeConstraint tc : b.getTimeConstraint()) {
				if (!first)
					append(" && ");
				first = false;
				append("(");
				writeCondition(tc.getTimeConstraintExpression(), localDataUseVariables);
				append(")");
			}
			if (operator != null) {
				DataUse top = b.getTimeConstraint().get(0).getTimeConstraintExpression();
				if (top instanceof CastDataUse)
					top = ((CastDataUse) top).getDataUse();
				append(", new Object[] { ");
				first = true;
				for (DataUse operand : getDataUseArgumentValues(top)) {
					if (!first)
						append(", ");
					first = false;
					write(operand, localDataUseVariables);
				}
				append(" }");
			}
			line(");");
		}
		blockCloseInline();
		if (operator != null)
			append(", \"" + escape(operator) + "\"");
		append(")");
	}

	private FutureInfo writeTesterInput(Behaviour b, Map<DataUse, String> dataUseVariables) {
@@ -1817,27 +1935,6 @@ public class JUnitTestGenerator extends Renderer {
		}
	}

	private FutureInfo declareFuture(Behaviour b, TimeConstraint tc, Map<DataUse, String> dataUseVariables) {
		String futureName = "timeConstraint_" + getElementName(tc) + "_" + getElementName(b);

		line("ExecutionCallable " + futureName + " = " + COMPONENT_FIELD + ".timeConstraint(() -> ");
		blockOpen();

		DataUse exp = tc.getTimeConstraintExpression();
		// Need to redeclare datause variables
		Map<DataUse, String> localDataUseVariables = new Hashtable<DataUse, String>();
		declareDataUses(tc, localDataUseVariables, "");
		initializeDataUse(exp, localDataUseVariables);
		append("return ");
		write(exp, localDataUseVariables);
		line(";");

		blockClose();
		line(");");

		return new FutureInfo(futureName, "TimeoutResult", b);
	}

	private FutureInfo declareFuture(Behaviour b, Map<DataUse, String> dataUseVariables) {
		Target localTarget = null;
		if (b instanceof Interaction)
@@ -1860,7 +1957,7 @@ public class JUnitTestGenerator extends Renderer {
			initializeDataUse(period, dataUseVariables);

			append("ExecutionCallable " + futureName + " = " + COMPONENT_FIELD + "." + RuntimeApi.SLEEP + "(");
			write(period, dataUseVariables);
			writeMillis(period, dataUseVariables);
			append(")");
			line(";");

@@ -1873,7 +1970,7 @@ public class JUnitTestGenerator extends Renderer {
			initializeDataUse(period, dataUseVariables);

			append("ExecutionCallable " + futureName + " = " + COMPONENT_FIELD + "." + RuntimeApi.NO_INPUT + "(");
			write(period, dataUseVariables);
			writeMillis(period, dataUseVariables);
			append(", ");
			GateReference gr = ((Quiescence) b).getGateReference();
			if (gr != null)
@@ -2145,8 +2242,8 @@ public class JUnitTestGenerator extends Renderer {
					}
				}

			} else if (type instanceof Time || isTime)
				line("long " + varName + ";");
			} else if (isTime && type == null)
				line((isUnmapped() ? CORE_PACKAGE + ".ValueImpl" : "long") + " " + varName + ";");

			else
				line(getJavaType(type) + " " + varName + ";");
@@ -2724,23 +2821,41 @@ public class JUnitTestGenerator extends Renderer {

		// All data uses are variables
		if (d instanceof TimeLabelUse) {
			append(getTimeLabelName(((TimeLabelUse) d).getTimeLabel()));
			append("." + ((TimeLabelUse) d).getKind().getName().toLowerCase() + "()");
			TimeLabelUse use = (TimeLabelUse) d;
			String labelName = getTimeLabelName(use.getTimeLabel());
			// Unmapped: time values are Data
			if (isUnmapped())
				append(COMPONENT_FIELD + "." + RuntimeApi.TIME + "(");
			append(labelName);
			append("." + use.getKind().getName().toLowerCase() + "()");
			if (isUnmapped())
				append(")");

		} else {

			boolean timeConversion = false;
			if (!isUnmapped()) {
				DataType type = d.resolveDataType();
			if (type instanceof Time) {
				// Time unit conversion to default (ms)
				append("TimeUnit." + getElementName(type) + ".toSeconds(" + dataUseVariables.get(d) + ")");
				// Mapped: values that enter from the user side (literals, instances,
				// element uses) carry their declared unit and are converted to the
				// uniform runtime unit (ms) here. Unmapped: time values already hold
				// milliseconds (converted when initialized), no conversion.
				timeConversion = !isUnmapped() && type instanceof Time && (
						d instanceof DataElementUse ||
						d instanceof DataInstanceUse ||
						d instanceof LiteralValueUse);
				if (timeConversion)
					append("TimeUnit." + getElementName(type) + "." + RuntimeApi.TO_MILLIS + "(");
			}

			} else {
			if (d instanceof VariableUse)
				append(COMPONENT_FIELD + ".");
			append(dataUseVariables.get(d));
			if (isUnmapped())
					append(".asData()");
			}
				append("." + RuntimeApi.AS_DATA + "()");

			if (timeConversion)
				append(")");
		}

		for (MemberReference ref : d.getReduction()) {
@@ -2824,6 +2939,14 @@ public class JUnitTestGenerator extends Renderer {
		writeUnwrapped(RuntimeApi.INTEGER, d, dataUseVariables);
	}

	/**
	 * Writes a time data use where the engine needs a Java long in milliseconds
	 * (periods).
	 */
	private void writeMillis(DataUse d, Map<DataUse, String> dataUseVariables) {
		writeUnwrapped(RuntimeApi.MILLIS, d, dataUseVariables);
	}

	private void writeUnwrapped(String helper, DataUse d, Map<DataUse, String> dataUseVariables) {
		if (isUnmapped())
			append(COMPONENT_FIELD + "." + helper + "(");
@@ -3010,8 +3133,10 @@ public class JUnitTestGenerator extends Renderer {
	}

	private String getMessage(Behaviour b) {
		// XXX
		return b.eClass().getName() + ": " + b.getName();
		String name = b.getName();
		if (name == null)
			return getElementName(b);
		return b.eClass().getName() + ": " + name;
	}

	private String getAssertionResultVariable(Element assertion) {
+11 −0
Original line number Diff line number Diff line
@@ -136,6 +136,17 @@ public abstract class Renderer {
		line("}");
	}

	/**
	 * Closes a block and continues on the same line, e.g. for a lambda body that
	 * is followed by further arguments.
	 */
	protected void blockCloseInline() {
		indent = indent.substring(1);
		// Rewind a tab
		buf.position(buf.position() - 1);
		append("}");
	}

	protected void blockCloseArrow() {
		indent = indent.substring(1);
		// Rewind a tab
+0 −2
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@ final class RuntimeApi {
	static final String TERMINATE = "terminate";
	static final String STOP = "stop";
	static final String TIMEOUT = "timeout";
	static final String TIME_CONSTRAINT_DEADLINE = "timeConstraintDeadline";
	static final String TIME = "time";
	static final String SLEEP = "sleep";
	static final String SHUTDOWN = "shutdown";
@@ -98,7 +97,6 @@ final class RuntimeApi {
	static final String LAST = "last"; // TimeLabel#last
	static final String FIRST = "first"; // TimeLabel#first
	static final String PREVIOUS = "previous"; // TimeLabel#previous
	static final String FIRST_OR = "firstOr"; // TimeLabel#firstOr
	static final String NOW_MILLIS = "nowMillis"; // TimeLabel#nowMillis
	static final String TIMER_START = "start"; // Timer#start
	static final String TIMER_STOP = "stop"; // Timer#stop
+24 −0
Original line number Diff line number Diff line
@@ -84,4 +84,28 @@ public final class BehaviourProperties {
	@GeneratedCodeApi
	public static final String VALUE = "value";

	// Any atomic behaviour with time constraints (completion)

	/**
	 * {@link Boolean}: whether the time constraints held for the event (for a
	 * tester-input behaviour) or were waited for successfully.
	 */
	@GeneratedCodeApi
	public static final String TIME_CONSTRAINT_RESULT = "timeConstraintResult";

	/**
	 * {@link String}: name of the top-level operator of the (single) time
	 * constraint expression, if it is an operator application.
	 */
	@GeneratedCodeApi
	public static final String TIME_CONSTRAINT_OPERATOR = "timeConstraintOperator";

	/**
	 * <code>Object[]</code>: the operands of the top-level operator evaluated at
	 * the event time, in order; reported together with
	 * {@link #TIME_CONSTRAINT_OPERATOR}. Time values are in milliseconds.
	 */
	@GeneratedCodeApi
	public static final String TIME_CONSTRAINT_OPERANDS = "timeConstraintOperands";

}
+78 −2
Original line number Diff line number Diff line
package org.etsi.mts.tdl.execution.java.rt.core;

public interface Constraint {
	boolean evaluate();
import java.util.function.Supplier;

/**
 * The time constraints of an atomic behaviour as a predicate over the time
 * labels, evaluated with the labels as they are — including the behaviour's
 * own label, which therefore must carry the timestamp of the occurrence being
 * evaluated: for a tester-input behaviour the engine records the event time
 * before evaluating; for any other behaviour it provisionally records the
 * current time while waiting for the constraint to hold (see
 * {@link TestControl#awaitTimeConstraint}). A label without any timestamp (a
 * behaviour that has not occurred yet) is a specification error and makes the
 * evaluation fail with an exception.
 * <p>
 * One evaluation yields both the result and, for reporting, the evaluated
 * operands of the top-level operator, so that the constraint expression is
 * computed only once per evaluation.
 */
public final class Constraint {

	/**
	 * The outcome of evaluating a constraint.
	 */
	@GeneratedCodeApi
	public static final class Evaluation {
		/**
		 * Whether the constraint holds.
		 */
		@GeneratedCodeApi
		public final boolean holds;
		/**
		 * The evaluated operands of the top-level operator (see
		 * {@link Constraint#operator()}), or <b>null</b> if not an operator
		 * application.
		 */
		@GeneratedCodeApi
		public final Object[] operands;

		@GeneratedCodeApi
		public Evaluation(boolean holds, Object[] operands) {
			this.holds = holds;
			this.operands = operands;
		}

		@GeneratedCodeApi
		public Evaluation(boolean holds) {
			this(holds, null);
		}
	}

	private final Supplier<Evaluation> evaluation;
	private final String operator;

	@GeneratedCodeApi
	public Constraint(Supplier<Evaluation> evaluation) {
		this(evaluation, null);
	}

	/**
	 * @param evaluation The evaluation function, assembled by generated code.
	 * @param operator   Name of the top-level operator of the constraint
	 *                   expression, for reporting or <b>null</b> if the expression is
	 *                   not an operator application.
	 */
	@GeneratedCodeApi
	public Constraint(Supplier<Evaluation> evaluation, String operator) {
		this.evaluation = evaluation;
		this.operator = operator;
	}

	@GeneratedCodeApi
	public Evaluation evaluate() {
		return evaluation.get();
	}


	public String operator() {
		return operator;
	}
}
Loading