9f3f90073dc0624e7bcfc064f56c849dfff6ce94
[ccan] / ccan / charset / test / run.c
1 #include <ccan/charset/charset.h>
2 #include <ccan/charset/charset.c>
3 #include <ccan/tap/tap.h>
4
5 #include <assert.h>
6 #include <math.h>
7 #include <stdint.h>
8 #include <stdio.h>
9
10 /*
11  * Finds a pseudorandom 32-bit number from 0 to 2^32-1 .
12  * Uses the BCPL linear congruential generator method.
13  *
14  * Used instead of system RNG to ensure tests are consistent.
15  */
16 static uint32_t rand32(void)
17 {
18         static uint32_t rand32_state = 0;
19         rand32_state *= (uint32_t)0x7FF8A3ED;
20         rand32_state += (uint32_t)0x2AA01D31;
21         return rand32_state;
22 }
23
24 /*
25  * Make a Unicode character requiring exactly @len UTF-8 bytes.
26  *
27  * Unless utf8_allow_surrogates is set,
28  * do not return a value in the range U+D800 thru U+DFFF .
29  *
30  * If @len is not 1 thru 4, generate an out-of-range character.
31  */
32 static unsigned int utf8_randcode(int len)
33 {
34         uint32_t r = rand32();
35         unsigned int ret;
36         
37         switch (len) {
38                 case 1: return r % 0x80;
39                 case 2: return r % (0x800-0x80) + 0x80;
40                 case 3:
41                         for (;;) {
42                                 ret = r % (0x10000-0x800) + 0x800;
43                                 if (!utf8_allow_surrogates && ret >= 0xD800 && ret <= 0xDFFF)
44                                 {
45                                         r = rand32();
46                                         continue;
47                                 } else {
48                                         break;
49                                 }
50                         }
51                         return ret;
52                 case 4: return r % (0x110000-0x10000) + 0x10000;
53                 default:
54                         while (r < 0x110000)
55                                 r = rand32();
56                         return r;
57         }
58 }
59
60 static unsigned int rand_surrogate(void)
61 {
62         return rand32() % (0xE000 - 0xD800) + 0xD800;
63 }
64
65 /* Encode @uc as UTF-8 using exactly @len characters.
66    @len should be 1 thru 4.
67    @uc will be truncated to the bits it will go into.
68    If, after bit truncation, @uc is in the wrong range for its length,
69    an invalid character will be generated. */
70 static void utf8_encode_raw(char *out, unsigned int uc, int len)
71 {
72         switch (len) {
73                 case 1:
74                         *out++ = uc & 0x7F;
75                         break;
76                 case 2:
77                         *out++ = 0xC0 | ((uc >> 6) & 0x1F);
78                         *out++ = 0x80 | (uc & 0x3F);
79                         break;
80                 case 3:
81                         *out++ = 0xE0 | ((uc >> 12) & 0x0F);
82                         *out++ = 0x80 | ((uc >> 6) & 0x3F);
83                         *out++ = 0x80 | (uc & 0x3F);
84                         break;
85                 case 4:
86                         *out++ = 0xF0 | ((uc >> 18) & 0x07);
87                         *out++ = 0x80 | ((uc >> 12) & 0x3F);
88                         *out++ = 0x80 | ((uc >> 6) & 0x3F);
89                         *out++ = 0x80 | (uc & 0x3F);
90                         break;
91         }
92 }
93
94 /* Generate a UTF-8 string of the given byte length,
95    randomly deciding if it should be valid or not.
96    
97    Return true if it's valid, false if it's not. */
98 static bool utf8_mktest(char *out, int len)
99 {
100         int m, n;
101         bool valid = true;
102         bool v;
103         double pf;
104         uint32_t pu;
105         
106         /* Probability that, per character, it should be valid.
107            The goal is to make utf8_mktest as a whole
108            have a 50% chance of generating a valid string. */
109         pf = pow(0.5, 2.5/len);
110         
111         /* Convert to uint32_t to test against rand32. */
112         pu = pf * 4294967295.0;
113         
114         for (;len; len -= n) {
115                 v = len == 1 || rand32() <= pu;
116                 m = len < 4 ? len : 4;
117                 
118                 if (v) {
119                         /* Generate a valid character. */
120                         n = rand32() % m + 1;
121                         utf8_encode_raw(out, utf8_randcode(n), n);
122                 } else {
123                         /* Generate an invalid character. */
124                         assert(m >= 2);
125                         n = rand32() % (m-1) + 2;
126                         switch (n) {
127                                 case 2:
128                                         utf8_encode_raw(out, utf8_randcode(1), n);
129                                         break;
130                                 case 3:
131                                         if (!utf8_allow_surrogates && (rand32() & 1))
132                                                 utf8_encode_raw(out, rand_surrogate(), n);
133                                         else
134                                                 utf8_encode_raw(out, utf8_randcode(rand32() % (n-1) + 1), n);
135                                         break;
136                                 case 4:
137                                         utf8_encode_raw(out, utf8_randcode(rand32() % (n-1) + 1), n);
138                                         break;
139                         }
140                         valid = false;
141                 }
142                 out += n;
143         }
144         
145         return valid;
146 }
147
148 static void test_utf8_validate(bool allow_surrogates)
149 {
150         char buffer[1024];
151         int i;
152         int len;
153         bool valid;
154         int passed=0, p_valid=0, p_invalid=0, total=0;
155         int count;
156         
157         count = 10000;
158         
159         utf8_allow_surrogates = allow_surrogates;
160         
161         for (i=0; i<count; i++) {
162                 len = rand32() % (1024 + 1);
163                 valid = utf8_mktest(buffer, len);
164                 if (utf8_validate(buffer, len) == valid) {
165                         passed++;
166                         if (valid)
167                                 p_valid++;
168                         else
169                                 p_invalid++;
170                 }
171                 total++;
172         }
173         
174         if (passed == total) {
175                 printf("PASS:  %d valid tests, %d invalid tests\n",
176                         p_valid, p_invalid);
177         } else {
178                 printf("FAIL:  Passed %d out of %d tests\n", passed, total);
179         }
180         
181         ok(passed, "utf8_validate test passed%s",
182                 !allow_surrogates ? " (surrogates disallowed)" : "");
183         
184         ok(p_valid > count/10 && p_invalid > count/10,
185                 "   valid/invalid are balanced");
186 }
187
188 int main(void)
189 {
190         /* This is how many tests you plan to run */
191         plan_tests(4);
192         
193         test_utf8_validate(false);
194         test_utf8_validate(true);
195
196         /* This exits depending on whether all tests passed */
197         return exit_status();
198 }