Skip to content

Commit e9ddfd0

Browse files
committed
fix: reject CSV viewer close failures
1 parent 7afe63f commit e9ddfd0

10 files changed

Lines changed: 387 additions & 4 deletions

File tree

‎.github/workflows/release.yml‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,25 @@ jobs:
288288
- name: Test Notes with real LVGL
289289
shell: pwsh
290290
run: python tools/test_notes_io.py --compiler clang --lvgl-library build/offline-ui/out/lvgl_host.lib --output build/notes-ui
291+
- name: Test CSV viewers with real LVGL
292+
shell: pwsh
293+
run: python tools/test_csv_viewer.py --compiler clang --lvgl-library build/offline-ui/out/lvgl_host.lib --output build/csv-viewer-ci
294+
- name: Retain CSV viewer UI evidence
295+
uses: actions/upload-artifact@v4
296+
if: always()
297+
with:
298+
name: csv-viewer-logs
299+
path: |
300+
build/csv-viewer-ci/compile.log
301+
build/csv-viewer-ci/result.log
302+
build/csv-viewer-ci/source.json
303+
build/csv-viewer-ci/capture_viewer.c
304+
build/csv-viewer-ci/capture_data.c
305+
build/csv-viewer-ci/serial_log_viewer.c
306+
build/csv-viewer-ci/serial_log_data.c
307+
build/csv-viewer-ci/payload_clipboard.c
308+
if-no-files-found: warn
309+
retention-days: 14
291310
- name: Retain Notes UI test evidence
292311
uses: actions/upload-artifact@v4
293312
if: always()

‎ROADMAP.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ Phase 1 software progress is also underway:
6666
- [x] Ride history reports recovery/open/read/close errors as unavailable instead of showing zero or partial totals. Summary appends check SD availability and preparation errors, reject failed read chunks before copying, and retain the first error through cleanup. All 50 native file/fault cases pass; the preceding callbacks fail 24 controls. Exact final/backup/temporary bytes and 25 reopens are checked with closed modeled handles. Linux CI retains source hashes and logs, and the unflashed firmware builds with unchanged configuration. Physical SD/panel and power cuts remain open.
6767
- [x] Ring logging commits its saved timestamp only after tail recovery and a complete, successfully closed read. Failed loads retain queued samples and defer history requests until a successful retry; known SD unavailability also blocks a cached checkpoint. Appends check directory errors and preserve the first seek/tell/header/row error through close and repair. All 59 native file/queue/fault cases pass; the preceding callbacks fail 32 controls. Checks verify exact bytes, retry/deduplication, timeout handling while blocked and 25 checkpoint reopens with closed modeled handles. Linux CI retains source hashes and logs. Physical SD, BLE, panel and power cuts remain open.
6868
- [x] Ebook page reads publish their text and byte positions only after read/close success. Failed navigation retains the previous page for retry, including zero-byte and full-count read errors. Initial navigation is disabled until a page loads; callbacks reject disabled/offscreen actions and invalid positions. All 44 native file/state/fault checks pass, including 25 navigation cycles with one reused buffer and closed handles. Thirty-one preceding-callback controls fail; three new unsafe-boundary guards are tested only with the new callbacks. Linux CI retains source hashes, initialization source and logs. Physical SD/panel and power behavior remain open.
69+
- [x] Recognized Scope/I2C and saved serial CSV loads require a successful close before showing a graph or row-copy controls. Failed or empty capture loads release their row buffer immediately. All 36 real LVGL/native-file checks pass; the preceding viewers fail 15 controls. Checks cover both row limits, I2C gaps, read/seek/close and allocation faults, unchanged file/clipboard bytes and 25 success/error/reopen cycles per viewer with stable memory in the unchanged 96 KiB pool. Existing serial-copy/inspector checks pass. Windows CI retains complete viewer/parser sources and logs; physical SD/panel behavior remains open.
6970
- [x] Shared CSV tail recovery now scans backwards in 512-byte blocks. Eight previous-source controls exceed stdio seek budgets; a 64 KiB partial tail drops from 65,538 seeks to 130 with the same retained prefix. All 32 native byte/boundary/fault cases, original storage checks and five MQTT/HTTP logging cases pass with closed modeled handles. The unflashed firmware builds with unchanged configuration. Linux CI retains source hashes and compilation/result logs; physical SD timing and power cuts remain open.
7071
- [x] Built-in SD paths, timestamps, units, 8.3-safe collision names, `.TMP`, and `.BAK` behavior are documented.
7172
- [x] Persisted brightness choices, a dimmed two-minute screen, and configurable 5/10/30-minute or Never screen-off behavior are implemented.

‎docs/architecture.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ Ring's saved heart-rate timestamp becomes loaded only after incomplete-tail repa
9191

9292
Ebooks reads up to 8 KiB into its existing cached PSRAM buffer. Page text, current/next byte positions, navigation state and scroll position change only after successful seek, read and close. Failure preserves the confirmed page and positions, reports the first error and allows retry when storage is available. Reader initialization clears both offsets and disables navigation until loading succeeds. Callbacks recheck disabled state and screen pointers; negative positions and additions beyond the platform's `long` range are rejected. Native files and controlled UI/heap/SD boundaries verify the transitions; physical SD and panel behavior remain open.
9393

94+
The Scope/I2C capture and saved serial CSV viewers require a successful close before constructing their graph or row-copy controls. A read or close failure after format recognition shows an error and releases the viewer buffers immediately. Empty recognized captures also release their row buffer. Real LVGL checks cover error/reopen transitions and stable memory in the unchanged 96 KiB pool; filesystem faults and PSRAM allocation are controlled host boundaries, with physical SD and panel checks still open.
95+
9496
See [Storage and compatibility contract](compatibility.md) and [Data formats](data-formats.md).
9597

9698
## Network and radio

‎docs/data-formats.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ Files opens published Scope and I2C `.CSV` captures as graphs when their headers
2929

3030
Files also opens published UART and RS-485 `.CSV` logs as a timestamped RX/TX timeline. The viewer loads up to the first 512 well-formed rows, shows eight rows per page, and lets you filter by direction or switch between hex and escaped ASCII without changing the file. Unpublished `.TMP` files retain the text preview.
3131

32+
After a supported capture or serial header is recognized, seek/read/close failures show an error instead of a graph or copyable timeline. The viewers release their row/text buffers immediately on failed or empty loads. The original file remains unchanged; a successful reopen can display it again. These boundaries are checked with real LVGL and native files under controlled I/O failures; physical SD behavior remains open.
33+
3234
Choose a record from the current page and tap **COPY ROW** to put its original 1-128 bytes on the shared RAM clipboard. The selector and timeline use the same record numbers: positions among loaded valid rows, not physical CSV line numbers. Page/filter changes select the first matching record on the new page; changing Hex/ASCII preserves the selection. Rows of 129-256 bytes remain viewable but cannot be partially copied. Empty filters and failed copies preserve the clipboard. Record bytes can then be pasted into Byte Lab or RTU Frames; a logged RX/TX chunk may split or combine protocol frames, so a record is not a guarantee of a complete message. Copy changes neither the CSV nor the displayed byte values.
3335

3436
## CSV conventions

‎docs/testing.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Python 3 and Node.js are needed for the quick checks. The configuration checks a
1515
| Ride history loading or summary appends | [Ride history storage](#ride-history-storage) and a firmware build. |
1616
| Ring heart-rate logging, saved timestamps or queue processing | [Ring history storage](#ring-history-storage) and a firmware build. |
1717
| Ebook page loading or navigation | [Ebook pages](#ebook-pages) and a firmware build. |
18+
| Scope/I2C capture or saved serial CSV viewers | [CSV viewer storage and UI](#csv-viewer-storage-and-ui), existing serial-copy checks and a firmware build. |
1819
| Offline apps, SPI or I2C editor/result flows | [Offline and peripheral UI](#offline-and-peripheral-ui). |
1920
| Serial, saved serial logs or RTU Frames | [Serial and RTU UI](#serial-and-rtu-ui). |
2021
| Modbus TCP, NTP, Wake-on-LAN, UDP or HTTP fixtures | [Network fixtures and UI](#network-fixtures-and-ui). |
@@ -150,6 +151,18 @@ Retry cases use the actual navigation callbacks after storage availability is re
150151

151152
Reader initialization is retained and checked as source, including zero offsets and initially disabled controls; it is not compiled into the native fixture. The firmware build checks its real UI declarations. Full main/function/initialization/test hashes, generated callback/config/initialization source and compile/result logs stay under `build/ebook-pages/`. Linux CI retains these files as `ebook-pages-logs`, including compiler/test failures. These 44 cases are separate from the Windows native total.
152153

154+
## CSV viewer storage and UI
155+
156+
```powershell
157+
python tools/test_csv_viewer.py --compiler 'C:/path/to/clang.exe' --lvgl-library build/offline-ui/out/lvgl_host.lib
158+
```
159+
160+
This Windows runner reuses the cached Debug LVGL library and compiles both complete viewer/parser pairs plus the actual shared clipboard. Its 36 real UI/native-file cases check Scope and I2C graph values/gaps, serial copy-control admission, the 2,048/512-row limits, empty captures/logs, unsupported formats, open/allocation/seek failures, parser-header and row read faults, and close failures including missing errno and combined read/close faults. The preceding viewers fail 15 controls: close failures still construct a graph or copyable timeline, and capture buffers remain allocated on empty/failed loads.
161+
162+
Every case checks the original file fingerprint and retained clipboard bytes. Failed loads must release external row/text allocations before teardown. Both viewers also run 25 success/error/success cycles (75 opens/closes each) with unchanged LVGL free bytes and allocation counts in the existing 96 KiB pool. Heap and I/O failures are controlled; widgets, parsers, graph arrays and UI cleanup use real LVGL. Open/probe fallback retains its existing API contract; the new failure rule applies after header recognition. These checks do not establish physical SD, PSRAM, panel, latency or power-loss behavior.
163+
164+
Complete viewer/parser/clipboard snapshots, their source/header hashes, the I/O adapter/test hashes and compiler/runtime logs are retained under `build/csv-viewer/`. Windows CI runs these 36 checks after the original 820 native checks and retains `csv-viewer-logs`; this is separate from the Linux fixtures. Use `tools/test_serial_log_ui.ps1` for the existing row-copy/filter/page/inspector checks.
165+
153166
## OTA manifests
154167

155168
```powershell

‎main/capture_viewer.c‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ bool capture_viewer_show(lv_obj_t *parent, const char *path)
174174

175175
rows = heap_caps_malloc(VIEWER_MAX_ROWS * sizeof(*rows), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
176176
bool loaded = rows && capture_data_read(file, rows, VIEWER_MAX_ROWS, &data);
177-
fclose(file);
177+
if (fclose(file) != 0) loaded = false;
178178

179179
const char *name = strrchr(path, '/');
180180
name = name ? name + 1 : path;
@@ -183,10 +183,12 @@ bool capture_viewer_show(lv_obj_t *parent, const char *path)
183183
lv_label_set_text_fmt(title, "%s | %s", format == CAPTURE_FORMAT_SCOPE ? "Scope" : "I2C watch", name);
184184
lv_obj_set_style_text_font(title, &lv_font_montserrat_28, 0);
185185
if (!loaded || !data.count) {
186+
const char *error = !rows ? "Not enough memory to open capture" :
187+
!loaded ? "Could not read capture" : "No readable capture rows in this file";
188+
capture_viewer_stop();
186189
lv_obj_t *message = lv_label_create(parent);
187190
lv_obj_set_width(message, 640);
188-
lv_label_set_text(message, !rows ? "Not enough memory to open capture" :
189-
!loaded ? "Could not read capture" : "No readable capture rows in this file");
191+
lv_label_set_text(message, error);
190192
return true;
191193
}
192194

‎main/serial_log_viewer.c‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ bool serial_log_viewer_show(lv_obj_t *parent, const char *path)
292292
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
293293
page_text = heap_caps_malloc(VIEWER_TEXT_BYTES, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
294294
bool loaded = rows && page_text && serial_log_read(file, rows, VIEWER_MAX_ROWS, &data);
295-
fclose(file);
295+
if (fclose(file) != 0) loaded = false;
296296

297297
const char *name = strrchr(path, '/');
298298
name = name ? name + 1 : path;

‎tests/csv_viewer_host/io_faults.h‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#pragma once
2+
#include <stdio.h>
3+
4+
FILE *csv_fixture_open(const char *path, const char *mode);
5+
int csv_fixture_seek(FILE *file, long offset, int origin);
6+
char *csv_fixture_gets(char *text, int size, FILE *file);
7+
int csv_fixture_error(FILE *file);
8+
int csv_fixture_close(FILE *file);
9+
10+
#define fopen csv_fixture_open
11+
#define fseek csv_fixture_seek
12+
#define fgets csv_fixture_gets
13+
#define ferror csv_fixture_error
14+
#define fclose csv_fixture_close

0 commit comments

Comments
 (0)