From 258735625c4ca5a684f7a0943189a0cc6fbdfd23 Mon Sep 17 00:00:00 2001 From: streamich Date: Tue, 14 Nov 2023 11:21:39 +0100 Subject: [PATCH 1/4] =?UTF-8?q?test(json-crdt):=20=F0=9F=92=8D=20add=20abi?= =?UTF-8?q?lity=20to=20collect=20fuzzer=20traces?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__tests__/fuzzer/JsonCrdtFuzzer.ts | 6 +++ .../__tests__/fuzzer/generate-trace.ts | 39 +++++++++++++++++++ src/json-crdt/__tests__/fuzzer/types.ts | 3 ++ 3 files changed, 48 insertions(+) create mode 100644 src/json-crdt/__tests__/fuzzer/generate-trace.ts diff --git a/src/json-crdt/__tests__/fuzzer/JsonCrdtFuzzer.ts b/src/json-crdt/__tests__/fuzzer/JsonCrdtFuzzer.ts index 70e73d5b8c..5866303e61 100644 --- a/src/json-crdt/__tests__/fuzzer/JsonCrdtFuzzer.ts +++ b/src/json-crdt/__tests__/fuzzer/JsonCrdtFuzzer.ts @@ -5,6 +5,7 @@ import {FuzzerOptions} from './types'; import {RandomJson} from '../../../json-random/RandomJson'; import {generateInteger} from './util'; import {PatchBuilder} from '../../../json-crdt-patch/PatchBuilder'; +import {Patch} from '../../../json-crdt-patch'; export const defaultFuzzerOptions: FuzzerOptions = { startingValue: undefined, @@ -18,12 +19,14 @@ export const defaultFuzzerOptions: FuzzerOptions = { concurrentPeers: [1, 6], patchesPerPeer: [0, 12], testCodecs: true, + collectPatches: false, }; export class JsonCrdtFuzzer { public opts: FuzzerOptions; public model: Model; public picker: Picker; + public patches: Patch[] = []; constructor(opts: Partial = {}) { this.opts = {...defaultFuzzerOptions, ...opts}; @@ -39,6 +42,7 @@ export class JsonCrdtFuzzer { const builder = new PatchBuilder(this.model.clock); builder.root(builder.json(json)); const patch = builder.flush(); + this.patches.push(patch); this.model.applyPatch(patch); } @@ -47,6 +51,8 @@ export class JsonCrdtFuzzer { const session = new SessionLogical(this, concurrency); session.generateEdits(); session.synchronize(); + if (this.opts.collectPatches) + for (const patches of session.patches) this.patches.push(...patches); return session; } } diff --git a/src/json-crdt/__tests__/fuzzer/generate-trace.ts b/src/json-crdt/__tests__/fuzzer/generate-trace.ts new file mode 100644 index 0000000000..bb1e917b27 --- /dev/null +++ b/src/json-crdt/__tests__/fuzzer/generate-trace.ts @@ -0,0 +1,39 @@ +// Run: npx ts-node src/json-crdt/__tests__/fuzzer/generate-trace.ts + +import {Patch} from '../../../json-crdt-patch'; +import {Model} from '../../model'; +import {JsonCrdtFuzzer} from './JsonCrdtFuzzer'; +import {CborEncoder} from '../../../json-pack/cbor/CborEncoder'; +import {Writer} from '../../../util/buffers/Writer'; +import * as fs from 'fs'; + +const sessionNum = 100; +const fuzzer = new JsonCrdtFuzzer({ + // concurrentPeers: [20, 100], + // startingValue: new Uint8Array([1, 2, 3]), + collectPatches: true, +}); +fuzzer.setupModel(); + +for (let ses = 0; ses < sessionNum; ses++) { + fuzzer.executeConcurrentSession(); +} + +const patches: Patch[] = []; +const dupes: Set = new Set(); +for (const patch of fuzzer.patches) { + const key = `${patch.getId()?.sid}_${patch.getId()?.time}`; + if (dupes.has(key)) continue; + dupes.add(key); + patches.push(patch); +} + +const model = Model.withLogicalClock(); +model.applyBatch(patches); +const cborEncoder = new CborEncoder(new Writer()); +fs.writeFileSync(__dirname + '/trace.cbor', cborEncoder.encode(patches.map(p => p.toBinary()))); + +// console.log(Buffer.from(jsonEncoder.encode(encoded)).toString()); +console.log(model.view()); +console.log(fuzzer.model.view()); +// console.log(model + ''); diff --git a/src/json-crdt/__tests__/fuzzer/types.ts b/src/json-crdt/__tests__/fuzzer/types.ts index a11ee39446..7952aad979 100644 --- a/src/json-crdt/__tests__/fuzzer/types.ts +++ b/src/json-crdt/__tests__/fuzzer/types.ts @@ -34,4 +34,7 @@ export interface FuzzerOptions { /** Do not generate "__proto__" as a string, so it does not appear as object key. */ noProtoString?: boolean; + + /** Whether to collect all generated patches. */ + collectPatches?: boolean; } From 6b55b3a310d86fbd394ebac8499ef449620ef823 Mon Sep 17 00:00:00 2001 From: streamich Date: Tue, 14 Nov 2023 12:04:30 +0100 Subject: [PATCH 2/4] =?UTF-8?q?test(json-crdt):=20=F0=9F=92=8D=20run=20lat?= =?UTF-8?q?est=20editing=20traces=20and=20run=20fuzzer=20traces?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/json-crdt/__bench__/util/fuzzer-traces.ts | 20 ++++++++++++++ .../__tests__/editing-traces.spec.ts | 27 ++++++++++++++++++- yarn.lock | 4 +-- 4 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 src/json-crdt/__bench__/util/fuzzer-traces.ts diff --git a/package.json b/package.json index 6953f23dc6..54aba6e369 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,7 @@ "js-base64": "^3.7.2", "js-sdsl": "^4.4.0", "jsbi": "^4.3.0", - "json-crdt-traces": "https://github.com/streamich/json-crdt-traces#5f68a13baf798897d39b87d7028b0b3cd5a50a6c", + "json-crdt-traces": "https://github.com/streamich/json-crdt-traces#02718a7a5d09e0dc6c31ea7d45a9ce3cbb0bf085", "json-logic-js": "^2.0.1", "json-pack-napi": "^0.0.2", "load-script": "^2.0.0", diff --git a/src/json-crdt/__bench__/util/fuzzer-traces.ts b/src/json-crdt/__bench__/util/fuzzer-traces.ts new file mode 100644 index 0000000000..d0bc81b5c6 --- /dev/null +++ b/src/json-crdt/__bench__/util/fuzzer-traces.ts @@ -0,0 +1,20 @@ +import * as path from 'path'; +import * as fs from 'fs'; +import {Patch} from '../../../json-crdt-patch'; +import {CborDecoder} from '../../../json-pack/cbor/CborDecoder'; +import {Model} from '../../model'; +import {bufferToUint8Array} from '../../../util/buffers/bufferToUint8Array'; + +export const loadFuzzerTrace = (traceName: string): [batch: Patch[], model: Model] => { + const root = path.resolve(__dirname, '..', '..', '..', '..'); + const dir = path.join(root, 'node_modules', 'json-crdt-traces', 'traces', 'fuzzer', 'processed', traceName); + const patchFile = path.join(dir, 'patches.bin'); + const modelFile = path.join(dir, 'model.bin'); + const buf = fs.readFileSync(patchFile); + const modelBuf = bufferToUint8Array(fs.readFileSync(modelFile)); + const model = Model.fromBinary(modelBuf); + const cborDecoder = new CborDecoder(); + const data = cborDecoder.read(buf) as Uint8Array[]; + const batch = data.map((blob) => Patch.fromBinary(blob)); + return [batch, model]; +}; diff --git a/src/json-crdt/__tests__/editing-traces.spec.ts b/src/json-crdt/__tests__/editing-traces.spec.ts index e0b81c4ca9..2f9ba26cad 100644 --- a/src/json-crdt/__tests__/editing-traces.spec.ts +++ b/src/json-crdt/__tests__/editing-traces.spec.ts @@ -3,6 +3,7 @@ import {sequentialTraceNames, traces} from '../__bench__/util/traces'; import {editors} from '../__bench__/util/editors'; import {Model} from '../model'; import {loadConcurrentTrace} from '../__bench__/util/concurrent-trace'; +import {loadFuzzerTrace} from '../__bench__/util/fuzzer-traces'; describe('sequential traces', () => { const editor = editors['json-joy']; @@ -15,7 +16,7 @@ describe('sequential traces', () => { } }); -describe.skip('concurrent traces', () => { +describe('concurrent traces', () => { const traces: string[] = ['friendsforever']; for (const traceName of traces) { test(`"${traceName}" trace`, async () => { @@ -26,3 +27,27 @@ describe.skip('concurrent traces', () => { }); } }); + +describe('fuzzer traces', () => { + const traces = [ + 'trace-1', + 'trace-2', + 'trace-3', + 'long', + 'short', + 'low-concurrency', + 'high-concurrency', + 'str-only', + 'bin-only', + ]; + + for (const traceName of traces) { + test(`"${traceName}" trace`, async () => { + const [batch, doc] = loadFuzzerTrace(traceName); + const model = Model.withLogicalClock(1000000); + model.applyBatch(batch); + expect(Model.fromBinary(model.toBinary()).toString()).toBe(doc.toString()); + expect(model.view()).toStrictEqual(doc.view()); + }); + } +}); diff --git a/yarn.lock b/yarn.lock index 8d331fc535..64d325de9d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3664,9 +3664,9 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== -"json-crdt-traces@https://github.com/streamich/json-crdt-traces#5f68a13baf798897d39b87d7028b0b3cd5a50a6c": +"json-crdt-traces@https://github.com/streamich/json-crdt-traces#02718a7a5d09e0dc6c31ea7d45a9ce3cbb0bf085": version "0.0.1" - resolved "https://github.com/streamich/json-crdt-traces#5f68a13baf798897d39b87d7028b0b3cd5a50a6c" + resolved "https://github.com/streamich/json-crdt-traces#02718a7a5d09e0dc6c31ea7d45a9ce3cbb0bf085" json-logic-js@^2.0.1: version "2.0.2" From 2524c816f332b87d1ec6bda527cc3331695bf57a Mon Sep 17 00:00:00 2001 From: streamich Date: Tue, 14 Nov 2023 12:05:13 +0100 Subject: [PATCH 3/4] =?UTF-8?q?style:=20=F0=9F=92=84=20run=20Prettier?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/json-crdt/__tests__/fuzzer/JsonCrdtFuzzer.ts | 3 +-- src/json-crdt/__tests__/fuzzer/generate-trace.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/json-crdt/__tests__/fuzzer/JsonCrdtFuzzer.ts b/src/json-crdt/__tests__/fuzzer/JsonCrdtFuzzer.ts index 5866303e61..ff1b5a26d7 100644 --- a/src/json-crdt/__tests__/fuzzer/JsonCrdtFuzzer.ts +++ b/src/json-crdt/__tests__/fuzzer/JsonCrdtFuzzer.ts @@ -51,8 +51,7 @@ export class JsonCrdtFuzzer { const session = new SessionLogical(this, concurrency); session.generateEdits(); session.synchronize(); - if (this.opts.collectPatches) - for (const patches of session.patches) this.patches.push(...patches); + if (this.opts.collectPatches) for (const patches of session.patches) this.patches.push(...patches); return session; } } diff --git a/src/json-crdt/__tests__/fuzzer/generate-trace.ts b/src/json-crdt/__tests__/fuzzer/generate-trace.ts index bb1e917b27..7f6153b722 100644 --- a/src/json-crdt/__tests__/fuzzer/generate-trace.ts +++ b/src/json-crdt/__tests__/fuzzer/generate-trace.ts @@ -31,7 +31,7 @@ for (const patch of fuzzer.patches) { const model = Model.withLogicalClock(); model.applyBatch(patches); const cborEncoder = new CborEncoder(new Writer()); -fs.writeFileSync(__dirname + '/trace.cbor', cborEncoder.encode(patches.map(p => p.toBinary()))); +fs.writeFileSync(__dirname + '/trace.cbor', cborEncoder.encode(patches.map((p) => p.toBinary()))); // console.log(Buffer.from(jsonEncoder.encode(encoded)).toString()); console.log(model.view()); From 8859b591b4d057c21e67613db1f5dab021caaee7 Mon Sep 17 00:00:00 2001 From: streamich Date: Tue, 14 Nov 2023 12:07:49 +0100 Subject: [PATCH 4/4] =?UTF-8?q?style(json-crdt):=20=F0=9F=92=84=20make=20l?= =?UTF-8?q?inter=20happy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/json-crdt/__tests__/fuzzer/generate-trace.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/json-crdt/__tests__/fuzzer/generate-trace.ts b/src/json-crdt/__tests__/fuzzer/generate-trace.ts index 7f6153b722..88307e8f54 100644 --- a/src/json-crdt/__tests__/fuzzer/generate-trace.ts +++ b/src/json-crdt/__tests__/fuzzer/generate-trace.ts @@ -1,3 +1,4 @@ +/* tslint:disable no-console */ // Run: npx ts-node src/json-crdt/__tests__/fuzzer/generate-trace.ts import {Patch} from '../../../json-crdt-patch';