glib/gio/tests/converter-stream.c
Philip Withnall 96caca2b7a tests: Explicitly close stream in converter-stream
When the test has finished writing all the expanded content into the
socket, explicitly close the output stream, which should make the input
stream readable and non-blocking.

The code intended to do this before, but only as a side-effect of
dropping its last reference to `right`. If another reference was being
held to `right` somewhere else, it wouldn’t end up being closed, which
would lead to failures like
https://gitlab.gnome.org/GNOME/glib/-/jobs/1890000:
```
(/var/tmp/gitlab_runner/builds/Ff4WDDRj/0/GNOME/glib/_build/gio/tests/converter-stream:56570): GLib-GIO-DEBUG: 12:56:23.280: GSocketClient: Connection successful!
Bail out! GLib-GIO:ERROR:../gio/tests/converter-stream.c:1042:test_converter_pollable: assertion failed (error == NULL): Resource temporarily unavailable (g-io-error-quark, 27)
stderr:
```

This is a bit of a guess (I’m not sure it’ll fix the intermittent test
error, as I haven’t been able to reproduce that locally), but it’s worth
a try.

Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
2022-03-14 17:59:37 +00:00

1236 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);
/* After closing the write end, we can't get WOULD_BLOCK any more */
if (!socket_out)
{
g_assert_no_error (error);
g_assert_cmpint (res, !=, -1);
}
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();
}