query: support bare regex filter values and array-index dot paths
The Node driver sends {field: /re/} as a BSON regex element (type 0x0B),
which the matcher previously only handled via the $regex operator form;
and dot paths with numeric segments (tags.0) were ignored because array
descent only recursed into embedded docs. Both are part of standard
MongoDB query semantics and were caught by the driver e2e suite.
server: unbounded Io async limit so the accept loop never wedges, and
treat header-read failures (client RST on pool teardown) as clean
disconnects. With the default cpu_count-1 limit, groupAsync's eager
fallback ran connection handlers inline on the accept-loop fiber once
that many connections were alive, stalling accept() and timing out
handshakes for further clients.
Add tests/e2e/: official driver CRUD, concurrency, and kill -9 recovery
suites (29 + 2 + 3 checks), plus unit tests for the query fixes.
This commit is contained in:
107
tests/e2e/e2e2.js
Normal file
107
tests/e2e/e2e2.js
Normal file
@@ -0,0 +1,107 @@
|
||||
// E2E part 2: concurrent clients + crash recovery, official driver.
|
||||
const { MongoClient } = require('mongodb');
|
||||
|
||||
const URL = 'mongodb://127.0.0.1:27020';
|
||||
const results = [];
|
||||
function check(name, cond, detail = '') {
|
||||
results.push({ name, ok: !!cond, detail: String(detail) });
|
||||
if (!cond) console.error(` ✗ ${name} ${detail}`);
|
||||
}
|
||||
|
||||
async function concurrentClients() {
|
||||
// 8 clients: 4 inserting unique docs, 4 reading concurrently.
|
||||
const WRITERS = 4;
|
||||
const READERS = 4;
|
||||
const PER = 100;
|
||||
const TOTAL = WRITERS * PER;
|
||||
|
||||
const clients = await Promise.all(
|
||||
Array.from({ length: WRITERS + READERS }, () => new MongoClient(URL, { serverSelectionTimeoutMS: 5000 }).connect()),
|
||||
);
|
||||
await clients[0].db('conc').collection('items').drop().catch(() => {});
|
||||
|
||||
let nextId = 1;
|
||||
const pending = { n: TOTAL };
|
||||
const writerJobs = [];
|
||||
for (let w = 0; w < WRITERS; w++) {
|
||||
writerJobs.push((async () => {
|
||||
const db = clients[w].db('conc');
|
||||
const coll = db.collection('items');
|
||||
while (true) {
|
||||
const id = nextId++;
|
||||
if (id > TOTAL) break;
|
||||
await coll.insertOne({ _id: id, w: w, payload: 'x'.repeat(64) });
|
||||
pending.n--;
|
||||
}
|
||||
})());
|
||||
}
|
||||
const readerJobs = [];
|
||||
for (let r = 0; r < READERS; r++) {
|
||||
const idx = WRITERS + r;
|
||||
readerJobs.push((async () => {
|
||||
const db = clients[idx].db('conc');
|
||||
const coll = db.collection('items');
|
||||
while (pending.n > 0) {
|
||||
const n = await coll.countDocuments({});
|
||||
if (n > TOTAL) throw new Error('reader saw impossible count ' + n);
|
||||
await coll.findOne({ _id: Math.floor(Math.random() * TOTAL) + 1 });
|
||||
}
|
||||
})());
|
||||
}
|
||||
await Promise.all([...writerJobs, ...readerJobs]);
|
||||
|
||||
const final = await clients[0].db('conc').collection('items').countDocuments({});
|
||||
check('concurrent writers+readers final count', final === TOTAL, final);
|
||||
const spot = await clients[0].db('conc').collection('items').findOne({ _id: TOTAL });
|
||||
check('concurrent insert visible', spot?._id === TOTAL, JSON.stringify(spot));
|
||||
|
||||
await Promise.all(clients.map((c) => c.close()));
|
||||
}
|
||||
|
||||
async function crashRecovery() {
|
||||
// Phase A: write and record state.
|
||||
const c1 = await new MongoClient(URL, { serverSelectionTimeoutMS: 5000 }).connect();
|
||||
const coll = c1.db('crash').collection('docs');
|
||||
await coll.deleteMany({});
|
||||
const docs = [];
|
||||
for (let i = 1; i <= 50; i++) {
|
||||
docs.push({ _id: i, name: 'doc-' + i });
|
||||
}
|
||||
await coll.insertMany(docs);
|
||||
await c1.close();
|
||||
check('crash: committed before kill', true);
|
||||
|
||||
// (The harness kills -9 the server between phases.)
|
||||
await new Promise((r) => setTimeout(r, 200));
|
||||
}
|
||||
|
||||
async function crashVerify() {
|
||||
const c2 = await new MongoClient(URL, { serverSelectionTimeoutMS: 5000 }).connect();
|
||||
const coll = c2.db('crash').collection('docs');
|
||||
const n = await coll.countDocuments({});
|
||||
check('crash recovery: all 50 docs survived kill -9', n === 50, n);
|
||||
const one = await coll.findOne({ _id: 37 });
|
||||
check('crash recovery: doc content intact', one?.name === 'doc-37', JSON.stringify(one));
|
||||
// keep writing after recovery (log reopened correctly)
|
||||
await coll.insertOne({ _id: 51, name: 'post-recovery' });
|
||||
const n2 = await coll.countDocuments({});
|
||||
check('crash recovery: writes continue', n2 === 51, n2);
|
||||
await c2.close();
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const phase = process.argv[2];
|
||||
if (phase === 'concurrent') await concurrentClients();
|
||||
if (phase === 'crash-a') await crashRecovery();
|
||||
if (phase === 'crash-b') await crashVerify();
|
||||
|
||||
const failed = results.filter((r) => !r.ok);
|
||||
console.log(`\n${results.length - failed.length}/${results.length} checks passed`);
|
||||
if (failed.length) {
|
||||
console.log('FAILED:', failed.map((f) => f.name).join(', '));
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(phase === 'crash-a' ? 'CRASH_PHASE_A_OK' : 'E2E2_OK');
|
||||
}
|
||||
|
||||
main().catch((e) => { console.error('E2E2_FAIL', e); process.exit(1); });
|
||||
Reference in New Issue
Block a user