StorageRedirectTest 是 srx_core 的回归测试套件,用来验证 Storage Redirect X 在修改核心代码后,文件重定向、路径映射、真实路径放行、MediaStore 访问等功能是否仍然正常。
本地默认配合源码目录:
C:\Users\12988\Desktop\srx_core
测试 App 包名:
me.fakerqu.test.storageredirect
app:测试 App,包含 Compose 调试界面、TestService、广播入口和测试用例。media-file-api:被测试 App 调用的文件与 MediaStore API 封装。.github/workflows/android.yml:CI 稳定性测试流程。.github/scripts/install-storage-redirect-module.sh:CI 中给 x86_64 模拟器安装 Magisk 与 Storage Redirect X 模块。.github/scripts/run-storage-redirect-scenarios.sh/.ps1:CI/ADB 场景脚本,验证不同 SRX 配置下文件落点和 App 视角。
- JDK 21。
- Android SDK / platform-tools,确保
adb可用。 - Gradle Wrapper 使用本仓库的
gradlew.bat或./gradlew。 - 需要一台可用 root 的测试设备或模拟器,并已刷入 Storage Redirect X 模块。
- 默认设备侧测试使用设备上当前已安装的模块,不主动编译、刷入本地
srx_core,也不因此重启设备。 - 只有在用户明确要求编译/刷入本地模块,或明确说明要验证当前本地
srx_core模块产物时,才从C:\Users\12988\Desktop\srx_core构建并刷入本地模块包。
本项目已优先使用国内下载源:Gradle Wrapper 使用腾讯 Gradle 镜像,Gradle daemon JDK 21 使用清华 TUNA Adoptium 镜像,Maven/插件依赖优先走阿里云和腾讯 Maven 镜像,官方源仅作为兜底。
先确认设备在线、root 可用,并记录当前已安装模块版本。默认不重新编译或刷入模块:
adb wait-for-device
adb shell "su -c 'cat /data/adb/modules/storage.redirect.x/module.prop'"再构建并安装测试 App:
cd C:\Users\12988\Desktop\StorageRedirectTest
.\gradlew.bat --no-daemon :app:testDebugUnitTest :media-file-api:testDebugUnitTest :app:assembleDebug
adb install -r app\build\outputs\apk\debug\app-debug.apk如用户明确要求验证本地 srx_core 模块产物,则先构建并刷入模块。模块更新后需要重启设备,重启完成后重新确认 module.prop:
cd C:\Users\12988\Desktop\srx_core
.\scripts\build-local-module.ps1
adb wait-for-device
adb shell "su -c 'cat /data/adb/modules/storage.redirect.x/module.prop'"授予测试 App 必要权限:
adb shell pm grant me.fakerqu.test.storageredirect android.permission.READ_EXTERNAL_STORAGE 2>$null
adb shell pm grant me.fakerqu.test.storageredirect android.permission.WRITE_EXTERNAL_STORAGE 2>$null
adb shell pm grant me.fakerqu.test.storageredirect android.permission.READ_MEDIA_IMAGES 2>$null
adb shell pm grant me.fakerqu.test.storageredirect android.permission.READ_MEDIA_VIDEO 2>$null
adb shell pm grant me.fakerqu.test.storageredirect android.permission.READ_MEDIA_AUDIO 2>$null
adb shell pm grant me.fakerqu.test.storageredirect android.permission.POST_NOTIFICATIONS 2>$null
adb shell appops set me.fakerqu.test.storageredirect MANAGE_EXTERNAL_STORAGE allow给 SRX 写入测试 App 配置,最小配置表示开启完整隔离模式:
$config = '{"users":{"0":{"enabled":true}}}'
adb shell "su -c 'mkdir -p /data/adb/modules/storage.redirect.x/config/apps'"
$config | adb shell "su -c 'cat > /data/adb/modules/storage.redirect.x/config/apps/me.fakerqu.test.storageredirect.json'"
adb shell am force-stop me.fakerqu.test.storageredirect运行默认回归用例:
adb shell am start-foreground-service -n me.fakerqu.test.storageredirect/.TestService -a me.fakerqu.test.storageredirection.TEST_CASE --es test_case all查看日志和结果:
adb logcat -d -s StorageRedirectTest
adb shell "su -c 'ls -t /sdcard/Android/data/me.fakerqu.test.storageredirect/files/test_case_result/result_*.txt 2>/dev/null | head -1'"
adb shell "su -c 'cat /sdcard/Android/data/me.fakerqu.test.storageredirect/files/test_case_result/result_*.txt 2>/dev/null | tail -80'"test_case=all 会运行查询、创建、读取、写入、stat、access、truncate、ftruncate 和文件 API 的主要路径,但不会自动执行 MediaStore 删除、缩略图、chmod、link 或 symlink 用例。它会创建 srt_* 开头的测试媒体文件,并在用例结束时删除本次记录下来的 MediaStore URI 和 srt_file_tests bootstrap 目录。单个 MediaStore create 用例仍会保留返回的 URI,便于后续手动 read/write/delete。
测试入口支持通过 test_case 和额外参数运行单个用例。
常用用例 id:
allmediastore_query_imagemediastore_query_videomediastore_query_audiomediastore_query_filemediastore_query_downloadmediastore_query_read_only_imagemediastore_query_path_imagemediastore_query_path_videomediastore_query_path_audiomediastore_query_path_filemediastore_query_path_downloadmediastore_create_imagemediastore_create_image_relative_datamediastore_read_imagemediastore_write_imagemediastore_delete_imagemediastore_thumbnail_imagefile_list_dirfile_createfile_readfile_writefile_write_deniedfile_deletefile_delete_deniedfile_mkdirfile_mkdir_deniedfile_renamefile_rename_deniedfile_statfile_accessfile_readlinkfile_truncatefile_truncate_deniedfile_ftruncatefile_ftruncate_deniedfile_chmodfile_chmod_deniedfile_fchmodfile_fchmod_deniedfile_linkfile_link_deniedfile_symlinkfile_symlink_denied
File API 示例:
adb shell am start-foreground-service -n me.fakerqu.test.storageredirect/.TestService -a me.fakerqu.test.storageredirection.TEST_CASE --es test_case file_write --es file_path /storage/emulated/0/Download/SrtProbe/srt_ci_probe.txt --es payload "storage-redirect-test:file:manual" --es expected_payload "storage-redirect-test:file:manual"只读或拒绝类用例用于验证 srx_core 是否正确阻止写操作。例如:
adb shell am start-foreground-service -n me.fakerqu.test.storageredirect/.TestService -a me.fakerqu.test.storageredirection.TEST_CASE --es test_case file_write_denied --es file_path /storage/emulated/0/Download/SrtReadOnly/write_denied.txt --es payload "blocked"
adb shell am start-foreground-service -n me.fakerqu.test.storageredirect/.TestService -a me.fakerqu.test.storageredirection.TEST_CASE --es test_case file_rename_denied --es file_path /storage/emulated/0/Download/SrtReadOnly/srt_read_only_seed.txt --es target_file_path /storage/emulated/0/Download/SrtReadOnly/renamed.txtMediaStore 读写类用例需要先运行对应的 create 用例,从结果中的 uri= 取出 content://...,再作为 media_uri 传入:
adb shell am start-foreground-service -n me.fakerqu.test.storageredirect/.TestService -a me.fakerqu.test.storageredirection.TEST_CASE --es test_case mediastore_create_image
adb shell am start-foreground-service -n me.fakerqu.test.storageredirect/.TestService -a me.fakerqu.test.storageredirection.TEST_CASE --es test_case mediastore_read_image --es media_uri "content://media/external/images/media/12345"使用相对 _data 创建图片时,relative_path 既可以传 Pictures/Album,也可以传包含文件名的 Pictures/Album/example.jpg。该用例会通过 ContentResolver.insert() 和 openOutputStream() 创建、发布并读回校验媒体内容:
adb shell am start-foreground-service -n me.fakerqu.test.storageredirect/.TestService -a me.fakerqu.test.storageredirection.TEST_CASE --es test_case mediastore_create_image_relative_data --es relative_path Pictures/SrtRelativeData --es file_name srt_relative.jpg支持参数:
media_uri:MediaStore 读、写、删、缩略图用例的目标 URI。file_path:文件读、写、删、创建、重命名源路径等用例的目标路径。target_file_path:file_rename/file_rename_denied的目标路径。file_dir:目录列表和 mkdir 类用例的目标目录。file_name:MediaStore 创建用例的文件名,或mediastore_query_path_*/mediastore_query_read_only_image用例用于定位目标行的文件名。relative_path:普通 MediaStore 创建用例写入的相对目录,例如Download/SrtMonitor;mediastore_create_image_relative_data中表示传给_data的安全相对路径,可包含文件名。keep_pending:MediaStore 创建用例是否保留IS_PENDING=1;支持1、true、yes,用于创建后立即由同一测试 APP 继续读写 URI。payload:写入内容。expected_payload:读回校验内容。expected_path:file_readlink的期望链接目标,或mediastore_query_path_*/mediastore_query_read_only_image的期望 cursorDATA路径。length:file_truncate/file_ftruncate的目标长度。mode:file_access、file_chmod、file_fchmod的访问模式或权限模式;支持十进制、0600八进制和0o600八进制写法。
已经安装模块和测试 App 后,可以使用 CI 同款场景脚本。Windows 下建议用 Git Bash 或 WSL 执行:
cd /c/Users/12988/Desktop/StorageRedirectTest
bash .github/scripts/run-storage-redirect-scenarios.sh脚本覆盖 scenario 1-29:基础重定向与映射、4 个 FUSE daemon 混合模式、3 个默认 mount namespace 回退、文件监视与 MediaStore 系统代写、只读真实图片查询,以及运行中配置热更新。MediaStore 文件监视场景还会验证安全相对 _data 路径、根相对 /Pictures/Nnngram 保存链路,以及跨应用只读规则不能误伤当前调用方。可以通过 RUN_FUSE_DAEMON_SCENARIOS=1 强制运行 FUSE daemon 场景,或通过 RUN_FUSE_DAEMON_SCENARIOS=0 跳过这些场景。
脚本默认在每个服务用例前冷启动测试 App,并确认当前进程已经获得该场景要求的挂载;scenario 29 会临时保持同一进程验证配置热更新。测试 App 会写入固定的 result_current.txt,且每次写结果前重新创建父目录,避免存储视图刷新导致结果丢失。可用 SRT_FRESH_APP_PER_CASE=0 调试复用进程,只跑部分场景可设置 SRT_SCENARIOS=9,17,19,22,23,24,26,28,29。SRT_FAIL_FAST=1 会在首个失败场景停止,SRT_SCENARIO_TIMEOUT_SECONDS 控制单场景超时,SRT_SKIP_FINAL_CLEANUP=1 只适用于一次性 CI 模拟器。结果轮询、启动缓冲、挂载确认和用例间隔可分别通过 SRT_RESULT_POLL_MS、SRT_APP_LAUNCH_SETTLE_MS、SRT_MOUNT_CONFIRM_TIMEOUT_MS、SRT_SERVICE_CASE_SETTLE_MS 调整。
- 未启用应用配置,验证默认真实路径写入。
- 启用重定向,验证写入应用私有空间。
- 启用路径映射,验证
Download/SrtProbe写入真实Download/Test。 - 路径映射叠加真实路径放行,验证映射优先级。
- 放行真实
Download,验证保持原路径写入。 - 启用
mapping_mode_only且未命中映射,验证保持真实路径写入。 - 启用
mapping_mode_only且命中映射,验证写入映射目标。 - 启用
mapping_mode_only+sandboxed_paths,验证.xlDownload/.xldownload沙盒化。 - 启用
read_only_paths,验证可读、可stat/access,但拒绝写入、truncate、ftruncate、chmod、fchmod、link、symlink、删除、mkdir、rename。 - 启用路径映射且映射目标为只读路径,验证从映射请求写入会被拒绝。
- 启用
allowed_real_paths内联排除和通配符排除,验证放行路径保持真实写入、排除目录可写入私有空间、通配符排除会命中并创建到应用私有空间。 - 启用旧版
excluded_real_paths字段,验证它会并入allowed_real_paths排除规则。 - 启用
allowed_real_paths的?通配符,验证普通应用直写单字符匹配时保持真实路径写入,并验证 MediaStore 系统代写单字符匹配放行、多字符不匹配时仍进入私有空间。 - 启用多条
path_mappings,验证最长前缀映射优先。 - 启用字符串形式
sandboxed_paths且同路径也命中path_mappings,验证映射优先于局部沙盒。 - 启用全局
fuse_daemon_redirect_enabled,验证普通放行规则和*/?通配符放行规则可以并存:普通规则仍保持真实路径写入,普通应用直写与 MediaStore 系统代写命中的通配符路径也保持真实路径写入,不命中的同类路径进入应用私有空间。 - 启用全局
fuse_daemon_redirect_enabled,验证read_only_paths中!排除优先:父路径只读时,排除子路径可写,未排除子路径写入被拒绝。 - 启用全局
fuse_daemon_redirect_enabled,验证路径映射和只读路径共同存在时,写权限由映射最终目标决定:最终目标被!排除则可写,最终目标仍落在只读父路径下则拒绝写入。 - 启用全局
fuse_daemon_redirect_enabled,验证同一父级路径下多个通配符规则互不污染:分别放行的子路径保持真实写入,只读通配符子路径拒绝写入,未命中的兄弟路径进入应用私有空间。 - 关闭
fuse_daemon_redirect_enabled时,验证默认 mount namespace 下allowed_real_paths的*/?通配符不会被忽略,而是回退到预期的具体路径规则,普通应用直写与 MediaStore 系统代写命中时保持真实路径写入。 - 关闭
fuse_daemon_redirect_enabled时,验证默认 mount namespace 下read_only_paths的通配符不会被忽略,而是回退到预期的具体路径规则,并继续保持只读拒绝语义。 - 关闭
fuse_daemon_redirect_enabled时,验证默认 mount namespace 下路径映射和只读规则同时存在时,写权限仍由映射最终目标决定:请求路径不会被错误创建,最终目标被!排除则可写,最终目标仍落在只读父路径下则拒绝写入。 - 启用
read_only_paths=["Pictures/SrtReadOnlyMedia"],预置真实图片并扫描进 MediaStore,验证测试 App 通过 MediaStore 查询仍能看到只读真实路径下的图片行。 - 启用
file_monitor_enabled且普通应用关闭重定向时,验证普通公共路径保存和 MediaStore 系统代写保存成功后file_monitor.log都有成功记录。 - 启用
file_monitor_enabled且 fuse daemon 关闭/开启时,验证普通应用直接保存到放行路径或映射请求路径后都有成功记录;最终路径命中只读规则时写入失败且不落盘;最终路径命中只读排除规则时有成功记录。 - 启用
file_monitor_enabled且 fuse daemon 关闭/开启时,验证 MediaStore 系统代写方式在放行路径、映射请求路径、最终只读路径、最终只读排除路径下分别产生对应成功或失败记录。 - 保持测试 App 进程运行,在线把配置从默认重定向切换为路径映射,验证后续写入无需重启应用即可使用新配置。
脚本输出 scenario-*-result.txt 和 media-health.txt。失败时会抓取模块配置、模块日志、存储挂载状态和相关 logcat。
脚本开跑前和清理后会重启 MediaProvider,避免上一轮 shell 侧 MediaStore 清理影响下一轮 MediaStore 写入归因。脚本结束时会执行设备侧清理,成功和失败都会尽量收尾。清理范围是白名单式的:
- 测试 App 的
test_case_result和srt_file_tests目录,包括被 SRX 重定向后的 App 私有镜像路径。 - 场景脚本创建的
Download/Srt*、Pictures/SrtLocked、Pictures/SrtReadOnlyMedia、DCIM/SrtFuse*等固定测试目录,以及固定文件srt_ci_probe.part、srt_qmark*.txt、srt_mountns_*_media.bin、srt_fuse_*_media.bin、srt_read_only_media.jpg和文件监视专用srt_monitor_*.bin。 - MediaStore 中名称匹配
srt_image_<数字>.jpg、srt_read_only_media.jpg、srt_video_<数字>.mp4、srt_audio_<数字>.mp3、srt_file_<数字>.txt、srt_download_<数字>.bin和脚本固定媒体文件名的测试行,并且只限定在对应的 Pictures、Movies、Music、Documents、Download 或测试 App 私有镜像路径;同样清理 MediaProvider 可能留下的.pending-<数字>-srt_*/.trashed-<数字>-srt_*临时名和同名冲突(数字)后缀。 - 真实物理目录中同样命名规则的随机测试文件,以及固定测试媒体文件的
.pending-*/.trashed-*变体,只在上述公共媒体目录和测试 App 私有镜像目录的白名单层级删除。
清理不会递归删除整个 Download、Pictures 等用户目录,也不会删除白名单之外的普通 srt_* 文件。scenario-*-result.txt 和 media-health.txt 是脚本在本仓库工作区生成的诊断文件,会保留给 CI 或本地排查使用。
GitHub Actions 当前会:
- 在 Android 13、14、15、16(API 33、34、35、36)的 x86_64 模拟器上运行完整 scenario 1-29。
- 下载
Kindness-Kismet/Storage-redirection-X-Public的最新 x86_64 Release 模块。 - 构建测试 App 和 AndroidTest APK。
- root 模拟器、安装 Magisk、安装 Storage Redirect X 模块、安装测试 App。
- 执行
.github/scripts/run-storage-redirect-scenarios.sh。
注意:当前 CI 默认验证的是上游 Release 模块,不会自动使用本地 C:\Users\12988\Desktop\srx_core 的未发布修改。本地设备侧测试默认也使用当前设备已安装模块;只有在用户明确要求编译/刷入,或明确说明要验证本地模块产物时,才按本 README 的可选步骤构建并刷入 srx_core。
本仓库是个人 fork 工作区。当前 origin 指向:
https://github.com/z1298808165/StorageRedirectTest.git
当 StorageRedirectTest 本身发生修改并需要提交时,只提交并推送到自己的 fork。不要向原仓库提交 PR,除非用户明确要求“向原仓库提交 PR”。
如果原仓库更新,需要同步到本项目:
- 先确认当前工作区是否有未提交修改,避免覆盖本地工作。
- 先阅读上游变更内容,尤其是 Gradle、测试脚本、包名、CI、测试用例和结果格式相关变更。
- 根据本项目作为
srx_core回归测试套件的用途判断是否应该同步,不要机械合并。 - 同步前后都要验证关键路径,至少运行 Gradle 单元测试和测试 App 构建。
- 如果同步影响
.github/scripts/run-storage-redirect-scenarios.sh或测试用例语义,需要在设备或模拟器上跑一遍相关场景。 - 解决冲突时保留本项目已有的本地使用方式、fork 提交流程和
srx_core配合测试说明。