Fix config level completion on device
This commit is contained in:
@@ -12,6 +12,7 @@ import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import java.io.File
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
@@ -48,10 +49,27 @@ class GitRepositoryRuntimeInstrumentedTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun configLevelCompletesThroughUiWithArbitraryValues() {
|
||||
launchFreshApp().use {
|
||||
submitTerminalCommand("git init")
|
||||
waitForExercise(configLevel())
|
||||
|
||||
submitTerminalCommand("git config user.name githug")
|
||||
submitTerminalCommand("git config user.email xxx@yy.com")
|
||||
|
||||
waitForLevelCompletion(nextLevel = addLevel())
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchFreshApp(): ActivityScenario<MainActivity> {
|
||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
File(context.applicationInfo.dataDir, "files").deleteRecursively()
|
||||
File(context.filesDir, "githug-sandboxes").deleteRecursively()
|
||||
File(context.applicationInfo.dataDir, "cache").deleteRecursively()
|
||||
runBlocking {
|
||||
GameProgressStore(context.applicationContext).saveProgress(emptySet(), activeLevelId = null)
|
||||
GameProgressStore(context.applicationContext).saveShowHelpOnStart(showHelpOnStart = true)
|
||||
}
|
||||
val scenario = ActivityScenario.launch(MainActivity::class.java)
|
||||
waitForExercise(initLevel())
|
||||
return scenario
|
||||
|
||||
@@ -4,13 +4,16 @@
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/wait.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define LOG_TAG "GitHugNativeGit"
|
||||
#define GIT_COMMAND_TIMEOUT_MS 30000
|
||||
|
||||
typedef int (*git_main_fn)(int argc, const char **argv);
|
||||
typedef void (*git_init_fn)(const char **argv);
|
||||
@@ -73,6 +76,14 @@ static int drain_available_output(int fd, struct output_buffer *output, int *saw
|
||||
}
|
||||
}
|
||||
|
||||
static long long monotonic_millis(void) {
|
||||
struct timespec now;
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) {
|
||||
return 0;
|
||||
}
|
||||
return ((long long)now.tv_sec * 1000LL) + ((long long)now.tv_nsec / 1000000LL);
|
||||
}
|
||||
|
||||
static jobjectArray make_result(JNIEnv *env, int exit_code, const char *output) {
|
||||
jclass string_class = (*env)->FindClass(env, "java/lang/String");
|
||||
jobjectArray result = (*env)->NewObjectArray(env, 2, string_class, NULL);
|
||||
@@ -227,6 +238,17 @@ Java_solutions_tretter_githugandroid_NativeGitBridge_runGitMainNative(
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&git_mutex);
|
||||
if (load_git(library_path_chars) != 0) {
|
||||
const char *error = dlerror();
|
||||
close(pipe_fds[0]);
|
||||
close(pipe_fds[1]);
|
||||
pthread_mutex_unlock(&git_mutex);
|
||||
free_string_array(argv, argc);
|
||||
free_string_array(env_entries, envc);
|
||||
(*env)->ReleaseStringUTFChars(env, library_path, library_path_chars);
|
||||
(*env)->ReleaseStringUTFChars(env, working_directory, working_directory_chars);
|
||||
return make_error(env, error != NULL ? error : "Native Git execution failed: init_git/cmd_main not found");
|
||||
}
|
||||
|
||||
pid_t child = fork();
|
||||
if (child < 0) {
|
||||
@@ -241,6 +263,7 @@ Java_solutions_tretter_githugandroid_NativeGitBridge_runGitMainNative(
|
||||
}
|
||||
|
||||
if (child == 0) {
|
||||
setpgid(0, 0);
|
||||
close(pipe_fds[0]);
|
||||
dup2(pipe_fds[1], STDOUT_FILENO);
|
||||
dup2(pipe_fds[1], STDERR_FILENO);
|
||||
@@ -251,20 +274,13 @@ Java_solutions_tretter_githugandroid_NativeGitBridge_runGitMainNative(
|
||||
(void)saved_env;
|
||||
chdir(working_directory_chars);
|
||||
|
||||
if (load_git(library_path_chars) != 0) {
|
||||
const char *error = dlerror();
|
||||
fprintf(stderr, "%s\n", error != NULL ? error : "Native Git execution failed: init_git/cmd_main not found");
|
||||
fflush(stdout);
|
||||
fflush(stderr);
|
||||
_exit(127);
|
||||
}
|
||||
|
||||
git_init((const char **)argv);
|
||||
int child_exit_code = git_main(argc, (const char **)argv);
|
||||
fflush(stdout);
|
||||
fflush(stderr);
|
||||
_exit(child_exit_code);
|
||||
}
|
||||
setpgid(child, child);
|
||||
|
||||
close(pipe_fds[1]);
|
||||
struct output_buffer output = {0};
|
||||
@@ -276,6 +292,8 @@ Java_solutions_tretter_githugandroid_NativeGitBridge_runGitMainNative(
|
||||
int child_status = 0;
|
||||
int child_done = 0;
|
||||
int saw_eof = 0;
|
||||
int timed_out = 0;
|
||||
long long started_at = monotonic_millis();
|
||||
while (!child_done || !saw_eof) {
|
||||
if (drain_available_output(pipe_fds[0], &output, &saw_eof) != 0) {
|
||||
break;
|
||||
@@ -288,6 +306,16 @@ Java_solutions_tretter_githugandroid_NativeGitBridge_runGitMainNative(
|
||||
child_done = 1;
|
||||
}
|
||||
}
|
||||
if (!child_done && started_at > 0 && monotonic_millis() - started_at > GIT_COMMAND_TIMEOUT_MS) {
|
||||
timed_out = 1;
|
||||
kill(-child, SIGKILL);
|
||||
kill(child, SIGKILL);
|
||||
while (waitpid(child, &child_status, 0) < 0 && errno == EINTR) {
|
||||
}
|
||||
child_done = 1;
|
||||
const char *timeout_message = "\nNative Git execution timed out.\n";
|
||||
append_output(&output, timeout_message, strlen(timeout_message));
|
||||
}
|
||||
if (child_done) {
|
||||
if (!saw_eof) {
|
||||
drain_available_output(pipe_fds[0], &output, &saw_eof);
|
||||
@@ -300,7 +328,9 @@ Java_solutions_tretter_githugandroid_NativeGitBridge_runGitMainNative(
|
||||
pthread_mutex_unlock(&git_mutex);
|
||||
|
||||
int exit_code = -1;
|
||||
if (WIFEXITED(child_status)) {
|
||||
if (timed_out) {
|
||||
exit_code = 124;
|
||||
} else if (WIFEXITED(child_status)) {
|
||||
exit_code = WEXITSTATUS(child_status);
|
||||
} else if (WIFSIGNALED(child_status)) {
|
||||
exit_code = 128 + WTERMSIG(child_status);
|
||||
|
||||
@@ -200,9 +200,33 @@ internal class GitRepositoryInspector(
|
||||
userEmailResult.outputLines.firstOrNull()
|
||||
?.takeIf { userEmailResult.exitCode == 0 && it.isNotBlank() }
|
||||
?.let { put("user.email", it) }
|
||||
putAll(readGitConfigFile(inspectionRoot).filterKeys { it !in keys })
|
||||
}
|
||||
}
|
||||
|
||||
private fun readGitConfigFile(inspectionRoot: File): Map<String, String> {
|
||||
val configFile = File(inspectionRoot, ".git/config")
|
||||
if (!configFile.isFile) return emptyMap()
|
||||
|
||||
val entries = linkedMapOf<String, String>()
|
||||
var section = ""
|
||||
configFile.forEachLine { rawLine ->
|
||||
val line = rawLine.trim()
|
||||
when {
|
||||
line.isBlank() || line.startsWith("#") || line.startsWith(";") -> return@forEachLine
|
||||
line.startsWith("[") && line.endsWith("]") -> {
|
||||
section = line.removePrefix("[").removeSuffix("]").trim().substringBefore(' ')
|
||||
}
|
||||
"=" in line && section.isNotBlank() -> {
|
||||
val key = line.substringBefore('=').trim()
|
||||
val value = line.substringAfter('=').trim()
|
||||
entries["$section.$key"] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
private fun inspectRemoteRefs(git: File, inspectionRoot: File, remoteLines: List<String>): RemoteRefs {
|
||||
val remotes = remoteLines.mapNotNull { line ->
|
||||
val parts = line.trim().split(Regex("\\s+"))
|
||||
|
||||
@@ -268,12 +268,12 @@ class GitSandboxEngineTest {
|
||||
val level = configLevel()
|
||||
val repo = runtime.prepareLevel(level)
|
||||
|
||||
val (namedRepo, _) = runtime.execute(level, repo, "git config user.name GitHug")
|
||||
val (configuredRepo, _) = runtime.execute(level, namedRepo, "git config user.email githug@example.com")
|
||||
val (namedRepo, _) = runtime.execute(level, repo, "git config user.name githug")
|
||||
val (configuredRepo, _) = runtime.execute(level, namedRepo, "git config user.email xxx@yy.com")
|
||||
|
||||
assertEquals("GitHug", configuredRepo.config["user.name"])
|
||||
assertEquals("githug@example.com", configuredRepo.config["user.email"])
|
||||
assertTrue(level.validator(configuredRepo, "git config user.email githug@example.com"))
|
||||
assertEquals("githug", configuredRepo.config["user.name"])
|
||||
assertEquals("xxx@yy.com", configuredRepo.config["user.email"])
|
||||
assertTrue(level.validator(configuredRepo, "git config user.email xxx@yy.com"))
|
||||
} finally {
|
||||
root.deleteRecursively()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user