From 1ade6c77849aec2c8486a568720258547aa1d670 Mon Sep 17 00:00:00 2001 From: zmiao Date: Tue, 21 Jan 2020 23:23:35 +0200 Subject: [PATCH] [test-runner] Enable IOS RenderTestRunner and run it on Firebase (#16111) * [test-runner] Setup iOS RenderTestRunner * [test-runner] Make app runnable on firebase * Add iOS platform dependent expectation and ignores; Run prepare script via cmake * Make RenderTestRunner report test status --- .gitignore | 5 + circle.yml | 102 ++++++++++- .../expected.png | Bin 0 -> 422 bytes .../expected.png | Bin 0 -> 428 bytes .../line-dasharray/overscaled/expected.png | Bin 0 -> 20714 bytes metrics/ignores/platform-ios.json | 12 ++ .../next-ios-render-test-runner-style.json | 13 ++ next/platform/ios/ios.cmake | 100 +++++++++++ next/render-test/CMakeLists.txt | 7 + render-test/ios/AppDelegate.h | 7 + render-test/ios/AppDelegate.m | 38 +++++ render-test/ios/Gemfile | 3 + render-test/ios/Gemfile.lock | 159 ++++++++++++++++++ render-test/ios/Info.plist | 62 +++++++ render-test/ios/LaunchScreen.storyboard | 27 +++ render-test/ios/Main.storyboard | 27 +++ ...estApp_iphoneos13.2-arm64e-debug.xctestrun | 68 ++++++++ ...tApp_iphoneos13.2-arm64e-release.xctestrun | 68 ++++++++ render-test/ios/ViewController.h | 6 + render-test/ios/ViewController.m | 22 +++ .../RenderTestApp.app.xcent.template | 16 ++ .../RenderTestAppTests.xctest.xcent.template | 16 ++ ...AutomationSupport.framework.xcent.template | 16 ++ .../XCTest.framework.xcent.template | 16 ++ render-test/ios/codesigning/codesign_all.sh | 8 + .../codesigning/generate-entitlements.swift | 40 +++++ ...libXCTestSwiftSupport.dylib.xcent.template | 16 ++ render-test/ios/fastlane/Appfile | 3 + render-test/ios/fastlane/Matchfile | 5 + render-test/ios/fastlane/README.md | 44 +++++ render-test/ios/iosTestRunner.h | 6 + render-test/ios/iosTestRunner.mm | 98 +++++++++++ render-test/ios/ios_test_runner.cpp | 40 +++++ render-test/ios/ios_test_runner.hpp | 14 ++ render-test/ios/main.m | 9 + render-test/ios/setup_test_data.sh | 12 ++ render-test/ios/tests/Info.plist | 22 +++ render-test/ios/tests/Tests.m | 38 +++++ render-test/runner.cpp | 2 +- 39 files changed, 1140 insertions(+), 7 deletions(-) create mode 100644 metrics/expectations/platform-ios/render-tests/combinations/fill-translucent--symbol-translucent/expected.png create mode 100644 metrics/expectations/platform-ios/render-tests/combinations/symbol-translucent--fill-translucent/expected.png create mode 100644 metrics/expectations/platform-ios/render-tests/line-dasharray/overscaled/expected.png create mode 100644 metrics/ignores/platform-ios.json create mode 100644 metrics/next-ios-render-test-runner-style.json create mode 100644 render-test/ios/AppDelegate.h create mode 100644 render-test/ios/AppDelegate.m create mode 100644 render-test/ios/Gemfile create mode 100644 render-test/ios/Gemfile.lock create mode 100644 render-test/ios/Info.plist create mode 100644 render-test/ios/LaunchScreen.storyboard create mode 100644 render-test/ios/Main.storyboard create mode 100644 render-test/ios/RenderTestApp_iphoneos13.2-arm64e-debug.xctestrun create mode 100644 render-test/ios/RenderTestApp_iphoneos13.2-arm64e-release.xctestrun create mode 100644 render-test/ios/ViewController.h create mode 100644 render-test/ios/ViewController.m create mode 100644 render-test/ios/codesigning/RenderTestApp.app.xcent.template create mode 100644 render-test/ios/codesigning/RenderTestAppTests.xctest.xcent.template create mode 100644 render-test/ios/codesigning/XCTAutomationSupport.framework.xcent.template create mode 100644 render-test/ios/codesigning/XCTest.framework.xcent.template create mode 100755 render-test/ios/codesigning/codesign_all.sh create mode 100755 render-test/ios/codesigning/generate-entitlements.swift create mode 100644 render-test/ios/codesigning/libXCTestSwiftSupport.dylib.xcent.template create mode 100644 render-test/ios/fastlane/Appfile create mode 100644 render-test/ios/fastlane/Matchfile create mode 100644 render-test/ios/fastlane/README.md create mode 100644 render-test/ios/iosTestRunner.h create mode 100644 render-test/ios/iosTestRunner.mm create mode 100644 render-test/ios/ios_test_runner.cpp create mode 100644 render-test/ios/ios_test_runner.hpp create mode 100644 render-test/ios/main.m create mode 100755 render-test/ios/setup_test_data.sh create mode 100644 render-test/ios/tests/Info.plist create mode 100644 render-test/ios/tests/Tests.m diff --git a/.gitignore b/.gitignore index a09e87751..b12fb8a38 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,11 @@ render-test/android/package.json render-test/android/app/app.iml render-test/android/android.iml +# IOS RenderTestRunner +render-test/ios/codesigning/*.xcent +render-test/ios/fastlane/README.md +test-data/ + # Generated file from npm/yarn package-lock.json diff --git a/circle.yml b/circle.yml index 7aa497708..fbd27a5ef 100644 --- a/circle.yml +++ b/circle.yml @@ -9,6 +9,7 @@ workflows: - next-android-api-breakage - next-android-render-test-runner - next-android-unit-test-runner + - next-ios-render-test-runner - next-baselines: requires: - next-android-arm64-v8a-release @@ -140,12 +141,7 @@ workflows: - next-macos-xcode11-release config_params: '-G Ninja -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_BUILD_TYPE=Debug' style_tests: true - - next-build-template: - name: next-ios-xcode11-release - executor_name: macos-11_0_0 - target_is_macos: true - config_params: '-G Xcode -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_SYSROOT=iphonesimulator' - test_params: '-Q -N' + - next-ios-xcode11-release mbgl-legacy: jobs: # @@ -241,11 +237,23 @@ commands: command: | Xvfb :99 -noreset -screen 0 1280x1024x24 next-prepare-macos: + parameters: + build-ios-test-runner: + type: boolean + default: false steps: - run: name: Prepare macOS command: | brew install cmake ccache glfw ninja pkgconfig qt + - when: + condition: << parameters.build-ios-test-runner >> + steps: + - run: + name: Prepare macOS with extra environment + command: | + brew cask install google-cloud-sdk + brew install chargepoint/xcparse/xcparse - run: name: Code Generators macOS command: | @@ -671,6 +679,88 @@ commands: fi jobs: + next-ios-xcode11-release: + executor: macos-11_0_0 + steps: + - checkout + - next-prepare + - next-prepare-macos + - run: + name: Build + command: | + cmake next -B Build -G Xcode -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_SYSROOT=iphonesimulator + cd Build && xcodebuild -sdk iphonesimulator -project Mapbox\ GL\ Native.xcodeproj -configuration Release + - next-save + next-ios-render-test-runner: + executor: macos-11_0_0 + steps: + - checkout + - next-prepare + - next-prepare-macos: + build-ios-test-runner: true + - run: + name: Prepare codesign keychain + command: | + cd render-test/ios + fastlane run create_keychain name:fastlane_keychain password:$FASTLANE_PASSWORD timeout:false unlock:true + fastlane match + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $FASTLANE_PASSWORD fastlane_keychain + /usr/bin/env xcrun security find-identity -v -p codesigning + - run: + name: Build IOS RenderTestApp + command: | + cd render-test/ios + cmake ../../next -B Build -G Xcode -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphoneos -DMBGL_IOS_RENDER_TEST=ON + cd Build + xcodebuild -sdk iphoneos -project Mapbox\ GL\ Native.xcodeproj -scheme RenderTestApp build-for-testing -arch arm64 CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO -configuration Release + xcodebuild -sdk iphoneos -project Mapbox\ GL\ Native.xcodeproj build -target RenderTestAppTests -arch arm64 CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO -configuration Release + - run: + name: Codesign RenderTestApp for running on a physical device + command: | + cd render-test/ios + ./codesigning/generate-entitlements.swift + ls codesigning + cd Build + ../codesigning/codesign_all.sh + codesign -dv --verbose=4 Release-iphoneos/RenderTestApp.app # for debugging that app is signed or not + - run: + name: Login to IOS Google Cloud Platform + command: | + if [[ -n "${GCLOUD_SERVICE_ACCOUNT_JSON_IOS}" ]]; then + echo "${GCLOUD_SERVICE_ACCOUNT_JSON_IOS}" > iosAccount.json + gcloud auth activate-service-account --key-file iosAccount.json --project ios-mapbox-gl-native + rm iosAccount.json + fi + - run: + name: Run IOSRenderTestApp Tests on Firebase + no_output_timeout: 20m + command: | + if [[ -n "${GCLOUD_SERVICE_ACCOUNT_JSON_IOS}" ]]; then + # arrange files in a way Firebase expects it, and package them in a zip file + cd render-test/ios/Build + cp ../RenderTestApp_iphoneos13.2-arm64e-release.xctestrun . + zip testapp.zip -r Release-iphoneos/RenderTestApp.app + zip testapp.zip RenderTestApp_iphoneos13.2-arm64e-release.xctestrun + gcloud firebase test ios models list + gcloud firebase test ios run \ + --test testapp.zip \ + --device model=iphonexs,version=12.0,locale=en,orientation=portrait --xcode-version=11.1 --timeout 20m \ + --verbosity=debug --no-record-video --results-dir ios-render-test-app-${CIRCLE_BUILD_NUM} + fi + - run: + name: Retrieve Test Result from gcloud + when: always + command: | + if [[ -n "${GCLOUD_SERVICE_ACCOUNT_JSON_IOS}" ]]; then + testResult=$(gsutil ls -d gs://test-lab-hn7kwqcz3108z-hjm4qtbqqkymk/ios-render-test-app-${CIRCLE_BUILD_NUM}/iphonexs-12.0-en-portrait/TestLogs/) + mkdir -p render-test-app + gsutil -m cp -r $testResult render-test-app + cd render-test-app/TestLogs && ls + xcparse attachments *.xcresult + cd ../.. && mkdir -p /tmp/tests/ios-render-test + if ls render-test-app/TestLogs/*.html 1> /dev/null 2>&1; then cp render-test-app/TestLogs/*.html /tmp/tests/ios-render-test; fi + fi + - next-save next-android-benchmark-runner: executor: ubuntu-disco steps: diff --git a/metrics/expectations/platform-ios/render-tests/combinations/fill-translucent--symbol-translucent/expected.png b/metrics/expectations/platform-ios/render-tests/combinations/fill-translucent--symbol-translucent/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..38016b2511b6fa069f70df405f5e9f318d5c29f5 GIT binary patch literal 422 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEV9fP&aSW-L^LEmH?Uq8B@%1=95 zXqfcNNj@-k(bvNj$M;wy{qm9zT1M@H>(Y7f6VLoV0h*M@f@O*icC=~tRP z`@pBTnU4<(JN#~1)+*`qmM`J|68@Q=16BC-FYXLF_@jXJgC|=80|*)Hz1_+v(SQa0 x%WdE-)Kn;6rPbiZ=f$$}S-w~mWHC=@ogP-K(cvDMQ!5@Z}Q>-la! zox18{_l=sFhkkaR|F|lZX~+L&&X^~UPKc#V*g5Oi%}jl*m-ap3x}pjSk`0U!K%}sy zwDbT24-)DEi65E(A{U82aJa*9GUf=A#Fv^KLcov!2M3U-`M`NQc=@rSyK-*8SYYsU L^>bP0l+XkKU2nJW literal 0 HcmV?d00001 diff --git a/metrics/expectations/platform-ios/render-tests/line-dasharray/overscaled/expected.png b/metrics/expectations/platform-ios/render-tests/line-dasharray/overscaled/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..0c30f8feae76144138e7876dce56ed7229d8e260 GIT binary patch literal 20714 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

nD`xL z3J^Evlg$#4r|R|?7S+QoQ96oBj2kNNh=0qj?L4Umu|D@{>m+NV;kTb#ueOSfKFMkM z%r|?BDoQ&1;cPi*1)2`Fm{VB;AmA3{RY{A7aDso2b-Y?P?k%T=6)Aa?!Qtfzqyy6% zih;l5!d<@@!kNi2K{j}0Q!3M+=snfv+GaZitiZ$-N-X+MfbVT%5gX~aatUL1YVyao zlf173d?b3ZKQ)NrHDGS`Kz>S8^0Z3kQBT&mkeVVrFc#}%(o>s49B_R#?~ep|D~h%W zjY#UGQ-`vayt$>FzboeEti|I6A}OJr4KFQWtXi`KSKuOi^N=82%M94bVoOi`Po*-W-wG@2iG;4VeBIx`Fp2;Myz$GZGq z0LBVA_3q-~VVy8xf|e`2|Ni^+qOfe)vXm2!cpteXPzwHm$LOV(UdmGAkL#HGp=8OD z7Dtvl&p-dX9w(kQ;m9!8;W5NBYg08b*?g#sHvn+qGWk0@FYy7AU`8SwlQM2$5o&)!WGep7tLH-330-lBP<;!dDjx$FgKIzfC5c`Nv6a%hB#yGo4?w>Yo+Gw|5 zyc?3mhFvCo4uWQIWeUl-_xaBGp2?*NGQ^WJO=K)pcs>cEnr9f}KM3lAY%K9QJBf?| zz(UTViA#n!I1?Z{H|LvXf+l`L=?x1tUlML$r?nQpV8_SK*_Fk^!qK*ETkR%3=bUr& zq)ZI`{rBI`avYN~ZsRBmX@n6xD;QyZXE{5shjE1G2$%5Cp+i-V9zArp#Mus*5Cw5Z zXD&m$hX`X9Zu}2;M-Rjrpuu z)Y*_636jZ-^lZ=}=Sc3IyfbIc99<=5J1=15$dR#olYbimZvZgiJ@d>nIxcP|Gcw6Z z=yD#%LQNc=-48B%>hrTxh+`3_h7-3)5VE+V3@&K#de{R`l*w4;g>vG~ojYw0D^gIQ z)4O-?*k_DBLozW~@&Wqy?;jcwE71aQ4?M|Jr%uhYc1b!s^w2|D$`>IJ1^{tVlA`!P zNNS6Rn~sdecz0)`MvbKAIRvl+!u>_Pb1J2Sf_spRh~-adrzv?fh!LDVcngt_#XFf7 znBSzQLuP?DfIJ=(ex^0CUs?h$zWAc{c$3$rlTdxRY7VfoCn+l49nZj=mL>^PFTC(V zLM^rsY>X0MuU@@u4<$#aoL~o+$p#9~A^h$gG0!T`E*>`_D})`- zJ@;I#9!bnUQ@lp(6sd@^oyZntvMc$fg3Zh79m0ZnHUNn0l66dNGdBgbq0S^P;$W26 zRjXD_m&74(^6tCu%FaH6pbV3ksIzP`Xovyue~^?$KMNEsd0ykmWH&JG{rDt56d>L3 zYEd#ccI?=|=QAO4#`|WxGKc#Ec{xTs6|E1{7}LesCkApf0H{lLCJU`!zg{O0#P948 zFLA2gvC|K#DxKgDEEr*E9wG)nB7Aqw#6V((ELPcxi9`wJEy9o_OQNAbi5?|=CRd5i zz%7vI=A7NTcX#caZh}jiGH5hToO&qqA93~xq4X33l7=~yzVTVnC&jrwU_{`8X4mUy z7JQgs?M2czzR?-p)Kn^XkRQhbkFu!~Z2(~5BA~-f&w-UHRnlMZ7himl;Z)FI0V0ftmV{9^dxAOG&tm~$H9)s5Dt=9$s zRz>_5V!mLfkcGH^gs6d5L|z}NOoRoKcGB^41e0-?LIdo8NH#XfsVqDsC&z>5gZ>Zf ze5sCL(?fz5$Ga_r!{^6&(ESfzPcn&?&MGnf-x+>k@i*TQ=fhZV8g^la`;t<5XWq$?B+N}77B4pK z*)@RE1^})YKD0aUyi;5HA{#2C3a-5{8H(A(#e|$;mqpz&leg(=7X(nMBSeWn7-gp{ zJkBr%@~o!)GyNaPVM=CKPxnavX71d%u8!F#2O|A!3*+WT!W;>88_zCABw=IcwON-) zDCcwMyQ4`K|918i!Jt4JCf-|k

    47%*T!j(;BrAs`_d z@DP(g#)KlB=%S73Z4nRsGYAjh&ZSc#<&}6?oDGT6@fvVnIdhZfgJAl4m=~SvoC^gm zYLcn)+GLA(Hs>5QYE;VO&JGL~M9!SzXMRAbC*M!}XND0F_VPwrtfPEM7{@Ph9b1q; zVYF6E&jAwd&87yA6-M2W5CcGP4Xku-L>i%ljWD^gg9_UA&QKysXG@kW$#NVM7mE+^ zVseV8gJzL+CXEu$U9n0}Yo>R2>~@^>(k$vU_rkqTm}2te$=Xfq)^3*20i7I? z8utRf?>#>ac^M==@yCStE}iYI$qCFVm5h=PkEIKOQjv1lOSK zlbdky;>EUyk|#($k{V}sAjychF*{7ciDbGvJq7gOXx_Yef}yZx&6+I7q5R2$AAekx zDpe8`LA_4=;@-eG!NU9T4;H(+FhQahWlmBC=r1g$BpKtToIiiQ-jOZb;y70nex|a(N6$`&H0;p&R+h!#@_-?6G!JC#{nJ(2X2z|x33^#}YAh-gZrf|`c zd=(}r0TdkoAxa?KTeoiAES{6$!-ps2L}V(20QfuzSE2b3PuR+VNyIhZao4V06Mm2P zhcZ)h47oY+AvrMs1Xmydka9Yw+;h+oAt1pPcr{2YbW;wU=Z_qSqD6~p&lU-}nd0V8 z1!w3F(HzP$aj6F#N-pD@=;Z|7wlQPIs8Xd$#dIy6eDX>CJ23!+jK&xMk^>+d3Bna5 z8@hR)v~Jy6-+Pq0rAl@NH=z>*V1&Fw9*R-BZQQtVLIP`$Aa{wXE6Pce1@S2afsD~9 z27r*!U;ywygh{(V@E@uRP*kJOiCdsUz(SKs8Q`2bbF`bkR;^kIihM%qkf2KGsu3vS zSa8`Lx&_@bIp;g?ypz=N+>2LVeYO4@{+1F;!F!x@0ESqP9z9(32IU5M9PQh; zcfGr+7yv@b&@-ONh+6F^fDuEJE0Sd5GpK9VuB|0zB~dVQL0pA:YES> + ) + + target_include_directories( + RenderTestAppTests + PUBLIC ${MBGL_ROOT}/render-test/ios + ) + + xctest_add_test(XCTest.RenderTestApp RenderTestAppTests) + + set_target_properties(RenderTestAppTests PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${MBGL_ROOT}/render-test/ios/tests/Info.plist) +endif() unset(IOS_DEPLOYMENT_TARGET CACHE) diff --git a/next/render-test/CMakeLists.txt b/next/render-test/CMakeLists.txt index d8622d038..7949c2b3c 100644 --- a/next/render-test/CMakeLists.txt +++ b/next/render-test/CMakeLists.txt @@ -18,6 +18,13 @@ add_library( ${MBGL_ROOT}/render-test/runner.hpp ) +if(CMAKE_SYSTEM_NAME STREQUAL iOS) + set_target_properties(mbgl-render-test PROPERTIES XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET "${IOS_DEPLOYMENT_TARGET}") + set_target_properties(mbgl-render-test PROPERTIES XCODE_ATTRIBUTE_ENABLE_BITCODE "YES") + set_target_properties(mbgl-render-test PROPERTIES XCODE_ATTRIBUTE_BITCODE_GENERATION_MODE bitcode) + set_target_properties(mbgl-render-test PROPERTIES XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH $<$:YES>) +endif() + # FIXME: Should not use core private interface target_include_directories( mbgl-render-test diff --git a/render-test/ios/AppDelegate.h b/render-test/ios/AppDelegate.h new file mode 100644 index 000000000..134c8063d --- /dev/null +++ b/render-test/ios/AppDelegate.h @@ -0,0 +1,7 @@ +#import // UIApplicationDelegate + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + +@end \ No newline at end of file diff --git a/render-test/ios/AppDelegate.m b/render-test/ios/AppDelegate.m new file mode 100644 index 000000000..874d18de2 --- /dev/null +++ b/render-test/ios/AppDelegate.m @@ -0,0 +1,38 @@ +#import "AppDelegate.h" + +@interface AppDelegate() + +@end + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Insert code here to initialize your application + NSLog(@"didFinishLaunchingWithOptions"); + return YES; +} + +- (void)applicationWillResignActive:(UIApplication *)application { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. +} + +- (void)applicationDidEnterBackground:(UIApplication *)application { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. +} + +- (void)applicationWillEnterForeground:(UIApplication *)application { + // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. +} + +- (void)applicationDidBecomeActive:(UIApplication *)application { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. +} + +- (void)applicationWillTerminate:(UIApplication *)application { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. +} + + +@end diff --git a/render-test/ios/Gemfile b/render-test/ios/Gemfile new file mode 100644 index 000000000..adc90d98c --- /dev/null +++ b/render-test/ios/Gemfile @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gem "fastlane" \ No newline at end of file diff --git a/render-test/ios/Gemfile.lock b/render-test/ios/Gemfile.lock new file mode 100644 index 000000000..23018150b --- /dev/null +++ b/render-test/ios/Gemfile.lock @@ -0,0 +1,159 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.2) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + atomos (0.1.3) + babosa (1.0.3) + claide (1.0.3) + colored (1.2) + colored2 (3.1.2) + commander-fastlane (4.4.6) + highline (~> 1.7.2) + declarative (0.0.10) + declarative-option (0.1.0) + digest-crc (0.4.1) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) + dotenv (2.7.5) + emoji_regex (1.0.1) + excon (0.71.1) + faraday (0.17.3) + multipart-post (>= 1.2, < 3) + faraday-cookie_jar (0.0.6) + faraday (>= 0.7.4) + http-cookie (~> 1.0.0) + faraday_middleware (0.13.1) + faraday (>= 0.7.4, < 1.0) + fastimage (2.1.7) + fastlane (2.139.0) + CFPropertyList (>= 2.3, < 4.0.0) + addressable (>= 2.3, < 3.0.0) + babosa (>= 1.0.2, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored + commander-fastlane (>= 4.4.6, < 5.0.0) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 2.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 0.17) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 0.13.1) + fastimage (>= 2.1.0, < 3.0.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-api-client (>= 0.29.2, < 0.37.0) + google-cloud-storage (>= 1.15.0, < 2.0.0) + highline (>= 1.7.2, < 2.0.0) + json (< 3.0.0) + jwt (~> 2.1.0) + mini_magick (>= 4.9.4, < 5.0.0) + multi_xml (~> 0.5) + multipart-post (~> 2.0.0) + plist (>= 3.1.0, < 4.0.0) + public_suffix (~> 2.0.0) + rubyzip (>= 1.3.0, < 2.0.0) + security (= 0.1.3) + simctl (~> 1.6.3) + slack-notifier (>= 2.0.0, < 3.0.0) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (>= 1.4.5, < 2.0.0) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.3.0) + xcpretty-travis-formatter (>= 0.0.3) + gh_inspector (1.1.3) + google-api-client (0.36.4) + addressable (~> 2.5, >= 2.5.1) + googleauth (~> 0.9) + httpclient (>= 2.8.1, < 3.0) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.0) + signet (~> 0.12) + google-cloud-core (1.4.1) + google-cloud-env (~> 1.0) + google-cloud-env (1.3.0) + faraday (~> 0.11) + google-cloud-storage (1.25.0) + addressable (~> 2.5) + digest-crc (~> 0.4) + google-api-client (~> 0.33) + google-cloud-core (~> 1.2) + googleauth (~> 0.9) + mini_mime (~> 1.0) + googleauth (0.10.0) + faraday (~> 0.12) + jwt (>= 1.4, < 3.0) + memoist (~> 0.16) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (~> 0.12) + highline (1.7.10) + http-cookie (1.0.3) + domain_name (~> 0.5) + httpclient (2.8.3) + json (2.3.0) + jwt (2.1.0) + memoist (0.16.2) + mini_magick (4.10.1) + mini_mime (1.0.2) + multi_json (1.14.1) + multi_xml (0.6.0) + multipart-post (2.0.0) + nanaimo (0.2.6) + naturally (2.2.0) + os (1.0.1) + plist (3.5.0) + public_suffix (2.0.5) + representable (3.0.4) + declarative (< 0.1.0) + declarative-option (< 0.2.0) + uber (< 0.2.0) + retriable (3.1.2) + rouge (2.0.7) + rubyzip (1.3.0) + security (0.1.3) + signet (0.12.0) + addressable (~> 2.3) + faraday (~> 0.9) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) + simctl (1.6.7) + CFPropertyList + naturally + slack-notifier (2.3.2) + terminal-notifier (2.0.0) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + tty-cursor (0.7.0) + tty-screen (0.7.0) + tty-spinner (0.9.2) + tty-cursor (~> 0.7) + uber (0.1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.6) + unicode-display_width (1.6.0) + word_wrap (1.0.0) + xcodeproj (1.14.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.2.6) + xcpretty (0.3.0) + rouge (~> 2.0.7) + xcpretty-travis-formatter (1.0.0) + xcpretty (~> 0.2, >= 0.0.7) + +PLATFORMS + ruby + +DEPENDENCIES + fastlane + +BUNDLED WITH + 2.1.4 diff --git a/render-test/ios/Info.plist b/render-test/ios/Info.plist new file mode 100644 index 000000000..de3af9d72 --- /dev/null +++ b/render-test/ios/Info.plist @@ -0,0 +1,62 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + RenderTestApp + CFBundleIdentifier + com.mapbox.RenderTestApp + CFBundleGetInfoString + com.mapbox.RenderTestApp + CFBundleInfoDictionaryVersion + 1.0 + CFBundleLongVersionString + 1.0 + CFBundleName + com.mapbox.RenderTestApp + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + MBGL + CFBundleVersion + 7877 + CSResourcesFileMapped + + NSHumanReadableCopyright + © 2014–2020 Mapbox + LSMinimumSystemVersion + ${MACOSX_DEPLOYMENT_TARGET} + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIBackgroundModes + + fetch + remote-notification + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + + \ No newline at end of file diff --git a/render-test/ios/LaunchScreen.storyboard b/render-test/ios/LaunchScreen.storyboard new file mode 100644 index 000000000..c9b756433 --- /dev/null +++ b/render-test/ios/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/render-test/ios/Main.storyboard b/render-test/ios/Main.storyboard new file mode 100644 index 000000000..34d4c7e2e --- /dev/null +++ b/render-test/ios/Main.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/render-test/ios/RenderTestApp_iphoneos13.2-arm64e-debug.xctestrun b/render-test/ios/RenderTestApp_iphoneos13.2-arm64e-debug.xctestrun new file mode 100644 index 000000000..b92ac8c32 --- /dev/null +++ b/render-test/ios/RenderTestApp_iphoneos13.2-arm64e-debug.xctestrun @@ -0,0 +1,68 @@ + + + + + RenderTestAppTests + + BlueprintName + RenderTestAppTests + BundleIdentifiersForCrashReportEmphasis + + com.mapbox.RenderTestApp + com.mapbox.RenderTestAppTests + + CommandLineArguments + + DependentProductPaths + + __TESTROOT__/Debug-iphoneos/RenderTestApp.app + __TESTROOT__/Debug-iphoneos/RenderTestApp.app/PlugIns/RenderTestAppTests.xctest + + EnvironmentVariables + + OS_ACTIVITY_DT_MODE + YES + SQLITE_ENABLE_THREAD_ASSERTIONS + 1 + + IsAppHostedTestBundle + + ProductModuleName + RenderTestAppTests + RunOrder + 0 + SystemAttachmentLifetime + deleteOnSuccess + TestBundlePath + __TESTHOST__/PlugIns/RenderTestAppTests.xctest + TestHostBundleIdentifier + com.mapbox.RenderTestApp + TestHostPath + __TESTROOT__/Debug-iphoneos/RenderTestApp.app + TestLanguage + + TestRegion + + TestingEnvironmentVariables + + DYLD_FALLBACK_FRAMEWORK_PATH + + DYLD_INSERT_LIBRARIES + __PLATFORMS__/iPhoneOS.platform/Developer/usr/lib/libXCTestBundleInject.dylib + XCInjectBundleInto + unused + + ToolchainsSettingValue + + UITargetAppCommandLineArguments + + UserAttachmentLifetime + deleteOnSuccess + + __xctestrun_metadata__ + + FormatVersion + 1 + + + diff --git a/render-test/ios/RenderTestApp_iphoneos13.2-arm64e-release.xctestrun b/render-test/ios/RenderTestApp_iphoneos13.2-arm64e-release.xctestrun new file mode 100644 index 000000000..20ce81e9c --- /dev/null +++ b/render-test/ios/RenderTestApp_iphoneos13.2-arm64e-release.xctestrun @@ -0,0 +1,68 @@ + + + + + RenderTestAppTests + + BlueprintName + RenderTestAppTests + BundleIdentifiersForCrashReportEmphasis + + com.mapbox.RenderTestApp + com.mapbox.RenderTestAppTests + + CommandLineArguments + + DependentProductPaths + + __TESTROOT__/Release-iphoneos/RenderTestApp.app + __TESTROOT__/Release-iphoneos/RenderTestApp.app/PlugIns/RenderTestAppTests.xctest + + EnvironmentVariables + + OS_ACTIVITY_DT_MODE + YES + SQLITE_ENABLE_THREAD_ASSERTIONS + 1 + + IsAppHostedTestBundle + + ProductModuleName + RenderTestAppTests + RunOrder + 0 + SystemAttachmentLifetime + deleteOnSuccess + TestBundlePath + __TESTHOST__/PlugIns/RenderTestAppTests.xctest + TestHostBundleIdentifier + com.mapbox.RenderTestApp + TestHostPath + __TESTROOT__/Release-iphoneos/RenderTestApp.app + TestLanguage + + TestRegion + + TestingEnvironmentVariables + + DYLD_FALLBACK_FRAMEWORK_PATH + + DYLD_INSERT_LIBRARIES + __PLATFORMS__/iPhoneOS.platform/Developer/usr/lib/libXCTestBundleInject.dylib + XCInjectBundleInto + unused + + ToolchainsSettingValue + + UITargetAppCommandLineArguments + + UserAttachmentLifetime + deleteOnSuccess + + __xctestrun_metadata__ + + FormatVersion + 1 + + + diff --git a/render-test/ios/ViewController.h b/render-test/ios/ViewController.h new file mode 100644 index 000000000..9c7dfc57e --- /dev/null +++ b/render-test/ios/ViewController.h @@ -0,0 +1,6 @@ +#import + +@interface ViewController : UIViewController + +@end + diff --git a/render-test/ios/ViewController.m b/render-test/ios/ViewController.m new file mode 100644 index 000000000..e9c526122 --- /dev/null +++ b/render-test/ios/ViewController.m @@ -0,0 +1,22 @@ +#import "ViewController.h" +#import "iosTestRunner.h" + +@interface ViewController () +{ + IosTestRunner* i; +} +@end + +@implementation ViewController + +- (void)viewDidLoad { + [super viewDidLoad]; +// In order to run test runner with app itself instead of with unit test, comment out the following line. +// i = [[IosTestRunner alloc]init]; +} + +- (void)didReceiveMemoryWarning { + [super didReceiveMemoryWarning]; +} + +@end diff --git a/render-test/ios/codesigning/RenderTestApp.app.xcent.template b/render-test/ios/codesigning/RenderTestApp.app.xcent.template new file mode 100644 index 000000000..933c054b3 --- /dev/null +++ b/render-test/ios/codesigning/RenderTestApp.app.xcent.template @@ -0,0 +1,16 @@ + + + + + application-identifier + $TEAM_ID.com.mapbox.RenderTestApp + com.apple.developer.team-identifier + $TEAM_ID + get-task-allow + + keychain-access-groups + + $TEAM_ID.com.mapbox.RenderTestApp + + + \ No newline at end of file diff --git a/render-test/ios/codesigning/RenderTestAppTests.xctest.xcent.template b/render-test/ios/codesigning/RenderTestAppTests.xctest.xcent.template new file mode 100644 index 000000000..c2138e492 --- /dev/null +++ b/render-test/ios/codesigning/RenderTestAppTests.xctest.xcent.template @@ -0,0 +1,16 @@ + + + + + application-identifier + $TEAM_ID.com.mapbox.RenderTestAppTests + com.apple.developer.team-identifier + $TEAM_ID + get-task-allow + + keychain-access-groups + + $TEAM_ID.com.mapbox.RenderTestAppTests + + + \ No newline at end of file diff --git a/render-test/ios/codesigning/XCTAutomationSupport.framework.xcent.template b/render-test/ios/codesigning/XCTAutomationSupport.framework.xcent.template new file mode 100644 index 000000000..c2138e492 --- /dev/null +++ b/render-test/ios/codesigning/XCTAutomationSupport.framework.xcent.template @@ -0,0 +1,16 @@ + + + + + application-identifier + $TEAM_ID.com.mapbox.RenderTestAppTests + com.apple.developer.team-identifier + $TEAM_ID + get-task-allow + + keychain-access-groups + + $TEAM_ID.com.mapbox.RenderTestAppTests + + + \ No newline at end of file diff --git a/render-test/ios/codesigning/XCTest.framework.xcent.template b/render-test/ios/codesigning/XCTest.framework.xcent.template new file mode 100644 index 000000000..c2138e492 --- /dev/null +++ b/render-test/ios/codesigning/XCTest.framework.xcent.template @@ -0,0 +1,16 @@ + + + + + application-identifier + $TEAM_ID.com.mapbox.RenderTestAppTests + com.apple.developer.team-identifier + $TEAM_ID + get-task-allow + + keychain-access-groups + + $TEAM_ID.com.mapbox.RenderTestAppTests + + + \ No newline at end of file diff --git a/render-test/ios/codesigning/codesign_all.sh b/render-test/ios/codesigning/codesign_all.sh new file mode 100755 index 000000000..37aad618e --- /dev/null +++ b/render-test/ios/codesigning/codesign_all.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +codesign --force --sign $CODESIGNIDENTITY --entitlements ../codesigning/RenderTestApp.app.xcent --timestamp=none ./Release-iphoneos/RenderTestApp.app +codesign --force --sign $CODESIGNIDENTITY --deep --preserve-metadata=identifier,entitlements,flags --timestamp=none ./Release-iphoneos/RenderTestApp.app/Frameworks/libXCTestSwiftSupport.dylib +codesign --force --sign $CODESIGNIDENTITY --deep --preserve-metadata=identifier,entitlements,flags --timestamp=none ./Release-iphoneos/RenderTestApp.app/Frameworks/libXCTestBundleInject.dylib +codesign --force --sign $CODESIGNIDENTITY --deep --preserve-metadata=identifier,entitlements,flags --timestamp=none ./Release-iphoneos/RenderTestApp.app/Frameworks/XCTAutomationSupport.framework +codesign --force --sign $CODESIGNIDENTITY --deep --preserve-metadata=identifier,entitlements,flags --timestamp=none ./Release-iphoneos/RenderTestApp.app/Frameworks/XCTest.framework +codesign --force --sign $CODESIGNIDENTITY --deep --entitlements ../codesigning/RenderTestAppTests.xctest.xcent --timestamp=none ./Release-iphoneos/RenderTestApp.app/PlugIns/RenderTestAppTests.xctest diff --git a/render-test/ios/codesigning/generate-entitlements.swift b/render-test/ios/codesigning/generate-entitlements.swift new file mode 100755 index 000000000..83834483b --- /dev/null +++ b/render-test/ios/codesigning/generate-entitlements.swift @@ -0,0 +1,40 @@ +#!/usr/bin/swift + +/// Generates Entitlement files .xcent for codesigning, containing the correct team id +import Swift +import Foundation + +let fileManager = FileManager.default + +let contentsOfCurrentWorkingDirectory = try FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: "./codesigning/"), includingPropertiesForKeys: nil, options: []) + +guard let rawTeamIdEnv = getenv("IOS_DEVELOPMENT_TEAM") else { + print("Please add a IOS_DEVELOPMENT_TEAM environment variable.") + exit(1) +} + +let teamId2 = String(utf8String: rawTeamIdEnv) + +for file in contentsOfCurrentWorkingDirectory { + if (file.pathExtension == "template") { + var content = "" + do { + content = try String(contentsOf: file, encoding: .utf8) + } + catch { + print("Error reading xcent file: \(error).") + } + + content = content.replacingOccurrences(of: "$TEAM_ID", with: teamId2!) + + var targetFile = file + targetFile.deletePathExtension() + do { + try content.write(to: targetFile, atomically: false, encoding: .utf8) + print("Entitlement " + targetFile.absoluteString + " generated.") + } + catch { + print("Error writing xcent file: \(error).") + } + } +} \ No newline at end of file diff --git a/render-test/ios/codesigning/libXCTestSwiftSupport.dylib.xcent.template b/render-test/ios/codesigning/libXCTestSwiftSupport.dylib.xcent.template new file mode 100644 index 000000000..c2138e492 --- /dev/null +++ b/render-test/ios/codesigning/libXCTestSwiftSupport.dylib.xcent.template @@ -0,0 +1,16 @@ + + + + + application-identifier + $TEAM_ID.com.mapbox.RenderTestAppTests + com.apple.developer.team-identifier + $TEAM_ID + get-task-allow + + keychain-access-groups + + $TEAM_ID.com.mapbox.RenderTestAppTests + + + \ No newline at end of file diff --git a/render-test/ios/fastlane/Appfile b/render-test/ios/fastlane/Appfile new file mode 100644 index 000000000..b9fbc62d2 --- /dev/null +++ b/render-test/ios/fastlane/Appfile @@ -0,0 +1,3 @@ +app_identifier("com.mapbox.RenderTestApp") # The bundle identifier of your app +apple_id ENV['MAPBOX_APPLE_ID'] +team_id ENV['MAPBOX_APPLE_TEAM_ID'] # Developer Portal Team ID \ No newline at end of file diff --git a/render-test/ios/fastlane/Matchfile b/render-test/ios/fastlane/Matchfile new file mode 100644 index 000000000..6c2f977cc --- /dev/null +++ b/render-test/ios/fastlane/Matchfile @@ -0,0 +1,5 @@ +git_url("git@github.com:mapbox/apple-certificates.git") +type("development") # The default type, can be: appstore, adhoc, enterprise or development +app_identifier(["com.mapbox.RenderTestApp", "com.mapbox.RenderTestAppTests"]) +username ENV['MAPBOX_APPLE_ID'] +keychain_name("fastlane_keychain") \ No newline at end of file diff --git a/render-test/ios/fastlane/README.md b/render-test/ios/fastlane/README.md new file mode 100644 index 000000000..29cbacab7 --- /dev/null +++ b/render-test/ios/fastlane/README.md @@ -0,0 +1,44 @@ +fastlane documentation +================ +# Installation + +Make sure you have the latest version of the Xcode command line tools installed: + +``` +xcode-select --install +``` + +Install _fastlane_ using +``` +[sudo] gem install fastlane -NV +``` +or alternatively using `brew cask install fastlane` + +# Available Actions +## iOS +### ios ios_build +``` +fastlane ios ios_build +``` +Description of what the lane does +### ios gym_build +``` +fastlane ios gym_build +``` +Description of what the lane does +### ios xcode_build +``` +fastlane ios xcode_build +``` +Description of what the lane does +### ios ci_build +``` +fastlane ios ci_build +``` +Description of what the lane does + +---- + +This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. +More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). +The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). diff --git a/render-test/ios/iosTestRunner.h b/render-test/ios/iosTestRunner.h new file mode 100644 index 000000000..c83a3db4c --- /dev/null +++ b/render-test/ios/iosTestRunner.h @@ -0,0 +1,6 @@ +#import +__attribute__((visibility ("default"))) +@interface IosTestRunner : NSObject +- (NSString*) getResultPath; +- (BOOL) getTestStatus; +@end diff --git a/render-test/ios/iosTestRunner.mm b/render-test/ios/iosTestRunner.mm new file mode 100644 index 000000000..580c62c94 --- /dev/null +++ b/render-test/ios/iosTestRunner.mm @@ -0,0 +1,98 @@ +#import "iosTestRunner.h" + +#include "ios_test_runner.hpp" + +#include + +@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 = false; + self.runner = new TestRunner(); + NSString *path = nil; + 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:@"test-data"]) { + NSString *destinationPath = [documentsDir stringByAppendingPathComponent: dirName]; + //Using NSFileManager we can perform many file system operations. + BOOL success = [fileManager fileExistsAtPath: destinationPath]; + if (success) { + success = [fileManager removeItemAtPath:destinationPath error:NULL]; + } + break; + } + } + + for (uint32_t i = 0; i < bundleContents.count; i++) { + NSString *dirName = [bundleContents objectAtIndex: i]; + if ([dirName isEqualToString:@"test-data"]) { + NSString *destinationPath = [documentsDir stringByAppendingPathComponent: dirName]; + BOOL success = [fileManager fileExistsAtPath: destinationPath]; + if (!success) { + NSString *copyDirPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent: dirName]; + + success = [fileManager copyItemAtPath: copyDirPath toPath: destinationPath error: &error]; + + if (!success){ + NSAssert1(0, @"Failed to copy file '%@'.", [error localizedDescription]); + NSLog(@"Failed to copy %@ file, error %@", dirName, [error localizedDescription]); + } + else { + path = destinationPath; + NSLog(@"File copied %@ OK", dirName); + } + } + else { + NSLog(@"File exits %@, skip copy", dirName); + } + break; + } + } + if (path) { + NSString *manifestPath = [path stringByAppendingPathComponent:@"/next-ios-render-test-runner-style.json"]; + std::string manifest = std::string([manifestPath UTF8String]); + + self.testStatus = self.runner->startTest(manifest); + self.resultPath = [path stringByAppendingPathComponent:@"/next-ios-render-test-runner-style.html"]; + + BOOL fileFound = [fileManager fileExistsAtPath: self.resultPath]; + if (!fileFound) { + NSLog(@"File doese not exit %@", self.resultPath); + } + self.testStatus &= fileFound; + } + + delete self.runner; + self.runner = nullptr; + } + return self; +} + +- (NSString*) getResultPath { + return self.resultPath; +} + +- (BOOL) getTestStatus { + return self.testStatus; +} +@end diff --git a/render-test/ios/ios_test_runner.cpp b/render-test/ios/ios_test_runner.cpp new file mode 100644 index 000000000..842318c64 --- /dev/null +++ b/render-test/ios/ios_test_runner.cpp @@ -0,0 +1,40 @@ +#include "ios_test_runner.hpp" + +#include + +#include + +#include + +#define EXPORT __attribute__((visibility("default"))) + +EXPORT +bool TestRunner::startTest(const std::string& manifest) { + auto runTestWithManifest = [](const std::string& manifest) -> bool { + std::vector arguments = {"mbgl-render-test-runner", "-p", manifest}; + std::vector argv; + for (const auto& arg : arguments) { + argv.push_back(const_cast(arg.data())); + } + argv.push_back(nullptr); + + int finishedTestCount = 0; + std::function testStatus = [&]() { + mbgl::Log::Info(mbgl::Event::General, "Current finished tests number is '%d' ", ++finishedTestCount); + }; + mbgl::Log::Info(mbgl::Event::General, "Start running RenderTestRunner with manifest: '%s'", manifest.c_str()); + + auto result = mbgl::runRenderTests(static_cast(argv.size() - 1), argv.data(), testStatus) == 0; + mbgl::Log::Info(mbgl::Event::General, "End running RenderTestRunner with manifest: '%s'", manifest.c_str()); + return result; + }; + + auto ret = false; + try { + ret = runTestWithManifest(manifest); + } catch (...) { + mbgl::Log::Info(mbgl::Event::General, "testFailed"); + } + mbgl::Log::Info(mbgl::Event::General, "All tests are finished!"); + return ret; +} diff --git a/render-test/ios/ios_test_runner.hpp b/render-test/ios/ios_test_runner.hpp new file mode 100644 index 000000000..4ec1da93b --- /dev/null +++ b/render-test/ios/ios_test_runner.hpp @@ -0,0 +1,14 @@ +#ifndef ios_test_runner_hpp +#define ios_test_runner_hpp + +#include + +class TestRunner { +public: + TestRunner() = default; + ~TestRunner() = default; + + bool startTest(const std::string& manifest); +}; + +#endif /* ios_test_runner_hpp */ diff --git a/render-test/ios/main.m b/render-test/ios/main.m new file mode 100644 index 000000000..f813c8fea --- /dev/null +++ b/render-test/ios/main.m @@ -0,0 +1,9 @@ +#import +#import "AppDelegate.h" + +int main(int argc, char * argv[]) +{ + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/render-test/ios/setup_test_data.sh b/render-test/ios/setup_test_data.sh new file mode 100755 index 000000000..9a1dc8382 --- /dev/null +++ b/render-test/ios/setup_test_data.sh @@ -0,0 +1,12 @@ +#!/bin/bash +rm -rf ../../test-data +mkdir ../../test-data +mkdir ../../test-data/integration + +cp -r ../../mapbox-gl-js/test/integration/render-tests ../../test-data/integration/ +cp -r ../../mapbox-gl-js/test/integration/query-tests ../../test-data/integration/ +cp -r ../../metrics/expectations ../../test-data/ +cp -r ../../metrics/ignores ../../test-data/ +cp -r ../../metrics/next-ios-render-test-runner-style.json ../../test-data/ +cp -r ../../metrics/cache-metrics.db ../../test-data/ +cp -r ../../metrics/cache-style.db ../../test-data/ \ No newline at end of file diff --git a/render-test/ios/tests/Info.plist b/render-test/ios/tests/Info.plist new file mode 100644 index 000000000..aa599858d --- /dev/null +++ b/render-test/ios/tests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + RenderTestAppTests + CFBundleIdentifier + com.mapbox.RenderTestAppTests + CFBundleInfoDictionaryVersion + 1.0 + CFBundleName + RenderTestAppTests + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/render-test/ios/tests/Tests.m b/render-test/ios/tests/Tests.m new file mode 100644 index 000000000..5dd52a7fa --- /dev/null +++ b/render-test/ios/tests/Tests.m @@ -0,0 +1,38 @@ +#import +#import "iosTestRunner.h" +@interface Tests : XCTestCase + +@end + +@implementation Tests + +- (void)setUp { + [super setUp]; +} + +- (void)tearDown { + [super tearDown]; +} + +- (void)testStartRenderTestRunner { + IosTestRunner* runner = [[IosTestRunner alloc] init]; + XCTAssert(runner, @"IOSTestRunner is not initialized correctly"); + + NSString* result = [runner getResultPath]; + XCTAssert(result, @"IOSTestRunner does not produce a result file"); + + NSFileManager *fileManager = [NSFileManager defaultManager]; + BOOL fileFound = [fileManager fileExistsAtPath: result]; + XCTAssert(fileFound, @"Test result html '%@' doese not exit", result); + + NSURL *url = [NSURL fileURLWithPath:result]; + XCTAttachment *attachmentURL = [XCTAttachment attachmentWithContentsOfFileAtURL: url]; + XCTAssert(attachmentURL, @"Failed to attach test result '%@'", result); + attachmentURL.lifetime = XCTAttachmentLifetimeKeepAlways; + [self addAttachment:attachmentURL]; + + BOOL success = [runner getTestStatus]; + XCTAssert(success, @"IOSTestRunner reports error because some of the tests are not passed, please check the test report"); +} + +@end diff --git a/render-test/runner.cpp b/render-test/runner.cpp index cbfb0c34b..b0fdcc386 100644 --- a/render-test/runner.cpp +++ b/render-test/runner.cpp @@ -666,7 +666,7 @@ uint32_t getImageTileOffset(const std::set& dims, uint32_t dim) { assert(false); return 0; } - return std::distance(dims.begin(), it) * kTileSizeUint; + return static_cast(std::distance(dims.begin(), it)) * kTileSizeUint; } } // namespace