CE-936 - Test coverage on quartz code

This commit is contained in:
2024-03-12 15:47:15 -05:00
parent 7181643abb
commit b093ff5ece
13 changed files with 614 additions and 42 deletions

View File

@ -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++;
}
}
}

View File

@ -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();
}
}

View File

@ -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));
}
}

View File

@ -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).");