test-conf.c revision a4e54154
1/*
2 * fontconfig/test/test-conf.c
3 *
4 * Copyright © 2000 Keith Packard
5 * Copyright © 2018 Akira TAGOH
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, and that the name of the author(s) not be used in
12 * advertising or publicity pertaining to distribution of the software without
13 * specific, written prior permission.  The authors make no
14 * representations about the suitability of this software for any purpose.  It
15 * is provided "as is" without express or implied warranty.
16 *
17 * THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
18 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
19 * EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
20 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
21 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
22 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
23 * PERFORMANCE OF THIS SOFTWARE.
24 */
25#include <stdio.h>
26#include <string.h>
27#include <fontconfig/fontconfig.h>
28#include <json.h>
29
30struct _FcConfig {
31    FcStrSet	*configDirs;	    /* directories to scan for fonts */
32    FcStrSet    *configMapDirs;
33    FcStrSet	*fontDirs;
34    FcStrSet	*cacheDirs;
35    FcStrSet	*configFiles;	    /* config files loaded */
36    void	*subst[FcMatchKindEnd];
37    int		maxObjects;	    /* maximum number of tests in all substs */
38    FcStrSet	*acceptGlobs;
39    FcStrSet	*rejectGlobs;
40    FcFontSet	*acceptPatterns;
41    FcFontSet	*rejectPatterns;
42    FcFontSet	*fonts[FcSetApplication + 1];
43};
44
45static FcPattern *
46build_pattern (json_object *obj)
47{
48    json_object_iter iter;
49    FcPattern *pat = FcPatternCreate ();
50
51    json_object_object_foreachC (obj, iter)
52    {
53	FcValue v;
54	FcBool destroy_v = FcFalse;
55	FcMatrix matrix;
56
57	if (json_object_get_type (iter.val) == json_type_boolean)
58	{
59	    v.type = FcTypeBool;
60	    v.u.b = json_object_get_boolean (iter.val);
61	}
62	else if (json_object_get_type (iter.val) == json_type_double)
63	{
64	    v.type = FcTypeDouble;
65	    v.u.d = json_object_get_double (iter.val);
66	}
67	else if (json_object_get_type (iter.val) == json_type_int)
68	{
69	    v.type = FcTypeInteger;
70	    v.u.i = json_object_get_int (iter.val);
71	}
72	else if (json_object_get_type (iter.val) == json_type_string)
73	{
74	    const FcObjectType *o = FcNameGetObjectType (iter.key);
75	    if (o && (o->type == FcTypeRange || o->type == FcTypeDouble || o->type == FcTypeInteger))
76	    {
77		const FcConstant *c = FcNameGetConstant ((const FcChar8 *) json_object_get_string (iter.val));
78		if (!c) {
79		    fprintf (stderr, "E: value is not a known constant\n");
80		    fprintf (stderr, "   key: %s\n", iter.key);
81		    fprintf (stderr, "   val: %s\n", json_object_get_string (iter.val));
82		    continue;
83		}
84		if (strcmp (c->object, iter.key) != 0)
85		{
86		    fprintf (stderr, "E: value is a constant of different object\n");
87		    fprintf (stderr, "   key: %s\n", iter.key);
88		    fprintf (stderr, "   val: %s\n", json_object_get_string (iter.val));
89		    fprintf (stderr, "   key implied by value: %s\n", c->object);
90		    continue;
91		}
92		v.type = FcTypeInteger;
93		v.u.i = c->value;
94	    }
95	    else if (strcmp (json_object_get_string (iter.val), "DontCare") == 0)
96	    {
97		v.type = FcTypeBool;
98		v.u.b = FcDontCare;
99	    }
100	    else
101	    {
102		v.type = FcTypeString;
103		v.u.s = (const FcChar8 *) json_object_get_string (iter.val);
104	    }
105	}
106	else if (json_object_get_type (iter.val) == json_type_null)
107	{
108	    v.type = FcTypeVoid;
109	}
110	else if (json_object_get_type (iter.val) == json_type_array)
111	{
112	    json_object *o;
113	    json_type type;
114	    int i, n;
115
116	    n = json_object_array_length (iter.val);
117	    if (n == 0) {
118		fprintf (stderr, "E: value is an empty array\n");
119		continue;
120	    }
121
122	    o = json_object_array_get_idx (iter.val, 0);
123	    type = json_object_get_type (o);
124	    if (type == json_type_string) {
125		const FcObjectType *fc_o = FcNameGetObjectType (iter.key);
126		if (fc_o && fc_o->type == FcTypeCharSet) {
127		    FcCharSet* cs = FcCharSetCreate ();
128		    if (!cs) {
129			fprintf (stderr, "E: failed to create charset\n");
130			continue;
131		    }
132		    v.type = FcTypeCharSet;
133		    v.u.c = cs;
134		    destroy_v = FcTrue;
135		    for (i = 0; i < n; i++)
136	            {
137			const FcChar8 *src;
138			int len, nchar, wchar;
139			FcBool valid;
140			FcChar32 dst;
141
142			o = json_object_array_get_idx (iter.val, i);
143			type = json_object_get_type (o);
144			if (type != json_type_string) {
145			    fprintf (stderr, "E: charset value not string\n");
146			    FcValueDestroy (v);
147			    continue;
148			}
149			src = (const FcChar8 *) json_object_get_string (o);
150			len = json_object_get_string_len (o);
151			valid = FcUtf8Len (src, len, &nchar, &wchar);
152			if (valid == FcFalse) {
153			    fprintf (stderr, "E: charset entry not well formed\n");
154			    FcValueDestroy (v);
155			    continue;
156			}
157			if (nchar != 1) {
158			    fprintf (stderr, "E: charset entry not not one codepoint\n");
159			    FcValueDestroy (v);
160			    continue;
161			}
162			FcUtf8ToUcs4 (src, &dst, len);
163			if (FcCharSetAddChar (cs, dst) == FcFalse) {
164			    fprintf (stderr, "E: failed to add to charset\n");
165			    FcValueDestroy (v);
166			    continue;
167			}
168		    }
169		} else if (fc_o && fc_o->type == FcTypeString) {
170		    for (i = 0; i < n; i++)
171		    {
172			o = json_object_array_get_idx (iter.val, i);
173			type = json_object_get_type (o);
174			if (type != json_type_string) {
175			    fprintf (stderr, "E: unable to convert to string\n");
176			    continue;
177			}
178			v.type = FcTypeString;
179			v.u.s = (const FcChar8 *) json_object_get_string (o);
180			FcPatternAdd (pat, iter.key, v, FcTrue);
181			v.type = FcTypeVoid;
182		    }
183		    continue;
184		} else {
185		    FcLangSet* ls = FcLangSetCreate ();
186		    if (!ls) {
187			fprintf (stderr, "E: failed to create langset\n");
188			continue;
189		    }
190		    v.type = FcTypeLangSet;
191		    v.u.l = ls;
192		    destroy_v = FcTrue;
193		    for (i = 0; i < n; i++)
194	            {
195			o = json_object_array_get_idx (iter.val, i);
196			type = json_object_get_type (o);
197			if (type != json_type_string) {
198			    fprintf (stderr, "E: langset value not string\n");
199			    FcValueDestroy (v);
200			    continue;
201			}
202			if (FcLangSetAdd (ls, (const FcChar8 *)json_object_get_string (o)) == FcFalse) {
203			    fprintf (stderr, "E: failed to add to langset\n");
204			    FcValueDestroy (v);
205			    continue;
206			}
207		    }
208		}
209	    } else if (type == json_type_double || type == json_type_int) {
210		const FcObjectType *fc_o = FcNameGetObjectType (iter.key);
211		double values[4];
212
213		if (fc_o && fc_o->type == FcTypeDouble) {
214		    for (i = 0; i < n; i++)
215		    {
216			o = json_object_array_get_idx (iter.val, i);
217			type = json_object_get_type (o);
218			if (type == json_type_double) {
219			    v.type = FcTypeDouble;
220			    v.u.d = json_object_get_double (o);
221			} else if (type == json_type_int) {
222			    v.type = FcTypeInteger;
223			    v.u.i = json_object_get_int (o);
224			} else {
225			    fprintf (stderr, "E: unable to convert to double\n");
226			    continue;
227			}
228			FcPatternAdd (pat, iter.key, v, FcTrue);
229			v.type = FcTypeVoid;
230		    }
231		    continue;
232		} else {
233		    if (n != 2 && n != 4) {
234			fprintf (stderr, "E: array starting with number not range or matrix\n");
235			continue;
236		    }
237		    for (i = 0; i < n; i++) {
238			o = json_object_array_get_idx (iter.val, i);
239			type = json_object_get_type (o);
240			if (type != json_type_double && type != json_type_int) {
241			    fprintf (stderr, "E: numeric array entry not a number\n");
242			    continue;
243			}
244			values[i] = json_object_get_double (o);
245		    }
246		    if (n == 2) {
247			v.type = FcTypeRange;
248			v.u.r = FcRangeCreateDouble (values[0], values[1]);
249			if (!v.u.r) {
250			    fprintf (stderr, "E: failed to create range\n");
251			    continue;
252			}
253			destroy_v = FcTrue;
254		    } else {
255			v.type = FcTypeMatrix;
256			v.u.m = &matrix;
257			matrix.xx = values[0];
258			matrix.xy = values[1];
259			matrix.yx = values[2];
260			matrix.yy = values[3];
261		    }
262		}
263	    } else {
264		fprintf (stderr, "E: array format not recognized\n");
265		continue;
266	    }
267	}
268	else
269	{
270	    fprintf (stderr, "W: unexpected object to build a pattern: (%s %s)", iter.key, json_type_to_name (json_object_get_type (iter.val)));
271	    continue;
272	}
273	if (v.type != FcTypeVoid)
274	    FcPatternAdd (pat, iter.key, v, FcTrue);
275	if (destroy_v)
276	    FcValueDestroy (v);
277    }
278    return pat;
279}
280
281static FcFontSet *
282build_fs (json_object *obj)
283{
284    FcFontSet *fs = FcFontSetCreate ();
285    int i, n;
286
287    n = json_object_array_length (obj);
288    for (i = 0; i < n; i++)
289    {
290	json_object *o = json_object_array_get_idx (obj, i);
291	FcPattern *pat;
292
293	if (json_object_get_type (o) != json_type_object)
294	    continue;
295	pat = build_pattern (o);
296	FcFontSetAdd (fs, pat);
297    }
298
299    return fs;
300}
301
302static FcBool
303build_fonts (FcConfig *config, json_object *root)
304{
305    json_object *fonts;
306    FcFontSet *fs;
307
308    if (!json_object_object_get_ex (root, "fonts", &fonts) ||
309	json_object_get_type (fonts) != json_type_array)
310    {
311	fprintf (stderr, "W: No fonts defined\n");
312	return FcFalse;
313    }
314    fs = build_fs (fonts);
315    /* FcConfigSetFonts (config, fs, FcSetSystem); */
316    if (config->fonts[FcSetSystem])
317	FcFontSetDestroy (config->fonts[FcSetSystem]);
318    config->fonts[FcSetSystem] = fs;
319
320    return FcTrue;
321}
322
323static FcBool
324run_test (FcConfig *config, json_object *root)
325{
326    json_object *tests;
327    int i, n, fail = 0;
328
329    if (!json_object_object_get_ex (root, "tests", &tests) ||
330	json_object_get_type (tests) != json_type_array)
331    {
332	fprintf (stderr, "W: No test cases defined\n");
333	return FcFalse;
334    }
335    n = json_object_array_length (tests);
336    for (i = 0; i < n; i++)
337    {
338	json_object *obj = json_object_array_get_idx (tests, i);
339	json_object_iter iter;
340	FcPattern *query = NULL;
341	FcPattern *result = NULL;
342	FcFontSet *result_fs = NULL;
343	const char *method = NULL;
344
345	if (json_object_get_type (obj) != json_type_object)
346	    continue;
347	json_object_object_foreachC (obj, iter)
348	{
349	    if (strcmp (iter.key, "method") == 0)
350	    {
351		if (json_object_get_type (iter.val) != json_type_string)
352		{
353		    fprintf (stderr, "W: invalid type of method: (%s)\n", json_type_to_name (json_object_get_type (iter.val)));
354		    continue;
355		}
356		method = json_object_get_string (iter.val);
357	    }
358	    else if (strcmp (iter.key, "query") == 0)
359	    {
360		if (json_object_get_type (iter.val) != json_type_object)
361		{
362		    fprintf (stderr, "W: invalid type of query: (%s)\n", json_type_to_name (json_object_get_type (iter.val)));
363		    continue;
364		}
365		if (query)
366		    FcPatternDestroy (query);
367		query = build_pattern (iter.val);
368	    }
369	    else if (strcmp (iter.key, "result") == 0)
370	    {
371		if (json_object_get_type (iter.val) != json_type_object)
372		{
373		    fprintf (stderr, "W: invalid type of result: (%s)\n", json_type_to_name (json_object_get_type (iter.val)));
374		    continue;
375		}
376		if (result)
377		    FcPatternDestroy (result);
378		result = build_pattern (iter.val);
379	    }
380	    else if (strcmp (iter.key, "result_fs") == 0)
381	    {
382		if (json_object_get_type (iter.val) != json_type_array)
383		{
384		    fprintf (stderr, "W: invalid type of result_fs: (%s)\n", json_type_to_name (json_object_get_type (iter.val)));
385		    continue;
386		}
387		if (result_fs)
388		    FcFontSetDestroy (result_fs);
389		result_fs = build_fs (iter.val);
390	    }
391	    else
392	    {
393		fprintf (stderr, "W: unknown object: %s\n", iter.key);
394	    }
395	}
396	if (method != NULL && strcmp (method, "match") == 0)
397	{
398	    FcPattern *match;
399	    FcResult res;
400
401	    if (!query)
402	    {
403		fprintf (stderr, "E: no query defined.\n");
404		fail++;
405		goto bail;
406	    }
407	    if (!result)
408	    {
409		fprintf (stderr, "E: no result defined.\n");
410		fail++;
411		goto bail;
412	    }
413	    FcConfigSubstitute (config, query, FcMatchPattern);
414	    FcDefaultSubstitute (query);
415	    match = FcFontMatch (config, query, &res);
416	    if (match)
417	    {
418		FcPatternIter iter;
419		int x, vc;
420
421		FcPatternIterStart (result, &iter);
422		do
423		{
424		    vc = FcPatternIterValueCount (result, &iter);
425		    for (x = 0; x < vc; x++)
426		    {
427			FcValue vr, vm;
428
429			if (FcPatternIterGetValue (result, &iter, x, &vr, NULL) != FcResultMatch)
430			{
431			    fprintf (stderr, "E: unable to obtain a value from the expected result\n");
432			    fail++;
433			    goto bail;
434			}
435			if (FcPatternGet (match, FcPatternIterGetObject (result, &iter), x, &vm) != FcResultMatch)
436			{
437			    vm.type = FcTypeVoid;
438			}
439			if (!FcValueEqual (vm, vr))
440			{
441			    printf ("E: failed to compare %s:\n", FcPatternIterGetObject (result, &iter));
442			    printf ("   actual result:");
443			    FcValuePrint (vm);
444			    printf ("\n   expected result:");
445			    FcValuePrint (vr);
446			    printf ("\n");
447			    fail++;
448			    goto bail;
449			}
450		    }
451		} while (FcPatternIterNext (result, &iter));
452	    bail:
453		FcPatternDestroy (match);
454	    }
455	    else
456	    {
457		fprintf (stderr, "E: no match\n");
458		fail++;
459	    }
460	}
461	else if (method != NULL && strcmp (method, "list") == 0)
462	{
463	    FcFontSet *fs;
464
465	    if (!query)
466	    {
467		fprintf (stderr, "E: no query defined.\n");
468		fail++;
469		goto bail2;
470	    }
471	    if (!result_fs)
472	    {
473		fprintf (stderr, "E: no result_fs defined.\n");
474		fail++;
475		goto bail2;
476	    }
477	    fs = FcFontList (config, query, NULL);
478	    if (!fs)
479	    {
480		fprintf (stderr, "E: failed on FcFontList\n");
481		fail++;
482	    }
483	    else
484	    {
485		int i;
486
487		if (fs->nfont != result_fs->nfont)
488		{
489		    printf ("E: The number of results is different:\n");
490		    printf ("   actual result: %d\n", fs->nfont);
491		    printf ("   expected result: %d\n", result_fs->nfont);
492		    fail++;
493		    goto bail2;
494		}
495		for (i = 0; i < fs->nfont; i++)
496		{
497		    FcPatternIter iter;
498		    int x, vc;
499
500		    FcPatternIterStart (result_fs->fonts[i], &iter);
501		    do
502		    {
503			vc = FcPatternIterValueCount (result_fs->fonts[i], &iter);
504			for (x = 0; x < vc; x++)
505			{
506			    FcValue vr, vm;
507
508			    if (FcPatternIterGetValue (result_fs->fonts[i], &iter, x, &vr, NULL) != FcResultMatch)
509			    {
510				fprintf (stderr, "E: unable to obtain a value from the expected result\n");
511				fail++;
512				goto bail2;
513			    }
514			    if (FcPatternGet (fs->fonts[i], FcPatternIterGetObject (result_fs->fonts[i], &iter), x, &vm) != FcResultMatch)
515			    {
516				vm.type = FcTypeVoid;
517			    }
518			    if (!FcValueEqual (vm, vr))
519			    {
520				printf ("E: failed to compare %s:\n", FcPatternIterGetObject (result_fs->fonts[i], &iter));
521				printf ("   actual result:");
522				FcValuePrint (vm);
523				printf ("\n   expected result:");
524				FcValuePrint (vr);
525				printf ("\n");
526				fail++;
527				goto bail2;
528			    }
529			}
530		    } while (FcPatternIterNext (result_fs->fonts[i], &iter));
531		}
532	    bail2:
533		FcFontSetDestroy (fs);
534	    }
535	}
536	else
537	{
538	    fprintf (stderr, "W: unknown testing method: %s\n", method);
539	}
540	if (method)
541	    method = NULL;
542	if (result)
543	{
544	    FcPatternDestroy (result);
545	    result = NULL;
546	}
547	if (result_fs)
548	{
549	    FcFontSetDestroy (result_fs);
550	    result_fs = NULL;
551	}
552	if (query)
553	{
554	    FcPatternDestroy (query);
555	    query = NULL;
556	}
557    }
558
559    return fail == 0;
560}
561
562static FcBool
563run_scenario (FcConfig *config, char *file)
564{
565    FcBool ret = FcTrue;
566    json_object *root, *scenario;
567
568    root = json_object_from_file (file);
569    if (!root)
570    {
571	fprintf (stderr, "E: Unable to read the file: %s\n", file);
572	return FcFalse;
573    }
574    if (!build_fonts (config, root))
575    {
576	ret = FcFalse;
577	goto bail1;
578    }
579    if (!run_test (config, root))
580    {
581	ret = FcFalse;
582	goto bail1;
583    }
584
585bail1:
586    json_object_put (root);
587
588    return ret;
589}
590
591static FcBool
592load_config (FcConfig *config, char *file)
593{
594    FILE *fp;
595    long len;
596    char *buf = NULL;
597    FcBool ret = FcTrue;
598
599    if ((fp = fopen(file, "rb")) == NULL)
600	return FcFalse;
601    fseek (fp, 0L, SEEK_END);
602    len = ftell (fp);
603    fseek (fp, 0L, SEEK_SET);
604    buf = malloc (sizeof (char) * (len + 1));
605    if (!buf)
606    {
607	ret = FcFalse;
608	goto bail1;
609    }
610    fread (buf, (size_t)len, sizeof (char), fp);
611    buf[len] = 0;
612
613    ret = FcConfigParseAndLoadFromMemory (config, (const FcChar8 *) buf, FcTrue);
614bail1:
615    fclose (fp);
616    if (buf)
617	free (buf);
618
619    return ret;
620}
621
622int
623main (int argc, char **argv)
624{
625    FcConfig *config;
626    int retval = 0;
627
628    if (argc < 3)
629    {
630	fprintf(stderr, "Usage: %s <conf file> <test scenario>\n", argv[0]);
631	return 1;
632    }
633
634    config = FcConfigCreate ();
635    if (!load_config (config, argv[1]))
636    {
637	fprintf(stderr, "E: Failed to load config\n");
638	retval = 1;
639	goto bail1;
640    }
641    if (!run_scenario (config, argv[2]))
642    {
643	retval = 1;
644	goto bail1;
645    }
646bail1:
647    FcConfigDestroy (config);
648
649    return retval;
650}
651