Ticket #55: savannah: tab completion vs. spaces and escaping
authorSlava Zanko <slavazanko@gmail.com>
Fri, 25 Jan 2013 11:30:17 +0000 (25 14:30 +0300)
committerSlava Zanko <slavazanko@gmail.com>
Mon, 4 Feb 2013 12:29:15 +0000 (4 15:29 +0300)
* Split big functions.
* Add unit tests

Signed-off-by: Slava Zanko <slavazanko@gmail.com>
configure.ac
lib/widget/input_complete.c
tests/lib/Makefile.am
tests/lib/widget/Makefile.am [new file with mode: 0644]
tests/lib/widget/complete_engine.c [new file with mode: 0644]

index fdece27..0933256 100644 (file)
@@ -644,6 +644,7 @@ tests/lib/Makefile
 tests/lib/mcconfig/Makefile
 tests/lib/search/Makefile
 tests/lib/vfs/Makefile
+tests/lib/widget/Makefile
 tests/src/Makefile
 tests/src/filemanager/Makefile
 ])
index b47a506..934fb96 100644 (file)
@@ -3,11 +3,12 @@
    (Let mc type for you...)
 
    Copyright (C) 1995, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
-   2007, 2011
+   2007, 2011, 2013
    the Free Software Foundation, Inc.
 
    Written by:
    Jakub Jelinek, 1995
+   Slava Zanko <slavazanko@gmail.com>, 2013
 
    This file is part of the Midnight Commander.
 
@@ -79,6 +80,17 @@ extern char **environ;
 
 typedef char *CompletionFunction (const char *text, int state, input_complete_t flags);
 
+typedef struct
+{
+    size_t in_command_position;
+    char *word;
+    char *p;
+    char *q;
+    char *r;
+    gboolean is_cd;
+    input_complete_t flags;
+} try_complete_automation_state_t;
+
 /*** file scope variables ************************************************************************/
 
 static char **hosts = NULL;
@@ -94,6 +106,9 @@ static int end = 0;
 /*** file scope functions ************************************************************************/
 /* --------------------------------------------------------------------------------------------- */
 
+char **try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags);
+void complete_engine_fill_completions (WInput * in);
+
 #ifdef DO_COMPLETION_DEBUG
 /**
  * Useful to print/debug completion flags
@@ -819,155 +834,114 @@ check_is_cd (const char *text, int lc_start, input_complete_t flags)
 }
 
 /* --------------------------------------------------------------------------------------------- */
-/** Returns an array of matches, or NULL if none. */
-static char **
-try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags)
-{
-    size_t in_command_position = 0;
-    char *word;
-    char **matches = NULL;
-    char *p = NULL, *q = NULL, *r = NULL;
-    gboolean is_cd;
 
-    SHOW_C_CTX ("try_complete");
-    word = g_strndup (text + *lc_start, *lc_end - *lc_start);
+static void
+try_complete_commands_prepare (try_complete_automation_state_t * state, char *text, int *lc_start)
+{
+    const char *command_separator_chars = ";|&{(`";
+    char *ti;
 
-    is_cd = check_is_cd (text, *lc_start, flags);
+    if (*lc_start == 0)
+        ti = text;
+    else
+    {
+        ti = str_get_prev_char (&text[*lc_start]);
+        while (ti > text && (ti[0] == ' ' || ti[0] == '\t'))
+            str_prev_char (&ti);
+    }
 
-    /* Determine if this could be a command word. It is if it appears at
-       the start of the line (ignoring preceding whitespace), or if it
-       appears after a character that separates commands. And we have to
-       be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
-    if (!is_cd && (flags & INPUT_COMPLETE_COMMANDS))
+    if (ti == text)
+        state->in_command_position++;
+    else if (strchr (command_separator_chars, ti[0]) != NULL)
     {
-        const char *command_separator_chars = ";|&{(`";
-        char *ti;
+        int this_char, prev_char;
 
-        if (*lc_start == 0)
-            ti = text;
-        else
-        {
-            ti = str_get_prev_char (&text[*lc_start]);
-            while (ti > text && (ti[0] == ' ' || ti[0] == '\t'))
-                str_prev_char (&ti);
-        }
+        state->in_command_position++;
 
-        if (ti == text)
-            in_command_position++;
-        else if (strchr (command_separator_chars, ti[0]) != NULL)
+        if (ti != text)
         {
-            int this_char, prev_char;
-
-            in_command_position++;
-
-            if (ti != text)
-            {
-                /* Handle the two character tokens `>&', `<&', and `>|'.
-                   We are not in a command position after one of these. */
-                this_char = ti[0];
-                prev_char = str_get_prev_char (ti)[0];
-
-                /* Quoted */
-                if ((this_char == '&' && (prev_char == '<' || prev_char == '>'))
-                    || (this_char == '|' && prev_char == '>') || (ti != text
-                                                                  && str_get_prev_char (ti)[0] ==
-                                                                  '\\'))
-                    in_command_position = 0;
-            }
+            /* Handle the two character tokens `>&', `<&', and `>|'.
+               We are not in a command position after one of these. */
+            this_char = ti[0];
+            prev_char = str_get_prev_char (ti)[0];
+
+            /* Quoted */
+            if ((this_char == '&' && (prev_char == '<' || prev_char == '>'))
+                || (this_char == '|' && prev_char == '>') || (ti != text
+                                                              && str_get_prev_char (ti)[0] == '\\'))
+                state->in_command_position = 0;
         }
     }
