]> asedeno.scripts.mit.edu Git - cl-protobufs.git/commitdiff
Merge branch 'master' of git://common-lisp.net/projects/qitab/cl-protobufs
authorScott McKay <swmckay@gmail.com>
Sun, 9 Dec 2012 05:04:07 +0000 (10:34 +0530)
committerScott McKay <swmckay@gmail.com>
Sun, 9 Dec 2012 05:04:07 +0000 (10:34 +0530)
Conflicts:
serialize.lisp

1  2 
TODO
asdf-support.lisp
conditions.lisp
parser.lisp
serialize.lisp
tests/pkgdcl.lisp
tests/qtest.lisp
text-format.lisp

diff --cc TODO
index 44b496f2e554d3b255e8ff13a0139dd724b28a8e,44b496f2e554d3b255e8ff13a0139dd724b28a8e..b2ea39e49888c90d321d37efd72653c6d100c53f
--- 1/TODO
--- 2/TODO
+++ b/TODO
@@@ -17,13 -17,13 +17,13 @@@ TO D
  [  - 'find-message' and 'find-enum' need to search namespaces                 ]
  [     if the quick compare against the name/class fails                               ]
    - 'message Foo { message Bar { ... } ... }'
--    should (maybe!) produce a Lisp class defaultly named 'foo.bar'
++    should not produce a Lisp class defaultly named 'foo.bar', but...
      - Add a keyword arg, :class-name, that overrides this convention;
        in .proto files this should be called lisp_class
      - Also, add :slot-name for slots and :type-name for enums
        (called lisp_slot and lisp_type in .proto files)
--    - Or maybe not, since 'foo.bar' isn't very Lispy;
--      for the most part, packages provide enough room to avoid name clashes
++    - Rationale: 'foo.bar' isn't very Lispy;
++      for the most part, packages provide enough namespaces to avoid clashes
  
  - Get 'merge-from-message' fully working
    - See the place in 'deserialize-object' that needs to merge, too
@@@ -53,6 -53,6 +53,9 @@@
  [  - &key name should give the Protobufs name for the field                   ]
  [  - 'option lisp_name="pkg:name"' should give the Lisp name for the slot     ]
  
++- Refactor 'define-message'/'define-extend'/'define-group'
++  to avoid so much duplicated code
++
  [- Need search paths in the ASDF .proto module                                        ]
  
  [- Make 'import' really work                                                  ]
