summaryrefslogtreecommitdiff
path: root/tests/t_buf.c
blob: dcfb0bbb77cdd3f454f312a432d7b9e6c0e10aeb (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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
   It is considered beerware. Prost. Skol. Cheers or whatever.

   $id$ */

/* System */
#include <stdint.h>
#include <string.h>

/* Opentracker */
#include "ot_buf.h"
#include "test.h"

static void case_starts_empty(void) {
  ot_buf buf = {NULL, 0};

  test_case("a fresh buffer holds nothing and has no data pointer");
  CHECK_PTR(buf.data, NULL);
  CHECK_INT(buf.size, 0);
}

static void case_cat_appends(void) {
  ot_buf buf = {NULL, 0};

  test_case("appends concatenate in order and report success");
  CHECK_INT(buf_cat(&buf, "GET /ann", 8), 0);
  CHECK(buf.data != NULL);
  CHECK_INT(buf.size, 8);
  CHECK_INT(buf_cat(&buf, "ounce", 5), 0);
  CHECK_INT(buf.size, 13);
  CHECK_MEM(buf.data, "GET /announce", 13);
  buf_reset(&buf);
}

static void case_cat_of_nothing_allocates_nothing(void) {
  ot_buf buf = {NULL, 0};

  /* handle_read tells an untouched buffer from a partial request by the data
     pointer, so a zero length append must not quietly allocate one */
  test_case("appending zero bytes leaves the buffer untouched");
  CHECK_INT(buf_cat(&buf, "ignored", 0), 0);
  CHECK_PTR(buf.data, NULL);
  CHECK_INT(buf.size, 0);
  buf_reset(&buf);
}

static void case_grows_across_many_appends(void) {
  ot_buf buf = {NULL, 0};
  char   chunk[100];
  size_t i;

  test_case("content survives growing one packet at a time");
  memset(chunk, 'x', sizeof(chunk));
  for (i = 0; i < 100; ++i)
    if (buf_cat(&buf, chunk, sizeof(chunk))) {
      test_failf(__FILE__, __LINE__, "append %zu was refused", i);
      buf_reset(&buf);
      return;
    }
  ++test_checks;
  CHECK_INT(buf.size, 10000);
  for (i = 0; i < 10000; ++i)
    if (buf.data[i] != 'x') {
      test_failf(__FILE__, __LINE__, "byte %zu was not preserved across growth", i);
      buf_reset(&buf);
      return;
    }
  ++test_checks;
  buf_reset(&buf);
}

static void case_grows_one_byte_at_a_time(void) {
  ot_buf buf = {NULL, 0};
  size_t i;

  /* The shape a trickling client produces. Nothing here is optimised for it,
     so the point is only that it stays correct. */
  test_case("a byte at a time accumulates correctly");
  for (i = 0; i < 2048; ++i) {
    char c = (char)('a' + (i % 26));
    if (buf_cat(&buf, &c, 1)) {
      test_failf(__FILE__, __LINE__, "append %zu was refused", i);
      buf_reset(&buf);
      return;
    }
  }
  ++test_checks;
  CHECK_INT(buf.size, 2048);
  for (i = 0; i < 2048; ++i)
    if (buf.data[i] != (char)('a' + (i % 26))) {
      test_failf(__FILE__, __LINE__, "byte %zu differs", i);
      buf_reset(&buf);
      return;
    }
  ++test_checks;
  buf_reset(&buf);
}

static void case_truncate_shrinks_only(void) {
  ot_buf buf = {NULL, 0};

  test_case("truncate drops the tail but never grows");
  buf_cat(&buf, "0123456789", 10);
  buf_truncate(&buf, 4);
  CHECK_INT(buf.size, 4);
  CHECK_MEM(buf.data, "0123", 4);

  buf_truncate(&buf, 9);
  CHECK_INT(buf.size, 4);
  buf_truncate(&buf, 4);
  CHECK_INT(buf.size, 4);

  buf_truncate(&buf, 0);
  CHECK_INT(buf.size, 0);
  buf_reset(&buf);
}

static void case_append_after_truncate(void) {
  ot_buf buf = {NULL, 0};

  test_case("appending after a truncate overwrites the dropped tail");
  buf_cat(&buf, "keepdrop", 8);
  buf_truncate(&buf, 4);
  CHECK_INT(buf_cat(&buf, "MORE", 4), 0);
  CHECK_INT(buf.size, 8);
  CHECK_MEM(buf.data, "keepMORE", 8);
  buf_reset(&buf);
}

static void case_reset_clears_everything(void) {
  ot_buf buf = {NULL, 0};

  test_case("reset releases the buffer and restores the initial state");
  buf_cat(&buf, "something", 9);
  buf_reset(&buf);
  CHECK_PTR(buf.data, NULL);
  CHECK_INT(buf.size, 0);
}

static void case_reset_of_empty_is_safe(void) {
  ot_buf buf = {NULL, 0};

  test_case("resetting a buffer that was never used is harmless");
  buf_reset(&buf);
  buf_reset(&buf);
  CHECK_PTR(buf.data, NULL);
  CHECK_INT(buf.size, 0);
}

static void case_refused_append_is_harmless(void) {
  ot_buf buf = {NULL, 0};

  /* A length that would wrap the size is rejected before src is read and
     before anything is allocated, so a short string is safe to pass here.
     A refused append must not destroy the partial request already collected:
     the caller answers and closes rather than losing both. */
  test_case("a length that would wrap the size is refused");
  CHECK_INT(buf_cat(&buf, "already here", 12), 0);
  CHECK_INT(buf_cat(&buf, "x", (size_t)-1), -1);

  test_case("and earlier content survives it");
  CHECK_INT(buf.size, 12);
  CHECK_MEM(buf.data, "already here", 12);

  test_case("and the buffer still works afterwards");
  CHECK_INT(buf_cat(&buf, "!", 1), 0);
  CHECK_INT(buf.size, 13);
  CHECK_MEM(buf.data, "already here!", 13);
  buf_reset(&buf);
}

static void case_accumulates_a_split_request(void) {
  /* What handle_read actually does: collect a request that arrived in
     pieces, then hand over the whole of it */
  ot_buf      buf      = {NULL, 0};
  const char *pieces[] = {"GET /announce?info_hash=", "0123456789abcdefghij", " HTTP/1.0\r\n\r\n"};
  size_t      i;

  test_case("a request split over several reads is reassembled");
  for (i = 0; i < 3; ++i)
    if (buf_cat(&buf, pieces[i], strlen(pieces[i]))) {
      test_failf(__FILE__, __LINE__, "piece %zu was refused", i);
      buf_reset(&buf);
      return;
    }
  ++test_checks;

  CHECK_INT(buf.size, 24 + 20 + 13);
  CHECK_MEM(buf.data, "GET /announce?info_hash=0123456789abcdefghij HTTP/1.0\r\n\r\n", 57);
  buf_reset(&buf);
}

void test_buf(void) {
  case_starts_empty();
  case_cat_appends();
  case_cat_of_nothing_allocates_nothing();
  case_grows_across_many_appends();
  case_grows_one_byte_at_a_time();
  case_truncate_shrinks_only();
  case_append_after_truncate();
  case_reset_clears_everything();
  case_reset_of_empty_is_safe();
  case_refused_append_is_harmless();
  case_accumulates_a_split_request();
}