Add cmdline tests
diff --git a/include/grpc/support/cmdline.h b/include/grpc/support/cmdline.h
index 028dac2..3058cf9 100644
--- a/include/grpc/support/cmdline.h
+++ b/include/grpc/support/cmdline.h
@@ -83,8 +83,12 @@
 void gpr_cmdline_on_extra_arg(
     gpr_cmdline *cl, const char *name, const char *help,
     void (*on_extra_arg)(void *user_data, const char *arg), void *user_data);
-/* Parse the command line */
-void gpr_cmdline_parse(gpr_cmdline *cl, int argc, char **argv);
+/* Enable surviving failure: default behavior is to exit the process */
+void gpr_cmdline_set_survive_failure(gpr_cmdline *cl);
+/* Parse the command line; returns 1 on success, on failure either dies
+   (by default) or returns 0 if gpr_cmdline_set_survive_failure() has been
+   called */
+int gpr_cmdline_parse(gpr_cmdline *cl, int argc, char **argv);
 /* Destroy the parser */
 void gpr_cmdline_destroy(gpr_cmdline *cl);
 /* Get a string describing usage */
diff --git a/src/core/support/cmdline.c b/src/core/support/cmdline.c
index 87f60bc..b517f30 100644
--- a/src/core/support/cmdline.c
+++ b/src/core/support/cmdline.c
@@ -62,11 +62,13 @@
   void (*extra_arg)(void *user_data, const char *arg);
   void *extra_arg_user_data;
 
-  void (*state)(gpr_cmdline *cl, char *arg);
+  int (*state)(gpr_cmdline *cl, char *arg);
   arg *cur_arg;
+
+  int survive_failure;
 };
 
-static void normal_state(gpr_cmdline *cl, char *arg);
+static int normal_state(gpr_cmdline *cl, char *arg);
 
 gpr_cmdline *gpr_cmdline_create(const char *description) {
   gpr_cmdline *cl = gpr_malloc(sizeof(gpr_cmdline));
@@ -78,6 +80,10 @@
   return cl;
 }
 
