Skip to content
This repository was archived by the owner on Sep 22, 2025. It is now read-only.

Commit d247c2b

Browse files
committed
Add doc tests
Taken from the slint repo, these verify that the code snippets included in the docs compile.
1 parent 4712457 commit d247c2b

File tree

5 files changed

+195
-2
lines changed

5 files changed

+195
-2
lines changed

.github/workflows/ci.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,20 @@ jobs:
2727
secrets:
2828
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
2929
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
30+
tests:
31+
env:
32+
CARGO_PROFILE_RELEASE_OPT_LEVEL: s
33+
CARGO_INCREMENTAL: false
34+
runs-on: ubuntu-latest
35+
steps:
36+
- uses: actions/checkout@v4
37+
- name: Install Rust
38+
uses: dtolnay/rust-toolchain@stable
39+
- uses: Swatinem/rust-cache@v2
40+
with:
41+
key: tests
42+
- name: Build & run tests
43+
run: cargo test
3044

3145
deploy:
3246
runs-on: ubuntu-latest

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
# SPDX-License-Identifier: MIT
33

44
[workspace]
5-
members = ["crate", "examples/gallery"]
6-
default-members = ["crate", "examples/gallery"]
5+
members = ["crate", "examples/gallery", "tests/doctests"]
6+
default-members = ["crate", "examples/gallery", "tests/doctests"]
77
resolver = "3"
88

99
[workspace.package]

tests/doctests/Cargo.toml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Copyright © SixtyFPS GmbH <info@slint.dev>
2+
# SPDX-License-Identifier: MIT
3+
4+
[package]
5+
name = "doctests"
6+
authors.workspace = true
7+
edition.workspace = true
8+
homepage.workspace = true
9+
license.workspace = true
10+
repository.workspace = true
11+
rust-version.workspace = true
12+
version.workspace = true
13+
publish = false
14+
build = "build.rs"
15+
16+
[[bin]]
17+
path = "main.rs"
18+
name = "doctests"
19+
20+
[build-dependencies]
21+
walkdir = "2"
22+
23+
[dev-dependencies]
24+
slint-interpreter = { version = "1.12.0", features = ["display-diagnostics"] }
25+
spin_on = { version = "0.1" }

tests/doctests/build.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright © SixtyFPS GmbH <info@slint.dev>
2+
// SPDX-License-Identifier: MIT
3+
4+
use std::io::{BufWriter, Write};
5+
use std::path::Path;
6+
7+
fn main() -> Result<(), Box<dyn std::error::Error>> {
8+
let tests_file_path =
9+
std::path::Path::new(&std::env::var_os("OUT_DIR").unwrap()).join("test_functions.rs");
10+
let mut tests_file = BufWriter::new(std::fs::File::create(&tests_file_path)?);
11+
12+
let prefix = Path::new(env!("CARGO_MANIFEST_DIR"))
13+
.join("../..")
14+
.canonicalize()?;
15+
for entry in walkdir::WalkDir::new(&prefix)
16+
.follow_links(false)
17+
.into_iter()
18+
.filter_entry(|entry| entry.file_name() != "target")
19+
{
20+
let entry = entry?;
21+
let path = entry.path();
22+
if path.extension().is_none_or(|e| e != "md" && e != "mdx") {
23+
continue;
24+
}
25+
26+
let file = std::fs::read_to_string(path)?;
27+
let file = file.replace('\r', ""); // Remove \r, because Windows.
28+
29+
const BEGIN_MARKER: &str = "\n```slint";
30+
if !file.contains(BEGIN_MARKER) {
31+
continue;
32+
}
33+
34+
let stem = path
35+
.strip_prefix(&prefix)?
36+
.to_string_lossy()
37+
.replace('-', "ˍ")
38+
.replace(['/', '\\'], "Ⳇ")
39+
.replace(['.'], "ᐧ")
40+
.to_lowercase();
41+
42+
writeln!(tests_file, "\nmod {stem} {{")?;
43+
44+
let mut rest = file.as_str();
45+
let mut line = 1;
46+
47+
while let Some(begin) = rest.find(BEGIN_MARKER) {
48+
line += rest[..begin].bytes().filter(|&c| c == b'\n').count() + 1;
49+
rest = rest[begin..].strip_prefix(BEGIN_MARKER).unwrap();
50+
51+
// Permit `slint,no-preview` and `slint,no-auto-preview` but skip `slint,ignore` and others.
52+
rest = match rest.split_once('\n') {
53+
Some((",ignore", _)) => continue,
54+
Some((x, _)) if x.contains("no-test") => continue,
55+
Some((_, rest)) => rest,
56+
_ => continue,
57+
};
58+
59+
let end = rest.find("\n```\n").ok_or_else(|| {
60+
format!(
61+
"Could not find the end of a code snippet in {}",
62+
path.display()
63+
)
64+
})?;
65+
let snippet = &rest[..end];
66+
67+
if snippet.starts_with("{{#include") {
68+
// Skip non literal slint text
69+
continue;
70+
}
71+
72+
rest = &rest[end..];
73+
74+
write!(
75+
tests_file,
76+
r##"
77+
#[test]
78+
fn line_{}() {{
79+
crate::do_test("{}", "{}").unwrap();
80+
}}
81+
82+
"##,
83+
line,
84+
snippet.escape_default(),
85+
path.to_string_lossy().escape_default()
86+
)?;
87+
88+
line += snippet.bytes().filter(|&c| c == b'\n').count() + 1;
89+
}
90+
writeln!(tests_file, "}}")?;
91+
println!("cargo:rerun-if-changed={}", path.display());
92+
}
93+
94+
println!(
95+
"cargo:rustc-env=TEST_FUNCTIONS={}",
96+
tests_file_path.to_string_lossy()
97+
);
98+
println!("cargo:rustc-env=SLINT_ENABLE_EXPERIMENTAL_FEATURES=1");
99+
100+
Ok(())
101+
}

tests/doctests/main.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright © SixtyFPS GmbH <info@slint.dev>
2+
// SPDX-License-Identifier: MIT
3+
4+
#![allow(uncommon_codepoints)]
5+
6+
#[cfg(test)]
7+
fn do_test(snippet: &str, path: &str) -> Result<(), Box<dyn std::error::Error>> {
8+
let must_wrap = !snippet.contains("component ") && !snippet.contains("global ");
9+
10+
let code = if must_wrap {
11+
format!(
12+
"import {{
13+
CheckBox, CheckState, Switch, TimePicker, NavigationBar}} from\"material.slint\";
14+
component Example {{\n{snippet}\n}}"
15+
)
16+
} else {
17+
snippet.into()
18+
};
19+
20+
let mut compiler = slint_interpreter::Compiler::default();
21+
22+
compiler.set_include_paths(vec![std::path::PathBuf::from(concat!(
23+
env!("CARGO_MANIFEST_DIR"),
24+
"/../../"
25+
))]);
26+
27+
let result = spin_on::spin_on(compiler.build_from_source(code, path.into()));
28+
29+
let diagnostics = result
30+
.diagnostics()
31+
.filter(|d| {
32+
let msg = d.message();
33+
// It is ok if there is no components
34+
msg != "No component found"
35+
// Ignore warning about examples that don't inherit from Window or not exported
36+
&& !msg.contains(" doesn't inherit Window.")
37+
&& msg != "Component is implicitly marked for export. This is deprecated and it should be explicitly exported"
38+
39+
})
40+
.collect::<Vec<_>>();
41+
slint_interpreter::print_diagnostics(&diagnostics);
42+
43+
if result.has_errors() && !diagnostics.is_empty() {
44+
return Err(format!("Error when loading {snippet:?} in {path:?}: {diagnostics:?}").into());
45+
}
46+
Ok(())
47+
}
48+
49+
include!(env!("TEST_FUNCTIONS"));
50+
51+
fn main() {
52+
println!("Nothing to see here, please run me through cargo test :)");
53+
}

0 commit comments

Comments
 (0)