Skip to repository content862 lines · 31.9 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T07:46:42.965Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
run_tests.rs
1use gh_workflow::{
2 Container, Event, Expression, Input, Job, Level, MergeGroup, Permissions, Port, PullRequest,
3 Push, Run, Step, Strategy, Use, UsesJob, Workflow,
4};
5use indexmap::IndexMap;
6use indoc::formatdoc;
7use serde_json::json;
8
9use crate::tasks::workflows::{
10 steps::{
11 CommonJobConditions, CommonPermissionSets, cache_rust_dependencies_namespace,
12 repository_owner_guard_expression, use_clang,
13 },
14 vars::{self, PathCondition},
15};
16
17use super::{
18 deploy_docs,
19 runners::{self, Arch, Platform},
20 steps::{self, FluentBuilder, NamedJob, named, release_job},
21};
22
23pub(crate) fn run_tests() -> Workflow {
24 // Specify anything which should potentially skip full test suite in this regex:
25 // - docs/
26 // - script/update_top_ranking_issues/
27 // - .github/ISSUE_TEMPLATE/
28 // - .github/workflows/ (except .github/workflows/ci.yml)
29 // - extensions/ (these have their own test workflow)
30 let should_run_tests = PathCondition::inverted(
31 "run_tests",
32 r"^(docs/|script/update_top_ranking_issues/|\.github/(ISSUE_TEMPLATE|workflows/(?!run_tests))|extensions/)",
33 );
34 let should_check_docs = PathCondition::new("run_docs", r"^(docs/|crates/.*\.rs)");
35 let should_check_scripts = PathCondition::new(
36 "run_action_checks",
37 r"^\.github/(workflows/|actions/|actionlint.yml)|tooling/xtask|script/",
38 );
39 let should_check_licences =
40 PathCondition::new("run_licenses", r"^(Cargo.lock|script/.*licenses)");
41
42 let orchestrate = orchestrate(&[
43 &should_check_scripts,
44 &should_check_docs,
45 &should_check_licences,
46 &should_run_tests,
47 ]);
48
49 let mut jobs = vec![
50 orchestrate,
51 check_style(),
52 should_run_tests
53 .and_not_in_merge_queue()
54 .then(clippy(Platform::Windows, None, true)),
55 should_run_tests
56 .and_always()
57 .then(clippy(Platform::Linux, None, true)),
58 should_run_tests
59 .and_not_in_merge_queue()
60 .then(clippy(Platform::Mac, None, true)),
61 should_run_tests.and_not_in_merge_queue().then(clippy(
62 Platform::Mac,
63 Some(Arch::X86_64),
64 true,
65 )),
66 should_run_tests
67 .and_not_in_merge_queue()
68 .then(run_platform_tests(Platform::Windows)),
69 should_run_tests
70 .and_not_in_merge_queue()
71 .then(run_platform_tests(Platform::Linux)),
72 should_run_tests
73 .and_not_in_merge_queue()
74 .then(run_platform_tests(Platform::Mac)),
75 should_run_tests
76 .and_not_in_merge_queue()
77 .then(miri_scheduler()),
78 should_run_tests.and_not_in_merge_queue().then(doctests()),
79 should_run_tests
80 .and_not_in_merge_queue()
81 .then(check_workspace_binaries()),
82 should_run_tests
83 .and_not_in_merge_queue()
84 .then(build_visual_tests_binary()),
85 should_run_tests.and_not_in_merge_queue().then(check_wasm()),
86 should_run_tests
87 .and_not_in_merge_queue()
88 .then(check_dependencies()), // could be more specific here?
89 should_check_docs
90 .and_not_in_merge_queue()
91 .then(deploy_docs::check_docs()),
92 should_check_licences
93 .and_not_in_merge_queue()
94 .then(check_licenses()),
95 should_check_scripts.and_always().then(check_scripts(true)),
96 ];
97 let ext_tests = extension_tests();
98 let tests_pass = tests_pass(&jobs, &[&ext_tests.name]);
99
100 // TODO: For merge queues, this should fail in the merge queue context
101 jobs.push(
102 should_run_tests
103 .and_always()
104 .then(check_postgres_and_protobuf_migrations()),
105 ); // could be more specific here?
106
107 named::workflow()
108 .with_minimal_permissions()
109 .add_event(
110 Event::default()
111 .push(
112 Push::default()
113 .add_branch("main")
114 .add_branch("v[0-9]+.[0-9]+.x"),
115 )
116 .pull_request(PullRequest::default().add_branch("**"))
117 .merge_group(MergeGroup::default()),
118 )
119 .concurrency(vars::one_workflow_per_non_main_branch())
120 .add_env(("CARGO_TERM_COLOR", "always"))
121 .add_env(("RUST_BACKTRACE", 1))
122 .add_env(("CARGO_INCREMENTAL", 0))
123 .map(|mut workflow| {
124 for job in jobs {
125 workflow = workflow.add_job(job.name, job.job)
126 }
127 workflow
128 })
129 .add_job(ext_tests.name, ext_tests.job)
130 .add_job(tests_pass.name, tests_pass.job)
131}
132
133/// Controls which features `orchestrate_impl` includes in the generated script.
134#[derive(PartialEq, Eq)]
135enum OrchestrateTarget {
136 /// For the main Zed repo: includes the cargo package filter and extension
137 /// change detection, but no working-directory scoping.
138 ZedRepo,
139 /// For individual extension repos: scopes changed-file detection to the
140 /// working directory, with no package filter or extension detection.
141 Extension,
142}
143
144// Generates a bash script that checks changed files against regex patterns
145// and sets GitHub output variables accordingly
146pub fn orchestrate(rules: &[&PathCondition]) -> NamedJob {
147 orchestrate_impl(rules, OrchestrateTarget::ZedRepo)
148}
149
150pub fn orchestrate_for_extension(rules: &[&PathCondition]) -> NamedJob {
151 orchestrate_impl(rules, OrchestrateTarget::Extension)
152}
153
154fn orchestrate_impl(rules: &[&PathCondition], target: OrchestrateTarget) -> NamedJob {
155 let name = "orchestrate".to_owned();
156 let step_name = "filter".to_owned();
157 let mut script = String::new();
158
159 script.push_str(indoc::indoc! {r#"
160 set -euo pipefail
161 if [ -z "$GITHUB_BASE_REF" ]; then
162 echo "Not in a PR context (i.e., push to main/stable/preview)"
163 COMPARE_REV="$(git rev-parse HEAD~1)"
164 else
165 echo "In a PR context comparing to pull_request.base.ref"
166 git fetch origin "$GITHUB_BASE_REF" --depth=350
167 COMPARE_REV="$(git merge-base "origin/${GITHUB_BASE_REF}" HEAD)"
168 fi
169 CHANGED_FILES="$(git diff --name-only "$COMPARE_REV" "$GITHUB_SHA")"
170
171 "#});
172
173 if target == OrchestrateTarget::Extension {
174 script.push_str(indoc::indoc! {r#"
175 # When running from a subdirectory, git diff returns repo-root-relative paths.
176 # Filter to only files within the current working directory and strip the prefix.
177 REPO_SUBDIR="$(git rev-parse --show-prefix)"
178 REPO_SUBDIR="${REPO_SUBDIR%/}"
179 if [ -n "$REPO_SUBDIR" ]; then
180 CHANGED_FILES="$(echo "$CHANGED_FILES" | grep "^${REPO_SUBDIR}/" | sed "s|^${REPO_SUBDIR}/||" || true)"
181 fi
182
183 "#});
184 }
185
186 script.push_str(indoc::indoc! {r#"
187 check_pattern() {
188 local output_name="$1"
189 local pattern="$2"
190 local grep_arg="$3"
191
192 echo "$CHANGED_FILES" | grep "$grep_arg" "$pattern" && \
193 echo "${output_name}=true" >> "$GITHUB_OUTPUT" || \
194 echo "${output_name}=false" >> "$GITHUB_OUTPUT"
195 }
196
197 "#});
198
199 let mut outputs = IndexMap::new();
200
201 if target == OrchestrateTarget::ZedRepo {
202 script.push_str(indoc::indoc! {r#"
203 # Check for changes that require full rebuild (no filter)
204 # Direct pushes to main/stable/preview always run full suite
205 if [ -z "$GITHUB_BASE_REF" ]; then
206 echo "Not a PR, running full test suite"
207 echo "changed_packages=" >> "$GITHUB_OUTPUT"
208 elif echo "$CHANGED_FILES" | grep -qP '^(rust-toolchain\.toml|\.cargo/|\.github/|Cargo\.(toml|lock)$)'; then
209 echo "Toolchain, cargo config, or root Cargo files changed, will run all tests"
210 echo "changed_packages=" >> "$GITHUB_OUTPUT"
211 else
212 # Extract changed directories from file paths
213 CHANGED_DIRS=$(echo "$CHANGED_FILES" | \
214 grep -oP '^(crates|tooling)/\K[^/]+' | \
215 sort -u || true)
216
217 # Build directory-to-package mapping using cargo metadata
218 DIR_TO_PKG=$(cargo metadata --format-version=1 --no-deps 2>/dev/null | \
219 jq -r '.packages[] | select(.manifest_path | test("crates/|tooling/")) | "\(.manifest_path | capture("(crates|tooling)/(?<dir>[^/]+)") | .dir)=\(.name)"')
220
221 # Map directory names to package names
222 FILE_CHANGED_PKGS=""
223 for dir in $CHANGED_DIRS; do
224 pkg=$(echo "$DIR_TO_PKG" | grep "^${dir}=" | cut -d= -f2 | head -1 || true)
225 # Only add directories that map to a real root-workspace package.
226 # Some directories (e.g. tooling/lints) belong to a separate workspace
227 # and are not root members, so they have no mapping here. Previously we
228 # fell back to the raw directory name, which fabricated a bogus package
229 # (e.g. "lints") and produced a nextest filter like rdeps(lints) that
230 # hard-errors ("operator didn't match any packages"). Skipping such
231 # directories leaves the package set empty, which falls through to the
232 # "run all tests" path below.
233 if [ -n "$pkg" ]; then
234 FILE_CHANGED_PKGS=$(printf '%s\n%s' "$FILE_CHANGED_PKGS" "$pkg")
235 fi
236 done
237 FILE_CHANGED_PKGS=$(echo "$FILE_CHANGED_PKGS" | grep -v '^$' | sort -u || true)
238
239 # If assets/ changed, add crates that depend on those assets
240 if echo "$CHANGED_FILES" | grep -qP '^assets/'; then
241 FILE_CHANGED_PKGS=$(printf '%s\n%s\n%s' "$FILE_CHANGED_PKGS" "settings" "assets" | sort -u)
242 fi
243
244 # Combine all changed packages
245 ALL_CHANGED_PKGS=$(echo "$FILE_CHANGED_PKGS" | grep -v '^$' || true)
246
247 if [ -z "$ALL_CHANGED_PKGS" ]; then
248 echo "No package changes detected, will run all tests"
249 echo "changed_packages=" >> "$GITHUB_OUTPUT"
250 else
251 # Build nextest filterset with rdeps for each package
252 FILTERSET=$(echo "$ALL_CHANGED_PKGS" | \
253 sed 's/.*/rdeps(&)/' | \
254 tr '\n' '|' | \
255 sed 's/|$//')
256 echo "Changed packages filterset: $FILTERSET"
257 echo "changed_packages=$FILTERSET" >> "$GITHUB_OUTPUT"
258 fi
259 fi
260
261 "#});
262
263 outputs.insert(
264 "changed_packages".to_owned(),
265 format!("${{{{ steps.{}.outputs.changed_packages }}}}", step_name),
266 );
267 }
268
269 for rule in rules {
270 assert!(
271 rule.set_by_step
272 .borrow_mut()
273 .replace(name.clone())
274 .is_none()
275 );
276 assert!(
277 outputs
278 .insert(
279 rule.name.to_owned(),
280 format!("${{{{ steps.{}.outputs.{} }}}}", step_name, rule.name)
281 )
282 .is_none()
283 );
284
285 let grep_arg = if rule.invert { "-qvP" } else { "-qP" };
286 script.push_str(&format!(
287 "check_pattern \"{}\" '{}' {}\n",
288 rule.name, rule.pattern, grep_arg
289 ));
290 }
291
292 if target == OrchestrateTarget::ZedRepo {
293 script.push_str(DETECT_CHANGED_EXTENSIONS_SCRIPT);
294 script.push_str("echo \"changed_extensions=$EXTENSIONS_JSON\" >> \"$GITHUB_OUTPUT\"\n");
295
296 outputs.insert(
297 "changed_extensions".to_owned(),
298 format!("${{{{ steps.{}.outputs.changed_extensions }}}}", step_name),
299 );
300 }
301
302 let job = Job::default()
303 .runs_on(runners::LINUX_SMALL)
304 .with_repository_owner_guard()
305 .outputs(outputs)
306 .when(target == OrchestrateTarget::ZedRepo, |this| {
307 this.add_step(steps::harden_runner())
308 })
309 .add_step(steps::checkout_repo().with_deep_history_on_non_main())
310 .add_step(Step::new(step_name.clone()).run(script).id(step_name));
311
312 NamedJob { name, job }
313}
314
315pub fn tests_pass(jobs: &[NamedJob], extra_job_names: &[&str]) -> NamedJob {
316 let mut script = String::from(indoc::indoc! {r#"
317 set +x
318 EXIT_CODE=0
319
320 check_result() {
321 echo "* $1: $2"
322 if [[ "$2" != "skipped" && "$2" != "success" ]]; then EXIT_CODE=1; fi
323 }
324
325 "#});
326
327 let all_names: Vec<&str> = jobs
328 .iter()
329 .map(|job| job.name.as_str())
330 .chain(extra_job_names.iter().copied())
331 .collect();
332
333 let env_entries: Vec<_> = all_names
334 .iter()
335 .map(|name| {
336 let env_name = format!("RESULT_{}", name.to_uppercase());
337 let env_value = format!("${{{{ needs.{}.result }}}}", name);
338 (env_name, env_value)
339 })
340 .collect();
341
342 script.push_str(
343 &all_names
344 .iter()
345 .zip(env_entries.iter())
346 .map(|(name, (env_name, _))| format!("check_result \"{}\" \"${}\"", name, env_name))
347 .collect::<Vec<_>>()
348 .join("\n"),
349 );
350
351 script.push_str("\n\nexit $EXIT_CODE\n");
352
353 let job = Job::default()
354 .runs_on(runners::LINUX_SMALL)
355 .needs(
356 all_names
357 .iter()
358 .map(|name| name.to_string())
359 .collect::<Vec<String>>(),
360 )
361 .cond(repository_owner_guard_expression(true))
362 .add_step(
363 env_entries
364 .into_iter()
365 .fold(named::bash(&script), |step, env_item| {
366 step.add_env(env_item)
367 }),
368 );
369
370 named::job(job)
371}
372
373/// Bash script snippet that detects changed extension directories from `$CHANGED_FILES`.
374/// Assumes `$CHANGED_FILES` is already set. Sets `$EXTENSIONS_JSON` to a JSON array of
375/// changed extension paths. Callers are responsible for writing the result to `$GITHUB_OUTPUT`.
376pub(crate) const DETECT_CHANGED_EXTENSIONS_SCRIPT: &str = indoc::indoc! {r#"
377 # Detect changed extension directories (excluding extensions/workflows)
378 CHANGED_EXTENSIONS=$(echo "$CHANGED_FILES" | grep -oP '^extensions/[^/]+(?=/)' | sort -u | grep -v '^extensions/workflows$' || true)
379 # Filter out deleted extensions
380 EXISTING_EXTENSIONS=""
381 for ext in $CHANGED_EXTENSIONS; do
382 if [ -f "$ext/extension.toml" ]; then
383 EXISTING_EXTENSIONS=$(printf '%s\n%s' "$EXISTING_EXTENSIONS" "$ext")
384 fi
385 done
386 CHANGED_EXTENSIONS=$(echo "$EXISTING_EXTENSIONS" | sed '/^$/d')
387 if [ -n "$CHANGED_EXTENSIONS" ]; then
388 EXTENSIONS_JSON=$(echo "$CHANGED_EXTENSIONS" | jq -R -s -c 'split("\n") | map(select(length > 0))')
389 else
390 EXTENSIONS_JSON="[]"
391 fi
392"#};
393
394const TS_QUERY_LS_FILE: &str = "ts_query_ls-x86_64-unknown-linux-gnu.tar.gz";
395const CI_TS_QUERY_RELEASE: &str = "tags/v3.15.1";
396
397pub(crate) fn fetch_ts_query_ls() -> Step<Use> {
398 named::uses(
399 "dsaltares",
400 "fetch-gh-release-asset",
401 "aa37ae5c44d3c9820bc12fe675e8670ecd93bd1c",
402 ) // v1.1.1
403 .add_with(("repo", "ribru17/ts_query_ls"))
404 .add_with(("version", CI_TS_QUERY_RELEASE))
405 .add_with(("file", TS_QUERY_LS_FILE))
406}
407
408pub(crate) enum RunContext {
409 ZedRepository,
410 Extension,
411}
412
413pub(crate) fn run_ts_query_ls(context: RunContext) -> Step<Run> {
414 named::bash(formatdoc!(
415 r#"tar -xf "$GITHUB_WORKSPACE/{TS_QUERY_LS_FILE}" -C "$GITHUB_WORKSPACE"
416 "$GITHUB_WORKSPACE/ts_query_ls" format --check {directory} || {{
417 echo "Found unformatted queries, please format them with ts_query_ls."
418 echo "For easy use, install the Tree-sitter query extension:"
419 echo "zed://extension/tree-sitter-query"
420 false
421 }}"#,
422 directory = match context {
423 RunContext::Extension => "languages",
424 RunContext::ZedRepository => ".",
425 }
426 ))
427}
428
429fn check_style() -> NamedJob {
430 fn check_for_typos() -> Step<Use> {
431 named::uses(
432 "crate-ci",
433 "typos",
434 "2d0ce569feab1f8752f1dde43cc2f2aa53236e06",
435 ) // v1.40.0
436 .with(("config", "./typos.toml"))
437 }
438
439 named::job(
440 release_job(&[])
441 .runs_on(runners::LINUX_MEDIUM)
442 .add_step(steps::harden_runner())
443 .add_step(steps::checkout_repo())
444 .add_step(steps::cache_rust_dependencies_namespace())
445 .add_step(steps::setup_pnpm())
446 .add_step(steps::prettier())
447 .add_step(steps::cargo_fmt())
448 .add_step(steps::script("./script/check-todos"))
449 .add_step(steps::script("./script/check-keymaps"))
450 .add_step(check_for_typos())
451 .add_step(fetch_ts_query_ls())
452 .add_step(run_ts_query_ls(RunContext::ZedRepository)),
453 )
454}
455
456fn check_dependencies() -> NamedJob {
457 fn install_cargo_machete() -> Step<Use> {
458 steps::taiki_install_action("cargo-machete@0.7.0")
459 }
460
461 fn run_cargo_machete() -> Step<Run> {
462 named::bash("cargo machete")
463 }
464
465 fn check_cargo_lock() -> Step<Run> {
466 named::bash("cargo update --locked --workspace")
467 }
468
469 fn check_vulnerable_dependencies() -> Step<Use> {
470 named::uses(
471 "actions",
472 "dependency-review-action",
473 "67d4f4bd7a9b17a0db54d2a7519187c65e339de8", // v4
474 )
475 .if_condition(Expression::new("github.event_name == 'pull_request'"))
476 .with(("license-check", false))
477 }
478
479 named::job(use_clang(
480 release_job(&[])
481 .runs_on(runners::LINUX_SMALL)
482 .add_step(steps::harden_runner())
483 .add_step(steps::checkout_repo())
484 .add_step(steps::cache_rust_dependencies_namespace())
485 .add_step(install_cargo_machete())
486 .add_step(run_cargo_machete())
487 .add_step(check_cargo_lock())
488 .add_step(check_vulnerable_dependencies()),
489 ))
490}
491
492fn check_wasm() -> NamedJob {
493 fn install_nightly_wasm_toolchain() -> Step<Run> {
494 named::bash(
495 "rustup toolchain install nightly --component rust-src --target wasm32-unknown-unknown",
496 )
497 }
498
499 fn cargo_check_wasm() -> Step<Run> {
500 named::bash(concat!(
501 "cargo -Zbuild-std=std,panic_abort ",
502 "check --target wasm32-unknown-unknown -p gpui_platform -p cloud_api_client",
503 ))
504 .add_env((
505 "CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUSTFLAGS",
506 "-C target-feature=+atomics,+bulk-memory,+mutable-globals",
507 ))
508 .add_env(("RUSTC_BOOTSTRAP", "1"))
509 }
510
511 named::job(
512 release_job(&[])
513 .runs_on(runners::LINUX_LARGE)
514 .add_step(steps::harden_runner())
515 .add_step(steps::checkout_repo())
516 .add_step(steps::setup_cargo_config(Platform::Linux))
517 .add_step(steps::cache_rust_dependencies_namespace())
518 .add_step(install_nightly_wasm_toolchain())
519 .add_step(steps::setup_sccache(Platform::Linux))
520 .add_step(cargo_check_wasm())
521 .add_step(steps::show_sccache_stats(Platform::Linux))
522 .add_step(steps::cleanup_cargo_config(Platform::Linux)),
523 )
524}
525
526fn check_workspace_binaries() -> NamedJob {
527 named::job(use_clang(
528 release_job(&[])
529 .runs_on(runners::LINUX_LARGE)
530 .add_step(steps::harden_runner())
531 .add_step(steps::checkout_repo())
532 .add_step(steps::setup_cargo_config(Platform::Linux))
533 .add_step(steps::cache_rust_dependencies_namespace())
534 .map(steps::install_linux_dependencies)
535 .add_step(steps::setup_sccache(Platform::Linux))
536 .add_step(steps::script("cargo build -p collab"))
537 .add_step(steps::script("cargo build --workspace --bins --examples"))
538 .add_step(steps::show_sccache_stats(Platform::Linux))
539 .add_step(steps::cleanup_cargo_config(Platform::Linux)),
540 ))
541}
542
543pub(crate) fn clippy(platform: Platform, arch: Option<Arch>, harden: bool) -> NamedJob {
544 let target = arch.map(|arch| match (platform, arch) {
545 (Platform::Mac, Arch::X86_64) => "x86_64-apple-darwin",
546 (Platform::Mac, Arch::AARCH64) => "aarch64-apple-darwin",
547 _ => unimplemented!("cross-arch clippy not supported for {platform}/{arch}"),
548 });
549 let runner = match platform {
550 Platform::Windows => runners::WINDOWS_DEFAULT,
551 Platform::Linux => runners::LINUX_DEFAULT,
552 Platform::Mac => runners::MAC_DEFAULT,
553 };
554 let mut job = release_job(&[])
555 .runs_on(runner)
556 .when(harden && platform == Platform::Linux, |this| {
557 this.add_step(steps::harden_runner())
558 })
559 .add_step(steps::checkout_repo())
560 .add_step(steps::setup_cargo_config(platform))
561 .when(
562 platform == Platform::Linux || platform == Platform::Mac,
563 |this| this.add_step(steps::cache_rust_dependencies_namespace()),
564 )
565 .when(
566 platform == Platform::Linux,
567 steps::install_linux_dependencies,
568 )
569 .when_some(target, |this, target| {
570 this.add_step(steps::install_rustup_target(target))
571 })
572 .add_step(steps::setup_sccache(platform))
573 .add_step(steps::clippy(platform, target))
574 .add_step(steps::show_sccache_stats(platform));
575 if platform == Platform::Linux {
576 job = use_clang(job);
577 }
578 let name = match arch {
579 Some(arch) => format!("clippy_{platform}_{arch}"),
580 None => format!("clippy_{platform}"),
581 };
582 NamedJob { name, job }
583}
584
585pub(crate) fn run_platform_tests(platform: Platform) -> NamedJob {
586 run_platform_tests_impl(platform, true, true)
587}
588
589pub(crate) fn run_platform_tests_no_filter(platform: Platform) -> NamedJob {
590 run_platform_tests_impl(platform, false, false)
591}
592
593fn run_platform_tests_impl(platform: Platform, filter_packages: bool, harden: bool) -> NamedJob {
594 let runner = match platform {
595 Platform::Windows => runners::WINDOWS_DEFAULT,
596 Platform::Linux => runners::LINUX_DEFAULT,
597 Platform::Mac => runners::MAC_DEFAULT,
598 };
599 NamedJob {
600 name: format!("run_tests_{platform}"),
601 job: release_job(&[])
602 .runs_on(runner)
603 .when(platform == Platform::Linux, |job| {
604 job.add_service(
605 "postgres",
606 Container::new("postgres:15@sha256:1b92e7a80c021647bf70f5d3eb66066a998e4f5cf43c07bb9dc9f729782cf88e")
607 .add_env(("POSTGRES_HOST_AUTH_METHOD", "trust"))
608 .ports(vec![Port::Name("5432:5432".into())])
609 .options(
610 "--health-cmd pg_isready \
611 --health-interval 500ms \
612 --health-timeout 5s \
613 --health-retries 10",
614 ),
615 )
616 })
617 .when(harden && platform == Platform::Linux, |this| {
618 this.add_step(steps::harden_runner())
619 })
620 .add_step(steps::checkout_repo())
621 .add_step(steps::setup_cargo_config(platform))
622 .when(platform == Platform::Mac, |this| {
623 this.add_step(steps::cache_rust_dependencies_namespace())
624 })
625 .when(platform == Platform::Linux, |this| {
626 use_clang(this.add_step(steps::cache_rust_dependencies_namespace()))
627 })
628 .when(
629 platform == Platform::Linux,
630 steps::install_linux_dependencies,
631 )
632 .add_step(steps::setup_node())
633 .when(
634 platform == Platform::Linux || platform == Platform::Mac,
635 |job| job.add_step(steps::cargo_install_nextest()),
636 )
637 .add_step(steps::clear_target_dir_if_large(platform))
638 .add_step(steps::setup_sccache(platform))
639 .when(filter_packages, |job| {
640 job.add_step(
641 steps::cargo_nextest(platform).with_changed_packages_filter("orchestrate"),
642 )
643 })
644 .when(!filter_packages, |job| {
645 job.add_step(steps::cargo_nextest(platform))
646 })
647 .add_step(steps::show_sccache_stats(platform))
648 .add_step(steps::cleanup_cargo_config(platform)),
649 }
650}
651
652fn build_visual_tests_binary() -> NamedJob {
653 pub fn cargo_build_visual_tests() -> Step<Run> {
654 named::bash("cargo build -p zed --bin zed_visual_test_runner --features visual-tests")
655 }
656
657 named::job(
658 Job::default()
659 .runs_on(runners::MAC_DEFAULT)
660 .add_step(steps::checkout_repo())
661 .add_step(steps::setup_cargo_config(Platform::Mac))
662 .add_step(steps::cache_rust_dependencies_namespace())
663 .add_step(cargo_build_visual_tests())
664 .add_step(steps::cleanup_cargo_config(Platform::Mac)),
665 )
666}
667
668pub(crate) fn check_postgres_and_protobuf_migrations() -> NamedJob {
669 fn ensure_fresh_merge() -> Step<Run> {
670 named::bash(indoc::indoc! {r#"
671 if [ -z "$GITHUB_BASE_REF" ];
672 then
673 echo "BUF_BASE_BRANCH=$(git merge-base origin/main HEAD)" >> "$GITHUB_ENV"
674 else
675 git checkout -B temp
676 git merge -q "origin/$GITHUB_BASE_REF" -m "merge main into temp"
677 echo "BUF_BASE_BRANCH=$GITHUB_BASE_REF" >> "$GITHUB_ENV"
678 fi
679 "#})
680 }
681
682 fn bufbuild_setup_action() -> Step<Use> {
683 named::uses(
684 "bufbuild",
685 "buf-setup-action",
686 "a47c93e0b1648d5651a065437926377d060baa99", // v1.50.0
687 )
688 .add_with(("version", "v1.29.0"))
689 .add_with(("github_token", vars::GITHUB_TOKEN))
690 }
691
692 fn bufbuild_breaking_action() -> Step<Use> {
693 named::uses(
694 "bufbuild",
695 "buf-breaking-action",
696 "c57b3d842a5c3f3b454756ef65305a50a587c5ba", // v1.1.4
697 )
698 .add_with(("input", "crates/proto/proto/"))
699 .add_with(("against", "https://github.com/${GITHUB_REPOSITORY}.git#branch=${BUF_BASE_BRANCH},subdir=crates/proto/proto/"))
700 }
701
702 fn buf_lint() -> Step<Run> {
703 named::bash("buf lint crates/proto/proto")
704 }
705
706 fn check_protobuf_formatting() -> Step<Run> {
707 named::bash("buf format --diff --exit-code crates/proto/proto")
708 }
709
710 named::job(
711 release_job(&[])
712 .runs_on(runners::LINUX_DEFAULT)
713 .add_env(("GIT_AUTHOR_NAME", "Protobuf Action"))
714 .add_env(("GIT_AUTHOR_EMAIL", "ci@zed.dev"))
715 .add_env(("GIT_COMMITTER_NAME", "Protobuf Action"))
716 .add_env(("GIT_COMMITTER_EMAIL", "ci@zed.dev"))
717 .add_step(steps::harden_runner())
718 .add_step(steps::checkout_repo().with_full_history())
719 .add_step(ensure_fresh_merge())
720 .add_step(bufbuild_setup_action())
721 .add_step(bufbuild_breaking_action())
722 .add_step(buf_lint())
723 .add_step(check_protobuf_formatting()),
724 )
725}
726
727fn miri_scheduler() -> NamedJob {
728 fn install_miri() -> Step<Run> {
729 named::bash(
730 "rustup toolchain install nightly --profile minimal --component miri --component rust-src",
731 )
732 }
733
734 fn run_scheduler_tests_under_miri() -> Step<Run> {
735 named::bash("cargo +nightly -q miri test -p scheduler")
736 }
737
738 named::job(
739 release_job(&[])
740 .runs_on(runners::LINUX_DEFAULT)
741 .add_step(steps::harden_runner())
742 .add_step(steps::checkout_repo())
743 .add_step(steps::setup_cargo_config(Platform::Linux))
744 .add_step(steps::cache_rust_dependencies_namespace())
745 .add_step(install_miri())
746 .add_step(run_scheduler_tests_under_miri())
747 .add_step(steps::cleanup_cargo_config(Platform::Linux)),
748 )
749}
750
751fn doctests() -> NamedJob {
752 fn run_doctests() -> Step<Run> {
753 named::bash(indoc::indoc! {r#"
754 cargo test --workspace --doc --no-fail-fast
755 "#})
756 .id("run_doctests")
757 }
758
759 named::job(use_clang(
760 release_job(&[])
761 .runs_on(runners::LINUX_DEFAULT)
762 .add_step(steps::harden_runner())
763 .add_step(steps::checkout_repo())
764 .add_step(steps::cache_rust_dependencies_namespace())
765 .map(steps::install_linux_dependencies)
766 .add_step(steps::setup_cargo_config(Platform::Linux))
767 .add_step(steps::setup_sccache(Platform::Linux))
768 .add_step(run_doctests())
769 .add_step(steps::show_sccache_stats(Platform::Linux))
770 .add_step(steps::cleanup_cargo_config(Platform::Linux)),
771 ))
772}
773
774fn check_licenses() -> NamedJob {
775 named::job(
776 Job::default()
777 .runs_on(runners::LINUX_SMALL)
778 .add_step(steps::harden_runner())
779 .add_step(steps::checkout_repo())
780 .add_step(steps::cache_rust_dependencies_namespace())
781 .add_step(steps::script("./script/check-licenses"))
782 .add_step(steps::script("./script/generate-licenses")),
783 )
784}
785
786pub(crate) fn check_scripts(harden: bool) -> NamedJob {
787 fn download_actionlint() -> Step<Run> {
788 named::bash(
789 "bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)",
790 )
791 }
792
793 fn run_actionlint() -> Step<Run> {
794 named::bash(r#""$ACTIONLINT_BIN" -color"#).add_env((
795 "ACTIONLINT_BIN",
796 "${{ steps.get_actionlint.outputs.executable }}",
797 ))
798 }
799
800 fn run_shellcheck() -> Step<Run> {
801 named::bash("./script/shellcheck-scripts error")
802 }
803
804 fn run_zizmor() -> Step<Use> {
805 named::uses(
806 "zizmorcore",
807 "zizmor-action",
808 "6599ee8b7a49aef6a770f63d261d214911a7ce02", // v0.6.0
809 )
810 .add_with(("advanced-security", false))
811 .add_with(("min-severity", "high"))
812 .add_with(("version", "latest"))
813 }
814
815 fn check_xtask_workflows() -> Step<Run> {
816 named::bash(indoc::indoc! {r#"
817 cargo xtask workflows
818 if ! git diff --exit-code .github; then
819 echo "Error: .github directory has uncommitted changes after running 'cargo xtask workflows'"
820 echo "Please run 'cargo xtask workflows' locally and commit the changes"
821 exit 1
822 fi
823 "#})
824 }
825
826 named::job(
827 release_job(&[])
828 .runs_on(runners::LINUX_LARGE)
829 .when(harden, |this| this.add_step(steps::harden_runner()))
830 .add_step(steps::checkout_repo())
831 .add_step(run_shellcheck())
832 .add_step(cache_rust_dependencies_namespace())
833 .add_step(check_xtask_workflows())
834 .add_step(download_actionlint().id("get_actionlint"))
835 .add_step(run_actionlint())
836 .add_step(run_zizmor()),
837 )
838}
839
840fn extension_tests() -> NamedJob<UsesJob> {
841 let job = Job::default()
842 .needs(vec!["orchestrate".to_owned()])
843 .cond(Expression::new(
844 "needs.orchestrate.outputs.changed_extensions != '[]'",
845 ))
846 .permissions(Permissions::default().contents(Level::Read))
847 .strategy(
848 Strategy::default()
849 .fail_fast(false)
850 // TODO: Remove the limit. We currently need this to workaround the concurrency group issue
851 // where different matrix jobs would be placed in the same concurrency group and thus cancelled.
852 .max_parallel(1u32)
853 .matrix(json!({
854 "extension": "${{ fromJson(needs.orchestrate.outputs.changed_extensions) }}"
855 })),
856 )
857 .uses_local(".github/workflows/extension_tests.yml")
858 .with(Input::default().add("working-directory", "${{ matrix.extension }}"));
859
860 named::job(job)
861}
862