mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
CE-936 - Test coverage on quartz code
This commit is contained in:
@ -309,6 +309,7 @@ public class QScheduleManager
|
||||
public void unInit()
|
||||
{
|
||||
qScheduleManager = null;
|
||||
schedulers.values().forEach(s -> s.unInit());
|
||||
schedulers.clear();
|
||||
}
|
||||
}
|
||||
|
@ -34,12 +34,26 @@ import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMeta
|
||||
*******************************************************************************/
|
||||
public interface QSchedulerInterface
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
void setupProcess(QProcessMetaData process, Map<String, Serializable> backendVariantData, boolean allowedToStart);
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
void setupSqsProvider(SQSQueueProviderMetaData queueProvider, boolean allowedToStart);
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
void setupAutomationProviderPerTable(QAutomationProviderMetaData automationProvider, boolean allowedToStart);
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
void start();
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -51,19 +65,12 @@ public interface QSchedulerInterface
|
||||
void stop();
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
** Handle a whole shutdown of the scheduler system (e.g., between unit tests).
|
||||
*******************************************************************************/
|
||||
void setupAutomationProviderPerTable(QAutomationProviderMetaData automationProvider, boolean allowedToStart);
|
||||
default void unInit()
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
void setupProcess(QProcessMetaData process, Map<String, Serializable> backendVariantData, boolean allowedToStart);
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
void start();
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** let the scheduler know when the schedule manager is at the start of setting up schedules.
|
||||
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.scheduler.quartz;
|
||||
|
||||
|
||||
import org.quartz.JobDetail;
|
||||
import org.quartz.Trigger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public record QuartzJobAndTriggerWrapper(JobDetail jobDetail, Trigger trigger, Trigger.TriggerState triggerState)
|
||||
{
|
||||
}
|
@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.scheduler.quartz;
|
||||
import java.io.Serializable;
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@ -250,7 +251,7 @@ public class QuartzScheduler implements QSchedulerInterface
|
||||
}
|
||||
else
|
||||
{
|
||||
long intervalMillis = Objects.requireNonNullElse(scheduleMetaData.getRepeatMillis(), scheduleMetaData.getRepeatSeconds() * 1000);
|
||||
long intervalMillis = Objects.requireNonNullElseGet(scheduleMetaData.getRepeatMillis(), () -> scheduleMetaData.getRepeatSeconds() * 1000);
|
||||
scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
|
||||
.withIntervalInMilliseconds(intervalMillis)
|
||||
.repeatForever();
|
||||
@ -376,6 +377,8 @@ public class QuartzScheduler implements QSchedulerInterface
|
||||
this.scheduler.scheduleJob(jobDetail, trigger);
|
||||
LOG.info("Scheduled new job: " + jobKey);
|
||||
}
|
||||
|
||||
// todo - think about... clear memoization - but - when this is used in bulk, that's when we want the memo!
|
||||
}
|
||||
|
||||
|
||||
@ -499,4 +502,46 @@ public class QuartzScheduler implements QSchedulerInterface
|
||||
{
|
||||
this.scheduler.resumeJob(new JobKey(jobName, groupName));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
List<QuartzJobAndTriggerWrapper> queryQuartz() throws SchedulerException
|
||||
{
|
||||
List<QuartzJobAndTriggerWrapper> rs = new ArrayList<>();
|
||||
List<String> jobGroupNames = scheduler.getJobGroupNames();
|
||||
|
||||
for(String group : jobGroupNames)
|
||||
{
|
||||
Set<JobKey> jobKeys = scheduler.getJobKeys(GroupMatcher.groupEquals(group));
|
||||
for(JobKey jobKey : jobKeys)
|
||||
{
|
||||
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
|
||||
List<? extends Trigger> triggersOfJob = scheduler.getTriggersOfJob(jobKey);
|
||||
for(Trigger trigger : triggersOfJob)
|
||||
{
|
||||
Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
|
||||
rs.add(new QuartzJobAndTriggerWrapper(jobDetail, trigger, triggerState));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (rs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void unInit()
|
||||
{
|
||||
///////////////////////////////////////////////////
|
||||
// resetting the singleton should be sufficient! //
|
||||
///////////////////////////////////////////////////
|
||||
quartzScheduler = null;
|
||||
}
|
||||
}
|
||||
|
@ -51,12 +51,15 @@ public class QuartzSqsPollerJob implements Job
|
||||
@Override
|
||||
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException
|
||||
{
|
||||
String queueProviderName = null;
|
||||
String queueName = null;
|
||||
|
||||
try
|
||||
{
|
||||
JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
|
||||
String queueProviderName = jobDataMap.getString("queueProviderName");
|
||||
String queueName = jobDataMap.getString("queueName");
|
||||
QInstance qInstance = QuartzScheduler.getInstance().getQInstance();
|
||||
JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
|
||||
queueProviderName = jobDataMap.getString("queueProviderName");
|
||||
queueName = jobDataMap.getString("queueName");
|
||||
QInstance qInstance = QuartzScheduler.getInstance().getQInstance();
|
||||
|
||||
SQSQueuePoller sqsQueuePoller = new SQSQueuePoller();
|
||||
sqsQueuePoller.setQueueProviderMetaData((SQSQueueProviderMetaData) qInstance.getQueueProvider(queueProviderName));
|
||||
@ -67,9 +70,13 @@ public class QuartzSqsPollerJob implements Job
|
||||
/////////////
|
||||
// run it. //
|
||||
/////////////
|
||||
LOG.debug("Running quartz SQS Poller", logPair("queueName", queueName));
|
||||
LOG.debug("Running quartz SQS Poller", logPair("queueName", queueName), logPair("queueProviderName", queueProviderName));
|
||||
sqsQueuePoller.run();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error running SQS Poller", e, logPair("queueName", queueName), logPair("queueProviderName", queueProviderName));
|
||||
}
|
||||
finally
|
||||
{
|
||||
QContext.clear();
|
||||
|
@ -51,13 +51,17 @@ public class QuartzTableAutomationsJob implements Job
|
||||
@Override
|
||||
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException
|
||||
{
|
||||
String tableName = null;
|
||||
String automationProviderName = null;
|
||||
AutomationStatus automationStatus = null;
|
||||
|
||||
try
|
||||
{
|
||||
JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
|
||||
String tableName = jobDataMap.getString("tableName");
|
||||
String automationProviderName = jobDataMap.getString("automationProviderName");
|
||||
AutomationStatus automationStatus = AutomationStatus.valueOf(jobDataMap.getString("automationStatus"));
|
||||
QInstance qInstance = QuartzScheduler.getInstance().getQInstance();
|
||||
JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
|
||||
tableName = jobDataMap.getString("tableName");
|
||||
automationProviderName = jobDataMap.getString("automationProviderName");
|
||||
automationStatus = AutomationStatus.valueOf(jobDataMap.getString("automationStatus"));
|
||||
QInstance qInstance = QuartzScheduler.getInstance().getQInstance();
|
||||
|
||||
PollingAutomationPerTableRunner.TableActionsInterface tableAction = new PollingAutomationPerTableRunner.TableActions(tableName, automationStatus);
|
||||
PollingAutomationPerTableRunner runner = new PollingAutomationPerTableRunner(qInstance, automationProviderName, QuartzScheduler.getInstance().getSessionSupplier(), tableAction);
|
||||
@ -68,6 +72,10 @@ public class QuartzTableAutomationsJob implements Job
|
||||
LOG.debug("Running Table Automations", logPair("tableName", tableName), logPair("automationStatus", automationStatus));
|
||||
runner.run();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error running Table Automations", e, logPair("tableName", tableName), logPair("automationStatus", automationStatus));
|
||||
}
|
||||
finally
|
||||
{
|
||||
QContext.clear();
|
||||
|
@ -77,7 +77,7 @@ public class PauseQuartzJobsProcess extends AbstractLoadStep implements MetaData
|
||||
QuartzScheduler instance = QuartzScheduler.getInstance();
|
||||
for(QRecord record : runBackendStepInput.getRecords())
|
||||
{
|
||||
instance.pauseJob(record.getValueString("JOB_NAME"), record.getValueString("GROUP_NAME"));
|
||||
instance.pauseJob(record.getValueString("jobName"), record.getValueString("groupName"));
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
|
@ -77,7 +77,7 @@ public class ResumeQuartzJobsProcess extends AbstractLoadStep implements MetaDat
|
||||
QuartzScheduler instance = QuartzScheduler.getInstance();
|
||||
for(QRecord record : runBackendStepInput.getRecords())
|
||||
{
|
||||
instance.resumeJob(record.getValueString("JOB_NAME"), record.getValueString("GROUP_NAME"));
|
||||
instance.resumeJob(record.getValueString("jobName"), record.getValueString("groupName"));
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
|
@ -305,7 +305,6 @@ public class SimpleScheduler implements QSchedulerInterface
|
||||
*******************************************************************************/
|
||||
static void resetSingleton()
|
||||
{
|
||||
simpleScheduler = null;
|
||||
}
|
||||
|
||||
|
||||
@ -339,4 +338,17 @@ public class SimpleScheduler implements QSchedulerInterface
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void unInit()
|
||||
{
|
||||
//////////////////////////////////////////////////
|
||||
// resetting the singleton should be sufficient //
|
||||
//////////////////////////////////////////////////
|
||||
simpleScheduler = null;
|
||||
}
|
||||
}
|
||||
|
@ -22,12 +22,29 @@
|
||||
package com.kingsrook.qqq.backend.core.scheduler.quartz;
|
||||
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QCollectingLogger;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.QScheduleManager;
|
||||
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.quartz.SchedulerException;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -35,18 +52,14 @@ import org.quartz.SchedulerException;
|
||||
*******************************************************************************/
|
||||
class QuartzSchedulerTest extends BaseTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@BeforeEach
|
||||
void beforeEach() throws SchedulerException
|
||||
@AfterEach
|
||||
void afterEach()
|
||||
{
|
||||
Properties quartzProperties = new Properties();
|
||||
quartzProperties.put("", "");
|
||||
quartzProperties.put("org.quartz.scheduler.instanceName", "TestScheduler");
|
||||
quartzProperties.put("org.quartz.threadPool.threadCount", "3");
|
||||
quartzProperties.put("org.quartz.jobStore.class", "org.quartz.simpl.RAMJobStore");
|
||||
QuartzScheduler.initInstance(QContext.getQInstance(), "TestScheduler", quartzProperties, QContext::getQSession);
|
||||
QScheduleManager.getInstance().unInit();
|
||||
}
|
||||
|
||||
|
||||
@ -55,9 +68,84 @@ class QuartzSchedulerTest extends BaseTest
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void test()
|
||||
void test() throws Exception
|
||||
{
|
||||
try
|
||||
{
|
||||
QInstance qInstance = QContext.getQInstance();
|
||||
QuartzTestUtils.setupInstanceForQuartzTests();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// set these runners to use collecting logger, so we can assert that they did run, and didn't throw //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QCollectingLogger quartzSqsPollerJobLog = QLogger.activateCollectingLoggerForClass(QuartzSqsPollerJob.class);
|
||||
QCollectingLogger quartzTableAutomationsJobLog = QLogger.activateCollectingLoggerForClass(QuartzTableAutomationsJob.class);
|
||||
|
||||
//////////////////////////////////////////
|
||||
// add a process we can run and observe //
|
||||
//////////////////////////////////////////
|
||||
qInstance.addProcess(new QProcessMetaData()
|
||||
.withName("testScheduledProcess")
|
||||
.withSchedule(new QScheduleMetaData()
|
||||
.withSchedulerName(QuartzTestUtils.QUARTZ_SCHEDULER_NAME)
|
||||
.withRepeatMillis(2)
|
||||
.withInitialDelaySeconds(0))
|
||||
.withStepList(List.of(new QBackendStepMetaData()
|
||||
.withName("step")
|
||||
.withCode(new QCodeReference(BasicStep.class)))));
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// start the schedule manager, which will schedule things, and start quartz //
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
QSession qSession = QContext.getQSession();
|
||||
QScheduleManager qScheduleManager = QScheduleManager.initInstance(qInstance, () -> qSession);
|
||||
qScheduleManager.start();
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// give a moment for the job to run a few times //
|
||||
//////////////////////////////////////////////////
|
||||
SleepUtils.sleep(50, TimeUnit.MILLISECONDS);
|
||||
qScheduleManager.stopAsync();
|
||||
|
||||
System.out.println("Ran: " + BasicStep.counter + " times");
|
||||
assertTrue(BasicStep.counter > 1, "Scheduled process should have ran at least twice (but only ran [" + BasicStep.counter + "] time(s).");
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// make sure poller ran, and didn't issue any warns //
|
||||
//////////////////////////////////////////////////////
|
||||
assertThat(quartzSqsPollerJobLog.getCollectedMessages())
|
||||
.anyMatch(m -> m.getLevel().equals(Level.DEBUG) && m.getMessage().contains("Running quartz SQS Poller"))
|
||||
.noneMatch(m -> m.getLevel().equals(Level.WARN));
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// make sure poller ran, and didn't issue any warns //
|
||||
//////////////////////////////////////////////////////
|
||||
assertThat(quartzTableAutomationsJobLog.getCollectedMessages())
|
||||
.anyMatch(m -> m.getLevel().equals(Level.DEBUG) && m.getMessage().contains("Running Table Automations"))
|
||||
.noneMatch(m -> m.getLevel().equals(Level.WARN));
|
||||
}
|
||||
finally
|
||||
{
|
||||
QLogger.deactivateCollectingLoggerForClass(QuartzSqsPollerJob.class);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class BasicStep implements BackendStep
|
||||
{
|
||||
static int counter = 0;
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
|
||||
{
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.scheduler.quartz;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.quartz.QuartzSchedulerMetaData;
|
||||
import org.quartz.SchedulerException;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QuartzTestUtils
|
||||
{
|
||||
public final static String QUARTZ_SCHEDULER_NAME = "TestQuartzScheduler";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static Properties getQuartzProperties()
|
||||
{
|
||||
Properties quartzProperties = new Properties();
|
||||
quartzProperties.put("org.quartz.scheduler.instanceName", QUARTZ_SCHEDULER_NAME);
|
||||
quartzProperties.put("org.quartz.threadPool.threadCount", "3");
|
||||
quartzProperties.put("org.quartz.jobStore.class", "org.quartz.simpl.RAMJobStore");
|
||||
return (quartzProperties);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void setupInstanceForQuartzTests()
|
||||
{
|
||||
QInstance qInstance = QContext.getQInstance();
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
// remove the simple scheduler from the instance //
|
||||
///////////////////////////////////////////////////
|
||||
qInstance.getSchedulers().clear();
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// add the quartz scheduler meta-data to the instance //
|
||||
////////////////////////////////////////////////////////
|
||||
qInstance.addScheduler(new QuartzSchedulerMetaData()
|
||||
.withProperties(getQuartzProperties())
|
||||
.withName(QUARTZ_SCHEDULER_NAME));
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// set the queue providers & automation providers to use the quartz scheduler //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
qInstance.getAutomationProviders().values()
|
||||
.forEach(ap -> ap.getSchedule().setSchedulerName(QUARTZ_SCHEDULER_NAME));
|
||||
|
||||
qInstance.getQueueProviders().values()
|
||||
.forEach(qp -> ((SQSQueueProviderMetaData) qp).getSchedule().setSchedulerName(QUARTZ_SCHEDULER_NAME));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static List<QuartzJobAndTriggerWrapper> queryQuartz() throws SchedulerException
|
||||
{
|
||||
return QuartzScheduler.getInstance().queryQuartz();
|
||||
}
|
||||
}
|
@ -0,0 +1,277 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.scheduler.quartz.processes;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallbackFactory;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerHelper;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.QScheduleManager;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzJobAndTriggerWrapper;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzScheduler;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzTestUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.quartz.SchedulerException;
|
||||
import org.quartz.Trigger;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit tests for the various quartz management processes
|
||||
*******************************************************************************/
|
||||
class QuartzJobsProcessTest extends BaseTest
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@BeforeEach
|
||||
void beforeEach() throws QException
|
||||
{
|
||||
QInstance qInstance = QContext.getQInstance();
|
||||
qInstance.addTable(new QTableMetaData()
|
||||
.withName("quartzTriggers")
|
||||
.withBackendName(TestUtils.MEMORY_BACKEND_NAME)
|
||||
.withPrimaryKeyField("id")
|
||||
.withField(new QFieldMetaData("id", QFieldType.LONG)));
|
||||
MetaDataProducerHelper.processAllMetaDataProducersInPackage(qInstance, QuartzScheduler.class.getPackageName());
|
||||
|
||||
QuartzTestUtils.setupInstanceForQuartzTests();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// start the schedule manager, which will schedule things, and start quartz //
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
QSession qSession = QContext.getQSession();
|
||||
QScheduleManager qScheduleManager = QScheduleManager.initInstance(qInstance, () -> qSession);
|
||||
qScheduleManager.start();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@AfterEach
|
||||
void afterEach()
|
||||
{
|
||||
QScheduleManager.getInstance().stop();
|
||||
QScheduleManager.getInstance().unInit();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testPauseAllQuartzJobs() throws QException, SchedulerException
|
||||
{
|
||||
////////////////////////////////////////
|
||||
// make sure nothing starts as paused //
|
||||
////////////////////////////////////////
|
||||
assertNoneArePaused();
|
||||
|
||||
///////////////////////////////
|
||||
// run the pause-all process //
|
||||
///////////////////////////////
|
||||
RunProcessInput input = new RunProcessInput();
|
||||
input.setProcessName(PauseAllQuartzJobsProcess.class.getSimpleName());
|
||||
new RunProcessAction().execute(input);
|
||||
|
||||
//////////////////////////////////////
|
||||
// assert everything becomes paused //
|
||||
//////////////////////////////////////
|
||||
assertAllArePaused();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testResumeAllQuartzJobs() throws QException, SchedulerException
|
||||
{
|
||||
///////////////////////////////
|
||||
// run the pause-all process //
|
||||
///////////////////////////////
|
||||
RunProcessInput input = new RunProcessInput();
|
||||
input.setProcessName(PauseAllQuartzJobsProcess.class.getSimpleName());
|
||||
new RunProcessAction().execute(input);
|
||||
|
||||
//////////////////////////////////////
|
||||
// assert everything becomes paused //
|
||||
//////////////////////////////////////
|
||||
assertAllArePaused();
|
||||
|
||||
////////////////////
|
||||
// run resume all //
|
||||
////////////////////
|
||||
input = new RunProcessInput();
|
||||
input.setProcessName(ResumeAllQuartzJobsProcess.class.getSimpleName());
|
||||
new RunProcessAction().execute(input);
|
||||
|
||||
////////////////////////////////////////
|
||||
// make sure nothing ends up as paused //
|
||||
////////////////////////////////////////
|
||||
assertNoneArePaused();
|
||||
|
||||
////////////////////
|
||||
// pause just one //
|
||||
////////////////////
|
||||
List<QuartzJobAndTriggerWrapper> quartzJobAndTriggerWrappers = QuartzTestUtils.queryQuartz();
|
||||
new InsertAction().execute(new InsertInput("quartzTriggers").withRecord(new QRecord()
|
||||
.withValue("jobName", quartzJobAndTriggerWrappers.get(0).jobDetail().getKey().getName())
|
||||
.withValue("groupName", quartzJobAndTriggerWrappers.get(0).jobDetail().getKey().getGroup())
|
||||
));
|
||||
|
||||
input = new RunProcessInput();
|
||||
input.setProcessName(PauseQuartzJobsProcess.class.getSimpleName());
|
||||
input.setCallback(QProcessCallbackFactory.forFilter(new QQueryFilter()));
|
||||
input.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.SKIP);
|
||||
new RunProcessAction().execute(input);
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// make sure at least 1 is paused, some are not paused //
|
||||
/////////////////////////////////////////////////////////
|
||||
assertAnyAre(Trigger.TriggerState.PAUSED);
|
||||
assertAnyAreNot(Trigger.TriggerState.PAUSED);
|
||||
|
||||
//////////////////////////
|
||||
// run resume all again //
|
||||
//////////////////////////
|
||||
input = new RunProcessInput();
|
||||
input.setProcessName(ResumeAllQuartzJobsProcess.class.getSimpleName());
|
||||
new RunProcessAction().execute(input);
|
||||
|
||||
////////////////////////////////////////
|
||||
// make sure nothing ends up as paused //
|
||||
////////////////////////////////////////
|
||||
assertNoneArePaused();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testPauseOneResumeOne() throws QException, SchedulerException
|
||||
{
|
||||
/////////////////////////////////////
|
||||
// make sure nothing starts paused //
|
||||
/////////////////////////////////////
|
||||
assertNoneArePaused();
|
||||
|
||||
////////////////////
|
||||
// pause just one //
|
||||
////////////////////
|
||||
List<QuartzJobAndTriggerWrapper> quartzJobAndTriggerWrappers = QuartzTestUtils.queryQuartz();
|
||||
new InsertAction().execute(new InsertInput("quartzTriggers").withRecord(new QRecord()
|
||||
.withValue("jobName", quartzJobAndTriggerWrappers.get(0).jobDetail().getKey().getName())
|
||||
.withValue("groupName", quartzJobAndTriggerWrappers.get(0).jobDetail().getKey().getGroup())
|
||||
));
|
||||
|
||||
RunProcessInput input = new RunProcessInput();
|
||||
input.setProcessName(PauseQuartzJobsProcess.class.getSimpleName());
|
||||
input.setCallback(QProcessCallbackFactory.forFilter(new QQueryFilter()));
|
||||
input.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.SKIP);
|
||||
new RunProcessAction().execute(input);
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// make sure at least 1 is paused, some are not paused //
|
||||
/////////////////////////////////////////////////////////
|
||||
assertAnyAre(Trigger.TriggerState.PAUSED);
|
||||
assertAnyAreNot(Trigger.TriggerState.PAUSED);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// now resume the same one (will still be only row in our in-memory table) //
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
input = new RunProcessInput();
|
||||
input.setProcessName(ResumeQuartzJobsProcess.class.getSimpleName());
|
||||
input.setCallback(QProcessCallbackFactory.forFilter(new QQueryFilter()));
|
||||
input.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.SKIP);
|
||||
new RunProcessAction().execute(input);
|
||||
|
||||
//////////////////////////////////////
|
||||
// make sure nothing ends up paused //
|
||||
//////////////////////////////////////
|
||||
assertNoneArePaused();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void assertAnyAre(Trigger.TriggerState triggerState) throws SchedulerException
|
||||
{
|
||||
assertThat(QuartzTestUtils.queryQuartz()).anyMatch(qjtw -> qjtw.triggerState().equals(triggerState));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void assertAnyAreNot(Trigger.TriggerState triggerState) throws SchedulerException
|
||||
{
|
||||
assertThat(QuartzTestUtils.queryQuartz()).anyMatch(qjtw -> !qjtw.triggerState().equals(triggerState));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void assertNoneArePaused() throws SchedulerException
|
||||
{
|
||||
assertThat(QuartzTestUtils.queryQuartz()).noneMatch(qjtw -> qjtw.triggerState().equals(Trigger.TriggerState.PAUSED));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void assertAllArePaused() throws SchedulerException
|
||||
{
|
||||
assertThat(QuartzTestUtils.queryQuartz()).allMatch(qjtw -> qjtw.triggerState().equals(Trigger.TriggerState.PAUSED));
|
||||
}
|
||||
|
||||
}
|
@ -58,7 +58,7 @@ class SimpleSchedulerTest extends BaseTest
|
||||
@AfterEach
|
||||
void afterEach()
|
||||
{
|
||||
SimpleScheduler.resetSingleton();
|
||||
QScheduleManager.getInstance().unInit();
|
||||
}
|
||||
|
||||
|
||||
@ -81,7 +81,6 @@ class SimpleSchedulerTest extends BaseTest
|
||||
assertThat(simpleScheduler.getExecutors()).isNotEmpty();
|
||||
|
||||
qScheduleManager.stop();
|
||||
qScheduleManager.unInit();
|
||||
}
|
||||
|
||||
|
||||
@ -106,8 +105,7 @@ class SimpleSchedulerTest extends BaseTest
|
||||
.withInitialDelaySeconds(0))
|
||||
.withStepList(List.of(new QBackendStepMetaData()
|
||||
.withName("step")
|
||||
.withCode(new QCodeReference(BasicStep.class))))
|
||||
);
|
||||
.withCode(new QCodeReference(BasicStep.class)))));
|
||||
|
||||
BasicStep.counter = 0;
|
||||
|
||||
@ -120,7 +118,6 @@ class SimpleSchedulerTest extends BaseTest
|
||||
//////////////////////////////////////////////////
|
||||
SleepUtils.sleep(50, TimeUnit.MILLISECONDS);
|
||||
qScheduleManager.stopAsync();
|
||||
qScheduleManager.unInit();
|
||||
|
||||
System.out.println("Ran: " + BasicStep.counter + " times");
|
||||
assertTrue(BasicStep.counter > 1, "Scheduled process should have ran at least twice (but only ran [" + BasicStep.counter + "] time(s).");
|
||||
|
Reference in New Issue
Block a user