|
39 | 39 | script: | |
40 | 40 | const usage = [ |
41 | 41 | '**Usage:** `derek bench <subcommand> [args]` (also `decofe bench ...`).\n', |
42 | | - '- `bench invariant [compare-ref=REF] [timeout=N] [workers=N] [benchmark-type=property|optimization]`', |
43 | | - '- `bench symex [compare-ref=REF] [timeout=N]`', |
| 42 | + '- `bench invariant [compare-ref=REF] [timeout=N] [workers=N] [benchmark-type=property|optimization]`\n', |
| 43 | + '- `bench symex [compare-ref=REF] [timeout=N]`\n', |
| 44 | + '- `bench test [compare-ref=REF] [timeout=N] [isolate=true|false]`\n', |
| 45 | + '- `bench build [compare-ref=REF] [timeout=N] [cache=true|false]`\n', |
| 46 | + '- `bench fuzz [compare-ref=REF] [timeout=N]`\n', |
| 47 | + '- `bench coverage [compare-ref=REF] [timeout=N]`\n', |
| 48 | + '- `bench all [compare-ref=REF] [timeout=N]`', |
44 | 49 | ].join(''); |
45 | 50 | const actor = context.payload.comment.user.login; |
46 | 51 | const trustedAssociations = new Set(['OWNER', 'MEMBER', 'COLLABORATOR']); |
@@ -82,17 +87,11 @@ jobs: |
82 | 87 | const subcommand = subMatch ? subMatch[1].toLowerCase() : ''; |
83 | 88 | const rawArgs = (subMatch ? afterBench.slice(subMatch[0].length) : afterBench).trim(); |
84 | 89 |
|
85 | | - // Only `invariant` and `symex` are live; build/all are reserved. |
86 | | - const supported = new Set(['invariant', 'symex']); |
87 | | - const planned = new Set(['build', 'all']); |
| 90 | + const supported = new Set(['invariant', 'symex', 'test', 'build', 'fuzz', 'coverage', 'all']); |
88 | 91 | if (!subcommand) { |
89 | 92 | await failWithComment('Missing bench subcommand.'); |
90 | 93 | return; |
91 | 94 | } |
92 | | - if (planned.has(subcommand)) { |
93 | | - await failWithComment(`\`bench ${subcommand}\` is not available yet.`); |
94 | | - return; |
95 | | - } |
96 | 95 | if (!supported.has(subcommand)) { |
97 | 96 | await failWithComment(`Unknown bench subcommand \`${subcommand}\`.`); |
98 | 97 | return; |
@@ -139,6 +138,62 @@ jobs: |
139 | 138 | return { unknown, invalid }; |
140 | 139 | }; |
141 | 140 |
|
| 141 | + const parseFoundryBenchArgs = (opts, boolArgs = new Set()) => { |
| 142 | + const { unknown, invalid } = parseArgs( |
| 143 | + opts, |
| 144 | + new Set(['compare-ref']), |
| 145 | + new Set(['timeout']), |
| 146 | + Object.fromEntries(Array.from(boolArgs).map((key) => [key, new Set(['true', 'false'])])), |
| 147 | + ); |
| 148 | +
|
| 149 | + const safeRef = /^[A-Za-z0-9._/-]{1,128}$/; |
| 150 | + if (!safeRef.test(opts['compare-ref'])) { |
| 151 | + invalid.push("`compare-ref` may only contain letters, numbers, '.', '_', '-', and '/'"); |
| 152 | + } |
| 153 | + const timeout = Number(opts.timeout); |
| 154 | + if (!Number.isInteger(timeout) || timeout < 60 || timeout > 1800) { |
| 155 | + invalid.push('`timeout` must be between 60 and 1800 seconds'); |
| 156 | + } |
| 157 | + return { unknown, invalid }; |
| 158 | + }; |
| 159 | +
|
| 160 | + const buildFoundryBenchPayload = (opts, benchmarks) => ({ |
| 161 | + repository: repoFullName, |
| 162 | + event: 'foundry-bench', |
| 163 | + data: { |
| 164 | + subcommand, |
| 165 | + benchmarks, |
| 166 | + pr_number: String(context.issue.number), |
| 167 | + head_repo: pr.head.repo.full_name, |
| 168 | + actor, |
| 169 | + foundry_git_ref: pr.head.sha, |
| 170 | + compare_foundry_git_ref: opts['compare-ref'], |
| 171 | + timeout_seconds: opts.timeout, |
| 172 | + }, |
| 173 | + }); |
| 174 | +
|
| 175 | + const foundryBenchSummary = (opts, benchmarks, extra = []) => [ |
| 176 | + `subcommand: \`${subcommand}\``, |
| 177 | + `PR SHA: \`${pr.head.sha.slice(0, 12)}\``, |
| 178 | + `compare-ref: \`${opts['compare-ref']}\``, |
| 179 | + `timeout: \`${opts.timeout}s\``, |
| 180 | + `benchmarks: \`${benchmarks}\``, |
| 181 | + ...extra, |
| 182 | + ].join(', '); |
| 183 | +
|
| 184 | + const foundryBenchmarksBySubcommand = { |
| 185 | + symex: 'forge_symbolic_test', |
| 186 | + fuzz: 'forge_fuzz_test', |
| 187 | + coverage: 'forge_coverage', |
| 188 | + all: [ |
| 189 | + 'forge_isolate_test', |
| 190 | + 'forge_build_no_cache', |
| 191 | + 'forge_fuzz_test', |
| 192 | + 'forge_coverage', |
| 193 | + 'forge_symbolic_test', |
| 194 | + ].join(','), |
| 195 | + }; |
| 196 | +
|
142 | 197 | let payload; |
143 | 198 | let summary; |
144 | 199 |
|
@@ -208,57 +263,46 @@ jobs: |
208 | 263 | ].join(', '); |
209 | 264 | } |
210 | 265 |
|
211 | | - if (subcommand === 'symex') { |
| 266 | + if (subcommand !== 'invariant') { |
212 | 267 | const opts = { |
213 | 268 | 'compare-ref': 'master', |
214 | 269 | timeout: '600', |
215 | 270 | }; |
216 | | - const { unknown, invalid } = parseArgs( |
217 | | - opts, |
218 | | - new Set(['compare-ref']), |
219 | | - new Set(['timeout']), |
220 | | - {}, |
221 | | - ); |
222 | 271 |
|
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 '/'"); |
| 272 | + const boolArgs = new Set(); |
| 273 | + if (subcommand === 'test') { |
| 274 | + opts.isolate = 'true'; |
| 275 | + boolArgs.add('isolate'); |
226 | 276 | } |
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'); |
| 277 | + if (subcommand === 'build') { |
| 278 | + opts.cache = 'false'; |
| 279 | + boolArgs.add('cache'); |
| 280 | + } |
| 281 | +
|
| 282 | + const { unknown, invalid } = parseFoundryBenchArgs(opts, boolArgs); |
| 283 | +
|
| 284 | + let benchmarks = foundryBenchmarksBySubcommand[subcommand]; |
| 285 | + const extra = []; |
| 286 | + if (subcommand === 'test') { |
| 287 | + benchmarks = opts.isolate === 'true' ? 'forge_isolate_test' : 'forge_test'; |
| 288 | + extra.push(`isolate: \`${opts.isolate}\``); |
| 289 | + } else if (subcommand === 'build') { |
| 290 | + benchmarks = opts.cache === 'true' ? 'forge_build_with_cache' : 'forge_build_no_cache'; |
| 291 | + extra.push(`cache: \`${opts.cache}\``); |
230 | 292 | } |
231 | 293 |
|
232 | 294 | const errors = []; |
233 | 295 | if (unknown.length) errors.push(`Unknown argument(s): \`${unknown.join('`, `')}\``); |
234 | 296 | if (invalid.length) errors.push(`Invalid value(s): ${invalid.join(', ')}`); |
235 | 297 | if (errors.length) { |
236 | | - await failWithComment(`Invalid \`bench symex\` command\n\n${errors.join('\n')}`); |
| 298 | + await failWithComment(`Invalid \`bench ${subcommand}\` command\n\n${errors.join('\n')}`); |
237 | 299 | return; |
238 | 300 | } |
239 | 301 |
|
240 | 302 | // PR identity + safe knobs only; benchmark targets and pod sizing |
241 | 303 | // 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(', '); |
| 304 | + payload = buildFoundryBenchPayload(opts, benchmarks); |
| 305 | + summary = foundryBenchSummary(opts, benchmarks, extra); |
262 | 306 | } |
263 | 307 |
|
264 | 308 | core.setOutput('actor', actor); |
|
0 commit comments