diff --git a/package.json b/package.json index 12aaaaed5..92046b5b8 100644 --- a/package.json +++ b/package.json @@ -291,7 +291,7 @@ "npm-run-all": "4.1.5", "nyc": "11.4.1", "patch-package": "6.4.7", - "playwright": "1.30.0", + "playwright": "1.31.2", "prettier": "2.8.0", "sass": "1.49.7", "sass-loader": "10.2.0", diff --git a/ts/test-mock/benchmarks/convo_open_bench.ts b/ts/test-mock/benchmarks/convo_open_bench.ts index 5e4865c4b..185d33dd8 100644 --- a/ts/test-mock/benchmarks/convo_open_bench.ts +++ b/ts/test-mock/benchmarks/convo_open_bench.ts @@ -5,103 +5,87 @@ import assert from 'assert'; import type { PrimaryDevice } from '@signalapp/mock-server'; -import type { App } from './fixtures'; import { Bootstrap, debug, stats, RUN_COUNT, DISCARD_COUNT } from './fixtures'; const CONVERSATION_SIZE = 1000; // messages const DELAY = 50; // milliseconds -void (async () => { - const bootstrap = new Bootstrap({ - benchmark: true, - }); +Bootstrap.benchmark(async (bootstrap: Bootstrap): Promise => { + const app = await bootstrap.link(); + const { server, contacts, phone, desktop } = bootstrap; - await bootstrap.init(); + const [first, second] = contacts; - let app: App | undefined; - try { - app = await bootstrap.link(); - const { server, contacts, phone, desktop } = bootstrap; + const messages = new Array(); + debug('encrypting'); + // Send messages from just two contacts + for (const contact of [second, first]) { + for (let i = 0; i < CONVERSATION_SIZE; i += 1) { + const messageTimestamp = bootstrap.getTimestamp(); + messages.push( + await contact.encryptText( + desktop, + `hello from: ${contact.profileName}`, + { + timestamp: messageTimestamp, + sealed: true, + } + ) + ); - const [first, second] = contacts; - - const messages = new Array(); - debug('encrypting'); - // Send messages from just two contacts - for (const contact of [second, first]) { - for (let i = 0; i < CONVERSATION_SIZE; i += 1) { - const messageTimestamp = bootstrap.getTimestamp(); - messages.push( - await contact.encryptText( - desktop, - `hello from: ${contact.profileName}`, + messages.push( + await phone.encryptSyncRead(desktop, { + timestamp: bootstrap.getTimestamp(), + messages: [ { + senderUUID: contact.device.uuid, timestamp: messageTimestamp, - sealed: true, - } - ) - ); + }, + ], + }) + ); + } + } - messages.push( - await phone.encryptSyncRead(desktop, { - timestamp: bootstrap.getTimestamp(), - messages: [ - { - senderUUID: contact.device.uuid, - timestamp: messageTimestamp, - }, - ], - }) - ); + const sendQueue = async (): Promise => { + await Promise.all(messages.map(message => server.send(desktop, message))); + }; + + const measure = async (): Promise => { + assert(app); + const window = await app.getWindow(); + + const leftPane = window.locator('.left-pane-wrapper'); + + const openConvo = async (contact: PrimaryDevice): Promise => { + debug('opening conversation', contact.profileName); + const item = leftPane.locator( + `[data-testid="${contact.toContact().uuid}"]` + ); + + await item.click(); + }; + + const deltaList = new Array(); + for (let runId = 0; runId < RUN_COUNT + DISCARD_COUNT; runId += 1) { + await openConvo(runId % 2 === 0 ? first : second); + + debug('waiting for timing from the app'); + const { delta } = await app.waitForConversationOpen(); + + // Let render complete + await new Promise(resolve => setTimeout(resolve, DELAY)); + + if (runId >= DISCARD_COUNT) { + deltaList.push(delta); + console.log('run=%d info=%j', runId - DISCARD_COUNT, { delta }); + } else { + console.log('discarded=%d info=%j', runId, { delta }); } } - const sendQueue = async (): Promise => { - await Promise.all(messages.map(message => server.send(desktop, message))); - }; + console.log('stats info=%j', { delta: stats(deltaList, [99, 99.8]) }); + }; - const measure = async (): Promise => { - assert(app); - const window = await app.getWindow(); - - const leftPane = window.locator('.left-pane-wrapper'); - - const openConvo = async (contact: PrimaryDevice): Promise => { - debug('opening conversation', contact.profileName); - const item = leftPane.locator( - `[data-testid="${contact.toContact().uuid}"]` - ); - - await item.click(); - }; - - const deltaList = new Array(); - for (let runId = 0; runId < RUN_COUNT + DISCARD_COUNT; runId += 1) { - await openConvo(runId % 2 === 0 ? first : second); - - debug('waiting for timing from the app'); - const { delta } = await app.waitForConversationOpen(); - - // Let render complete - await new Promise(resolve => setTimeout(resolve, DELAY)); - - if (runId >= DISCARD_COUNT) { - deltaList.push(delta); - console.log('run=%d info=%j', runId - DISCARD_COUNT, { delta }); - } else { - console.log('discarded=%d info=%j', runId, { delta }); - } - } - - console.log('stats info=%j', { delta: stats(deltaList, [99, 99.8]) }); - }; - - await Promise.all([sendQueue(), measure()]); - } catch (error) { - await bootstrap.saveLogs(app); - throw error; - } finally { - await app?.close(); - await bootstrap.teardown(); - } -})(); + await Promise.all([sendQueue(), measure()]); +}); diff --git a/ts/test-mock/benchmarks/group_send_bench.ts b/ts/test-mock/benchmarks/group_send_bench.ts index 443c8af2e..fb1abeb6a 100644 --- a/ts/test-mock/benchmarks/group_send_bench.ts +++ b/ts/test-mock/benchmarks/group_send_bench.ts @@ -10,7 +10,6 @@ import { ReceiptType, } from '@signalapp/mock-server'; -import type { App } from './fixtures'; import { Bootstrap, debug, @@ -23,13 +22,7 @@ import { const CONVERSATION_SIZE = 500; // messages const LAST_MESSAGE = 'start sending messages now'; -void (async () => { - const bootstrap = new Bootstrap({ - benchmark: true, - }); - - await bootstrap.init(); - +Bootstrap.benchmark(async (bootstrap: Bootstrap): Promise => { const { contacts, phone } = bootstrap; const members = [...contacts].slice(0, GROUP_SIZE); @@ -45,145 +38,131 @@ void (async () => { .pinGroup(group) ); - let app: App | undefined; + const app = await bootstrap.link(); - try { - app = await bootstrap.link(); + const { server, desktop } = bootstrap; + const [first] = members; - const { server, desktop } = bootstrap; - const [first] = members; + const messages = new Array(); + debug('encrypting'); + // Fill left pane + for (const contact of members.slice().reverse()) { + const messageTimestamp = bootstrap.getTimestamp(); - const messages = new Array(); - debug('encrypting'); - // Fill left pane - for (const contact of members.slice().reverse()) { - const messageTimestamp = bootstrap.getTimestamp(); - - messages.push( - await contact.encryptText( - desktop, - `hello from: ${contact.profileName}`, + messages.push( + await contact.encryptText(desktop, `hello from: ${contact.profileName}`, { + timestamp: messageTimestamp, + sealed: true, + }) + ); + messages.push( + await phone.encryptSyncRead(desktop, { + timestamp: bootstrap.getTimestamp(), + messages: [ { + senderUUID: contact.device.uuid, timestamp: messageTimestamp, - sealed: true, - } - ) - ); - messages.push( - await phone.encryptSyncRead(desktop, { - timestamp: bootstrap.getTimestamp(), - messages: [ - { - senderUUID: contact.device.uuid, - timestamp: messageTimestamp, - }, - ], - }) - ); - } + }, + ], + }) + ); + } - // Fill group - for (let i = 0; i < CONVERSATION_SIZE; i += 1) { - const contact = members[i % members.length]; - const messageTimestamp = bootstrap.getTimestamp(); + // Fill group + for (let i = 0; i < CONVERSATION_SIZE; i += 1) { + const contact = members[i % members.length]; + const messageTimestamp = bootstrap.getTimestamp(); - const isLast = i === CONVERSATION_SIZE - 1; + const isLast = i === CONVERSATION_SIZE - 1; - messages.push( - await contact.encryptText( - desktop, - isLast ? LAST_MESSAGE : `#${i} from: ${contact.profileName}`, + messages.push( + await contact.encryptText( + desktop, + isLast ? LAST_MESSAGE : `#${i} from: ${contact.profileName}`, + { + timestamp: messageTimestamp, + sealed: true, + group, + } + ) + ); + messages.push( + await phone.encryptSyncRead(desktop, { + timestamp: bootstrap.getTimestamp(), + messages: [ { + senderUUID: contact.device.uuid, timestamp: messageTimestamp, - sealed: true, - group, - } - ) - ); - messages.push( - await phone.encryptSyncRead(desktop, { - timestamp: bootstrap.getTimestamp(), - messages: [ - { - senderUUID: contact.device.uuid, - timestamp: messageTimestamp, - }, - ], - }) - ); - } - debug('encrypted'); + }, + ], + }) + ); + } + debug('encrypted'); - await Promise.all(messages.map(message => server.send(desktop, message))); + await Promise.all(messages.map(message => server.send(desktop, message))); - const window = await app.getWindow(); + const window = await app.getWindow(); - debug('opening conversation'); - { - const leftPane = window.locator('.left-pane-wrapper'); + debug('opening conversation'); + { + const leftPane = window.locator('.left-pane-wrapper'); - const item = leftPane - .locator( - '.module-conversation-list__item--contact-or-conversation' + - `>> text=${LAST_MESSAGE}` - ) - .first(); - await item.click(); - } + const item = leftPane + .locator( + '.module-conversation-list__item--contact-or-conversation' + + `>> text=${LAST_MESSAGE}` + ) + .first(); + await item.click(); + } - const timeline = window.locator( - '.timeline-wrapper, .conversation .ConversationView' + const timeline = window.locator( + '.timeline-wrapper, .conversation .ConversationView' + ); + + const deltaList = new Array(); + for (let runId = 0; runId < RUN_COUNT + DISCARD_COUNT; runId += 1) { + debug('finding composition input and clicking it'); + const composeArea = window.locator( + '.composition-area-wrapper, .conversation .ConversationView' ); - const deltaList = new Array(); - for (let runId = 0; runId < RUN_COUNT + DISCARD_COUNT; runId += 1) { - debug('finding composition input and clicking it'); - const composeArea = window.locator( - '.composition-area-wrapper, .conversation .ConversationView' - ); + const input = composeArea.locator('[data-testid=CompositionInput]'); - const input = composeArea.locator('[data-testid=CompositionInput]'); + debug('entering message text'); + await input.type(`my message ${runId}`); + await input.press('Enter'); - debug('entering message text'); - await input.type(`my message ${runId}`); - await input.press('Enter'); + debug('waiting for message on server side'); + const { body, source, envelopeType } = await first.waitForMessage(); + assert.strictEqual(body, `my message ${runId}`); + assert.strictEqual(source, desktop); + assert.strictEqual(envelopeType, EnvelopeType.SenderKey); - debug('waiting for message on server side'); - const { body, source, envelopeType } = await first.waitForMessage(); - assert.strictEqual(body, `my message ${runId}`); - assert.strictEqual(source, desktop); - assert.strictEqual(envelopeType, EnvelopeType.SenderKey); + debug('waiting for timing from the app'); + const { timestamp, delta } = await app.waitForMessageSend(); - debug('waiting for timing from the app'); - const { timestamp, delta } = await app.waitForMessageSend(); + debug('sending delivery receipts'); + const delivery = await first.encryptReceipt(desktop, { + timestamp: timestamp + 1, + messageTimestamps: [timestamp], + type: ReceiptType.Delivery, + }); - debug('sending delivery receipts'); - const delivery = await first.encryptReceipt(desktop, { - timestamp: timestamp + 1, - messageTimestamps: [timestamp], - type: ReceiptType.Delivery, - }); + await server.send(desktop, delivery); - await server.send(desktop, delivery); + debug('waiting for message state change'); + const message = timeline.locator(`[data-testid="${timestamp}"]`); + await message.waitFor(); - debug('waiting for message state change'); - const message = timeline.locator(`[data-testid="${timestamp}"]`); - await message.waitFor(); - - if (runId >= DISCARD_COUNT) { - deltaList.push(delta); - console.log('run=%d info=%j', runId - DISCARD_COUNT, { delta }); - } else { - console.log('discarded=%d info=%j', runId, { delta }); - } + if (runId >= DISCARD_COUNT) { + deltaList.push(delta); + console.log('run=%d info=%j', runId - DISCARD_COUNT, { delta }); + } else { + console.log('discarded=%d info=%j', runId, { delta }); } - - console.log('stats info=%j', { delta: stats(deltaList, [99, 99.8]) }); - } catch (error) { - await bootstrap.saveLogs(app); - throw error; - } finally { - await app?.close(); - await bootstrap.teardown(); } -})(); + + console.log('stats info=%j', { delta: stats(deltaList, [99, 99.8]) }); +}); diff --git a/ts/test-mock/benchmarks/send_bench.ts b/ts/test-mock/benchmarks/send_bench.ts index b84ea0cf9..02ba84fe3 100644 --- a/ts/test-mock/benchmarks/send_bench.ts +++ b/ts/test-mock/benchmarks/send_bench.ts @@ -6,131 +6,115 @@ import assert from 'assert'; import { ReceiptType } from '@signalapp/mock-server'; -import type { App } from './fixtures'; import { Bootstrap, debug, stats, RUN_COUNT, DISCARD_COUNT } from './fixtures'; const CONVERSATION_SIZE = 500; // messages const LAST_MESSAGE = 'start sending messages now'; -void (async () => { - const bootstrap = new Bootstrap({ - benchmark: true, - }); +Bootstrap.benchmark(async (bootstrap: Bootstrap): Promise => { + const app = await bootstrap.link(); - await bootstrap.init(); - let app: App | undefined; + const { server, contacts, phone, desktop } = bootstrap; - try { - app = await bootstrap.link(); + const [first] = contacts; - const { server, contacts, phone, desktop } = bootstrap; + const messages = new Array(); + debug('encrypting'); + // Note: make it so that we receive the latest message from the first + // contact. + for (const contact of contacts.slice().reverse()) { + let count = 1; + if (contact === first) { + count = CONVERSATION_SIZE; + } - const [first] = contacts; + for (let i = 0; i < count; i += 1) { + const messageTimestamp = bootstrap.getTimestamp(); - const messages = new Array(); - debug('encrypting'); - // Note: make it so that we receive the latest message from the first - // contact. - for (const contact of contacts.slice().reverse()) { - let count = 1; - if (contact === first) { - count = CONVERSATION_SIZE; - } + const isLast = i === count - 1; - for (let i = 0; i < count; i += 1) { - const messageTimestamp = bootstrap.getTimestamp(); - - const isLast = i === count - 1; - - messages.push( - await contact.encryptText( - desktop, - isLast ? LAST_MESSAGE : `#${i} from: ${contact.profileName}`, + messages.push( + await contact.encryptText( + desktop, + isLast ? LAST_MESSAGE : `#${i} from: ${contact.profileName}`, + { + timestamp: messageTimestamp, + sealed: true, + } + ) + ); + messages.push( + await phone.encryptSyncRead(desktop, { + timestamp: bootstrap.getTimestamp(), + messages: [ { + senderUUID: contact.device.uuid, timestamp: messageTimestamp, - sealed: true, - } - ) - ); - messages.push( - await phone.encryptSyncRead(desktop, { - timestamp: bootstrap.getTimestamp(), - messages: [ - { - senderUUID: contact.device.uuid, - timestamp: messageTimestamp, - }, - ], - }) - ); - } - } - - await Promise.all(messages.map(message => server.send(desktop, message))); - - const window = await app.getWindow(); - - debug('opening conversation'); - { - const leftPane = window.locator('.left-pane-wrapper'); - const item = leftPane.locator( - `[data-testid="${first.toContact().uuid}"] >> text=${LAST_MESSAGE}` + }, + ], + }) ); - await item.click(); } - - const timeline = window.locator( - '.timeline-wrapper, .conversation .ConversationView' - ); - - const deltaList = new Array(); - for (let runId = 0; runId < RUN_COUNT + DISCARD_COUNT; runId += 1) { - debug('finding composition input and clicking it'); - const composeArea = window.locator( - '.composition-area-wrapper, .conversation .ConversationView' - ); - const input = composeArea.locator('[data-testid=CompositionInput]'); - - debug('entering message text'); - await input.type(`my message ${runId}`); - await input.press('Enter'); - - debug('waiting for message on server side'); - const { body, source } = await first.waitForMessage(); - assert.strictEqual(body, `my message ${runId}`); - assert.strictEqual(source, desktop); - - debug('waiting for timing from the app'); - const { timestamp, delta } = await app.waitForMessageSend(); - - debug('sending delivery receipt'); - const delivery = await first.encryptReceipt(desktop, { - timestamp: timestamp + 1, - messageTimestamps: [timestamp], - type: ReceiptType.Delivery, - }); - - await server.send(desktop, delivery); - - debug('waiting for message state change'); - const message = timeline.locator(`[data-testid="${timestamp}"]`); - await message.waitFor(); - - if (runId >= DISCARD_COUNT) { - deltaList.push(delta); - console.log('run=%d info=%j', runId - DISCARD_COUNT, { delta }); - } else { - console.log('discarded=%d info=%j', runId, { delta }); - } - } - - console.log('stats info=%j', { delta: stats(deltaList, [99, 99.8]) }); - } catch (error) { - await bootstrap.saveLogs(app); - throw error; - } finally { - await app?.close(); - await bootstrap.teardown(); } -})(); + + await Promise.all(messages.map(message => server.send(desktop, message))); + + const window = await app.getWindow(); + + debug('opening conversation'); + { + const leftPane = window.locator('.left-pane-wrapper'); + const item = leftPane.locator( + `[data-testid="${first.toContact().uuid}"] >> text=${LAST_MESSAGE}` + ); + await item.click(); + } + + const timeline = window.locator( + '.timeline-wrapper, .conversation .ConversationView' + ); + + const deltaList = new Array(); + for (let runId = 0; runId < RUN_COUNT + DISCARD_COUNT; runId += 1) { + debug('finding composition input and clicking it'); + const composeArea = window.locator( + '.composition-area-wrapper, .conversation .ConversationView' + ); + const input = composeArea.locator('[data-testid=CompositionInput]'); + + debug('entering message text'); + await input.type(`my message ${runId}`); + await input.press('Enter'); + + debug('waiting for message on server side'); + const { body, source } = await first.waitForMessage(); + assert.strictEqual(body, `my message ${runId}`); + assert.strictEqual(source, desktop); + + debug('waiting for timing from the app'); + const { timestamp, delta } = await app.waitForMessageSend(); + + debug('sending delivery receipt'); + const delivery = await first.encryptReceipt(desktop, { + timestamp: timestamp + 1, + messageTimestamps: [timestamp], + type: ReceiptType.Delivery, + }); + + await server.send(desktop, delivery); + + debug('waiting for message state change'); + const message = timeline.locator(`[data-testid="${timestamp}"]`); + await message.waitFor(); + + if (runId >= DISCARD_COUNT) { + deltaList.push(delta); + console.log('run=%d info=%j', runId - DISCARD_COUNT, { delta }); + } else { + console.log('discarded=%d info=%j', runId, { delta }); + } + } + + console.log('stats info=%j', { delta: stats(deltaList, [99, 99.8]) }); +}); diff --git a/ts/test-mock/benchmarks/startup_bench.ts b/ts/test-mock/benchmarks/startup_bench.ts index 6150090ee..c706e13f6 100644 --- a/ts/test-mock/benchmarks/startup_bench.ts +++ b/ts/test-mock/benchmarks/startup_bench.ts @@ -10,127 +10,115 @@ const MESSAGE_BATCH_SIZE = 1000; // messages const ENABLE_RECEIPTS = Boolean(process.env.ENABLE_RECEIPTS); -void (async () => { - const bootstrap = new Bootstrap({ - benchmark: true, - }); - - await bootstrap.init(); +Bootstrap.benchmark(async (bootstrap: Bootstrap): Promise => { await bootstrap.linkAndClose(); - try { - const { server, contacts, phone, desktop } = bootstrap; + const { server, contacts, phone, desktop } = bootstrap; - const messagesPerSec = new Array(); + const messagesPerSec = new Array(); - for (let runId = 0; runId < RUN_COUNT; runId += 1) { - // Generate messages - const messagePromises = new Array>(); - debug('started generating messages'); + for (let runId = 0; runId < RUN_COUNT; runId += 1) { + // Generate messages + const messagePromises = new Array>(); + debug('started generating messages'); - for (let i = 0; i < MESSAGE_BATCH_SIZE; i += 1) { - const contact = contacts[Math.floor(i / 2) % contacts.length]; - const direction = i % 2 ? 'message' : 'reply'; + for (let i = 0; i < MESSAGE_BATCH_SIZE; i += 1) { + const contact = contacts[Math.floor(i / 2) % contacts.length]; + const direction = i % 2 ? 'message' : 'reply'; - const messageTimestamp = bootstrap.getTimestamp(); - - if (direction === 'message') { - messagePromises.push( - contact.encryptText( - desktop, - `Ping from mock server ${i + 1} / ${MESSAGE_BATCH_SIZE}`, - { - timestamp: messageTimestamp, - sealed: true, - } - ) - ); - - if (ENABLE_RECEIPTS) { - messagePromises.push( - phone.encryptSyncRead(desktop, { - timestamp: bootstrap.getTimestamp(), - messages: [ - { - senderUUID: contact.device.uuid, - timestamp: messageTimestamp, - }, - ], - }) - ); - } - continue; - } + const messageTimestamp = bootstrap.getTimestamp(); + if (direction === 'message') { messagePromises.push( - phone.encryptSyncSent( + contact.encryptText( desktop, - `Pong from mock server ${i + 1} / ${MESSAGE_BATCH_SIZE}`, + `Ping from mock server ${i + 1} / ${MESSAGE_BATCH_SIZE}`, { timestamp: messageTimestamp, - destinationUUID: contact.device.uuid, + sealed: true, } ) ); if (ENABLE_RECEIPTS) { messagePromises.push( - contact.encryptReceipt(desktop, { + phone.encryptSyncRead(desktop, { timestamp: bootstrap.getTimestamp(), - messageTimestamps: [messageTimestamp], - type: ReceiptType.Delivery, - }) - ); - messagePromises.push( - contact.encryptReceipt(desktop, { - timestamp: bootstrap.getTimestamp(), - messageTimestamps: [messageTimestamp], - type: ReceiptType.Read, + messages: [ + { + senderUUID: contact.device.uuid, + timestamp: messageTimestamp, + }, + ], }) ); } + continue; } - debug('ended generating messages'); + messagePromises.push( + phone.encryptSyncSent( + desktop, + `Pong from mock server ${i + 1} / ${MESSAGE_BATCH_SIZE}`, + { + timestamp: messageTimestamp, + destinationUUID: contact.device.uuid, + } + ) + ); - const messages = await Promise.all(messagePromises); - - // Open the flood gates - { - debug('got synced, sending messages'); - - // Queue all messages - const queue = async (): Promise => { - await Promise.all( - messages.map(message => { - return server.send(desktop, message); - }) - ); - }; - - const run = async (): Promise => { - const app = await bootstrap.startApp(); - const appLoadedInfo = await app.waitUntilLoaded(); - - console.log('run=%d info=%j', runId, appLoadedInfo); - - messagesPerSec.push(appLoadedInfo.messagesPerSec); - - await app.close(); - }; - - await Promise.all([queue(), run()]); + if (ENABLE_RECEIPTS) { + messagePromises.push( + contact.encryptReceipt(desktop, { + timestamp: bootstrap.getTimestamp(), + messageTimestamps: [messageTimestamp], + type: ReceiptType.Delivery, + }) + ); + messagePromises.push( + contact.encryptReceipt(desktop, { + timestamp: bootstrap.getTimestamp(), + messageTimestamps: [messageTimestamp], + type: ReceiptType.Read, + }) + ); } } - // Compute human-readable statistics - if (messagesPerSec.length !== 0) { - console.log('stats info=%j', { messagesPerSec: stats(messagesPerSec) }); + debug('ended generating messages'); + + const messages = await Promise.all(messagePromises); + + // Open the flood gates + { + debug('got synced, sending messages'); + + // Queue all messages + const queue = async (): Promise => { + await Promise.all( + messages.map(message => { + return server.send(desktop, message); + }) + ); + }; + + const run = async (): Promise => { + const app = await bootstrap.startApp(); + const appLoadedInfo = await app.waitUntilLoaded(); + + console.log('run=%d info=%j', runId, appLoadedInfo); + + messagesPerSec.push(appLoadedInfo.messagesPerSec); + + await app.close(); + }; + + await Promise.all([queue(), run()]); } - } catch (error) { - await bootstrap.saveLogs(); - throw error; - } finally { - await bootstrap.teardown(); } -})(); + + // Compute human-readable statistics + if (messagesPerSec.length !== 0) { + console.log('stats info=%j', { messagesPerSec: stats(messagesPerSec) }); + } +}); diff --git a/ts/test-mock/benchmarks/storage_sync_bench.ts b/ts/test-mock/benchmarks/storage_sync_bench.ts index 6006a30e2..7e55b4130 100644 --- a/ts/test-mock/benchmarks/storage_sync_bench.ts +++ b/ts/test-mock/benchmarks/storage_sync_bench.ts @@ -5,22 +5,16 @@ import type { PrimaryDevice } from '@signalapp/mock-server'; import { StorageState } from '@signalapp/mock-server'; -import type { App } from './fixtures'; import { Bootstrap } from './fixtures'; const CONTACT_COUNT = 1000; -void (async () => { +Bootstrap.benchmark(async (bootstrap: Bootstrap): Promise => { const contactNames = new Array(); for (let i = 0; i < CONTACT_COUNT; i += 1) { contactNames.push(`Contact ${i}`); } - const bootstrap = new Bootstrap({ - benchmark: true, - }); - - await bootstrap.init(); const { phone, server } = bootstrap; let state = StorageState.getEmpty(); @@ -50,25 +44,16 @@ void (async () => { await phone.setStorageState(state); const start = Date.now(); - let app: App | undefined; - try { - app = await bootstrap.link(); - const window = await app.getWindow(); + const app = await bootstrap.link(); + const window = await app.getWindow(); - const leftPane = window.locator('.left-pane-wrapper'); + const leftPane = window.locator('.left-pane-wrapper'); - const item = leftPane.locator( - `[data-testid="${lastContact?.toContact().uuid}"]` - ); - await item.waitFor(); + const item = leftPane.locator( + `[data-testid="${lastContact?.toContact().uuid}"]` + ); + await item.waitFor(); - const duration = Date.now() - start; - console.log(`Took: ${(duration / 1000).toFixed(2)} seconds`); - } catch (error) { - await bootstrap.saveLogs(app); - throw error; - } finally { - await app?.close(); - await bootstrap.teardown(); - } -})(); + const duration = Date.now() - start; + console.log(`Took: ${(duration / 1000).toFixed(2)} seconds`); +}); diff --git a/ts/test-mock/bootstrap.ts b/ts/test-mock/bootstrap.ts index db9dfc605..5c9faca4a 100644 --- a/ts/test-mock/bootstrap.ts +++ b/ts/test-mock/bootstrap.ts @@ -6,11 +6,13 @@ import fs from 'fs/promises'; import path from 'path'; import os from 'os'; import createDebug from 'debug'; +import pTimeout from 'p-timeout'; import type { Device, PrimaryDevice } from '@signalapp/mock-server'; import { Server, UUIDKind, loadCertificates } from '@signalapp/mock-server'; import { MAX_READ_KEYS as MAX_STORAGE_READ_KEYS } from '../services/storageConstants'; import * as durations from '../util/durations'; +import { drop } from '../util/drop'; import { App } from './playwright'; export { App }; @@ -111,6 +113,7 @@ export class Bootstrap { private privDesktop?: Device; private storagePath?: string; private timestamp: number = Date.now() - durations.WEEK; + private lastApp?: App; constructor(options: BootstrapOptions = {}) { this.server = new Server({ @@ -178,6 +181,13 @@ export class Bootstrap { debug('setting storage path=%j', this.storagePath); } + public static benchmark( + fn: (bootstrap: Bootstrap) => Promise, + timeout = 5 * durations.MINUTE + ): void { + drop(Bootstrap.runBenchmark(fn, timeout)); + } + public get logsDir(): string { assert( this.storagePath !== undefined, @@ -191,10 +201,13 @@ export class Bootstrap { debug('tearing down'); await Promise.race([ - this.storagePath - ? fs.rm(this.storagePath, { recursive: true }) - : Promise.resolve(), - this.server.close(), + Promise.all([ + this.storagePath + ? fs.rm(this.storagePath, { recursive: true }) + : Promise.resolve(), + this.server.close(), + this.lastApp?.close(), + ]), new Promise(resolve => setTimeout(resolve, CLOSE_TIMEOUT).unref()), ]); } @@ -260,6 +273,13 @@ export class Bootstrap { await app.start(); + this.lastApp = app; + app.on('close', () => { + if (this.lastApp === app) { + this.lastApp = undefined; + } + }); + return app; } @@ -269,7 +289,7 @@ export class Bootstrap { return result; } - public async saveLogs(app?: App): Promise { + public async saveLogs(app: App | undefined = this.lastApp): Promise { const { ARTIFACTS_DIR } = process.env; if (!ARTIFACTS_DIR) { // eslint-disable-next-line no-console @@ -334,6 +354,26 @@ export class Bootstrap { // Private // + private static async runBenchmark( + fn: (bootstrap: Bootstrap) => Promise, + timeout: number + ): Promise { + const bootstrap = new Bootstrap({ + benchmark: true, + }); + + await bootstrap.init(); + + try { + await pTimeout(fn(bootstrap), timeout); + } catch (error) { + await bootstrap.saveLogs(); + throw error; + } finally { + await bootstrap.teardown(); + } + } + private async generateConfig(port: number): Promise { const url = `https://127.0.0.1:${port}`; return JSON.stringify({ diff --git a/ts/test-mock/playwright.ts b/ts/test-mock/playwright.ts index cfd999d43..9778c5a36 100644 --- a/ts/test-mock/playwright.ts +++ b/ts/test-mock/playwright.ts @@ -3,6 +3,7 @@ import type { ElectronApplication, Page } from 'playwright'; import { _electron as electron } from 'playwright'; +import { EventEmitter } from 'events'; import type { IPCRequest as ChallengeRequestType, @@ -39,10 +40,12 @@ export type AppOptionsType = Readonly<{ config: string; }>; -export class App { +export class App extends EventEmitter { private privApp: ElectronApplication | undefined; - constructor(private readonly options: AppOptionsType) {} + constructor(private readonly options: AppOptionsType) { + super(); + } public async start(): Promise { this.privApp = await electron.launch({ @@ -54,6 +57,8 @@ export class App { }, locale: 'en', }); + + this.privApp.on('close', () => this.emit('close')); } public async waitForProvisionURL(): Promise { @@ -111,6 +116,29 @@ export class App { return this.app.firstWindow(); } + // EventEmitter types + + public override on(type: 'close', callback: () => void): this; + + public override on( + type: string | symbol, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + listener: (...args: Array) => void + ): this { + return super.on(type, listener); + } + + public override emit(type: 'close'): boolean; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public override emit(type: string | symbol, ...args: Array): boolean { + return super.emit(type, ...args); + } + + // + // Private + // + private async waitForEvent(event: string): Promise { const window = await this.getWindow(); diff --git a/yarn.lock b/yarn.lock index c3254726f..ae1edc93c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14546,17 +14546,17 @@ pkg-dir@^5.0.0: dependencies: find-up "^5.0.0" -playwright-core@1.30.0: - version "1.30.0" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.30.0.tgz#de987cea2e86669e3b85732d230c277771873285" - integrity sha512-7AnRmTCf+GVYhHbLJsGUtskWTE33SwMZkybJ0v6rqR1boxq2x36U7p1vDRV7HO2IwTZgmycracLxPEJI49wu4g== +playwright-core@1.31.2: + version "1.31.2" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.31.2.tgz#debf4b215d14cb619adb7e511c164d068075b2ed" + integrity sha512-a1dFgCNQw4vCsG7bnojZjDnPewZcw7tZUNFN0ZkcLYKj+mPmXvg4MpaaKZ5SgqPsOmqIf2YsVRkgqiRDxD+fDQ== -playwright@1.30.0: - version "1.30.0" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.30.0.tgz#b1d7be2d45d97fbb59f829f36f521f12010fe072" - integrity sha512-ENbW5o75HYB3YhnMTKJLTErIBExrSlX2ZZ1C/FzmHjUYIfxj/UnI+DWpQr992m+OQVSg0rCExAOlRwB+x+yyIg== +playwright@1.31.2: + version "1.31.2" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.31.2.tgz#4252280586c596746122cd1fdf9f8ff6a63fa852" + integrity sha512-jpC47n2PKQNtzB7clmBuWh6ftBRS/Bt5EGLigJ9k2QAKcNeYXZkEaDH5gmvb6+AbcE0DO6GnXdbl9ogG6Eh+og== dependencies: - playwright-core "1.30.0" + playwright-core "1.31.2" plist@^3.0.1, plist@^3.0.4: version "3.0.5"