Skip to content

Commit 2dd65a7

Browse files
authored
Merge pull request #65 from strykeforce/healthcheck
Add health check to Third Coast
2 parents b9271cd + ae873f5 commit 2dd65a7

File tree

13 files changed

+743
-29
lines changed

13 files changed

+743
-29
lines changed

build.gradle

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,25 @@ plugins {
22
id "java"
33
id "idea"
44
id "maven-publish"
5-
id "org.jetbrains.kotlin.jvm" version "1.3.61"
5+
id "org.jetbrains.kotlin.jvm" version "1.3.70"
66
id "org.jetbrains.kotlin.kapt" version "1.3.61"
7-
id "edu.wpi.first.GradleRIO" version "2020.1.2"
7+
id "edu.wpi.first.GradleRIO" version "2020.3.2"
88
}
99

1010
group = 'org.strykeforce'
11-
version = '20.2.1'
11+
version = '20.3.2'
1212

1313
sourceCompatibility = JavaVersion.VERSION_11
1414
targetCompatibility = JavaVersion.VERSION_11
1515

1616
// Set this to true to enable desktop support.
1717
def includeDesktopSupport = false
1818

19+
repositories {
20+
jcenter()
21+
maven {url = "http://first.wpi.edu/FRC/roborio/maven/release"}
22+
}
23+
1924
// Defining my dependencies. In this case, WPILib (+ friends), and vendor libraries.
2025
// Also defines JUnit 5.
2126
dependencies {
@@ -33,6 +38,7 @@ dependencies {
3338
implementation "org.eclipse.jetty:jetty-server:9.4.19.v20190610"
3439
implementation "com.squareup.okhttp3:okhttp:3.12.5"
3540
implementation "io.github.microutils:kotlin-logging:1.7.6"
41+
implementation "org.jetbrains.kotlinx:kotlinx-html-jvm:0.7.1"
3642
implementation "com.squareup.moshi:moshi:1.9.2"
3743
kapt "com.squareup.moshi:moshi-kotlin-codegen:1.9.2"
3844

@@ -52,6 +58,10 @@ java {
5258
withSourcesJar()
5359
}
5460

61+
test {
62+
useJUnitPlatform()
63+
}
64+
5565
idea {
5666
module {
5767
downloadJavadoc = true
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package org.strykeforce.thirdcoast.healthcheck
2+
3+
import edu.wpi.first.wpilibj.TimedRobot
4+
import kotlinx.html.*
5+
import kotlinx.html.stream.appendHTML
6+
import mu.KotlinLogging
7+
import java.io.File
8+
import java.text.SimpleDateFormat
9+
import java.util.*
10+
11+
private const val HTML_PATH = "/var/local/natinst/www/healthcheck/index.html"
12+
private val logger = KotlinLogging.logger {}
13+
14+
class HealthCheck {
15+
var period = TimedRobot.kDefaultPeriod
16+
17+
private val testGroups = mutableListOf<TestGroup>()
18+
private var state = State.STARTING
19+
private lateinit var iterator: Iterator<TestGroup>
20+
private lateinit var currentTestGroup: TestGroup
21+
22+
fun talonCheck(init: TalonGroup.() -> Unit): TalonGroup {
23+
val test = TalonGroup(this)
24+
test.init()
25+
testGroups.add(test)
26+
return test
27+
}
28+
29+
fun execute() = when (state) {
30+
State.STARTING -> {
31+
check(testGroups.isNotEmpty()) { "no test groups" }
32+
iterator = testGroups.iterator()
33+
currentTestGroup = iterator.next()
34+
state = State.RUNNING
35+
}
36+
State.RUNNING -> if (!currentTestGroup.isFinished()) {
37+
currentTestGroup.execute()
38+
} else if (iterator.hasNext()) {
39+
currentTestGroup = iterator.next()
40+
} else {
41+
state = State.STOPPING
42+
}
43+
State.STOPPING -> {
44+
logger.info { "health check finished" }
45+
state = State.STOPPED
46+
}
47+
State.STOPPED -> throw IllegalStateException()
48+
}
49+
50+
fun isFinised() = state == State.STOPPED
51+
52+
fun report() {
53+
File(HTML_PATH).writer().use { writer ->
54+
writer.appendln("<!DOCTYPE html>")
55+
val tagConsumer = writer.appendHTML()
56+
tagConsumer.html {
57+
attributes["lang"] = "en"
58+
head {
59+
title { +"Health Check" }
60+
style {
61+
unsafe {
62+
raw(HealthCheck::class.java.getResource("/healthcheck.css").readText())
63+
}
64+
}
65+
}
66+
body {
67+
h1 { +"Health Check ${SimpleDateFormat("HH:mm:ss").format(Date())}" }
68+
testGroups.forEach { it.report(tagConsumer) }
69+
}
70+
}
71+
}
72+
logger.info { "health check report: http://10.27.67.2/healthcheck/index.html" }
73+
74+
}
75+
76+
override fun toString(): String {
77+
return "HealthCheck(testGroups=$testGroups)"
78+
}
79+
80+
81+
private enum class State {
82+
STARTING,
83+
RUNNING,
84+
STOPPING,
85+
STOPPED
86+
}
87+
88+
}
89+
90+
fun healthCheck(init: HealthCheck.() -> Unit): HealthCheck = HealthCheck().apply(init)
91+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.strykeforce.thirdcoast.healthcheck
2+
3+
import kotlinx.html.TagConsumer
4+
import kotlinx.html.table
5+
import java.lang.Appendable
6+
7+
interface Reportable{
8+
fun reportTable(tagConsumer: TagConsumer<Appendable>){
9+
tagConsumer.table{
10+
reportHeader(tagConsumer)
11+
reportRows(tagConsumer)
12+
}
13+
}
14+
15+
fun reportHeader(tagConsumer: TagConsumer<Appendable>)
16+
fun reportRows(tagConsumer: TagConsumer<Appendable>)
17+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.strykeforce.thirdcoast.healthcheck
2+
3+
import kotlinx.html.TagConsumer
4+
import java.lang.Appendable
5+
6+
fun<T : Comparable<T>> ClosedRange<T>.statusOf(value: T): String = if(this.contains(value)) "pass" else "fail"
7+
8+
interface Test{
9+
var name: String
10+
fun execute()
11+
fun isFinished(): Boolean
12+
fun report(tagConsumer: TagConsumer<Appendable>)
13+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package org.strykeforce.thirdcoast.healthcheck
2+
3+
import com.ctre.phoenix.motorcontrol.can.BaseTalon
4+
import kotlinx.html.TagConsumer
5+
import kotlinx.html.div
6+
import kotlinx.html.h2
7+
import kotlinx.html.table
8+
import org.strykeforce.thirdcoast.healthcheck.tests.*
9+
10+
import mu.KotlinLogging
11+
12+
private val logger = KotlinLogging.logger {}
13+
14+
abstract class TestGroup(val healthCheck: HealthCheck) : Test {
15+
override var name = "name not set"
16+
17+
protected val tests = mutableListOf<Test>()
18+
private var state = State.STARTING
19+
private lateinit var iterator: Iterator<Test>
20+
private lateinit var currentTest: Test
21+
22+
23+
override fun execute() = when (state) {
24+
State.STARTING -> {
25+
logger.info { "$name starting" }
26+
check(tests.isNotEmpty()) { "no tests in test group '$name'" }
27+
iterator = tests.iterator()
28+
currentTest = iterator.next()
29+
state = State.RUNNING
30+
}
31+
32+
State.RUNNING -> if (!currentTest.isFinished()) {
33+
currentTest.execute()
34+
} else if (iterator.hasNext()) {
35+
currentTest = iterator.next()
36+
} else {
37+
logger.info { "$name finished" }
38+
state = State.STOPPED
39+
}
40+
41+
State.STOPPED -> throw IllegalStateException()
42+
}
43+
44+
override fun isFinished() = state == State.STOPPED
45+
46+
override fun report(tagConsumer: TagConsumer<Appendable>) {
47+
tagConsumer.div {
48+
h2 { +name }
49+
table {
50+
val reportable = tests.filterIsInstance<Reportable>()
51+
if (!reportable.isEmpty()) {
52+
reportable.first().apply { reportHeader(tagConsumer) }
53+
reportable.forEach { it.reportRows(tagConsumer) }
54+
}
55+
}
56+
}
57+
}
58+
59+
60+
override fun toString(): String {
61+
return "TestGroup(name='$name', tests=$tests)"
62+
}
63+
64+
65+
private enum class State {
66+
STARTING,
67+
RUNNING,
68+
STOPPED
69+
}
70+
71+
}
72+
73+
74+
class TalonGroup(healthCheck: HealthCheck) : TestGroup(healthCheck) {
75+
var talons = emptyList<BaseTalon>()
76+
77+
fun timedTest(init: TalonTimedTest.() -> Unit): Test {
78+
val spinTest = TalonTimedTest(this)
79+
spinTest.init()
80+
tests.add(spinTest)
81+
return spinTest
82+
}
83+
84+
fun followerTimedTest(init: TalonFollowerTimedTest.() -> Unit): Test {
85+
val spinTest = TalonFollowerTimedTest(this)
86+
spinTest.init()
87+
tests.add(spinTest)
88+
return spinTest
89+
}
90+
91+
fun positionTest(init: TalonPositionTest.() -> Unit): Test {
92+
val positionTest = TalonPositionTest(this)
93+
positionTest.init()
94+
tests.add(positionTest)
95+
return positionTest
96+
}
97+
98+
fun positionTalon(init: TalonPosition.() -> Unit): Test {
99+
val position = TalonPosition(this)
100+
position.init()
101+
tests.add(position)
102+
return position
103+
}
104+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package org.strykeforce.thirdcoast.healthcheck
2+
3+
interface Zeroable {
4+
fun zero(): Boolean;
5+
}

0 commit comments

Comments
 (0)