+void gpr_cmdline_set_survive_failure(gpr_cmdline *cl) {
+  cl->survive_failure = 1;
+}
+
 void gpr_cmdline_destroy(gpr_cmdline *cl) {
   while (cl->args) {
     arg *a = cl->args;
@@ -185,16 +191,22 @@
   return tmp;
 }
 
-static void print_usage_and_die(gpr_cmdline *cl) {
+static int print_usage_and_die(gpr_cmdline *cl) {
   char *usage = gpr_cmdline_usage_string(cl, cl->argv0);
   fprintf(stderr, "%s", usage);
   gpr_free(usage);
-  exit(1);
+  if (!cl->survive_failure) {
+    exit(1);
+  }
+  return 0;
 }
 
-static void extra_state(gpr_cmdline *cl, char *str) {
-  if (!cl->extra_arg) print_usage_and_die(cl);
+static int extra_state(gpr_cmdline *cl, char *str) {
+  if (!cl->extra_arg) {
+    return print_usage_and_die(cl);
+  }
   cl->extra_arg(cl->extra_arg_user_data, str);
+  return 1;
 }
 
 static arg *find_arg(gpr_cmdline *cl, char *name) {
@@ -208,13 +220,13 @@
 
   if (!a) {
     fprintf(stderr, "Unknown argument: %s\n", name);
-    print_usage_and_die(cl);
+    return NULL;
   }
 
   return a;
 }
 
-static void value_state(gpr_cmdline *cl, char *str) {
+static int value_state(gpr_cmdline *cl, char *str) {
   long intval;
   char *end;
 
@@ -226,7 +238,7 @@
       if (*end || intval < INT_MIN || intval > INT_MAX) {
         fprintf(stderr, "expected integer, got '%s' for %s\n", str,
                 cl->cur_arg->name);
-        print_usage_and_die(cl);
+        return print_usage_and_die(cl);
       }
       *(int *)cl->cur_arg->value = (int)intval;
       break;
@@ -238,7 +250,7 @@
       } else {
         fprintf(stderr, "expected boolean, got '%s' for %s\n", str,
                 cl->cur_arg->name);
-        print_usage_and_die(cl);
+        return print_usage_and_die(cl);
       }
       break;
     case ARGTYPE_STRING:
@@ -247,16 +259,18 @@
   }
 
   cl->state = normal_state;
+  return 1;
 }
 
-static void normal_state(gpr_cmdline *cl, char *str) {
+static int normal_state(gpr_cmdline *cl, char *str) {
   char *eq = NULL;
   char *tmp = NULL;
   char *arg_name = NULL;
+  int r = 1;
 
   if (0 == strcmp(str, "-help") || 0 == strcmp(str, "--help") ||
       0 == strcmp(str, "-h")) {
-    print_usage_and_die(cl);
+    return print_usage_and_die(cl);
   }
 
   cl->cur_arg = NULL;
@@ -266,7 +280,7 @@
       if (str[2] == 0) {
         /* handle '--' to move to just extra args */
         cl->state = extra_state;
-        return;
+        return 1;
       }
       str += 2;
     } else {
@@ -277,12 +291,15 @@
       /* str is of the form '--no-foo' - it's a flag disable */
       str += 3;
       cl->cur_arg = find_arg(cl, str);
+      if (cl->cur_arg == NULL) {
+        return print_usage_and_die(cl);
+      }
       if (cl->cur_arg->type != ARGTYPE_BOOL) {
         fprintf(stderr, "%s is not a flag argument\n", str);
-        print_usage_and_die(cl);
+        return print_usage_and_die(cl);
       }
       *(int *)cl->cur_arg->value = 0;
-      return; /* early out */
+      return 1; /* early out */
     }
     eq = strchr(str, '=');
     if (eq != NULL) {
@@ -294,9 +311,12 @@
       arg_name = str;
     }
     cl->cur_arg = find_arg(cl, arg_name);
+    if (cl->cur_arg == NULL) {
+      return print_usage_and_die(cl);
+    }
     if (eq != NULL) {
       /* str was of the type --foo=value, parse the value */
-      value_state(cl, eq + 1);
+      r = value_state(cl, eq + 1);
     } else if (cl->cur_arg->type != ARGTYPE_BOOL) {
       /* flag types don't have a '--foo value' variant, other types do */
       cl->state = value_state;
@@ -305,19 +325,23 @@
       *(int *)cl->cur_arg->value = 1;
     }
   } else {
-    extra_state(cl, str);
+    r = extra_state(cl, str);
   }
 
   gpr_free(tmp);
+  return r;
 }
 
-void gpr_cmdline_parse(gpr_cmdline *cl, int argc, char **argv) {
+int gpr_cmdline_parse(gpr_cmdline *cl, int argc, char **argv) {
   int i;
 
   GPR_ASSERT(argc >= 1);
   cl->argv0 = argv[0];
 
   for (i = 1; i < argc; i++) {
-    cl->state(cl, argv[i]);
+    if (!cl->state(cl, argv[i])) {
+      return 0;
+    }
   }
+  return 1;
 }
diff --git a/test/core/support/cmdline_test.c b/test/core/support/cmdline_test.c
index 1c77c15..4730fcc 100644
--- a/test/core/support/cmdline_test.c
+++ b/test/core/support/cmdline_test.c
@@ -40,7 +40,7 @@
 #include <grpc/support/useful.h>
 #include "test/core/util/test_config.h"
 
-#define LOG_TEST() gpr_log(GPR_INFO, "%s", __FILE__)
+#define LOG_TEST() gpr_log(GPR_INFO, "test at %s:%d", __FILE__, __LINE__)
 
 static void test_simple_int(void) {
   int x = 1;
@@ -273,6 +273,44 @@
   gpr_cmdline_destroy(cl);
 }
 
+static void extra_arg_cb(void *user_data, const char *arg) {
+  int *count = user_data;
+  GPR_ASSERT(arg != NULL);
+  GPR_ASSERT(strlen(arg) == 1);
+  GPR_ASSERT(arg[0] == 'a' + *count);
+  ++*count;
+}
+
+static void test_extra(void) {
+  gpr_cmdline *cl;
+  int count = 0;
+  char *args[] = {(char *)__FILE__, "a", "b", "c"};
+
+  LOG_TEST();
+
+  cl = gpr_cmdline_create(NULL);
+  gpr_cmdline_on_extra_arg(cl, "file", "filenames to process", extra_arg_cb,
+                           &count);
+  gpr_cmdline_parse(cl, GPR_ARRAY_SIZE(args), args);
+  GPR_ASSERT(count == 3);
+  gpr_cmdline_destroy(cl);
+}
+
+static void test_extra_dashdash(void) {
+  gpr_cmdline *cl;
+  int count = 0;
+  char *args[] = {(char *)__FILE__, "--", "a", "b", "c"};
+
+  LOG_TEST();
+
+  cl = gpr_cmdline_create(NULL);
+  gpr_cmdline_on_extra_arg(cl, "file", "filenames to process", extra_arg_cb,
+                           &count);
+  gpr_cmdline_parse(cl, GPR_ARRAY_SIZE(args), args);
+  GPR_ASSERT(count == 3);
+  gpr_cmdline_destroy(cl);
+}
+
 static void test_usage(void) {
   gpr_cmdline *cl;
   char *usage;
@@ -281,17 +319,151 @@
   int x = 0;
   int flag = 2;
 
+  LOG_TEST();
+
   cl = gpr_cmdline_create(NULL);
   gpr_cmdline_add_string(cl, "str", NULL, &str);
   gpr_cmdline_add_int(cl, "x", NULL, &x);
   gpr_cmdline_add_flag(cl, "flag", NULL, &flag);
+  gpr_cmdline_on_extra_arg(cl, "file", "filenames to process", extra_arg_cb,
+                           NULL);
 
   usage = gpr_cmdline_usage_string(cl, "test");
-  GPR_ASSERT(
-      0 == strcmp(usage,
-                  "Usage: test [--str=string] [--x=int] [--flag|--no-flag]\n"));
+  GPR_ASSERT(0 == strcmp(usage,
+                         "Usage: test [--str=string] [--x=int] "
+                         "[--flag|--no-flag] [file...]\n"));
   gpr_free(usage);
 
+  usage = gpr_cmdline_usage_string(cl, "/foo/test");
+  GPR_ASSERT(0 == strcmp(usage,
+                         "Usage: test [--str=string] [--x=int] "
+                         "[--flag|--no-flag] [file...]\n"));
+  gpr_free(usage);
+
+  gpr_cmdline_destroy(cl);
+}
+
+static void test_help(void) {
+  gpr_cmdline *cl;
+
+  char *str = NULL;
+  int x = 0;
+  int flag = 2;
+
+  char *help[] = {(char *)__FILE__, "-h"};
+
+  LOG_TEST();
+
+  cl = gpr_cmdline_create(NULL);
+  gpr_cmdline_set_survive_failure(cl);
+  gpr_cmdline_add_string(cl, "str", NULL, &str);
+  gpr_cmdline_add_int(cl, "x", NULL, &x);
+  gpr_cmdline_add_flag(cl, "flag", NULL, &flag);
+  gpr_cmdline_on_extra_arg(cl, "file", "filenames to process", extra_arg_cb,
+                           NULL);
+
+  GPR_ASSERT(0 == gpr_cmdline_parse(cl, GPR_ARRAY_SIZE(help), help));
+
+  gpr_cmdline_destroy(cl);
+}
+
+static void test_badargs1(void) {
+  gpr_cmdline *cl;
+
+  char *str = NULL;
+  int x = 0;
+  int flag = 2;
+
+  char *bad_arg_name[] = {(char *)__FILE__, "--y"};
+
+  LOG_TEST();
+
+  cl = gpr_cmdline_create(NULL);
+  gpr_cmdline_set_survive_failure(cl);
+  gpr_cmdline_add_string(cl, "str", NULL, &str);
+  gpr_cmdline_add_int(cl, "x", NULL, &x);
+  gpr_cmdline_add_flag(cl, "flag", NULL, &flag);
+  gpr_cmdline_on_extra_arg(cl, "file", "filenames to process", extra_arg_cb,
+                           NULL);
+
+  GPR_ASSERT(0 ==
+             gpr_cmdline_parse(cl, GPR_ARRAY_SIZE(bad_arg_name), bad_arg_name));
+
+  gpr_cmdline_destroy(cl);
+}
+
+static void test_badargs2(void) {
+  gpr_cmdline *cl;
+
+  char *str = NULL;
+  int x = 0;
+  int flag = 2;
+
+  char *bad_int_value[] = {(char *)__FILE__, "--x", "henry"};
+
+  LOG_TEST();
+
+  cl = gpr_cmdline_create(NULL);
+  gpr_cmdline_set_survive_failure(cl);
+  gpr_cmdline_add_string(cl, "str", NULL, &str);
+  gpr_cmdline_add_int(cl, "x", NULL, &x);
+  gpr_cmdline_add_flag(cl, "flag", NULL, &flag);
+  gpr_cmdline_on_extra_arg(cl, "file", "filenames to process", extra_arg_cb,
+                           NULL);
+
+  GPR_ASSERT(
+      0 == gpr_cmdline_parse(cl, GPR_ARRAY_SIZE(bad_int_value), bad_int_value));
+
+  gpr_cmdline_destroy(cl);
+}
+
+static void test_badargs3(void) {
+  gpr_cmdline *cl;
+
+  char *str = NULL;
+  int x = 0;
+  int flag = 2;
+
+  char *bad_bool_value[] = {(char *)__FILE__, "--flag=henry"};
+
+  LOG_TEST();
+
+  cl = gpr_cmdline_create(NULL);
+  gpr_cmdline_set_survive_failure(cl);
+  gpr_cmdline_add_string(cl, "str", NULL, &str);
+  gpr_cmdline_add_int(cl, "x", NULL, &x);
+  gpr_cmdline_add_flag(cl, "flag", NULL, &flag);
+  gpr_cmdline_on_extra_arg(cl, "file", "filenames to process", extra_arg_cb,
+                           NULL);
+
+  GPR_ASSERT(0 == gpr_cmdline_parse(cl, GPR_ARRAY_SIZE(bad_bool_value),
+                                    bad_bool_value));
+
+  gpr_cmdline_destroy(cl);
+}
+
+static void test_badargs4(void) {
+  gpr_cmdline *cl;
+
+  char *str = NULL;
+  int x = 0;
+  int flag = 2;
+
+  char *bad_bool_value[] = {(char *)__FILE__, "--no-str"};
+
+  LOG_TEST();
+
+  cl = gpr_cmdline_create(NULL);
+  gpr_cmdline_set_survive_failure(cl);
+  gpr_cmdline_add_string(cl, "str", NULL, &str);
+  gpr_cmdline_add_int(cl, "x", NULL, &x);
+  gpr_cmdline_add_flag(cl, "flag", NULL, &flag);
+  gpr_cmdline_on_extra_arg(cl, "file", "filenames to process", extra_arg_cb,
+                           NULL);
+
+  GPR_ASSERT(0 == gpr_cmdline_parse(cl, GPR_ARRAY_SIZE(bad_bool_value),
+                                    bad_bool_value));
+
   gpr_cmdline_destroy(cl);
 }
 
@@ -312,6 +484,13 @@
   test_flag_val_true();
   test_flag_val_false();
   test_many();
+  test_extra();
+  test_extra_dashdash();
   test_usage();
+  test_help();
+  test_badargs1();
+  test_badargs2();
+  test_badargs3();
+  test_badargs4();
   return 0;
 }