mirror of
https://github.com/HeyPuter/puter.git
synced 2026-01-13 08:30:55 -06:00
Revert 2206 eric/25 cj0 more benchmarks (#2208)
* Revert "perf: more benchmarks (2)" This reverts commit2c60faf516. * Revert "perf: more benchmarks" This reverts commitd3c1fc3103.
This commit is contained in:
@@ -1,173 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2024-present Puter Technologies Inc.
|
||||
*
|
||||
* This file is part of Puter.
|
||||
*
|
||||
* Puter is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { bench, describe } from 'vitest';
|
||||
const { is_valid_path, is_valid_node_name } = require('./validation');
|
||||
|
||||
// Test data
|
||||
const shortPath = '/home/user/file.txt';
|
||||
const mediumPath = '/home/user/documents/projects/puter/src/backend/file.js';
|
||||
const longPath = '/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/file.txt';
|
||||
const deeplyNestedPath = `${Array(50).fill('directory').join('/') }/file.txt`;
|
||||
|
||||
const simpleFilename = 'document.pdf';
|
||||
const filenameWithSpaces = 'my document file.pdf';
|
||||
const filenameWithNumbers = 'report_2024_final_v2.xlsx';
|
||||
const maxLengthFilename = 'a'.repeat(255);
|
||||
|
||||
// Invalid paths for testing rejection speed
|
||||
const pathWithNull = '/home/user/\x00file.txt';
|
||||
const pathWithRTL = '/home/user/\u202Efile.txt';
|
||||
const pathWithLTR = '/home/user/\u200Efile.txt';
|
||||
|
||||
describe('is_valid_path - Valid paths', () => {
|
||||
bench('short path (/home/user/file.txt)', () => {
|
||||
is_valid_path(shortPath);
|
||||
});
|
||||
|
||||
bench('medium path (~50 chars)', () => {
|
||||
is_valid_path(mediumPath);
|
||||
});
|
||||
|
||||
bench('long path (26 components)', () => {
|
||||
is_valid_path(longPath);
|
||||
});
|
||||
|
||||
bench('deeply nested path (50 components)', () => {
|
||||
is_valid_path(`/${ deeplyNestedPath}`);
|
||||
});
|
||||
|
||||
bench('relative path starting with dot', () => {
|
||||
is_valid_path('./relative/path/to/file.txt');
|
||||
});
|
||||
});
|
||||
|
||||
describe('is_valid_path - With options', () => {
|
||||
bench('with no_relative_components option', () => {
|
||||
is_valid_path(mediumPath, { no_relative_components: true });
|
||||
});
|
||||
|
||||
bench('with allow_path_fragment option', () => {
|
||||
is_valid_path('partial/path/fragment', { allow_path_fragment: true });
|
||||
});
|
||||
|
||||
bench('with both options', () => {
|
||||
is_valid_path(shortPath, { no_relative_components: true, allow_path_fragment: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('is_valid_path - Invalid paths (rejection speed)', () => {
|
||||
bench('path with null character', () => {
|
||||
is_valid_path(pathWithNull);
|
||||
});
|
||||
|
||||
bench('path with RTL override', () => {
|
||||
is_valid_path(pathWithRTL);
|
||||
});
|
||||
|
||||
bench('path with LTR mark', () => {
|
||||
is_valid_path(pathWithLTR);
|
||||
});
|
||||
|
||||
bench('empty string', () => {
|
||||
is_valid_path('');
|
||||
});
|
||||
|
||||
bench('non-string input (number)', () => {
|
||||
is_valid_path(12345);
|
||||
});
|
||||
|
||||
bench('path not starting with / or .', () => {
|
||||
is_valid_path('invalid/path/start');
|
||||
});
|
||||
});
|
||||
|
||||
describe('is_valid_node_name - Valid names', () => {
|
||||
bench('simple filename', () => {
|
||||
is_valid_node_name(simpleFilename);
|
||||
});
|
||||
|
||||
bench('filename with spaces', () => {
|
||||
is_valid_node_name(filenameWithSpaces);
|
||||
});
|
||||
|
||||
bench('filename with numbers and underscores', () => {
|
||||
is_valid_node_name(filenameWithNumbers);
|
||||
});
|
||||
|
||||
bench('filename at max length (255 chars)', () => {
|
||||
is_valid_node_name(maxLengthFilename);
|
||||
});
|
||||
|
||||
bench('filename with multiple extensions', () => {
|
||||
is_valid_node_name('archive.tar.gz');
|
||||
});
|
||||
});
|
||||
|
||||
describe('is_valid_node_name - Invalid names (rejection speed)', () => {
|
||||
bench('name with forward slash', () => {
|
||||
is_valid_node_name('invalid/name');
|
||||
});
|
||||
|
||||
bench('name with null character', () => {
|
||||
is_valid_node_name('invalid\x00name');
|
||||
});
|
||||
|
||||
bench('single dot (.)', () => {
|
||||
is_valid_node_name('.');
|
||||
});
|
||||
|
||||
bench('double dot (..)', () => {
|
||||
is_valid_node_name('..');
|
||||
});
|
||||
|
||||
bench('only dots (...)', () => {
|
||||
is_valid_node_name('...');
|
||||
});
|
||||
|
||||
bench('name exceeding max length', () => {
|
||||
is_valid_node_name('a'.repeat(300));
|
||||
});
|
||||
|
||||
bench('non-string input', () => {
|
||||
is_valid_node_name(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('is_valid_path - Batch validation simulation', () => {
|
||||
const paths = [
|
||||
'/home/user/file1.txt',
|
||||
'/home/user/file2.txt',
|
||||
'/home/user/documents/report.pdf',
|
||||
'/var/log/system.log',
|
||||
'/etc/config.json',
|
||||
];
|
||||
|
||||
bench('validate 5 paths sequentially', () => {
|
||||
for ( const path of paths ) {
|
||||
is_valid_path(path);
|
||||
}
|
||||
});
|
||||
|
||||
bench('validate 100 paths', () => {
|
||||
for ( let i = 0; i < 100; i++ ) {
|
||||
is_valid_path(paths[i % paths.length]);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,182 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2024-present Puter Technologies Inc.
|
||||
*
|
||||
* This file is part of Puter.
|
||||
*
|
||||
* Puter is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { bench, describe } from 'vitest';
|
||||
const { FileTracker } = require('./FileTracker');
|
||||
|
||||
// Helper to create a tracker with some access history
|
||||
const createTrackerWithHistory = (accessCount) => {
|
||||
const tracker = new FileTracker({ key: 'test-key', size: 1024 });
|
||||
for ( let i = 0; i < accessCount; i++ ) {
|
||||
tracker.touch();
|
||||
}
|
||||
return tracker;
|
||||
};
|
||||
|
||||
describe('FileTracker - Construction', () => {
|
||||
bench('create new FileTracker', () => {
|
||||
new FileTracker({ key: `test-key-${ Math.random()}`, size: 1024 });
|
||||
});
|
||||
|
||||
bench('create multiple FileTrackers', () => {
|
||||
for ( let i = 0; i < 100; i++ ) {
|
||||
new FileTracker({ key: `key-${i}`, size: i * 100 });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('FileTracker - touch() operation', () => {
|
||||
bench('touch() on new tracker', () => {
|
||||
const tracker = new FileTracker({ key: 'test', size: 1024 });
|
||||
for ( let i = 0; i < 1000; i++ ) {
|
||||
tracker.touch();
|
||||
}
|
||||
});
|
||||
|
||||
bench('touch() with EWMA calculation', () => {
|
||||
const tracker = new FileTracker({ key: 'test', size: 1024 });
|
||||
// Pre-warm with some touches
|
||||
for ( let i = 0; i < 10; i++ ) {
|
||||
tracker.touch();
|
||||
}
|
||||
// Benchmark steady-state touches
|
||||
for ( let i = 0; i < 1000; i++ ) {
|
||||
tracker.touch();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('FileTracker - score calculation', () => {
|
||||
bench('score on fresh tracker', () => {
|
||||
const tracker = new FileTracker({ key: 'test', size: 1024 });
|
||||
tracker.touch(); // Need at least one touch for meaningful score
|
||||
for ( let i = 0; i < 1000; i++ ) {
|
||||
void tracker.score;
|
||||
}
|
||||
});
|
||||
|
||||
bench('score on tracker with history (10 accesses)', () => {
|
||||
const tracker = createTrackerWithHistory(10);
|
||||
for ( let i = 0; i < 1000; i++ ) {
|
||||
void tracker.score;
|
||||
}
|
||||
});
|
||||
|
||||
bench('score on tracker with history (100 accesses)', () => {
|
||||
const tracker = createTrackerWithHistory(100);
|
||||
for ( let i = 0; i < 1000; i++ ) {
|
||||
void tracker.score;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('FileTracker - age calculation', () => {
|
||||
bench('age getter', () => {
|
||||
const tracker = new FileTracker({ key: 'test', size: 1024 });
|
||||
for ( let i = 0; i < 10000; i++ ) {
|
||||
void tracker.age;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('FileTracker - Cache eviction simulation', () => {
|
||||
bench('compare scores of multiple trackers', () => {
|
||||
// Simulate cache with 100 items
|
||||
const trackers = [];
|
||||
for ( let i = 0; i < 100; i++ ) {
|
||||
const tracker = new FileTracker({ key: `file-${i}`, size: i * 100 });
|
||||
// Simulate varying access patterns
|
||||
const accessCount = Math.floor(Math.random() * 20);
|
||||
for ( let j = 0; j < accessCount; j++ ) {
|
||||
tracker.touch();
|
||||
}
|
||||
trackers.push(tracker);
|
||||
}
|
||||
|
||||
// Find lowest score (eviction candidate)
|
||||
for ( let i = 0; i < 100; i++ ) {
|
||||
let minScore = Infinity;
|
||||
let evictCandidate = null;
|
||||
for ( const tracker of trackers ) {
|
||||
const score = tracker.score;
|
||||
if ( score < minScore ) {
|
||||
minScore = score;
|
||||
evictCandidate = tracker;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
bench('sort trackers by score (eviction ordering)', () => {
|
||||
const trackers = [];
|
||||
for ( let i = 0; i < 50; i++ ) {
|
||||
const tracker = new FileTracker({ key: `file-${i}`, size: i * 100 });
|
||||
for ( let j = 0; j < i % 10; j++ ) {
|
||||
tracker.touch();
|
||||
}
|
||||
trackers.push(tracker);
|
||||
}
|
||||
|
||||
// Sort by score
|
||||
for ( let i = 0; i < 10; i++ ) {
|
||||
[...trackers].sort((a, b) => a.score - b.score);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('FileTracker - Real-world access patterns', () => {
|
||||
bench('hot file pattern (frequent access)', () => {
|
||||
const tracker = new FileTracker({ key: 'hot-file', size: 1024 });
|
||||
for ( let i = 0; i < 1000; i++ ) {
|
||||
tracker.touch();
|
||||
if ( i % 10 === 0 ) {
|
||||
void tracker.score;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
bench('cold file pattern (rare access)', () => {
|
||||
const tracker = new FileTracker({ key: 'cold-file', size: 1024 });
|
||||
tracker.touch();
|
||||
for ( let i = 0; i < 1000; i++ ) {
|
||||
void tracker.score;
|
||||
void tracker.age;
|
||||
}
|
||||
});
|
||||
|
||||
bench('mixed access with score checks', () => {
|
||||
const trackers = [];
|
||||
for ( let i = 0; i < 20; i++ ) {
|
||||
trackers.push(new FileTracker({ key: `file-${i}`, size: 1024 }));
|
||||
}
|
||||
|
||||
for ( let i = 0; i < 500; i++ ) {
|
||||
// Random access
|
||||
const idx = Math.floor(Math.random() * trackers.length);
|
||||
trackers[idx].touch();
|
||||
|
||||
// Periodic eviction check
|
||||
if ( i % 50 === 0 ) {
|
||||
for ( const t of trackers ) {
|
||||
void t.score;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,216 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2024-present Puter Technologies Inc.
|
||||
*
|
||||
* This file is part of Puter.
|
||||
*
|
||||
* Puter is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { bench, describe } from 'vitest';
|
||||
import { Context } from './context';
|
||||
|
||||
describe('Context - Creation', () => {
|
||||
bench('create empty context', () => {
|
||||
Context.create({});
|
||||
});
|
||||
|
||||
bench('create context with single value', () => {
|
||||
Context.create({ user: 'testuser' });
|
||||
});
|
||||
|
||||
bench('create context with multiple values', () => {
|
||||
Context.create({
|
||||
user: 'testuser',
|
||||
requestId: '12345',
|
||||
timestamp: Date.now(),
|
||||
metadata: { key: 'value' },
|
||||
});
|
||||
});
|
||||
|
||||
bench('create 100 contexts', () => {
|
||||
for ( let i = 0; i < 100; i++ ) {
|
||||
Context.create({ index: i });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Context - Sub-context creation', () => {
|
||||
const parentContext = Context.create({ parent: 'value' });
|
||||
|
||||
bench('create sub-context (empty)', () => {
|
||||
parentContext.sub({});
|
||||
});
|
||||
|
||||
bench('create sub-context with values', () => {
|
||||
parentContext.sub({ child: 'childValue' });
|
||||
});
|
||||
|
||||
bench('create sub-context with name', () => {
|
||||
parentContext.sub({}, 'named-context');
|
||||
});
|
||||
|
||||
bench('create deeply nested sub-contexts (5 levels)', () => {
|
||||
let ctx = parentContext;
|
||||
for ( let i = 0; i < 5; i++ ) {
|
||||
ctx = ctx.sub({ level: i });
|
||||
}
|
||||
});
|
||||
|
||||
bench('create deeply nested sub-contexts (10 levels)', () => {
|
||||
let ctx = parentContext;
|
||||
for ( let i = 0; i < 10; i++ ) {
|
||||
ctx = ctx.sub({ level: i });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Context - Get/Set operations', () => {
|
||||
const ctx = Context.create({
|
||||
key1: 'value1',
|
||||
key2: 'value2',
|
||||
key3: { nested: 'object' },
|
||||
});
|
||||
|
||||
bench('get existing key', () => {
|
||||
ctx.get('key1');
|
||||
});
|
||||
|
||||
bench('get non-existing key', () => {
|
||||
ctx.get('nonexistent');
|
||||
});
|
||||
|
||||
bench('get nested object', () => {
|
||||
ctx.get('key3');
|
||||
});
|
||||
|
||||
bench('set new value', () => {
|
||||
ctx.set('dynamic', Math.random());
|
||||
});
|
||||
|
||||
bench('get/set cycle (100 operations)', () => {
|
||||
for ( let i = 0; i < 100; i++ ) {
|
||||
ctx.set(`key_${i}`, i);
|
||||
ctx.get(`key_${i}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Context - Prototype chain lookup', () => {
|
||||
// Create a deep context chain
|
||||
let deepCtx = Context.create({ root: 'rootValue' });
|
||||
for ( let i = 0; i < 10; i++ ) {
|
||||
deepCtx = deepCtx.sub({ [`level${i}`]: `value${i}` });
|
||||
}
|
||||
|
||||
bench('get value from root (10 levels up)', () => {
|
||||
deepCtx.get('root');
|
||||
});
|
||||
|
||||
bench('get value from middle (5 levels up)', () => {
|
||||
deepCtx.get('level5');
|
||||
});
|
||||
|
||||
bench('get value from current level', () => {
|
||||
deepCtx.get('level9');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Context - arun async execution', () => {
|
||||
const ctx = Context.create({ test: 'value' });
|
||||
|
||||
bench('arun with simple callback', async () => {
|
||||
await ctx.arun(async () => {
|
||||
return 'result';
|
||||
});
|
||||
});
|
||||
|
||||
bench('arun with Context.get inside', async () => {
|
||||
await ctx.arun(async () => {
|
||||
Context.get('test');
|
||||
return 'result';
|
||||
});
|
||||
});
|
||||
|
||||
bench('nested arun calls (3 levels)', async () => {
|
||||
await ctx.arun(async () => {
|
||||
const subCtx = Context.get().sub({ level: 1 });
|
||||
await subCtx.arun(async () => {
|
||||
const subSubCtx = Context.get().sub({ level: 2 });
|
||||
await subSubCtx.arun(async () => {
|
||||
return Context.get('level');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Context - abind', () => {
|
||||
const ctx = Context.create({ bound: 'value' });
|
||||
|
||||
bench('create bound function', () => {
|
||||
ctx.abind(() => 'result');
|
||||
});
|
||||
|
||||
bench('execute bound function', async () => {
|
||||
const boundFn = ctx.abind(async () => Context.get('bound'));
|
||||
await boundFn();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Context - describe/debug', () => {
|
||||
const ctx = Context.create({ test: 'value' }, 'test-context');
|
||||
const deepCtx = ctx.sub({ level: 1 }, 'sub1').sub({ level: 2 }, 'sub2');
|
||||
|
||||
bench('describe shallow context', () => {
|
||||
ctx.describe();
|
||||
});
|
||||
|
||||
bench('describe deep context', () => {
|
||||
deepCtx.describe();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Context - unlink (memory cleanup)', () => {
|
||||
bench('create and unlink context', () => {
|
||||
const ctx = Context.create({
|
||||
user: 'test',
|
||||
data: { large: 'object' },
|
||||
});
|
||||
ctx.unlink();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Context - Real-world simulation', () => {
|
||||
bench('HTTP request context lifecycle', async () => {
|
||||
// Simulate creating a context for an HTTP request
|
||||
const reqCtx = Context.create({
|
||||
req: { method: 'GET', path: '/api/test' },
|
||||
res: {},
|
||||
trace_request: 'uuid-here',
|
||||
}, 'req');
|
||||
|
||||
await reqCtx.arun(async () => {
|
||||
// Simulate middleware adding data
|
||||
const ctx = Context.get();
|
||||
ctx.set('user', { id: 1, name: 'test' });
|
||||
|
||||
// Simulate sub-operation
|
||||
const opCtx = ctx.sub({ operation: 'readFile' });
|
||||
await opCtx.arun(async () => {
|
||||
Context.get('user');
|
||||
Context.get('operation');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,165 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2024-present Puter Technologies Inc.
|
||||
*
|
||||
* This file is part of Puter.
|
||||
*
|
||||
* Puter is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { bench, describe } from 'vitest';
|
||||
const { stringify_serializable_object, hash_serializable_object } = require('./datautil');
|
||||
|
||||
// Test data generators
|
||||
const createFlatObject = (size) => {
|
||||
const obj = {};
|
||||
for ( let i = 0; i < size; i++ ) {
|
||||
obj[`key${i}`] = `value${i}`;
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
const createNestedObject = (depth, breadth) => {
|
||||
if ( depth === 0 ) {
|
||||
return { leaf: 'value' };
|
||||
}
|
||||
const obj = {};
|
||||
for ( let i = 0; i < breadth; i++ ) {
|
||||
obj[`level${depth}_child${i}`] = createNestedObject(depth - 1, breadth);
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
const createMixedObject = () => ({
|
||||
string: 'hello world',
|
||||
number: 42,
|
||||
boolean: true,
|
||||
null: null,
|
||||
array: [1, 2, 3, { nested: 'array' }],
|
||||
nested: {
|
||||
deep: {
|
||||
value: 'found',
|
||||
numbers: [1, 2, 3],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Objects with different key orderings (should produce same hash)
|
||||
const objA = { z: 1, a: 2, m: 3 };
|
||||
const objB = { a: 2, m: 3, z: 1 };
|
||||
const objC = { m: 3, z: 1, a: 2 };
|
||||
|
||||
describe('stringify_serializable_object - Flat objects', () => {
|
||||
const small = createFlatObject(5);
|
||||
const medium = createFlatObject(20);
|
||||
const large = createFlatObject(100);
|
||||
|
||||
bench('small flat object (5 keys)', () => {
|
||||
stringify_serializable_object(small);
|
||||
});
|
||||
|
||||
bench('medium flat object (20 keys)', () => {
|
||||
stringify_serializable_object(medium);
|
||||
});
|
||||
|
||||
bench('large flat object (100 keys)', () => {
|
||||
stringify_serializable_object(large);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stringify_serializable_object - Nested objects', () => {
|
||||
const shallow = createNestedObject(2, 3); // depth 2, 3 children each
|
||||
const medium = createNestedObject(3, 3); // depth 3, 3 children each
|
||||
const deep = createNestedObject(4, 2); // depth 4, 2 children each
|
||||
|
||||
bench('shallow nested (depth=2, breadth=3)', () => {
|
||||
stringify_serializable_object(shallow);
|
||||
});
|
||||
|
||||
bench('medium nested (depth=3, breadth=3)', () => {
|
||||
stringify_serializable_object(medium);
|
||||
});
|
||||
|
||||
bench('deep nested (depth=4, breadth=2)', () => {
|
||||
stringify_serializable_object(deep);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stringify_serializable_object - Mixed types', () => {
|
||||
const mixed = createMixedObject();
|
||||
|
||||
bench('mixed type object', () => {
|
||||
stringify_serializable_object(mixed);
|
||||
});
|
||||
|
||||
bench('primitives', () => {
|
||||
stringify_serializable_object('string');
|
||||
stringify_serializable_object(42);
|
||||
stringify_serializable_object(true);
|
||||
stringify_serializable_object(null);
|
||||
stringify_serializable_object(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stringify_serializable_object - Key ordering normalization', () => {
|
||||
bench('objects with different key orderings', () => {
|
||||
// All should produce the same output
|
||||
stringify_serializable_object(objA);
|
||||
stringify_serializable_object(objB);
|
||||
stringify_serializable_object(objC);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stringify_serializable_object vs JSON.stringify', () => {
|
||||
const obj = createFlatObject(20);
|
||||
|
||||
bench('stringify_serializable_object', () => {
|
||||
stringify_serializable_object(obj);
|
||||
});
|
||||
|
||||
bench('JSON.stringify (baseline, no key sorting)', () => {
|
||||
JSON.stringify(obj);
|
||||
});
|
||||
|
||||
bench('JSON.stringify with sorted keys (manual)', () => {
|
||||
const sortedObj = {};
|
||||
Object.keys(obj).sort().forEach(k => {
|
||||
sortedObj[k] = obj[k];
|
||||
});
|
||||
JSON.stringify(sortedObj);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hash_serializable_object', () => {
|
||||
const small = createFlatObject(5);
|
||||
const medium = createFlatObject(20);
|
||||
const mixed = createMixedObject();
|
||||
|
||||
bench('hash small object', () => {
|
||||
hash_serializable_object(small);
|
||||
});
|
||||
|
||||
bench('hash medium object', () => {
|
||||
hash_serializable_object(medium);
|
||||
});
|
||||
|
||||
bench('hash mixed object', () => {
|
||||
hash_serializable_object(mixed);
|
||||
});
|
||||
|
||||
bench('hash objects with different key orderings (should be equal)', () => {
|
||||
hash_serializable_object(objA);
|
||||
hash_serializable_object(objB);
|
||||
hash_serializable_object(objC);
|
||||
});
|
||||
});
|
||||
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2024-present Puter Technologies Inc.
|
||||
*
|
||||
* This file is part of Puter.
|
||||
*
|
||||
* Puter is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { bench, describe } from 'vitest';
|
||||
const { generate_identifier, generate_random_code } = require('./identifier');
|
||||
|
||||
describe('generate_identifier - Basic generation', () => {
|
||||
bench('generate single identifier (default separator)', () => {
|
||||
generate_identifier();
|
||||
});
|
||||
|
||||
bench('generate identifier with hyphen separator', () => {
|
||||
generate_identifier('-');
|
||||
});
|
||||
|
||||
bench('generate identifier with empty separator', () => {
|
||||
generate_identifier('');
|
||||
});
|
||||
|
||||
bench('generate 100 identifiers', () => {
|
||||
for ( let i = 0; i < 100; i++ ) {
|
||||
generate_identifier();
|
||||
}
|
||||
});
|
||||
|
||||
bench('generate 1000 identifiers', () => {
|
||||
for ( let i = 0; i < 1000; i++ ) {
|
||||
generate_identifier();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('generate_identifier - With custom RNG', () => {
|
||||
// Seeded pseudo-random for reproducibility
|
||||
const seededRng = () => {
|
||||
let seed = 12345;
|
||||
return () => {
|
||||
seed = (seed * 1103515245 + 12345) & 0x7fffffff;
|
||||
return seed / 0x7fffffff;
|
||||
};
|
||||
};
|
||||
|
||||
bench('generate with Math.random (default)', () => {
|
||||
generate_identifier('_', Math.random);
|
||||
});
|
||||
|
||||
bench('generate with seeded RNG', () => {
|
||||
const rng = seededRng();
|
||||
generate_identifier('_', rng);
|
||||
});
|
||||
});
|
||||
|
||||
describe('generate_random_code - Various lengths', () => {
|
||||
bench('generate 4-char code', () => {
|
||||
generate_random_code(4);
|
||||
});
|
||||
|
||||
bench('generate 8-char code', () => {
|
||||
generate_random_code(8);
|
||||
});
|
||||
|
||||
bench('generate 16-char code', () => {
|
||||
generate_random_code(16);
|
||||
});
|
||||
|
||||
bench('generate 32-char code', () => {
|
||||
generate_random_code(32);
|
||||
});
|
||||
|
||||
bench('generate 64-char code', () => {
|
||||
generate_random_code(64);
|
||||
});
|
||||
});
|
||||
|
||||
describe('generate_random_code - Custom character sets', () => {
|
||||
const numericOnly = '0123456789';
|
||||
const hexChars = '0123456789ABCDEF';
|
||||
const alphaLower = 'abcdefghijklmnopqrstuvwxyz';
|
||||
const fullAlphanumeric = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
bench('numeric only (10 chars)', () => {
|
||||
generate_random_code(10, { chars: numericOnly });
|
||||
});
|
||||
|
||||
bench('hex chars (16 chars)', () => {
|
||||
generate_random_code(16, { chars: hexChars });
|
||||
});
|
||||
|
||||
bench('lowercase alpha (10 chars)', () => {
|
||||
generate_random_code(10, { chars: alphaLower });
|
||||
});
|
||||
|
||||
bench('full alphanumeric (16 chars)', () => {
|
||||
generate_random_code(16, { chars: fullAlphanumeric });
|
||||
});
|
||||
});
|
||||
|
||||
describe('generate_random_code - Batch generation', () => {
|
||||
bench('generate 100 codes (8 chars each)', () => {
|
||||
for ( let i = 0; i < 100; i++ ) {
|
||||
generate_random_code(8);
|
||||
}
|
||||
});
|
||||
|
||||
bench('generate 1000 codes (8 chars each)', () => {
|
||||
for ( let i = 0; i < 1000; i++ ) {
|
||||
generate_random_code(8);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Comparison with alternatives', () => {
|
||||
bench('generate_identifier', () => {
|
||||
generate_identifier();
|
||||
});
|
||||
|
||||
bench('generate_random_code (8 chars)', () => {
|
||||
generate_random_code(8);
|
||||
});
|
||||
|
||||
bench('Math.random().toString(36).slice(2, 10)', () => {
|
||||
Math.random().toString(36).slice(2, 10);
|
||||
});
|
||||
|
||||
bench('Date.now().toString(36)', () => {
|
||||
Date.now().toString(36);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Real-world usage patterns', () => {
|
||||
bench('generate username suggestion', () => {
|
||||
// Pattern: adjective_noun_number
|
||||
generate_identifier('_');
|
||||
});
|
||||
|
||||
bench('generate session token (32 chars)', () => {
|
||||
generate_random_code(32);
|
||||
});
|
||||
|
||||
bench('generate verification code (6 chars, numeric)', () => {
|
||||
generate_random_code(6, { chars: '0123456789' });
|
||||
});
|
||||
|
||||
bench('generate file suffix (8 chars)', () => {
|
||||
generate_random_code(8);
|
||||
});
|
||||
});
|
||||
@@ -1,283 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2024-present Puter Technologies Inc.
|
||||
*
|
||||
* This file is part of Puter.
|
||||
*
|
||||
* Puter is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { bench, describe } from 'vitest';
|
||||
const { RWLock } = require('./lockutil');
|
||||
|
||||
describe('RWLock - Creation', () => {
|
||||
bench('create RWLock', () => {
|
||||
new RWLock();
|
||||
});
|
||||
|
||||
bench('create 100 RWLocks', () => {
|
||||
for ( let i = 0; i < 100; i++ ) {
|
||||
new RWLock();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('RWLock - Mode checking', () => {
|
||||
const lock = new RWLock();
|
||||
|
||||
bench('check effective_mode (idle)', () => {
|
||||
void lock.effective_mode;
|
||||
});
|
||||
});
|
||||
|
||||
describe('RWLock - Read locks (no contention)', () => {
|
||||
bench('single rlock/unlock cycle', async () => {
|
||||
const lock = new RWLock();
|
||||
const handle = await lock.rlock();
|
||||
handle.unlock();
|
||||
});
|
||||
|
||||
bench('10 sequential rlock/unlock cycles', async () => {
|
||||
const lock = new RWLock();
|
||||
for ( let i = 0; i < 10; i++ ) {
|
||||
const handle = await lock.rlock();
|
||||
handle.unlock();
|
||||
}
|
||||
});
|
||||
|
||||
bench('concurrent read locks (5 readers)', async () => {
|
||||
const lock = new RWLock();
|
||||
const handles = await Promise.all([
|
||||
lock.rlock(),
|
||||
lock.rlock(),
|
||||
lock.rlock(),
|
||||
lock.rlock(),
|
||||
lock.rlock(),
|
||||
]);
|
||||
for ( const handle of handles ) {
|
||||
handle.unlock();
|
||||
}
|
||||
});
|
||||
|
||||
bench('concurrent read locks (10 readers)', async () => {
|
||||
const lock = new RWLock();
|
||||
const promises = [];
|
||||
for ( let i = 0; i < 10; i++ ) {
|
||||
promises.push(lock.rlock());
|
||||
}
|
||||
const handles = await Promise.all(promises);
|
||||
for ( const handle of handles ) {
|
||||
handle.unlock();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('RWLock - Write locks (no contention)', () => {
|
||||
bench('single wlock/unlock cycle', async () => {
|
||||
const lock = new RWLock();
|
||||
const handle = await lock.wlock();
|
||||
handle.unlock();
|
||||
});
|
||||
|
||||
bench('10 sequential wlock/unlock cycles', async () => {
|
||||
const lock = new RWLock();
|
||||
for ( let i = 0; i < 10; i++ ) {
|
||||
const handle = await lock.wlock();
|
||||
handle.unlock();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('RWLock - Mixed read/write patterns', () => {
|
||||
bench('read then write then read', async () => {
|
||||
const lock = new RWLock();
|
||||
|
||||
const r1 = await lock.rlock();
|
||||
r1.unlock();
|
||||
|
||||
const w = await lock.wlock();
|
||||
w.unlock();
|
||||
|
||||
const r2 = await lock.rlock();
|
||||
r2.unlock();
|
||||
});
|
||||
|
||||
bench('write then multiple reads', async () => {
|
||||
const lock = new RWLock();
|
||||
|
||||
const w = await lock.wlock();
|
||||
w.unlock();
|
||||
|
||||
const handles = await Promise.all([
|
||||
lock.rlock(),
|
||||
lock.rlock(),
|
||||
lock.rlock(),
|
||||
]);
|
||||
for ( const h of handles ) {
|
||||
h.unlock();
|
||||
}
|
||||
});
|
||||
|
||||
bench('alternating read/write (10 cycles)', async () => {
|
||||
const lock = new RWLock();
|
||||
for ( let i = 0; i < 10; i++ ) {
|
||||
if ( i % 2 === 0 ) {
|
||||
const h = await lock.rlock();
|
||||
h.unlock();
|
||||
} else {
|
||||
const h = await lock.wlock();
|
||||
h.unlock();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('RWLock - Contention patterns', () => {
|
||||
bench('readers waiting for writer', async () => {
|
||||
const lock = new RWLock();
|
||||
|
||||
// Writer goes first
|
||||
const writePromise = (async () => {
|
||||
const h = await lock.wlock();
|
||||
// Simulate work
|
||||
h.unlock();
|
||||
})();
|
||||
|
||||
// Readers queue up
|
||||
const readerPromises = [];
|
||||
for ( let i = 0; i < 5; i++ ) {
|
||||
readerPromises.push((async () => {
|
||||
const h = await lock.rlock();
|
||||
h.unlock();
|
||||
})());
|
||||
}
|
||||
|
||||
await Promise.all([writePromise, ...readerPromises]);
|
||||
});
|
||||
|
||||
bench('writer waiting for readers', async () => {
|
||||
const lock = new RWLock();
|
||||
|
||||
// Readers go first
|
||||
const readerPromises = [];
|
||||
for ( let i = 0; i < 5; i++ ) {
|
||||
readerPromises.push((async () => {
|
||||
const h = await lock.rlock();
|
||||
h.unlock();
|
||||
})());
|
||||
}
|
||||
|
||||
// Writer queues up
|
||||
const writePromise = (async () => {
|
||||
const h = await lock.wlock();
|
||||
h.unlock();
|
||||
})();
|
||||
|
||||
await Promise.all([...readerPromises, writePromise]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('RWLock - Queue behavior', () => {
|
||||
bench('check_queue_ with empty queue', () => {
|
||||
const lock = new RWLock();
|
||||
lock.check_queue_();
|
||||
});
|
||||
});
|
||||
|
||||
describe('RWLock - on_empty_ callback', () => {
|
||||
bench('set on_empty_ callback', () => {
|
||||
const lock = new RWLock();
|
||||
lock.on_empty_ = () => {
|
||||
};
|
||||
});
|
||||
|
||||
bench('trigger on_empty_ via lock cycle', async () => {
|
||||
const lock = new RWLock();
|
||||
lock.on_empty_ = () => {
|
||||
};
|
||||
|
||||
const h = await lock.rlock();
|
||||
h.unlock();
|
||||
// on_empty_ should be called
|
||||
});
|
||||
});
|
||||
|
||||
describe('Real-world patterns', () => {
|
||||
bench('cache read pattern (10 concurrent readers)', async () => {
|
||||
const lock = new RWLock();
|
||||
const promises = [];
|
||||
|
||||
for ( let i = 0; i < 10; i++ ) {
|
||||
promises.push((async () => {
|
||||
const h = await lock.rlock();
|
||||
// Simulate cache read
|
||||
h.unlock();
|
||||
})());
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
});
|
||||
|
||||
bench('cache invalidation pattern', async () => {
|
||||
const lock = new RWLock();
|
||||
|
||||
// Some readers first
|
||||
const readerPromises = [];
|
||||
for ( let i = 0; i < 3; i++ ) {
|
||||
readerPromises.push((async () => {
|
||||
const h = await lock.rlock();
|
||||
h.unlock();
|
||||
})());
|
||||
}
|
||||
|
||||
// Invalidation (write)
|
||||
const invalidatePromise = (async () => {
|
||||
const h = await lock.wlock();
|
||||
// Simulate cache clear
|
||||
h.unlock();
|
||||
})();
|
||||
|
||||
// New readers after invalidation
|
||||
for ( let i = 0; i < 3; i++ ) {
|
||||
readerPromises.push((async () => {
|
||||
const h = await lock.rlock();
|
||||
h.unlock();
|
||||
})());
|
||||
}
|
||||
|
||||
await Promise.all([...readerPromises, invalidatePromise]);
|
||||
});
|
||||
|
||||
bench('file access pattern (mostly reads, occasional write)', async () => {
|
||||
const lock = new RWLock();
|
||||
const operations = [];
|
||||
|
||||
for ( let i = 0; i < 20; i++ ) {
|
||||
if ( i % 5 === 0 ) {
|
||||
// Write every 5th operation
|
||||
operations.push((async () => {
|
||||
const h = await lock.wlock();
|
||||
h.unlock();
|
||||
})());
|
||||
} else {
|
||||
// Read otherwise
|
||||
operations.push((async () => {
|
||||
const h = await lock.rlock();
|
||||
h.unlock();
|
||||
})());
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(operations);
|
||||
});
|
||||
});
|
||||
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2024-present Puter Technologies Inc.
|
||||
*
|
||||
* This file is part of Puter.
|
||||
*
|
||||
* Puter is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { bench, describe } from 'vitest';
|
||||
const { EWMA, MovingMode, TimeWindow, normalize } = require('./opmath');
|
||||
|
||||
describe('EWMA - Exponential Weighted Moving Average', () => {
|
||||
bench('EWMA put() with constant alpha', () => {
|
||||
const ewma = new EWMA({ initial: 0, alpha: 0.2 });
|
||||
for ( let i = 0; i < 1000; i++ ) {
|
||||
ewma.put(Math.random() * 100);
|
||||
}
|
||||
});
|
||||
|
||||
bench('EWMA put() with function alpha', () => {
|
||||
const ewma = new EWMA({ initial: 0, alpha: () => 0.2 });
|
||||
for ( let i = 0; i < 1000; i++ ) {
|
||||
ewma.put(Math.random() * 100);
|
||||
}
|
||||
});
|
||||
|
||||
bench('EWMA get() after many puts', () => {
|
||||
const ewma = new EWMA({ initial: 0, alpha: 0.2 });
|
||||
for ( let i = 0; i < 100; i++ ) {
|
||||
ewma.put(i);
|
||||
}
|
||||
for ( let i = 0; i < 1000; i++ ) {
|
||||
ewma.get();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('MovingMode - Mode calculation with sliding window', () => {
|
||||
bench('MovingMode put() with window_size=30', () => {
|
||||
const mode = new MovingMode({ initial: 0, window_size: 30 });
|
||||
for ( let i = 0; i < 1000; i++ ) {
|
||||
mode.put(Math.floor(Math.random() * 10));
|
||||
}
|
||||
});
|
||||
|
||||
bench('MovingMode put() with window_size=100', () => {
|
||||
const mode = new MovingMode({ initial: 0, window_size: 100 });
|
||||
for ( let i = 0; i < 1000; i++ ) {
|
||||
mode.put(Math.floor(Math.random() * 10));
|
||||
}
|
||||
});
|
||||
|
||||
bench('MovingMode with high cardinality values', () => {
|
||||
const mode = new MovingMode({ initial: 0, window_size: 50 });
|
||||
for ( let i = 0; i < 1000; i++ ) {
|
||||
mode.put(Math.floor(Math.random() * 1000));
|
||||
}
|
||||
});
|
||||
|
||||
bench('MovingMode with low cardinality values', () => {
|
||||
const mode = new MovingMode({ initial: 0, window_size: 50 });
|
||||
for ( let i = 0; i < 1000; i++ ) {
|
||||
mode.put(Math.floor(Math.random() * 3));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('TimeWindow - Time-based sliding window', () => {
|
||||
bench('TimeWindow add() and get()', () => {
|
||||
let fakeTime = 0;
|
||||
const tw = new TimeWindow({
|
||||
window_duration: 1000,
|
||||
reducer: values => values.reduce((a, b) => a + b, 0),
|
||||
now: () => fakeTime,
|
||||
});
|
||||
for ( let i = 0; i < 1000; i++ ) {
|
||||
fakeTime += 10;
|
||||
tw.add(Math.random());
|
||||
}
|
||||
});
|
||||
|
||||
bench('TimeWindow with stale entry removal', () => {
|
||||
let fakeTime = 0;
|
||||
const tw = new TimeWindow({
|
||||
window_duration: 100,
|
||||
reducer: values => values.length,
|
||||
now: () => fakeTime,
|
||||
});
|
||||
for ( let i = 0; i < 1000; i++ ) {
|
||||
fakeTime += 50; // Fast time progression causes stale removal
|
||||
tw.add(i);
|
||||
tw.get();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalize - Exponential normalization', () => {
|
||||
bench('normalize() single value', () => {
|
||||
for ( let i = 0; i < 10000; i++ ) {
|
||||
normalize({ high_value: 0.001 }, Math.random());
|
||||
}
|
||||
});
|
||||
|
||||
bench('normalize() with varying high_value', () => {
|
||||
const high_values = [0.001, 0.01, 0.1, 1, 10];
|
||||
for ( let i = 0; i < 10000; i++ ) {
|
||||
const hv = high_values[i % high_values.length];
|
||||
normalize({ high_value: hv }, Math.random() * 100);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,206 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2024-present Puter Technologies Inc.
|
||||
*
|
||||
* This file is part of Puter.
|
||||
*
|
||||
* Puter is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { bench, describe } from 'vitest';
|
||||
const { PathBuilder } = require('./pathutil');
|
||||
|
||||
describe('PathBuilder - Creation', () => {
|
||||
bench('create PathBuilder (default)', () => {
|
||||
PathBuilder.create();
|
||||
});
|
||||
|
||||
bench('create PathBuilder (puterfs mode)', () => {
|
||||
PathBuilder.create({ puterfs: true });
|
||||
});
|
||||
|
||||
bench('create via new', () => {
|
||||
new PathBuilder();
|
||||
});
|
||||
});
|
||||
|
||||
describe('PathBuilder - Static add', () => {
|
||||
bench('static add single fragment', () => {
|
||||
PathBuilder.add('directory');
|
||||
});
|
||||
|
||||
bench('static add with traversal prevention', () => {
|
||||
PathBuilder.add('../../../etc/passwd');
|
||||
});
|
||||
|
||||
bench('static add with allow_traversal', () => {
|
||||
PathBuilder.add('../parent', { allow_traversal: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('PathBuilder - Static resolve', () => {
|
||||
bench('resolve simple path', () => {
|
||||
PathBuilder.resolve('/home/user/file.txt');
|
||||
});
|
||||
|
||||
bench('resolve relative path', () => {
|
||||
PathBuilder.resolve('./relative/path');
|
||||
});
|
||||
|
||||
bench('resolve with puterfs', () => {
|
||||
PathBuilder.resolve('/home/user/file.txt', { puterfs: true });
|
||||
});
|
||||
|
||||
bench('resolve complex path', () => {
|
||||
PathBuilder.resolve('/a/b/c/../d/./e/f');
|
||||
});
|
||||
});
|
||||
|
||||
describe('PathBuilder - Instance add', () => {
|
||||
bench('add single fragment', () => {
|
||||
const builder = PathBuilder.create();
|
||||
builder.add('directory');
|
||||
});
|
||||
|
||||
bench('add multiple fragments (chain)', () => {
|
||||
PathBuilder.create()
|
||||
.add('home')
|
||||
.add('user')
|
||||
.add('documents')
|
||||
.add('file.txt');
|
||||
});
|
||||
|
||||
bench('add 10 fragments', () => {
|
||||
const builder = PathBuilder.create();
|
||||
for ( let i = 0; i < 10; i++ ) {
|
||||
builder.add(`dir${i}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('PathBuilder - Traversal prevention', () => {
|
||||
bench('sanitize parent traversal (..)', () => {
|
||||
PathBuilder.create().add('..');
|
||||
});
|
||||
|
||||
bench('sanitize multiple parent traversals', () => {
|
||||
PathBuilder.create().add('../../..');
|
||||
});
|
||||
|
||||
bench('sanitize mixed traversal patterns', () => {
|
||||
PathBuilder.create().add('../foo/../../bar/../baz');
|
||||
});
|
||||
|
||||
bench('sanitize with backslash traversal', () => {
|
||||
PathBuilder.create().add('..\\..\\..\\etc\\passwd');
|
||||
});
|
||||
|
||||
bench('allow_traversal option', () => {
|
||||
PathBuilder.create().add('../parent/child', { allow_traversal: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('PathBuilder - Build', () => {
|
||||
bench('build empty path', () => {
|
||||
PathBuilder.create().build();
|
||||
});
|
||||
|
||||
bench('build simple path', () => {
|
||||
PathBuilder.create()
|
||||
.add('home')
|
||||
.add('user')
|
||||
.build();
|
||||
});
|
||||
|
||||
bench('build long path', () => {
|
||||
const builder = PathBuilder.create();
|
||||
for ( let i = 0; i < 20; i++ ) {
|
||||
builder.add(`directory${i}`);
|
||||
}
|
||||
builder.build();
|
||||
});
|
||||
});
|
||||
|
||||
describe('PathBuilder - Complete workflows', () => {
|
||||
bench('create, add, build (simple)', () => {
|
||||
PathBuilder.create()
|
||||
.add('home')
|
||||
.add('user')
|
||||
.add('file.txt')
|
||||
.build();
|
||||
});
|
||||
|
||||
bench('create, add, build (with sanitization)', () => {
|
||||
PathBuilder.create()
|
||||
.add('../attempt')
|
||||
.add('actual')
|
||||
.add('path')
|
||||
.build();
|
||||
});
|
||||
|
||||
bench('puterfs path building', () => {
|
||||
PathBuilder.create({ puterfs: true })
|
||||
.add('username')
|
||||
.add('documents')
|
||||
.add('report.pdf')
|
||||
.build();
|
||||
});
|
||||
});
|
||||
|
||||
describe('PathBuilder - Batch operations', () => {
|
||||
const fragments = ['home', 'user', 'documents', 'projects', 'puter'];
|
||||
|
||||
bench('build 10 paths', () => {
|
||||
for ( let i = 0; i < 10; i++ ) {
|
||||
const builder = PathBuilder.create();
|
||||
for ( const frag of fragments ) {
|
||||
builder.add(frag);
|
||||
}
|
||||
builder.build();
|
||||
}
|
||||
});
|
||||
|
||||
bench('build 100 paths', () => {
|
||||
for ( let i = 0; i < 100; i++ ) {
|
||||
const builder = PathBuilder.create();
|
||||
for ( const frag of fragments ) {
|
||||
builder.add(frag);
|
||||
}
|
||||
builder.build();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Comparison with native path operations', () => {
|
||||
const path = require('path');
|
||||
|
||||
bench('PathBuilder.resolve', () => {
|
||||
PathBuilder.resolve('/home/user/file.txt');
|
||||
});
|
||||
|
||||
bench('native path.resolve', () => {
|
||||
path.resolve('/home/user/file.txt');
|
||||
});
|
||||
|
||||
bench('PathBuilder chain vs path.join', () => {
|
||||
PathBuilder.create()
|
||||
.add('home')
|
||||
.add('user')
|
||||
.add('file.txt')
|
||||
.build();
|
||||
});
|
||||
|
||||
bench('native path.join', () => {
|
||||
path.join('home', 'user', 'file.txt');
|
||||
});
|
||||
});
|
||||
@@ -1,240 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2024-present Puter Technologies Inc.
|
||||
*
|
||||
* This file is part of Puter.
|
||||
*
|
||||
* Puter is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { bench, describe } from 'vitest';
|
||||
const { cart_product, apply_keys } = require('./structutil');
|
||||
|
||||
describe('cart_product - Small inputs', () => {
|
||||
bench('2 keys, 2 values each', () => {
|
||||
cart_product({
|
||||
a: [1, 2],
|
||||
b: ['x', 'y'],
|
||||
});
|
||||
});
|
||||
|
||||
bench('3 keys, 2 values each', () => {
|
||||
cart_product({
|
||||
a: [1, 2],
|
||||
b: ['x', 'y'],
|
||||
c: [true, false],
|
||||
});
|
||||
});
|
||||
|
||||
bench('2 keys, 3 values each', () => {
|
||||
cart_product({
|
||||
a: [1, 2, 3],
|
||||
b: ['x', 'y', 'z'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('cart_product - Medium inputs', () => {
|
||||
bench('4 keys, 2 values each (16 combinations)', () => {
|
||||
cart_product({
|
||||
a: [1, 2],
|
||||
b: [3, 4],
|
||||
c: [5, 6],
|
||||
d: [7, 8],
|
||||
});
|
||||
});
|
||||
|
||||
bench('3 keys, 3 values each (27 combinations)', () => {
|
||||
cart_product({
|
||||
a: [1, 2, 3],
|
||||
b: [4, 5, 6],
|
||||
c: [7, 8, 9],
|
||||
});
|
||||
});
|
||||
|
||||
bench('5 keys, 2 values each (32 combinations)', () => {
|
||||
cart_product({
|
||||
a: [1, 2],
|
||||
b: [3, 4],
|
||||
c: [5, 6],
|
||||
d: [7, 8],
|
||||
e: [9, 10],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('cart_product - Large inputs', () => {
|
||||
bench('3 keys, 5 values each (125 combinations)', () => {
|
||||
cart_product({
|
||||
a: [1, 2, 3, 4, 5],
|
||||
b: [6, 7, 8, 9, 10],
|
||||
c: [11, 12, 13, 14, 15],
|
||||
});
|
||||
});
|
||||
|
||||
bench('4 keys, 4 values each (256 combinations)', () => {
|
||||
cart_product({
|
||||
a: [1, 2, 3, 4],
|
||||
b: [5, 6, 7, 8],
|
||||
c: [9, 10, 11, 12],
|
||||
d: [13, 14, 15, 16],
|
||||
});
|
||||
});
|
||||
|
||||
bench('6 keys, 2 values each (64 combinations)', () => {
|
||||
cart_product({
|
||||
a: [1, 2],
|
||||
b: [3, 4],
|
||||
c: [5, 6],
|
||||
d: [7, 8],
|
||||
e: [9, 10],
|
||||
f: [11, 12],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('cart_product - Single values', () => {
|
||||
bench('3 keys, 1 value each (1 combination)', () => {
|
||||
cart_product({
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3,
|
||||
});
|
||||
});
|
||||
|
||||
bench('mixed single and array values', () => {
|
||||
cart_product({
|
||||
a: 1,
|
||||
b: [2, 3],
|
||||
c: 4,
|
||||
d: [5, 6],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('cart_product - Edge cases', () => {
|
||||
bench('empty object', () => {
|
||||
cart_product({});
|
||||
});
|
||||
|
||||
bench('single key with array', () => {
|
||||
cart_product({
|
||||
only: [1, 2, 3, 4, 5],
|
||||
});
|
||||
});
|
||||
|
||||
bench('many keys with single values', () => {
|
||||
cart_product({
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3,
|
||||
d: 4,
|
||||
e: 5,
|
||||
f: 6,
|
||||
g: 7,
|
||||
h: 8,
|
||||
i: 9,
|
||||
j: 10,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('apply_keys - Basic operations', () => {
|
||||
const keys = ['a', 'b', 'c'];
|
||||
|
||||
bench('apply to single entry', () => {
|
||||
apply_keys(keys, [1, 2, 3]);
|
||||
});
|
||||
|
||||
bench('apply to 5 entries', () => {
|
||||
apply_keys(keys,
|
||||
[1, 2, 3],
|
||||
[4, 5, 6],
|
||||
[7, 8, 9],
|
||||
[10, 11, 12],
|
||||
[13, 14, 15]);
|
||||
});
|
||||
|
||||
bench('apply to 10 entries', () => {
|
||||
const entries = [];
|
||||
for ( let i = 0; i < 10; i++ ) {
|
||||
entries.push([i * 3, i * 3 + 1, i * 3 + 2]);
|
||||
}
|
||||
apply_keys(keys, ...entries);
|
||||
});
|
||||
});
|
||||
|
||||
describe('apply_keys - Varying key counts', () => {
|
||||
bench('2 keys', () => {
|
||||
apply_keys(['a', 'b'], [1, 2], [3, 4], [5, 6]);
|
||||
});
|
||||
|
||||
bench('5 keys', () => {
|
||||
apply_keys(['a', 'b', 'c', 'd', 'e'],
|
||||
[1, 2, 3, 4, 5],
|
||||
[6, 7, 8, 9, 10]);
|
||||
});
|
||||
|
||||
bench('10 keys', () => {
|
||||
const keys = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];
|
||||
const entry = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
apply_keys(keys, entry, entry, entry);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Combined cart_product + apply_keys workflow', () => {
|
||||
bench('generate and label small product', () => {
|
||||
const product = cart_product({
|
||||
size: ['small', 'medium', 'large'],
|
||||
color: ['red', 'blue'],
|
||||
});
|
||||
apply_keys(['size', 'color'], ...product);
|
||||
});
|
||||
|
||||
bench('generate and label medium product', () => {
|
||||
const product = cart_product({
|
||||
a: [1, 2, 3],
|
||||
b: [4, 5, 6],
|
||||
c: [7, 8, 9],
|
||||
});
|
||||
apply_keys(['a', 'b', 'c'], ...product);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Real-world configuration generation', () => {
|
||||
bench('test matrix generation (browser x OS)', () => {
|
||||
const matrix = cart_product({
|
||||
browser: ['chrome', 'firefox', 'safari'],
|
||||
os: ['windows', 'macos', 'linux'],
|
||||
});
|
||||
apply_keys(['browser', 'os'], ...matrix);
|
||||
});
|
||||
|
||||
bench('feature flag combinations', () => {
|
||||
cart_product({
|
||||
featureA: [true, false],
|
||||
featureB: [true, false],
|
||||
featureC: [true, false],
|
||||
featureD: [true, false],
|
||||
});
|
||||
});
|
||||
|
||||
bench('API endpoint parameter combinations', () => {
|
||||
const combinations = cart_product({
|
||||
method: ['GET', 'POST'],
|
||||
auth: ['none', 'token', 'session'],
|
||||
format: ['json', 'xml'],
|
||||
});
|
||||
apply_keys(['method', 'auth', 'format'], ...combinations);
|
||||
});
|
||||
});
|
||||
@@ -1,162 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2024-present Puter Technologies Inc.
|
||||
*
|
||||
* This file is part of Puter.
|
||||
*
|
||||
* Puter is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { bench, describe } from 'vitest';
|
||||
const { UUIDFPE } = require('./uuidfpe');
|
||||
const crypto = require('crypto');
|
||||
|
||||
// Test data
|
||||
const testKey = Buffer.from('0123456789abcdef'); // 16-byte key
|
||||
const testUuid = '550e8400-e29b-41d4-a716-446655440000';
|
||||
const fpe = new UUIDFPE(testKey);
|
||||
const encryptedUuid = fpe.encrypt(testUuid);
|
||||
|
||||
// Pre-generate UUIDs for batch tests
|
||||
const uuids = [];
|
||||
for ( let i = 0; i < 100; i++ ) {
|
||||
uuids.push(crypto.randomUUID());
|
||||
}
|
||||
|
||||
describe('UUIDFPE - Construction', () => {
|
||||
bench('create UUIDFPE instance', () => {
|
||||
new UUIDFPE(testKey);
|
||||
});
|
||||
|
||||
bench('create with random key', () => {
|
||||
const key = crypto.randomBytes(16);
|
||||
new UUIDFPE(key);
|
||||
});
|
||||
});
|
||||
|
||||
describe('UUIDFPE - Static utilities', () => {
|
||||
bench('uuidToBuffer', () => {
|
||||
UUIDFPE.uuidToBuffer(testUuid);
|
||||
});
|
||||
|
||||
bench('bufferToUuid', () => {
|
||||
const buffer = Buffer.from('550e8400e29b41d4a716446655440000', 'hex');
|
||||
UUIDFPE.bufferToUuid(buffer);
|
||||
});
|
||||
|
||||
bench('round-trip buffer conversion', () => {
|
||||
const buffer = UUIDFPE.uuidToBuffer(testUuid);
|
||||
UUIDFPE.bufferToUuid(buffer);
|
||||
});
|
||||
});
|
||||
|
||||
describe('UUIDFPE - Encryption', () => {
|
||||
bench('encrypt single UUID', () => {
|
||||
fpe.encrypt(testUuid);
|
||||
});
|
||||
|
||||
bench('encrypt 10 UUIDs', () => {
|
||||
for ( let i = 0; i < 10; i++ ) {
|
||||
fpe.encrypt(uuids[i]);
|
||||
}
|
||||
});
|
||||
|
||||
bench('encrypt 100 UUIDs', () => {
|
||||
for ( const uuid of uuids ) {
|
||||
fpe.encrypt(uuid);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('UUIDFPE - Decryption', () => {
|
||||
bench('decrypt single UUID', () => {
|
||||
fpe.decrypt(encryptedUuid);
|
||||
});
|
||||
|
||||
// Pre-encrypt for decryption benchmarks
|
||||
const encryptedUuids = uuids.map(uuid => fpe.encrypt(uuid));
|
||||
|
||||
bench('decrypt 10 UUIDs', () => {
|
||||
for ( let i = 0; i < 10; i++ ) {
|
||||
fpe.decrypt(encryptedUuids[i]);
|
||||
}
|
||||
});
|
||||
|
||||
bench('decrypt 100 UUIDs', () => {
|
||||
for ( const encrypted of encryptedUuids ) {
|
||||
fpe.decrypt(encrypted);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('UUIDFPE - Round-trip', () => {
|
||||
bench('encrypt then decrypt (single)', () => {
|
||||
const encrypted = fpe.encrypt(testUuid);
|
||||
fpe.decrypt(encrypted);
|
||||
});
|
||||
|
||||
bench('encrypt then decrypt (10 UUIDs)', () => {
|
||||
for ( let i = 0; i < 10; i++ ) {
|
||||
const encrypted = fpe.encrypt(uuids[i]);
|
||||
fpe.decrypt(encrypted);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('UUIDFPE - Comparison with alternatives', () => {
|
||||
bench('UUIDFPE encrypt', () => {
|
||||
fpe.encrypt(testUuid);
|
||||
});
|
||||
|
||||
bench('native crypto.randomUUID (for comparison)', () => {
|
||||
crypto.randomUUID();
|
||||
});
|
||||
|
||||
bench('SHA256 hash of UUID (for comparison)', () => {
|
||||
crypto.createHash('sha256').update(testUuid).digest('hex');
|
||||
});
|
||||
});
|
||||
|
||||
describe('UUIDFPE - Different keys', () => {
|
||||
const keys = [];
|
||||
for ( let i = 0; i < 10; i++ ) {
|
||||
keys.push(crypto.randomBytes(16));
|
||||
}
|
||||
|
||||
bench('encrypt with 10 different keys', () => {
|
||||
for ( const key of keys ) {
|
||||
const instance = new UUIDFPE(key);
|
||||
instance.encrypt(testUuid);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Real-world patterns', () => {
|
||||
bench('obfuscate user ID', () => {
|
||||
// Simulate hiding internal UUID from external API
|
||||
fpe.encrypt(testUuid);
|
||||
});
|
||||
|
||||
bench('de-obfuscate incoming ID', () => {
|
||||
// Simulate receiving obfuscated ID and decrypting
|
||||
fpe.decrypt(encryptedUuid);
|
||||
});
|
||||
|
||||
bench('API response transformation (10 items)', () => {
|
||||
// Simulate transforming a list of items with obfuscated IDs
|
||||
uuids.slice(0, 10).map(uuid => ({
|
||||
id: fpe.encrypt(uuid),
|
||||
name: 'item',
|
||||
}));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user