Skip to content

Commit

Permalink
test
Browse files Browse the repository at this point in the history
  • Loading branch information
cunla committed Feb 14, 2024
1 parent e602bf1 commit 0f46f40
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 305 deletions.
8 changes: 0 additions & 8 deletions src/main/kotlin/com/dsoftware/ghmanager/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,6 @@ object Constants {
const val LOG_MSG_MISSING = "Job logs missing for: "
const val LOG_MSG_JOB_IN_PROGRESS = "Job is still in progress, can't view logs."

fun emptyTextMessage(logValue: String): Boolean {
return (logValue.startsWith(LOG_MSG_MISSING)
|| logValue.startsWith(LOG_MSG_PICK_JOB)
|| logValue.startsWith(
LOG_MSG_JOB_IN_PROGRESS
))
}

fun updateEmptyText(logValue: String, emptyText: StatusText): Boolean {
if (logValue.startsWith(LOG_MSG_MISSING)
|| logValue.startsWith(LOG_MSG_PICK_JOB)
Expand Down
127 changes: 127 additions & 0 deletions src/main/kotlin/com/dsoftware/ghmanager/api/GetJobLogRequest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package com.dsoftware.ghmanager.api

import com.dsoftware.ghmanager.api.model.Job
import com.dsoftware.ghmanager.api.model.JobStep
import com.intellij.openapi.diagnostic.logger
import org.jetbrains.plugins.github.api.GithubApiRequest
import org.jetbrains.plugins.github.api.GithubApiResponse
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStream
import java.io.InputStreamReader
import java.text.SimpleDateFormat
import java.util.Date
import java.util.TimeZone

typealias JobLog = Map<Int, StringBuilder>

class GetJobLogRequest(private val job: Job) : GithubApiRequest.Get<String>(job.url + "/logs") {
private val stepsPeriodMap = job.steps?.associate { step ->
step.number to (step.startedAt to step.completedAt)
} ?: emptyMap()
private val lastStepNumber: Int = stepsPeriodMap.keys.maxOrNull() ?: 0

override fun extractResult(response: GithubApiResponse): String {
LOG.debug("extracting result for $url")
return response.handleBody {
extractJobLogFromStream(it)
}
}

fun extractJobLogFromStream(inputStream: InputStream): String {
val stepLogs = extractLogByStep(inputStream)
return stepsAsLog(stepLogs)
}

fun extractLogByStep(inputStream: InputStream): Map<Int, StringBuilder> {
val dateTimePattern = "yyyy-MM-dd'T'HH:mm:ss.SSS"
val formatter = SimpleDateFormat(dateTimePattern)

val contentBuilders = HashMap<Int, StringBuilder>()

formatter.timeZone = TimeZone.getTimeZone("UTC")
var lineNum = 0
var currStep = 1
try {
val reader = BufferedReader(InputStreamReader(inputStream))
val lines = reader.readLines()
for (line in lines) {
++lineNum
if (line.length < 29) {
contentBuilders.getOrDefault(currStep, StringBuilder()).append(line + "\n")
continue
}
val datetimeStr = line.substring(0, 23)
val time = formatter.parse(datetimeStr)
currStep = findStep(currStep, time)

contentBuilders.getOrPut(currStep) { StringBuilder(400_000) }.append(line + "\n")
}
} catch (e: IOException) {
LOG.warn(e.message)
throw e
}
return contentBuilders
}

private fun findStep(initialStep: Int, time: Date): Int {
var currStep = initialStep
while (currStep < lastStepNumber) {
if (!stepsPeriodMap.containsKey(currStep)) {
currStep += 1
continue
}
val currStart = stepsPeriodMap[currStep]?.first
val currEnd = stepsPeriodMap[currStep]?.second
if (currStart != null && currStart.after(time)) {
return currStep
}
if ((currStart == null || currStart.before(time) || currStart == time)
&& (currEnd == null || currEnd.after(time) || currEnd == time)
) {
return currStep
}
currStep += 1
}
return currStep
}

private fun stepsAsLog(stepLogs: JobLog): String {
val stepsResult: Map<Int, JobStep> = if (job.steps == null) {
emptyMap()
} else {
job.steps.associateBy { it.number }
}
val stepNumbers = stepsResult.keys.sorted()
if (!stepNumbers.containsAll(stepLogs.keys)) {
LOG.warn(
"Some logs do not have a step-result associated " +
"[steps in results=$stepNumbers, step with logs=${stepLogs.keys}] "
)
}
val res = StringBuilder(1_000_000)
for (index in stepNumbers) {
val stepInfo = stepsResult[index]!!
val indexStr = "%3d".format(index)
res.append(
when (stepInfo.conclusion) {
"skipped" -> "\u001B[0m\u001B[37m---- Step ${indexStr}: ${stepInfo.name} (skipped) ----\u001b[0m\n"
"failure" -> "\u001B[0m\u001B[31m---- Step ${indexStr}: ${stepInfo.name} (failed) ----\u001b[0m\n"
else -> "\u001B[0m\u001B[32m---- Step ${indexStr}: ${stepInfo.name} ----\u001b[0m\n"
}
)
if (stepInfo.conclusion != "skipped" && stepLogs.containsKey(index) && (res.length < 950_000)) {
if (res.length + (stepLogs[index]?.length ?: 0) < 990_000) {
res.append(stepLogs[index])
} else {
res.append("Log is too big to display, showing only first 1mb")
}
}
}
return res.toString()
}

companion object {
private val LOG = logger<GetJobLogRequest>()
}
}
80 changes: 0 additions & 80 deletions src/main/kotlin/com/dsoftware/ghmanager/api/GetRunLogRequest.kt

This file was deleted.

3 changes: 0 additions & 3 deletions src/main/kotlin/com/dsoftware/ghmanager/api/GithubApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ data class WorkflowRunFilter(
val workflowId: Long? = null,
)

typealias JobLog = Map<Int, String>
typealias WorkflowRunLog = Map<String, JobLog>

object GithubApi : GithubApiRequests.Entity("/repos") {
private val LOG = logger<GithubApi>()
fun getJobLog(job: Job) = GetJobLogRequest(job).withOperationName("Get Job log ${job.id}")
Expand Down
10 changes: 8 additions & 2 deletions src/main/kotlin/com/dsoftware/ghmanager/api/model/JobModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ data class Job(

/* The id of the job. */
val id: Long,
val workflowName: String?,
val headBranch: String?,
/* The id of the associated workflow run. */
val runId: Long,
val runUrl: String,
Expand All @@ -49,6 +51,8 @@ data class Job(
val status: String,
/* The outcome of the job. */
val conclusion: String?,
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "UTC")
val createdAt: Date?,
/* The time that the job started, in ISO 8601 format. */
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "UTC")
val startedAt: Date?,
Expand Down Expand Up @@ -109,8 +113,10 @@ data class JobStep(
/* The name of the job. */
val name: String,
val number: Int,
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSX", timezone = "UTC")
/* The time that the step started, in ISO 8601 format. */
val startedAt: String? = null,
val startedAt: Date? = null,
/* The time that the job finished, in ISO 8601 format. */
val completedAt: String? = null
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSX", timezone = "UTC")
val completedAt: Date? = null
)
6 changes: 2 additions & 4 deletions src/main/kotlin/com/dsoftware/ghmanager/data/DataProviders.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.dsoftware.ghmanager.data

import com.dsoftware.ghmanager.api.WorkflowRunLog
import com.dsoftware.ghmanager.api.GithubApi
import com.dsoftware.ghmanager.api.JobLog
import com.dsoftware.ghmanager.api.model.Job
import com.dsoftware.ghmanager.api.model.WorkflowRunJobs
import com.intellij.openapi.Disposable
Expand Down Expand Up @@ -67,11 +65,11 @@ class JobLogDataProvider(
progressManager: ProgressManager,
requestExecutor: GithubApiRequestExecutor,
job: Job
) : DataProvider<JobLog>(
) : DataProvider<String>(
progressManager,
requestExecutor,
GithubApi.getJobLog(job),
emptyMap()
null
)

class WorkflowRunJobsDataProvider(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class LogLoadingModelListener(
private val jobsSelectionHolder: JobListSelectionHolder,
) : GHLoadingModel.StateChangeListener {
val logModel = SingleValueModel<String?>(null)
val logsLoadingModel = GHCompletableFutureLoadingModel<JobLog>(workflowRunDisposable)
val logsLoadingModel = GHCompletableFutureLoadingModel<String>(workflowRunDisposable)

init {
jobsSelectionHolder.addSelectionChangeListener(workflowRunDisposable, this::setLogValue)
Expand Down Expand Up @@ -50,34 +50,7 @@ class LogLoadingModelListener(

}

private fun stepsAsLog(stepLogs: Map<Int, String>, selection: Job): String {
val stepsResult: Map<Int, JobStep> = if (selection.steps == null) {
emptyMap()
} else {
selection.steps.associateBy { it.number }
}
val stepNumbers = stepsResult.keys.sorted()
if (!stepNumbers.containsAll(stepLogs.keys)) {
LOG.warn(
"Some logs do not have a step-result associated " +
"[steps in results=$stepNumbers, step with logs=${stepLogs.keys}] "
)
}
val res = StringBuilder()
for (index in stepNumbers) {
val stepInfo = stepsResult[index]!!
val logs = if (stepLogs.containsKey(index)) stepLogs[index] else ""
val indexStr = "%3d".format(index)
res.append(
when (stepInfo.conclusion) {
"skipped" -> "\u001B[0m\u001B[37m---- Step ${indexStr}: ${stepInfo.name} (skipped) ----\u001b[0m\n"
"failure" -> "\u001B[0m\u001B[31m---- Step ${indexStr}: ${stepInfo.name} (failed) ----\u001b[0m\n${logs}"
else -> "\u001B[0m\u001B[32m---- Step ${indexStr}: ${stepInfo.name} ----\u001b[0m\n${logs}"
}
)
}
return res.toString()
}


private fun setLogValue() {
val jobSelection = jobsSelectionHolder.selection
Expand All @@ -91,7 +64,7 @@ class LogLoadingModelListener(
jobSelection == null -> LOG_MSG_PICK_JOB
jobSelection.status == "in_progress" -> LOG_MSG_JOB_IN_PROGRESS
logs == null -> LOG_MSG_MISSING + jobSelection.name
else -> stepsAsLog(logs, jobSelection)
else -> logs
}
}

Expand Down
28 changes: 22 additions & 6 deletions src/test/kotlin/com/dsoftware/ghmanager/TestGetJobLogRequest.kt
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@
package com.dsoftware.ghmanager

import com.dsoftware.ghmanager.api.model.Job
import com.dsoftware.ghmanager.api.GetJobLogRequest
import com.dsoftware.ghmanager.api.model.WorkflowRunJobs
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.PropertyNamingStrategies
import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import com.intellij.testFramework.fixtures.BasePlatformTestCase
import junit.framework.TestCase

class TestGetJobLogRequest : BasePlatformTestCase() {
class TestGetJobLogRequest : TestCase() {
private val mapper = ObjectMapper().registerKotlinModule()

override fun setUp() {
super.setUp()
mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
}

fun testGetJobLogRequest() {
val logContent = TestGetJobLogRequest::class.java.getResource("wf-run-single-job.log")?.readText()
val jobJson = TestGetJobLogRequest::class.java.getResource("wf-run-single-job.json")?.readText()
val obj: List<Job> = mapper.readValue(json)
// arrange
val logContent = TestGetJobLogRequest::class.java.getResource("/wf-run-single-job.log")!!.readText()
val wfJobsJson = TestGetJobLogRequest::class.java.getResource("/wf-run-jobs.json")!!.readText()
val wfJobs: WorkflowRunJobs = mapper.readValue(wfJobsJson)
val job = wfJobs.jobs.first()

//act
val jobLog = GetJobLogRequest(job).extractLogByStep(logContent.byteInputStream())

//assert
val jobLogLinesCount = jobLog.map { it.key to it.value.split("\n").size }.toMap()
assertTrue(jobLogLinesCount == mapOf((1 to 33), (2 to 21), (3 to 834), (4 to 813), (6 to 5), (8 to 15)))
}
}
Loading

0 comments on commit 0f46f40

Please sign in to comment.