index a7a032cb54bf26a0b58c12a95de1f4ceb6c2fbc7,33137e10461d6fd358d5474ac518de34dc3a2070..31f26f325d5ac95fc3df73ac58d11bae4657b763
          (error 'compile-error
                 :component component :operation op)))))
  
 -  (append (output-files (make-instance 'compile-op) component) ; fasl
 -          (cdr (output-files (make-instance 'proto-to-lisp) component)))) ; proto-imports
+ (defmethod input-files ((op load-op) (component protobuf-file))
+   "The input files are the .fasl and .proto-imports files."
+   (declare (ignorable op))
++  (append (output-files (make-instance 'compile-op) component)            ;fasl
++          (cdr (output-files (make-instance 'proto-to-lisp) component)))) ;proto-imports
+ (defmethod perform ((op load-op) (component protobuf-file))
+   (let* ((input  (protobuf-input-file component))
+          (paths  (cons (directory-namestring input) (resolve-search-path component)))
+          (proto-impl:*protobuf-search-path* paths)
+          (proto-impl:*protobuf-output-path* (first (input-files op component))))
+     (destructuring-bind (fasl proto-imports)
+         (input-files op component)
+       (proto-impl:process-imports-from-file proto-imports)
+       (load fasl))))
  (defmethod operation-description ((op compile-op) (component protobuf-file))
    (format nil (compatfmt "~@<compiling ~3i~_~A~@:>")
-           (make-pathname :name (pathname-name (component-pathname component))
-                          :type "lisp"
-                          :defaults (first (output-files op component)))))
+           (first (input-files op component))))
  
  \f
  ;;; Processing of imports
            (setf (proto-imported-schemas schema)
                  (nconc (proto-imported-schemas schema) (list imported)))
            (return-from import-one))
-         (dolist (path search-path (error "Could not import ~S" import))
-           (let* ((base-path  (asdf::merge-pathnames* import path))
-                  (proto-file (make-pathname :name import-name :type "proto"
-                                             :defaults base-path))
-                  (lisp-file  (if output-path
-                                (make-pathname :name import-name :type "lisp"
-                                               :directory (pathname-directory output-path))
-                                (make-pathname :type "lisp" :defaults base-path)))
-                  (fasl-file  (compile-file-pathname lisp-file))
-                  (asdf:*asdf-verbose* nil)      ;for safe-file-write-date
-                  (proto-date (asdf::safe-file-write-date proto-file))
-                  (lisp-date  (asdf::safe-file-write-date lisp-file))
-                  (fasl-date  (asdf::safe-file-write-date fasl-file)))
-             (when (probe-file proto-file)
-               (let ((*protobuf-pathname* proto-file))
-                 (when (string= (pathname-type base-path) "proto")
-                   ;; The user asked to import a .proto file
-                   ;; If there's no .lisp file or an older .lisp file, parse the .proto file now
-                   (cond ((not proto-date)
-                          (warn "Could not find the .proto file to be imported: ~A" proto-file))
-                         ((or (not lisp-date)
-                              (< lisp-date proto-date))
-                          (parse-protobuf-file proto-file lisp-file)
-                          (setq lisp-date (file-write-date lisp-file)))))
-                 ;; Compile the .lisp file, if necessary
-                 (cond ((not lisp-date)
-                        (unless (string= (pathname-type base-path) "proto")
-                          (warn "Could not find the .lisp file to be compiled: ~A" lisp-file)))
-                       (t
-                        (when (or (not fasl-date)
-                                  (< fasl-date lisp-date))
-                          (let ((*compile-file-pathname* lisp-file)
-                                (*load-pathname* nil))
-                            (setq fasl-file (compile-file lisp-file)))
-                          (setq fasl-date (file-write-date fasl-file)))
-                        ;; Now we can load the .fasl file
-                        (let ((*compile-file-pathname* nil)
-                              (*load-pathname* fasl-file))
-                          (load fasl-file)))))
-               (let* ((imported (find-schema base-path)))
-                 (when imported
-                   (setf (proto-imported-schemas schema)
-                         (nconc (proto-imported-schemas schema) (list imported))))
-                 (return-from import-one)))))))))
 -        (%process-import import import-name)
++        (do-process-import import import-name)
+         (let* ((imported (find-schema (class-name->proto import-name))))
+           (when imported
+             (setf (proto-imported-schemas schema)
+                   (nconc (proto-imported-schemas schema) (list imported))))
+           (return-from import-one))))))
+ (defun process-imports-from-file (imports-file)
+   (when (probe-file imports-file)
+     (let ((imports (with-open-file (stream imports-file
+                                     :direction :input
+                                     :external-format :utf-8
+                                     :element-type 'character)
+                      (with-standard-io-syntax (read stream)))))
+       (dolist (import imports)
+         (let* ((import      (pathname import))
+                (import-name (pathname-name import)))
+           ;; If this schema has already been loaded, we're done.
+           (unless (find-schema (class-name->proto import-name))
 -            (%process-import import import-name)))))))
++            (do-process-import import import-name)))))))
 -(defun %process-import (import import-name
 -                        &key (search-path *protobuf-search-path*)
 -                             (output-path *protobuf-output-path*))
++(defun do-process-import (import import-name
++                          &key (search-path *protobuf-search-path*)
++                               (output-path *protobuf-output-path*))
+   (dolist (path search-path (error "Could not import ~S" import))
+     (let* ((base-path  (asdf::merge-pathnames* import path))
+            (proto-file (make-pathname :name import-name :type "proto"
+                                       :defaults base-path))
+            (lisp-file  (asdf::lispize-pathname
+                         (if output-path
+                             (make-pathname :name import-name
+                                            :directory (pathname-directory output-path))
+                             base-path)))
+            (imports-file (make-pathname :type "proto-imports"
+                                         :defaults lisp-file))
+            (fasl-file  (compile-file-pathname lisp-file))
+            (asdf:*asdf-verbose* nil)    ;for safe-file-write-date
+            (proto-date (asdf::safe-file-write-date proto-file))
+            (lisp-date  (asdf::safe-file-write-date lisp-file))
+            (fasl-date  (asdf::safe-file-write-date fasl-file))
+            (imports-date  (asdf::safe-file-write-date imports-file)))
+       (when (probe-file proto-file)
+         (let ((*protobuf-pathname* proto-file))
+           (when (string= (pathname-type base-path) "proto")
+             ;; The user asked to import a .proto file
+             ;; If there's no .lisp file or an older .lisp file, or no
+             ;; .proto-imports file or an older .proto-imports file parse
+             ;; the .proto file now
+             ;; If we did not parse the .proto file, process the generated
+             ;; .proto-imports file now.
+             (cond ((not proto-date)
+                    (warn "Could not find the .proto file to be imported: ~A" proto-file))
+                   ((or (not (and lisp-date imports-date))
+                        (< lisp-date proto-date)
+                        (< imports-date proto-date))
+                    (parse-protobuf-file proto-file lisp-file imports-file)
+                    (setq lisp-date (file-write-date lisp-file))
+                    (setq imports-date (file-write-date imports-file)))
+                   (t
+                    (process-imports-from-file imports-file))))
+           ;; Compile the .lisp file, if necessary
+           (cond ((not lisp-date)
+                  (unless (string= (pathname-type base-path) "proto")
+                    (warn "Could not find the .lisp file to be compiled: ~A" lisp-file)))
+                 (t
+                  (when (or (not fasl-date)
+                            (< fasl-date lisp-date))
+                    (let ((*compile-file-pathname* lisp-file)
+                          (*load-pathname* nil))
+                      (setq fasl-file (compile-file lisp-file)))
+                    (setq fasl-date (file-write-date fasl-file)))))
+           ;; Load the .fasl file
+           (cond ((not fasl-date)
+                  (unless (string= (pathname-type base-path) "proto")
+                    (warn "Could not find the .fasl file to be loaded: ~A" fasl-file)))
+                 (t
+                  (let ((*compile-file-pathname* nil)
+                        (*load-pathname* fasl-file))
+                    (load fasl-file)))))
+         (return (values))))))
diff --cc conditions.lisp
index 0000000000000000000000000000000000000000,3632d970a92f0f1a349e0262f55bc4533343dc0e..012c3bb45ae91af3f47bb31eca1b9d25ff3a6e58
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,74 +1,79 @@@
 -              :initarg :type-name
 -              :documentation "The name of the type which can not be found."))
+ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+ ;;;                                                                  ;;;
+ ;;; Free Software published under an MIT-like license. See LICENSE   ;;;
+ ;;;                                                                  ;;;
+ ;;; Copyright (c) 2012 Google, Inc.  All rights reserved.            ;;;
+ ;;;                                                                  ;;;
+ ;;; Original author: Ben Wagner                                      ;;;
+ ;;;                                                                  ;;;
+ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+ (in-package "PROTO-IMPL")
+ ;;; Protocol buffers conditions
+ (define-condition undefined-type (simple-error)
+   ((type-name :type string
+               :reader error-type-name
 -             (format stream "~? ~s"
++              :initarg :type-name))
+   (:documentation "Indicates that a schema references a type which has not been defined.")
+   (:default-initargs :format-control "Undefined type:")
+   (:report (lambda (condition stream)
 -          :initarg :field
 -          :documentation "The field whose type is TYPE-NAME."))
++             (format stream "~? ~S"
+                      (simple-condition-format-control condition)
+                      (simple-condition-format-arguments condition)
+                      (error-type-name condition)))))
+ (define-condition undefined-field-type (undefined-type)
+   ((field :type protobuf-field
+           :reader error-field
 -             (format stream "~? Field ~s in message ~s has unknown type ~s."
++          :initarg :field))
+   (:documentation "Indicates that a schema contains a message with a field whose type is not a
+                    primitive type and is not a known message (or extend) or enum.")
+   (:report (lambda (condition stream)
 -           :initarg :method
 -           :documentation "The method that references TYPE-NAME.")
++             (format stream "~? Field ~S in message ~S has unknown type ~S"
+                      (simple-condition-format-control condition)
+                      (simple-condition-format-arguments condition)
+                      (error-field condition)
+                      (proto-parent (error-field condition))
+                      (error-type-name condition)))))
++;; The serializers use this a lot, so wrap it up
++(defun undefined-field-type (format-control object type field)
++  (error 'undefined-field-type
++    :format-control format-control
++    :format-arguments (list object)
++    :type-name (prin1-to-string type)
++    :field field))
++
+ (define-condition undefined-method-type (undefined-type)
+   ((method :type protobuf-method
+            :reader error-method
 -             (format stream "~? ~a type for rpc ~s in service ~s has unknown type ~s."
++           :initarg :method)
+    (where :type string
+           :reader error-where
+           :initarg :where
+           :documentation "Description of which type referenced by the method is undefined."))
+   (:documentation "Superclass for `undefined-type' errors related to a `protobuf-method'.  Indicates
+                    that a schema contains a service with a method whose input, output, or stream
+                    type is not a known message (or extend).")
+   (:report (lambda (condition stream)
++             (format stream "~? ~A type for RPC ~S in service ~S has unknown type ~S"
+                      (simple-condition-format-control condition)
+                      (simple-condition-format-arguments condition)
+                      (error-where condition)
+                      (error-method condition)
+                      (proto-parent (error-method condition))
+                      (error-type-name condition)))))
+ (define-condition undefined-input-type (undefined-method-type)
+   ()
+   (:default-initargs :where "Input"))
+ (define-condition undefined-output-type (undefined-method-type)
+   ()
+   (:default-initargs :where "Output"))
+ (define-condition undefined-stream-type (undefined-method-type)
+   ()
+   (:default-initargs :where "Stream"))
diff --cc parser.lisp
index 5454a66848e9fde45225b50b54a8fcfd30d77959,620c487619c11b97d5c76bd6bfb91b6181ccc473..167978b594587cf213ad24352fb345d7bae86cd0
                (t
                 (error "Syntax error at position ~D" (file-position stream))))))))
  
 -  "Recursively resolves protobuf type names to lisp type names in the messages and services in
 -   'schema'."
