iOS C++ Unit Tests Bazel target & running on CI (#1534)
This commit is contained in:
@@ -97,6 +97,7 @@ cc_library(
|
||||
"//platform/darwin:__pkg__",
|
||||
"//platform/ios:__pkg__",
|
||||
"//platform/linux:__pkg__",
|
||||
"//test:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
"//:mbgl-core",
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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
|
||||
68
platform/ios/test/core/BUILD.bazel
Normal file
68
platform/ios/test/core/BUILD.bazel
Normal 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",
|
||||
],
|
||||
)
|
||||
56
platform/ios/test/core/Info.plist
Normal file
56
platform/ios/test/core/Info.plist
Normal 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>
|
||||
11
platform/ios/test/core/iosTestRunner.h
Normal file
11
platform/ios/test/core/iosTestRunner.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
__attribute__((visibility ("default")))
|
||||
@interface IosTestRunner : NSObject
|
||||
|
||||
- (NSString*) getResultPath;
|
||||
- (BOOL) getTestStatus;
|
||||
|
||||
@end
|
||||
92
platform/ios/test/core/iosTestRunner.mm
Normal file
92
platform/ios/test/core/iosTestRunner.mm
Normal 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
|
||||
31
platform/ios/test/core/ios_test_runner.cpp
Normal file
31
platform/ios/test/core/ios_test_runner.cpp
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user