summaryrefslogtreecommitdiff
path: root/tests/write_fuzz_test.c
blob: 8c1475a98e1b668b2dc3ee9ca444a0ec32078bd2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/*
 * Differential fuzz test for term_feed. Each seed makes random terminal
 * output: text, controls, CSI and ESC sequences, raw bytes, and resizes.
 * One terminal gets the output in a few large writes; the bulk fast paths
 * handle it. A second terminal gets it one byte at a time; the generic
 * parser handles it. The two must show the same cursor, screen, and
 * scrollback.
 */
#include "term_api.h"
#include "fuzz_gen.h"

#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Each terminal has its own snapshot, so cells of both stay readable at
 * the same time. */
static struct term_snapshot *snap_bulk, *snap_bytes;

/* The code points of a cell's text. A cluster has handles that differ
 * between snapshots, so the test compares its code points. */
static int cell_text(const struct term_snapshot *s, const struct term_cell *cell,
                     uint32_t *cps, int cap)
{
    int n = term_snapshot_cluster(s, cell->text, cps, cap);
    return n < cap ? n : cap;
}

static bool same_cell(const struct term_cell *a, const struct term_cell *b)
{
    if (a->fg != b->fg || a->bg != b->bg || a->attrs != b->attrs || a->width != b->width)
        return false;
    uint32_t ta[16], tb[16];
    int na = cell_text(snap_bulk, a, ta, 16);
    int nb = cell_text(snap_bytes, b, tb, 16);
    return na == nb && memcmp(ta, tb, (size_t)na * sizeof(ta[0])) == 0;
}

static void assert_same(struct term *bulk, struct term *bytes, int seed, int step)
{
    int cols = tt_cols(bulk), rows = tt_rows(bulk);
    int history = tt_history(bulk);
    bool same = tt_cursor_x(bulk) == tt_cursor_x(bytes) &&
                tt_cursor_y(bulk) == tt_cursor_y(bytes) &&
                history == tt_history(bytes);
    term_scroll(bulk, history);
    term_scroll(bytes, history);
    for (int page = 0; same && page <= history; page += rows) {
        bool copied = term_snapshot_update(snap_bulk, bulk);
        copied &= term_snapshot_update(snap_bytes, bytes);
        assert(copied);
        for (int y = 0; same && y < rows; y++) {
            const struct term_cell *ra = term_snapshot_row(snap_bulk, y);
            const struct term_cell *rb = term_snapshot_row(snap_bytes, y);
            for (int x = 0; x < cols; x++) {
                const struct term_cell *a = &ra[x], *b = &rb[x];
                if (!same_cell(a, b)) {
                    fprintf(stderr, "seed %d step %d: page %d row %d col %d: "
                            "bulk %x/%x/%x/%x/%u, bytes %x/%x/%x/%x/%u\n",
                            seed, step, page, y, x, a->text, a->fg, a->bg, a->attrs,
                            a->width, b->text, b->fg, b->bg, b->attrs, b->width);
                    same = false;
                    break;
                }
            }
        }
        term_scroll(bulk, -rows);
        term_scroll(bytes, -rows);
    }
    term_scroll_to(bulk, 0);
    term_scroll_to(bytes, 0);
    if (!same)
        fprintf(stderr, "seed %d step %d: cursor %d,%d vs %d,%d, history %d vs %d\n",
                seed, step, tt_cursor_x(bulk), tt_cursor_y(bulk),
                tt_cursor_x(bytes), tt_cursor_y(bytes),
                history, tt_history(bytes));
    assert(same);
}

int main(int argc, char **argv)
{
    int seeds = argc > 1 ? atoi(argv[1]) : 50000;
    for (int seed = 1; seed <= seeds; seed++) {
        rng = 0x9e3779b97f4a7c15ull * (uint64_t)seed;
        int cols = 4 + rn(30), rows = 2 + rn(10);
        struct term *bulk = tt_new(cols, rows);
        struct term *bytes = tt_new(cols, rows);
        /* A new terminal can get the address of a freed one, so each seed
         * uses new snapshots. */
        snap_bulk = term_snapshot_new();
        snap_bytes = term_snapshot_new();
        assert(bulk && bytes && snap_bulk && snap_bytes);
        for (int step = 0; step < 6; step++) {
            blen = 0;
            gen(20 + rn(60), cols, rows);
            size_t split = blen ? (size_t)rn((int)blen + 1) : 0;
            tt_write(bulk, buf, split);
            tt_write(bulk, buf + split, blen - split);
            for (size_t i = 0; i < blen; i++)
                tt_write(bytes, buf + i, 1);
            assert_same(bulk, bytes, seed, step);
            if (rn(4) == 0) {
                cols = 4 + rn(30);
                rows = 2 + rn(10);
                bool resized = tt_resize(bulk, cols, rows);
                resized &= tt_resize(bytes, cols, rows);
                assert(resized);
                assert_same(bulk, bytes, seed, step);
            }
        }
        term_snapshot_free(snap_bulk);
        term_snapshot_free(snap_bytes);
        term_free(bulk);
        term_free(bytes);
    }
    return 0;
}