From http://www.jwz.org/xscreensaver/xscreensaver-5.16.tar.gz
[xscreensaver] / driver / test-mlstring.c
1 /* 
2  * (c) 2007, Quest Software, Inc. All rights reserved.
3  *
4  * This file is part of XScreenSaver,
5  * Copyright (c) 1993-2004 Jamie Zawinski <jwz@jwz.org>
6  *
7  * Permission to use, copy, modify, distribute, and sell this software and its
8  * documentation for any purpose is hereby granted without fee, provided that
9  * the above copyright notice appear in all copies and that both that
10  * copyright notice and this permission notice appear in supporting
11  * documentation.  No representations are made about the suitability of this
12  * software for any purpose.  It is provided "as is" without express or 
13  * implied warranty.
14  */
15
16 #include <string.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19
20 #include "mlstring.c"  /* hokey, but whatever */
21
22 #define WRAP_WIDTH_PX 100
23
24 #undef Bool
25 #undef True
26 #undef False
27 typedef int Bool;
28 #define True 1
29 #define False 0
30
31 #define SKIPPED -1
32 #define SUCCESS 0
33 #define FAILURE 1
34
35 #define FAIL(msg, ...)                 \
36   do {                                 \
37     ++failcount;                       \
38     fprintf(stderr, "[FAIL] ");        \
39     fprintf(stderr, msg, __VA_ARGS__); \
40     putc('\n', stderr);                \
41     return FAILURE;                    \
42   } while (0)
43
44 #define SUCCEED(testname)                          \
45   do {                                             \
46     fprintf(stderr, "[SUCCESS] %s\n", (testname)); \
47   } while (0)
48
49 #define SKIP(testname)                             \
50   do {                                             \
51     fprintf(stderr, "[SKIPPED] %s\n", (testname)); \
52   } while (0)
53
54 extern mlstring* mlstring_allocate(const char *msg);
55 extern void mlstring_wrap(mlstring *mstr, XFontStruct *font, Dimension width);
56
57 static int failcount = 0;
58
59 static char *mlstring_to_cstr(const mlstring *mlstr) {
60   char *cstr;
61   size_t cstrlen = 0, alloclen = 1024;
62   const struct mlstr_line *line;
63
64   cstr = malloc(alloclen);
65   if (!cstr)
66     return NULL;
67   cstr[0] = '\0';
68
69   for (line = mlstr->lines; line; line = line->next_line) {
70     /* Extend the buffer if necessary. */
71     if (cstrlen + strlen(line->line) + 1 > alloclen) {
72       cstr = realloc(cstr, alloclen *= 2);
73       if (!cstr)
74         return NULL;
75     }
76
77     /* If this is not the first line */
78     if (line != mlstr->lines) {
79       /* Append a newline character */
80       cstr[cstrlen] = '\n';
81       ++cstrlen;
82       cstr[cstrlen] = '\0';
83     }
84
85     strcat(cstr, line->line);
86     cstrlen += strlen(line->line);
87   }
88   return cstr;
89 }
90
91 /* Pass -1 for expect_min or expect_exact to not check that value.
92  * expect_empty_p means an empty line is expected at some point in the string.
93  * Also ensures that the string was not too wide after wrapping. */
94 static int mlstring_expect_lines(const mlstring *mlstr, int expect_min, int expect_exact, Bool expect_empty_p)
95 {
96   int count;
97   Bool got_empty_line = False;
98   const struct mlstr_line *line = mlstr->lines;
99
100   for (count = 0; line; line = line->next_line) {
101     if (line->line[0] == '\0') {
102       if (!expect_empty_p)
103         FAIL("Not expecting empty lines, but got one on line %d of [%s]", count + 1, mlstring_to_cstr(mlstr));
104       got_empty_line = True;
105     }
106     ++count;
107   }
108
109   if (expect_empty_p && !got_empty_line)
110     FAIL("Expecting an empty line, but none found in [%s]", mlstring_to_cstr(mlstr));
111
112   if (expect_exact != -1 && expect_exact != count)
113     FAIL("Expected %d lines, got %d", expect_exact, count);
114   
115   if (expect_min != -1 && count < expect_min)
116     FAIL("Expected at least %d lines, got %d", expect_min, count);
117
118   return SUCCESS;
119 }
120
121 static int mlstring_expect(const char *msg, int expect_lines, const mlstring *mlstr, Bool expect_empty_p)
122 {
123   char *str, *str_top;
124   const struct mlstr_line *cur;
125   int linecount = 0;
126
127   /* Duplicate msg so we can chop it up */
128   str_top = strdup(msg);
129   if (!str_top)
130     return SKIPPED;
131
132   /* Replace all newlines with NUL */
133   str = str_top;
134   while ((str = strchr(str, '\n')))
135     *str++ = '\0';
136
137   /* str is now used to point to the expected string */
138   str = str_top;
139
140   for (cur = mlstr->lines; cur; cur = cur->next_line)
141     {
142       ++linecount;
143       if (strcmp(cur->line, str))
144         FAIL("lines didn't match; expected [%s], got [%s]", str, cur->line);
145
146       str += strlen(str) + 1; /* Point to the next expected string */
147     }
148
149   free(str_top);
150
151   return mlstring_expect_lines(mlstr, -1, expect_lines, expect_empty_p);
152 }
153
154 /* Ensures that the width has been set properly after wrapping */
155 static int check_width(const char *msg, const mlstring *mlstr) {
156   if (mlstr->overall_width == 0)
157     FAIL("Overall width was zero for string [%s]", msg);
158
159   if (mlstr->overall_width > WRAP_WIDTH_PX)
160     FAIL("Overall width was %hu but the maximum wrap width was %d", mlstr->overall_width, WRAP_WIDTH_PX);
161
162   return SUCCESS;
163 }
164
165 /* FAIL() actually returns the wrong return codes in main, but it
166  * prints a message which is what we want. */
167
168 #define TRY_NEW(str, numl, expect_empty)                      \
169   do {                                                        \
170     mlstr = mlstring_allocate((str));                         \
171     if (!mlstr)                                               \
172       FAIL("%s", #str);                                       \
173     if (SUCCESS == mlstring_expect((str), (numl), mlstr, (expect_empty))) \
174       SUCCEED(#str);                                          \
175     free(mlstr);                                              \
176   } while (0)
177
178 /* Expects an XFontStruct* font, and tries to wrap to 100px */
179 #define TRY_WRAP(str, minl, expect_empty)                        \
180   do {                                                           \
181     mltest = mlstring_allocate((str));                           \
182     if (!mltest)                                                 \
183       SKIP(#str);                                                \
184     else {                                                       \
185       mlstring_wrap(mltest, font, WRAP_WIDTH_PX);                \
186       check_width((str), mltest);                                \
187       if (SUCCESS == mlstring_expect_lines(mltest, (minl), -1, (expect_empty)))  \
188         SUCCEED(#str);                                           \
189       free(mltest);                                              \
190       mltest = NULL;                                             \
191     }                                                            \
192   } while (0)
193
194
195 /* Ideally this function would use stub functions rather than real Xlib.
196  * Then it would be possible to test for exact line counts, which would be
197  * more reliable.
198  * It also doesn't handle Xlib errors.
199  *
200  * Don't print anything based on the return value of this function, it only
201  * returns a value so that I can use the FAIL() macro without warning.
202  *
203  * Anyone who understands this function wins a cookie ;)
204  */
205 static int test_wrapping(void)
206 {
207   Display *dpy = NULL;
208   XFontStruct *font = NULL;
209   mlstring *mltest = NULL;
210   int ok = 0;
211   int chars_per_line, chars_first_word, i;
212   
213   const char *test_short = "a";
214   const char *test_hardwrap = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
215     "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
216     "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
217   const char *test_withnewlines = "a\nb";
218   char *test_softwrap = NULL;
219
220   dpy = XOpenDisplay(NULL);
221   if (!dpy)
222     goto end;
223   
224   font = XLoadQueryFont(dpy, "fixed");
225   if (!font)
226     goto end;
227
228   TRY_WRAP(test_short, 1, False);
229   TRY_WRAP(test_hardwrap, 2, False);
230   TRY_WRAP(test_withnewlines, 2, False);
231
232   /* See if wrapping splits on word boundaries like it should */
233   chars_per_line = WRAP_WIDTH_PX / font->max_bounds.width;
234   if (chars_per_line < 3)
235     goto end;
236
237   /* Allocate for 2 lines + \0 */
238   test_softwrap = malloc(chars_per_line * 2 + 1);
239   if (!test_softwrap)
240     goto end;
241
242   /* 2 = strlen(' a'); that is, the minimum space required to start a new word
243    * on the same line. */
244   chars_first_word = chars_per_line - 2;
245
246   for (i = 0; i < chars_first_word; ++i) {
247     test_softwrap[i] = 'a'; /* first word */
248     test_softwrap[i + chars_per_line] = 'b'; /* second word */
249   }
250   /* space between first & second words */
251   test_softwrap[chars_first_word] = ' ';
252   /* first char of second word (last char of first line) */
253   test_softwrap[chars_first_word + 1] = 'b';
254   /* after second word */
255   test_softwrap[chars_per_line * 2] = '\0';
256
257   mltest = mlstring_allocate(test_softwrap);
258   mlstring_wrap(mltest, font, WRAP_WIDTH_PX);
259
260   /* reusing 'i' for a moment here to make freeing mltest easier */
261   i = strlen(mltest->lines->line);
262   free(mltest);
263
264   if (i != chars_first_word)
265     FAIL("Soft wrap failed, expected the first line to be %d chars, but it was %d.", chars_first_word, i);
266   SUCCEED("Soft wrap");
267
268   ok = 1;
269
270 end:
271   if (test_softwrap)
272     free(test_softwrap);
273
274   if (font)
275     XFreeFont(dpy, font);
276
277   if (dpy)
278     XCloseDisplay(dpy);
279
280   if (!ok)
281     SKIP("wrapping");
282
283   return ok ? SUCCESS : SKIPPED; /* Unused, actually */
284 }
285
286
287 int main(int argc, char *argv[])
288 {
289   const char *oneline = "1Foo";
290   const char *twolines = "2Foo\nBar";
291   const char *threelines = "3Foo\nBar\nWhippet";
292   const char *trailnewline = "4Foo\n";
293   const char *trailnewlines = "5Foo\n\n";
294   const char *embeddednewlines = "6Foo\n\nBar";
295   mlstring *mlstr;
296
297   TRY_NEW(oneline, 1, False);
298   TRY_NEW(twolines, 2, False);
299   TRY_NEW(threelines, 3, False);
300   TRY_NEW(trailnewline, 2, True);
301   TRY_NEW(trailnewlines, 3, True);
302   TRY_NEW(embeddednewlines, 3, True);
303
304   (void) test_wrapping();
305
306   fprintf(stdout, "%d test failures.\n", failcount);
307
308   return !!failcount;
309 }
310
311 /* vim:ts=8:sw=2:noet
312  */