mirror of
				https://github.com/JakeWharton/mosaic.git
				synced 2025-10-31 18:58:37 +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"); | ||||||
|  | 		(*env)->ThrowNew(env, ise, "TestTty or Tty already bound"); | ||||||
|  | 	} else if (result.error) { | ||||||
| 		throwIoe(env, result.error); | 		throwIoe(env, result.error); | ||||||
| 	return 0; | 	} else { | ||||||
|  | 		jclass ise = (*env)->FindClass(env, "java/lang/OutOfMemoryException"); | ||||||
|  | 		(*env)->ThrowNew(env, ise, NULL); | ||||||
|  | 	} | ||||||
|  | 	return 0; // Unused | ||||||
| } | } | ||||||
|  |  | ||||||
| JNIEXPORT jint JNICALL | JNIEXPORT jint JNICALL | ||||||
|  | |||||||
| @ -10,15 +10,12 @@ 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() |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@Throws(IOException::class) | 	@Throws(IOException::class) | ||||||
|  | |||||||
| @ -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
	 Jake Wharton
					Jake Wharton