1 | /* |
2 | * Copyright © 2009 Codethink Limited |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Lesser General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2.1 of the License, or (at your option) any later version. |
8 | * |
9 | * See the included COPYING file for more information. |
10 | * |
11 | * Author: Ryan Lortie <desrt@desrt.ca> |
12 | */ |
13 | |
14 | #include <gio/gio.h> |
15 | #include <string.h> |
16 | |
17 | #define MAX_PIECE_SIZE 100 |
18 | #define MAX_PIECES 60 |
19 | |
20 | static gchar * |
21 | cook_piece (void) |
22 | { |
23 | char buffer[MAX_PIECE_SIZE * 2]; |
24 | gint symbols, i = 0; |
25 | |
26 | symbols = g_test_rand_int_range (begin: 1, MAX_PIECE_SIZE + 1); |
27 | |
28 | while (symbols--) |
29 | { |
30 | gint c = g_test_rand_int_range (begin: 0, end: 30); |
31 | |
32 | switch (c) |
33 | { |
34 | case 26: |
35 | buffer[i++] = '\n'; |
36 | G_GNUC_FALLTHROUGH; |
37 | case 27: |
38 | buffer[i++] = '\r'; |
39 | break; |
40 | |
41 | case 28: |
42 | buffer[i++] = '\r'; |
43 | G_GNUC_FALLTHROUGH; |
44 | case 29: |
45 | buffer[i++] = '\n'; |
46 | break; |
47 | |
48 | default: |
49 | buffer[i++] = c + 'a'; |
50 | break; |
51 | } |
52 | |
53 | g_assert_cmpint (i, <=, sizeof buffer); |
54 | } |
55 | |
56 | return g_strndup (str: buffer, n: i); |
57 | } |
58 | |
59 | static gchar ** |
60 | cook_pieces (void) |
61 | { |
62 | gchar **array; |
63 | gint pieces; |
64 | |
65 | pieces = g_test_rand_int_range (begin: 0, MAX_PIECES + 1); |
66 | array = g_new (char *, pieces + 1); |
67 | array[pieces] = NULL; |
68 | |
69 | while (pieces--) |
70 | array[pieces] = cook_piece (); |
71 | |
72 | return array; |
73 | } |
74 | |
75 | typedef struct |
76 | { |
77 | GInputStream parent_instance; |
78 | |
79 | gboolean built_to_fail; |
80 | gchar **pieces; |
81 | gint index; |
82 | |
83 | const gchar *current; |
84 | } SleepyStream; |
85 | |
86 | typedef GInputStreamClass SleepyStreamClass; |
87 | |
88 | GType sleepy_stream_get_type (void); |
89 | |
90 | G_DEFINE_TYPE (SleepyStream, sleepy_stream, G_TYPE_INPUT_STREAM) |
91 | |
92 | static gssize |
93 | sleepy_stream_read (GInputStream *stream, |
94 | void *buffer, |
95 | gsize length, |
96 | GCancellable *cancellable, |
97 | GError **error) |
98 | { |
99 | SleepyStream *sleepy = (SleepyStream *) stream; |
100 | |
101 | if (sleepy->pieces[sleepy->index] == NULL) |
102 | { |
103 | if (sleepy->built_to_fail) |
104 | { |
105 | g_set_error (err: error, G_IO_ERROR, code: G_IO_ERROR_FAILED, format: "fail" ); |
106 | return -1; |
107 | } |
108 | else |
109 | return 0; |
110 | } |
111 | else |
112 | { |
113 | if (!sleepy->current) |
114 | sleepy->current = sleepy->pieces[sleepy->index++]; |
115 | |
116 | length = MIN (strlen (sleepy->current), length); |
117 | memcpy (dest: buffer, src: sleepy->current, n: length); |
118 | |
119 | sleepy->current += length; |
120 | if (*sleepy->current == '\0') |
121 | sleepy->current = NULL; |
122 | |
123 | return length; |
124 | } |
125 | } |
126 | |
127 | static void |
128 | sleepy_stream_init (SleepyStream *sleepy) |
129 | { |
130 | sleepy->pieces = cook_pieces (); |
131 | sleepy->built_to_fail = FALSE; |
132 | sleepy->index = 0; |
133 | } |
134 | |
135 | static void |
136 | sleepy_stream_finalize (GObject *object) |
137 | { |
138 | SleepyStream *sleepy = (SleepyStream *) object; |
139 | |
140 | g_strfreev (str_array: sleepy->pieces); |
141 | G_OBJECT_CLASS (sleepy_stream_parent_class) |
142 | ->finalize (object); |
143 | } |
144 | |
145 | static void |
146 | sleepy_stream_class_init (SleepyStreamClass *class) |
147 | { |
148 | G_OBJECT_CLASS (class)->finalize = sleepy_stream_finalize; |
149 | class->read_fn = sleepy_stream_read; |
150 | |
151 | /* no read_async implementation. |
152 | * main thread will sleep while read runs in a worker. |
153 | */ |
154 | } |
155 | |
156 | static SleepyStream * |
157 | sleepy_stream_new (void) |
158 | { |
159 | return g_object_new (object_type: sleepy_stream_get_type (), NULL); |
160 | } |
161 | |
162 | static gboolean |
163 | read_line (GDataInputStream *stream, |
164 | GString *string, |
165 | const gchar *eol, |
166 | GError **error) |
167 | { |
168 | gsize length; |
169 | char *str; |
170 | |
171 | str = g_data_input_stream_read_line (stream, length: &length, NULL, error); |
172 | |
173 | if (str == NULL) |
174 | return FALSE; |
175 | |
176 | g_assert (strstr (str, eol) == NULL); |
177 | g_assert (strlen (str) == length); |
178 | |
179 | g_string_append (string, val: str); |
180 | g_string_append (string, val: eol); |
181 | g_free (mem: str); |
182 | |
183 | return TRUE; |
184 | } |
185 | |
186 | static void |
187 | build_comparison (GString *str, |
188 | SleepyStream *stream) |
189 | { |
190 | /* build this for comparison */ |
191 | gint i; |
192 | |
193 | for (i = 0; stream->pieces[i]; i++) |
194 | g_string_append (string: str, val: stream->pieces[i]); |
195 | |
196 | if (str->len && str->str[str->len - 1] != '\n') |
197 | g_string_append_c (str, '\n'); |
198 | } |
199 | |
200 | |
201 | static void |
202 | test (void) |
203 | { |
204 | SleepyStream *stream = sleepy_stream_new (); |
205 | GDataInputStream *data; |
206 | GError *error = NULL; |
207 | GString *one; |
208 | GString *two; |
209 | |
210 | one = g_string_new (NULL); |
211 | two = g_string_new (NULL); |
212 | |
213 | data = g_data_input_stream_new (G_INPUT_STREAM (stream)); |
214 | g_data_input_stream_set_newline_type (stream: data, type: G_DATA_STREAM_NEWLINE_TYPE_LF); |
215 | build_comparison (str: one, stream); |
216 | |
217 | while (read_line (stream: data, string: two, eol: "\n" , error: &error)); |
218 | |
219 | g_assert_cmpstr (one->str, ==, two->str); |
220 | g_string_free (string: one, TRUE); |
221 | g_string_free (string: two, TRUE); |
222 | g_object_unref (object: stream); |
223 | g_object_unref (object: data); |
224 | } |
225 | |
226 | static GDataInputStream *data; |
227 | static GString *one, *two; |
228 | static GMainLoop *loop; |
229 | static const gchar *eol; |
230 | |
231 | static void |
232 | asynch_ready (GObject *object, |
233 | GAsyncResult *result, |
234 | gpointer user_data) |
235 | { |
236 | GError *error = NULL; |
237 | gsize length; |
238 | gchar *str; |
239 | |
240 | g_assert (data == G_DATA_INPUT_STREAM (object)); |
241 | |
242 | str = g_data_input_stream_read_line_finish (stream: data, result, length: &length, error: &error); |
243 | |
244 | if (str == NULL) |
245 | { |
246 | g_main_loop_quit (loop); |
247 | if (error) |
248 | g_error_free (error); |
249 | } |
250 | else |
251 | { |
252 | g_assert (length == strlen (str)); |
253 | g_string_append (string: two, val: str); |
254 | g_string_append (string: two, val: eol); |
255 | g_free (mem: str); |
256 | |
257 | /* MOAR!! */ |
258 | g_data_input_stream_read_line_async (stream: data, io_priority: 0, NULL, callback: asynch_ready, NULL); |
259 | } |
260 | } |
261 | |
262 | |
263 | static void |
264 | asynch (void) |
265 | { |
266 | SleepyStream *sleepy = sleepy_stream_new (); |
267 | |
268 | data = g_data_input_stream_new (G_INPUT_STREAM (sleepy)); |
269 | one = g_string_new (NULL); |
270 | two = g_string_new (NULL); |
271 | eol = "\n" ; |
272 | |
273 | build_comparison (str: one, stream: sleepy); |
274 | g_data_input_stream_read_line_async (stream: data, io_priority: 0, NULL, callback: asynch_ready, NULL); |
275 | g_main_loop_run (loop: loop = g_main_loop_new (NULL, FALSE)); |
276 | |
277 | g_assert_cmpstr (one->str, ==, two->str); |
278 | g_string_free (string: one, TRUE); |
279 | g_string_free (string: two, TRUE); |
280 | g_object_unref (object: sleepy); |
281 | g_object_unref (object: data); |
282 | } |
283 | |
284 | int |
285 | main (int argc, char **argv) |
286 | { |
287 | g_test_init (argc: &argc, argv: &argv, NULL); |
288 | g_test_bug_base (uri_pattern: "http://bugzilla.gnome.org/" ); |
289 | |
290 | g_test_add_func (testpath: "/filter-stream/input" , test_func: test); |
291 | g_test_add_func (testpath: "/filter-stream/async" , test_func: asynch); |
292 | |
293 | return g_test_run(); |
294 | } |
295 | |