Skip to content

Commit d54959d

Browse files
committed
testkit-backend: Separate the socket connection, protocol and controller
The logic of reading and writing in the socket, call the handlers and parse the messages were mixed in the Backend class. This makes difficult to add different protocols, socket implementation or controllers to the backend. The goal of this change is separate this three concerns in `SocketServer`, `Controller` and `Protocol` with the `Backend` class as the glue between these concepts. New implementations of the `Controller` could enable this backend to test the driver running in the browser without have to change the logic of parsing the messages between testkit and testkit-backend, for instance.
1 parent a29020d commit d54959d

File tree

8 files changed

+287
-118
lines changed

8 files changed

+287
-118
lines changed

packages/testkit-backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"private": true,
77
"type": "module",
88
"scripts": {
9-
"start": "node -r esm src/main.js",
9+
"start": "node -r esm src/index.js",
1010
"clean": "rm -fr node_modules"
1111
},
1212
"repository": {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import SocketServer from './socket.server'
2+
import Controller from './controller'
3+
4+
export default class Backend {
5+
constructor (port, newController = () => new Controller(), newSocketServer = port => new SocketServer(port)) {
6+
this._socketServer = newSocketServer(port)
7+
this._controller = newController()
8+
9+
this._controller.on('response', ({ contextId, response }) => {
10+
this._socketServer.writeResponse(contextId, response.name, response.data)
11+
})
12+
13+
this._socketServer.on('contextOpen', ({ contextId }) => this._controller.onContextOpen(contextId))
14+
this._socketServer.on('contextClose', ({ contextId }) => this._controller.onContextClose(contextId))
15+
16+
this._socketServer.on('request', ({ contextId, request }) => {
17+
try {
18+
this._controller.handle(contextId, request.name, request.data )
19+
} catch (e) {
20+
this._socketServer.writeBackendError(contextId, e)
21+
}
22+
})
23+
}
24+
25+
start () {
26+
this._controller.start()
27+
this._socketServer.start()
28+
}
29+
30+
stop () {
31+
this._socketServer.stop()
32+
this._controller.stop()
33+
}
34+
35+
}
36+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { EventEmitter } from 'events'
2+
3+
export default class Controller extends EventEmitter {
4+
5+
start () {
6+
7+
}
8+
9+
stop () {
10+
11+
}
12+
13+
onContextOpen(contextId) {
14+
throw new Error('not implemented')
15+
}
16+
17+
onContextClose(contextId) {
18+
throw new Error('not implemented')
19+
}
20+
21+
handle(contextId, name, data) {
22+
throw new Error('not implemented')
23+
}
24+
}

packages/testkit-backend/src/index.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import Backend from './backend'
2+
import NodeController from './node.controller'
3+
4+
function main( ) {
5+
const backend = new Backend(process.env.BACKEND_PORT || 9876, () => new NodeController())
6+
7+
backend.start()
8+
9+
// cleaning up
10+
process.on('exit', backend.stop.bind(backend));
11+
12+
// Capturing signals
13+
process.on('SIGINT', process.exit.bind(process));
14+
process.on('SIGUSR1', process.exit.bind(process));
15+
process.on('SIGUSR2', process.exit.bind(process));
16+
process.on('uncaughtException', process.exit.bind(process));
17+
}
18+
19+
main()

packages/testkit-backend/src/main.js

Lines changed: 0 additions & 117 deletions
This file was deleted.
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import Context from './context'
2+
import Controller from './controller'
3+
import * as _requestHandlers from './request-handlers'
4+
5+
6+
export default class NodeController extends Controller {
7+
8+
constructor(requestHandlers = _requestHandlers) {
9+
super()
10+
this._requestHandlers = requestHandlers
11+
this._contexts = new Map()
12+
}
13+
14+
onContextOpen(contextId) {
15+
this._contexts.set(contextId, new Context())
16+
}
17+
18+
onContextClose(contextId) {
19+
this._contexts.delete(contextId)
20+
}
21+
22+
23+
handle(contextId, name, data) {
24+
if (!this._contexts.has(contextId)) {
25+
throw new Error(`Context ${contextId} does not exist`)
26+
} else if (!(name in this._requestHandlers)) {
27+
console.log('Unknown request: ' + name)
28+
console.log(stringify(data))
29+
throw new Error(`Unknown request: ${name}`)
30+
}
31+
32+
this._requestHandlers[name](this._contexts.get(contextId), data, {
33+
writeResponse: (name, data) => this._writeResponse(contextId, name, data),
34+
writeError: (e) => this._writeError(contextId, e),
35+
writeBackendError: (msg) => this._writeBackendError(contextId, msg)
36+
})
37+
38+
}
39+
40+
_writeResponse (contextId, name, data) {
41+
console.log('> writing response', name, data)
42+
let response = {
43+
name: name,
44+
data: data
45+
}
46+
47+
this.emit('response', { contextId, response })
48+
}
49+
50+
_writeBackendError (contextId, msg) {
51+
this._writeResponse(contextId, 'BackendError', { msg: msg })
52+
}
53+
54+
_writeError (contextId, e) {
55+
if (e.name) {
56+
const id = this._contexts.get(contextId).addError(e)
57+
this._writeResponse(contextId, 'DriverError', {
58+
id,
59+
msg: e.message + ' (' + e.code + ')',
60+
code: e.code
61+
})
62+
return
63+
}
64+
this._writeBackendError(contextId, e)
65+
}
66+
67+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
2+
import EventEmitter from 'events'
3+
4+
export default class Protocol extends EventEmitter {
5+
constructor () {
6+
super()
7+
console.log('Backend connected')
8+
this._inRequest = false
9+
this._request = ''
10+
}
11+
12+
// Called whenever a new line is received.
13+
processLine (line) {
14+
switch (line) {
15+
case '#request begin':
16+
if (this._inRequest) {
17+
throw new Error('Already in request')
18+
}
19+
this._inRequest = true
20+
break
21+
case '#request end':
22+
if (!this._inRequest) {
23+
throw new Error('End while not in request')
24+
}
25+
try {
26+
this._emitRequest()
27+
} catch (e) {
28+
console.log('error', e)
29+
this.emit('error', e)
30+
}
31+
this._request = ''
32+
this._inRequest = false
33+
break
34+
default:
35+
if (!this._inRequest) {
36+
throw new Error('Line while not in request')
37+
}
38+
this._request += line
39+
break
40+
}
41+
}
42+
43+
serializeResponse (name, data) {
44+
console.log('> writing response', name, data)
45+
let response = {
46+
name: name,
47+
data: data
48+
}
49+
response = this._stringify(response)
50+
return ['#response begin', response, '#response end'].join('\n') + '\n'
51+
}
52+
53+
_emitRequest () {
54+
const request = JSON.parse(this._request)
55+
const { name, data } = request
56+
console.log('> Got request ' + name, data)
57+
this.emit('request', { name, data })
58+
}
59+
60+
_stringify (val) {
61+
return JSON.stringify(val, (_, value) =>
62+
typeof value === 'bigint' ? `${value}n` : value
63+
)
64+
}
65+
}

0 commit comments

Comments
 (0)