Add support for running C++ tests on iOS

pull/19885/head
Prashant Jaikumar 6 years ago
parent d44dfbc77e
commit 141c2d24d1
  1. 34
      bazel/grpc_build_system.bzl
  2. 2
      src/proto/grpc/testing/BUILD
  3. 16
      test/cpp/end2end/BUILD
  4. 11
      test/cpp/end2end/filter_end2end_test.cc
  5. 4
      test/cpp/util/BUILD
  6. 31
      third_party/objective_c/google_toolbox_for_mac/BUILD
  7. 233
      third_party/objective_c/google_toolbox_for_mac/UnitTesting/GTMGoogleTestRunner.mm
  8. 19
      tools/internal_ci/macos/grpc_bazel_cpp_ios_tests.cfg
  9. 39
      tools/internal_ci/macos/grpc_run_bazel_cpp_ios_tests.sh

@ -26,6 +26,8 @@
load("//bazel:cc_grpc_library.bzl", "cc_grpc_library")
load("@build_bazel_rules_apple//apple:resources.bzl", "apple_resource_bundle")
load("@upb//bazel:upb_proto_library.bzl", "upb_proto_library")
load("@build_bazel_rules_apple//apple:ios.bzl", "ios_unit_test")
# The set of pollers to test against if a test exercises polling
POLLERS = ["epollex", "epoll1", "poll"]
@ -137,6 +139,32 @@ def grpc_proto_library(
use_external = use_external,
generate_mocks = generate_mocks,
)
def ios_cc_test(
name,
tags = [],
**kwargs):
ios_test_adapter = "//third_party/objective_c/google_toolbox_for_mac:GTM_GoogleTestRunner_GTM_USING_XCTEST";
test_lib_ios = name + "_test_lib_ios"
ios_tags = tags + ["manual", "ios_cc_test"]
if not any([t for t in tags if t.startswith("no_test_ios")]):
native.objc_library(
name = test_lib_ios,
srcs = kwargs.get("srcs"),
deps = kwargs.get("deps"),
copts = kwargs.get("copts"),
tags = ios_tags,
alwayslink = 1,
testonly = 1,
)
ios_test_deps = [ios_test_adapter, ":" + test_lib_ios]
ios_unit_test(
name = name + "_on_ios",
size = kwargs.get("size"),
tags = ios_tags,
minimum_os_version = "9.0",
deps = ios_test_deps,
)
def grpc_cc_test(name, srcs = [], deps = [], external_deps = [], args = [], data = [], uses_polling = True, language = "C++", size = "medium", timeout = None, tags = [], exec_compatible_with = []):
copts = if_mac(["-DGRPC_CFSTREAM"])
@ -183,6 +211,12 @@ def grpc_cc_test(name, srcs = [], deps = [], external_deps = [], args = [], data
)
else:
native.cc_test(tags = tags, **args)
ios_cc_test(
name = name,
tags = tags,
**args
)
def grpc_cc_binary(name, srcs = [], deps = [], external_deps = [], args = [], data = [], language = "C++", testonly = False, linkshared = False, linkopts = [], tags = []):
copts = []

