summaryrefslogtreecommitdiff
path: root/opendc-web/opendc-web-runner/src/test
diff options
context:
space:
mode:
Diffstat (limited to 'opendc-web/opendc-web-runner/src/test')
-rw-r--r--opendc-web/opendc-web-runner/src/test/kotlin/org/opendc/web/runner/internal/JobManagerImplTest.kt159
-rw-r--r--opendc-web/opendc-web-runner/src/test/kotlin/org/opendc/web/runner/internal/ReportCollectorTest.kt170
-rw-r--r--opendc-web/opendc-web-runner/src/test/resources/log4j2-test.xml13
3 files changed, 342 insertions, 0 deletions
diff --git a/opendc-web/opendc-web-runner/src/test/kotlin/org/opendc/web/runner/internal/JobManagerImplTest.kt b/opendc-web/opendc-web-runner/src/test/kotlin/org/opendc/web/runner/internal/JobManagerImplTest.kt
new file mode 100644
index 00000000..d5c40799
--- /dev/null
+++ b/opendc-web/opendc-web-runner/src/test/kotlin/org/opendc/web/runner/internal/JobManagerImplTest.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2024 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.web.runner.internal
+
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.verify
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertFalse
+import org.junit.jupiter.api.Assertions.assertNull
+import org.junit.jupiter.api.Assertions.assertTrue
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.opendc.web.client.runner.JobResource
+import org.opendc.web.client.runner.OpenDCRunnerClient
+import org.opendc.web.proto.JobState
+import org.opendc.web.proto.runner.Job
+import org.opendc.web.proto.runner.Report
+
+/**
+ * Test suite for [JobManagerImpl].
+ */
+class JobManagerImplTest {
+ private lateinit var client: OpenDCRunnerClient
+ private lateinit var jobResource: JobResource
+ private lateinit var manager: JobManagerImpl
+
+ @BeforeEach
+ fun setUp() {
+ client = mockk()
+ jobResource = mockk()
+ every { client.jobs } returns jobResource
+ manager = JobManagerImpl(client)
+ }
+
+ private fun makeJob(
+ id: Long,
+ state: JobState,
+ ): Job =
+ mockk {
+ every { this@mockk.id } returns id
+ every { this@mockk.state } returns state
+ }
+
+ @Test
+ fun testFindNextReturnsNullWhenEmpty() {
+ every { jobResource.queryPending() } returns emptyList()
+
+ assertNull(manager.findNext())
+ }
+
+ @Test
+ fun testFindNextReturnsFirstJob() {
+ val job = makeJob(1L, JobState.PENDING)
+ every { jobResource.queryPending() } returns listOf(job)
+
+ val result = manager.findNext()
+ assertEquals(job, result)
+ }
+
+ @Test
+ fun testClaimSuccess() {
+ every { jobResource.update(1L, Job.Update(JobState.CLAIMED, 0, null, null)) } returns makeJob(1L, JobState.CLAIMED)
+
+ assertTrue(manager.claim(1L))
+ }
+
+ @Test
+ fun testClaimFailsOnIllegalState() {
+ every { jobResource.update(1L, Job.Update(JobState.CLAIMED, 0, null, null)) } throws IllegalStateException("conflict")
+
+ assertFalse(manager.claim(1L))
+ }
+
+ @Test
+ fun testHeartbeatSuccessWhenNotFailed() {
+ val job = makeJob(1L, JobState.RUNNING)
+ every { jobResource.update(1L, Job.Update(JobState.RUNNING, 30, null, null)) } returns job
+
+ assertTrue(manager.heartbeat(1L, 30))
+ }
+
+ @Test
+ fun testHeartbeatReturnsFalseWhenJobFailed() {
+ val job = makeJob(1L, JobState.FAILED)
+ every { jobResource.update(1L, Job.Update(JobState.RUNNING, 30, null, null)) } returns job
+
+ assertFalse(manager.heartbeat(1L, 30))
+ }
+
+ @Test
+ fun testHeartbeatReturnsTrueWhenResponseNull() {
+ every { jobResource.update(1L, Job.Update(JobState.RUNNING, 30, null, null)) } returns null
+
+ // null response means no FAILED state, so heartbeat can continue
+ assertTrue(manager.heartbeat(1L, 30))
+ }
+
+ @Test
+ fun testFail() {
+ val report =
+ Report(null, null, emptyList(), Report.Summary(0, 1, null, null), Report.ErrorInfo("some error", "RuntimeException", null))
+ every { jobResource.update(1L, Job.Update(JobState.FAILED, 60, null, report)) } returns makeJob(1L, JobState.FAILED)
+
+ manager.fail(1L, 60, report)
+
+ verify { jobResource.update(1L, Job.Update(JobState.FAILED, 60, null, report)) }
+ }
+
+ @Test
+ fun testFailWithNullReport() {
+ every { jobResource.update(1L, Job.Update(JobState.FAILED, 60, null, null)) } returns makeJob(1L, JobState.FAILED)
+
+ manager.fail(1L, 60, null)
+
+ verify { jobResource.update(1L, Job.Update(JobState.FAILED, 60, null, null)) }
+ }
+
+ @Test
+ fun testFinish() {
+ val results = mapOf("total_power_draw" to listOf(100.0))
+ val report = Report(null, null, emptyList(), Report.Summary(0, 0, 120, null), null)
+ every { jobResource.update(1L, Job.Update(JobState.FINISHED, 120, results, report)) } returns makeJob(1L, JobState.FINISHED)
+
+ manager.finish(1L, 120, results, report)
+
+ verify { jobResource.update(1L, Job.Update(JobState.FINISHED, 120, results, report)) }
+ }
+
+ @Test
+ fun testFinishWithNullReport() {
+ val results = mapOf("total_power_draw" to listOf(100.0))
+ every { jobResource.update(1L, Job.Update(JobState.FINISHED, 120, results, null)) } returns makeJob(1L, JobState.FINISHED)
+
+ manager.finish(1L, 120, results, null)
+
+ verify { jobResource.update(1L, Job.Update(JobState.FINISHED, 120, results, null)) }
+ }
+}
diff --git a/opendc-web/opendc-web-runner/src/test/kotlin/org/opendc/web/runner/internal/ReportCollectorTest.kt b/opendc-web/opendc-web-runner/src/test/kotlin/org/opendc/web/runner/internal/ReportCollectorTest.kt
new file mode 100644
index 00000000..14ad8362
--- /dev/null
+++ b/opendc-web/opendc-web-runner/src/test/kotlin/org/opendc/web/runner/internal/ReportCollectorTest.kt
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2024 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.web.runner.internal
+
+import org.apache.logging.log4j.LogManager
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertNotNull
+import org.junit.jupiter.api.Assertions.assertNull
+import org.junit.jupiter.api.Assertions.assertTrue
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import java.time.Instant
+
+/**
+ * Test suite for [ReportCollector].
+ */
+class ReportCollectorTest {
+ private lateinit var collector: ReportCollector
+
+ @BeforeEach
+ fun setUp() {
+ collector = ReportCollector()
+ }
+
+ @Test
+ fun testCollectEmpty() {
+ val report = collector.collect()
+
+ assertTrue(report.logs().isEmpty())
+ assertEquals(0, report.summary().totalWarnings())
+ assertEquals(0, report.summary().totalErrors())
+ }
+
+ @Test
+ fun testCollectWithRuntimeAndWaitTime() {
+ val report = collector.collect(runtimeSeconds = 120, waitTimeSeconds = 30)
+
+ assertEquals(120, report.summary().runtimeSeconds())
+ assertEquals(30, report.summary().waitTimeSeconds())
+ }
+
+ @Test
+ fun testCollectWithTimestamps() {
+ val createdAt = Instant.parse("2024-01-01T00:00:00Z")
+ val startedAt = Instant.parse("2024-01-01T00:01:00Z")
+
+ val report = collector.collect(createdAt = createdAt, startedAt = startedAt)
+
+ assertEquals(createdAt.toString(), report.createdAt())
+ assertEquals(startedAt.toString(), report.startedAt())
+ }
+
+ @Test
+ fun testCollectWithoutOptionalParams() {
+ val report = collector.collect()
+
+ assertNull(report.createdAt())
+ assertNull(report.startedAt())
+ assertNull(report.summary().runtimeSeconds())
+ assertNull(report.summary().waitTimeSeconds())
+ }
+
+ @Test
+ fun testAttachAndDetect() {
+ // Attach collector to root logger
+ collector.attach()
+
+ val logger = LogManager.getLogger(ReportCollectorTest::class.java)
+ logger.warn("test warning message")
+ logger.error("test error message")
+
+ collector.detach()
+
+ // Log after detach should not be captured
+ logger.warn("this should not be captured")
+
+ val report = collector.collect()
+
+ assertEquals(2, report.logs().size)
+ assertEquals(1, report.summary().totalWarnings())
+ assertEquals(1, report.summary().totalErrors())
+ }
+
+ @Test
+ fun testOnlyWarnAndErrorCaptured() {
+ collector.attach()
+
+ val logger = LogManager.getLogger(ReportCollectorTest::class.java)
+ logger.info("info message - should not be captured")
+ logger.debug("debug message - should not be captured")
+ logger.warn("warn message - should be captured")
+ logger.error("error message - should be captured")
+
+ collector.detach()
+
+ val report = collector.collect()
+
+ assertEquals(2, report.logs().size)
+ }
+
+ @Test
+ fun testLogEntryStructure() {
+ collector.attach()
+
+ val logger = LogManager.getLogger(ReportCollectorTest::class.java)
+ logger.warn("test warning")
+
+ collector.detach()
+
+ val report = collector.collect()
+ val entry = report.logs()[0]
+
+ assertNotNull(entry.timestamp())
+ assertEquals("WARN", entry.level())
+ assertEquals(ReportCollectorTest::class.java.name, entry.logger())
+ assertEquals("test warning", entry.message())
+ }
+
+ @Test
+ fun testClear() {
+ collector.attach()
+
+ val logger = LogManager.getLogger(ReportCollectorTest::class.java)
+ logger.warn("test warning")
+
+ collector.detach()
+
+ collector.clear()
+
+ val report = collector.collect()
+ assertTrue(report.logs().isEmpty())
+ }
+
+ @Test
+ fun testSummaryCountsPerLevel() {
+ collector.attach()
+
+ val logger = LogManager.getLogger(ReportCollectorTest::class.java)
+ logger.warn("warn 1")
+ logger.warn("warn 2")
+ logger.error("error 1")
+
+ collector.detach()
+
+ val report = collector.collect()
+
+ assertEquals(2, report.summary().totalWarnings())
+ assertEquals(1, report.summary().totalErrors())
+ }
+}
diff --git a/opendc-web/opendc-web-runner/src/test/resources/log4j2-test.xml b/opendc-web/opendc-web-runner/src/test/resources/log4j2-test.xml
new file mode 100644
index 00000000..cb99c416
--- /dev/null
+++ b/opendc-web/opendc-web-runner/src/test/resources/log4j2-test.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="WARN">
+ <Appenders>
+ <Console name="Console" target="SYSTEM_OUT">
+ <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
+ </Console>
+ </Appenders>
+ <Loggers>
+ <Root level="warn">
+ <AppenderRef ref="Console"/>
+ </Root>
+ </Loggers>
+</Configuration>