+}
 
-    if (flags & INPUT_COMPLETE_COMMANDS)
-        p = strrchr (word, '`');
-    if (flags & (INPUT_COMPLETE_COMMANDS | INPUT_COMPLETE_VARIABLES))
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+try_complete_find_start_sign (try_complete_automation_state_t * state)
+{
+    if (state->flags & INPUT_COMPLETE_COMMANDS)
+        state->p = strrchr (state->word, '`');
+    if (state->flags & (INPUT_COMPLETE_COMMANDS | INPUT_COMPLETE_VARIABLES))
     {
-        q = strrchr (word, '$');
+        state->q = strrchr (state->word, '$');
 
         /* don't substitute variable in \$ case */
-        if (q != NULL && q != word && q[-1] == '\\')
+        if (strutils_is_char_escaped (state->word, state->q))
         {
             size_t qlen;
 
-            qlen = strlen (q);
+            qlen = strlen (state->q);
             /* drop '\\' */
-            memmove (q - 1, q, qlen + 1);
+            memmove (state->q - 1, state->q, qlen + 1);
             /* adjust flags */
-            flags &= ~INPUT_COMPLETE_VARIABLES;
-            q = NULL;
+            state->flags &= ~INPUT_COMPLETE_VARIABLES;
+            state->q = NULL;
         }
     }
-    if (flags & INPUT_COMPLETE_HOSTNAMES)
-        r = strrchr (word, '@');
-    if (q && q[1] == '(' && (flags & INPUT_COMPLETE_COMMANDS))
-    {
-        if (q > p)
-            p = str_get_next_char (q);
-        q = NULL;
-    }
-
-    /* Command substitution? */
-    if (p > q && p > r)
-    {
-        SHOW_C_CTX ("try_complete:cmd_backq_subst");
-        matches = completion_matches (str_cget_next_char (p),
-                                      command_completion_function,
-                                      flags & (~INPUT_COMPLETE_FILENAMES));
-        if (matches)
-            *lc_start += str_get_next_char (p) - word;
-    }
-
-    /* Variable name? */
-    else if (q > p && q > r)
-    {
-        SHOW_C_CTX ("try_complete:var_subst");
-        matches = completion_matches (q, variable_completion_function, flags);
-        if (matches)
-            *lc_start += q - word;
-    }
-
-    /* Starts with '@', then look through the known hostnames for 
-       completion first. */
-    else if (r > p && r > q)
+    if (state->flags & INPUT_COMPLETE_HOSTNAMES)
+        state->r = strrchr (state->word, '@');
+    if (state->q && state->q[1] == '(' && (state->flags & INPUT_COMPLETE_COMMANDS))
     {
-        SHOW_C_CTX ("try_complete:host_subst");
-        matches = completion_matches (r, hostname_completion_function, flags);
-        if (matches)
-            *lc_start += r - word;
+        if (state->q > state->p)
+            state->p = str_get_next_char (state->q);
+        state->q = NULL;
     }
+}
 
