geoip2_c 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/ext/geoip2/libmaxminddb/.gitignore +35 -0
  3. data/ext/geoip2/libmaxminddb/.gitmodules +9 -0
  4. data/ext/geoip2/libmaxminddb/.perltidyrc +11 -0
  5. data/ext/geoip2/libmaxminddb/.travis.yml +48 -0
  6. data/ext/geoip2/libmaxminddb/.uncrustify.cfg +78 -0
  7. data/ext/geoip2/libmaxminddb/AUTHORS +0 -0
  8. data/ext/geoip2/libmaxminddb/Changes.md +238 -0
  9. data/ext/geoip2/libmaxminddb/LICENSE +202 -0
  10. data/ext/geoip2/libmaxminddb/Makefile.am +41 -0
  11. data/ext/geoip2/libmaxminddb/NOTICE +13 -0
  12. data/ext/geoip2/libmaxminddb/README.dev.md +58 -0
  13. data/ext/geoip2/libmaxminddb/README.md +122 -0
  14. data/ext/geoip2/libmaxminddb/appveyor.yml +33 -0
  15. data/ext/geoip2/libmaxminddb/bin/Makefile.am +5 -0
  16. data/ext/geoip2/libmaxminddb/bin/mmdblookup.c +397 -0
  17. data/ext/geoip2/libmaxminddb/bootstrap +21 -0
  18. data/ext/geoip2/libmaxminddb/common.mk +7 -0
  19. data/ext/geoip2/libmaxminddb/configure.ac +132 -0
  20. data/ext/geoip2/libmaxminddb/dev-bin/make-man-pages.pl +76 -0
  21. data/ext/geoip2/libmaxminddb/dev-bin/ppa-release.sh +50 -0
  22. data/ext/geoip2/libmaxminddb/dev-bin/regen-prototypes.pl +136 -0
  23. data/ext/geoip2/libmaxminddb/dev-bin/regen-win32-test-projs.pl +54 -0
  24. data/ext/geoip2/libmaxminddb/dev-bin/release.sh +106 -0
  25. data/ext/geoip2/libmaxminddb/dev-bin/uncrustify-all.sh +21 -0
  26. data/ext/geoip2/libmaxminddb/dev-bin/valgrind-all.pl +46 -0
  27. data/ext/geoip2/libmaxminddb/doc/libmaxminddb.md +889 -0
  28. data/ext/geoip2/libmaxminddb/doc/mmdblookup.md +103 -0
  29. data/ext/geoip2/libmaxminddb/include/maxminddb.h +232 -0
  30. data/ext/geoip2/libmaxminddb/include/maxminddb_config.h.in +14 -0
  31. data/ext/geoip2/libmaxminddb/projects/VS12/README.md +59 -0
  32. data/ext/geoip2/libmaxminddb/projects/VS12/libmaxminddb-release.props +32 -0
  33. data/ext/geoip2/libmaxminddb/projects/VS12/libmaxminddb-x64.props +14 -0
  34. data/ext/geoip2/libmaxminddb/projects/VS12/libmaxminddb.props +32 -0
  35. data/ext/geoip2/libmaxminddb/projects/VS12/libmaxminddb.sln +150 -0
  36. data/ext/geoip2/libmaxminddb/projects/VS12/libmaxminddb.vcxproj +141 -0
  37. data/ext/geoip2/libmaxminddb/projects/VS12/libmaxminddb.vcxproj.filters +26 -0
  38. data/ext/geoip2/libmaxminddb/projects/VS12/maxminddb_config.h +14 -0
  39. data/ext/geoip2/libmaxminddb/projects/VS12-tests/bad_pointers.vcxproj +105 -0
  40. data/ext/geoip2/libmaxminddb/projects/VS12-tests/basic_lookup.vcxproj +105 -0
  41. data/ext/geoip2/libmaxminddb/projects/VS12-tests/data_entry_list.vcxproj +105 -0
  42. data/ext/geoip2/libmaxminddb/projects/VS12-tests/data_types.vcxproj +105 -0
  43. data/ext/geoip2/libmaxminddb/projects/VS12-tests/dump.vcxproj +105 -0
  44. data/ext/geoip2/libmaxminddb/projects/VS12-tests/get_value.vcxproj +105 -0
  45. data/ext/geoip2/libmaxminddb/projects/VS12-tests/get_value_pointer_bug.vcxproj +105 -0
  46. data/ext/geoip2/libmaxminddb/projects/VS12-tests/ipv4_start_cache.vcxproj +105 -0
  47. data/ext/geoip2/libmaxminddb/projects/VS12-tests/ipv6_lookup_in_ipv4.vcxproj +105 -0
  48. data/ext/geoip2/libmaxminddb/projects/VS12-tests/libtap.vcxproj +85 -0
  49. data/ext/geoip2/libmaxminddb/projects/VS12-tests/maxminddb_test_helper.vcxproj +107 -0
  50. data/ext/geoip2/libmaxminddb/projects/VS12-tests/metadata.vcxproj +105 -0
  51. data/ext/geoip2/libmaxminddb/projects/VS12-tests/metadata_pointers.vcxproj +105 -0
  52. data/ext/geoip2/libmaxminddb/projects/VS12-tests/no_map_get_value.vcxproj +105 -0
  53. data/ext/geoip2/libmaxminddb/projects/VS12-tests/read_node.vcxproj +105 -0
  54. data/ext/geoip2/libmaxminddb/projects/VS12-tests/shared.vcxproj +104 -0
  55. data/ext/geoip2/libmaxminddb/projects/VS12-tests/threads.vcxproj +103 -0
  56. data/ext/geoip2/libmaxminddb/projects/VS12-tests/version.vcxproj +105 -0
  57. data/ext/geoip2/libmaxminddb/projects/test.vcxproj.template +105 -0
  58. data/ext/geoip2/libmaxminddb/src/Makefile.am +9 -0
  59. data/ext/geoip2/libmaxminddb/src/libmaxminddb.pc.in +11 -0
  60. data/ext/geoip2/libmaxminddb/src/maxminddb-compat-util.h +167 -0
  61. data/ext/geoip2/libmaxminddb/src/maxminddb.c +2171 -0
  62. data/ext/geoip2/libmaxminddb/t/Makefile.am +23 -0
  63. data/ext/geoip2/libmaxminddb/t/bad_databases_t.c +66 -0
  64. data/ext/geoip2/libmaxminddb/t/bad_pointers_t.c +53 -0
  65. data/ext/geoip2/libmaxminddb/t/basic_lookup_t.c +172 -0
  66. data/ext/geoip2/libmaxminddb/t/compile_c++_t.pl +107 -0
  67. data/ext/geoip2/libmaxminddb/t/data_entry_list_t.c +353 -0
  68. data/ext/geoip2/libmaxminddb/t/data_types_t.c +439 -0
  69. data/ext/geoip2/libmaxminddb/t/dump_t.c +103 -0
  70. data/ext/geoip2/libmaxminddb/t/get_value_pointer_bug_t.c +66 -0
  71. data/ext/geoip2/libmaxminddb/t/get_value_t.c +249 -0
  72. data/ext/geoip2/libmaxminddb/t/ipv4_start_cache_t.c +36 -0
  73. data/ext/geoip2/libmaxminddb/t/ipv6_lookup_in_ipv4_t.c +48 -0
  74. data/ext/geoip2/libmaxminddb/t/maxminddb_test_helper.c +255 -0
  75. data/ext/geoip2/libmaxminddb/t/maxminddb_test_helper.h +69 -0
  76. data/ext/geoip2/libmaxminddb/t/metadata_pointers_t.c +32 -0
  77. data/ext/geoip2/libmaxminddb/t/metadata_t.c +226 -0
  78. data/ext/geoip2/libmaxminddb/t/mmdblookup_t.pl +158 -0
  79. data/ext/geoip2/libmaxminddb/t/no_map_get_value_t.c +32 -0
  80. data/ext/geoip2/libmaxminddb/t/read_node_t.c +157 -0
  81. data/ext/geoip2/libmaxminddb/t/threads_t.c +196 -0
  82. data/ext/geoip2/libmaxminddb/t/version_t.c +10 -0
  83. data/geoip2_c.gemspec +1 -1
  84. data/lib/geoip2/version.rb +1 -1
  85. metadata +82 -1
