char-slop/berry
multipurpose self-modifiable agentic harness
git clone https://git.t4t.associates/char-slop/berry
0853dde
main
1import { assert , assertEquals , assertRejects , assertStringIncludes } from "@std/assert" ; 2import { BACKGROUND_CONTEXT , withAbortSignal } from "@earendil-works/chord/context" ; 3import { join } from "node:path" ; 4import { tempDir , until } from "@berry/util" ; 5import { localEnvironment } from "./environment.ts" ; 6 7Deno . test ({ 8name : 9"pseudo-terminals preserve argv, Unicode, cwd and environment, and support input and resizing" , 10ignore :! [ "linux" , "windows" ]. includes ( Deno . build . os ), 11async fn () { 12await usingroot = await tempDir (); 13const cwd = join ( root . path , "terminal 🐈" ); 14await Deno . mkdir ( cwd ); 15const env = localEnvironment ( cwd ); 16const args = [ "" , 'a "quote"' , "C:\\trailing\\" , "héllo 🐈" ]; 17const script = ` 18console.log('args=' + JSON.stringify(Deno.args)); 19console.log('cwd=' + Deno.cwd()); 20console.log('env=' + Deno.env.get('BERRY_PTY_TEST')); 21console.log('tty=' + Deno.stdin.isTerminal() + ',' + Deno.stdout.isTerminal()); 22if (Deno.build.os === 'windows') { 23const kernel = Deno.dlopen('kernel32.dll', { 24SetConsoleCP: { parameters: ['u32'], result: 'i32' }, 25}); 26// ReadFile uses the application's console code page, not ConPTY's UTF-8 transport 27if (!kernel.symbols.SetConsoleCP(65001)) throw new Error('SetConsoleCP failed'); 28kernel.close(); 29} 30Deno.stdin.setRaw(true); 31console.log('ready'); 32const input = new Uint8Array(1024); 33const decoder = new TextDecoder(); 34let typed = ''; 35while (!typed.includes('\\r')) { 36const count = await Deno.stdin.read(input); 37if (count === null) break; 38typed += decoder.decode(input.subarray(0, count), { stream: true }); 39} 40console.log('input=' + typed.trimEnd()); 41console.log('size=' + JSON.stringify(Deno.consoleSize())); 42Deno.exit(7); 43` ; 44const pty = await env . pty ([ Deno . execPath (), "eval" , script , ...args ], { 45cols :160 , 46rows :30 , 47env :{ BERRY_PTY_TEST :"value 🐈" }, 48}, BACKGROUND_CONTEXT ); 49let output = "" ; 50const reading = ( async () => { 51const decoder = new TextDecoder (); 52for await ( const chunk of pty . readable ) output += decoder . decode ( chunk , { stream :true }); 53output += decoder . decode (); 54})(); 55const input = pty . writable . getWriter (); 56try { 57await until (() => output . includes ( "ready" )); 58assertStringIncludes ( output , "args=" + JSON . stringify ( args )); 59assertStringIncludes ( output , "cwd=" + cwd ); 60assertStringIncludes ( output , "env=value 🐈" ); 61assertStringIncludes ( output , "tty=true,true" ); 62pty . resize ( 140 , 40 ); 63await input . write ( new TextEncoder (). encode ( "typed héllo\r" )); 64assertEquals ( await pty . exited , 7 ); 65await reading ; 66assertStringIncludes ( output , "input=typed héllo" ); 67assertStringIncludes ( output , 'size={"columns":140,"rows":40}' ); 68} finally { 69pty . kill (); 70await reading ; 71await pty . exited ; 72input . releaseLock (); 73} 74}, 75}); 76 77Deno . test ({ 78name :"ConPTY closes on abort and output cancellation, and can start again after a failed spawn" , 79ignore :Deno . build . os !== "windows" , 80async fn () { 81const env = localEnvironment ( Deno . cwd ()); 82await assertRejects (() => 83env . pty ([ "berry-nonexistent-program.exe" ], { 84cols :80 , 85rows :24 , 86}, BACKGROUND_CONTEXT ) 87); 88 89const controller = new AbortController (); 90const pty = await env . pty ([ Deno . execPath (), "eval" , "setInterval(() => {}, 1000)" ], { 91cols :80 , 92rows :24 , 93}, withAbortSignal ( controller . signal , BACKGROUND_CONTEXT )); 94const drained = pty . readable . pipeTo ( new WritableStream ()); 95controller . abort (); 96assertEquals ( await pty . exited , null ); 97await drained ; 98 99const noisy = await env . pty ( 100[ 101Deno . execPath (), 102"eval" , 103` 104const block = new TextEncoder().encode('x'.repeat(65536)); 105while (true) await Deno.stdout.write(block); 106` , 107], 108{ cols :80 , rows :24 }, 109BACKGROUND_CONTEXT , 110); 111const reader = noisy . readable . getReader (); 112assert ( ! ( await reader . read ()). done ); 113await reader . cancel (); 114await noisy . exited ; 115noisy . kill (); 116}, 117});