Skip to content

Commit

Permalink
More tests
Browse files Browse the repository at this point in the history
  • Loading branch information
trueangle committed Aug 27, 2024
1 parent ef33a73 commit c50c9b4
Show file tree
Hide file tree
Showing 11 changed files with 434 additions and 154 deletions.
11 changes: 3 additions & 8 deletions lambda-runtime/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.mokkery)
alias(libs.plugins.allopen)
alias(libs.plugins.kotlinx.resources)
}

kotlin {
Expand All @@ -31,20 +31,15 @@ kotlin {
}

nativeTest.dependencies {
implementation(projects.lambdaEvents)
implementation(libs.kotlin.test)
implementation(libs.kotlin.coroutines.test)
implementation(libs.ktor.client.mock)
implementation(libs.kotlinx.resources)
}
}
}

fun isTestingTask(name: String) = name.endsWith("Test")
val isTesting = gradle.startParameter.taskNames.any(::isTestingTask)

if (isTesting) allOpen {
annotation("kotlin.Metadata")
}

mokkery {
defaultMockMode.set(MockMode.autoUnit)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ internal fun Throwable.prettyPrint(includeStackTrace: Boolean = true) = buildStr
}
}

internal fun <T> unsafeLazy(initializer: () -> T): Lazy<T> = lazy(LazyThreadSafetyMode.NONE, initializer)
internal fun <T> unsafeLazy(initializer: () -> T): Lazy<T> = lazy(LazyThreadSafetyMode.NONE, initializer)
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,40 @@ import io.github.trueangle.knative.lambda.runtime.ReservedRuntimeEnvironmentVari
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.toKString
import platform.posix.getenv
import kotlin.system.exitProcess

