From ec8d6a7359e8b50a475c600dec14d13a983a9bd1 Mon Sep 17 00:00:00 2001 From: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com> Date: Thu, 7 Aug 2025 10:33:21 -0700 Subject: [PATCH] Faster CI runs --- .github/workflows/benchmark.yml | 183 ++++++------------ .github/workflows/ci.yml | 19 +- package.json | 4 +- pnpm-lock.yaml | 28 +-- preload.wrapper.ts | 5 + ts/ConversationController.ts | 30 ++- ts/background.ts | 1 + ts/components/ConversationList.tsx | 25 ++- .../conversation/MessageTextRenderer.tsx | 8 +- ts/environment.ts | 2 +- ts/jobs/helpers/commonShouldJobContinue.ts | 4 +- ts/scripts/generate-preload-cache.ts | 1 + ts/scripts/mocha-separator.ts | 27 +++ ts/services/backups/index.ts | 1 + ts/services/storage.ts | 13 +- ts/state/ducks/conversations.ts | 9 +- ts/state/smart/ChatsTab.tsx | 14 -- ts/test-mock/bootstrap.ts | 27 ++- ts/test-mock/messaging/readSync_test.ts | 4 +- ts/test-mock/messaging/relink_test.ts | 2 + ts/test-mock/messaging/unprocessed_test.ts | 4 +- ts/test-mock/playwright.ts | 1 + ts/test-mock/storage/drop_test.ts | 10 +- ts/test-mock/storage/pin_unpin_test.ts | 7 +- ts/test-mock/storage/sticker_test.ts | 15 +- ts/textsecure/AccountManager.ts | 5 +- 26 files changed, 242 insertions(+), 207 deletions(-) create mode 100644 ts/scripts/mocha-separator.ts diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 526672546b6..0d09c2bb027 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -14,6 +14,52 @@ on: jobs: linux: + strategy: + matrix: + metric: + - startup + - send + - groupSend + - largeGroupSendWithBlocks + - largeGroupSend + - convoOpen + - callHistorySearch + - backup + include: + - metric: startup + script: ts/test-mock/benchmarks/startup_bench.js + runCount: 10 + - metric: send + script: ts/test-mock/benchmarks/send_bench.js + runCount: 100 + - metric: groupSend + script: ts/test-mock/benchmarks/group_send_bench.js + runCount: 100 + conversationSize: 500 + - metric: largeGroupSendWithBlocks + script: ts/test-mock/benchmarks/group_send_bench.js + runCount: 50 + conversationSize: 500 + groupSize: 500 + contactCount: 500 + blockedCount: 10 + discardCount: 2 + - metric: largeGroupSend + script: ts/test-mock/benchmarks/group_send_bench.js + runCount: 20 + conversationSize: 50 + groupSize: 500 + contactCount: 500 + discardCount: 2 + - metric: convoOpen + script: ts/test-mock/benchmarks/group_send_bench.js + runCount: 100 + - metric: callHistorySearch + script: ts/test-mock/benchmarks/call_history_search_bench.js + runCount: 100 + - metric: backup + script: ts/test-mock/benchmarks/backup_bench.js + runs-on: ubuntu-22.04-8-cores if: ${{ github.repository == 'signalapp/Signal-Desktop-Private' && (!github.event.schedule || github.ref == 'refs/heads/main') }} timeout-minutes: 30 @@ -72,132 +118,22 @@ jobs: run: | echo "MAX_CYCLES=2" >> "$GITHUB_ENV" - - name: Run startup benchmarks + - name: Run ${{ matrix.metric }} run: | set -o pipefail - xvfb-run --auto-servernum node ts/test-mock/benchmarks/startup_bench.js | - tee benchmark-startup.log + xvfb-run --auto-servernum node ${{ matrix.script }} | tee benchmark.log timeout-minutes: 10 env: NODE_ENV: production - RUN_COUNT: 10 ELECTRON_ENABLE_STACK_DUMPING: on DEBUG: 'mock:benchmarks' - ARTIFACTS_DIR: artifacts/startup - - - name: Run send benchmarks - run: | - set -o pipefail - rm -rf /tmp/mock - xvfb-run --auto-servernum node ts/test-mock/benchmarks/send_bench.js | - tee benchmark-send.log - timeout-minutes: 10 - env: - NODE_ENV: production - RUN_COUNT: 100 - ELECTRON_ENABLE_STACK_DUMPING: on - # DEBUG: 'mock:benchmarks' - ARTIFACTS_DIR: artifacts/send - - - name: Run group send benchmarks - run: | - set -o pipefail - rm -rf /tmp/mock - xvfb-run --auto-servernum node \ - ts/test-mock/benchmarks/group_send_bench.js | \ - tee benchmark-group-send.log - timeout-minutes: 10 - env: - NODE_ENV: production - RUN_COUNT: 100 - CONVERSATION_SIZE: 500 - ELECTRON_ENABLE_STACK_DUMPING: on - # DEBUG: 'mock:benchmarks' - ARTIFACTS_DIR: artifacts/group-send - - - name: Run large group send benchmarks with blocks - run: | - set -o pipefail - rm -rf /tmp/mock - xvfb-run --auto-servernum node \ - ts/test-mock/benchmarks/group_send_bench.js | \ - tee benchmark-large-group-send-with-blocks.log - timeout-minutes: 10 - env: - NODE_ENV: production - GROUP_SIZE: 500 - CONTACT_COUNT: 500 - BLOCKED_COUNT: 10 - DISCARD_COUNT: 2 - RUN_COUNT: 50 - CONVERSATION_SIZE: 500 - ELECTRON_ENABLE_STACK_DUMPING: on - # DEBUG: 'mock:benchmarks' - ARTIFACTS_DIR: artifacts/group-send - - - name: Run large group send benchmarks with delivery receipts - run: | - set -o pipefail - rm -rf /tmp/mock - xvfb-run --auto-servernum node \ - ts/test-mock/benchmarks/group_send_bench.js | \ - tee benchmark-large-group-send.log - timeout-minutes: 10 - env: - NODE_ENV: production - GROUP_SIZE: 500 - CONTACT_COUNT: 500 - GROUP_DELIVERY_RECEIPTS: 500 - DISCARD_COUNT: 2 - RUN_COUNT: 20 - CONVERSATION_SIZE: 50 - ELECTRON_ENABLE_STACK_DUMPING: on - # DEBUG: 'mock:benchmarks' - ARTIFACTS_DIR: artifacts/large-group-send - - - name: Run conversation open benchmarks - run: | - set -o pipefail - rm -rf /tmp/mock - xvfb-run --auto-servernum node \ - ts/test-mock/benchmarks/convo_open_bench.js | \ - tee benchmark-convo-open.log - timeout-minutes: 10 - env: - NODE_ENV: production - RUN_COUNT: 100 - ELECTRON_ENABLE_STACK_DUMPING: on - # DEBUG: 'mock:benchmarks' - ARTIFACTS_DIR: artifacts/convo-open - - - name: Run call history search benchmarks - run: | - set -o pipefail - rm -rf /tmp/mock - xvfb-run --auto-servernum node \ - ts/test-mock/benchmarks/call_history_search_bench.js | \ - tee benchmark-call-history-search.log - timeout-minutes: 10 - env: - NODE_ENV: production - RUN_COUNT: 100 - ELECTRON_ENABLE_STACK_DUMPING: on - # DEBUG: 'mock:benchmarks' - ARTIFACTS_DIR: artifacts/call-history-search - - - name: Run backup benchmarks - run: | - set -o pipefail - rm -rf /tmp/mock - xvfb-run --auto-servernum node \ - ts/test-mock/benchmarks/backup_bench.js | \ - tee benchmark-backup.log - timeout-minutes: 10 - env: - NODE_ENV: production - ELECTRON_ENABLE_STACK_DUMPING: on - # DEBUG: 'mock:benchmarks' - ARTIFACTS_DIR: artifacts/backup-bench + ARTIFACTS_DIR: artifacts/${{ matrix.metric }} + GROUP_SIZE: ${{ matrix.groupSize }} + CONTACT_COUNT: ${{ matrix.contactCount }} + BLOCKED_COUNT: ${{ matrix.blockedCount }} + DISCARD_COUNT: ${{ matrix.discardCount }} + RUN_COUNT: ${{ matrix.runCount }} + CONVERSATION_SIZE: ${{ matrix.conversationSize }} - name: Upload benchmark logs on failure if: failure() @@ -222,13 +158,6 @@ jobs: - name: Publish to DataDog working-directory: benchmark-results run: | - node ./bin/publish.js ../benchmark-startup.log desktop.ci.performance.startup - node ./bin/publish.js ../benchmark-send.log desktop.ci.performance.send - node ./bin/publish.js ../benchmark-group-send.log desktop.ci.performance.groupSend - node ./bin/publish.js ../benchmark-large-group-send-with-blocks.log desktop.ci.performance.largeGroupSendWithBlocks - node ./bin/publish.js ../benchmark-large-group-send.log desktop.ci.performance.largeGroupSend - node ./bin/publish.js ../benchmark-convo-open.log desktop.ci.performance.convoOpen - node ./bin/publish.js ../benchmark-call-history-search.log desktop.ci.performance.callHistorySearch - node ./bin/publish.js ../benchmark-backup.log desktop.ci.performance.backup + node ./bin/publish.js ../benchmark.log destop.ci.performance.${{ matrix.metric }} env: DD_API_KEY: ${{ secrets.DATADOG_API_KEY }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 12fd4eff547..a8d2b594ea7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -370,6 +370,11 @@ jobs: mock-tests: needs: lint + + strategy: + matrix: + workerIndex: [0, 1, 2, 3] + runs-on: ubuntu-22.04-8-cores if: ${{ github.repository == 'signalapp/Signal-Desktop-Private' }} timeout-minutes: 30 @@ -429,12 +434,24 @@ jobs: run: | set -o pipefail xvfb-run --auto-servernum pnpm run test-mock + timeout-minutes: 15 + env: + NODE_ENV: production + DEBUG: mock:test:* + ARTIFACTS_DIR: artifacts/mock + WORKER_INDEX: ${{ matrix.workerIndex }} + WORKER_COUNT: 4 + + - name: Run docker mock server tests + if: ${{ matrix.workerIndex == 0 }} + run: | + set -o pipefail xvfb-run --auto-servernum pnpm run test-mock-docker timeout-minutes: 15 env: NODE_ENV: production DEBUG: mock:test:* - ARTIFACTS_DIR: artifacts/startup + ARTIFACTS_DIR: artifacts/mock-docker - name: Upload mock server test logs on failure if: failure() diff --git a/package.json b/package.json index 3813df4754e..dece504362e 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "test-electron": "node ts/scripts/test-electron.js", "test-release": "node ts/scripts/test-release.js", "test-node": "cross-env LANG=en-us electron-mocha --timeout 10000 --main test/fix-linux-gtk.js --file test/setup-test-node.js --recursive ts/test-node", - "test-mock": "mocha --require ts/test-mock/setup-ci.js ts/test-mock/**/*_test.js", + "test-mock": "node ts/scripts/mocha-separator.js --require ts/test-mock/setup-ci.js -- ts/test-mock/**/*_test.js", "test-mock-docker": "mocha --require ts/test-mock/setup-ci.js ts/test-mock/**/*_test.docker.js", "test-eslint": "mocha .eslint/rules/**/*.test.js --ignore-leaks", "test-lint-intl": "ts-node ./build/intl-linter/linter.ts --test", @@ -336,7 +336,7 @@ "npm-run-all": "4.1.5", "p-limit": "3.1.0", "pixelmatch": "5.3.0", - "playwright": "1.45.0", + "playwright": "1.54.2", "pngjs": "7.0.0", "postcss": "8.5.3", "postcss-loader": "8.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7b4948e8d3..0dbacbea662 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -745,8 +745,8 @@ importers: specifier: 5.3.0 version: 5.3.0 playwright: - specifier: 1.45.0 - version: 1.45.0 + specifier: 1.54.2 + version: 1.54.2 pngjs: specifier: 7.0.0 version: 7.0.0 @@ -8578,18 +8578,18 @@ packages: resolution: {integrity: sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==} engines: {node: '>=14.16'} - playwright-core@1.45.0: - resolution: {integrity: sha512-lZmHlFQ0VYSpAs43dRq1/nJ9G/6SiTI7VPqidld9TDefL9tX87bTKExWZZUF5PeRyqtXqd8fQi2qmfIedkwsNQ==} - engines: {node: '>=18'} - hasBin: true - playwright-core@1.50.1: resolution: {integrity: sha512-ra9fsNWayuYumt+NiM069M6OkcRb1FZSK8bgi66AtpFoWkg2+y0bJSNmkFrWhMbEBbVKC/EruAHH3g0zmtwGmQ==} engines: {node: '>=18'} hasBin: true - playwright@1.45.0: - resolution: {integrity: sha512-4z3ac3plDfYzGB6r0Q3LF8POPR20Z8D0aXcxbJvmfMgSSq1hkcgvFRXJk9rUq5H/MJ0Ktal869hhOdI/zUTeLA==} + playwright-core@1.54.2: + resolution: {integrity: sha512-n5r4HFbMmWsB4twG7tJLDN9gmBUeSPcsBZiWSE4DnYz9mJMAFqr2ID7+eGC9kpEnxExJ1epttwR59LEWCk8mtA==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.54.2: + resolution: {integrity: sha512-Hu/BMoA1NAdRUuulyvQC0pEqZ4vQbGfn8f7wPXcnqQmM+zct9UliKxsIkLNmz/ku7LElUNqmaiv1TG/aL5ACsw==} engines: {node: '>=18'} hasBin: true @@ -14312,7 +14312,7 @@ snapshots: jest-serializer-html: 7.1.0 jest-watch-typeahead: 2.2.2(jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@swc/core@1.10.16(@swc/helpers@0.5.15))(@types/node@20.17.6)(typescript@5.6.3))) nyc: 15.1.0 - playwright: 1.45.0 + playwright: 1.54.2 storybook: 8.4.4(bufferutil@4.0.9)(prettier@3.3.3)(utf-8-validate@5.0.10) transitivePeerDependencies: - '@swc/helpers' @@ -20302,13 +20302,13 @@ snapshots: dependencies: find-up: 6.3.0 - playwright-core@1.45.0: {} - playwright-core@1.50.1: {} - playwright@1.45.0: + playwright-core@1.54.2: {} + + playwright@1.54.2: dependencies: - playwright-core: 1.45.0 + playwright-core: 1.54.2 optionalDependencies: fsevents: 2.3.2 diff --git a/preload.wrapper.ts b/preload.wrapper.ts index ebefa6cac3c..00528618237 100644 --- a/preload.wrapper.ts +++ b/preload.wrapper.ts @@ -61,6 +61,11 @@ const fn = script.runInThisContext({ // See `ts/scripts/generate-preload-cache.ts` if (process.env.GENERATE_PRELOAD_CACHE) { + // Use hottest cache possible in CI + if (process.env.CI) { + fn(require, __dirname); + window.startApp(); + } writeFileSync(cachePath, script.createCachedData()); ipcRenderer.send('shutdown'); } else { diff --git a/ts/ConversationController.ts b/ts/ConversationController.ts index e5f6cf855e7..3df134a8796 100644 --- a/ts/ConversationController.ts +++ b/ts/ConversationController.ts @@ -351,6 +351,16 @@ export class ConversationController { if (isGroupV1(conversation.attributes)) { maybeDeriveGroupV2Id(conversation); } + + // If conversation does not have pre-existing storageID and is not our + // own (that we create on link), it might need to be uploaded to storage + // service. + if (conversation.attributes.storageID == null) { + StorageService.storageServiceUploadJob({ + reason: 'new conversation', + }); + } + await saveConversation(conversation.attributes); } catch (error) { log.error( @@ -1221,11 +1231,15 @@ export class ConversationController { log.warn(`${logId}: Update messages table`); await migrateConversationMessages(obsoleteId, currentId); - log.warn(`${logId}: Emit refreshConversation event to close old/open new`); - window.Whisper.events.trigger('refreshConversation', { - newId: currentId, - oldId: obsoleteId, - }); + if ( + window.reduxStore.getState().conversations.selectedConversationId === + obsoleteId + ) { + log.warn(`${logId}: opening new conversation`); + window.reduxActions.conversations.showConversation({ + conversationId: currentId, + }); + } log.warn( `${logId}: Eliminate old conversation from ConversationController lookups` @@ -1236,8 +1250,10 @@ export class ConversationController { current.captureChange('combineConversations'); drop(current.updateLastMessage()); - const state = window.reduxStore.getState(); - if (state.conversations.selectedConversationId === current.id) { + if ( + window.reduxStore.getState().conversations.selectedConversationId === + current.id + ) { // TODO: DESKTOP-4807 drop(current.loadNewestMessages(undefined, undefined)); } diff --git a/ts/background.ts b/ts/background.ts index a5f98e8a51b..f198cb09672 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -1457,6 +1457,7 @@ export async function startApp(): Promise { await StorageService.runStorageServiceSyncJob({ reason: andSync, }); + StorageService.runStorageServiceSyncJob.flush(); } } diff --git a/ts/components/ConversationList.tsx b/ts/components/ConversationList.tsx index 01d0b1bb8a6..3caa6e14537 100644 --- a/ts/components/ConversationList.tsx +++ b/ts/components/ConversationList.tsx @@ -318,14 +318,15 @@ export function ConversationList({ ); const renderRow: ListRowRenderer = useCallback( - ({ key, index, style }) => { + ({ key: providedKey, index, style }) => { const row = getRow(index); if (!row) { assertDev(false, `Expected a row at index ${index}`); - return
; + return
; } let result: ReactNode; + let key: string; switch (row.type) { case RowType.ArchiveButton: result = ( @@ -344,9 +345,11 @@ export function ConversationList({ ); + key = 'archive'; break; case RowType.Blank: result = undefined; + key = `blank:${providedKey}`; break; case RowType.Contact: { const { isClickable = true, hasContextMenu = false } = row; @@ -368,6 +371,7 @@ export function ConversationList({ onRemove={isClickable ? removeConversation : undefined} /> ); + key = `contact:${row.contact.id}`; break; } case RowType.ContactCheckbox: @@ -382,6 +386,7 @@ export function ConversationList({ theme={theme} /> ); + key = `contact-checkbox:${row.contact.id}`; break; case RowType.ClearFilterButton: result = ( @@ -400,6 +405,7 @@ export function ConversationList({
); + key = 'clear-filter'; break; case RowType.PhoneNumberCheckbox: result = ( @@ -419,6 +425,7 @@ export function ConversationList({ theme={theme} /> ); + key = `phone-number-checkbox:${row.phoneNumber.e164}`; break; case RowType.UsernameCheckbox: result = ( @@ -438,6 +445,7 @@ export function ConversationList({ theme={theme} /> ); + key = `username-checkbox:${row.username}`; break; case RowType.GenericCheckbox: result = ( @@ -453,6 +461,7 @@ export function ConversationList({ clickable /> ); + key = `generic-checkbox:${providedKey}`; break; case RowType.Conversation: { const itemProps = pick(row.conversation, [ @@ -486,6 +495,7 @@ export function ConversationList({ 'serviceId', ]); const { badges, title, unreadCount, lastMessage } = itemProps; + key = `conversation:${itemProps.id}`; result = ( ); + key = 'create-new-group'; break; case RowType.FindByUsername: result = ( @@ -523,6 +534,7 @@ export function ConversationList({ onClick={showFindByUsername} /> ); + key = 'find-by-username'; break; case RowType.FindByPhoneNumber: result = ( @@ -532,6 +544,7 @@ export function ConversationList({ onClick={showFindByPhoneNumber} /> ); + key = 'find-by-phonenumber'; break; case RowType.Header: { const headerText = row.getHeaderText(i18n); @@ -543,16 +556,20 @@ export function ConversationList({ {headerText}
); + key = `header:${providedKey}`; break; } case RowType.MessageSearchResult: result = <>{renderMessageSearchResult?.(row.messageId)}; + key = `message-search-result:${row.messageId}`; break; case RowType.SearchResultsLoadingFakeHeader: result = ; + key = `loading-header:${providedKey}`; break; case RowType.SearchResultsLoadingFakeRow: result = ; + key = `loading-row:${providedKey}`; break; case RowType.SelectSingleGroup: result = ( @@ -562,6 +579,7 @@ export function ConversationList({ onSelectGroup={onSelectConversation} /> ); + key = 'select-single-group'; break; case RowType.StartNewConversation: result = ( @@ -577,6 +595,7 @@ export function ConversationList({ showConversation={showConversation} /> ); + key = 'start-new-conversation'; break; case RowType.UsernameSearchResult: result = ( @@ -592,6 +611,7 @@ export function ConversationList({ showConversation={showConversation} /> ); + key = `username-search-result:${row.username}`; break; case RowType.EmptyResults: result = ( @@ -599,6 +619,7 @@ export function ConversationList({ {row.message} ); + key = 'empty-results'; break; default: throw missingCaseError(row); diff --git a/ts/components/conversation/MessageTextRenderer.tsx b/ts/components/conversation/MessageTextRenderer.tsx index 0a4ba853df3..4356c0cc398 100644 --- a/ts/components/conversation/MessageTextRenderer.tsx +++ b/ts/components/conversation/MessageTextRenderer.tsx @@ -272,7 +272,13 @@ function renderNode({ !isLinkSneaky(node.url) ) { return ( - + {content} ); diff --git a/ts/environment.ts b/ts/environment.ts index 79131e0e78b..ac7216031dd 100644 --- a/ts/environment.ts +++ b/ts/environment.ts @@ -48,7 +48,7 @@ export const parseEnvironment = makeEnumParser( export const isTestEnvironment = (env: Environment): boolean => env === Environment.Test; -const isMockEnvironment = (): boolean => { +export const isMockEnvironment = (): boolean => { if (isMockTestEnvironment == null) { log.error('Mock test environment not set'); } diff --git a/ts/jobs/helpers/commonShouldJobContinue.ts b/ts/jobs/helpers/commonShouldJobContinue.ts index ae6a4caa951..5552d0c0d6a 100644 --- a/ts/jobs/helpers/commonShouldJobContinue.ts +++ b/ts/jobs/helpers/commonShouldJobContinue.ts @@ -24,7 +24,9 @@ export async function commonShouldJobContinue({ } try { - await waitForOnline({ timeout: timeRemaining }); + if (isDeviceLinked()) { + await waitForOnline({ timeout: timeRemaining }); + } } catch (err: unknown) { log.info("didn't come online in time, giving up"); return false; diff --git a/ts/scripts/generate-preload-cache.ts b/ts/scripts/generate-preload-cache.ts index 365cde35687..f3c8fede731 100644 --- a/ts/scripts/generate-preload-cache.ts +++ b/ts/scripts/generate-preload-cache.ts @@ -39,6 +39,7 @@ async function main(): Promise { WAYLAND_DISPLAY: process.env.WAYLAND_DISPLAY, XAUTHORITY: process.env.XAUTHORITY, + CI: process.env.CI ? 'on' : undefined, GENERATE_PRELOAD_CACHE: 'on', SIGNAL_CI_CONFIG: JSON.stringify({ storagePath, diff --git a/ts/scripts/mocha-separator.ts b/ts/scripts/mocha-separator.ts new file mode 100644 index 00000000000..ed65258aa86 --- /dev/null +++ b/ts/scripts/mocha-separator.ts @@ -0,0 +1,27 @@ +// Copyright 2025 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { spawnSync } from 'node:child_process'; +import { join } from 'node:path'; + +const MOCHA = join(__dirname, '..', '..', 'node_modules', '.bin', 'mocha'); +const WORKER_COUNT = parseInt(process.env.WORKER_COUNT || '1', 10); +const WORKER_INDEX = parseInt(process.env.WORKER_INDEX || '0', 10); + +const separator = process.argv.indexOf('--'); +if (separator === -1) { + throw new Error('Expected `--` separator between options and files'); +} + +const flags = process.argv.slice(2, separator); +const files = process.argv.slice(separator + 1); + +const filteredFiles = files.filter((_file, index) => { + return index % WORKER_COUNT === WORKER_INDEX; +}); + +console.log(`Running on ${filteredFiles.length}/${files.length} of files`); + +spawnSync(MOCHA, [...flags, ...filteredFiles], { + stdio: 'inherit', +}); diff --git a/ts/services/backups/index.ts b/ts/services/backups/index.ts index a16e1aa8b82..48819709c13 100644 --- a/ts/services/backups/index.ts +++ b/ts/services/backups/index.ts @@ -1034,6 +1034,7 @@ export class BackupsService { window.Whisper.events.once('storageService:syncComplete', resolve); runStorageServiceSyncJob({ reason }); + runStorageServiceSyncJob.flush(); await storageService; } diff --git a/ts/services/storage.ts b/ts/services/storage.ts index 3dff3a85587..f0db9865496 100644 --- a/ts/services/storage.ts +++ b/ts/services/storage.ts @@ -82,7 +82,9 @@ import { getRoomIdFromRootKeyString, } from '../util/callLinksRingrtc'; import { fromPniUuidBytesOrUntaggedString } from '../util/ServiceId'; +import { isDone as isRegistrationDone } from '../util/registration'; import { callLinkRefreshJobQueue } from '../jobs/callLinkRefreshJobQueue'; +import { isMockEnvironment } from '../environment'; const log = createLogger('storage'); @@ -2083,7 +2085,6 @@ async function upload({ if (!window.storage.get('storageKey')) { // requesting new keys runs the sync job which will detect the conflict // and re-run the upload job once we're merged and up-to-date. - log.info(`${logId}: no storageKey, requesting new keys`); backOff.reset(); if (window.ConversationController.areWePrimaryDevice()) { @@ -2091,6 +2092,12 @@ async function upload({ return; } + if (!isRegistrationDone()) { + log.warn(`${logId}: no storageKey, unlinked`); + return; + } + + log.info(`${logId}: no storageKey, requesting new keys`); await singleProtoJobQueue.add(MessageSender.getRequestKeySyncMessage()); return; @@ -2267,7 +2274,7 @@ export const storageServiceUploadJob = debounce( `upload v${window.storage.get('manifestVersion')}` ); }, - 500 + isMockEnvironment() ? 0 : 500 ); export const runStorageServiceSyncJob = debounce( @@ -2289,7 +2296,7 @@ export const runStorageServiceSyncJob = debounce( ) ); }, - 500 + isMockEnvironment() ? 0 : 500 ); export const addPendingDelete = (item: ExtendedStorageID): void => { diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index 9e0f67af474..167b93a612d 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -4879,14 +4879,17 @@ function onConversationClosed( ): ThunkAction { return async dispatch => { const conversation = window.ConversationController.get(conversationId); + // Conversation was removed due to the merge if (!conversation) { - throw new Error('onConversationClosed: Conversation not found'); + log.warn( + `onConversationClosed: Conversation ${conversationId} not found` + ); } - const logId = `onConversationClosed/${conversation.idForLogging()}`; + const logId = `onConversationClosed/${conversation?.idForLogging() ?? conversationId}`; log.info(`${logId}: unloading due to ${reason}`); - if (conversation.get('draftChanged')) { + if (conversation?.get('draftChanged')) { if (conversation.hasDraft()) { log.info(`${logId}: new draft info needs update`); const now = Date.now(); diff --git a/ts/state/smart/ChatsTab.tsx b/ts/state/smart/ChatsTab.tsx index c190b0a1066..ed583e43f53 100644 --- a/ts/state/smart/ChatsTab.tsx +++ b/ts/state/smart/ChatsTab.tsx @@ -102,18 +102,6 @@ export const SmartChatsTab = memo(function SmartChatsTab() { }, [prevConversationId, selectedConversationId]); useEffect(() => { - function refreshConversation({ - newId, - oldId, - }: { - newId: string; - oldId: string; - }) { - if (prevConversationId === oldId) { - showConversation({ conversationId: newId }); - } - } - // Close current opened conversation to reload the group information once // linked. function unload() { @@ -128,12 +116,10 @@ export const SmartChatsTab = memo(function SmartChatsTab() { } window.Whisper.events.on('pack-install-failed', packInstallFailed); - window.Whisper.events.on('refreshConversation', refreshConversation); window.Whisper.events.on('setupAsNewDevice', unload); return () => { window.Whisper.events.off('pack-install-failed', packInstallFailed); - window.Whisper.events.off('refreshConversation', refreshConversation); window.Whisper.events.off('setupAsNewDevice', unload); }; }, [onConversationClosed, prevConversationId, showConversation, showToast]); diff --git a/ts/test-mock/bootstrap.ts b/ts/test-mock/bootstrap.ts index 41361b14cdf..1e5182d37ff 100644 --- a/ts/test-mock/bootstrap.ts +++ b/ts/test-mock/bootstrap.ts @@ -400,17 +400,22 @@ export class Bootstrap { await app.stageLocalBackupForImport(localBackup); } - debug('looking for QR code or relink button'); - const qrCode = window.locator( - '.module-InstallScreenQrCodeNotScannedStep__qr-code__code' + let gotProvisionURL = false; + + drop( + (async () => { + try { + const relinkButton = window.locator('.LeftPaneDialog__icon--relink'); + await relinkButton.waitFor(); + if (gotProvisionURL) { + return; + } + await relinkButton.click(); + } catch { + // Ignore, provision will fail if QR code was never generated + } + })() ); - const relinkButton = window.locator('.LeftPaneDialog__icon--relink'); - await qrCode.or(relinkButton).waitFor(); - if (await relinkButton.isVisible()) { - debug('unlinked, clicking left pane button'); - await relinkButton.click(); - await qrCode.waitFor(); - } debug('waiting for provision'); const provision = await this.server.waitForProvision(); @@ -418,6 +423,8 @@ export class Bootstrap { debug('waiting for provision URL'); const provisionURL = await app.waitForProvisionURL(); + gotProvisionURL = true; + debug('completing provision'); this.#privDesktop = await provision.complete({ provisionURL, diff --git a/ts/test-mock/messaging/readSync_test.ts b/ts/test-mock/messaging/readSync_test.ts index 1056243fd2c..20ed985abfc 100644 --- a/ts/test-mock/messaging/readSync_test.ts +++ b/ts/test-mock/messaging/readSync_test.ts @@ -57,7 +57,9 @@ describe('readSync', function (this: Mocha.Suite) { const leftPane = page.locator('#LeftPane'); await leftPane - .locator('.module-conversation-list__item--contact-or-conversation') + .locator( + '.module-conversation-list__item--contact-or-conversation >> "<(˶ᵔᵕᵔ˶)>"' + ) .first() .waitFor(); diff --git a/ts/test-mock/messaging/relink_test.ts b/ts/test-mock/messaging/relink_test.ts index 272b0ebc159..211b78ec76d 100644 --- a/ts/test-mock/messaging/relink_test.ts +++ b/ts/test-mock/messaging/relink_test.ts @@ -87,11 +87,13 @@ describe('messaging/relink', function (this: Mocha.Suite) { ) .waitFor(); + debug('unlinkng'); await app.unlink(); await app.waitForUnlink(); await phone.unlink(desktop); await server.removeDevice(desktop.number, desktop.deviceId); + debug('closing'); await app.close(); debug('change pinned contact, identity key'); diff --git a/ts/test-mock/messaging/unprocessed_test.ts b/ts/test-mock/messaging/unprocessed_test.ts index 7edb02fb29a..7a6efda4c50 100644 --- a/ts/test-mock/messaging/unprocessed_test.ts +++ b/ts/test-mock/messaging/unprocessed_test.ts @@ -88,9 +88,7 @@ describe('unprocessed', function (this: Mocha.Suite) { const page = await app.getWindow(); debug('opening conversation'); - await page - .locator(`[data-testid="${alice.device.aci}"] >> "${alice.profileName}"`) - .click(); + await page.getByTestId(alice.device.aci).click(); await page.locator('.module-message__text >> "hello: 4"').waitFor(); await page.locator('.module-message__text >> "hello: 5"').waitFor(); diff --git a/ts/test-mock/playwright.ts b/ts/test-mock/playwright.ts index 4b4cacdd521..d81ad47bb75 100644 --- a/ts/test-mock/playwright.ts +++ b/ts/test-mock/playwright.ts @@ -83,6 +83,7 @@ export class App extends EventEmitter { snapshots: true, }); } + await page?.emulateMedia({ reducedMotion: 'reduce' }); await page?.waitForLoadState('load'); })(), 20 * SECOND diff --git a/ts/test-mock/storage/drop_test.ts b/ts/test-mock/storage/drop_test.ts index c5e44e6a822..64162e2347b 100644 --- a/ts/test-mock/storage/drop_test.ts +++ b/ts/test-mock/storage/drop_test.ts @@ -110,10 +110,12 @@ describe('storage service', function (this: Mocha.Suite) { } const updatedState = await phone.setStorageState( - state.addRecord({ - type: IdentifierType.ACCOUNT, - record: oldAccount.record, - }) + state + .addRecord({ + type: IdentifierType.ACCOUNT, + record: oldAccount.record, + }) + .updateAccount({}) ); debug('sending fetch storage'); diff --git a/ts/test-mock/storage/pin_unpin_test.ts b/ts/test-mock/storage/pin_unpin_test.ts index 6fd44abdb21..0c222feed8e 100644 --- a/ts/test-mock/storage/pin_unpin_test.ts +++ b/ts/test-mock/storage/pin_unpin_test.ts @@ -48,20 +48,21 @@ describe('storage service', function (this: Mocha.Suite) { debug('Unpinning group via storage service'); { const state = await phone.expectStorageState('initial state'); + const newState = state.unpinGroup(group); - await phone.setStorageState(state.unpinGroup(group)); + await phone.setStorageState(newState); await phone.sendFetchStorage({ timestamp: bootstrap.getTimestamp(), }); - await leftPane.locator(`[data-testid="${group.id}"]`).waitFor(); + await app.waitForManifestVersion(newState.version); } debug('Pinning group in the app'); { const state = await phone.expectStorageState('consistency check'); - const convo = leftPane.locator(`[data-testid="${group.id}"]`); + const convo = leftPane.getByTestId(group.id); await convo.click(); const moreButton = conversationStack.locator( diff --git a/ts/test-mock/storage/sticker_test.ts b/ts/test-mock/storage/sticker_test.ts index f104d0b7e06..d793df50fb7 100644 --- a/ts/test-mock/storage/sticker_test.ts +++ b/ts/test-mock/storage/sticker_test.ts @@ -70,11 +70,10 @@ describe('storage service', function (this: Mocha.Suite) { await conversationView .locator(`a:has-text("${STICKER_PACKS[0].id.toString('hex')}")`) - .click({ noWaitAfter: true }); + .click(); await window - .locator( - '.module-sticker-manager__preview-modal__footer--install button >> "Install"' - ) + .getByTestId('StickerPreviewModal') + .getByRole('button', { name: 'Install' }) .click(); debug('waiting for sync message'); @@ -114,12 +113,10 @@ describe('storage service', function (this: Mocha.Suite) { await conversationView .locator(`a:has-text("${STICKER_PACKS[0].id.toString('hex')}")`) - .click({ noWaitAfter: true }); + .click(); await window - .locator( - '.module-sticker-manager__preview-modal__footer--install button ' + - '>> "Uninstall"' - ) + .getByTestId('StickerPreviewModal') + .getByRole('button', { name: 'Uninstall' }) .click(); // Confirm diff --git a/ts/textsecure/AccountManager.ts b/ts/textsecure/AccountManager.ts index e3d676785fb..e99df27ca8d 100644 --- a/ts/textsecure/AccountManager.ts +++ b/ts/textsecure/AccountManager.ts @@ -28,6 +28,7 @@ import type { import createTaskWithTimeout from './TaskWithTimeout'; import * as Bytes from '../Bytes'; import * as Errors from '../types/errors'; +import { isMockEnvironment } from '../environment'; import { senderCertificateService } from '../services/senderCertificate'; import { backupsService } from '../services/backups'; import { @@ -96,7 +97,9 @@ const LAST_RESORT_KEY_UPDATE_TIME_KEY: StorageKeyByServiceIdKind = { }; const PRE_KEY_ARCHIVE_AGE = 90 * DAY; -const PRE_KEY_GEN_BATCH_SIZE = 100; +// Use 20 keys for mock tests which is above the minimum, but takes much less +// time to generate and store in the database (especially for PQ keys) +const PRE_KEY_GEN_BATCH_SIZE = isMockEnvironment() ? 20 : 100; const PRE_KEY_MAX_COUNT = 200; const PRE_KEY_ID_KEY: StorageKeyByServiceIdKind = { [ServiceIdKind.ACI]: 'maxPreKeyId',