Skip to content

Commit eb8dd67

Browse files
committed
Initial Library Release
1 parent 01dd2ea commit eb8dd67

File tree

28 files changed

+543
-373
lines changed

28 files changed

+543
-373
lines changed

CanaryLibrary/build.gradle

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,57 @@
11
plugins {
22
id 'com.android.library'
33
id 'org.jetbrains.kotlin.android'
4+
id 'org.jetbrains.kotlin.plugin.serialization'
45
}
56

67
android {
78
compileSdk 32
89

910
defaultConfig {
10-
minSdk 21
11+
minSdk 17
1112
targetSdk 32
12-
13+
multiDexEnabled = true
1314
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
1415
consumerProguardFiles "consumer-rules.pro"
1516
}
16-
17+
1718
buildTypes {
1819
release {
1920
minifyEnabled false
2021
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
2122
}
2223
}
24+
2325
compileOptions {
24-
sourceCompatibility JavaVersion.VERSION_1_8
25-
targetCompatibility JavaVersion.VERSION_1_8
26+
sourceCompatibility JavaVersion.VERSION_11
27+
targetCompatibility JavaVersion.VERSION_11
2628
}
2729
kotlinOptions {
2830
jvmTarget = '1.8'
31+
freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
2932
}
33+
3034
}
3135

