← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/pygettextpo:py3 into pygettextpo:main

 

Colin Watson has proposed merging ~cjwatson/pygettextpo:py3 into pygettextpo:main.

Commit message:
Port to Python 3

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/pygettextpo/+git/pygettextpo/+merge/391412

If this looks OK, then I plan to release this to PyPI, which will allow us to consume it from Launchpad in the normal way and fix https://bugs.launchpad.net/launchpad/+bug/1016333.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/pygettextpo:py3 into pygettextpo:main.
diff --git a/.gitignore b/.gitignore
index 1ff8f33..d0f7d29 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
 gettextpo.html
-gettextpo.so
+gettextpo*.so
+/*.egg-info
 /build
 /dist
 /MANIFEST
diff --git a/Makefile b/Makefile
index bcff939..c42eb05 100644
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,7 @@ check: all
 	$(PYTHON) test_gettextpo.py -v
 
 clean:
-	rm -rf build dist gettextpo.so gettextpo.html
+	rm -rf build dist gettextpo*.so gettextpo.html
 
 gettextpo.so: setup.py gettextpo.c
 	$(PYTHON) setup.py build_ext -i
diff --git a/README b/README
index 5fdef4e..f690fe3 100644
--- a/README
+++ b/README
@@ -6,11 +6,6 @@ GPL v3 or later as of October 2007 (before that it was GPL v2 or
 later) [1].  Therefore, any code using this module must be under a license
 compatible with the GPL v2, v3, or any later version.
 
-Note: when this code was first added to Launchpad, it made use of
-interfaces added in gettext version 0.14.2, which is not yet widely
-distributed (e.g. not in Hoary), and so a trimmed down copy of the
-code is included here.  That code probably isn't needed here anymore.
-
 See also http://code.google.com/p/pygettextpo/, which is probably a
 duplicate project, though it has not had a public release yet (as of
 26 May 2009).
diff --git a/gettextpo.c b/gettextpo.c
index d125726..451e284 100644
--- a/gettextpo.c
+++ b/gettextpo.c
@@ -10,11 +10,26 @@
 
 #include <gettext-po.h>
 
-#define MIN_REQUIRED_GETTEXTPO_VERSION 0x000E02
-#define MIN_MSGCTXT_GETTEXTPO_VERSION 0x000F00
+#define MIN_REQUIRED_GETTEXTPO_VERSION 0x000F00
 
 #if LIBGETTEXTPO_VERSION < MIN_REQUIRED_GETTEXTPO_VERSION
-#  error "this module requires gettext >= 0.14.2"
+#  error "this module requires gettext >= 0.15"
+#endif
+
+#if PY_MAJOR_VERSION >= 3
+#  define PyNativeString_FromString(s) PyUnicode_FromString(s)
+#  define PyNativeString_FromFormat(format, ...) \
+          PyUnicode_FromFormat(format, __VA_ARGS__)
+#  if PY_VERSION_HEX >= 0x03030000
+#    define PyNativeString_Size(s) PyUnicode_GetLength(s)
+#  else
+#    define PyNativeString_Size(s) PyUnicode_GetSize(s)
+#  endif
+#else
+#  define PyNativeString_FromString(s) PyBytes_FromString(s)
+#  define PyNativeString_FromFormat(format, ...) \
+          PyString_FromFormat(format, __VA_ARGS__)
+#  define PyNativeString_Size(s) PyBytes_Size(s)
 #endif
 
 static PyObject *gettextpo_error = NULL;
@@ -42,22 +57,22 @@ static PyTypeObject PyPoMessage_Type;
 /* ---------------------------- */
 
 /**
- * get_pystring_from_pyobject:
+ * get_pybytes_from_pyobject:
  * @object: a PyObject that represents a PyUnicode object.
  *
- * Gets a PyString that represents the @object as UTF-8.  If the
- * object is a PyString, then assume it is in UTF-8 and return it.
+ * Gets a PyBytes that represents the @object as UTF-8.  If the
+ * object is a PyBytes, then assume it is in UTF-8 and return it.
  *
- * Return value: a new #PyString object or NULL if there is any error
+ * Return value: a new #PyBytes object or NULL if there is any error
  * in which case, PyErr is set.
  **/
 static PyObject *
