Drive all levels through UI instrumentation

This commit is contained in:
Joe Tretter
2026-06-24 19:44:09 -05:00
parent 1c15297ae6
commit 1e87bd9aa7
8 changed files with 171 additions and 44 deletions

View File

@@ -21,11 +21,6 @@ struct output_buffer {
size_t capacity;
};
struct reader_state {
int fd;
struct output_buffer output;
};
static pthread_mutex_t git_mutex = PTHREAD_MUTEX_INITIALIZER;
static void *git_handle = NULL;
static git_main_fn git_main = NULL;
@@ -54,23 +49,28 @@ static int append_output(struct output_buffer *buffer, const char *data, size_t
return 0;
}
static void *read_output(void *arg) {
struct reader_state *state = (struct reader_state *)arg;
static int drain_available_output(int fd, struct output_buffer *output, int *saw_eof) {
char chunk[4096];
for (;;) {
ssize_t count = read(state->fd, chunk, sizeof(chunk));
ssize_t count = read(fd, chunk, sizeof(chunk));
if (count > 0) {
if (append_output(&state->output, chunk, (size_t)count) != 0) {
break;
if (append_output(output, chunk, (size_t)count) != 0) {
return -1;
}
} else if (count == 0) {
break;
} else if (errno != EINTR) {
break;
continue;
}
if (count == 0) {
*saw_eof = 1;
return 0;
}
if (errno == EINTR) {
continue;
}
if (errno == EAGAIN || errno == EWOULDBLOCK) {
return 0;
}
return -1;
}
close(state->fd);
return NULL;
}
static jobjectArray make_result(JNIEnv *env, int exit_code, const char *output) {
@@ -267,12 +267,36 @@ Java_solutions_tretter_githugandroid_NativeGitBridge_runGitMainNative(
}
close(pipe_fds[1]);
struct reader_state reader = { .fd = pipe_fds[0], .output = {0} };
read_output(&reader);
struct output_buffer output = {0};
int flags = fcntl(pipe_fds[0], F_GETFL, 0);
if (flags >= 0) {
fcntl(pipe_fds[0], F_SETFL, flags | O_NONBLOCK);
}
int child_status = 0;
while (waitpid(child, &child_status, 0) < 0 && errno == EINTR) {
int child_done = 0;
int saw_eof = 0;
while (!child_done || !saw_eof) {
if (drain_available_output(pipe_fds[0], &output, &saw_eof) != 0) {
break;
}
if (!child_done) {
pid_t wait_result = waitpid(child, &child_status, WNOHANG);
if (wait_result == child) {
child_done = 1;
} else if (wait_result < 0 && errno != EINTR) {
child_done = 1;
}
}
if (child_done) {
if (!saw_eof) {
drain_available_output(pipe_fds[0], &output, &saw_eof);
}
break;
}
usleep(10000);
}
close(pipe_fds[0]);
pthread_mutex_unlock(&git_mutex);
int exit_code = -1;
@@ -282,9 +306,9 @@ Java_solutions_tretter_githugandroid_NativeGitBridge_runGitMainNative(
exit_code = 128 + WTERMSIG(child_status);
}
jobjectArray result = make_result(env, exit_code, reader.output.data);
jobjectArray result = make_result(env, exit_code, output.data);
free(reader.output.data);
free(output.data);
free_string_array(argv, argc);
free_string_array(env_entries, envc);
(*env)->ReleaseStringUTFChars(env, library_path, library_path_chars);

View File

@@ -25,6 +25,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.layout.boundsInRoot
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
@@ -51,7 +52,12 @@ fun ExercisePane(
}
SelectionContainer {
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
Text(level.title, style = MaterialTheme.typography.titleLarge, color = TextPrimary)
Text(
text = level.title,
modifier = Modifier.testTag("exercise-title-${level.id}"),
style = MaterialTheme.typography.titleLarge,
color = TextPrimary,
)
Text(level.description, color = TextSecondary)
}
}

View File

@@ -11,6 +11,7 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
@Composable
@@ -33,7 +34,9 @@ fun LevelsPane(
) {
TextButton(
onClick = { onSelect(index) },
modifier = Modifier.fillMaxWidth(),
modifier = Modifier
.fillMaxWidth()
.testTag("level-${level.id}"),
colors = ButtonDefaults.textButtonColors(
contentColor = if (index == currentLevelIndex) Accent else TextSecondary,
),
@@ -49,4 +52,4 @@ fun LevelsPane(
}
}
}
}
}

View File

@@ -45,6 +45,7 @@ import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.key.type
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.TextStyle
@@ -232,6 +233,7 @@ private fun RowScope.TerminalInputField(
onValueChange = onValueChange,
modifier = Modifier
.weight(1f)
.testTag("terminal-command-input")
.focusRequester(focusRequester)
.onFocusChanged { state ->
if (state.isFocused) {