diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 0307b48..e562363 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -10,7 +10,6 @@
-
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index fe63bb6..6d0ee1c 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index ab930d2..55f1f21 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -5,12 +5,28 @@
-
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -82,8 +98,8 @@
@@ -99,55 +115,59 @@
- {
- "keyToString": {
- "Gradle.Build OpenTestLabAndroid.executor": "Run",
- "Gradle.OpenTestLabAndroid [build].executor": "Run",
- "Gradle.OpenTestLabAndroid [clean].executor": "Run",
- "Gradle.OpenTestLabAndroid [publishToMavenLocal].executor": "Run",
- "Gradle.OpenTestLabAndroid [run].executor": "Run",
- "Gradle.OpenTestLabAndroid:androidTestLibrary [build].executor": "Run",
- "Gradle.OpenTestLabAndroid:androidTestLibrary [publishToMavenLocal].executor": "Run",
- "Gradle.OpenTestLabAndroid:androidplugin [build].executor": "Run",
- "Gradle.OpenTestLabAndroid:androidplugin [publishPluginMavenPublicationToMavenLocalRepository].executor": "Run",
- "Gradle.OpenTestLabAndroid:androidplugin [publishToMavenLocal].executor": "Run",
- "Kotlin.MainKt.executor": "Run",
- "RunOnceActivity.ShowReadmeOnStart": "true",
- "RunOnceActivity.cidr.known.project.marker": "true",
- "RunOnceActivity.readMode.enableVisualFormatting": "true",
- "cf.first.check.clang-format": "false",
- "cidr.known.project.marker": "true",
- "dart.analysis.tool.window.visible": "false",
- "git-widget-placeholder": "master",
- "jdk.selected.JAVA_MODULE": "corretto-17",
- "kotlin-language-version-configured": "true",
- "last_opened_file_path": "C:/Users/anmol/StudioProjects/OpenTestLabAndroid/sampleandroidapp",
- "node.js.detected.package.eslint": "true",
- "node.js.selected.package.eslint": "(autodetect)",
- "node.js.selected.package.tslint": "(autodetect)",
- "nodejs_package_manager_path": "npm",
- "project.structure.last.edited": "Modules",
- "project.structure.proportion": "0.0",
- "project.structure.side.proportion": "0.0",
- "settings.editor.selected.configurable": "experimental",
- "show.migrate.to.gradle.popup": "false",
- "vue.rearranger.settings.migration": "true"
+
+}]]>
+
+
+
+
-
+
@@ -245,9 +265,9 @@
+
-
@@ -273,7 +293,9 @@
-
+
+
+
@@ -367,14 +389,9 @@
file://$PROJECT_DIR$/src/main/kotlin/dev/oianmol/opentestlab/android/testexec/TestExecutionService.kt
- 52
+ 51
-
- file://$PROJECT_DIR$/src/main/kotlin/dev/oianmol/opentestlab/android/devicefarm/DeviceFarmService.kt
- 13
-
-
file://$PROJECT_DIR$/src/main/kotlin/dev/oianmol/opentestlab/android/devicefarm/cli/Adb.kt
56
diff --git a/androidplugin/build.gradle.kts b/androidplugin/build.gradle.kts
index 5e207ab..5666b17 100644
--- a/androidplugin/build.gradle.kts
+++ b/androidplugin/build.gradle.kts
@@ -1,9 +1,8 @@
plugins {
`kotlin-dsl`
- kotlin("jvm") version "1.9.23"
+ kotlin("jvm") version "2.0.0"
id("java-gradle-plugin")
id("maven-publish")
- id("com.google.protobuf") version "0.9.0"
}
group = "dev.oianmol"
@@ -21,14 +20,6 @@ publishing {
}
}
-object Versions {
- const val GRPC = "1.57.2"
- const val GRPC_KOTLIN = "1.3.1"
- const val PROTOBUF = "3.24.2"
- const val COROUTINES = "1.7.3"
-}
-
-
repositories {
google()
gradlePluginPortal()
@@ -41,51 +32,12 @@ repositories {
dependencies {
compileOnly("com.android.tools.build:gradle:3.6.1")
-
compileOnly("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23")
- protobuf(project(":protos"))
compileOnly("org.apache.tomcat:annotations-api:6.0.53")
- implementation("io.grpc:grpc-netty:1.57.2")
- implementation("io.grpc:grpc-stub:1.57.2")
- implementation("io.grpc:grpc-protobuf-lite:1.57.2")
- implementation("com.google.protobuf:protobuf-java-util:3.24.3")
- implementation("com.google.protobuf:protobuf-kotlin:3.24.3")
- implementation("io.grpc:grpc-kotlin-stub:1.3.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.12.7")
}
-protobuf {
- protoc {
- artifact = "com.google.protobuf:protoc:3.24.3"
- }
- plugins {
- create("grpc") {
- artifact = "io.grpc:protoc-gen-grpc-java:1.57.2"
- }
- create("grpckt") {
- artifact = "io.grpc:protoc-gen-grpc-kotlin:1.3.1:jdk8@jar"
- }
- }
- generateProtoTasks {
- all().forEach {
- it.builtins {
- named("java") {
- option("lite")
- }
- }
- it.plugins {
- create("grpc"){
- option("lite")
- }
- create("grpckt")
- }
- }
- }
-}
-
-
-
gradlePlugin {
plugins {
register("androidDeviceFarm") {
diff --git a/build.gradle.kts b/build.gradle.kts
index 8752021..859068c 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,22 +1,25 @@
-import com.google.protobuf.gradle.id
+val kotlin_version: String by project
+val logback_version: String by project
+val postgres_version: String by project
+val h2_version: String by project
+val exposed_version: String by project
+
plugins {
- kotlin("jvm") version "1.9.23"
- id("java")
- application
- id("com.google.protobuf") version "0.9.0"
+ kotlin("jvm") version "2.0.0"
+ id("io.ktor.plugin") version "2.3.12"
+ id("org.jetbrains.kotlin.plugin.serialization") version "2.0.0"
}
group = "dev.oianmol"
version = "1.0-SNAPSHOT"
-object Versions {
- const val GRPC = "1.57.2"
- const val GRPC_KOTLIN = "1.3.1"
- const val PROTOBUF = "3.24.2"
- const val COROUTINES = "1.7.3"
-}
+application {
+ mainClass.set("dev.oianmol.opentestlab")
+ val isDevelopment: Boolean = project.ext.has("development")
+ applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment")
+}
repositories {
mavenCentral()
@@ -24,63 +27,43 @@ repositories {
}
dependencies {
- protobuf(project(":protos"))
- implementation("io.insert-koin:koin-core:3.4.0")
- implementation("io.grpc:grpc-netty-shaded:1.57.2")
- implementation("com.google.protobuf:protobuf-java-util:3.24.2")
- implementation("com.google.protobuf:protobuf-kotlin:3.24.2")
- implementation("io.grpc:grpc-protobuf:1.57.2")
- implementation("io.grpc:grpc-stub:1.57.2")
- implementation("io.grpc:grpc-kotlin-stub:1.3.1")
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
+ implementation("io.insert-koin:koin-ktor:3.5.3")
+ implementation("io.insert-koin:koin-core:3.5.3")
+
+ implementation("io.ktor:ktor-server-core-jvm")
+ implementation("io.ktor:ktor-server-auth-jvm")
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.12.7")
implementation("javax.xml.stream:stax-api:1.0-2")
- testImplementation(kotlin("test"))
+ implementation("org.postgresql:postgresql:$postgres_version")
+ implementation("com.h2database:h2:$h2_version")
+ implementation("org.jetbrains.exposed:exposed-core:$exposed_version")
+ implementation("org.jetbrains.exposed:exposed-jdbc:$exposed_version")
+ implementation("org.jetbrains.exposed:exposed-dao:$exposed_version")
+ implementation("org.jetbrains.exposed:exposed-java-time:$exposed_version")
+ implementation("com.zaxxer:HikariCP:4.0.3")
+ implementation("org.slf4j:slf4j-api:1.7.30")
+ implementation("ch.qos.logback:logback-classic:1.2.3")
+
+ implementation("io.ktor:ktor-server-auto-head-response-jvm")
+ implementation("io.ktor:ktor-server-webjars-jvm")
+ implementation("io.github.smiley4:ktor-swagger-ui:2.9.0")
+ implementation("io.ktor:ktor-server-resources-jvm")
+ implementation("io.ktor:ktor-server-host-common-jvm")
+ implementation("io.ktor:ktor-server-status-pages-jvm")
+ implementation("io.ktor:ktor-server-cors-jvm")
+ implementation("io.ktor:ktor-server-openapi")
+ implementation("io.ktor:ktor-server-partial-content-jvm")
+ implementation("io.ktor:ktor-server-swagger-jvm")
+ implementation("io.ktor:ktor-server-call-logging-jvm")
+ implementation("io.ktor:ktor-server-content-negotiation-jvm")
+ implementation("io.ktor:ktor-serialization-kotlinx-json-jvm")
+
+ implementation("io.ktor:ktor-server-cio-jvm")
+ implementation("ch.qos.logback:logback-classic:$logback_version")
+ testImplementation("io.ktor:ktor-server-tests-jvm")
testImplementation("io.mockk:mockk:1.13.7")
testImplementation("app.cash.turbine:turbine:1.0.0")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
- testImplementation("io.grpc:grpc-testing:1.51.0")
-}
-
-protobuf {
- protoc {
- artifact = "com.google.protobuf:protoc:${Versions.PROTOBUF}"
- }
-
- plugins {
- id("grpc") {
- artifact = "io.grpc:protoc-gen-grpc-java:${Versions.GRPC}"
- }
- id("grpckt") {
- artifact = "io.grpc:protoc-gen-grpc-kotlin:${Versions.GRPC_KOTLIN}:jdk8@jar"
- }
- }
- generateProtoTasks {
- ofSourceSet("main").forEach {
- it.plugins {
- id("grpc") {
- option("lite")
- }
- id("grpckt") {
- option("lite")
- }
- }
-
- it.builtins {
- id("kotlin") {
- option("lite")
- }
- }
- }
- }
-}
-
-tasks.test {
- useJUnitPlatform()
-}
-
-application {
- mainClass.set("MainKt")
}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index 7fc6f1f..e0df2ca 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1 +1,8 @@
kotlin.code.style=official
+
+ktor_version=2.3.12
+kotlin_version=2.0.0
+logback_version=1.4.14
+postgres_version=42.5.1
+h2_version=2.1.214
+exposed_version=0.41.1
diff --git a/protos/build.gradle.kts b/protos/build.gradle.kts
deleted file mode 100644
index 5b605af..0000000
--- a/protos/build.gradle.kts
+++ /dev/null
@@ -1,8 +0,0 @@
-version = "unspecified"
-plugins {
- `java-library`
-}
-
-java {
- sourceSets.getByName("main").resources.srcDir("src/main/proto")
-}
\ No newline at end of file
diff --git a/protos/src/main/proto/device_farm.proto b/protos/src/main/proto/device_farm.proto
deleted file mode 100644
index eb3b3ac..0000000
--- a/protos/src/main/proto/device_farm.proto
+++ /dev/null
@@ -1,37 +0,0 @@
-syntax = "proto3";
-
-package dev.oianmol.opentestlab;
-
-option java_multiple_files = true;
-
-// Represents a device in the device farm.
-message DeviceFarmDevice {
- string ipAddress = 1; // The IP address of the device.
- string port = 2; // The port of the device.
- string serialNumber = 3; // The serial number of the device.
- string model = 4; // The model of the device.
-}
-
-// Contains information about the device farm, including test machines.
-message DeviceFarmInfo {
- repeated TestMachine machines = 1; // A list of test machines in the device farm.
-}
-
-// Represents a test machine in the system.
-message TestMachine {
- string ipAddress = 1; // The IP address of the test machine.
- string name = 2; // The name of the test machine.
- string location = 3; // The location of the test machine.
- bool isBusy = 4; // Indicates if the test machine is busy.
- repeated DeviceFarmDevice devices = 5; // A list of devices connected to the test machine.
-}
-
-// An empty message typically used for requests that do not need any input.
-message Empty {}
-
-// Enum representing the type of file being uploaded.
-enum FileType {
- TestApk = 0; // Test APK file.
- AndroidApk = 1; // Android APK file.
- AppBundle = 2; // The android app bundle.
-}
diff --git a/protos/src/main/proto/device_farm_service.proto b/protos/src/main/proto/device_farm_service.proto
deleted file mode 100644
index 3aeea40..0000000
--- a/protos/src/main/proto/device_farm_service.proto
+++ /dev/null
@@ -1,30 +0,0 @@
-syntax = "proto3";
-
-package dev.oianmol.opentestlab;
-
-import "device_farm.proto";
-import "test_execution.proto";
-import "report_management.proto";
-
-option java_multiple_files = true;
-
-// Service for managing device farm information.
-service DeviceFarmService {
- // Retrieves the device farm information.
- rpc GetDeviceFarm(Empty) returns (DeviceFarmInfo);
-}
-
-// Service for test execution on the device farm.
-service TestExecutionService {
- // Uploads a test APK and returns log messages.
- rpc UploadTestApk(stream TestApkUpload) returns (stream StreamLogs);
-
- // Executes tests based on the provided specification and returns the results.
- rpc ExecuteTests(DeviceFarmTestSpec) returns (DeviceFarmTestResults);
-}
-
-// Service for managing test reports.
-service ReportManagementService {
- // Pulls report files for a specific CI build.
- rpc PullReportFiles(ReportsRequest) returns (stream ReportFiles);
-}
\ No newline at end of file
diff --git a/protos/src/main/proto/report_management.proto b/protos/src/main/proto/report_management.proto
deleted file mode 100644
index fa206ee..0000000
--- a/protos/src/main/proto/report_management.proto
+++ /dev/null
@@ -1,16 +0,0 @@
-syntax = "proto3";
-
-package dev.oianmol.opentestlab;
-
-option java_multiple_files = true;
-
-// Represents report files.
-message ReportFiles {
- bytes reportFile = 1; // The binary content of the report file.
- string fileName = 2; // The name of the report file.
-}
-
-// Request for pulling report files.
-message ReportsRequest {
- string ciUniqueBuildNumber = 1; // The unique build number from the CI system.
-}
diff --git a/protos/src/main/proto/test_execution.proto b/protos/src/main/proto/test_execution.proto
deleted file mode 100644
index dc99638..0000000
--- a/protos/src/main/proto/test_execution.proto
+++ /dev/null
@@ -1,71 +0,0 @@
-syntax = "proto3";
-import "device_farm.proto";
-
-package dev.oianmol.opentestlab;
-
-option java_multiple_files = true;
-
-// Specifies the test details to be executed on the device farm.
-message DeviceFarmTestSpec {
- string ciUniqueBuildNumber = 1; // The unique build number from the CI system.
- string listenerClass = 2; // The class name of the test listener.
- string testPackageFilter = 3; // The filter for test packages.
- string instrumentPackage = 4; // The instrumentation package name.
- string customRunner = 5; // The custom test runner class name.
- string packageName = 6;
- string testPackageName = 7;
-}
-
-// Contains the results of the tests executed.
-message DeviceFarmTestResults {
-
- // Represents a name-value pair property.
- message Property {
- string name = 1; // The name of the property.
- string value = 2; // The value of the property.
- }
-
- // Contains a list of properties.
- message Properties {
- repeated Property property = 1; // A list of properties.
- }
-
- // Represents a single test case result.
- message Testcase {
- string classname = 1; // The class name of the test case.
- string name = 2; // The name of the test case.
- string status = 3; // The status of the test case (e.g., passed, failed).
- uint32 time = 4; // The time taken by the test case.
- string trace = 5; // The stack trace if the test case failed.
- string message = 6; // The failure message if the test case failed.
- string type = 7; // The type of the test case.
- }
-
- // Represents a suite of tests.
- message Testsuite {
- uint32 errors = 1; // The number of errors in the test suite.
- uint32 failures = 2; // The number of failures in the test suite.
- string hostname = 3; // The hostname where the test suite was run.
- string name = 4; // The name of the test suite.
- Properties properties = 5; // The properties of the test suite.
- uint32 skipped = 6; // The number of skipped tests in the test suite.
- repeated Testcase testcase = 7; // A list of test cases in the test suite.
- uint32 tests = 8; // The total number of tests in the test suite.
- uint32 time = 9; // The total time taken by the test suite.
- string timestamp = 10; // The timestamp when the test suite was run.
- }
-
- repeated Testsuite testsuite = 1; // A list of test suites.
-}
-
-// Used for uploading test APK files.
-message TestApkUpload {
- bytes testApk = 1; // The binary content of the test APK.
- string ciUniqueBuildNumber = 2; // The unique build number from the CI system.
- FileType fileType = 3; // The type of the file being uploaded.
-}
-
-// Represents a log message stream.
-message StreamLogs {
- string message = 1; // The log message.
-}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 4dd431a..7aeabe5 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -17,4 +17,3 @@ plugins {
rootProject.name = "OpenTestLabAndroid"
include("androidplugin")
-include("protos")
diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt
deleted file mode 100644
index bd62b07..0000000
--- a/src/main/kotlin/Main.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-package dev.oianmol
-
-import dev.oianmol.dev.oianmol.opentestlab.android.adbandfriends.DeviceDiscoveryViaADB
-import dev.oianmol.dev.oianmol.opentestlab.android.reporting.DeviceFarmReportManagementService
-import dev.oianmol.dev.oianmol.opentestlab.android.adbandfriends.DeviceFarmService
-import dev.oianmol.dev.oianmol.opentestlab.android.testexec.TestExecutionService
-import dev.oianmol.opentestlab.android.devicefarm.DefaultDeviceAvailabilityStore
-import dev.oianmol.opentestlab.android.testexec.DeviceFarmTestExecScope
-import dev.oianmol.opentestlab.android.testexec.commandrunner.DefaultDeviceCommandRunner
-import dev.oianmol.opentestlab.android.testexec.resultreader.DefaultDeviceTestResultReader
-import dev.oianmol.opentestlab.android.testexec.testrunner.DeviceFarmTestRunner
-import io.grpc.ServerBuilder
-
-fun main() {
- val server = ServerBuilder.forPort(8081)
- .maxInboundMessageSize(Int.MAX_VALUE)
- .addService(DeviceFarmService(deviceDiscovery = DeviceDiscoveryViaADB))
- .addService(
- TestExecutionService(
- testExecScope = DeviceFarmTestExecScope,
- testRunner = DeviceFarmTestRunner(
- iDiscoverDevices = DeviceDiscoveryViaADB,
- deviceCommandRunner = DefaultDeviceCommandRunner,
- deviceAvailabilityStore = DefaultDeviceAvailabilityStore,
- deviceTestResultReader = DefaultDeviceTestResultReader
- )
- )
- )
- .addService(DeviceFarmReportManagementService(testExecScope = DeviceFarmTestExecScope))
- .build()
- .start()
- println("server started at port:${server.port}")
- server.awaitTermination()
- println("server stopped!")
-
-}
\ No newline at end of file
diff --git a/src/main/kotlin/dev/oianmol/opentestlab/Application.kt b/src/main/kotlin/dev/oianmol/opentestlab/Application.kt
new file mode 100644
index 0000000..ef02540
--- /dev/null
+++ b/src/main/kotlin/dev/oianmol/opentestlab/Application.kt
@@ -0,0 +1,25 @@
+package dev.oianmol.dev.oianmol.opentestlab
+
+import dev.oianmol.dev.oianmol.opentestlab.plugins.*
+import dev.oianmol.opentestlab.plugins.configureRouting
+import dev.oianmol.opentestlab.plugins.configureSerialization
+import dev.oianmol.opentestlab.plugins.initDatabase
+import io.ktor.server.application.*
+import io.ktor.server.cio.*
+import io.ktor.server.engine.*
+
+fun main() {
+ initDatabase()
+
+ embeddedServer(CIO, port = 8080, host = "0.0.0.0", module = Application::module)
+ .start(wait = true)
+}
+
+fun Application.module() {
+ configureDI()
+ configureHTTP()
+ configureMonitoring()
+ configureSerialization()
+ configureAdministration()
+ configureRouting()
+}
diff --git a/src/main/kotlin/dev/oianmol/opentestlab/Coroutine+Extensions.kt b/src/main/kotlin/dev/oianmol/opentestlab/Coroutine+Extensions.kt
deleted file mode 100644
index 857d047..0000000
--- a/src/main/kotlin/dev/oianmol/opentestlab/Coroutine+Extensions.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package dev.oianmol.dev.oianmol.opentestlab
-
-import kotlin.Result
-import kotlin.coroutines.cancellation.CancellationException
-
-suspend fun runCatchingCancellable(block: suspend () -> R): Result {
- return try {
- Result.success(block())
- } catch (c: CancellationException) {
- throw c
- } catch (e: Throwable) {
- Result.failure(e)
- }
-}
\ No newline at end of file
diff --git a/src/main/kotlin/dev/oianmol/opentestlab/CoroutinesExtension.kt b/src/main/kotlin/dev/oianmol/opentestlab/CoroutinesExtension.kt
new file mode 100644
index 0000000..219eb9f
--- /dev/null
+++ b/src/main/kotlin/dev/oianmol/opentestlab/CoroutinesExtension.kt
@@ -0,0 +1,11 @@
+package dev.oianmol.dev.oianmol.opentestlab
+
+import kotlin.coroutines.cancellation.CancellationException
+
+suspend fun suspendRunCatching(block: suspend () -> T): Result = try {
+ Result.success(block())
+} catch (cancellationException: CancellationException) {
+ throw cancellationException
+} catch (exception: Exception) {
+ Result.failure(exception)
+}
diff --git a/src/main/kotlin/dev/oianmol/opentestlab/android/devicefarm/DeviceAvailabilityStore.kt b/src/main/kotlin/dev/oianmol/opentestlab/android/devicefarm/DeviceAvailabilityStore.kt
index 1981b21..d6c3e1c 100644
--- a/src/main/kotlin/dev/oianmol/opentestlab/android/devicefarm/DeviceAvailabilityStore.kt
+++ b/src/main/kotlin/dev/oianmol/opentestlab/android/devicefarm/DeviceAvailabilityStore.kt
@@ -1,6 +1,7 @@
package dev.oianmol.opentestlab.android.devicefarm
-import dev.oianmol.opentestlab.DeviceFarmDevice
+import dev.oianmol.opentestlab.api.models.DeviceFarmDevice
+
interface DeviceAvailabilityStore {
suspend fun markDeviceUnavailable(deviceFarmDevice: DeviceFarmDevice)
diff --git a/src/main/kotlin/dev/oianmol/opentestlab/android/devicefarm/DeviceDiscovery.kt b/src/main/kotlin/dev/oianmol/opentestlab/android/devicefarm/DeviceDiscovery.kt
index a6381e1..38bfd1b 100644
--- a/src/main/kotlin/dev/oianmol/opentestlab/android/devicefarm/DeviceDiscovery.kt
+++ b/src/main/kotlin/dev/oianmol/opentestlab/android/devicefarm/DeviceDiscovery.kt
@@ -1,10 +1,10 @@
package dev.oianmol.dev.oianmol.opentestlab.android.adbandfriends
-import dev.oianmol.opentestlab.DeviceFarmDevice
-import dev.oianmol.opentestlab.TestMachine
import dev.oianmol.dev.oianmol.opentestlab.android.devicefarm.cli.CommandLine
import dev.oianmol.dev.oianmol.opentestlab.android.devicefarm.friends.Adb
import dev.oianmol.opentestlab.android.devicefarm.Device
+import dev.oianmol.opentestlab.api.models.DeviceFarmDevice
+import dev.oianmol.opentestlab.api.models.TestMachine
import java.net.InetAddress
interface DeviceDiscovery {
@@ -30,11 +30,12 @@ fun Device.toDeviceFarmDevice(): DeviceFarmDevice {
val serialNumber = getDeviceProperty("shell getprop ro.boot.serialno")
val model = getDeviceProperty("shell getprop ro.product.model")
- return DeviceFarmDevice.newBuilder()
- .setIpAddress(ip?.trim() ?: "Unknown")
- .setModel(model?.trim() ?: "Unknown")
- .setSerialNumber(this.id)
- .build()
+ return DeviceFarmDevice(
+ ipAddress = ip?.trim() ?: "Unknown",
+ model = model?.trim() ?: "Unknown",
+ serialNumber = serialNumber ?: "Unknown",
+ port = ip?.split(":")?.lastOrNull() ?: "0"
+ )
}
private fun Device.getDeviceProperty(commandSuffix: String): String? {
@@ -48,10 +49,10 @@ private fun Device.getDeviceProperty(commandSuffix: String): String? {
}
fun testMachine(devices: List): TestMachine {
- return TestMachine.newBuilder()
- .setIpAddress(InetAddress.getLocalHost().hostAddress)
- .setName(InetAddress.getLocalHost().hostName)
- .setLocation("PRIMARY DEVICE!")
- .addAllDevices(devices)
- .build()
+ return TestMachine(
+ ipAddress = InetAddress.getLocalHost().hostAddress,
+ name = InetAddress.getLocalHost().hostName,
+ location = "PRIMARY DEVICE!",
+ devices = devices, isBusy = false
+ )
}
diff --git a/src/main/kotlin/dev/oianmol/opentestlab/android/devicefarm/DeviceFarmService.kt b/src/main/kotlin/dev/oianmol/opentestlab/android/devicefarm/DeviceFarmService.kt
deleted file mode 100644
index 1aeaed6..0000000
--- a/src/main/kotlin/dev/oianmol/opentestlab/android/devicefarm/DeviceFarmService.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package dev.oianmol.dev.oianmol.opentestlab.android.adbandfriends
-
-import dev.oianmol.opentestlab.DeviceFarmInfo
-import dev.oianmol.opentestlab.DeviceFarmServiceGrpcKt
-import dev.oianmol.opentestlab.Empty
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.Dispatchers
-
-class DeviceFarmService(
- private val deviceDiscovery: DeviceDiscovery,
- dispatcher: CoroutineDispatcher = Dispatchers.IO
-) : DeviceFarmServiceGrpcKt.DeviceFarmServiceCoroutineImplBase(dispatcher) {
- override suspend fun getDeviceFarm(request: Empty): DeviceFarmInfo {
- return DeviceFarmInfo.newBuilder()
- .addAllMachines( // TODO in future we need to send a list of all machine in the test lab which has devices connected to it
- listOf(
- deviceDiscovery.hostMachine()
- )
- )
- .build()
- }
-}
\ No newline at end of file
diff --git a/src/main/kotlin/dev/oianmol/opentestlab/android/reporting/DeviceFarmReportManagementService.kt b/src/main/kotlin/dev/oianmol/opentestlab/android/reporting/DeviceFarmReportManagementService.kt
index 8529417..3b87f11 100644
--- a/src/main/kotlin/dev/oianmol/opentestlab/android/reporting/DeviceFarmReportManagementService.kt
+++ b/src/main/kotlin/dev/oianmol/opentestlab/android/reporting/DeviceFarmReportManagementService.kt
@@ -1,10 +1,8 @@
package dev.oianmol.dev.oianmol.opentestlab.android.reporting
-import com.google.protobuf.ByteString
-import dev.oianmol.opentestlab.ReportFiles
-import dev.oianmol.opentestlab.ReportManagementServiceGrpcKt
-import dev.oianmol.opentestlab.ReportsRequest
import dev.oianmol.opentestlab.android.testexec.TestExecScope
+import dev.oianmol.opentestlab.api.models.ReportFiles
+import dev.oianmol.opentestlab.api.models.ReportsRequest
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flow
@@ -13,8 +11,8 @@ import kotlinx.coroutines.flow.flowOn
class DeviceFarmReportManagementService(
private val testExecScope: TestExecScope,
dispatcher: CoroutineDispatcher = Dispatchers.IO
-) : ReportManagementServiceGrpcKt.ReportManagementServiceCoroutineImplBase(dispatcher) {
- override fun pullReportFiles(request: ReportsRequest) = flow {
+) {
+ fun pullReportFiles(request: ReportsRequest) = flow {
for (it in testExecScope.workingReportsDir(request).listFiles() ?: emptyArray()) {
val stream = it.inputStream()
var size = it.length()
@@ -23,10 +21,7 @@ class DeviceFarmReportManagementService(
val readBytes = stream.read(read)
size -= readBytes
emit(
- ReportFiles.newBuilder()
- .setFileName(it.name)
- .setReportFile(ByteString.copyFrom(read))
- .build()
+ ReportFiles(fileName = it.name, reportFile = read)
)
}
diff --git a/src/main/kotlin/dev/oianmol/opentestlab/android/testexec/TestExecutionService.kt b/src/main/kotlin/dev/oianmol/opentestlab/android/testexec/TestExecutionService.kt
index 484c3bb..4c23992 100644
--- a/src/main/kotlin/dev/oianmol/opentestlab/android/testexec/TestExecutionService.kt
+++ b/src/main/kotlin/dev/oianmol/opentestlab/android/testexec/TestExecutionService.kt
@@ -1,10 +1,11 @@
package dev.oianmol.dev.oianmol.opentestlab.android.testexec
-import dev.oianmol.opentestlab.*
import dev.oianmol.opentestlab.android.testexec.TestExecScope
import dev.oianmol.opentestlab.android.testexec.testrunner.ITestRunner
-import io.grpc.Status
-import io.grpc.StatusException
+import dev.oianmol.opentestlab.api.models.DeviceFarmTestResults
+import dev.oianmol.opentestlab.api.models.DeviceFarmTestSpec
+import dev.oianmol.opentestlab.api.models.StreamLogs
+import dev.oianmol.opentestlab.api.models.TestApkUpload
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
@@ -16,11 +17,10 @@ import kotlin.time.Duration.Companion.seconds
class TestExecutionService(
private val testExecScope: TestExecScope,
private val testRunner: ITestRunner,
- dispatcher: CoroutineDispatcher = Dispatchers.IO
-) : TestExecutionServiceGrpcKt.TestExecutionServiceCoroutineImplBase(dispatcher) {
+) {
private val logger = Logger.getLogger("TestExecutionService")
- override suspend fun executeTests(request: DeviceFarmTestSpec): DeviceFarmTestResults {
+ suspend fun executeTests(request: DeviceFarmTestSpec): DeviceFarmTestResults {
val taskExecSpec = testExecScope.prepareFor(request)
if (testRunner.canExecute(taskExecSpec)) {
@@ -41,30 +41,28 @@ class TestExecutionService(
val canExecute = testRunner.canExecute(taskExecSpec)
println("can execute after 10 minutes $canExecute ?")
if (canExecute.not()) {
- println("Caused ${Status.RESOURCE_EXHAUSTED}")
- throw StatusException(Status.RESOURCE_EXHAUSTED)
+ throw Exception("can not execute tests")
} else {
return testRunner.execute(taskExecSpec)
}
}
}
- override fun uploadTestApk(requests: Flow): Flow = flow {
+ fun uploadTestApk(requests: Flow): Flow = flow {
var dirCreated = false
requests.collect { testApkUpload ->
if (dirCreated.not()) {
testExecScope.createDirs(testApkUpload.ciUniqueBuildNumber)
emit(
- StreamLogs.newBuilder()
- .setMessage("Test Directory created for ${testApkUpload.ciUniqueBuildNumber}").build()
+ StreamLogs("Test Directory created for ${testApkUpload.ciUniqueBuildNumber}")
)
dirCreated = true
}
- if (testApkUpload.testApk.isEmpty) {
+ if (testApkUpload.testApk.isEmpty()) {
currentCoroutineContext().cancel()
} else {
testExecScope.writeFiles(testApkUpload) {
- emit(StreamLogs.newBuilder().setMessage("Writing file ${testApkUpload.fileType.name}").build())
+ emit(StreamLogs("Writing file ${testApkUpload.fileType.name}"))
}
}
}
diff --git a/src/main/kotlin/dev/oianmol/opentestlab/android/testexec/commandrunner/DeviceCommandRunner.kt b/src/main/kotlin/dev/oianmol/opentestlab/android/testexec/commandrunner/DeviceCommandRunner.kt
index 55494a9..4162c00 100644
--- a/src/main/kotlin/dev/oianmol/opentestlab/android/testexec/commandrunner/DeviceCommandRunner.kt
+++ b/src/main/kotlin/dev/oianmol/opentestlab/android/testexec/commandrunner/DeviceCommandRunner.kt
@@ -1,9 +1,9 @@
package dev.oianmol.opentestlab.android.testexec.commandrunner
-import dev.oianmol.opentestlab.DeviceFarmDevice
import dev.oianmol.dev.oianmol.opentestlab.android.devicefarm.friends.Adb
import dev.oianmol.dev.oianmol.opentestlab.android.devicefarm.cli.CommandLine
import dev.oianmol.opentestlab.android.testexec.TestExecutionTaskSpec
+import dev.oianmol.opentestlab.api.models.DeviceFarmDevice
import java.io.File
import java.util.logging.Logger
diff --git a/src/main/kotlin/dev/oianmol/opentestlab/android/testexec/resultreader/DeviceTestResultReader.kt b/src/main/kotlin/dev/oianmol/opentestlab/android/testexec/resultreader/DeviceTestResultReader.kt
index d0679e4..9603865 100644
--- a/src/main/kotlin/dev/oianmol/opentestlab/android/testexec/resultreader/DeviceTestResultReader.kt
+++ b/src/main/kotlin/dev/oianmol/opentestlab/android/testexec/resultreader/DeviceTestResultReader.kt
@@ -1,9 +1,9 @@
package dev.oianmol.opentestlab.android.testexec.resultreader
-import dev.oianmol.opentestlab.DeviceFarmTestResults
import dev.oianmol.opentestlab.android.testexec.TestExecutionTaskSpec
import dev.oianmol.opentestlab.android.testexec.resultreader.utils.XmlTestSuite
import dev.oianmol.opentestlab.android.testexec.resultreader.utils.parseAs
+import dev.oianmol.opentestlab.api.models.DeviceFarmTestResults
import java.nio.file.Files
import kotlin.io.path.extension
import kotlin.io.path.reader
@@ -19,34 +19,34 @@ object DefaultDeviceTestResultReader : DeviceTestResultReader {
?.toList()
?.map {
val testResultItem = it.reader().readText().parseAs()
- DeviceFarmTestResults.Testsuite.newBuilder()
- .setTests(testResultItem.tests?.toIntOrNull() ?: -1)
- .setErrors(testResultItem.errors?.toIntOrNull() ?: -1)
- .setFailures(testResultItem.failures?.toIntOrNull() ?: -1)
- .setFailures(testResultItem.skipped?.toIntOrNull() ?: -1)
- .setName(it.toFile().name ?: testResultItem.name ?: "Name")
- .setHostname(testResultItem.hostname ?: "")
- .setTime(testResultItem.time?.toIntOrNull() ?: -1)
- .setTimestamp(testResultItem.timestamp)
- .setProperties(
- DeviceFarmTestResults.Properties.newBuilder()
- .addAllProperty(testResultItem.properties?.property?.map {
- DeviceFarmTestResults.Property.newBuilder()
- .setName(it.name)
- .setValue(it.value)
+ DeviceFarmTestResults.Testsuite.Builder()
+ .tests(testResultItem.tests?.toIntOrNull() ?: -1)
+ .errors(testResultItem.errors?.toIntOrNull() ?: -1)
+ .failures(testResultItem.failures?.toIntOrNull() ?: -1)
+ .skipped(testResultItem.skipped?.toIntOrNull() ?: -1)
+ .name(it.toFile().name ?: testResultItem.name ?: "Name")
+ .hostname(testResultItem.hostname ?: "")
+ .time(testResultItem.time?.toIntOrNull() ?: -1)
+ .timestamp(testResultItem.timestamp ?: "")
+ .properties(
+ DeviceFarmTestResults.Properties.Builder()
+ .property(testResultItem.properties?.property?.map { property ->
+ DeviceFarmTestResults.Property.Builder()
+ .name(property.name ?: "")
+ .value(property.value ?: "")
.build()
- })
+ } ?: emptyList())
.build()
)
- .addAllTestcase(testResultItem.testcase?.map {
- DeviceFarmTestResults.Testcase.newBuilder()
- .setName(it.name)
- .setClassname(it.classname)
- .setStatus(it.status)
- .setMessage(it.message)
- .setType(it.type)
- .setTime(it.time?.toIntOrNull() ?: -1)
- .setTrace(it.trace)
+ .testcase(testResultItem.testcase?.map { testcase ->
+ DeviceFarmTestResults.Testcase.Builder()
+ .name(testcase.name ?: "")
+ .classname(testcase.classname ?: "")
+ .status(testcase.status ?: "")
+ .message(testcase.message)
+ .type(testcase.type)
+ .time(testcase.time?.toIntOrNull() ?: -1)
+ .trace(testcase.trace)
.build()
}).build()
} ?: emptyList()
diff --git a/src/main/kotlin/dev/oianmol/opentestlab/android/testexec/resultreader/utils/XmlTestSuite.kt b/src/main/kotlin/dev/oianmol/opentestlab/android/testexec/resultreader/utils/XmlTestSuite.kt
index 54b7b22..1f5e8f6 100644
--- a/src/main/kotlin/dev/oianmol/opentestlab/android/testexec/resultreader/utils/XmlTestSuite.kt
+++ b/src/main/kotlin/dev/oianmol/opentestlab/android/testexec/resultreader/utils/XmlTestSuite.kt
@@ -1,27 +1,27 @@
package dev.oianmol.opentestlab.android.testexec.resultreader.utils
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement
-import javax.xml.bind.annotation.XmlAttribute
@JacksonXmlRootElement(localName = "testsuite")
data class XmlTestSuite(
val properties: Properties? = null,
val testcase: MutableList? = null,
- @XmlAttribute
+ @JacksonXmlProperty
val name: String? = null,
- @XmlAttribute
+ @JacksonXmlProperty
val tests: String? = null,
- @XmlAttribute
+ @JacksonXmlProperty
val failures: String? = null,
- @XmlAttribute
+ @JacksonXmlProperty
val errors: String? = null,
- @XmlAttribute
+ @JacksonXmlProperty
val skipped: String? = null,
- @XmlAttribute
+ @JacksonXmlProperty
val time: String? = null,
- @XmlAttribute
+ @JacksonXmlProperty
val timestamp: String? = null,
- @XmlAttribute
+ @JacksonXmlProperty
val hostname: String? = null,
)
@@ -30,26 +30,26 @@ data class Properties(
)
data class Property(
- @XmlAttribute
+ @JacksonXmlProperty
val name: String? = null,
- @XmlAttribute
+ @JacksonXmlProperty
val value: String? = null,
)
data class Testcase(
- @XmlAttribute
+ @JacksonXmlProperty
val name: String? = null,
- @XmlAttribute
+ @JacksonXmlProperty
val classname: String? = null,
- @XmlAttribute
+ @JacksonXmlProperty
val time: String? = null,
- @XmlAttribute
+ @JacksonXmlProperty
val status: String? = null,
- @XmlAttribute
+ @JacksonXmlProperty
val trace: String? = null,
- @XmlAttribute
+ @JacksonXmlProperty
val message: String? = null,
- @XmlAttribute
+ @JacksonXmlProperty
val type: String? = null,
)
diff --git a/src/main/kotlin/dev/oianmol/opentestlab/android/testexec/testrunner/ITestRunner.kt b/src/main/kotlin/dev/oianmol/opentestlab/android/testexec/testrunner/ITestRunner.kt
index a4319e0..25616dd 100644
--- a/src/main/kotlin/dev/oianmol/opentestlab/android/testexec/testrunner/ITestRunner.kt
+++ b/src/main/kotlin/dev/oianmol/opentestlab/android/testexec/testrunner/ITestRunner.kt
@@ -1,13 +1,13 @@
package dev.oianmol.opentestlab.android.testexec.testrunner
-import dev.oianmol.opentestlab.DeviceFarmDevice
-import dev.oianmol.opentestlab.DeviceFarmTestResults
import dev.oianmol.dev.oianmol.opentestlab.android.adbandfriends.DeviceDiscovery
-import dev.oianmol.dev.oianmol.opentestlab.runCatchingCancellable
+import dev.oianmol.dev.oianmol.opentestlab.suspendRunCatching
import dev.oianmol.opentestlab.android.devicefarm.DeviceAvailabilityStore
import dev.oianmol.opentestlab.android.testexec.TestExecutionTaskSpec
import dev.oianmol.opentestlab.android.testexec.commandrunner.DeviceCommandRunner
import dev.oianmol.opentestlab.android.testexec.resultreader.DeviceTestResultReader
+import dev.oianmol.opentestlab.api.models.DeviceFarmDevice
+import dev.oianmol.opentestlab.api.models.DeviceFarmTestResults
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
@@ -39,7 +39,7 @@ class DeviceFarmTestRunner(
}
override suspend fun execute(testExecutionTaskSpec: TestExecutionTaskSpec): DeviceFarmTestResults {
- val deviceFarmTestResults = DeviceFarmTestResults.newBuilder()
+ val deviceFarmTestResults = DeviceFarmTestResults.Builder()
val allDevices = listDevices()
@@ -50,12 +50,12 @@ class DeviceFarmTestRunner(
val deviceInstalledTo = mutableListOf()
devices.map { device ->
async {
- runCatchingCancellable {
+ suspendRunCatching {
markDeviceUnavailable(device)
val deviceConnection = deviceConnection(device = device)
- runCatchingCancellable {
+ suspendRunCatching {
deviceConnection.uninstallApks(
testPackageName = testExecutionTaskSpec.testPackageName,
packageName = testExecutionTaskSpec.packageName
@@ -75,12 +75,12 @@ class DeviceFarmTestRunner(
deviceInstalledTo.mapIndexed { currentDeviceIndex, device ->
async {
- runCatchingCancellable {
+ suspendRunCatching {
val deviceConnection = deviceConnection(device)
deviceConnection.deleteTestResults() // previous if any
- runCatchingCancellable {
+ suspendRunCatching {
deviceConnection.runAndroidTests(
testExecutionTaskSpec = testExecutionTaskSpec,
totalDevices = totalDevices,
diff --git a/src/main/kotlin/dev/oianmol/opentestlab/api/models/Models.kt b/src/main/kotlin/dev/oianmol/opentestlab/api/models/Models.kt
new file mode 100644
index 0000000..60de7e7
--- /dev/null
+++ b/src/main/kotlin/dev/oianmol/opentestlab/api/models/Models.kt
@@ -0,0 +1,274 @@
+package dev.oianmol.opentestlab.api.models
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class DeviceFarmDevice(
+ val ipAddress: String,
+ val port: String,
+ val serialNumber: String,
+ val model: String
+) {
+ class Builder {
+ private var ipAddress: String = ""
+ private var port: String = ""
+ private var serialNumber: String = ""
+ private var model: String = ""
+
+ fun ipAddress(ipAddress: String) = apply { this.ipAddress = ipAddress }
+ fun port(port: String) = apply { this.port = port }
+ fun serialNumber(serialNumber: String) = apply { this.serialNumber = serialNumber }
+ fun model(model: String) = apply { this.model = model }
+
+ fun build() = DeviceFarmDevice(ipAddress, port, serialNumber, model)
+ }
+}
+
+@Serializable
+data class TestMachine(
+ val ipAddress: String,
+ val name: String,
+ val location: String,
+ val isBusy: Boolean,
+ val devices: List
+) {
+ class Builder {
+ private var ipAddress: String = ""
+ private var name: String = ""
+ private var location: String = ""
+ private var isBusy: Boolean = false
+ private var devices: List = listOf()
+
+ fun ipAddress(ipAddress: String) = apply { this.ipAddress = ipAddress }
+ fun name(name: String) = apply { this.name = name }
+ fun location(location: String) = apply { this.location = location }
+ fun isBusy(isBusy: Boolean) = apply { this.isBusy = isBusy }
+ fun devices(devices: List) = apply { this.devices = devices }
+
+ fun build() = TestMachine(ipAddress, name, location, isBusy, devices)
+ }
+}
+
+@Serializable
+data class DeviceFarmInfo(
+ val machines: List
+) {
+ class Builder {
+ private var machines: List = listOf()
+
+ fun machines(machines: List) = apply { this.machines = machines }
+
+ fun build() = DeviceFarmInfo(machines)
+ }
+}
+
+enum class FileType {
+ TestApk,
+ AndroidApk,
+ AppBundle
+}
+
+@Serializable
+data class ReportFiles(
+ val reportFile: ByteArray,
+ val fileName: String
+) {
+ class Builder {
+ private var reportFile: ByteArray = byteArrayOf()
+ private var fileName: String = ""
+
+ fun reportFile(reportFile: ByteArray) = apply { this.reportFile = reportFile }
+ fun fileName(fileName: String) = apply { this.fileName = fileName }
+
+ fun build() = ReportFiles(reportFile, fileName)
+ }
+}
+
+@Serializable
+data class ReportsRequest(
+ val ciUniqueBuildNumber: String
+) {
+ class Builder {
+ private var ciUniqueBuildNumber: String = ""
+
+ fun ciUniqueBuildNumber(ciUniqueBuildNumber: String) = apply { this.ciUniqueBuildNumber = ciUniqueBuildNumber }
+
+ fun build() = ReportsRequest(ciUniqueBuildNumber)
+ }
+}
+
+@Serializable
+data class DeviceFarmTestSpec(
+ val ciUniqueBuildNumber: String,
+ val listenerClass: String,
+ val testPackageFilter: String,
+ val instrumentPackage: String,
+ val customRunner: String,
+ val packageName: String,
+ val testPackageName: String
+) {
+ class Builder {
+ private var ciUniqueBuildNumber: String = ""
+ private var listenerClass: String = ""
+ private var testPackageFilter: String = ""
+ private var instrumentPackage: String = ""
+ private var customRunner: String = ""
+ private var packageName: String = ""
+ private var testPackageName: String = ""
+
+ fun ciUniqueBuildNumber(ciUniqueBuildNumber: String) = apply { this.ciUniqueBuildNumber = ciUniqueBuildNumber }
+ fun listenerClass(listenerClass: String) = apply { this.listenerClass = listenerClass }
+ fun testPackageFilter(testPackageFilter: String) = apply { this.testPackageFilter = testPackageFilter }
+ fun instrumentPackage(instrumentPackage: String) = apply { this.instrumentPackage = instrumentPackage }
+ fun customRunner(customRunner: String) = apply { this.customRunner = customRunner }
+ fun packageName(packageName: String) = apply { this.packageName = packageName }
+ fun testPackageName(testPackageName: String) = apply { this.testPackageName = testPackageName }
+
+ fun build() = DeviceFarmTestSpec(ciUniqueBuildNumber, listenerClass, testPackageFilter, instrumentPackage, customRunner, packageName, testPackageName)
+ }
+}
+
+@Serializable
+data class DeviceFarmTestResults(
+ val testsuite: List
+) {
+ class Builder {
+ private var testsuite: List = listOf()
+
+ fun testsuite(testsuite: List) = apply { this.testsuite = testsuite }
+
+ fun build() = DeviceFarmTestResults(testsuite)
+ }
+
+ @Serializable
+ data class Property(
+ val name: String,
+ val value: String
+ ) {
+ class Builder {
+ private var name: String = ""
+ private var value: String = ""
+
+ fun name(name: String) = apply { this.name = name }
+ fun value(value: String) = apply { this.value = value }
+
+ fun build() = Property(name, value)
+ }
+ }
+
+ @Serializable
+ data class Properties(
+ val property: List
+ ) {
+ class Builder {
+ private var property: List = listOf()
+
+ fun property(property: List) = apply { this.property = property }
+
+ fun build() = Properties(property)
+ }
+ }
+
+ @Serializable
+ data class Testcase(
+ val classname: String,
+ val name: String,
+ val status: String,
+ val time: Int,
+ val trace: String?,
+ val message: String?,
+ val type: String?
+ ) {
+ class Builder {
+ private var classname: String = ""
+ private var name: String = ""
+ private var status: String = ""
+ private var time: Int = 0
+ private var trace: String? = null
+ private var message: String? = null
+ private var type: String? = null
+
+ fun classname(classname: String) = apply { this.classname = classname }
+ fun name(name: String) = apply { this.name = name }
+ fun status(status: String) = apply { this.status = status }
+ fun time(time: Int) = apply { this.time = time }
+ fun trace(trace: String?) = apply { this.trace = trace }
+ fun message(message: String?) = apply { this.message = message }
+ fun type(type: String?) = apply { this.type = type }
+
+ fun build() = Testcase(classname, name, status, time, trace, message, type)
+ }
+ }
+
+ @Serializable
+ data class Testsuite(
+ val errors: Int,
+ val failures: Int,
+ val hostname: String,
+ val name: String,
+ val properties: Properties,
+ val skipped: Int,
+ val testcase: List,
+ val tests: Int,
+ val time: Int,
+ val timestamp: String
+ ) {
+ class Builder {
+ private var errors: Int = 0
+ private var failures: Int = 0
+ private var hostname: String = ""
+ private var name: String = ""
+ private var properties: Properties = Properties(listOf())
+ private var skipped: Int = 0
+ private var testcase: List = listOf()
+ private var tests: Int = 0
+ private var time: Int = 0
+ private var timestamp: String = ""
+
+ fun errors(errors: Int) = apply { this.errors = errors }
+ fun failures(failures: Int) = apply { this.failures = failures }
+ fun hostname(hostname: String) = apply { this.hostname = hostname }
+ fun name(name: String) = apply { this.name = name }
+ fun properties(properties: Properties) = apply { this.properties = properties }
+ fun skipped(skipped: Int) = apply { this.skipped = skipped }
+ fun testcase(testcase: List?) = apply { this.testcase = testcase?: emptyList() }
+ fun tests(tests: Int) = apply { this.tests = tests }
+ fun time(time: Int) = apply { this.time = time }
+ fun timestamp(timestamp: String) = apply { this.timestamp = timestamp }
+
+ fun build() = Testsuite(errors, failures, hostname, name, properties, skipped, testcase, tests, time, timestamp)
+ }
+ }
+}
+
+@Serializable
+data class TestApkUpload(
+ val testApk: ByteArray,
+ val ciUniqueBuildNumber: String,
+ val fileType: FileType
+) {
+ class Builder {
+ private var testApk: ByteArray = byteArrayOf()
+ private var ciUniqueBuildNumber: String = ""
+ private var fileType: FileType = FileType.TestApk
+
+ fun testApk(testApk: ByteArray) = apply { this.testApk = testApk }
+ fun ciUniqueBuildNumber(ciUniqueBuildNumber: String) = apply { this.ciUniqueBuildNumber = ciUniqueBuildNumber }
+ fun fileType(fileType: FileType) = apply { this.fileType = fileType }
+
+ fun build() = TestApkUpload(testApk, ciUniqueBuildNumber, fileType)
+ }
+}
+
+@Serializable
+data class StreamLogs(
+ val message: String
+) {
+ class Builder {
+ private var message: String = ""
+
+ fun message(message: String) = apply { this.message = message }
+
+ fun build() = StreamLogs(message)
+ }
+}
diff --git a/src/main/kotlin/dev/oianmol/opentestlab/plugins/Administration.kt b/src/main/kotlin/dev/oianmol/opentestlab/plugins/Administration.kt
new file mode 100644
index 0000000..3a05da5
--- /dev/null
+++ b/src/main/kotlin/dev/oianmol/opentestlab/plugins/Administration.kt
@@ -0,0 +1,14 @@
+package dev.oianmol.dev.oianmol.opentestlab.plugins
+
+import io.ktor.server.application.*
+import io.ktor.server.engine.*
+import io.ktor.server.response.*
+
+fun Application.configureAdministration() {
+ install(ShutDownUrl.ApplicationCallPlugin) {
+ // The URL that will be intercepted (you can also use the application.conf's ktor.deployment.shutdown.url key)
+ shutDownUrl = "/ktor/application/shutdown"
+ // A function that will be executed to get the exit code of the process
+ exitCodeSupplier = { 0 } // ApplicationCall.() -> Int
+ }
+}
diff --git a/src/main/kotlin/dev/oianmol/opentestlab/plugins/Database.kt b/src/main/kotlin/dev/oianmol/opentestlab/plugins/Database.kt
new file mode 100644
index 0000000..f615b6d
--- /dev/null
+++ b/src/main/kotlin/dev/oianmol/opentestlab/plugins/Database.kt
@@ -0,0 +1,17 @@
+package dev.oianmol.opentestlab.plugins
+
+import org.jetbrains.exposed.sql.Database
+import org.jetbrains.exposed.sql.transactions.transaction
+
+// Database connection
+fun initDatabase() {
+ Database.connect(
+ "jdbc:postgresql://localhost:5432/openTestLab",
+ driver = "org.postgresql.Driver",
+ user = "chameleon",
+ password = ""
+ )
+ transaction {
+ // SchemaUtils.create()
+ }
+}
diff --git a/src/main/kotlin/dev/oianmol/opentestlab/plugins/HTTP.kt b/src/main/kotlin/dev/oianmol/opentestlab/plugins/HTTP.kt
new file mode 100644
index 0000000..ea2ceda
--- /dev/null
+++ b/src/main/kotlin/dev/oianmol/opentestlab/plugins/HTTP.kt
@@ -0,0 +1,31 @@
+package dev.oianmol.dev.oianmol.opentestlab.plugins
+
+import io.ktor.http.*
+import io.ktor.server.application.*
+import io.ktor.server.plugins.cors.routing.*
+import io.ktor.server.plugins.openapi.*
+import io.ktor.server.plugins.partialcontent.*
+import io.ktor.server.plugins.swagger.*
+import io.ktor.server.response.*
+import io.ktor.server.routing.*
+
+fun Application.configureHTTP() {
+ install(CORS) {
+ allowMethod(HttpMethod.Options)
+ allowMethod(HttpMethod.Put)
+ allowMethod(HttpMethod.Get)
+ allowMethod(HttpMethod.Post)
+ allowMethod(HttpMethod.Delete)
+ allowMethod(HttpMethod.Patch)
+ allowHeader(HttpHeaders.Authorization)
+ allowHeader("MyCustomHeader")
+ allowHeader(HttpHeaders.ContentType)
+ anyHost() // @TODO: Don't do this in production if possible. Try to limit it.
+ }
+ routing {
+ openAPI(path = "openapi")
+ }
+ routing {
+ swaggerUI(path = "openapi")
+ }
+}
diff --git a/src/main/kotlin/dev/oianmol/opentestlab/plugins/Koin.kt b/src/main/kotlin/dev/oianmol/opentestlab/plugins/Koin.kt
new file mode 100644
index 0000000..8c093ed
--- /dev/null
+++ b/src/main/kotlin/dev/oianmol/opentestlab/plugins/Koin.kt
@@ -0,0 +1,38 @@
+package dev.oianmol.dev.oianmol.opentestlab.plugins
+
+import dev.oianmol.dev.oianmol.opentestlab.serialization.LocalDateSerializer
+import io.ktor.server.application.*
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.modules.SerializersModule
+import kotlinx.serialization.modules.contextual
+import org.koin.dsl.module
+import org.koin.ktor.plugin.Koin
+
+fun Application.configureDI() {
+ install(Koin) {
+ modules(appModule + dbModule + repositoryModule)
+ }
+}
+
+val appModule = module {
+ single {
+ Json {
+ this.ignoreUnknownKeys = true
+ this.prettyPrint = true
+ this.encodeDefaults = true
+ this.useAlternativeNames = true
+ this.isLenient = true
+ serializersModule = SerializersModule {
+ contextual(LocalDateSerializer)
+ }
+ }
+ }
+}
+
+val dbModule = module {
+
+}
+
+val repositoryModule = module {
+
+}
diff --git a/src/main/kotlin/dev/oianmol/opentestlab/plugins/Monitoring.kt b/src/main/kotlin/dev/oianmol/opentestlab/plugins/Monitoring.kt
new file mode 100644
index 0000000..28a457d
--- /dev/null
+++ b/src/main/kotlin/dev/oianmol/opentestlab/plugins/Monitoring.kt
@@ -0,0 +1,15 @@
+package dev.oianmol.dev.oianmol.opentestlab.plugins
+
+import io.ktor.server.application.*
+import io.ktor.server.plugins.callloging.*
+import io.ktor.server.request.*
+import io.ktor.server.response.*
+import io.ktor.server.routing.*
+import org.slf4j.event.*
+
+fun Application.configureMonitoring() {
+ install(CallLogging) {
+ level = Level.INFO
+ filter { call -> call.request.path().startsWith("/") }
+ }
+}
diff --git a/src/main/kotlin/dev/oianmol/opentestlab/plugins/Routing.kt b/src/main/kotlin/dev/oianmol/opentestlab/plugins/Routing.kt
new file mode 100644
index 0000000..29f8ea8
--- /dev/null
+++ b/src/main/kotlin/dev/oianmol/opentestlab/plugins/Routing.kt
@@ -0,0 +1,86 @@
+package dev.oianmol.opentestlab.plugins
+
+import dev.oianmol.dev.oianmol.opentestlab.android.adbandfriends.DeviceDiscovery
+import dev.oianmol.dev.oianmol.opentestlab.android.reporting.DeviceFarmReportManagementService
+import dev.oianmol.dev.oianmol.opentestlab.android.testexec.TestExecutionService
+import dev.oianmol.opentestlab.api.models.*
+import io.github.smiley4.ktorswaggerui.SwaggerUI
+import io.ktor.http.*
+import io.ktor.server.application.*
+import io.ktor.server.plugins.autohead.*
+import io.ktor.server.plugins.statuspages.*
+import io.ktor.server.request.*
+import io.ktor.server.resources.*
+import io.ktor.server.response.*
+import io.ktor.server.routing.*
+import org.koin.ktor.ext.inject
+
+fun Application.configureRouting() {
+ install(AutoHeadResponse)
+ install(SwaggerUI) {
+ swagger {
+ swaggerUrl = "swagger-ui"
+ forwardRoot = true
+ }
+ info {
+ title = "Example API"
+ version = "latest"
+ description = "Example API for testing and demonstration purposes."
+ }
+ server {
+ url = "http://localhost:8080"
+ description = "Development Server"
+ }
+ }
+ install(Resources)
+ install(StatusPages) {
+ exception { call, cause ->
+ call.respondText(text = "500: $cause", status = HttpStatusCode.InternalServerError)
+ }
+ }
+ routing {
+ deviceFarmService()
+ testExecutionService()
+ reportManagementService()
+ }
+
+}
+
+fun Routing.deviceFarmService() {
+ val deviceDiscovery: DeviceDiscovery by inject()
+
+ route("/deviceFarm") {
+ get("/info") {
+ // Implement your logic to get device farm info
+ call.respond(DeviceFarmInfo(machines = listOf(deviceDiscovery.hostMachine())))
+ }
+ }
+}
+
+fun Routing.testExecutionService() {
+ val testExecutionService: TestExecutionService by inject()
+ route("/testExecution") {
+ post("/uploadTestApk") {
+ val testApkUpload = call.receive()
+ testExecutionService.uploadTestApk(testApkUpload)
+ call.respond(StreamLogs("Log message"))
+ }
+
+ post("/executeTests") {
+ val testSpec = call.receive()
+ val results = testExecutionService.executeTests(testSpec)
+ call.respond(results)
+ }
+ }
+}
+
+fun Routing.reportManagementService() {
+ val deviceFarmReportManagementService: DeviceFarmReportManagementService by inject()
+
+ route("/reportManagement") {
+ post("/pullReportFiles") {
+ val request = call.receive()
+ call.respond(deviceFarmReportManagementService.pullReportFiles(request))
+ }
+ }
+}
diff --git a/src/main/kotlin/dev/oianmol/opentestlab/plugins/Serialization.kt b/src/main/kotlin/dev/oianmol/opentestlab/plugins/Serialization.kt
new file mode 100644
index 0000000..7991ac7
--- /dev/null
+++ b/src/main/kotlin/dev/oianmol/opentestlab/plugins/Serialization.kt
@@ -0,0 +1,14 @@
+package dev.oianmol.opentestlab.plugins
+
+import io.ktor.serialization.kotlinx.json.*
+import io.ktor.server.application.*
+import io.ktor.server.plugins.contentnegotiation.*
+import kotlinx.serialization.json.Json
+import org.koin.ktor.ext.inject
+
+fun Application.configureSerialization() {
+ val json: Json by inject()
+ install(ContentNegotiation) {
+ json(json = json)
+ }
+}
diff --git a/src/main/kotlin/dev/oianmol/opentestlab/serialization/LocalDateSerializer.kt b/src/main/kotlin/dev/oianmol/opentestlab/serialization/LocalDateSerializer.kt
new file mode 100644
index 0000000..ae36121
--- /dev/null
+++ b/src/main/kotlin/dev/oianmol/opentestlab/serialization/LocalDateSerializer.kt
@@ -0,0 +1,25 @@
+package dev.oianmol.dev.oianmol.opentestlab.serialization
+
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import java.time.LocalDate
+import java.time.format.DateTimeFormatter
+
+object LocalDateSerializer : KSerializer {
+ private val formatter: DateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE
+ override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.STRING)
+
+ override fun serialize(encoder: Encoder, value: LocalDate) {
+ encoder.encodeString(value.format(dev.oianmol.dev.oianmol.opentestlab.serialization.LocalDateSerializer.formatter))
+ }
+
+ override fun deserialize(decoder: Decoder): LocalDate {
+ return LocalDate.parse(decoder.decodeString(),
+ dev.oianmol.dev.oianmol.opentestlab.serialization.LocalDateSerializer.formatter
+ )
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/dev/oianmol/opentestlab/ApplicationTest.kt b/src/test/kotlin/dev/oianmol/opentestlab/ApplicationTest.kt
new file mode 100644
index 0000000..2944590
--- /dev/null
+++ b/src/test/kotlin/dev/oianmol/opentestlab/ApplicationTest.kt
@@ -0,0 +1,22 @@
+package dev.oianmol.opentestlab
+
+import dev.oianmol.opentestlab.plugins.configureRouting
+import io.ktor.client.request.*
+import io.ktor.client.statement.*
+import io.ktor.http.*
+import io.ktor.server.testing.*
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class ApplicationTest {
+ @Test
+ fun testRoot() = testApplication {
+ application {
+ configureRouting()
+ }
+ client.get("/").apply {
+ assertEquals(HttpStatusCode.OK, status)
+ assertEquals("Hello World!", bodyAsText())
+ }
+ }
+}