Skip to content

Commit 7356e13

Browse files
committed
test: add tests for deno compatibility
1 parent 58e64a2 commit 7356e13

File tree

6 files changed

+122
-35
lines changed

6 files changed

+122
-35
lines changed

.github/workflows/test.yml

+11
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,17 @@ jobs:
6363
- name: Run tests
6464
run: npm run test:node
6565

66+
testDeno:
67+
timeout-minutes: 15
68+
runs-on: ubuntu-latest
69+
steps:
70+
- uses: actions/checkout@v3
71+
- uses: denoland/setup-deno@v1
72+
with:
73+
deno-version: v1.x
74+
- name: Run tests
75+
run: npm run test:deno
76+
6677
testBun:
6778
if: always() && github.event.inputs.bun == 'true'
6879
timeout-minutes: 15

README.md

-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ es.close()
100100
- [ ] Figure out what to do on broken connection on request body
101101
- [ ] Configurable stalled connection detection (eg no data)
102102
- [ ] Configurable reconnection policy
103-
- [ ] Deno support/tests
104103
- [ ] Consider legacy build
105104

106105
## License

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@
2929
"prebuild": "npm run clean",
3030
"prepublishOnly": "npm run build",
3131
"test": "npm run test:node && npm run test:browser",
32+
"test:browser": "ts-node test/browser/client.browser.test.ts",
3233
"test:bun": "bun run test/bun/client.bun.test.ts",
33-
"test:node": "ts-node test/node/client.node.test.ts",
34-
"test:browser": "ts-node test/browser/client.browser.test.ts"
34+
"test:deno": "deno run --allow-net --allow-read --allow-env --unstable-sloppy-imports test/deno/client.deno.test.ts",
35+
"test:node": "ts-node test/node/client.node.test.ts"
3536
},
3637
"files": [
3738
"dist",

test/deno/client.deno.test.ts

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import {createEventSource} from '../../src/default.js'
2+
import {getServer} from '../server.js'
3+
import {registerTests} from '../tests.js'
4+
import {createRunner} from '../waffletest/index.js'
5+
import {nodeReporter} from '../waffletest/reporters/nodeReporter.js'
6+
7+
const DENO_TEST_PORT = 3947
8+
9+
// Run the tests in deno
10+
;(async function run() {
11+
const server = await getServer(DENO_TEST_PORT)
12+
13+
const runner = registerTests({
14+
environment: 'deno',
15+
runner: createRunner(nodeReporter),
16+
createEventSource,
17+
fetch: globalThis.fetch,
18+
port: DENO_TEST_PORT,
19+
})
20+
21+
const result = await runner.runTests()
22+
23+
// Teardown
24+
await server.close()
25+
26+
if (typeof process !== 'undefined' && 'exit' in process && typeof process.exit === 'function') {
27+
// eslint-disable-next-line no-process-exit
28+
process.exit(result.failures)
29+
} else if (typeof globalThis.Deno !== 'undefined') {
30+
globalThis.Deno.exit(result.failures)
31+
} else if (result.failures > 0) {
32+
throw new Error(`Tests failed: ${result.failures}`)
33+
}
34+
})()

test/server.ts

+55-28
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,19 @@ import esbuild from 'esbuild'
77

88
import {unicodeLines} from './fixtures.js'
99

10+
const isDeno = typeof globalThis.Deno !== 'undefined'
11+
1012
export function getServer(port: number): Promise<Server> {
1113
return new Promise((resolve, reject) => {
1214
const server = createServer(onRequest)
1315
.on('error', reject)
14-
.listen(port, '::', () => resolve(server))
16+
.listen(port, isDeno ? '127.0.0.1' : '::', () => resolve(server))
1517
})
1618
}
1719

1820
function onRequest(req: IncomingMessage, res: ServerResponse) {
1921
// Disable Nagle's algorithm for testing
20-
if (res.socket) {
22+
if (res.socket && 'setNoDelay' in res.socket) {
2123
res.socket.setNoDelay(true)
2224
}
2325

@@ -65,15 +67,16 @@ function writeDefault(_req: IncomingMessage, res: ServerResponse) {
6567
Connection: 'keep-alive',
6668
})
6769

68-
res.write(
70+
tryWrite(
71+
res,
6972
formatEvent({
7073
event: 'welcome',
7174
data: 'Hello, world!',
7275
}),
7376
)
7477

7578
// For some reason, Bun seems to need this to flush
76-
res.write(':\n')
79+
tryWrite(res, ':\n')
7780
}
7881

7982
/**
@@ -87,12 +90,13 @@ async function writeCounter(req: IncomingMessage, res: ServerResponse) {
8790
Connection: 'keep-alive',
8891
})
8992

90-
res.write(formatEvent({retry: 50, data: ''}))
93+
tryWrite(res, formatEvent({retry: 50, data: ''}))
9194

9295
let counter = parseInt(getLastEventId(req) || '0', 10)
9396
for (let i = 0; i < 3; i++) {
9497
counter++
95-
res.write(
98+
tryWrite(
99+
res,
96100
formatEvent({
97101
event: 'counter',
98102
data: `Counter is at ${counter}`,
@@ -114,8 +118,9 @@ function writeOne(req: IncomingMessage, res: ServerResponse) {
114118
})
115119

116120
if (!last) {
117-
res.write(formatEvent({retry: 50, data: ''}))
118-
res.write(
121+
tryWrite(res, formatEvent({retry: 50, data: ''}))
122+
tryWrite(
123+
res,
119124
formatEvent({
120125
event: 'progress',
121126
data: '100%',
@@ -136,7 +141,8 @@ async function writeSlowConnect(_req: IncomingMessage, res: ServerResponse) {
136141
Connection: 'keep-alive',
137142
})
138143

139-
res.write(
144+
tryWrite(
145+
res,
140146
formatEvent({
141147
event: 'welcome',
142148
data: 'That was a slow connect, was it not?',
@@ -156,7 +162,8 @@ async function writeStalledConnection(req: IncomingMessage, res: ServerResponse)
156162
const lastId = getLastEventId(req)
157163
const reconnected = lastId === '1'
158164

159-
res.write(
165+
tryWrite(
166+
res,
160167
formatEvent({
161168
id: reconnected ? '2' : '1',
162169
event: 'welcome',
@@ -168,7 +175,8 @@ async function writeStalledConnection(req: IncomingMessage, res: ServerResponse)
168175

169176
if (reconnected) {
170177
await delay(250)
171-
res.write(
178+
tryWrite(
179+
res,
172180
formatEvent({
173181
id: '3',
174182
event: 'success',
@@ -189,14 +197,16 @@ async function writeUnicode(_req: IncomingMessage, res: ServerResponse) {
189197
Connection: 'keep-alive',
190198
})
191199

192-
res.write(
200+
tryWrite(
201+
res,
193202
formatEvent({
194203
event: 'welcome',
195204
data: 'Connected - I will now send some chonks (cuter chunks) with unicode',
196205
}),
197206
)
198207

199-
res.write(
208+
tryWrite(
209+
res,
200210
formatEvent({
201211
event: 'unicode',
202212
data: unicodeLines[0],
@@ -206,22 +216,22 @@ async function writeUnicode(_req: IncomingMessage, res: ServerResponse) {
206216
await delay(100)
207217

208218
// Start of a valid SSE chunk
209-
res.write('event: unicode\ndata: ')
219+
tryWrite(res, 'event: unicode\ndata: ')
210220

211221
// Write "Espen ❤️ Kokos" in two halves:
212222
// 1st: Espen � [..., 226, 153]
213223
// 2st: � Kokos [165, 32, ...]
214-
res.write(new Uint8Array([69, 115, 112, 101, 110, 32, 226, 153]))
224+
tryWrite(res, new Uint8Array([69, 115, 112, 101, 110, 32, 226, 153]))
215225

216226
// Give time to the client to process the first half
217227
await delay(1000)
218228

219-
res.write(new Uint8Array([165, 32, 75, 111, 107, 111, 115]))
229+
tryWrite(res, new Uint8Array([165, 32, 75, 111, 107, 111, 115]))
220230

221231
// Closing end of packet
222-
res.write('\n\n\n\n')
232+
tryWrite(res, '\n\n\n\n')
223233

224-
res.write(formatEvent({event: 'disconnect', data: 'Thanks for listening'}))
234+
tryWrite(res, formatEvent({event: 'disconnect', data: 'Thanks for listening'}))
225235
res.end()
226236
}
227237

@@ -232,7 +242,8 @@ async function writeTricklingConnection(_req: IncomingMessage, res: ServerRespon
232242
Connection: 'keep-alive',
233243
})
234244

235-
res.write(
245+
tryWrite(
246+
res,
236247
formatEvent({
237248
event: 'welcome',
238249
data: 'Connected - now I will keep sending "comments" for a while',
@@ -241,10 +252,10 @@ async function writeTricklingConnection(_req: IncomingMessage, res: ServerRespon
241252

242253
for (let i = 0; i < 60; i++) {
243254
await delay(500)
244-
res.write(':\n')
255+
tryWrite(res, ':\n')
245256
}
246257

247-
res.write(formatEvent({event: 'disconnect', data: 'Thanks for listening'}))
258+
tryWrite(res, formatEvent({event: 'disconnect', data: 'Thanks for listening'}))
248259
res.end()
249260
}
250261

@@ -259,7 +270,8 @@ function writeCors(req: IncomingMessage, res: ServerResponse) {
259270
...cors,
260271
})
261272

262-
res.write(
273+
tryWrite(
274+
res,
263275
formatEvent({
264276
event: 'origin',
265277
data: origin || '<none>',
@@ -282,7 +294,7 @@ async function writeDebug(req: IncomingMessage, res: ServerResponse) {
282294
bodyHash = await hash
283295
} catch (err: unknown) {
284296
res.writeHead(500, 'Internal Server Error')
285-
res.write(err instanceof Error ? err.message : `${err}`)
297+
tryWrite(res, err instanceof Error ? err.message : `${err}`)
286298
res.end()
287299
return
288300
}
@@ -293,7 +305,8 @@ async function writeDebug(req: IncomingMessage, res: ServerResponse) {
293305
Connection: 'keep-alive',
294306
})
295307

296-
res.write(
308+
tryWrite(
309+
res,
297310
formatEvent({
298311
event: 'debug',
299312
data: JSON.stringify({
@@ -319,7 +332,7 @@ function writeCookies(_req: IncomingMessage, res: ServerResponse) {
319332
'Set-Cookie': 'someSession=someValue; Path=/authed; HttpOnly; SameSite=Lax;',
320333
Connection: 'keep-alive',
321334
})
322-
res.write(JSON.stringify({cookiesWritten: true}))
335+
tryWrite(res, JSON.stringify({cookiesWritten: true}))
323336
res.end()
324337
}
325338

@@ -330,7 +343,8 @@ function writeAuthed(req: IncomingMessage, res: ServerResponse) {
330343
Connection: 'keep-alive',
331344
})
332345

333-
res.write(
346+
tryWrite(
347+
res,
334348
formatEvent({
335349
event: 'authInfo',
336350
data: JSON.stringify({cookies: req.headers.cookie || ''}),
@@ -347,7 +361,7 @@ function writeFallback(_req: IncomingMessage, res: ServerResponse) {
347361
Connection: 'close',
348362
})
349363

350-
res.write('File not found')
364+
tryWrite(res, 'File not found')
351365
res.end()
352366
}
353367

@@ -377,7 +391,7 @@ async function writeBrowserTestScript(_req: IncomingMessage, res: ServerResponse
377391
outdir: 'out',
378392
})
379393

380-
res.write(build.outputFiles.map((file) => file.text).join('\n\n'))
394+
tryWrite(res, build.outputFiles.map((file) => file.text).join('\n\n'))
381395
res.end()
382396
}
383397

@@ -443,3 +457,16 @@ export function encodeData(text: string): string {
443457

444458
return output
445459
}
460+
461+
function tryWrite(res: ServerResponse, chunk: string | Uint8Array) {
462+
try {
463+
res.write(chunk)
464+
} catch (err: unknown) {
465+
// Deno randomly throws on write after close, it seems
466+
if (err instanceof TypeError && err.message.includes('cannot close or enqueue')) {
467+
return
468+
}
469+
470+
throw err
471+
}
472+
}

test/waffletest/reporters/nodeReporter.ts

+19-4
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,27 @@ function green(str: string): string {
4444
return CAN_USE_COLORS ? `\x1b[32m${str}\x1b[39m` : str
4545
}
4646

47+
function getEnv(envVar: string): string | undefined {
48+
if (typeof process !== 'undefined' && 'env' in process && typeof process.env === 'object') {
49+
return process.env[envVar]
50+
}
51+
52+
if (typeof globalThis.Deno !== 'undefined') {
53+
return globalThis.Deno.env.get(envVar)
54+
}
55+
56+
throw new Error('Unable to find environment variables')
57+
}
58+
59+
function hasEnv(envVar: string): boolean {
60+
return typeof getEnv(envVar) !== 'undefined'
61+
}
62+
4763
function canUseColors(): boolean {
4864
const isWindows = platform() === 'win32'
49-
const isDumbTerminal = process.env.TERM === 'dumb'
50-
const isCompatibleTerminal = isatty(1) && process.env.TERM && !isDumbTerminal
65+
const isDumbTerminal = getEnv('TERM') === 'dumb'
66+
const isCompatibleTerminal = isatty(1) && getEnv('TERM') && !isDumbTerminal
5167
const isCI =
52-
'CI' in process.env &&
53-
('GITHUB_ACTIONS' in process.env || 'GITLAB_CI' in process.env || 'CIRCLECI' in process.env)
68+
hasEnv('CI') && (hasEnv('GITHUB_ACTIONS') || hasEnv('GITLAB_CI') || hasEnv('CIRCLECI'))
5469
return (isWindows && !isDumbTerminal) || isCompatibleTerminal || isCI
5570
}

0 commit comments

Comments
 (0)