Skip to content

Commit 5200539

Browse files
committed
test: fix error messages and add currently disabled cleanup tests.
1 parent 07bcedc commit 5200539

5 files changed

Lines changed: 322 additions & 7 deletions

File tree

test/iterator-test.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,17 @@ describe(`Urkel Iterator (${name})`, function () {
7979

8080
// Transaction is still closed, but check the nurkel side of things.
8181
txn.isOpen = true;
82-
assert.throws(() => {
82+
83+
let err;
84+
85+
try {
8386
txn.iterator();
84-
}, {
85-
message: 'is closed.'
86-
});
87+
} catch (e) {
88+
err = e;
89+
}
90+
91+
assert(err, 'should throw error');
92+
assert.strictEqual(err.message, 'Transaction is closed.');
8793
});
8894

8995
it('should create an iterator from snapshot and iterate sync', async () => {

test/transaction-gc-test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ describe('Urkel Transaction (GC)', function () {
8080
}
8181

8282
assert(err, 'Close should throw.');
83-
assert.strictEqual(err.message, 'is closed.');
83+
assert.strictEqual(err.message, 'Tree is closed.');
8484
});
8585

8686
it('should close all transactions', async () => {
@@ -128,7 +128,7 @@ describe('Urkel Transaction (GC)', function () {
128128
if (i <= 3)
129129
assert.strictEqual(err.message, 'Transaction is not open.');
130130
else
131-
assert.strictEqual(err.message, 'is closed.');
131+
assert.strictEqual(err.message, 'Tree is closed.');
132132
}
133133
});
134134

test/tree-cleanup-test.js

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
'use strict';
2+
3+
const {testdir} = require('./util/common');
4+
const nurkel = require('..');
5+
const {runForked} = require('./util/worker');
6+
const fs = require('bfile');
7+
8+
const cleanupTests = {
9+
openTree: {
10+
name: 'should clean up opened tree',
11+
fn: async (prefix) => {
12+
const tree = nurkel.create({ prefix });
13+
14+
await tree.open();
15+
}
16+
},
17+
openMultipleTrees: {
18+
name: 'should clean up multiple opened trees',
19+
fn: async (prefix) => {
20+
for (let i = 0; i < 10; i++) {
21+
const prefixN = `${prefix}/${i}`;
22+
const tree = nurkel.create({ prefix: prefixN });
23+
24+
await tree.open();
25+
}
26+
}
27+
},
28+
openClosingTree: {
29+
name: 'should clean up opened and closed tree',
30+
fn: async (prefix) => {
31+
const tree = nurkel.create({ prefix });
32+
33+
await tree.open();
34+
tree.close();
35+
}
36+
},
37+
openClosingTrees: {
38+
name: 'should clean up multiple opened and closed trees',
39+
fn: async (prefix) => {
40+
for (let i = 0; i < 10; i++) {
41+
const prefixN = `${prefix}/${i}`;
42+
const tree = nurkel.create({ prefix: prefixN });
43+
44+
await tree.open();
45+
tree.close();
46+
}
47+
}
48+
},
49+
openTreeTx: {
50+
name: 'should clean up opened tree with transaction',
51+
fn: async (prefix) => {
52+
const tree = nurkel.create({ prefix });
53+
54+
await tree.open();
55+
56+
const tx = tree.txn();
57+
await tx.open();
58+
}
59+
},
60+
openTreesTxs: {
61+
name: 'should clean up multiple opened trees with transactions',
62+
fn: async (prefix) => {
63+
for (let i = 0; i < 10; i++) {
64+
const prefixN = `${prefix}/${i}`;
65+
const tree = nurkel.create({ prefix: prefixN });
66+
67+
await tree.open();
68+
69+
const txs = [];
70+
for (let t = 0; t < 20; t++) {
71+
const tx = tree.txn();
72+
await tx.open();
73+
txs.push(tx);
74+
75+
// close half
76+
if (t < 10)
77+
tx.close();
78+
}
79+
80+
// close 10th tree
81+
if (i === 10)
82+
tree.close();
83+
}
84+
}
85+
},
86+
openTreeTxIter: {
87+
name: 'should clean up opened tree with transaction and iterator',
88+
fn: async (prefix) => {
89+
const tree = nurkel.create({ prefix });
90+
91+
await tree.open();
92+
93+
const tx = tree.txn();
94+
await tx.open();
95+
96+
tx.iterator();
97+
98+
// TODO: separate iterator init from open.
99+
// const iter = tx.iterator();
100+
// await iter.open();
101+
}
102+
},
103+
openTreesTxsIters: {
104+
name: 'should clean up multiple opened trees with transactions and iterators',
105+
fn: async (prefix) => {
106+
for (let i = 0; i < 10; i++) {
107+
const tree = nurkel.create({ prefix: `${prefix}/${i}` });
108+
109+
await tree.open();
110+
111+
for (let t = 0; t < 20; t++) {
112+
const tx = tree.txn();
113+
await tx.open();
114+
115+
for (let j = 0; j < 20; j++) {
116+
const iter = tx.iterator();
117+
118+
if (j === 10)
119+
iter.end();
120+
}
121+
122+
if (t < 10)
123+
tx.close();
124+
}
125+
}
126+
},
127+
timeout: 3000
128+
}
129+
};
130+
131+
const isForked = Boolean(process.send);
132+
const isMainThread = !isForked;
133+
134+
if (!isMainThread) {
135+
const data = JSON.parse(process.env.WORKER_DATA);
136+
137+
// select the cleanup test
138+
const {
139+
testName,
140+
prefix
141+
} = data;
142+
143+
const testCase = cleanupTests[testName];
144+
145+
if (!testCase)
146+
throw new Error(`Test ${testName} not found`);
147+
148+
testCase.fn(prefix).catch((e) => {
149+
console.error(e);
150+
process.exit(1);
151+
});
152+
153+
return;
154+
}
155+
156+
// Main Thread runs the tests.
157+
describe.skip('Tree env cleanup', function() {
158+
let prefix;
159+
160+
beforeEach(() => {
161+
prefix = testdir('cleanup-tree');
162+
});
163+
164+
afterEach(async () => {
165+
await fs.rimraf(prefix);
166+
});
167+
168+
for (const [id, testDetails] of Object.entries(cleanupTests)) {
169+
it(testDetails.name, async () => {
170+
if (testDetails.timeout != null)
171+
this.timeout(testDetails.timeout);
172+
173+
await runForked(__filename, {
174+
testName: id,
175+
prefix: prefix
176+
}, testDetails.timeout);
177+
});
178+
}
179+
});

test/tree-debug-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,7 @@ describe('Nurkel debug state', function() {
495495
}
496496

497497
assert(err);
498-
assert.strictEqual(err.message, 'is closing.');
498+
assert.strictEqual(err.message, 'Transaction is closing.');
499499

500500
await close;
501501

test/util/worker.js

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
'use strict';
2+
3+
const workerThreads = require('worker_threads');
4+
const { fork } = require('child_process');
5+
6+
class ForkError extends Error {
7+
constructor(message) {
8+
super(message);
9+
10+
this.name = 'ForkError';
11+
}
12+
}
13+
14+
exports.runWorker = (filename, workerData, timeout = 1500) => {
15+
return new Promise((resolve, reject) => {
16+
let timeoutId = null;
17+
let workerError = null;
18+
19+
const worker = new workerThreads.Worker(filename, {
20+
workerData,
21+
stdout: true,
22+
stderr: true
23+
});
24+
25+
if (timeout > 0) {
26+
timeoutId = setTimeout(() => {
27+
workerError = new Error(`Worker timed out after ${timeout}ms`);
28+
worker.terminate();
29+
}, timeout);
30+
}
31+
32+
worker.on('error', (err) => {
33+
workerError = err;
34+
});
35+
36+
// Handle worker exit
37+
worker.on('exit', (code) => {
38+
if (timeoutId)
39+
clearTimeout(timeoutId);
40+
41+
if (code !== 0) {
42+
const error = workerError || new Error(`Worker stopped with exit code ${code}`);
43+
reject(error);
44+
return;
45+
}
46+
47+
if (workerError) {
48+
reject(workerError);
49+
return;
50+
}
51+
52+
resolve();
53+
});
54+
});
55+
};
56+
57+
exports.runForked = (filename, data, timeout = 1500) => {
58+
return new Promise((resolve, reject) => {
59+
let timeoutId = null;
60+
let processError = null;
61+
const stderrs = [];
62+
63+
const child = fork(filename, {
64+
stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
65+
env: {
66+
...process.env,
67+
WORKER_DATA: JSON.stringify(data) // Pass data through env since we can't use workerData
68+
}
69+
});
70+
71+
child.stderr.on('data', data => stderrs.push(data));
72+
73+
if (timeout > 0) {
74+
timeoutId = setTimeout(() => {
75+
processError = new Error(`Process timed out after ${timeout}ms`);
76+
77+
// SIGTERM first, then SIGKILL after a grace period
78+
child.kill('SIGTERM');
79+
80+
setTimeout(() => {
81+
if (!child.killed)
82+
child.kill('SIGKILL');
83+
}, 200);
84+
}, timeout);
85+
}
86+
87+
child.on('error', (err) => {
88+
processError = err;
89+
});
90+
91+
// Handle process exit
92+
child.on('exit', (code, signal) => {
93+
if (timeoutId)
94+
clearTimeout(timeoutId);
95+
96+
if (signal) {
97+
let error = processError;
98+
99+
if (!error)
100+
error = new Error(`Process killed with signal ${signal}`);
101+
102+
reject(error);
103+
return;
104+
}
105+
106+
if (code !== 0) {
107+
let error = processError;
108+
109+
if (!error && stderrs.length) {
110+
const str = stderrs.join('');
111+
error = new ForkError(str.split('\n')[0]);
112+
error.stack = str.split('\n').slice(1).join('\n');
113+
}
114+
115+
if (!error)
116+
error = new Error(`Process stopped with exit code ${code}`);
117+
118+
reject(error);
119+
return;
120+
}
121+
122+
if (processError) {
123+
reject(processError);
124+
return;
125+
}
126+
127+
resolve();
128+
});
129+
});
130+
};

0 commit comments

Comments
 (0)