mirror of
https://github.com/Eyre-S/Coeur-Morny-Cono.git
synced 2024-11-22 19:24:53 +08:00
add CronTask, tests optimize
- add lib cron-utils: v9.2.0 - add CronTask - add CronTask's test - change MedicationTimer using cron as time calculation backend (not using CronTask) - change OnQuestionMarkReply support `⸘` - minor SchedulerTest "immediately" test logic changes
This commit is contained in:
parent
89c414e853
commit
3d44972233
@ -90,6 +90,7 @@ dependencies {
|
|||||||
implementation group: 'com.softwaremill.sttp.client3', name: scala('okhttp-backend'), version: lib_sttp_v
|
implementation group: 'com.softwaremill.sttp.client3', name: scala('okhttp-backend'), version: lib_sttp_v
|
||||||
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: lib_okhttp_v
|
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: lib_okhttp_v
|
||||||
implementation group: 'com.google.code.gson', name: 'gson', version: lib_gson_v
|
implementation group: 'com.google.code.gson', name: 'gson', version: lib_gson_v
|
||||||
|
implementation group: 'com.cronutils', name: 'cron-utils', version: lib_cron_utils_v
|
||||||
|
|
||||||
testImplementation group: 'org.scalatest', name: scala('scalatest'), version: lib_scalatest_v
|
testImplementation group: 'org.scalatest', name: scala('scalatest'), version: lib_scalatest_v
|
||||||
testImplementation group: 'org.scalatest', name: scala('scalatest-freespec'), version: lib_scalatest_v
|
testImplementation group: 'org.scalatest', name: scala('scalatest-freespec'), version: lib_scalatest_v
|
||||||
|
@ -5,7 +5,7 @@ MORNY_ARCHIVE_NAME = morny-coeur
|
|||||||
MORNY_CODE_STORE = https://github.com/Eyre-S/Coeur-Morny-Cono
|
MORNY_CODE_STORE = https://github.com/Eyre-S/Coeur-Morny-Cono
|
||||||
MORNY_COMMIT_PATH = https://github.com/Eyre-S/Coeur-Morny-Cono/commit/%s
|
MORNY_COMMIT_PATH = https://github.com/Eyre-S/Coeur-Morny-Cono/commit/%s
|
||||||
|
|
||||||
VERSION = 1.3.0-dev1
|
VERSION = 1.3.0-dev2
|
||||||
|
|
||||||
USE_DELTA = false
|
USE_DELTA = false
|
||||||
VERSION_DELTA =
|
VERSION_DELTA =
|
||||||
@ -25,5 +25,6 @@ lib_javatelegramapi_v = 6.2.0
|
|||||||
lib_sttp_v = 3.9.0
|
lib_sttp_v = 3.9.0
|
||||||
lib_okhttp_v = 4.11.0
|
lib_okhttp_v = 4.11.0
|
||||||
lib_gson_v = 2.10.1
|
lib_gson_v = 2.10.1
|
||||||
|
lib_cron_utils_v = 9.2.0
|
||||||
|
|
||||||
lib_scalatest_v = 3.2.17
|
lib_scalatest_v = 3.2.17
|
||||||
|
@ -4,7 +4,6 @@ import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener}
|
|||||||
import cc.sukazyo.cono.morny.MornyCoeur
|
import cc.sukazyo.cono.morny.MornyCoeur
|
||||||
import cc.sukazyo.cono.morny.bot.event.OnQuestionMarkReply.isAllMessageMark
|
import cc.sukazyo.cono.morny.bot.event.OnQuestionMarkReply.isAllMessageMark
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
|
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
|
||||||
import com.pengrad.telegrambot.model.Update
|
|
||||||
import com.pengrad.telegrambot.request.SendMessage
|
import com.pengrad.telegrambot.request.SendMessage
|
||||||
|
|
||||||
import scala.language.postfixOps
|
import scala.language.postfixOps
|
||||||
@ -33,7 +32,9 @@ class OnQuestionMarkReply (using coeur: MornyCoeur) extends EventListener {
|
|||||||
|
|
||||||
object OnQuestionMarkReply {
|
object OnQuestionMarkReply {
|
||||||
|
|
||||||
private val QUESTION_MARKS = Set('?', '?', '¿', '⁈', '⁇', '‽', '❔', '❓')
|
// todo: due to the limitation of Java char, the ⁉️ character (actually not a
|
||||||
|
// single character) is not supported yet.
|
||||||
|
private val QUESTION_MARKS = Set('?', '?', '¿', '⁈', '⁇', '‽', '⸘', '❔', '❓')
|
||||||
|
|
||||||
def isAllMessageMark (using text: String): Boolean = {
|
def isAllMessageMark (using text: String): Boolean = {
|
||||||
boundary[Boolean] {
|
boundary[Boolean] {
|
||||||
|
@ -7,11 +7,14 @@ import cc.sukazyo.cono.morny.util.schedule.RoutineTask
|
|||||||
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
|
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
|
||||||
import cc.sukazyo.cono.morny.util.CommonFormat
|
import cc.sukazyo.cono.morny.util.CommonFormat
|
||||||
import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis
|
import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis
|
||||||
|
import com.cronutils.builder.CronBuilder
|
||||||
|
import com.cronutils.model.definition.{CronDefinition, CronDefinitionBuilder}
|
||||||
|
import com.cronutils.model.time.ExecutionTime
|
||||||
import com.pengrad.telegrambot.model.{Message, MessageEntity}
|
import com.pengrad.telegrambot.model.{Message, MessageEntity}
|
||||||
import com.pengrad.telegrambot.request.{EditMessageText, SendMessage}
|
import com.pengrad.telegrambot.request.{EditMessageText, SendMessage}
|
||||||
import com.pengrad.telegrambot.response.SendResponse
|
import com.pengrad.telegrambot.response.SendResponse
|
||||||
|
|
||||||
import java.time.{LocalDateTime, ZoneOffset}
|
import java.time.{Instant, ZonedDateTime, ZoneOffset}
|
||||||
import scala.collection.mutable.ArrayBuffer
|
import scala.collection.mutable.ArrayBuffer
|
||||||
import scala.language.implicitConversions
|
import scala.language.implicitConversions
|
||||||
|
|
||||||
@ -85,18 +88,24 @@ class MedicationTimer (using coeur: MornyCoeur) {
|
|||||||
|
|
||||||
object MedicationTimer {
|
object MedicationTimer {
|
||||||
|
|
||||||
|
//noinspection ScalaWeakerAccess
|
||||||
|
val cronDef: CronDefinition = CronDefinitionBuilder.defineCron
|
||||||
|
.withHours.and
|
||||||
|
.instance
|
||||||
|
|
||||||
@throws[IllegalArgumentException]
|
@throws[IllegalArgumentException]
|
||||||
def calcNextRoutineTimestamp (baseTimeMillis: EpochMillis, zone: ZoneOffset, notifyAt: Set[Int]): EpochMillis = {
|
def calcNextRoutineTimestamp (baseTimeMillis: EpochMillis, zone: ZoneOffset, notifyAt: Set[Int]): EpochMillis = {
|
||||||
if (notifyAt isEmpty) throw new IllegalArgumentException("notify time is not set")
|
if (notifyAt isEmpty) throw new IllegalArgumentException("notify time is not set")
|
||||||
var time = LocalDateTime.ofEpochSecond(
|
import com.cronutils.model.field.expression.FieldExpressionFactory.*
|
||||||
baseTimeMillis / 1000, ((baseTimeMillis % 1000) * 1000 * 1000) toInt,
|
ExecutionTime.forCron(CronBuilder.cron(cronDef)
|
||||||
zone
|
.withHour(and({
|
||||||
).withMinute(0).withSecond(0).withNano(0)
|
import scala.jdk.CollectionConverters.*
|
||||||
time = time plusHours 1
|
(for (i <- notifyAt) yield on(i)).toList.asJava
|
||||||
while (!(notifyAt contains(time getHour))) {
|
}))
|
||||||
time = time plusHours 1
|
.instance
|
||||||
}
|
).nextExecution(
|
||||||
(time toInstant zone) toEpochMilli
|
ZonedDateTime ofInstant (Instant ofEpochMilli baseTimeMillis, zone.normalized)
|
||||||
|
).get.toInstant.toEpochMilli
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
package cc.sukazyo.cono.morny.util.schedule
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis
|
||||||
|
import com.cronutils.model.time.ExecutionTime
|
||||||
|
import com.cronutils.model.Cron
|
||||||
|
|
||||||
|
import java.time.{Instant, ZonedDateTime, ZoneId}
|
||||||
|
import scala.jdk.OptionConverters.*
|
||||||
|
|
||||||
|
trait CronTask extends RoutineTask {
|
||||||
|
|
||||||
|
private transparent inline def cronCalc = ExecutionTime.forCron(cron)
|
||||||
|
|
||||||
|
def cron: Cron
|
||||||
|
|
||||||
|
def zone: ZoneId
|
||||||
|
|
||||||
|
override def firstRoutineTimeMillis: EpochMillis =
|
||||||
|
cronCalc.nextExecution(
|
||||||
|
ZonedDateTime.ofInstant(
|
||||||
|
Instant.now, zone
|
||||||
|
)
|
||||||
|
).get.toInstant.toEpochMilli
|
||||||
|
|
||||||
|
override def nextRoutineTimeMillis (previousRoutineScheduledTimeMillis: EpochMillis): EpochMillis | Null =
|
||||||
|
cronCalc.nextExecution(
|
||||||
|
ZonedDateTime.ofInstant(
|
||||||
|
Instant.ofEpochMilli(previousRoutineScheduledTimeMillis),
|
||||||
|
zone
|
||||||
|
)
|
||||||
|
).toScala match
|
||||||
|
case Some(time) => time.toInstant.toEpochMilli
|
||||||
|
case None => null
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object CronTask {
|
||||||
|
|
||||||
|
def apply (_name: String, _cron: Cron, _zone: ZoneId, _main: =>Unit): CronTask =
|
||||||
|
new CronTask:
|
||||||
|
override def name: String = _name
|
||||||
|
override def cron: Cron = _cron
|
||||||
|
override def zone: ZoneId = _zone
|
||||||
|
override def main: Unit = _main
|
||||||
|
|
||||||
|
}
|
@ -16,10 +16,19 @@ class OnQuestionMarkReplyTest extends MornyTests with TableDrivenPropertyChecks
|
|||||||
("为什么?", false),
|
("为什么?", false),
|
||||||
("?这不合理", false),
|
("?这不合理", false),
|
||||||
("??尊嘟假嘟", false),
|
("??尊嘟假嘟", false),
|
||||||
|
(":¿", false),
|
||||||
("?????", true),
|
("?????", true),
|
||||||
|
("¿", true),
|
||||||
|
("⁈??", true),
|
||||||
|
("?!??", false),
|
||||||
|
("⁇", true),
|
||||||
|
("‽", true),
|
||||||
|
("?⸘?", true),
|
||||||
("?", true),
|
("?", true),
|
||||||
("?", true),
|
("??", true),
|
||||||
("??❔", true),
|
("❔", true),
|
||||||
|
("❓❓❓", true),
|
||||||
|
// ("⁉️", true)
|
||||||
)
|
)
|
||||||
forAll(examples) { (text, is) =>
|
forAll(examples) { (text, is) =>
|
||||||
|
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
package cc.sukazyo.cono.morny.test.utils.schedule
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.test.MornyTests
|
||||||
|
import cc.sukazyo.cono.morny.util.schedule.{CronTask, Scheduler}
|
||||||
|
import cc.sukazyo.cono.morny.util.CommonFormat.formatDate
|
||||||
|
import com.cronutils.builder.CronBuilder
|
||||||
|
import com.cronutils.model.definition.CronDefinitionBuilder
|
||||||
|
import com.cronutils.model.field.expression.FieldExpressionFactory as C
|
||||||
|
import com.cronutils.model.time.ExecutionTime
|
||||||
|
import org.scalatest.tagobjects.Slow
|
||||||
|
|
||||||
|
import java.lang.System.currentTimeMillis
|
||||||
|
import java.time.{ZonedDateTime, ZoneOffset}
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
|
|
||||||
|
class CronTaskTest extends MornyTests {
|
||||||
|
|
||||||
|
"cron task works fine" taggedAs Slow in {
|
||||||
|
|
||||||
|
val scheduler = Scheduler()
|
||||||
|
val cronSecondly =
|
||||||
|
CronBuilder.cron(
|
||||||
|
CronDefinitionBuilder.defineCron
|
||||||
|
.withSeconds.and
|
||||||
|
.instance
|
||||||
|
).withSecond(C.every(1)).instance
|
||||||
|
Thread.sleep(
|
||||||
|
ExecutionTime.forCron(cronSecondly)
|
||||||
|
.timeToNextExecution(ZonedDateTime.now)
|
||||||
|
.get.get(ChronoUnit.NANOS)/1000
|
||||||
|
) // aligned current time to millisecond 000
|
||||||
|
note(s"CronTask test time aligned to ${formatDate(currentTimeMillis, 0)}")
|
||||||
|
|
||||||
|
var times = 0
|
||||||
|
val task = CronTask("cron-task", cronSecondly, ZoneOffset.ofHours(0).normalized, {
|
||||||
|
times = times + 1
|
||||||
|
note(s"CronTask executed at ${formatDate(currentTimeMillis, 0)}")
|
||||||
|
})
|
||||||
|
scheduler ++ task
|
||||||
|
Thread.sleep(10300)
|
||||||
|
|
||||||
|
// it should be at 300ms position to 10 seconds
|
||||||
|
|
||||||
|
scheduler % task
|
||||||
|
scheduler.stop()
|
||||||
|
note(s"CronTasks done at ${formatDate(currentTimeMillis, 0)}")
|
||||||
|
times shouldEqual 10
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -17,7 +17,7 @@ class IntervalsTest extends MornyTests {
|
|||||||
val timeUsed = System.currentTimeMillis() - startTime
|
val timeUsed = System.currentTimeMillis() - startTime
|
||||||
times shouldEqual 10
|
times shouldEqual 10
|
||||||
timeUsed should (be <= 2100L and be >= 1900L)
|
timeUsed should (be <= 2100L and be >= 1900L)
|
||||||
info(s"interval 200ms for 10 times used time ${timeUsed}ms")
|
info(s"Interval Task with interval 200ms for 10 times used time ${timeUsed}ms")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -12,13 +12,15 @@ class SchedulerTest extends MornyTests {
|
|||||||
|
|
||||||
"Task with scheduleTime smaller than current time should be executed immediately" in {
|
"Task with scheduleTime smaller than current time should be executed immediately" in {
|
||||||
val scheduler = Scheduler()
|
val scheduler = Scheduler()
|
||||||
var time = System.currentTimeMillis
|
val time = System.currentTimeMillis
|
||||||
|
var doneTime: Option[Long] = None
|
||||||
scheduler ++ Task("task", 0, {
|
scheduler ++ Task("task", 0, {
|
||||||
time = System.currentTimeMillis - time
|
doneTime = Some(System.currentTimeMillis)
|
||||||
})
|
})
|
||||||
scheduler.waitForStopAtAllDone()
|
Thread.sleep(10)
|
||||||
time should be <= 10L
|
scheduler.stop()
|
||||||
info(s"Immediately Task done with time $time")
|
doneTime shouldBe defined
|
||||||
|
info(s"Immediately Task done in ${doneTime.get - time}ms")
|
||||||
}
|
}
|
||||||
|
|
||||||
"Task's running thread name should be task name" in {
|
"Task's running thread name should be task name" in {
|
||||||
|
Loading…
Reference in New Issue
Block a user