glib/gio/tests/converter-stream.c
Philip Withnall aec5d17d77 tests: Remove an incorrect assertion in converter-stream
While the assertion always turned out to be true on Linux, it frequently
caused spurious test failures on FreeBSD.

After some remote debugging, I *think* the cause is as written up in the
comment in the code in this commit. However, I cannot be certain, as the
more debugging messages I added, the harder the failure was to
reproduce; and I don’t have access to a FreeBSD machine.

This fixes failures like:
```
Bail out! GLib-GIO:ERROR:../gio/tests/converter-stream.c:1043:test_converter_pollable: assertion failed (error == NULL): Resource temporarily unavailable (g-io-error-quark, 27)
```

It’s succeeded 1000 times in a row on the FreeBSD CI now; previously
it was failing one time in three:
https://gitlab.gnome.org/GNOME/glib/-/jobs/1936395.

Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
2022-04-05 18:19:23 +01:00

1235 lines
34 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* GLib testing framework examples and tests
* Copyright (C) 2009 Red Hat, Inc.
* Authors: Alexander Larsson <alexl@redhat.com>
*
* This work is provided "as is"; redistribution and modification
* in whole or in part, in any medium, physical or electronic is
* permitted without restriction.
*
* This work 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.
*
* In no event shall the authors or contributors be liable for any
* direct, indirect, incidental, special, exemplary, or consequential
* damages (including, but not limited to, procurement of substitute
* goods or services; loss of use, data, or profits; or business
* interruption) however caused and on any theory of liability, whether
* in contract, strict liability, or tort (including negligence or
* otherwise) arising in any way out of the use of this software, even
* if advised of the possibility of such damage.
*/
#include <glib/glib.h>
#include <gio/gio.h>
#include <stdlib.h>
#include <string.h>
#define G_TYPE_EXPANDER_CONVERTER (g_expander_converter_get_type ())
#define G_EXPANDER_CONVERTER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_EXPANDER_CONVERTER, GExpanderConverter))
#define G_EXPANDER_CONVERTER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_EXPANDER_CONVERTER, GExpanderConverterClass))
#define G_IS_EXPANDER_CONVERTER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_EXPANDER_CONVERTER))
#define G_IS_EXPANDER_CONVERTER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_EXPANDER_CONVERTER))
#define G_EXPANDER_CONVERTER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_EXPANDER_CONVERTER, GExpanderConverterClass))
typedef struct _GExpanderConverter GExpanderConverter;
typedef struct _GExpanderConverterClass GExpanderConverterClass;
struct _GExpanderConverterClass
{
GObjectClass parent_class;
};
GType g_expander_converter_get_type (void) G_GNUC_CONST;
GConverter *g_expander_converter_new (void);
static void g_expander_converter_iface_init (GConverterIface *iface);
struct _GExpanderConverter
{
GObject parent_instance;
};
G_DEFINE_TYPE_WITH_CODE (GExpanderConverter, g_expander_converter, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_CONVERTER,
g_expander_converter_iface_init))
static void
g_expander_converter_class_init (GExpanderConverterClass *klass)
{
}
static void
g_expander_converter_init (GExpanderConverter *local)
{
}
GConverter *
g_expander_converter_new (void)
{
GConverter *conv;
conv = g_object_new (G_TYPE_EXPANDER_CONVERTER, NULL);
return conv;
}
static void
g_expander_converter_reset (GConverter *converter)
{
}
static GConverterResult
g_expander_converter_convert (GConverter *converter,
const void *inbuf,
gsize inbuf_size,
void *outbuf,
gsize outbuf_size,
GConverterFlags flags,
gsize *bytes_read,
gsize *bytes_written,
GError **error)
{
const guint8 *in, *in_end;
guint8 v, *out;
gsize i;
gsize block_size;
in = inbuf;
out = outbuf;
in_end = in + inbuf_size;
while (in < in_end)
{
v = *in;
if (v == 0)
block_size = 10;
else
block_size = v * 1000;
if (outbuf_size < block_size)
{
if (*bytes_read > 0)
return G_CONVERTER_CONVERTED;
g_set_error_literal (error, G_IO_ERROR,
G_IO_ERROR_NO_SPACE,
"No space in dest");
return G_CONVERTER_ERROR;
}
in++;
*bytes_read += 1;
*bytes_written += block_size;
outbuf_size -= block_size;
for (i = 0; i < block_size; i++)
*out++ = v;
}
if (in == in_end && (flags & G_CONVERTER_INPUT_AT_END))
return G_CONVERTER_FINISHED;
return G_CONVERTER_CONVERTED;
}
static void
g_expander_converter_iface_init (GConverterIface *iface)
{
iface->convert = g_expander_converter_convert;
iface->reset = g_expander_converter_reset;
}
#define G_TYPE_COMPRESSOR_CONVERTER (g_compressor_converter_get_type ())
#define G_COMPRESSOR_CONVERTER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_COMPRESSOR_CONVERTER, GCompressorConverter))
#define G_COMPRESSOR_CONVERTER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_COMPRESSOR_CONVERTER, GCompressorConverterClass))
#define G_IS_COMPRESSOR_CONVERTER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_COMPRESSOR_CONVERTER))
#define G_IS_COMPRESSOR_CONVERTER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_COMPRESSOR_CONVERTER))
#define G_COMPRESSOR_CONVERTER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_COMPRESSOR_CONVERTER, GCompressorConverterClass))
typedef struct _GCompressorConverter GCompressorConverter;
typedef struct _GCompressorConverterClass GCompressorConverterClass;
struct _GCompressorConverterClass
{
GObjectClass parent_class;
};
GType g_compressor_converter_get_type (void) G_GNUC_CONST;
GConverter *g_compressor_converter_new (void);
static void g_compressor_converter_iface_init (GConverterIface *iface);
struct _GCompressorConverter
{
GObject parent_instance;
};
G_DEFINE_TYPE_WITH_CODE (GCompressorConverter, g_compressor_converter, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_CONVERTER,
g_compressor_converter_iface_init))
static void
g_compressor_converter_class_init (GCompressorConverterClass *klass)
{
}
static void
g_compressor_converter_init (GCompressorConverter *local)
{
}
GConverter *
g_compressor_converter_new (void)
{
GConverter *conv;
conv = g_object_new (G_TYPE_COMPRESSOR_CONVERTER, NULL);
return conv;
}
static void
g_compressor_converter_reset (GConverter *converter)
{
}
static GConverterResult
g_compressor_converter_convert (GConverter *converter,
const void *inbuf,
gsize inbuf_size,
void *outbuf,
gsize outbuf_size,
GConverterFlags flags,
gsize *bytes_read,
gsize *bytes_written,
GError **error)
{
const guint8 *in, *in_end;
guint8 v, *out;
gsize i;
gsize block_size;
in = inbuf;
out = outbuf;
in_end = in + inbuf_size;
while (in < in_end)
{
v = *in;
if (v == 0)
{
block_size = 0;
while (in+block_size < in_end && *(in+block_size) == 0)
block_size ++;
}
else
block_size = v * 1000;
/* Not enough data */
if ((gsize) (in_end - in) < block_size)
{
if (*bytes_read > 0)
break;
g_set_error_literal (error, G_IO_ERROR,
G_IO_ERROR_PARTIAL_INPUT,
"Need more data");
return G_CONVERTER_ERROR;
}
for (i = 0; i < block_size; i++)
{
if (*(in + i) != v)
{
if (*bytes_read > 0)
break;
g_set_error_literal (error, G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"invalid data");
return G_CONVERTER_ERROR;
}
}
if (v == 0 && (gsize) (in_end - in) == block_size && (flags & G_CONVERTER_INPUT_AT_END) == 0)
{
if (*bytes_read > 0)
break;
g_set_error_literal (error, G_IO_ERROR,
G_IO_ERROR_PARTIAL_INPUT,
"Need more data");
return G_CONVERTER_ERROR;
}
in += block_size;
*out++ = v;
*bytes_read += block_size;
*bytes_written += 1;
}
if (in == in_end && (flags & G_CONVERTER_INPUT_AT_END))
return G_CONVERTER_FINISHED;
return G_CONVERTER_CONVERTED;
}
static void
g_compressor_converter_iface_init (GConverterIface *iface)
{
iface->convert = g_compressor_converter_convert;
iface->reset = g_compressor_converter_reset;
}
guint8 unexpanded_data[] = { 0,1,3,4,5,6,7,3,12,0,0};
static void
test_expander (void)
{
guint8 *converted1, *converted2, *ptr;
gsize n_read, n_written;
gsize total_read;
gssize res;
GConverterResult cres;
GInputStream *mem, *cstream;
GOutputStream *mem_out, *cstream_out;
GConverter *expander;
GConverter *converter;
GError *error;
gsize i;
expander = g_expander_converter_new ();
converted1 = g_malloc (100*1000); /* Large enough */
converted2 = g_malloc (100*1000); /* Large enough */
cres = g_converter_convert (expander,
unexpanded_data, sizeof(unexpanded_data),
converted1, 100*1000,
G_CONVERTER_INPUT_AT_END,
&n_read, &n_written, NULL);
g_assert_cmpint (cres, ==, G_CONVERTER_FINISHED);
g_assert_cmpuint (n_read, ==, 11);
g_assert_cmpuint (n_written, ==, 41030);
g_converter_reset (expander);
mem = g_memory_input_stream_new_from_data (unexpanded_data,
sizeof (unexpanded_data),
NULL);
cstream = g_converter_input_stream_new (mem, expander);
g_assert_true (g_converter_input_stream_get_converter (G_CONVERTER_INPUT_STREAM (cstream)) == expander);
g_object_get (cstream, "converter", &converter, NULL);
g_assert_true (converter == expander);
g_object_unref (converter);
g_object_unref (mem);
total_read = 0;
ptr = converted2;
while (TRUE)
{
error = NULL;
res = g_input_stream_read (cstream,
ptr, 1,
NULL, &error);
g_assert_cmpint (res, !=, -1);
if (res == 0)
break;
ptr += res;
total_read += res;
}
g_assert_cmpmem (converted1, n_written, converted2, total_read);
g_converter_reset (expander);
mem_out = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
cstream_out = g_converter_output_stream_new (mem_out, expander);
g_assert_true (g_converter_output_stream_get_converter (G_CONVERTER_OUTPUT_STREAM (cstream_out)) == expander);
g_object_get (cstream_out, "converter", &converter, NULL);
g_assert_true (converter == expander);
g_object_unref (converter);
g_object_unref (mem_out);
for (i = 0; i < sizeof(unexpanded_data); i++)
{
error = NULL;
res = g_output_stream_write (cstream_out,
unexpanded_data + i, 1,
NULL, &error);
g_assert_cmpint (res, !=, -1);
if (res == 0)
{
g_assert_cmpuint (i, ==, sizeof(unexpanded_data) -1);
break;
}
g_assert_cmpint (res, ==, 1);
}
g_output_stream_close (cstream_out, NULL, NULL);
g_assert_cmpmem (g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (mem_out)),
g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (mem_out)),
converted1, n_written);
g_free (converted1);
g_free (converted2);
g_object_unref (cstream);
g_object_unref (cstream_out);
g_object_unref (expander);
}
static void
test_compressor (void)
{
guint8 *converted, *expanded, *ptr;
gsize n_read, expanded_size;
gsize total_read;
gssize res;
GConverterResult cres;
GInputStream *mem, *cstream;
GOutputStream *mem_out, *cstream_out;
GConverter *expander, *compressor;
GError *error;
gsize i;
expander = g_expander_converter_new ();
expanded = g_malloc (100*1000); /* Large enough */
cres = g_converter_convert (expander,
unexpanded_data, sizeof(unexpanded_data),
expanded, 100*1000,
G_CONVERTER_INPUT_AT_END,
&n_read, &expanded_size, NULL);
g_assert_cmpint (cres, ==, G_CONVERTER_FINISHED);
g_assert_cmpuint (n_read, ==, 11);
g_assert_cmpuint (expanded_size, ==, 41030);
compressor = g_compressor_converter_new ();
converted = g_malloc (100*1000); /* Large enough */
mem = g_memory_input_stream_new_from_data (expanded,
expanded_size,
NULL);
cstream = g_converter_input_stream_new (mem, compressor);
g_object_unref (mem);
total_read = 0;
ptr = converted;
while (TRUE)
{
error = NULL;
res = g_input_stream_read (cstream,
ptr, 1,
NULL, &error);
g_assert_cmpint (res, !=, -1);
if (res == 0)
break;
ptr += res;
total_read += res;
}
/* "n_read - 1" because last 2 zeros are combined */
g_assert_cmpmem (unexpanded_data, n_read - 1, converted, total_read);
g_object_unref (cstream);
g_converter_reset (compressor);
mem_out = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
cstream_out = g_converter_output_stream_new (mem_out, compressor);
g_object_unref (mem_out);
for (i = 0; i < expanded_size; i++)
{
error = NULL;
res = g_output_stream_write (cstream_out,
expanded + i, 1,
NULL, &error);
g_assert_cmpint (res, !=, -1);
if (res == 0)
{
g_assert_cmpuint (i, ==, expanded_size -1);
break;
}
g_assert_cmpint (res, ==, 1);
}
g_output_stream_close (cstream_out, NULL, NULL);
/* "n_read - 1" because last 2 zeros are combined */
g_assert_cmpmem (g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (mem_out)),
g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (mem_out)),
unexpanded_data,
n_read - 1);
g_object_unref (cstream_out);
g_converter_reset (compressor);
memset (expanded, 5, 5*1000*2);
mem = g_memory_input_stream_new_from_data (expanded,
5*1000,
NULL);
cstream = g_converter_input_stream_new (mem, compressor);
g_object_unref (mem);
total_read = 0;
ptr = converted;
while (TRUE)
{
error = NULL;
res = g_input_stream_read (cstream,
ptr, 1,
NULL, &error);
g_assert_cmpint (res, !=, -1);
if (res == 0)
break;
ptr += res;
total_read += res;
}
g_assert_cmpuint (total_read, ==, 1);
g_assert_cmpuint (*converted, ==, 5);
g_object_unref (cstream);
mem = g_memory_input_stream_new_from_data (expanded,
5*1000 * 2,
NULL);
cstream = g_converter_input_stream_new (mem, compressor);
g_object_unref (mem);
total_read = 0;
ptr = converted;
while (TRUE)
{
error = NULL;
res = g_input_stream_read (cstream,
ptr, 1,
NULL, &error);
g_assert_cmpint (res, !=, -1);
if (res == 0)
break;
ptr += res;
total_read += res;
}
g_assert_cmpuint (total_read, ==, 2);
g_assert_cmpuint (converted[0], ==, 5);
g_assert_cmpuint (converted[1], ==, 5);
g_object_unref (cstream);
g_converter_reset (compressor);
mem = g_memory_input_stream_new_from_data (expanded,
5*1000 * 2 - 1,
NULL);
cstream = g_converter_input_stream_new (mem, compressor);
g_object_unref (mem);
total_read = 0;
ptr = converted;
while (TRUE)
{
error = NULL;
res = g_input_stream_read (cstream,
ptr, 1,
NULL, &error);
if (res == -1)
{
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT);
g_error_free (error);
break;
}
g_assert_cmpint (res, !=, 0);
ptr += res;
total_read += res;
}
g_assert_cmpuint (total_read, ==, 1);
g_assert_cmpuint (converted[0], ==, 5);
g_object_unref (cstream);
g_free (expanded);
g_free (converted);
g_object_unref (expander);
g_object_unref (compressor);
}
#define LEFTOVER_SHORT_READ_SIZE 512
#define G_TYPE_LEFTOVER_CONVERTER (g_leftover_converter_get_type ())
#define G_LEFTOVER_CONVERTER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_LEFTOVER_CONVERTER, GLeftoverConverter))
#define G_LEFTOVER_CONVERTER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_LEFTOVER_CONVERTER, GLeftoverConverterClass))
#define G_IS_LEFTOVER_CONVERTER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_LEFTOVER_CONVERTER))
#define G_IS_LEFTOVER_CONVERTER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_LEFTOVER_CONVERTER))
#define G_LEFTOVER_CONVERTER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_LEFTOVER_CONVERTER, GLeftoverConverterClass))
typedef struct _GLeftoverConverter GLeftoverConverter;
typedef struct _GLeftoverConverterClass GLeftoverConverterClass;
struct _GLeftoverConverterClass
{
GObjectClass parent_class;
};
GType g_leftover_converter_get_type (void) G_GNUC_CONST;
GConverter *g_leftover_converter_new (void);
static void g_leftover_converter_iface_init (GConverterIface *iface);
struct _GLeftoverConverter
{
GObject parent_instance;
};
G_DEFINE_TYPE_WITH_CODE (GLeftoverConverter, g_leftover_converter, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_CONVERTER,
g_leftover_converter_iface_init))
static void
g_leftover_converter_class_init (GLeftoverConverterClass *klass)
{
}
static void
g_leftover_converter_init (GLeftoverConverter *local)
{
}
GConverter *
g_leftover_converter_new (void)
{
GConverter *conv;
conv = g_object_new (G_TYPE_LEFTOVER_CONVERTER, NULL);
return conv;
}
static void
g_leftover_converter_reset (GConverter *converter)
{
}
static GConverterResult
g_leftover_converter_convert (GConverter *converter,
const void *inbuf,
gsize inbuf_size,
void *outbuf,
gsize outbuf_size,
GConverterFlags flags,
gsize *bytes_read,
gsize *bytes_written,
GError **error)
{
if (outbuf_size == LEFTOVER_SHORT_READ_SIZE)
{
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_PARTIAL_INPUT,
"partial input");
return G_CONVERTER_ERROR;
}
if (inbuf_size < 100)
*bytes_read = *bytes_written = MIN (inbuf_size, outbuf_size);
else
*bytes_read = *bytes_written = MIN (inbuf_size - 10, outbuf_size);
memcpy (outbuf, inbuf, *bytes_written);
if (*bytes_read == inbuf_size && (flags & G_CONVERTER_INPUT_AT_END))
return G_CONVERTER_FINISHED;
return G_CONVERTER_CONVERTED;
}
static void
g_leftover_converter_iface_init (GConverterIface *iface)
{
iface->convert = g_leftover_converter_convert;
iface->reset = g_leftover_converter_reset;
}
#define LEFTOVER_BUFSIZE 8192
#define INTERNAL_BUFSIZE 4096
static void
test_converter_leftover (void)
{
gchar *orig, *converted;
gsize total_read;
gssize res;
goffset offset;
GInputStream *mem, *cstream;
GConverter *converter;
GError *error;
int i;
converter = g_leftover_converter_new ();
orig = g_malloc (LEFTOVER_BUFSIZE);
converted = g_malloc (LEFTOVER_BUFSIZE);
for (i = 0; i < LEFTOVER_BUFSIZE; i++)
orig[i] = i % 64 + 32;
mem = g_memory_input_stream_new_from_data (orig, LEFTOVER_BUFSIZE, NULL);
cstream = g_converter_input_stream_new (mem, G_CONVERTER (converter));
g_object_unref (mem);
total_read = 0;
error = NULL;
res = g_input_stream_read (cstream,
converted, LEFTOVER_SHORT_READ_SIZE,
NULL, &error);
g_assert_cmpint (res, ==, LEFTOVER_SHORT_READ_SIZE);
total_read += res;
offset = g_seekable_tell (G_SEEKABLE (mem));
g_assert_cmpint (offset, >, LEFTOVER_SHORT_READ_SIZE);
g_assert_cmpint (offset, <, LEFTOVER_BUFSIZE);
/* At this point, @cstream has both a non-empty input_buffer
* and a non-empty converted_buffer, which is the case
* we want to test.
*/
while (TRUE)
{
error = NULL;
res = g_input_stream_read (cstream,
converted + total_read,
LEFTOVER_BUFSIZE - total_read,
NULL, &error);
g_assert_cmpint (res, >=, 0);
if (res == 0)
break;
total_read += res;
}
g_assert_cmpmem (orig, LEFTOVER_BUFSIZE, converted, total_read);
g_object_unref (cstream);
g_free (orig);
g_free (converted);
g_object_unref (converter);
}
#define DATA_LENGTH 1000000
typedef struct {
const gchar *path;
GZlibCompressorFormat format;
gint level;
} CompressorTest;
static void
test_roundtrip (gconstpointer data)
{
const CompressorTest *test = data;
GError *error = NULL;
guint32 *data0, *data1;
gsize data1_size;
gint i;
GInputStream *istream0, *istream1, *cistream1;
GOutputStream *ostream1, *ostream2, *costream1;
GConverter *compressor, *decompressor;
GZlibCompressorFormat fmt;
gint lvl;
GFileInfo *info;
GFileInfo *info2;
g_test_bug ("https://bugzilla.gnome.org/show_bug.cgi?id=619945");
data0 = g_malloc (DATA_LENGTH * sizeof (guint32));
for (i = 0; i < DATA_LENGTH; i++)
data0[i] = g_random_int ();
istream0 = g_memory_input_stream_new_from_data (data0,
DATA_LENGTH * sizeof (guint32), NULL);
ostream1 = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
compressor = G_CONVERTER (g_zlib_compressor_new (test->format, test->level));
info = g_file_info_new ();
g_file_info_set_name (info, "foo");
g_object_set (compressor, "file-info", info, NULL);
info2 = g_zlib_compressor_get_file_info (G_ZLIB_COMPRESSOR (compressor));
g_assert_true (info == info2);
g_object_unref (info);
costream1 = g_converter_output_stream_new (ostream1, compressor);
g_assert_true (g_converter_output_stream_get_converter (G_CONVERTER_OUTPUT_STREAM (costream1)) == compressor);
g_output_stream_splice (costream1, istream0, 0, NULL, &error);
g_assert_no_error (error);
g_object_unref (costream1);
g_converter_reset (compressor);
g_object_get (compressor, "format", &fmt, "level", &lvl, NULL);
g_assert_cmpint (fmt, ==, test->format);
g_assert_cmpint (lvl, ==, test->level);
g_object_unref (compressor);
data1 = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (ostream1));
data1_size = g_memory_output_stream_get_data_size (
G_MEMORY_OUTPUT_STREAM (ostream1));
g_object_unref (ostream1);
g_object_unref (istream0);
istream1 = g_memory_input_stream_new_from_data (data1, data1_size, g_free);
decompressor = G_CONVERTER (g_zlib_decompressor_new (test->format));
cistream1 = g_converter_input_stream_new (istream1, decompressor);
ostream2 = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
g_output_stream_splice (ostream2, cistream1, 0, NULL, &error);
g_assert_no_error (error);
g_assert_cmpmem (data0, DATA_LENGTH * sizeof (guint32),
g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (ostream2)),
g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (ostream2)));
g_object_unref (istream1);
g_converter_reset (decompressor);
g_object_get (decompressor, "format", &fmt, NULL);
g_assert_cmpint (fmt, ==, test->format);
g_object_unref (decompressor);
g_object_unref (cistream1);
g_object_unref (ostream2);
g_free (data0);
}
typedef struct {
const gchar *path;
const gchar *charset_in;
const gchar *text_in;
const gchar *charset_out;
const gchar *text_out;
gint n_fallbacks;
} CharsetTest;
static void
test_charset (gconstpointer data)
{
const CharsetTest *test = data;
GInputStream *in, *in2;
GConverter *conv;
gchar *buffer;
gsize count;
gsize bytes_read;
GError *error;
gboolean fallback;
conv = (GConverter *)g_charset_converter_new (test->charset_out, test->charset_in, NULL);
g_object_get (conv, "use-fallback", &fallback, NULL);
g_assert_false (fallback);
in = g_memory_input_stream_new_from_data (test->text_in, -1, NULL);
in2 = g_converter_input_stream_new (in, conv);
count = 2 * strlen (test->text_out);
buffer = g_malloc0 (count);
error = NULL;
g_input_stream_read_all (in2, buffer, count, &bytes_read, NULL, &error);
if (test->n_fallbacks == 0)
{
g_assert_no_error (error);
g_assert_cmpint (bytes_read, ==, strlen (test->text_out));
g_assert_cmpstr (buffer, ==, test->text_out);
}
else
{
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA);
g_error_free (error);
}
g_free (buffer);
g_object_unref (in2);
g_object_unref (in);
if (test->n_fallbacks == 0)
{
g_object_unref (conv);
return;
}
g_converter_reset (conv);
g_assert_false (g_charset_converter_get_use_fallback (G_CHARSET_CONVERTER (conv)));
g_charset_converter_set_use_fallback (G_CHARSET_CONVERTER (conv), TRUE);
in = g_memory_input_stream_new_from_data (test->text_in, -1, NULL);
in2 = g_converter_input_stream_new (in, conv);
count = 2 * strlen (test->text_out);
buffer = g_malloc0 (count);
error = NULL;
g_input_stream_read_all (in2, buffer, count, &bytes_read, NULL, &error);
g_assert_no_error (error);
g_assert_cmpstr (buffer, ==, test->text_out);
g_assert_cmpint (bytes_read, ==, strlen (test->text_out));
g_assert_cmpint (test->n_fallbacks, ==, g_charset_converter_get_num_fallbacks (G_CHARSET_CONVERTER (conv)));
g_free (buffer);
g_object_unref (in2);
g_object_unref (in);
g_object_unref (conv);
}
static void
client_connected (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GSocketClient *client = G_SOCKET_CLIENT (source);
GSocketConnection **conn = user_data;
GError *error = NULL;
*conn = g_socket_client_connect_finish (client, result, &error);
g_assert_no_error (error);
}
static void
server_connected (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GSocketListener *listener = G_SOCKET_LISTENER (source);
GSocketConnection **conn = user_data;
GError *error = NULL;
*conn = g_socket_listener_accept_finish (listener, result, NULL, &error);
g_assert_no_error (error);
}
static void
make_socketpair (GIOStream **left,
GIOStream **right)
{
GInetAddress *iaddr;
GSocketAddress *saddr, *effective_address;
GSocketListener *listener;
GSocketClient *client;
GError *error = NULL;
GSocketConnection *client_conn = NULL, *server_conn = NULL;
iaddr = g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV4);
saddr = g_inet_socket_address_new (iaddr, 0);
g_object_unref (iaddr);
listener = g_socket_listener_new ();
g_socket_listener_add_address (listener, saddr,
G_SOCKET_TYPE_STREAM,
G_SOCKET_PROTOCOL_TCP,
NULL,
&effective_address,
&error);
g_assert_no_error (error);
g_object_unref (saddr);
client = g_socket_client_new ();
g_socket_client_connect_async (client,
G_SOCKET_CONNECTABLE (effective_address),
NULL, client_connected, &client_conn);
g_socket_listener_accept_async (listener, NULL,
server_connected, &server_conn);
while (!client_conn || !server_conn)
g_main_context_iteration (NULL, TRUE);
g_object_unref (client);
g_object_unref (listener);
g_object_unref (effective_address);
*left = G_IO_STREAM (client_conn);
*right = G_IO_STREAM (server_conn);
}
static void
test_converter_pollable (void)
{
GIOStream *left, *right;
guint8 *converted, *inptr;
guint8 *expanded, *outptr, *expanded_end;
gsize n_read, expanded_size;
gsize total_read;
gssize res;
gboolean is_readable;
GConverterResult cres;
GInputStream *cstream;
GPollableInputStream *pollable_in;
GOutputStream *socket_out, *mem_out, *cstream_out;
GPollableOutputStream *pollable_out;
GConverter *expander, *compressor;
GError *error;
gsize i;
expander = g_expander_converter_new ();
expanded = g_malloc (100*1000); /* Large enough */
cres = g_converter_convert (expander,
unexpanded_data, sizeof(unexpanded_data),
expanded, 100*1000,
G_CONVERTER_INPUT_AT_END,
&n_read, &expanded_size, NULL);
g_assert_cmpint (cres, ==, G_CONVERTER_FINISHED);
g_assert_cmpuint (n_read, ==, 11);
g_assert_cmpuint (expanded_size, ==, 41030);
expanded_end = expanded + expanded_size;
make_socketpair (&left, &right);
compressor = g_compressor_converter_new ();
converted = g_malloc (100*1000); /* Large enough */
cstream = g_converter_input_stream_new (g_io_stream_get_input_stream (left),
compressor);
pollable_in = G_POLLABLE_INPUT_STREAM (cstream);
g_assert_true (g_pollable_input_stream_can_poll (pollable_in));
socket_out = g_io_stream_get_output_stream (right);
total_read = 0;
outptr = expanded;
inptr = converted;
while (TRUE)
{
error = NULL;
if (outptr < expanded_end)
{
res = g_output_stream_write (socket_out,
outptr,
MIN (1000, (expanded_end - outptr)),
NULL, &error);
g_assert_cmpint (res, >, 0);
outptr += res;
}
else if (socket_out)
{
g_output_stream_close (socket_out, NULL, NULL);
g_object_unref (right);
socket_out = NULL;
}
/* Wait a few ticks to check for the pipe to propagate the
* write. We cant wait on a GSource as that might affect the stream under
* test, so just poll. */
while (!g_pollable_input_stream_is_readable (pollable_in))
g_usleep (80L);
is_readable = g_pollable_input_stream_is_readable (pollable_in);
res = g_pollable_input_stream_read_nonblocking (pollable_in,
inptr, 1,
NULL, &error);
/* is_readable can be a false positive, but not a false negative */
if (!is_readable)
g_assert_cmpint (res, ==, -1);
/* Even after closing the write end, we can get WOULD_BLOCK (particularly
* on FreeBSD), so we cant make any assertions based on `!socket_out`.
* This is because the FIN packets may still be in the out buffer of one
* half of the socket pair, while the in buffer of the other half has some
* data, but not enough for a full block for the converter to consume. */
if (res == -1)
{
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK);
g_error_free (error);
continue;
}
if (res == 0)
break;
inptr += res;
total_read += res;
}
/* "n_read - 1" because last 2 zeros are combined */
g_assert_cmpmem (unexpanded_data, n_read - 1, converted, total_read);
g_object_unref (cstream);
g_object_unref (left);
g_converter_reset (compressor);
/* This doesn't actually test the behavior on
* G_IO_ERROR_WOULD_BLOCK; to do that we'd need to implement a
* custom GOutputStream that we could control blocking on.
*/
mem_out = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
cstream_out = g_converter_output_stream_new (mem_out, compressor);
g_object_unref (mem_out);
pollable_out = G_POLLABLE_OUTPUT_STREAM (cstream_out);
g_assert_true (g_pollable_output_stream_can_poll (pollable_out));
g_assert_true (g_pollable_output_stream_is_writable (pollable_out));
for (i = 0; i < expanded_size; i++)
{
error = NULL;
res = g_pollable_output_stream_write_nonblocking (pollable_out,
expanded + i, 1,
NULL, &error);
g_assert_cmpint (res, !=, -1);
if (res == 0)
{
g_assert_cmpuint (i, ==, expanded_size -1);
break;
}
g_assert_cmpint (res, ==, 1);
}
g_output_stream_close (cstream_out, NULL, NULL);
/* "n_read - 1" because last 2 zeros are combined */
g_assert_cmpmem (g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (mem_out)),
g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (mem_out)),
unexpanded_data,
n_read - 1);
g_object_unref (cstream_out);
g_free (expanded);
g_free (converted);
g_object_unref (expander);
g_object_unref (compressor);
}
static void
test_truncation (gconstpointer data)
{
const CompressorTest *test = data;
GError *error = NULL;
guint32 *data0, *data1;
gsize data1_size;
gint i;
GInputStream *istream0, *istream1, *cistream1;
GOutputStream *ostream1, *ostream2, *costream1;
GConverter *compressor, *decompressor;
data0 = g_malloc (DATA_LENGTH * sizeof (guint32));
for (i = 0; i < DATA_LENGTH; i++)
data0[i] = g_random_int ();
istream0 = g_memory_input_stream_new_from_data (data0,
DATA_LENGTH * sizeof (guint32), NULL);
ostream1 = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
compressor = G_CONVERTER (g_zlib_compressor_new (test->format, -1));
costream1 = g_converter_output_stream_new (ostream1, compressor);
g_assert_true (g_converter_output_stream_get_converter (G_CONVERTER_OUTPUT_STREAM (costream1)) == compressor);
g_output_stream_splice (costream1, istream0, 0, NULL, &error);
g_assert_no_error (error);
g_object_unref (costream1);
g_object_unref (compressor);
data1 = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (ostream1));
data1_size = g_memory_output_stream_get_data_size (
G_MEMORY_OUTPUT_STREAM (ostream1));
g_object_unref (ostream1);
g_object_unref (istream0);
/* truncate */
data1_size /= 2;
istream1 = g_memory_input_stream_new_from_data (data1, data1_size, g_free);
decompressor = G_CONVERTER (g_zlib_decompressor_new (test->format));
cistream1 = g_converter_input_stream_new (istream1, decompressor);
ostream2 = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
g_output_stream_splice (ostream2, cistream1, 0, NULL, &error);
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT);
g_error_free (error);
g_object_unref (istream1);
g_object_unref (decompressor);
g_object_unref (cistream1);
g_object_unref (ostream2);
g_free (data0);
}
static void
test_converter_basics (void)
{
GConverter *converter;
GError *error = NULL;
gchar *to;
gchar *from;
converter = (GConverter *)g_charset_converter_new ("utf-8", "latin1", &error);
g_assert_no_error (error);
g_object_get (converter,
"to-charset", &to,
"from-charset", &from,
NULL);
g_assert_cmpstr (to, ==, "utf-8");
g_assert_cmpstr (from, ==, "latin1");
g_free (to);
g_free (from);
g_object_unref (converter);
}
int
main (int argc,
char *argv[])
{
CompressorTest compressor_tests[] = {
{ "/converter-output-stream/roundtrip/zlib-0", G_ZLIB_COMPRESSOR_FORMAT_ZLIB, 0 },
{ "/converter-output-stream/roundtrip/zlib-9", G_ZLIB_COMPRESSOR_FORMAT_ZLIB, 9 },
{ "/converter-output-stream/roundtrip/gzip-0", G_ZLIB_COMPRESSOR_FORMAT_GZIP, 0 },
{ "/converter-output-stream/roundtrip/gzip-9", G_ZLIB_COMPRESSOR_FORMAT_GZIP, 9 },
{ "/converter-output-stream/roundtrip/raw-0", G_ZLIB_COMPRESSOR_FORMAT_RAW, 0 },
{ "/converter-output-stream/roundtrip/raw-9", G_ZLIB_COMPRESSOR_FORMAT_RAW, 9 },
};
CompressorTest truncation_tests[] = {
{ "/converter-input-stream/truncation/zlib", G_ZLIB_COMPRESSOR_FORMAT_ZLIB, 0 },
{ "/converter-input-stream/truncation/gzip", G_ZLIB_COMPRESSOR_FORMAT_GZIP, 0 },
{ "/converter-input-stream/truncation/raw", G_ZLIB_COMPRESSOR_FORMAT_RAW, 0 },
};
CharsetTest charset_tests[] = {
{ "/converter-input-stream/charset/utf8->latin1", "UTF-8", "\303\205rr Sant\303\251", "ISO-8859-1", "\305rr Sant\351", 0 },
{ "/converter-input-stream/charset/latin1->utf8", "ISO-8859-1", "\305rr Sant\351", "UTF-8", "\303\205rr Sant\303\251", 0 },
{ "/converter-input-stream/charset/fallbacks", "UTF-8", "Some characters just don't fit into latin1: πא", "ISO-8859-1", "Some characters just don't fit into latin1: \\CF\\80\\D7\\90", 4 },
};
gsize i;
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/converter/basics", test_converter_basics);
g_test_add_func ("/converter-input-stream/expander", test_expander);
g_test_add_func ("/converter-input-stream/compressor", test_compressor);
for (i = 0; i < G_N_ELEMENTS (compressor_tests); i++)
g_test_add_data_func (compressor_tests[i].path, &compressor_tests[i], test_roundtrip);
for (i = 0; i < G_N_ELEMENTS (truncation_tests); i++)
g_test_add_data_func (truncation_tests[i].path, &truncation_tests[i], test_truncation);
for (i = 0; i < G_N_ELEMENTS (charset_tests); i++)
g_test_add_data_func (charset_tests[i].path, &charset_tests[i], test_charset);
g_test_add_func ("/converter-stream/pollable", test_converter_pollable);
g_test_add_func ("/converter-stream/leftover", test_converter_leftover);
return g_test_run();
}