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
37 changes: 31 additions & 6 deletions crates/aster-srv/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,8 @@ merged JSON.
`GET /v1/test-results/durations?target=<address>&target=…&day=<YYYY-MM-DD>`
returns each test file's median duration over the targets' recent runs, for
splitting a partitioned suite (`MIX_TEST_PARTITION=K mix test --partitions N`)
by time. Every partition of one commit asks with the same targets and the
commit's UTC `day`, and must get the same answer, so the answer depends on
nothing else: it covers runs received in the 7 days before `day` began (less
by time. The answer for a set of targets and a UTC `day` depends on nothing
else: it covers runs received in the 7 days before `day` began (less
15 minutes for uploads still committing), and a `day` that has not begun is
`409`. Per file, the first-attempt durations of its tests are summed in each
of the latest 20 uncached, completely captured runs of each target, and the
Expand All @@ -201,9 +200,35 @@ empty without history.
}
```

Aster asks when a target runs a partition of a suite and `ASTER_SRV_URL` is
set, then runs the files that partition gets from a longest-first split
(`ASTER_BALANCE_PARTITIONS=off` keeps Mix's own split; so does any error).
Partitions never ask the server while they run: one shard that could not
reach it would split differently from the rest, and a file would run on two
shards or none. Instead `aster results durations` asks for every partitioned
suite in the workspace and writes `aster-durations.json` at its root, to be
committed. A target that runs a partition of a suite reads its suite there
and runs the files it gets from a longest-first split, so every shard of a
commit splits the same way. Without the file or the suite in it, or with
`ASTER_BALANCE_PARTITIONS=off`, Mix splits as usual. Files new since the
refresh count as the suite's median. Refresh it on a schedule, for example:

```yaml
on:
schedule: [{ cron: "0 6 * * 1" }]
permissions: { id-token: write, contents: write, pull-requests: write }
jobs:
durations:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: aster results durations
env: { ASTER_SRV_URL: https://aster-srv.example.com }
- run: |
git diff --quiet aster-durations.json && exit 0
git switch -c aster-durations-$(date +%F)
git commit -am "chore: refresh aster-durations.json"
git push -u origin HEAD && gh pr create --fill
env: { GH_TOKEN: "${{ github.token }}" }
```

The shards' tests are recorded under the project whose suite it is (the
directory the command `cd`s to), so a test's id does not depend on the shard
that ran it.
Expand Down
4 changes: 2 additions & 2 deletions docs/specs/test-tracking.html
Original file line number Diff line number Diff line change
Expand Up @@ -455,8 +455,8 @@ <h1>Aster Test Tracking</h1>
acc: ["Payload signed as X-Aster-Signature: sha256=<hmac>", "A failing endpoint never blocks ingest or classification", "Delivery log kept 30 days", "No endpoints configured means nothing is sent"],
files: ["crates/aster-srv/src/webhooks.rs", "crates/aster-srv/src/config.rs"], deps: ["T22"] },
{ id: "T24", p: "P4", area: "client", title: "Duration-balanced mix partitions", sub: "p50 file durations, longest first",
obj: "A MIX_TEST_PARTITION=K mix test --partitions N target (the firstlanding shard projects) runs an explicit file list: Mix's file set, split longest-first by each file's median duration from GET /v1/test-results/durations, as of the commit's UTC day so every shard gets the same answer. Lanes stay configured selectors. Shard tests are recorded under the project that owns them (the cd ../platform target), so a test keeps its id on any shard.",
acc: ["Every shard of one commit gets the same split: no file runs on no shard or on two", "Falls back to Mix's own split, unchanged, with no ASTER_SRV_URL, no history, an error, or ASTER_BALANCE_PARTITIONS=off", "Retry and result capture work on the rewritten command", "Slowest partition's time drops on firstlanding (measured after rollout; logs show up to ~20% headroom)"],
obj: "A MIX_TEST_PARTITION=K mix test --partitions N target (the firstlanding shard projects) runs an explicit file list: Mix's file set, split longest-first by each file's median duration from aster-durations.json, committed at the workspace root and refreshed from GET /v1/test-results/durations by aster results durations (on a schedule). Shards never ask the server while running, so every shard of a commit reads the same durations. Lanes stay configured selectors. Shard tests are recorded under the project that owns them (the cd ../platform target), so a test keeps its id on any shard.",
acc: ["Every shard of one commit gets the same split: no file runs on no shard or on two", "Falls back to Mix's own split, unchanged, on every shard alike: no aster-durations.json, the suite missing from it, a malformed file, or ASTER_BALANCE_PARTITIONS=off", "Retry and result capture work on the rewritten command", "Slowest partition's time drops on firstlanding (measured after rollout; logs show up to ~20% headroom)"],
files: ["src/plugins/mix_partitions.rs", "src/test_results/balance.rs", "src/executor/runner/partitions.rs", "crates/aster-srv/src/test_results/durations.rs"], deps: ["T17"] },
{ id: "T25", p: "P4", area: "docs", title: "Docs", sub: "README, GETTING_STARTED, service runbook",
obj: "Document capture per runner, the env vars (ASTER_SRV_URL, ASTER_QUARANTINE), the quarantine rules and self-hosting.",
Expand Down
14 changes: 14 additions & 0 deletions src/cli/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,20 @@ pub enum ResultsCommands {
#[arg(long)]
url: Option<String>,
},
/// Write aster-durations.json from aster-srv, for balanced partitions
///
/// Finds every `MIX_TEST_PARTITION=K mix test --partitions N` suite in
/// the workspace and writes each test file's median duration, from the
/// partitions' recent runs, to aster-durations.json at the workspace
/// root. Commit it: each partition then splits the suite by those
/// durations, from the checkout, so all partitions of a commit agree.
/// Uses the credentials `upload` uses. Leaves the file unchanged and
/// exits 1 when any suite cannot be fetched.
Durations {
/// The aster-srv URL [default: $ASTER_SRV_URL]
#[arg(long)]
url: Option<String>,
},
}

/// Project-level subcommands
Expand Down
2 changes: 1 addition & 1 deletion src/executor/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ use crate::test_results::{
use crate::ui::ProgressDisplay;

mod partitions;
pub use partitions::partitioned_suites;
mod retry;

use super::command::parse_command;
Expand Down Expand Up @@ -802,7 +803,6 @@ impl<'a> Executor<'a> {
&commands,
&working_dir,
self.workspace_root,
project_map,
&|name| std::env::var(name).ok(),
) {
Some(balanced) => vec![balanced],
Expand Down
Loading
Loading