|
40 | 40 | const usage = [ |
41 | 41 | '**Usage:** `derek bench <subcommand> [args]` (also `decofe bench ...`).\n', |
42 | 42 | '- `bench invariant [compare-ref=REF] [timeout=N] [workers=N] [benchmark-type=property|optimization]`', |
| 43 | + '- `bench symex [compare-ref=REF] [timeout=N]`', |
43 | 44 | ].join(''); |
44 | 45 | const actor = context.payload.comment.user.login; |
45 | 46 | const trustedAssociations = new Set(['OWNER', 'MEMBER', 'COLLABORATOR']); |
|
81 | 82 | const subcommand = subMatch ? subMatch[1].toLowerCase() : ''; |
82 | 83 | const rawArgs = (subMatch ? afterBench.slice(subMatch[0].length) : afterBench).trim(); |
83 | 84 |
|
84 | | - // Only `invariant` is live; build/all are reserved. |
85 | | - const supported = new Set(['invariant']); |
| 85 | + // Only `invariant` and `symex` are live; build/all are reserved. |
| 86 | + const supported = new Set(['invariant', 'symex']); |
86 | 87 | const planned = new Set(['build', 'all']); |
87 | 88 | if (!subcommand) { |
88 | 89 | await failWithComment('Missing bench subcommand.'); |
@@ -207,6 +208,59 @@ jobs: |
207 | 208 | ].join(', '); |
208 | 209 | } |
209 | 210 |
|
| 211 | + if (subcommand === 'symex') { |
| 212 | + const opts = { |
| 213 | + 'compare-ref': 'master', |
| 214 | + timeout: '600', |
| 215 | + }; |
| 216 | + const { unknown, invalid } = parseArgs( |
| 217 | + opts, |
| 218 | + new Set(['compare-ref']), |
| 219 | + new Set(['timeout']), |
| 220 | + {}, |
| 221 | + ); |
| 222 | +
|
| 223 | + const safeRef = /^[A-Za-z0-9._/-]{1,128}$/; |
| 224 | + if (!safeRef.test(opts['compare-ref'])) { |
| 225 | + invalid.push("`compare-ref` may only contain letters, numbers, '.', '_', '-', and '/'"); |
| 226 | + } |
| 227 | + const timeout = Number(opts.timeout); |
| 228 | + if (!Number.isInteger(timeout) || timeout < 60 || timeout > 1800) { |
| 229 | + invalid.push('`timeout` must be between 60 and 1800 seconds'); |
| 230 | + } |
| 231 | +
|
| 232 | + const errors = []; |
| 233 | + if (unknown.length) errors.push(`Unknown argument(s): \`${unknown.join('`, `')}\``); |
| 234 | + if (invalid.length) errors.push(`Invalid value(s): ${invalid.join(', ')}`); |
| 235 | + if (errors.length) { |
| 236 | + await failWithComment(`Invalid \`bench symex\` command\n\n${errors.join('\n')}`); |
| 237 | + return; |
| 238 | + } |
| 239 | +
|
| 240 | + // PR identity + safe knobs only; benchmark targets and pod sizing |
| 241 | + // are owned by the server-side foundry-bench workflow. |
| 242 | + payload = { |
| 243 | + repository: repoFullName, |
| 244 | + event: 'foundry-bench', |
| 245 | + data: { |
| 246 | + subcommand: 'symex', |
| 247 | + pr_number: String(context.issue.number), |
| 248 | + head_repo: pr.head.repo.full_name, |
| 249 | + actor, |
| 250 | + foundry_git_ref: pr.head.sha, |
| 251 | + compare_foundry_git_ref: opts['compare-ref'], |
| 252 | + timeout_seconds: opts.timeout, |
| 253 | + }, |
| 254 | + }; |
| 255 | +
|
| 256 | + summary = [ |
| 257 | + `subcommand: \`symex\``, |
| 258 | + `PR SHA: \`${pr.head.sha.slice(0, 12)}\``, |
| 259 | + `compare-ref: \`${opts['compare-ref']}\``, |
| 260 | + `timeout: \`${opts.timeout}s\``, |
| 261 | + ].join(', '); |
| 262 | + } |
| 263 | +
|
210 | 264 | core.setOutput('actor', actor); |
211 | 265 | core.setOutput('subcommand', subcommand); |
212 | 266 | core.setOutput('summary', summary); |
|
0 commit comments