Skip to content

Commit e208822

Browse files
authored
Merge pull request #283 from effigies/mock-opener
test: Add opener implementations for tests
2 parents 1560a88 + 2b67763 commit e208822

File tree

6 files changed

+185
-58
lines changed

6 files changed

+185
-58
lines changed

src/files/filetree.test.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,11 @@ import { assert, assertEquals } from '@std/assert'
22
import { FileIgnoreRules } from './ignore.ts'
33
import { BIDSFile, type FileOpener, type FileTree } from '../types/filetree.ts'
44
import { filesToTree } from './filetree.ts'
5-
import { asyncStreamFromString } from '../tests/utils.ts'
6-
7-
class NullFileOpener implements FileOpener {
8-
size = 0
9-
stream = () => asyncStreamFromString('')
10-
text = () => Promise.resolve('')
11-
readBytes = async (size: number, offset?: number) => new Uint8Array()
12-
}
5+
import { StringOpener } from './openers.test.ts'
136

147
export function pathToFile(path: string, ignored: boolean = false): BIDSFile {
158
const name = path.split('/').pop() as string
16-
return new BIDSFile(path, new NullFileOpener(), ignored)
9+
return new BIDSFile(path, new StringOpener(''), ignored)
1710
}
1811

1912
export function pathsToTree(paths: string[], ignore?: string[]): FileTree {

src/files/openers.test.ts

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/**
2+
* File openers for use in testing
3+
*/
4+
import { assertEquals } from '@std/assert'
5+
import { type FileOpener } from '../types/filetree.ts'
6+
import { streamFromString } from '../tests/utils.ts'
7+
import { createUTF8Stream } from './streams.ts'
8+
9+
const textEncoder = new TextEncoder()
10+
const textDecoder = new TextDecoder()
11+
12+
export class BytesOpener implements FileOpener {
13+
contents: Uint8Array<ArrayBuffer>
14+
size: number
15+
16+
constructor(contents: Uint8Array<ArrayBuffer>) {
17+
this.contents = contents
18+
this.size = contents.length
19+
}
20+
21+
async text(): Promise<string> {
22+
return textDecoder.decode(this.contents)
23+
}
24+
25+
async readBytes(size: number, offset = 0): Promise<Uint8Array<ArrayBuffer>> {
26+
return this.contents.slice(offset, offset + size)
27+
}
28+
29+
async stream(): Promise<ReadableStream<Uint8Array<ArrayBuffer>>> {
30+
const contents = this.contents
31+
return new ReadableStream({
32+
start(controller) {
33+
controller.enqueue(contents)
34+
controller.close()
35+
},
36+
})
37+
}
38+
}
39+
40+
export class StringOpener extends BytesOpener {
41+
constructor(contents: string) {
42+
super(textEncoder.encode(contents))
43+
}
44+
}
45+
46+
export class StreamOpener implements FileOpener {
47+
#stream: ReadableStream<Uint8Array<ArrayBuffer>>
48+
size: number
49+
50+
constructor(stream: ReadableStream<Uint8Array<ArrayBuffer>>, size: number) {
51+
this.#stream = stream
52+
this.size = size
53+
}
54+
55+
async stream(): Promise<ReadableStream<Uint8Array<ArrayBuffer>>> {
56+
const tee = this.#stream.tee()
57+
this.#stream = tee[1]
58+
return tee[0]
59+
}
60+
61+
async text(): Promise<string> {
62+
const stream = await this.stream()
63+
const reader = stream.pipeThrough(createUTF8Stream()).getReader()
64+
const chunks: string[] = []
65+
try {
66+
while (true) {
67+
const { done, value } = await reader.read()
68+
if (done) break
69+
chunks.push(value)
70+
}
71+
return chunks.join('')
72+
} finally {
73+
reader.releaseLock()
74+
}
75+
}
76+
77+
// Not a thoroughly tested implementation
78+
// Do not move this out of test files without adding more substantial tests
79+
async readBytes(size: number, offset = 0): Promise<Uint8Array<ArrayBuffer>> {
80+
const stream = await this.stream()
81+
const reader = stream.getReader()
82+
const result = new Uint8Array(size)
83+
let pos = 0
84+
try {
85+
while (pos < offset + size) {
86+
const { done, value } = await reader.read()
87+
if (done) break
88+
if (pos + value.length > offset) {
89+
result.set(
90+
value.subarray(Math.max(0, offset - pos), Math.min(value.length, offset + size - pos)),
91+
Math.max(0, pos - offset),
92+
)
93+
}
94+
pos += value.length
95+
}
96+
} finally {
97+
reader.releaseLock()
98+
}
99+
return result.subarray(0, Math.min(size, pos - offset))
100+
}
101+
}
102+
103+
export class CompressedStringOpener extends StreamOpener {
104+
constructor(contents: string) {
105+
// Use uncompressed length as size, for simplicity
106+
super(streamFromString(contents).pipeThrough(new CompressionStream('gzip')), contents.length)
107+
}
108+
}
109+
110+
async function testOpener(t: Deno.TestContext, opener: FileOpener) {
111+
await t.step('size', async () => {
112+
assertEquals(opener.size, 13)
113+
})
114+
await t.step('text()', async () => {
115+
assertEquals(await opener.text(), 'Hello, world!')
116+
})
117+
await t.step('readBytes()', async () => {
118+
assertEquals(textDecoder.decode(await opener.readBytes(5)), 'Hello')
119+
assertEquals(textDecoder.decode(await opener.readBytes(7, 5)), ', world')
120+
})
121+
await t.step('stream()', async () => {
122+
const stream = await opener.stream()
123+
const chunks: string[] = []
124+
for await (const chunk of stream) {
125+
chunks.push(textDecoder.decode(chunk))
126+
}
127+
assertEquals(chunks.join(''), 'Hello, world!')
128+
})
129+
}
130+
131+
Deno.test('Validate BytesOpener', async (t) => {
132+
await testOpener(t, new BytesOpener(textEncoder.encode('Hello, world!')))
133+
})
134+
135+
Deno.test('Validate StringOpener', async (t) => {
136+
await testOpener(t, new StringOpener('Hello, world!'))
137+
})
138+
139+
Deno.test('Validate StreamOpener', async (t) => {
140+
await testOpener(t, new StreamOpener(streamFromString('Hello, world!'), 13))
141+
})
142+
143+
Deno.test('Validate CompressedStringOpener', async (t) => {
144+
const opener = new CompressedStringOpener('Hello, world!')
145+
await t.step('Decompress', async () => {
146+
const stream = await opener.stream()
147+
const decompressedStream = stream.pipeThrough(new DecompressionStream('gzip'))
148+
const chunks: string[] = []
149+
for await (const chunk of decompressedStream) {
150+
chunks.push(textDecoder.decode(chunk))
151+
}
152+
assertEquals(chunks.join(''), 'Hello, world!')
153+
})
154+
})

0 commit comments

Comments
 (0)