1 /*
2 * Copyright 2015 MongoDB, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17
18 #include "mongoc-config.h"
19 #include "mongoc-server-description-private.h"
20 #include "mongoc-topology-description-private.h"
21 #include "mongoc-topology-private.h"
22 #include "mongoc-util-private.h"
23
24 #include "TestSuite.h"
25 #include "test-conveniences.h"
26
27 #include "json-test.h"
28
29 #ifdef _MSC_VER
30 #include <io.h>
31 #else
32 #include <dirent.h>
33 #endif
34
35 #ifdef HAVE_STRINGS_H
36 #include <strings.h>
37 #endif
38
39
40 mongoc_topology_description_type_t
topology_type_from_test(const char * type)41 topology_type_from_test (const char *type)
42 {
43 if (strcmp (type, "ReplicaSetWithPrimary") == 0) {
44 return MONGOC_TOPOLOGY_RS_WITH_PRIMARY;
45 } else if (strcmp (type, "ReplicaSetNoPrimary") == 0) {
46 return MONGOC_TOPOLOGY_RS_NO_PRIMARY;
47 } else if (strcmp (type, "Unknown") == 0) {
48 return MONGOC_TOPOLOGY_UNKNOWN;
49 } else if (strcmp (type, "Single") == 0) {
50 return MONGOC_TOPOLOGY_SINGLE;
51 } else if (strcmp (type, "Sharded") == 0) {
52 return MONGOC_TOPOLOGY_SHARDED;
53 }
54
55 fprintf (stderr, "can't parse this: %s", type);
56 BSON_ASSERT (0);
57 return 0;
58 }
59
60 mongoc_server_description_type_t
server_type_from_test(const char * type)61 server_type_from_test (const char *type)
62 {
63 if (strcmp (type, "RSPrimary") == 0) {
64 return MONGOC_SERVER_RS_PRIMARY;
65 } else if (strcmp (type, "RSSecondary") == 0) {
66 return MONGOC_SERVER_RS_SECONDARY;
67 } else if (strcmp (type, "Standalone") == 0) {
68 return MONGOC_SERVER_STANDALONE;
69 } else if (strcmp (type, "Mongos") == 0) {
70 return MONGOC_SERVER_MONGOS;
71 } else if (strcmp (type, "PossiblePrimary") == 0) {
72 return MONGOC_SERVER_POSSIBLE_PRIMARY;
73 } else if (strcmp (type, "RSArbiter") == 0) {
74 return MONGOC_SERVER_RS_ARBITER;
75 } else if (strcmp (type, "RSOther") == 0) {
76 return MONGOC_SERVER_RS_OTHER;
77 } else if (strcmp (type, "RSGhost") == 0) {
78 return MONGOC_SERVER_RS_GHOST;
79 } else if (strcmp (type, "Unknown") == 0) {
80 return MONGOC_SERVER_UNKNOWN;
81 }
82 fprintf (stderr, "ERROR: Unknown server type %s\n", type);
83 BSON_ASSERT (0);
84 return 0;
85 }
86
87
88 static mongoc_read_mode_t
read_mode_from_test(const char * mode)89 read_mode_from_test (const char *mode)
90 {
91 if (strcmp (mode, "Primary") == 0) {
92 return MONGOC_READ_PRIMARY;
93 } else if (strcmp (mode, "PrimaryPreferred") == 0) {
94 return MONGOC_READ_PRIMARY_PREFERRED;
95 } else if (strcmp (mode, "Secondary") == 0) {
96 return MONGOC_READ_SECONDARY;
97 } else if (strcmp (mode, "SecondaryPreferred") == 0) {
98 return MONGOC_READ_SECONDARY_PREFERRED;
99 } else if (strcmp (mode, "Nearest") == 0) {
100 return MONGOC_READ_NEAREST;
101 }
102
103 return MONGOC_READ_PRIMARY;
104 }
105
106
107 static mongoc_ss_optype_t
optype_from_test(const char * op)108 optype_from_test (const char *op)
109 {
110 if (strcmp (op, "read") == 0) {
111 return MONGOC_SS_READ;
112 } else if (strcmp (op, "write") == 0) {
113 return MONGOC_SS_WRITE;
114 }
115
116 return MONGOC_SS_READ;
117 }
118
119
120 /*
121 *-----------------------------------------------------------------------
122 *
123 * server_description_by_hostname --
124 *
125 * Return a reference to a mongoc_server_description_t or NULL.
126 *
127 *-----------------------------------------------------------------------
128 */
129 mongoc_server_description_t *
server_description_by_hostname(mongoc_topology_description_t * topology,const char * address)130 server_description_by_hostname (mongoc_topology_description_t *topology,
131 const char *address)
132 {
133 mongoc_set_t *set = topology->servers;
134 mongoc_server_description_t *server_iter;
135 int i;
136
137 for (i = 0; i < set->items_len; i++) {
138 server_iter = (mongoc_server_description_t *) mongoc_set_get_item (
139 topology->servers, i);
140
141 if (strcasecmp (address, server_iter->connection_address) == 0) {
142 return server_iter;
143 }
144 }
145
146 return NULL;
147 }
148
149
150 /*
151 *-----------------------------------------------------------------------
152 *
153 * process_sdam_test_ismaster_responses --
154 *
155 * Update a topology description with the ismaster responses in a "phase"
156 * from an SDAM or SDAM Monitoring test, like:
157 *
158 * [
159 * [
160 * "a:27017",
161 * {
162 * "ok": 1,
163 * "ismaster": false
164 * }
165 * ]
166 * ]
167 *
168 * See:
169 * https://github.com/mongodb/specifications/tree/master/source/server-discovery-and-monitoring/tests
170 *
171 *-----------------------------------------------------------------------
172 */
173 void
process_sdam_test_ismaster_responses(bson_t * phase,mongoc_topology_description_t * td)174 process_sdam_test_ismaster_responses (bson_t *phase,
175 mongoc_topology_description_t *td)
176 {
177 mongoc_server_description_t *sd;
178 bson_t ismasters;
179 bson_t ismaster;
180 bson_t response;
181 bson_iter_t phase_field_iter;
182 bson_iter_t ismaster_iter;
183 bson_iter_t ismaster_field_iter;
184 const char *hostname;
185
186 /* grab ismaster responses out and feed them to topology */
187 BSON_ASSERT (bson_iter_init_find (&phase_field_iter, phase, "responses"));
188 bson_iter_bson (&phase_field_iter, &ismasters);
189 bson_iter_init (&ismaster_iter, &ismasters);
190
191 while (bson_iter_next (&ismaster_iter)) {
192 bson_iter_bson (&ismaster_iter, &ismaster);
193
194 bson_iter_init_find (&ismaster_field_iter, &ismaster, "0");
195 hostname = bson_iter_utf8 (&ismaster_field_iter, NULL);
196 sd = server_description_by_hostname (td, hostname);
197
198 /* if server has been removed from topology, skip */
199 if (!sd) {
200 continue;
201 }
202
203 bson_iter_init_find (&ismaster_field_iter, &ismaster, "1");
204 bson_iter_bson (&ismaster_field_iter, &response);
205
206 /* send ismaster through the topology description's handler */
207 mongoc_topology_description_handle_ismaster (
208 td, sd->id, &response, 1, NULL);
209 }
210 }
211
212
213 /*
214 *-----------------------------------------------------------------------
215 *
216 * check_json_apm_events --
217 *
218 * Compare actual APM events with expected sequence. The two docs
219 * are like:
220 *
221 * [
222 * {
223 * "command_started_event": {
224 * "command": { ... },
225 * "command_name": "count",
226 * "database_name": "command-monitoring-tests"
227 * }
228 * },
229 * {
230 * "command_failed_event": {
231 * "command_name": "count"
232 * }
233 * }
234 * ]
235 *
236 *-----------------------------------------------------------------------
237 */
238 void
check_json_apm_events(const bson_t * events,const bson_t * expectations)239 check_json_apm_events (const bson_t *events, const bson_t *expectations)
240 {
241 char errmsg[1000] = {0};
242 match_ctx_t ctx = {0};
243 uint32_t expected_keys;
244 uint32_t actual_keys;
245
246 /* Old mongod returns a double for "count", newer returns int32.
247 * Ignore this and other insignificant type differences. */
248 ctx.strict_numeric_types = false;
249 ctx.errmsg = errmsg;
250 ctx.errmsg_len = sizeof errmsg;
251
252 expected_keys = bson_count_keys (expectations);
253 actual_keys = bson_count_keys (events);
254
255 if (expected_keys != actual_keys) {
256 test_error ("command monitoring test failed expectations:\n\n"
257 "%s\n\n"
258 "events:\n%s\n\n"
259 "expected %" PRIu32 " events, got %" PRIu32,
260 bson_as_canonical_extended_json (expectations, NULL),
261 bson_as_canonical_extended_json (events, NULL),
262 expected_keys,
263 actual_keys);
264
265 abort ();
266 }
267
268 if (!match_bson_with_ctx (events, expectations, false, &ctx)) {
269 test_error ("command monitoring test failed expectations:\n\n"
270 "%s\n\n"
271 "events:\n%s\n\n%s",
272 bson_as_canonical_extended_json (expectations, NULL),
273 bson_as_canonical_extended_json (events, NULL),
274 errmsg);
275
276 abort ();
277 }
278 }
279
280
281 /*
282 *-----------------------------------------------------------------------
283 *
284 * test_server_selection_logic_cb --
285 *
286 * Runs the JSON tests for server selection logic that are
287 * included with the Server Selection spec.
288 *
289 *-----------------------------------------------------------------------
290 */
291 void
test_server_selection_logic_cb(bson_t * test)292 test_server_selection_logic_cb (bson_t *test)
293 {
294 bool expected_error;
295 bson_error_t error;
296 int32_t heartbeat_msec;
297 mongoc_topology_description_t topology;
298 mongoc_server_description_t *sd;
299 mongoc_read_prefs_t *read_prefs;
300 mongoc_read_mode_t read_mode;
301 mongoc_ss_optype_t op;
302 bson_iter_t iter;
303 bson_iter_t topology_iter;
304 bson_iter_t server_iter;
305 bson_iter_t sd_iter;
306 bson_iter_t read_pref_iter;
307 bson_iter_t tag_sets_iter;
308 bson_iter_t last_write_iter;
309 bson_iter_t expected_servers_iter;
310 bson_t first_tag_set;
311 bson_t test_topology;
312 bson_t test_servers;
313 bson_t server;
314 bson_t test_read_pref;
315 bson_t test_tag_sets;
316 const char *type;
317 uint32_t i = 0;
318 bool matched_servers[50];
319 mongoc_array_t selected_servers;
320
321 _mongoc_array_init (&selected_servers,
322 sizeof (mongoc_server_description_t *));
323
324 BSON_ASSERT (test);
325
326 expected_error =
327 bson_iter_init_find (&iter, test, "error") && bson_iter_as_bool (&iter);
328
329 heartbeat_msec = MONGOC_TOPOLOGY_HEARTBEAT_FREQUENCY_MS_SINGLE_THREADED;
330
331 if (bson_iter_init_find (&iter, test, "heartbeatFrequencyMS")) {
332 heartbeat_msec = bson_iter_int32 (&iter);
333 }
334
335 /* pull out topology description field */
336 BSON_ASSERT (bson_iter_init_find (&iter, test, "topology_description"));
337 bson_iter_bson (&iter, &test_topology);
338
339 /* set topology state from test */
340 BSON_ASSERT (bson_iter_init_find (&topology_iter, &test_topology, "type"));
341 type = bson_iter_utf8 (&topology_iter, NULL);
342
343 if (strcmp (type, "Single") == 0) {
344 mongoc_topology_description_init (
345 &topology, MONGOC_TOPOLOGY_SINGLE, heartbeat_msec);
346 } else {
347 mongoc_topology_description_init (
348 &topology, MONGOC_TOPOLOGY_UNKNOWN, heartbeat_msec);
349 topology.type =
350 topology_type_from_test (bson_iter_utf8 (&topology_iter, NULL));
351 }
352
353 /* for each server description in test, add server to our topology */
354 BSON_ASSERT (
355 bson_iter_init_find (&topology_iter, &test_topology, "servers"));
356 bson_iter_bson (&topology_iter, &test_servers);
357
358 bson_iter_init (&server_iter, &test_servers);
359 while (bson_iter_next (&server_iter)) {
360 bson_iter_bson (&server_iter, &server);
361
362 /* initialize new server description with given address */
363 sd = (mongoc_server_description_t *) bson_malloc0 (sizeof *sd);
364 BSON_ASSERT (bson_iter_init_find (&sd_iter, &server, "address"));
365 mongoc_server_description_init (sd, bson_iter_utf8 (&sd_iter, NULL), i++);
366
367 BSON_ASSERT (bson_iter_init_find (&sd_iter, &server, "type"));
368 sd->type = server_type_from_test (bson_iter_utf8 (&sd_iter, NULL));
369
370 if (bson_iter_init_find (&sd_iter, &server, "avg_rtt_ms")) {
371 sd->round_trip_time_msec = bson_iter_int32 (&sd_iter);
372 } else if (sd->type != MONGOC_SERVER_UNKNOWN) {
373 test_error ("%s has no avg_rtt_ms", sd->host.host_and_port);
374 abort ();
375 }
376
377 if (bson_iter_init_find (&sd_iter, &server, "maxWireVersion")) {
378 sd->max_wire_version = (int32_t) bson_iter_as_int64 (&sd_iter);
379 }
380
381 if (bson_iter_init_find (&sd_iter, &server, "lastUpdateTime")) {
382 sd->last_update_time_usec = bson_iter_as_int64 (&sd_iter) * 1000;
383 }
384
385 if (bson_iter_init_find (&sd_iter, &server, "lastWrite")) {
386 BSON_ASSERT (BSON_ITER_HOLDS_DOCUMENT (&sd_iter) &&
387 bson_iter_recurse (&sd_iter, &last_write_iter) &&
388 bson_iter_find (&last_write_iter, "lastWriteDate") &&
389 BSON_ITER_HOLDS_INT (&last_write_iter));
390 sd->last_write_date_ms = bson_iter_as_int64 (&last_write_iter);
391 }
392
393 if (bson_iter_init_find (&sd_iter, &server, "tags")) {
394 bson_iter_bson (&sd_iter, &sd->tags);
395 } else {
396 bson_init (&sd->tags);
397 }
398
399 /* add new server to our topology description */
400 mongoc_set_add (topology.servers, sd->id, sd);
401 }
402
403 /* create read preference document from test */
404 BSON_ASSERT (bson_iter_init_find (&iter, test, "read_preference"));
405 bson_iter_bson (&iter, &test_read_pref);
406
407 if (bson_iter_init_find (&read_pref_iter, &test_read_pref, "mode")) {
408 read_mode = read_mode_from_test (bson_iter_utf8 (&read_pref_iter, NULL));
409 ASSERT_CMPINT (read_mode, !=, 0);
410 } else {
411 read_mode = MONGOC_READ_PRIMARY;
412 }
413
414 read_prefs = mongoc_read_prefs_new (read_mode);
415
416 if (bson_iter_init_find (&read_pref_iter, &test_read_pref, "tag_sets")) {
417 /* ignore "tag_sets: [{}]" */
418 if (bson_iter_recurse (&read_pref_iter, &tag_sets_iter) &&
419 bson_iter_next (&tag_sets_iter) &&
420 BSON_ITER_HOLDS_DOCUMENT (&tag_sets_iter)) {
421 bson_iter_bson (&tag_sets_iter, &first_tag_set);
422 if (!bson_empty (&first_tag_set)) {
423 /* not empty */
424 bson_iter_bson (&read_pref_iter, &test_tag_sets);
425 mongoc_read_prefs_set_tags (read_prefs, &test_tag_sets);
426 }
427 }
428 }
429
430 if (bson_iter_init_find (
431 &read_pref_iter, &test_read_pref, "maxStalenessSeconds")) {
432 mongoc_read_prefs_set_max_staleness_seconds (
433 read_prefs, bson_iter_as_int64 (&read_pref_iter));
434 }
435
436 /* get operation type */
437 op = MONGOC_SS_READ;
438
439 if (bson_iter_init_find (&iter, test, "operation")) {
440 op = optype_from_test (bson_iter_utf8 (&iter, NULL));
441 }
442
443 if (expected_error) {
444 BSON_ASSERT (!mongoc_read_prefs_is_valid (read_prefs) ||
445 !mongoc_topology_compatible (&topology, read_prefs, &error));
446 goto DONE;
447 }
448
449 /* no expected error */
450 BSON_ASSERT (mongoc_read_prefs_is_valid (read_prefs));
451 BSON_ASSERT (mongoc_topology_compatible (&topology, read_prefs, &error));
452
453 /* read in latency window servers */
454 BSON_ASSERT (bson_iter_init_find (&iter, test, "in_latency_window"));
455
456 /* TODO: use topology_select instead? */
457 mongoc_topology_description_suitable_servers (
458 &selected_servers,
459 op,
460 &topology,
461 read_prefs,
462 MONGOC_TOPOLOGY_LOCAL_THRESHOLD_MS);
463
464 /* check each server in expected_servers is in selected_servers */
465 memset (matched_servers, 0, sizeof (matched_servers));
466 bson_iter_recurse (&iter, &expected_servers_iter);
467 while (bson_iter_next (&expected_servers_iter)) {
468 bool found = false;
469 bson_iter_t host;
470
471 BSON_ASSERT (bson_iter_recurse (&expected_servers_iter, &host));
472 BSON_ASSERT (bson_iter_find (&host, "address"));
473
474 for (i = 0; i < selected_servers.len; i++) {
475 sd = _mongoc_array_index (
476 &selected_servers, mongoc_server_description_t *, i);
477
478 if (strcmp (sd->host.host_and_port, bson_iter_utf8 (&host, NULL)) ==
479 0) {
480 found = true;
481 break;
482 }
483 }
484
485 if (!found) {
486 test_error ("Should have been selected but wasn't: %s",
487 bson_iter_utf8 (&host, NULL));
488 abort ();
489 }
490
491 matched_servers[i] = true;
492 }
493
494 /* check each server in selected_servers is in expected_servers */
495 for (i = 0; i < selected_servers.len; i++) {
496 if (!matched_servers[i]) {
497 sd = _mongoc_array_index (
498 &selected_servers, mongoc_server_description_t *, i);
499
500 test_error ("Shouldn't have been selected but was: %s",
501 sd->host.host_and_port);
502 abort ();
503 }
504 }
505
506 DONE:
507 mongoc_read_prefs_destroy (read_prefs);
508 mongoc_topology_description_destroy (&topology);
509 _mongoc_array_destroy (&selected_servers);
510 }
511
512 /*
513 *-----------------------------------------------------------------------
514 *
515 * assemble_path --
516 *
517 * Given a parent directory and filename, compile a full path to
518 * the child file.
519 *
520 * "dst" receives the joined path, delimited by "/" even on Windows.
521 *
522 *-----------------------------------------------------------------------
523 */
524 void
assemble_path(const char * parent_path,const char * child_name,char * dst)525 assemble_path (const char *parent_path,
526 const char *child_name,
527 char *dst /* OUT */)
528 {
529 char *p;
530 int path_len = (int) strlen (parent_path);
531 int name_len = (int) strlen (child_name);
532
533 BSON_ASSERT (path_len + name_len + 1 < MAX_TEST_NAME_LENGTH);
534
535 memset (dst, '\0', MAX_TEST_NAME_LENGTH * sizeof (char));
536 strncat (dst, parent_path, path_len);
537 strncat (dst, "/", 1);
538 strncat (dst, child_name, name_len);
539
540 for (p = dst; *p; ++p) {
541 if (*p == '\\') {
542 *p = '/';
543 }
544 }
545 }
546
547 /*
548 *-----------------------------------------------------------------------
549 *
550 * collect_tests_from_dir --
551 *
552 * Recursively search the directory at @dir_path for files with
553 * '.json' in their filenames. Append all found file paths to
554 * @paths, and return the number of files found.
555 *
556 *-----------------------------------------------------------------------
557 */
558 int
collect_tests_from_dir(char (* paths)[MAX_TEST_NAME_LENGTH],const char * dir_path,int paths_index,int max_paths)559 collect_tests_from_dir (char (*paths)[MAX_TEST_NAME_LENGTH] /* OUT */,
560 const char *dir_path,
561 int paths_index,
562 int max_paths)
563 {
564 #ifdef _MSC_VER
565 intptr_t handle;
566 struct _finddata_t info;
567
568 char child_path[MAX_TEST_NAME_LENGTH];
569
570 handle = _findfirst (dir_path, &info);
571
572 if (handle == -1) {
573 return 0;
574 }
575
576 while (1) {
577 BSON_ASSERT (paths_index < max_paths);
578
579 if (_findnext (handle, &info) == -1) {
580 break;
581 }
582
583 if (info.attrib & _A_SUBDIR) {
584 /* recursively call on child directories */
585 if (strcmp (info.name, "..") != 0 && strcmp (info.name, ".") != 0) {
586 assemble_path (dir_path, info.name, child_path);
587 paths_index = collect_tests_from_dir (
588 paths, child_path, paths_index, max_paths);
589 }
590 } else if (strstr (info.name, ".json")) {
591 /* if this is a JSON test, collect its path */
592 assemble_path (dir_path, info.name, paths[paths_index++]);
593 }
594 }
595
596 _findclose (handle);
597
598 return paths_index;
599 #else
600 struct dirent *entry;
601 struct stat dir_stat;
602 char child_path[MAX_TEST_NAME_LENGTH];
603 DIR *dir;
604
605 dir = opendir (dir_path);
606 BSON_ASSERT (dir);
607 while ((entry = readdir (dir))) {
608 BSON_ASSERT (paths_index < max_paths);
609 if (strcmp (entry->d_name, "..") == 0 ||
610 strcmp (entry->d_name, ".") == 0) {
611 continue;
612 }
613
614 assemble_path (dir_path, entry->d_name, child_path);
615
616 if (0 == stat (child_path, &dir_stat) && S_ISDIR (dir_stat.st_mode)) {
617 /* recursively call on child directories */
618 paths_index =
619 collect_tests_from_dir (paths, child_path, paths_index, max_paths);
620 } else if (strstr (entry->d_name, ".json")) {
621 /* if this is a JSON test, collect its path */
622 assemble_path (dir_path, entry->d_name, paths[paths_index++]);
623 }
624 }
625
626 closedir (dir);
627
628 return paths_index;
629 #endif
630 }
631
632 /*
633 *-----------------------------------------------------------------------
634 *
635 * get_bson_from_json_file --
636 *
637 * Open the file at @filename and store its contents in a
638 * bson_t. This function assumes that @filename contains a
639 * single JSON object.
640 *
641 * NOTE: caller owns returned bson_t and must free it.
642 *
643 *-----------------------------------------------------------------------
644 */
645 bson_t *
get_bson_from_json_file(char * filename)646 get_bson_from_json_file (char *filename)
647 {
648 FILE *file;
649 long length;
650 bson_t *data;
651 bson_error_t error;
652 const char *buffer;
653
654 file = fopen (filename, "rb");
655 if (!file) {
656 return NULL;
657 }
658
659 /* get file length */
660 fseek (file, 0, SEEK_END);
661 length = ftell (file);
662 fseek (file, 0, SEEK_SET);
663 if (length < 1) {
664 return NULL;
665 }
666
667 /* read entire file into buffer */
668 buffer = (const char *) bson_malloc0 (length);
669 if (fread ((void *) buffer, 1, length, file) != length) {
670 abort ();
671 }
672
673 fclose (file);
674 if (!buffer) {
675 return NULL;
676 }
677
678 /* convert to bson */
679 data = bson_new_from_json ((const uint8_t *) buffer, length, &error);
680 if (!data) {
681 fprintf (stderr, "Cannot parse %s: %s\n", filename, error.message);
682 abort ();
683 }
684
685 bson_free ((void *) buffer);
686
687 return data;
688 }
689
690 /*
691 *-----------------------------------------------------------------------
692 *
693 * install_json_test_suite --
694 *
695 * Given a path to a directory containing JSON tests, import each
696 * test into a BSON blob and call the provided callback for
697 * evaluation.
698 *
699 * It is expected that the callback will BSON_ASSERT on failure, so if
700 * callback returns quietly the test is considered to have passed.
701 *
702 *-----------------------------------------------------------------------
703 */
704 void
install_json_test_suite(TestSuite * suite,const char * dir_path,test_hook callback)705 install_json_test_suite (TestSuite *suite,
706 const char *dir_path,
707 test_hook callback)
708 {
709 char test_paths[MAX_NUM_TESTS][MAX_TEST_NAME_LENGTH];
710 int num_tests;
711 int i;
712 bson_t *test;
713 char *skip_json;
714 char *ext;
715
716 num_tests =
717 collect_tests_from_dir (&test_paths[0], dir_path, 0, MAX_NUM_TESTS);
718
719 for (i = 0; i < num_tests; i++) {
720 test = get_bson_from_json_file (test_paths[i]);
721 skip_json = COALESCE (strstr (test_paths[i], "/json"),
722 strstr (test_paths[i], "\\json"));
723 BSON_ASSERT (skip_json);
724 skip_json += strlen ("/json");
725 ext = strstr (skip_json, ".json");
726 BSON_ASSERT (ext);
727 ext[0] = '\0';
728
729 TestSuite_AddFull (suite,
730 skip_json,
731 (void (*) (void *)) callback,
732 (void (*) (void *)) bson_destroy,
733 test,
734 TestSuite_CheckLive);
735 }
736 }
737