mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-03 16:41:26 +01:00
test(acceptance): cover B113, B88 and the closed pipe, and report more readably
New live cases: - regression B113: files renamed and moved while sync is paused upload without a conflict copy. - regression B88: pdfs mkdir, rename and rm by local path, the mount-root guard, a path outside every mount, and the refusal in a mirrored folder. - pdfs output into a closed pipe exits 0 and prints nothing on stderr. - open B114 and open B115 check two bugs that are still open. While one reproduces, its case ends as "known", which is listed but does not fail the run. The console output names the pdfs version, the storage and the daemon state of each target, and prints one aligned line per case, with its details beneath. A failed case points to its docs/BUGS.md entry. The run ends with a summary table, the slowest cases, the known issues and the numbered failures. A queue wait now fails at once on a parked op instead of waiting out its timeout. docs/BUGS.md records B114, B115 and B116, which the first live run found, and marks B113 and B88 as verified live.
This commit is contained in:
1 parent
8cde79b8e3
commit
625776081b
3 files changed
+914
-146
No files matched your search
+90
-4
@@ -12,9 +12,77 @@ Conventions:
|
||||
|
||||
---
|
||||
|
||||
## B116 — Trashing a folder keeps the queued creates in its subfolders
|
||||
|
||||
**Status:** Open.
|
||||
**Found:** 2026-10-02, by the first live run of the acceptance case "regression B113". That
|
||||
version wrote `*.part` files, which park their create until a rename instead of minting the file
|
||||
(B70). It moved one of them, still named `moved.part`, into the subfolder `b113/moved here` and
|
||||
then hung, waiting for a queue that held a parked op. The run was interrupted, and its cleanup
|
||||
trashed the test root with `pdfs rm` and deleted it permanently. The parked create stayed queued
|
||||
and kept its staged bytes. The cleanup reported it as left over (`queued create #2 for
|
||||
…/b113/moved here/moved.part`).
|
||||
|
||||
**Where:** `drop_doomed_ops` in `crates/pdfs-core/src/db/ops.rs`, which
|
||||
`Core::discard_queued_ops` runs when a folder is trashed.
|
||||
|
||||
**Cause.** The ops removed with a folder are those for the folder and for the nodes queued below
|
||||
it through `pending_op.parent_uid`, plus the revisions of every file the node tree places below
|
||||
it (B102). A create for a file inside a *real* subfolder is neither: its parent is not a queued
|
||||
node, and the tree only contributes revisions. Such a create comes from a transient name, or
|
||||
from a file created offline in a folder that already exists on Drive. A parked one stays queued
|
||||
for good, holding its bytes. One that is due would run into a trashed parent, and by the code the
|
||||
drain then re-homes it to the root (`parent folder is gone remotely; creating in the root
|
||||
instead`), so the file would reappear in My files after its folder was trashed. Only the parked
|
||||
case has been seen.
|
||||
|
||||
**Required fix/test:** take queued creates and mkdirs from the node tree too, as revisions are.
|
||||
Test: a db test that trashes a folder holding a real subfolder with a queued create below it, and
|
||||
expects the create and its blob gone.
|
||||
|
||||
## B115 — `pdfs ls` shows a file's encrypted size
|
||||
|
||||
**Status:** Open.
|
||||
**Found:** 2026-10-02, while verifying B88 live. `pdfs ls ~/Downloads/pdfs-b88-test` listed the
|
||||
6-byte `note.txt` as 57 bytes, and listed `tickets.pdf` as 202316 bytes where the mount's `stat`
|
||||
said 202257. The acceptance case "open B115" then showed it for every file it wrote, in My files
|
||||
and in an on-demand folder, right after the upload and again after `pdfs refresh`: 6 bytes listed
|
||||
as 57, 4097 as 4150, and 4 MiB + 7 bytes (4194311) as 4194421. The mount's `stat` had the real
|
||||
sizes throughout.
|
||||
|
||||
**Where:** `node_size` in `crates/pdfs-fuse/src/lib.rs` and `crates/pdfs-core/src/db/nodes.rs`.
|
||||
|
||||
**Cause (suspected).** `node_size` reports the node's `claimed_size` and falls back to
|
||||
`total_size_on_storage` when it has none. That is the size on Drive's storage: encrypted blocks
|
||||
plus their signatures. A node decoded without a claimed size therefore lists at its encrypted size.
|
||||
The sizes seen fit that: 51 to 55 bytes more per 4 MiB block.
|
||||
|
||||
**Test:** the acceptance case "open B115" compares the sizes `pdfs ls` reports with the written
|
||||
ones, after the upload and again after `pdfs refresh`. It reports a known issue while they differ.
|
||||
|
||||
## B114 — A name `pdfs rm` or `pdfs rename` took away still resolves for up to 30 seconds
|
||||
|
||||
**Status:** Open.
|
||||
**Found:** 2026-10-02, while verifying B88 live. After `pdfs rm ~/Downloads/pdfs-b88-test` the
|
||||
folder was gone from `ls ~/Downloads`, but `ls -d ~/Downloads/pdfs-b88-test` still found it for
|
||||
up to 30 seconds. `~/ProtonDrive` behaves the same, so B88's routing did not cause it.
|
||||
|
||||
**Where:** `Core::rename` and `Core::delete` in `crates/pdfs-fuse/src/lib.rs`, and the `TTL` the
|
||||
kernel gets for entries in the same file.
|
||||
|
||||
**Cause.** Both change Drive, forget the node and drop the parent's listing, but never tell the
|
||||
kernel. The kernel cached the name's lookup for `TTL`, 30 seconds, and answers from that cache
|
||||
until it expires. A rename or unlink through the mount has no such gap, because the kernel made
|
||||
that change itself. `pdfs move` already sends `inval_entry` for its destination
|
||||
(`settle_dest` in `crates/pdfs-fuse/src/relocate.rs`); its source was not checked.
|
||||
|
||||
**Test:** the acceptance case "open B114" removes one file with `pdfs rm` and renames another with
|
||||
`pdfs rename`, then checks at once that neither old name resolves. It reports a known issue while
|
||||
one does. It reproduced in My files and in an on-demand folder.
|
||||
|
||||
## B113 — A new file moved before its upload drains lands as a conflict copy of itself
|
||||
|
||||
**Status:** Fixed (unverified).
|
||||
**Status:** Fixed (verified 2026-10-02).
|
||||
**Found:** 2026-10-02, on the production account. aria2 downloaded two seasons into the on-demand
|
||||
`~/Downloads`. The episodes were moved into `Videos/anime/…` while their uploads were still
|
||||
queued. 45 of 48 landed as `(sync-conflict …)` copies next to a 0-byte original, each with
|
||||
@@ -35,8 +103,18 @@ id.
|
||||
**Fix.** `WriteHandle::created` takes the minted node's revision id along with its mtime. When a
|
||||
queued create lands, `adopt_real_uid` rebases the handles still open on it onto the new node,
|
||||
as `refresh_after_upload` already does after a sealed upload. Both go through
|
||||
`WriteHandle::rebase_onto`. Unverified: no file has been moved mid-upload against the fixed
|
||||
daemon yet.
|
||||
`WriteHandle::rebase_onto`.
|
||||
|
||||
**Verified live (2026-10-02)** on the installed daemon, in a scratch folder in `~/Downloads`. A
|
||||
`pdfs move` advanced an empty file's server mtime from 1790962386 to 1790962408, the change the old
|
||||
check took for another device's edit. With sync paused, three 16 MiB files were written and moved
|
||||
into a subfolder while their uploads were queued. After the resume all three logged `pending upload
|
||||
landed`, with no conflict warning or copy, and they read back at their exact sizes. Each upload was
|
||||
put off once after the move (`the node is missing locally and was re-read`) and landed on the
|
||||
retry about 8 seconds later. Meanwhile `pdfs sync queue` showed raw ids instead of the new paths.
|
||||
Not verified live: a file created offline that is still open when its create lands. Only the unit
|
||||
test covers that half. The acceptance case "regression B113" replays the online order through the
|
||||
mount, with a rename and a move, and passed in My files and in an on-demand folder.
|
||||
|
||||
## B112 — Folders shared by the same person disappear from Shared with me
|
||||
|
||||
@@ -805,7 +883,7 @@ in every version — but that was luck, and the next question of this shape may
|
||||
## B88 — `pdfs pin`, `rename`, `move`, `delete` and sharing still cannot name a path under a secondary mount
|
||||
|
||||
**Status:** Partly fixed. B86's fix covers `ls` and `refresh`. `move` takes paths in any location
|
||||
through `Core::move_between`. `rm`, `rename` and `mkdir` were fixed 2026-10-02 (unverified live).
|
||||
through `Core::move_between`. `rm`, `rename` and `mkdir` were fixed and verified live 2026-10-02.
|
||||
`pin`, `unpin`, opening a file, thumbnails, uploads, sharing and versions are still open.
|
||||
**Found:** 2026-08-17, while fixing B86.
|
||||
**Where:** `crates/pdfs-fuse/src/control.rs`, every handler still calling `rel_to_mount`.
|
||||
@@ -847,6 +925,14 @@ folder from under its mount. `rm` and `rename` therefore go through `route_node`
|
||||
path naming a mount's root. Test: `only_a_path_below_a_mount_root_names_a_node`. There is still no
|
||||
handler-level test, because a `Core` can only be built against a live client.
|
||||
|
||||
**Verified live (2026-10-02)** in `~/Downloads`, an on-demand folder. `pdfs mkdir` created a
|
||||
folder. `pdfs rename` of a file whose upload was still queued reached Drive under the new name, at
|
||||
6 bytes and with no conflict. `pdfs rm` trashed a file and then the folder. `pdfs rm ~/Downloads`,
|
||||
`pdfs rename ~/Downloads …` and `pdfs rm ~/ProtonDrive` were refused as the root of a synced
|
||||
location, and a path outside every mount as not under the mountpoint. `mkdir` and `rm` in
|
||||
`~/ProtonDrive` still work. Not verified: the refusal in a mirrored folder, because the account has
|
||||
none. The acceptance case "regression B88" repeats this on every location of a run.
|
||||
|
||||
---
|
||||
|
||||
## B87 — A revision's block geometry is learned a read too late
|
||||
|
||||
+73
-6
@@ -74,6 +74,44 @@ removes its directory on success, failure, or interruption:
|
||||
scripts/fuse-acceptance.sh --live /mnt/on-demand-testmount
|
||||
```
|
||||
|
||||
### Reading the output
|
||||
|
||||
A run opens with what it is about to test: the mode, the `pdfs` binary and its
|
||||
version, Python and the kernel, the per-case limit, the options and the start
|
||||
time. Each target then names its root, the storage behind it, the daemon it
|
||||
talks to and how many cases it runs, followed by one line per case:
|
||||
|
||||
```
|
||||
==> live FUSE /mnt/testmount
|
||||
-> root /mnt/testmount/pdfs-acceptance-…
|
||||
-> storage FUSE mount /mnt/testmount (fuse.protondrive)
|
||||
-> daemon pdfs 2.8.3, user@proton.me, online, 0 queued ops
|
||||
-> cases 37 of 37
|
||||
[ 1/37] creation and open flags .................................. ok 0.41s
|
||||
…
|
||||
[21/37] throughput floors ........................................ ok 48.20s
|
||||
write 31 MiB/s, read 54 MiB/s, 4 KiB writes 12.0 MiB/s, metadata 9 ops/s
|
||||
…
|
||||
[36/37] open B115: pdfs ls reports a file's real size ............ known 6.12s
|
||||
B115: pdfs ls shows …
|
||||
-> reference matches the ordinary filesystem in 412 compared observations
|
||||
-> result 35 passed, 1 skipped, 1 known issue in 9m41s
|
||||
```
|
||||
|
||||
A case ends as `ok`, `FAIL`, `TIMEOUT`, `skip` when it does not apply to the
|
||||
target (no daemon, no share at that role), or `known` when an `open B<n>` case
|
||||
reproduced a bug that is still open (see below). The dimmed lines under a case
|
||||
are its details: the reason for a skip, measured rates, a hash. A failed
|
||||
`regression B<n>` case also points to its entry in `docs/BUGS.md`. When a case
|
||||
prints something itself, such as a service restart, the case line is printed
|
||||
again once the case ends.
|
||||
|
||||
The run ends with a table of every target's counts and time, the report paths,
|
||||
the slowest cases, the cases over `--budget`, the known issues that still
|
||||
reproduce, any errors the daemon logged, and each failure with its target. The
|
||||
last line is `PASS:` or `FAIL:` and the totals. Colour is used only on a
|
||||
terminal and never with `NO_COLOR` set.
|
||||
|
||||
### Edge cases and performance
|
||||
|
||||
Besides the syscall contract, the suite covers:
|
||||
@@ -105,19 +143,20 @@ Besides the syscall contract, the suite covers:
|
||||
The local reference run is not only a self-test of the runner. Every case
|
||||
records the facts it established, and a live run is compared against those
|
||||
recordings, so a case fails when the mount *differs* from an ordinary
|
||||
filesystem — not only when it raises. The report names the exact key:
|
||||
filesystem — not only when it raises. The target's `reference` line names the
|
||||
exact key:
|
||||
|
||||
```
|
||||
[DIVERGENCE] live FUSE /mnt/testmount does not match the reference filesystem:
|
||||
! creation and open flags.truncated.size: live=6 reference=0
|
||||
-> reference 1 observation of 412 differs from the ordinary filesystem:
|
||||
! creation and open flags.truncated.size: live FUSE /mnt/testmount=6 reference=0
|
||||
```
|
||||
|
||||
Facts are recorded in one of two ways, chosen in the test body:
|
||||
|
||||
- **compared** — a claim about filesystem semantics. The mount must agree.
|
||||
- **noted** — context that legitimately differs: inode numbers, block counts,
|
||||
reported mode bits, and which optional syscalls exist at all. Reported under
|
||||
`[capabilities]`, never a failure.
|
||||
reported mode bits, and which optional syscalls exist at all. Listed under
|
||||
the target's `accepted` line, never a failure.
|
||||
|
||||
Because the choice is made at each call site, the set of accepted divergences
|
||||
is visible in the test that accepts it. There is no separate allowlist to drift
|
||||
@@ -151,6 +190,24 @@ brought back the provisional sizes the bug reported.
|
||||
different times before writing, so their empty creates land before, during and after the
|
||||
bytes arrive, then checks every file after the queue drains.
|
||||
|
||||
**B113** (moving a file before its upload makes no conflict) pauses sync with
|
||||
`pdfs sync pause --for 10m` and writes three files. While their uploads are held,
|
||||
it renames one, moves one into a folder, and renames and moves the third. It then
|
||||
resumes sync, waits for the queue, and checks the bytes and names and that no
|
||||
conflict copy appeared. It skips when sync was already paused. If the run dies
|
||||
before the resume, the pause ends by itself after ten minutes.
|
||||
|
||||
**B88** (`pdfs mkdir`, `rename` and `rm` by local path) creates, renames and
|
||||
removes a folder and a file by their paths in the mount. Then it checks the
|
||||
refusals: `pdfs rename` of the mount's root to its own name, and `pdfs rm` of a
|
||||
file outside every mount. `pdfs rm` of the root itself is tried only on a sync
|
||||
folder the run created. In a mirrored sync folder all three commands must be
|
||||
refused instead, and nothing may change.
|
||||
|
||||
**pdfs output into a closed pipe** runs `pdfs ls`, `pdfs --json ls` and
|
||||
`pdfs status` with stdout on a pipe whose reader is already gone. Each must exit
|
||||
0 with nothing on stderr, as `pdfs ls | head -1` needs.
|
||||
|
||||
B74 was found by exactly this mechanism: the B70 case failed, and narrowing it
|
||||
produced a smaller reproduction that got its own case. The defect is fixed and
|
||||
live-validated (2026-07-26); the case now guards against its return.
|
||||
@@ -186,6 +243,15 @@ nothing: the local attribute and content caches will happily return the bytes
|
||||
that were just written even when nothing reached Drive, which is precisely how
|
||||
B74 stayed invisible.
|
||||
|
||||
### Bugs that are still open
|
||||
|
||||
Cases named `open B<n>` check a bug that `docs/BUGS.md` still lists as open.
|
||||
While the bug reproduces, the case ends as `known`: it is listed at the end of
|
||||
the run, but does not fail it. So a known bug does not hide new failures, and
|
||||
the run notices when a fix lands, because the case then passes. Rename it to
|
||||
`regression B<n>` then. **B114** (a name `pdfs rm` or `rename` took away stops
|
||||
resolving) and **B115** (`pdfs ls` reports a file's real size) are of this kind.
|
||||
|
||||
### Timeouts, reports, and hung mounts
|
||||
|
||||
A wedged FUSE operation cannot be interrupted from Python, so each case runs
|
||||
@@ -200,7 +266,8 @@ scripts/fuse-acceptance.sh --live /mnt/testmount \
|
||||
```
|
||||
|
||||
`--report-json` also writes every recorded observation, which is what to attach
|
||||
to a bug report. `--budget SECONDS` flags cases that pass but got slower —
|
||||
to a bug report. A known issue has the status `known` there; JUnit has no such
|
||||
status, so `--report-junit` reports it as skipped. `--budget SECONDS` flags cases that pass but got slower —
|
||||
B5 was a 186 ms-per-call regression that a pass/fail suite could not see.
|
||||
`--list` prints every case and which targets it runs against, and `--fail-fast`
|
||||
stops at the first failure.
|
||||
|
||||
+751
-136
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user