This repository has been archived on 2024-02-08. You can view files and clone it, but cannot push or open issues or pull requests.
Gleb Koval ba050cbc79
All checks were successful
Test Workflow / Lint and test library (push) Successful in 1m59s
Implement multipart tests (#4)
Currently, tests do not test multipart uploads (since they use very small files). Implement some large (randomly generated) files as well.

Reviewed-on: #4
2023-12-31 09:02:36 +00:00

165 lines
6.6 KiB
Kotlin

package backup
import aws.sdk.kotlin.services.s3.*
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.*
import org.junit.jupiter.api.Assertions.*
import java.io.File
import kotlin.io.path.*
import kotlin.random.Random
val bucketName = System.getenv("BACKUP_BUCKET") ?: "teamcity-executors-test-task"
class BackupClientTest {
lateinit var s3: S3Client
lateinit var backupClient: BackupClient
@BeforeEach
fun `before each`() =
runBlocking {
s3 = S3Client.fromEnvironment {}
backupClient = BackupClient(s3, bucketName, 1024 * 1024 * 10)
}
@AfterEach
fun `after each`() {
s3.close()
}
@TestFactory
fun `round-trip tests`() =
listOf(
"empty directory" to {
listOf(
Path("_test").createDirectory(),
)
},
"single file" to {
listOf(
Path("_test.txt").apply { writeText("Hello World!") },
)
},
"directory structure" to {
listOf(
Path("_test").createDirectory(),
Path("_test/a.txt").apply { writeText("This is file A!\nAnother line here.") },
Path("_test/folder").createDirectory(),
Path("_test/another-folder").createDirectory(),
Path("_test/another-folder/b").apply { writeText("This is file B\n") },
Path("_test/another-folder/c.txt").createFile(),
Path("_test/README.md").apply { writeText("# This is a test directory structure.") },
)
},
"single large file" to {
val bytes = ByteArray(1024 * 1024 * 32)
Random.nextBytes(bytes)
listOf(
Path("_test.txt").apply { writeBytes(bytes) },
)
},
"large directory structure" to {
val bytes1 = ByteArray(1024 * 1024 * 32)
val bytes2 = ByteArray(1024 * 1024 * 48)
listOf(
Path("_test").createDirectory(),
Path("_test/a.txt").apply { writeBytes(bytes1) },
Path("_test/folder").createDirectory(),
Path("_test/another-folder").createDirectory(),
Path("_test/another-folder/b").apply { writeText("This is file B\n") },
Path("_test/another-folder/c.txt").createFile(),
Path("_test/README.md").apply { writeBytes(bytes2) },
)
},
).map { (name, pathsGen) ->
DynamicTest.dynamicTest(name) {
val paths = pathsGen()
val backupKey =
assertDoesNotThrow("should upload files") {
runBlocking {
backupClient.upload(paths.first().toFile())
}
}
val restoreDir = Path("_test_restore").createDirectory()
assertDoesNotThrow("should recover files") {
runBlocking {
backupClient.restore(restoreDir, backupKey)
}
}
assertEquals(
paths.size,
restoreDir.toFile().countEntries() - 1,
"number of files in backup restore should be equal to original",
)
val individualRestoreDir = Path("_test_restore_individual").createDirectory()
paths.forEach { path ->
if (path.isDirectory()) {
individualRestoreDir.resolve(path).createDirectory()
} else {
assertDoesNotThrow("should recover file '$path'") {
runBlocking {
backupClient.restoreFile(
individualRestoreDir.resolve(path).parent,
backupKey,
path.toString(),
)
}
}
}
}
assertEquals(
paths.size,
individualRestoreDir.toFile().countEntries() - 1,
"number of files in individual backup restore should be equal to original",
)
paths.asReversed().forEach { path ->
val restorePath = restoreDir.resolve(path)
val individualRestorePath = individualRestoreDir.resolve(path)
if (path.isDirectory()) {
assertTrue(restorePath.exists(), "'$path' should exist in backup")
assertTrue(restorePath.isDirectory(), "'$path' should be a directory in backup")
assertTrue(individualRestorePath.exists(), "'$path' should exist in backup (individual)")
assertTrue(
individualRestorePath.isDirectory(),
"'$path' should be a directory in backup (individual)",
)
} else {
val originalBytes = path.toFile().readBytes().asList()
assertEquals(
originalBytes,
restorePath.toFile().readBytes().asList(),
"File contents of '$path' should equal",
)
assertEquals(
originalBytes,
individualRestorePath.toFile().readBytes().asList(),
"File contents of '$path' should equal (individual)",
)
}
// cleanup
path.deleteExisting()
restorePath.deleteExisting()
individualRestorePath.deleteExisting()
}
// cleanup
restoreDir.deleteExisting()
individualRestoreDir.deleteExisting()
runBlocking {
s3.deleteObject {
bucket = bucketName
key = backupKey
}
}
}
}
}
internal fun File.countEntries(): Int {
val queue = ArrayDeque<File>()
queue.add(this)
return queue.count { file ->
if (file.isDirectory) {
queue.addAll(file.listFiles()!!) // shouldn't ever be null, since we know it's a directory
}
true
}
}