1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-07 13:22:56 +03:00

test RESP decoder + fix some bugs

This commit is contained in:
Leibale
2023-12-12 16:15:50 -05:00
parent 7f7a53a1b1
commit 94cb86f605
2 changed files with 278 additions and 215 deletions

View File

@@ -1,226 +1,286 @@
// import { strict as assert } from 'node:assert'; import { strict as assert } from 'node:assert';
// import { SinonSpy, spy } from 'sinon'; import { SinonSpy, spy } from 'sinon';
// import { Decoder, RESP_TYPES } from './decoder'; import { Decoder, RESP_TYPES } from './decoder';
// import { ErrorReply } from '../errors'; import { BlobError, ErrorReply, SimpleError } from '../errors';
// import { TypeMapping } from './types'; import { TypeMapping } from './types';
// function createDecoderAndSpies(typeMapping: TypeMapping = {}) { interface Test {
// const typeMappingSpy = spy(() => typeMapping), toWrite: Buffer;
// onReplySpy = spy(), typeMapping?: TypeMapping;
// onErrorReplySpy = spy(), replies?: Array<unknown>;
// onPushSpy = spy(); errorReplies?: Array<unknown>;
pushReplies?: Array<unknown>;
}
// return { function test(name: string, config: Test) {
// decoder: new Decoder({ describe(name, () => {
// getTypeMapping: typeMappingSpy, it('single chunk', () => {
// onReply: onReplySpy, const setup = setupTest(config);
// onErrorReply: onErrorReplySpy, setup.decoder.write(config.toWrite);
// onPush: onPushSpy assertCalls(config, setup);
// }), });
// typeMappingSpy,
// onReplySpy,
// onErrorReplySpy,
// onPushSpy
// };
// }
// function writeChunks(stream: Decoder, buffer: Buffer) { it('byte by byte', () => {
// let i = 0; const setup = setupTest(config);
// while (i < buffer.length) { for (let i = 0; i < config.toWrite.length; i++) {
// stream.write(buffer.subarray(i, ++i)); setup.decoder.write(config.toWrite.subarray(i, i + 1));
// } }
// } assertCalls(config, setup);
});
})
}
// type Replies = Array<Array<unknown>>; function setupTest(config: Test) {
const onReplySpy = spy(),
onErrorReplySpy = spy(),
onPushSpy = spy();
// function generateTests(toWrite: Buffer, tests: Array<Test & { name: string }>): void { return {
// for (const test of tests) { decoder: new Decoder({
// describe(test.name, () => { getTypeMapping: () => config.typeMapping ?? {},
// generateTests(toWrite, test); onReply: onReplySpy,
// }); onErrorReply: onErrorReplySpy,
// } onPush: onPushSpy
// } }),
onReplySpy,
onErrorReplySpy,
onPushSpy
};
}
// interface Test { function assertCalls(config: Test, spies: ReturnType<typeof setupTest>) {
// typeMapping?: TypeMapping; assertSpyCalls(spies.onReplySpy, config.replies);
// replies?: Replies; assertSpyCalls(spies.onErrorReplySpy, config.errorReplies);
// errorReplies?: Replies; assertSpyCalls(spies.onPushSpy, config.pushReplies);
// pushReplies?: Replies; }
// }
// function genetareTypeTests(toWrite: Buffer, { typeMapping, replies, errorReplies, pushReplies }: Test) { function assertSpyCalls(spy: SinonSpy, replies?: Array<unknown>) {
// const total = (replies?.length ?? 0) + (errorReplies?.length ?? 0) + (pushReplies?.length ?? 0); if (!replies) {
// it('single chunk', () => { assert.equal(spy.callCount, 0);
// const { decoder, typeMappingSpy, onReplySpy, onErrorReplySpy, onPushSpy } = createDecoderAndSpies(typeMapping); return;
// decoder.write(toWrite); }
// assert.equal(typeMappingSpy.callCount, total);
// testReplies(onReplySpy, replies);
// testReplies(onErrorReplySpy, errorReplies);
// testReplies(onPushSpy, pushReplies);
// });
// it('multiple chunks', () => { assert.equal(spy.callCount, replies.length);
// const { decoder, typeMappingSpy, onReplySpy, onErrorReplySpy, onPushSpy } = createDecoderAndSpies(typeMapping); for (const [i, reply] of replies.entries()) {
// writeChunks(decoder, toWrite); assert.deepEqual(
// assert.equal(typeMappingSpy.callCount, total); spy.getCall(i).args,
// testReplies(onReplySpy, replies); [reply]
// testReplies(onErrorReplySpy, errorReplies); );
// testReplies(onPushSpy, pushReplies); }
// }); }
// }
// function testReplies(spy: SinonSpy, replies?: Replies): void { describe.only('RESP Decoder', () => {
// if (!replies) { test('Null', {
// assert.equal(spy.callCount, 0); toWrite: Buffer.from('_\r\n'),
// return; replies: [null]
// } });
// assert.equal(spy.callCount, replies.length); describe('Boolean', () => {
// for (const [i, reply] of replies.entries()) { test('true', {
// assert.deepEqual( toWrite: Buffer.from('#t\r\n'),
// spy.getCall(i).args, replies: [true]
// reply });
// );
// }
// }
// describe('RESP2Parser', () => { test('false', {
// describe('Null', () => { toWrite: Buffer.from('#f\r\n'),
// genetareTypeTests(Buffer.from('_\r\n'), { replies: [false]
// replies: [[null]] });
// }); });
// });
// describe('Boolean', () => { describe('Number', () => {
// genetareTypeTests(Buffer.from('#t\r\n'), { test('0', {
// replies: [[null]] toWrite: Buffer.from(':0\r\n'),
// }); replies: [0]
// }); });
// describe('Number', () => { test('1', {
// generateTests(Buffer.from(':-1\r\n')) toWrite: Buffer.from(':+1\r\n'),
// describe('as number', () => { replies: [1]
// describe('-1', () => { });
// generateTests({
// toWrite: ,
// replies: [[-1]]
// });
// });
// describe('0', () => { test('+1', {
// generateTests({ toWrite: Buffer.from(':+1\r\n'),
// toWrite: Buffer.from(':0\r\n'), replies: [1]
// replies: [[0]] });
// });
// });
// describe('+1', () => { test('-1', {
// generateTests({ toWrite: Buffer.from(':-1\r\n'),
// toWrite: Buffer.from(':+1\r\n'), replies: [-1]
// replies: [[1]] });
// }); });
// });
// });
// });
// describe('Simple String', () => { describe('BigNumber', () => {
// describe('as strings', () => { test('0', {
// generateTests({ toWrite: Buffer.from('(0\r\n'),
// toWrite: Buffer.from('+OK\r\n'), replies: [0n]
// replies: [['OK']] });
// });
// });
// describe('as buffers', () => { test('1', {
// generateTests({ toWrite: Buffer.from('(1\r\n'),
// toWrite: Buffer.from('+OK\r\n'), replies: [1n]
// typeMapping: { });
// [RESP_TYPES.SIMPLE_STRING]: Buffer
// },
// replies: [[Buffer.from('OK')]]
// });
// });
// });
// describe('Error', () => { test('+1', {
// generateTests({ toWrite: Buffer.from('(+1\r\n'),
// toWrite: Buffer.from('-ERR\r\n'), replies: [1n]
// errorReplies: [[new ErrorReply('ERR')]] });
// });
// });
test('-1', {
toWrite: Buffer.from('(-1\r\n'),
replies: [-1n]
});
});
describe('Double', () => {
test('0', {
toWrite: Buffer.from(',0\r\n'),
replies: [0]
});
// describe('Bulk String', () => { test('1', {
// describe('null', () => { toWrite: Buffer.from(',1\r\n'),
// generateTests({ replies: [1]
// toWrite: Buffer.from('$-1\r\n'), });
// returnStringsAsBuffers: false,
// replies: [[null]]
// });
// });
// describe('as strings', () => { test('+1', {
// generateTests({ toWrite: Buffer.from(',+1\r\n'),
// toWrite: Buffer.from('$2\r\naa\r\n'), replies: [1]
// returnStringsAsBuffers: false, });
// replies: [['aa']]
// });
// });
// describe('as buffers', () => { test('-1', {
// generateTests({ toWrite: Buffer.from(',-1\r\n'),
// toWrite: Buffer.from('$2\r\naa\r\n'), replies: [-1]
// returnStringsAsBuffers: true, });
// replies: [[Buffer.from('aa')]]
// });
// });
// });
// describe('Array', () => { test('1.1', {
// describe('null', () => { toWrite: Buffer.from(',1.1\r\n'),
// generateTests({ replies: [1.1]
// toWrite: Buffer.from('*-1\r\n'), });
// returnStringsAsBuffers: false,
// replies: [[null]]
// });
// });
// const arrayBuffer = Buffer.from( test('nan', {
// '*5\r\n' + toWrite: Buffer.from(',nan\r\n'),
// '+OK\r\n' + replies: [NaN]
// '-ERR\r\n' + });
// ':0\r\n' +
// '$1\r\na\r\n' +
// '*0\r\n'
// );
// describe('as strings', () => { test('inf', {
// generateTests({ toWrite: Buffer.from(',inf\r\n'),
// toWrite: arrayBuffer, replies: [Infinity]
// returnStringsAsBuffers: false, });
// replies: [[[
// 'OK',
// new ErrorReply('ERR'),
// 0,
// 'a',
// []
// ]]]
// });
// });
// describe('as buffers', () => { test('+inf', {
// generateTests({ toWrite: Buffer.from(',+inf\r\n'),
// toWrite: arrayBuffer, replies: [Infinity]
// returnStringsAsBuffers: true, });
// replies: [[[
// Buffer.from('OK'), test('-inf', {
// new ErrorReply('ERR'), toWrite: Buffer.from(',-inf\r\n'),
// 0, replies: [-Infinity]
// Buffer.from('a'), });
// []
// ]]] test('1e1', {
// }); toWrite: Buffer.from(',1e1\r\n'),
// }); replies: [1e1]
// }); });
// });
test('-1.1E1', {
toWrite: Buffer.from(',-1.1E1\r\n'),
replies: [-1.1E1]
});
});
test('SimpleString', {
toWrite: Buffer.from('+OK\r\n'),
replies: ['OK']
});
describe('BlobString', () => {
test("''", {
toWrite: Buffer.from('$0\r\n\r\n'),
replies: ['']
});
test("'1234567890'", {
toWrite: Buffer.from('$10\r\n1234567890\r\n'),
replies: ['1234567890']
});
test('null (RESP2 backwards compatibility)', {
toWrite: Buffer.from('$-1\r\n'),
replies: [null]
});
});
describe('VerbatimString', () => {
test("''", {
toWrite: Buffer.from('=4\r\ntxt:\r\n'),
replies: ['']
});
test("'123456'", {
toWrite: Buffer.from('=10\r\ntxt:123456\r\n'),
replies: ['123456']
});
});
test('SimpleError', {
toWrite: Buffer.from('-ERROR\r\n'),
errorReplies: [new SimpleError('ERROR')]
});
test('BlobError', {
toWrite: Buffer.from('!5\r\nERROR\r\n'),
errorReplies: [new BlobError('ERROR')]
});
describe('Array', () => {
test('[]', {
toWrite: Buffer.from('*0\r\n'),
replies: [[]]
});
test('[0..9]', {
toWrite: Buffer.from(`*10\r\n:0\r\n:1\r\n:2\r\n:3\r\n:4\r\n:5\r\n:6\r\n:7\r\n:8\r\n:9\r\n`),
replies: [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]]
});
test('null (RESP2 backwards compatibility)', {
toWrite: Buffer.from('*-1\r\n'),
replies: [null]
});
});
describe('Set', () => {
test('empty', {
toWrite: Buffer.from('~0\r\n'),
replies: [[]]
});
test('of 0..9', {
toWrite: Buffer.from(`*10\r\n:0\r\n:1\r\n:2\r\n:3\r\n:4\r\n:5\r\n:6\r\n:7\r\n:8\r\n:9\r\n`),
replies: [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]]
});
});
describe('Map', () => {
test('{}', {
toWrite: Buffer.from('%0\r\n'),
replies: [Object.create(null)]
});
test("{ '0'..'9': <key> }", {
toWrite: Buffer.from(`%10\r\n+0\r\n+0\r\n+1\r\n+1\r\n+2\r\n+2\r\n+3\r\n+3\r\n+4\r\n+4\r\n+5\r\n+5\r\n+6\r\n+6\r\n+7\r\n+7\r\n+8\r\n+8\r\n+9\r\n+9\r\n`),
replies: [Object.create(null, {
0: { value: '0', enumerable: true },
1: { value: '1', enumerable: true },
2: { value: '2', enumerable: true },
3: { value: '3', enumerable: true },
4: { value: '4', enumerable: true },
5: { value: '5', enumerable: true },
6: { value: '6', enumerable: true },
7: { value: '7', enumerable: true },
8: { value: '8', enumerable: true },
9: { value: '9', enumerable: true }
})]
});
});
});