-    /* Starts with `~' and there is no slash in the word, then
-       try completing this word as a username. */
-    if (!matches && *word == '~' && (flags & INPUT_COMPLETE_USERNAMES) && !strchr (word, PATH_SEP))
-    {
-        SHOW_C_CTX ("try_complete:user_subst");
-        matches = completion_matches (word, username_completion_function, flags);
-    }
+/* --------------------------------------------------------------------------------------------- */
 
+static char **
+try_complete_all_possible (try_complete_automation_state_t * state, char *text, int *lc_start)
+{
+    char **matches = NULL;
 
-    /* And finally if this word is in a command position, then
-       complete over possible command names, including aliases, functions,
-       and command names. */
-    if (!matches && in_command_position != 0)
+    if (state->in_command_position != 0)
     {
         SHOW_C_CTX ("try_complete:cmd_subst");
         matches =
-            completion_matches (word, command_completion_function,
-                                flags & (~INPUT_COMPLETE_FILENAMES));
+            completion_matches (state->word, command_completion_function,
+                                state->flags & (~INPUT_COMPLETE_FILENAMES));
     }
-
-    else if (!matches && (flags & INPUT_COMPLETE_FILENAMES))
+    else if ((state->flags & INPUT_COMPLETE_FILENAMES) != 0)
     {
-        if (is_cd)
-            flags &= ~(INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_COMMANDS);
+        if (state->is_cd)
+            state->flags &= ~(INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_COMMANDS);
         SHOW_C_CTX ("try_complete:filename_subst_1");
-        matches = completion_matches (word, filename_completion_function, flags);
-        if (!matches && is_cd && *word != PATH_SEP && *word != '~')
+        matches = completion_matches (state->word, filename_completion_function, state->flags);
+
+        if (matches == NULL && state->is_cd && *state->word != PATH_SEP && *state->word != '~')
         {
-            q = text + *lc_start;
-            for (p = text; *p && p < q && (*p == ' ' || *p == '\t'); str_next_char (&p));
-            if (!strncmp (p, "cd", 2))
-                for (p += 2; *p && p < q && (*p == ' ' || *p == '\t'); str_next_char (&p));
-            if (p == q)
+            state->q = text + *lc_start;
+            for (state->p = text;
+                 *state->p && state->p < state->q && (*state->p == ' ' || *state->p == '\t');
+                 str_next_char (&state->p))
+                ;
+            if (!strncmp (state->p, "cd", 2))
+                for (state->p += 2;
+                     *state->p && state->p < state->q && (*state->p == ' ' || *state->p == '\t');
+                     str_next_char (&state->p))
+                ;
+            if (state->p == state->q)
             {
                 char *const cdpath_ref = g_strdup (getenv ("CDPATH"));
                 char *cdpath = cdpath_ref;
@@ -986,10 +960,12 @@ try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags)
                     *s = 0;
                     if (*cdpath)
                     {
-                        r = mc_build_filename (cdpath, word, NULL);
+                        state->r = mc_build_filename (cdpath, state->word, NULL);
                         SHOW_C_CTX ("try_complete:filename_subst_2");
-                        matches = completion_matches (r, filename_completion_function, flags);
-                        g_free (r);
+                        matches =
+                            completion_matches (state->r, filename_completion_function,
+                                                state->flags);
+                        g_free (state->r);
                     }
                     *s = c;
                     cdpath = str_get_next_char (s);
@@ -998,9 +974,6 @@ try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags)
             }
         }
     }
