Skip to content

Commit 4ac6802

Browse files
committed
tests: refactor e2e test and add shader tests
* Refactored e2e tests into more of their own files. * Added shader and animated_cursor tests. * Added the software GPU `lavapipe` to Github actions.
1 parent 6ce763a commit 4ac6802

File tree

16 files changed

+761
-511
lines changed

16 files changed

+761
-511
lines changed

.github/workflows/ci.yaml

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,16 @@ jobs:
3737
steps:
3838
- if: matrix.os == 'ubuntu-latest'
3939
name: Install Linux system dependencies
40-
run: sudo apt-get install libxcb1-dev libdbus-1-dev
40+
run: |
41+
sudo apt-get install \
42+
libxcb1-dev libdbus-1-dev \
43+
mesa-vulkan-drivers \
44+
libvulkan1 \
45+
vulkan-tools \
46+
vulkan-validationlayers \
47+
libasound2-dev \
48+
libudev-dev
49+
vulkaninfo
4150
- if: matrix.os == 'macos-latest'
4251
name: Install MacOS dependencies
4352
run: brew install bash nano watch
@@ -50,8 +59,16 @@ jobs:
5059
cache-on-failure: true
5160
- name: Build
5261
run: cargo build --verbose --all
53-
- name: Run tests
54-
run: cargo nextest run --no-fail-fast --retries 2
62+
- name: Run tests (excluding GPU tests)
63+
# Multiline escapes don't work on Windows
64+
run: |
65+
cargo nextest run --filterset 'not (test(/gpu/))' --no-fail-fast --retries 2
66+
- if: matrix.os == 'ubuntu-latest'
67+
name: Run GPU tests
68+
run: |
69+
cargo nextest run \
70+
--filterset 'test(/gpu/)' \
71+
--no-fail-fast --retries 2
5572
- name: Output e2e test logs (on failure)
5673
if: failure()
5774
run: cat crates/tests/tattoy.log

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ tracing = "0.1.40"
2323
tracing-subscriber = {version = "0.3.18", features = ["env-filter"]}
2424

2525
[workspace.dependencies.shadow-terminal]
26-
version = "0.2.1"
26+
version = "0.2.2"
2727
# path = "../shadow-terminal/shadow-terminal/"
2828

2929
# Canonical lints for whole crate

crates/tattoy/src/loader.rs

Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,40 @@ use color_eyre::eyre::Result;
66

77
use crate::{run::FrameUpdate, tattoys::gpu::shaderer::Shaderer as _};
88

