- Rename CrossCompileGitForAndroid.sh to CompileGitForAllTargetPlatforms.sh and support host, Android-only, and all-target modes. - Compile a development-host Git binary at build/host-git/libgit.so for JVM tests. - Route level and sandbox tests through GITHUG_TEST_GIT_BINARY so they exercise the compiled host Git rather than the system Git fallback. - Cross-compile packaged Git binaries for arm64-v8a, armeabi-v7a, x86, and x86_64 Android targets. - Remove the temporary device-bound Pull rebase instrumentation path from the previous debugging attempt.
251 lines
9.0 KiB
Kotlin
251 lines
9.0 KiB
Kotlin
package solutions.tretter.githugandroid
|
|
|
|
import org.junit.Assert.assertEquals
|
|
import org.junit.Assert.assertTrue
|
|
import org.junit.Test
|
|
import java.io.File
|
|
|
|
class LevelSolutionsTest {
|
|
@Test
|
|
fun everyLevelHasEmbeddedSolutionScenarios() {
|
|
val missing = allGithugLevels().filter { it.testCases.isEmpty() }.map { it.id }
|
|
|
|
assertTrue(
|
|
"Expected every level source file to define at least one solution scenario. Missing:\n${missing.joinToString("\n")}",
|
|
missing.isEmpty(),
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun levelIdsAreUnique() {
|
|
val ids = allGithugLevels().map { it.id }
|
|
|
|
assertEquals(ids, ids.distinct())
|
|
}
|
|
|
|
@Test
|
|
fun levelOrderMatchesUpstreamRubyGithug() {
|
|
assertEquals(UPSTREAM_LEVEL_ORDER, allGithugLevels().map { it.id })
|
|
}
|
|
|
|
@Test
|
|
fun knownSolutionsCompleteEveryLevel() {
|
|
val evidence = StringBuilder()
|
|
val gitBinary = testGitBinary()
|
|
val runtimeRoot = testSandboxRoot().apply {
|
|
deleteRecursively()
|
|
mkdirs()
|
|
}
|
|
evidence.appendLine("GitHug Android level solution evidence")
|
|
evidence.appendLine("Engine: GitRepositoryRuntime filesystem sandbox")
|
|
evidence.appendLine("Git binary: ${gitBinary.absolutePath}")
|
|
evidence.appendLine("Sandbox root: ${runtimeRoot.absolutePath}")
|
|
evidence.appendLine("Scope: executes embedded solution scenarios through the same runtime and per-level sandbox path used by the app.")
|
|
evidence.appendLine("Levels: ${allGithugLevels().size}")
|
|
evidence.appendLine()
|
|
|
|
val failures = allGithugLevels().flatMap { level ->
|
|
val runtime = GitRepositoryRuntime(runtimeRoot, gitBinary)
|
|
evidence.appendLine("================================================================================")
|
|
evidence.appendLine("Level: ${level.id}")
|
|
evidence.appendLine("Title: ${level.title}")
|
|
evidence.appendLine("Initial repo:")
|
|
evidence.append(runtime.prepareLevel(level).describeForEvidence().prependIndent(" "))
|
|
evidence.appendLine()
|
|
|
|
level.testCases.mapNotNull { testCase ->
|
|
var repo = runtime.prepareLevel(level)
|
|
var solved = false
|
|
|
|
evidence.appendLine("Scenario: ${testCase.name}")
|
|
evidence.appendLine("Solution commands:")
|
|
testCase.commands.forEachIndexed { index, command ->
|
|
evidence.appendLine(" ${index + 1}. $command")
|
|
}
|
|
evidence.appendLine()
|
|
|
|
testCase.commands.forEachIndexed { index, command ->
|
|
val (nextRepo, output) = runtime.execute(level, repo, command)
|
|
repo = nextRepo
|
|
solved = level.validator(repo, command)
|
|
|
|
evidence.appendLine("Command ${index + 1}: $command")
|
|
evidence.appendLine("Output:")
|
|
if (output.isEmpty()) {
|
|
evidence.appendLine(" <no output>")
|
|
} else {
|
|
output.forEach { line -> evidence.appendLine(" $line") }
|
|
}
|
|
evidence.appendLine("Repo after command:")
|
|
evidence.append(repo.describeForEvidence().prependIndent(" "))
|
|
evidence.appendLine("Validator passed after command: $solved")
|
|
evidence.appendLine()
|
|
}
|
|
|
|
evidence.appendLine("Scenario result: ${if (solved) "PASS" else "FAIL"}")
|
|
evidence.appendLine()
|
|
|
|
if (solved) null else "${level.id} / ${testCase.name}: ${testCase.commands.joinToString(" && ")}"
|
|
}
|
|
}
|
|
|
|
writeEvidenceLog(evidence.toString())
|
|
|
|
assertTrue(
|
|
"Expected every known solution to complete its level. Failures:\n${failures.joinToString("\n")}",
|
|
failures.isEmpty(),
|
|
)
|
|
}
|
|
|
|
private companion object {
|
|
fun writeEvidenceLog(content: String) {
|
|
val repoRoot = File(System.getProperty("user.dir") ?: ".")
|
|
val appDir = if (File(repoRoot, "app/build.gradle.kts").exists()) {
|
|
File(repoRoot, "app")
|
|
} else {
|
|
repoRoot
|
|
}
|
|
val reportDir = File(appDir, "build/reports/level-solutions")
|
|
reportDir.mkdirs()
|
|
File(reportDir, "level-solutions.log").writeText(content)
|
|
}
|
|
|
|
fun testSandboxRoot(): File {
|
|
val repoRoot = File(System.getProperty("user.dir") ?: ".")
|
|
val appDir = if (File(repoRoot, "app/build.gradle.kts").exists()) {
|
|
File(repoRoot, "app")
|
|
} else {
|
|
repoRoot
|
|
}
|
|
return File(appDir, "build/test-sandboxes/level-solutions")
|
|
}
|
|
|
|
fun testGitBinary(): File {
|
|
System.getenv("GITHUG_TEST_GIT_BINARY")
|
|
?.takeIf { it.isNotBlank() }
|
|
?.let { File(it) }
|
|
?.takeIf { it.exists() && it.canExecute() }
|
|
?.let { return it }
|
|
|
|
val repoHostGit = File(repoRoot(), "build/host-git/libgit.so")
|
|
if (repoHostGit.exists() && repoHostGit.canExecute()) return repoHostGit
|
|
|
|
val candidates = listOf(
|
|
File("/usr/bin/git"),
|
|
File("/usr/local/bin/git"),
|
|
)
|
|
candidates.firstOrNull { it.exists() && it.canExecute() }?.let { return it }
|
|
|
|
val process = ProcessBuilder("sh", "-c", "command -v git")
|
|
.redirectErrorStream(true)
|
|
.start()
|
|
val output = process.inputStream.bufferedReader().readText().trim()
|
|
val exitCode = process.waitFor()
|
|
assertTrue("A real git executable is required for level solution tests.", exitCode == 0 && output.isNotBlank())
|
|
return File(output)
|
|
}
|
|
|
|
fun repoRoot(): File {
|
|
val userDir = File(System.getProperty("user.dir") ?: ".")
|
|
return if (File(userDir, "app/build.gradle.kts").exists()) userDir else userDir.parentFile ?: userDir
|
|
}
|
|
|
|
fun RepoState.describeForEvidence(): String = buildString {
|
|
appendLine("initialized=$initialized")
|
|
appendLine("headBranch=$headBranch")
|
|
appendLine("currentDir=$currentDir")
|
|
appendLine("branches=${branches.toSortedMap()}")
|
|
appendLine("tags=${tags.sorted()}")
|
|
appendLine("remotes=${remotes.toSortedMap()}")
|
|
appendLine("config=${config.toSortedMap()}")
|
|
appendLine("stashes=$stashes")
|
|
appendLine("fetchedBranches=${fetchedBranches.sorted()}")
|
|
appendLine("pushedBranches=${pushedBranches.sorted()}")
|
|
appendLine("pushedTags=${pushedTags.sorted()}")
|
|
appendLine("submodules=${submodules.toSortedMap()}")
|
|
appendLine("maintenanceActions=${maintenanceActions.sorted()}")
|
|
appendLine("files=")
|
|
if (files.isEmpty()) {
|
|
appendLine(" <none>")
|
|
} else {
|
|
files.sortedBy { it.name }.forEach { file ->
|
|
appendLine(" ${file.name} staged=${file.staged} tracked=${file.tracked} content=${file.content.toEvidenceValue()}")
|
|
}
|
|
}
|
|
appendLine("commits=")
|
|
if (commits.isEmpty()) {
|
|
appendLine(" <none>")
|
|
} else {
|
|
commits.forEach { commit ->
|
|
appendLine(" ${commit.id} ${commit.message}")
|
|
}
|
|
}
|
|
}
|
|
|
|
fun String.toEvidenceValue(): String {
|
|
if (isEmpty()) return "\"\""
|
|
return lineSequence().joinToString("\\n", prefix = "\"", postfix = "\"")
|
|
}
|
|
|
|
val UPSTREAM_LEVEL_ORDER = listOf(
|
|
"init",
|
|
"config",
|
|
"add",
|
|
"commit",
|
|
"clone",
|
|
"clone_to_folder",
|
|
"ignore",
|
|
"include",
|
|
"status",
|
|
"number_of_files_committed",
|
|
"rm",
|
|
"rm_cached",
|
|
"stash",
|
|
"rename",
|
|
"restructure",
|
|
"log",
|
|
"tag",
|
|
"push_tags",
|
|
"commit_amend",
|
|
"commit_in_future",
|
|
"reset",
|
|
"reset_soft",
|
|
"checkout_file",
|
|
"remote",
|
|
"remote_url",
|
|
"pull",
|
|
"remote_add",
|
|
"push",
|
|
"diff",
|
|
"blame",
|
|
"branch",
|
|
"checkout",
|
|
"checkout_tag",
|
|
"checkout_tag_over_branch",
|
|
"branch_at",
|
|
"delete_branch",
|
|
"push_branch",
|
|
"merge",
|
|
"fetch",
|
|
"rebase",
|
|
"rebase_onto",
|
|
"repack",
|
|
"cherry-pick",
|
|
"grep",
|
|
"rename_commit",
|
|
"squash",
|
|
"merge_squash",
|
|
"reorder",
|
|
"bisect",
|
|
"stage_lines",
|
|
"find_old_branch",
|
|
"revert",
|
|
"restore",
|
|
"conflict",
|
|
"submodule",
|
|
"contribute",
|
|
)
|
|
|
|
}
|
|
}
|