diff --git a/flake.nix b/flake.nix
index 9bb5cc9..e32d029 100644
--- a/flake.nix
+++ b/flake.nix
@@ -26,9 +26,10 @@
     devShell = forAllSystems (system: pkgs: toolchain: pkgs.mkShell {
       packages = [
         (toolchain.withComponents [
-          "cargo" "rustc" "rustfmt" "clippy"
+          "cargo" "rustc" "rustfmt" "clippy" "llvm-tools"
         ])
         pkgs.mpv
+        pkgs.grcov
       ];
       RUST_SRC_PATH = "${toolchain.rust-src}/lib/rustlib/src/rust/";
     });
diff --git a/scripts/coverage.sh b/scripts/coverage.sh
new file mode 100755
index 0000000..a712de2
--- /dev/null
+++ b/scripts/coverage.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+rm -rf target/coverage || true
+mkdir -p target/coverage
+
+echo "Running tests"
+RUST_LOG=mpvipc=trace RUSTFLAGS="-Cinstrument-coverage" LLVM_PROFILE_FILE="target/coverage/%p-%m.profraw" cargo test --all-features --release --no-fail-fast
+
+echo "Generating coverage report"
+grcov \
+  --source-dir . \
+  --binary-path ./target/release/deps/ \
+  --excl-start 'mod test* \{' \
+  --ignore 'tests/*' \
+  --ignore "*test.rs" \
+  --ignore "*tests.rs" \
+  --ignore "*github.com*" \
+  --ignore "*libcore*" \
+  --ignore "*rustc*" \
+  --ignore "*liballoc*" \
+  --ignore "*cargo*" \
+  -t html \
+  -o ./target/coverage/html \
+  target/coverage/