goscript 0.0.83 → 0.0.84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,298 @@
1
+ export class HostUnsupportedError extends Error {
2
+ constructor() {
3
+ super('operation not implemented in JavaScript environment')
4
+ }
5
+ }
6
+
7
+ export type NodeFSModule = {
8
+ readSync(
9
+ fd: number,
10
+ buffer: Uint8Array,
11
+ offset?: number,
12
+ length?: number,
13
+ position?: number | null,
14
+ ): number
15
+ writeSync(
16
+ fd: number,
17
+ buffer: Uint8Array,
18
+ offset?: number,
19
+ length?: number,
20
+ position?: number | null,
21
+ ): number
22
+ closeSync?(fd: number): void
23
+ fstatSync?(fd: number): any
24
+ fsyncSync?(fd: number): void
25
+ ftruncateSync?(fd: number, len?: number): void
26
+ openSync?(path: string, flags: number | string, mode?: number): number
27
+ chmodSync?(path: string, mode: number): void
28
+ chownSync?(path: string, uid: number, gid: number): void
29
+ lchownSync?(path: string, uid: number, gid: number): void
30
+ linkSync?(existingPath: string, newPath: string): void
31
+ lstatSync?(path: string): any
32
+ mkdirSync?(path: string, options?: number | { mode?: number; recursive?: boolean }): void
33
+ readFileSync?(path: string): Uint8Array
34
+ readdirSync?(path: string, options?: { withFileTypes?: boolean }): any[]
35
+ readlinkSync?(path: string): string
36
+ renameSync?(oldPath: string, newPath: string): void
37
+ rmSync?(path: string, options?: { force?: boolean; recursive?: boolean }): void
38
+ rmdirSync?(path: string): void
39
+ statSync?(path: string): any
40
+ symlinkSync?(target: string, path: string): void
41
+ truncateSync?(path: string, len?: number): void
42
+ unlinkSync?(path: string): void
43
+ utimesSync?(path: string, atime: Date | number, mtime: Date | number): void
44
+ writeFileSync?(path: string, data: Uint8Array, options?: { mode?: number }): void
45
+ }
46
+
47
+ export type DenoStream = {
48
+ readSync?(buffer: Uint8Array): number | null
49
+ writeSync?(buffer: Uint8Array): number
50
+ }
51
+
52
+ export type DenoFileLike = DenoStream & {
53
+ close?(): void
54
+ rid?: number
55
+ seekSync?(offset: number, whence: number): number
56
+ syncSync?(): void
57
+ statSync?(): any
58
+ truncateSync?(len?: number): void
59
+ }
60
+
61
+ type HostReadFD = (fd: number, buffer: Uint8Array) => number | null
62
+ type HostWriteFD = (fd: number, buffer: Uint8Array) => number
63
+ type HostTextWrite = (data: string) => void
64
+
65
+ export type HostRuntime = {
66
+ deno: any | null
67
+ nodeFS: NodeFSModule | null
68
+ platform: string
69
+ processObj: any | null
70
+ getEnv(name: string): string
71
+ getStdioHandle(fd: number): DenoFileLike | null
72
+ readFD: HostReadFD
73
+ writeFD: HostWriteFD
74
+ writeStderrText: HostTextWrite
75
+ writeStdoutText: HostTextWrite
76
+ }
77
+
78
+ const encoder = new TextEncoder()
79
+
80
+ function getDynamicRequire(): ((specifier: string) => unknown) | null {
81
+ try {
82
+ return Function(
83
+ "return typeof require !== 'undefined' ? require : null",
84
+ )() as ((specifier: string) => unknown) | null
85
+ } catch {
86
+ return null
87
+ }
88
+ }
89
+
90
+ function writeAllSync(
91
+ writeChunk: (chunk: Uint8Array) => number,
92
+ buffer: Uint8Array,
93
+ ): number {
94
+ let offset = 0
95
+ while (offset < buffer.length) {
96
+ const n = writeChunk(buffer.subarray(offset))
97
+ if (!Number.isFinite(n) || n < 0) {
98
+ throw new Error(`invalid write result: ${n}`)
99
+ }
100
+ if (n === 0) {
101
+ throw new Error('short write')
102
+ }
103
+ offset += n
104
+ }
105
+ return buffer.length
106
+ }
107
+
108
+ function writeAllText(
109
+ writeChunk: (chunk: Uint8Array) => number,
110
+ data: string,
111
+ ): void {
112
+ const bytes = encoder.encode(data)
113
+ if (bytes.length === 0) {
114
+ return
115
+ }
116
+ writeAllSync(writeChunk, bytes)
117
+ }
118
+
119
+ function detectNodeFS(processObj: any | null): NodeFSModule | null {
120
+ if (processObj && typeof processObj.getBuiltinModule === 'function') {
121
+ const module = processObj.getBuiltinModule('fs')
122
+ if (
123
+ module &&
124
+ typeof module.readSync === 'function' &&
125
+ typeof module.writeSync === 'function'
126
+ ) {
127
+ return module as NodeFSModule
128
+ }
129
+ }
130
+
131
+ const requireFn = getDynamicRequire()
132
+ if (requireFn) {
133
+ for (const specifier of ['node:fs', 'fs']) {
134
+ try {
135
+ const module = requireFn(specifier) as NodeFSModule | null
136
+ if (
137
+ module &&
138
+ typeof module.readSync === 'function' &&
139
+ typeof module.writeSync === 'function'
140
+ ) {
141
+ return module
142
+ }
143
+ } catch {
144
+ // Try the next fallback.
145
+ }
146
+ }
147
+ }
148
+
149
+ return null
150
+ }
151
+
152
+ function unsupportedReadFD(_fd: number, _buffer: Uint8Array): number | null {
153
+ throw new HostUnsupportedError()
154
+ }
155
+
156
+ function unsupportedWriteFD(_fd: number, _buffer: Uint8Array): number {
157
+ throw new HostUnsupportedError()
158
+ }
159
+
160
+ function fallbackConsoleWriter(method: 'error' | 'log'): HostTextWrite {
161
+ return (data: string) => {
162
+ const consoleMethod = (globalThis as any).console?.[method]
163
+ if (!consoleMethod) {
164
+ return
165
+ }
166
+ if (data.endsWith('\n')) {
167
+ consoleMethod.call((globalThis as any).console, data.slice(0, -1))
168
+ return
169
+ }
170
+ consoleMethod.call((globalThis as any).console, data)
171
+ }
172
+ }
173
+
174
+ function detectHostRuntime(): HostRuntime {
175
+ const globalObj = globalThis as any
176
+ const deno = globalObj.Deno ?? null
177
+ const processObj = globalObj.process ?? null
178
+ const nodeFS = detectNodeFS(processObj)
179
+
180
+ const getStdioHandle = (fd: number): DenoFileLike | null => {
181
+ if (!deno) {
182
+ return null
183
+ }
184
+ switch (fd) {
185
+ case 0:
186
+ return deno.stdin ?? null
187
+ case 1:
188
+ return deno.stdout ?? null
189
+ case 2:
190
+ return deno.stderr ?? null
191
+ default:
192
+ return null
193
+ }
194
+ }
195
+
196
+ const platform =
197
+ deno?.build?.os ??
198
+ processObj?.platform ??
199
+ 'unknown'
200
+
201
+ const getEnv = (name: string): string => {
202
+ if (deno?.env?.get) {
203
+ try {
204
+ return deno.env.get(name) ?? ''
205
+ } catch {
206
+ return ''
207
+ }
208
+ }
209
+ return processObj?.env?.[name] ?? ''
210
+ }
211
+
212
+ let readFD: HostReadFD = unsupportedReadFD
213
+ let writeFD: HostWriteFD = unsupportedWriteFD
214
+ let writeStdoutText: HostTextWrite = fallbackConsoleWriter('log')
215
+ let writeStderrText: HostTextWrite = fallbackConsoleWriter('error')
216
+
217
+ if (deno) {
218
+ readFD = (fd: number, buffer: Uint8Array): number | null => {
219
+ const handle = getStdioHandle(fd)
220
+ if (!handle || typeof handle.readSync !== 'function') {
221
+ throw new HostUnsupportedError()
222
+ }
223
+ return handle.readSync(buffer)
224
+ }
225
+ writeFD = (fd: number, buffer: Uint8Array): number => {
226
+ const handle = getStdioHandle(fd)
227
+ if (!handle || typeof handle.writeSync !== 'function') {
228
+ throw new HostUnsupportedError()
229
+ }
230
+ return writeAllSync(
231
+ (chunk: Uint8Array) => handle.writeSync!(chunk),
232
+ buffer,
233
+ )
234
+ }
235
+ writeStdoutText = (data: string) => {
236
+ const handle = getStdioHandle(1)
237
+ if (!handle || typeof handle.writeSync !== 'function') {
238
+ fallbackConsoleWriter('log')(data)
239
+ return
240
+ }
241
+ writeAllText((chunk: Uint8Array) => handle.writeSync!(chunk), data)
242
+ }
243
+ writeStderrText = (data: string) => {
244
+ const handle = getStdioHandle(2)
245
+ if (!handle || typeof handle.writeSync !== 'function') {
246
+ fallbackConsoleWriter('error')(data)
247
+ return
248
+ }
249
+ writeAllText((chunk: Uint8Array) => handle.writeSync!(chunk), data)
250
+ }
251
+ } else if (nodeFS) {
252
+ readFD = (fd: number, buffer: Uint8Array): number | null =>
253
+ nodeFS.readSync(fd, buffer, 0, buffer.length, null)
254
+ writeFD = (fd: number, buffer: Uint8Array): number =>
255
+ writeAllSync(
256
+ (chunk: Uint8Array) =>
257
+ nodeFS.writeSync(fd, chunk, 0, chunk.length, null),
258
+ buffer,
259
+ )
260
+ writeStdoutText = (data: string) =>
261
+ writeAllText(
262
+ (chunk: Uint8Array) => nodeFS.writeSync(1, chunk, 0, chunk.length, null),
263
+ data,
264
+ )
265
+ writeStderrText = (data: string) =>
266
+ writeAllText(
267
+ (chunk: Uint8Array) => nodeFS.writeSync(2, chunk, 0, chunk.length, null),
268
+ data,
269
+ )
270
+ }
271
+
272
+ return {
273
+ deno,
274
+ getEnv,
275
+ getStdioHandle,
276
+ nodeFS,
277
+ platform,
278
+ processObj,
279
+ readFD,
280
+ writeFD,
281
+ writeStderrText,
282
+ writeStdoutText,
283
+ }
284
+ }
285
+
286
+ export let hostRuntime = detectHostRuntime()
287
+
288
+ export function resetHostRuntimeForTests(): void {
289
+ hostRuntime = detectHostRuntime()
290
+ }
291
+
292
+ export function writeHostStdoutText(data: string): void {
293
+ hostRuntime.writeStdoutText(data)
294
+ }
295
+
296
+ export function writeHostStderrText(data: string): void {
297
+ hostRuntime.writeStderrText(data)
298
+ }
@@ -0,0 +1,48 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { formatPrintedArgs } from './print.js'
4
+
5
+ describe('builtin println formatting', () => {
6
+ it('formats Uint8Array values with a stable inspect-style representation', () => {
7
+ expect(
8
+ formatPrintedArgs(['b2:', new Uint8Array([72, 101, 108, 108, 111])]),
9
+ ).toBe('b2: Uint8Array(5) [ 72, 101, 108, 108, 111 ]')
10
+ })
11
+
12
+ it('quotes nested string array elements', () => {
13
+ expect(formatPrintedArgs(['strings.Split:', ['a', 'b', 'c']])).toBe(
14
+ 'strings.Split: [ "a", "b", "c" ]',
15
+ )
16
+ })
17
+
18
+ it('formats plain objects across multiple lines', () => {
19
+ expect(
20
+ formatPrintedArgs([
21
+ 'out:',
22
+ {
23
+ exampleField: new Uint8Array([104, 101, 108, 108, 111]),
24
+ exampleText: 'world',
25
+ },
26
+ ]),
27
+ ).toBe(`out: {
28
+ exampleField: Uint8Array(5) [ 104, 101, 108, 108, 111 ],
29
+ exampleText: "world",
30
+ }`)
31
+ })
32
+
33
+ it('formats goscript struct field bags using field values', () => {
34
+ expect(
35
+ formatPrintedArgs([
36
+ {
37
+ _fields: {
38
+ Name: { value: 'hello' },
39
+ Count: { value: 3 },
40
+ },
41
+ },
42
+ ]),
43
+ ).toBe(`{
44
+ Name: "hello",
45
+ Count: 3,
46
+ }`)
47
+ })
48
+ })
@@ -0,0 +1,154 @@
1
+ import { asArray, isSliceProxy, type Slice } from './slice.js'
2
+
3
+ /**
4
+ * formatPrintedArgs formats builtin println arguments deterministically.
5
+ */
6
+ export function formatPrintedArgs(args: readonly any[]): string {
7
+ return args.map((arg) => formatPrintedValue(arg)).join(' ')
8
+ }
9
+
10
+ /**
11
+ * formatPrintedValue formats a single builtin println argument deterministically.
12
+ */
13
+ export function formatPrintedValue(value: any): string {
14
+ return formatValue(value, 0, false, new WeakSet<object>())
15
+ }
16
+
17
+ function formatValue(
18
+ value: any,
19
+ depth: number,
20
+ nested: boolean,
21
+ seen: WeakSet<object>,
22
+ ): string {
23
+ if (value === null) {
24
+ return 'null'
25
+ }
26
+
27
+ if (value === undefined) {
28
+ return '<nil>'
29
+ }
30
+
31
+ if (typeof value === 'string') {
32
+ return nested ? JSON.stringify(value) : value
33
+ }
34
+
35
+ if (typeof value === 'boolean' || typeof value === 'number') {
36
+ return String(value)
37
+ }
38
+
39
+ if (typeof value === 'bigint') {
40
+ return value.toString()
41
+ }
42
+
43
+ if (value instanceof Uint8Array) {
44
+ return formatUint8Array(value)
45
+ }
46
+
47
+ if (Array.isArray(value)) {
48
+ return formatArray(value, depth, seen)
49
+ }
50
+
51
+ if (isSliceProxy(value as Slice<unknown>)) {
52
+ return formatArray(asArray(value as Slice<unknown>), depth, seen)
53
+ }
54
+
55
+ if (typeof value === 'function') {
56
+ return value.name ? `[Function: ${value.name}]` : '[Function]'
57
+ }
58
+
59
+ if (typeof value !== 'object') {
60
+ return String(value)
61
+ }
62
+
63
+ if (seen.has(value)) {
64
+ return '[Circular]'
65
+ }
66
+ seen.add(value)
67
+
68
+ try {
69
+ if (value instanceof Map) {
70
+ return formatArray(
71
+ Array.from(value.entries()).map(([k, v]) => `${formatValue(k, depth + 1, true, seen)} => ${formatValue(v, depth + 1, true, seen)}`),
72
+ depth,
73
+ seen,
74
+ )
75
+ }
76
+
77
+ if (value instanceof Set) {
78
+ return formatArray(Array.from(value.values()), depth, seen)
79
+ }
80
+
81
+ if (value instanceof Error) {
82
+ return value.message || value.toString()
83
+ }
84
+
85
+ if (typeof value.GoString === 'function') {
86
+ return value.GoString()
87
+ }
88
+
89
+ if (typeof value.Error === 'function') {
90
+ return value.Error()
91
+ }
92
+
93
+ if (typeof value.String === 'function') {
94
+ return value.String()
95
+ }
96
+
97
+ const entries = getObjectEntries(value)
98
+ if (entries.length === 0) {
99
+ return '{}'
100
+ }
101
+
102
+ return formatObject(entries, depth, seen)
103
+ } finally {
104
+ seen.delete(value)
105
+ }
106
+ }
107
+
108
+ function formatUint8Array(value: Uint8Array): string {
109
+ if (value.length === 0) {
110
+ return 'Uint8Array(0) []'
111
+ }
112
+
113
+ return `Uint8Array(${value.length}) [ ${Array.from(value).join(', ')} ]`
114
+ }
115
+
116
+ function formatArray(value: readonly any[], depth: number, seen: WeakSet<object>): string {
117
+ if (value.length === 0) {
118
+ return '[]'
119
+ }
120
+
121
+ return `[ ${value.map((item) => formatValue(item, depth + 1, true, seen)).join(', ')} ]`
122
+ }
123
+
124
+ function formatObject(
125
+ entries: readonly [string, any][],
126
+ depth: number,
127
+ seen: WeakSet<object>,
128
+ ): string {
129
+ const pad = ' '.repeat(depth + 1)
130
+ const closePad = ' '.repeat(depth)
131
+ const body = entries
132
+ .map(
133
+ ([key, value]) =>
134
+ `${pad}${key}: ${formatValue(value, depth + 1, true, seen)},`,
135
+ )
136
+ .join('\n')
137
+
138
+ return `{\n${body}\n${closePad}}`
139
+ }
140
+
141
+ function getObjectEntries(value: Record<string, any>): [string, any][] {
142
+ const fields = value._fields
143
+ if (fields && typeof fields === 'object' && !Array.isArray(fields)) {
144
+ return Object.keys(fields).map((key) => {
145
+ const field = fields[key]
146
+ if (field && typeof field === 'object' && 'value' in field) {
147
+ return [key, field.value]
148
+ }
149
+ return [key, field]
150
+ })
151
+ }
152
+
153
+ return Object.entries(value).filter(([, entry]) => typeof entry !== 'function')
154
+ }
@@ -1,38 +1,49 @@
1
- import { describe, it, expect } from 'vitest'
1
+ import { afterEach, describe, expect, it, vi } from 'vitest'
2
+ import { resetHostRuntimeForTests } from '@goscript/builtin/hostio.js'
2
3
  import * as fmt from './fmt.js'
3
4
 
4
- // Helper to capture stdout via internal stdout.write
5
- // We will monkey-patch global process.stdout.write if available
5
+ const originalDeno = (globalThis as any).Deno
6
+ const originalProcess = (globalThis as any).process
7
+
8
+ afterEach(() => {
9
+ if (originalDeno === undefined) {
10
+ delete (globalThis as any).Deno
11
+ } else {
12
+ ;(globalThis as any).Deno = originalDeno
13
+ }
14
+
15
+ if (originalProcess === undefined) {
16
+ delete (globalThis as any).process
17
+ } else {
18
+ ;(globalThis as any).process = originalProcess
19
+ }
20
+
21
+ resetHostRuntimeForTests()
22
+ })
23
+
24
+ // Helper to capture stdout via the hostio text output path.
6
25
  function captureStdout(run: () => void): string {
7
26
  let buf = ''
8
- const hasProcess =
9
- typeof process !== 'undefined' &&
10
- (process as any).stdout &&
11
- typeof (process as any).stdout.write === 'function'
12
-
13
- if (hasProcess) {
14
- const orig = (process as any).stdout.write
15
- ;(process as any).stdout.write = (chunk: any) => {
16
- buf += typeof chunk === 'string' ? chunk : String(chunk)
17
- return true
18
- }
19
- try {
20
- run()
21
- } finally {
22
- ;(process as any).stdout.write = orig
23
- }
24
- } else {
25
- // Fallback: spy on console.log for environments without process
26
- const origLog = console.log
27
- ;(console as any).log = (msg: any) => {
28
- buf += String(msg) + '\n'
29
- }
30
- try {
31
- run()
32
- } finally {
33
- console.log = origLog
34
- }
27
+ delete (globalThis as any).Deno
28
+ ;(globalThis as any).process = {
29
+ getBuiltinModule: vi.fn(() => ({
30
+ readSync: vi.fn(),
31
+ writeSync: vi.fn(
32
+ (
33
+ _fd: number,
34
+ chunk: Uint8Array,
35
+ _offset?: number,
36
+ length?: number,
37
+ _position?: number | null,
38
+ ) => {
39
+ buf += new TextDecoder().decode(chunk.subarray(0, length ?? chunk.length))
40
+ return length ?? chunk.length
41
+ },
42
+ ),
43
+ })),
35
44
  }
45
+ resetHostRuntimeForTests()
46
+ run()
36
47
 
37
48
  return buf
38
49
  }
package/gs/fmt/fmt.ts CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  import * as $ from '@goscript/builtin/index.js'
5
5
  import * as errors from '@goscript/errors/index.js'
6
+ import { writeHostStdoutText } from '@goscript/builtin/hostio.js'
6
7
 
7
8
  // Basic interfaces
8
9
  export interface Stringer {
@@ -259,28 +260,7 @@ function parseFormat(format: string, args: any[]): string {
259
260
  // Global stdout simulation for Print functions
260
261
  let stdout = {
261
262
  write: (data: string) => {
262
- // Use process.stdout.write if available (Node.js), otherwise fallback to console.log
263
- // but we need to avoid adding extra newlines that console.log adds
264
- if (
265
- typeof process !== 'undefined' &&
266
- process.stdout &&
267
- process.stdout.write
268
- ) {
269
- process.stdout.write(data)
270
- } else {
271
- // In browser environments, we need to use console.log but handle newlines carefully
272
- // If the data already ends with \n, we should strip it to avoid double newlines
273
- if (data.endsWith('\n')) {
274
- console.log(data.slice(0, -1))
275
- } else {
276
- // Use console.log without adding newline by using a custom method
277
- if (console.log) {
278
- // For data without newlines, we can just print it directly
279
- // This is a bit of a hack but works for most cases
280
- console.log(data)
281
- }
282
- }
283
- }
263
+ writeHostStdoutText(data)
284
264
  },
285
265
  }
286
266
 
@@ -4,6 +4,7 @@ import * as io from '@goscript/io/index.js'
4
4
 
5
5
  import { NewFile, Stderr, Stdin, Stdout } from './file_unix_js.gs.js'
6
6
  import { ErrClosed } from './error.gs.js'
7
+ import { createHostFile, resetHostRuntimeForTests } from './types_js.gs.js'
7
8
 
8
9
  const originalDeno = (globalThis as any).Deno
9
10
  const originalProcess = (globalThis as any).process
@@ -20,6 +21,8 @@ afterEach(() => {
20
21
  } else {
21
22
  ;(globalThis as any).process = originalProcess
22
23
  }
24
+
25
+ resetHostRuntimeForTests()
23
26
  })
24
27
 
25
28
  describe('os stdio', () => {
@@ -45,6 +48,7 @@ describe('os stdio', () => {
45
48
  stdout: { writeSync: stdoutWriteSync },
46
49
  }
47
50
  delete (globalThis as any).process
51
+ resetHostRuntimeForTests()
48
52
 
49
53
  const input = NewFile(0, 'stdin')
50
54
  const output = NewFile(1, 'stdout')
@@ -93,6 +97,7 @@ describe('os stdio', () => {
93
97
  writeSync,
94
98
  })),
95
99
  }
100
+ resetHostRuntimeForTests()
96
101
 
97
102
  const input = NewFile(0, 'stdin')
98
103
  const output = NewFile(1, 'stdout')
@@ -117,6 +122,7 @@ describe('os stdio', () => {
117
122
  stdin: { readSync: stdinReadSync },
118
123
  }
119
124
  delete (globalThis as any).process
125
+ resetHostRuntimeForTests()
120
126
 
121
127
  const input = NewFile(0, 'stdin')!
122
128
  const [readN, readErr] = input.Read(new Uint8Array(1))
@@ -128,4 +134,51 @@ describe('os stdio', () => {
128
134
  expect(closedN).toBe(0)
129
135
  expect(closedErr).toBe(ErrClosed)
130
136
  })
137
+
138
+ it('retries short writes until the full buffer is written', () => {
139
+ const writeSync = vi
140
+ .fn<(buffer: Uint8Array) => number>()
141
+ .mockImplementationOnce((_buffer: Uint8Array) => 2)
142
+ .mockImplementationOnce((buffer: Uint8Array) => buffer.length)
143
+
144
+ ;(globalThis as any).Deno = {
145
+ stdout: { writeSync },
146
+ }
147
+ delete (globalThis as any).process
148
+ resetHostRuntimeForTests()
149
+
150
+ const output = NewFile(1, 'stdout')!
151
+ const [writeN, writeErr] = output.Write(new Uint8Array([1, 2, 3, 4, 5]))
152
+ expect(writeN).toBe(5)
153
+ expect(writeErr).toBeNull()
154
+ expect(writeSync).toHaveBeenCalledTimes(2)
155
+ expect(Array.from(writeSync.mock.calls[1][0])).toEqual([3, 4, 5])
156
+ })
157
+
158
+ it('prefers direct handle read and write methods when available', () => {
159
+ const handle = {
160
+ readSync: vi.fn((buffer: Uint8Array) => {
161
+ buffer.set([11, 12, 13], 0)
162
+ return 3
163
+ }),
164
+ writeSync: vi
165
+ .fn<(buffer: Uint8Array) => number>()
166
+ .mockImplementationOnce((_buffer: Uint8Array) => 1)
167
+ .mockImplementationOnce((buffer: Uint8Array) => buffer.length),
168
+ }
169
+
170
+ const file = createHostFile('host-file', 99, handle)
171
+ const readBuf = new Uint8Array(4)
172
+
173
+ const [readN, readErr] = file.Read(readBuf)
174
+ expect(readN).toBe(3)
175
+ expect(readErr).toBeNull()
176
+ expect(Array.from(readBuf.slice(0, 3))).toEqual([11, 12, 13])
177
+
178
+ const [writeN, writeErr] = file.Write(new Uint8Array([21, 22, 23]))
179
+ expect(writeN).toBe(3)
180
+ expect(writeErr).toBeNull()
181
+ expect(handle.writeSync).toHaveBeenCalledTimes(2)
182
+ expect(Array.from(handle.writeSync.mock.calls[1][0])).toEqual([22, 23])
183
+ })
131
184
  })