diff --git a/src/main/scala/cc/sukazyo/cono/morny/MornyCoeur.scala b/src/main/scala/cc/sukazyo/cono/morny/MornyCoeur.scala
index f763451..b1cc8d1 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/MornyCoeur.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/MornyCoeur.scala
@@ -8,6 +8,7 @@ import cc.sukazyo.cono.morny.bot.api.EventListenerManager
import cc.sukazyo.cono.morny.bot.event.{MornyEventListeners, MornyOnInlineQuery, MornyOnTelegramCommand, MornyOnUpdateTimestampOffsetLock}
import cc.sukazyo.cono.morny.bot.query.MornyQueries
import cc.sukazyo.cono.morny.util.schedule.Scheduler
+import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis
import com.pengrad.telegrambot.TelegramBot
import com.pengrad.telegrambot.request.GetMe
@@ -54,7 +55,7 @@ class MornyCoeur (using val config: MornyConfig) {
*
* in milliseconds.
*/
- val coeurStartTimestamp: Long = System.currentTimeMillis
+ val coeurStartTimestamp: EpochMillis = System.currentTimeMillis
/** [[TelegramBot]] account of this Morny */
val account: TelegramBot = __loginResult.account
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMe.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMe.scala
index 4f80a69..5dbbb9f 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMe.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMe.scala
@@ -73,13 +73,14 @@ class OnCallMe (using coeur: MornyCoeur) extends EventListener {
lastDinnerData.forwardFromMessageId
)
import cc.sukazyo.cono.morny.util.CommonFormat.{formatDate, formatDuration}
+ import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h
- def lastDinner_dateMillis: Long = lastDinnerData.forwardDate longValue;
+ def lastDinner_dateMillis: EpochMillis = EpochMillis fromEpochSeconds lastDinnerData.forwardDate
coeur.account exec SendMessage(
req.from.id,
"on %s [UTC+8]
\n- %s
before".formatted(
h(formatDate(lastDinner_dateMillis, 8)),
- h(formatDuration(lastDinner_dateMillis))
+ h(formatDuration(System.currentTimeMillis - lastDinner_dateMillis))
)
).parseMode(ParseMode HTML).replyToMessageId(sendResp.message.messageId)
isAllowed = true
diff --git a/src/main/scala/cc/sukazyo/cono/morny/daemon/MedicationTimer.scala b/src/main/scala/cc/sukazyo/cono/morny/daemon/MedicationTimer.scala
index 7d81ebd..522e6ab 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/daemon/MedicationTimer.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/daemon/MedicationTimer.scala
@@ -6,6 +6,7 @@ import cc.sukazyo.cono.morny.daemon.MedicationTimer.calcNextRoutineTimestamp
import cc.sukazyo.cono.morny.util.schedule.RoutineTask
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
import cc.sukazyo.cono.morny.util.CommonFormat
+import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis
import com.pengrad.telegrambot.model.{Message, MessageEntity}
import com.pengrad.telegrambot.request.{EditMessageText, SendMessage}
import com.pengrad.telegrambot.response.SendResponse
@@ -30,15 +31,15 @@ class MedicationTimer (using coeur: MornyCoeur) {
override def name: String = DAEMON_THREAD_NAME_DEF
- def calcNextSendTime: Long =
+ def calcNextSendTime: EpochMillis =
val next_time = calcNextRoutineTimestamp(System.currentTimeMillis, use_timeZone, notify_atHour)
logger info s"medication timer will send next notify at ${CommonFormat.formatDate(next_time, use_timeZone.getTotalSeconds / 60 / 60)} with $use_timeZone [$next_time]"
next_time
- override def firstRoutineTimeMillis: Long =
+ override def firstRoutineTimeMillis: EpochMillis =
calcNextSendTime
- override def nextRoutineTimeMillis (previousRoutineScheduledTimeMillis: Long): Long | Null =
+ override def nextRoutineTimeMillis (previousRoutineScheduledTimeMillis: EpochMillis): EpochMillis | Null =
calcNextSendTime
override def main: Unit = {
@@ -85,7 +86,7 @@ class MedicationTimer (using coeur: MornyCoeur) {
object MedicationTimer {
@throws[IllegalArgumentException]
- def calcNextRoutineTimestamp (baseTimeMillis: Long, zone: ZoneOffset, notifyAt: Set[Int]): Long = {
+ def calcNextRoutineTimestamp (baseTimeMillis: EpochMillis, zone: ZoneOffset, notifyAt: Set[Int]): EpochMillis = {
if (notifyAt isEmpty) throw new IllegalArgumentException("notify time is not set")
var time = LocalDateTime.ofEpochSecond(
baseTimeMillis / 1000, ((baseTimeMillis % 1000) * 1000 * 1000) toInt,
diff --git a/src/main/scala/cc/sukazyo/cono/morny/data/MornyJrrp.scala b/src/main/scala/cc/sukazyo/cono/morny/data/MornyJrrp.scala
index b707576..b2e9172 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/data/MornyJrrp.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/data/MornyJrrp.scala
@@ -1,15 +1,16 @@
package cc.sukazyo.cono.morny.data
+import cc.sukazyo.cono.morny.util.EpochDateTime.{EpochDays, EpochMillis}
import com.pengrad.telegrambot.model.User
import scala.language.postfixOps
object MornyJrrp {
- def jrrp_of_telegramUser (user: User, timestamp: Long): Double =
- jrrp_v_xmomi(user.id, timestamp/(1000*60*60*24)) * 100.0
+ def jrrp_of_telegramUser (user: User, timestamp: EpochMillis): Double =
+ jrrp_v_xmomi(user.id, EpochDays fromEpochMillis timestamp) * 100.0
- private def jrrp_v_xmomi (identifier: Long, dayStamp: Long): Double =
+ private def jrrp_v_xmomi (identifier: Long, dayStamp: EpochDays): Double =
import cc.sukazyo.cono.morny.util.CommonEncrypt.MD5
import cc.sukazyo.cono.morny.util.ConvertByteHex.toHex
java.lang.Long.parseLong(MD5(s"$identifier@$dayStamp").toHex.substring(0, 4), 16) / (0xffff toDouble)
diff --git a/src/main/scala/cc/sukazyo/cono/morny/util/EpochDateTime.scala b/src/main/scala/cc/sukazyo/cono/morny/util/EpochDateTime.scala
index 81613c9..5f3d7a2 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/util/EpochDateTime.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/util/EpochDateTime.scala
@@ -10,9 +10,6 @@ object EpochDateTime {
* aka. Milliseconds since 00:00:00 UTC on Thursday, 1 January 1970.
*/
type EpochMillis = Long
- /** Time duration/interval in milliseconds. */
- type DurationMillis = Long
-
object EpochMillis:
/** convert a localtime with timezone to epoch milliseconds
*
@@ -31,5 +28,41 @@ object EpochDateTime {
def apply (time_zone: (String, String)): EpochMillis =
time_zone match
case (time, zone) => apply(time, zone)
+
+ /** Convert from [[EpochSeconds]].
+ *
+ * Due to the missing accuracy, the converted EpochMillis will
+ * be always in 0ms aligned.
+ */
+ def fromEpochSeconds (epochSeconds: EpochSeconds): EpochMillis =
+ epochSeconds.longValue * 1000L
+
+ /** The UNIX Epoch Time in seconds.
+ *
+ * aka. Seconds since 00:00:00 UTC on Thursday, 1 January 1970.
+ *
+ * Normally is the epochSeconds = (epochMillis / 1000)
+ *
+ * Notice that, currently, it stores using [[Int]] (also the implementation
+ * method of Telegram), which will only store times before 2038-01-19 03:14:07.
+ */
+ type EpochSeconds = Int
+
+ /** The UNIX Epoch Time in day.
+ *
+ * aka. days since 00:00:00 UTC on Thursday, 1 January 1970.
+ *
+ * Normally is the epochDays = (epochMillis / 1000 / 60 / 60 / 24)
+ *
+ * Notice that, currently, it stores using [[Short]] (also the implementation
+ * method of Telegram), which will only store times before 2059-09-18.
+ */
+ type EpochDays = Short
+ object EpochDays:
+ def fromEpochMillis (epochMillis: EpochMillis): EpochDays =
+ (epochMillis / (1000*60*60*24)).toShort
+
+ /** Time duration/interval in milliseconds. */
+ type DurationMillis = Long
}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/util/schedule/DelayedTask.scala b/src/main/scala/cc/sukazyo/cono/morny/util/schedule/DelayedTask.scala
index a8a9499..130c4e3 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/util/schedule/DelayedTask.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/util/schedule/DelayedTask.scala
@@ -1,16 +1,18 @@
package cc.sukazyo.cono.morny.util.schedule
+import cc.sukazyo.cono.morny.util.EpochDateTime.{DurationMillis, EpochMillis}
+
trait DelayedTask (
- val delayedMillis: Long
+ val delayedMillis: DurationMillis
) extends Task {
- override val scheduledTimeMillis: Long = System.currentTimeMillis + delayedMillis
+ override val scheduledTimeMillis: EpochMillis = System.currentTimeMillis + delayedMillis
}
object DelayedTask {
- def apply (_name: String, delayedMillis: Long, task: =>Unit): DelayedTask =
+ def apply (_name: String, delayedMillis: DurationMillis, task: =>Unit): DelayedTask =
new DelayedTask (delayedMillis):
override val name: String = _name
override def main: Unit = task
diff --git a/src/main/scala/cc/sukazyo/cono/morny/util/schedule/IntervalTask.scala b/src/main/scala/cc/sukazyo/cono/morny/util/schedule/IntervalTask.scala
index cc87240..37667ba 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/util/schedule/IntervalTask.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/util/schedule/IntervalTask.scala
@@ -1,24 +1,26 @@
package cc.sukazyo.cono.morny.util.schedule
+import cc.sukazyo.cono.morny.util.EpochDateTime.{DurationMillis, EpochMillis}
+
trait IntervalTask extends RoutineTask {
- def intervalMillis: Long
+ def intervalMillis: DurationMillis
- override def firstRoutineTimeMillis: Long =
+ override def firstRoutineTimeMillis: EpochMillis =
System.currentTimeMillis() + intervalMillis
override def nextRoutineTimeMillis (
- previousScheduledRoutineTimeMillis: Long
- ): Long|Null =
+ previousScheduledRoutineTimeMillis: EpochMillis
+ ): EpochMillis|Null =
previousScheduledRoutineTimeMillis + intervalMillis
}
object IntervalTask {
- def apply (_name: String, _intervalMillis: Long, task: =>Unit): IntervalTask =
+ def apply (_name: String, _intervalMillis: DurationMillis, task: =>Unit): IntervalTask =
new IntervalTask:
- override def intervalMillis: Long = _intervalMillis
+ override def intervalMillis: DurationMillis = _intervalMillis
override def name: String = _name
override def main: Unit = task
diff --git a/src/main/scala/cc/sukazyo/cono/morny/util/schedule/IntervalWithTimesTask.scala b/src/main/scala/cc/sukazyo/cono/morny/util/schedule/IntervalWithTimesTask.scala
index c2055a9..5e55a8a 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/util/schedule/IntervalWithTimesTask.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/util/schedule/IntervalWithTimesTask.scala
@@ -1,11 +1,13 @@
package cc.sukazyo.cono.morny.util.schedule
+import cc.sukazyo.cono.morny.util.EpochDateTime.{DurationMillis, EpochMillis}
+
trait IntervalWithTimesTask extends IntervalTask {
def times: Int
private var currentExecutedTimes = 1
- override def nextRoutineTimeMillis (previousScheduledRoutineTimeMillis: Long): Long | Null =
+ override def nextRoutineTimeMillis (previousScheduledRoutineTimeMillis: EpochMillis): EpochMillis | Null =
if currentExecutedTimes >= times then
null
else
@@ -16,11 +18,11 @@ trait IntervalWithTimesTask extends IntervalTask {
object IntervalWithTimesTask {
- def apply (_name: String, _intervalMillis: Long, _times: Int, task: =>Unit): IntervalWithTimesTask =
+ def apply (_name: String, _intervalMillis: DurationMillis, _times: Int, task: =>Unit): IntervalWithTimesTask =
new IntervalWithTimesTask:
override def name: String = _name
override def times: Int = _times
- override def intervalMillis: Long = _intervalMillis
+ override def intervalMillis: DurationMillis = _intervalMillis
override def main: Unit = task
}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/util/schedule/RoutineTask.scala b/src/main/scala/cc/sukazyo/cono/morny/util/schedule/RoutineTask.scala
index 09ade60..dce86fe 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/util/schedule/RoutineTask.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/util/schedule/RoutineTask.scala
@@ -1,12 +1,45 @@
package cc.sukazyo.cono.morny.util.schedule
+import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis
+
+/** The task that can execute multiple times with custom routine function.
+ *
+ * When creating a Routine Task, the task's [[firstRoutineTimeMillis]] function
+ * will be called and the result value will be the first task scheduled time.
+ *
+ * After every execution complete and enter the post effect, the [[nextRoutineTimeMillis]]
+ * function will be called, then its value will be stored as the new task's
+ * scheduled time and re-scheduled by its scheduler.
+ */
trait RoutineTask extends Task {
- private[schedule] var currentScheduledTimeMillis: Long = firstRoutineTimeMillis
- override def scheduledTimeMillis: Long = currentScheduledTimeMillis
+ private[schedule] var currentScheduledTimeMillis: EpochMillis = firstRoutineTimeMillis
- def firstRoutineTimeMillis: Long
+ /** Next running time of this task.
+ *
+ * Should be auto generated from [[firstRoutineTimeMillis]] and
+ * [[nextRoutineTimeMillis]].
+ */
+ override def scheduledTimeMillis: EpochMillis = currentScheduledTimeMillis
- def nextRoutineTimeMillis (previousRoutineScheduledTimeMillis: Long): Long|Null
+ /** The task scheduled time at initial.
+ *
+ * In the default environment, this function will only be called once
+ * when the task object is just created.
+ */
+ def firstRoutineTimeMillis: EpochMillis
+
+ /** The function to calculate the next scheduled time after previous task
+ * routine complete.
+ *
+ * This function will be called every time the task is done once, in the
+ * task runner thread and the post effect scope.
+ *
+ * @param previousRoutineScheduledTimeMillis The previous task routine's
+ * scheduled time.
+ * @return The next task routine's scheduled time, or [[null]] means end
+ * of the task.
+ */
+ def nextRoutineTimeMillis (previousRoutineScheduledTimeMillis: EpochMillis): EpochMillis|Null
}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/util/schedule/Scheduler.scala b/src/main/scala/cc/sukazyo/cono/morny/util/schedule/Scheduler.scala
index 190f9f7..411be39 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/util/schedule/Scheduler.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/util/schedule/Scheduler.scala
@@ -1,5 +1,7 @@
package cc.sukazyo.cono.morny.util.schedule
+import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis
+
import scala.annotation.targetName
import scala.collection.mutable
@@ -78,7 +80,7 @@ class Scheduler {
runtimeStatus = State.PREPARE_RUN
- val nextMove: Task|Long|"None" = taskList.synchronized {
+ val nextMove: Task|EpochMillis|"None" = taskList.synchronized {
taskList.headOption match
case Some(_readyToRun) if System.currentTimeMillis >= _readyToRun.scheduledTimeMillis =>
taskList -= _readyToRun
@@ -107,7 +109,7 @@ class Scheduler {
currentRunning match
case routine: RoutineTask =>
routine.nextRoutineTimeMillis(routine.currentScheduledTimeMillis) match
- case next: Long =>
+ case next: EpochMillis =>
routine.currentScheduledTimeMillis = next
if (!currentRunning_isScheduledCancel) schedule(routine)
case _ =>
@@ -117,7 +119,7 @@ class Scheduler {
currentRunning = null
this setName runnerName
- case needToWaitMillis: Long =>
+ case needToWaitMillis: EpochMillis =>
runtimeStatus = State.WAITING
try Thread.sleep(needToWaitMillis)
catch case _: InterruptedException => {}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/util/schedule/Task.scala b/src/main/scala/cc/sukazyo/cono/morny/util/schedule/Task.scala
index 2dc4503..6250d9a 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/util/schedule/Task.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/util/schedule/Task.scala
@@ -55,3 +55,13 @@ trait Task extends Ordered[Task] {
s"""${super.toString}{"$name": $scheduledTimeMillis}"""
}
+
+object Task {
+
+ def apply (_name: String, _scheduledTime: EpochMillis, _main: =>Unit): Task =
+ new Task:
+ override def name: String = _name
+ override def scheduledTimeMillis: EpochMillis = _scheduledTime
+ override def main: Unit = _main
+
+}
diff --git a/src/test/scala/cc/sukazyo/cono/morny/test/utils/EpochDateTimeTest.scala b/src/test/scala/cc/sukazyo/cono/morny/test/utils/EpochDateTimeTest.scala
index a0d218a..a766428 100644
--- a/src/test/scala/cc/sukazyo/cono/morny/test/utils/EpochDateTimeTest.scala
+++ b/src/test/scala/cc/sukazyo/cono/morny/test/utils/EpochDateTimeTest.scala
@@ -1,11 +1,55 @@
package cc.sukazyo.cono.morny.test.utils
import cc.sukazyo.cono.morny.test.MornyTests
-import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis
+import cc.sukazyo.cono.morny.util.EpochDateTime.{EpochDays, EpochMillis, EpochSeconds}
import org.scalatest.prop.TableDrivenPropertyChecks
class EpochDateTimeTest extends MornyTests with TableDrivenPropertyChecks {
+ "while converting to EpochMillis :" - {
+
+ "from EpochSeconds :" - {
+
+ val examples = Table[EpochSeconds, EpochMillis](
+ ("EpochSeconds", "EpochMillis"),
+ (1699176068, 1699176068000L),
+ (1699176000, 1699176000000L),
+ (1, 1000L),
+ )
+
+ forAll(examples) { (epochSeconds, epochMillis) =>
+ s"EpochSeconds($epochSeconds) should be converted to EpochMillis($epochMillis)" in {
+ (EpochMillis fromEpochSeconds epochSeconds) shouldEqual epochMillis
+ }
+ }
+
+ }
+
+ }
+
+ "while converting to EpochDays :" - {
+
+ "from EpochMillis :" - {
+
+ val examples = Table(
+ ("EpochMillis", "EpochDays"),
+ (0L, 0),
+ (1000L, 0),
+ (80000000L, 0),
+ (90000000L, 1),
+ (1699176549059L, 19666)
+ )
+
+ forAll(examples) { (epochMillis, epochDays) =>
+ s"EpochMillis($epochMillis) should be converted to EpochDays($epochDays)" in {
+ (EpochDays fromEpochMillis epochMillis) shouldEqual epochDays
+ }
+ }
+
+ }
+
+ }
+
"while converting date-time string to time-millis : " - {
"while using ISO-Offset-Date-Time : " - {
diff --git a/src/test/scala/cc/sukazyo/cono/morny/test/utils/schedule/IntervalsTest.scala b/src/test/scala/cc/sukazyo/cono/morny/test/utils/schedule/IntervalsTest.scala
new file mode 100644
index 0000000..c0d1d81
--- /dev/null
+++ b/src/test/scala/cc/sukazyo/cono/morny/test/utils/schedule/IntervalsTest.scala
@@ -0,0 +1,23 @@
+package cc.sukazyo.cono.morny.test.utils.schedule
+
+import cc.sukazyo.cono.morny.test.MornyTests
+import cc.sukazyo.cono.morny.util.schedule.{IntervalWithTimesTask, Scheduler}
+import org.scalatest.tagobjects.Slow
+
+class IntervalsTest extends MornyTests {
+
+ "IntervalWithTimesTest should work even scheduler is scheduled to stop" taggedAs Slow in {
+ val scheduler = Scheduler()
+ var times = 0
+ scheduler ++ IntervalWithTimesTask("intervals-10", 200, 10, {
+ times = times + 1
+ })
+ val startTime = System.currentTimeMillis()
+ scheduler.waitForStopAtAllDone()
+ val timeUsed = System.currentTimeMillis() - startTime
+ times shouldEqual 10
+ timeUsed should (be <= 2100L and be >= 1900L)
+ info(s"interval 200ms for 10 times used time ${timeUsed}ms")
+ }
+
+}
diff --git a/src/test/scala/cc/sukazyo/cono/morny/test/utils/schedule/SchedulerTest.scala b/src/test/scala/cc/sukazyo/cono/morny/test/utils/schedule/SchedulerTest.scala
new file mode 100644
index 0000000..bb9271e
--- /dev/null
+++ b/src/test/scala/cc/sukazyo/cono/morny/test/utils/schedule/SchedulerTest.scala
@@ -0,0 +1,47 @@
+package cc.sukazyo.cono.morny.test.utils.schedule
+
+import cc.sukazyo.cono.morny.test.MornyTests
+import cc.sukazyo.cono.morny.util.schedule.{DelayedTask, Scheduler, Task}
+import org.scalatest.tagobjects.Slow
+
+import scala.collection.mutable
+
+class SchedulerTest extends MornyTests {
+
+ "While executing tasks using scheduler :" - {
+
+ "Task with scheduleTime smaller than current time should be executed immediately" in {
+ val scheduler = Scheduler()
+ var time = System.currentTimeMillis
+ scheduler ++ Task("task", 0, {
+ time = System.currentTimeMillis - time
+ })
+ scheduler.waitForStopAtAllDone()
+ time should be <= 10L
+ info(s"Immediately Task done with time $time")
+ }
+
+ "Task's running thread name should be task name" in {
+ val scheduler = Scheduler()
+ var executedThread: Option[String] = None
+ scheduler ++ Task("task", 0, {
+ executedThread = Some(Thread.currentThread.getName)
+ })
+ scheduler.waitForStopAtAllDone()
+ executedThread shouldEqual Some("task")
+ }
+
+ "Task's execution order should be ordered by task Ordering but not insert order" taggedAs Slow in {
+ val scheduler = Scheduler()
+ val result = mutable.ArrayBuffer.empty[String]
+ scheduler
+ ++ DelayedTask("task-later", 400L, { result += Thread.currentThread.getName })
+ ++ DelayedTask("task-very-late", 800L, { result += Thread.currentThread.getName })
+ ++ DelayedTask("task-early", 100L, { result += Thread.currentThread.getName })
+ scheduler.waitForStopAtAllDone()
+ result.toArray shouldEqual Array("task-early", "task-later", "task-very-late")
+ }
+
+ }
+
+}
diff --git a/src/test/scala/cc/sukazyo/cono/morny/test/utils/schedule/TaskBasicTest.scala b/src/test/scala/cc/sukazyo/cono/morny/test/utils/schedule/TaskBasicTest.scala
new file mode 100644
index 0000000..0883040
--- /dev/null
+++ b/src/test/scala/cc/sukazyo/cono/morny/test/utils/schedule/TaskBasicTest.scala
@@ -0,0 +1,46 @@
+package cc.sukazyo.cono.morny.test.utils.schedule
+
+import cc.sukazyo.cono.morny.test.MornyTests
+import cc.sukazyo.cono.morny.util.schedule.Task
+import org.scalatest.tagobjects.Slow
+
+class TaskBasicTest extends MornyTests {
+
+ "while comparing tasks :" - {
+
+ "tasks with different scheduleTime should be compared using scheduledTime" in {
+ Task("task-a", 21747013400912L, {}) should be > Task("task-b", 21747013400138L, {})
+ Task("task-a", 100L, {}) should be > Task("task-b", 99L, {})
+ Task("task-a", 100L, {}) should be < Task("task-b", 101, {})
+ Task("task-a", -19943L, {}) should be < Task("task-b", 0L, {})
+ }
+
+ "task with the same scheduledTime should not be equal" in {
+ Task("same-task?", 0L, {}) should not equal Task("same-task?", 0L, {})
+ }
+
+ "tasks which is only the same object should be equal" in {
+ def createNewTask = Task("same-task?", 0L, {})
+ val task1 = createNewTask
+ val task2 = createNewTask
+ val task1_copy = task1
+ task1 shouldEqual task1_copy
+ task1 should not equal task2
+ }
+
+ }
+
+ "task can be sync executed by calling its main method." taggedAs Slow in {
+
+ Thread.currentThread setName "parent-thread"
+ val data = StringBuilder("")
+ val task = Task("some-task", 0L, {
+ Thread.sleep(100)
+ data ++= Thread.currentThread.getName ++= " // " ++= "task-complete"
+ })
+ task.main
+ data.toString shouldEqual "parent-thread // task-complete"
+
+ }
+
+}