Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions ci/diagnose.js
Original file line number Diff line number Diff line change
Expand Up @@ -506,8 +506,10 @@ function checkSupportedFrameworks (results, frameworks) {
)
}

for (const note of framework.notes || []) {
addResult(results, 'info', `${framework.name} capability note`, note)
if (framework.notes) {
for (const note of framework.notes) {
addResult(results, 'info', `${framework.name} capability note`, note)
}
}
}
}
Expand Down
12 changes: 9 additions & 3 deletions ci/test-optimization-validation/approval-artifacts.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,16 @@ function getCoveredFilesManifest (material) {
}
for (const executable of material.executables) {
if (executable.path && executable.sha256) files.set(executable.path, executable.sha256)
for (const delegated of executable.delegated || []) files.set(delegated.path, delegated.sha256)
for (const entrypoint of executable.entrypoints || []) files.set(entrypoint.path, entrypoint.sha256)
if (executable.delegated) {
for (const delegated of executable.delegated) files.set(delegated.path, delegated.sha256)
}
if (executable.entrypoints) {
for (const entrypoint of executable.entrypoints) files.set(entrypoint.path, entrypoint.sha256)
}
}
if (material.projectFiles) {
for (const projectFile of material.projectFiles) files.set(projectFile.path, projectFile.sha256)
}
for (const projectFile of material.projectFiles || []) files.set(projectFile.path, projectFile.sha256)

