Ben Pfaff wrote:
> I think the API is fine, at first glance.

Thanks for the review.

I'm adding this as a Gnulib module now.
It's functionally equivalent to 'getopt_long', except that it doesn't
support the "W;" feature that probably no application uses [1].

Compared to yesterday's draft, I
  - changed the allocation of the arrays from 'static' to stack-allocated,
  - added a test suite,
  - fixed a bug.

[1] 
https://sourceware.org/git/?p=glibc.git;a=commit;h=7e161bef0bc9d5ea5e6f3dd490ecd5da6f642671


2025-06-27  Bruno Haible  <br...@clisp.org>

        options: Add tests.
        * tests/test-options.c: New file, based on tests/test-getopt_long.h.
        * tests/test-options-prog.c: New file.
        * modules/options-tests: New file.

        options: New module.
        * lib/options.h: New file.
        * lib/options.c: New file.
        * modules/options: New file.
        * doc/glibc-functions/getopt_long.texi: Mention the new module.

>From 0d1077abf990afe45e0bc8d16997f3997e0dac51 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Fri, 27 Jun 2025 15:27:24 +0200
Subject: [PATCH 1/2] options: New module.

* lib/options.h: New file.
* lib/options.c: New file.
* modules/options: New file.
* doc/glibc-functions/getopt_long.texi: Mention the new module.
---
 ChangeLog                            |   8 +
 doc/glibc-functions/getopt_long.texi | 103 ++++++++++++
 lib/options.c                        | 145 ++++++++++++++++
 lib/options.h                        | 238 +++++++++++++++++++++++++++
 modules/options                      |  24 +++
 5 files changed, 518 insertions(+)
 create mode 100644 lib/options.c
 create mode 100644 lib/options.h
 create mode 100644 modules/options

diff --git a/ChangeLog b/ChangeLog
index b83e1a74a8..8e4355a079 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2025-06-27  Bruno Haible  <br...@clisp.org>
+
+	options: New module.
+	* lib/options.h: New file.
+	* lib/options.c: New file.
+	* modules/options: New file.
+	* doc/glibc-functions/getopt_long.texi: Mention the new module.
+
 2025-06-26  Bruno Haible  <br...@clisp.org>
 
 	kwset: Improve header file.
diff --git a/doc/glibc-functions/getopt_long.texi b/doc/glibc-functions/getopt_long.texi
index b0220d1c92..32b5fb9352 100644
--- a/doc/glibc-functions/getopt_long.texi
+++ b/doc/glibc-functions/getopt_long.texi
@@ -62,3 +62,106 @@
 Portability problems not fixed by Gnulib:
 @itemize
 @end itemize
