Fix level setup, reword flow, and catalog scope
- create and enter the level's declared starting directory - inspect Git state from the repository containing the active directory - start the init level in /sandbox/git_hug and cover it with a runtime test - apply edited reword subjects as commit messages in the in-app rebase editor - cover rename_commit with a native interactive rebase regression test - remove the upstream contribute call to action from the playable level catalog - update level parity documentation
This commit is contained in:
@@ -19,8 +19,8 @@ android {
|
||||
applicationId = "solutions.tretter.githugandroid"
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = 167
|
||||
versionName = "0.1.166"
|
||||
versionCode = 168
|
||||
versionName = "0.1.167"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
|
||||
@@ -24,6 +24,6 @@ fun parseGitHelpInvocation(command: String): GitHelpInvocation? {
|
||||
tokens[1] == "--help" -> tokens[2]
|
||||
tokens.drop(2).any { it == "--help" } -> tokens[1]
|
||||
else -> return null
|
||||
}.takeIf { it.matches(Regex("[A-Za-z0-9_-]+")) } ?: return null
|
||||
}.takeIf { !it.startsWith("-") && it.matches(Regex("[A-Za-z0-9_-]+")) } ?: return null
|
||||
return GitHelpInvocation(topic = topic, command = command)
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ class GitRepositoryRuntime private constructor(
|
||||
writeText(file.content)
|
||||
}
|
||||
}
|
||||
File(sandbox, desired.currentDir).mkdirs()
|
||||
|
||||
val needsGit = desired.initialized || desired.files.any { it.staged || it.tracked } || desired.commits.isNotEmpty()
|
||||
if (needsGit) {
|
||||
@@ -80,7 +81,7 @@ class GitRepositoryRuntime private constructor(
|
||||
levelMaterializer.materialize(nativeGit, sandbox, desired, level)
|
||||
}
|
||||
|
||||
return inspectSandbox(level)
|
||||
return inspectSandbox(level, desired.currentDir).copy(currentDir = desired.currentDir)
|
||||
}
|
||||
|
||||
fun execute(level: Level, currentRepo: RepoState, command: String): Pair<RepoState, List<String>> {
|
||||
@@ -99,7 +100,7 @@ class GitRepositoryRuntime private constructor(
|
||||
val shellTokens = GitSandboxEngine.tokenizeShellCommand(command)
|
||||
val invocation = parseEnvironmentPrefixedCommand(shellTokens)
|
||||
val tokens = invocation.command.map { it.value }
|
||||
if (tokens.isEmpty()) return inspectSandbox(level).copy(currentDir = currentRepo.currentDir) to emptyList()
|
||||
if (tokens.isEmpty()) return inspectSandbox(level, currentRepo.currentDir).copy(currentDir = currentRepo.currentDir) to emptyList()
|
||||
if (currentRepo.interactiveAddSession != null) {
|
||||
val (updatedRepo, output) = GitSandboxEngine.execute(currentRepo, command)
|
||||
val newlyStagedPaths = updatedRepo.files.filter { updatedFile ->
|
||||
@@ -108,7 +109,7 @@ class GitRepositoryRuntime private constructor(
|
||||
if (newlyStagedPaths.isNotEmpty()) {
|
||||
runGit(nativeGit, workingDir, listOf("add") + newlyStagedPaths)
|
||||
}
|
||||
val inspectedRepo = inspectSandbox(level).copy(
|
||||
val inspectedRepo = inspectSandbox(level, updatedRepo.currentDir).copy(
|
||||
currentDir = updatedRepo.currentDir,
|
||||
interactiveAddSession = updatedRepo.interactiveAddSession,
|
||||
)
|
||||
@@ -131,7 +132,7 @@ class GitRepositoryRuntime private constructor(
|
||||
?: helperCommands.execute(sandboxRoot, workingDir, currentRepo, expandedTokens)
|
||||
}
|
||||
|
||||
val inspectedRepo = inspectSandbox(level).copy(currentDir = result.first.currentDir)
|
||||
val inspectedRepo = inspectSandbox(level, result.first.currentDir).copy(currentDir = result.first.currentDir)
|
||||
return augmentObservedRepoFacts(currentRepo, inspectedRepo, expandedTokens, result.second) to result.second
|
||||
}
|
||||
|
||||
@@ -219,7 +220,7 @@ class GitRepositoryRuntime private constructor(
|
||||
|
||||
file.parentFile?.mkdirs()
|
||||
file.writeText(content)
|
||||
return inspectSandbox(level).copy(currentDir = currentRepo.currentDir) to listOf("Saved $path")
|
||||
return inspectSandbox(level, currentRepo.currentDir).copy(currentDir = currentRepo.currentDir) to listOf("Saved $path")
|
||||
}
|
||||
|
||||
fun gitEditorInitialContent(level: Level, currentRepo: RepoState, invocation: GitEditorInvocation): String {
|
||||
@@ -245,7 +246,7 @@ class GitRepositoryRuntime private constructor(
|
||||
appendLine("#")
|
||||
appendLine("# Commands:")
|
||||
appendLine("# p, pick <commit> = use commit")
|
||||
appendLine("# r, reword <commit> = use commit, but edit the commit message")
|
||||
appendLine("# r, reword <commit> = use commit and replace its message with the text on this line")
|
||||
appendLine("# e, edit <commit> = use commit, but stop for amending")
|
||||
appendLine("# s, squash <commit> = use commit, but meld into previous commit")
|
||||
appendLine("# f, fixup [-C | -c] <commit> = like squash but keep only the previous commit's log message")
|
||||
@@ -282,7 +283,7 @@ class GitRepositoryRuntime private constructor(
|
||||
if (newlyStagedPaths.isNotEmpty()) {
|
||||
runGit(nativeGit, workingDir, listOf("add") + newlyStagedPaths)
|
||||
}
|
||||
val inspectedRepo = inspectSandbox(level).copy(
|
||||
val inspectedRepo = inspectSandbox(level, updatedRepo.currentDir).copy(
|
||||
currentDir = updatedRepo.currentDir,
|
||||
interactiveAddSession = updatedRepo.interactiveAddSession,
|
||||
)
|
||||
@@ -300,7 +301,7 @@ class GitRepositoryRuntime private constructor(
|
||||
val result = runGit(nativeGit, workingDir, arguments)
|
||||
messageFile.delete()
|
||||
|
||||
return inspectSandbox(level).copy(currentDir = currentRepo.currentDir) to result.outputLines
|
||||
return inspectSandbox(level, currentRepo.currentDir).copy(currentDir = currentRepo.currentDir) to result.outputLines
|
||||
}
|
||||
|
||||
private fun executeInteractiveRebaseEditorCommand(
|
||||
@@ -316,6 +317,7 @@ class GitRepositoryRuntime private constructor(
|
||||
val todoFile = File(gitDir, "GITHUG_ANDROID_REBASE_TODO").apply {
|
||||
writeText(todo)
|
||||
}
|
||||
val rewordMessages = todo.lineSequence().mapNotNull(::rewordMessageFromTodoLine).toList()
|
||||
val editorScript = File(gitDir, "githug-android-sequence-editor.sh").apply {
|
||||
writeText(
|
||||
"""
|
||||
@@ -325,6 +327,31 @@ class GitRepositoryRuntime private constructor(
|
||||
)
|
||||
setReadable(true, true)
|
||||
}
|
||||
val rewordMessageFiles = rewordMessages.mapIndexed { index, message ->
|
||||
File(gitDir, "GITHUG_ANDROID_REWORD_${index + 1}").apply {
|
||||
writeText(message.trimEnd() + "\n")
|
||||
}
|
||||
}
|
||||
val rewordCounterFile = File(gitDir, "GITHUG_ANDROID_REWORD_COUNTER")
|
||||
val messageEditorScript = File(gitDir, "githug-android-message-editor.sh").apply {
|
||||
writeText(
|
||||
"""
|
||||
|#!/bin/sh
|
||||
|counter_file=${rewordCounterFile.absolutePath.toShellSingleQuoted()}
|
||||
|index=0
|
||||
|if [ -f "${'$'}counter_file" ]; then
|
||||
| index=$(cat "${'$'}counter_file")
|
||||
|fi
|
||||
|index=$((index + 1))
|
||||
|printf '%s\n' "${'$'}index" > "${'$'}counter_file"
|
||||
|message_file=${File(gitDir, "GITHUG_ANDROID_REWORD_").absolutePath.toShellSingleQuoted()}"${'$'}index"
|
||||
|if [ -f "${'$'}message_file" ]; then
|
||||
| cat "${'$'}message_file" > "$1"
|
||||
|fi
|
||||
|""".trimMargin(),
|
||||
)
|
||||
setReadable(true, true)
|
||||
}
|
||||
val arguments = GitSandboxEngine.tokenizeCommand(invocation.command).drop(1)
|
||||
val result = runGit(
|
||||
nativeGit,
|
||||
@@ -332,13 +359,28 @@ class GitRepositoryRuntime private constructor(
|
||||
arguments,
|
||||
mapOf(
|
||||
"GIT_SEQUENCE_EDITOR" to "${shellExecutable().toShellSingleQuoted()} ${editorScript.absolutePath.toShellSingleQuoted()}",
|
||||
"GIT_EDITOR" to "true",
|
||||
"GIT_EDITOR" to if (rewordMessages.isEmpty()) {
|
||||
"true"
|
||||
} else {
|
||||
"${shellExecutable().toShellSingleQuoted()} ${messageEditorScript.absolutePath.toShellSingleQuoted()}"
|
||||
},
|
||||
),
|
||||
)
|
||||
todoFile.delete()
|
||||
editorScript.delete()
|
||||
rewordMessageFiles.forEach { it.delete() }
|
||||
rewordCounterFile.delete()
|
||||
messageEditorScript.delete()
|
||||
|
||||
return inspectSandbox(level).copy(currentDir = currentRepo.currentDir) to result.outputLines
|
||||
return inspectSandbox(level, currentRepo.currentDir).copy(currentDir = currentRepo.currentDir) to result.outputLines
|
||||
}
|
||||
|
||||
private fun rewordMessageFromTodoLine(line: String): String? {
|
||||
val trimmed = line.trim()
|
||||
if (trimmed.isEmpty() || trimmed.startsWith("#")) return null
|
||||
val parts = trimmed.split(Regex("\\s+"), limit = 3)
|
||||
if (parts.size < 3 || parts[0] !in setOf("r", "reword")) return null
|
||||
return parts[2].takeIf { it.isNotBlank() }
|
||||
}
|
||||
|
||||
private fun executeSyntheticGitCommand(
|
||||
@@ -394,22 +436,33 @@ class GitRepositoryRuntime private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun inspectSandbox(level: Level): RepoState {
|
||||
private fun inspectSandbox(level: Level, currentDir: String = "."): RepoState {
|
||||
val sandbox = sandboxDir(level)
|
||||
val nativeGit = requireNativeGit()
|
||||
val filesOnDisk = sandbox.walkTopDown()
|
||||
.filter { it.isFile && !it.relativeTo(sandbox).path.startsWith(".git/") }
|
||||
val workingDir = File(sandbox, currentDir).canonicalFile
|
||||
.takeIf { it.path == sandbox.canonicalPath || it.path.startsWith(sandbox.canonicalPath + File.separator) }
|
||||
?: sandbox
|
||||
val repositoryRoot = generateSequence(workingDir) { directory ->
|
||||
directory.parentFile?.takeIf {
|
||||
it.path == sandbox.canonicalPath || it.path.startsWith(sandbox.canonicalPath + File.separator)
|
||||
}
|
||||
}.firstOrNull { File(it, ".git").exists() }
|
||||
val inspectionRoot = repositoryRoot ?: sandbox
|
||||
val filesOnDisk = inspectionRoot.walkTopDown()
|
||||
.filter { it.isFile && !it.relativeTo(inspectionRoot).path.startsWith(".git/") }
|
||||
.orEmpty()
|
||||
.toList()
|
||||
|
||||
if (!File(sandbox, ".git").exists()) {
|
||||
if (repositoryRoot == null) {
|
||||
return RepoState(
|
||||
initialized = File(sandbox, ".git").exists(),
|
||||
files = filesOnDisk.map { GitFile(name = it.name, content = it.readText()) },
|
||||
initialized = false,
|
||||
files = filesOnDisk.map {
|
||||
GitFile(name = it.relativeTo(inspectionRoot).path, content = it.readText())
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
val statusResult = runGit(nativeGit, sandbox, listOf("status", "--porcelain"))
|
||||
val statusResult = runGit(nativeGit, inspectionRoot, listOf("status", "--porcelain"))
|
||||
val statusMap = mutableMapOf<String, Pair<Boolean, Boolean>>()
|
||||
val deletedStatusPaths = mutableSetOf<String>()
|
||||
statusResult.outputLines.forEach { line ->
|
||||
@@ -425,16 +478,16 @@ class GitRepositoryRuntime private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
val logResult = runGit(nativeGit, sandbox, listOf("log", "--pretty=format:%h\t%at\t%P\t%s"))
|
||||
val branchResult = runGit(nativeGit, sandbox, listOf("branch", "--list"))
|
||||
val remoteBranchResult = runGit(nativeGit, sandbox, listOf("branch", "-r", "--list"))
|
||||
val tagResult = runGit(nativeGit, sandbox, listOf("tag", "--list"))
|
||||
val remoteResult = runGit(nativeGit, sandbox, listOf("remote", "-v"))
|
||||
val headResult = runGit(nativeGit, sandbox, listOf("branch", "--show-current"))
|
||||
val exactTagResult = runGit(nativeGit, sandbox, listOf("describe", "--tags", "--exact-match"))
|
||||
val userNameResult = runGit(nativeGit, sandbox, listOf("config", "--get", "user.name"))
|
||||
val userEmailResult = runGit(nativeGit, sandbox, listOf("config", "--get", "user.email"))
|
||||
val fetchHeadCount = File(sandbox, ".git/FETCH_HEAD")
|
||||
val logResult = runGit(nativeGit, inspectionRoot, listOf("log", "--pretty=format:%h\t%at\t%P\t%s"))
|
||||
val branchResult = runGit(nativeGit, inspectionRoot, listOf("branch", "--list"))
|
||||
val remoteBranchResult = runGit(nativeGit, inspectionRoot, listOf("branch", "-r", "--list"))
|
||||
val tagResult = runGit(nativeGit, inspectionRoot, listOf("tag", "--list"))
|
||||
val remoteResult = runGit(nativeGit, inspectionRoot, listOf("remote", "-v"))
|
||||
val headResult = runGit(nativeGit, inspectionRoot, listOf("branch", "--show-current"))
|
||||
val exactTagResult = runGit(nativeGit, inspectionRoot, listOf("describe", "--tags", "--exact-match"))
|
||||
val userNameResult = runGit(nativeGit, inspectionRoot, listOf("config", "--get", "user.name"))
|
||||
val userEmailResult = runGit(nativeGit, inspectionRoot, listOf("config", "--get", "user.email"))
|
||||
val fetchHeadCount = File(inspectionRoot, ".git/FETCH_HEAD")
|
||||
.takeIf { it.isFile }
|
||||
?.readLines()
|
||||
?.count { it.isNotBlank() }
|
||||
@@ -474,7 +527,7 @@ class GitRepositoryRuntime private constructor(
|
||||
.map { it.removePrefix("*").trim() }
|
||||
.filter { it.isNotBlank() && !it.startsWith("(") }
|
||||
.associateWith { branch ->
|
||||
runGit(nativeGit, sandbox, listOf("rev-list", "--count", branch))
|
||||
runGit(nativeGit, inspectionRoot, listOf("rev-list", "--count", branch))
|
||||
.outputLines
|
||||
.firstOrNull()
|
||||
?.toIntOrNull()
|
||||
@@ -490,7 +543,7 @@ class GitRepositoryRuntime private constructor(
|
||||
return RepoState(
|
||||
initialized = true,
|
||||
files = filesOnDisk.map { file ->
|
||||
val relativePath = file.relativeTo(sandbox).path
|
||||
val relativePath = file.relativeTo(inspectionRoot).path
|
||||
val (staged, tracked) = statusMap[relativePath] ?: (false to true)
|
||||
GitFile(
|
||||
name = relativePath,
|
||||
@@ -499,7 +552,7 @@ class GitRepositoryRuntime private constructor(
|
||||
tracked = tracked,
|
||||
)
|
||||
} + deletedStatusPaths
|
||||
.filterNot { deletedPath -> filesOnDisk.any { it.relativeTo(sandbox).path == deletedPath } }
|
||||
.filterNot { deletedPath -> filesOnDisk.any { it.relativeTo(inspectionRoot).path == deletedPath } }
|
||||
.map { deletedPath ->
|
||||
val (staged, tracked) = statusMap[deletedPath] ?: (false to true)
|
||||
GitFile(
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package solutions.tretter.githugandroid
|
||||
|
||||
/**
|
||||
* Port of the upstream ruby-githug `contribute` level.
|
||||
*
|
||||
* Setup documents the repository shape the learner explores. Evaluation is kept
|
||||
* state-based whenever the exercise changes repository objects; answer-only
|
||||
* levels intentionally validate the answer entered at the prompt.
|
||||
*/
|
||||
internal fun contributeLevel(): Level = level(
|
||||
id = "contribute",
|
||||
title = "Contribute",
|
||||
description = "This is the final level, the goal is to contribute to this repository by making a pull request on GitHub. Please note that this level is designed to encourage you to add a valid contribution to Githug, not testing your ability to create a pull request. Contributions that are likely to be accepted are levels, bug fixes and improved documentation.",
|
||||
hints = listOf("Forking the repository would be a good start!"),
|
||||
commandSuggestions = listOf("Open a pull request"),
|
||||
setup = { RepoState() },
|
||||
validator = { _, command -> command.isNotBlank() },
|
||||
testCases = listOf(
|
||||
levelTestCase("acknowledge contribution goal", "Open a pull request"),
|
||||
),
|
||||
)
|
||||
@@ -13,9 +13,15 @@ internal fun initLevel(): Level = level(
|
||||
description = "A new directory, `git_hug`, has been created; initialize an empty repository in it.",
|
||||
hints = listOf("You can type `git --help` or `git` in your shell to get a list of available git commands."),
|
||||
commandSuggestions = listOf("git init", "git status"),
|
||||
setup = { RepoState() },
|
||||
setup = { RepoState(currentDir = "git_hug") },
|
||||
validator = repoPredicate { it.initialized },
|
||||
testCases = listOf(
|
||||
levelTestCase("plain init", "git init"),
|
||||
),
|
||||
setupChecks = listOf(
|
||||
levelSetupCheck(
|
||||
name = "starts in git_hug",
|
||||
failureMessage = "Expected the learner to start in the created git_hug directory.",
|
||||
) { repo -> repo.currentDir == "git_hug" && !repo.initialized },
|
||||
),
|
||||
)
|
||||
|
||||
@@ -56,7 +56,6 @@ fun allGithugLevels(): List<Level> = listOf(
|
||||
restoreLevel(),
|
||||
conflictLevel(),
|
||||
submoduleLevel(),
|
||||
contributeLevel(),
|
||||
)
|
||||
|
||||
internal fun level(
|
||||
|
||||
@@ -26,6 +26,14 @@ class GitHelpCommandsTest {
|
||||
assertEquals("git --help tag", invocation?.command)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun leavesGitHelpListingOptionsForNativeGit() {
|
||||
assertNull(parseGitHelpInvocation("git help -a"))
|
||||
assertNull(parseGitHelpInvocation("git help --all"))
|
||||
assertNull(parseGitHelpInvocation("git help -g"))
|
||||
assertNull(parseGitHelpInvocation("git help --guides"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parsesManGitCommand() {
|
||||
val invocation = parseGitHelpInvocation("man git-cherry-pick")
|
||||
|
||||
@@ -9,6 +9,31 @@ import java.io.File
|
||||
import java.nio.file.Files
|
||||
|
||||
class GitSandboxEngineTest {
|
||||
@Test
|
||||
fun initLevelStartsInsideCreatedGitHugDirectory() {
|
||||
val git = testGitBinary()
|
||||
assumeTrue(git.exists() && git.canExecute())
|
||||
val root = Files.createTempDirectory("githug-init-level").toFile()
|
||||
try {
|
||||
val runtime = GitRepositoryRuntime(root, git)
|
||||
val level = initLevel()
|
||||
val repo = runtime.prepareLevel(level)
|
||||
|
||||
val (_, pwdOutput) = runtime.execute(level, repo, "pwd")
|
||||
val (initializedRepo, _) = runtime.execute(level, repo, "git init")
|
||||
|
||||
assertEquals("git_hug", repo.currentDir)
|
||||
assertFalse(repo.initialized)
|
||||
assertTrue(File(root, "init/git_hug").isDirectory)
|
||||
assertEquals(listOf("/sandbox/git_hug"), pwdOutput)
|
||||
assertTrue(initializedRepo.initialized)
|
||||
assertTrue(File(root, "init/git_hug/.git").isDirectory)
|
||||
assertTrue(level.validator(initializedRepo, "git init"))
|
||||
} finally {
|
||||
root.deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun statusShowsDeletedTrackedFiles() {
|
||||
val repo = RepoState(
|
||||
@@ -203,6 +228,36 @@ class GitSandboxEngineTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nativeGitHelpListsCommandsAndGuides() {
|
||||
val git = testGitBinary()
|
||||
assumeTrue(git.exists() && git.canExecute())
|
||||
val root = Files.createTempDirectory("githug-help-listing").toFile()
|
||||
try {
|
||||
val runtime = GitRepositoryRuntime(root, git)
|
||||
val level = level(
|
||||
id = "help-listing-test",
|
||||
title = "Help Listing Test",
|
||||
description = "",
|
||||
hints = emptyList(),
|
||||
commandSuggestions = emptyList(),
|
||||
setup = { RepoState(initialized = true) },
|
||||
validator = { _, _ -> false },
|
||||
)
|
||||
val repo = runtime.prepareLevel(level)
|
||||
|
||||
val (_, commandOutput) = runtime.execute(level, repo, "git help -a")
|
||||
val (_, guideOutput) = runtime.execute(level, repo, "git help -g")
|
||||
|
||||
assertTrue(commandOutput.any { it.contains("Main Porcelain Commands") })
|
||||
assertTrue(commandOutput.any { it.contains("commit") && it.contains("Record changes") })
|
||||
assertTrue(guideOutput.any { it.contains("Git concept guides") })
|
||||
assertTrue(guideOutput.any { it.contains("tutorial") })
|
||||
} finally {
|
||||
root.deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nativeGitExecAliasesRefreshWhenBinaryChanges() {
|
||||
val git = testGitBinary()
|
||||
@@ -354,6 +409,34 @@ class GitSandboxEngineTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nativeInteractiveRebaseRewordUsesEditedTodoMessage() {
|
||||
val git = testGitBinary()
|
||||
assumeTrue(git.exists() && git.canExecute())
|
||||
val root = Files.createTempDirectory("githug-reword-editor").toFile()
|
||||
try {
|
||||
val runtime = GitRepositoryRuntime(root, git)
|
||||
val level = renameCommitLevel()
|
||||
val repo = runtime.prepareLevel(level)
|
||||
val invocation = parseGitEditorInvocation("git rebase -i HEAD~2")
|
||||
?: error("Expected interactive rebase editor invocation")
|
||||
val todo = runtime.gitEditorInitialContent(level, repo, invocation)
|
||||
val rewordedTodo = todo.replace(
|
||||
Regex("(?m)^pick (\\S+) First coommit$"),
|
||||
"reword $1 First commit",
|
||||
)
|
||||
|
||||
val (updatedRepo, output) = runtime.executeGitEditorCommand(level, repo, invocation, rewordedTodo)
|
||||
|
||||
assertFalse(output.any { it.contains("error:", ignoreCase = true) || it.contains("fatal:", ignoreCase = true) })
|
||||
assertTrue(updatedRepo.commits.any { it.message == "First commit" })
|
||||
assertFalse(updatedRepo.commits.any { it.message == "First coommit" })
|
||||
assertTrue(level.validator(updatedRepo, invocation.command))
|
||||
} finally {
|
||||
root.deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nativeExecutableShortcutRunsScriptThroughShell() {
|
||||
val git = testGitBinary()
|
||||
|
||||
@@ -24,8 +24,8 @@ class LevelSolutionsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun levelOrderMatchesUpstreamRubyGithug() {
|
||||
assertEquals(UPSTREAM_LEVEL_ORDER, allGithugLevels().map { it.id })
|
||||
fun implementedLevelOrderMatchesUpstreamExercises() {
|
||||
assertEquals(IMPLEMENTED_UPSTREAM_LEVEL_ORDER, allGithugLevels().map { it.id })
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -240,7 +240,7 @@ class LevelSolutionsTest {
|
||||
return lineSequence().joinToString("\\n", prefix = "\"", postfix = "\"")
|
||||
}
|
||||
|
||||
val UPSTREAM_LEVEL_ORDER = listOf(
|
||||
val IMPLEMENTED_UPSTREAM_LEVEL_ORDER = listOf(
|
||||
"init",
|
||||
"config",
|
||||
"add",
|
||||
@@ -296,7 +296,6 @@ class LevelSolutionsTest {
|
||||
"restore",
|
||||
"conflict",
|
||||
"submodule",
|
||||
"contribute",
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user