View File

@@ -257,7 +257,7 @@ export class Decoder {
private _maybeDecodeNumberValue(isNegative, chunk) { private _maybeDecodeNumberValue(isNegative, chunk) {
const cb = this._decodeUnsingedNumber.bind(this, 0); const cb = this._decodeUnsingedNumber.bind(this, 0);
return ++this._cursor === chunk.length ? return ++this._cursor === chunk.length ?
this._decodeNumberValue.bind(isNegative, cb) : this._decodeNumberValue.bind(this, isNegative, cb) :
this._decodeNumberValue(isNegative, cb, chunk); this._decodeNumberValue(isNegative, cb, chunk);
} }
@@ -306,8 +306,8 @@ export class Decoder {
private _maybeDecodeBigNumberValue(isNegative, chunk) { private _maybeDecodeBigNumberValue(isNegative, chunk) {
const cb = this._decodeUnsingedBigNumber.bind(this, 0n); const cb = this._decodeUnsingedBigNumber.bind(this, 0n);
return this._cursor === chunk.length ? return ++this._cursor === chunk.length ?
this._decodeBigNumberValue.bind(isNegative, cb) : this._decodeBigNumberValue.bind(this, isNegative, cb) :
this._decodeBigNumberValue(isNegative, cb, chunk); this._decodeBigNumberValue(isNegative, cb, chunk);
} }
@@ -357,7 +357,7 @@ export class Decoder {
private _maybeDecodeDoubleInteger(isNegative, chunk) { private _maybeDecodeDoubleInteger(isNegative, chunk) {
return ++this._cursor === chunk.length ? return ++this._cursor === chunk.length ?
this._decodeDoubleInteger.bind(this, isNegative, 0) : this._decodeDoubleInteger.bind(this, isNegative, 0) :
this._decodeDoubleInteger(true, 0, chunk); this._decodeDoubleInteger(isNegative, 0, chunk);
} }
private _decodeDoubleInteger(isNegative, integer, chunk) { private _decodeDoubleInteger(isNegative, integer, chunk) {
@@ -376,14 +376,17 @@ export class Decoder {
switch (byte) { switch (byte) {
case ASCII['.']: case ASCII['.']:
this._cursor = cursor + 1; // skip . this._cursor = cursor + 1; // skip .
return cursor < chunk.length ? return this._cursor < chunk.length ?
this._decodeDoubleDecimal(isNegative, 0, integer, chunk) : this._decodeDoubleDecimal(isNegative, 0, integer, chunk) :
this._decodeDoubleDecimal.bind(this, isNegative, 0, integer); this._decodeDoubleDecimal.bind(this, isNegative, 0, integer);
case ASCII.E: case ASCII.E:
case ASCII.e: case ASCII.e:
this._cursor = cursor + 1; // skip e this._cursor = cursor + 1; // skip E/e
return this._decodeDoubleExponent(isNegative ? -integer : integer, chunk); const i = isNegative ? -integer : integer;
return this._cursor < chunk.length ?
this._decodeDoubleExponent(i, chunk) :
this._decodeDoubleExponent.bind(this, i);
case ASCII['\r']: case ASCII['\r']:
this._cursor = cursor + 2; // skip \r\n this._cursor = cursor + 2; // skip \r\n
@@ -414,11 +417,11 @@ export class Decoder {
switch (byte) { switch (byte) {
case ASCII.E: case ASCII.E:
case ASCII.e: case ASCII.e:
this._cursor = cursor + 1; // skip e this._cursor = cursor + 1; // skip E/e
const d = isNegative ? -double : double; const d = isNegative ? -double : double;
return this._cursor === chunk.length ? return this._cursor === chunk.length ?
this._decodeDoubleExponent.bind(this, d, false, 0) : this._decodeDoubleExponent.bind(this, d) :
this._decodeDoubleExponent(d, false, 0, chunk); this._decodeDoubleExponent(d, chunk);
case ASCII['\r']: case ASCII['\r']:
this._cursor = cursor + 2; // skip \r\n this._cursor = cursor + 2; // skip \r\n