+
+@mindex options
+Gnulib provides also a module @code{options}, that
+fixes the following shortcomings of the @code{getopt_long} API.
+
+These shortcomings are best illustrated with an example:
+
+@example
+static struct option const long_options[] =
+@{
+  @{ "width", required_argument, NULL, 'w' @},
+  @{ "help", no_argument, &show_help, 1 @},
+  @{ "version", no_argument, &show_version, 1 @},
+  @{ NULL, 0, NULL, 0 @}
+@};
+
+while ((optchar = getopt_long (argc, argv, "w:xhV", long_options, NULL))
+       != -1)
+  switch (optchar)
+    @{
+    case '\0':           /* Long option with flag != NULL.  */
+      break;
+    case 'w':
+      set_width (optarg);
+      break;
+    case 'x':
+      do_x = true;
+      break;
+    case 'h':
+      show_help = 1;     /* Action code duplication!  */
+      break;
+    case 'V':
+      show_version = 1;  /* Action code duplication!  */
+      break;
+    default:
+      usage (EXIT_FAILURE);
+    @}
+@end example
+
+@itemize
+@item
+The information whether an option takes a required vs.@: optional argument
+needs to be specified twice:
+in the @code{option[]} array for the long option
+and in the string argument for the short option.
+It is too easy to forget to
+add the @code{":"} or @code{"::"} part in the string argument
+and thus get inconsistent behaviour
+between the long option and the short option.
+@item
+This information needs to be specified twice, but in different ways:
+@multitable @columnfractions .3 .3
+@headitem In the array @tab In the string
+@item @code{no_argument} @tab @code{""}
+@item @code{required_argument} @tab @code{":"}
+@item @code{optional_argument} @tab @code{"::"}
+@end multitable
+@item
+For an action that merely sets an @code{int}-typed variable to a value,
+you can specify this action in the @code{options[]} array,
+and thus omit the handling in the @code{switch} statement.
+But this works only for options that are
+long options without a corresponding short option.
+As soon as the option has a corresponding short option,
+you do need to handle it in the @code{switch} statement.
+Here again, there is the opportunity for
+inconsistent behaviour between the long option and the short option.
+@item
+The @code{val} field in a @code{struct option} has different meanings,
+depending on another field:
+If the @code{flag} field is non-NULL,
+@code{val} is a value to be stored in a variable.
+If the @code{flag} field is NULL,
+@code{val} is a key to be returned from @code{getopt_long}
+and subject to the @code{switch} statement.
+@item
+The handling of non-option arguments is specified by
+prepending a certain character (@samp{+} or @samp{-}) to the string argument.
+This is not one of the usual ways to specify things in an API.
+The conventional way in an API is
+an argument of @code{enum} type, or a flags word.
+@item
+The handling of errors consists of two independent flags,
+and each of the flags has to be specified in a different way:
+one flag is specified by
+prepending a certain character (@samp{:}) to the string argument;
+the other flag is specified through the global variable @code{opterr}.
+@item
+The @code{struct option} is a misnomer:
+It cannot encode short options.
+Therefore, it would have better been called @code{struct long_option}.
+@item
+The @code{getopt_long} function is expected to
+receive the same arguments in each call, in the @code{while} loop.
+The effects are undefined if you don't follow this (unwritten!) constraint.
+@item
+The fifth argument to @code{getopt_long}, @var{indexptr}, is redundant, because
+when the @code{flag} is non-NULL,
+the @code{switch} statement does not need to handle the option,
+and when the @code{flag} is NULL,
+@code{getopt_long} returns the value of @code{val},
+as a way to identify which option was seen.
+@end itemize
diff --git a/lib/options.c b/lib/options.c
new file mode 100644
index 0000000000..f981613bf8
--- /dev/null
+++ b/lib/options.c
@@ -0,0 +1,145 @@
+/* Parsing program options.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published
+   by the Free Software Foundation, either version 3 of the License,
+   or (at your option) any later version.
+
+   This file is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "options.h"
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+/* State for communicating between _gl_start_options and get_next_option.  */
+static struct
+  {
+    int argc;
+    char **argv;
+    const struct program_option *options;
+    size_t n_options;
+    struct option *long_options;
+    char *short_options;
+  }
+state;
+
+void
+_gl_start_options (int argc, char **argv,
+                   const struct program_option *options, size_t n_options,
+                   struct option *long_options, char *short_options,
+                   enum non_option_handling nonopt_handling,
+                   unsigned int error_handling)
+{
+  /* Construct the long_options array.  */
+  {
+    struct option *p = long_options;
+
+    for (size_t i = 0; i < n_options; i++)
+      if (options[i].name != NULL)
+        {
+          if (options[i].key == 0 && options[i].variable == NULL)
+            fprintf (stderr,
+                     "start_options: warning: Option '--%s' has no action. Use the 'key' or the 'variable' field to specify an action.\n",
+                     options[i].name);
+
+          p->name = options[i].name;
+          p->has_arg = options[i].has_arg;
+          if (options[i].key == 0 && options[i].variable != NULL)
+            {
+              p->flag = options[i].variable;
+              p->val = options[i].value;
+            }
+          else
+            {
+              p->flag = NULL;
+              p->val = options[i].key;
+            }
+          p++;
+        }
+
+    p->name = NULL;
+    p->has_arg = 0;
+    p->flag = NULL;
+    p->val = 0;
+    p++;
+    /* Verify that we haven't exceeded its allocated size.  */
+    if (!(p - long_options <= _GL_LONG_OPTIONS_SIZE (n_options)))
+      abort ();
+  }
+
+  /* Construct the short_options string.  */
+  {
+    char *p = short_options;
+
+    if (nonopt_handling == NON_OPTION_TERMINATES_OPTIONS)
+      *p++ = '+';
+    else if (nonopt_handling == PROCESS_NON_OPTIONS)
+      *p++ = '-';
+
+    if (error_handling & OPTIONS_MISSING_IS_COLON)
+      *p++ = ':';
+
+    for (size_t i = 0; i < n_options; i++)
+      if (options[i].key != 0 && options[i].key <= CHAR_MAX)
+        {
+          *p++ = options[i].key;
+          if (options[i].has_arg != no_argument)
+            {
+              *p++ = ':';
+              if (options[i].has_arg == optional_argument)
+                *p++ = ':';
+            }
+        }
+
+    *p++ = '\0';
+    /* Verify that we haven't exceeded its allocated size.  */
+    if (!(p - short_options <= _GL_SHORT_OPTIONS_SIZE (n_options)))
+      abort ();
+  }
+
+  state.argc = argc;
+  state.argv = argv;
+  state.options = options;
+  state.n_options = n_options;
+  state.long_options = long_options;
+  state.short_options = short_options;
+  opterr = (error_handling & OPTIONS_ERRORS_SILENT) == 0;
+}
+
+int
+get_next_option (void)
+{
+  if (state.argv == NULL)
+    {
+      fprintf (stderr, "fatal: start_options has not been invoked\n");
+      abort ();
+    }
+  int ret = getopt_long (state.argc, state.argv,
+                         state.short_options, state.long_options, NULL);
+  if (ret > 1)
+    {
+      const struct program_option *options = state.options;
+      size_t n_options = state.n_options;
+      for (size_t i = 0; i < n_options; i++)
+        if (ret == options[i].key)
+          {
+            if (options[i].variable != NULL)
+              *(options[i].variable) = options[i].value;
+          }
+    }
+  return ret;
+}
diff --git a/lib/options.h b/lib/options.h
new file mode 100644
index 0000000000..18e590804f
--- /dev/null
+++ b/lib/options.h
@@ -0,0 +1,238 @@
+/* Parsing program options.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published
+   by the Free Software Foundation, either version 3 of the License,
+   or (at your option) any later version.
+
+   This file is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>.  */
+
+#ifndef _OPTIONS_H
+#define _OPTIONS_H
+
+/* This file provides a more convenient API to parsing program options,
+   based on GNU getopt_long() and thus compatible with the option parsing
+   conventions for GNU programs
+   <https://www.gnu.org/prep/standards/html_node/Command_002dLine-Interfaces.html>.
+
+   Instead of writing
+
+     static struct option const long_options[] =
+     {
+       { "width", required_argument, NULL, 'w' },
+       { "help", no_argument, &show_help, 1 },
+       { "version", no_argument, &show_version, 1 },
+       { NULL, 0, NULL, 0 }
+     };
+
+     while ((optchar = getopt_long (argc, argv, "w:xhV", long_options, NULL))
+            != -1)
+       switch (optchar)
+         {
+         case '\0':      // Long option with flag != NULL.
+           break;
+         case 'w':
+           set_width (optarg);
+           break;
+         case 'x':
+           do_x = true;
+           break;
+         case 'h':
+           show_help = 1;
+           break;
+         case 'V':
+           show_version = 1;
+           break;
+         default:
+           usage (EXIT_FAILURE);
+         }
+
+   you write
+
+     static struct program_option const options[] =
+     {
+       { "width",   'w', required_argument },
+       { NULL,      'x', no_argument       },
+       { "help",    'h', no_argument,      &show_help, 1 },
+       { "version", 'V', no_argument,      &show_version, 1 },
+     };
+
+     start_options (argc, argv, options, MOVE_OPTIONS_FIRST, 0);
+     while ((optchar = get_next_option ()) != -1)
+       switch (optchar)
+         {
+         case '\0':      // Long option with key == 0.
+           break;
+         case 'w':
+           set_width (optarg);
+           break;
+         case 'x':
+           do_x = true;
+           break;
+         case 'h':
+         case 'V':
+           break;
+         default:
+           usage (EXIT_FAILURE);
+         }
+
+   This API fixes the following shortcomings of the getopt_long() API:
+
+     * The information whether an option takes a required vs. optional argument
+       needs to be specified twice: in the option[] array for the long option
+       and in the string argument for the short option.
+       It is too easy to forget to add the ":" or "::" part in the string
+       argument and thus get inconsistent behaviour between the long option and
+       the short option.
+
+     * This information needs to be specified twice, but in different ways:
+         In the array        In the string
+         ------------        -------------
+         no_argument         ""
+         required_argument   ":"
+         optional_argument   "::"
+
+     * For an action that merely sets an 'int'-typed variable to a value, you
+       can specify this action in the options[] array, and thus omit the
+       handling in the 'switch' statement.  But this works only for options
+       that are long options without a corresponding short option.  As soon
+       as the option has a corresponding short option, you *do* need to handle
+       it in the 'switch' statement.  Here again, there is the opportunity
+       for inconsistent behaviour between the long option and the short option.
+
+     * The 'val' field in a 'struct option' has different meanings, depending
+       on another field:  If the 'flag' field is non-NULL, 'val' is a value to
+       be stored in a variable.  If the 'flag' field is NULL, 'val' is a key
+       to be returned from getopt_long() and subject to the 'switch' statement.
+
+     * The handling of non-option arguments is specified by prepending a
+       certain character ('+' or '-') to the string argument.  This is not
+       one of the usual ways to specify things in an API.  The conventional
+       way in an API is an argument of 'enum' type, or a flags word.
+
+     * The handling of errors consists of two independent flags, and each of
+       the flags has to be specified in a different way: one flag is specified
+       by prepending a certain character (':') to the string argument; the
+       other flag is specified through the global variable 'opterr'.
+
+     * The 'struct option' is a misnomer: It cannot encode short options.
+       Therefore, it would have better been called 'struct long_option'.
+
+     * The getopt_long() function is expected to receive the same arguments in
+       each call, in the 'while' loop.  The effects are undefined if you
+       don't follow this (unwritten!) constraint.
+
+     * The fifth argument to getopt_long(), indexptr, is redundant, because
+       when the 'flag' is non-NULL, the switch statement does not need to
+       handle the option, and when the 'flag' is NULL, getopt_long returns
+       the value of 'val', as a way to identify which option was seen.
+
+   It keeps the following properties the getopt_long() API:
+
+     * The programmer writes in actions directly in the main() function.
+       That is, the actions don't go into separate callback functions
+       (like with argp).  Such callback functions are a fine thing in languages
+       with nested function (and implicit closures), like Lisp and C++, but not
+       in C.
+
+     * The option processing does not require dynamic memory allocation.  That
+       is, you don't need to worry about out-of-memory situations here.
+ */
+
+/* Get no_argument, required_argument, optional_argument.  */
+#include <getopt.h>
+/* Get size_t.  */
+#include <stddef.h>
+/* Get countof.  */
+#include <stdcountof.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Description of an option (long option or short option or both).  */
+struct program_option
+{
+  /* The name of the long option, or NULL for a short-option-only.  */
+  const char *name;
+  /* A key that uniquely identifies the option.
+     If the option has a short option, use the character of that short option.
+     Otherwise, use an arbitrary unique value > CHAR_MAX.
+     Don't use '\0' or '\1' here, because they have a special meaning.  */
+  int key;
+
+  /* One of: no_argument, required_argument, optional_argument.  */
+  int has_arg;
+
+  /* For an action that consists in assigned a value to an 'int'-typed variable,
+     put the variable and the value here.
+     Otherwise, use NULL and 0, or omit these fields.  */
+  int *variable;
+  int value;
+};
+
+/* Handling of non-option arguments.  */
+enum non_option_handling {
+  /* Move options before non-option arguments.
+     This is suitable for most programs.  */
+  MOVE_OPTIONS_FIRST,
+  /* Option processing stops when the first non-option argument is encountered.
+     This is suitable for programs that pass an entire argument list to another
+     program (such as 'env'), or for programs that accept normal arguments that
+     start with '-' (such as 'expr' and 'printf').  */
+  NON_OPTION_TERMINATES_OPTIONS,
+  /* Process non-option arguments as if they were options, associated with the
+     key '\1'.  */
+  PROCESS_NON_OPTIONS
+};
+
+/* Handling of errors: A bit mask.  */
+#define OPTIONS_ERRORS_SILENT     0x1
+#define OPTIONS_MISSING_IS_COLON  0x2
+
+/* Starts the processing of options.  */
+#define start_options(argc, argv, options, nonopt_handling, error_handling)    \
+  /* Allocate room for the long options and short options.  */                 \
+  struct option _gl_long_options[_GL_LONG_OPTIONS_SIZE (countof (options))];   \
+  char _gl_short_options[_GL_SHORT_OPTIONS_SIZE (countof (options))];          \
+  _gl_start_options (argc, argv,                                               \
+                     options, countof (options),                               \
+                     _gl_long_options, _gl_short_options,                      \
+                     nonopt_handling, error_handling)
+#define _GL_LONG_OPTIONS_SIZE(count) ((count) + 1)
+#define _GL_SHORT_OPTIONS_SIZE(count) (3 * (count) + 3)
+
+extern void _gl_start_options (int argc, /*const*/ char **argv,
+                               const struct program_option *options,
+                               size_t n_options,
+                               struct option *long_options, char *short_options,
+                               enum non_option_handling nonopt_handling,
+                               unsigned int error_handling);
+
+/* Processes the next option (or, if PROCESS_NON_OPTIONS was specified,
+   non-option).
+   Requires a prior 'start_options' invocation in the same scope or an outer
+   scope.
+   If the option has a 'variable' field, that variable is assigned the 'value'
+   field.
+   Returns the key of the option, or '\1' when returning a non-option argument.
+   If the option lacks an argument, it returns '?'.
+   If the option is unknown, it returns '?' or (if OPTIONS_MISSING_IS_COLON was
+   specified) ':'.
+   If the processing is terminated, it returns -1.  */
+extern int get_next_option (void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _OPTIONS_H */
diff --git a/modules/options b/modules/options
new file mode 100644
index 0000000000..2e73ce886e
--- /dev/null
+++ b/modules/options
@@ -0,0 +1,24 @@
+Description:
+Parsing program options.
+
+Files:
+lib/options.h
+lib/options.c
+
+Depends-on:
+getopt-gnu
+stdcountof-h
+
+configure.ac:
+
+Makefile.am:
+lib_SOURCES += options.c
+
+Include:
+"options.h"
+
+License:
+GPL
+
+Maintainer:
+all
-- 
2.43.0

>From c6a73ed9b50751f41276c30475e20fa40f87b2be Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Fri, 27 Jun 2025 15:30:23 +0200
Subject: [PATCH 2/2] options: Add tests.

* tests/test-options.c: New file, based on tests/test-getopt_long.h.
* tests/test-options-prog.c: New file.
* modules/options-tests: New file.
---
 ChangeLog                 |    5 +
 modules/options-tests     |   16 +
 tests/test-options-prog.c |  140 ++++
 tests/test-options.c      | 1574 +++++++++++++++++++++++++++++++++++++
 4 files changed, 1735 insertions(+)
 create mode 100644 modules/options-tests
 create mode 100644 tests/test-options-prog.c
 create mode 100644 tests/test-options.c

diff --git a/ChangeLog b/ChangeLog
index 8e4355a079..db31670380 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
 2025-06-27  Bruno Haible  <br...@clisp.org>
 
+	options: Add tests.
+	* tests/test-options.c: New file, based on tests/test-getopt_long.h.
+	* tests/test-options-prog.c: New file.
+	* modules/options-tests: New file.
+
 	options: New module.
 	* lib/options.h: New file.
 	* lib/options.c: New file.
diff --git a/modules/options-tests b/modules/options-tests
new file mode 100644
index 0000000000..23f618d3ee
--- /dev/null
+++ b/modules/options-tests
@@ -0,0 +1,16 @@
+Files:
+tests/test-options.c
+tests/test-options-prog.c
+tests/macros.h
+
+Depends-on:
+bool
+setenv
+unsetenv
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-options
+check_PROGRAMS += test-options
+noinst_PROGRAMS += test-options-prog
diff --git a/tests/test-options-prog.c b/tests/test-options-prog.c
new file mode 100644
index 0000000000..1e2de5db48
--- /dev/null
+++ b/tests/test-options-prog.c
@@ -0,0 +1,140 @@
+/* Test program for program options.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2025.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "options.h"
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+/* Display usage information and exit.  */
+static void
+usage (int status)
+{
+  if (status != EXIT_SUCCESS)
+    fprintf (stderr, "Try 'foo --help' for more information.\n");
+  else
+    {
+      printf ("\
+Usage: foo [OPTION] STRING\n\
+");
+      printf ("\n");
+      printf ("\
+Does something with the STRING.\n");
+      printf ("\n");
+      printf ("\
+Options and arguments:\n");
+      printf ("\
+  -w, --width=WIDTH         specify line width\n");
+      printf ("\
+  STRING                    a string\n");
+      printf ("\
+Informative output:\n");
+      printf ("\
+  -h, --help                display this help and exit\n");
+      printf ("\
+  -V, --version             display version information and exit\n");
+    }
+
+  exit (status);
+}
+
+/* Default values for command line options.  */
+static int show_help = 0;
+static int show_version = 0;
+static int width = 80;
+static bool do_x = false;
+
+static void
+set_width (const char *arg)
+{
+  width = atoi (arg);
+}
+
+int
+main (int argc, char *argv[])
+{
+  /* Parse command line options.  */
+  {
+    static struct program_option const options[] =
+    {
+      { "width",   'w', required_argument },
+      { NULL,      'x', no_argument       },
+      { "help",    'h', no_argument,      &show_help, 1 },
+      { "version", 'V', no_argument,      &show_version, 1 },
+    };
+
+    start_options (argc, argv, options, MOVE_OPTIONS_FIRST, 0);
+    int optchar;
+    while ((optchar = get_next_option ()) != -1)
+      switch (optchar)
+        {
+        case 'w':
+          set_width (optarg);
+          break;
+        case 'x':
+          do_x = true;
+          break;
+        case 'h':
+        case 'V':
+          break;
+        default:
+          usage (EXIT_FAILURE);
+        }
+  }
+
+  /* Version information is requested.  */
+  if (show_version)
+    {
+      printf ("foo 0.0\n");
+      printf ("Copyright (C) %s Free Software Foundation, Inc.\n\
+License GPLv3+: GNU GPL version 3 or later <%s>\n\
+This is free software: you are free to change and redistribute it.\n\
+There is NO WARRANTY, to the extent permitted by law.\n\
+",
+              "2025", "https://gnu.org/licenses/gpl.html";);
+      exit (EXIT_SUCCESS);
+    }
+
+  /* Help is requested.  */
+  if (show_help)
+    usage (EXIT_SUCCESS);
+
+  /* The STRING argument is the first non-option argument.  */
+  if (!(argc - optind >= 1))
+    {
+      fprintf (stderr, "missing argument\n");
+      usage (EXIT_FAILURE);
+    }
+  const char *string = argv[optind++];
+  if (!(argc == optind))
+    {
+      fprintf (stderr, "too many arguments\n");
+      usage (EXIT_FAILURE);
+    }
+
+  printf ("Width:  %d\n", width);
+  printf ("x:      %s\n", do_x ? "true" : "false");
+  printf ("String: %s\n", string);
+
+  exit (EXIT_SUCCESS);
+}
+
diff --git a/tests/test-options.c b/tests/test-options.c
new file mode 100644
index 0000000000..d3e7ac3613
--- /dev/null
+++ b/tests/test-options.c
@@ -0,0 +1,1574 @@
+/* Test of command line argument processing.
+   Copyright (C) 2009-2025 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2009.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "options.h"
+
+#include <string.h>
+
+#include "macros.h"
+
+static int a_seen;
+static int b_seen;
+static int q_seen;
+
+/* Reduce casting, so we can use string literals elsewhere.
+   getopt_long takes an array of char*, but luckily does not modify
+   those elements, so we can pass const char*.  */
+
+static void
+options_loop (enum non_option_handling nonopt_handling,
+              unsigned int error_handling,
+              const char **p_value, const char **q_value,
+              int *non_options_count, const char **non_options,
+              int *unrecognized)
+{
+  int c;
+
+  q_seen = 0;
+  while ((c = get_next_option ()) != -1)
+    {
+      switch (c)
+        {
+        case 0:
+          /* An option with key == 0 was processed.  */
+          if (q_seen)
+            *q_value = optarg;
+          break;
+        case 'a':
+          a_seen++;
+          break;
+        case 'b':
+          b_seen = 1;
+          break;
+        case 'p':
+          *p_value = optarg;
+          break;
+        case 'q':
+          *q_value = optarg;
+          break;
+        case '\1':
+          ASSERT (nonopt_handling == PROCESS_NON_OPTIONS);
+          non_options[(*non_options_count)++] = optarg;
+          break;
+        case ':':
+          ASSERT (error_handling & OPTIONS_MISSING_IS_COLON);
+          FALLTHROUGH;
+        case '?':
+          *unrecognized = optopt;
+          break;
+        default:
+          *unrecognized = c;
+          break;
+        }
+    }
+}
+
+static void
+test_getopt_long (void)
+{
+  int start;
+
+  /* Test disambiguation of options.  */
+  {
+    static const struct program_option options[] =
+      {
+        { "alpha",     'a',  no_argument        },
+        { "beta",      0,    no_argument,       &b_seen, 1 },
+        { "prune",     1000, required_argument  },
+        { "quetsche",  0,    required_argument, &q_seen, 1 },
+        { "xtremely-", 1003, no_argument        },
+        { "xtra",      1001, no_argument        },
+        { "xtreme",    1002, no_argument        },
+        { "xtremely",  1003, no_argument        },
+        { NULL,        'b',  no_argument        },
+      };
+
+    {
+      int argc = 0;
+      const char *argv[10];
+      int c;
+
+      argv[argc++] = "program";
+      argv[argc++] = "--x";
+      argv[argc] = NULL;
+      optind = 1;
+      start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+      c = get_next_option ();
+      ASSERT (c == '?');
+      ASSERT (optopt == 0);
+    }
+    {
+      int argc = 0;
+      const char *argv[10];
+      int c;
+
+      argv[argc++] = "program";
+      argv[argc++] = "--xt";
+      argv[argc] = NULL;
+      optind = 1;
+      start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+      c = get_next_option ();
+      ASSERT (c == '?');
+      ASSERT (optopt == 0);
+    }
+    {
+      int argc = 0;
+      const char *argv[10];
+      int c;
+
+      argv[argc++] = "program";
+      argv[argc++] = "--xtr";
+      argv[argc] = NULL;
+      optind = 1;
+      start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+      c = get_next_option ();
+      ASSERT (c == '?');
+      ASSERT (optopt == 0);
+    }
+    {
+      int argc = 0;
+      const char *argv[10];
+      int c;
+
+      argv[argc++] = "program";
+      argv[argc++] = "--xtra";
+      argv[argc] = NULL;
+      optind = 1;
+      start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+      c = get_next_option ();
+      ASSERT (c == 1001);
+    }
+    {
+      int argc = 0;
+      const char *argv[10];
+      int c;
+
+      argv[argc++] = "program";
+      argv[argc++] = "--xtre";
+      argv[argc] = NULL;
+      optind = 1;
+      start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+      c = get_next_option ();
+      ASSERT (c == '?');
+      ASSERT (optopt == 0);
+    }
+    {
+      int argc = 0;
+      const char *argv[10];
+      int c;
+
+      argv[argc++] = "program";
+      argv[argc++] = "--xtrem";
+      argv[argc] = NULL;
+      optind = 1;
+      start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+      c = get_next_option ();
+      ASSERT (c == '?');
+      ASSERT (optopt == 0);
+    }
+    {
+      int argc = 0;
+      const char *argv[10];
+      int c;
+
+      argv[argc++] = "program";
+      argv[argc++] = "--xtreme";
+      argv[argc] = NULL;
+      optind = 1;
+      start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+      c = get_next_option ();
+      ASSERT (c == 1002);
+    }
+    {
+      int argc = 0;
+      const char *argv[10];
+      int c;
+
+      argv[argc++] = "program";
+      argv[argc++] = "--xtremel";
+      argv[argc] = NULL;
+      optind = 1;
+      start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+      c = get_next_option ();
+      ASSERT (c == 1003);
+    }
+    {
+      int argc = 0;
+      const char *argv[10];
+      int c;
+
+      argv[argc++] = "program";
+      argv[argc++] = "--xtremely";
+      argv[argc] = NULL;
+      optind = 1;
+      start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+      c = get_next_option ();
+      ASSERT (c == 1003);
+    }
+  }
+
+  {
+    static const struct program_option options[] =
+      {
+        { "alpha",     'a',  no_argument        },
+        { "beta",      0,    no_argument,       &b_seen, 1 },
+        { "prune",     1000, required_argument  },
+        { "quetsche",  0,    required_argument, &q_seen, 1 },
+        { "xtremely-", 1003, no_argument        },
+        { "xtra",      1001, no_argument        },
+        { "xtreme",    1002, no_argument        },
+        { "xtremely",  1003, no_argument        },
+        { NULL,        'b',  no_argument        },
+      };
+
+    /* Test processing of boolean short options.  */
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "-a";
+        argv[argc++] = "foo";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 1);
+        ASSERT (b_seen == 0);
+        ASSERT (p_value == NULL);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 2);
+      }
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "-b";
+        argv[argc++] = "-a";
+        argv[argc++] = "foo";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 1);
+        ASSERT (b_seen == 1);
+        ASSERT (p_value == NULL);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 3);
+      }
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "-ba";
+        argv[argc++] = "foo";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 1);
+        ASSERT (b_seen == 1);
+        ASSERT (p_value == NULL);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 2);
+      }
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "-ab";
+        argv[argc++] = "-a";
+        argv[argc++] = "foo";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 2);
+        ASSERT (b_seen == 1);
+        ASSERT (p_value == NULL);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 3);
+      }
+
+    /* Test processing of boolean long options.  */
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "--alpha";
+        argv[argc++] = "foo";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 1);
+        ASSERT (b_seen == 0);
+        ASSERT (p_value == NULL);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 2);
+      }
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "--beta";
+        argv[argc++] = "--alpha";
+        argv[argc++] = "foo";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 1);
+        ASSERT (b_seen == 1);
+        ASSERT (p_value == NULL);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 3);
+      }
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "--alpha";
+        argv[argc++] = "--beta";
+        argv[argc++] = "--alpha";
+        argv[argc++] = "--beta";
+        argv[argc++] = "foo";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 2);
+        ASSERT (b_seen == 1);
+        ASSERT (p_value == NULL);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 5);
+      }
+  }
+
+  {
+    static const struct program_option options[] =
+      {
+        { "alpha",     0,    no_argument        },
+        { "beta",      0,    no_argument,       &b_seen, 1 },
+        { "prune",     'p',  required_argument  },
+        { "quetsche",  0,    required_argument, &q_seen, 1 },
+        { "xtremely-", 1003, no_argument        },
+        { "xtra",      1001, no_argument        },
+        { "xtreme",    1002, no_argument        },
+        { "xtremely",  1003, no_argument        },
+        { NULL,        'q',  required_argument  },
+      };
+    static const struct program_option options_with_ab[] =
+      {
+        { "alpha",     'a',  no_argument        },
+        { "beta",      0,    no_argument,       &b_seen, 1 },
+        { "prune",     'p',  required_argument  },
+        { "quetsche",  0,    required_argument, &q_seen, 1 },
+        { "xtremely-", 1003, no_argument        },
+        { "xtra",      1001, no_argument        },
+        { "xtreme",    1002, no_argument        },
+        { "xtremely",  1003, no_argument        },
+        { NULL,        'q',  required_argument  },
+        { NULL,        'b',  no_argument        },
+      };
+
+    /* Test processing of short options with arguments.  */
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "-pfoo";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 0);
+        ASSERT (b_seen == 0);
+        ASSERT (p_value != NULL && strcmp (p_value, "foo") == 0);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 2);
+      }
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "-p";
+        argv[argc++] = "foo";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 0);
+        ASSERT (b_seen == 0);
+        ASSERT (p_value != NULL && strcmp (p_value, "foo") == 0);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 3);
+      }
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "-ab";
+        argv[argc++] = "-q";
+        argv[argc++] = "baz";
+        argv[argc++] = "-pfoo";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options_with_ab, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 1);
+        ASSERT (b_seen == 1);
+        ASSERT (p_value != NULL && strcmp (p_value, "foo") == 0);
+        ASSERT (q_value != NULL && strcmp (q_value, "baz") == 0);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 5);
+      }
+
+    /* Test processing of long options with arguments.  */
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "--p=foo";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 0);
+        ASSERT (b_seen == 0);
+        ASSERT (p_value != NULL && strcmp (p_value, "foo") == 0);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 2);
+      }
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "--p";
+        argv[argc++] = "foo";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 0);
+        ASSERT (b_seen == 0);
+        ASSERT (p_value != NULL && strcmp (p_value, "foo") == 0);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 3);
+      }
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "-ab";
+        argv[argc++] = "--q";
+        argv[argc++] = "baz";
+        argv[argc++] = "--p=foo";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options_with_ab, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 1);
+        ASSERT (b_seen == 1);
+        ASSERT (p_value != NULL && strcmp (p_value, "foo") == 0);
+        ASSERT (q_value != NULL && strcmp (q_value, "baz") == 0);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 5);
+      }
+  }
+
+  {
+    static const struct program_option options[] =
+      {
+        { "alpha",     0,    no_argument        },
+        { "beta",      0,    no_argument,       &b_seen, 1 },
+        { "prune",     'p',  optional_argument  },
+        { "quetsche",  0,    optional_argument, &q_seen, 1 },
+        { NULL,        'q',  optional_argument  },
+      };
+    static const struct program_option options_with_ab[] =
+      {
+        { "alpha",     'a',  no_argument        },
+        { "beta",      0,    no_argument,       &b_seen, 1 },
+        { "prune",     'p',  optional_argument  },
+        { "quetsche",  0,    optional_argument, &q_seen, 1 },
+        { NULL,        'q',  optional_argument  },
+        { NULL,        'b',  no_argument        },
+      };
+
+    /* Test processing of short options with optional arguments.  */
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "-pfoo";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 0);
+        ASSERT (b_seen == 0);
+        ASSERT (p_value != NULL && strcmp (p_value, "foo") == 0);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 2);
+      }
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "-p";
+        argv[argc++] = "foo";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 0);
+        ASSERT (b_seen == 0);
+        ASSERT (p_value == NULL);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 2);
+      }
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "-p";
+        argv[argc++] = "-a";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options_with_ab, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 1);
+        ASSERT (b_seen == 0);
+        ASSERT (p_value == NULL);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 3);
+      }
+
+    /* Test processing of long options with optional arguments.  */
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "--p=foo";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 0);
+        ASSERT (b_seen == 0);
+        ASSERT (p_value != NULL && strcmp (p_value, "foo") == 0);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 2);
+      }
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "--p";
+        argv[argc++] = "foo";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 0);
+        ASSERT (b_seen == 0);
+        ASSERT (p_value == NULL);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 2);
+      }
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "--p=";
+        argv[argc++] = "foo";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 0);
+        ASSERT (b_seen == 0);
+        ASSERT (p_value != NULL && *p_value == '\0');
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 2);
+      }
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "--p";
+        argv[argc++] = "-a";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options_with_ab, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 1);
+        ASSERT (b_seen == 0);
+        ASSERT (p_value == NULL);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 3);
+      }
+  }
+
+  {
+    static const struct program_option options[] =
+      {
+        { "alpha",     'a',  no_argument        },
+        { "beta",      0,    no_argument,       &b_seen, 1 },
+        { "prune",     'p',  required_argument  },
+        { "quetsche",  0,    required_argument, &q_seen, 1 },
+        { "xtremely-", 1003, no_argument        },
+        { "xtra",      1001, no_argument        },
+        { "xtreme",    1002, no_argument        },
+        { "xtremely",  1003, no_argument        },
+        { NULL,        'q',  required_argument  },
+        { NULL,        'b',  no_argument        },
+      };
+
+    /* Check that invalid options are recognized.  */
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "-p";
+        argv[argc++] = "foo";
+        argv[argc++] = "-x";
+        argv[argc++] = "-a";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 1);
+        ASSERT (b_seen == 0);
+        ASSERT (p_value != NULL && strcmp (p_value, "foo") == 0);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 'x');
+        ASSERT (optind == 5);
+      }
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "-p";
+        argv[argc++] = "foo";
+        argv[argc++] = "-:";
+        argv[argc++] = "-a";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 1);
+        ASSERT (b_seen == 0);
+        ASSERT (p_value != NULL && strcmp (p_value, "foo") == 0);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == ':');
+        ASSERT (optind == 5);
+      }
+
+    /* Check that unexpected arguments are recognized.  */
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "-p";
+        argv[argc++] = "foo";
+        argv[argc++] = "--a=";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 0);
+        ASSERT (b_seen == 0);
+        ASSERT (p_value != NULL && strcmp (p_value, "foo") == 0);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 'a');
+        ASSERT (optind == 4);
+      }
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "-p";
+        argv[argc++] = "foo";
+        argv[argc++] = "--b=";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 0);
+        ASSERT (b_seen == 0);
+        ASSERT (p_value != NULL && strcmp (p_value, "foo") == 0);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        /* When flag is non-zero, glibc sets optopt anyway, but BSD
+           leaves optopt unchanged.  */
+        ASSERT (unrecognized == 1 || unrecognized == 0);
+        ASSERT (optind == 4);
+      }
+
+    /* Check that by default, non-options arguments are moved to the end.  */
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "donald";
+        argv[argc++] = "-p";
+        argv[argc++] = "billy";
+        argv[argc++] = "duck";
+        argv[argc++] = "-a";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (strcmp (argv[0], "program") == 0);
+        ASSERT (strcmp (argv[1], "-p") == 0);
+        ASSERT (strcmp (argv[2], "billy") == 0);
+        ASSERT (strcmp (argv[3], "-a") == 0);
+        ASSERT (strcmp (argv[4], "donald") == 0);
+        ASSERT (strcmp (argv[5], "duck") == 0);
+        ASSERT (strcmp (argv[6], "bar") == 0);
+        ASSERT (argv[7] == NULL);
+        ASSERT (a_seen == 1);
+        ASSERT (b_seen == 0);
+        ASSERT (p_value != NULL && strcmp (p_value, "billy") == 0);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 4);
+      }
+
+    /* Check that '--' ends the argument processing.  */
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[20];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "donald";
+        argv[argc++] = "-p";
+        argv[argc++] = "billy";
+        argv[argc++] = "duck";
+        argv[argc++] = "-a";
+        argv[argc++] = "--";
+        argv[argc++] = "-b";
+        argv[argc++] = "foo";
+        argv[argc++] = "-q";
+        argv[argc++] = "johnny";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (strcmp (argv[0], "program") == 0);
+        ASSERT (strcmp (argv[1], "-p") == 0);
+        ASSERT (strcmp (argv[2], "billy") == 0);
+        ASSERT (strcmp (argv[3], "-a") == 0);
+        ASSERT (strcmp (argv[4], "--") == 0);
+        ASSERT (strcmp (argv[5], "donald") == 0);
+        ASSERT (strcmp (argv[6], "duck") == 0);
+        ASSERT (strcmp (argv[7], "-b") == 0);
+        ASSERT (strcmp (argv[8], "foo") == 0);
+        ASSERT (strcmp (argv[9], "-q") == 0);
+        ASSERT (strcmp (argv[10], "johnny") == 0);
+        ASSERT (strcmp (argv[11], "bar") == 0);
+        ASSERT (argv[12] == NULL);
+        ASSERT (a_seen == 1);
+        ASSERT (b_seen == 0);
+        ASSERT (p_value != NULL && strcmp (p_value, "billy") == 0);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 5);
+      }
+
+    /* Check that the PROCESS_NON_OPTIONS flag causes non-options to be
+       returned in order.  */
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "donald";
+        argv[argc++] = "-p";
+        argv[argc++] = "billy";
+        argv[argc++] = "duck";
+        argv[argc++] = "-a";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, PROCESS_NON_OPTIONS, OPTIONS_ERRORS_SILENT);
+        options_loop (PROCESS_NON_OPTIONS, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (strcmp (argv[0], "program") == 0);
+        ASSERT (strcmp (argv[1], "donald") == 0);
+        ASSERT (strcmp (argv[2], "-p") == 0);
+        ASSERT (strcmp (argv[3], "billy") == 0);
+        ASSERT (strcmp (argv[4], "duck") == 0);
+        ASSERT (strcmp (argv[5], "-a") == 0);
+        ASSERT (strcmp (argv[6], "bar") == 0);
+        ASSERT (argv[7] == NULL);
+        ASSERT (a_seen == 1);
+        ASSERT (b_seen == 0);
+        ASSERT (p_value != NULL && strcmp (p_value, "billy") == 0);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 3);
+        ASSERT (strcmp (non_options[0], "donald") == 0);
+        ASSERT (strcmp (non_options[1], "duck") == 0);
+        ASSERT (strcmp (non_options[2], "bar") == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 7);
+      }
+
+    /* Check that '--' ends the argument processing.  */
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[20];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "donald";
+        argv[argc++] = "-p";
+        argv[argc++] = "billy";
+        argv[argc++] = "duck";
+        argv[argc++] = "-a";
+        argv[argc++] = "--";
+        argv[argc++] = "-b";
+        argv[argc++] = "foo";
+        argv[argc++] = "-q";
+        argv[argc++] = "johnny";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, PROCESS_NON_OPTIONS, OPTIONS_ERRORS_SILENT);
+        options_loop (PROCESS_NON_OPTIONS, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (strcmp (argv[0], "program") == 0);
+        ASSERT (strcmp (argv[1], "donald") == 0);
+        ASSERT (strcmp (argv[2], "-p") == 0);
+        ASSERT (strcmp (argv[3], "billy") == 0);
+        ASSERT (strcmp (argv[4], "duck") == 0);
+        ASSERT (strcmp (argv[5], "-a") == 0);
+        ASSERT (strcmp (argv[6], "--") == 0);
+        ASSERT (strcmp (argv[7], "-b") == 0);
+        ASSERT (strcmp (argv[8], "foo") == 0);
+        ASSERT (strcmp (argv[9], "-q") == 0);
+        ASSERT (strcmp (argv[10], "johnny") == 0);
+        ASSERT (strcmp (argv[11], "bar") == 0);
+        ASSERT (argv[12] == NULL);
+        ASSERT (a_seen == 1);
+        ASSERT (b_seen == 0);
+        ASSERT (p_value != NULL && strcmp (p_value, "billy") == 0);
+        ASSERT (q_value == NULL);
+        if (non_options_count == 2)
+        {
+          /* glibc behaviour.  */
+          ASSERT (non_options_count == 2);
+          ASSERT (strcmp (non_options[0], "donald") == 0);
+          ASSERT (strcmp (non_options[1], "duck") == 0);
+          ASSERT (unrecognized == 0);
+          ASSERT (optind == 7);
+        }
+        else
+        {
+          /* Another valid behaviour.  */
+          ASSERT (non_options_count == 7);
+          ASSERT (strcmp (non_options[0], "donald") == 0);
+          ASSERT (strcmp (non_options[1], "duck") == 0);
+          ASSERT (strcmp (non_options[2], "-b") == 0);
+          ASSERT (strcmp (non_options[3], "foo") == 0);
+          ASSERT (strcmp (non_options[4], "-q") == 0);
+          ASSERT (strcmp (non_options[5], "johnny") == 0);
+          ASSERT (strcmp (non_options[6], "bar") == 0);
+          ASSERT (unrecognized == 0);
+          ASSERT (optind == 12);
+        }
+      }
+
+    /* Check that the NON_OPTION_TERMINATES_OPTIONS flag causes the first
+       non-option to terminate the loop.  */
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "donald";
+        argv[argc++] = "-p";
+        argv[argc++] = "billy";
+        argv[argc++] = "duck";
+        argv[argc++] = "-a";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, NON_OPTION_TERMINATES_OPTIONS, OPTIONS_ERRORS_SILENT);
+        options_loop (NON_OPTION_TERMINATES_OPTIONS, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (strcmp (argv[0], "program") == 0);
+        ASSERT (strcmp (argv[1], "donald") == 0);
+        ASSERT (strcmp (argv[2], "-p") == 0);
+        ASSERT (strcmp (argv[3], "billy") == 0);
+        ASSERT (strcmp (argv[4], "duck") == 0);
+        ASSERT (strcmp (argv[5], "-a") == 0);
+        ASSERT (strcmp (argv[6], "bar") == 0);
+        ASSERT (argv[7] == NULL);
+        ASSERT (a_seen == 0);
+        ASSERT (b_seen == 0);
+        ASSERT (p_value == NULL);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 1);
+      }
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "-+";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, NON_OPTION_TERMINATES_OPTIONS, OPTIONS_ERRORS_SILENT);
+        options_loop (NON_OPTION_TERMINATES_OPTIONS, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 0);
+        ASSERT (b_seen == 0);
+        ASSERT (p_value == NULL);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == '+');
+        ASSERT (optind == 2);
+      }
+
+    /* Check that '--' ends the argument processing.  */
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[20];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "donald";
+        argv[argc++] = "-p";
+        argv[argc++] = "billy";
+        argv[argc++] = "duck";
+        argv[argc++] = "-a";
+        argv[argc++] = "--";
+        argv[argc++] = "-b";
+        argv[argc++] = "foo";
+        argv[argc++] = "-q";
+        argv[argc++] = "johnny";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, NON_OPTION_TERMINATES_OPTIONS, OPTIONS_ERRORS_SILENT);
+        options_loop (NON_OPTION_TERMINATES_OPTIONS, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (strcmp (argv[0], "program") == 0);
+        ASSERT (strcmp (argv[1], "donald") == 0);
+        ASSERT (strcmp (argv[2], "-p") == 0);
+        ASSERT (strcmp (argv[3], "billy") == 0);
+        ASSERT (strcmp (argv[4], "duck") == 0);
+        ASSERT (strcmp (argv[5], "-a") == 0);
+        ASSERT (strcmp (argv[6], "--") == 0);
+        ASSERT (strcmp (argv[7], "-b") == 0);
+        ASSERT (strcmp (argv[8], "foo") == 0);
+        ASSERT (strcmp (argv[9], "-q") == 0);
+        ASSERT (strcmp (argv[10], "johnny") == 0);
+        ASSERT (strcmp (argv[11], "bar") == 0);
+        ASSERT (argv[12] == NULL);
+        ASSERT (a_seen == 0);
+        ASSERT (b_seen == 0);
+        ASSERT (p_value == NULL);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 1);
+      }
+  }
+}
+
+/* Test behavior of getopt_long when POSIXLY_CORRECT is set in the
+   environment.  Options with optional arguments should not change
+   behavior just because of an environment variable.
+   https://lists.gnu.org/r/bug-m4/2006-09/msg00028.html  */
+static void
+test_getopt_long_posix (void)
+{
+  int start;
+
+  {
+    static const struct program_option options[] =
+      {
+        { "alpha",     'a',  no_argument        },
+        { "beta",      0,    no_argument,       &b_seen, 1 },
+        { "prune",     'p',  required_argument  },
+        { "quetsche",  0,    required_argument, &q_seen, 1 },
+        { "xtremely-", 1003, no_argument        },
+        { "xtra",      1001, no_argument        },
+        { "xtreme",    1002, no_argument        },
+        { "xtremely",  1003, no_argument        },
+        { NULL,        'q',  required_argument  },
+        { NULL,        'b',  no_argument        },
+      };
+
+    /* Check that POSIXLY_CORRECT stops parsing the same as leading '+'.  */
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "donald";
+        argv[argc++] = "-p";
+        argv[argc++] = "billy";
+        argv[argc++] = "duck";
+        argv[argc++] = "-a";
+        argv[argc++] = "bar";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (strcmp (argv[0], "program") == 0);
+        ASSERT (strcmp (argv[1], "donald") == 0);
+        ASSERT (strcmp (argv[2], "-p") == 0);
+        ASSERT (strcmp (argv[3], "billy") == 0);
+        ASSERT (strcmp (argv[4], "duck") == 0);
+        ASSERT (strcmp (argv[5], "-a") == 0);
+        ASSERT (strcmp (argv[6], "bar") == 0);
+        ASSERT (argv[7] == NULL);
+        ASSERT (a_seen == 0);
+        ASSERT (b_seen == 0);
+        ASSERT (p_value == NULL);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 1);
+      }
+  }
+
+  {
+    static const struct program_option options[] =
+      {
+        { "alpha",     0,    no_argument        },
+        { "beta",      0,    no_argument,       &b_seen, 1 },
+        { "prune",     0,    required_argument  },
+        { "quetsche",  0,    required_argument, &q_seen, 1 },
+        { "xtremely-", 1003, no_argument        },
+        { "xtra",      1001, no_argument        },
+        { "xtreme",    1002, no_argument        },
+        { "xtremely",  1003, no_argument        },
+        { NULL,        'p',  optional_argument  },
+      };
+
+    /* Check that POSIXLY_CORRECT doesn't change optional arguments.  */
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "-p";
+        argv[argc++] = "billy";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT);
+        options_loop (MOVE_OPTIONS_FIRST, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 0);
+        ASSERT (b_seen == 0);
+        ASSERT (p_value == NULL);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 2);
+      }
+  }
+
+  {
+    static const struct program_option options[] =
+      {
+        { "alpha",     'a',  no_argument        },
+        { "beta",      0,    no_argument,       &b_seen, 1 },
+        { "prune",     0,    required_argument  },
+        { "quetsche",  0,    required_argument, &q_seen, 1 },
+        { "xtremely-", 1003, no_argument        },
+        { "xtra",      1001, no_argument        },
+        { "xtreme",    1002, no_argument        },
+        { "xtremely",  1003, no_argument        },
+        { NULL,        'b',  no_argument        },
+      };
+
+    /* Check that leading - still sees options after non-options.  */
+    for (start = 0; start <= 1; start++)
+      {
+        const char *p_value = NULL;
+        const char *q_value = NULL;
+        int non_options_count = 0;
+        const char *non_options[10];
+        int unrecognized = 0;
+        int argc = 0;
+        const char *argv[10];
+        a_seen = 0;
+        b_seen = 0;
+
+        argv[argc++] = "program";
+        argv[argc++] = "-a";
+        argv[argc++] = "billy";
+        argv[argc++] = "-b";
+        argv[argc] = NULL;
+        optind = start;
+        start_options (argc, (char **) argv, options, PROCESS_NON_OPTIONS, OPTIONS_ERRORS_SILENT);
+        options_loop (PROCESS_NON_OPTIONS, OPTIONS_ERRORS_SILENT,
+                      &p_value, &q_value,
+                      &non_options_count, non_options, &unrecognized);
+        ASSERT (a_seen == 1);
+        ASSERT (b_seen == 1);
+        ASSERT (p_value == NULL);
+        ASSERT (q_value == NULL);
+        ASSERT (non_options_count == 1);
+        ASSERT (strcmp (non_options[0], "billy") == 0);
+        ASSERT (unrecognized == 0);
+        ASSERT (optind == 4);
+      }
+  }
+}
+
+int
+main ()
+{
+  setenv ("POSIXLY_CORRECT", "1", 1);
+
+  test_getopt_long_posix ();
+
+  unsetenv ("POSIXLY_CORRECT");
+
+  test_getopt_long ();
+
+  return test_exit_status;
+}
-- 
2.43.0

Reply via email to