doxygen fixes
[oweals/gnunet.git] / src / regex / test_regex_eval_api.c
1 /*
2      This file is part of GNUnet
3      (C) 2012 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20 /**
21  * @file regex/test_regex_eval_api.c
22  * @brief test for regex.c
23  * @author Maximilian Szengel
24  */
25 #include <regex.h>
26 #include <time.h>
27 #include "platform.h"
28 #include "gnunet_regex_lib.h"
29
30 enum Match_Result
31 {
32   match = 0,
33   nomatch = 1
34 };
35
36 struct Regex_String_Pair
37 {
38   char *regex;
39   int string_count;
40   char *strings[20];
41   enum Match_Result expected_results[20];
42 };
43
44 static const char allowed_literals[] =
45     "0123456789" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz";
46
47 /**
48  * Random regex test. Generate a random regex as well as 'str_count' strings to
49  * match it against. Will match using GNUNET_REGEX implementation and compare
50  * the result to glibc regex result. 'rx_length' has to be smaller then 'max_str_len'.
51  *
52  * @param rx_length length of the regular expression.
53  * @param max_str_len maximum length of the random strings.
54  * @param str_count number of generated random strings.
55  *
56  * @return 0 on success, non 0 otherwise.
57  */
58 int
59 test_random (unsigned int rx_length, unsigned int max_str_len,
60              unsigned int str_count)
61 {
62   int i;
63   int j;
64   int rx_exp;
65   char rand_rx[rx_length + 1];
66   char matching_str[str_count][max_str_len + 1];
67   char *rand_rxp;
68   char *matching_strp;
69   int char_op_switch;
70   int last_was_op;
71   char current_char;
72   int eval;
73   int eval_check;
74   int eval_canonical;
75   struct GNUNET_REGEX_Automaton *dfa;
76   regex_t rx;
77   regmatch_t matchptr[1];
78   char error[200];
79   int result;
80   unsigned int str_len;
81   char *canonical_regex;
82
83   // At least one string is needed for matching
84   GNUNET_assert (str_count > 0);
85   // The string should be at least as long as the regex itself
86   GNUNET_assert (max_str_len >= rx_length);
87
88   rand_rxp = rand_rx;
89   matching_strp = matching_str[0];
90   current_char = 0;
91   last_was_op = 1;
92
93   // Generate random regex and a string that matches the regex
94   for (i = 0; i < rx_length; i++)
95   {
96     char_op_switch = 0 + (int) (1.0 * rand () / (RAND_MAX + 1.0));
97
98     if (0 == char_op_switch && !last_was_op)
99     {
100       last_was_op = 1;
101       rx_exp = rand () % 4;
102
103       switch (rx_exp)
104       {
105       case 0:
106         current_char = '+';
107         break;
108       case 1:
109         current_char = '*';
110         break;
111       case 2:
112         current_char = '?';
113         break;
114       case 3:
115         if (i < rx_length - 1)  // '|' cannot be at the end
116           current_char = '|';
117         else
118           current_char =
119               allowed_literals[rand () % (sizeof (allowed_literals) - 1)];
120         break;
121       }
122     }
123     else
124     {
125       current_char =
126           allowed_literals[rand () % (sizeof (allowed_literals) - 1)];
127       last_was_op = 0;
128     }
129
130     if (current_char != '+' && current_char != '*' && current_char != '?' &&
131         current_char != '|')
132     {
133       *matching_strp = current_char;
134       matching_strp++;
135     }
136
137     *rand_rxp = current_char;
138     rand_rxp++;
139   }
140   *rand_rxp = '\0';
141   *matching_strp = '\0';
142
143   // Generate some random strings for matching...
144   // Start at 1, because the first string is generated above during regex generation
145   for (i = 1; i < str_count; i++)
146   {
147     str_len = rand () % max_str_len;
148     for (j = 0; j < str_len; j++)
149       matching_str[i][j] =
150           allowed_literals[rand () % (sizeof (allowed_literals) - 1)];
151     matching_str[i][str_len] = '\0';
152   }
153
154   // Now match
155   result = 0;
156   for (i = 0; i < str_count; i++)
157   {
158     // Match string using DFA
159     dfa = GNUNET_REGEX_construct_dfa (rand_rx, strlen (rand_rx));
160     if (NULL == dfa)
161     {
162       GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Constructing DFA failed\n");
163       return -1;
164     }
165
166     eval = GNUNET_REGEX_eval (dfa, matching_str[i]);
167     canonical_regex = GNUNET_strdup (GNUNET_REGEX_get_canonical_regex (dfa));
168     GNUNET_REGEX_automaton_destroy (dfa);
169
170     // Match string using glibc regex
171     if (0 != regcomp (&rx, rand_rx, REG_EXTENDED))
172     {
173       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
174                   "Could not compile regex using regcomp\n");
175       return -1;
176     }
177
178     eval_check = regexec (&rx, matching_str[i], 1, matchptr, 0);
179     regfree (&rx);
180
181     // Match canonical regex
182     if (0 != regcomp (&rx, canonical_regex, REG_EXTENDED))
183     {
184       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
185                   "Could not compile regex using regcomp: %s\n",
186                   canonical_regex);
187       return -1;
188     }
189
190     eval_canonical = regexec (&rx, matching_str[i], 1, matchptr, 0);
191     regfree (&rx);
192     GNUNET_free (canonical_regex);
193
194     // We only want to match the whole string, because that's what our DFA does, too.
195     if (eval_check == 0 &&
196         (matchptr[0].rm_so != 0 ||
197          matchptr[0].rm_eo != strlen (matching_str[i])))
198       eval_check = 1;
199
200     // compare result
201     if (eval_check != eval)
202     {
203       regerror (eval_check, &rx, error, sizeof error);
204       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
205                   "Unexpected result:\nregex: %s\nstring: %s\ngnunet regex: %i\nglibc regex: %i\nglibc error: %s\n\n",
206                   rand_rx, matching_str, eval, eval_check, error);
207       result += 1;
208     }
209   }
210   return result;
211 }
212
213 /**
214  * Automaton test that compares the result of matching regular expression 'rx'
215  * with the strings and expected results in 'rxstr' with the result of matching
216  * the same strings with glibc regex.
217  *
218  * @param a automaton.
219  * @param rx compiled glibc regex.
220  * @param rxstr regular expression and strings with expected results to match against.
221  *
222  * @return 0 on successfull, non 0 otherwise
223  */
224 int
225 test_automaton (struct GNUNET_REGEX_Automaton *a, regex_t * rx,
226                 struct Regex_String_Pair *rxstr)
227 {
228   int result;
229   int eval;
230   int eval_check;
231   char error[200];
232   regmatch_t matchptr[1];
233   int i;
234
235   if (NULL == a)
236   {
237     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Automaton was NULL\n");
238     return 1;
239   }
240
241   result = 0;
242
243   for (i = 0; i < rxstr->string_count; i++)
244   {
245     eval = GNUNET_REGEX_eval (a, rxstr->strings[i]);
246     eval_check = regexec (rx, rxstr->strings[i], 1, matchptr, 0);
247
248     // We only want to match the whole string, because that's what our DFA does, too.
249     if (eval_check == 0 &&
250         (matchptr[0].rm_so != 0 ||
251          matchptr[0].rm_eo != strlen (rxstr->strings[i])))
252       eval_check = 1;
253
254     if ((rxstr->expected_results[i] == match && (0 != eval || 0 != eval_check))
255         || (rxstr->expected_results[i] == nomatch &&
256             (0 == eval || 0 == eval_check)))
257     {
258       result = 1;
259       regerror (eval_check, rx, error, sizeof error);
260       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
261                   "Unexpected result:\nregex: %s\nstring: %s\nexpected result: %i\n"
262                   "gnunet regex: %i\nglibc regex: %i\nglibc error: %s\nrm_so: %i\nrm_eo: %i\n\n",
263                   rxstr->regex, rxstr->strings[i], rxstr->expected_results[i],
264                   eval, eval_check, error, matchptr[0].rm_so,
265                   matchptr[0].rm_eo);
266     }
267   }
268   return result;
269 }
270
271 int
272 main (int argc, char *argv[])
273 {
274   GNUNET_log_setup ("test-regex",
275 #if VERBOSE
276                     "DEBUG",
277 #else
278                     "WARNING",
279 #endif
280                     NULL);
281
282   struct GNUNET_REGEX_Automaton *a;
283   regex_t rx;
284   int i;
285   int check_nfa;
286   int check_dfa;
287   int check_rand;
288   char *check_proof;
289
290   struct Regex_String_Pair rxstr[14] = {
291     {"ab?(abcd)?", 5,
292      {"ababcd", "abab", "aabcd", "a", "abb"},
293      {match, nomatch, match, match, nomatch}},
294     {"ab(c|d)+c*(a(b|c)d)+", 5,
295      {"abcdcdcdcdddddabd", "abcd", "abcddddddccccccccccccccccccccccccabdacdabd",
296       "abccccca", "abcdcdcdccdabdabd"},
297      {match, nomatch, match, nomatch, match}},
298     {"ab+c*(a(bx|c)d)+", 5,
299      {"abcdcdcdcdddddabd", "abcd", "abcddddddccccccccccccccccccccccccabdacdabd",
300       "abccccca", "abcdcdcdccdabdabd"},
301      {nomatch, nomatch, nomatch, nomatch, nomatch}},
302     {"a+X*y+c|p|R|Z*K*y*R+w|Y*6+n+h*k*w+V*F|W*B*e*", 1,
303      {"kaXycQepRZKyRwY6nhkwVFWBegNVtLPj39XhJJ6bEifRSZRYZg"},
304      {nomatch}},
305     {"k|a+X*y+c|Q*e|p|R|Z*K*y*R+w|Y*6+n+h*k*w+V*F|W*B*e*g|N+V|t+L|P*j*3*9+X*h*J|J*6|b|E*i*f*R+S|Z|R|Y*Z|g*", 1,
306      {"kaXycQepRZKyRwY6nhkwVFWBegNVtLPj39XhJJ6bEifRSZRYZg"},
307      {nomatch}},
308     {"F?W+m+2*6*c*s|P?U?a|B|y*i+t+A|V|6*C*7*e?Z*n*i|J?5+g?W*V?7*j?p?1|r?B?C+E+3+6*i+W*P?K?0|D+7?y*m+3?g?K?", 1,
309      {"osfjsodfonONONOnosndfsdnfsd"},
310      {nomatch}},
311     {"V|M*o?x*p*d+h+b|E*m?h?Y*E*O?W*W*P+o?Z+H*M|I*q+C*a+5?5*9|b?z|G*y*k?R|p+u|8*h?B+l*H|e|L*O|1|F?v*0?5|C+", 1,
312      {"VMoxpdhbEmhYEOWWPoZHMIqCa559bzGykRpu8hBlHeLO1Fv05C"},
313      {nomatch}},
314     {"(bla)*", 8,
315      {"", "bla", "blabla", "bl", "la", "b", "l", "a"},
316      {match, match, match, nomatch, nomatch, nomatch, nomatch, nomatch}},
317     {"ab(c|d)+c*(a(b|c)+d)+(bla)(bla)*", 8,
318      {"ab", "abcabdbla", "abdcccccccccccabcbccdblablabla", "bl", "la", "b", "l",
319       "a"},
320      {nomatch, match, match, nomatch, nomatch, nomatch, nomatch, nomatch}},
321     {"a|aa*a", 6,
322      {"", "a", "aa", "aaa", "aaaa", "aaaaa"},
323      {nomatch, match, match, match, match, match}},
324     {"ab(c|d)+c*(a(b|c)+d)+(bla)+", 1,
325      {"abcabdblaacdbla"},
326      {nomatch}},
327     {"(ac|b)+", 8,
328      {"b", "bb", "ac", "", "acb", "bacbacac", "acacac", "abc"},
329      {match, match, match, nomatch, match, match, match, nomatch}},
330     {"(ab|c)+", 7,
331      {"", "ab", "c", "abc", "ababcc", "acc", "abac"},
332      {nomatch, match, match, match, match, nomatch, nomatch}},
333     {"ab(c|d)+c*(a(b|c)d)+", 1,
334      {"abacd"},
335      {nomatch}}
336   };
337
338   check_nfa = 0;
339   check_dfa = 0;
340   check_rand = 0;
341
342   for (i = 0; i < 14; i++)
343   {
344     if (0 != regcomp (&rx, rxstr[i].regex, REG_EXTENDED))
345     {
346       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
347                   "Could not compile regex using regcomp()\n");
348       return 1;
349     }
350
351     // NFA test
352     a = GNUNET_REGEX_construct_nfa (rxstr[i].regex, strlen (rxstr[i].regex));
353     check_nfa += test_automaton (a, &rx, &rxstr[i]);
354     GNUNET_REGEX_automaton_destroy (a);
355
356     // DFA test
357     a = GNUNET_REGEX_construct_dfa (rxstr[i].regex, strlen (rxstr[i].regex));
358     check_dfa += test_automaton (a, &rx, &rxstr[i]);
359     check_proof = GNUNET_strdup (GNUNET_REGEX_get_canonical_regex (a));
360     GNUNET_REGEX_automaton_destroy (a);
361     a = GNUNET_REGEX_construct_dfa (check_proof, strlen (check_proof));
362     check_dfa += test_automaton (a, &rx, &rxstr[i]);
363     GNUNET_REGEX_automaton_destroy (a);
364     if (0 != check_dfa)
365       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "check_proof: %s\n", check_proof);
366     GNUNET_free_non_null (check_proof);
367
368     regfree (&rx);
369   }
370
371   srand (time (NULL));
372   for (i = 0; i < 50; i++)
373     check_rand += test_random (100, 120, 20);
374
375   return check_nfa + check_dfa + check_rand;
376 }