Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T07:46:42.965Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

run_tests.rs

862 lines · 31.9 KB · rust
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
Served at tenant.openagents/omega Member data and write actions are omitted.