-
-
Notifications
You must be signed in to change notification settings - Fork 187
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ci/cd: trigger URL checks more, and limit amount
Fix all URL checks failing in GitHub runner due to: - Missing Happy Eyeballs in Node.js nodejs/undici$1531 nodejs/node$41625 - Missing IPv6 support in GitHub runners: actions/runner$3138 actions/runner-images$668 Tried (did not work): 1) ``` import dns from 'dns'; dns.setDefaultResultOrder('ipv4first'); ``` 2) Bumping node to v20. 3) TODO: Try autoSelectFamily - Or is it due too to many max connections? Test this. Mentioned in comment nodejs/node$41625. Key changes: - Run URL checks more frequently on every change. - Introduce environment variable to randomly select and limit URLs tested, this way the tests will provide quicker feedback on code changes. Other supporting changes: - Log more information about test before running the test to enable easier troubleshooting. - Move shuffle function for arrays for reusability and missing tests.
- Loading branch information
1 parent
287b8e6
commit 3341de4
Showing
6 changed files
with
218 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/* | ||
Shuffle an array of strings, returning a new array with elements in random order. | ||
Uses the Fisher-Yates (or Durstenfeld) algorithm. | ||
*/ | ||
export function shuffle<T>(array: readonly T[]): T[] { | ||
const shuffledArray = [...array]; | ||
for (let i = array.length - 1; i > 0; i--) { | ||
const j = Math.floor(Math.random() * (i + 1)); | ||
[shuffledArray[i], shuffledArray[j]] = [shuffledArray[j], shuffledArray[i]]; | ||
} | ||
return shuffledArray; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import type { IApplication } from '@/domain/IApplication'; | ||
import type { TestExecutionDetailsLogger } from './TestExecutionDetailsLogger'; | ||
|
||
interface UrlExtractionContext { | ||
readonly logger: TestExecutionDetailsLogger; | ||
readonly application: IApplication; | ||
readonly urlExclusionPatterns: readonly RegExp[]; | ||
} | ||
|
||
export function extractDocumentationUrls( | ||
context: UrlExtractionContext, | ||
): string[] { | ||
const urlsInApplication = extractUrlsFromApplication(context.application); | ||
context.logger.logLabeledInformation( | ||
'Extracted URLs from application', | ||
urlsInApplication.length.toString(), | ||
); | ||
const uniqueUrls = filterDuplicateUrls(urlsInApplication); | ||
context.logger.logLabeledInformation( | ||
'Unique URLs after deduplication', | ||
`${uniqueUrls.length} (duplicates removed)`, | ||
); | ||
context.logger.logLabeledInformation( | ||
'Exclusion patterns for URLs', | ||
context.urlExclusionPatterns.length === 0 | ||
? 'None (all URLs included)' | ||
: context.urlExclusionPatterns.map((pattern, index) => `${index + 1}) ${pattern.toString()}`).join('\n'), | ||
); | ||
const includedUrls = filterUrlsExcludingPatterns(uniqueUrls, context.urlExclusionPatterns); | ||
context.logger.logLabeledInformation( | ||
'URLs extracted for testing', | ||
`${includedUrls.length} (after applying exclusion patterns; ${uniqueUrls.length - includedUrls.length} URLs ignored)`, | ||
); | ||
return includedUrls; | ||
} | ||
|
||
function extractUrlsFromApplication(application: IApplication): string[] { | ||
return [ // Get all executables | ||
...application.collections.flatMap((c) => c.getAllCategories()), | ||
...application.collections.flatMap((c) => c.getAllScripts()), | ||
] | ||
// Get all docs | ||
.flatMap((documentable) => documentable.docs) | ||
// Parse all URLs | ||
.flatMap((docString) => extractUrlsExcludingCodeBlocks(docString)); | ||
} | ||
|
||
function filterDuplicateUrls(urls: readonly string[]): string[] { | ||
return urls.filter((url, index, array) => array.indexOf(url) === index); | ||
} | ||
|
||
function filterUrlsExcludingPatterns( | ||
urls: readonly string[], | ||
patterns: readonly RegExp[], | ||
): string[] { | ||
return urls.filter((url) => !patterns.some((pattern) => pattern.test(url))); | ||
} | ||
|
||
function extractUrlsExcludingCodeBlocks(textWithInlineCode: string): string[] { | ||
/* | ||
Matches URLs: | ||
- Excludes inline code blocks as they may contain URLs not intended for user interaction | ||
and not guaranteed to support expected HTTP methods, leading to false-negatives. | ||
- Supports URLs containing parentheses, avoiding matches within code that might not represent | ||
actual links. | ||
*/ | ||
const nonCodeBlockUrlRegex = /(?<!`)(https?:\/\/[^\s`"<>()]+(?:\([^\s`"<>()]*\))?[^\s`"<>()]*)/g; | ||
return textWithInlineCode.match(nonCodeBlockUrlRegex) || []; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { indentText } from '@tests/shared/Text'; | ||
|
||
export class TestExecutionDetailsLogger { | ||
public logTestSectionStartDelimiter(): void { | ||
this.logSectionDelimiterLine(); | ||
} | ||
|
||
public logTestSectionEndDelimiter(): void { | ||
this.logSectionDelimiterLine(); | ||
} | ||
|
||
public logLabeledInformation( | ||
label: string, | ||
detailedInformation: string, | ||
): void { | ||
console.log([ | ||
`${label}:`, | ||
indentText(detailedInformation), | ||
].join('\n')); | ||
} | ||
|
||
private logSectionDelimiterLine(): void { | ||
const horizontalLine = '─'.repeat(40); | ||
console.log(horizontalLine); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { describe, it, expect } from 'vitest'; | ||
import { shuffle } from '@/application/Common/Shuffle'; | ||
|
||
describe('Shuffle', () => { | ||
describe('shuffle', () => { | ||
it('returns a new array', () => { | ||
// arrange | ||
const inputArray = ['a', 'b', 'c', 'd']; | ||
// act | ||
const result = shuffle(inputArray); | ||
// assert | ||
expect(result).not.to.equal(inputArray); | ||
}); | ||
|
||
it('returns an array of the same length', () => { | ||
// arrange | ||
const inputArray = ['a', 'b', 'c', 'd']; | ||
// act | ||
const result = shuffle(inputArray); | ||
// assert | ||
expect(result.length).toBe(inputArray.length); | ||
}); | ||
|
||
it('contains the same elements', () => { | ||
// arrange | ||
const inputArray = ['a', 'b', 'c', 'd']; | ||
// act | ||
const result = shuffle(inputArray); | ||
// assert | ||
expect(result).to.have.members(inputArray); | ||
}); | ||
|
||
it('does not modify the input array', () => { | ||
// arrange | ||
const inputArray = ['a', 'b', 'c', 'd']; | ||
const inputArrayCopy = [...inputArray]; | ||
// act | ||
shuffle(inputArray); | ||
// assert | ||
expect(inputArray).to.deep.equal(inputArrayCopy); | ||
}); | ||
|
||
it('handles an empty array correctly', () => { | ||
// arrange | ||
const inputArray: string[] = []; | ||
// act | ||
const result = shuffle(inputArray); | ||
// assert | ||
expect(result).have.lengthOf(0); | ||
}); | ||
}); | ||
}); |