docs: v0.152.0 REPORT/CONTEXT + fix an async race in TestFabUpload_GCAndIdleTimeout

The fab-upload GC test stat-ed the .part immediately after observing the slot
free, but expireIdleUpload unlinks AFTER releasing the mutex. Passed alone,
failed in the full package once this release's render tests made web heavier.
Not a production defect - a new upload mints a fresh random .part. The test now
waits for the outcome it asserts on the same deadline; red-proofed by removing
the unlink from production.
This commit is contained in:
2026-07-20 13:55:03 +02:00
parent 37e12c82a7
commit fd40b29119
4 changed files with 163 additions and 114 deletions
+9 -3
View File
@@ -43,9 +43,15 @@ browse queries on the wire, but the test Mac's sidebar stayed empty — it had n
section shown at all, which is a Finder Settings toggle rather than something the box controls. This section shown at all, which is a Finder Settings toggle rather than something the box controls. This
is recorded as OPEN in the DIAG, deliberately not as a shipped feature. is recorded as OPEN in the DIAG, deliberately not as a shipped feature.
`TestRenderSambaCompose` asserted the literal tag `felhom-samba:1.0.0`, so a routine image bump read **Two test bugs surfaced and fixed, neither a production defect.** `TestRenderSambaCompose` asserted
as a renderer regression. It now derives from `SambaImage` and separately asserts what actually the literal tag `felhom-samba:1.0.0`, so a routine image bump read as a renderer regression; it now
matters — that the tag is explicit and never `:latest`. derives from `SambaImage` and separately asserts what actually matters — that the tag is explicit and
never `:latest`. And `TestFabUpload_GCAndIdleTimeout` raced: `expireIdleUpload` nils the slot,
releases the mutex, and only then closes and unlinks the `.part`, so "the slot is free" does not yet
mean "the file is gone" — the test stat-ed immediately and passed only by luck. It failed in the full
package while passing in isolation once this release's new render tests made the `web` package
heavier. Now it waits for the outcome it asserts, on the same 3 s deadline; red-proofed by removing
the unlink from production, which still fails it.
### v0.151.0 — the Megosztás page stops reloading, and says how to connect (2026-07-20) ### v0.151.0 — the Megosztás page stops reloading, and says how to connect (2026-07-20)
+18 -1
View File
@@ -7,7 +7,24 @@
> >
> Ask Claude Code: "Please update CONTEXT.md with what we did today" > Ask Claude Code: "Please update CONTEXT.md with what we did today"
Last updated: 2026-07-20 (v0.151.0 — Megosztás reload loop fixed + connect-address card) Last updated: 2026-07-20 (v0.152.0 + samba 1.1.0 — mDNS for macOS; S-3 closed)
> **2026-07-20 — v0.152.0 + felhom-samba 1.1.0 (Megosztás on a Mac).** Closes **S-3**. **A capture
> on the box overturned the earlier guess:** macOS DOES send a correct NBNS query for `<NÉV><20>` and
> nmbd DOES answer it correctly in 140 µs (flags `0x8580`, RCODE=0, right address) — macOS simply
> never acts on it. NetBIOS there feeds legacy browsing, not `smb://` URL resolution, so **the bare
> `smb://<NÉV>` can never work from a Mac** and nmbd was never the broken part (it is what serves
> Windows). felhom-samba 1.1.0 adds **avahi + dbus**, templating `avahi-daemon.conf` and the
> `_smb._tcp` service file from `FELHOM_SERVER_NAME` so a rename re-advertises; both daemons are
> non-fatal on failure. v0.151.0's card had offered `smb://<NÉV>` for Mac — the one dead form — now
> `smb://<NÉV>.local`; Windows keeps flat `\\<NÉV>`. Spiked live by hand and confirmed from the
> operator's Mac BEFORE publishing the image (the operator's call, and it chose the design too).
> **STILL OPEN: Finder-sidebar discovery is NOT shipped** — the record is published and answers
> browse queries, but was never observed working; likely a Finder Settings → Sidebar toggle, but
> unverified. **Windows was not retested.** Two test bugs fixed en route, neither a production
> defect: `TestRenderSambaCompose` pinned a literal image tag, and `TestFabUpload_GCAndIdleTimeout`
> asserted an async unlink synchronously (it passed alone, failed in the full package once the new
> render tests made `web` heavier). 23/23 green twice; 2 red-proofs.
> **2026-07-20 — v0.151.0 (Megosztás).** Closes **S-1/S-2/S-4-core/S-5** of > **2026-07-20 — v0.151.0 (Megosztás).** Closes **S-1/S-2/S-4-core/S-5** of
> `felhom.eu/documentation/audits/DIAG-sharing-2026-07-20.md`; **S-3 (no mDNS/Bonjour) stays OPEN**, > `felhom.eu/documentation/audits/DIAG-sharing-2026-07-20.md`; **S-3 (no mDNS/Bonjour) stays OPEN**,
+121 -109
View File
@@ -1,141 +1,153 @@
# REPORT — v0.151.0: Megosztás status contract (S-1 + S-4 core) + connect-address card (S-2 + S-5) # REPORT — v0.152.0 + felhom-samba 1.1.0: mDNS for macOS (S-3), and the connect card stops offering a dead form
**Date:** 2026-07-20 · **Repo:** felhom-controller (v0.150.0 → **v0.151.0**) · Trunk, pushed to `main`. **Date:** 2026-07-20 · **Repo:** felhom-controller (v0.151.0 → **v0.152.0**) + **felhom-samba
**Origin:** `felhom.eu/documentation/audits/DIAG-sharing-2026-07-20.md`. 1.0.0 → 1.1.0** · Trunk, pushed to `main`.
**Class:** implementation (code + tests + build + deploy + live verify). **S-3 (no mDNS) deliberately **Origin:** operator report — `smb://FELHOM` still failing from a Mac after v0.151.0; S-3 of
out of scope** — it awaits Viktor's two Mac probes and is an image change, not a controller one. `felhom.eu/documentation/audits/DIAG-sharing-2026-07-20.md`.
## Baselines used ## Baselines
| Repo | start `main` @ | end `main` @ | version | | Repo / artifact | start | end |
|---|---|---|---| |---|---|---|
| felhom-controller | `8db9232` (== `origin/main`, clean tree) | `badf17b` | v0.150.0 → **v0.151.0** | | felhom-controller | `5c105fb`, v0.151.0 live on 9201 | `37e12c8` (+ this docs commit), **v0.152.0** |
| felhom.eu | `0c48869` | `eaa2173` | docs only | | felhom-samba | `1.0.0` | **`1.1.0`**, digest `sha256:1c17c094…` |
| felhom.eu | `a7d9837` | `1d1d60a` |
Deployed at start: `felhom-controller:0.150.0` on guest 9201 — verified `Up (healthy)`, not assumed. ## The finding that redirected the fix
## §4.1 — what `b5d78d1` actually intended, and what became of it My earlier conclusion — "macOS no longer does NetBIOS" — **was wrong**, and a packet capture on the
box disproved it. On a bare `smb://FELHOM` from the operator's Mac (`192.168.0.11`):
Read in full before touching the coercion. It was **defensive, not a fix for a named repro**: the ```
v0.147.0 commit message describes slice 4b as detached-and-polled bring-up feedback and never 11:18:30.222080 .11:52844 > 192.168.0.255:137 NBNS query, 50 B
mentions a status-contradiction bug, and the code's own comment states the intent outright — name → "FELHOM" + 9 pad + suffix 0x20 (File Server Service — correct for SMB)
*„A stale `idle`/`running` job must never contradict reality: liveness wins on a fresh page."* 11:18:30.222220 .104:137 > .11:52844 NBNS response, 62 B, +140 µs
flags 0x8580 = response, AUTHORITATIVE, RCODE=0 · ANCOUNT 1
TTL 259200 · NB_FLAGS 0x0000 (unique, B-node) · RDATA 192.168.0.104
```
That intent is legitimate and survives; it was simply asserted on the wrong channel. Liveness is **macOS asked correctly, was answered correctly in 140 microseconds, and never opened a TCP
already carried by the `running` field in the same JSON object, which is where a LEVEL belongs. So connection.** It retried once, was answered again, gave up. Sixteen seconds later the same Mac
instead of a repro-derived regression test there is a named test on that field: queried `FELHOM.local` over mDNS and went straight to port 445.
`TestSharingStatus_LivenessStillReportedWithoutAnyJob` asserts that with **no job in memory at all**,
`running` tracks `SambaRunning()` in both directions — exactly the guarantee the coercion was written
to provide, minus the phase-channel side effect that produced the loop.
## Files changed (all in commit `badf17b`) So NetBIOS on macOS feeds legacy browsing, not `smb://` URL resolution: **the bare `smb://<NÉV>`
cannot be made to work from a Mac by any change on our side**, and nmbd — which the R-6/S4b spike
was right to insist on — was never the broken part. It is precisely what serves Windows.
Investigated and dismissed: the box answers **twice** per broadcast (nmbd holds `0.0.0.0:137`,
`<ip>:137` and `<bcast>:137`; a broadcast lands on two). Standard Samba, and a duplicated correct
answer is still a correct answer that macOS declined to use.
### Proven matrix
| Client | Working form | Served by |
|---|---|---|
| Windows | `\\<NÉV>` | nmbd (+ wsdd for the Network view) |
| macOS | **`smb://<NÉV>.local`** | avahi/mDNS — new in 1.1.0 |
| Any | `smb://<IP>` | direct |
## Method — spike before publish (operator's call, and it paid)
avahi was installed by hand into the *running* container and configured, then proven from a second
machine before any image was built. The operator confirmed `smb://FELHOM.local` connects. Only then
was 1.1.0 built. The spike also chose the design: a **static avahi service file** beats smbd's own
`multicast dns register`, because `smb.conf` is bind-mounted READ-ONLY and owned by the controller's
renderer, and a static file additionally publishes `_device-info._tcp`.
## Changes
| File | Change | | File | Change |
|---|---| |---|---|
| `controller/internal/web/sharing_handlers.go` | A.1 coercion deleted + the two-channel contract documented; B page data gains `SMBDirectAddress` (enabled-only); `sambaLANAddress()` seam resolver; `SMBRunning` gains the nil-manager guard the status handler already had | | `controller/infra-images/samba/Dockerfile` | `avahi` + `dbus` added; `rm` of packaged service files; header documents the captured NBNS proof and that sidebar discovery is NOT claimed |
| `controller/internal/web/samba_ensure_job.go` | A.2 `consumeIfRunning()` serve-once for terminal `running`, and only while the single-flight slot is free | | `controller/infra-images/samba/entrypoint.sh` | templates `avahi-daemon.conf` + `_smb._tcp`/`_device-info._tcp` service file **from `FELHOM_SERVER_NAME`**; starts dbus + avahi, both **non-fatal** |
| `controller/internal/web/server.go` | `sambaAddrFn` seam field | | `controller/internal/infra/infra.go` | `SambaImage` pin → `1.1.0` (`Images()` and the golden bake follow automatically) |
| `controller/internal/web/templates/sharing.html` | B.2 „Csatlakozás a megosztáshoz" section — **markup only** | | `controller/internal/web/templates/sharing.html` | Mac line `smb://<NÉV>`**`smb://<NÉV>.local`**; Windows flat name untouched |
| `controller/internal/stacks/samba.go` | `SambaLANAddress()` + the `sambaLANAddr()` seam + the pure `parseIPv4FromIPAddrOutput` | | `controller/internal/web/sharing_connect_card_test.go` | assertions updated + new `TestSharingConnectCard_MacLineIsDotLocalNotBareName` |
| `controller/internal/stacks/manager.go` | `sambaAddrFn` field; `SetSambaRunProbe` (exported seam setter — internal/web's tests need a live-container world from another package, same precedent as `SetMigrationDoneHook`) | | `controller/internal/infra/samba_test.go` | tag assertion derives from `SambaImage`; adds an explicit non-`:latest` assertion |
| `controller/internal/infra/samba.go` | `SambaHostInterface` — single source for the nic named by smb.conf's `interfaces =`, the container's `FELHOM_IFACE`, and the address read | | `controller/internal/web/handler_export_upload_test.go` | async-race fix (below) |
| **new** `web/sharing_status_contract_test.go`, `web/sharing_connect_card_test.go`, `stacks/samba_lanaddr_test.go` | Scenarios A/B/C/D + the parser matrix | | `CHANGELOG.md`, `controller/README.md`, `REPORT.md` | docs |
| `CHANGELOG.md`, `REUSE.md`, `controller/README.md`, `REPORT.md`, `CONTEXT.md` | docs |
## Test results ## Tests
**23/23 packages green, run twice** — the second run with `-count=1` (no cache). Package count **23/23 packages green, run twice with `-count=1`.**
unchanged: this task added tests, not packages.
| Test | Scenario | Red-proofs, both mutated → FAILED → restored:
| Mutation | Result |
|---|---| |---|---|
| `TestSharingStatus_SteadyStateProducesNoEdge` | **A** — two polls, live container, empty slot → `phase:"idle"`, `running:true` both times | | revert the template's Mac line to the bare `smb://{{.SMBServerName}}` | `TestSharingConnectCard_MacLineIsDotLocalNotBareName` **FAILED** on both the missing `.local` and the present bare form, for both configured names (`FELHOM`, `OTTHON`) |
| `TestSharingStatus_LivenessStillReportedWithoutAnyJob` | the `b5d78d1` intent regression (both liveness directions) | | delete `os.Remove(job.partPath)` from `expireIdleUpload` | `TestFabUpload_GCAndIdleTimeout` **FAILED**`idle-expired .part must be deleted` |
| `TestSharingStatus_RealBringUpServedExactlyOnce` | **B** — first poll `running`, second `idle`, `running:true` on both |
| `TestSharingStatus_RunningNotConsumedWhileInFlight` | the in-flight window: not consumed while the slot is held, served once after `release()` |
| `TestSharingStatus_NonEdgePhasesStaySticky` | **C**`failed`/`needs_password`/`pulling`/`starting` each persist over 3 polls |
| `TestSharingStatus_EnvelopeShapeUnchanged` | exactly the 3 keys `phase`/`error`/`running` |
| `TestSharingStatus_NilStackManagerIsQuiet` | a nil manager does not panic the poll |
| `TestSharingConnectCard_ShowsNameAndDirectAddress` | **D** — Windows + Mac + direct forms present, seam invoked on this render |
| `TestSharingConnectCard_AddressDerivedFreshEveryRender` | **D** — one derivation per render; a changed address reaches the page |
| `TestSharingConnectCard_NoAddressOmitsTheLine` | **D**`""` omits the address block, keeps the name lines |
| `TestSharingConnectCard_UsesConfiguredName` | **D**`OTTHON` renders and `FELHOM` appears nowhere |
| `TestSharingConnectCard_AbsentWhenSharingDisabled` | card gated on the feature; zero derivations when off |
| `TestSharingConnectCard_UnsetNameFallsBackToEffective` | unset name → the effective default, never an empty `smb://` |
| `TestParseIPv4FromIPAddrOutput` | 3 accepted forms (incl. verbatim live output) + 8 rejected (link-local, unspecified, loopback-only, IPv6-only, docker error text, …) |
| `TestSambaLANAddressFailsQuiet` | error → `""`, address → passthrough |
### Red-proofs (§10) — all three mutated → FAILED → restored → green ### Two test bugs found, neither a production defect
| # | Mutation | Result | 1. **`TestRenderSambaCompose` pinned the literal tag `1.0.0`**, so a routine image bump read as a
|---|---|---| renderer regression. Now derives from `SambaImage`, plus a separate assertion for what actually
| **A** | reinstated `if phase == idle && running { phase = running }` | `TestSharingStatus_SteadyStateProducesNoEdge` **FAILED**`call 1: phase = "running", want "idle"`, and again on call 2. Restored → green. | matters — the tag is explicit and never `:latest`.
| **B** | deleted the `s.cur = nil` serve-once clear | `TestSharingStatus_RealBringUpServedExactlyOnce` **FAILED**`second call: phase = "running", want "idle"`; `TestSharingStatus_RunningNotConsumedWhileInFlight` also failed at its post-release assertion. Restored → green. | 2. **`TestFabUpload_GCAndIdleTimeout` raced.** `expireIdleUpload` nils the slot, releases the mutex,
| **D** | replaced fresh derivation with a package-level cached value | `TestSharingConnectCard_AddressDerivedFreshEveryRender` **FAILED** on all three assertions — `total derivations = 1, want 2`, the new address absent, the old address still on the page. Restored → green. | and only *then* closes and unlinks the `.part` — so "the slot is free" does not yet mean "the file
is gone", and the test stat-ed immediately. It passed in isolation and failed in the full package
once this release's new render tests made `web` heavier. **Not caused by this change and not a
production bug** (a new upload mints a fresh random `.part`, so the gap is harmless); the test was
asserting an async post-condition synchronously. It now waits on the same 3 s deadline with the
assertion unchanged.
### Other gates Design gates `template_id_gate` / `emoji_gate` / `native_confirm_gate` / `offbox_rename_gate`: **OK**.
`template_id_gate` · `emoji_gate` · `native_confirm_gate` · `offbox_rename_gate`**all OK**. ## Build, publish, deploy
**`sharing.html`'s `<script>` block is byte-identical to v0.150.0** — verified by extracting the
block from `git show HEAD:…` and from the working tree and comparing (one block each, equal). Both
fixes are server-side, so the client contract is proven fixed rather than worked around.
## Deploy
``` ```
./build.sh 0.151.0 --push → sha256:0141b62fe64e3d471446d8a1a8c1a719b3669dab26888e50acbf13644677510b, 145M ./controller/scripts/build-samba-image.sh 1.1.0 --push → sha256:1c17c09422be…
docker ps --filter name=felhom-controller --format '{{.Image}} {{.Status}}' smoke test (DooPlex, DEFAULT BRIDGE — never --network host on this host, it would bind 445/5353)
gitea.dooplex.hu/admin/felhom-controller:0.151.0 Up 26 seconds (healthy) → smbd · nmbd · wsdd · dbus-daemon · avahi-daemon: running [SMOKETEST.local] (all 5 up)
→ host-name=SMOKETEST, allow-interfaces=eth0, _smb._tcp/445 in the service file
anonymous pull from guest 9201 (no registry creds) → OK
./build.sh 0.152.0 --push → deployed to 9201
``` ```
Clean-tree gate held before the build: `git status --porcelain` empty, `HEAD == origin/main`. Live: `felhom-controller:0.152.0 Up (healthy)` · `felhom-samba:1.1.0 Up` — the controller's reconcile
recreated the samba container onto the real image, discarding the hand-patched spike.
## Live validation ## Live validation
**Method: endpoint-level** — the exact endpoints the UI invokes, so no server logic is skipped, only **Method: on-the-wire from a second machine (felhom-pve) + endpoint-level for the page.**
rendering. `claude-in-chrome` is not available on DooPlex.
1. **`GET /sharing/status` ×3, authenticated** → `{"data":{"error":"","phase":"idle","running":true},"ok":true}` ```
on all three calls. Before the fix the same call returned `phase:"running"` every time. The mDNS from felhom-pve, against the SHIPPED image:
client's reload path is reachable only from `phase === "running"`, so this is the server-side FELHOM.local (A) -> A 192.168.0.104 (from 192.168.0.104)
proof that the loop's trigger is gone. _smb._tcp (browse) -> PTR FELHOM._smb._tcp.local (from 192.168.0.104)
2. **`GET /sharing`, authenticated** → the connect card is present (2 `Csatlakoz` hits — the heading
and the Mac line) showing `smb://FELHOM` **and `smb://192.168.0.104`**. That address is the
guest's real current DHCP lease and is a DERIVED value: it matches what `ip -4 -o addr show eth0`
reports inside the samba container, and it exists as a literal nowhere in the codebase. Grepped
with ASCII-safe substrings per the CLAUDE.md trap.
3. **Human check — OPEN, Viktor's to give:** that the page visibly sits still. A browser is the one
method not available here.
## DIAG finding-status flips (`felhom.eu@eaa2173`) daemons in felhom-samba:1.1.0: smbd · nmbd · wsdd · dbus-daemon · avahi-daemon: running [FELHOM.local]
S-1 → **FIXED v0.151.0** · S-2 → **SHIPPED v0.151.0** · S-4 → **CORE SHIPPED v0.151.0** (the unified GET /sharing -> smb://FELHOM.local smb://192.168.0.104 \\FELHOM
async-job layer stays R-45, which gained a one-line note recording the lesson) · S-5 → **SHIPPED GET /sharing/status ×2 -> phase:"idle", running:true (v0.151.0 contract still holding)
v0.151.0** · S-3 → **OPEN**, awaiting `smbutil lookup FELHOM` and `dns-sd -B _smb._tcp` from the Mac. ```
Operator, from the Mac: **`smb://FELHOM.local` connects and prompts for credentials.**
## Still open
- **Finder-sidebar discovery — NOT shipped, NOT claimed.** The `_smb._tcp` record is published and
answers browse queries on the wire, but FELHOM did not appear in the operator's Finder sidebar.
That window showed no Network/Bonjour section at all, which points at **Finder Settings → Sidebar
→ Locations** rather than at the box — unverified either way. Next: that setting, and
`dns-sd -B _smb._tcp` on the Mac. Zero-typing discovery was the R-6 spike's original ambition and
is still not demonstrated.
- **Windows was not retested** this session. `\\FELHOM` is served by nmbd, which this release does
not touch, and the capture proves nmbd answers correctly — but no Windows client was exercised.
## Observations — noticed, not acted on ## Observations — noticed, not acted on
- **The poll timer's idle path.** `tick()` does call `stop()` in the idle branch, so on a steady-state - **The username trap.** macOS prefills the local account name (`Viktor.Nagyfenyvesi`) in the SMB
page the interval now ends after the first response — but the `.catch()` branch deliberately keeps credential dialog; the household account is `felhom`. The page states this in the password card,
polling on a transient failure, so a page whose status endpoint is erroring polls forever at 1.5 s. but not next to the connect addresses where the customer is looking at that moment. Worth a
Recorded, not fixed: the JS was read-only in this task, and this is the next thing anyone auditing sentence in the connect card — deliberately not added mid-session without a design pass.
that block will ask about. - **nmbd's duplicate answer** is harmless here but would look alarming in any future capture. Noted
- **Multi-tab success banner.** With two `/sharing` tabs open during a real bring-up, only the tab in the DIAG so the next person does not re-investigate it.
that polls first sees „A megosztási szolgáltatás fut." Accepted, and documented at the method - **The spike left avahi installed by hand** in the old container. It was discarded when the
itself; both tabs still show the true state, which comes from the `running` level and the reconcile recreated the container on 1.1.0 — worth remembering that hand-patching a *managed*
server-rendered badge. infra container is always temporary by construction, which is a safety property, not a limitation.
- **`SMBRunning` in `sharingPageData` had no nil-manager guard** while the status handler beside it - **`dns-sd`/`avahi-browse` were not available** anywhere on the box side, so both the NBNS and mDNS
did. Added (one `!= nil`) — noted because it is the only change in this task not traceable to a verifications were done with small hand-written UDP probes run from felhom-pve. That turned out to
DIAG finding. be the more valuable method anyway: querying from a *second machine* is what distinguishes "the
- **`setup.DetectLocalIPs` is the same problem answered differently.** It needs a `HOST_IP` env var daemon answers itself" from "the daemon answers the network", and it is what proved the box
because the controller sits on a docker bridge; the new read goes through the samba container innocent before any code changed.
instead, which needs no env plumbing but only works while sharing is up. Neither is wrong — worth
knowing both exist if a third caller ever wants a guest IP.
- **The samba container has no `ps`** (found while establishing what the exec path could rely on) but
does have `/sbin/ip`. Any future in-container probe should verify its tool the way the catalog
healthcheck rule already requires.
- **The `.162` half of the original report is still worth a product answer.** The connect card now
gives customers the right address, but nothing stops a *stale saved favourite* on the Mac from
failing silently forever. Out of scope here; it is the kind of thing the S-3 mDNS work would make
moot by putting the box in the Finder sidebar.
@@ -340,7 +340,21 @@ func TestFabUpload_GCAndIdleTimeout(t *testing.T) {
} }
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
} }
if _, err := os.Stat(part); !os.IsNotExist(err) { // The unlink is ASYNCHRONOUS with respect to the slot: expireIdleUpload nils `cur`, releases
// the mutex, and only then closes + removes the file. So "the slot is free" does not yet mean
// "the .part is gone", and stat-ing immediately is a race the test loses under package load
// (observed 2026-07-20 — passes alone, fails in the full package). Wait for the outcome being
// asserted; the assertion itself is unchanged, and the deadline still fails a part that is
// never deleted.
partGone := false
for deadline := time.Now().Add(3 * time.Second); time.Now().Before(deadline); {
if _, err := os.Stat(part); os.IsNotExist(err) {
partGone = true
break
}
time.Sleep(10 * time.Millisecond)
}
if !partGone {
t.Fatal("idle-expired .part must be deleted") t.Fatal("idle-expired .part must be deleted")
} }
if rr := uploadChunk(s, id, 0, []byte("late")); rr.Code != http.StatusNotFound { if rr := uploadChunk(s, id, 0, []byte("late")); rr.Code != http.StatusNotFound {