iOS C++ Unit Tests Bazel target & running on CI (#1534)

This commit is contained in:
Bart Louwers
2023-08-22 21:52:20 +02:00
committed by GitHub
parent e16995ec33
commit 32d65850c9
21 changed files with 308 additions and 155 deletions

View File

@@ -97,6 +97,7 @@ cc_library(
"//platform/darwin:__pkg__",
"//platform/ios:__pkg__",
"//platform/linux:__pkg__",
"//test:__pkg__",
],
deps = [
"//:mbgl-core",

View File

@@ -7,7 +7,7 @@ load(
"xcodeproj",
)
load("//platform/ios/bazel:macros.bzl", "info_plist")
load("//platform/ios/bazel:config.bzl", "BUNDLE_ID_PREFIX")
load("//platform/ios/bazel:config.bzl", "BUILD_MODE", "BUNDLE_ID_PREFIX")
load(
"//platform/ios/bazel:files.bzl",
"MLN_IOS_PRIVATE_HEADERS",
@@ -213,7 +213,7 @@ genrule(
xcodeproj(
name = "xcodeproj",
bazel_env = {"PATH": "/bin:/usr/bin:/usr/local/bin:/opt/homebrew/bin"},
build_mode = "xcode",
build_mode = BUILD_MODE,
default_xcode_configuration = "Debug",
minimum_xcode_version = "14",
project_name = "MapLibre",
@@ -247,6 +247,13 @@ xcodeproj(
"device",
],
),
top_level_target(
"//platform/ios/test/core:CppUnitTests",
target_environments = [
"simulator",
"device",
],
),
],
xcode_configurations = {
"Debug": {"//command_line_option:compilation_mode": "dbg"},

View File

@@ -95,12 +95,6 @@ cp bazel/example_config.bzl bazel/config.bzl
You need to set your `BUNDLE_ID_PREFIX` to be unique (ideally use a domain that you own in reverse domain name notation).
```
APPLE_MOBILE_PROVISIONING_PROFILE_TEAM_ID = "FFNX6834U8"
APPLE_MOBILE_PROVISIONING_PROFILE_NAME = "iOS Team Provisioning Profile: *"
BUNDLE_ID_PREFIX = "com.firstnamelastname"
```
You can keep leave the `APPLE_MOBILE_PROVISIONING_PROFILE_NAME` alone.
Set the Team ID to the Team ID of your Apple Developer Account (paid or unpaid both work). If you do not know your Team ID, enter the following command in the terminal:
@@ -130,6 +124,8 @@ Confirm that no errors are shown:
Try to run the example App in the simulator and on a device to confirm your setup works.
If no provisioning profile was found, you could try changing the `BUILD_MODE` in `config.bzl` to `"xcode"`. Try the steps in this section again afterwards.
#### Using Bazel from the Command Line
It is also possible to build and run the test application in a simulator from the command line without opening Xcode.

View File

@@ -1,3 +1,8 @@
APPLE_MOBILE_PROVISIONING_PROFILE_TEAM_ID = "TC45MCF93C"
APPLE_MOBILE_PROVISIONING_PROFILE_NAME = "iOS Team Provisioning Profile: *"
BUNDLE_ID_PREFIX = "com.firstnamelastname"
# Change the BUILD_MODE to "xcode" below if you experience problems with
# not finding a provisioning profile when building.
# https://github.com/MobileNativeFoundation/rules_xcodeproj/blob/main/docs/bazel.md#xcodeproj-build_mode
BUILD_MODE = "bazel"

View File

@@ -10,12 +10,13 @@ cc_library(
".",
],
visibility = [
"//platform/ios/test/core:__pkg__",
"//render-test/ios:__pkg__",
],
)
objc_library(
name = "render_test_app_srcs",
name = "test_app_srcs",
srcs = [
"AppDelegate.h",
"AppDelegate.m",
@@ -25,6 +26,9 @@ objc_library(
"LaunchScreen.storyboard",
"Main.storyboard",
],
visibility = [
"//platform/ios/test/core:__pkg__",
],
deps = [
"ios_test_runner_lib",
],
@@ -43,7 +47,7 @@ ios_application(
"//visibility:public",
],
deps = [
"render_test_app_srcs",
"test_app_srcs",
"//render-test/ios:render_test_ios_src",
],
)

View File

@@ -1,84 +0,0 @@
#import "iosTestRunner.h"
#include <ios_test_runner.hpp>
#include <string>
@interface IosTestRunner ()
@property (nullable) TestRunner *runner;
@property (copy, nullable) NSString *resultPath;
@property BOOL testStatus;
@end
@implementation IosTestRunner
-(instancetype)init
{
self = [super init];
if (self) {
self.testStatus = NO;
self.runner = new TestRunner();
NSError *error;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *bundleRoot = [[NSBundle mainBundle] bundlePath];
NSArray *bundleContents = [fileManager contentsOfDirectoryAtPath: bundleRoot error: &error];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex: 0];
for (uint32_t i = 0; i < bundleContents.count; i++) {
NSString *dirName = [bundleContents objectAtIndex: i];
if ([dirName isEqualToString: dataDir]) {
NSString *destinationPath = [documentsDir stringByAppendingPathComponent: dirName];
BOOL success = [fileManager fileExistsAtPath: destinationPath];
if (success) {
[fileManager removeItemAtPath:destinationPath error:NULL];
}
success = [fileManager fileExistsAtPath: destinationPath];
if (!success) {
NSString *copyDirPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent: dirName];
success = [fileManager copyItemAtPath: copyDirPath toPath: destinationPath error: &error];
if (!success) {
NSLog(@"Failed to copy file '%@'", dirName);
NSAssert1(0, @"Failed to copy file, error '%@'", [error localizedDescription]);
} else {
NSLog(@"File '%@' copied OK", dirName);
}
} else {
NSLog(@"Failed to remove file '%@'", dirName);
NSAssert1(0, @"Failed to remove file, error '%@'", [error localizedDescription]);
}
break;
}
}
NSString *baseDir = [documentsDir stringByAppendingPathComponent: dataDir];
std::string basePath = std::string([baseDir UTF8String]);
self.testStatus = self.runner->startTest(basePath) ? YES : NO;
self.resultPath = [baseDir stringByAppendingPathComponent: resultFilePath];
BOOL fileFound = [fileManager fileExistsAtPath: self.resultPath];
if (fileFound == NO) {
NSLog(@"Test result file '%@' does not exist", self.resultPath);
self.testStatus = NO;
}
delete self.runner;
self.runner = nullptr;
}
return self;
}
- (NSString*) getResultPath {
return self.resultPath;
}
- (BOOL) getTestStatus {
return self.testStatus;
}
@end

View File

@@ -0,0 +1,68 @@
load("@build_bazel_rules_apple//apple:ios.bzl", "ios_application", "ios_unit_test")
load("//platform/ios/bazel:config.bzl", "BUNDLE_ID_PREFIX")
load("//bazel:flags.bzl", "CPP_FLAGS", "MAPLIBRE_FLAGS")
load("@build_bazel_rules_apple//apple:resources.bzl", "apple_resource_bundle")
cc_library(
name = "test_cpp_srcs",
srcs = [
"ios_test_runner.cpp",
],
copts = CPP_FLAGS + MAPLIBRE_FLAGS,
deps = [
"//platform:ios-sdk",
"//platform/ios/test/common:ios_test_runner_lib",
"//test:testlib",
"//test:tests",
],
)
apple_resource_bundle(
name = "TestData",
infoplists = [
"//render-test/ios:Empty.plist",
],
structured_resources = [
"//test:fixtures",
"//:scripts/style-spec-reference/v8.json",
],
)
objc_library(
name = "test_objc_srcs",
srcs = [
"Tests.m",
"iosTestRunner.h",
"iosTestRunner.mm",
],
deps = [
"TestData",
"test_cpp_srcs",
"//platform/ios/test/common:ios_test_runner_lib",
],
)
ios_application(
name = "CppUnitTestsApp",
bundle_id = "{}.maplibre.UnitTestsApp".format(BUNDLE_ID_PREFIX),
families = ["iphone"],
infoplists = [
"Info.plist",
],
minimum_os_version = "12.0",
provisioning_profile = "//platform/ios:xcode_profile",
deps = [
"//platform/ios/test/common:test_app_srcs",
],
)
ios_unit_test(
name = "CppUnitTests",
minimum_os_version = "12.0",
provisioning_profile = "//platform/ios:xcode_profile",
test_host = "CppUnitTestsApp",
visibility = ["@rules_xcodeproj//xcodeproj:generated"],
deps = [
"test_objc_srcs",
],
)

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundleExecutable</key>
<string>UnitTestsApp</string>
<key>CFBundleGetInfoString</key>
<string>com.mapbox.UnitTestsApp</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>1.0</string>
<key>CFBundleLongVersionString</key>
<string>1.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>MBGL</string>
<key>CFBundleVersion</key>
<string>7877</string>
<key>CSResourcesFileMapped</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>© 2014–2020 Mapbox</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,11 @@
#pragma once
#import <Foundation/Foundation.h>
__attribute__((visibility ("default")))
@interface IosTestRunner : NSObject
- (NSString*) getResultPath;
- (BOOL) getTestStatus;
@end

View File

@@ -0,0 +1,92 @@
#import "iosTestRunner.h"
#include <ios_test_runner.hpp>
#include <string>
@interface IosTestRunner ()
@property (nullable) TestRunner *runner;
@property (copy, nullable) NSString *resultPath;
@property BOOL testStatus;
@end
@implementation IosTestRunner
-(instancetype)init
{
self = [super init];
if (self) {
self.testStatus = NO;
self.runner = new TestRunner();
NSError *error;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *xcTestBundleRoot = [[NSBundle bundleForClass:[self class]] resourcePath];
NSString *testDataDir = [xcTestBundleRoot stringByAppendingString:@"/TestData.bundle"];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex: 0];
NSString *destinationPath = [documentsDir stringByAppendingPathComponent: @"test"];
[self copyFileAtPath:testDataDir toPath:destinationPath];
[self copyFileAtPath:[testDataDir stringByAppendingPathComponent: @"scripts"] toPath:[documentsDir stringByAppendingPathComponent: @"scripts"]];
NSLog(@"Starting test");
std::string basePath = std::string([documentsDir UTF8String]);
self.testStatus = self.runner->startTest(basePath) ? YES : NO;
self.resultPath = [destinationPath stringByAppendingPathComponent: @"results.xml"];
BOOL fileFound = [fileManager fileExistsAtPath: self.resultPath];
if (fileFound == NO) {
NSLog(@"Test result file '%@' does not exist", self.resultPath);
self.testStatus = NO;
}
delete self.runner;
self.runner = nullptr;
}
return self;
}
/**
Copies a file from the source path to the destination path.
@param sourcePath The path of the source file to be copied.
@param destinationPath The destination path where the file should be copied.
*/
- (void)copyFileAtPath:(NSString *)sourcePath toPath:(NSString *)destinationPath {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = nil;
BOOL success;
// delete destinationPath if it exists
if ([fileManager fileExistsAtPath:destinationPath]) {
success = [fileManager removeItemAtPath: destinationPath error: &error];
if (!success) {
NSAssert1(0, @"Failed to delete '%@'.", destinationPath);
}
}
success = [fileManager copyItemAtPath:sourcePath toPath:destinationPath error:&error];
if (!success) {
NSAssert1(0, @"Failed to copy file '%@'.", [error localizedDescription]);
NSLog(@"Failed to copy %@ file, error %@", sourcePath, [error localizedDescription]);
} else {
NSLog(@"File copied %@ OK", sourcePath);
}
}
- (NSString*) getResultPath {
return self.resultPath;
}
- (BOOL) getTestStatus {
return self.testStatus;
}
@end

View File

@@ -0,0 +1,31 @@
#include <ios_test_runner.hpp>
#include <mbgl/test.hpp>
#include <mbgl/util/logging.hpp>
#include <unistd.h>
#include <vector>
#define EXPORT __attribute__((visibility("default")))
EXPORT
bool TestRunner::startTest(const std::string& basePath) {
std::vector<std::string> arguments = {"mbgl-test-runner", "--gtest_output=xml:" + basePath + "/test/results.xml"};
std::vector<char*> argv;
for (const auto& arg : arguments) {
argv.push_back(const_cast<char*>(arg.data()));
}
argv.push_back(nullptr);
if (chdir(basePath.c_str())) {
mbgl::Log::Error(mbgl::Event::General, "Failed to change the directory to " + basePath);
return false;
}
mbgl::Log::Info(mbgl::Event::General, "Start TestRunner");
int status = mbgl::runTests(static_cast<uint32_t>(argv.size()), argv.data());
mbgl::Log::Info(mbgl::Event::General, "TestRunner finished with status: '" + std::to_string(status) + "'");
return status == 0;
}