@ -50,7 +50,6 @@ grpc_proto_library(
grpc_proto_library(
name = "echo_messages_proto",
srcs = ["echo_messages.proto"],
has_services = False,
)
grpc_proto_library(
@ -145,7 +144,6 @@ grpc_proto_library(
grpc_proto_library(
name = "simple_messages_proto",
srcs = ["simple_messages.proto"],
has_services = False,
)
grpc_proto_library(

@ -78,6 +78,7 @@ grpc_cc_test(
"//test/core/util:grpc_test_util",
"//test/cpp/util:test_util",
],
tags = ["no_test_ios"],
)
grpc_cc_test(
@ -89,7 +90,7 @@ grpc_cc_test(
external_deps = [
"gtest",
],
tags = ["no_windows"],
tags = ["no_windows", "no_test_ios"],
deps = [
":test_service_impl",
"//:gpr",
@ -121,6 +122,7 @@ grpc_cc_test(
"//test/core/util:grpc_test_util",
"//test/cpp/util:test_util",
],
tags = ["no_test_ios"],
)
grpc_cc_binary(
@ -163,6 +165,7 @@ grpc_cc_test(
"//test/core/util:grpc_test_util",
"//test/cpp/util:test_util",
],
tags = ["no_test_ios"],
)
grpc_cc_test(
@ -437,6 +440,7 @@ grpc_cc_test(
"//test/core/util:test_lb_policies",
"//test/cpp/util:test_util",
],
tags = ["no_test_ios"],
)
grpc_cc_test(
@ -477,6 +481,7 @@ grpc_cc_test(
"//test/core/util:grpc_test_util",
"//test/cpp/util:test_util",
],
tags = ["no_test_ios"],
)
grpc_cc_test(
@ -499,6 +504,7 @@ grpc_cc_test(
"//test/core/util:grpc_test_util",
"//test/cpp/util:test_util",
],
tags = ["no_test_ios"],
)
grpc_cc_test(
@ -561,6 +567,7 @@ grpc_cc_test(
"//test/core/util:grpc_test_util",
"//test/cpp/util:test_util",
],
tags = ["no_test_ios"],
)
grpc_cc_binary(
@ -614,6 +621,7 @@ grpc_cc_test(
"//src/proto/grpc/testing:echo_proto",
"//test/cpp/util:test_util",
],
tags = ["no_test_ios"],
)
grpc_cc_test(
@ -622,7 +630,7 @@ grpc_cc_test(
external_deps = [
"gtest",
],
tags = ["manual"],
tags = ["manual", "no_test_ios"],
deps = [
":test_service_impl",
"//:gpr",
@ -689,6 +697,7 @@ grpc_cc_test(
"//test/core/util:grpc_test_util",
"//test/cpp/util:test_util",
],
tags = ["no_test_ios"],
)
grpc_cc_test(
@ -697,7 +706,7 @@ grpc_cc_test(
external_deps = [
"gtest",
],
tags = ["manual"], # test requires root, won't work with bazel RBE
tags = ["manual", "no_test_ios"], # test requires root, won't work with bazel RBE
deps = [
":test_service_impl",
"//:gpr",
@ -746,4 +755,5 @@ grpc_cc_test(
"//test/core/util:grpc_test_util",
"//test/cpp/util:test_util",
],
tags = ["no_test_ios"],
)

@ -122,9 +122,14 @@ class FilterEnd2endTest : public ::testing::Test {
FilterEnd2endTest() : server_host_("localhost") {}
static void SetUpTestCase() {
gpr_log(GPR_ERROR, "In SetUpTestCase");
grpc::RegisterChannelFilter<ChannelDataImpl, CallDataImpl>(
"test-filter", GRPC_SERVER_CHANNEL, INT_MAX, nullptr);
// Workaround for
// https://github.com/google/google-toolbox-for-mac/issues/242
static bool setup_done = false;
if (!setup_done) {
setup_done = true;
grpc::RegisterChannelFilter<ChannelDataImpl, CallDataImpl>(
"test-filter", GRPC_SERVER_CHANNEL, INT_MAX, nullptr);
}
}
void SetUp() override {

@ -187,7 +187,9 @@ grpc_cc_test(
external_deps = [
"gtest",
],
tags = ["nomsan"], # death tests seem to be incompatible with msan
tags = ["nomsan", # death tests seem to be incompatible with msan
"no_test_ios"
],
deps = [
":grpc_cli_libs",
":test_util",

@ -0,0 +1,31 @@
# gRPC Bazel BUILD file.
#
# Copyright 2019 gRPC authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
licenses(["notice"])
native.objc_library(
name = "GTM_GoogleTestRunner_GTM_USING_XCTEST",
testonly = 1,
srcs = [
"UnitTesting/GTMGoogleTestRunner.mm",
],
copts = [
"-DGTM_USING_XCTEST",
],
deps = [
"//external:gtest",
],
visibility = ["//visibility:public"]
)

@ -0,0 +1,233 @@
//
// GTMGoogleTestRunner.mm
//
// Copyright 2013 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy
// of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
//
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
// This is a SenTest/XCTest based unit test that will run all of the GoogleTest
// https://code.google.com/p/googletest/
// based tests in the project, and will report results correctly via SenTest so
// that Xcode can pick them up in it's UI.
// SenTest dynamically creates one SenTest per GoogleTest.
// GoogleTest is set up using a custom event listener (GoogleTestPrinter)
// which knows how to log GoogleTest test results in a manner that SenTest (and
// the Xcode IDE) understand.
// Note that this does not able you to control individual tests from the Xcode
// UI. You can only turn on/off all of the C++ tests. It does however give
// you output that you can click on in the Xcode UI and immediately jump to a
// test failure.
// This class is not compiled as part of the standard Google Toolbox For Mac
// project because of it's dependency on https://code.google.com/p/googletest/
// To use this:
// - If you are using XCTest (vs SenTest) make sure to define GTM_USING_XCTEST
// in the settings for your testing bundle.
// - Add GTMGoogleTestRunner to your test bundle sources.
// - Add gtest-all.cc from gtest to your test bundle sources.
// - Write some C++ tests and add them to your test bundle sources.
// - Build and run tests. Your C++ tests should just execute.
// If you are using this with XCTest (as opposed to SenTestingKit)
// make sure to define GTM_USING_XCTEST.
#ifndef GTM_USING_XCTEST
#define GTM_USING_XCTEST 0
#endif
#if GTM_USING_XCTEST
#import <XCTest/XCTest.h>
#define SenTestCase XCTestCase
#define SenTestSuite XCTestSuite
#else // GTM_USING_XCTEST
#import <SenTestingKit/SenTestingKit.h>
#endif // GTM_USING_XCTEST
#import <objc/runtime.h>
#include <gtest/gtest.h>
using ::testing::EmptyTestEventListener;
using ::testing::TestCase;
using ::testing::TestEventListeners;
using ::testing::TestInfo;
using ::testing::TestPartResult;
using ::testing::TestResult;
using ::testing::UnitTest;
namespace {
// A gtest printer that takes care of reporting gtest results via the
// SenTest interface. Note that a test suite in SenTest == a test case in gtest
// and a test case in SenTest == a test in gtest.
// This will handle fatal and non-fatal gtests properly.
class GoogleTestPrinter : public EmptyTestEventListener {
public:
GoogleTestPrinter(SenTestCase *test_case) : test_case_(test_case) {}
virtual ~GoogleTestPrinter() {}
virtual void OnTestPartResult(const TestPartResult &test_part_result) {
if (!test_part_result.passed()) {
NSString *file = @(test_part_result.file_name());
int line = test_part_result.line_number();
NSString *summary = @(test_part_result.summary());
// gtest likes to give multi-line summaries. These don't look good in
// the Xcode UI, so we clean them up.
NSString *oneLineSummary =
[summary stringByReplacingOccurrencesOfString:@"\n" withString:@" "];
#if GTM_USING_XCTEST
BOOL expected = test_part_result.nonfatally_failed();
[test_case_ recordFailureWithDescription:oneLineSummary
inFile:file
atLine:line
expected:expected];
#else // GTM_USING_XCTEST
NSException *exception =
[NSException failureInFile:file
atLine:line
withDescription:@"%@", oneLineSummary];
// failWithException: will log appropriately.
[test_case_ failWithException:exception];
#endif // GTM_USING_XCTEST
}
}
private:
SenTestCase *test_case_;
};
NSString *SelectorNameFromGTestName(NSString *testName) {
NSRange dot = [testName rangeOfString:@"."];
return [NSString stringWithFormat:@"%@::%@",
[testName substringToIndex:dot.location],
[testName substringFromIndex:dot.location + 1]];
}
} // namespace
// GTMGoogleTestRunner is a GTMTestCase that makes a sub test suite populated
// with all of the GoogleTest unit tests.
@interface GTMGoogleTestRunner : SenTestCase {
NSString *testName_;
}
// The name for a test is the GoogleTest name which is "TestCase.Test"
- (id)initWithName:(NSString *)testName;
@end
@implementation GTMGoogleTestRunner
+ (void)initGoogleTest {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSArray *arguments = [NSProcessInfo processInfo].arguments;
int argc = (int)arguments.count;
char **argv = static_cast<char **>(alloca((sizeof(char *) * (argc + 1))));
for (int index = 0; index < argc; index++) {
argv[index] = const_cast<char *> ([arguments[index] UTF8String]);
}
argv[argc] = NULL;
testing::InitGoogleTest(&argc, argv);
});
}
+ (id)defaultTestSuite {
[GTMGoogleTestRunner initGoogleTest];
SenTestSuite *result =
[[SenTestSuite alloc] initWithName:NSStringFromClass(self)];
UnitTest *test = UnitTest::GetInstance();
// Walk the GoogleTest tests, adding sub tests and sub suites as appropriate.
int total_test_case_count = test->total_test_case_count();
for (int i = 0; i < total_test_case_count; ++i) {
const TestCase *test_case = test->GetTestCase(i);
int total_test_count = test_case->total_test_count();
SenTestSuite *subSuite =
[[SenTestSuite alloc] initWithName:@(test_case->name())];
[result addTest:subSuite];
for (int j = 0; j < total_test_count; ++j) {
const TestInfo *test_info = test_case->GetTestInfo(j);
NSString *testName = [NSString stringWithFormat:@"%s.%s",
test_case->name(), test_info->name()];
SenTestCase *senTest = [[self alloc] initWithName:testName];
[subSuite addTest:senTest];
}
}
return result;
}
- (id)initWithName:(NSString *)testName {
// Xcode 6.1 started taking the testName from the selector instead of calling
// -name.
// So we will add selectors to GTMGoogleTestRunner.
// They should all be unique because the selectors are named cppclass.method
// Filed as radar 18798444.
Class cls = [self class];
NSString *selectorTestName = SelectorNameFromGTestName(testName);
SEL selector = sel_registerName([selectorTestName UTF8String]);
Method method = class_getInstanceMethod(cls, @selector(runGoogleTest));
IMP implementation = method_getImplementation(method);
const char *encoding = method_getTypeEncoding(method);
if (!class_addMethod(cls, selector, implementation, encoding)) {
// If we can't add a method, we should blow up here.
[NSException raise:NSInternalInconsistencyException
format:@"Unable to add %@ to %@.", testName, cls];
}
if ((self = [super initWithSelector:selector])) {
testName_ = testName;
}
return self;
}
- (NSString *)name {
// A SenTest name must be "-[foo bar]" or it won't be parsed properly.
NSRange dot = [testName_ rangeOfString:@"."];
return [NSString stringWithFormat:@"-[%@ %@]",
[testName_ substringToIndex:dot.location],
[testName_ substringFromIndex:dot.location + 1]];
}
- (void)runGoogleTest {
[GTMGoogleTestRunner initGoogleTest];
// Gets hold of the event listener list.
TestEventListeners& listeners = UnitTest::GetInstance()->listeners();
// Adds a listener to the end. Google Test takes the ownership.
listeners.Append(new GoogleTestPrinter(self));
// Remove the default printer.
delete listeners.Release(listeners.default_result_printer());
// Since there is no way of running a single GoogleTest directly, we use the
// filter mechanism in GoogleTest to simulate it for us.
::testing::GTEST_FLAG(filter) = [testName_ UTF8String];
// Intentionally ignore return value of RUN_ALL_TESTS. We will be printing
// the output appropriately, and there is no reason to mark this test as
// "failed" if RUN_ALL_TESTS returns non-zero.
(void)RUN_ALL_TESTS();
}
@end

@ -0,0 +1,19 @@
# Copyright 2019 The gRPC Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Config file for the internal CI (in protobuf text format)
# Location of the continuous shell script in repository.
build_file: "grpc/tools/internal_ci/macos/grpc_run_bazel_cpp_ios_tests.sh"

@ -0,0 +1,39 @@
#!/usr/bin/env bash
# Copyright 2019 The gRPC Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -ex
# change to grpc repo root
cd $(dirname $0)/../../..
# Download bazel
temp_dir="$(mktemp -d)"
wget -q https://github.com/bazelbuild/bazel/releases/download/0.26.0/bazel-0.26.0-darwin-x86_64 -O "${temp_dir}/bazel"
chmod 755 "${temp_dir}/bazel"
export PATH="${temp_dir}:${PATH}"
# This should show ${temp_dir}/bazel
which bazel
./tools/run_tests/start_port_server.py
dirs=(end2end server client common codegen util grpclb test)
for dir in ${dirs[*]}; do
echo $dir
out=`bazel query "kind(ios_unit_test, tests(//test/cpp/$dir/...))"`
for test in $out; do
echo "Running: $test"
bazel test --test_summary=detailed --test_output=all $test
done
done
Loading…
Cancel
Save