diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c53b9f1..c124e30 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,12 +7,14 @@ Start with the [README](README.md) for what the studio does and how to build it. PRs into `main` are squash-merged, so the PR title becomes the commit subject in `git log`. - Use conventional-commit subjects (`feat(scene): ...`, `fix: ...`, `chore: ...`, `docs: ...`). + A PR that only adds or edits quests uses the `quest` type, with or without a scope (`quest: ...`, `quest(m0/ux): ...`). - Never commit binaries or build artifacts. Keep other binary files, such as images and fixtures, under 500 KiB. # PRs Keep the body short and structured, not narrated. -Have at least these sections: +A PR that only adds or edits quests follows the quest guide's format (`quest guide`) instead of these sections. +Every other PR has at least these sections: - **Problem**: a summary of the problem and why this PR is needed. - **Approach**: a summary of the approach taken to solve the problem. diff --git a/justfile b/justfile index 4909c6c..45d15e3 100644 --- a/justfile +++ b/justfile @@ -11,6 +11,7 @@ default: # Lint the repository tooling, the quest tree, the join page, and the workspace. check: sh/check-moq-pin.sh + sh/check-ignore-recipes.sh quest check actionlint shellcheck .claude/hooks/*.sh .codex/hooks/*.sh sh/*.sh packaging/flatpak/*.sh diff --git a/quest/m0/doc-drift.md b/quest/m0/doc-drift.md deleted file mode 100644 index 38017c0..0000000 --- a/quest/m0/doc-drift.md +++ /dev/null @@ -1,42 +0,0 @@ -# [S] Docs and test notes match the code - -## Goal - -Ignored tests name a recipe that exists, and `just check` keeps it so, -`record.rs` describes how recording actually encodes, and `CONTRIBUTING.md`'s -PR rules fit the quest PRs agents open, so neither people nor agents follow -instructions that fail. - -## Plan - -Decided 2026-11-12 (maintainer, /quest-plan), from studio's trimmed Pullfrog -learnings: - -- `CONTRIBUTING.md:9` and `:15-21` require conventional-commit titles and - Problem/Approach/Impact sections on every PR, but quest PRs (#108, #110, - #112, #114, #115, #126, #141, #143) use the quest guide's sections and - `quest` type titles, such as `quest:` or `quest(m0/ux):`. Which changes? - - ✅ Exempt quest PRs: a PR that only adds or edits quests follows the quest - guide's format and a `quest` type title, with or without a scope - (rejected: make quest PRs carry the template too). -- 59 `#[ignore]` reasons say `just rs render-sw` (54 in - `rs/moq-scene/src/compositor.rs`, 5 in `rs/moq-studio/src/engine.rs`), but - the recipe is `just render-sw` in the root `justfile`. Fix every one main - holds, and add a `just check` step that fails when an `#[ignore]` reason - names a `just` recipe the root `justfile` does not define (`just - --summary` lists them). Open PR - [moq-dev/studio#146](https://github.com/moq-dev/studio/pull/146) adds - another with the wrong name and edits the recipe. Check skips drafts but - runs when one is marked ready, testing its merge with `main` as of that - run, so #146, a draft held until moq's timed catalog is pinned, runs the - lint once marked ready after this lands; if #146 lands first, this fixes - its reason too. `main`'s ruleset does not require an up-to-date branch, - so if #146 is ready with a passing Check when this lands, update its - branch (`gh pr update-branch 146`), or ask the session holding it to, and - hold it from merging until that Check passes. -- `rs/moq-studio/src/record.rs:4-6` says a recording of the program shares - the live encoders, but `rs/moq-studio/src/engine.rs:1811-1852` gives the - recording its own full-size encoder (`recorded_video`, `record: true`) in - a hidden broadcast. Rewrite only the program's half: the sources' half - (`record.rs:6-9`) is accurate, since local sources are read from the - studio's local broadcast (`engine.rs:932-935`). diff --git a/rs/moq-scene/src/compositor.rs b/rs/moq-scene/src/compositor.rs index 83b0940..077aad1 100644 --- a/rs/moq-scene/src/compositor.rs +++ b/rs/moq-scene/src/compositor.rs @@ -2394,7 +2394,7 @@ mod tests { /// scene stacks it; without one showing, the layered program is the /// program itself. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_published_source_draws_over_the_program() { local(async { let mut collection = collection(); @@ -2428,7 +2428,7 @@ mod tests { /// Items draw where their rect says, later items over earlier ones, and /// a hidden item not at all. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn items_draw_in_place_and_in_order() { local(async { let mut studio = Studio::new(collection()).await; @@ -2450,7 +2450,7 @@ mod tests { /// Straight alpha blends over what is below, and the canvas stays opaque. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn alpha_blends_over_black() { local(async { let mut collection = collection(); @@ -2474,7 +2474,7 @@ mod tests { /// A fade mixes the outgoing and incoming scenes by its progress on the /// program clock, and lands on the incoming scene; preview never fades. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_fade_mixes_by_progress() { local(async { let mut studio = Studio::new(collection()).await; @@ -2514,7 +2514,7 @@ mod tests { /// A clone draws its source's picture through its own filters, then the /// item's, and leaves the source it clones as it was. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_clone_draws_through_its_own_filters() { local(async { let mut collection = collection(); @@ -2549,7 +2549,7 @@ mod tests { /// The top layer draws over preview and program, and over a fade rather /// than fading with it. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn the_top_layer_stays_over_a_fade() { local(async { let mut collection = collection(); @@ -2622,7 +2622,7 @@ mod tests { /// not drawn has no picture, one left out is forgotten, and a preview no /// one looks at is not drawn. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn an_output_draws_its_scene_at_its_own_size() { local(async { let mut collection = tall(vec![Item::new("red", BOTTOM)]); @@ -2681,7 +2681,7 @@ mod tests { /// An output's items play their keyframes from when it was first named, /// drawn or not, and from the start again once named anew. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn an_output_plays_keyframes_from_when_it_went_on() { local(async { let mut slide = slide_in(); @@ -2717,7 +2717,7 @@ mod tests { /// A cut lands at once, even mid-fade, and an empty program is black. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_cut_ends_a_fade() { local(async { let mut studio = Studio::new(collection()).await; @@ -2745,7 +2745,7 @@ mod tests { /// left half on the right; a wipe leaves the outgoing scene in place and /// uncovers the incoming one where the slide brought it in. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_slide_pushes_and_a_wipe_uncovers() { local(async { let mut collection = collection(); @@ -2812,7 +2812,7 @@ mod tests { /// over, and fades in what only the incoming one does, landing on the /// incoming scene as drawn. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_move_glides_what_both_scenes_place() { local(async { let mut collection = collection(); @@ -2895,7 +2895,7 @@ mod tests { /// preview, and from when it went on program, for the program, so one scene /// can be at two points of them at once. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn keyframes_play_from_when_the_scene_goes_on() { local(async { let mut collection = collection(); @@ -2943,7 +2943,7 @@ mod tests { /// Two outputs at either side of a jump, keyframes at the same moment, each /// draw their own side of it rather than share one. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_jump_draws_each_side_on_its_own_output() { local(async { let mut collection = collection(); @@ -2972,7 +2972,7 @@ mod tests { /// A transition starts the incoming scene's keyframes, however long it was /// in preview, while the outgoing scene's carry on. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_transition_starts_the_incoming_keyframes() { local(async { let mut collection = collection(); @@ -3006,7 +3006,7 @@ mod tests { /// incoming scene's halfway, and an item's filter settings mix from one /// scene's to the other's as it goes. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_move_restacks_halfway_and_mixes_filters() { local(async { let mut collection = collection(); @@ -3053,7 +3053,7 @@ mod tests { /// A source with no new picture repeats its last one; an edited source never /// shows the old one's picture, and a removed source disappears. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_stalled_source_repeats_its_last_picture() { local(async { let mut studio = Studio::new(collection()).await; @@ -3122,7 +3122,7 @@ mod tests { /// switches the scenes under it at its cut point into the clip, and ends /// with the clip, which holds its last picture. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_stinger_cuts_under_its_clip() { local(async { let mut studio = stinging(crate::media::tests::CLIP).await; @@ -3214,7 +3214,7 @@ mod tests { /// A stinger whose clip cannot play cuts at once, as does one whose clip /// is played again from elsewhere, such as the Sources panel. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_stinger_that_cannot_play_cuts() { local(async { let mut studio = stinging(b"not a video").await; @@ -3248,7 +3248,7 @@ mod tests { /// clip ends its stinger at once, and a looping clip stops with its /// stinger after one pass. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_stinger_stops_its_clip() { local(async { let mut studio = stinging(crate::media::tests::CLIP).await; @@ -3386,7 +3386,7 @@ mod tests { /// A picture the GPU cannot hold says why, rather than leaving its source /// live and missing from the canvas. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_picture_that_cannot_draw_reports_why() { local(async { let mut collection = collection(); @@ -3416,7 +3416,7 @@ mod tests { /// picture once the model has it, and its whole picture again once kept, /// in its scene or a scene that scene shows. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn removes_the_background() { local(async { let dir = tempfile::tempdir().unwrap(); @@ -3496,7 +3496,7 @@ mod tests { /// again, never shows the cut-out of a picture from before, even when its /// next picture overtakes the clear. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_clear_drops_the_cut_out_in_hand() { let (device, queue) = gpu().await; let effects = Effects::new(&device, &queue, 256, 144); @@ -3570,7 +3570,7 @@ mod tests { /// Each CSS color filter maps a color as the spec's matrix does, clamping /// after each filter rather than once at the end. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn color_filters_follow_css() { let cases = [ (vec![Filter::Grayscale { amount: 1.0 }], [118, 118, 118]), @@ -3610,7 +3610,7 @@ mod tests { /// A crop trims the picture and stretches the rest over the item: an image /// red on its left half and blue on its right shows blue where red was. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_crop_fills_the_item() { local(async { let dir = tempfile::tempdir().unwrap(); @@ -3642,7 +3642,7 @@ mod tests { /// its edges, as CSS's edge mode none: a blue square bleeds onto the black /// around it by the discrete Gaussian's weights. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_blur_spreads_past_the_edges() { local(async { let square = Rect { @@ -3678,7 +3678,7 @@ mod tests { /// A blur many pixels wide is drawn on halved rasters, still matching the /// Gaussian of a square to within a few levels. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_wide_blur_matches_the_gaussian() { local(async { let mut collection = collection(); @@ -3711,7 +3711,7 @@ mod tests { /// A drop shadow is the picture's shape, offset and filled, under it. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_drop_shadow_falls_under() { local(async { let square = Rect { @@ -3744,7 +3744,7 @@ mod tests { /// A chroma key hides its color and keeps the rest; a luma key hides what /// is out of its range. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn keys_hide_what_they_key() { local(async { let mut collection = collection(); @@ -3790,7 +3790,7 @@ mod tests { /// A LUT maps each color through its table, interpolating between points. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_lut_grades() { local(async { let dir = tempfile::tempdir().unwrap(); @@ -3848,7 +3848,7 @@ mod tests { /// A mask keeps the picture where its source is opaque, or bright in /// luminance mode, and an item whose mask has no picture yet draws nothing. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_mask_keeps_what_it_covers() { local(async { let dir = tempfile::tempdir().unwrap(); @@ -3907,7 +3907,7 @@ mod tests { /// transparent texel bleeds into no edge, of a still, an overlay's /// straight paint, or a luminance mask, while the edge still fades. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn scaling_bleeds_no_transparent_color() { local(async { let dir = tempfile::tempdir().unwrap(); @@ -3998,7 +3998,7 @@ mod tests { /// new width, a canvas the GPU cannot hold changes nothing, and a scene /// grown since the last fit is not drawn. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_resize_draws_the_new_canvas() { local(async { let mut collection = collection(); @@ -4076,7 +4076,7 @@ mod tests { /// even when another scene takes its old name in the same edit, and a scene /// the collection lost leaves them. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_renamed_scene_stays_where_it_is() { local(async { let mut studio = Studio::new(collection()).await; @@ -4115,7 +4115,7 @@ mod tests { /// A blend mode mixes an item with what is under it by the CSS formula, /// weighted by the item's opacity. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn blend_modes_mix_with_what_is_under() { let cases = [ (Blend::Multiply, 255, [0, 0, 0]), @@ -4152,7 +4152,7 @@ mod tests { /// An overlay's image, a file beside the scene file, is on the first /// frame that shows the overlay. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn an_overlay_draws_its_image_on_its_first_frame() { local(async { let dir = tempfile::tempdir().unwrap(); @@ -4186,7 +4186,7 @@ mod tests { /// that frame is drawn, even the first one to show it. This one does not /// restart, so going on program does not start it over. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn an_overlay_animates_on_the_program_clock() { local(async { let mut collection = collection(); @@ -4234,7 +4234,7 @@ mod tests { /// included. One that does /// not restart runs on from when it opened. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn an_overlay_restarts_going_on() { local(async { let mut collection = collection(); @@ -4319,7 +4319,7 @@ mod tests { /// showing it goes on program, and a preview showing it directly never /// restarts it while program shows it through that scene. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn an_overlay_in_a_nested_scene_restarts_going_on() { local(async { let mut collection = collection(); @@ -4380,7 +4380,7 @@ mod tests { /// A preview never restarts an overlay a stinger under way masks its clip /// with on program, though no scene on air shows it. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_stinger_mask_runs_on_under_a_preview() { local(async { let mut studio = stinging(crate::media::tests::CLIP).await; @@ -4425,7 +4425,7 @@ mod tests { /// An overlay a move glides is painted on the program clock while both /// scenes are on air, and drawn where the move has it. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_move_glides_an_overlay() { local(async { let mut collection = collection(); @@ -4477,7 +4477,7 @@ mod tests { /// When the overlay thread stops, every overlay it held fails, including /// one off air, rather than showing its last paint when cut to. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_stopped_overlay_thread_fails_every_overlay() { local(async { let mut collection = collection(); @@ -4541,7 +4541,7 @@ mod tests { /// picture, and transparent where the scene draws nothing. It is drawn once /// however many items show it, and not at all once nothing drawn does. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_scene_source_draws_its_scene() { local(async { let fill = Rect::fill(&collection().canvas); @@ -4593,7 +4593,7 @@ mod tests { /// a transition under way included, and a scene showing it cannot go on /// program. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_program_source_shows_the_program_on_the_same_frame() { local(async { let mut studio = Studio::new(nesting(vec![Item::new("on air", CORNER)])).await; @@ -4631,7 +4631,7 @@ mod tests { /// A scene source showing a scene that shows the program, even through /// another, is drawn after the program, on the same frame. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_scene_showing_the_program_is_drawn_after_it() { local(async { let fill = Rect::fill(&collection().canvas); @@ -4661,7 +4661,7 @@ mod tests { /// then at the moment the scene it lands on is at, as the stacking /// changes. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_move_shows_a_nested_scene_as_the_scene_it_leaves_until_halfway() { local(async { let fill = Rect::fill(&collection().canvas); @@ -4710,7 +4710,7 @@ mod tests { /// nested scene, mixes with the backdrop as much as the backdrop covers, /// as CSS does. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_blend_mode_over_a_partly_transparent_backdrop_mixes_partly() { local(async { let fill = Rect::fill(&collection().canvas); @@ -4737,7 +4737,7 @@ mod tests { /// on, so preview and program can show it at two moments, each drawn once, /// and once both settle, drawn once for both. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_nested_scene_plays_its_keyframes_from_when_its_scene_goes_on() { local(async { let fill = Rect::fill(&collection().canvas); @@ -4780,7 +4780,7 @@ mod tests { /// A filter reading around each pixel, such as a blur, reads the nested /// scene drawn this render, not the last one. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_blur_reads_the_nested_scene_drawn_now() { local(async { let fill = Rect::fill(&collection().canvas); @@ -4801,7 +4801,7 @@ mod tests { /// A blend mode in a nested scene mixes with what the scene draws under it, /// and where it draws nothing, leaves the picture as it is, as CSS does. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_blend_mode_over_nothing_in_a_nested_scene_draws_the_picture() { local(async { let fill = Rect::fill(&collection().canvas); @@ -4862,7 +4862,7 @@ mod tests { /// first render after it is named until it is left out; one of a scene /// that does not exist shows nothing. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_thumbnail_shows_its_scene_scaled_down_while_shown() { local(async { let mut studio = Studio::new(wide(Blend::Normal)).await; @@ -4892,7 +4892,7 @@ mod tests { /// Each thumbnail is redrawn a few times a second on the program clock, /// and two named at once on different renders. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn thumbnails_redraw_a_few_times_a_second_on_renders_of_their_own() { local(async { let mut studio = Studio::new(collection()).await; @@ -4942,7 +4942,7 @@ mod tests { /// The scene on program is sampled from the program, top layer and all, /// rather than drawn again; a scene off program is drawn alone. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_thumbnail_of_the_scene_on_program_samples_the_program() { local(async { let mut collection = collection(); @@ -4975,7 +4975,7 @@ mod tests { /// scene source shows its scene so too; one with no picture yet shows /// nothing. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_source_thumbnail_shows_its_picture_through_its_crop_and_filters() { local(async { let dir = tempfile::tempdir().unwrap(); @@ -5026,7 +5026,7 @@ mod tests { /// A blend mode in a thumbnail smaller than its scene mixes with what is /// under it there. #[tokio::test] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn a_blend_mode_draws_in_a_thumbnail_smaller_than_its_scene() { local(async { let mut studio = Studio::new(wide(Blend::Multiply)).await; diff --git a/rs/moq-studio/src/engine.rs b/rs/moq-studio/src/engine.rs index 3e05bf9..f04b4c8 100644 --- a/rs/moq-studio/src/engine.rs +++ b/rs/moq-studio/src/engine.rs @@ -3210,7 +3210,7 @@ mod tests { /// the layout ends and the catalog stops listing it, and quitting ends a /// layout published again. #[tokio::test(flavor = "multi_thread")] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn the_program_carries_a_layer_layout() { use moq_scene::layout::{Layout, TRACK}; @@ -3300,7 +3300,7 @@ mod tests { /// mix other than the one on program records its audio. #[tokio::test(flavor = "multi_thread")] - #[ignore = "needs a Vulkan device and an H.264 encoder; run by just rs render-sw"] + #[ignore = "needs a Vulkan device and an H.264 encoder; run by just render-sw"] async fn the_program_records_without_a_relay() { let dir = tempfile::tempdir().unwrap(); let gpu = Gpu::new(wgpu::Backends::all()).await.expect("a GPU"); @@ -3395,7 +3395,7 @@ mod tests { /// as does the clip once a scene on air places it. The program shows the /// clip, and what its source's mask reads, while a stinger plays it. #[tokio::test(flavor = "multi_thread")] - #[ignore = "needs a Vulkan device; run by just rs render-sw"] + #[ignore = "needs a Vulkan device; run by just render-sw"] async fn the_program_rests_a_stinger_clip() { let dir = tempfile::tempdir().unwrap(); let clip = include_bytes!("../../moq-scene/src/test_data/clip.mkv"); @@ -3701,7 +3701,7 @@ mod tests { /// viewer the relay serves, then its broadcast; unpublishing gives its /// session back. #[tokio::test(flavor = "multi_thread")] - #[ignore = "needs a Vulkan device and an H.264 encoder; run by just rs render-sw"] + #[ignore = "needs a Vulkan device and an H.264 encoder; run by just render-sw"] async fn a_published_scene_has_its_own_broadcast() { let _ = moq_tokio::crypto::install_default(); let fixture = moq_relay::test_relay().await.expect("test relay"); @@ -4257,7 +4257,7 @@ mod tests { /// at the canvas size and at half of it, with its audio alongside. The half /// size holds an encode session only once watched. #[tokio::test(flavor = "multi_thread")] - #[ignore = "needs a Vulkan device and an H.264 encoder; run by just rs render-sw"] + #[ignore = "needs a Vulkan device and an H.264 encoder; run by just render-sw"] async fn the_program_reaches_a_relay() { let _ = moq_tokio::crypto::install_default(); let fixture = moq_relay::test_relay().await.expect("test relay"); diff --git a/rs/moq-studio/src/record.rs b/rs/moq-studio/src/record.rs index f7a775c..4961cab 100644 --- a/rs/moq-studio/src/record.rs +++ b/rs/moq-studio/src/record.rs @@ -1,10 +1,12 @@ //! Recording the program, and optionally each source, to disk beside any live //! output. //! -//! A recording reads the studio's broadcast the way a relay viewer does, so it -//! shares the live encoders and starts and stops without touching what the -//! relay serves, each local source's track from the studio's local broadcast, -//! which its reading keeps encoding, and each broadcast source, a guest's +//! A recording reads, the way a relay viewer does, the program from a hidden +//! broadcast no relay sees, holding its own full-size encode of the video, at +//! full quality whatever the grant, and of the mix, so it starts and stops +//! without touching what the relay serves; each local source's track from the +//! studio's local broadcast, which its reading keeps encoding; and each +//! broadcast source, a guest's //! included, as it arrives through the origin the studio subscribes through, //! copied without a re-encode. Each file is an MP4 that plays while it //! grows, so a crash still leaves a playable file, and becomes a regular MP4 diff --git a/sh/check-ignore-recipes.sh b/sh/check-ignore-recipes.sh new file mode 100755 index 0000000..101525d --- /dev/null +++ b/sh/check-ignore-recipes.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# An `#[ignore]` reason tells the reader which `just` recipe runs the test, so +# fail if one names a recipe the root justfile does not define. +set -euo pipefail + +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +read -ra recipes <<<"$(just --justfile "$root/justfile" --summary)" +fail=0 + +# Tracked files only, so the moq submodule, whose recipes are its own, is skipped. +while IFS= read -r match; do + reason=$(grep -oE 'ignore = "[^"]*"' <<<"$match") + while read -r _ recipe; do + [[ " ${recipes[*]} " == *" $recipe "* ]] && continue + echo "check-ignore-recipes: ${match%%:*}: the reason names 'just $recipe', which the justfile does not define" >&2 + fail=1 + done < <(grep -oE '(^|[^[:alnum:]_])just [[:alnum:]_-]+' <<<"$reason" | grep -oE 'just [[:alnum:]_-]+') +done < <(git -C "$root" grep -nE 'ignore = "[^"]*just ' -- '*.rs' || true) + +exit "$fail"