3236
dependencies {
37+
androidTestImplementation 'org.testng:testng:7.4.0'
38+
def coroutines_version = '1.6.4'
3339

34-
implementation 'androidx.core:core-ktx:1.7.0'
40+
implementation 'androidx.core:core-ktx:1.8.0'
3541
implementation 'androidx.appcompat:appcompat:1.4.2'
42+
implementation "androidx.multidex:multidex:2.0.1"
43+
44+
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3"
45+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
46+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
47+
3648
implementation 'com.google.android.material:material:1.6.1'
3749
implementation 'com.github.OperatorFoundation:ShapeshifterAndroidKotlin:3.1.0'
3850
implementation 'com.beust:klaxon:5.5'
51+
3952
testImplementation 'junit:junit:4.13.2'
53+
4054
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
4155
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
56+
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
4257
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.example.CanaryLibrary
2+
3+
import android.os.Environment
4+
import androidx.test.platform.app.InstrumentationRegistry
5+
import kotlinx.coroutines.ExperimentalCoroutinesApi
6+
import kotlinx.coroutines.test.runTest
7+
import org.junit.Test
8+
import org.junit.Assert.*
9+
import org.operatorfoundation.shapeshifter.shadow.kotlin.ShadowConfig
10+
11+
class ExampleInstrumentedTest {
12+
@Test
13+
fun useAppContext() {
14+
// Context of the app under test.
15+
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
16+
assertEquals("CanaryLibrary.test", appContext.packageName)
17+
}
18+
19+
@OptIn(ExperimentalCoroutinesApi::class)
20+
@Test
21+
fun createCanaryInstance() = runTest {
22+
// ***** This is the actual API for the Canary Library ******
23+
val configDirectory = Environment.getStorageDirectory()
24+
val canary = Canary(configDirectory)
25+
assertNotNull(canary)
26+
canary.runTest()
27+
}
28+
29+
@OptIn(ExperimentalCoroutinesApi::class)
30+
@Test
31+
fun createCanaryTest() = runTest {
32+
val configDirectory = Environment.getStorageDirectory()
33+
val chirp = CanaryTest(configDirectory, 1)
34+
35+
chirp.begin()
36+
}
37+
38+
@Test
39+
fun checkSetupEmptyConfigDir()
40+
{
41+
val configDirectory = Environment.getStorageDirectory()
42+
val chirp = CanaryTest(configDirectory, 1)
43+
44+
assertFalse(chirp.checkSetup())
45+
}
46+
47+
@OptIn(ExperimentalCoroutinesApi::class)
48+
@Test
49+
fun testController() = runTest {
50+
val testController = TestController()
51+
val shadowConfig = ShadowConfig("", "DarkStar")
52+
val canaryConfig = CanaryConfig<ShadowConfig>("", 1234, shadowConfig)
53+
val transport = Transport("ShadowExample", TransportType.shadow, canaryConfig)
54+
55+
val result = testController.runTransportTest(transport)
56+
assertNotNull(result)
57+
}
58+
}

CanaryLibrary/src/androidTest/java/com/example/canarylibrary/ExampleInstrumentedTest.kt

Lines changed: 0 additions & 24 deletions
This file was deleted.
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3-
package="com.example.canarylibrary">
4-
3+
package="CanaryLibrary">
4+
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
5+
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
56
</manifest>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.example.CanaryLibrary
2+
3+
import kotlinx.coroutines.MainScope
4+
import kotlinx.coroutines.launch
5+
import java.io.File
6+
7+
class Canary(configDirectoryFile: File, timesToRun: Int = 1)
8+
{
9+
private var chirp: CanaryTest
10+
11+
init
12+
{
13+
chirp = CanaryTest(configDirectoryFile, timesToRun)
14+
}
15+
16+
fun runTest()
17+
{
18+
// TODO: Better coroutines
19+
MainScope().launch {
20+
chirp.begin()
21+
}
22+
}
23+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.example.CanaryLibrary
2+
3+
import kotlinx.serialization.Serializable
4+
5+
@Serializable
6+
data class CanaryConfig<out T : Any>(val serverIP: String, val serverPort: Int, val transportConfig: T)
7+
{
8+
9+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package com.example.CanaryLibrary
2+
3+
import kotlinx.serialization.decodeFromString
4+
import kotlinx.serialization.json.Json
5+
import org.operatorfoundation.shapeshifter.shadow.kotlin.ShadowConfig
6+
import java.io.File
7+
8+
class CanaryTest(val configDirectory: File, val timesToRun: Int = 1, var saveDirectory: File? = null)
9+
{
10+
suspend fun begin()
11+
{
12+
println("\n attempting to run tests...\n")
13+
14+
// Make sure we have everything we need first
15+
if (!checkSetup())
16+
{
17+
return
18+
}
19+
20+
runAllTests()
21+
}
22+
23+
private suspend fun runAllTests()
24+
{
25+
val testController = TestController()
26+
27+
for (i in 1..timesToRun)
28+
{
29+
println("\n***************************\nRunning test batch $i of $timesToRun\n***************************\n")
30+
31+
for (transport in testingTransports)
32+
{
33+
println("\n 🧪 Starting test for ${transport.name} 🧪")
34+
testController.test(transport)
35+
}
36+
}
37+
}
38+
39+
fun checkSetup(): Boolean
40+
{
41+
if (saveDirectory != null)
42+
{
43+
// Does the save directory exist?
44+
if (!saveDirectory!!.exists())
45+
{
46+
println("\n‼️ The selected save directory does not exist at ${saveDirectory!!.path}.\n")
47+
return false
48+
}
49+
else if (!saveDirectory!!.isDirectory)
50+
{
51+
println("\n‼️ The selected save directory is not a directory. Please select a directory for saving your results. \nSelected path: ${saveDirectory!!.path}.\n")
52+
return false
53+
}
54+
55+
println("\n✔️ User selected save directory: ${saveDirectory!!.path}\n")
56+
}
57+
58+
// Does the Config Directory Exist?
59+
if (!configDirectory.exists())
60+
{
61+
println("\n‼️ The selected config directory does not exist at ${configDirectory.path}.\n")
62+
return false
63+
}
64+
else if (!configDirectory.isDirectory)
65+
{
66+
println("\n‼️ The selected config directory is not a directory. Please select the directory where your transport config files are located. \nSelected path: ${configDirectory.path}.\n")
67+
return false
68+
}
69+
70+
println("\n✔️ Config directory: ${configDirectory.path}\n")
71+
72+
if (!prepareTransports())
73+
{
74+
return false
75+
}
76+
77+
println("✔️ Check setup complete")
78+
return true
79+
}
80+
81+
private fun prepareTransports(): Boolean
82+
{
83+
// Get a list of all of the files in the config directory
84+
// return false if we are unable to retrieve a list of files
85+
val configFiles = configDirectory.listFiles() ?: return false
86+
87+
if (configFiles.isEmpty())
88+
{
89+
println("\n ‼️ There are no config files in the selected directory: ${configDirectory.path}")
90+
return false
91+
}
92+
93+
configFiles.forEach { configFile ->
94+
for (transportType in possibleTransportTypes)
95+
{
96+
// Check each file name to see if it contains the name of a supported transport
97+
if (configFile.name.contains(transportType.name, true))
98+
{
99+
val configString = configFile.readText()
100+
val canaryConfig: CanaryConfig<ShadowConfig> = Json.decodeFromString(configString)
101+
102+
val maybeNewTransport = Transport(configFile.name, transportType, canaryConfig)
103+
testingTransports += maybeNewTransport
104+
println("\n✔️ ${maybeNewTransport.name} test is ready\n")
105+
}
106+
}
107+
}
108+
109+
if (testingTransports.isEmpty())
110+
{
111+
println("‼️ There were no valid transport configs in the provided directory. Ending test.\nConfig Directory: $configDirectory.path")
112+
return false
113+
}
114+
115+
return true
116+
}
117+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.example.CanaryLibrary
2+
3+
var testingTransports = arrayOf<Transport>()
4+
5+
val possibleTransportTypes = arrayOf(TransportType.shadow)
6+
val httpRequestString = "GET / HTTP/1.0\r\nConnection: close\r\n\r\n"
7+
val canaryString = "Yeah!\n"
8+
val resultsFileName = "CanaryResults.csv"
9+
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.example.CanaryLibrary
2+
3+
import android.os.Environment
4+
import android.os.Environment.MEDIA_MOUNTED
5+
import java.io.File
6+
import java.util.*
7+
8+
9+
class TestController()
10+
{
11+
suspend fun runTransportTest(transport: Transport): TestResult?
12+
{
13+
// Connection test
14+
val connectionTest = TransportConnectionTest(transport)
15+
val success = connectionTest.run()
16+
17+
// Save the result to a file
18+
val hostString = transport.serverIP + ":${transport.port}"
19+
val result = TestResult(hostString, Date(), transport.name, success)
20+
save(result, transport.name)
21+
22+
return null
23+
}
24+
25+
// Saves the provided test results to a csv file with a filename that contains a timestamp.
26+
// If a file with this name already exists it will append the results to the end of the file.
27+
// - Parameter result: The test result information to be saved. The type is a TestResult struct.
28+
// - Returns: A boolean value indicating whether or not the results were saved successfully.
29+
fun save(result: TestResult, testName: String): Boolean
30+
{
31+
if (Environment.getExternalStorageState() != MEDIA_MOUNTED)
32+
{
33+
println("Unable to save the results file: external storage is not available for reading/writing")
34+
return false
35+
}
36+
37+
val extDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)
38+
val saveFile = File(extDir, resultsFileName)
39+
40+
// Make sure the Documents directory exists.
41+
extDir.mkdirs()
42+
43+
if (!saveFile.exists())
44+
{
45+
// Make a new csv file for our test results
46+
saveFile.createNewFile()
47+
48+
// The first row should be our labels
49+
val labelRow = "TestDate, ServerIP, Transport, Success\n"
50+
saveFile.appendText(labelRow)
51+
}
52+
53+
// Add out newest test results to the file
54+
val resultString = "${result.testDate}, ${result.hostString}, $testName, ${result.success}\n"
55+
saveFile.appendText(resultString)
56+
57+
return saveFile.exists()
58+
}
59+
60+
suspend fun test(transport: Transport) {
61+
println("Testing ${transport.name} transport...")
62+
63+
val transportTestResult = runTransportTest(transport)
64+
65+
if (transportTestResult == null)
66+
{
67+
println("\n Received a null result when testing ${transport.name} transport. \n")
68+
}
69+
}
70+
}

0 commit comments

Comments
 (0)