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