diff --git a/src/backend/src/filesystem/validation.bench.js b/src/backend/src/filesystem/validation.bench.js new file mode 100644 index 000000000..4790cca17 --- /dev/null +++ b/src/backend/src/filesystem/validation.bench.js @@ -0,0 +1,173 @@ +/* + * 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 . + */ + +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]); + } + }); +}); diff --git a/src/backend/src/util/context.bench.js b/src/backend/src/util/context.bench.js new file mode 100644 index 000000000..0b20b8f06 --- /dev/null +++ b/src/backend/src/util/context.bench.js @@ -0,0 +1,216 @@ +/* + * 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 . + */ + +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'); + }); + }); + }); +}); diff --git a/src/backend/src/util/identifier.bench.js b/src/backend/src/util/identifier.bench.js new file mode 100644 index 000000000..b634c83ec --- /dev/null +++ b/src/backend/src/util/identifier.bench.js @@ -0,0 +1,163 @@ +/* + * 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 . + */ + +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); + }); +}); diff --git a/src/backend/src/util/lockutil.bench.js b/src/backend/src/util/lockutil.bench.js new file mode 100644 index 000000000..bd7413f5d --- /dev/null +++ b/src/backend/src/util/lockutil.bench.js @@ -0,0 +1,283 @@ +/* + * 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 . + */ + +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); + }); +}); diff --git a/src/backend/src/util/pathutil.bench.js b/src/backend/src/util/pathutil.bench.js new file mode 100644 index 000000000..561e6b19c --- /dev/null +++ b/src/backend/src/util/pathutil.bench.js @@ -0,0 +1,206 @@ +/* + * 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 . + */ + +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'); + }); +}); diff --git a/src/backend/src/util/structutil.bench.js b/src/backend/src/util/structutil.bench.js new file mode 100644 index 000000000..f26210541 --- /dev/null +++ b/src/backend/src/util/structutil.bench.js @@ -0,0 +1,240 @@ +/* + * 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 . + */ + +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); + }); +}); diff --git a/src/backend/src/util/uuidfpe.bench.js b/src/backend/src/util/uuidfpe.bench.js new file mode 100644 index 000000000..ac773c73f --- /dev/null +++ b/src/backend/src/util/uuidfpe.bench.js @@ -0,0 +1,162 @@ +/* + * 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 . + */ + +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', + })); + }); +});