diff --git a/tools/copy_tests.sh b/tools/copy_tests.sh
new file mode 100755
index 0000000000000000000000000000000000000000..2ea9d51ec212dae840cc13a5c3f1a04e55e57c3f
--- /dev/null
+++ b/tools/copy_tests.sh
@@ -0,0 +1,152 @@
+#!/usr/bin/env bash
+
+# A little helper for copying built tests into an implementation's repo, for
+# testing changes.
+#
+# The destination directory is specified with `-d`, `--dest` or `--destination`.
+# Source files can be positional (in which case, they are ignored if not found
+# to be relative to the test directory), or specified via utilities such as
+# `--head` (tests changed in the current working directory), or `--since N`
+# (tests changed in the last N commits).
+
+script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+root_dir="$( dirname "$script_dir" )"
+tests_dir="$root_dir/test"
+
+help_msg="$(cat <<-END
+
+Help:
+
+    -d <argument>             | -d=<argument>
+    --dest <argument>         | --dest=<argument>
+    --destination <argument>  | --destination=<argument>
+
+        Specify a destination directory to receive copied tests. If not present,
+        the script runs in dry-run mode and does not copy files.
+
+    -n
+    --dryrun
+    --dry-run
+
+        Don't copy files, but print the operations which would be performed.
+        Useful for debugging.
+
+    --head
+
+        Copy test files which have changed in the working directory since the
+        last commit. Performs \`git diff HEAD --name-only\`.
+
+    -s <argument>             | -s=<argument>
+    --diff <argument>         | --diff=<argument>
+    --since <argument>        | --since=<argument>
+
+        Copy files which have changed in the past N commits. Also includes
+        changes in the current working directory since the last commit. Performs
+        \`git diff HEAD~\<argument> --name-only\`.
+
+    -h
+    --help
+
+        Print this help message.
+
+END
+)"
+
+check_path() {
+    local file="$1"
+    if ( [ ! -f "$file" ] ); then
+        return
+    fi
+
+    local path=$(echo "$( realpath --relative-to="$root_dir/test" "$file" )" | tr "/" " ")
+    for part in $path; do
+        if [ "$part" = ".." ]; then
+            return
+        fi
+    done
+
+    echo "$file"
+}
+
+files=( )
+dry_run=0
+while [[ $# -gt 0 ]]; do
+    case "$1" in
+        -d|--dest|--destination)
+            # Select the destination to copy into (value is following
+            # positional parameter)
+            destination="$2"
+            shift
+            shift
+            ;;
+        -d=*|--dest=*|--destination=*)
+            # Select the destination to copy into (value follows `=` sign)
+            destination="${i#*=}"
+            shift
+            ;;
+        -n|--dryrun|--dry-run)
+            # Don't actually copy files, but instead list the files to copy
+            dry_run=1
+            shift
+            ;;
+        --head)
+            # Add test files changed since the last commit
+            files+=( "$( git diff HEAD --name-only --diff-filter=d | grep "^test/" )" )
+            shift
+            ;;
+        -s|--diff|--since)
+            # Add test files changed in the last N commits (N is following
+            # positional parameter)
+            files+=( "$( git diff HEAD~$2 --name-only --diff-filter=d | grep "^test/" )" )
+            shift
+            shift
+            ;;
+        -s=*|--diff=*|--since=*)
+            # Add test files changed in the last N commits (N follows `=` sign)
+            files+=( "$( git diff HEAD~${i#*=} --name-only --diff-filter=d | grep "^test/" )" )
+            shift
+            ;;
+        -h|--help)
+            echo -n "$help_msg"
+            exit 0
+            ;;
+        *)
+            # Add a specific test, if it's a file relative to $tests_dir
+            if [ -n "$( check_path "$1" )" ]; then
+              files+=( "$1" )
+            fi
+            shift
+            ;;
+    esac
+done
+
+if [ ${#destination} -eq 0 ]; then
+  echo -n "Error: No destination specified."
+  echo -n "$help_msg"
+  exit 1
+fi
+
+function main {
+    for file in $files; do
+        # Convert absolute path to relative path
+        file="$( realpath --relative-to="$root_dir" "$file" )"
+        target_path="$destination/$( dirname "$file" )"
+        cmd=$( cat <<-END
+mkdir -p "$target_path"
+cp -f "$file" "$target_path"
+
+END
+)
+        if [ $dry_run -eq 0 ]; then
+          eval "$cmd"
+        else
+          echo "$cmd"
+          echo ""
+        fi
+    done
+}
+
+old_dir=$( pwd )
+cd "$root_dir"
+main
+cd "$old_dir"