mirror of
https://github.com/MidnightCommander/mc
synced 2024-12-22 20:36:50 +03:00
(mc_pstream_get_string): new API.
Signed-off-by: Andrew Borodin <aborodin@vmail.ru>
This commit is contained in:
parent
1e45efecb2
commit
a10bf6a244
@ -87,6 +87,8 @@ typedef struct
|
||||
int fd;
|
||||
/* data read from fd */
|
||||
char buf[MC_PIPE_BUFSIZE];
|
||||
/* current position in @buf (used by mc_pstream_get_string()) */
|
||||
size_t pos;
|
||||
/* positive: length of data in buf;
|
||||
* MC_PIPE_STREAM_EOF: EOF of fd;
|
||||
* MC_PIPE_STREAM_UNREAD: there was not read from fd;
|
||||
@ -221,6 +223,8 @@ mc_pipe_t *mc_popen (const char *command, gboolean read_out, gboolean read_err,
|
||||
void mc_pread (mc_pipe_t * p, GError ** error);
|
||||
void mc_pclose (mc_pipe_t * p, GError ** error);
|
||||
|
||||
GString *mc_pstream_get_string (mc_pipe_stream_t * ps);
|
||||
|
||||
void my_exit (int status);
|
||||
void save_stop_handler (void);
|
||||
|
||||
|
@ -277,6 +277,8 @@ mc_pread_stream (mc_pipe_stream_t * ps, const fd_set * fds)
|
||||
if (ps->null_term)
|
||||
ps->buf[(size_t) ps->len] = '\0';
|
||||
}
|
||||
|
||||
ps->pos = 0;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
@ -628,6 +630,50 @@ mc_pread (mc_pipe_t * p, GError ** error)
|
||||
p->err.len = MC_PIPE_STREAM_UNREAD;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
/**
|
||||
* Reads a line from @stream. Reading stops after an EOL or a newline. If a newline is read,
|
||||
* it is appended to the line.
|
||||
*
|
||||
* @stream mc_pipe_stream_t object
|
||||
*
|
||||
* @return newly created GString or NULL in case of EOL;
|
||||
*/
|
||||
|
||||
GString *
|
||||
mc_pstream_get_string (mc_pipe_stream_t * ps)
|
||||
{
|
||||
char *s;
|
||||
size_t size, i;
|
||||
gboolean escape = FALSE;
|
||||
|
||||
g_return_val_if_fail (ps != NULL, NULL);
|
||||
|
||||
if (ps->len < 0)
|
||||
return NULL;
|
||||
|
||||
size = ps->len - ps->pos;
|
||||
|
||||
if (size == 0)
|
||||
return NULL;
|
||||
|
||||
s = ps->buf + ps->pos;
|
||||
|
||||
if (s[0] == '\0')
|
||||
return NULL;
|
||||
|
||||
/* find '\0' or unescaped '\n' */
|
||||
for (i = 0; i < size && !(s[i] == '\0' || (s[i] == '\n' && !escape)); i++)
|
||||
escape = s[i] == '\\' ? !escape : FALSE;
|
||||
|
||||
if (i != size && s[i] == '\n')
|
||||
i++;
|
||||
|
||||
ps->pos += i;
|
||||
|
||||
return g_string_new_len (s, i);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
/**
|
||||
* Close pipe and destroy pipe descriptor.
|
||||
|
@ -20,6 +20,7 @@ TESTS = \
|
||||
mc_build_filename \
|
||||
name_quote \
|
||||
serialize \
|
||||
utilunix__mc_pstream_get_string \
|
||||
utilunix__my_system_fork_fail \
|
||||
utilunix__my_system_fork_child_shell \
|
||||
utilunix__my_system_fork_child \
|
||||
@ -46,6 +47,9 @@ name_quote_SOURCES = \
|
||||
serialize_SOURCES = \
|
||||
serialize.c
|
||||
|
||||
utilunix__mc_pstream_get_string_SOURCES = \
|
||||
utilunix__mc_pstream_get_string.c
|
||||
|
||||
utilunix__my_system_fork_fail_SOURCES = \
|
||||
utilunix__my_system-fork_fail.c
|
||||
|
||||
|
396
tests/lib/utilunix__mc_pstream_get_string.c
Normal file
396
tests/lib/utilunix__mc_pstream_get_string.c
Normal file
@ -0,0 +1,396 @@
|
||||
/*
|
||||
lib - Read string from mc_pipe_stream
|
||||
|
||||
Copyright (C) 2021
|
||||
Free Software Foundation, Inc.
|
||||
|
||||
Written by:
|
||||
Andrew Borodin <aborodin@vmail.ru>, 2021
|
||||
|
||||
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/util"
|
||||
|
||||
#include "tests/mctest.h"
|
||||
|
||||
#include "lib/util.h"
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
|
||||
#define MAX_CHUNKS 8
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
|
||||
static mc_pipe_stream_t stream;
|
||||
|
||||
static char etalon_long_file_list[BUF_1K];
|
||||
static size_t etalon_long_file_list_pos;
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
|
||||
/* @Before */
|
||||
static void
|
||||
setup (void)
|
||||
{
|
||||
}
|
||||
|
||||
/* @After */
|
||||
static void
|
||||
teardown (void)
|
||||
{
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
|
||||
/* @DataSource("data_source") */
|
||||
/* *INDENT-OFF* */
|
||||
static const struct data_source
|
||||
{
|
||||
/* input */
|
||||
const char *buf; /* string to read */
|
||||
|
||||
/* output */
|
||||
int pos[MAX_CHUNKS]; /* ps.pos values */
|
||||
const char *str[MAX_CHUNKS]; /* chunks */
|
||||
size_t len[MAX_CHUNKS]; /* chunk lengths */
|
||||
}
|
||||
data_source[] =
|
||||
{
|
||||
/* 0 */
|
||||
{
|
||||
.buf = "",
|
||||
.pos = { 0 },
|
||||
.str = { "" },
|
||||
.len = { 0 }
|
||||
},
|
||||
/* 1 */
|
||||
{
|
||||
.buf = "\n",
|
||||
.pos = { 0, 1 },
|
||||
.str = { "\n" },
|
||||
.len = { 1, 0 }
|
||||
},
|
||||
/* 2 */
|
||||
{
|
||||
.buf = "\\\n",
|
||||
.pos = { 0, 2 },
|
||||
.str = { "\\\n" },
|
||||
.len = { 2, 0 }
|
||||
},
|
||||
/* 3 */
|
||||
{
|
||||
.buf = "\\\\\n",
|
||||
.pos = { 0, 3 },
|
||||
.str = { "\\\\\n" },
|
||||
.len = { 3, 0 }
|
||||
},
|
||||
/* 4 */
|
||||
{
|
||||
.buf = "\\\\\\\n",
|
||||
.pos = { 0, 4 },
|
||||
.str = { "\\\\\\\n" },
|
||||
.len = { 4, 0 }
|
||||
},
|
||||
/* 5 */
|
||||
{
|
||||
.buf = "\\\\\\\\\n",
|
||||
.pos = { 0, 5 },
|
||||
.str = { "\\\\\\\\\n" },
|
||||
.len = { 5, 0 }
|
||||
},
|
||||
/* 6 */
|
||||
{
|
||||
.buf = "12345",
|
||||
.pos = { 0, 5 },
|
||||
.str = { "12345" },
|
||||
.len = { 5, 0 }
|
||||
},
|
||||
/* 7 */
|
||||
{
|
||||
.buf = "12345\n",
|
||||
.pos = { 0, 6 },
|
||||
.str = { "12345\n" },
|
||||
.len = { 6, 0 }
|
||||
},
|
||||
/* 8 */
|
||||
{
|
||||
.buf = "12345\\\n",
|
||||
.pos = { 0, 7 },
|
||||
.str = { "12345\\\n" },
|
||||
.len = { 7, 0 }
|
||||
},
|
||||
/* 9 */
|
||||
{
|
||||
.buf = "12345\\\\\n",
|
||||
.pos = { 0, 8 },
|
||||
.str = { "12345\\\\\n" },
|
||||
.len = { 8, 0 }
|
||||
},
|
||||
/* 10 */
|
||||
{
|
||||
.buf = "12345\nabcd",
|
||||
.pos = { 0, 6, 10 },
|
||||
.str = { "12345\n", "abcd" },
|
||||
.len = { 6, 4, 0 }
|
||||
},
|
||||
/* 11 */
|
||||
{
|
||||
.buf = "12345\\\nabcd",
|
||||
.pos = { 0, 11 },
|
||||
.str = { "12345\\\nabcd" },
|
||||
.len = { 11, 0 }
|
||||
},
|
||||
/* 12 */
|
||||
{
|
||||
.buf = "12345\\\\\nabcd",
|
||||
.pos = { 0, 8, 12 },
|
||||
.str = { "12345\\\\\n", "abcd" },
|
||||
.len = { 8, 4, 0 }
|
||||
},
|
||||
/* 13 */
|
||||
{
|
||||
.buf = "12345\\\\\\\nabcd",
|
||||
.pos = { 0, 13 },
|
||||
.str = { "12345\\\\\\\nabcd" },
|
||||
.len = { 13, 0 }
|
||||
},
|
||||
/* 14 */
|
||||
{
|
||||
.buf = "12345\\\\\\\\\nabcd",
|
||||
.pos = { 0, 10, 14 },
|
||||
.str = { "12345\\\\\\\\\n", "abcd" },
|
||||
.len = { 10, 4, 0 }
|
||||
},
|
||||
/* 15 */
|
||||
{
|
||||
.buf = "12345\nabcd\n",
|
||||
.pos = { 0, 6, 11 },
|
||||
.str = { "12345\n", "abcd\n" },
|
||||
.len = { 6, 5, 0 }
|
||||
},
|
||||
/* 16 */
|
||||
{
|
||||
.buf = "12345\nabcd\n~!@#$%^",
|
||||
.pos = { 0, 6, 11, 18 },
|
||||
.str = { "12345\n", "abcd\n", "~!@#$%^" },
|
||||
.len = { 6, 5, 7, 0 }
|
||||
},
|
||||
/* 17 */
|
||||
{
|
||||
.buf = "12345\nabcd\n~!@#$%^\n",
|
||||
.pos = { 0, 6, 11, 19 },
|
||||
.str = { "12345\n", "abcd\n", "~!@#$%^\n" },
|
||||
.len = { 6, 5, 8, 0 }
|
||||
}
|
||||
};
|
||||
/* *INDENT-ON* */
|
||||
|
||||
/* @Test(dataSource = "data_source") */
|
||||
/* *INDENT-OFF* */
|
||||
START_PARAMETRIZED_TEST (mc_pstream_get_string_test, data_source)
|
||||
/* *INDENT-ON* */
|
||||
{
|
||||
/* given */
|
||||
int j = 0;
|
||||
|
||||
/* when */
|
||||
memset (&stream, 0, sizeof (stream));
|
||||
stream.len = strlen (data->buf);
|
||||
memmove (&stream.buf, data->buf, stream.len);
|
||||
|
||||
/* then */
|
||||
do
|
||||
{
|
||||
GString *ret;
|
||||
|
||||
mctest_assert_int_eq (stream.pos, data->pos[j]);
|
||||
|
||||
ret = mc_pstream_get_string (&stream);
|
||||
if (ret == NULL)
|
||||
break;
|
||||
|
||||
mctest_assert_int_eq (ret->len, data->len[j]);
|
||||
mctest_assert_str_eq (ret->str, data->str[j]);
|
||||
|
||||
g_string_free (ret, TRUE);
|
||||
|
||||
j++;
|
||||
}
|
||||
while (TRUE);
|
||||
}
|
||||
/* *INDENT-OFF* */
|
||||
END_PARAMETRIZED_TEST
|
||||
/* *INDENT-ON* */
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
|
||||
static mc_pipe_t *
|
||||
test_mc_popen (void)
|
||||
{
|
||||
mc_pipe_t *p;
|
||||
|
||||
p = g_try_new0 (mc_pipe_t, 1);
|
||||
/* make less than sizeof (etalon_long_file_list) */
|
||||
p->out.len = 128;
|
||||
|
||||
etalon_long_file_list_pos = 0;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
static void
|
||||
test_mc_pread (mc_pipe_t * p)
|
||||
{
|
||||
size_t len;
|
||||
|
||||
p->out.pos = 0;
|
||||
|
||||
if (etalon_long_file_list_pos >= sizeof (etalon_long_file_list))
|
||||
{
|
||||
etalon_long_file_list_pos = sizeof (etalon_long_file_list);
|
||||
p->out.len = MC_PIPE_STREAM_EOF;
|
||||
return;
|
||||
}
|
||||
|
||||
len = sizeof (etalon_long_file_list) - etalon_long_file_list_pos;
|
||||
len = MIN (len, (size_t) p->out.len);
|
||||
memmove (p->out.buf, etalon_long_file_list + etalon_long_file_list_pos, len);
|
||||
p->out.len = (ssize_t) len;
|
||||
|
||||
etalon_long_file_list_pos += len;
|
||||
}
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
START_TEST (mc_pstream_get_long_file_list_test)
|
||||
/* *INDENT-ON* */
|
||||
|
||||
{
|
||||
/* given */
|
||||
GString *result_long_file_list = NULL;
|
||||
mc_pipe_t *pip;
|
||||
GString *remain_file_name = NULL;
|
||||
|
||||
/* when */
|
||||
/* fill the list */
|
||||
memset (etalon_long_file_list, 'a', sizeof (etalon_long_file_list) - 1);
|
||||
/* create an \n-separated list */
|
||||
etalon_long_file_list[5] = '\n';
|
||||
etalon_long_file_list[25] = '\n';
|
||||
etalon_long_file_list[50] = '\n';
|
||||
etalon_long_file_list[75] = '\n';
|
||||
etalon_long_file_list[127] = '\n';
|
||||
etalon_long_file_list[200] = '\n';
|
||||
etalon_long_file_list[310] = '\n';
|
||||
etalon_long_file_list[325] = '\n';
|
||||
etalon_long_file_list[360] = '\n';
|
||||
etalon_long_file_list[512] = '\n';
|
||||
etalon_long_file_list[701] = '\n';
|
||||
etalon_long_file_list[725] = '\n';
|
||||
etalon_long_file_list[800] = '\n';
|
||||
etalon_long_file_list[sizeof (etalon_long_file_list) - 2] = '\n';
|
||||
etalon_long_file_list[sizeof (etalon_long_file_list) - 1] = '\0';
|
||||
|
||||
/* then */
|
||||
/* read file list */
|
||||
pip = test_mc_popen ();
|
||||
|
||||
while (TRUE)
|
||||
{
|
||||
GString *line;
|
||||
|
||||
test_mc_pread (pip);
|
||||
|
||||
if (pip->out.len == MC_PIPE_STREAM_EOF)
|
||||
break;
|
||||
|
||||
while ((line = mc_pstream_get_string (&pip->out)) != NULL)
|
||||
{
|
||||
/* handle an \n-separated file list */
|
||||
|
||||
if (line->str[line->len - 1] == '\n')
|
||||
{
|
||||
/* entire file name or last chunk */
|
||||
|
||||
g_string_truncate (line, line->len - 1);
|
||||
|
||||
/* join filename chunks */
|
||||
if (remain_file_name != NULL)
|
||||
{
|
||||
g_string_append_len (remain_file_name, line->str, line->len);
|
||||
g_string_free (line, TRUE);
|
||||
line = remain_file_name;
|
||||
remain_file_name = NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* first or middle chunk of file name */
|
||||
if (remain_file_name == NULL)
|
||||
remain_file_name = line;
|
||||
else
|
||||
{
|
||||
g_string_append_len (remain_file_name, line->str, line->len);
|
||||
g_string_free (line, TRUE);
|
||||
}
|
||||
|
||||
line = NULL;
|
||||
}
|
||||
|
||||
/* collect file names to assemble the result string */
|
||||
if (line == NULL)
|
||||
continue;
|
||||
|
||||
if (result_long_file_list == NULL)
|
||||
result_long_file_list = line;
|
||||
else
|
||||
{
|
||||
g_string_append_len (result_long_file_list, line->str, line->len);
|
||||
g_string_free (line, TRUE);
|
||||
}
|
||||
|
||||
g_string_append_c (result_long_file_list, '\n');
|
||||
}
|
||||
}
|
||||
|
||||
mctest_assert_str_eq (etalon_long_file_list, result_long_file_list->str);
|
||||
g_string_free (result_long_file_list, TRUE);
|
||||
|
||||
}
|
||||
/* *INDENT-OFF* */
|
||||
END_TEST
|
||||
/* *INDENT-ON* */
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
|
||||
int
|
||||
main (void)
|
||||
{
|
||||
TCase *tc_core;
|
||||
|
||||
tc_core = tcase_create ("Core");
|
||||
|
||||
tcase_add_checked_fixture (tc_core, setup, teardown);
|
||||
|
||||
/* Add new tests here: *************** */
|
||||
mctest_add_parameterized_test (tc_core, mc_pstream_get_string_test, data_source);
|
||||
tcase_add_test (tc_core, mc_pstream_get_long_file_list_test);
|
||||
/* *********************************** */
|
||||
|
||||
return mctest_run_all (tc_core);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
Loading…
Reference in New Issue
Block a user