char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
688e96e
main
1mod common; 2 3use std:: fs; 4use std:: process::{ Command , Stdio }; 5 6fn run ( 7source : & str , 8flags : & [ & str ], 9fixtures : & [( & str , & str )], 10) ->( tempfile:: TempDir , std:: process:: Output ) { 11let dir = tempfile:: tempdir (). unwrap (); 12 fs:: write ( dir. path (). join ( "main.js" ), source). unwrap (); 13 fs:: write ( dir. path (). join ( "data" ), b"abcdef" ). unwrap (); 14for ( name, contents) in fixtures{ 15let path = dir. path (). join ( name); 16 fs:: create_dir_all ( path. parent (). unwrap ()). unwrap (); 17 fs:: write ( path, contents). unwrap (); 18} 19let child =Command :: new ( env! ( "CARGO_BIN_EXE_avril" )) 20. arg ( "run" ) 21. args ( flags) 22. arg ( "main.js" ) 23. current_dir ( dir. path ()) 24. stdin ( Stdio :: null ()) 25. stdout ( Stdio :: piped ()) 26. stderr ( Stdio :: piped ()) 27. spawn () 28. unwrap (); 29let output = common:: wait ( child); 30assert! ( 31 output. status . success (), 32"{}" , 33String :: from_utf8_lossy ( & output. stderr ) 34); 35( dir, output) 36} 37 38# [ test ] 39fn mixed_operations_share_handles_and_cursor_without_running_js () { 40let ( dir, output) =run ( 41r#" 42import { open as openAsync, FileHandle } from 'avril:std'; 43import { open, create, SeekMode, stat, FileHandle as SyncHandle } from 'avril:std/sync'; 44import { readTextFile } from 'avril:fs/sync'; 45if (FileHandle !== SyncHandle) throw Error('Separate handle families'); 46const file = open('data', {read: true, write: true}); 47if (!(file instanceof FileHandle)) throw Error('Wrong handle type'); 48const a = new Uint8Array(2), b = new Uint8Array(2); 49let ran = false; 50queueMicrotask(() => ran = true); 51const first = file.read(a); 52if (file.readSync(b) !== 2 || new TextDecoder().decode(b) !== 'cd') throw Error('Read order'); 53if (ran) throw Error('Sync read ran JavaScript'); 54const seek = file.seek(0, SeekMode.Start); 55if (file.writeSync(new Uint8Array([88])) !== 1) throw Error('Write'); 56if (file.seekSync(-1, SeekMode.End) !== 5 || file.readSync(b) !== 1 || b[0] !== 102) throw Error('Seek'); 57if (file.readSync(b) !== null || file.readSync(new Uint8Array()) !== 0) throw Error('EOF'); 58if (await first !== 2 || new TextDecoder().decode(a) !== 'ab' || await seek !== 0) throw Error('Async completion'); 59file.truncateSync(3); file.syncDataSync(); file.syncSync(); 60if (file.statSync().size !== 3 || stat(new URL('./data', import.meta.url)).size !== 3) throw Error('Stat'); 61file.close(); 62const reopened = await openAsync('data'); 63if (reopened.readSync(b) !== 2 || b[0] !== 88) throw Error('Async-opened handle'); 64reopened[Symbol.dispose](); 65const out = create('out'); 66const writes = []; 67for (let i = 0; i < 100; i++) writes.push(out.write(new Uint8Array([65 + i % 26]))); 68out.seekSync(0, 0); 69const all = new Uint8Array(100); 70if (out.readSync(all) !== 100 || all.some((v, i) => v !== 65 + i % 26)) throw Error('Queued writes'); 71if ((await Promise.all(writes)).some(n => n !== 1)) throw Error('Write completions'); 72out.close(); 73if (readTextFile('data') !== 'Xbc') throw Error('Persisted contents'); 74console.log('mixed IO'); 75"# , 76& [], 77& [], 78); 79assert_eq! ( output. stdout , b"mixed IO\n" ); 80assert_eq! ( fs:: read ( dir. path (). join ( "data" )). unwrap (), b"Xbc" ); 81} 82 83# [ test ] 84fn synchronous_paths_and_workflows_preserve_contents () { 85let ( dir, _) =run ( 86r#" 87import { mkdir, readDir, copyFile, rename, remove, stat, lstat, realPath, truncate } from 'avril:std/sync'; 88import { mkdirs, copy, move, exists, readFile, readTextFile, writeFile, writeTextFile } from 'avril:fs/sync'; 89mkdir('tree'); mkdirs('tree/deep/nested'); mkdirs('tree/deep/nested'); 90const bytes = new Uint8Array(200003); 91for (let i = 0; i < bytes.length; i++) bytes[i] = i % 251; 92writeFile('tree/deep/nested/bytes', bytes); 93const actual = readFile(new URL('./tree/deep/nested/bytes', import.meta.url)); 94if (actual.length !== bytes.length || actual.some((v, i) => v !== bytes[i])) throw Error('Partial transfer'); 95writeTextFile('tree/text', 'héllo 🌷'); writeTextFile('tree/text', '!', {append: true}); 96writeFile('empty', new Uint8Array()); 97if (readFile('empty').length !== 0 || readTextFile('tree/text') !== 'héllo 🌷!') throw Error('Text/empty'); 98const entries = [...readDir('tree')].sort((a, b) => a.name.localeCompare(b.name)); 99if (entries.length !== 2 || entries[0].name !== 'deep' || !entries[0].isDirectory || !entries[1].isFile) throw Error('Directory entries'); 100copy('tree', 'copies/tree'); 101move('copies/tree', 'moved'); 102if (exists('copies/tree') || !exists('moved/deep/nested/bytes')) throw Error('Move'); 103copyFile('moved/text', 'copied'); rename('copied', 'renamed'); truncate('renamed', 2); 104if (stat('renamed').size !== 2 || !lstat('renamed').isFile) throw Error('Path metadata'); 105if (!realPath('renamed').endsWith('renamed') || exists('missing')) throw Error('Resolution'); 106remove('tree', {recursive: true}); remove('empty'); 107if (exists('tree')) throw Error('Remove'); 108"# , 109& [], 110& [], 111); 112assert_eq! ( fs:: read ( dir. path (). join ( "renamed" )). unwrap (), b"h\xc3" ); 113assert_eq! ( 114 fs:: read_to_string ( dir. path (). join ( "moved/text" )). unwrap (), 115"héllo 🌷!" 116); 117assert_eq! ( 118 fs:: metadata ( dir. path (). join ( "moved/deep/nested/bytes" )) 119. unwrap () 120. len (), 121200003 122); 123} 124 125# [ test ] 126fn synchronous_temp_paths_match_async_behavior () { 127run ( 128r#" 129import { makeTempDir, makeTempFile, exists } from 'avril:fs/sync'; 130import { stat, remove, errors } from 'avril:std/sync'; 131const plain = makeTempDir(); 132if (!/^[0-9a-f]{8,16}$/.test(plain.split('/').pop())) throw Error('Hex name'); 133const named = makeTempDir({ prefix: 'sync-', suffix: '.d' }); 134if (!named.split('/').pop().startsWith('sync-') || !named.endsWith('.d')) throw Error('Affixes'); 135const file = makeTempFile({ dir: plain }); 136if (!file.startsWith(plain + '/') || stat(file).size !== 0 || !stat(file).isFile) throw Error('Temp file'); 137const seen = new Set(); 138for (let i = 0; i < 10; i++) { const candidate = makeTempFile({ dir: plain }); if (seen.has(candidate)) throw Error('Collision'); seen.add(candidate); } 139try { makeTempDir({ suffix: 'a|b' }); throw Error('Accepted characters'); } catch (e) { if (!(e instanceof errors.InvalidInput)) throw e; } 140try { makeTempFile({ dir: plain + '/missing' }); throw Error('Accepted parents'); } catch (e) { if (!(e instanceof errors.NotFound)) throw e; } 141const dirInfo = stat(named); 142if (!dirInfo.isDirectory) throw Error('Directory kind'); 143if (dirInfo.mode !== null && (dirInfo.mode & 0o777) !== 0o700) throw Error('Directory mode'); 144remove(plain, { recursive: true }); remove(named, { recursive: true }); 145if (exists(plain) || exists(named)) throw Error('Cleanup'); 146"# , 147& [], 148& [], 149); 150} 151 152# [ test ] 153fn failures_throw_synchronously_and_handles_keep_their_brand_and_lifecycle () { 154run ( 155r#" 156import { open, create, stat, mkdir, remove, errors, FileHandle } from 'avril:std/sync'; 157import { readFile, writeFile, writeTextFile, mkdirs, copy, exists } from 'avril:fs/sync'; 158function fails(call, C) { 159try { call(); } catch (e) { if (e instanceof C) return; throw e; } 160throw Error('Expected synchronous ' + C.name); 161} 162fails(() => open('missing'), errors.NotFound); 163fails(() => open(), TypeError); 164fails(() => open('data', {}), errors.InvalidInput); 165fails(() => open('data', {read: true, truncate: true}), errors.InvalidInput); 166fails(() => open('data', {read: 'yes'}), TypeError); 167fails(() => open('data', {write: true, mode: 8192}), RangeError); 168fails(() => open(new URL('https://example.test')), errors.InvalidInput); 169fails(() => open('.'), errors.NotSupported); 170fails(() => stat(''), errors.NotFound); 171fails(() => mkdir('directory', {mode: -1}), RangeError); 172fails(() => mkdirs('directory', {mode: -1}), RangeError); 173fails(() => remove('data', {recursive: 1}), TypeError); 174fails(() => copy('data', 'copy', {dereference: 1}), TypeError); 175fails(() => writeTextFile('new', 123), TypeError); 176fails(() => writeFile('new', new Uint8Array(), {create: false}), errors.NotFound); 177fails(() => writeFile('data', new Uint8Array(), {createNew: true}), errors.AlreadyExists); 178fails(() => readFile(), TypeError); 179if (exists('new') || exists('directory') || exists('copy')) throw Error('Validation had side effects'); 180const file = open('data'); 181fails(() => file.writeSync(new Uint8Array([1])), errors.BadResource); 182fails(() => file.readSync(new DataView(new ArrayBuffer(1))), TypeError); 183fails(() => file.seekSync(2 ** 53, 0), RangeError); 184fails(() => FileHandle.prototype.readSync.call({}, new Uint8Array(1)), TypeError); 185fails(() => structuredClone(file), DOMException); 186const detached = new Uint8Array(1); 187structuredClone(detached.buffer, {transfer: [detached.buffer]}); 188fails(() => file.readSync(detached), TypeError); 189const read = FileHandle.prototype.readSync, close = FileHandle.prototype.close; 190Object.setPrototypeOf(file, null); 191if (read.call(file, new Uint8Array(1)) !== 1) throw Error('Private brand'); 192close.call(file); 193fails(() => read.call(file, new Uint8Array(1)), errors.BadResource); 194const out = create('out'); 195const shared = new Uint8Array(new SharedArrayBuffer(3)); shared.set([1, 2, 3]); 196if (out.writeSync(shared) !== 3) throw Error('Shared buffer'); 197out[Symbol.dispose](); out[Symbol.dispose](); 198fails(() => out.statSync(), errors.BadResource); 199fails(() => out.close(), errors.BadResource); 200"# , 201& [], 202& [], 203); 204} 205 206# [ test ] 207fn synchronous_io_obeys_scoped_and_live_capabilities () { 208let ( dir, _) =run ( 209r#" 210import { pledge } from 'avril:std'; 211import { open, stat, readDir, errors } from 'avril:std/sync'; 212import { readTextFile, writeTextFile, copy, exists } from 'avril:fs/sync'; 213function denied(call) { 214try { call(); } catch (e) { if (e instanceof errors.PermissionDenied) return; throw e; } 215throw Error('Retained authority'); 216} 217if (readTextFile('data') !== 'abcdef') throw Error('Read grant'); 218writeTextFile('out', 'allowed'); 219denied(() => writeTextFile('forbidden', 'no')); 220denied(() => readTextFile('allowed/secret')); 221denied(() => exists('allowed/secret')); 222const entries = [...readDir('allowed')]; 223if (entries.length !== 1 || entries[0].name !== 'public') throw Error('Listing leaked denied child'); 224denied(() => copy('allowed', 'target')); 225const file = open('data', {read: true, write: true}); 226pledge({read: false, write: false}); 227for (const call of [ 228() => file.readSync(new Uint8Array(1)), () => file.writeSync(new Uint8Array([1])), 229() => file.statSync(), () => file.truncateSync(), () => file.syncSync(), 230() => stat('data'), () => readTextFile('data'), () => writeTextFile('out', 'no'), 231]) denied(call); 232file.close(); 233"# , 234& [ 235"-0" , 236"--allow-read=data" , 237"--allow-write=data" , 238"--allow-write=out" , 239"--allow-read=allowed/" , 240"--deny-read=allowed/secret" , 241"--allow-write=target/" , 242], 243& [( "allowed/public" , "yes" ), ( "allowed/secret" , "no" )], 244); 245assert_eq! ( fs:: read ( dir. path (). join ( "data" )). unwrap (), b"abcdef" ); 246assert_eq! ( fs:: read ( dir. path (). join ( "out" )). unwrap (), b"allowed" ); 247assert! ( !dir. path (). join ( "forbidden" ). exists ()); 248assert! ( !dir. path (). join ( "target" ). exists ()); 249} 250 251# [ test ] 252fn streams_and_async_callbacks_can_use_sync_handles () { 253let ( dir, _) =run ( 254r#" 255import { open, create } from 'avril:std/sync'; 256import { readTextFile, writeTextFile } from 'avril:fs/sync'; 257const source = open('data'), target = create('streamed'); 258await source.readable.pipeTo(target.writable); 259await new Promise(resolve => setTimeout(() => { 260writeTextFile('callback', readTextFile('streamed')); resolve(); 261}, 0)); 262"# , 263& [], 264& [], 265); 266assert_eq! ( fs:: read ( dir. path (). join ( "callback" )). unwrap (), b"abcdef" ); 267} 268 269# [ test ] 270fn workers_can_use_sync_io_from_their_event_loop () { 271let ( dir, _) =run ( 272r#" 273const worker = new Worker(new URL('./worker.js', import.meta.url), {type: 'module'}); 274const answer = new Promise((resolve, reject) => { 275worker.onmessage = ({data}) => resolve(data); 276worker.onerror = reject; 277}); 278worker.postMessage('go'); 279if (await answer !== 'abcdef!') throw Error('Worker IO'); 280worker.terminate(); 281"# , 282& [], 283& [( 284"worker.js" , 285r#" 286import { readTextFile, writeTextFile } from 'avril:fs/sync'; 287onmessage = () => { 288writeTextFile('worker-out', readTextFile('data') + '!'); 289postMessage(readTextFile('worker-out')); 290}; 291"# , 292)], 293); 294assert_eq! ( fs:: read ( dir. path (). join ( "worker-out" )). unwrap (), b"abcdef!" ); 295} 296 297# [ test ] 298fn sync_modules_keep_helpers_private_and_capture_intrinsics () { 299run ( 300r#" 301import * as std from 'avril:std/sync'; 302import * as fs from 'avril:fs/sync'; 303import { pledge } from 'avril:std'; 304if ('path' in std || 'openArgs' in std || 'writeOptions' in fs) throw Error('Private exports'); 305for (const name of ['ext:file/sync.js', 'ext:file/fs_sync.js']) { 306try { await import(name); throw Error('Private import succeeded'); } 307catch (e) { if (!e.message.includes('private')) throw e; } 308} 309const log = console.log; 310const fail = () => { throw Error('Mutable intrinsic'); }; 311std.FileHandle.prototype.readSync = fail; 312std.FileHandle.prototype.writeSync = fail; 313Uint8Array.prototype.set = fail; 314TextEncoder.prototype.encode = fail; 315TextDecoder.prototype.decode = fail; 316globalThis.Uint8Array = fail; 317Object.prototype.toJSON = fail; 318fs.writeTextFile('out', fs.readTextFile('data')); 319if (fs.readTextFile('out') !== 'abcdef') throw Error('Contents'); 320pledge({read: false, write: false, jsExec: false}); 321if ((await import('avril:fs/sync')).readFile !== fs.readFile) throw Error('Lost builtin'); 322log('intrinsics'); 323"# , 324& [], 325& [], 326); 327} 328 329# [ cfg ( unix )] 330# [ test ] 331fn sync_posix_metadata_links_and_permissions_match_async_handles () { 332run ( 333r#" 334import { open, stat, lstat, chmod, chown, lchown, utime, lutime, link, symlink, readLink, realPath, errors } from 'avril:std/sync'; 335import { copy, readTextFile, mkdirs, exists } from 'avril:fs/sync'; 336const file = open('data', {read: true, write: true}); 337const initial = file.statSync(); 338file.fchmodSync(0o640); file.fchownSync(initial.uid, initial.gid); 339file.futimeSync(new Date(1234567890123), 1234567890.125); 340let info = stat('data'); 341if ((info.mode & 0o777) !== 0o640 || +info.mtime !== 1234567890125 || +info.atime !== 1234567890123) throw Error('Handle metadata'); 342chmod('data', 0o600); chown('data', initial.uid, null); utime('data', 1234567000, new Date(1234567890456)); 343if ((stat('data').mode & 0o777) !== 0o600 || +stat('data').mtime !== 1234567890456) throw Error('Path metadata'); 344link('data', 'hard'); symlink('data', 'soft'); 345if (!lstat('soft').isSymlink || !stat('soft').isFile || readLink('soft') !== 'data' || realPath('soft') !== realPath('data')) throw Error('Links'); 346lchown('soft', initial.uid, initial.gid); lutime('soft', 1234500000, 1234500000); 347if (+lstat('soft').mtime !== 1234500000000 || +stat('data').mtime !== 1234567890456) throw Error('Link times'); 348copy('soft', 'preserved'); copy('soft', 'followed', {dereference: true}); 349if (!lstat('preserved').isSymlink || lstat('followed').isSymlink || readTextFile('followed') !== 'abcdef') throw Error('Copy links'); 350mkdirs('tree'); symlink('.', 'tree/cycle'); 351try { copy('tree', 'cycle-copy', {dereference: true}); throw Error('Accepted cycle'); } 352catch (e) { if (!(e instanceof errors.InvalidInput)) throw e; } 353file.flockSync(); file.funlockSync(); file.flockSync(false); await file.funlock(); file.close(); 354if (!exists('hard')) throw Error('Hard link'); 355"# , 356& [], 357& [], 358); 359} 360 361# [ cfg ( unix )] 362# [ test ] 363fn synchronous_calls_do_not_follow_ungranted_symlink_targets () { 364let ( dir, _) =run ( 365r#" 366import { pledge } from 'avril:std'; 367import { mkdir, symlink, open, stat, truncate, errors } from 'avril:std/sync'; 368import { readFile, writeTextFile, copy, exists } from 'avril:fs/sync'; 369mkdir('allowed'); symlink('../data', 'allowed/alias'); 370pledge({read: {allow: ['allowed/']}, write: {allow: ['allowed/', 'out']}}); 371for (const call of [ 372() => open('allowed/alias'), () => stat('allowed/alias'), () => truncate('allowed/alias'), 373() => readFile('allowed/alias'), () => exists('allowed/alias'), () => writeTextFile('allowed/alias', 'no'), 374() => copy('allowed/alias', 'out', {dereference: true}), 375]) { 376try { call(); throw Error('Followed denied target'); } 377catch (e) { if (!(e instanceof errors.PermissionDenied)) throw e; } 378} 379"# , 380& [], 381& [], 382); 383assert_eq! ( fs:: read ( dir. path (). join ( "data" )). unwrap (), b"abcdef" ); 384assert! ( !dir. path (). join ( "out" ). exists ()); 385} 386 387# [ cfg ( unix )] 388# [ test ] 389fn terminating_a_worker_interrupts_a_synchronous_lock_wait () { 390run ( 391r#" 392import { open, create } from 'avril:std/sync'; 393const file = open('data', {read: true, write: true}), lifetime = create('lifetime'); 394file.flockSync(); 395const worker = new Worker(new URL('./worker.js', import.meta.url), {type: 'module'}); 396const ready = new Promise((resolve, reject) => { 397worker.onmessage = resolve; worker.onerror = reject; 398}); 399worker.postMessage('go'); 400await ready; 401await new Promise(resolve => setTimeout(resolve, 30)); 402worker.terminate(); 403lifetime.flockSync(); lifetime.funlockSync(); lifetime.close(); 404file.funlockSync(); file.close(); 405"# , 406& [], 407& [( 408"worker.js" , 409r#" 410import { open } from 'avril:std/sync'; 411onmessage = () => { 412const file = open('data', {read: true, write: true}); 413const lifetime = open('lifetime', {read: true, write: true}); 414lifetime.flockSync(); 415postMessage('waiting'); 416file.flockSync(); 417throw Error('Acquired locked file'); 418}; 419"# , 420)], 421); 422} 423 424# [ cfg ( unix )] 425# [ test ] 426fn synchronous_lock_waits_for_another_isolate_without_pumping_js () { 427run ( 428r#" 429import { open } from 'avril:std/sync'; 430const worker = new Worker(new URL('./worker.js', import.meta.url), {type: 'module'}); 431await new Promise((resolve, reject) => { worker.onmessage = resolve; worker.onerror = reject; }); 432const file = open('data', {read: true, write: true}); 433let callback = false; 434queueMicrotask(() => callback = true); 435worker.postMessage('release'); 436file.flockSync(); 437if (callback) throw Error('Lock wait pumped JS'); 438file.funlockSync(); file.close(); worker.terminate(); 439"# , 440& [], 441& [( 442"worker.js" , 443r#" 444import { open } from 'avril:std/sync'; 445const file = open('data', {read: true, write: true}); 446file.flockSync(); 447onmessage = () => setTimeout(() => { file.funlockSync(); file.close(); }, 50); 448postMessage('locked'); 449"# , 450)], 451); 452} 453 454# [ test ] 455fn repl_and_test_callbacks_support_sync_io () { 456use std:: io:: Write ; 457 458let dir = tempfile:: tempdir (). unwrap (); 459 fs:: write ( dir. path (). join ( "data" ), "hello" ). unwrap (); 460 fs:: write ( 461 dir. path (). join ( "io_test.ts" ), 462r#" 463import { test } from 'avril:test'; 464import { open, type FileInfo } from 'avril:std/sync'; 465import { readTextFile, writeTextFile } from 'avril:fs/sync'; 466test('sync IO after await', async () => { 467await new Promise(resolve => setTimeout(resolve, 0)); 468const file = open('data'); 469const info: FileInfo = file.statSync(); 470if (info.size !== 5) throw Error('Metadata'); 471file.close(); 472writeTextFile('test-out', readTextFile('data')); 473}); 474"# , 475) 476. unwrap (); 477let child =Command :: new ( env! ( "CARGO_BIN_EXE_avril" )) 478. args ([ "test" , "io_test.ts" ]) 479. current_dir ( dir. path ()) 480. stdout ( Stdio :: piped ()) 481. stderr ( Stdio :: piped ()) 482. spawn () 483. unwrap (); 484let output = common:: wait ( child); 485assert! ( 486 output. status . success (), 487"{}" , 488String :: from_utf8_lossy ( & output. stderr ) 489); 490assert_eq! ( fs:: read ( dir. path (). join ( "test-out" )). unwrap (), b"hello" ); 491 492let mut child =Command :: new ( env! ( "CARGO_BIN_EXE_avril" )) 493. arg ( "repl" ) 494. current_dir ( dir. path ()) 495. stdin ( Stdio :: piped ()) 496. stdout ( Stdio :: piped ()) 497. stderr ( Stdio :: piped ()) 498. spawn () 499. unwrap (); 500 child. stdin . take (). unwrap (). write_all ( b"import { readTextFile, writeTextFile } from 'avril:fs/sync'\nawait new Promise(resolve => setTimeout(() => { writeTextFile('repl-out', readTextFile('data')); resolve('done'); }, 0))\n.exit\n" ). unwrap (); 501let output = common:: wait ( child); 502assert! ( 503 output. status . success (), 504"{}" , 505String :: from_utf8_lossy ( & output. stderr ) 506); 507assert_eq! ( fs:: read ( dir. path (). join ( "repl-out" )). unwrap (), b"hello" ); 508} 509 510# [ cfg ( unix )] 511# [ test ] 512fn sync_opened_handles_can_be_lent_to_subprocesses () { 513let ( dir, _) =run ( 514r#" 515import { runSync } from 'avril:std'; 516import { open, create } from 'avril:std/sync'; 517const input = open('data'), output = create('out'); 518input.seekSync(2, 0); 519runSync('/bin/sh', ['-c', 'cat'], {stdin: {handle: input}, stdout: {handle: output}}); 520if (output.statSync().size !== 4) throw Error('Borrowed handle'); 521input.close(); output.close(); 522"# , 523& [], 524& [], 525); 526assert_eq! ( fs:: read ( dir. path (). join ( "out" )). unwrap (), b"cdef" ); 527} 528 529# [ test ] 530fn sync_writev_shares_the_queue_orders_with_async_and_validates () { 531let ( dir, output) =run ( 532r#" 533import { open, create, SeekMode, errors, FileHandle } from 'avril:std/sync'; 534import { open as openAsync } from 'avril:std'; 535function fails(call, C) { 536try { call(); } catch (e) { if (e instanceof C) return; throw e; } 537throw Error('Expected synchronous ' + C.name); 538} 539const file = create('out'); 540const pending = file.write(new Uint8Array([1,2,3])); 541if (file.writevSync([new Uint8Array([4]), new Uint8Array(0), new Uint8Array([5,6])]) !== 3) throw Error('Sync count'); 542if (await pending !== 3) throw Error('Async count'); 543if (file.writevSync([]) !== 0) throw Error('Empty'); 544if (file.seekSync(0, SeekMode.Start) !== 0) throw Error('Seek'); 545const back = new Uint8Array(6); 546if (file.readSync(back) !== 6 || back.join() !== '1,2,3,4,5,6') throw Error('Order ' + back.join()); 547const wide = create('wide'), segs = [new Uint8Array(0)]; 548for (let i = 0; i < 20; i++) { const view = new Uint8Array(5000); view.fill(i + 1); segs.push(view); } 549segs.push(new Uint8Array(0)); 550if (wide.writevSync(segs) !== 100000) throw Error('Wide'); 551wide.close(); 552const view = new Uint8Array([7,8]); 553if (file.writevSync([view.subarray(1)]) !== 1) throw Error('Subarray'); 554view.fill(0); 555const shared = new Uint8Array(new SharedArrayBuffer(1)); shared[0] = 9; 556if (file.writevSync([shared]) !== 1) throw Error('Shared'); 557fails(() => file.writevSync('bytes'), TypeError); 558fails(() => file.writevSync([new Uint8Array([1]), null]), TypeError); 559fails(() => file.writevSync(new Uint8Array([1])), TypeError); 560fails(() => FileHandle.prototype.writevSync.call({}, [new Uint8Array([1])]), TypeError); 561const detached = new Uint8Array([1]); 562structuredClone(detached.buffer, {transfer: [detached.buffer]}); 563fails(() => file.writevSync([detached]), TypeError); 564file.close(); 565fails(() => file.writevSync([new Uint8Array([1])]), errors.BadResource); 566const check = await openAsync('out'); 567const all = new Uint8Array(8); 568let offset = 0, n; 569while (offset < 8 && (n = await check.read(all.subarray(offset))) !== null) offset += n; 570if (offset !== 8 || all.join() !== '1,2,3,4,5,6,8,9') throw Error('Bytes ' + all.join()); 571check.close(); 572console.log('sync-vectors'); 573"# , 574& [], 575& [], 576); 577assert_eq! ( output. stdout , b"sync-vectors\n" ); 578let wide = fs:: read ( dir. path (). join ( "wide" )). unwrap (); 579let expected: Vec < u8 > =( 1 ..=20 ). flat_map ( |value|vec! [ value; 5000 ]). collect (); 580assert_eq! ( wide, expected); 581assert_eq! ( 582 fs:: read ( dir. path (). join ( "out" )). unwrap (), 583[ 1 , 2 , 3 , 4 , 5 , 6 , 8 , 9 ] 584); 585}