Skip to content

Commit 420364e

Browse files
Google tests framework for UnitTests (#1277)
- Fetch and use Google Tests package - Use it in UnitTests. Now the JS tests are 1 of the Google Tests - noop renderer for Android when no window/context provided (case of unittests) - Added 2 new jobs for Android that run the UnitTests in the simulator. Commented for now - Redirect stdout to logcat for Android ~~- added life cycle test~~ Notes for Android: Android Emulator freezes on the CI when running the test. It's not related to the stdout/logcat pipeline (I've tested it). And it does not repro locally. Most of this change is the Android project. --------- Co-authored-by: Gary Hsu <[email protected]>
1 parent 4b8b9e6 commit 420364e

33 files changed

+826
-30
lines changed

.github/jobs/android_tests.yml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
parameters:
2+
name: ''
3+
vmImage: ''
4+
JSEngine: ''
5+
macOSCodename: ''
6+
7+
jobs:
8+
- job: ${{ parameters.name }}
9+
timeoutInMinutes: 45
10+
pool:
11+
vmImage: ${{ parameters.vmImage }}
12+
13+
steps:
14+
- script: |
15+
git submodule update --init --recursive
16+
displayName: 'Checkout dependencies'
17+
- template: cmake.yml
18+
parameters:
19+
vmImage: ${{ parameters.vmImage }}
20+
21+
- script: |
22+
echo Install Android image
23+
echo 'y' | $ANDROID_HOME/tools/bin/sdkmanager --install 'system-images;android-27;default;x86_64'
24+
echo 'y' | $ANDROID_HOME/tools/bin/sdkmanager --licenses
25+
echo Create AVD
26+
$ANDROID_HOME/tools/bin/avdmanager create avd -n Pixel_API_27 -d pixel -k 'system-images;android-27;default;x86_64'
27+
displayName: 'Install Android Emulator'
28+
- script: |
29+
echo Start emulator
30+
nohup $ANDROID_HOME/emulator/emulator -avd Pixel_API_27 -gpu host -no-window 2>&1 &
31+
echo Wait for emulator
32+
$ANDROID_HOME/platform-tools/adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do echo '.'; sleep 1; done'
33+
$ANDROID_HOME/platform-tools/adb devices
34+
displayName: 'Start Android Emulator'
35+
36+
- task: Gradle@3
37+
inputs:
38+
gradleWrapperFile: 'Apps/UnitTests/Android/gradlew'
39+
workingDirectory: 'Apps/UnitTests/Android'
40+
options: '-PabiFilters=x86_64 -PjsEngine=${{parameters.jsEngine}}'
41+
tasks: 'connectedAndroidTest'
42+
jdkVersionOption: 1.11
43+
displayName: 'Run Connected Android Test'
44+
45+
- script: |
46+
export results=$(find ./app/build/outputs/androidTest-results -name "*.txt")
47+
echo cat "$results"
48+
cat "$results"
49+
workingDirectory: 'Apps/UnitTests/Android'
50+
condition: succeededOrFailed()
51+
displayName: 'Dump logcat from Test Results'
52+
53+
- task: PublishBuildArtifacts@1
54+
inputs:
55+
pathToPublish: 'Apps/UnitTests/Android/app/build/outputs/androidTest-results/connected'
56+
artifactName: 'AndroidTestResults_${{parameters.jsEngine}}'
57+
condition: succeededOrFailed()
58+
displayName: 'Publish Test Results'

