From 1745f7eae9bc206ffeb47d63dcdfa039ef3774e8 Mon Sep 17 00:00:00 2001 From: Thomas Van Lenten Date: Tue, 17 Nov 2015 10:18:49 -0500 Subject: [PATCH] Add support for the conformance test for objc when run on OS X --- .gitignore | 3 + configure.ac | 10 ++ conformance/Makefile.am | 38 +++++- conformance/conformance_objc.m | 179 ++++++++++++++++++++++++++ conformance/failure_list_objc.txt | 2 + objectivec/DevTools/full_mac_build.sh | 14 +- travis.sh | 6 +- 7 files changed, 245 insertions(+), 7 deletions(-) create mode 100644 conformance/conformance_objc.m create mode 100644 conformance/failure_list_objc.txt diff --git a/.gitignore b/.gitignore index b0a92f8222..19e5212986 100644 --- a/.gitignore +++ b/.gitignore @@ -103,9 +103,12 @@ conformance/com/ conformance/conformance-cpp conformance/conformance-csharp conformance/conformance-java +conformance/conformance-objc conformance/conformance-test-runner conformance/conformance.pb.cc conformance/conformance.pb.h +conformance/Conformance.pbobjc.h +conformance/Conformance.pbobjc.m conformance/conformance.rb conformance/javac_middleman conformance/protoc_middleman diff --git a/configure.ac b/configure.ac index 0e58789348..42605ae397 100644 --- a/configure.ac +++ b/configure.ac @@ -59,6 +59,7 @@ AC_LANG([C++]) ACX_USE_SYSTEM_EXTENSIONS m4_ifdef([AM_PROG_AR], [AM_PROG_AR]) AM_CONDITIONAL(GCC, test "$GCC" = yes) # let the Makefile know if we're gcc +AC_PROG_OBJC # test_util.cc takes forever to compile with GCC and optimization turned on. AC_MSG_CHECKING([C++ compiler flags...]) @@ -163,6 +164,15 @@ case "$target_os" in ;; esac +# Enable ObjC support for conformance directory on OS X. +OBJC_CONFORMANCE_TEST=0 +case "$target_os" in + darwin*) + OBJC_CONFORMANCE_TEST=1 + ;; +esac +AM_CONDITIONAL([OBJC_CONFORMANCE_TEST], [test $OBJC_CONFORMANCE_TEST = 1]) + # HACK: Make gmock's configure script pick up our copy of CFLAGS and CXXFLAGS, # since the flags added by ACX_CHECK_SUNCC must be used when compiling gmock # too. diff --git a/conformance/Makefile.am b/conformance/Makefile.am index b6fda2a82f..d7bb9397a4 100644 --- a/conformance/Makefile.am +++ b/conformance/Makefile.am @@ -9,7 +9,9 @@ protoc_outputs = \ other_language_protoc_outputs = \ conformance.rb \ - com/google/protobuf/conformance/Conformance.java + com/google/protobuf/conformance/Conformance.java \ + Conformance.pbobjc.h \ + Conformance.pbobjc.m bin_PROGRAMS = conformance-test-runner conformance-cpp @@ -17,16 +19,37 @@ conformance_test_runner_LDADD = $(top_srcdir)/src/libprotobuf.la conformance_test_runner_SOURCES = conformance_test.cc conformance_test_runner.cc nodist_conformance_test_runner_SOURCES = conformance.pb.cc conformance_test_runner_CPPFLAGS = -I$(top_srcdir)/src +# Explicit deps beacuse BUILT_SOURCES are only done before a "make all/check" +# so a direct "make test_cpp" could fail if parallel enough. +conformance_test_runner-conformance_test.$(OBJEXT): conformance.pb.h +conformance_test_runner-conformance_test_runner.$(OBJEXT): conformance.pb.h conformance_cpp_LDADD = $(top_srcdir)/src/libprotobuf.la conformance_cpp_SOURCES = conformance_cpp.cc nodist_conformance_cpp_SOURCES = conformance.pb.cc conformance_cpp_CPPFLAGS = -I$(top_srcdir)/src +# Explicit dep beacuse BUILT_SOURCES are only done before a "make all/check" +# so a direct "make test_cpp" could fail if parallel enough. +conformance_cpp-conformance_cpp.$(OBJEXT): conformance.pb.h + +if OBJC_CONFORMANCE_TEST + +bin_PROGRAMS += conformance-objc + +conformance_objc_SOURCES = conformance_objc.m ../objectivec/GPBProtocolBuffers.m +nodist_conformance_objc_SOURCES = Conformance.pbobjc.m +conformance_objc_CPPFLAGS = -I$(top_srcdir)/objectivec +conformance_objc_LDFLAGS = -framework Foundation +# Explicit dep beacuse BUILT_SOURCES are only done before a "make all/check" +# so a direct "make test_objc" could fail if parallel enough. +conformance_objc-conformance_objc.$(OBJEXT): Conformance.pbobjc.h + +endif if USE_EXTERNAL_PROTOC protoc_middleman: $(protoc_inputs) - $(PROTOC) -I$(srcdir) --cpp_out=. --java_out=. --ruby_out=. $^ + $(PROTOC) -I$(srcdir) --cpp_out=. --java_out=. --ruby_out=. --objc_out=. $^ touch protoc_middleman else @@ -35,7 +58,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 $(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) ) touch protoc_middleman endif @@ -44,7 +67,7 @@ $(protoc_outputs): protoc_middleman $(other_language_protoc_outputs): protoc_middleman -BUILT_SOURCES = $(protoc_outputs) +BUILT_SOURCES = $(protoc_outputs) $(other_language_protoc_outputs) CLEANFILES = $(protoc_outputs) protoc_middleman javac_middleman conformance-java conformance-csharp $(other_language_protoc_outputs) @@ -82,3 +105,10 @@ 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 + +if OBJC_CONFORMANCE_TEST + +test_objc: protoc_middleman conformance-test-runner conformance-objc + ./conformance-test-runner --failure_list failure_list_objc.txt ./conformance-objc + +endif diff --git a/conformance/conformance_objc.m b/conformance/conformance_objc.m new file mode 100644 index 0000000000..06c97a82d0 --- /dev/null +++ b/conformance/conformance_objc.m @@ -0,0 +1,179 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2015 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. + +#import + +#import "Conformance.pbobjc.h" + +static void Die(NSString *format, ...) __dead2; + +static BOOL verbose = NO; +static int32_t testCount = 0; + +static void Die(NSString *format, ...) { + va_list args; + va_start(args, format); + NSString *msg = [[NSString alloc] initWithFormat:format arguments:args]; + NSLog(@"%@", msg); + va_end(args); + [msg release]; + exit(66); +} + +static NSData *CheckedReadDataOfLength(NSFileHandle *handle, NSUInteger numBytes) { + NSData *data = [handle readDataOfLength:numBytes]; + NSUInteger dataLen = data.length; + if (dataLen == 0) { + return nil; // EOF. + } + if (dataLen != numBytes) { + Die(@"Failed to read the request length (%d), only got: %@", + numBytes, data); + } + return data; +} + +static ConformanceResponse *DoTest(ConformanceRequest *request) { + ConformanceResponse *response = [ConformanceResponse message]; + TestAllTypes *testMessage = nil; + + switch (request.payloadOneOfCase) { + case ConformanceRequest_Payload_OneOfCase_GPBUnsetOneOfCase: + Die(@"Request didn't have a payload: %@", request); + break; + + case ConformanceRequest_Payload_OneOfCase_ProtobufPayload: { + NSError *error = nil; + testMessage = [TestAllTypes parseFromData:request.protobufPayload + error:&error]; + if (!testMessage) { + response.parseError = + [NSString stringWithFormat:@"Parse error: %@", error]; + } + break; + } + + case ConformanceRequest_Payload_OneOfCase_JsonPayload: + response.skipped = @"ObjC doesn't support parsing JSON"; + break; + } + + if (testMessage) { + switch (request.requestedOutputFormat) { + case WireFormat_GPBUnrecognizedEnumeratorValue: + case WireFormat_Unspecified: + Die(@"Unrecognized/unspecified output format: %@", request); + break; + + case WireFormat_Protobuf: + response.protobufPayload = testMessage.data; + if (!response.protobufPayload) { + response.runtimeError = + [NSString stringWithFormat:@"Failed to make data from: %@", testMessage]; + } + break; + + case WireFormat_Json: + response.skipped = @"ObjC doesn't support generating JSON"; + break; + } + } + + return response; +} + +static uint32_t UInt32FromLittleEndianData(NSData *data) { + if (data.length != sizeof(uint32_t)) { + Die(@"Data not the right size for uint32_t: %@", data); + } + uint32_t value; + memcpy(&value, data.bytes, sizeof(uint32_t)); + return CFSwapInt32LittleToHost(value); +} + +static NSData *UInt32ToLittleEndianData(uint32_t num) { + uint32_t value = CFSwapInt32HostToLittle(num); + return [NSData dataWithBytes:&value length:sizeof(uint32_t)]; +} + +static BOOL DoTestIo(NSFileHandle *input, NSFileHandle *output) { + // See conformance_test_runner.cc for the wire format. + NSData *data = CheckedReadDataOfLength(input, sizeof(uint32_t)); + if (!data) { + // EOF. + return NO; + } + uint32_t numBytes = UInt32FromLittleEndianData(data); + data = CheckedReadDataOfLength(input, numBytes); + if (!data) { + Die(@"Failed to read request"); + } + + NSError *error = nil; + ConformanceRequest *request = [ConformanceRequest parseFromData:data + error:&error]; + if (!request) { + Die(@"Failed to parse the message data: %@", error); + } + + ConformanceResponse *response = DoTest(request); + if (!response) { + Die(@"Failed to make a reply from %@", request); + } + + data = response.data; + [output writeData:UInt32ToLittleEndianData((int32_t)data.length)]; + [output writeData:data]; + + if (verbose) { + NSLog(@"Request: %@", request); + NSLog(@"Response: %@", response); + } + + ++testCount; + return YES; +} + +int main(int argc, const char *argv[]) { + @autoreleasepool { + NSFileHandle *input = [[NSFileHandle fileHandleWithStandardInput] retain]; + NSFileHandle *output = [[NSFileHandle fileHandleWithStandardOutput] retain]; + + BOOL notDone = YES; + while (notDone) { + @autoreleasepool { + notDone = DoTestIo(input, output); + } + } + + NSLog(@"Received EOF from test runner after %d tests, exiting.", testCount); + } + return 0; +} diff --git a/conformance/failure_list_objc.txt b/conformance/failure_list_objc.txt new file mode 100644 index 0000000000..a34b3f5b94 --- /dev/null +++ b/conformance/failure_list_objc.txt @@ -0,0 +1,2 @@ +ProtobufInput.PrematureEofInDelimitedDataForKnownNonRepeatedValue.MESSAGE +ProtobufInput.PrematureEofInDelimitedDataForKnownRepeatedValue.MESSAGE diff --git a/objectivec/DevTools/full_mac_build.sh b/objectivec/DevTools/full_mac_build.sh index 71d3fa45f8..251f2599d9 100755 --- a/objectivec/DevTools/full_mac_build.sh +++ b/objectivec/DevTools/full_mac_build.sh @@ -38,6 +38,8 @@ OPTIONS: Skip the invoke of Xcode to test the runtime on iOS. --skip-xcode-osx Skip the invoke of Xcode to test the runtime on OS X. + --skip-objc-conformance + Skip the Objective C conformance tests (run on OS X). EOF } @@ -73,6 +75,7 @@ REGEN_CPP_DESCRIPTORS=no CORE_ONLY=no DO_XCODE_IOS_TESTS=yes DO_XCODE_OSX_TESTS=yes +DO_OBJC_CONFORMANCE_TESTS=yes while [[ $# != 0 ]]; do case "${1}" in -h | --help ) @@ -105,6 +108,9 @@ while [[ $# != 0 ]]; do --skip-xcode-osx ) DO_XCODE_OSX_TESTS=no ;; + --skip-objc-conformance ) + DO_OBJC_CONFORMANCE_TESTS=no + ;; -*) echo "ERROR: Unknown option: ${1}" 1>&2 printUsage @@ -172,7 +178,7 @@ else wrapped_make -j "${NUM_MAKE_JOBS}" check # Fire off the conformance tests also. cd conformance - wrapped_make -j "${NUM_MAKE_JOBS}" + wrapped_make -j "${NUM_MAKE_JOBS}" test_cpp cd .. fi @@ -264,3 +270,9 @@ if [[ "${DO_XCODE_OSX_TESTS}" == "yes" ]] ; then header "Doing Xcode OS X build/tests - Release" "${XCODEBUILD_TEST_BASE_OSX[@]}" -configuration Release test fi + +if [[ "${DO_OBJC_CONFORMANCE_TESTS}" == "yes" ]] ; then + cd conformance + wrapped_make -j "${NUM_MAKE_JOBS}" test_objc + cd .. +fi diff --git a/travis.sh b/travis.sh index 3b6ec3362f..459f83f5f8 100755 --- a/travis.sh +++ b/travis.sh @@ -136,8 +136,9 @@ internal_objectivec_common () { brew update brew outdated xctool || brew upgrade xctool # Reused the build script that takes care of configuring and ensuring things - # are up to date. - objectivec/DevTools/full_mac_build.sh --core-only --skip-xcode + # are up to date. Xcode and conformance tests will be directly invoked. + objectivec/DevTools/full_mac_build.sh \ + --core-only --skip-xcode --skip-objc-conformance } internal_xctool_debug_and_release() { @@ -178,6 +179,7 @@ build_objectivec_osx() { -scheme ProtocolBuffers \ -destination "platform=OS X,arch=x86_64" \ test + cd conformance && make test_objc && cd .. } build_python() {