9+
/// Start all the enabled tattoys.
10+
pub(crate) async fn start_tattoys(
11+
enabled_tattoys: Vec<String>,
12+
output: tokio::sync::mpsc::Sender<FrameUpdate>,
13+
state: Arc<crate::shared_state::SharedState>,
14+
) -> std::thread::JoinHandle<Result<(), color_eyre::eyre::Error>> {
15+
convert_cli_enabled_args(&enabled_tattoys, &state).await;
16+
let handle = spawn(enabled_tattoys.clone(), output, Arc::clone(&state));
17+
wait_for_enabled_tattoys_to_start(enabled_tattoys, &state).await;
18+
handle
19+
}
20+
21+
/// Centralise all "enabled" settings into the state config. Saves us having to check both the CLI
22+
/// and state everytime.
23+
async fn convert_cli_enabled_args(
24+
enabled_tattoys: &Vec<String>,
25+
state: &Arc<crate::shared_state::SharedState>,
26+
) {
27+
for tattoy in enabled_tattoys {
28+
match tattoy.as_ref() {
29+
"startup_logo" => state.config.write().await.show_startup_logo = true,
30+
"notifications" => state.config.write().await.notifications.enabled = true,
31+
"minimap" => state.config.write().await.minimap.enabled = true,
32+
"shaders" => state.config.write().await.shader.enabled = true,
33+
"animated_cursor" => state.config.write().await.animated_cursor.enabled = true,
34+
"bg_command" => state.config.write().await.bg_command.enabled = true,
35+
_ => (),
36+
}
37+
}
38+
}
39+
940
/// Start the main loader thread
1041
#[expect(clippy::too_many_lines, reason = "It's mostly repetitive")]
11-
pub(crate) fn start_tattoys(
42+
pub(crate) fn spawn(
1243
enabled_tattoys: Vec<String>,
1344
output: tokio::sync::mpsc::Sender<FrameUpdate>,
1445
state: Arc<crate::shared_state::SharedState>,
@@ -21,9 +52,7 @@ pub(crate) fn start_tattoys(
2152
let palette = crate::config::main::Config::load_palette(Arc::clone(&state)).await?;
2253
let mut tattoy_futures = tokio::task::JoinSet::new();
2354

24-
if enabled_tattoys.contains(&"startup_logo".to_owned())
25-
|| state.config.read().await.show_startup_logo
26-
{
55+
if state.config.read().await.show_startup_logo {
2756
tracing::info!("Starting 'startup_logo' tattoy...");
2857
tattoy_futures.spawn(crate::tattoys::startup_logo::StartupLogo::start(
2958
output.clone(),
@@ -32,9 +61,7 @@ pub(crate) fn start_tattoys(
3261
));
3362
}
3463

35-
if enabled_tattoys.contains(&"notifications".to_owned())
36-
|| state.config.read().await.notifications.enabled
37-
{
64+
if state.config.read().await.notifications.enabled {
3865
tracing::info!("Starting 'notifications' tattoy...");
3966
tattoy_futures.spawn(crate::tattoys::notifications::main::Notifications::start(
4067
output.clone(),
@@ -58,39 +85,31 @@ pub(crate) fn start_tattoys(
5885
));
5986
}
6087

61-
if enabled_tattoys.contains(&"minimap".to_owned())
62-
|| state.config.read().await.minimap.enabled
63-
{
88+
if state.config.read().await.minimap.enabled {
6489
tracing::info!("Starting 'minimap' tattoy...");
6590
tattoy_futures.spawn(crate::tattoys::minimap::Minimap::start(
6691
output.clone(),
6792
Arc::clone(&state),
6893
));
6994
}
7095

71-
if enabled_tattoys.contains(&"shaders".to_owned())
72-
|| state.config.read().await.shader.enabled
73-
{
96+
if state.config.read().await.shader.enabled {
7497
tracing::info!("Starting 'shaders' tattoy...");
7598
tattoy_futures.spawn(crate::tattoys::shader::Shaders::start(
7699
output.clone(),
77100
Arc::clone(&state),
78101
));
79102
}
80103

81-
if enabled_tattoys.contains(&"animated_cursor".to_owned())
82-
|| state.config.read().await.animated_cursor.enabled
83-
{
104+
if state.config.read().await.animated_cursor.enabled {
84105
tracing::info!("Starting 'animated_cursor' tattoy...");
85106
tattoy_futures.spawn(crate::tattoys::animated_cursor::AnimatedCursor::start(
86107
output.clone(),
87108
Arc::clone(&state),
88109
));
89110
}
90111

91-
if enabled_tattoys.contains(&"bg_command".to_owned())
92-
|| state.config.read().await.bg_command.enabled
93-
{
112+
if state.config.read().await.bg_command.enabled {
94113
tracing::info!("Starting 'bg_command' tattoy...");
95114
tattoy_futures.spawn(crate::tattoys::bg_command::BGCommand::start(
96115
output.clone(),
@@ -140,3 +159,25 @@ pub(crate) fn start_tattoys(
140159
})
141160
})
142161
}
162+
163+
/// Wait for tattoys that need to be running before the PTY starts.
164+
async fn wait_for_enabled_tattoys_to_start(
165+
enabled_tattoys: Vec<String>,
166+
state: &Arc<crate::shared_state::SharedState>,
167+
) {
168+
if enabled_tattoys.contains(&"random_walker".to_owned()) {
169+
crate::run::wait_for_system(state, "random_walker").await;
170+
}
171+
172+
if state.config.read().await.shader.enabled {
173+
crate::run::wait_for_system(state, "shader").await;
174+
}
175+
176+
if state.config.read().await.minimap.enabled {
177+
crate::run::wait_for_system(state, "minimap").await;
178+
}
179+
180+
if state.config.read().await.animated_cursor.enabled {
181+
crate::run::wait_for_system(state, "animated_cursor").await;
182+
}
183+
}

crates/tattoy/src/run.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ pub(crate) async fn run(state_arc: &std::sync::Arc<SharedState>) -> Result<()> {
9797
cli_args.enabled_tattoys.clone(),
9898
surfaces_tx.clone(),
9999
Arc::clone(state_arc),
100-
);
100+
)
101+
.await;
101102

102103
let scrollback_size = state_arc.config.read().await.scrollback_size;
103104
let shadow_terminal_config = shadow_terminal::shadow_terminal::Config {

crates/tattoy/src/tattoys/bg_command.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ impl BGCommand {
9090
let mut protocol = state.protocol_tx.subscribe();
9191
let mut commander = Self::new(output, &state, palette).await;
9292

93+
state
94+
.initialised_systems
95+
.write()
96+
.await
97+
.push("bg_command".to_owned());
98+
9399
#[expect(
94100
clippy::integer_division_remainder_used,
95101
reason = "This is caused by the `tokio::select!`"

crates/tattoy/src/tattoys/gpu/shaderer.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,12 @@ pub(crate) trait Shaderer: Sized {
127127
let mut protocol = state.protocol_tx.subscribe();
128128
let mut shader = Self::new(output, std::sync::Arc::clone(state)).await?;
129129

130+
state
131+
.initialised_systems
132+
.write()
133+
.await
134+
.push(shader.tattoy().id.clone());
135+
130136
#[expect(
131137
clippy::integer_division_remainder_used,
132138
reason = "This is caused by the `tokio::select!`"
@@ -188,6 +194,7 @@ pub(crate) trait Shaderer: Sized {
188194
/// Tick the render
189195
async fn render(&mut self) -> Result<()> {
190196
let rendered_pixels = self.gpu_mut().render().await?;
197+
tracing::info!("!!");
191198

192199
if self.is_upload_tty_as_pixels().await {
193200
if self.gpu().tty_pixels.dimensions().1 == 0 {

crates/tattoy/src/tattoys/minimap.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,12 @@ impl Minimap {
8989
state: Arc<crate::shared_state::SharedState>,
9090
) -> Result<()> {
9191
let mut protocol = state.protocol_tx.subscribe();
92-
let mut minimap = Self::new(output, state).await;
92+
let mut minimap = Self::new(output, Arc::clone(&state)).await;
93+
state
94+
.initialised_systems
95+
.write()
96+
.await
97+
.push("minimap".to_owned());
9398

9499
#[expect(
95100
clippy::integer_division_remainder_used,

crates/tattoy/src/tattoys/random_walker.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,13 @@ impl RandomWalker {
5454
state: std::sync::Arc<crate::shared_state::SharedState>,
5555
) -> Result<()> {
5656
let mut protocol = state.protocol_tx.subscribe();
57-
let mut random_walker = Self::new(output, state).await;
57+
let mut random_walker = Self::new(output, std::sync::Arc::clone(&state)).await;
58+
59+
state
60+
.initialised_systems
61+
.write()
62+
.await
63+
.push("random_walker".to_owned());
5864

5965
#[expect(
6066
clippy::integer_division_remainder_used,

crates/tattoy/src/utils.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,25 @@ pub(crate) fn simple_hash(input: &[u8]) -> u64 {
4040
let mut hash: u64 = 0;
4141
for byte in input {
4242
let byte_u64 = u64::from(*byte);
43-
hash = ((hash << 5u8) + hash) + byte_u64;
43+
let shifted = safe_add(hash << 5u8, hash);
44+
hash = safe_add(shifted, byte_u64);
4445
}
4546
hash
4647
}
48+
49+
/// Safely add 2 `u64`s by wrapping on overflow.
50+
#[expect(
51+
clippy::as_conversions,
52+
clippy::cast_possible_truncation,
53+
clippy::integer_division_remainder_used,
54+
reason = "u64 always fits into u128"
55+
)]
56+
const fn safe_add(left: u64, right: u64) -> u64 {
57+
match left.checked_add(right) {
58+
Some(result) => result,
59+
None => {
60+
let wrapped_result = (left as u128 + right as u128) % (u64::MAX as u128 + 1);
61+
wrapped_result as u64
62+
}
63+
}
64+
}

0 commit comments

Comments
 (0)