+ (defun set-lisp-package (schema lisp-package-name)
+   "Set the package for generated lisp names of 'schema'."
+   (check-type schema protobuf-schema)
+   (check-type lisp-package-name string)
+   (let ((package (or (find-proto-package lisp-package-name)
+                      ;; Try to put symbols into the right package
+                      (make-package (string-upcase lisp-package-name) :use ())
+                      *protobuf-package*)))
+     (setf (proto-lisp-package schema) lisp-package-name)
+     (setq *protobuf-package* package)))
+ (defmethod resolve-lisp-names ((schema protobuf-schema))
++  "Recursively resolves Protobuf type names to Lisp type names in the messages and services in 'schema'."
+   (map () #'resolve-lisp-names (proto-messages schema))
+   (map () #'resolve-lisp-names (proto-services schema)))
  (defun parse-proto-syntax (stream schema &optional (terminator #\;))
    "Parse a Protobufs syntax line from 'stream'.
     Updates the 'protobuf-schema' object to use the syntax."
                               ((or (digit-char-p ch) (member ch '(#\- #\+ #\.)))
                                (parse-number stream))
                               ((eql ch #\{)
 -                              ;;---bwagner: this is incorrect -- we need to find the field name in
 -                              ;;   the locally-extended version of
++                              ;;---bwagner: This is incorrect
++                              ;;   We need to find the field name in the locally-extended version of
+                               ;;   google.protobuf.[File,Message,Field,Enum,EnumValue,Service,Method]Options
+                               ;;   and get its type
                                (let ((message (find-message (or protobuf *protobuf*) key)))
                                  (if message
                                    ;; We've got a complex message as a value to an option
           (name (prog1 (parse-token stream)
                   (expect-char stream #\{ () "extend")
                   (maybe-skip-comments stream)))
 -         ;;---bwagner: is 'extend' allowed to use a forward reference to a message?
++         ;;---bwagner: Is 'extend' allowed to use a forward reference to a message?
           (message (find-message protobuf name))
           (extends (and message
                         (make-instance 'protobuf-message
          (setf (proto-fields message) (nconc (proto-fields message) (list field)))
          field))))
  
 -             :type-name type
 -             :field field))
+ (defmethod resolve-lisp-names ((field protobuf-field))
+   "Resolves the field's protobuf type to a lisp type and sets `proto-class' for 'field'."
+   (let* ((type  (proto-type field))
+          (ptype (when (member type '("int32" "int64" "uint32" "uint64" "sint32" "sint64"
+                                      "fixed32" "fixed64" "sfixed32" "sfixed64"
+                                      "string" "bytes" "bool" "float" "double") :test #'string=)
+                   (kintern type)))
+          (message (unless ptype
+                     (or (find-message (proto-parent field) type)
+                         (find-enum (proto-parent field) type)))))
+     (unless (or ptype message)
+       (error 'undefined-field-type
++        :type-name type
++        :field field))
+     (setf (proto-class field) (or ptype (proto-class message))))
+   nil)
  (defun parse-proto-group (stream message required &optional extended-from)
    "Parse a (deprecated) Protobufs group from 'stream'.
     Updates the 'protobuf-message' object to have the group type and field."
      (setf (proto-methods service) (nconc (proto-methods service) (list method)))
      method))
  
 -  (let* ((input-name (proto-input-name method))
 -         (output-name (proto-output-name method))
+ (defmethod resolve-lisp-names ((method protobuf-method))
+   "Resolves input, output, and streams protobuf type names to lisp type names and sets
+    `proto-input-type', `proto-output-type', and, if `proto-streams-name' is set,
+    `proto-streams-type' on 'method'."
 -         (schema (proto-parent service))
 -         (input-message (find-message schema input-name))
 -         (output-message (find-message schema output-name))
++  (let* ((input-name   (proto-input-name method))
++         (output-name  (proto-output-name method))
+          (streams-name (proto-streams-name method))
+          (service (proto-parent method))
 -                               ;; this is supposed to be the fully-qualified name, but we don't
 -                               ;; require that
++         (schema  (proto-parent service))
++         (input-message   (find-message schema input-name))
++         (output-message  (find-message schema output-name))
+          (streams-message (and streams-name
 -             :type-name input-name
 -             :method method))
++                               ;; This is supposed to be the fully-qualified name,
++                               ;; but we don't require that
+                                (find-message schema streams-name))))
+     (unless input-message
+       (error 'undefined-input-type
 -             :type-name output-name
 -             :method method))
++        :type-name input-name
++        :method method))
+     (unless output-message
+       (error 'undefined-output-type
 -               :type-name streams-name
 -               :method method))
++        :type-name output-name
++        :method method))
+     (setf (proto-input-type method) (proto-class input-message))
+     (setf (proto-output-type method) (proto-class output-message))
+     (when streams-name
+       (unless streams-message
+         (error 'undefined-stream-type
++          :type-name streams-name
++          :method method))
+       (setf (proto-streams-type method) (proto-class streams-message))))
+   nil)
  (defun parse-proto-method-options (stream)
    "Parse any options in a Protobufs method from 'stream'.
     Returns a list of 'protobuf-option' objects."
diff --cc serialize.lisp
index 4aaf0479f14e743b2b73f44ef1a516ce0427a444,981b87e945a26ae3539888d65a0f2c10b5ea28dd..b5be439fd449bafa1387a489f1ac2e081d2be1d5
                                            (tag  (make-tag type (proto-index field))))
                                       (doseq (v (read-slot object slot reader))
                                         (let ((v (funcall (proto-serializer msg) v)))
-                                          (setq index (serialize-prim v type tag buffer index))))))))
+                                          (setq index (serialize-prim v type tag buffer index))))))
+                                   (t
 -                                   (error 'undefined-field-type
 -                                          :format-control "While serializing ~s to protobuf,"
 -                                          :format-arguments (list object)
 -                                          :type-name (prin1-to-string type)
 -                                          :field field))))
++                                   (undefined-field-type "While serializing ~S,"
++                                                         object type field))))
                             (t
                              (cond ((eq type :bool)
                                     ;; We have to handle optional boolean fields specially
                                         (let* ((v    (funcall (proto-serializer msg) v))
                                                (type (proto-proto-type msg))
                                                (tag  (make-tag type (proto-index field))))
-                                          (setq index (serialize-prim v type tag buffer index)))))))))))))
+                                          (setq index (serialize-prim v type tag buffer index))))))
+                                   (t
 -                                   (error 'undefined-field-type
 -                                          :format-control "While serializing ~s to protobuf,"
 -                                          :format-arguments (list object)
 -                                          :type-name (prin1-to-string type)
 -                                          :field field)))))))))
++                                   (undefined-field-type "While serializing ~S,"
++                                                         object type field)))))))))
          (declare (dynamic-extent #'do-field))
          (dolist (field (proto-fields message))
            (do-field object message field))))
                                            (tag  (make-tag type (proto-index field))))
                                       (doseq (v (read-slot object slot reader))
                                         (let ((v (funcall (proto-serializer msg) v)))
-                                          (iincf size (prim-size v type tag))))))))
+                                          (iincf size (prim-size v type tag))))))
+                                   (t
 -                                   (error 'undefined-field-type
 -                                          :format-control "While computing the size of ~s in bytes,"
 -                                          :format-arguments (list object)
 -                                          :type-name (prin1-to-string type)
 -                                          :field field))))
++                                   (undefined-field-type "While computing the size of ~S,"
++                                                         object type field))))
                             (t
                              (cond ((eq type :bool)
                                     (let ((v (cond ((or (eq (proto-required field) :required)
                                         (let* ((v    (funcall (proto-serializer msg) v))
                                                (type (proto-proto-type msg))
                                                (tag  (make-tag type (proto-index field))))
-                                          (iincf size (prim-size v type tag)))))))))))))
+                                          (iincf size (prim-size v type tag))))))
+                                   (t
 -                                   (error 'undefined-field-type
 -                                          :format-control "While computing the size of ~s in bytes,"
 -                                          :format-arguments (list object)
 -                                          :type-name (prin1-to-string type)
 -                                          :field field)))))))))
++                                   (undefined-field-type "While computing the size of ~S,"
++                                                         object type field)))))))))
          (declare (dynamic-extent #'do-field))
          (dolist (field (proto-fields message))
            (do-field object message field))
                                      (tag   (make-tag class (proto-index field))))
                                 `(,iterator (,vval ,reader)
                                    (let ((,vval (funcall #',(proto-serializer msg) ,vval)))
-                                     (setq ,vidx (serialize-prim ,vval ,class ,tag ,vbuf ,vidx))))))))))
+                                     (setq ,vidx (serialize-prim ,vval ,class ,tag ,vbuf ,vidx)))))))
+                            (t
 -                            (error 'undefined-field-type
 -                                   :format-control "While generating the serialize-object method ~
 -                                                    for ~s,"
 -                                   :format-arguments (list message)
 -                                   :type-name (prin1-to-string class)
 -                                   :field field)))))
++                            (undefined-field-type "While generating 'serialize-object' for ~S,"
++                                                  message class field)))))
                    (t
                     (cond ((keywordp class)
                            (collect-serializer
                               `(let ((,vval ,reader))
                                  (when ,vval
                                    (let ((,vval (funcall #',(proto-serializer msg) ,vval)))
-                                     (setq ,vidx (serialize-prim ,vval ,class ,tag ,vbuf ,vidx))))))))))))))
+                                     (setq ,vidx (serialize-prim ,vval ,class ,tag ,vbuf ,vidx))))))))
+                          (t
 -                          (error 'undefined-field-type
 -                                 :format-control "While generating the serialize-object method ~
 -                                                  for ~s,"
 -                                 :format-arguments (list message)
 -                                 :type-name (prin1-to-string class)
 -                                 :field field))))))))
++                          (undefined-field-type "While generating 'serialize-object' for ~S,"
++                                                message class field))))))))
        `(defmethod serialize-object
             (,vobj (,vclass (eql ,message)) ,vbuf &optional (,vidx 0) visited)
           (declare #.$optimize-serialization)
                                 (multiple-value-bind (,vval idx)
                                     (deserialize-prim ,class ,vbuf ,vidx)
                                   (setq ,vidx idx)
-                                  (push (funcall #',(proto-deserializer msg) ,vval) ,temp))))))))
+                                  (push (funcall #',(proto-deserializer msg) ,vval) ,temp))))))
+                          (t
 -                          (error 'undefined-field-type
 -                                 :format-control "While generating the deserialize-object method ~
 -                                                  for ~s,"
 -                                 :format-arguments (list message)
 -                                 :type-name (prin1-to-string class)
 -                                 :field field))))
++                          (undefined-field-type "While generating 'deserialize-object' for ~S,"
++                                                message class field))))
                    (t
                     (cond ((keywordp class)
                            (collect-deserializer
                                       (deserialize-prim ,class ,vbuf ,vidx)
                                     (let ((,vval (funcall #',(proto-deserializer msg) ,vval)))
                                       (setq ,vidx idx)
-                                      ,(write-slot vobj field vval)))))))))))))
+                                      ,(write-slot vobj field vval)))))))
+                          (t
 -                          (error 'undefined-field-type
 -                                 :format-control "While generating the deserialize-object method ~
 -                                                  for ~s,"
 -                                 :format-arguments (list message)
 -                                 :type-name (prin1-to-string class)
 -                                 :field field))))))))
