]> git.scottworley.com Git - tl-append/blob - tl-append-test.c
20ae7fb6a7d807563f001e68ab7c56099a45f086
[tl-append] / tl-append-test.c
1 #define _POSIX_C_SOURCE 2
2 #define _XOPEN_SOURCE
3 #define _GNU_SOURCE
4 #include <assert.h>
5 #include <ctype.h>
6 #include <errno.h>
7 #include <fcntl.h>
8 #include <limits.h>
9 #include <pthread.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <time.h>
14 #include <unistd.h>
15
16 #include "common.h"
17
18 const size_t TIMESTAMP_LEN = 19;
19
20 typedef struct expectation {
21 time_t a, b;
22 const char *message;
23 } ex_t;
24
25 const ex_t END = {((time_t)-1), ((time_t)-1), NULL};
26 const ex_t CONSUMED = {((time_t)-2), ((time_t)-2), NULL};
27 static int is_end(ex_t exp) {
28 return exp.a == END.a && exp.b == END.b && exp.message == END.message;
29 }
30 static int is_consumed(ex_t exp) {
31 return exp.a == CONSUMED.a && exp.b == CONSUMED.b &&
32 exp.message == CONSUMED.message;
33 }
34 static ex_t expectation(time_t a, time_t b, const char *message) {
35 ex_t exp;
36 exp.a = a;
37 exp.b = b;
38 exp.message = message;
39 return exp;
40 }
41
42 static void remove_logfile() {
43 if (remove(FILENAME) != 0) {
44 if (errno != ENOENT) {
45 die_err("Error removing log file");
46 }
47 }
48 }
49
50 static ex_t write_to_tl_append(const char *content) {
51 FILE *p = popen("./tl-append", "w");
52 if (p == NULL)
53 die_err("Couldn't run tl-append");
54 time_t start = time(NULL);
55 if (fputs(content, p) == EOF)
56 die("Couldn't write to pipe");
57 int status = pclose(p);
58 time_t end = time(NULL);
59 if (status < 0)
60 die_err("Error closing pipe");
61 if (status != 0)
62 die("tl-append exited abnormally");
63 return expectation(start, end, content);
64 }
65
66 static char *timestamp_problem(const ex_t *ex, const char *line) {
67 struct tm tm;
68
69 /* localtime_r to set tm's timezone */
70 time_t now_time = time(NULL);
71 if (localtime_r(&now_time, &tm) == NULL)
72 return "Can't unpack current time?";
73
74 const char *strptime_result = strptime(line, "%Y %m %d %H %M %S", &tm);
75 if (strptime_result == NULL || strptime_result != &line[TIMESTAMP_LEN])
76 return "Wrong contents in log file: Couldn't parse timestamp";
77 time_t t = mktime(&tm);
78 int t_in_range = ex->a <= t && t <= ex->b;
79 if (!t_in_range)
80 return "Wrong contents in log file: Wrong time";
81 return NULL;
82 }
83
84 static char *line_problem(const ex_t *ex, const char *line) {
85 size_t line_len = strlen(line);
86 if (line_len < TIMESTAMP_LEN + 1)
87 return "Wrong contents in log file: Line too short";
88 char *trouble = timestamp_problem(ex, line);
89 if (trouble)
90 return trouble;
91 if (line[TIMESTAMP_LEN] != ' ')
92 return "Wrong contents in log file: Bad format";
93 if (strncmp(ex->message, &line[TIMESTAMP_LEN + 1], strlen(ex->message) + 1) !=
94 0)
95 return "Wrong contents in log file";
96 return NULL;
97 }
98
99 static void verify_line(const ex_t *ex, const char *line) {
100 char *trouble = line_problem(ex, line);
101 if (trouble)
102 die(trouble);
103 }
104
105 static void verify_log_contents(const ex_t exps[]) {
106 FILE *f = fopen(FILENAME, "r");
107 if (f == NULL)
108 die_err("Error opening log file");
109 for (size_t i = 0; !is_end(exps[i]); i++) {
110 size_t len = TIMESTAMP_LEN + 1 + strlen(exps[i].message);
111 if (len > INT_MAX - 1)
112 die("message too long");
113 char *buf = (char *)malloc(len + 2);
114 if (fgets(buf, len + 1, f) == NULL)
115 die("Error reading log file");
116 if (ferror(f))
117 die("Error reading log file");
118 verify_line(&exps[i], buf);
119 free(buf);
120 }
121 if (fclose(f) != 0)
122 die_err("Error closing log file");
123 }
124
125 static void consume_expectation(ex_t exps[], const char *line) {
126 for (size_t i = 0; !is_end(exps[i]); i++) {
127 if (line_problem(&exps[i], line) == NULL) {
128 exps[i] = CONSUMED;
129 return;
130 }
131 }
132 die("Wrong contents in log file: Line didn't match any expectation");
133 }
134
135 static void verify_log_contents_unordered(ex_t exps[]) {
136 FILE *f = fopen(FILENAME, "r");
137 if (f == NULL)
138 die_err("Error opening log file");
139 for (;;) {
140 const int MAX_LEN = 9999;
141 char buf[MAX_LEN];
142 if (fgets(buf, MAX_LEN, f) == NULL) {
143 if (feof(f))
144 break;
145 die("Error reading log file");
146 }
147 if (ferror(f))
148 die("Error reading log file");
149 consume_expectation(exps, buf);
150 }
151 if (fclose(f) != 0)
152 die_err("Error closing log file");
153 for (size_t i = 0; !is_end(exps[i]); i++) {
154 if (!is_consumed(exps[i]))
155 die("Wrong contents in log file: Unconsumed expectation");
156 }
157 }
158
159 static void test_encode_time() {
160 /* localtime_r to set tm's timezone */
161 time_t now_time = time(NULL);
162 struct tm tm;
163 if (localtime_r(&now_time, &tm) == NULL)
164 die_err("Can't unpack current time?");
165
166 const char *strptime_result = strptime("2011-12-13 14:15:16", "%F %T", &tm);
167 if (strptime_result == NULL || *strptime_result != '\0')
168 die("Couldn't parse time?");
169 time_t tt = mktime(&tm);
170 if (tt == (time_t)-1)
171 die_err("Can't pack time?");
172
173 const char *encoded = encode_time(tt);
174 /* Loose check to allow for daylight savings time changes between the current
175 * time and the target time. :( */
176 assert(encoded[0] == '2');
177 assert(encoded[1] == '0');
178 assert(encoded[2] == '1');
179 assert(encoded[3] == '1');
180 assert(encoded[4] == ' ');
181 assert(encoded[5] == '1');
182 assert(encoded[6] == '2');
183 assert(encoded[7] == ' ');
184 assert(encoded[8] == '1');
185 assert(encoded[9] == '3');
186 assert(encoded[10] == ' ');
187 assert(encoded[11] == '1');
188 assert(isdigit(encoded[12]));
189 assert(encoded[13] == ' ');
190 assert(isdigit(encoded[14]));
191 assert(isdigit(encoded[15]));
192 assert(encoded[16] == ' ');
193 assert(encoded[17] == '1');
194 assert(encoded[18] == '6');
195 assert(encoded[19] == '\0');
196 }
197
198 static FILE *take_lock() {
199 FILE *f = fopen(FILENAME, "a");
200 if (f == NULL)
201 die_err("Couldn't open file for locking");
202 int fd = fileno(f);
203 if (fd == -1)
204 die_err("Couldn't get file descriptor for locking");
205 if (fcntl(fd, F_SETLK,
206 &(struct flock){.l_type = F_WRLCK,
207 .l_whence = SEEK_SET,
208 .l_start = 0,
209 .l_len = 0}) == -1)
210 die_err("Couldn't take lock");
211 return f;
212 }
213
214 static void release_lock(FILE *f) {
215 if (fclose(f) != 0)
216 die_err("Error releasing lock");
217 }
218
219 static void *release_lock_after_delay(void *f) {
220 sleep(2);
221 release_lock((FILE *)f);
222 return NULL;
223 }
224
225 static void *writer_thread(void *start_signal) {
226 ex_t *ex = (ex_t *)malloc(sizeof(ex_t));
227 if (ex == NULL)
228 die_err("Couldn't allocate memory");
229 char *message;
230 if (asprintf(&message, "Hello from thread %lu\n", pthread_self()) == -1)
231 die("Couldn't prepare message");
232 pthread_rwlock_rdlock((pthread_rwlock_t *)start_signal);
233 *ex = write_to_tl_append(message);
234 return ex;
235 }
236
237 static void write_and_read_line() {
238 remove_logfile();
239 ex_t e = write_to_tl_append("foo\n");
240 verify_log_contents((ex_t[]){e, END});
241 }
242
243 static void write_and_read_two_lines() {
244 remove_logfile();
245 ex_t e1 = write_to_tl_append("foo\n");
246 ex_t e2 = write_to_tl_append("bar\n");
247 verify_log_contents((ex_t[]){e1, e2, END});
248 }
249
250 static void write_to_locked_log() {
251 remove_logfile();
252 ex_t e1 = write_to_tl_append("begin\n");
253 FILE *lock = take_lock();
254 pthread_t unlock_thread;
255 int create_ret =
256 pthread_create(&unlock_thread, NULL, &release_lock_after_delay, lock);
257 if (create_ret != 0) {
258 errno = create_ret;
259 die_err("Couldn't start thread");
260 }
261 ex_t e2 = write_to_tl_append("delayed\n");
262 int join_ret = pthread_join(unlock_thread, NULL);
263 if (join_ret != 0) {
264 errno = join_ret;
265 die_err("Couldn't join thread");
266 }
267 verify_log_contents((ex_t[]){e1, e2, END});
268 }
269
270 static void write_concurrently() {
271 remove_logfile();
272 const int PARALLELISM = 250;
273 pthread_t threads[PARALLELISM];
274 pthread_rwlock_t start_signal;
275 pthread_rwlock_init(&start_signal, NULL);
276 for (int i = 0; i < PARALLELISM; i++) {
277 int create_ret =
278 pthread_create(&threads[i], NULL, &writer_thread, &start_signal);
279 if (create_ret != 0) {
280 errno = create_ret;
281 die_err("Couldn't start thread");
282 }
283 }
284 pthread_rwlock_unlock(&start_signal);
285 ex_t results[PARALLELISM + 1];
286 for (int i = 0; i < PARALLELISM; i++) {
287 ex_t *ex;
288 int join_ret = pthread_join(threads[i], (void **)&ex);
289 if (join_ret != 0) {
290 errno = join_ret;
291 die_err("Couldn't join thread");
292 }
293 results[i] = *ex;
294 free(ex);
295 }
296 results[PARALLELISM] = END;
297 verify_log_contents_unordered(results);
298 }
299
300 int main() {
301 test_encode_time();
302 write_and_read_line();
303 write_and_read_two_lines();
304 write_to_locked_log();
305 write_concurrently();
306 }