@OptIn(ExperimentalForeignApi::class)
@PublishedApi
internal object LambdaEnvironment {
val FUNCTION_MEMORY_SIZE by unsafeLazy {
getenv(AWS_LAMBDA_FUNCTION_MEMORY_SIZE)?.toKString()?.toIntOrNull() ?: 128
}
val LOG_GROUP_NAME by unsafeLazy {
getenv(AWS_LAMBDA_LOG_GROUP_NAME)?.toKString().orEmpty()
}
val LOG_STREAM_NAME by unsafeLazy {
getenv(AWS_LAMBDA_LOG_STREAM_NAME)?.toKString().orEmpty()
}
val LAMBDA_LOG_LEVEL by unsafeLazy {
getenv(AWS_LAMBDA_LOG_LEVEL)?.toKString() ?: "INFO"
}
val LAMBDA_LOG_FORMAT by unsafeLazy {
getenv(AWS_LAMBDA_LOG_FORMAT)?.toKString() ?: "TEXT"
}
val FUNCTION_NAME by unsafeLazy {
getenv(AWS_LAMBDA_FUNCTION_NAME)?.toKString().orEmpty()
}
val FUNCTION_VERSION by unsafeLazy {
getenv(AWS_LAMBDA_FUNCTION_VERSION)?.toKString().orEmpty()
}
val RUNTIME_API by unsafeLazy {
getenv(AWS_LAMBDA_RUNTIME_API)?.toKString()
internal open class LambdaEnvironment {
// open due to Mokkery limits
open fun terminate(): Nothing = exitProcess(1)

@OptIn(ExperimentalForeignApi::class)
@PublishedApi
internal companion object Variables {
val FUNCTION_MEMORY_SIZE by unsafeLazy {
getenv(AWS_LAMBDA_FUNCTION_MEMORY_SIZE)?.toKString()?.toIntOrNull() ?: 128
}
val LOG_GROUP_NAME by unsafeLazy {
getenv(AWS_LAMBDA_LOG_GROUP_NAME)?.toKString().orEmpty()
}
val LOG_STREAM_NAME by unsafeLazy {
getenv(AWS_LAMBDA_LOG_STREAM_NAME)?.toKString().orEmpty()
}
val LAMBDA_LOG_LEVEL by unsafeLazy {
getenv(AWS_LAMBDA_LOG_LEVEL)?.toKString() ?: "INFO"
}
val LAMBDA_LOG_FORMAT by unsafeLazy {
getenv(AWS_LAMBDA_LOG_FORMAT)?.toKString() ?: "TEXT"
}
val FUNCTION_NAME by unsafeLazy {
getenv(AWS_LAMBDA_FUNCTION_NAME)?.toKString().orEmpty()
}
val FUNCTION_VERSION by unsafeLazy {
getenv(AWS_LAMBDA_FUNCTION_VERSION)?.toKString().orEmpty()
}
val RUNTIME_API by unsafeLazy {
getenv(AWS_LAMBDA_RUNTIME_API)?.toKString()
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.github.trueangle.knative.lambda.runtime
import io.github.trueangle.knative.lambda.runtime.LambdaEnvironmentException.NonRecoverableStateException
import io.github.trueangle.knative.lambda.runtime.api.Context
import io.github.trueangle.knative.lambda.runtime.api.LambdaClient
import io.github.trueangle.knative.lambda.runtime.api.LambdaClientImpl
import io.github.trueangle.knative.lambda.runtime.handler.LambdaBufferedHandler
import io.github.trueangle.knative.lambda.runtime.handler.LambdaHandler
import io.github.trueangle.knative.lambda.runtime.handler.LambdaStreamHandler
Expand All @@ -29,15 +30,14 @@ import io.ktor.utils.io.writeStringUtf8
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlin.system.exitProcess

object LambdaRuntime {
@OptIn(ExperimentalSerializationApi::class)
internal val json = Json { explicitNulls = false }

inline fun <reified I, reified O> run(crossinline initHandler: () -> LambdaHandler<I, O>) = runBlocking {
val curlHttpClient = createHttpClient(Curl.create())
val lambdaClient = LambdaClient(curlHttpClient)
val lambdaClient = LambdaClientImpl(curlHttpClient)

Runner(client = lambdaClient, log = Log).run(false, initHandler)
}
Expand All @@ -59,7 +59,8 @@ object LambdaRuntime {
@PublishedApi
internal class Runner(
val client: LambdaClient,
val log: LambdaLogger
val log: LambdaLogger,
val env: LambdaEnvironment = LambdaEnvironment()
) {
suspend inline fun <reified I, reified O> run(singleEventMode: Boolean = false, crossinline initHandler: () -> LambdaHandler<I, O>) {
val handler = try {
Expand All @@ -70,7 +71,8 @@ internal class Runner(
log.fatal(e)

client.reportError(e.asInitError())
exitProcess(1)

env.terminate()
}

val handlerName = handler::class.simpleName
Expand Down Expand Up @@ -116,15 +118,15 @@ internal class Runner(
is NonRecoverableStateException -> {
log.fatal(e)

exitProcess(1)
env.terminate()
}

else -> log.error(e)
}
} catch (e: Throwable) {
log.fatal(e)

exitProcess(1)
env.terminate()
}

if (singleEventMode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,22 @@ import kotlin.time.Duration.Companion.minutes
import io.ktor.http.ContentType.Application.Json as ContentTypeJson

@PublishedApi
internal class LambdaClient(private val httpClient: HttpClient) {
internal interface LambdaClient {
suspend fun <T> retrieveNextEvent(bodyType: TypeInfo): Pair<T, Context>
suspend fun <T> sendResponse(event: Context, body: T, bodyType: TypeInfo): HttpResponse
suspend fun streamResponse(event: Context, outgoingContent: OutgoingContent): HttpResponse
suspend fun reportError(error: LambdaRuntimeException)
}

@PublishedApi
internal class LambdaClientImpl(private val httpClient: HttpClient): LambdaClient {
private val baseUrl = requireNotNull(LambdaEnvironment.RUNTIME_API) {
"Can't find AWS_LAMBDA_RUNTIME_API env variable"
}
private val invokeUrl = "http://$baseUrl/2018-06-01/runtime"
private val requestTimeout = 15.minutes.inWholeMilliseconds

suspend fun <T> retrieveNextEvent(bodyType: TypeInfo): Pair<T, Context> {
override suspend fun <T> retrieveNextEvent(bodyType: TypeInfo): Pair<T, Context> {
val response = httpClient.get {
url("${invokeUrl}/invocation/next")

Expand All @@ -50,7 +58,7 @@ internal class LambdaClient(private val httpClient: HttpClient) {
return body to context
}

suspend fun <T> sendResponse(event: Context, body: T, bodyType: TypeInfo): HttpResponse {
override suspend fun <T> sendResponse(event: Context, body: T, bodyType: TypeInfo): HttpResponse {
val response = httpClient.post {
url("${invokeUrl}/invocation/${event.awsRequestId}/response")
contentType(ContentTypeJson)
Expand All @@ -67,7 +75,7 @@ internal class LambdaClient(private val httpClient: HttpClient) {
return validateResponse(response)
}

suspend fun streamResponse(event: Context, outgoingContent: OutgoingContent): HttpResponse {
override suspend fun streamResponse(event: Context, outgoingContent: OutgoingContent): HttpResponse {
val response = httpClient.post {
url("${invokeUrl}/invocation/${event.awsRequestId}/response")

Expand All @@ -92,13 +100,15 @@ internal class LambdaClient(private val httpClient: HttpClient) {
return response
}

suspend fun reportError(error: LambdaRuntimeException) {
val response = when (error) {
override suspend fun reportError(error: LambdaRuntimeException) {
when (error) {
is LambdaRuntimeException.Init -> sendInitError(error)
is LambdaRuntimeException.Invocation -> sendInvocationError(error)
}
is LambdaRuntimeException.Invocation -> {
val response = sendInvocationError(error)

validateResponse(response)
validateResponse(response)
}
}
}

private suspend fun sendInvocationError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ enum class LogLevel {

companion object {
fun fromEnv(): LogLevel {
val level = LambdaEnvironment.LAMBDA_LOG_LEVEL ?: "INFO"
val level = LambdaEnvironment.LAMBDA_LOG_LEVEL
return runCatching {
valueOf(level)
}.getOrElse {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,5 @@ class JsonLogFormatterTest {
assertEquals(expected, actual)
}

@Serializable
private data class SampleObject(val hello: String)
private data class NonSerialObject(val hello: String)
}

This file was deleted.

Loading

0 comments on commit c50c9b4

Please sign in to comment.