Clean up leftover files from source dirs.
diff --git a/mesonbuild/utils/universal.py b/mesonbuild/utils/universal.py
index 6aee268..4f18ec1 100644
--- a/mesonbuild/utils/universal.py
+++ b/mesonbuild/utils/universal.py
@@ -1788,7 +1788,7 @@
     return values
 
 
-def _make_tree_writable(topdir: str) -> None:
+def _make_tree_writable(topdir: T.Union[str, Path]) -> None:
     # Ensure all files and directories under topdir are writable
     # (and readable) by owner.
     for d, _, files in os.walk(topdir):
@@ -1799,7 +1799,7 @@
                 os.chmod(fpath, os.stat(fpath).st_mode | stat.S_IWRITE | stat.S_IREAD)
 
 
-def windows_proof_rmtree(f: str) -> None:
+def windows_proof_rmtree(f:  T.Union[str, Path]) -> None:
     # On Windows if anyone is holding a file open you can't
     # delete it. As an example an anti virus scanner might
     # be scanning files you are trying to delete. The only
@@ -1826,7 +1826,7 @@
     shutil.rmtree(f)
 
 
-def windows_proof_rm(fpath: str) -> None:
+def windows_proof_rm(fpath: T.Union[str, Path]) -> None:
     """Like windows_proof_rmtree, but for a single file."""
     if os.path.isfile(fpath):
         os.chmod(fpath, os.stat(fpath).st_mode | stat.S_IWRITE | stat.S_IREAD)
diff --git a/run_project_tests.py b/run_project_tests.py
index 974273f..a1feecd 100755
--- a/run_project_tests.py
+++ b/run_project_tests.py
@@ -278,6 +278,7 @@
         self.stdout: T.List[T.Dict[str, str]] = []
         self.skip_category = skip_category
         self.skip_expected = False
+        self.cleanup: T.List[str] = []
 
         # Always print a stack trace for Meson exceptions
         self.env['MESON_FORCE_BACKTRACE'] = '1'
@@ -844,6 +845,8 @@
 
     (t.skip, t.skip_expected) = _skip_keys(test_def)
 
+    cleanup = test_def.get('cleanup', [])
+
     # Skip tests if the tool requirements are not met
     if 'tools' in test_def:
         assert isinstance(test_def['tools'], dict)
@@ -859,6 +862,7 @@
         t.installed_files = installed
         t.do_not_set_opts = do_not_set_opts
         t.stdout = stdout
+        t.cleanup = cleanup
         return [t]
 
     new_opt_list: T.List[T.List[T.Tuple[str, str, bool, bool]]]
@@ -928,6 +932,8 @@
         test.do_not_set_opts = do_not_set_opts
         test.stdout = stdout
         test.skip_expected = skip_expected or t.skip_expected
+        test.cleanup = cleanup
+
         all_tests.append(test)
 
     return all_tests
@@ -1393,6 +1399,13 @@
         else:
             f.update_log(TestStatus.OK)
             passing_tests += 1
+            for cleanup_path in t.cleanup:
+                assert not os.path.isabs(cleanup_path)
+                abspath = t.path / cleanup_path
+                if abspath.is_file():
+                    mesonlib.windows_proof_rm(abspath)
+                else:
+                    mesonlib.windows_proof_rmtree(abspath)
         conf_time += result.conftime
         build_time += result.buildtime
         test_time += result.testtime
@@ -1550,7 +1563,7 @@
     print()
 
 tmpdir = list(Path('.').glob('test cases/**/*install functions and follow symlinks'))
-assert(len(tmpdir) == 1)
+assert len(tmpdir) == 1
 symlink_test_dir = tmpdir[0]
 symlink_file1 = symlink_test_dir / 'foo/link1'
 symlink_file2 = symlink_test_dir / 'foo/link2.h'
diff --git a/test cases/common/153 wrap file should not failed/test.json b/test cases/common/153 wrap file should not failed/test.json
new file mode 100644
index 0000000..7763d6e
--- /dev/null
+++ b/test cases/common/153 wrap file should not failed/test.json
@@ -0,0 +1,3 @@
+{
+    "cleanup": ["subprojects/foo-1.0-patchfile"]
+}
diff --git a/test cases/common/258 subsubproject inplace/test.json b/test cases/common/258 subsubproject inplace/test.json
new file mode 100644
index 0000000..bf08396
--- /dev/null
+++ b/test cases/common/258 subsubproject inplace/test.json
@@ -0,0 +1,3 @@
+{
+    "cleanup": ["subprojects/subsub.wrap"]
+}
diff --git a/test cases/rust/25 cargo lock/test.json b/test cases/rust/25 cargo lock/test.json
new file mode 100644
index 0000000..6e2a1ca
--- /dev/null
+++ b/test cases/rust/25 cargo lock/test.json
@@ -0,0 +1,3 @@
+{
+    "cleanup": ["subprojects/bar-0.1"]
+}