geoip2_c 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ }