MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

add coroutine/async this preservation tests

+280 -5
+3 -5
src/ant.c
··· 11140 11140 js->pos = try_start; 11141 11141 js->consumed = 1; 11142 11142 11143 + mkscope(js); 11143 11144 while (next(js) != TOK_EOF && next(js) != TOK_RBRACE && !(js->flags & (F_RETURN | F_THROW | F_BREAK))) { 11144 11145 try_result = js_stmt(js); 11145 - if (is_err(try_result)) { 11146 - had_exception = true; 11147 - break; 11148 - } 11149 - } 11146 + if (is_err(try_result)) { had_exception = true; break; } 11147 + } delscope(js); 11150 11148 11151 11149 if (js->flags & F_RETURN) { 11152 11150 try_returned = true;
+58
src/modules/process.c
··· 309 309 } 310 310 } 311 311 312 + static bool remove_listener_from_events(ProcessEventType *events, const char *event, jsval_t listener) { 313 + ProcessEventType *evt = NULL; 314 + HASH_FIND_STR(events, event, evt); 315 + if (!evt) return false; 316 + 317 + for (int i = 0; i < evt->listener_count; i++) { 318 + if (evt->listeners[i].listener != listener) continue; 319 + memmove( 320 + &evt->listeners[i], &evt->listeners[i + 1], 321 + (size_t)(evt->listener_count - i - 1) * sizeof(ProcessEventListener) 322 + ); 323 + return --evt->listener_count == 0; 324 + } 325 + 326 + return false; 327 + } 328 + 312 329 static bool stdin_is_tty(void) { 313 330 return uv_guess_handle(STDIN_FILENO) == UV_TTY; 314 331 } ··· 501 518 return this_obj; 502 519 } 503 520 521 + static jsval_t js_stdin_remove_listener(ant_t *js, jsval_t *args, int nargs) { 522 + jsval_t this_obj = js_getthis(js); 523 + if (nargs < 2) return this_obj; 524 + 525 + char *event = js_getstr(js, args[0], NULL); 526 + if (!event) return this_obj; 527 + 528 + bool now_empty = remove_listener_from_events(stdin_events, event, args[1]); 529 + if (now_empty && strcmp(event, "data") == 0) stdin_stop_reading(); 530 + 531 + return this_obj; 532 + } 533 + 504 534 static jsval_t js_stdout_write(ant_t *js, jsval_t *args, int nargs) { 505 535 if (nargs < 1) return js_mkfalse(); 506 536 size_t len = 0; ··· 567 597 HASH_FIND_STR(stdout_events, event, evt); 568 598 if (evt) evt->listener_count = 0; 569 599 600 + return this_obj; 601 + } 602 + 603 + static jsval_t js_stdout_remove_listener(ant_t *js, jsval_t *args, int nargs) { 604 + jsval_t this_obj = js_getthis(js); 605 + if (nargs < 2) return this_obj; 606 + 607 + char *event = js_getstr(js, args[0], NULL); 608 + if (!event) return this_obj; 609 + 610 + remove_listener_from_events(stdout_events, event, args[1]); 570 611 return this_obj; 571 612 } 572 613 ··· 670 711 HASH_FIND_STR(stderr_events, event, evt); 671 712 if (evt) evt->listener_count = 0; 672 713 714 + return this_obj; 715 + } 716 + 717 + static jsval_t js_stderr_remove_listener(ant_t *js, jsval_t *args, int nargs) { 718 + jsval_t this_obj = js_getthis(js); 719 + if (nargs < 2) return this_obj; 720 + 721 + char *event = js_getstr(js, args[0], NULL); 722 + if (!event) return this_obj; 723 + 724 + remove_listener_from_events(stderr_events, event, args[1]); 673 725 return this_obj; 674 726 } 675 727 ··· 1376 1428 js_set(js, stdin_proto, "resume", js_mkfun(js_stdin_resume)); 1377 1429 js_set(js, stdin_proto, "pause", js_mkfun(js_stdin_pause)); 1378 1430 js_set(js, stdin_proto, "on", js_mkfun(js_stdin_on)); 1431 + js_set(js, stdin_proto, "removeListener", js_mkfun(js_stdin_remove_listener)); 1432 + js_set(js, stdin_proto, "off", js_mkfun(js_stdin_remove_listener)); 1379 1433 js_set(js, stdin_proto, "removeAllListeners", js_mkfun(js_stdin_remove_all_listeners)); 1380 1434 js_set(js, stdin_proto, get_toStringTag_sym_key(), js_mkstr(js, "ReadStream", 10)); 1381 1435 ··· 1388 1442 js_set(js, stdout_proto, "write", js_mkfun(js_stdout_write)); 1389 1443 js_set(js, stdout_proto, "on", js_mkfun(js_stdout_on)); 1390 1444 js_set(js, stdout_proto, "once", js_mkfun(js_stdout_once)); 1445 + js_set(js, stdout_proto, "removeListener", js_mkfun(js_stdout_remove_listener)); 1446 + js_set(js, stdout_proto, "off", js_mkfun(js_stdout_remove_listener)); 1391 1447 js_set(js, stdout_proto, "removeAllListeners", js_mkfun(js_stdout_remove_all_listeners)); 1392 1448 js_set(js, stdout_proto, "getWindowSize", js_mkfun(js_stdout_get_window_size)); 1393 1449 js_set(js, stdout_proto, get_toStringTag_sym_key(), js_mkstr(js, "WriteStream", 11)); ··· 1403 1459 js_set(js, stderr_proto, "write", js_mkfun(js_stderr_write)); 1404 1460 js_set(js, stderr_proto, "on", js_mkfun(js_stderr_on)); 1405 1461 js_set(js, stderr_proto, "once", js_mkfun(js_stderr_once)); 1462 + js_set(js, stderr_proto, "removeListener", js_mkfun(js_stderr_remove_listener)); 1463 + js_set(js, stderr_proto, "off", js_mkfun(js_stderr_remove_listener)); 1406 1464 js_set(js, stderr_proto, "removeAllListeners", js_mkfun(js_stderr_remove_all_listeners)); 1407 1465 js_set(js, stderr_proto, get_toStringTag_sym_key(), js_mkstr(js, "WriteStream", 11)); 1408 1466
+219
tests/test_coro_this.js
··· 1 + // Test that `this` is preserved correctly in coroutine/async contexts 2 + 3 + class Widget { 4 + constructor(name) { 5 + this.name = name; 6 + this._value = 42; 7 + this._boundMethod = this._boundMethod.bind(this); 8 + } 9 + 10 + _boundMethod() { 11 + return this._value; 12 + } 13 + 14 + unboundMethod() { 15 + return this._value; 16 + } 17 + 18 + callBound() { 19 + return this._boundMethod(); 20 + } 21 + 22 + callUnbound() { 23 + return this.unboundMethod(); 24 + } 25 + } 26 + 27 + let passed = 0; 28 + let failed = 0; 29 + 30 + function test(name, fn) { 31 + try { 32 + const result = fn(); 33 + if (result instanceof Promise) { 34 + result.then(() => { 35 + console.log(`โœ“ ${name}`); 36 + passed++; 37 + }).catch(e => { 38 + console.log(`โœ— ${name}`); 39 + console.log(` Error: ${e.message}`); 40 + failed++; 41 + }); 42 + } else { 43 + console.log(`โœ“ ${name}`); 44 + passed++; 45 + } 46 + } catch (e) { 47 + console.log(`โœ— ${name}`); 48 + console.log(` Error: ${e.message}`); 49 + failed++; 50 + } 51 + } 52 + 53 + function assertEqual(a, b, msg) { 54 + if (a !== b) throw new Error(msg || `Expected ${b}, got ${a}`); 55 + } 56 + 57 + console.log('=== Coroutine/Async this preservation tests ===\n'); 58 + 59 + // Sync tests 60 + console.log('-- Synchronous --'); 61 + 62 + test('Direct method call preserves this', () => { 63 + const w = new Widget('test'); 64 + assertEqual(w.callBound(), 42); 65 + }); 66 + 67 + test('Bound method works', () => { 68 + const w = new Widget('test'); 69 + assertEqual(w._boundMethod(), 42); 70 + }); 71 + 72 + test('Unbound method via this works', () => { 73 + const w = new Widget('test'); 74 + assertEqual(w.callUnbound(), 42); 75 + }); 76 + 77 + // Test in callback context 78 + console.log('\n-- In callback context --'); 79 + 80 + test('Method call in setTimeout callback', () => { 81 + return new Promise((resolve, reject) => { 82 + const w = new Widget('test'); 83 + setTimeout(() => { 84 + try { 85 + assertEqual(w.callBound(), 42); 86 + resolve(); 87 + } catch (e) { 88 + reject(e); 89 + } 90 + }, 10); 91 + }); 92 + }); 93 + 94 + test('Method call in Promise.then', () => { 95 + const w = new Widget('test'); 96 + return Promise.resolve().then(() => { 97 + assertEqual(w.callBound(), 42); 98 + }); 99 + }); 100 + 101 + // Test in event handler context (simulated) 102 + console.log('\n-- In event handler context --'); 103 + 104 + test('Method call from stored handler', () => { 105 + const w = new Widget('test'); 106 + const handlers = []; 107 + 108 + handlers.push(() => { 109 + return w.callBound(); 110 + }); 111 + 112 + for (const h of handlers) { 113 + assertEqual(h(), 42); 114 + } 115 + }); 116 + 117 + test('Bound method passed as callback', () => { 118 + const w = new Widget('test'); 119 + const fn = w._boundMethod; 120 + assertEqual(fn(), 42); 121 + }); 122 + 123 + // Test class with stdin-like event pattern 124 + console.log('\n-- Event emitter pattern --'); 125 + 126 + class FakeScreen { 127 + constructor() { 128 + this._running = true; 129 + this._handlers = []; 130 + this._cleanup = this._cleanup.bind(this); 131 + } 132 + 133 + _cleanup() { 134 + if (!this._running) return 'already stopped'; 135 + this._running = false; 136 + return 'cleaned up'; 137 + } 138 + 139 + onKey(fn) { 140 + this._handlers.push(fn); 141 + } 142 + 143 + exit() { 144 + const result = this._cleanup(); 145 + return result; 146 + } 147 + 148 + emitKey(key) { 149 + for (const h of this._handlers) { 150 + h(key); 151 + } 152 + } 153 + } 154 + 155 + test('Exit method calls _cleanup correctly', () => { 156 + const screen = new FakeScreen(); 157 + let exitResult = null; 158 + 159 + screen.onKey((key) => { 160 + if (key === 'q') { 161 + exitResult = screen.exit(); 162 + } 163 + }); 164 + 165 + screen.emitKey('q'); 166 + assertEqual(exitResult, 'cleaned up'); 167 + }); 168 + 169 + // Test with async handler 170 + console.log('\n-- Async event handlers --'); 171 + 172 + test('Exit in async handler', async () => { 173 + const screen = new FakeScreen(); 174 + let exitResult = null; 175 + 176 + screen.onKey(async (key) => { 177 + if (key === 'q') { 178 + await Promise.resolve(); // simulate async work 179 + exitResult = screen.exit(); 180 + } 181 + }); 182 + 183 + screen.emitKey('q'); 184 + await Promise.resolve(); 185 + await Promise.resolve(); // Give time for async handler 186 + assertEqual(exitResult, 'cleaned up'); 187 + }); 188 + 189 + // Test stdin data event pattern 190 + console.log('\n-- stdin.on pattern --'); 191 + 192 + test('Method call from stdin data handler', () => { 193 + return new Promise((resolve, reject) => { 194 + const screen = new FakeScreen(); 195 + let result = null; 196 + 197 + const handler = (chunk) => { 198 + const str = chunk.toString ? chunk.toString() : chunk; 199 + if (str === 'q') { 200 + try { 201 + result = screen.exit(); 202 + assertEqual(result, 'cleaned up'); 203 + resolve(); 204 + } catch (e) { 205 + reject(e); 206 + } 207 + } 208 + }; 209 + 210 + // Simulate stdin event 211 + setTimeout(() => handler('q'), 10); 212 + }); 213 + }); 214 + 215 + // Wait for async tests 216 + setTimeout(() => { 217 + console.log(`\n=== Results: ${passed} passed, ${failed} failed ===`); 218 + process.exit(failed > 0 ? 1 : 0); 219 + }, 200);