mirror of
https://github.com/JakeWharton/mosaic.git
synced 2025-10-31 02:39:10 +08:00
Only bind one TestTty at a time (#873)
This commit is contained in:
@ -7,7 +7,7 @@ import kotlin.test.AfterTest
|
|||||||
import kotlin.test.BeforeTest
|
import kotlin.test.BeforeTest
|
||||||
|
|
||||||
abstract class BaseEventParserTest {
|
abstract class BaseEventParserTest {
|
||||||
internal val testTty = TestTty.create()
|
internal val testTty = TestTty.bind()
|
||||||
private val tty = testTty.tty
|
private val tty = testTty.tty
|
||||||
internal val parser = EventParser(tty)
|
internal val parser = EventParser(tty)
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import kotlinx.io.unsafe.UnsafeBufferOperations
|
|||||||
|
|
||||||
fun terminalTest(block: suspend TerminalTester.() -> Unit) {
|
fun terminalTest(block: suspend TerminalTester.() -> Unit) {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
TestTty.create().use { testTty ->
|
TestTty.bind().use { testTty ->
|
||||||
TerminalTester(testTty).block()
|
TerminalTester(testTty).block()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
public final class com/jakewharton/mosaic/tty/TestTty : java/lang/AutoCloseable {
|
public final class com/jakewharton/mosaic/tty/TestTty : java/lang/AutoCloseable {
|
||||||
public static final field Companion Lcom/jakewharton/mosaic/tty/TestTty$Companion;
|
public static final field Companion Lcom/jakewharton/mosaic/tty/TestTty$Companion;
|
||||||
public synthetic fun <init> (JLcom/jakewharton/mosaic/tty/Tty;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
|
public synthetic fun <init> (JLcom/jakewharton/mosaic/tty/Tty;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
public static final fun bind ()Lcom/jakewharton/mosaic/tty/TestTty;
|
||||||
public fun close ()V
|
public fun close ()V
|
||||||
public static final fun create ()Lcom/jakewharton/mosaic/tty/TestTty;
|
|
||||||
public final fun focusEvent (Z)V
|
public final fun focusEvent (Z)V
|
||||||
public final fun getTty ()Lcom/jakewharton/mosaic/tty/Tty;
|
public final fun getTty ()Lcom/jakewharton/mosaic/tty/Tty;
|
||||||
public final fun interruptRead ()V
|
public final fun interruptRead ()V
|
||||||
@ -14,7 +14,7 @@ public final class com/jakewharton/mosaic/tty/TestTty : java/lang/AutoCloseable
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class com/jakewharton/mosaic/tty/TestTty$Companion {
|
public final class com/jakewharton/mosaic/tty/TestTty$Companion {
|
||||||
public final fun create ()Lcom/jakewharton/mosaic/tty/TestTty;
|
public final fun bind ()Lcom/jakewharton/mosaic/tty/TestTty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class com/jakewharton/mosaic/tty/Tty : java/lang/AutoCloseable {
|
public final class com/jakewharton/mosaic/tty/Tty : java/lang/AutoCloseable {
|
||||||
|
|||||||
@ -24,7 +24,7 @@ final class com.jakewharton.mosaic.tty/TestTty : kotlin/AutoCloseable { // com.j
|
|||||||
final fun write(kotlin/ByteArray, kotlin/Int, kotlin/Int): kotlin/Int // com.jakewharton.mosaic.tty/TestTty.write|write(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0]
|
final fun write(kotlin/ByteArray, kotlin/Int, kotlin/Int): kotlin/Int // com.jakewharton.mosaic.tty/TestTty.write|write(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0]
|
||||||
|
|
||||||
final object Companion { // com.jakewharton.mosaic.tty/TestTty.Companion|null[0]
|
final object Companion { // com.jakewharton.mosaic.tty/TestTty.Companion|null[0]
|
||||||
final fun create(): com.jakewharton.mosaic.tty/TestTty // com.jakewharton.mosaic.tty/TestTty.Companion.create|create(){}[0]
|
final fun bind(): com.jakewharton.mosaic.tty/TestTty // com.jakewharton.mosaic.tty/TestTty.Companion.bind|bind(){}[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -49,8 +49,9 @@ MosaicTestTtyInitResult testTty_init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MosaicTtyInitResult ttyInitResult = tty_initWithFd(childFd);
|
MosaicTtyInitResult ttyInitResult = tty_initWithFd(childFd);
|
||||||
if (unlikely(ttyInitResult.error)) {
|
if (unlikely(!ttyInitResult.tty)) {
|
||||||
result.error = ttyInitResult.error;
|
result.error = ttyInitResult.error;
|
||||||
|
result.already_bound = ttyInitResult.already_bound;
|
||||||
goto err_child;
|
goto err_child;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,11 +14,11 @@ typedef struct MosaicTestTtyImpl {
|
|||||||
atomic_bool interrupt;
|
atomic_bool interrupt;
|
||||||
} MosaicTestTtyImpl;
|
} MosaicTestTtyImpl;
|
||||||
|
|
||||||
|
static atomic_flag globalTestTty = ATOMIC_FLAG_INIT;
|
||||||
|
|
||||||
MosaicTestTtyInitResult testTty_init() {
|
MosaicTestTtyInitResult testTty_init() {
|
||||||
MosaicTestTtyInitResult result = {};
|
MosaicTestTtyInitResult result = {};
|
||||||
|
|
||||||
// TODO Atomic boolean guaranteeing single instance
|
|
||||||
|
|
||||||
MosaicTestTtyImpl *testTty = calloc(1, sizeof(MosaicTestTtyImpl));
|
MosaicTestTtyImpl *testTty = calloc(1, sizeof(MosaicTestTtyImpl));
|
||||||
if (unlikely(testTty == NULL)) {
|
if (unlikely(testTty == NULL)) {
|
||||||
// result.testTty is set to 0 which will trigger OOM.
|
// result.testTty is set to 0 which will trigger OOM.
|
||||||
@ -46,10 +46,9 @@ MosaicTestTtyInitResult testTty_init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MosaicTtyInitResult ttyInitResult = tty_initWithHandles(conin, conoutWrite, true);
|
MosaicTtyInitResult ttyInitResult = tty_initWithHandles(conin, conoutWrite, true);
|
||||||
if (unlikely(ttyInitResult.error)) {
|
if (unlikely(!ttyInitResult.tty)) {
|
||||||
CloseHandle(conoutRead);
|
|
||||||
CloseHandle(conoutWrite);
|
|
||||||
result.error = ttyInitResult.error;
|
result.error = ttyInitResult.error;
|
||||||
|
result.already_bound = ttyInitResult.already_bound;
|
||||||
goto err_conout;
|
goto err_conout;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,6 +58,14 @@ MosaicTestTtyInitResult testTty_init() {
|
|||||||
|
|
||||||
result.testTty = testTty;
|
result.testTty = testTty;
|
||||||
|
|
||||||
|
if (unlikely(atomic_flag_test_and_set(&globalTestTty))) {
|
||||||
|
// We initialized an instance but there already was a global instance.
|
||||||
|
result.testTty = NULL;
|
||||||
|
result.error = tty_free(ttyInitResult.tty);
|
||||||
|
result.already_bound = true;
|
||||||
|
goto err_conout;
|
||||||
|
}
|
||||||
|
|
||||||
ret:
|
ret:
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
@ -188,6 +195,7 @@ uint32_t testTty_free(MosaicTestTty *testTty) {
|
|||||||
result = GetLastError();
|
result = GetLastError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
atomic_flag_clear(&globalTestTty);
|
||||||
free(testTty);
|
free(testTty);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ typedef struct MosaicTestTtyImpl MosaicTestTty;
|
|||||||
typedef struct MosaicTestTtyInitResult {
|
typedef struct MosaicTestTtyInitResult {
|
||||||
MosaicTestTty *testTty;
|
MosaicTestTty *testTty;
|
||||||
uint32_t error;
|
uint32_t error;
|
||||||
|
bool already_bound;
|
||||||
} MosaicTestTtyInitResult;
|
} MosaicTestTtyInitResult;
|
||||||
|
|
||||||
MosaicTestTtyInitResult testTty_init();
|
MosaicTestTtyInitResult testTty_init();
|
||||||
|
|||||||
@ -14,6 +14,8 @@
|
|||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
static _Atomic(MosaicTty *) globalTty;
|
||||||
|
|
||||||
MosaicTtyInitResult tty_initWithFd(int fd) {
|
MosaicTtyInitResult tty_initWithFd(int fd) {
|
||||||
MosaicTtyInitResult result = {};
|
MosaicTtyInitResult result = {};
|
||||||
|
|
||||||
@ -35,29 +37,6 @@ MosaicTtyInitResult tty_initWithFd(int fd) {
|
|||||||
|
|
||||||
result.tty = tty;
|
result.tty = tty;
|
||||||
|
|
||||||
ret:
|
|
||||||
return result;
|
|
||||||
|
|
||||||
err:
|
|
||||||
free(tty);
|
|
||||||
goto ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static _Atomic(MosaicTty *) globalTty;
|
|
||||||
|
|
||||||
MosaicTtyInitResult tty_init() {
|
|
||||||
MosaicTtyInitResult result = {};
|
|
||||||
|
|
||||||
int fd = open("/dev/tty", O_RDWR);
|
|
||||||
if (unlikely(fd == -1)) {
|
|
||||||
result.error = errno;
|
|
||||||
result.no_tty = true;
|
|
||||||
goto ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = tty_initWithFd(fd);
|
|
||||||
|
|
||||||
MosaicTty *tty = result.tty;
|
|
||||||
MosaicTty *expected = NULL;
|
MosaicTty *expected = NULL;
|
||||||
if (likely(tty) && !atomic_compare_exchange_strong(&globalTty, &expected, tty)) {
|
if (likely(tty) && !atomic_compare_exchange_strong(&globalTty, &expected, tty)) {
|
||||||
// We initialized an instance but there already was a global instance.
|
// We initialized an instance but there already was a global instance.
|
||||||
@ -68,6 +47,22 @@ MosaicTtyInitResult tty_init() {
|
|||||||
|
|
||||||
ret:
|
ret:
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
|
err:
|
||||||
|
free(tty);
|
||||||
|
goto ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
MosaicTtyInitResult tty_init() {
|
||||||
|
int fd = open("/dev/tty", O_RDWR);
|
||||||
|
if (likely(fd != -1)) {
|
||||||
|
return tty_initWithFd(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
MosaicTtyInitResult result = {};
|
||||||
|
result.error = errno;
|
||||||
|
result.no_tty = true;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void tty_setCallback(MosaicTty *tty, MosaicTtyCallback *callback) {
|
void tty_setCallback(MosaicTty *tty, MosaicTtyCallback *callback) {
|
||||||
@ -173,6 +168,8 @@ void sigwinchHandler(int value UNUSED) {
|
|||||||
} else {
|
} else {
|
||||||
// TODO Send errno somewhere? Maybe once we get debug logs working.
|
// TODO Send errno somewhere? Maybe once we get debug logs working.
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// TODO Send warning somewhere? Maybe once we get debug logs working.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,16 @@ package com.jakewharton.mosaic.tty
|
|||||||
|
|
||||||
public expect class TestTty : AutoCloseable {
|
public expect class TestTty : AutoCloseable {
|
||||||
public companion object {
|
public companion object {
|
||||||
public fun create(): TestTty
|
|
||||||
|
/**
|
||||||
|
* Initialize a [TestTty] instance. Only a single [TestTty] instance can be bound at a time,
|
||||||
|
* and only when a [Tty] is not also bound. Subsequent calls will throw an exception until
|
||||||
|
* [TestTty.close] is called.
|
||||||
|
*
|
||||||
|
* @throws IOException If an error occurred creating the PTY.
|
||||||
|
* @throws IllegalStateException If another instance is already bound.
|
||||||
|
*/
|
||||||
|
public fun bind(): TestTty
|
||||||
}
|
}
|
||||||
|
|
||||||
public val tty: Tty
|
public val tty: Tty
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
package com.jakewharton.mosaic.tty
|
package com.jakewharton.mosaic.tty
|
||||||
|
|
||||||
|
import assertk.assertFailure
|
||||||
import assertk.assertThat
|
import assertk.assertThat
|
||||||
|
import assertk.assertions.hasMessage
|
||||||
import assertk.assertions.isEqualTo
|
import assertk.assertions.isEqualTo
|
||||||
|
import assertk.assertions.isInstanceOf
|
||||||
import assertk.assertions.isZero
|
import assertk.assertions.isZero
|
||||||
import kotlin.test.AfterTest
|
import kotlin.test.AfterTest
|
||||||
import kotlin.test.BeforeTest
|
import kotlin.test.BeforeTest
|
||||||
@ -13,7 +16,7 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
||||||
class TestTtyTest {
|
class TestTtyTest {
|
||||||
private val testTty = TestTty.create()
|
private val testTty = TestTty.bind()
|
||||||
private val tty = testTty.tty
|
private val tty = testTty.tty
|
||||||
|
|
||||||
@BeforeTest fun before() {
|
@BeforeTest fun before() {
|
||||||
@ -26,15 +29,11 @@ class TestTtyTest {
|
|||||||
testTty.close()
|
testTty.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun canCreateMultiple() {
|
@Test fun onlyOne() {
|
||||||
if (isWindows()) return // TODO Not currently supported.
|
assertFailure {
|
||||||
|
TestTty.bind()
|
||||||
TestTty.create().use { testTty2 ->
|
}.isInstanceOf<IllegalStateException>()
|
||||||
testTty.write("hey\n")
|
.hasMessage("TestTty or Tty already bound")
|
||||||
testTty2.write("bye\n")
|
|
||||||
assertThat(testTty2.tty.read(4)).isEqualTo("bye\n")
|
|
||||||
assertThat(testTty.tty.read(4)).isEqualTo("hey\n")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun multipleRawModeResetCycles() {
|
@Test fun multipleRawModeResetCycles() {
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import kotlinx.coroutines.test.runTest
|
|||||||
|
|
||||||
class TtyTest {
|
class TtyTest {
|
||||||
private val events = ArrayDeque<String>()
|
private val events = ArrayDeque<String>()
|
||||||
private val testTty = TestTty.create()
|
private val testTty = TestTty.bind()
|
||||||
private val tty = testTty.tty
|
private val tty = testTty.tty
|
||||||
|
|
||||||
@BeforeTest fun before() {
|
@BeforeTest fun before() {
|
||||||
|
|||||||
@ -360,14 +360,20 @@ Java_com_jakewharton_mosaic_tty_Jni_testTtyInit(
|
|||||||
jclass type UNUSED
|
jclass type UNUSED
|
||||||
) {
|
) {
|
||||||
MosaicTestTtyInitResult result = testTty_init();
|
MosaicTestTtyInitResult result = testTty_init();
|
||||||
if (likely(!result.error)) {
|
if (likely(result.testTty)) {
|
||||||
return (jlong) result.testTty;
|
return (jlong) result.testTty;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This throw can fail, but the only condition that should cause that is OOM which
|
if (result.already_bound) {
|
||||||
// will occur from returning 0 (which is otherwise ignored if the throw succeeds).
|
jclass ise = (*env)->FindClass(env, "java/lang/IllegalStateException");
|
||||||
throwIoe(env, result.error);
|
(*env)->ThrowNew(env, ise, "TestTty or Tty already bound");
|
||||||
return 0;
|
} else if (result.error) {
|
||||||
|
throwIoe(env, result.error);
|
||||||
|
} else {
|
||||||
|
jclass ise = (*env)->FindClass(env, "java/lang/OutOfMemoryException");
|
||||||
|
(*env)->ThrowNew(env, ise, NULL);
|
||||||
|
}
|
||||||
|
return 0; // Unused
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jint JNICALL
|
JNIEXPORT jint JNICALL
|
||||||
|
|||||||
@ -10,14 +10,11 @@ public actual class TestTty private constructor(
|
|||||||
public actual companion object {
|
public actual companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
public actual fun create(): TestTty {
|
public actual fun bind(): TestTty {
|
||||||
val testTtyPtr = testTtyInit()
|
val testTtyPtr = testTtyInit()
|
||||||
if (testTtyPtr != 0L) {
|
val ttyPtr = testTtyGetTty(testTtyPtr)
|
||||||
val ttyPtr = testTtyGetTty(testTtyPtr)
|
val tty = Tty(ttyPtr)
|
||||||
val tty = Tty(ttyPtr)
|
return TestTty(testTtyPtr, tty)
|
||||||
return TestTty(testTtyPtr, tty)
|
|
||||||
}
|
|
||||||
throw OutOfMemoryError()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,10 +10,13 @@ public actual class TestTty private constructor(
|
|||||||
public actual val tty: Tty,
|
public actual val tty: Tty,
|
||||||
) : AutoCloseable {
|
) : AutoCloseable {
|
||||||
public actual companion object {
|
public actual companion object {
|
||||||
public actual fun create(): TestTty {
|
public actual fun bind(): TestTty {
|
||||||
val testTtyPtr = testTty_init().useContents {
|
val testTtyPtr = testTty_init().useContents {
|
||||||
testTty?.let { return@useContents it }
|
testTty?.let { return@useContents it }
|
||||||
|
|
||||||
|
if (already_bound) {
|
||||||
|
throw IllegalStateException("TestTty or Tty already bound")
|
||||||
|
}
|
||||||
if (error != 0U) {
|
if (error != 0U) {
|
||||||
throwIoe(error)
|
throwIoe(error)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user