jsgui3-server 0.0.138 → 0.0.140
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +87 -0
- package/README.md +12 -0
- package/docs/GUIDE_TO_AGENTIC_WORKFLOWS_BY_GROK.md +19 -0
- package/docs/advanced-usage-examples.md +1360 -0
- package/docs/agent-development-guide.md +386 -0
- package/docs/api-reference.md +916 -0
- package/docs/broken-functionality-tracker.md +285 -0
- package/docs/bundling-system-deep-dive.md +525 -0
- package/docs/cli-reference.md +393 -0
- package/docs/comprehensive-documentation.md +1403 -0
- package/docs/configuration-reference.md +808 -0
- package/docs/controls-development.md +859 -0
- package/docs/documentation-review/CURRENT_REVIEW.md +95 -0
- package/docs/function-publishers-json-apis.md +847 -0
- package/docs/getting-started-with-json.md +518 -0
- package/docs/minification-compression-sourcemaps-status.md +482 -0
- package/docs/minification-compression-sourcemaps-test-results.md +205 -0
- package/docs/publishers-guide.md +313 -0
- package/docs/resources-guide.md +615 -0
- package/docs/serve-helpers.md +406 -0
- package/docs/simple-server-api-design.md +13 -0
- package/docs/system-architecture.md +275 -0
- package/docs/troubleshooting.md +698 -0
- package/examples/json/README.md +115 -0
- package/examples/json/basic-api/README.md +345 -0
- package/examples/json/basic-api/server.js +199 -0
- package/examples/json/simple-api/README.md +125 -0
- package/examples/json/simple-api/diagnostic-report.json +73 -0
- package/examples/json/simple-api/diagnostic-test.js +433 -0
- package/examples/json/simple-api/server-debug.md +58 -0
- package/examples/json/simple-api/server.js +91 -0
- package/examples/json/simple-api/test.js +215 -0
- package/http/responders/static/Static_Route_HTTP_Responder.js +1 -2
- package/package.json +19 -8
- package/publishers/helpers/assigners/static-compressed-response-buffers/Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner.js +65 -12
- package/publishers/helpers/preparers/static/bundle/Static_Routes_Responses_Webpage_Bundle_Preparer.js +6 -1
- package/publishers/http-function-publisher.js +59 -38
- package/publishers/http-webpage-publisher.js +48 -1
- package/resources/processors/bundlers/js/esbuild/Advanced_JS_Bundler_Using_ESBuild.js +38 -146
- package/resources/processors/bundlers/js/esbuild/Core_JS_Non_Minifying_Bundler_Using_ESBuild.js +54 -5
- package/resources/processors/bundlers/js/esbuild/Core_JS_Single_File_Minifying_Bundler_Using_ESBuild.js +36 -4
- package/serve-factory.js +36 -9
- package/server.js +10 -4
- package/test-report.json +0 -0
- package/tests/README.md +250 -0
- package/tests/assigners.test.js +316 -0
- package/tests/bundlers.test.js +329 -0
- package/tests/configuration-validation.test.js +530 -0
- package/tests/content-analysis.test.js +641 -0
- package/tests/end-to-end.test.js +496 -0
- package/tests/error-handling.test.js +746 -0
- package/tests/performance.test.js +653 -0
- package/tests/publishers.test.js +395 -0
- package/tests/temp_invalid.js +7 -0
- package/tests/temp_invalid_utf8.js +1 -0
- package/tests/temp_malformed.js +10 -0
- package/tests/test-runner.js +261 -0
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const { describe, it, before, after } = require('mocha');
|
|
3
|
+
const fs = require('fs').promises;
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
// Import classes for performance testing
|
|
7
|
+
const Core_JS_Non_Minifying_Bundler_Using_ESBuild = require('../resources/processors/bundlers/js/esbuild/Core_JS_Non_Minifying_Bundler_Using_ESBuild');
|
|
8
|
+
const Core_JS_Single_File_Minifying_Bundler_Using_ESBuild = require('../resources/processors/bundlers/js/esbuild/Core_JS_Single_File_Minifying_Bundler_Using_ESBuild');
|
|
9
|
+
const Advanced_JS_Bundler_Using_ESBuild = require('../resources/processors/bundlers/js/esbuild/Advanced_JS_Bundler_Using_ESBuild');
|
|
10
|
+
const Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner = require('../publishers/helpers/assigners/static-compressed-response-buffers/Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner');
|
|
11
|
+
const Server = require('../server');
|
|
12
|
+
|
|
13
|
+
describe('Performance Tests', function() {
|
|
14
|
+
this.timeout(60000); // Allow longer timeout for performance tests
|
|
15
|
+
|
|
16
|
+
let testJsFile;
|
|
17
|
+
let largeJsContent;
|
|
18
|
+
let mediumJsContent;
|
|
19
|
+
let smallJsContent;
|
|
20
|
+
|
|
21
|
+
before(async function() {
|
|
22
|
+
// Create test files of different sizes
|
|
23
|
+
smallJsContent = `
|
|
24
|
+
function smallTest() {
|
|
25
|
+
return "small";
|
|
26
|
+
}
|
|
27
|
+
console.log(smallTest());
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
mediumJsContent = `
|
|
31
|
+
// Medium-sized JavaScript file
|
|
32
|
+
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
|
33
|
+
|
|
34
|
+
function processData(arr) {
|
|
35
|
+
return arr.map(x => x * 2)
|
|
36
|
+
.filter(x => x > 10)
|
|
37
|
+
.reduce((sum, x) => sum + x, 0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
class DataProcessor {
|
|
41
|
+
constructor(data) {
|
|
42
|
+
this.data = data;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
process() {
|
|
46
|
+
return processData(this.data);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getStatistics() {
|
|
50
|
+
return {
|
|
51
|
+
count: this.data.length,
|
|
52
|
+
sum: this.data.reduce((a, b) => a + b, 0),
|
|
53
|
+
average: this.data.reduce((a, b) => a + b, 0) / this.data.length
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const processor = new DataProcessor(data);
|
|
59
|
+
console.log('Result:', processor.process());
|
|
60
|
+
console.log('Stats:', processor.getStatistics());
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
largeJsContent = `
|
|
64
|
+
// Large JavaScript file for performance testing
|
|
65
|
+
const largeArray = [];
|
|
66
|
+
for (let i = 0; i < 10000; i++) {
|
|
67
|
+
largeArray.push({
|
|
68
|
+
id: i,
|
|
69
|
+
name: \`Item \${i}\`,
|
|
70
|
+
value: Math.random(),
|
|
71
|
+
nested: {
|
|
72
|
+
prop1: \`Nested \${i}\`,
|
|
73
|
+
prop2: i * 2,
|
|
74
|
+
prop3: {
|
|
75
|
+
deep: \`Deep nested \${i}\`,
|
|
76
|
+
deeper: {
|
|
77
|
+
value: i * i
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function processLargeArray(arr) {
|
|
85
|
+
return arr
|
|
86
|
+
.filter(item => item.value > 0.5)
|
|
87
|
+
.map(item => ({
|
|
88
|
+
...item,
|
|
89
|
+
processed: true,
|
|
90
|
+
computed: item.nested.prop2 * item.nested.prop3.deeper.value
|
|
91
|
+
}))
|
|
92
|
+
.sort((a, b) => b.computed - a.computed)
|
|
93
|
+
.slice(0, 100);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function createLargeString() {
|
|
97
|
+
let str = '';
|
|
98
|
+
for (let i = 0; i < 1000; i++) {
|
|
99
|
+
str += \`Line \${i}: This is a test string that will be repeated many times to create a large file. \`;
|
|
100
|
+
str += \`Additional content to make this even larger. \${i * i} \${Math.random()} \`;
|
|
101
|
+
}
|
|
102
|
+
return str;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const largeString = createLargeString();
|
|
106
|
+
const processedData = processLargeArray(largeArray);
|
|
107
|
+
|
|
108
|
+
console.log('Large array length:', largeArray.length);
|
|
109
|
+
console.log('Processed data length:', processedData.length);
|
|
110
|
+
console.log('Large string length:', largeString.length);
|
|
111
|
+
console.log('Sample processed item:', processedData[0]);
|
|
112
|
+
`;
|
|
113
|
+
|
|
114
|
+
// Create temporary file
|
|
115
|
+
testJsFile = path.join(__dirname, 'temp_performance_test.js');
|
|
116
|
+
await fs.writeFile(testJsFile, largeJsContent);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
after(async function() {
|
|
120
|
+
// Clean up
|
|
121
|
+
try {
|
|
122
|
+
await fs.unlink(testJsFile);
|
|
123
|
+
} catch (err) {
|
|
124
|
+
// Ignore if file doesn't exist
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('Bundling Performance Benchmarks', function() {
|
|
129
|
+
it('should measure bundling performance across different file sizes', async function() {
|
|
130
|
+
const testCases = [
|
|
131
|
+
{ name: 'Small', content: smallJsContent },
|
|
132
|
+
{ name: 'Medium', content: mediumJsContent },
|
|
133
|
+
{ name: 'Large', content: largeJsContent }
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
const bundler = new Core_JS_Non_Minifying_Bundler_Using_ESBuild();
|
|
137
|
+
const results = {};
|
|
138
|
+
|
|
139
|
+
for (const testCase of testCases) {
|
|
140
|
+
const startTime = Date.now();
|
|
141
|
+
const result = await bundler.bundle_js_string(testCase.content);
|
|
142
|
+
const endTime = Date.now();
|
|
143
|
+
|
|
144
|
+
const bundle = result[0];
|
|
145
|
+
const bundledContent = bundle._arr[0].text;
|
|
146
|
+
|
|
147
|
+
results[testCase.name] = {
|
|
148
|
+
inputSize: testCase.content.length,
|
|
149
|
+
outputSize: bundledContent.length,
|
|
150
|
+
duration: endTime - startTime,
|
|
151
|
+
throughput: testCase.content.length / ((endTime - startTime) / 1000) // bytes per second
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Log results
|
|
156
|
+
console.log('Bundling Performance Results:');
|
|
157
|
+
Object.entries(results).forEach(([name, result]) => {
|
|
158
|
+
console.log(`${name}: ${result.duration}ms, ${result.inputSize} → ${result.outputSize} bytes, ${(result.throughput / 1024).toFixed(2)} KB/s`);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Performance assertions
|
|
162
|
+
assert(results.Small.duration < 1000, 'Small file should bundle quickly');
|
|
163
|
+
assert(results.Medium.duration < 2000, 'Medium file should bundle reasonably quickly');
|
|
164
|
+
assert(results.Large.duration < 10000, 'Large file should bundle within reasonable time');
|
|
165
|
+
|
|
166
|
+
// Throughput should be reasonable
|
|
167
|
+
assert(results.Small.throughput > 1000, 'Should have reasonable throughput');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should compare minification performance at different levels', async function() {
|
|
171
|
+
const levels = ['conservative', 'normal', 'aggressive'];
|
|
172
|
+
const results = {};
|
|
173
|
+
|
|
174
|
+
for (const level of levels) {
|
|
175
|
+
const bundler = new Core_JS_Single_File_Minifying_Bundler_Using_ESBuild({
|
|
176
|
+
minify: { level, enabled: true }
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const startTime = Date.now();
|
|
180
|
+
const result = await bundler.bundle(largeJsContent);
|
|
181
|
+
const endTime = Date.now();
|
|
182
|
+
|
|
183
|
+
const bundle = result[0];
|
|
184
|
+
const minifiedContent = bundle._arr[0].text;
|
|
185
|
+
|
|
186
|
+
results[level] = {
|
|
187
|
+
duration: endTime - startTime,
|
|
188
|
+
inputSize: largeJsContent.length,
|
|
189
|
+
outputSize: minifiedContent.length,
|
|
190
|
+
compressionRatio: minifiedContent.length / largeJsContent.length
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Log results
|
|
195
|
+
console.log('Minification Performance Results:');
|
|
196
|
+
Object.entries(results).forEach(([level, result]) => {
|
|
197
|
+
console.log(`${level}: ${result.duration}ms, ${result.inputSize} → ${result.outputSize} bytes (${(result.compressionRatio * 100).toFixed(1)}%)`);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Performance should be reasonable
|
|
201
|
+
Object.values(results).forEach(result => {
|
|
202
|
+
assert(result.duration < 15000, 'Minification should complete within reasonable time');
|
|
203
|
+
assert(result.compressionRatio < 1, 'Minification should reduce size');
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('should benchmark advanced bundling with CSS extraction', async function() {
|
|
208
|
+
const bundler = new Advanced_JS_Bundler_Using_ESBuild({
|
|
209
|
+
debug: false,
|
|
210
|
+
bundler: {
|
|
211
|
+
minify: { enabled: true, level: 'normal' }
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const startTime = Date.now();
|
|
216
|
+
try {
|
|
217
|
+
const result = await bundler.bundle(testJsFile);
|
|
218
|
+
const endTime = Date.now();
|
|
219
|
+
|
|
220
|
+
const duration = endTime - startTime;
|
|
221
|
+
const bundle = result[0];
|
|
222
|
+
const jsItem = bundle._arr.find(item => item.type === 'JavaScript');
|
|
223
|
+
const cssItem = bundle._arr.find(item => item.type === 'CSS');
|
|
224
|
+
|
|
225
|
+
console.log(`Advanced bundling: ${duration}ms`);
|
|
226
|
+
console.log(`JS: ${largeJsContent.length} → ${jsItem.text.length} bytes`);
|
|
227
|
+
if (cssItem) {
|
|
228
|
+
console.log(`CSS extracted: ${cssItem.text.length} bytes`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
assert(duration < 20000, 'Advanced bundling should complete within reasonable time');
|
|
232
|
+
assert(jsItem, 'Should produce JavaScript bundle');
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.log(`Advanced bundling failed: ${error.message}`);
|
|
235
|
+
// Skip this test if advanced bundling fails
|
|
236
|
+
this.skip();
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should measure concurrent bundling performance', async function() {
|
|
241
|
+
const bundler = new Core_JS_Non_Minifying_Bundler_Using_ESBuild();
|
|
242
|
+
const concurrentBundles = 5;
|
|
243
|
+
|
|
244
|
+
const startTime = Date.now();
|
|
245
|
+
const promises = [];
|
|
246
|
+
|
|
247
|
+
for (let i = 0; i < concurrentBundles; i++) {
|
|
248
|
+
promises.push(bundler.bundle_js_string(mediumJsContent));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
await Promise.all(promises);
|
|
252
|
+
const endTime = Date.now();
|
|
253
|
+
|
|
254
|
+
const totalDuration = endTime - startTime;
|
|
255
|
+
const avgDuration = totalDuration / concurrentBundles;
|
|
256
|
+
|
|
257
|
+
console.log(`Concurrent bundling (${concurrentBundles} bundles): ${totalDuration}ms total, ${avgDuration.toFixed(2)}ms average`);
|
|
258
|
+
|
|
259
|
+
assert(totalDuration < 10000, 'Concurrent bundling should be efficient');
|
|
260
|
+
assert(avgDuration < 3000, 'Average bundling time should be reasonable');
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe('Compression Performance Benchmarks', function() {
|
|
265
|
+
let testItems;
|
|
266
|
+
|
|
267
|
+
beforeEach(function() {
|
|
268
|
+
// Create test items of different sizes and types
|
|
269
|
+
testItems = [
|
|
270
|
+
{
|
|
271
|
+
type: 'Small HTML',
|
|
272
|
+
extension: 'html',
|
|
273
|
+
text: '<!DOCTYPE html><html><body><h1>Small</h1></body></html>',
|
|
274
|
+
response_buffers: {}
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
type: 'Medium HTML',
|
|
278
|
+
extension: 'html',
|
|
279
|
+
text: '<!DOCTYPE html><html><head><title>Medium</title></head><body><h1>Medium</h1><p>' + 'Content '.repeat(500) + '</p></body></html>',
|
|
280
|
+
response_buffers: {}
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
type: 'Large HTML',
|
|
284
|
+
extension: 'html',
|
|
285
|
+
text: '<!DOCTYPE html><html><head><title>Large</title></head><body><h1>Large</h1><p>' + 'Content '.repeat(2000) + '</p></body></html>',
|
|
286
|
+
response_buffers: {}
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
type: 'JavaScript',
|
|
290
|
+
extension: 'js',
|
|
291
|
+
text: largeJsContent,
|
|
292
|
+
response_buffers: {}
|
|
293
|
+
}
|
|
294
|
+
];
|
|
295
|
+
|
|
296
|
+
// Initialize identity buffers
|
|
297
|
+
testItems.forEach(item => {
|
|
298
|
+
item.response_buffers.identity = Buffer.from(item.text, 'utf8');
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('should benchmark gzip compression performance', async function() {
|
|
303
|
+
const assigner = new Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner({
|
|
304
|
+
compression: {
|
|
305
|
+
enabled: true,
|
|
306
|
+
algorithms: ['gzip'],
|
|
307
|
+
gzip: { level: 6 }
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
const startTime = Date.now();
|
|
312
|
+
await assigner.assign(testItems);
|
|
313
|
+
const endTime = Date.now();
|
|
314
|
+
|
|
315
|
+
const duration = endTime - startTime;
|
|
316
|
+
|
|
317
|
+
console.log(`Gzip compression performance: ${duration}ms for ${testItems.length} items`);
|
|
318
|
+
|
|
319
|
+
// Calculate total sizes
|
|
320
|
+
const totalOriginal = testItems.reduce((sum, item) => sum + item.response_buffers.identity.length, 0);
|
|
321
|
+
const totalCompressed = testItems.reduce((sum, item) => item.response_buffers.gzip ? sum + item.response_buffers.gzip.length : sum, 0);
|
|
322
|
+
const avgCompressionRatio = totalCompressed / totalOriginal;
|
|
323
|
+
|
|
324
|
+
console.log(`Total: ${totalOriginal} → ${totalCompressed} bytes (${(avgCompressionRatio * 100).toFixed(1)}%)`);
|
|
325
|
+
|
|
326
|
+
assert(duration < 5000, 'Gzip compression should be fast');
|
|
327
|
+
assert(avgCompressionRatio < 0.8, 'Should achieve reasonable compression');
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('should benchmark brotli compression performance', async function() {
|
|
331
|
+
const assigner = new Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner({
|
|
332
|
+
compression: {
|
|
333
|
+
enabled: true,
|
|
334
|
+
algorithms: ['br'],
|
|
335
|
+
brotli: { quality: 6 }
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const startTime = Date.now();
|
|
340
|
+
await assigner.assign(testItems);
|
|
341
|
+
const endTime = Date.now();
|
|
342
|
+
|
|
343
|
+
const duration = endTime - startTime;
|
|
344
|
+
|
|
345
|
+
console.log(`Brotli compression performance: ${duration}ms for ${testItems.length} items`);
|
|
346
|
+
|
|
347
|
+
// Calculate total sizes
|
|
348
|
+
const totalOriginal = testItems.reduce((sum, item) => sum + item.response_buffers.identity.length, 0);
|
|
349
|
+
const totalCompressed = testItems.reduce((sum, item) => item.response_buffers.br ? sum + item.response_buffers.br.length : sum, 0);
|
|
350
|
+
const avgCompressionRatio = totalCompressed / totalOriginal;
|
|
351
|
+
|
|
352
|
+
console.log(`Total: ${totalOriginal} → ${totalCompressed} bytes (${(avgCompressionRatio * 100).toFixed(1)}%)`);
|
|
353
|
+
|
|
354
|
+
assert(duration < 10000, 'Brotli compression should complete reasonably quickly');
|
|
355
|
+
assert(avgCompressionRatio < 0.8, 'Should achieve good compression');
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('should compare gzip vs brotli performance', async function() {
|
|
359
|
+
const gzipAssigner = new Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner({
|
|
360
|
+
compression: {
|
|
361
|
+
enabled: true,
|
|
362
|
+
algorithms: ['gzip']
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
const brotliAssigner = new Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner({
|
|
367
|
+
compression: {
|
|
368
|
+
enabled: true,
|
|
369
|
+
algorithms: ['br']
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// Test gzip
|
|
374
|
+
const gzipStart = Date.now();
|
|
375
|
+
await gzipAssigner.assign(testItems);
|
|
376
|
+
const gzipEnd = Date.now();
|
|
377
|
+
|
|
378
|
+
// Test brotli
|
|
379
|
+
const brotliStart = Date.now();
|
|
380
|
+
await brotliAssigner.assign(testItems);
|
|
381
|
+
const brotliEnd = Date.now();
|
|
382
|
+
|
|
383
|
+
const gzipDuration = gzipEnd - gzipStart;
|
|
384
|
+
const brotliDuration = brotliEnd - brotliStart;
|
|
385
|
+
|
|
386
|
+
console.log(`Gzip: ${gzipDuration}ms, Brotli: ${brotliDuration}ms`);
|
|
387
|
+
|
|
388
|
+
// Calculate compression ratios
|
|
389
|
+
const totalOriginal = testItems.reduce((sum, item) => sum + item.response_buffers.identity.length, 0);
|
|
390
|
+
const gzipTotal = testItems.reduce((sum, item) => item.response_buffers.gzip ? sum + item.response_buffers.gzip.length : sum, 0);
|
|
391
|
+
const brotliTotal = testItems.reduce((sum, item) => item.response_buffers.br ? sum + item.response_buffers.br.length : sum, 0);
|
|
392
|
+
|
|
393
|
+
console.log(`Gzip ratio: ${(gzipTotal / totalOriginal * 100).toFixed(1)}%`);
|
|
394
|
+
console.log(`Brotli ratio: ${(brotliTotal / totalOriginal * 100).toFixed(1)}%`);
|
|
395
|
+
|
|
396
|
+
// Brotli typically takes longer but compresses better
|
|
397
|
+
assert(gzipDuration > 0 && brotliDuration > 0, 'Both algorithms should take some time');
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it('should test compression threshold performance impact', async function() {
|
|
401
|
+
const thresholds = [0, 512, 2048, 8192]; // Different threshold sizes
|
|
402
|
+
const results = {};
|
|
403
|
+
|
|
404
|
+
for (const threshold of thresholds) {
|
|
405
|
+
const assigner = new Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner({
|
|
406
|
+
compression: {
|
|
407
|
+
enabled: true,
|
|
408
|
+
algorithms: ['gzip'],
|
|
409
|
+
threshold: threshold
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
const startTime = Date.now();
|
|
414
|
+
await assigner.assign(testItems);
|
|
415
|
+
const endTime = Date.now();
|
|
416
|
+
|
|
417
|
+
const compressedCount = testItems.filter(item => item.response_buffers.gzip).length;
|
|
418
|
+
|
|
419
|
+
results[threshold] = {
|
|
420
|
+
duration: endTime - startTime,
|
|
421
|
+
compressedCount: compressedCount,
|
|
422
|
+
totalItems: testItems.length
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
console.log('Compression threshold performance:');
|
|
427
|
+
Object.entries(results).forEach(([threshold, result]) => {
|
|
428
|
+
console.log(`Threshold ${threshold}: ${result.duration}ms, ${result.compressedCount}/${result.totalItems} compressed`);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// Lower thresholds should result in more compression
|
|
432
|
+
assert(results[0].compressedCount >= results[8192].compressedCount,
|
|
433
|
+
'Lower threshold should compress more items');
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
describe('End-to-End Server Performance', function() {
|
|
438
|
+
let server;
|
|
439
|
+
let serverPort = 3002;
|
|
440
|
+
|
|
441
|
+
after(async function() {
|
|
442
|
+
if (server) {
|
|
443
|
+
await server.stop();
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it('should measure server startup performance', async function() {
|
|
448
|
+
const startTime = Date.now();
|
|
449
|
+
|
|
450
|
+
server = new Server();
|
|
451
|
+
try {
|
|
452
|
+
await server.serve({
|
|
453
|
+
ctrl: class TestControl {
|
|
454
|
+
all_html_render() {
|
|
455
|
+
return Promise.resolve(`<!DOCTYPE html>
|
|
456
|
+
<html>
|
|
457
|
+
<head><title>Test</title></head>
|
|
458
|
+
<body><h1>Test Control</h1></body>
|
|
459
|
+
</html>`);
|
|
460
|
+
}
|
|
461
|
+
},
|
|
462
|
+
port: serverPort,
|
|
463
|
+
debug: false,
|
|
464
|
+
bundler: {
|
|
465
|
+
minify: { enabled: true },
|
|
466
|
+
compression: { enabled: true }
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
const endTime = Date.now();
|
|
471
|
+
const startupTime = endTime - startTime;
|
|
472
|
+
|
|
473
|
+
console.log(`Server startup time: ${startupTime}ms`);
|
|
474
|
+
|
|
475
|
+
assert(startupTime < 10000, 'Server should start within reasonable time');
|
|
476
|
+
|
|
477
|
+
// Clean up
|
|
478
|
+
await server.stop();
|
|
479
|
+
server = null;
|
|
480
|
+
} catch (error) {
|
|
481
|
+
console.log(`Server startup failed: ${error.message}`);
|
|
482
|
+
// Skip this test if server startup fails
|
|
483
|
+
this.skip();
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it('should benchmark response times for different configurations', async function() {
|
|
488
|
+
const configurations = [
|
|
489
|
+
{ name: 'No compression', compression: false, minify: false },
|
|
490
|
+
{ name: 'Gzip only', compression: { enabled: true, algorithms: ['gzip'] }, minify: false },
|
|
491
|
+
{ name: 'Brotli only', compression: { enabled: true, algorithms: ['br'] }, minify: false },
|
|
492
|
+
{ name: 'Full optimization', compression: { enabled: true, algorithms: ['gzip', 'br'] }, minify: true }
|
|
493
|
+
];
|
|
494
|
+
|
|
495
|
+
const results = {};
|
|
496
|
+
|
|
497
|
+
for (const config of configurations) {
|
|
498
|
+
server = new Server();
|
|
499
|
+
try {
|
|
500
|
+
await server.serve({
|
|
501
|
+
ctrl: class TestControl {
|
|
502
|
+
all_html_render() {
|
|
503
|
+
return Promise.resolve(`<!DOCTYPE html>
|
|
504
|
+
<html>
|
|
505
|
+
<head><title>Test</title></head>
|
|
506
|
+
<body>
|
|
507
|
+
<h1>Test Control</h1>
|
|
508
|
+
<script>
|
|
509
|
+
// Large script to test compression
|
|
510
|
+
const data = [];
|
|
511
|
+
for (let i = 0; i < 1000; i++) {
|
|
512
|
+
data.push({ id: i, value: Math.random(), name: 'Item' + i });
|
|
513
|
+
}
|
|
514
|
+
console.log('Data loaded:', data.length);
|
|
515
|
+
</script>
|
|
516
|
+
</body>
|
|
517
|
+
</html>`);
|
|
518
|
+
}
|
|
519
|
+
},
|
|
520
|
+
port: serverPort,
|
|
521
|
+
debug: false,
|
|
522
|
+
bundler: {
|
|
523
|
+
minify: config.minify ? { enabled: true, level: 'normal' } : { enabled: false },
|
|
524
|
+
compression: config.compression
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
// Wait for server to be ready
|
|
529
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
530
|
+
|
|
531
|
+
// Measure response time
|
|
532
|
+
const responseStart = Date.now();
|
|
533
|
+
const response = await makeRequest(`http://localhost:${serverPort}/`, {
|
|
534
|
+
'Accept-Encoding': config.name.includes('Gzip') ? 'gzip' :
|
|
535
|
+
config.name.includes('Brotli') ? 'br' : 'identity'
|
|
536
|
+
});
|
|
537
|
+
const responseEnd = Date.now();
|
|
538
|
+
|
|
539
|
+
results[config.name] = {
|
|
540
|
+
responseTime: responseEnd - responseStart,
|
|
541
|
+
statusCode: response.statusCode,
|
|
542
|
+
contentLength: response.body.length,
|
|
543
|
+
contentEncoding: response.headers['content-encoding']
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
await server.stop();
|
|
547
|
+
server = null;
|
|
548
|
+
} catch (error) {
|
|
549
|
+
console.log(`Configuration ${config.name} failed: ${error.message}`);
|
|
550
|
+
results[config.name] = { error: error.message };
|
|
551
|
+
if (server) {
|
|
552
|
+
try {
|
|
553
|
+
await server.stop();
|
|
554
|
+
} catch (e) {
|
|
555
|
+
// Ignore cleanup errors
|
|
556
|
+
}
|
|
557
|
+
server = null;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
console.log('Server response performance:');
|
|
563
|
+
Object.entries(results).forEach(([name, result]) => {
|
|
564
|
+
if (result.error) {
|
|
565
|
+
console.log(`${name}: Failed - ${result.error}`);
|
|
566
|
+
} else {
|
|
567
|
+
console.log(`${name}: ${result.responseTime}ms, ${result.contentLength} bytes, encoding: ${result.contentEncoding || 'none'}`);
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
// Check successful responses
|
|
572
|
+
const successfulResults = Object.values(results).filter(result => !result.error);
|
|
573
|
+
successfulResults.forEach(result => {
|
|
574
|
+
assert.strictEqual(result.statusCode, 200, 'Successful responses should have status 200');
|
|
575
|
+
assert(result.responseTime < 5000, 'Successful responses should be reasonably fast');
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
describe('Memory Usage Analysis', function() {
|
|
581
|
+
it('should monitor memory usage during bundling operations', async function() {
|
|
582
|
+
const bundler = new Core_JS_Single_File_Minifying_Bundler_Using_ESBuild({
|
|
583
|
+
minify: { enabled: true, level: 'aggressive' }
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
const initialMemory = process.memoryUsage();
|
|
587
|
+
|
|
588
|
+
// Perform multiple bundling operations
|
|
589
|
+
for (let i = 0; i < 10; i++) {
|
|
590
|
+
await bundler.bundle(largeJsContent);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const finalMemory = process.memoryUsage();
|
|
594
|
+
|
|
595
|
+
const memoryIncrease = {
|
|
596
|
+
rss: finalMemory.rss - initialMemory.rss,
|
|
597
|
+
heapUsed: finalMemory.heapUsed - initialMemory.heapUsed,
|
|
598
|
+
heapTotal: finalMemory.heapTotal - initialMemory.heapTotal
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
console.log('Memory usage after bundling operations:');
|
|
602
|
+
console.log(`RSS: ${memoryIncrease.rss / 1024 / 1024} MB`);
|
|
603
|
+
console.log(`Heap Used: ${memoryIncrease.heapUsed / 1024 / 1024} MB`);
|
|
604
|
+
console.log(`Heap Total: ${memoryIncrease.heapTotal / 1024 / 1024} MB`);
|
|
605
|
+
|
|
606
|
+
// Memory usage should be reasonable
|
|
607
|
+
assert(memoryIncrease.heapUsed < 50 * 1024 * 1024, 'Memory usage should be reasonable (< 50MB increase)');
|
|
608
|
+
});
|
|
609
|
+
});
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
// Helper function for HTTP requests (simplified version for performance tests)
|
|
613
|
+
function makeRequest(url, headers = {}) {
|
|
614
|
+
return new Promise((resolve, reject) => {
|
|
615
|
+
const http = require('http');
|
|
616
|
+
const parsedUrl = new URL(url);
|
|
617
|
+
|
|
618
|
+
const options = {
|
|
619
|
+
hostname: parsedUrl.hostname,
|
|
620
|
+
port: parsedUrl.port,
|
|
621
|
+
path: parsedUrl.pathname,
|
|
622
|
+
method: 'GET',
|
|
623
|
+
headers: {
|
|
624
|
+
'User-Agent': 'JSGUI3-Performance-Test/1.0',
|
|
625
|
+
...headers
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
const req = http.request(options, (res) => {
|
|
630
|
+
let body = Buffer.alloc(0);
|
|
631
|
+
|
|
632
|
+
res.on('data', (chunk) => {
|
|
633
|
+
body = Buffer.concat([body, chunk]);
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
res.on('end', () => {
|
|
637
|
+
resolve({
|
|
638
|
+
statusCode: res.statusCode,
|
|
639
|
+
headers: res.headers,
|
|
640
|
+
body: body
|
|
641
|
+
});
|
|
642
|
+
});
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
req.on('error', reject);
|
|
646
|
+
req.setTimeout(10000, () => {
|
|
647
|
+
req.destroy();
|
|
648
|
+
reject(new Error('Request timeout'));
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
req.end();
|
|
652
|
+
});
|
|
653
|
+
}
|