Skip to content

Instantly share code, notes, and snippets.

@chaserb
Last active January 3, 2024 16:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chaserb/a319a994eb6ebb8c936206e3122fdf96 to your computer and use it in GitHub Desktop.
Save chaserb/a319a994eb6ebb8c936206e3122fdf96 to your computer and use it in GitHub Desktop.
Test Various ISO-8601 Duration Strings in Flowable Engine
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dataSource" class="org.flowable.common.engine.impl.test.ClosingDataSource">
<constructor-arg>
<bean class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<constructor-arg>
<bean class="com.zaxxer.hikari.HikariConfig">
<property name="minimumIdle" value="0" />
<property name="jdbcUrl" value="${jdbc.url:jdbc:h2:mem:flowable;DB_CLOSE_DELAY=1000}"/>
<property name="driverClassName" value="${jdbc.driver:org.h2.Driver}"/>
<property name="username" value="${jdbc.username:sa}"/>
<property name="password" value="${jdbc.password:}"/>
</bean>
</constructor-arg>
</bean>
</constructor-arg>
</bean>
<bean id="processEngineConfiguration" class="org.flowable.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="asyncExecutorActivate" value="false" />
<property name="asyncFailedJobWaitTime" value="1" />
<property name="asyncHistoryEnabled" value="false" />
<property name="clock">
<bean class="org.flowable.common.engine.impl.util.TestClockImpl" />
</property>
<property name="dataSource" ref="dataSource"/>
<property name="databaseSchemaUpdate" value="drop-create" />
<property name="enableHistoricTaskLogging" value="true"/>
<property name="enableProcessDefinitionHistoryLevel" value="true" />
<property name="enableProcessDefinitionInfoCache" value="true" />
<property name="history" value="full" />
</bean>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:flowable="http://flowable.org/bpmn" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef" exporter="Flowable Open Source Modeler" exporterVersion="6.8.0">
<process id="waitTask" name="Wait" isExecutable="true">
<startEvent id="wait-start" name="Start" flowable:formFieldValidation="true"></startEvent>
<intermediateCatchEvent id="wait-wait" name="Wait">
<timerEventDefinition>
<timeDuration>${waitTaskWaitTime}</timeDuration>
</timerEventDefinition>
</intermediateCatchEvent>
<endEvent id="wait-end" name="End"></endEvent>
<sequenceFlow id="wait-flow1" sourceRef="wait-start" targetRef="wait-wait"></sequenceFlow>
<sequenceFlow id="wait-flow2" sourceRef="wait-wait" targetRef="wait-end"></sequenceFlow>
</process>
</definitions>
package com.test;
import org.assertj.core.data.Offset;
import org.flowable.common.engine.impl.runtime.Clock;
import org.flowable.common.engine.impl.util.DefaultClockImpl;
import org.flowable.engine.HistoryService;
import org.flowable.engine.ManagementService;
import org.flowable.engine.ProcessEngineConfiguration;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.impl.test.JobTestHelper;
import org.flowable.engine.test.Deployment;
import org.flowable.engine.test.FlowableTest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import java.time.Duration;
import java.time.Instant;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
import static org.assertj.core.api.Assertions.assertThat;
@FlowableTest
public class WaitTaskTest {
private ProcessEngineConfiguration processEngineConfiguration;
private RuntimeService runtimeService;
private HistoryService historyService;
private ManagementService managementService;
private static final TimeZone UTC = TimeZone.getTimeZone(ZoneId.of("UTC"));
private final Clock testClock = new DefaultClockImpl(UTC);
private final Map<String, Object> input = new HashMap<>();
@BeforeEach
protected void setUpInput(ProcessEngineConfiguration processEngineConfiguration, RuntimeService runtimeService,
HistoryService historyService, ManagementService managementService) {
this.processEngineConfiguration = processEngineConfiguration;
this.runtimeService = runtimeService;
this.historyService = historyService;
this.managementService = managementService;
TimeZone.setDefault(UTC);
processEngineConfiguration.setClock(testClock);
testClock.setCurrentTime(new Date());
input.clear();
}
@ParameterizedTest
@ValueSource(strings = {"PT10S", "P2D", "P4DT12H30M10S"})
@Deployment(resources = {"processes/process-waitTask.bpmn20.xml"})
public void testWaitWithDuration(String durationString) {
// Set up a clock we can manually forward for testing
Duration totalDuration = Duration.parse(durationString);
Instant currentInstant = Instant.now();
Instant halfInterval = currentInstant.plus(totalDuration.dividedBy(2));
Instant fullInterval = halfInterval.plus(totalDuration.dividedBy(2));
input.put("waitTaskWaitTime", durationString);
testInterval(halfInterval, fullInterval, testClock, totalDuration.toMillis());
}
@ParameterizedTest
@ValueSource(strings = {"P5Y", "P4M", "P3W", "P2D"})
@Deployment(resources = {"processes/process-waitTask.bpmn20.xml"})
public void testWaitWithPeriod(String durationString) {
// Set up a clock we can manually forward for testing
Period totalPeriod = Period.parse(durationString);
ZonedDateTime currentTime = ZonedDateTime.now();
ZonedDateTime fullInterval = currentTime.plus(totalPeriod);
long periodInSeconds = fullInterval.toEpochSecond() - currentTime.toEpochSecond();
ZonedDateTime halfInterval = currentTime.plusSeconds(periodInSeconds / 2);
input.put("waitTaskWaitTime", durationString);
testInterval(halfInterval.toInstant(), fullInterval.toInstant(), testClock, periodInSeconds * 1000);
}
private void testInterval(final Instant halfInterval, final Instant fullInterval, final Clock testClock, final long intervalMillis) {
// Configure the wait time
runtimeService.startProcessInstanceByKey("waitTask", input);
waitForJobsAndTimers();
// Forward the clock half of the total wait time
testClock.setCurrentCalendar(GregorianCalendar.from(ZonedDateTime.ofInstant(halfInterval, UTC.toZoneId())));
waitForJobsAndTimers();
// Ensure that our workflow is still running (ie the `wait-end` activity has not been executed)
assertThat(historyService.createHistoricActivityInstanceQuery().activityId("wait-end").singleResult()).isNull();
// Forward the clock another half-interval (thus we've awaited the whole interval)
testClock.setCurrentCalendar(GregorianCalendar.from(ZonedDateTime.ofInstant(fullInterval, UTC.toZoneId())));
waitForJobsAndTimers();
// Ensure that our task is finished
assertThat(historyService.createHistoricActivityInstanceQuery().activityId("wait-end").singleResult()).isNotNull();
// Ensure that our wait task has an execution duration that matches our input
assertThat(historyService.createHistoricActivityInstanceQuery().activityId("wait-wait").singleResult().getDurationInMillis()).isCloseTo(intervalMillis, Offset.offset(100L));
}
protected void waitForJobsAndTimers() {
JobTestHelper.waitForJobExecutorToProcessAllJobs(processEngineConfiguration,
managementService, 10_000L, 100L);
}
}
@chaserb
Copy link
Author

chaserb commented Jan 3, 2024

Note, the "P3W" input on the testWaitWithPeriod() test fails with Flowable 7.0.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment