Files
g15stats/tests/render-test.c
T
Mikhail Yevchenko 7c06a8089d Add mixed-vendor GPU summary and fix startup heap corruption
Collect AMD amdgpu sysfs and optional NVIDIA nvidia-smi stats in the
background, auto-detecting supported adapters in PCI order and rotating
additional pairs alongside CPU/RAM. Explicit PCI selection and off mode
remain supported; missing metrics render as N/A.

Initialize the bitmap-only canvas locally instead of the ABI-dependent
libg15render initializer: the installed TTF-enabled renderer wrote an
8-byte FreeType pointer past the smaller bitmap-only allocation, corrupting
the heap before GPU threads started. Add collector, rendering and full-main
regression tests.
2026-09-07 17:06:32 +00:00

230 lines
10 KiB
C

/* SPDX-License-Identifier: GPL-2.0-or-later
* Exercise the real renderer without any daemon connection or input thread.
*/
#define main g15stats_application_main
#include "../g15stats.c"
#undef main
#include <assert.h>
/* Independent font-bitmap oracle: do not repeat renderString on a second
* canvas, since that also repeats any clipping/erasure in the renderer.
* The default fonts have zero gap and one blank trailing pixel column.
* Compare every cell, including blank pixels, against the font file's bits. */
static void assert_text(g15canvas *canvas, const char *text, int size, int x, int y, int right)
{
g15font *font = g15r_requestG15DefaultFont(size);
assert(font && font->default_gap == 0);
assert(font->font_height == font->ascender_height);
for (; *text; text++) {
unsigned char c = (unsigned char)*text;
int width = font->glyph[c].width;
int stride = (width + 7) / 8;
assert(font->active[c] && font->glyph[c].buffer);
assert(x >= 0 && x + width <= 160);
assert(y >= 0 && y + font->font_height <= 43);
for (int dy = 0; dy < font->font_height; dy++)
for (int dx = 0; dx < width; dx++) {
int bit = !!(font->glyph[c].buffer[dy * stride + dx / 8] &
(0x80 >> (dx % 8)));
/* A 13-character first-column header's final blank advance
* pixel shares x=105 with the next divider; no ink may do so. */
if (x + dx >= right) {
assert(!text[1] && dx == width - 1 && !bit);
continue;
}
if (g15r_getPixel(canvas, x + dx, y + dy) != bit) {
fprintf(stderr, "Glyph %c pixel (%d,%d): expected %d, got %d\n",
c, x + dx, y + dy, bit,
g15r_getPixel(canvas, x + dx, y + dy));
assert(0);
}
}
x += width;
}
}
int main(int argc, char **argv)
{
g15canvas canvas, before;
char tmp[MAX_LINES];
int x, y, variant;
size_t i;
g15font *font = g15r_requestG15DefaultFont(G15_TEXT_SMALL);
assert(font);
assert(g15r_testG15FontWidth(font, "T 8192.0PiB") <= 52);
assert(g15r_testG15FontWidth(font, "UTL 100%") <= 52);
{
char lines[5][32];
compact_ram_lines(lines, LLONG_MAX, LLONG_MAX);
assert(strstr(lines[1], "PiB") && strstr(lines[2], "PiB"));
assert(g15r_testG15FontWidth(font, lines[1]) <= 50);
assert(g15r_testG15FontWidth(font, lines[2]) <= 50);
compact_ram_lines(lines, 19 * 1048576, 17179869184LL);
assert(!strcmp(lines[1], "R 19.0MiB"));
assert(!strcmp(lines[2], "T 16.0GiB"));
}
memset(&canvas, 0xa5, sizeof(canvas));
init_canvas(&canvas);
assert(!canvas.mode_xor && !canvas.mode_reverse && !canvas.mode_cache);
for (y = 0; y < 43; y++) for (x = 0; x < 160; x++)
assert(!g15r_getPixel(&canvas, x, y));
/* Original full-width geometry must remain exact with the column off. */
init_canvas(&before);
summary_gpu = 0;
drawBar_both(&canvas, 1, 8, 37, 100, 63, 100);
g15r_drawBar(&before, BAR_START, 1, BAR_END, 8, G15_COLOR_BLACK, 37, 100, 4);
drawBar_reversed(&before, BAR_START, 1, BAR_END, 8, G15_COLOR_BLACK, 63, 100, 5);
assert(!memcmp(canvas.buffer, before.buffer, sizeof(canvas.buffer)));
glibtop_init();
/* No device fixtures: sample the actual selected GPU when requested. */
assert(!gpu_detect(argc > 1 ? argv[1] : "off"));
if (gpu_count && gpu_devices[0].backend) {
long long start = gpu_milliseconds();
assert(!gpu_start());
do {
gpu_frame frame = gpu_snapshot();
for (i = 0; i < frame.count; i++) if (frame.samples[i].total < 0) break;
if (i == frame.count) break;
poll(NULL, 0, 10);
} while (gpu_milliseconds() - start < 1800);
assert(i == gpu_count);
assert(!gpu_stop());
}
/* No-argument make check retains the worst-width fixture. Explicit off
* must exercise the application's actual zero-GPU/full-width layout.
* Optional count/page/profile arguments are in-memory visual cases only. */
if (!gpu_count && (argc == 1 || argc > 3)) {
gpu_count = argc > 3 ? (size_t)atoi(argv[3]) : 2;
assert(gpu_count >= 1 && gpu_count <= GPU_MAX);
gpu_devices[0].backend = 2;
gpu_devices[1].backend = 1;
strcpy(gpu_devices[0].bus, "0000:01:00.0");
strcpy(gpu_devices[1].bus, "0000:11:00.0");
gpu_publish(0, (gpu_sample){100, LLONG_MAX, LLONG_MAX, -273});
gpu_publish(1, (gpu_sample){100, LLONG_MAX, LLONG_MAX, 1000});
for (i = 2; i < gpu_count; i++) {
gpu_devices[i].backend = 1;
snprintf(gpu_devices[i].bus, 32, "0001:%02x:1f.7", (unsigned)i);
gpu_publish(i, i % 2 ? gpu_unknown : (gpu_sample){100, LLONG_MAX, LLONG_MAX, -273});
}
if (gpu_count > 2) {
/* Exercise every pair at the maximum inventory, including the
* 13-character AMD64 header and an odd final card. */
for (i = 0; i < (gpu_count + 1) / 2; i++) {
gpu_page = i;
gpu_page_due = LLONG_MAX;
g15r_clearScreen(&canvas, G15_COLOR_WHITE);
draw_gpu_column(&canvas);
for (y = 35; y < 43; y++) for (x = 0; x < 160; x++)
assert(!g15r_getPixel(&canvas, x, y));
}
/* Keep the final pair for the exported preview and pixel checks. */
}
}
if (argc > 4) {
gpu_page = (size_t)atoi(argv[4]);
assert(gpu_page < (gpu_count + 1) / 2);
gpu_page_due = LLONG_MAX;
}
if (argc > 5) {
assert(!strcmp(argv[5], "large") || !strcmp(argv[5], "unknown"));
for (i = 0; i < gpu_count; i++)
gpu_publish(i, !strcmp(argv[5], "unknown") ? gpu_unknown :
(gpu_sample){100, 100LL << 30, 192LL << 30, 1000});
}
for (i = 0; i < gpu_count; i++) {
char lines[5][32];
int row;
gpu_lines(i, gpu_cache[i], lines);
for (row = 0; row < 5; row++) {
assert(g15r_testG15FontWidth(font, lines[row]) <= 52);
printf("GPU %zu row %d: %s\n", i + 1, row, lines[row]);
}
}
for (variant = 0; variant < 2; variant++) {
cycle = SCREEN_SUMMARY;
mode[SCREEN_SUMMARY] = variant;
have_temp = have_fan = 0; /* Keep legacy generic hwmon probing out of QA. */
summary_gpu = gpu_count != 0;
draw_cpu_screen_multicore(&canvas, tmp, 0);
before = canvas;
draw_gpu_column(&canvas);
for (y = 0; y < 43; y++)
for (x = 0; x < 160; x++)
if (x < (gpu_count > 1 ? 51 : 105) || y >= 35)
assert(g15r_getPixel(&canvas, x, y) == g15r_getPixel(&before, x, y));
/* Re-render each column on an empty canvas: exact pixels prove neither
* neighboring column nor CPU text overlaps it, including edge glyphs. */
{
gpu_frame frame = gpu_snapshot();
size_t first = gpu_page_first(frame.count, gpu_page * 5000ULL);
for (i = first; i < frame.count && i < first + 2; i++) {
g15canvas isolated;
char lines[5][32];
int row, left = frame.count == 1 ? 108 : 54 + (int)(i-first) * 54;
init_canvas(&isolated);
gpu_lines(i, frame.samples[i], lines);
for (row = 0; row < 5; row++)
g15r_renderString(&isolated, (unsigned char *)lines[row], 0, G15_TEXT_SMALL, left, 1+row*7);
for (y = 0; y < 35; y++) for (x = left; x < (left == 54 ? 105 : 160); x++)
assert(g15r_getPixel(&canvas, x, y) == g15r_getPixel(&isolated, x, y));
}
}
summary_gpu = 0;
print_time_info(&canvas, tmp);
assert(g15r_testG15FontWidth(font, tmp) <= 160);
assert_text(&canvas, tmp, G15_TEXT_SMALL,
80 - (int)strlen(tmp) * 4 / 2, INFO_ROW, 160);
if (gpu_count < 2) {
const char *label = "TOTAL";
for (int row = 0; row < 5; row++) {
char letter[2] = {label[row], 0};
assert_text(&canvas, letter, G15_TEXT_MED,
gpu_count ? 98 : TEXT_RIGHT, row * 7, 160);
}
}
{
gpu_frame frame = gpu_snapshot();
size_t first = gpu_page_first(frame.count, gpu_page * 5000ULL);
for (i = first; i < frame.count && i < first + 2; i++) {
char lines[5][32];
int left = frame.count == 1 ? 108 : 54 + (int)(i-first) * 54;
gpu_lines(i, frame.samples[i], lines);
for (int row = 0; row < 5; row++)
assert_text(&canvas, lines[row], G15_TEXT_SMALL, left, 1+row*7,
left == 54 ? 105 : 160);
}
}
if (argc > 2) {
char path[PATH_MAX];
snprintf(path, sizeof(path), "%s-%d.bin", argv[2], variant);
FILE *raw = fopen(path, "wb");
assert(raw);
assert(fwrite(canvas.buffer, 1, 160 * 43 / 8, raw) == 160 * 43 / 8);
assert(!fclose(raw));
snprintf(path, sizeof(path), "%s-%d.pgm", argv[2], variant);
FILE *out = fopen(path, "w");
assert(out);
fprintf(out, "P2\n160 43\n255\n");
for (y = 0; y < 43; y++) {
for (x = 0; x < 160; x++) fprintf(out, "%d ", g15r_getPixel(&canvas, x, y) ? 0 : 255);
fputc('\n', out);
}
assert(!fclose(out));
}
}
if (argc > 2) {
FILE *out = fopen(argv[2], "w");
assert(out);
fprintf(out, "P2\n160 43\n255\n");
for (y = 0; y < 43; y++) {
for (x = 0; x < 160; x++)
fprintf(out, "%d ", g15r_getPixel(&canvas, x, y) ? 0 : 255);
fputc('\n', out);
}
assert(!fclose(out));
}
glibtop_close();
puts("PASS real LCD buffer: initialization, original geometry, both summary variants, independent glyph bits, complete TOTAL/footer, column bounds");
return 0;
}