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