Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CI Checks for Kotlin #186

Merged
merged 3 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
alexanderwiederin marked this conversation as resolved.
Show resolved Hide resolved
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
)