return [...files]
.sort(([left], [right]) => left.localeCompare(right))
Expand Down
42 changes: 24 additions & 18 deletions ci/test-optimization-validation/approval.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,21 +240,25 @@ function getApprovalCommand (id, command) {
*/
function getGeneratedFileMaterial (manifest, requestedScenario) {
const files = []
for (const framework of manifest.frameworks || []) {
const strategy = framework.generatedTestStrategy
const selectedPaths = getSelectedGeneratedPaths(strategy, requestedScenario)
for (const file of strategy?.files || []) {
if (!selectedPaths.has(path.resolve(file.path))) continue
const content = `${file.contentLines.join('\n')}\n`
files.push(sanitizeForReport({
frameworkId: framework.id,
path: path.resolve(file.path),
sha256: crypto.createHash('sha256').update(content).digest('hex'),
content,
removeAfterValidation: (strategy.cleanupPaths || []).some(cleanupPath => {
return path.resolve(cleanupPath) === path.resolve(file.path)
}),
}))
if (manifest.frameworks) {
for (const framework of manifest.frameworks) {
const strategy = framework.generatedTestStrategy
const selectedPaths = getSelectedGeneratedPaths(strategy, requestedScenario)
if (strategy?.files) {
for (const file of strategy.files) {
if (!selectedPaths.has(path.resolve(file.path))) continue
const content = `${file.contentLines.join('\n')}\n`
files.push(sanitizeForReport({
frameworkId: framework.id,
path: path.resolve(file.path),
sha256: crypto.createHash('sha256').update(content).digest('hex'),
content,
removeAfterValidation: (strategy.cleanupPaths || []).some(cleanupPath => {
return path.resolve(cleanupPath) === path.resolve(file.path)
}),
}))
}
}
}
}
return files
Expand All @@ -278,9 +282,11 @@ function getSelectedGeneratedPaths (strategy, requestedScenario) {
return path.resolve(scenario.testIdentities[0].file)
}))
const selectedPaths = new Set()
for (const file of strategy.files || []) {
const filename = path.resolve(file.path)
if (!requestedScenario || !scenarioPaths.has(filename)) selectedPaths.add(filename)
if (strategy.files) {
for (const file of strategy.files) {
const filename = path.resolve(file.path)
if (!requestedScenario || !scenarioPaths.has(filename)) selectedPaths.add(filename)
}
}
if (generatedId) {
const scenario = strategy.scenarios?.find(candidate => candidate.id === generatedId)
Expand Down
40 changes: 23 additions & 17 deletions ci/test-optimization-validation/ci-discovery.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,22 @@ function buildCiDiscovery ({ manifest, diagnosis }) {
function getManifestWorkflowLocations (manifest) {
const root = manifest.repository?.root
const locations = []
for (const framework of manifest.frameworks || []) {
const configFile = framework.ciWiring?.configFile
if (typeof configFile !== 'string') continue
if (!root || !path.isAbsolute(configFile)) {
locations.push(configFile)
continue
if (manifest.frameworks) {
for (const framework of manifest.frameworks) {
const configFile = framework.ciWiring?.configFile
if (typeof configFile !== 'string') continue
if (!root || !path.isAbsolute(configFile)) {
locations.push(configFile)
continue
}

const relative = path.relative(root, configFile)
const isRelativePath = relative && relative !== '..' && !relative.startsWith(`..${path.sep}`) &&
!path.isAbsolute(relative)
locations.push(isRelativePath
? relative.split(path.sep).join('/')
: configFile)
}

const relative = path.relative(root, configFile)
locations.push(relative && relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative)
? relative.split(path.sep).join('/')
: configFile)
}
return uniqueStrings(locations)
}
Expand Down Expand Up @@ -106,12 +110,14 @@ function getCiDiscoveryContradictions ({ manifest, declaredFound, staticFound })
)
}

for (const framework of manifest.frameworks || []) {
if (!frameworkClaimsNoCi(framework)) continue
contradictions.push(
`framework ${framework.id || '<unknown>'} records no CI workflow, but static diagnosis found ` +
formatList(staticFound)
)
if (manifest.frameworks) {
for (const framework of manifest.frameworks) {
if (!frameworkClaimsNoCi(framework)) continue
contradictions.push(
`framework ${framework.id || '<unknown>'} records no CI workflow, but static diagnosis found ` +
formatList(staticFound)
)
}
}

return contradictions
Expand Down
12 changes: 7 additions & 5 deletions ci/test-optimization-validation/command-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -555,11 +555,13 @@ function buildOfflineValidationEnv ({ fixture, outputRoot }) {
*/
function assertNoInlineValidationEnvOverrides (command, env) {
if (!env[VALIDATION_MODE_ENV]) return
for (const name of Object.keys(command.env || {})) {
const normalized = process.platform === 'win32' ? name.toUpperCase() : name
if (VALIDATION_RESERVED_ENV_NAMES.some(reserved => environmentNamesEqual(reserved, name)) ||
isDatadogEnvironmentName(name) || normalized.startsWith('OTEL_')) {
throw new Error(`Direct-runner adapter must not override validator-controlled environment variable ${name}.`)
if (command.env) {
for (const name of Object.keys(command.env)) {
const normalized = process.platform === 'win32' ? name.toUpperCase() : name
if (VALIDATION_RESERVED_ENV_NAMES.some(reserved => environmentNamesEqual(reserved, name)) ||
isDatadogEnvironmentName(name) || normalized.startsWith('OTEL_')) {
throw new Error(`Direct-runner adapter must not override validator-controlled environment variable ${name}.`)
}
}
}
}
Expand Down
6 changes: 4 additions & 2 deletions ci/test-optimization-validation/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,10 @@ function setEnvironmentValue (environment, name, value, platform = process.platf
* @returns {void}
*/
function mergeEnvironment (target, source, platform = process.platform) {
for (const [name, value] of Object.entries(source || {})) {
setEnvironmentValue(target, name, value, platform)
if (source) {
for (const [name, value] of Object.entries(source)) {
setEnvironmentValue(target, name, value, platform)
}
}
}

Expand Down
28 changes: 15 additions & 13 deletions ci/test-optimization-validation/executable.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,21 @@ function getResolvedExecutable (command) {
*/
function bindManifestExecutables (manifest) {
const identities = []
for (const framework of manifest.frameworks || []) {
if (framework.status !== 'runnable') continue
const command = getManifestCommands({ frameworks: [framework] })[0]?.[1]
try {
const identity = getCommandExecutableIdentity(command, manifest.repository.root)
bindApprovedExecutable(framework.validation, identity)
identities.push({ id: `framework:${framework.id}`, ...identity })
} catch (error) {
identities.push({
id: `framework:${framework.id}`,
unavailable: true,
reason: error?.message || String(error),
})
if (manifest.frameworks) {
for (const framework of manifest.frameworks) {
if (framework.status !== 'runnable') continue
const command = getManifestCommands({ frameworks: [framework] })[0]?.[1]
try {
const identity = getCommandExecutableIdentity(command, manifest.repository.root)
bindApprovedExecutable(framework.validation, identity)
identities.push({ id: `framework:${framework.id}`, ...identity })
} catch (error) {
identities.push({
id: `framework:${framework.id}`,
unavailable: true,
reason: error?.message || String(error),
})
}
}
}
return identities
Expand Down
66 changes: 38 additions & 28 deletions ci/test-optimization-validation/generated-files.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,16 @@ function cleanupGeneratedFiles (manifest, { keep = false } = {}) {
filesRemoved: 0,
filesRetained: 0,
}
for (const framework of manifest.frameworks || []) {
const strategy = framework.generatedTestStrategy
addCleanupOutcome(
outcome,
cleanupPaths(getSafeCleanupPaths(framework, strategy, { includeGeneratedFiles: true })),
'files'
)
addCleanupOutcome(outcome, cleanupCreatedDirectories(framework.project.root), 'directories')
if (manifest.frameworks) {
for (const framework of manifest.frameworks) {
const strategy = framework.generatedTestStrategy
addCleanupOutcome(
outcome,
cleanupPaths(getSafeCleanupPaths(framework, strategy, { includeGeneratedFiles: true })),
'files'
)
addCleanupOutcome(outcome, cleanupCreatedDirectories(framework.project.root), 'directories')
}
}
outcome.status = outcome.filesRetained > 0 || outcome.directoriesRetained > 0
? 'incomplete'
Expand Down Expand Up @@ -168,13 +170,15 @@ function initializeRuntimeCleanupFiles (framework, strategy) {
if (initializedCleanupStrategies.has(strategy)) return

const generatedFiles = new Set((strategy.files || []).map(file => validateGeneratedFilePath(framework, file.path)))
for (const cleanupPath of strategy.cleanupPaths || []) {
const filename = validateCleanupPath(framework, cleanupPath)
if (generatedFiles.has(filename) || isDirectory(filename) || !isNamespacedRuntimeFile(filename)) continue
if (fs.existsSync(filename)) {
throw new Error(`Refusing to delete pre-existing generated validation runtime file: ${filename}`)
if (strategy.cleanupPaths) {
for (const cleanupPath of strategy.cleanupPaths) {
const filename = validateCleanupPath(framework, cleanupPath)
if (generatedFiles.has(filename) || isDirectory(filename) || !isNamespacedRuntimeFile(filename)) continue
if (fs.existsSync(filename)) {
throw new Error(`Refusing to delete pre-existing generated validation runtime file: ${filename}`)
}
authorizedRuntimeCleanupFiles.set(filename, authorizePathForCleanup(framework.project.root, filename))
}
authorizedRuntimeCleanupFiles.set(filename, authorizePathForCleanup(framework.project.root, filename))
}
initializedCleanupStrategies.add(strategy)
}
Expand All @@ -183,20 +187,24 @@ function getSafeCleanupPaths (framework, strategy, { includeGeneratedFiles }) {
if (!strategy) return []

const generatedFiles = new Set()
for (const file of strategy.files || []) {
generatedFiles.add(validateGeneratedFilePath(framework, file.path))
if (strategy.files) {
for (const file of strategy.files) {
generatedFiles.add(validateGeneratedFilePath(framework, file.path))
}
}

const cleanupPaths = []
for (const cleanupPath of strategy.cleanupPaths || []) {
const filename = validateCleanupPath(framework, cleanupPath)
if (generatedFiles.has(filename)) {
if (includeGeneratedFiles && writtenGeneratedFiles.has(filename)) cleanupPaths.push(filename)
continue
}
if (strategy.cleanupPaths) {
for (const cleanupPath of strategy.cleanupPaths) {
const filename = validateCleanupPath(framework, cleanupPath)
if (generatedFiles.has(filename)) {
if (includeGeneratedFiles && writtenGeneratedFiles.has(filename)) cleanupPaths.push(filename)
continue
}

if (authorizedRuntimeCleanupFiles.has(filename)) {
cleanupPaths.push(filename)
if (authorizedRuntimeCleanupFiles.has(filename)) {
cleanupPaths.push(filename)
}
}
}

Expand Down Expand Up @@ -264,10 +272,12 @@ function authorizePathForCleanup (root, filename) {
}

function pinRuntimeCleanupParents (strategy) {
for (const cleanupPath of strategy.cleanupPaths || []) {
const authorization = authorizedRuntimeCleanupFiles.get(path.resolve(cleanupPath))
if (authorization && authorization.physicalParent === undefined) {
pinCleanupParent(authorization, cleanupPath)
if (strategy.cleanupPaths) {
for (const cleanupPath of strategy.cleanupPaths) {
const authorization = authorizedRuntimeCleanupFiles.get(path.resolve(cleanupPath))
if (authorization && authorization.physicalParent === undefined) {
pinCleanupParent(authorization, cleanupPath)
}
}
}
}
Expand Down
18 changes: 10 additions & 8 deletions ci/test-optimization-validation/generated-verifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,14 +209,16 @@ function getVerificationFailure (framework, evidence, artifacts, scenario, resul
function getGeneratedRuntimeFileStatus (strategy) {
const generatedFiles = new Set((strategy.files || []).map(file => path.resolve(file.path)))
let expectsRuntimeFile = false
for (const cleanupPath of strategy.cleanupPaths || []) {
const filename = path.resolve(cleanupPath)
if (generatedFiles.has(filename)) continue
expectsRuntimeFile = true
try {
const stat = fs.lstatSync(filename)
if (!stat.isSymbolicLink() && stat.isFile()) return true
} catch {}
if (strategy.cleanupPaths) {
for (const cleanupPath of strategy.cleanupPaths) {
const filename = path.resolve(cleanupPath)
if (generatedFiles.has(filename)) continue
expectsRuntimeFile = true
try {
const stat = fs.lstatSync(filename)
if (!stat.isSymbolicLink() && stat.isFile()) return true
} catch {}
}
}
return expectsRuntimeFile ? false : undefined
}
Expand Down
28 changes: 16 additions & 12 deletions ci/test-optimization-validation/manifest-scaffold.js
Original file line number Diff line number Diff line change
Expand Up @@ -457,15 +457,17 @@ function buildFramework (repositoryRoot, detection, ciDiscovery) {

function getImplicitConfigFiles (framework, projectRoot, repositoryRoot) {
const files = []
for (const basename of IMPLICIT_CONFIG_FILENAMES[framework] || []) {
const filename = path.join(projectRoot, basename)
try {
const stat = fs.lstatSync(filename)
const physical = fs.realpathSync(filename)
if (stat.isFile() && !stat.isSymbolicLink() &&
fs.statSync(physical).isFile() &&
isPathInside(fs.realpathSync(repositoryRoot), physical)) files.push(physical)
} catch {}
if (IMPLICIT_CONFIG_FILENAMES[framework]) {
for (const basename of IMPLICIT_CONFIG_FILENAMES[framework]) {
const filename = path.join(projectRoot, basename)
try {
const stat = fs.lstatSync(filename)
const physical = fs.realpathSync(filename)
if (stat.isFile() && !stat.isSymbolicLink() &&
fs.statSync(physical).isFile() &&
isPathInside(fs.realpathSync(repositoryRoot), physical)) files.push(physical)
} catch {}
}
}
return files
}
Expand Down Expand Up @@ -1135,9 +1137,11 @@ function getFrameworkPackageJson (repositoryRoot, detection) {
if (preciseLocation) return findOwningPackageJson(repositoryRoot, preciseLocation)

const owners = new Map()
for (const location of detection.locations || []) {
const owner = findOwningPackageJson(repositoryRoot, location)
if (owner) owners.set(owner.path, owner)
if (detection.locations) {
for (const location of detection.locations) {
const owner = findOwningPackageJson(repositoryRoot, location)
if (owner) owners.set(owner.path, owner)
}
}
if (owners.size === 1) return [...owners.values()][0]
}
Expand Down
Loading
Loading