@@ -0,0 +1,23 @@
1
+ include $(top_srcdir)/common.mk
2
+
3
+ all-local:
4
+ cd libtap && $(MAKE) $(AM_MAKEFLAGS) all
5
+ clean-local:
6
+ cd libtap && $(MAKE) $(AM_MAKEFLAGS) clean
7
+
8
+ AM_LDFLAGS = $(top_builddir)/src/libmaxminddb.la
9
+
10
+ noinst_LTLIBRARIES = libmmdbtest.la
11
+ libmmdbtest_la_SOURCES = maxminddb_test_helper.c
12
+
13
+ check_PROGRAMS = \
14
+ bad_pointers_t bad_databases_t basic_lookup_t data_entry_list_t \
15
+ data_types_t dump_t get_value_t get_value_pointer_bug_t \
16
+ ipv4_start_cache_t ipv6_lookup_in_ipv4_t metadata_t metadata_pointers_t \
17
+ no_map_get_value_t read_node_t threads_t version_t
18
+
19
+ threads_t_CFLAGS = $(CFLAGS) -pthread
20
+
21
+ TESTS = $(check_PROGRAMS) compile_c++_t.pl mmdblookup_t.pl
22
+
23
+ LDADD = libmmdbtest.la libtap/libtap.a
@@ -0,0 +1,66 @@
1
+ // This test currently does not work on Windows as nftw is
2
+ // not available.
3
+ #define _XOPEN_SOURCE 500
4
+ #include <ftw.h>
5
+
6
+ #include <libgen.h>
7
+ #include <unistd.h>
8
+
9
+ #include "maxminddb_test_helper.h"
10
+
11
+ int test_read(const char *path, const struct stat *UNUSED(
12
+ sbuf), int flags, struct FTW *UNUSED(ftw))
13
+ {
14
+ // Check if path is a regular file)
15
+ if (flags != FTW_F) {
16
+ return 0;
17
+ }
18
+
19
+ MMDB_s *mmdb = (MMDB_s *)calloc(1, sizeof(MMDB_s));
20
+
21
+ if (NULL == mmdb) {
22
+ BAIL_OUT("could not allocate memory for our MMDB_s struct");
23
+ }
24
+
25
+ int status = MMDB_open(path, MMDB_MODE_MMAP, mmdb);
26
+
27
+ if (status != MMDB_SUCCESS) {
28
+ ok(1, "received error when opening %s", path);
29
+ return 0;
30
+ }
31
+
32
+ int gai_error, mmdb_error;
33
+ MMDB_lookup_string(mmdb, "1.1.1.1", &gai_error, &mmdb_error);
34
+ if (gai_error != 0) {
35
+ BAIL_OUT("could not parse IP address");
36
+ }
37
+
38
+ cmp_ok(mmdb_error, "!=", MMDB_SUCCESS, "opening %s returned an error",
39
+ path);
40
+
41
+ MMDB_close(mmdb);
42
+ free(mmdb);
43
+ return 0;
44
+ }
45
+
46
+ int main(void)
47
+ {
48
+ char *test_db_dir;
49
+ #ifdef _WIN32
50
+ test_db_dir = "./t/maxmind-db/bad-data";
51
+ #else
52
+ char cwd[500];
53
+ char *UNUSED(tmp) = getcwd(cwd, 500);
54
+
55
+ if (strcmp(basename(cwd), "t") == 0) {
56
+ test_db_dir = "./maxmind-db/bad-data";
57
+ } else {
58
+ test_db_dir = "./t/maxmind-db/bad-data";
59
+ }
60
+ #endif
61
+ plan(NO_PLAN);
62
+ if (nftw(test_db_dir, test_read, 10, FTW_PHYS) != 0) {
63
+ BAIL_OUT("nftw failed");
64
+ }
65
+ done_testing();
66
+ }
@@ -0,0 +1,53 @@
1
+ #include "maxminddb_test_helper.h"
2
+
3
+ void run_tests(int mode, const char *mode_desc)
4
+ {
5
+ const char *filename = "MaxMind-DB-test-broken-pointers-24.mmdb";
6
+ const char *path = test_database_path(filename);
7
+ MMDB_s *mmdb = open_ok(path, mode, mode_desc);
8
+ free((void *)path);
9
+
10
+ {
11
+ const char *ip = "1.1.1.16";
12
+ MMDB_lookup_result_s result =
13
+ lookup_string_ok(mmdb, ip, filename, mode_desc);
14
+
15
+ MMDB_entry_data_s entry_data;
16
+ int status = MMDB_get_value(&result.entry, &entry_data, NULL);
17
+
18
+ cmp_ok(
19
+ status, "==", MMDB_INVALID_DATA_ERROR,
20
+ "MMDB_get_value returns MMDB_INVALID_DATA_ERROR for bad pointer in data section");
21
+
22
+ MMDB_entry_data_list_s *entry_data_list;
23
+ status = MMDB_get_entry_data_list(&result.entry, &entry_data_list);
24
+
25
+ cmp_ok(
26
+ status, "==", MMDB_INVALID_DATA_ERROR,
27
+ "MMDB_get_entry_data_list returns MMDB_INVALID_DATA_ERROR for bad pointer in data section");
28
+
29
+ MMDB_free_entry_data_list(entry_data_list);
30
+ }
31
+
32
+ {
33
+ const char *ip = "1.1.1.32";
34
+
35
+ int gai_error, mmdb_error;
36
+ MMDB_lookup_result_s UNUSED(result) =
37
+ MMDB_lookup_string(mmdb, ip, &gai_error, &mmdb_error);
38
+
39
+ cmp_ok(
40
+ mmdb_error, "==", MMDB_CORRUPT_SEARCH_TREE_ERROR,
41
+ "MMDB_lookup_string sets mmdb_error to MMDB_CORRUPT_SEARCH_TREE_ERROR when a search tree record points outside the data section");
42
+ }
43
+
44
+ MMDB_close(mmdb);
45
+ free(mmdb);
46
+ }
47
+
48
+ int main(void)
49
+ {
50
+ plan(NO_PLAN);
51
+ for_all_modes(&run_tests);
52
+ done_testing();
53
+ }
@@ -0,0 +1,172 @@
1
+ #include "maxminddb_test_helper.h"
2
+
3
+ /* These globals are gross but it's the easiest way to mix calling
4
+ * for_all_modes() and for_all_record_sizes() */
5
+ static int Current_Mode;
6
+ static const char *Current_Mode_Description;
7
+
8
+ void test_one_result(MMDB_s *mmdb, MMDB_lookup_result_s result,
9
+ const char *ip, const char *expect,
10
+ const char *function, const char *filename,
11
+ const char *mode_desc)
12
+ {
13
+ int is_ok = ok(result.found_entry,
14
+ "got a result for an IP in the database - %s - %s - %s - %s",
15
+ function, ip, filename, mode_desc);
16
+
17
+ if (!is_ok) {
18
+ return;
19
+ }
20
+
21
+ MMDB_entry_data_s data =
22
+ data_ok(&result, MMDB_DATA_TYPE_UTF8_STRING, "result{ip}", "ip", NULL);
23
+
24
+ char *string = strndup(data.utf8_string, data.data_size);
25
+
26
+ char *real_expect;
27
+ if (mmdb->metadata.ip_version == 4 || strncmp(expect, "::", 2) == 0) {
28
+ real_expect = strndup(expect, strlen(expect));
29
+ } else {
30
+ // When looking up IPv4 addresses in a mixed DB the result will be
31
+ // something like "::1.2.3.4", not just "1.2.3.4".
32
+ int maxlen = strlen(expect) + 3;
33
+ real_expect = malloc(maxlen);
34
+ snprintf(real_expect, maxlen, "::%s", expect);
35
+ }
36
+
37
+ is(string, real_expect,
38
+ "found expected result for ip key - %s - %s - %s - %s", function, ip,
39
+ filename, mode_desc);
40
+
41
+ free(real_expect);
42
+ free(string);
43
+ }
44
+
45
+ void test_one_ip(MMDB_s *mmdb, const char *ip, const char *expect,
46
+ const char *filename, const char *mode_desc)
47
+ {
48
+ MMDB_lookup_result_s result =
49
+ lookup_string_ok(mmdb, ip, filename, mode_desc);
50
+
51
+ test_one_result(mmdb, result, ip, expect, "MMDB_lookup_string", filename,
52
+ mode_desc);
53
+
54
+ result = lookup_sockaddr_ok(mmdb, ip, filename, mode_desc);
55
+ test_one_result(mmdb, result, ip, expect, "MMDB_lookup_addrinfo", filename,
56
+ mode_desc);
57
+ }
58
+
59
+ void run_ipX_tests(const char *filename, const char **missing_ips,
60
+ int missing_ips_length, const char *pairs[][2],
61
+ int pairs_rows)
62
+ {
63
+ const char *path = test_database_path(filename);
64
+ int mode = Current_Mode;
65
+ const char *mode_desc = Current_Mode_Description;
66
+
67
+ MMDB_s *mmdb = open_ok(path, mode, mode_desc);
68
+ free((void *)path);
69
+
70
+ char desc_suffix[500];
71
+ snprintf(desc_suffix, 500, "%s - %s", filename, mode_desc);
72
+
73
+ for (int i = 0; i < missing_ips_length; i++) {
74
+ const char *ip = missing_ips[i];
75
+
76
+ MMDB_lookup_result_s result =
77
+ lookup_string_ok(mmdb, ip, filename, mode_desc);
78
+
79
+ ok(
80
+ !result.found_entry,
81
+ "no result entry struct returned for IP address not in the database (string lookup) - %s - %s - %s",
82
+ ip, filename, mode_desc);
83
+
84
+ result = lookup_sockaddr_ok(mmdb, ip, filename, mode_desc);
85
+
86
+ ok(
87
+ !result.found_entry,
88
+ "no result entry struct returned for IP address not in the database (ipv4 lookup) - %s - %s - %s",
89
+ ip, filename, mode_desc);
90
+ }
91
+
92
+ for (int i = 0; i < pairs_rows; i += 1) {
93
+ const char *ip_to_lookup = pairs[i][0];
94
+ const char *expect = pairs[i][1];
95
+
96
+ test_one_ip(mmdb, ip_to_lookup, expect, filename, mode_desc);
97
+ }
98
+
99
+ MMDB_close(mmdb);
100
+ free(mmdb);
101
+ }
102
+
103
+ void run_ipv4_tests(int UNUSED(
104
+ record_size), const char *filename, const char *UNUSED(
105
+ ignored))
106
+ {
107
+ const char *pairs[9][2] = {
108
+ { "1.1.1.1", "1.1.1.1" },
109
+ { "1.1.1.2", "1.1.1.2" },
110
+ { "1.1.1.3", "1.1.1.2" },
111
+ { "1.1.1.7", "1.1.1.4" },
112
+ { "1.1.1.9", "1.1.1.8" },
113
+ { "1.1.1.15", "1.1.1.8" },
114
+ { "1.1.1.17", "1.1.1.16" },
115
+ { "1.1.1.31", "1.1.1.16" },
116
+ { "1.1.1.32", "1.1.1.32" },
117
+ };
118
+
119
+ const char *missing[1] = { "2.3.4.5" };
120
+ run_ipX_tests(filename, missing, 1, pairs, 9);
121
+ }
122
+
123
+ void run_ipv6_tests(int UNUSED(
124
+ record_size), const char *filename, const char *UNUSED(
125
+ ignored))
126
+ {
127
+ const char *pairs[9][2] = {
128
+ { "::1:ffff:ffff", "::1:ffff:ffff" },
129
+ { "::2:0:0", "::2:0:0" },
130
+ { "::2:0:1a", "::2:0:0" },
131
+ { "::2:0:40", "::2:0:40" },
132
+ { "::2:0:4f", "::2:0:40" },
133
+ { "::2:0:50", "::2:0:50" },
134
+ { "::2:0:52", "::2:0:50" },
135
+ { "::2:0:58", "::2:0:58" },
136
+ { "::2:0:59", "::2:0:58" },
137
+ };
138
+
139
+ const char *missing[2] = { "2.3.4.5", "::abcd" };
140
+ run_ipX_tests(filename, missing, 2, pairs, 9);
141
+ }
142
+
143
+ void all_record_sizes(int mode, const char *description)
144
+ {
145
+ const char *ipv4_filename_fmts[] = {
146
+ "MaxMind-DB-test-ipv4-%i.mmdb",
147
+ "MaxMind-DB-test-mixed-%i.mmdb"
148
+ };
149
+
150
+ Current_Mode = mode;
151
+ Current_Mode_Description = description;
152
+
153
+ for (int i = 0; i < 2; i++) {
154
+ for_all_record_sizes(ipv4_filename_fmts[i], &run_ipv4_tests);
155
+ }
156
+
157
+ const char *ipv6_filename_fmts[] = {
158
+ "MaxMind-DB-test-ipv6-%i.mmdb",
159
+ "MaxMind-DB-test-mixed-%i.mmdb"
160
+ };
161
+
162
+ for (int i = 0; i < 2; i++) {
163
+ for_all_record_sizes(ipv6_filename_fmts[i], &run_ipv6_tests);
164
+ }
165
+ }
166
+
167
+ int main(void)
168
+ {
169
+ plan(NO_PLAN);
170
+ for_all_modes(&all_record_sizes);
171
+ done_testing();
172
+ }
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env perl
2
+
3
+ use strict;
4
+ use warnings;
5
+
6
+ use Cwd qw( abs_path );
7
+ use FindBin qw( $Bin );
8
+
9
+ eval <<'EOF';
10
+ use Test::More 0.88;
11
+ use File::Temp qw( tempdir );
12
+ use IPC::Run3 qw( run3 );
13
+ EOF
14
+
15
+ if ($@) {
16
+ print
17
+ "1..0 # skip all tests skipped - these tests need the Test::More 0.88, File::Temp, and IPC::Run3 modules:\n";
18
+ print "$@";
19
+ exit 0;
20
+ }
21
+
22
+ my $test_db = "$Bin/maxmind-db/test-data/GeoIP2-City-Test.mmdb";
23
+
24
+ my $cpp_code = <<"EOF";
25
+ #include <maxminddb.h>
26
+
27
+ int main(int argc, char *argv[])
28
+ {
29
+ const char *fname = "$test_db";
30
+ MMDB_s mmdb;
31
+ return MMDB_open(fname, MMDB_MODE_MMAP, &mmdb);
32
+ }
33
+ EOF
34
+
35
+ my $tempdir = tempdir(CLEANUP => 1 );
36
+
37
+ my $file = "$tempdir/open.cpp";
38
+ open my $fh, '>', $file or die $!;
39
+ print {$fh} $cpp_code or die $!;
40
+ close $fh or die $!;
41
+
42
+ my $exe = "$tempdir/open";
43
+
44
+ my $include_dir = abs_path("$Bin/../include");
45
+ my $lib_dir = abs_path("$Bin/../src/.libs");
46
+
47
+ my $cxx = $ENV{CXX} || 'c++';
48
+ _test_cmd(
49
+ [ $cxx, $file, "-I$include_dir", "-L$lib_dir", "-lmaxminddb", "-o$exe" ],
50
+ qr/^$/,
51
+ q{},
52
+ 0,
53
+ 'compile C++ program which links against libmaxminddb',
54
+ );
55
+
56
+ # DYLD_LIBRARY_PATH is for Mac OS X
57
+ $ENV{LD_LIBRARY_PATH} = $ENV{DYLD_LIBRARY_PATH} = $lib_dir;
58
+
59
+ _test_cmd(
60
+ [$exe],
61
+ qr/^$/,
62
+ q{},
63
+ 0,
64
+ 'compiled C++ program executes without errors'
65
+ );
66
+
67
+ done_testing();
68
+
69
+ sub _test_cmd {
70
+ my $cmd = shift;
71
+ my $expect_stdout = shift;
72
+ my $expect_stderr = shift;
73
+ my $expect_status = shift;
74
+ my $desc = shift;
75
+
76
+ my $stdout;
77
+ my $stderr;
78
+ run3(
79
+ $cmd,
80
+ \undef,
81
+ \$stdout,
82
+ \$stderr,
83
+ );
84
+
85
+ my $exit_status = $? >> 8;
86
+
87
+ # We don't need to retest that the help output shows up for all errors
88
+ if ( defined $expect_stdout ) {
89
+ like(
90
+ $stdout,
91
+ $expect_stdout,
92
+ "stdout for @{$cmd}"
93
+ );
94
+ }
95
+
96
+ if ( ref $expect_stderr ) {
97
+ like( $stderr, $expect_stderr, "stderr for @{$cmd}" );
98
+ }
99
+ else {
100
+ is( $stderr, $expect_stderr, "stderr for @{$cmd}" );
101
+ }
102
+
103
+ is(
104
+ $exit_status, $expect_status,
105
+ "exit status was $expect_status for @{$cmd}"
106
+ );
107
+ }