Apps/UnitTests/Android/.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
*.iml
2+
.gradle/
3+
.idea/
4+
local.properties
5+
6+
app/.cxx/
7+
app/build/
8+
app/src/main/assets
9+
10+
build/
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
plugins {
2+
id 'com.android.application'
3+
}
4+
5+
def jsEngine = "V8"
6+
if (project.hasProperty("jsEngine")) {
7+
jsEngine = project.property("jsEngine")
8+
}
9+
10+
configurations { natives }
11+
12+
android {
13+
namespace 'com.babylonnative.unittests'
14+
compileSdk 33
15+
ndkVersion = "21.4.7075529"
16+
17+
defaultConfig {
18+
applicationId "com.babylonnative.unittests"
19+
minSdk 21
20+
targetSdk 33
21+
versionCode 1
22+
versionName "1.0"
23+
24+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
25+
26+
externalNativeBuild {
27+
cmake {
28+
arguments (
29+
"-DANDROID_STL=c++_shared",
30+
"-DNAPI_JAVASCRIPT_ENGINE=${jsEngine}",
31+
"-DJSRUNTIMEHOST_CORE_APPRUNTIME_V8_INSPECTOR=ON",
32+
)
33+
}
34+
}
35+
36+
if (project.hasProperty("abiFilters")) {
37+
ndk {
38+
abiFilters project.getProperty("abiFilters")
39+
}
40+
}
41+
}
42+
43+
buildTypes {
44+
release {
45+
minifyEnabled false
46+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
47+
}
48+
}
49+
compileOptions {
50+
sourceCompatibility JavaVersion.VERSION_1_8
51+
targetCompatibility JavaVersion.VERSION_1_8
52+
}
53+
externalNativeBuild {
54+
cmake {
55+
path file('src/main/cpp/CMakeLists.txt')
56+
buildStagingDirectory '../../../../Build/Android'
57+
version '3.22.1+'
58+
}
59+
}
60+
buildFeatures {
61+
viewBinding true
62+
}
63+
}
64+
65+
dependencies {
66+
implementation 'com.google.ar:core:1.16.0'
67+
natives 'com.google.ar:core:1.16.0'
68+
implementation 'androidx.appcompat:appcompat:1.6.0'
69+
implementation 'com.google.android.material:material:1.7.0'
70+
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
71+
implementation group: 'org.java-websocket', name: 'Java-WebSocket', version: '1.5.3'
72+
testImplementation 'junit:junit:4.13.2'
73+
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
74+
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
75+
}
76+
77+
task copyScripts {
78+
doLast {
79+
// run copy at execution phase because npm command is not done at configuration phase
80+
copy
81+
{
82+
from "../../../node_modules/chai"
83+
include "chai.js"
84+
into 'src/main/assets/Scripts'
85+
}
86+
copy
87+
{
88+
from "../../../node_modules/mocha"
89+
include "mocha.js"
90+
into 'src/main/assets/Scripts'
91+
}
92+
copy
93+
{
94+
from "../../../node_modules/babylonjs"
95+
include "babylon.max.js"
96+
into 'src/main/assets/Scripts'
97+
}
98+
copy
99+
{
100+
from "../../../node_modules/babylonjs-materials"
101+
include "babylonjs.materials.js"
102+
into 'src/main/assets/Scripts'
103+
}
104+
}
105+
}
106+
107+
// Run copyScripts task after CMake external build
108+
// And make sure merging assets into output is performed after the scripts copy
109+
tasks.whenTaskAdded { task ->
110+
if (task.name == 'mergeDebugNativeLibs') {
111+
task.finalizedBy(copyScripts)
112+
}
113+
if (task.name == 'mergeDebugAssets') {
114+
task.dependsOn(copyScripts)
115+
}
116+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.babylonnative.unittests;
2+
3+
import android.content.Context;
4+
5+
import androidx.test.platform.app.InstrumentationRegistry;
6+
import androidx.test.ext.junit.runners.AndroidJUnit4;
7+
8+
import org.junit.Test;
9+
import org.junit.runner.RunWith;
10+
11+
import static org.junit.Assert.*;
12+
13+
/**
14+
* Instrumented test, which will execute on an Android device.
15+
*
16+
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
17+
*/
18+
@RunWith(AndroidJUnit4.class)
19+
public class Main {
20+
@Test
21+
public void javaScriptTests() {
22+
// Context of the app under test.
23+
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24+
assertEquals("com.babylonnative.unittests", appContext.getPackageName());
25+
26+
assertEquals(0, Native.javaScriptTests(appContext));
27+
}
28+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.babylonnative.unittests;
2+
3+
import android.content.Context;
4+
5+
public class Native {
6+
// JNI interface
7+
static {
8+
System.loadLibrary("UnitTestsJNI");
9+
}
10+
11+
public static native int javaScriptTests(Context context);
12+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
<application android:label="@string/app_name" />
5+
6+
<uses-permission android:name="android.permission.INTERNET" />
7+
8+
</manifest>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
cmake_minimum_required(VERSION 3.18)
2+
3+
set(CMAKE_CXX_STANDARD 17)
4+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
5+
6+
project(UnitTestsJNI)
7+
8+
get_filename_component(UNIT_TESTS_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../../.." ABSOLUTE)
9+
get_filename_component(REPO_ROOT_DIR "${UNIT_TESTS_DIR}/../.." ABSOLUTE)
10+
11+
set(JSRUNTIMEHOST_TESTS OFF) # Turn off the tests folder for Android Studio path
12+
add_subdirectory(${REPO_ROOT_DIR} "${CMAKE_CURRENT_BINARY_DIR}/BabylonNative")
13+
14+
get_filename_component(APPS_DIR "${UNIT_TESTS_DIR}/.." ABSOLUTE)
15+
npm(install "${APPS_DIR}" "Apps" "--silent")
16+
17+
add_library(UnitTestsJNI SHARED
18+
JNI.cpp
19+
${UNIT_TESTS_DIR}/Shared/Tests.h)
20+
target_compile_definitions(UnitTestsJNI PRIVATE JSRUNTIMEHOST_PLATFORM="${JSRUNTIMEHOST_PLATFORM}")
21+
22+
target_include_directories(UnitTestsJNI
23+
PRIVATE ${UNIT_TESTS_DIR})
24+
25+
target_link_libraries(UnitTestsJNI
26+
PRIVATE GLESv3
27+
PRIVATE android
28+
PRIVATE EGL
29+
PRIVATE log
30+
PRIVATE -lz
31+
PRIVATE AndroidExtensions
32+
PRIVATE AppRuntime
33+
PRIVATE Canvas
34+
PRIVATE Console
35+
PRIVATE GraphicsDevice
36+
PRIVATE NativeCamera
37+
PRIVATE NativeEngine
38+
PRIVATE NativeInput
39+
PRIVATE NativeOptimizations
40+
PRIVATE ScriptLoader
41+
PRIVATE XMLHttpRequest
42+
PRIVATE gtest_main
43+
PRIVATE Window)
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#include "gtest/gtest.h"
2+
#include <jni.h>
3+
#include <Android/log.h>
4+
#include <AndroidExtensions/Globals.h>
5+
#include <AndroidExtensions/JavaWrappers.h>
6+
#include <atomic>
7+
#include <Shared/Tests.h>
8+
9+
namespace
10+
{
11+
int pfd[2];
12+
int fd_saved[2];
13+
}
14+
void* thread_func(void*)
15+
{
16+
ssize_t rdsz;
17+
char buf[128];
18+
while ((rdsz = read(pfd[0], buf, sizeof buf - 1)) > 0)
19+
{
20+
if (buf[rdsz - 1] == '\n') --rdsz;
21+
buf[rdsz] = 0;
22+
__android_log_write(ANDROID_LOG_DEBUG, "UnitTests", buf);
23+
}
24+
__android_log_write(ANDROID_LOG_DEBUG, "UnitTests", "Logger shutdown");
25+
return 0;
26+
}
27+
28+
void start_logger()
29+
{
30+
pthread_t thr;
31+
32+
// make stdout line-buffered and stderr unbuffered
33+
fd_saved[0] = setvbuf(stdout, 0, _IOLBF, 0);
34+
fd_saved[1] = setvbuf(stderr, 0, _IONBF, 0);
35+
36+
// create the pipe and redirect stdout and stderr
37+
pipe(pfd);
38+
dup2(pfd[1], 1);
39+
dup2(pfd[1], 2);
40+
41+
// spawn the logging thread
42+
if (pthread_create(&thr, 0, thread_func, 0) == -1)
43+
{
44+
return;
45+
}
46+
pthread_detach(thr);
47+
}
48+
49+
void stop_logger()
50+
{
51+
close(pfd[1]);
52+
close(pfd[0]);
53+
setvbuf(stdout, NULL, fd_saved[0], 0);
54+
setvbuf(stderr, NULL, fd_saved[1], 0);
55+
}
56+
57+
extern "C" JNIEXPORT jint JNICALL
58+
Java_com_babylonnative_unittests_Native_javaScriptTests(JNIEnv* env, jclass clazz, jobject context) {
59+
JavaVM* javaVM{};
60+
if (env->GetJavaVM(&javaVM) != JNI_OK)
61+
{
62+
throw std::runtime_error{"Failed to get Java VM"};
63+
}
64+
65+
start_logger();
66+
67+
android::global::Initialize(javaVM, context);
68+
auto testResult = Run();
69+
70+
stop_logger();
71+
return testResult;
72+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<resources>
2+
<string name="app_name">BabylonNative UnitTests</string>
3+
</resources>

Apps/UnitTests/Android/build.gradle

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Top-level build file where you can add configuration options common to all sub-projects/modules.
2+
plugins {
3+
id 'com.android.application' version '7.3.1' apply false
4+
id 'com.android.library' version '7.3.1' apply false
5+
}

0 commit comments

Comments
 (0)