Skip to content

Commit

Permalink
Merge pull request lightningdevkit#186 from alexanderwiederin/kotlin-…
Browse files Browse the repository at this point in the history
…lint

Add CI Checks for Kotlin
  • Loading branch information
tnull authored Nov 8, 2023
2 parents 7a4ecd0 + 52c9694 commit d955e62
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 45 deletions.
47 changes: 47 additions & 0 deletions .github/workflows/kotlin.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Continuous Integration Checks - Kotlin

on: [push, pull_request]

jobs:
check-kotlin:
runs-on: ubuntu-latest

env:
LDK_NODE_JVM_DIR: bindings/kotlin/ldk-node-jvm
LDK_NODE_ANDROID_DIR: bindings/kotlin/ldk-node-android

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 11

- name: Run ktlintCheck on ldk-node-jvm
run: |
cd $LDK_NODE_JVM_DIR
./gradlew ktlintCheck
- name: Run ktlintCheck on ldk-node-android
run: |
cd $LDK_NODE_ANDROID_DIR
./gradlew ktlintCheck
- name: Generate Kotlin JVM
run: ./scripts/uniffi_bindgen_generate_kotlin.sh

- name: Start bitcoind and electrs
run: docker compose up -d

- name: Run ldk-node-jvm tests
run: |
cd $LDK_NODE_JVM_DIR
./gradlew test -Penv=ci
- name: Run ldk-node-android tests
run: |
cd $LDK_NODE_ANDROID_DIR
./gradlew test
12 changes: 9 additions & 3 deletions bindings/kotlin/ldk-node-android/lib/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import org.gradle.api.tasks.testing.logging.TestExceptionFormat.*
import org.gradle.api.tasks.testing.logging.TestLogEvent.*

// library version is defined in gradle.properties
val libraryVersion: String by project

Expand All @@ -10,6 +7,7 @@ plugins {

id("maven-publish")
id("signing")
id("org.jlleitschuh.gradle.ktlint") version "11.6.1"
}

