From 325392dd6128ad9915f758e37307c12bcc56f064 Mon Sep 17 00:00:00 2001 From: Josh Haberman Date: Mon, 17 Aug 2015 12:30:49 -0700 Subject: [PATCH 1/4] Conformance test implementation for Python. --- conformance/Makefile.am | 12 ++- conformance/conformance_python.py | 122 ++++++++++++++++++++++++++++++ conformance/conformance_test.cc | 8 +- python/setup.py | 7 ++ python/tox.ini | 2 + 5 files changed, 146 insertions(+), 5 deletions(-) create mode 100755 conformance/conformance_python.py diff --git a/conformance/Makefile.am b/conformance/Makefile.am index ea5edbbaee..0e76b16add 100644 --- a/conformance/Makefile.am +++ b/conformance/Makefile.am @@ -53,7 +53,7 @@ endif if USE_EXTERNAL_PROTOC protoc_middleman: $(protoc_inputs) - $(PROTOC) -I$(srcdir) --cpp_out=. --java_out=. --ruby_out=. --objc_out=. $^ + $(PROTOC) -I$(srcdir) --cpp_out=. --java_out=. --ruby_out=. --objc_out=. --python_out=.$^ touch protoc_middleman else @@ -62,7 +62,7 @@ else # relative to srcdir, which may not be the same as the current directory when # building out-of-tree. protoc_middleman: $(top_srcdir)/src/protoc$(EXEEXT) $(protoc_inputs) - oldpwd=`pwd` && ( cd $(srcdir) && $$oldpwd/../src/protoc$(EXEEXT) -I. --cpp_out=$$oldpwd --java_out=$$oldpwd --ruby_out=$$oldpwd --objc_out=$$oldpwd $(protoc_inputs) ) + oldpwd=`pwd` && ( cd $(srcdir) && $$oldpwd/../src/protoc$(EXEEXT) -I. --cpp_out=$$oldpwd --java_out=$$oldpwd --ruby_out=$$oldpwd --objc_out=$$oldpwd --python_out=$$oldpwd $(protoc_inputs) ) touch protoc_middleman endif @@ -110,6 +110,14 @@ test_csharp: protoc_middleman conformance-test-runner conformance-csharp test_ruby: protoc_middleman conformance-test-runner $(other_language_protoc_outputs) RUBYLIB=../ruby/lib:. ./conformance-test-runner --failure_list failure_list_ruby.txt ./conformance_ruby.rb +# These depend on library paths being properly set up. The easiest way to +# run them is to just use "tox" from the python dir. +test_python: protoc_middleman conformance-test-runner + ./conformance-test-runner --failure_list failure_list_python.txt ./conformance_python.py + +test_python_cpp: protoc_middleman conformance-test-runner + ./conformance-test-runner --failure_list failure_list_python_cpp.txt ./conformance_python.py + if OBJC_CONFORMANCE_TEST test_objc: protoc_middleman conformance-test-runner conformance-objc diff --git a/conformance/conformance_python.py b/conformance/conformance_python.py new file mode 100755 index 0000000000..af3dc8ecf5 --- /dev/null +++ b/conformance/conformance_python.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python +# +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# https://developers.google.com/protocol-buffers/ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""A conformance test implementation for the Python protobuf library. + +See conformance.proto for more information. +""" + +import struct +import sys +import os +from google.protobuf import message +import conformance_pb2 + +sys.stdout = os.fdopen(sys.stdout.fileno(), 'wb', 0) +sys.stdin = os.fdopen(sys.stdin.fileno(), 'rb', 0) + +test_count = 0 +verbose = False + +def do_test(request): + test_message = conformance_pb2.TestAllTypes() + response = conformance_pb2.ConformanceResponse() + test_message = conformance_pb2.TestAllTypes() + + try: + if request.WhichOneof('payload') == 'protobuf_payload': + try: + test_message.ParseFromString(request.protobuf_payload) + except message.DecodeError as e: + response.parse_error = str(e) + return response + + elif request.WhichOneof('payload') == 'json_payload': + response.skipped = "JSON not supported yet." + return response + + else: + raise "Request didn't have payload." + + if request.requested_output_format == conformance_pb2.UNSPECIFIED: + raise "Unspecified output format" + + elif request.requested_output_format == conformance_pb2.PROTOBUF: + response.protobuf_payload = test_message.SerializeToString() + + elif request.requested_output_format == conformance_pb2.JSON: + response.skipped = "JSON not supported yet." + except Exception as e: + response.runtime_error = str(e) + + return response + +def do_test_io(): + length_bytes = sys.stdin.read(4) + if len(length_bytes) == 0: + return False # EOF + elif len(length_bytes) != 4: + raise IOError("I/O error") + + # "I" is "unsigned int", so this depends on running on a platform with + # 32-bit "unsigned int" type. The Python struct module unfortunately + # has no format specifier for uint32_t. + length = struct.unpack(" Date: Wed, 2 Dec 2015 13:21:42 -0800 Subject: [PATCH 2/4] Added JSON support to Python conformance tests. --- conformance/conformance_python.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/conformance/conformance_python.py b/conformance/conformance_python.py index af3dc8ecf5..32aa225556 100755 --- a/conformance/conformance_python.py +++ b/conformance/conformance_python.py @@ -39,6 +39,7 @@ import struct import sys import os from google.protobuf import message +from google.protobuf import json_format import conformance_pb2 sys.stdout = os.fdopen(sys.stdout.fileno(), 'wb', 0) @@ -61,8 +62,11 @@ def do_test(request): return response elif request.WhichOneof('payload') == 'json_payload': - response.skipped = "JSON not supported yet." - return response + try: + json_format.Parse(request.json_payload, test_message) + except json_format.ParseError as e: + response.parse_error = str(e) + return response else: raise "Request didn't have payload." @@ -74,7 +78,8 @@ def do_test(request): response.protobuf_payload = test_message.SerializeToString() elif request.requested_output_format == conformance_pb2.JSON: - response.skipped = "JSON not supported yet." + response.json_payload = json_format.MessageToJson(test_message) + except Exception as e: response.runtime_error = str(e) From 4b31ffa48856fb1f8293fd4682e603406a6d8e5f Mon Sep 17 00:00:00 2001 From: Josh Haberman Date: Thu, 3 Dec 2015 12:54:54 -0800 Subject: [PATCH 3/4] Added Python failure lists, and fixes to make sure failure propagates. --- conformance/conformance_test_runner.cc | 6 ++++++ conformance/failure_list_python.txt | 0 conformance/failure_list_python_cpp.txt | 25 +++++++++++++++++++++++++ python/setup.py | 3 ++- 4 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 conformance/failure_list_python.txt create mode 100644 conformance/failure_list_python_cpp.txt diff --git a/conformance/conformance_test_runner.cc b/conformance/conformance_test_runner.cc index 780e1c4447..c3b3db22c0 100644 --- a/conformance/conformance_test_runner.cc +++ b/conformance/conformance_test_runner.cc @@ -202,6 +202,12 @@ void UsageError() { void ParseFailureList(const char *filename, vector* failure_list) { std::ifstream infile(filename); + + if (!infile.is_open()) { + fprintf(stderr, "Couldn't open failure list file: %s\n", filename); + exit(1); + } + for (string line; getline(infile, line);) { // Remove whitespace. line.erase(std::remove_if(line.begin(), line.end(), ::isspace), diff --git a/conformance/failure_list_python.txt b/conformance/failure_list_python.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/conformance/failure_list_python_cpp.txt b/conformance/failure_list_python_cpp.txt new file mode 100644 index 0000000000..4b16a88b72 --- /dev/null +++ b/conformance/failure_list_python_cpp.txt @@ -0,0 +1,25 @@ +# This is the list of conformance tests that are known to fail for the +# Python/C++ implementation right now. These should be fixed. +# +# By listing them here we can keep tabs on which ones are failing and be sure +# that we don't introduce regressions in other tests. +# +# TODO(haberman): insert links to corresponding bugs tracking the issue. +# Should we use GitHub issues or the Google-internal bug tracker? + +ProtobufInput.PrematureEofInDelimitedDataForKnownNonRepeatedValue.MESSAGE +ProtobufInput.PrematureEofInDelimitedDataForKnownRepeatedValue.MESSAGE +ProtobufInput.PrematureEofInPackedField.BOOL +ProtobufInput.PrematureEofInPackedField.DOUBLE +ProtobufInput.PrematureEofInPackedField.ENUM +ProtobufInput.PrematureEofInPackedField.FIXED32 +ProtobufInput.PrematureEofInPackedField.FIXED64 +ProtobufInput.PrematureEofInPackedField.FLOAT +ProtobufInput.PrematureEofInPackedField.INT32 +ProtobufInput.PrematureEofInPackedField.INT64 +ProtobufInput.PrematureEofInPackedField.SFIXED32 +ProtobufInput.PrematureEofInPackedField.SFIXED64 +ProtobufInput.PrematureEofInPackedField.SINT32 +ProtobufInput.PrematureEofInPackedField.SINT64 +ProtobufInput.PrematureEofInPackedField.UINT32 +ProtobufInput.PrematureEofInPackedField.UINT64 diff --git a/python/setup.py b/python/setup.py index 05c16fc3aa..18865e03fd 100755 --- a/python/setup.py +++ b/python/setup.py @@ -147,7 +147,8 @@ class build_py(_build_py): class test_conformance(_build_py): target = 'test_python' def run(self): - os.system('cd ../conformance && make %s' % (test_conformance.target)) + cmd = 'cd ../conformance && make %s' % (test_conformance.target) + status = subprocess.check_call(cmd, shell=True) if __name__ == '__main__': From 874eb3648ee72b586eb151b2604e6c9a2812c5bc Mon Sep 17 00:00:00 2001 From: Josh Haberman Date: Fri, 4 Dec 2015 15:03:12 -0800 Subject: [PATCH 4/4] Remove all bare strings as exceptions. --- conformance/conformance_python.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/conformance/conformance_python.py b/conformance/conformance_python.py index 32aa225556..a490c8e8e5 100755 --- a/conformance/conformance_python.py +++ b/conformance/conformance_python.py @@ -48,6 +48,9 @@ sys.stdin = os.fdopen(sys.stdin.fileno(), 'rb', 0) test_count = 0 verbose = False +class ProtocolError(Exception): + pass + def do_test(request): test_message = conformance_pb2.TestAllTypes() response = conformance_pb2.ConformanceResponse() @@ -69,10 +72,10 @@ def do_test(request): return response else: - raise "Request didn't have payload." + raise ProtocolError("Request didn't have payload.") if request.requested_output_format == conformance_pb2.UNSPECIFIED: - raise "Unspecified output format" + raise ProtocolError("Unspecified output format") elif request.requested_output_format == conformance_pb2.PROTOBUF: response.protobuf_payload = test_message.SerializeToString() @@ -98,7 +101,7 @@ def do_test_io(): length = struct.unpack("