Skip to content
This repository was archived by the owner on Mar 1, 2026. It is now read-only.

Commit c7ad7d7

Browse files
authored
test: add basic memory stress test (#620)
1 parent 99f68e2 commit c7ad7d7

1 file changed

Lines changed: 308 additions & 0 deletions

File tree

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
import { createTestClient } from '@zenstackhq/testtools';
2+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
3+
4+
describe.skip('Memory usage test with repeated CRUD operations', () => {
5+
let client: any;
6+
7+
beforeEach(async () => {
8+
client = await createTestClient(
9+
`
10+
model User {
11+
id String @id @default(cuid())
12+
email String @unique
13+
name String
14+
createdAt DateTime @default(now())
15+
posts Post[]
16+
comments Comment[]
17+
}
18+
19+
model Post {
20+
id String @id @default(cuid())
21+
title String
22+
content String
23+
published Boolean @default(false)
24+
createdAt DateTime @default(now())
25+
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
26+
authorId String
27+
comments Comment[]
28+
}
29+
30+
model Comment {
31+
id String @id @default(cuid())
32+
content String
33+
createdAt DateTime @default(now())
34+
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
35+
postId String
36+
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
37+
authorId String
38+
}
39+
`,
40+
);
41+
});
42+
43+
afterEach(async () => {
44+
await client?.$disconnect();
45+
});
46+
47+
it('repeatedly executes CRUD operations with random data and tracks memory', async () => {
48+
// ============ CONFIGURATION ============
49+
// Adjust these values to test different workload scenarios
50+
const iterations = 100; // Number of complete CRUD cycles to execute
51+
const usersCount = 10; // Number of users to create per iteration
52+
const postsPerUser = 5; // Number of posts per user
53+
const commentsPerPost = 3; // Number of comments per post
54+
55+
// Calculated totals
56+
const totalPosts = usersCount * postsPerUser;
57+
const totalComments = totalPosts * commentsPerPost;
58+
59+
const memorySnapshots: Array<{
60+
iteration: number;
61+
rss: number;
62+
heapTotal: number;
63+
heapUsed: number;
64+
external: number;
65+
}> = [];
66+
67+
// Helper function to generate random string
68+
const randomString = (length: number) => {
69+
return Math.random()
70+
.toString(36)
71+
.substring(2, 2 + length);
72+
};
73+
74+
// Helper function to generate random content
75+
const randomContent = () => {
76+
const paragraphs = Math.floor(Math.random() * 5) + 1;
77+
return Array.from({ length: paragraphs }, () => randomString(100)).join('\n\n');
78+
};
79+
80+
console.log(`\nStarting ${iterations} iterations of CRUD operations...\n`);
81+
82+
for (let i = 0; i < iterations; i++) {
83+
// ============ CREATE ============
84+
85+
// Create users
86+
const users = await Promise.all(
87+
Array.from({ length: usersCount }, (_, idx) =>
88+
client.user.create({
89+
data: {
90+
email: `user${i}-${idx + 1}-${randomString(8)}@test.com`,
91+
name: `User ${i}-${idx + 1} ${randomString(10)}`,
92+
},
93+
}),
94+
),
95+
);
96+
97+
// Create posts per user
98+
const posts: any[] = [];
99+
for (const user of users) {
100+
for (let j = 0; j < postsPerUser; j++) {
101+
const post = await client.post.create({
102+
data: {
103+
title: `Post ${i}-${j} - ${randomString(20)}`,
104+
content: randomContent(),
105+
published: Math.random() > 0.5,
106+
authorId: user.id,
107+
},
108+
});
109+
posts.push(post);
110+
}
111+
}
112+
113+
// Create comments per post
114+
const comments: any[] = [];
115+
for (const post of posts) {
116+
for (let k = 0; k < commentsPerPost; k++) {
117+
const randomAuthor = users[Math.floor(Math.random() * users.length)]!;
118+
const comment = await client.comment.create({
119+
data: {
120+
content: randomString(100),
121+
postId: post.id,
122+
authorId: randomAuthor.id,
123+
},
124+
});
125+
comments.push(comment);
126+
}
127+
}
128+
129+
// ============ READ ============
130+
131+
// Read all users with posts and comments
132+
const allUsers = await client.user.findMany({
133+
include: {
134+
posts: {
135+
include: {
136+
comments: true,
137+
},
138+
},
139+
comments: true,
140+
},
141+
});
142+
expect(allUsers).toHaveLength(usersCount);
143+
144+
// Read all posts with filtering
145+
await client.post.findMany({
146+
where: {
147+
published: true,
148+
},
149+
include: {
150+
author: true,
151+
comments: true,
152+
},
153+
});
154+
155+
// Read individual comments
156+
await client.comment.findMany({
157+
include: {
158+
post: true,
159+
author: true,
160+
},
161+
});
162+
163+
// Aggregate operations
164+
const userCount = await client.user.count();
165+
const postCount = await client.post.count();
166+
const commentCount = await client.comment.count();
167+
168+
expect(userCount).toBeGreaterThanOrEqual(usersCount);
169+
expect(postCount).toBeGreaterThanOrEqual(totalPosts);
170+
expect(commentCount).toBeGreaterThanOrEqual(totalComments);
171+
172+
// ============ UPDATE ============
173+
174+
// Update random posts
175+
const postsToUpdate = posts.slice(0, 5);
176+
for (const post of postsToUpdate) {
177+
await client.post.update({
178+
where: { id: post.id },
179+
data: {
180+
title: `Updated - ${randomString(20)}`,
181+
content: randomContent(),
182+
},
183+
});
184+
}
185+
186+
// Update random users
187+
const userToUpdate = users[0]!;
188+
await client.user.update({
189+
where: { id: userToUpdate.id },
190+
data: {
191+
name: `Updated User - ${randomString(10)}`,
192+
},
193+
});
194+
195+
// Update many comments
196+
await client.comment.updateMany({
197+
where: {
198+
postId: posts[0]!.id,
199+
},
200+
data: {
201+
content: `Bulk updated - ${randomString(50)}`,
202+
},
203+
});
204+
205+
// ============ DELETE (Cleanup) ============
206+
207+
// Delete all comments first (due to foreign key constraints)
208+
await client.comment.deleteMany({});
209+
210+
// Delete all posts
211+
await client.post.deleteMany({});
212+
213+
// Delete all users
214+
await client.user.deleteMany({});
215+
216+
// Verify cleanup
217+
const remainingUsers = await client.user.count();
218+
const remainingPosts = await client.post.count();
219+
const remainingComments = await client.comment.count();
220+
221+
expect(remainingUsers).toBe(0);
222+
expect(remainingPosts).toBe(0);
223+
expect(remainingComments).toBe(0);
224+
225+
// ============ MEMORY SNAPSHOT ============
226+
227+
// Force garbage collection if available (run tests with --expose-gc flag)
228+
if (global.gc) {
229+
global.gc();
230+
}
231+
232+
const memUsage = process.memoryUsage();
233+
memorySnapshots.push({
234+
iteration: i + 1,
235+
rss: memUsage.rss,
236+
heapTotal: memUsage.heapTotal,
237+
heapUsed: memUsage.heapUsed,
238+
external: memUsage.external,
239+
});
240+
241+
// Log progress every 10 iterations
242+
if ((i + 1) % 10 === 0) {
243+
console.log(`Completed ${i + 1}/${iterations} iterations`);
244+
console.log(
245+
` Memory: ${(memUsage.heapUsed / 1024 / 1024).toFixed(2)} MB heap used, ${(memUsage.rss / 1024 / 1024).toFixed(2)} MB RSS`,
246+
);
247+
}
248+
}
249+
250+
// ============ MEMORY ANALYSIS ============
251+
252+
console.log('\n=== Memory Usage Summary ===\n');
253+
254+
const firstSnapshot = memorySnapshots[0]!;
255+
const lastSnapshot = memorySnapshots[memorySnapshots.length - 1]!;
256+
const maxHeapUsed = Math.max(...memorySnapshots.map((s) => s.heapUsed));
257+
const minHeapUsed = Math.min(...memorySnapshots.map((s) => s.heapUsed));
258+
const avgHeapUsed = memorySnapshots.reduce((sum, s) => sum + s.heapUsed, 0) / memorySnapshots.length;
259+
260+
const formatMB = (bytes: number) => (bytes / 1024 / 1024).toFixed(2);
261+
262+
console.log('Heap Used:');
263+
console.log(` Initial: ${formatMB(firstSnapshot.heapUsed)} MB`);
264+
console.log(` Final: ${formatMB(lastSnapshot.heapUsed)} MB`);
265+
console.log(` Min: ${formatMB(minHeapUsed)} MB`);
266+
console.log(` Max: ${formatMB(maxHeapUsed)} MB`);
267+
console.log(` Average: ${formatMB(avgHeapUsed)} MB`);
268+
console.log(
269+
` Growth: ${formatMB(lastSnapshot.heapUsed - firstSnapshot.heapUsed)} MB (${(((lastSnapshot.heapUsed - firstSnapshot.heapUsed) / firstSnapshot.heapUsed) * 100).toFixed(2)}%)`,
270+
);
271+
272+
console.log('\nRSS (Resident Set Size):');
273+
console.log(` Initial: ${formatMB(firstSnapshot.rss)} MB`);
274+
console.log(` Final: ${formatMB(lastSnapshot.rss)} MB`);
275+
console.log(
276+
` Growth: ${formatMB(lastSnapshot.rss - firstSnapshot.rss)} MB (${(((lastSnapshot.rss - firstSnapshot.rss) / firstSnapshot.rss) * 100).toFixed(2)}%)`,
277+
);
278+
279+
console.log('\nHeap Total:');
280+
console.log(` Initial: ${formatMB(firstSnapshot.heapTotal)} MB`);
281+
console.log(` Final: ${formatMB(lastSnapshot.heapTotal)} MB`);
282+
283+
console.log('\n=== Test Summary ===');
284+
console.log(`Total iterations: ${iterations}`);
285+
console.log(`Operations per iteration:`);
286+
console.log(` - Created: ${usersCount} users, ${totalPosts} posts, ${totalComments} comments`);
287+
console.log(` - Read: Multiple queries with includes and filters`);
288+
console.log(` - Updated: 5 posts, 1 user, bulk comment updates`);
289+
console.log(` - Deleted: All data (cleanup)`);
290+
const opsPerIteration = usersCount + totalPosts + totalComments + 10; // approximate CRUD ops
291+
console.log(`Total operations: ~${iterations * opsPerIteration}`);
292+
293+
// Check for significant memory leaks (> 50% growth is concerning)
294+
const heapGrowthPercent = ((lastSnapshot.heapUsed - firstSnapshot.heapUsed) / firstSnapshot.heapUsed) * 100;
295+
if (heapGrowthPercent > 50) {
296+
console.log(
297+
`\n⚠️ Warning: Heap usage grew by ${heapGrowthPercent.toFixed(2)}% which may indicate a memory leak`,
298+
);
299+
} else {
300+
console.log(`\n✓ Memory usage appears stable (${heapGrowthPercent.toFixed(2)}% growth)`);
301+
}
302+
303+
console.log('\n');
304+
305+
// Store snapshots for potential further analysis
306+
expect(memorySnapshots).toHaveLength(iterations);
307+
}, 120000); // 2 minute timeout for the test
308+
});

0 commit comments

Comments
 (0)