repositories {
Expand Down Expand Up @@ -106,3 +104,11 @@ signing {
// useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword)
sign(publishing.publications)
}

ktlint {
filter {
exclude { entry ->
entry.file.toString().contains("main")
}
}
}
52 changes: 41 additions & 11 deletions bindings/kotlin/ldk-node-jvm/lib/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import org.gradle.api.tasks.testing.logging.TestExceptionFormat.*
import org.gradle.api.tasks.testing.logging.TestLogEvent.*
import org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
import org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED
import org.gradle.api.tasks.testing.logging.TestLogEvent.PASSED
import org.gradle.api.tasks.testing.logging.TestLogEvent.SKIPPED
import org.gradle.api.tasks.testing.logging.TestLogEvent.STANDARD_ERROR
import org.gradle.api.tasks.testing.logging.TestLogEvent.STANDARD_OUT

// library version is defined in gradle.properties
val libraryVersion: String by project
Expand All @@ -12,6 +16,7 @@ plugins {
id("java-library")
id("maven-publish")
id("signing")
id("org.jlleitschuh.gradle.ktlint") version "11.6.1"
}

repositories {
Expand All @@ -31,12 +36,12 @@ dependencies {
// Use the JUnit 5 integration.
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.1")

//// This dependency is exported to consumers, that is to say found on their compile classpath.
//api("org.apache.commons:commons-math3:3.6.1")
// // This dependency is exported to consumers, that is to say found on their compile classpath.
// api("org.apache.commons:commons-math3:3.6.1")

//// This dependency is used internally, and not exposed to consumers on their own compile classpath.
//implementation("com.google.guava:guava:31.1-jre")
// Align versions of all Kotlin components
// // This dependency is used internally, and not exposed to consumers on their own compile classpath.
// implementation("com.google.guava:guava:31.1-jre")
// Align versions of all Kotlin components
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))

// Use the Kotlin JDK 8 standard library.
Expand All @@ -49,13 +54,30 @@ tasks.named<Test>("test") {
// Use JUnit Platform for unit tests.
useJUnitPlatform()

testLogging {
testLogging {
events(PASSED, SKIPPED, FAILED, STANDARD_OUT, STANDARD_ERROR)
exceptionFormat = FULL
showExceptions = true
showCauses = true
showStackTraces = true
showStandardStreams = true
showStandardStreams = true
}
}

tasks.test {
doFirst {
if (project.hasProperty("env") && project.property("env") == "ci") {
environment("BITCOIN_CLI_BIN", "docker exec ldk-node-bitcoin-1 bitcoin-cli")
environment("BITCOIND_RPC_USER", "user")
environment("BITCOIND_RPC_PASSWORD", "pass")
environment("ESPLORA_ENDPOINT", "http://127.0.0.1:3002")
} else {
// Adapt these to your local environment
environment("BITCOIN_CLI_BIN", "bitcoin-cli")
environment("BITCOIND_RPC_USER", "")
environment("BITCOIND_RPC_PASSWORD", "")
environment("ESPLORA_ENDPOINT", "http://127.0.0.1:3002")
}
}
}

Expand Down Expand Up @@ -88,8 +110,8 @@ afterEvaluate {
developers {
developer {
id.set("tnull")
name.set("Elias Rohrer")
email.set("[email protected]")
name.set("Elias Rohrer")
email.set("[email protected]")
}
}
}
Expand All @@ -111,3 +133,11 @@ signing {
// useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword)
sign(publishing.publications)
}

ktlint {
filter {
exclude { entry ->
entry.file.toString().contains("main")
}
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
/*
* This Kotlin source file was generated by the Gradle 'init' task.
*/
package org.lightningdevkit.ldknode

import kotlin.UInt
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.io.path.createTempDirectory
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import kotlin.io.path.createTempDirectory
import kotlin.test.assertEquals

fun runCommandAndWait(vararg cmd: String): String {
println("Running command \"${cmd.joinToString(" ")}\"")
Expand All @@ -24,12 +22,29 @@ fun runCommandAndWait(vararg cmd: String): String {
return stdout + stderr
}

fun bitcoinCli(vararg cmd: String): String {
val bitcoinCliBin = System.getenv("BITCOIN_CLI_BIN")?.split(" ") ?: listOf("bitcoin-cli")
val bitcoinDRpcUser = System.getenv("BITCOIND_RPC_USER") ?: ""
val bitcoinDRpcPassword = System.getenv("BITCOIND_RPC_PASSWORD") ?: ""

val baseCommand = bitcoinCliBin + "-regtest"

val rpcAuth = if (bitcoinDRpcUser.isNotBlank() && bitcoinDRpcPassword.isNotBlank()) {
listOf("-rpcuser=$bitcoinDRpcUser", "-rpcpassword=$bitcoinDRpcPassword")
} else {
emptyList()
}

val fullCommand = baseCommand + rpcAuth + cmd.toList()
return runCommandAndWait(*fullCommand.toTypedArray())
}

fun mine(blocks: UInt): String {
val address = runCommandAndWait("bitcoin-cli", "-regtest", "getnewaddress")
val output = runCommandAndWait("bitcoin-cli", "-regtest", "generatetoaddress", blocks.toString(), address)
val address = bitcoinCli("getnewaddress")
val output = bitcoinCli("generatetoaddress", blocks.toString(), address)
println("Mining output: $output")
val re = Regex("\n.+\n\\]$")
val lastBlock = re.find(output)!!.value.replace("]","").replace("\"", "").replace("\n","").trim()
val lastBlock = re.find(output)!!.value.replace("]", "").replace("\"", "").replace("\n", "").trim()
println("Last block: $lastBlock")
return lastBlock
}
Expand All @@ -41,54 +56,56 @@ fun mineAndWait(esploraEndpoint: String, blocks: UInt) {

fun sendToAddress(address: String, amountSats: UInt): String {
val amountBtc = amountSats.toDouble() / 100000000.0
val output = runCommandAndWait("bitcoin-cli", "-regtest", "sendtoaddress", address, amountBtc.toString())
val output = bitcoinCli("sendtoaddress", address, amountBtc.toString())
return output
}

fun setup() {
runCommandAndWait("bitcoin-cli", "-regtest", "createwallet", "ldk_node_test")
runCommandAndWait("bitcoin-cli", "-regtest", "loadwallet", "ldk_node_test", "true")
mine(101u)
Thread.sleep(5_000)
}

fun waitForTx(esploraEndpoint: String, txid: String) {
var esploraPickedUpTx = false
val re = Regex("\"txid\":\"$txid\"");
val re = Regex("\"txid\":\"$txid\"")
while (!esploraPickedUpTx) {
val client = HttpClient.newBuilder().build()
val request = HttpRequest.newBuilder()
.uri(URI.create(esploraEndpoint + "/tx/" + txid))
.build();
.build()

val response = client.send(request, HttpResponse.BodyHandlers.ofString());
val response = client.send(request, HttpResponse.BodyHandlers.ofString())

esploraPickedUpTx = re.containsMatchIn(response.body());
esploraPickedUpTx = re.containsMatchIn(response.body())
Thread.sleep(500)
}
}

fun waitForBlock(esploraEndpoint: String, blockHash: String) {
var esploraPickedUpBlock = false
val re = Regex("\"in_best_chain\":true");
val re = Regex("\"in_best_chain\":true")
while (!esploraPickedUpBlock) {
val client = HttpClient.newBuilder().build()
val request = HttpRequest.newBuilder()
.uri(URI.create(esploraEndpoint + "/block/" + blockHash + "/status"))
.build();
.build()

val response = client.send(request, HttpResponse.BodyHandlers.ofString());
val response = client.send(request, HttpResponse.BodyHandlers.ofString())

esploraPickedUpBlock = re.containsMatchIn(response.body());
esploraPickedUpBlock = re.containsMatchIn(response.body())
Thread.sleep(500)
}
}

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class LibraryTest {
@Test fun fullCycle() {
val esploraEndpoint = "http://127.0.0.1:3002"
setup()

val esploraEndpoint = System.getenv("ESPLORA_ENDPOINT")

@BeforeAll
fun setup() {
bitcoinCli("createwallet", "ldk_node_test")
bitcoinCli("loadwallet", "ldk_node_test", "true")
mine(101u)
Thread.sleep(5_000)
}

@Test fun fullCycle() {
val tmpDir1 = createTempDirectory("ldk_node").toString()
println("Random dir 1: $tmpDir1")
val tmpDir2 = createTempDirectory("ldk_node").toString()
Expand Down Expand Up @@ -172,7 +189,7 @@ class LibraryTest {

val fundingTxid = when (channelPendingEvent1) {
is Event.ChannelPending -> channelPendingEvent1.fundingTxo.txid
else -> return
else -> return
}

waitForTx(esploraEndpoint, fundingTxid)
Expand Down Expand Up @@ -202,7 +219,7 @@ class LibraryTest {

val channelId = when (channelReadyEvent2) {
is Event.ChannelReady -> channelReadyEvent2.channelId
else -> return
else -> return
}

val invoice = node2.receivePayment(2500000u, "asdf", 9217u)
Expand Down
53 changes: 53 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
version: '3'

services:
bitcoin:
image: blockstream/bitcoind:24.1
platform: linux/amd64
command:
[
"bitcoind",
"-printtoconsole",
"-regtest=1",
"-rpcallowip=0.0.0.0/0",
"-rpcbind=0.0.0.0",
"-rpcuser=user",
"-rpcpassword=pass",
"-fallbackfee=0.00001"
]
ports:
- "18443:18443" # Regtest RPC port
- "18444:18444" # Regtest P2P port
networks:
- bitcoin-electrs
healthcheck:
test: ["CMD", "bitcoin-cli", "-regtest", "-rpcuser=user", "-rpcpassword=pass", "getblockchaininfo"]
interval: 5s
timeout: 10s
retries: 5

electrs:
image: blockstream/esplora:electrs-cd9f90c115751eb9d2bca9a4da89d10d048ae931
platform: linux/amd64
depends_on:
bitcoin:
condition: service_healthy
command:
[
"/app/electrs_bitcoin/bin/electrs",
"-vvvv",
"--timestamp",
"--jsonrpc-import",
"--cookie=user:pass",
"--network=regtest",
"--daemon-rpc-addr=bitcoin:18443",
"--http-addr=0.0.0.0:3002"
]
ports:
- "3002:3002"
networks:
- bitcoin-electrs

networks:
bitcoin-electrs:
driver: bridge
15 changes: 15 additions & 0 deletions scripts/format_kotlin.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash
LDK_NODE_ANDROID_DIR="bindings/kotlin/ldk-node-android"
LDK_NODE_JVM_DIR="bindings/kotlin/ldk-node-jvm"

# Run ktlintFormat in ldk-node-android
(
cd $LDK_NODE_ANDROID_DIR || exit 1
./gradlew ktlintFormat
)

# Run ktlintFormat in ldk-node-jvm
(
cd $LDK_NODE_JVM_DIR || exit 1
./gradlew ktlintFormat
)

0 comments on commit d955e62

Please sign in to comment.