-get_pystring_from_pyobject(PyObject *object)
+get_pybytes_from_pyobject(PyObject *object)
 {
     PyObject *unicode;
     PyObject *string;
 
-    if (PyString_Check(object)) {
+    if (PyBytes_Check(object)) {
 	Py_INCREF(object);
 	return object;
     }
@@ -96,38 +111,6 @@ typedef struct {
 static ErrorClosure *error_closure = NULL;
 static PyThread_type_lock error_closure_lock= NULL;
 
-/* The error handler API changed in gettext-0.15 ... */
-#if LIBGETTEXTPO_VERSION < 0x000F00
-
-static void
-error_handler_error(int status, int errnum, const char *format, ...)
-{
-    va_list args;
-    PyObject *str;
-
-    /* printf the string */
-    va_start(args, format);
-    str = PyString_FromFormatV(format, args);
-    va_end(args);
-
-    /* store the errors in a list, and as a string */
-    PyList_Append(error_closure->error_list,
-		  Py_BuildValue("(sis)", "error", errnum, str));
-    PyString_ConcatAndDel(&(error_closure->error_string), str);
-
-    /* if status is nonzero, we are not meant to return */
-    if (status != 0) {
-	fprintf(stderr, "error_handler_error: status!=0, longjmp'ing out\n");
-	longjmp(error_closure->env, 1);
-    }
-}
-
-struct po_error_handler error_handler = {
-    .error = error_handler_error,
-};
-
-#else /* LIBGETTEXTPO_VERSION >= 0.15.0*/
-
 static void
 error_handler_xerror(int severity, po_message_t message,
 		     const char *filename, size_t lineno, size_t column,
@@ -136,12 +119,38 @@ error_handler_xerror(int severity, po_message_t message,
     PyObject *str;
 
     /* printf the string */
-    str = PyString_FromString(message_text);
+#if PY_MAJOR_VERSION >= 3
+    {
+	size_t size = strlen(message_text);
+	if (size > PY_SSIZE_T_MAX)
+	    /* We can't raise an exception here, so just truncate it. */
+	    size = PY_SSIZE_T_MAX;
+	str = PyUnicode_DecodeUTF8(message_text, (Py_ssize_t)size, "replace");
+    }
+#else
+    str = PyBytes_FromString(message_text);
+#endif
 
     /* store the errors in a list, and as a string */
-    PyList_Append(error_closure->error_list, Py_BuildValue("(sis)",
+    PyList_Append(error_closure->error_list, Py_BuildValue("(siO)",
         severity == PO_SEVERITY_WARNING ? "warning" : "error", 0, str));
-    PyString_ConcatAndDel(&(error_closure->error_string), str);
+#if PY_MAJOR_VERSION >= 3
+    {
+	PyObject *old_error_string = error_closure->error_string;
+	if (PyUnicode_GET_LENGTH(error_closure->error_string)) {
+	    error_closure->error_string = PyUnicode_FromFormat(
+		"%U\n%U", error_closure->error_string, str);
+	    Py_XDECREF(str);
+	} else
+	    error_closure->error_string = str;
+	Py_XDECREF(old_error_string);
+    }
+#else
+    if (PyBytes_GET_SIZE(error_closure->error_string))
+	PyBytes_ConcatAndDel(&(error_closure->error_string),
+			     PyBytes_FromString("\n"));
+    PyBytes_ConcatAndDel(&(error_closure->error_string), str);
+#endif
 
     /* if it is a fatal error, we are not meant to return */
     if (severity == PO_SEVERITY_FATAL_ERROR) {
@@ -155,8 +164,6 @@ struct po_xerror_handler error_handler = {
     .xerror = error_handler_xerror,
 };
 
-#endif /* LIBGETTEXTPO_VERSION */
-
 /* ---------------------------- */
 
 static int
@@ -174,7 +181,7 @@ pypo_file_init(PyPoFile *self, PyObject *args, PyObject *kwargs)
 	ErrorClosure closure;
 
 	closure.error_list = PyList_New(0);
-	closure.error_string = PyString_FromString("");
+	closure.error_string = PyNativeString_FromString("");
 
 	PyThread_acquire_lock(error_closure_lock, WAIT_LOCK);
 	error_closure = &closure;
@@ -186,7 +193,7 @@ pypo_file_init(PyPoFile *self, PyObject *args, PyObject *kwargs)
 	error_closure = NULL;
 	PyThread_release_lock(error_closure_lock);
 
-	if (PyString_Size(closure.error_string) != 0) {
+	if (PyNativeString_Size(closure.error_string) != 0) {
 	    PyObject *exc;
 
 	    /* set up the exception */
@@ -262,7 +269,7 @@ pypo_file_write(PyPoFile *self, PyObject *args)
 	return NULL;
 
     closure.error_list = PyList_New(0);
-    closure.error_string = PyString_FromString("");
+    closure.error_string = PyNativeString_FromString("");
 
     PyThread_acquire_lock(error_closure_lock, WAIT_LOCK);
     error_closure = &closure;
@@ -274,7 +281,7 @@ pypo_file_write(PyPoFile *self, PyObject *args)
     error_closure = NULL;
     PyThread_release_lock(error_closure_lock);
 
-    if (PyString_Size(closure.error_string) != 0) {
+    if (PyNativeString_Size(closure.error_string) != 0) {
 	PyObject *exc;
 
 	/* set up the exception */
@@ -309,7 +316,7 @@ pypo_file_domain_header(PyPoFile *self, PyObject *args)
 
     header = po_file_domain_header(self->pofile, domain);
     if (header) {
-	return PyString_FromString(header);
+	return PyBytes_FromString(header);
     } else {
 	Py_RETURN_NONE;
     }
@@ -335,7 +342,7 @@ pypo_file_domains(PyPoFile *self, void *closure)
     ret = PyList_New(0);
     domains = po_file_domains(self->pofile);
     while (domains && *domains) {
-	PyObject *item = PyString_FromString(*domains);
+	PyObject *item = PyBytes_FromString(*domains);
 
 	PyList_Append(ret, item);
 	Py_DECREF(item);
@@ -356,8 +363,7 @@ PyDoc_STRVAR(doc_PyPoFile_Type,
 "filename");
 
 static PyTypeObject PyPoFile_Type = {
-    PyObject_HEAD_INIT(NULL)
-    0,                                  /* ob_size */
+    PyVarObject_HEAD_INIT(NULL, 0)
     "gettextpo.PoFile",                 /* tp_name */
     sizeof(PyPoFile),                   /* tp_basicsize */
     .tp_flags = Py_TPFLAGS_DEFAULT,
@@ -449,8 +455,7 @@ PyDoc_STRVAR(doc_PyPoMessageIterator_Type,
 "Iterator type for PoFile.  Iterates over the PoFile's messages.");
 
 static PyTypeObject PyPoMessageIterator_Type = {
-    PyObject_HEAD_INIT(NULL)
-    0,
+    PyVarObject_HEAD_INIT(NULL, 0)
     "gettextpo.PoMessageIterator",
     sizeof(PyPoMessageIterator),
     .tp_flags = Py_TPFLAGS_DEFAULT,
@@ -511,8 +516,8 @@ pypo_message_repr(PyPoMessage *self)
     if (self->msg)
 	msgid = po_message_msgid(self->msg);
 
-    return PyString_FromFormat("<PoMessage for '%s'>",
-			       msgid ? msgid : "(null)");
+    return PyNativeString_FromFormat("<PoMessage for '%s'>",
+				     msgid ? msgid : "(null)");
 }
 
 static PyObject *
@@ -550,13 +555,13 @@ _message_set_field(PyPoMessage *self, PyObject *args, const char *field,
     if (object == Py_None) {
         (*setter)(self->msg, "");
     } else {
-        string = get_pystring_from_pyobject(object);
+        string = get_pybytes_from_pyobject(object);
 
         if (string == NULL)
             /* Got an exception */
             return NULL;
         else {
-            value = PyString_AsString(string);
+            value = PyBytes_AsString(string);
             (*setter)(self->msg, value);
             Py_DECREF(string);
         }
@@ -565,9 +570,6 @@ _message_set_field(PyPoMessage *self, PyObject *args, const char *field,
     Py_RETURN_NONE;
 }
 
-/* msgctxt support was added in 0.15. */
-#if LIBGETTEXTPO_VERSION >= MIN_MSGCTXT_GETTEXTPO_VERSION
-
 PyDoc_STRVAR(doc_pypo_message_set_msgctxt,
 "M.set_msgctxt(msgctxt) -> None.  Set the msgctxt for this PoMessage");
 
@@ -577,8 +579,6 @@ pypo_message_set_msgctxt(PyPoMessage *self, PyObject *args)
     return _message_set_field(self, args, "O:set_msgctxt",
                                     &po_message_set_msgctxt);
 }
-#endif
-
 
 PyDoc_STRVAR(doc_pypo_message_set_msgid,
 "M.set_msgid(msgid) -> None.  Set the msgid for this PoMessage");
@@ -637,13 +637,13 @@ pypo_message_set_msgstr_plural(PyPoMessage *self, PyObject *args)
     if (object == Py_None) {
         po_message_set_msgstr_plural(self->msg, index, "");
     } else {
-        string = get_pystring_from_pyobject(object);
+        string = get_pybytes_from_pyobject(object);
 
         if (string == NULL)
             /* Got an exception */
             return NULL;
         else {
-            msgstr = PyString_AsString(string);
+            msgstr = PyBytes_AsString(string);
             po_message_set_msgstr_plural(self->msg, index, msgstr);
             Py_DECREF(string);
         }
@@ -711,7 +711,7 @@ pypo_message_check_format(PyPoMessage *self)
     }
 
     closure.error_list = PyList_New(0);
-    closure.error_string = PyString_FromString("");
+    closure.error_string = PyNativeString_FromString("");
 
     PyThread_acquire_lock(error_closure_lock, WAIT_LOCK);
     error_closure = &closure;
@@ -723,7 +723,7 @@ pypo_message_check_format(PyPoMessage *self)
     error_closure = NULL;
     PyThread_release_lock(error_closure_lock);
 
-    if (PyString_Size(closure.error_string) != 0) {
+    if (PyNativeString_Size(closure.error_string) != 0) {
 	PyObject *exc;
 
 	/* set up the exception */
@@ -744,11 +744,8 @@ pypo_message_check_format(PyPoMessage *self)
 }
 
 static PyMethodDef pypo_message_methods[] = {
-/* msgctxt support was added in 0.15. */
-#if LIBGETTEXTPO_VERSION >= MIN_MSGCTXT_GETTEXTPO_VERSION
     { "set_msgctxt", (PyCFunction)pypo_message_set_msgctxt, METH_VARARGS,
       doc_pypo_message_set_msgctxt },
-#endif
     { "set_msgid", (PyCFunction)pypo_message_set_msgid, METH_VARARGS,
       doc_pypo_message_set_msgid },
     { "set_msgid_plural", (PyCFunction)pypo_message_set_msgid_plural, METH_VARARGS,
@@ -766,8 +763,6 @@ static PyMethodDef pypo_message_methods[] = {
     { NULL, 0, 0 }
 };
 
-/* msgctxt support was added in 0.15. */
-#if LIBGETTEXTPO_VERSION >= MIN_MSGCTXT_GETTEXTPO_VERSION
 PyDoc_STRVAR(doc_pypo_message_msgctxt,
 "M.msgctxt -> the msgctxt for this PoMessage.");
 
@@ -778,10 +773,9 @@ pypo_message_get_msgctxt(PyPoMessage *self, void *closure)
 
     msgctxt = po_message_msgctxt(self->msg);
     if (msgctxt)
-	return PyString_FromString(msgctxt);
+	return PyBytes_FromString(msgctxt);
     Py_RETURN_NONE;
 }
-#endif
 
 PyDoc_STRVAR(doc_pypo_message_msgid,
 "M.msgid -> the msgid for this PoMessage.");
@@ -793,7 +787,7 @@ pypo_message_get_msgid(PyPoMessage *self, void *closure)
 
     msgid = po_message_msgid(self->msg);
     if (msgid)
-	return PyString_FromString(msgid);
+	return PyBytes_FromString(msgid);
     Py_RETURN_NONE;
 }
 
@@ -807,7 +801,7 @@ pypo_message_get_msgid_plural(PyPoMessage *self, void *closure)
 
     msgid_plural = po_message_msgid_plural(self->msg);
     if (msgid_plural)
-	return PyString_FromString(msgid_plural);
+	return PyBytes_FromString(msgid_plural);
     Py_RETURN_NONE;
 }
 
@@ -821,7 +815,7 @@ pypo_message_get_msgstr(PyPoMessage *self, void *closure)
 
     msgstr = po_message_msgstr(self->msg);
     if (msgstr)
-	return PyString_FromString(msgstr);
+	return PyBytes_FromString(msgstr);
     Py_RETURN_NONE;
 }
 
@@ -839,7 +833,7 @@ pypo_message_get_msgstr_plural(PyPoMessage *self, void *closure)
     i = 0;
     msgstr = po_message_msgstr_plural(self->msg, i);
     while (msgstr) {
-	item = PyString_FromString(msgstr);
+	item = PyBytes_FromString(msgstr);
 	PyList_Append(ret, item);
 	Py_DECREF(item);
 
@@ -859,17 +853,14 @@ pypo_message_get_comments(PyPoMessage *self, void *closure)
 
     comments = po_message_comments(self->msg);
     if (comments)
-	return PyString_FromString(comments);
+	return PyBytes_FromString(comments);
     Py_RETURN_NONE;
 }
 
 
 static PyGetSetDef pypo_message_getsets[] = {
-/* msgctxt support was added in 0.15. */
-#if LIBGETTEXTPO_VERSION >= MIN_MSGCTXT_GETTEXTPO_VERSION
     { "msgctxt", (getter)pypo_message_get_msgctxt,             (setter)0,
       doc_pypo_message_msgctxt },
-#endif
     { "msgid", (getter)pypo_message_get_msgid,                 (setter)0,
       doc_pypo_message_msgid },
     { "msgid_plural", (getter)pypo_message_get_msgid_plural,   (setter)0,
@@ -887,8 +878,7 @@ PyDoc_STRVAR(doc_PyPoMessage_Type,
 "PyMessage() -> new empty PoMessage instance.");
 
 static PyTypeObject PyPoMessage_Type = {
-    PyObject_HEAD_INIT(NULL)
-    0,                                  /* ob_size */
+    PyVarObject_HEAD_INIT(NULL, 0)
     "gettextpo.PoMessage",              /* tp_name */
     sizeof(PyPoMessage),                /* tp_basicsize */
     .tp_flags = Py_TPFLAGS_DEFAULT,
@@ -908,14 +898,27 @@ PyDoc_STRVAR(doc_gettextpo,
 "be of use to translation applications, or applications that need to\n"
 "manipulate or validate translations.");
 
-PyMODINIT_FUNC
-initgettextpo(void)
+#if PY_MAJOR_VERSION >= 3
+#  define MOD_DEF(ob, name, doc, methods) \
+    do { \
+        static struct PyModuleDef moduledef = { \
+            PyModuleDef_HEAD_INIT, name, doc, -1, methods \
+        }; \
+        ob = PyModule_Create(&moduledef); \
+    } while (0)
+#else
+#  define MOD_DEF(ob, name, doc, methods) \
+    ob = Py_InitModule3(name, methods, doc);
+#endif
+
+static PyObject *
+do_init(void)
 {
     PyObject *mod;
 
     if (libgettextpo_version < MIN_REQUIRED_GETTEXTPO_VERSION) {
 	PyErr_SetString(PyExc_RuntimeError, "version of libgettextpo too old");
-	return;
+	return NULL;
     }
 
     gettextpo_error = PyErr_NewException("gettextpo.error",
@@ -925,25 +928,39 @@ initgettextpo(void)
 
     /* initialise PoMessage type */
 #define INIT_TYPE(type)                      \
-    if (!type.ob_type)                       \
-	type.ob_type = &PyType_Type;         \
     if (!type.tp_alloc)                      \
 	type.tp_alloc = PyType_GenericAlloc; \
     if (!type.tp_new)                        \
 	type.tp_new = PyType_GenericNew;     \
     if (PyType_Ready(&type) < 0)             \
-	return
+	return NULL
 
     INIT_TYPE(PyPoFile_Type);
     INIT_TYPE(PyPoMessageIterator_Type);
     INIT_TYPE(PyPoMessage_Type);
 
-    mod = Py_InitModule3("gettextpo", NULL, doc_gettextpo);
-					 
+    MOD_DEF(mod, "gettextpo", doc_gettextpo, NULL);
+
     Py_INCREF(&PyPoFile_Type);
     PyModule_AddObject(mod, "PoFile", (PyObject *)&PyPoFile_Type);
     Py_INCREF(&PyPoMessage_Type);
     PyModule_AddObject(mod, "PoMessage", (PyObject *)&PyPoMessage_Type);
     Py_INCREF(gettextpo_error);
     PyModule_AddObject(mod, "error", gettextpo_error);
+
+    return mod;
+}
+
+#if PY_MAJOR_VERSION >= 3
+PyMODINIT_FUNC
+PyInit_gettextpo(void)
+{
+    return do_init();
 }
+#else
+PyMODINIT_FUNC
+initgettextpo(void)
+{
+    do_init();
+}
+#endif
diff --git a/setup.py b/setup.py
index 076e994..576f14c 100755
--- a/setup.py
+++ b/setup.py
@@ -3,7 +3,7 @@
 # Copyright Canonical Ltd.  This software is licensed under the GNU
 # Affero General Public License version 3 (see the file LICENSE).
 
-from distutils.core import setup, Extension
+from setuptools import setup, Extension
 
 gettextpo = Extension(
     'gettextpo', ['gettextpo.c'],
@@ -11,7 +11,7 @@ gettextpo = Extension(
 
 setup(
     name='pygettextpo',
-    version='0.1',
+    version='0.2',
     author='Canonical Ltd',
     description='A binding for the libgettext-po library',
     ext_modules=[gettextpo])
diff --git a/test_gettextpo.py b/test_gettextpo.py
index 41c4207..7dbee20 100644
--- a/test_gettextpo.py
+++ b/test_gettextpo.py
@@ -10,24 +10,24 @@ class PoFileTestCase(unittest.TestCase):
     def testCreateEmpty(self):
         # Test that we can create an empty pofile object
         pofile = gettextpo.PoFile()
-        self.assertEquals(list(iter(pofile)), [])
+        self.assertEqual(list(iter(pofile)), [])
 
     def testAddMessage(self):
         # Test that we can add messages to a new pofile object
         pofile = gettextpo.PoFile()
         msg = gettextpo.PoMessage()
-        msg.set_msgid('Hello')
+        msg.set_msgid(b'Hello')
         poiter = iter(pofile)
         poiter.insert(msg)
 
-        self.assertEquals(list(iter(pofile)), [msg])
+        self.assertEqual(list(iter(pofile)), [msg])
 
     def testAddMessageTwice(self):
         # A message object can only be added to one pofile object
         pofile1 = gettextpo.PoFile()
         pofile2 = gettextpo.PoFile()
         msg = gettextpo.PoMessage()
-        msg.set_msgid('Hello')
+        msg.set_msgid(b'Hello')
 
         poiter = iter(pofile1)
         poiter.insert(msg)
@@ -44,39 +44,47 @@ class PoMessageTestCase(unittest.TestCase):
 
     def testSetMsgId(self):
         msg = gettextpo.PoMessage()
-        msg.set_msgid('Hello')
-        self.assertEquals(msg.msgid, 'Hello')
-        msg.set_msgid_plural('Hellos')
-        self.assertEquals(msg.msgid_plural, 'Hellos')
+        msg.set_msgid(b'Hello')
+        self.assertEqual(msg.msgid, b'Hello')
+        msg.set_msgid_plural(b'Hellos')
+        self.assertEqual(msg.msgid_plural, b'Hellos')
 
     def testSetMsgCtxt(self):
         msg = gettextpo.PoMessage()
-        msg.set_msgctxt('Hello')
-        self.assertEquals(msg.msgctxt, 'Hello')
+        msg.set_msgctxt(b'Hello')
+        self.assertEqual(msg.msgctxt, b'Hello')
 
     def testSetMsgStr(self):
         msg = gettextpo.PoMessage()
-        msg.set_msgstr('Hello World')
-        self.assertEquals(msg.msgstr, 'Hello World')
+        msg.set_msgstr(b'Hello World')
+        self.assertEqual(msg.msgstr, b'Hello World')
 
     def testSetMsgStrPlural(self):
         # Test handling of plural msgstrs.  The PoMessage object can
         # not hold plural msgstrs if the msgid does not have a plural.
         msg = gettextpo.PoMessage()
-        msg.set_msgid('Something')
-        self.assertRaises(ValueError, msg.set_msgstr_plural, 0, 'Zero')
-        self.assertEquals(msg.msgstr_plural, [])
+        msg.set_msgid(b'Something')
+        self.assertRaises(ValueError, msg.set_msgstr_plural, 0, b'Zero')
+        self.assertEqual(msg.msgstr_plural, [])
 
         # need to set the plural msgid first, then add the plural msgstrs
-        msg.set_msgid_plural('Somethings')
-        msg.set_msgstr_plural(0, 'Zero')
-        msg.set_msgstr_plural(1, 'One')
-        msg.set_msgstr_plural(2, 'Two')
-        self.assertEquals(msg.msgstr_plural, ['Zero', 'One', 'Two'])
+        msg.set_msgid_plural(b'Somethings')
+        msg.set_msgstr_plural(0, b'Zero')
+        msg.set_msgstr_plural(1, b'One')
+        msg.set_msgstr_plural(2, b'Two')
+        self.assertEqual(msg.msgstr_plural, [b'Zero', b'One', b'Two'])
 
 
 class CheckFormatTestCase(unittest.TestCase):
 
+    def assertGettextPoError(self, expected_errors, msg):
+        with self.assertRaises(gettextpo.error) as raised:
+            msg.check_format()
+        self.assertEqual(expected_errors, raised.exception.error_list)
+        self.assertEqual(
+            "\n".join(message for _, _, message in expected_errors),
+            str(raised.exception))
+
     def testGoodFormat(self):
         # Check that no exception is raised on a good translation.
 
@@ -85,36 +93,49 @@ class CheckFormatTestCase(unittest.TestCase):
         # format a floating point value, so no error should be raised on
         # that kind of change.
         msg = gettextpo.PoMessage()
-        msg.set_msgid('Hello %s %d %g')
+        msg.set_msgid(b'Hello %s %d %g')
         msg.set_format('c-format', True)
-        msg.set_msgstr('Bye %s %.2d %f')
+        msg.set_msgstr(b'Bye %s %.2d %f')
 
         # this should run without an exception
         msg.check_format()
 
     def testAddFormatSpec(self):
-        #Test that an exception is raised when a format string is added.
+        # Test that an exception is raised when a format string is added.
         msg = gettextpo.PoMessage()
-        msg.set_msgid('No format specifiers')
+        msg.set_msgid(b'No format specifiers')
         msg.set_format('c-format', True)
-        msg.set_msgstr('One format specifier: %20s')
-        self.assertRaises(gettextpo.error, msg.check_format)
+        msg.set_msgstr(b'One format specifier: %20s')
+        expected_errors = [
+            ("error", 0,
+             "number of format specifications in 'msgid' and 'msgstr' does "
+             "not match"),
+            ]
+        self.assertGettextPoError(expected_errors, msg)
 
     def testSwapFormatSpecs(self):
         # Test that an exception is raised when format strings are transposed.
         msg = gettextpo.PoMessage()
-        msg.set_msgid('Spec 1: %s, Spec 2: %d')
+        msg.set_msgid(b'Spec 1: %s, Spec 2: %d')
         msg.set_format('c-format', True)
-        msg.set_msgstr('Spec 1: %d, Spec 2: %s')
-        self.assertRaises(gettextpo.error, msg.check_format)
+        msg.set_msgstr(b'Spec 1: %d, Spec 2: %s')
+        expected_errors = [
+            ("error", 0,
+             "format specifications in 'msgid' and 'msgstr' for argument 1 "
+             "are not the same"),
+            ("error", 0,
+             "format specifications in 'msgid' and 'msgstr' for argument 2 "
+             "are not the same"),
+            ]
+        self.assertGettextPoError(expected_errors, msg)
 
     def testNonFormatString(self):
         # Test that no exception is raised if the message is not marked as
         # a format string.
         msg = gettextpo.PoMessage()
-        msg.set_msgid('Spec 1: %s, Spec 2: %d')
+        msg.set_msgid(b'Spec 1: %s, Spec 2: %d')
         msg.set_format('c-format', False)
-        msg.set_msgstr('Spec 1: %d, Spec 2: %s')
+        msg.set_msgstr(b'Spec 1: %d, Spec 2: %s')
 
         # this should run without an exception
         msg.check_format()
@@ -122,7 +143,7 @@ class CheckFormatTestCase(unittest.TestCase):
     def testEmptyMsgStr(self):
         # Test that empty translations do not trigger a failure.
         msg = gettextpo.PoMessage()
-        msg.set_msgid('Hello %s')
+        msg.set_msgid(b'Hello %s')
         msg.set_format('c-format', True)
         msg.set_msgstr(None)
 
@@ -132,12 +153,12 @@ class CheckFormatTestCase(unittest.TestCase):
     def testGoodPlural(self):
         # Test that a good plural message passes without error.
         msg = gettextpo.PoMessage()
-        msg.set_msgid('%d apple')
-        msg.set_msgid_plural('%d apples')
+        msg.set_msgid(b'%d apple')
+        msg.set_msgid_plural(b'%d apples')
         msg.set_format('c-format', True)
-        msg.set_msgstr_plural(0, '%d orange')
-        msg.set_msgstr_plural(1, '%d oranges')
-        msg.set_msgstr_plural(2, '%d oranges_')
+        msg.set_msgstr_plural(0, b'%d orange')
+        msg.set_msgstr_plural(1, b'%d oranges')
+        msg.set_msgstr_plural(2, b'%d oranges_')
 
         # this should run without an exception
         msg.check_format()
@@ -145,29 +166,34 @@ class CheckFormatTestCase(unittest.TestCase):
     def testBadPlural(self):
         # Test that bad plural translations raise an error error.
         msg = gettextpo.PoMessage()
-        msg.set_msgid('%d apple')
-        msg.set_msgid_plural('%d apples')
+        msg.set_msgid(b'%d apple')
+        msg.set_msgid_plural(b'%d apples')
         msg.set_format('c-format', True)
-        msg.set_msgstr_plural(0, '%d orange')
-        msg.set_msgstr_plural(1, '%d oranges')
-        msg.set_msgstr_plural(2, '%g oranges_')
-        self.assertRaises(gettextpo.error, msg.check_format)
+        msg.set_msgstr_plural(0, b'%d orange')
+        msg.set_msgstr_plural(1, b'%d oranges')
+        msg.set_msgstr_plural(2, b'%g oranges_')
+        expected_errors = [
+            ("error", 0,
+             "format specifications in 'msgid_plural' and 'msgstr[2]' for "
+             "argument 1 are not the same"),
+            ]
+        self.assertGettextPoError(expected_errors, msg)
 
     def testUnicodeString(self):
         # Test that a translation with unicode chars is working.
         msg = gettextpo.PoMessage()
         msg.set_msgid(u'Carlos Perell\xf3 Mar\xedn')
         msg.set_msgstr(u'Carlos Perell\xf3 Mar\xedn')
-        self.assertEqual(msg.msgid, 'Carlos Perell\xc3\xb3 Mar\xc3\xadn')
-        self.assertEqual(msg.msgstr, 'Carlos Perell\xc3\xb3 Mar\xc3\xadn')
+        self.assertEqual(msg.msgid, b'Carlos Perell\xc3\xb3 Mar\xc3\xadn')
+        self.assertEqual(msg.msgstr, b'Carlos Perell\xc3\xb3 Mar\xc3\xadn')
 
 ## XXXX - gettext doesn't seem to check for this one
 #
 #    def testBadPluralMsgId(self):
 #        # Test that conflicting plural msgids raise errors on their own.
 #        msg = gettextpo.PoMessage()
-#        msg.set_msgid('%d apple')
-#        msg.set_msgid_plural('%g apples')
+#        msg.set_msgid(b'%d apple')
+#        msg.set_msgid_plural(b'%g apples')
 #        msg.set_format('c-format', True)
 #        self.assertRaises(gettextpo.error, msg.check_format)
 #