++                          (undefined-field-type "While generating 'deserialize-object' for ~S,"
++                                                message class field))))))))
        (let* ((rslots  (delete-duplicates rslots :key #'first))
               (rfields (mapcar #'first  rslots))
               (rtemps  (mapcar #'second rslots)))
                                      (tag   (make-tag class index)))
                                 `(,iterator (,vval ,reader)
                                    (let ((,vval (funcall #',(proto-serializer msg) ,vval)))
-                                     (iincf ,vsize (prim-size ,vval ,class ,tag))))))))))
+                                     (iincf ,vsize (prim-size ,vval ,class ,tag)))))))
+                            (t
 -                            (error 'undefined-field-type
 -                                   :format-control "While generating the object-size method for ~s,"
 -                                   :format-arguments (list message)
 -                                   :type-name (prin1-to-string class)
 -                                   :field field)))))
++                            (undefined-field-type "While generating 'object-size' for ~S,"
++                                                  message class field)))))
                    (t
                     (cond ((keywordp class)
                            (let ((tag (make-tag class index)))
                                    (tag   (make-tag class index)))
                               `(let ((,vval ,reader))
                                  (when ,vval
 -                                  (iincf ,vsize (prim-size
 -                                                 (funcall #',(proto-serializer msg) ,vval)
 -                                                 ,class ,tag)))))))
 +                                  (iincf ,vsize (prim-size (funcall #',(proto-serializer msg) ,vval)
-                                                            ,class ,tag)))))))))))))
++                                                           ,class ,tag)))))))
+                          (t
 -                          (error 'undefined-field-type
 -                                 :format-control "While generating the object-size method for ~s,"
 -                                 :format-arguments (list message)
 -                                 :type-name (prin1-to-string class)
 -                                 :field field))))))))
