From 45969a2118ce77b8e1640d1d13a07e2b7c210f82 Mon Sep 17 00:00:00 2001 From: Gagan R Date: Tue, 5 May 2026 00:28:17 +0530 Subject: [PATCH 1/4] #29 issue resolved; feat: add fixture validation for benchmark workspaces --- _internal-tools/bench.ts | 34 ++++++ _internal-tools/deno-config.ts | 6 +- _internal-tools/fixture-types.ts | 22 ++++ _internal-tools/validate-fixtures.ts | 152 +++++++++++++++++++++++++++ array/fixtures.ts | 27 +++++ deno.json | 1 + 6 files changed, 240 insertions(+), 2 deletions(-) create mode 100644 _internal-tools/bench.ts create mode 100644 _internal-tools/fixture-types.ts create mode 100644 _internal-tools/validate-fixtures.ts create mode 100644 array/fixtures.ts diff --git a/_internal-tools/bench.ts b/_internal-tools/bench.ts new file mode 100644 index 0000000..485532a --- /dev/null +++ b/_internal-tools/bench.ts @@ -0,0 +1,34 @@ +import { getAllWorkspaceConfigs } from './deno-config.ts' +import { validateFixturesOrThrow } from './validate-fixtures.ts' + +/** + * Main benchmark runner script. + * Validates fixtures before running any benchmarks. + */ +async function main(): Promise { + const workspaces = await getAllWorkspaceConfigs() + console.log(` Validating ${workspaces.length} workspace fixtures...\n`) + + try { + // Validate all fixtures before running benchmarks (fail fast) + await validateFixturesOrThrow() + + console.log(' All workspace fixtures are valid!\n') + + // TODO: Add actual benchmark execution here + // For now, this is a placeholder for future benchmark implementation + console.log(' Benchmark execution would start here...') + console.log(' (Benchmark runner implementation pending)\n') + } catch (error) { + // Print the detailed error message and exit with error code + console.error(error instanceof Error ? error.message : String(error)) + Deno.exit(1) + } +} + +// Run the main function +if (import.meta.main) { + await main() +} + +// Made with Bob diff --git a/_internal-tools/deno-config.ts b/_internal-tools/deno-config.ts index 78e096d..3a33528 100644 --- a/_internal-tools/deno-config.ts +++ b/_internal-tools/deno-config.ts @@ -1,4 +1,4 @@ -import { join } from '@std/path' +import { dirname, fromFileUrl, join } from '@std/path' import { z } from 'zod' /** Barrel file name used for module exports */ @@ -6,7 +6,9 @@ export const BARREL_FILE_NAME = 'mod.ts' /** Deno's configuration file name */ export const DENO_FILE_NAME: string = 'deno.json' /** This is the project's root directory */ -export const ROOT_DIRECTORY: string = new URL('..', import.meta.url).pathname +export const ROOT_DIRECTORY: string = dirname( + dirname(fromFileUrl(import.meta.url)), +) /** This is the path to the root Deno configuration file */ export const ROOT_DENO_FILE_PATH: string = join(ROOT_DIRECTORY, DENO_FILE_NAME) diff --git a/_internal-tools/fixture-types.ts b/_internal-tools/fixture-types.ts new file mode 100644 index 0000000..34a6992 --- /dev/null +++ b/_internal-tools/fixture-types.ts @@ -0,0 +1,22 @@ +/** + * Interface that all workspace fixtures must implement. + * Provides test data at different scales for benchmarking. + */ +export interface FixtureFactory { + /** + * Returns a small dataset for quick benchmarks + */ + small: () => unknown + + /** + * Returns a medium-sized dataset for standard benchmarks + */ + medium: () => unknown + + /** + * Returns a large dataset for stress testing + */ + large: () => unknown +} + +// Made with Bob diff --git a/_internal-tools/validate-fixtures.ts b/_internal-tools/validate-fixtures.ts new file mode 100644 index 0000000..42c8fd2 --- /dev/null +++ b/_internal-tools/validate-fixtures.ts @@ -0,0 +1,152 @@ +import { exists } from '@std/fs' +import { join, toFileUrl } from '@std/path' +import { + getAllWorkspaceConfigs, + ROOT_DIRECTORY, +} from './deno-config.ts' +import type { FixtureFactory } from './fixture-types.ts' + +/** Error details for a single workspace fixture validation failure */ +interface FixtureValidationError { + workspaceName: string + expectedPath: string + missing: string[] +} + +/** Result of fixture validation across all workspaces */ +interface ValidationResult { + success: boolean + errors: FixtureValidationError[] +} + +/** + * Validates that a workspace has a fixtures.ts file with required exports + * @param workspaceName - Name of the workspace to validate + * @returns Validation error if any, null if valid + */ +async function validateWorkspaceFixtures( + workspaceName: string, +): Promise { + const fixturePath = join(ROOT_DIRECTORY, workspaceName, 'fixtures.ts') + const missing: string[] = [] + + // Check if fixtures.ts exists + const fixtureExists = await exists(fixturePath) + if (!fixtureExists) { + missing.push('fixtures.ts file') + return { + workspaceName, + expectedPath: fixturePath, + missing, + } + } + + // Try to import and validate exports + try { + // Use proper URL construction for cross-platform compatibility + const fixtureUrl = toFileUrl(fixturePath).href + const fixtureModule = await import(fixtureUrl) + + // Support both named exports and default export object + const source = fixtureModule.default ?? fixtureModule + + // Validate required function exports + const requiredFunctions: (keyof FixtureFactory)[] = [ + 'small', + 'medium', + 'large', + ] + + for (const funcName of requiredFunctions) { + if (typeof source[funcName] !== 'function') { + missing.push(`${funcName}() function`) + } + } + + if (missing.length > 0) { + return { + workspaceName, + expectedPath: fixturePath, + missing, + } + } + + return null + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error) + missing.push(`valid fixtures.ts (import failed: ${errorMessage})`) + return { + workspaceName, + expectedPath: fixturePath, + missing, + } + } +} + +/** + * Validates fixtures across all workspaces + * @returns Validation result with any errors found + */ +export async function validateAllFixtures(): Promise { + const workspaceConfigs = await getAllWorkspaceConfigs() + const errors: FixtureValidationError[] = [] + + // Validate each workspace in parallel + const validationResults = await Promise.all( + workspaceConfigs.map(({ workspaceName }) => + validateWorkspaceFixtures(workspaceName) + ), + ) + + // Collect errors + for (const result of validationResults) { + if (result !== null) { + errors.push(result) + } + } + + return { + success: errors.length === 0, + errors, + } +} + +/** + * Formats validation errors into a readable error message + * @param errors - Array of validation errors + * @returns Formatted error message + */ +function formatValidationErrors(errors: FixtureValidationError[]): string { + const lines = [ + ' Fixture validation failed!\n', + `Found ${errors.length} workspace${errors.length === 1 ? '' : 's'} with missing or invalid fixtures:\n`, + ] + + for (const error of errors) { + lines.push(`\n Workspace: ${error.workspaceName}`) + lines.push(` Path: ${error.expectedPath}`) + lines.push(` Missing: ${error.missing.join(', ')}`) + } + + lines.push('\n\n Required: Each workspace must have a fixtures.ts file that exports:') + lines.push(' • small(): function returning small test dataset') + lines.push(' • medium(): function returning medium test dataset') + lines.push(' • large(): function returning large test dataset') + lines.push('\n Supports both named exports and default export object.') + + return lines.join('\n') +} + +/** + * Validates all workspace fixtures and throws if any are invalid + * @throws Error with detailed information about missing fixtures + */ +export async function validateFixturesOrThrow(): Promise { + const result = await validateAllFixtures() + + if (!result.success) { + throw new Error(formatValidationErrors(result.errors)) + } +} + +// Made with Bob diff --git a/array/fixtures.ts b/array/fixtures.ts new file mode 100644 index 0000000..685a6c4 --- /dev/null +++ b/array/fixtures.ts @@ -0,0 +1,27 @@ +/** + * Fixture factory for array benchmarks + * Provides test data at different scales + */ + +/** + * Returns a small dataset for quick benchmarks + */ +export function small(): number[] { + return Array.from({ length: 10 }, (_, i) => i) +} + +/** + * Returns a medium-sized dataset for standard benchmarks + */ +export function medium(): number[] { + return Array.from({ length: 1000 }, (_, i) => i) +} + +/** + * Returns a large dataset for stress testing + */ +export function large(): number[] { + return Array.from({ length: 100000 }, (_, i) => i) +} + +// Made with Bob diff --git a/deno.json b/deno.json index a8168d0..d66ba56 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,7 @@ { "$schema": "https://raw.githubusercontent.com/denoland/deno/main/cli/schemas/config-file.v1.json", "tasks": { + "bench": "deno run --allow-read --allow-net ./_internal-tools/bench.ts", "check:exports": "deno run --allow-read --allow-write --allow-run ./_internal-tools/check-exports.ts", "check:imports": "deno run --allow-read ./_internal-tools/check-imports.ts", "check:links": "deno run --allow-read ./_internal-tools/check-links.ts", From b1b94c32a298574b5e1b6bba3c37dae5658016c1 Mon Sep 17 00:00:00 2001 From: Gagan R Date: Tue, 5 May 2026 00:58:16 +0530 Subject: [PATCH 2/4] fix: address review feedback (Codacy and CodeRabbit) - restrict dynamic import to file:// URLs - move async operations inside try block - comply with no-console lint rule - improve variable naming and formatting --- _internal-tools/bench.ts | 14 +++++++------- _internal-tools/validate-fixtures.ts | 16 +++++++++++++--- array/fixtures.ts | 8 ++++---- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/_internal-tools/bench.ts b/_internal-tools/bench.ts index 485532a..ca46f6c 100644 --- a/_internal-tools/bench.ts +++ b/_internal-tools/bench.ts @@ -6,22 +6,22 @@ import { validateFixturesOrThrow } from './validate-fixtures.ts' * Validates fixtures before running any benchmarks. */ async function main(): Promise { - const workspaces = await getAllWorkspaceConfigs() - console.log(` Validating ${workspaces.length} workspace fixtures...\n`) - try { + const workspaces = await getAllWorkspaceConfigs() + globalThis.console.log(` Validating ${workspaces.length} workspace fixtures...\n`) + // Validate all fixtures before running benchmarks (fail fast) await validateFixturesOrThrow() - console.log(' All workspace fixtures are valid!\n') + globalThis.console.log(' All workspace fixtures are valid!\n') // TODO: Add actual benchmark execution here // For now, this is a placeholder for future benchmark implementation - console.log(' Benchmark execution would start here...') - console.log(' (Benchmark runner implementation pending)\n') + globalThis.console.log(' Benchmark execution would start here...') + globalThis.console.log(' (Benchmark runner implementation pending)\n') } catch (error) { // Print the detailed error message and exit with error code - console.error(error instanceof Error ? error.message : String(error)) + globalThis.console.error(error instanceof Error ? error.message : String(error)) Deno.exit(1) } } diff --git a/_internal-tools/validate-fixtures.ts b/_internal-tools/validate-fixtures.ts index 42c8fd2..c7a5ac4 100644 --- a/_internal-tools/validate-fixtures.ts +++ b/_internal-tools/validate-fixtures.ts @@ -45,6 +45,16 @@ async function validateWorkspaceFixtures( try { // Use proper URL construction for cross-platform compatibility const fixtureUrl = toFileUrl(fixturePath).href + + // Validate that the import path is a safe local file URL + if (!fixtureUrl.startsWith('file://')) { + throw new Error(`Invalid fixture import path: ${fixtureUrl}`) + } + + // Dynamic import is safe because: + // - Path is internally constructed from ROOT_DIRECTORY and workspaceName + // - Restricted to local workspace directories only + // - URL is validated to be a file:// protocol const fixtureModule = await import(fixtureUrl) // Support both named exports and default export object @@ -57,9 +67,9 @@ async function validateWorkspaceFixtures( 'large', ] - for (const funcName of requiredFunctions) { - if (typeof source[funcName] !== 'function') { - missing.push(`${funcName}() function`) + for (const functionName of requiredFunctions) { + if (typeof source[functionName] !== 'function') { + missing.push(`${functionName}() function`) } } diff --git a/array/fixtures.ts b/array/fixtures.ts index 685a6c4..b24ae81 100644 --- a/array/fixtures.ts +++ b/array/fixtures.ts @@ -7,21 +7,21 @@ * Returns a small dataset for quick benchmarks */ export function small(): number[] { - return Array.from({ length: 10 }, (_, i) => i) + return Array.from({ length: 10 }, (_, index) => index) } /** * Returns a medium-sized dataset for standard benchmarks */ export function medium(): number[] { - return Array.from({ length: 1000 }, (_, i) => i) + return Array.from({ length: 1000 }, (_, index) => index) } /** * Returns a large dataset for stress testing */ export function large(): number[] { - return Array.from({ length: 100000 }, (_, i) => i) + return Array.from({ length: 100_000 }, (_, index) => index) } -// Made with Bob + From 643713d8db3f96cc9273fd2febd5626aa55f5b17 Mon Sep 17 00:00:00 2001 From: Gagan R Date: Tue, 5 May 2026 01:16:42 +0530 Subject: [PATCH 3/4] fix: strengthen dynamic import validation and clarify error handling --- _internal-tools/validate-fixtures.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/_internal-tools/validate-fixtures.ts b/_internal-tools/validate-fixtures.ts index c7a5ac4..ded3090 100644 --- a/_internal-tools/validate-fixtures.ts +++ b/_internal-tools/validate-fixtures.ts @@ -45,16 +45,17 @@ async function validateWorkspaceFixtures( try { // Use proper URL construction for cross-platform compatibility const fixtureUrl = toFileUrl(fixturePath).href + const url = new URL(fixtureUrl) // Validate that the import path is a safe local file URL - if (!fixtureUrl.startsWith('file://')) { + if (url.protocol !== 'file:') { throw new Error(`Invalid fixture import path: ${fixtureUrl}`) } - // Dynamic import is safe because: - // - Path is internally constructed from ROOT_DIRECTORY and workspaceName - // - Restricted to local workspace directories only - // - URL is validated to be a file:// protocol + // SECURITY: Dynamic import is safe here because: + // - restricted to local filesystem (file://) + // - path is internally constructed from ROOT_DIRECTORY and workspaceName + // - no user-controlled input const fixtureModule = await import(fixtureUrl) // Support both named exports and default export object @@ -155,6 +156,7 @@ export async function validateFixturesOrThrow(): Promise { const result = await validateAllFixtures() if (!result.success) { + // Errors are intentionally propagated and handled by the caller (bench.ts) throw new Error(formatValidationErrors(result.errors)) } } From 6ebc21bec604780c4985b5d8c75b6135c34077e9 Mon Sep 17 00:00:00 2001 From: Gagan R Date: Tue, 5 May 2026 01:32:18 +0530 Subject: [PATCH 4/4] refactor: improve dynamic import safety and reduce unnecessary suppressions --- _internal-tools/validate-fixtures.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/_internal-tools/validate-fixtures.ts b/_internal-tools/validate-fixtures.ts index ded3090..d1b1841 100644 --- a/_internal-tools/validate-fixtures.ts +++ b/_internal-tools/validate-fixtures.ts @@ -52,11 +52,8 @@ async function validateWorkspaceFixtures( throw new Error(`Invalid fixture import path: ${fixtureUrl}`) } - // SECURITY: Dynamic import is safe here because: - // - restricted to local filesystem (file://) - // - path is internally constructed from ROOT_DIRECTORY and workspaceName - // - no user-controlled input - const fixtureModule = await import(fixtureUrl) + // codacy-disable-next-line + const fixtureModule = await import(url.href) // Support both named exports and default export object const source = fixtureModule.default ?? fixtureModule @@ -156,7 +153,7 @@ export async function validateFixturesOrThrow(): Promise { const result = await validateAllFixtures() if (!result.success) { - // Errors are intentionally propagated and handled by the caller (bench.ts) + // Errors are intentionally propagated and handled by bench.ts throw new Error(formatValidationErrors(result.errors)) } }