-
-    g_free (word);
-
     return matches;
 }
 
@@ -1224,43 +1197,16 @@ query_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *d
 }
 
 /* --------------------------------------------------------------------------------------------- */
-/** Returns 1 if the user would like to see us again */
 
+/** Returns 1 if the user would like to see us again */
 static int
 complete_engine (WInput * in, int what_to_do)
 {
     if (in->completions != NULL && str_offset_to_pos (in->buffer, in->point) != end)
         input_free_completions (in);
-    if (in->completions == NULL)
-    {
-        char *s;
-
-        end = str_offset_to_pos (in->buffer, in->point);
-
-        s = in->buffer;
-        if (in->point != 0)
-        {
-            /* get symbol before in->point */
-            size_t i;
-            for (i = in->point - 1; i > 0; i--)
-                str_next_char (&s);
-        }
 
-        for (; s >= in->buffer; str_prev_char (&s))
-        {
-            start = s - in->buffer;
-            if (strchr (" \t;|<>", *s) != NULL)
-                break;
-        }
-
-        if (start < end)
-        {
-            str_next_char (&s);
-            start = s - in->buffer;
-        }
-
-        in->completions = try_complete (in->buffer, &start, &end, in->completion_flags);
-    }
+    if (in->completions == NULL)
+        complete_engine_fill_completions (in);
 
     if (in->completions != NULL)
     {
@@ -1356,6 +1302,117 @@ complete_engine (WInput * in, int what_to_do)
 /*** public functions ****************************************************************************/
 /* --------------------------------------------------------------------------------------------- */
 
+/** Returns an array of matches, or NULL if none. */
+char **
+try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags)
+{
+    try_complete_automation_state_t state;
+    char **matches = NULL;
+
+    memset (&state, 0, sizeof (try_complete_automation_state_t));
+    state.flags = flags;
+
+    SHOW_C_CTX ("try_complete");
+    state.word = g_strndup (text + *lc_start, *lc_end - *lc_start);
+
+    state.is_cd = check_is_cd (text, *lc_start, state.flags);
+
+    /* Determine if this could be a command word. It is if it appears at
+       the start of the line (ignoring preceding whitespace), or if it
+       appears after a character that separates commands. And we have to
+       be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
+    if (!state.is_cd && (flags & INPUT_COMPLETE_COMMANDS))
+        try_complete_commands_prepare (&state, text, lc_start);
+
+    try_complete_find_start_sign (&state);
+
+    /* Command substitution? */
+    if (state.p > state.q && state.p > state.r)
+    {
+        SHOW_C_CTX ("try_complete:cmd_backq_subst");
+        matches = completion_matches (str_cget_next_char (state.p),
+                                      command_completion_function,
+                                      state.flags & (~INPUT_COMPLETE_FILENAMES));
+        if (matches)
+            *lc_start += str_get_next_char (state.p) - state.word;
+    }
+
+    /* Variable name? */
+    else if (state.q > state.p && state.q > state.r)
+    {
+        SHOW_C_CTX ("try_complete:var_subst");
+        matches = completion_matches (state.q, variable_completion_function, state.flags);
+        if (matches)
+            *lc_start += state.q - state.word;
+    }
+
+    /* Starts with '@', then look through the known hostnames for 
+       completion first. */
+    else if (state.r > state.p && state.r > state.q)
+    {
+        SHOW_C_CTX ("try_complete:host_subst");
+        matches = completion_matches (state.r, hostname_completion_function, state.flags);
+        if (matches)
+            *lc_start += state.r - state.word;
+    }
+
+    /* Starts with `~' and there is no slash in the word, then
+       try completing this word as a username. */
+    if (!matches && *state.word == '~' && (state.flags & INPUT_COMPLETE_USERNAMES)
+        && !strchr (state.word, PATH_SEP))
+    {
+        SHOW_C_CTX ("try_complete:user_subst");
+        matches = completion_matches (state.word, username_completion_function, state.flags);
+    }
+
+    /* And finally if this word is in a command position, then
+       complete over possible command names, including aliases, functions,
+       and command names. */
+    if (matches == NULL)
+        matches = try_complete_all_possible (&state, text, lc_start);
+
+    g_free (state.word);
+
+    return matches;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+complete_engine_fill_completions (WInput * in)
+{
+    char *s;
+
+    end = str_offset_to_pos (in->buffer, in->point);
+
+    s = in->buffer;
+    if (in->point != 0)
+    {
+        /* get symbol before in->point */
+        size_t i;
+
+        for (i = in->point - 1; i > 0; i--)
+            str_next_char (&s);
+    }
+
+    for (; s >= in->buffer; str_prev_char (&s))
+    {
+        start = s - in->buffer;
+        if (strchr (" \t;|<>", *s) != NULL)
+            break;
+    }
+
+    if (start < end)
+    {
+        str_next_char (&s);
+        start = s - in->buffer;
+    }
+
+    in->completions = try_complete (in->buffer, &start, &end, in->completion_flags);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
 /* declared in lib/widget/input.h */
 void
 complete (WInput * in)
index 8dd721a..f5ed3a3 100644 (file)
@@ -1,4 +1,4 @@
-SUBDIRS = . mcconfig search vfs
+SUBDIRS = . mcconfig search vfs widget
 
 AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir) @CHECK_CFLAGS@
 
diff --git a/tests/lib/widget/Makefile.am b/tests/lib/widget/Makefile.am
new file mode 100644 (file)
index 0000000..37a91ef
--- /dev/null
@@ -0,0 +1,19 @@
+
+AM_CPPFLAGS = \
+       $(GLIB_CFLAGS) \
+       -I$(top_srcdir) \
+       -I$(top_srcdir)/lib/vfs \
+       @CHECK_CFLAGS@
+
+AM_LDFLAGS = @TESTS_LDFLAGS@
+
+LIBS=@CHECK_LIBS@  \
+    $(top_builddir)/lib/libmc.la
+
+TESTS = \
+       complete_engine
+
+check_PROGRAMS = $(TESTS)
+
+complete_engine_SOURCES = \
+       complete_engine.c
diff --git a/tests/lib/widget/complete_engine.c b/tests/lib/widget/complete_engine.c
new file mode 100644 (file)
index 0000000..656f157
--- /dev/null
@@ -0,0 +1,233 @@
+/*
+   lib/widget - tests for autocomplete feature
+
+   Copyright (C) 2013
+   The Free Software Foundation, Inc.
+
+   Written by:
+   Slava Zanko <slavazanko@gmail.com>, 2013
+
+   This file is part of the Midnight Commander.
+
+   The Midnight Commander 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.
+
+   The Midnight Commander 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 <http://www.gnu.org/licenses/>.
+ */
+
+#define TEST_SUITE_NAME "/lib/widget"
+
+#include <config.h>
+
+#include <check.h>
+
+#include "lib/global.h"
+#include "lib/strutil.h"
+#include "lib/widget.h"
+
+/* --------------------------------------------------------------------------------------------- */
+
+void complete_engine_fill_completions (WInput * in);
+char **try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags);
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* @CapturedValue */
+static char *try_complete__text__captured;
+/* @CapturedValue */
+static int try_complete__lc_start__captured;
+/* @CapturedValue */
+static int try_complete__lc_end__captured;
+/* @CapturedValue */
+static input_complete_t try_complete__flags__captured;
+
+/* @ThenReturnValue */
+static char **try_complete__return_value;
+
+/* @Mock */
+char **
+try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags)
+{
+    try_complete__text__captured = g_strdup (text);
+    try_complete__lc_start__captured = *lc_start;
+    try_complete__lc_end__captured = *lc_end;
+    try_complete__flags__captured = flags;
+
+    return try_complete__return_value;
+}
+
+static void
+try_complete__init (void)
+{
+    try_complete__text__captured = NULL;
+    try_complete__lc_start__captured = 0;
+    try_complete__lc_end__captured = 0;
+    try_complete__flags__captured = 0;
+}
+
+static void
+try_complete__deinit (void)
+{
+    g_free (try_complete__text__captured);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* @Before */
+static void
+setup (void)
+{
+    str_init_strings (NULL);
+    try_complete__init ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* @After */
+static void
+teardown (void)
+{
+    try_complete__deinit ();
+    str_uninit_strings ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* @DataSource("test_complete_engine_fill_completions_ds") */
+/* *INDENT-OFF* */
+static const struct test_complete_engine_fill_completions_ds
+{
+    const char *input_buffer;
+    const int input_point;
+    const input_complete_t input_completion_flags;
+    int expected_start;
+    int expected_end;
+} test_complete_engine_fill_completions_ds[] =
+{
+    {
+        "string",
+        3,
+        INPUT_COMPLETE_DEFAULT,
+        0,
+        3
+    },
+    {
+        "some string",
+        7,
+        INPUT_COMPLETE_DEFAULT,
+        5,
+        7
+    },
+    {
+        "some\tstring",
+        7,
+        INPUT_COMPLETE_DEFAULT,
+        5,
+        7
+    },
+    {
+        "some;string",
+        7,
+        INPUT_COMPLETE_DEFAULT,
+        5,
+        7
+    },
+    {
+        "some|string",
+        7,
+        INPUT_COMPLETE_DEFAULT,
+        5,
+        7
+    },
+    {
+        "some<string",
+        7,
+        INPUT_COMPLETE_DEFAULT,
+        5,
+        7
+    },
+    {
+        "some>string",
+        7,
+        INPUT_COMPLETE_DEFAULT,
+        5,
+        7
+    },
+    {
+        "some!@#$%^&*()_\\+~`\"',./?:string",
+        30,
+        INPUT_COMPLETE_DEFAULT,
+        0,
+        30
+    },
+};
+/* *INDENT-ON* */
+// " \t;|<>"
+
+/* @Test(dataSource = "test_complete_engine_fill_completions_ds") */
+/* *INDENT-OFF* */
+START_TEST (test_complete_engine_fill_completions)
+/* *INDENT-ON* */
+{
+    /* given */
+
+    WInput *w_input;
+    const struct test_complete_engine_fill_completions_ds *data =
+        &test_complete_engine_fill_completions_ds[_i];
+
+    w_input = g_new (WInput, 1);
+    w_input->buffer = g_strdup (data->input_buffer);
+    w_input->point = data->input_point;
+    w_input->completion_flags = data->input_completion_flags;
+
+    /* when */
+    complete_engine_fill_completions (w_input);
+
+    /* then */
+    g_assert_cmpstr (try_complete__text__captured, ==, data->input_buffer);
+    ck_assert_int_eq (try_complete__lc_start__captured, data->expected_start);
+    ck_assert_int_eq (try_complete__lc_end__captured, data->expected_end);
+    ck_assert_int_eq (try_complete__flags__captured, data->input_completion_flags);
+
+}
+/* *INDENT-OFF* */
+END_TEST
+/* *INDENT-ON* */
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+main (void)
+{
+    int number_failed;
+
+    Suite *s = suite_create (TEST_SUITE_NAME);
+    TCase *tc_core = tcase_create ("Core");
+    SRunner *sr;
+
+    tcase_add_checked_fixture (tc_core, setup, teardown);
+
+    /* Add new tests here: *************** */
+    tcase_add_loop_test (tc_core, test_complete_engine_fill_completions, 0,
+                         sizeof (test_complete_engine_fill_completions_ds) /
+                         sizeof (test_complete_engine_fill_completions_ds[0]));
+    /* *********************************** */
+
+    suite_add_tcase (s, tc_core);
+    sr = srunner_create (s);
+    srunner_set_log (sr, "complete_engine.log");
+    srunner_run_all (sr, CK_NORMAL);
+    number_failed = srunner_ntests_failed (sr);
+    srunner_free (sr);
+    return (number_failed == 0) ? 0 : 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */