Merge lp:~ted/whoopsie/recoverable-problem into lp:whoopsie

Proposed by Ted Gould on 2014-05-09
Status: Merged
Merged at revision: 667
Proposed branch: lp:~ted/whoopsie/recoverable-problem
Merge into: lp:whoopsie
Prerequisite: lp:~ted/whoopsie/no-root-no-test
Diff against target: 515 lines (+380/-10)
9 files modified
.bzrignore (+11/-0)
Makefile (+6/-6)
debian/libwhoopsie0.symbols (+16/-0)
debian/rules (+2/-0)
src/recoverable-problem.c (+177/-0)
src/recoverable-problem.h (+48/-0)
src/tests/Makefile (+17/-4)
src/tests/recoverable_mock.sh (+4/-0)
src/tests/test_recoverable.c (+99/-0)
To merge this branch: bzr merge lp:~ted/whoopsie/recoverable-problem
Reviewer Review Type Date Requested Status
Evan 2014-05-09 Approve on 2015-06-05
Review via email: mp+219066@code.launchpad.net

Commit Message

libwhoopsie function to create a recoverable error

Description of the Change

Makes building a recoverable error in a program a bit easier. Putting it in libwhoopsie so many programs who are already using it can remove the duplicate code and just link into libwhoopsie. This is very non-Go-like, but we're okay with it.

To post a comment you must log in.
Evan (ev) wrote :

Could I trouble you for a test case?

Otherwise this looks good.

review: Needs Fixing
lp:~ted/whoopsie/recoverable-problem updated on 2015-06-05
599. By Ted Gould on 2015-06-05

Merge trunk

600. By Ted Gould on 2015-06-05

Adding in a stub for recoverable testing

601. By Ted Gould on 2015-06-05

Drop the identifier because it's disabled

602. By Ted Gould on 2015-06-05

Allow overriding the path to the recoverable problem script for testing

603. By Ted Gould on 2015-06-05

Adding a simple mock

604. By Ted Gould on 2015-06-05

Adding in some content and cmdline tests

605. By Ted Gould on 2015-06-05

Make sure we drop pidstr

606. By Ted Gould on 2015-06-05

Make it so that we can disable reporting of the recoverable errors

Ted Gould (ted) wrote :

Updated to trunk and tests added. Sorry that took so long.

lp:~ted/whoopsie/recoverable-problem updated on 2015-06-05
607. By Ted Gould on 2015-06-05

Updated symbols file

Evan (ev) :
Evan (ev) wrote :

This looks good. Thanks for the persistence, Ted!

review: Approve
Ted Gould (ted) :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2013-08-06 15:41:19 +0000
3+++ .bzrignore 2015-06-05 21:31:49 +0000
4@@ -9,7 +9,18 @@
5 debian/files
6 debian/whoopsie.substvars
7 src/tests/test_parse_report
8+src/tests/test_parse_report_coverage
9 src/tests/test_utils
10+src/tests/test_utils_coverage
11 src/tests/test_monitor
12+src/tests/test_monitor_coverage
13 src/tests/test_identifier
14 src/tests/test_logging
15+src/tests/test_logging_coverage
16+src/tests/test_recoverable
17+src/tests/test_recoverable_coverage
18+src/tests/recoverable_mock.cmdline
19+src/tests/recoverable_mock.contents
20+*.gcov
21+*.gcda
22+*.gcno
23
24=== modified file 'Makefile'
25--- Makefile 2015-03-13 19:35:46 +0000
26+++ Makefile 2015-06-05 21:31:49 +0000
27@@ -1,8 +1,9 @@
28 VERSION=$(shell dpkg-parsechangelog | grep ^Version: | cut -d' ' -f2)
29
30-libwhoopsie_SOURCES=src/identifier.c
31+libwhoopsie_SOURCES=src/identifier.c \
32+ src/recoverable-problem.c
33 libwhoopsie_OBJECTS=$(libwhoopsie_SOURCES:.c=.o)
34-libwhoopsie_CFLAGS=$(shell pkg-config --cflags glib-2.0) \
35+libwhoopsie_CFLAGS=$(shell pkg-config --cflags glib-2.0 gio-2.0) \
36 $(shell libgcrypt-config --cflags) \
37 $(CFLAGS) $(CPPFLAGS)
38 libwhoopsie_LIBS=$(shell pkg-config --libs gio-2.0 glib-2.0) \
39@@ -10,7 +11,8 @@
40 $(LDFLAGS)
41
42 whoopsie_CFLAGS=$(shell pkg-config --cflags gio-2.0 glib-2.0 libcurl) \
43- -g -Ilib -Wall -Werror -Os -DVERSION=\"$(VERSION)\" \
44+ $(shell libgcrypt-config --cflags) \
45+ -g -Ilib -Wall -Werror -Os -DVERSION=\"$(VERSION)\" -fPIC \
46 $(CFLAGS) $(CPPFLAGS)
47 whoopsie_LIBS=$(shell pkg-config --libs gio-2.0 glib-2.0 libcurl) -lcap \
48 $(LDFLAGS)
49@@ -31,9 +33,6 @@
50
51 all: $(SOURCES) $(EXECUTABLE)
52
53-$(libwhoopsie_OBJECTS): $(libwhoopsie_SOURCES)
54- $(CC) -std=c99 -fPIC -c $(libwhoopsie_CFLAGS) -o $@ $^
55-
56 src/libwhoopsie.so.0.0: $(libwhoopsie_OBJECTS)
57 $(CC) -std=c99 -shared -Wl,-soname,libwhoopsie.so.0 -o $@ $^ $(libwhoopsie_LIBS)
58 ln -sf libwhoopsie.so.0.0 src/libwhoopsie.so.0
59@@ -75,3 +74,4 @@
60 install -m644 lib/libwhoopsie.pc $(DESTDIR)/usr/lib/pkgconfig
61 install -d $(DESTDIR)/usr/include/libwhoopsie
62 install -m644 src/identifier.h $(DESTDIR)/usr/include/libwhoopsie
63+ install -m644 src/recoverable-problem.h $(DESTDIR)/usr/include/libwhoopsie
64
65=== added file 'debian/libwhoopsie0.symbols'
66--- debian/libwhoopsie0.symbols 1970-01-01 00:00:00 +0000
67+++ debian/libwhoopsie0.symbols 2015-06-05 21:31:49 +0000
68@@ -0,0 +1,16 @@
69+libwhoopsie.so.0 libwhoopsie0 #MINVER#
70+ whoopsie_hex_to_char@Base 0.2.48
71+ whoopsie_identifier_append_imei@Base 0.2.48
72+ whoopsie_identifier_fail_next_get_mac@Base 0.2.48
73+ whoopsie_identifier_fail_next_get_serial@Base 0.2.48
74+ whoopsie_identifier_fail_next_get_uuid@Base 0.2.48
75+ whoopsie_identifier_generate@Base 0.2.48
76+ whoopsie_identifier_get_android_serialno@Base 0.2.48
77+ whoopsie_identifier_get_cached@Base 0.2.48
78+ whoopsie_identifier_get_mac_address@Base 0.2.48
79+ whoopsie_identifier_get_system_uuid@Base 0.2.48
80+ whoopsie_identifier_set_cache_file@Base 0.2.48
81+ whoopsie_identifier_set_ofono_name@Base 0.2.48
82+ whoopsie_identifier_sha512@Base 0.2.48
83+ whoopsie_report_recoverable_problem@Base 0.2.48
84+
85
86=== modified file 'debian/rules'
87--- debian/rules 2014-11-07 10:17:30 +0000
88+++ debian/rules 2015-06-05 21:31:49 +0000
89@@ -9,6 +9,8 @@
90 # Uncomment this to turn on verbose mode.
91 #export DH_VERBOSE=1
92
93+export DPKG_GENSYMBOLS_CHECK_LEVEL = 4
94+
95 export DESTDIR=$(CURDIR)/debian/whoopsie
96
97 %:
98
99=== added file 'src/recoverable-problem.c'
100--- src/recoverable-problem.c 1970-01-01 00:00:00 +0000
101+++ src/recoverable-problem.c 2015-06-05 21:31:49 +0000
102@@ -0,0 +1,177 @@
103+/*
104+ * Copyright 2013 Canonical Ltd.
105+ *
106+ * This program is free software: you can redistribute it and/or modify it
107+ * under the terms of the GNU General Public License version 3, as published
108+ * by the Free Software Foundation.
109+ *
110+ * This program is distributed in the hope that it will be useful, but
111+ * WITHOUT ANY WARRANTY; without even the implied warranties of
112+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
113+ * PURPOSE. See the GNU General Public License for more details.
114+ *
115+ * You should have received a copy of the GNU General Public License along
116+ * with this program. If not, see <http://www.gnu.org/licenses/>.
117+ *
118+ * Authors:
119+ * Ted Gould <ted.gould@canonical.com>
120+ */
121+
122+#include "recoverable-problem.h"
123+#include <glib/gstdio.h>
124+#include <string.h>
125+#include <errno.h>
126+
127+/* Helpers to ensure we write nicely */
128+static void
129+write_string (int fd,
130+ const gchar *string)
131+{
132+ int res;
133+ do
134+ res = write (fd, string, strlen (string));
135+ while (G_UNLIKELY (res == -1 && errno == EINTR));
136+}
137+
138+/* Make NULLs fast and fun! */
139+static void
140+write_null (int fd)
141+{
142+ int res;
143+ do
144+ res = write (fd, "", 1);
145+ while (G_UNLIKELY (res == -1 && errno == EINTR));
146+}
147+
148+/* Child watcher */
149+static gboolean
150+apport_child_watch (GPid pid, gint status, gpointer user_data)
151+{
152+ g_main_loop_quit((GMainLoop *)user_data);
153+ return FALSE;
154+}
155+
156+static gboolean
157+apport_child_timeout (gpointer user_data)
158+{
159+ g_warning("Recoverable Error Reporter Timeout");
160+ g_main_loop_quit((GMainLoop *)user_data);
161+ return FALSE;
162+}
163+
164+/* Code to report an error */
165+void
166+whoopsie_report_recoverable_problem (const gchar * signature, GPid report_pid, gboolean wait, const gchar * additional_properties[])
167+{
168+ GError * error = NULL;
169+ gint error_stdin = 0;
170+ GPid pid = 0;
171+ gchar * pid_str = NULL;
172+ const gchar * recov_env = NULL;
173+ gchar ** argv = NULL;
174+ gchar * argv_nopid[2] = {
175+ "/usr/share/apport/recoverable_problem",
176+ NULL
177+ };
178+ gchar * argv_pid[4] = {
179+ "/usr/share/apport/recoverable_problem",
180+ "-p",
181+ NULL, /* put pid_str when allocated here */
182+ NULL
183+ };
184+
185+ /* Mostly for test suites, we don't want the recoverable errors from those */
186+ if (g_getenv("WHOOPSIE_RECOVERABLE_PROBLEM_DISABLE")) {
187+ g_debug("Reporting of recoverable problems disabled");
188+ return;
189+ }
190+
191+ argv = (gchar **)argv_nopid;
192+
193+ if (report_pid != 0) {
194+ pid_str = g_strdup_printf("%d", report_pid);
195+ argv_pid[2] = pid_str;
196+ argv = (gchar**)argv_pid;
197+ }
198+
199+ if ((recov_env = g_getenv("WHOOPSIE_RECOVERABLE_PROBLEM_PATH")) != NULL) {
200+ argv[0] = (gchar *)recov_env;
201+ }
202+
203+ GSpawnFlags flags = G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL;
204+ if (wait) {
205+ flags |= G_SPAWN_DO_NOT_REAP_CHILD;
206+ }
207+
208+ g_spawn_async_with_pipes(NULL, /* cwd */
209+ argv,
210+ NULL, /* envp */
211+ flags,
212+ NULL, NULL, /* child setup func */
213+ &pid,
214+ &error_stdin,
215+ NULL, /* stdout */
216+ NULL, /* stderr */
217+ &error);
218+
219+ if (error != NULL) {
220+ g_warning("Unable to report a recoverable error: %s", error->message);
221+ g_error_free(error);
222+ g_free(pid_str);
223+ return;
224+ }
225+
226+ gboolean first = TRUE;
227+
228+ if (error_stdin != 0 && signature != NULL) {
229+ write_string(error_stdin, "DuplicateSignature");
230+ write_null(error_stdin);
231+ write_string(error_stdin, signature);
232+
233+ first = FALSE;
234+ }
235+
236+ if (error_stdin != 0 && additional_properties != NULL) {
237+ gint i;
238+ for (i = 0; additional_properties[i] != NULL; i++) {
239+ if (!first) {
240+ write_null(error_stdin);
241+ } else {
242+ first = FALSE;
243+ }
244+
245+ write_string(error_stdin, additional_properties[i]);
246+ }
247+ }
248+
249+ if (error_stdin != 0) {
250+ close(error_stdin);
251+ }
252+
253+ if (wait && pid != 0) {
254+ GSource * child_source, * timeout_source;
255+ GMainContext * context = g_main_context_new();
256+ GMainLoop * loop = g_main_loop_new(context, FALSE);
257+
258+ child_source = g_child_watch_source_new(pid);
259+ g_source_attach(child_source, context);
260+ g_source_set_callback(child_source, (GSourceFunc)apport_child_watch, loop, NULL);
261+
262+ timeout_source = g_timeout_source_new_seconds(5);
263+ g_source_attach(timeout_source, context);
264+ g_source_set_callback(timeout_source, apport_child_timeout, loop, NULL);
265+
266+ g_main_loop_run(loop);
267+
268+ g_source_destroy(timeout_source);
269+ g_source_destroy(child_source);
270+ g_main_loop_unref(loop);
271+ g_main_context_unref(context);
272+
273+ g_spawn_close_pid(pid);
274+ }
275+
276+ g_free(pid_str);
277+
278+ return;
279+}
280
281=== added file 'src/recoverable-problem.h'
282--- src/recoverable-problem.h 1970-01-01 00:00:00 +0000
283+++ src/recoverable-problem.h 2015-06-05 21:31:49 +0000
284@@ -0,0 +1,48 @@
285+/*
286+ * Copyright 2013 Canonical Ltd.
287+ *
288+ * This program is free software: you can redistribute it and/or modify it
289+ * under the terms of the GNU General Public License version 3, as published
290+ * by the Free Software Foundation.
291+ *
292+ * This program is distributed in the hope that it will be useful, but
293+ * WITHOUT ANY WARRANTY; without even the implied warranties of
294+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
295+ * PURPOSE. See the GNU General Public License for more details.
296+ *
297+ * You should have received a copy of the GNU General Public License along
298+ * with this program. If not, see <http://www.gnu.org/licenses/>.
299+ *
300+ * Authors:
301+ * Ted Gould <ted.gould@canonical.com>
302+ */
303+
304+#include <glib.h>
305+#include <gio/gio.h>
306+
307+#ifndef WHOOPSIE_RECOVERABLE_PROBLEM__
308+#define WHOOPSIE_RECOVERABLE_PROBLEM__ 1
309+
310+G_BEGIN_DECLS
311+
312+/**
313+ * whoopsie_report_recoverable_problem:
314+ * @signature: The signature bucket for this problem
315+ * @report_pid: PID to report the error on or zero for this process
316+ * @wait: Wait until the problem is reported (important if this
317+ * and there's a chance we'd exit
318+ * @additional_properties: Other properties that should be included
319+ * in the report. Should have a multiple of 2 number of entries.
320+ *
321+ * Reports a recoverable problem so that it can be tracked on
322+ * daisy. Generally these are issues that can be dealt with, but having
323+ * tracking of them would be helpful.
324+ */
325+void whoopsie_report_recoverable_problem (const gchar * signature,
326+ GPid report_pid,
327+ gboolean wait,
328+ const gchar * additional_properties[]);
329+
330+G_END_DECLS
331+
332+#endif /* WHOOPSIE_RECOVERABLE_PROBLEM__ */
333
334=== modified file 'src/tests/Makefile'
335--- src/tests/Makefile 2014-10-09 22:13:17 +0000
336+++ src/tests/Makefile 2015-06-05 21:31:49 +0000
337@@ -8,6 +8,7 @@
338 test_parse_report_SOURCES=test_parse_report.c \
339 ../whoopsie.c \
340 ../logging.c \
341+ ../recoverable-problem.c \
342 ../utils.c \
343 ../identifier.c \
344 ../../lib/bson/bson.c \
345@@ -38,11 +39,16 @@
346 test_logging_EXECUTABLE=test_logging
347 test_logging_OBJECTS=$(test_logging_SOURCES:.c=.test.o)
348
349+test_recoverable_SOURCES=test_recoverable.c \
350+ ../recoverable-problem.c
351+test_recoverable_EXECUTABLE=test_recoverable
352+test_recoverable_OBJECTS=$(test_recoverable_SOURCES:.c=.test.o)
353+
354 .PHONY: all clean check
355
356 all: check
357
358-check: $(test_parse_report_EXECUTABLE) $(test_utils_EXECUTABLE) $(test_monitor_EXECUTABLE) $(test_logging_EXECUTABLE)
359+check: $(test_parse_report_EXECUTABLE) $(test_utils_EXECUTABLE) $(test_monitor_EXECUTABLE) $(test_logging_EXECUTABLE) $(test_recoverable_EXECUTABLE)
360 @r=0; for t in $^; do \
361 ./$$t -k || r=1; \
362 done; \
363@@ -58,6 +64,8 @@
364 #$(CC) -std=c99 $^ $(LIBS) -o $@
365 $(test_logging_EXECUTABLE): $(test_logging_OBJECTS)
366 $(CC) -std=c99 $^ $(LIBS) -o $@
367+$(test_recoverable_EXECUTABLE): $(test_recoverable_OBJECTS)
368+ $(CC) -std=c99 $^ $(LIBS) -o $@
369
370 test_parse_report_coverage: $(test_parse_report_OBJECTS:.test.o=.coverage.o)
371 $(CC) -std=c99 $^ $(LIBS) -lgcov -o $@
372@@ -65,11 +73,13 @@
373 $(CC) -std=c99 $^ $(LIBS) -lgcov -o $@
374 test_monitor_coverage: $(test_monitor_OBJECTS:.test.o=.coverage.o)
375 $(CC) -std=c99 $^ $(LIBS) -lgcov -o $@
376-test_identifier_coverage: $(test_identifier_OBJECTS:.test.o=.coverage.o)
377- $(CC) -std=c99 $^ $(LIBS) -lgcov -o $@
378+#test_identifier_coverage: $(test_identifier_OBJECTS:.test.o=.coverage.o)
379+# $(CC) -std=c99 $^ $(LIBS) -lgcov -o $@
380 test_logging_coverage: $(test_logging_OBJECTS:.test.o=.coverage.o)
381 $(CC) -std=c99 $^ $(LIBS) -lgcov -o $@
382-coverage: test_parse_report_coverage test_utils_coverage test_monitor_coverage test_identifier_coverage test_logging_coverage
383+test_recoverable_coverage: $(test_recoverable_OBJECTS:.test.o=.coverage.o)
384+ $(CC) -std=c99 $^ $(LIBS) -lgcov -o $@
385+coverage: test_parse_report_coverage test_utils_coverage test_monitor_coverage test_logging_coverage test_recoverable_coverage
386 @r=0; for t in $^; do \
387 ./$$t -k || r=1; \
388 done; \
389@@ -87,10 +97,13 @@
390 $(test_identifier_EXECUTABLE) \
391 $(test_logging_EXECUTABLE) \
392 $(test_logging_OBJECTS) \
393+ $(test_recoverable_EXECUTABLE) \
394+ $(test_recoverable_OBJECTS) \
395 test_parse_report_coverage \
396 test_utils_coverage \
397 test_identifier_coverage \
398 test_logging_coverage \
399+ test_recoverable_coverage \
400 coverage
401 find ../.. \( -name '*.coverage.o' -o \
402 -name '*.gcda' -o \
403
404=== added file 'src/tests/recoverable_mock.sh'
405--- src/tests/recoverable_mock.sh 1970-01-01 00:00:00 +0000
406+++ src/tests/recoverable_mock.sh 2015-06-05 21:31:49 +0000
407@@ -0,0 +1,4 @@
408+#!/bin/bash
409+
410+echo -n $@ > recoverable_mock.cmdline
411+cat /dev/stdin > recoverable_mock.contents
412
413=== added file 'src/tests/test_recoverable.c'
414--- src/tests/test_recoverable.c 1970-01-01 00:00:00 +0000
415+++ src/tests/test_recoverable.c 2015-06-05 21:31:49 +0000
416@@ -0,0 +1,99 @@
417+/* whoopsie
418+ *
419+ * Copyright © 2015 Canonical Ltd.
420+ * Author: Ted Gould <ted.gould@canonical.com>
421+ *
422+ * This program is free software: you can redistribute it and/or modify
423+ * it under the terms of the GNU General Public License as published by
424+ * the Free Software Foundation; version 3 of the License.
425+ *
426+ * This program is distributed in the hope that it will be useful,
427+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
428+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
429+ * GNU General Public License for more details.
430+ *
431+ * You should have received a copy of the GNU General Public License
432+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
433+ */
434+
435+#include <string.h>
436+#include <glib.h>
437+#include "../recoverable-problem.h"
438+
439+#define TEST_SIGNATURE "DuplicateSignature\0test-signature"
440+#define TEST_DATALIST "DuplicateSignature\0test-signature\0Foo\0Bar\0LongDataList\0A\n B\n C"
441+
442+void
443+test_recoverable (void)
444+{
445+ gchar * contents = NULL;
446+ gsize length = 0;
447+
448+ /* Basic First Run */
449+ whoopsie_report_recoverable_problem("test-signature", 0, TRUE, NULL);
450+
451+ g_file_get_contents("recoverable_mock.contents", &contents, &length, NULL);
452+ if (length != sizeof(TEST_SIGNATURE) - 1) { /* Don't count trailing NULL */
453+ g_error("Size of contents is %d instead of %d", (int)length, (int)sizeof(TEST_SIGNATURE) - 1);
454+ return;
455+ }
456+ if (memcmp(contents, TEST_SIGNATURE, length) != 0) {
457+ g_error("Contents is '%s' instead of '%s'", contents, TEST_SIGNATURE);
458+ return;
459+ }
460+
461+ g_free(contents);
462+
463+ /* Let's add some properties */
464+ const gchar * properties[5] = {
465+ "Foo", "Bar",
466+ "LongDataList", "A\n B\n C",
467+ NULL
468+ };
469+
470+ whoopsie_report_recoverable_problem("test-signature", 0, TRUE, properties);
471+
472+ g_file_get_contents("recoverable_mock.contents", &contents, &length, NULL);
473+ if (length != sizeof(TEST_DATALIST) - 1) { /* Don't count trailing NULL */
474+ g_error("Size of contents is %d instead of %d", (int)length, (int)sizeof(TEST_DATALIST) - 1);
475+ return;
476+ }
477+ if (memcmp(contents, TEST_DATALIST, length) != 0) {
478+ g_error("Contents is '%s' instead of '%s'", contents, TEST_DATALIST);
479+ return;
480+ }
481+
482+ g_free(contents);
483+
484+ /* Set a PID */
485+ whoopsie_report_recoverable_problem("test-signature", 1234, TRUE, NULL);
486+
487+ g_file_get_contents("recoverable_mock.cmdline", &contents, &length, NULL);
488+ if (g_strcmp0(contents, "-p 1234") != 0) {
489+ g_error("Contents is '%s' instead of '%s'", contents, "-p 1234");
490+ return;
491+ }
492+
493+ g_free(contents);
494+
495+ /* Don't wait */
496+ /* We can't reliably test to see if we can't wait, so this is
497+ here to ensure mostly that we don't crash */
498+ whoopsie_report_recoverable_problem("test-signature", 0, FALSE, NULL);
499+}
500+
501+int
502+main (int argc, char** argv)
503+{
504+#if GLIB_MAJOR_VERSION <= 2 && GLIB_MINOR_VERSION < 35
505+ /* Deprecated in glib 2.35/2.36. */
506+ g_type_init ();
507+#endif
508+ g_test_init (&argc, &argv, NULL);
509+
510+ g_setenv("WHOOPSIE_RECOVERABLE_PROBLEM_PATH", "./recoverable_mock.sh", TRUE);
511+
512+ g_test_add_func ("/whoopsie/recoverbale-error",
513+ test_recoverable);
514+ return g_test_run ();
515+}

Subscribers

People subscribed via source and target branches

to all changes:
to status/vote changes: