一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
143 行
3.9 KiB
TypeScript
143 行
3.9 KiB
TypeScript
import Logger from '@joplin/utils/Logger';
|
|
import ActionTracker from '../model/ActionTracker';
|
|
import Client from './Client';
|
|
import { FuzzContext } from '../types';
|
|
import { join } from 'path';
|
|
import { mkdir } from 'fs-extra';
|
|
import { readdir } from 'fs/promises';
|
|
|
|
type ClientFilter = (client: Client)=> boolean;
|
|
|
|
const logger = Logger.create('ClientPool');
|
|
|
|
export default class ClientPool {
|
|
public static async create(context: FuzzContext) {
|
|
return new ClientPool(context);
|
|
}
|
|
|
|
public static async fromSnapshot(snapshotDirectory: string, actionTracker: ActionTracker, context: FuzzContext) {
|
|
const pool = await ClientPool.create(context);
|
|
|
|
const matchingDirectories = (await readdir(snapshotDirectory))
|
|
.filter(child => child.startsWith('client-'))
|
|
.map(child => join(snapshotDirectory, child))
|
|
.sort();
|
|
|
|
const accounts = new Map();
|
|
for (const clientDirectory of matchingDirectories) {
|
|
const client = await Client.fromSnapshotDirectory(clientDirectory, actionTracker, context, accounts);
|
|
pool.clients_.push(client);
|
|
pool.listenForClientClose_(client);
|
|
}
|
|
|
|
return pool;
|
|
}
|
|
|
|
private clients_: Client[] = [];
|
|
|
|
private constructor(
|
|
private readonly context_: FuzzContext,
|
|
) {
|
|
}
|
|
|
|
private listenForClientClose_(client: Client) {
|
|
client.onClose(() => {
|
|
this.clients_ = this.clients_.filter(other => other !== client);
|
|
});
|
|
}
|
|
|
|
public async saveSnapshot(outputDirectory: string) {
|
|
let i = 0;
|
|
for (const client of this.clients) {
|
|
const outputChildDirectory = join(outputDirectory, `client-${i++}`);
|
|
await mkdir(outputChildDirectory);
|
|
await client.saveSnapshot(outputChildDirectory);
|
|
}
|
|
}
|
|
|
|
public async newClient(model: ActionTracker) {
|
|
const client = await Client.create(model, this.context_);
|
|
|
|
this.clients_.push(client);
|
|
this.listenForClientClose_(client);
|
|
}
|
|
|
|
public async createRandomInitialItemsAndSync() {
|
|
for (const client of this.clients) {
|
|
logger.info('Creating items for ', client.email);
|
|
const actionCount = this.context_.randomFrom([0, 10, 100]);
|
|
await client.createOrUpdateMany(actionCount, { createProbability: 1, updateProbability: 1, deleteProbability: 1 });
|
|
|
|
await client.sync();
|
|
}
|
|
}
|
|
|
|
public clientsByEmail(email: string) {
|
|
return this.clients.filter(client => client.email === email);
|
|
}
|
|
|
|
public clientById(id: number) {
|
|
const client = this.clients[id];
|
|
if (!client) throw new Error(`Not found: ${id}`);
|
|
return client;
|
|
}
|
|
|
|
public getClientId(client: Client): number {
|
|
const index = this.clients.indexOf(client);
|
|
if (index === -1) throw new Error(`Not found: ${client}`);
|
|
return index;
|
|
}
|
|
|
|
public randomClient(filter: ClientFilter = ()=>true) {
|
|
const clients = this.clients_.filter(filter);
|
|
return clients[
|
|
this.context_.randInt(0, clients.length)
|
|
];
|
|
}
|
|
|
|
public async newWithSameAccount(sourceClient: Client) {
|
|
const client = await sourceClient.createClientOnSameAccount();
|
|
this.listenForClientClose_(client);
|
|
this.clients_ = [...this.clients_, client];
|
|
return client;
|
|
}
|
|
|
|
public othersWithSameAccount(client: Client) {
|
|
return this.clients_.filter(other => other !== client && other.hasSameAccount(client));
|
|
}
|
|
|
|
public async checkState() {
|
|
for (const client of this.clients_) {
|
|
await client.checkState();
|
|
}
|
|
}
|
|
|
|
public async syncAll() {
|
|
// Sync all clients at roughly the same time. Some sync bugs are only apparent
|
|
// when multiple clients are syncing simultaneously.
|
|
await Promise.all(this.clients_.map(c => c.sync()));
|
|
|
|
// Note: For more deterministic behavior, sync clients individually instead:
|
|
// for (const client of this.clients_) { await client.sync(); }
|
|
}
|
|
|
|
public get clients() {
|
|
return [...this.clients_];
|
|
}
|
|
|
|
public helpText() {
|
|
return this.clients_.map(client => client.getHelpText()).join('\n\n');
|
|
}
|
|
|
|
public async close() {
|
|
for (const client of this.clients) {
|
|
try {
|
|
await client.close();
|
|
} catch (error) {
|
|
logger.warn('Failed to close client', error);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|