Skip to content

Commit

Permalink
KTOR-3420 Add integration tests for generating and running projects
Browse files Browse the repository at this point in the history
  • Loading branch information
Ololoshechkin committed Dec 17, 2021
1 parent 64ac1e7 commit 0969e9f
Show file tree
Hide file tree
Showing 12 changed files with 236 additions and 91 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
.idea

# AWS User-specific
.idea/**/aws.xml
Expand Down
33 changes: 14 additions & 19 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,6 @@ repositories {
}

kotlin {
val hostOs = System.getProperty("os.name")
val isMingwX64 = hostOs.startsWith("Windows")
val nativeTarget = when {
hostOs == "Mac OS X" -> macosX64("native")
hostOs == "Linux" -> linuxX64("native")
isMingwX64 -> mingwX64("native")
else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
}

nativeTarget.apply {
binaries {
executable {
entryPoint = "main"
}
}

}
linuxX64 {
binaries {
executable {
Expand All @@ -59,7 +42,7 @@ kotlin {
compilations["main"].enableEndorsedLibs = true
}
sourceSets {
val nativeMain by getting {
val nativeMain by creating {
dependencies {
implementation("io.ktor:ktor-client-core:$ktor_version")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2-native-mt")
Expand All @@ -69,7 +52,11 @@ kotlin {
implementation("org.jetbrains.kotlinx:kotlinx-cli:$kotlinx_cli_version")
}
}
val nativeTest by getting
val nativeTest by creating {
dependencies {
implementation(kotlin("test"))
}
}

val linuxX64Main by getting
val linuxX64Test by getting
Expand All @@ -87,5 +74,13 @@ kotlin {
).forEach { module ->
module.dependsOn(nativeMain)
}

listOf(
linuxX64Test,
mingwX64Test,
macosX64Test
).forEach { module ->
module.dependsOn(nativeTest)
}
}
}
4 changes: 3 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ kotlin.code.style=official
kotlin.mpp.enableGranularSourceSetsMetadata=true
kotlin.native.enableDependencyPropagation=false
ktor_version=1.6.4
kotlinx_cli_version=0.3.3
kotlinx_cli_version=0.3.3
kotlin.mpp.enableCInteropCommonization=true
kotlin.internal.mpp.hierarchicalStructureByDefault=true
Binary file added gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
45 changes: 21 additions & 24 deletions src/nativeMain/kotlin/CliGeneratorMain.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,73 +2,70 @@ import io.ktor.client.*
import io.ktor.client.features.*
import io.ktor.client.features.json.*
import io.ktor.client.features.json.serializer.*
import io.ktor.generator.api.*
import io.ktor.generator.bundle.*
import io.ktor.generator.cli.installer.*
import kotlinx.cli.*

private const val DEFAULT_KTOR_URL = "https://ktor-plugin.europe-north1-gke.intellij.net"
const val DEFAULT_KTOR_URL = "https://ktor-plugin.europe-north1-gke.intellij.net"

@OptIn(ExperimentalCli::class)
abstract class KtorCommand(
name: String,
description: String,
client: HttpClient
name: String, description: String, client: HttpClient
) : Subcommand(name, description) {
private val host: String by option(
ArgType.String,
fullName = "host",
description = PropertiesBundle.message("ktor.backend.url.description")
ArgType.String, fullName = "host", description = PropertiesBundle.message("ktor.backend.url.description")
).default(DEFAULT_KTOR_URL)

protected val projectName: String by argument(
ArgType.String, description = PropertiesBundle.message("project.name.description")
)

protected val ktorInstaller: KtorInstaller by lazy { KtorInstaller(client, host) }
protected val ktorInstaller: KtorInstaller by lazy { KtorInstaller(KtorGeneratorWebImpl(client, host)) }
}

class GenerateProject(client: HttpClient) : KtorCommand(
"generate",
description = PropertiesBundle.message("generate.command.description"),
client = client
"generate", description = PropertiesBundle.message("generate.command.description"), client = client
) {
override fun execute() {
ktorInstaller.downloadKtorProject(projectName)
}
}

class RunProject(client: HttpClient) : KtorCommand(
"run",
description = PropertiesBundle.message("run.command.description"),
client = client
"run", description = PropertiesBundle.message("run.command.description"), client = client
) {
private val args: List<String> by argument(
ArgType.String,
fullName = "args",
description = PropertiesBundle.message("run.arguments.description")
ArgType.String, fullName = "args", description = PropertiesBundle.message("run.arguments.description")
).vararg()

override fun execute() {
ktorInstaller.runKtorProject(projectName, args)
}
}

@OptIn(ExperimentalCli::class)
class KtorParser(client: HttpClient) : ArgParser(PropertiesBundle.message("program.name")) {
init {
subcommands(GenerateProject(client), RunProject(client))
}
}

fun createHttpClient(): HttpClient = HttpClient {
install(JsonFeature) {
serializer = KotlinxSerializer()
}
install(BodyProgress)
}

@OptIn(ExperimentalCli::class)
fun main(args: Array<String>) {
val client = HttpClient {
install(JsonFeature) {
serializer = KotlinxSerializer()
}
install(BodyProgress)
}
val client = createHttpClient()

val parser = KtorParser(client)

if (args.isEmpty()) {
parser.parse(arrayOf("--help"))
return
}
parser.parse(args)
}
42 changes: 42 additions & 0 deletions src/nativeMain/kotlin/io/ktor/generator/api/KtorWebGenerator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.ktor.generator.api

import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.generator.cli.installer.*
import io.ktor.generator.cli.utils.*
import io.ktor.generator.configuration.json.*
import io.ktor.http.*

interface KtorGeneratorWeb {
/**
* Downloads jdk archive and returns its byte content
* */
suspend fun downloadJdkArchive(): ByteArray

/**
* Generates Ktor project and returns its byte content
* */
suspend fun generateKtorProject(configuration: SelectedProjectConfiguration): ByteArray

/**
* Generates Ktor project and returns its byte content
* */
suspend fun genProjectSettings(): ProjectSettingsTemplate
}

class KtorGeneratorWebImpl(val client: HttpClient, private val ktorBackendHost: String) : KtorGeneratorWeb {
override suspend fun downloadJdkArchive(): ByteArray = client.fetchZipContent(jdkDownloadUrl)

override suspend fun generateKtorProject(configuration: SelectedProjectConfiguration): ByteArray =
client.fetchZipContent(
"$ktorBackendHost/project/generate",
httpMethod = HttpMethod.Post
) {
contentType(ContentType.Application.Json)

body = configuration
}

override suspend fun genProjectSettings(): ProjectSettingsTemplate =
client.get("$ktorBackendHost/project/settings")
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ object PropertiesBundle {

assertNotNull(argId, "Bad argument format in $property")
assert(argId >= 0) { "Negative argument index in $property: $argId. Must be non-negative" }
assert(argId < args.size) { "Insufficient number of arguments provided for $property. Required at least $argId but only ${args.size} found" }
assert(argId < args.size) { "Insufficient number of arguments provided for $property. Required at least ${argId + 1} but only ${args.size} found" }

return@replace args[argId]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
package io.ktor.generator.cli.installer

import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.generator.api.*
import io.ktor.generator.bundle.*
import io.ktor.generator.cli.utils.*
import io.ktor.generator.configuration.json.*
import io.ktor.http.*
import kotlinx.cinterop.toKString
import kotlinx.coroutines.runBlocking
import platform.posix.*
import platform.posix.chdir
import platform.posix.setenv

class KtorInstaller(private val client: HttpClient, private val ktorBackendHost: String) {
class KtorInstaller(private val service: KtorGeneratorWeb) {
private val ktorRootDir: Directory by lazy { Directory.home().createDirIfNeeded(rootKtorDirName) }
private val ktorRcFile: File by lazy { ktorRootDir.createFileIfNeeded(KTOR_RC_FILENAME) }

Expand Down Expand Up @@ -71,7 +69,7 @@ class KtorInstaller(private val client: HttpClient, private val ktorBackendHost:

PropertiesBundle.writeMessage("jdk.11.not.found", jdkDownloadUrl)
runBlocking {
client.downloadZip(jdkDownloadUrl, jdkArchiveFile)
jdkArchiveFile.writeContent(service.downloadJdkArchive())
}
PropertiesBundle.writeMessage("jdk.installed.success")

Expand All @@ -97,27 +95,20 @@ class KtorInstaller(private val client: HttpClient, private val ktorBackendHost:

PropertiesBundle.writeMessage("generating.project")
runBlocking {
val defaultKtorVersion =
client.get<ProjectSettingsTemplate>("$ktorBackendHost/project/settings").ktorVersion.default

client.downloadZip(
"$ktorBackendHost/project/generate",
projectZip,
httpMethod = HttpMethod.Post
) {
contentType(ContentType.Application.Json)

body = SelectedProjectConfiguration(
settings = ProjectSettings(
name = projectName,
companyWebsite = "example.com",
ktorEngine = "NETTY",
buildSystemType = "GRADLE_KTS",
ktorVersion = defaultKtorVersion,
kotlinVersion = "LAST_KOTLIN_VERSION",
), features = emptyList(), addWrapper = true
)
}
val defaultKtorVersion = service.genProjectSettings().ktorVersion.default

val projectConfiguration = SelectedProjectConfiguration(
settings = ProjectSettings(
name = projectName,
companyWebsite = "example.com",
ktorEngine = "NETTY",
buildSystemType = "GRADLE_KTS",
ktorVersion = defaultKtorVersion,
kotlinVersion = "LAST_KOTLIN_VERSION",
), features = emptyList(), addWrapper = true
)

projectZip.writeContent(service.generateKtorProject(projectConfiguration))
}

val projectDir = currentDir.createDirIfNeeded(projectName)
Expand All @@ -132,6 +123,7 @@ class KtorInstaller(private val client: HttpClient, private val ktorBackendHost:

chdir(projectDir.path)
runGradle(gradleFile, GRADLE_BUILD, ktorJavaHome)
chdir(currentDir.path)

PropertiesBundle.writeMessage("project.generated", projectName)
}
Expand All @@ -140,15 +132,17 @@ class KtorInstaller(private val client: HttpClient, private val ktorBackendHost:
installJdkIfAbsent()
val ktorJavaHome = getRcProperty(JAVA_HOME)!!

val projectDir = Directory.current().subdir(path)
val currentDir = Directory.current()
val projectDir = currentDir.subdir(path)
if (!projectDir.exists()) {
PropertiesBundle.writeMessage("project.not.exists")
PropertiesBundle.writeMessage("project.not.exists", path)
return
}

val gradleFile = projectDir.gradleWrapper() ?: return
chdir(projectDir.path)
runGradle(gradleFile, GRADLE_RUN, ktorJavaHome, args)
chdir(currentDir.path)
}

companion object {
Expand Down
20 changes: 8 additions & 12 deletions src/nativeMain/kotlin/io/ktor/generator/cli/utils/ClientUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,14 @@ import io.ktor.http.*
import io.ktor.util.*
import io.ktor.utils.io.*

suspend fun HttpClient.downloadZip(
suspend fun HttpClient.fetchZipContent(
urlString: String,
zipFile: File,
httpMethod: HttpMethod = HttpMethod.Get,
block: HttpRequestBuilder.() -> Unit = {}
) {
val zipBytes =
request<HttpStatement> {
url.takeFrom(urlString)
method = httpMethod
apply(block)
}
.receive<ByteArray>()
zipFile.writeContent(zipBytes)
}
): ByteArray =
request<HttpStatement> {
url.takeFrom(urlString)
method = httpMethod
apply(block)
}
.receive()
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,18 @@ data class Directory(override val path: String) : FsUnit {
}
}

fun createFileIfNeeded(name: String): File {
fun file(name: String): File {
val filePath = calcEntryPath(name)
if (!FileSystem.SYSTEM.exists(filePath.toPath())) {
FileSystem.SYSTEM.sink(filePath.toPath())
return File(filePath)
}

fun createFileIfNeeded(name: String): File {
val newFile = file(name)
if (!newFile.exists()) {
FileSystem.SYSTEM.sink(newFile.path.toPath())
}

return File(filePath)
return newFile
}

fun subdir(name: String): Directory {
Expand All @@ -69,6 +74,10 @@ data class Directory(override val path: String) : FsUnit {
return dir
}

fun delete() {
FileSystem.SYSTEM.deleteRecursively(path.toPath(), false)
}

companion object {
fun home(): Directory = Directory(homePath())

Expand Down
Loading

0 comments on commit 0969e9f

Please sign in to comment.