diff --git a/bazel/grpc_build_system.bzl b/bazel/grpc_build_system.bzl index 7111b5b8c34..390624f3785 100644 --- a/bazel/grpc_build_system.bzl +++ b/bazel/grpc_build_system.bzl @@ -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 = [] diff --git a/src/proto/grpc/testing/BUILD b/src/proto/grpc/testing/BUILD index 15be8227de1..8bea8ed7ca0 100644 --- a/src/proto/grpc/testing/BUILD +++ b/src/proto/grpc/testing/BUILD @@ -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( diff --git a/test/cpp/end2end/BUILD b/test/cpp/end2end/BUILD index f3cc14cb848..5567170d371 100644 --- a/test/cpp/end2end/BUILD +++ b/test/cpp/end2end/BUILD @@ -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"], ) diff --git a/test/cpp/end2end/filter_end2end_test.cc b/test/cpp/end2end/filter_end2end_test.cc index e96825c33eb..a224d6596a3 100644 --- a/test/cpp/end2end/filter_end2end_test.cc +++ b/test/cpp/end2end/filter_end2end_test.cc @@ -122,9 +122,14 @@ class FilterEnd2endTest : public ::testing::Test { FilterEnd2endTest() : server_host_("localhost") {} static void SetUpTestCase() { - gpr_log(GPR_ERROR, "In SetUpTestCase"); - grpc::RegisterChannelFilter( - "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( + "test-filter", GRPC_SERVER_CHANNEL, INT_MAX, nullptr); + } } void SetUp() override { diff --git a/test/cpp/util/BUILD b/test/cpp/util/BUILD index d112611ef35..858b88eb01b 100644 --- a/test/cpp/util/BUILD +++ b/test/cpp/util/BUILD @@ -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", diff --git a/third_party/objective_c/google_toolbox_for_mac/BUILD b/third_party/objective_c/google_toolbox_for_mac/BUILD new file mode 100644 index 00000000000..dc505f30fc3 --- /dev/null +++ b/third_party/objective_c/google_toolbox_for_mac/BUILD @@ -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"] +) diff --git a/third_party/objective_c/google_toolbox_for_mac/UnitTesting/GTMGoogleTestRunner.mm b/third_party/objective_c/google_toolbox_for_mac/UnitTesting/GTMGoogleTestRunner.mm new file mode 100644 index 00000000000..3e8e9245fd4 --- /dev/null +++ b/third_party/objective_c/google_toolbox_for_mac/UnitTesting/GTMGoogleTestRunner.mm @@ -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 +#define SenTestCase XCTestCase +#define SenTestSuite XCTestSuite +#else // GTM_USING_XCTEST +#import +#endif // GTM_USING_XCTEST + +#import + +#include + +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(alloca((sizeof(char *) * (argc + 1)))); + for (int index = 0; index < argc; index++) { + argv[index] = const_cast ([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 diff --git a/tools/internal_ci/macos/grpc_bazel_cpp_ios_tests.cfg b/tools/internal_ci/macos/grpc_bazel_cpp_ios_tests.cfg new file mode 100644 index 00000000000..ccc27e8fbe5 --- /dev/null +++ b/tools/internal_ci/macos/grpc_bazel_cpp_ios_tests.cfg @@ -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" + diff --git a/tools/internal_ci/macos/grpc_run_bazel_cpp_ios_tests.sh b/tools/internal_ci/macos/grpc_run_bazel_cpp_ios_tests.sh new file mode 100644 index 00000000000..bc2a8295521 --- /dev/null +++ b/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