Address review feedback: deterministic tests, cached normalization, simplified promisePool (#9)
* Initial plan * Apply PR review comments: deterministic delays, cached normalization, simplified promisePool Co-authored-by: felickz <[email protected]> * Improve comment clarity for ecoLower field Co-authored-by: felickz <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: felickz <[email protected]>
This commit is contained in:
@@ -747,8 +747,8 @@ test('addChangeVulnerabilitiesToSummary() - respects concurrency limit for API c
|
|||||||
currentConcurrent++
|
currentConcurrent++
|
||||||
maxConcurrent = Math.max(maxConcurrent, currentConcurrent)
|
maxConcurrent = Math.max(maxConcurrent, currentConcurrent)
|
||||||
|
|
||||||
// Simulate async API call with variable delay
|
// Simulate async API call with a small deterministic delay
|
||||||
await new Promise(resolve => setTimeout(resolve, Math.random() * 10))
|
await new Promise(resolve => setTimeout(resolve, 5))
|
||||||
|
|
||||||
currentConcurrent--
|
currentConcurrent--
|
||||||
|
|
||||||
|
|||||||
+15
-15
@@ -1820,15 +1820,11 @@ function countScorecardWarnings(scorecard, config) {
|
|||||||
*/
|
*/
|
||||||
function promisePool(tasks, limit) {
|
function promisePool(tasks, limit) {
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
const results = [];
|
|
||||||
const executing = new Set();
|
const executing = new Set();
|
||||||
for (let i = 0; i < tasks.length; i++) {
|
for (const task of tasks) {
|
||||||
const task = tasks[i];
|
// Execute task and clean up
|
||||||
const index = i;
|
|
||||||
// Wrap task execution to store result and clean up
|
|
||||||
const wrappedPromise = (() => __awaiter(this, void 0, void 0, function* () {
|
const wrappedPromise = (() => __awaiter(this, void 0, void 0, function* () {
|
||||||
const result = yield task();
|
yield task();
|
||||||
results[index] = result;
|
|
||||||
}))();
|
}))();
|
||||||
executing.add(wrappedPromise);
|
executing.add(wrappedPromise);
|
||||||
// When promise completes, remove it from the executing set
|
// When promise completes, remove it from the executing set
|
||||||
@@ -1842,7 +1838,6 @@ function promisePool(tasks, limit) {
|
|||||||
}
|
}
|
||||||
// Wait for all remaining promises
|
// Wait for all remaining promises
|
||||||
yield Promise.all(executing);
|
yield Promise.all(executing);
|
||||||
return results;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function addChangeVulnerabilitiesToSummary(vulnerableChanges, severity) {
|
function addChangeVulnerabilitiesToSummary(vulnerableChanges, severity) {
|
||||||
@@ -1860,6 +1855,7 @@ function addChangeVulnerabilitiesToSummary(vulnerableChanges, severity) {
|
|||||||
}
|
}
|
||||||
// Query GitHub API for patch info with concurrency limiting
|
// Query GitHub API for patch info with concurrency limiting
|
||||||
// Store all vulnerability entries (may be multiple per package with different ranges)
|
// Store all vulnerability entries (may be multiple per package with different ranges)
|
||||||
|
// Pre-normalize ecosystem, package name, and range to avoid repeated work in rendering
|
||||||
const patchInfo = {};
|
const patchInfo = {};
|
||||||
const apiClient = (0, utils_1.octokitClient)();
|
const apiClient = (0, utils_1.octokitClient)();
|
||||||
// Create tasks for promise pool
|
// Create tasks for promise pool
|
||||||
@@ -1879,11 +1875,17 @@ function addChangeVulnerabilitiesToSummary(vulnerableChanges, severity) {
|
|||||||
const vulnRange = v.vulnerable_version_range || '';
|
const vulnRange = v.vulnerable_version_range || '';
|
||||||
const patchVerId = extractPatchVersionId(v.first_patched_version);
|
const patchVerId = extractPatchVersionId(v.first_patched_version);
|
||||||
if (patchVerId) {
|
if (patchVerId) {
|
||||||
|
// Pre-normalize and cache values to avoid repeated work in rendering loop
|
||||||
|
const trimmedRange = vulnRange.trim();
|
||||||
|
const normalizedRange = trimmedRange.replace(/,\s*/g, ' ');
|
||||||
patchInfo[advId].push({
|
patchInfo[advId].push({
|
||||||
eco: normalizedEco,
|
eco: normalizedEco,
|
||||||
pkg: pkgName,
|
pkg: pkgName,
|
||||||
range: vulnRange,
|
range: vulnRange,
|
||||||
patch: patchVerId
|
patch: patchVerId,
|
||||||
|
ecoLower: normalizedEco, // Ecosystem already normalized to lowercase
|
||||||
|
pkgLower: pkgName.toLowerCase(),
|
||||||
|
normalizedRange
|
||||||
});
|
});
|
||||||
core.debug(`Added patch info for ${pkgName} (${normalizedEco}): ${patchVerId} for range ${vulnRange}`);
|
core.debug(`Added patch info for ${pkgName} (${normalizedEco}): ${patchVerId} for range ${vulnRange}`);
|
||||||
}
|
}
|
||||||
@@ -1920,19 +1922,17 @@ function addChangeVulnerabilitiesToSummary(vulnerableChanges, severity) {
|
|||||||
const normalizedChangeVersion = change.version.trim();
|
const normalizedChangeVersion = change.version.trim();
|
||||||
core.debug(`Looking up patch for ${change.name}@${change.version} (${ecoLowercase}) in ${vuln.advisory_ghsa_id}`);
|
core.debug(`Looking up patch for ${change.name}@${change.version} (${ecoLowercase}) in ${vuln.advisory_ghsa_id}`);
|
||||||
// Find matching entry by ecosystem, package name (case-insensitive), and version range
|
// Find matching entry by ecosystem, package name (case-insensitive), and version range
|
||||||
|
// Use pre-normalized values from cache to avoid repeated lowercasing and range conversion
|
||||||
let foundEntry;
|
let foundEntry;
|
||||||
for (const vulnEntry of advisoryEntries) {
|
for (const vulnEntry of advisoryEntries) {
|
||||||
if (vulnEntry.eco.toLowerCase() !== ecoLowercase)
|
if (vulnEntry.ecoLower !== ecoLowercase)
|
||||||
continue;
|
continue;
|
||||||
if (vulnEntry.pkg.toLowerCase() !== packageLowercase)
|
if (vulnEntry.pkgLower !== packageLowercase)
|
||||||
continue;
|
continue;
|
||||||
// Normalize range once for both cache key and function call
|
|
||||||
const trimmedRangeOnce = vulnEntry.range.trim();
|
|
||||||
const normalizedRange = trimmedRangeOnce.replace(/,\s*/g, ' ');
|
|
||||||
// Use fail-open (failClosed: false) for patch selection to avoid
|
// Use fail-open (failClosed: false) for patch selection to avoid
|
||||||
// incorrectly matching on invalid ranges
|
// incorrectly matching on invalid ranges
|
||||||
// Use preTrimmed and preNormalized optimizations since we've done both
|
// Use preTrimmed and preNormalized optimizations since we've done both
|
||||||
const isInRange = versionInRange(normalizedChangeVersion, normalizedRange, { preTrimmed: true, preNormalized: true, failClosed: false });
|
const isInRange = versionInRange(normalizedChangeVersion, vulnEntry.normalizedRange, { preTrimmed: true, preNormalized: true, failClosed: false });
|
||||||
if (isInRange) {
|
if (isInRange) {
|
||||||
foundEntry = vulnEntry;
|
foundEntry = vulnEntry;
|
||||||
break;
|
break;
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
+27
-21
@@ -246,21 +246,16 @@ function countScorecardWarnings(
|
|||||||
* @param tasks - Array of functions that return promises
|
* @param tasks - Array of functions that return promises
|
||||||
* @param limit - Maximum number of concurrent promises
|
* @param limit - Maximum number of concurrent promises
|
||||||
*/
|
*/
|
||||||
async function promisePool<T>(
|
async function promisePool(
|
||||||
tasks: (() => Promise<T>)[],
|
tasks: (() => Promise<void>)[],
|
||||||
limit: number
|
limit: number
|
||||||
): Promise<T[]> {
|
): Promise<void> {
|
||||||
const results: T[] = []
|
|
||||||
const executing: Set<Promise<void>> = new Set()
|
const executing: Set<Promise<void>> = new Set()
|
||||||
|
|
||||||
for (let i = 0; i < tasks.length; i++) {
|
for (const task of tasks) {
|
||||||
const task = tasks[i]
|
// Execute task and clean up
|
||||||
const index = i
|
|
||||||
|
|
||||||
// Wrap task execution to store result and clean up
|
|
||||||
const wrappedPromise = (async () => {
|
const wrappedPromise = (async () => {
|
||||||
const result = await task()
|
await task()
|
||||||
results[index] = result
|
|
||||||
})()
|
})()
|
||||||
|
|
||||||
executing.add(wrappedPromise)
|
executing.add(wrappedPromise)
|
||||||
@@ -278,7 +273,6 @@ async function promisePool<T>(
|
|||||||
|
|
||||||
// Wait for all remaining promises
|
// Wait for all remaining promises
|
||||||
await Promise.all(executing)
|
await Promise.all(executing)
|
||||||
return results
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addChangeVulnerabilitiesToSummary(
|
export async function addChangeVulnerabilitiesToSummary(
|
||||||
@@ -301,9 +295,18 @@ export async function addChangeVulnerabilitiesToSummary(
|
|||||||
|
|
||||||
// Query GitHub API for patch info with concurrency limiting
|
// Query GitHub API for patch info with concurrency limiting
|
||||||
// Store all vulnerability entries (may be multiple per package with different ranges)
|
// Store all vulnerability entries (may be multiple per package with different ranges)
|
||||||
|
// Pre-normalize ecosystem, package name, and range to avoid repeated work in rendering
|
||||||
const patchInfo: Record<
|
const patchInfo: Record<
|
||||||
string,
|
string,
|
||||||
{eco: string; pkg: string; range: string; patch: string}[]
|
{
|
||||||
|
eco: string
|
||||||
|
pkg: string
|
||||||
|
range: string
|
||||||
|
patch: string
|
||||||
|
ecoLower: string
|
||||||
|
pkgLower: string
|
||||||
|
normalizedRange: string
|
||||||
|
}[]
|
||||||
> = {}
|
> = {}
|
||||||
const apiClient = octokitClient()
|
const apiClient = octokitClient()
|
||||||
|
|
||||||
@@ -326,11 +329,17 @@ export async function addChangeVulnerabilitiesToSummary(
|
|||||||
const vulnRange = v.vulnerable_version_range || ''
|
const vulnRange = v.vulnerable_version_range || ''
|
||||||
const patchVerId = extractPatchVersionId(v.first_patched_version)
|
const patchVerId = extractPatchVersionId(v.first_patched_version)
|
||||||
if (patchVerId) {
|
if (patchVerId) {
|
||||||
|
// Pre-normalize and cache values to avoid repeated work in rendering loop
|
||||||
|
const trimmedRange = vulnRange.trim()
|
||||||
|
const normalizedRange = trimmedRange.replace(/,\s*/g, ' ')
|
||||||
patchInfo[advId].push({
|
patchInfo[advId].push({
|
||||||
eco: normalizedEco,
|
eco: normalizedEco,
|
||||||
pkg: pkgName,
|
pkg: pkgName,
|
||||||
range: vulnRange,
|
range: vulnRange,
|
||||||
patch: patchVerId
|
patch: patchVerId,
|
||||||
|
ecoLower: normalizedEco, // Ecosystem already normalized to lowercase
|
||||||
|
pkgLower: pkgName.toLowerCase(),
|
||||||
|
normalizedRange
|
||||||
})
|
})
|
||||||
core.debug(
|
core.debug(
|
||||||
`Added patch info for ${pkgName} (${normalizedEco}): ${patchVerId} for range ${vulnRange}`
|
`Added patch info for ${pkgName} (${normalizedEco}): ${patchVerId} for range ${vulnRange}`
|
||||||
@@ -380,23 +389,20 @@ export async function addChangeVulnerabilitiesToSummary(
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Find matching entry by ecosystem, package name (case-insensitive), and version range
|
// Find matching entry by ecosystem, package name (case-insensitive), and version range
|
||||||
|
// Use pre-normalized values from cache to avoid repeated lowercasing and range conversion
|
||||||
let foundEntry:
|
let foundEntry:
|
||||||
| {eco: string; pkg: string; range: string; patch: string}
|
| {eco: string; pkg: string; range: string; patch: string}
|
||||||
| undefined
|
| undefined
|
||||||
for (const vulnEntry of advisoryEntries) {
|
for (const vulnEntry of advisoryEntries) {
|
||||||
if (vulnEntry.eco.toLowerCase() !== ecoLowercase) continue
|
if (vulnEntry.ecoLower !== ecoLowercase) continue
|
||||||
if (vulnEntry.pkg.toLowerCase() !== packageLowercase) continue
|
if (vulnEntry.pkgLower !== packageLowercase) continue
|
||||||
|
|
||||||
// Normalize range once for both cache key and function call
|
|
||||||
const trimmedRangeOnce = vulnEntry.range.trim()
|
|
||||||
const normalizedRange = trimmedRangeOnce.replace(/,\s*/g, ' ')
|
|
||||||
|
|
||||||
// Use fail-open (failClosed: false) for patch selection to avoid
|
// Use fail-open (failClosed: false) for patch selection to avoid
|
||||||
// incorrectly matching on invalid ranges
|
// incorrectly matching on invalid ranges
|
||||||
// Use preTrimmed and preNormalized optimizations since we've done both
|
// Use preTrimmed and preNormalized optimizations since we've done both
|
||||||
const isInRange = versionInRange(
|
const isInRange = versionInRange(
|
||||||
normalizedChangeVersion,
|
normalizedChangeVersion,
|
||||||
normalizedRange,
|
vulnEntry.normalizedRange,
|
||||||
{preTrimmed: true, preNormalized: true, failClosed: false}
|
{preTrimmed: true, preNormalized: true, failClosed: false}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user