++                          (undefined-field-type "While generating 'object-size' for ~S,"
++                                                message class field))))))))
        `(defmethod object-size
             (,vobj (,vclass (eql ,message)) &optional visited)
           (declare #.$optimize-serialization)
index 4b7fd6711a27b103ddf5bce8b774bc10e4f5f45b,74174a45600624f5400836842791627a5c4a7a28..a59202a7c305208f3c84b726464d5cf6ee7841ba
     "RUN-TEST"
     "ASSERT-EQUAL"
     "ASSERT-TRUE"
-    "ASSERT-FALSE"))
+    "ASSERT-FALSE"
++   "ASSERT-ERROR")
++  (:export
++   "DEFINE-TEST"
++   "DEFINE-TEST-SUITE"
++   "REGISTER-TEST"
++   "RUN-TEST"
++   "RUN-ALL-TESTS"
++   "ASSERT-EQUAL"
++   "ASSERT-TRUE"
++   "ASSERT-FALSE"
+    "ASSERT-ERROR"))
++
++;;; Packages used by .oroto files
 +
  (defpackage protobuf-unittest
    (:use :common-lisp :protobufs)
    (:nicknames :pbtest))
index 3b426b44a1a9bf8f8cdf93c7e2c44198796341e1,bbcab3ad9be4736ff118554ffa37222d8b4996f6..414fc83c2f6bfe28d584fc902c1b65b0f0207196
      (format t "~&Running test ~A" test)
      (funcall test)))
  
- (defmacro assert-equal (actual expected &key (test 'eql))
+ (defmacro assert-equal (actual expected &key (test 'equal))
    `(unless (,test ,actual ,expected)
--     (warn "The value ~S is not equal to the expected value ~S"
--           ',actual ',expected)))
++     (warn "The value of ~S (~S) is not equal to the expected value ~S"
++           ',actual ,actual ,expected)))
  
  (defmacro assert-true (form)
    `(unless ,form
--     (warn "The value ~S does not evaluate to 'true'"
--           ',form)))
++     (warn "The value of ~S (~S) does not evaluate to 'true'"
++           ',form ,form)))
  
  (defmacro assert-false (form)
    `(when ,form
--     (warn "The value ~S does not evaluate to 'false'"
--           ',form)))
++     (warn "The value ~S (~S) does not evaluate to 'false'"
++           ',form ,form)))
+ (defmacro assert-error (condition &body body)
+   "Checks if BODY signals a condition of class CONDITION. If it does not, a failure is
+    reported. If it is, the condition is caught and the condition object returned so that the test
+    can perform further checks on the condition object."
+   (let ((c (gensym "C")))
+     `(handler-case (progn ,@body)
+        (,condition (,c)
+          ,c)
+        (:no-error ()
+          (warn "Expected condition ~a while evaluating~{ ~s~}" ',condition ',body)))))
index a5369897b90cacd2fad571abe71bcf5de7b1c467,89795a3f1f9a54d099796b7fb496bdf4252b3e80..3b4bb5c1332c6dc974298cb0f9f8202824b512cd
                                       (doseq (v (read-slot object slot reader))
                                         (let ((v (funcall (proto-serializer msg) v)))
                                           (print-prim v type field stream
-                                                      (or suppress-line-breaks indent))))))))
+                                                      (or suppress-line-breaks indent))))))
+                                   (t
 -                                   (error 'undefined-field-type
 -                                          :format-control "While printing ~s to text format,"
 -                                          :format-arguments (list object)
 -                                          :type-name (prin1-to-string type)
 -                                          :field field))))
++                                   (undefined-field-type "While printing ~S to text format,"
++                                                         object type field))))
                             (t
                              (cond ((eq type :bool)
                                     (let ((v (cond ((or (eq (proto-required field) :required)
                                         (let ((v    (funcall (proto-serializer msg) v))
                                               (type (proto-proto-type msg)))
                                           (print-prim v type field stream
-                                                      (or suppress-line-breaks indent)))))))))))))
+                                                      (or suppress-line-breaks indent))))))
+                                   (t
 -                                   (error 'undefined-field-type
 -                                          :format-control "While printing ~s to text format,"
 -                                          :format-arguments (list object)
 -                                          :type-name (prin1-to-string type)
 -                                          :field field)))))))))
++                                   (undefined-field-type "While printing ~S to text format,"
++                                                         object type field)))))))))
          (declare (dynamic-extent #'do-field))
          (if print-name
            (if suppress-line-breaks
                                         (when slot
                                           (pushnew slot rslots)
                                           (push (funcall (proto-deserializer msg) val)
-                                                (slot-value object slot))))))))
+                                                (slot-value object slot))))))
+                                   (t
 -                                   (error 'undefined-field-type
 -                                          :format-control "While parsing ~s from text format,"
 -                                          :format-arguments (list message)
 -                                          :type-name (prin1-to-string type)
 -                                          :field field))))
++                                   (undefined-field-type "While parsing ~S from text format,"
++                                                         message type field))))
                             (t
                              (cond ((keywordp type)
                                     (expect-char stream #\:)
                                                    (otherwise (parse-signed-int stream)))))
                                         (when slot
                                           (setf (slot-value object slot)
-                                                (funcall (proto-deserializer msg) val)))))))))))))))
+                                                (funcall (proto-deserializer msg) val))))))
+                                   (t
 -                                   (error 'undefined-field-type
 -                                          :format-control "While parsing ~s from text format,"
 -                                          :format-arguments (list message)
 -                                          :type-name (prin1-to-string type)
 -                                          :field field)))))))))))
++                                   (undefined-field-type "While parsing ~S from text format,"
++                                                         message type field)))))))))))
      (declare (dynamic-extent #'deserialize))
      (deserialize (proto-class message) message)))