diff --git a/src/args.rs b/src/args.rs
index 067594791e829b0d4810445786dbc0d97f2ca47d..09d301dde0410a9fcfd0ebf7141bd6e10bddc80b 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -228,7 +228,11 @@ impl Expel {
 
 #[derive(Debug, Parser, tlua::Push)]
 #[clap(about = "Run picodata integration tests")]
-pub struct Test {}
+pub struct Test {
+    #[clap()]
+    /// Only run tests matching the filter.
+    pub filter: Option<String>,
+}
 
 impl Test {
     pub fn tt_args(&self) -> Result<Vec<CString>, String> {
diff --git a/src/main.rs b/src/main.rs
index 2280f43c233f996e29808b344380d5e28dc3f4f9..3c45ee7825e24ec890732e0295eb1b7657399da3 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -918,15 +918,22 @@ fn main_test(args: args::Test) -> ! {
     const FAILED: &str = color![red "FAILED" clear];
     let mut cnt_passed = 0u32;
     let mut cnt_failed = 0u32;
+    let mut cnt_skipped = 0u32;
 
     let now = std::time::Instant::now();
 
     println!();
     println!(
-        "running {} tests",
+        "total {} tests",
         inventory::iter::<InnerTest>.into_iter().count()
     );
     for t in inventory::iter::<InnerTest> {
+        if let Some(filter) = args.filter.as_ref() {
+            if !t.name.contains(filter) {
+                cnt_skipped += 1;
+                continue;
+            }
+        }
         print!("test {} ... ", t.name);
 
         let (mut rx, tx) = ipc::pipe().expect("pipe creation failed");
@@ -991,6 +998,7 @@ fn main_test(args: args::Test) -> ! {
     print!("test result: {}.", if ok { PASSED } else { FAILED });
     print!(" {cnt_passed} passed;");
     print!(" {cnt_failed} failed;");
+    print!(" {cnt_skipped} skipped;");
     println!(" finished in {:.2}s", now.elapsed().as_secs_f32());
     println!();