Add execution metadata to tasks and scheduled tasks
This commit adds new information about the execution and scheduling of tasks. The `Task` type now exposes the `TaskExecutionOutcome` of the latest execution; this includes the instant the execution started, the execution outcome and any thrown exception. The `ScheduledTask` contract can now provide the time when the next execution is scheduled. Closes gh-24560
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,7 +16,9 @@
|
||||
|
||||
package org.springframework.scheduling.config;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@@ -25,6 +27,7 @@ import org.springframework.lang.Nullable;
|
||||
* used as a return value for scheduling methods.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Brian Clozel
|
||||
* @since 4.3
|
||||
* @see ScheduledTaskRegistrar#scheduleCronTask(CronTask)
|
||||
* @see ScheduledTaskRegistrar#scheduleFixedRateTask(FixedRateTask)
|
||||
@@ -76,6 +79,22 @@ public final class ScheduledTask {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next scheduled execution of the task, or {@code null}
|
||||
* if the task has been cancelled or no new execution is scheduled.
|
||||
* @since 6.2
|
||||
*/
|
||||
@Nullable
|
||||
public Instant nextExecution() {
|
||||
if (this.future != null && !this.future.isCancelled()) {
|
||||
long delay = this.future.getDelay(TimeUnit.MILLISECONDS);
|
||||
if (delay > 0) {
|
||||
return Instant.now().plusMillis(delay);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.task.toString();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.scheduling.config;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -24,12 +26,15 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Juergen Hoeller
|
||||
* @author Brian Clozel
|
||||
* @since 3.2
|
||||
*/
|
||||
public class Task {
|
||||
|
||||
private final Runnable runnable;
|
||||
|
||||
private TaskExecutionOutcome lastExecutionOutcome;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code Task}.
|
||||
@@ -37,7 +42,8 @@ public class Task {
|
||||
*/
|
||||
public Task(Runnable runnable) {
|
||||
Assert.notNull(runnable, "Runnable must not be null");
|
||||
this.runnable = runnable;
|
||||
this.runnable = new OutcomeTrackingRunnable(runnable);
|
||||
this.lastExecutionOutcome = TaskExecutionOutcome.create();
|
||||
}
|
||||
|
||||
|
||||
@@ -48,10 +54,45 @@ public class Task {
|
||||
return this.runnable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the outcome of the last task execution.
|
||||
* @since 6.2
|
||||
*/
|
||||
public TaskExecutionOutcome getLastExecutionOutcome() {
|
||||
return this.lastExecutionOutcome;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.runnable.toString();
|
||||
}
|
||||
|
||||
|
||||
private class OutcomeTrackingRunnable implements Runnable {
|
||||
|
||||
private final Runnable runnable;
|
||||
|
||||
public OutcomeTrackingRunnable(Runnable runnable) {
|
||||
this.runnable = runnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Task.this.lastExecutionOutcome = Task.this.lastExecutionOutcome.start(Instant.now());
|
||||
this.runnable.run();
|
||||
Task.this.lastExecutionOutcome = Task.this.lastExecutionOutcome.success();
|
||||
}
|
||||
catch (Throwable exc) {
|
||||
Task.this.lastExecutionOutcome = Task.this.lastExecutionOutcome.failure(exc);
|
||||
throw exc;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.runnable.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.scheduling.config;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Outcome of a {@link Task} execution.
|
||||
* @param executionTime the instant when the task execution started, {@code null} if the task has not started.
|
||||
* @param status the {@link Status} of the execution outcome.
|
||||
* @param throwable the exception thrown from the task execution, if any.
|
||||
* @author Brian Clozel
|
||||
* @since 6.2
|
||||
*/
|
||||
public record TaskExecutionOutcome(@Nullable Instant executionTime, Status status, @Nullable Throwable throwable) {
|
||||
|
||||
TaskExecutionOutcome start(Instant executionTime) {
|
||||
return new TaskExecutionOutcome(executionTime, Status.STARTED, null);
|
||||
}
|
||||
|
||||
TaskExecutionOutcome success() {
|
||||
Assert.state(this.executionTime != null, "Task has not been started yet");
|
||||
return new TaskExecutionOutcome(this.executionTime, Status.SUCCESS, null);
|
||||
}
|
||||
|
||||
TaskExecutionOutcome failure(Throwable throwable) {
|
||||
Assert.state(this.executionTime != null, "Task has not been started yet");
|
||||
return new TaskExecutionOutcome(this.executionTime, Status.ERROR, throwable);
|
||||
}
|
||||
|
||||
static TaskExecutionOutcome create() {
|
||||
return new TaskExecutionOutcome(null, Status.NONE, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Status of the task execution outcome.
|
||||
*/
|
||||
public enum Status {
|
||||
/**
|
||||
* The task has not been executed so far.
|
||||
*/
|
||||
NONE,
|
||||
/**
|
||||
* The task execution has been started and is ongoing.
|
||||
*/
|
||||
STARTED,
|
||||
/**
|
||||
* The task execution finished successfully.
|
||||
*/
|
||||
SUCCESS,
|
||||
/**
|
||||
* The task execution finished with an error.
|
||||
*/
|
||||
ERROR
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,6 @@ import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
@@ -32,6 +31,7 @@ import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.assertj.core.api.AbstractAssert;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ParameterContext;
|
||||
@@ -116,11 +116,7 @@ class ScheduledAnnotationBeanPostProcessorTests {
|
||||
new DirectFieldAccessor(registrar).getPropertyValue("fixedDelayTasks");
|
||||
assertThat(fixedDelayTasks).hasSize(1);
|
||||
IntervalTask task = fixedDelayTasks.get(0);
|
||||
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
|
||||
Object targetObject = runnable.getTarget();
|
||||
Method targetMethod = runnable.getMethod();
|
||||
assertThat(targetObject).isEqualTo(target);
|
||||
assertThat(targetMethod.getName()).isEqualTo("fixedDelay");
|
||||
assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("fixedDelay");
|
||||
assertThat(task.getInitialDelayDuration()).isZero();
|
||||
assertThat(task.getIntervalDuration()).isEqualTo(
|
||||
Duration.ofMillis(expectedInterval < 0 ? Long.MAX_VALUE : expectedInterval));
|
||||
@@ -150,11 +146,7 @@ class ScheduledAnnotationBeanPostProcessorTests {
|
||||
new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks");
|
||||
assertThat(fixedRateTasks).hasSize(1);
|
||||
IntervalTask task = fixedRateTasks.get(0);
|
||||
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
|
||||
Object targetObject = runnable.getTarget();
|
||||
Method targetMethod = runnable.getMethod();
|
||||
assertThat(targetObject).isEqualTo(target);
|
||||
assertThat(targetMethod.getName()).isEqualTo("fixedRate");
|
||||
assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("fixedRate");
|
||||
assertSoftly(softly -> {
|
||||
softly.assertThat(task.getInitialDelayDuration()).as("initial delay").isZero();
|
||||
softly.assertThat(task.getIntervalDuration()).as("interval").isEqualTo(Duration.ofMillis(expectedInterval));
|
||||
@@ -185,11 +177,7 @@ class ScheduledAnnotationBeanPostProcessorTests {
|
||||
new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks");
|
||||
assertThat(fixedRateTasks).hasSize(1);
|
||||
IntervalTask task = fixedRateTasks.get(0);
|
||||
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
|
||||
Object targetObject = runnable.getTarget();
|
||||
Method targetMethod = runnable.getMethod();
|
||||
assertThat(targetObject).isEqualTo(target);
|
||||
assertThat(targetMethod.getName()).isEqualTo("fixedRate");
|
||||
assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("fixedRate");
|
||||
assertSoftly(softly -> {
|
||||
softly.assertThat(task.getInitialDelayDuration()).as("initial delay").isEqualTo(Duration.ofMillis(expectedInitialDelay));
|
||||
softly.assertThat(task.getIntervalDuration()).as("interval").isEqualTo(Duration.ofMillis(expectedInterval));
|
||||
@@ -250,19 +238,11 @@ class ScheduledAnnotationBeanPostProcessorTests {
|
||||
new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks");
|
||||
assertThat(fixedRateTasks).hasSize(2);
|
||||
IntervalTask task1 = fixedRateTasks.get(0);
|
||||
ScheduledMethodRunnable runnable1 = (ScheduledMethodRunnable) task1.getRunnable();
|
||||
Object targetObject = runnable1.getTarget();
|
||||
Method targetMethod = runnable1.getMethod();
|
||||
assertThat(targetObject).isEqualTo(target);
|
||||
assertThat(targetMethod.getName()).isEqualTo("fixedRate");
|
||||
assertThatScheduledRunnable(task1.getRunnable()).hasTarget(target).hasMethodName("fixedRate");
|
||||
assertThat(task1.getInitialDelayDuration()).isZero();
|
||||
assertThat(task1.getIntervalDuration()).isEqualTo(Duration.ofMillis(4_000L));
|
||||
IntervalTask task2 = fixedRateTasks.get(1);
|
||||
ScheduledMethodRunnable runnable2 = (ScheduledMethodRunnable) task2.getRunnable();
|
||||
targetObject = runnable2.getTarget();
|
||||
targetMethod = runnable2.getMethod();
|
||||
assertThat(targetObject).isEqualTo(target);
|
||||
assertThat(targetMethod.getName()).isEqualTo("fixedRate");
|
||||
assertThatScheduledRunnable(task2.getRunnable()).hasTarget(target).hasMethodName("fixedRate");
|
||||
assertThat(task2.getInitialDelayDuration()).isEqualTo(Duration.ofMillis(2_000L));
|
||||
assertThat(task2.getIntervalDuration()).isEqualTo(Duration.ofMillis(4_000L));
|
||||
}
|
||||
@@ -286,11 +266,7 @@ class ScheduledAnnotationBeanPostProcessorTests {
|
||||
new DirectFieldAccessor(registrar).getPropertyValue("oneTimeTasks");
|
||||
assertThat(oneTimeTasks).hasSize(1);
|
||||
OneTimeTask task = oneTimeTasks.get(0);
|
||||
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
|
||||
Object targetObject = runnable.getTarget();
|
||||
Method targetMethod = runnable.getMethod();
|
||||
assertThat(targetObject).isEqualTo(target);
|
||||
assertThat(targetMethod.getName()).isEqualTo("oneTimeTask");
|
||||
assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("oneTimeTask");
|
||||
assertThat(task.getInitialDelayDuration()).isEqualTo(Duration.ofMillis(2_000L));
|
||||
}
|
||||
|
||||
@@ -313,11 +289,7 @@ class ScheduledAnnotationBeanPostProcessorTests {
|
||||
new DirectFieldAccessor(registrar).getPropertyValue("cronTasks");
|
||||
assertThat(cronTasks).hasSize(1);
|
||||
CronTask task = cronTasks.get(0);
|
||||
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
|
||||
Object targetObject = runnable.getTarget();
|
||||
Method targetMethod = runnable.getMethod();
|
||||
assertThat(targetObject).isEqualTo(target);
|
||||
assertThat(targetMethod.getName()).isEqualTo("cron");
|
||||
assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("cron");
|
||||
assertThat(task.getExpression()).isEqualTo("*/7 * * * * ?");
|
||||
}
|
||||
|
||||
@@ -340,11 +312,7 @@ class ScheduledAnnotationBeanPostProcessorTests {
|
||||
new DirectFieldAccessor(registrar).getPropertyValue("cronTasks");
|
||||
assertThat(cronTasks).hasSize(1);
|
||||
CronTask task = cronTasks.get(0);
|
||||
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
|
||||
Object targetObject = runnable.getTarget();
|
||||
Method targetMethod = runnable.getMethod();
|
||||
assertThat(targetObject).isEqualTo(target);
|
||||
assertThat(targetMethod.getName()).isEqualTo("cron");
|
||||
assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("cron");
|
||||
assertThat(task.getExpression()).isEqualTo("0 0 0-4,6-23 * * ?");
|
||||
Trigger trigger = task.getTrigger();
|
||||
assertThat(trigger).isNotNull();
|
||||
@@ -404,11 +372,9 @@ class ScheduledAnnotationBeanPostProcessorTests {
|
||||
new DirectFieldAccessor(registrar).getPropertyValue("cronTasks");
|
||||
assertThat(cronTasks).hasSize(1);
|
||||
CronTask task = cronTasks.get(0);
|
||||
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
|
||||
Object targetObject = runnable.getTarget();
|
||||
Method targetMethod = runnable.getMethod();
|
||||
assertThat(targetObject).isEqualTo(context.getBean(ScopedProxyUtils.getTargetBeanName("target")));
|
||||
assertThat(targetMethod.getName()).isEqualTo("cron");
|
||||
assertThatScheduledRunnable(task.getRunnable())
|
||||
.hasTarget(context.getBean(ScopedProxyUtils.getTargetBeanName("target")))
|
||||
.hasMethodName("cron");
|
||||
assertThat(task.getExpression()).isEqualTo("*/7 * * * * ?");
|
||||
}
|
||||
|
||||
@@ -431,11 +397,7 @@ class ScheduledAnnotationBeanPostProcessorTests {
|
||||
new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks");
|
||||
assertThat(fixedRateTasks).hasSize(1);
|
||||
IntervalTask task = fixedRateTasks.get(0);
|
||||
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
|
||||
Object targetObject = runnable.getTarget();
|
||||
Method targetMethod = runnable.getMethod();
|
||||
assertThat(targetObject).isEqualTo(target);
|
||||
assertThat(targetMethod.getName()).isEqualTo("checkForUpdates");
|
||||
assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("checkForUpdates");
|
||||
assertThat(task.getIntervalDuration()).isEqualTo(Duration.ofMillis(5_000L));
|
||||
}
|
||||
|
||||
@@ -458,11 +420,7 @@ class ScheduledAnnotationBeanPostProcessorTests {
|
||||
new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks");
|
||||
assertThat(fixedRateTasks).hasSize(1);
|
||||
IntervalTask task = fixedRateTasks.get(0);
|
||||
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
|
||||
Object targetObject = runnable.getTarget();
|
||||
Method targetMethod = runnable.getMethod();
|
||||
assertThat(targetObject).isEqualTo(target);
|
||||
assertThat(targetMethod.getName()).isEqualTo("checkForUpdates");
|
||||
assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("checkForUpdates");
|
||||
assertThat(task.getIntervalDuration()).isEqualTo(Duration.ofMillis(5_000L));
|
||||
assertThat(task.getInitialDelayDuration()).isEqualTo(Duration.ofMillis(1_000L));
|
||||
}
|
||||
@@ -486,11 +444,7 @@ class ScheduledAnnotationBeanPostProcessorTests {
|
||||
new DirectFieldAccessor(registrar).getPropertyValue("cronTasks");
|
||||
assertThat(cronTasks).hasSize(1);
|
||||
CronTask task = cronTasks.get(0);
|
||||
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
|
||||
Object targetObject = runnable.getTarget();
|
||||
Method targetMethod = runnable.getMethod();
|
||||
assertThat(targetObject).isEqualTo(target);
|
||||
assertThat(targetMethod.getName()).isEqualTo("generateReport");
|
||||
assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("generateReport");
|
||||
assertThat(task.getExpression()).isEqualTo("0 0 * * * ?");
|
||||
}
|
||||
|
||||
@@ -519,11 +473,7 @@ class ScheduledAnnotationBeanPostProcessorTests {
|
||||
new DirectFieldAccessor(registrar).getPropertyValue("cronTasks");
|
||||
assertThat(cronTasks).hasSize(1);
|
||||
CronTask task = cronTasks.get(0);
|
||||
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
|
||||
Object targetObject = runnable.getTarget();
|
||||
Method targetMethod = runnable.getMethod();
|
||||
assertThat(targetObject).isEqualTo(target);
|
||||
assertThat(targetMethod.getName()).isEqualTo("x");
|
||||
assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("x");
|
||||
assertThat(task.getExpression()).isEqualTo(businessHoursCronExpression);
|
||||
}
|
||||
|
||||
@@ -578,11 +528,7 @@ class ScheduledAnnotationBeanPostProcessorTests {
|
||||
new DirectFieldAccessor(registrar).getPropertyValue("fixedDelayTasks");
|
||||
assertThat(fixedDelayTasks).hasSize(1);
|
||||
IntervalTask task = fixedDelayTasks.get(0);
|
||||
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
|
||||
Object targetObject = runnable.getTarget();
|
||||
Method targetMethod = runnable.getMethod();
|
||||
assertThat(targetObject).isEqualTo(target);
|
||||
assertThat(targetMethod.getName()).isEqualTo("fixedDelay");
|
||||
assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("fixedDelay");
|
||||
assertSoftly(softly -> {
|
||||
softly.assertThat(task.getInitialDelayDuration()).as("initial delay").isEqualTo(Duration.ofMillis(expectedInitialDelay));
|
||||
softly.assertThat(task.getIntervalDuration()).as("interval").isEqualTo(Duration.ofMillis(expectedInterval));
|
||||
@@ -622,11 +568,7 @@ class ScheduledAnnotationBeanPostProcessorTests {
|
||||
new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks");
|
||||
assertThat(fixedRateTasks).hasSize(1);
|
||||
IntervalTask task = fixedRateTasks.get(0);
|
||||
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
|
||||
Object targetObject = runnable.getTarget();
|
||||
Method targetMethod = runnable.getMethod();
|
||||
assertThat(targetObject).isEqualTo(target);
|
||||
assertThat(targetMethod.getName()).isEqualTo("fixedRate");
|
||||
assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("fixedRate");
|
||||
assertSoftly(softly -> {
|
||||
softly.assertThat(task.getInitialDelayDuration()).as("initial delay").isEqualTo(Duration.ofMillis(expectedInitialDelay));
|
||||
softly.assertThat(task.getIntervalDuration()).as("interval").isEqualTo(Duration.ofMillis(expectedInterval));
|
||||
@@ -656,11 +598,7 @@ class ScheduledAnnotationBeanPostProcessorTests {
|
||||
new DirectFieldAccessor(registrar).getPropertyValue("cronTasks");
|
||||
assertThat(cronTasks).hasSize(1);
|
||||
CronTask task = cronTasks.get(0);
|
||||
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
|
||||
Object targetObject = runnable.getTarget();
|
||||
Method targetMethod = runnable.getMethod();
|
||||
assertThat(targetObject).isEqualTo(target);
|
||||
assertThat(targetMethod.getName()).isEqualTo("x");
|
||||
assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("x");
|
||||
assertThat(task.getExpression()).isEqualTo(businessHoursCronExpression);
|
||||
}
|
||||
|
||||
@@ -689,11 +627,7 @@ class ScheduledAnnotationBeanPostProcessorTests {
|
||||
new DirectFieldAccessor(registrar).getPropertyValue("cronTasks");
|
||||
assertThat(cronTasks).hasSize(1);
|
||||
CronTask task = cronTasks.get(0);
|
||||
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
|
||||
Object targetObject = runnable.getTarget();
|
||||
Method targetMethod = runnable.getMethod();
|
||||
assertThat(targetObject).isEqualTo(target);
|
||||
assertThat(targetMethod.getName()).isEqualTo("y");
|
||||
assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("y");
|
||||
assertThat(task.getExpression()).isEqualTo(businessHoursCronExpression);
|
||||
}
|
||||
|
||||
@@ -716,11 +650,7 @@ class ScheduledAnnotationBeanPostProcessorTests {
|
||||
new DirectFieldAccessor(registrar).getPropertyValue("cronTasks");
|
||||
assertThat(cronTasks).hasSize(1);
|
||||
CronTask task = cronTasks.get(0);
|
||||
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
|
||||
Object targetObject = runnable.getTarget();
|
||||
Method targetMethod = runnable.getMethod();
|
||||
assertThat(targetObject).isEqualTo(target);
|
||||
assertThat(targetMethod.getName()).isEqualTo("cron");
|
||||
assertThatScheduledRunnable(task.getRunnable()).hasTarget(target).hasMethodName("cron");
|
||||
assertThat(task.getExpression()).isEqualTo("0 0 9-17 * * MON-FRI");
|
||||
}
|
||||
|
||||
@@ -1088,4 +1018,29 @@ class ScheduledAnnotationBeanPostProcessorTests {
|
||||
}
|
||||
}
|
||||
|
||||
static ScheduledMethodRunnableAssert assertThatScheduledRunnable(Runnable runnable) {
|
||||
return new ScheduledMethodRunnableAssert(runnable);
|
||||
}
|
||||
|
||||
static class ScheduledMethodRunnableAssert extends AbstractAssert<ScheduledMethodRunnableAssert, Runnable> {
|
||||
|
||||
public ScheduledMethodRunnableAssert(Runnable actual) {
|
||||
super(actual, ScheduledMethodRunnableAssert.class);
|
||||
assertThat(actual).extracting("runnable").isInstanceOf(ScheduledMethodRunnable.class);
|
||||
}
|
||||
|
||||
public ScheduledMethodRunnableAssert hasTarget(Object target) {
|
||||
isNotNull();
|
||||
assertThat(actual).extracting("runnable.target").isEqualTo(target);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ScheduledMethodRunnableAssert hasMethodName(String name) {
|
||||
isNotNull();
|
||||
assertThat(actual).extracting("runnable.method.name").isEqualTo(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.scheduling.config;
|
||||
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import org.awaitility.Awaitility;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.scheduling.concurrent.SimpleAsyncTaskScheduler;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ScheduledTask}.
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class ScheduledTaskTests {
|
||||
|
||||
private CountingRunnable countingRunnable = new CountingRunnable();
|
||||
|
||||
private SimpleAsyncTaskScheduler taskScheduler = new SimpleAsyncTaskScheduler();
|
||||
|
||||
private ScheduledTaskRegistrar taskRegistrar = new ScheduledTaskRegistrar();
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.taskRegistrar.setTaskScheduler(this.taskScheduler);
|
||||
taskScheduler.start();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
taskScheduler.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnConfiguredTask() {
|
||||
Task task = new Task(countingRunnable);
|
||||
ScheduledTask scheduledTask = new ScheduledTask(task);
|
||||
assertThat(scheduledTask.getTask()).isEqualTo(task);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseTaskToString() {
|
||||
Task task = new Task(countingRunnable);
|
||||
ScheduledTask scheduledTask = new ScheduledTask(task);
|
||||
assertThat(scheduledTask.toString()).isEqualTo(task.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void unscheduledTaskShouldNotHaveNextExecution() {
|
||||
ScheduledTask scheduledTask = new ScheduledTask(new Task(countingRunnable));
|
||||
assertThat(scheduledTask.nextExecution()).isNull();
|
||||
assertThat(countingRunnable.executionCount).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
void scheduledTaskShouldHaveNextExecution() {
|
||||
ScheduledTask scheduledTask = taskRegistrar.scheduleFixedDelayTask(new FixedDelayTask(countingRunnable,
|
||||
Duration.ofSeconds(10), Duration.ofSeconds(10)));
|
||||
assertThat(scheduledTask.nextExecution()).isBefore(Instant.now().plusSeconds(11));
|
||||
}
|
||||
|
||||
@Test
|
||||
void cancelledTaskShouldNotHaveNextExecution() {
|
||||
ScheduledTask scheduledTask = taskRegistrar.scheduleFixedDelayTask(new FixedDelayTask(countingRunnable,
|
||||
Duration.ofSeconds(10), Duration.ofSeconds(10)));
|
||||
scheduledTask.cancel(true);
|
||||
assertThat(scheduledTask.nextExecution()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleExecutionShouldNotHaveNextExecution() {
|
||||
ScheduledTask scheduledTask = taskRegistrar.scheduleOneTimeTask(new OneTimeTask(countingRunnable, Duration.ofSeconds(0)));
|
||||
Awaitility.await().atMost(Duration.ofSeconds(5)).until(() -> countingRunnable.executionCount > 0);
|
||||
assertThat(scheduledTask.nextExecution()).isNull();
|
||||
}
|
||||
|
||||
class CountingRunnable implements Runnable {
|
||||
|
||||
int executionCount;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
executionCount++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,11 +16,11 @@
|
||||
|
||||
package org.springframework.scheduling.config;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import org.assertj.core.api.ObjectAssert;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.springframework.scheduling.TriggerContext;
|
||||
import org.springframework.scheduling.support.ScheduledMethodRunnable;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
@@ -68,11 +69,13 @@ public class ScheduledTasksBeanDefinitionParserTests {
|
||||
List<IntervalTask> tasks = (List<IntervalTask>) new DirectFieldAccessor(
|
||||
this.registrar).getPropertyValue("fixedRateTasks");
|
||||
Runnable runnable = tasks.get(0).getRunnable();
|
||||
assertThat(runnable.getClass()).isEqualTo(ScheduledMethodRunnable.class);
|
||||
Object targetObject = ((ScheduledMethodRunnable) runnable).getTarget();
|
||||
Method targetMethod = ((ScheduledMethodRunnable) runnable).getMethod();
|
||||
assertThat(targetObject).isEqualTo(this.testBean);
|
||||
assertThat(targetMethod.getName()).isEqualTo("test");
|
||||
|
||||
ObjectAssert<ScheduledMethodRunnable> runnableAssert = assertThat(runnable)
|
||||
.extracting("runnable")
|
||||
.isInstanceOf(ScheduledMethodRunnable.class)
|
||||
.asInstanceOf(type(ScheduledMethodRunnable.class));
|
||||
runnableAssert.extracting("target").isEqualTo(testBean);
|
||||
runnableAssert.extracting("method.name").isEqualTo("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.scheduling.config;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link TaskExecutionOutcome}.
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class TaskExecutionOutcomeTests {
|
||||
|
||||
@Test
|
||||
void shouldCreateWithNoneStatus() {
|
||||
TaskExecutionOutcome outcome = TaskExecutionOutcome.create();
|
||||
assertThat(outcome.status()).isEqualTo(TaskExecutionOutcome.Status.NONE);
|
||||
assertThat(outcome.executionTime()).isNull();
|
||||
assertThat(outcome.throwable()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void startedTaskShouldBeOngoing() {
|
||||
TaskExecutionOutcome outcome = TaskExecutionOutcome.create();
|
||||
Instant now = Instant.now();
|
||||
outcome = outcome.start(now);
|
||||
assertThat(outcome.status()).isEqualTo(TaskExecutionOutcome.Status.STARTED);
|
||||
assertThat(outcome.executionTime()).isEqualTo(now);
|
||||
assertThat(outcome.throwable()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectSuccessWhenNotStarted() {
|
||||
TaskExecutionOutcome outcome = TaskExecutionOutcome.create();
|
||||
assertThatIllegalStateException().isThrownBy(outcome::success);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectErrorWhenNotStarted() {
|
||||
TaskExecutionOutcome outcome = TaskExecutionOutcome.create();
|
||||
assertThatIllegalStateException().isThrownBy(() -> outcome.failure(new IllegalArgumentException("test error")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void finishedTaskShouldBeSuccessful() {
|
||||
TaskExecutionOutcome outcome = TaskExecutionOutcome.create();
|
||||
Instant now = Instant.now();
|
||||
outcome = outcome.start(now);
|
||||
outcome = outcome.success();
|
||||
assertThat(outcome.status()).isEqualTo(TaskExecutionOutcome.Status.SUCCESS);
|
||||
assertThat(outcome.executionTime()).isEqualTo(now);
|
||||
assertThat(outcome.throwable()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void errorTaskShouldBeFailure() {
|
||||
TaskExecutionOutcome outcome = TaskExecutionOutcome.create();
|
||||
Instant now = Instant.now();
|
||||
outcome = outcome.start(now);
|
||||
outcome = outcome.failure(new IllegalArgumentException(("test error")));
|
||||
assertThat(outcome.status()).isEqualTo(TaskExecutionOutcome.Status.ERROR);
|
||||
assertThat(outcome.executionTime()).isEqualTo(now);
|
||||
assertThat(outcome.throwable()).isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void newTaskExecutionShouldNotFail() {
|
||||
TaskExecutionOutcome outcome = TaskExecutionOutcome.create();
|
||||
Instant now = Instant.now();
|
||||
outcome = outcome.start(now);
|
||||
outcome = outcome.failure(new IllegalArgumentException(("test error")));
|
||||
|
||||
outcome = outcome.start(now.plusSeconds(2));
|
||||
assertThat(outcome.status()).isEqualTo(TaskExecutionOutcome.Status.STARTED);
|
||||
assertThat(outcome.executionTime()).isAfter(now);
|
||||
assertThat(outcome.throwable()).isNull();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.scheduling.config;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link Task}.
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class TaskTests {
|
||||
|
||||
@Test
|
||||
void shouldRejectNullRunnable() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new Task(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void initialStateShouldBeUnknown() {
|
||||
TestRunnable testRunnable = new TestRunnable();
|
||||
Task task = new Task(testRunnable);
|
||||
assertThat(testRunnable.hasRun).isFalse();
|
||||
TaskExecutionOutcome executionOutcome = task.getLastExecutionOutcome();
|
||||
assertThat(executionOutcome.executionTime()).isNull();
|
||||
assertThat(executionOutcome.status()).isEqualTo(TaskExecutionOutcome.Status.NONE);
|
||||
assertThat(executionOutcome.throwable()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void stateShouldUpdateAfterRun() {
|
||||
TestRunnable testRunnable = new TestRunnable();
|
||||
Task task = new Task(testRunnable);
|
||||
task.getRunnable().run();
|
||||
|
||||
assertThat(testRunnable.hasRun).isTrue();
|
||||
TaskExecutionOutcome executionOutcome = task.getLastExecutionOutcome();
|
||||
assertThat(executionOutcome.executionTime()).isInThePast();
|
||||
assertThat(executionOutcome.status()).isEqualTo(TaskExecutionOutcome.Status.SUCCESS);
|
||||
assertThat(executionOutcome.throwable()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void stateShouldUpdateAfterFailingRun() {
|
||||
FailingTestRunnable testRunnable = new FailingTestRunnable();
|
||||
Task task = new Task(testRunnable);
|
||||
assertThatIllegalStateException().isThrownBy(() -> task.getRunnable().run());
|
||||
|
||||
assertThat(testRunnable.hasRun).isTrue();
|
||||
TaskExecutionOutcome executionOutcome = task.getLastExecutionOutcome();
|
||||
assertThat(executionOutcome.executionTime()).isInThePast();
|
||||
assertThat(executionOutcome.status()).isEqualTo(TaskExecutionOutcome.Status.ERROR);
|
||||
assertThat(executionOutcome.throwable()).isInstanceOf(IllegalStateException.class);
|
||||
}
|
||||
|
||||
|
||||
static class TestRunnable implements Runnable {
|
||||
|
||||
boolean hasRun;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
this.hasRun = true;
|
||||
}
|
||||
}
|
||||
|
||||
static class FailingTestRunnable implements Runnable {
|
||||
|
||||
boolean hasRun;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
this.hasRun = true;
|
||||
throw new IllegalStateException("test exception");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user