feat(android): run zeroerr unit_test suite as an Android test app
Adds EVENGINE_ANDROID_TEST_APP build mode: the unit_test sources are compiled into libmain.so (SDL entry) and packaged by the existing Gradle APK along with the test assets, so the full zeroerr suite can run on a device via adb logcat. - test/main.cpp: Android runner with LogcatBuf (cout/cerr -> logcat), chdir to <files>/evengine_test, SDL_Init; keeps SDL_main symbol unmangled. - test/PathBesideSource.h: shared pathBesideThisSource helper that maps __FILE__ to the on-device unpacked tree on Android. - EVTestActivity: unpacks assets/test + assets/examples to internal storage and passes an optional --testcase filter from an intent extra. - test/CMakeLists.txt: builds `main` instead of unit_test on Android, excludes desktop-only devtools tests, links Android system libs. - Makefile: build/android-debug-test, sync/android-test-assets, run/android-test-debug, log/android-test targets. - Gradle wrapper bumped to 8.5 for Java 21 compatibility.
This commit is contained in:
1 parent
d2eafa3899
commit
a6901a0691
23 files changed
+463
-94
No files matched your search
@@ -38,3 +38,6 @@ platform/android/apk/app/src/main/jniLibs/
|
||||
# Filled by `make sync/android-assets` (source: platform/android/game-shell or example/).
|
||||
platform/android/apk/app/src/main/assets/game/*
|
||||
!platform/android/apk/app/src/main/assets/game/.gitkeep
|
||||
# Filled by `make sync/android-test-assets` (zeroerr suite + examples on-device tree).
|
||||
platform/android/apk/app/src/main/assets/test/
|
||||
platform/android/apk/app/src/main/assets/examples/
|
||||
+10
-2
@@ -7,8 +7,12 @@ set(EVENGINE_PATCH_VERSION "0")
|
||||
|
||||
option(BUILD_TESTING "enable testing" ON)
|
||||
option(EVENGINE_BUILD_DEMO "Build optional Demo module (procedural demo assets + default demo.nut)" ON)
|
||||
option(EVENGINE_ANDROID_TEST_APP
|
||||
"Build the unit_test suite as an Android test app (libmain.so entry replaced by the zeroerr runner)"
|
||||
OFF)
|
||||
message("Build UnitTest: " ${BUILD_TESTING})
|
||||
message("Build Demo module: " ${EVENGINE_BUILD_DEMO})
|
||||
message("Build Android test app: " ${EVENGINE_ANDROID_TEST_APP})
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "")
|
||||
set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "The build type (Debug, Release)")
|
||||
endif()
|
||||
@@ -46,8 +50,12 @@ function(main)
|
||||
set(USE_MOLD OFF CACHE BOOL "" FORCE)
|
||||
set(COLORFUL_OUTPUT OFF CACHE STRING "" FORCE)
|
||||
if(ANDROID OR (CMAKE_SYSTEM_NAME STREQUAL "Android"))
|
||||
set(BUILD_TESTING OFF CACHE BOOL "enable testing" FORCE)
|
||||
message(STATUS "Android build: unit tests disabled")
|
||||
if(EVENGINE_ANDROID_TEST_APP)
|
||||
message(STATUS "Android build: unit tests enabled (Android test app)")
|
||||
else()
|
||||
set(BUILD_TESTING OFF CACHE BOOL "enable testing" FORCE)
|
||||
message(STATUS "Android build: unit tests disabled")
|
||||
endif()
|
||||
endif()
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "iOS")
|
||||
set(BUILD_TESTING OFF CACHE BOOL "enable testing" FORCE)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
feat(android): run zeroerr unit_test suite as an Android test app
|
||||
|
||||
Adds EVENGINE_ANDROID_TEST_APP build mode: the unit_test sources are
|
||||
compiled into libmain.so (SDL entry) and packaged by the existing Gradle
|
||||
APK along with the test assets, so the full zeroerr suite can run on a
|
||||
device via adb logcat.
|
||||
|
||||
- test/main.cpp: Android runner with LogcatBuf (cout/cerr -> logcat),
|
||||
chdir to <files>/evengine_test, SDL_Init; keeps SDL_main symbol unmangled.
|
||||
- test/PathBesideSource.h: shared pathBesideThisSource helper that maps
|
||||
__FILE__ to the on-device unpacked tree on Android.
|
||||
- EVTestActivity: unpacks assets/test + assets/examples to internal
|
||||
storage and passes an optional --testcase filter from an intent extra.
|
||||
- test/CMakeLists.txt: builds `main` instead of unit_test on Android,
|
||||
excludes desktop-only devtools tests, links Android system libs.
|
||||
- Makefile: build/android-debug-test, sync/android-test-assets,
|
||||
run/android-test-debug, log/android-test targets.
|
||||
- Gradle wrapper bumped to 8.5 for Java 21 compatibility.
|
||||
@@ -100,11 +100,13 @@ GAME ?=
|
||||
|
||||
.PHONY: all build/win32 build/linux build/macosx build/android build/ios \
|
||||
build/win32-debug build/linux-debug build/macosx-debug build/android-debug build/ios-debug \
|
||||
build/android-debug-test \
|
||||
wsl/linux wsl/linux-debug show-targets \
|
||||
debug release example \
|
||||
run run/win32 run/linux run/macosx \
|
||||
run/win32-debug run/linux-debug run/macosx-debug \
|
||||
sync/android-libs sync/android-assets install/android-debug run/android-debug log/android \
|
||||
sync/android-libs sync/android-assets sync/android-test-assets install/android-debug run/android-debug log/android \
|
||||
run/android-test-debug log/android-test \
|
||||
install/ios-debug run/ios-debug log/ios \
|
||||
sdk/win32 sdk/linux sdk/macosx sdk/android sdk/ios \
|
||||
sdk/win32-debug sdk/linux-debug sdk/macosx-debug sdk/android-debug sdk/ios-debug \
|
||||
@@ -340,9 +342,16 @@ reinstall/third-party/ios-debug:
|
||||
cmake --build build/third-party/ios-debug --target install -j 8
|
||||
|
||||
# Copy native shared libraries into the Gradle jniLibs tree.
|
||||
# In Android test-app mode the runner lives in test/libmain.so (zeroerr suite);
|
||||
# otherwise it is the engine's src/engine/libmain.so.
|
||||
sync/android-libs:
|
||||
mkdir -p $(JNI_LIBS)
|
||||
cp -f $(BUILD_DIR)/src/engine/libmain.so $(JNI_LIBS)/
|
||||
@if [ -f $(BUILD_DIR)/test/libmain.so ]; then \
|
||||
echo "syncing test runner libmain.so"; \
|
||||
cp -f $(BUILD_DIR)/test/libmain.so $(JNI_LIBS)/; \
|
||||
else \
|
||||
cp -f $(BUILD_DIR)/src/engine/libmain.so $(JNI_LIBS)/; \
|
||||
fi
|
||||
@TP_DIR=build/third-party-binary/$$(basename $(BUILD_DIR)); \
|
||||
if [ ! -d "$$TP_DIR/lib" ]; then TP_DIR=build/third-party-binary/android-debug; fi; \
|
||||
if [ ! -d "$$TP_DIR/lib" ]; then TP_DIR=build/third-party-binary/android; fi; \
|
||||
@@ -373,6 +382,16 @@ sync/android-assets:
|
||||
echo "Synced Android game assets ($$src) -> $(ANDROID_ASSETS_GAME)"; \
|
||||
ls -la "$(ANDROID_ASSETS_GAME)"
|
||||
|
||||
# Populate APK assets/test + assets/examples for the Android test app
|
||||
# (EVTestActivity unpacks them to <files>/evengine_test at first launch).
|
||||
sync/android-test-assets:
|
||||
@rm -rf "$(APK_DIR)/app/src/main/assets/test" "$(APK_DIR)/app/src/main/assets/examples" "$(APK_DIR)/app/src/main/assets/game"
|
||||
@mkdir -p "$(APK_DIR)/app/src/main/assets/test" "$(APK_DIR)/app/src/main/assets/examples"
|
||||
cp -R test/. "$(APK_DIR)/app/src/main/assets/test/"
|
||||
cp -R examples/. "$(APK_DIR)/app/src/main/assets/examples/"
|
||||
@echo "Synced Android test assets -> $(APK_DIR)/app/src/main/assets/{test,examples}"
|
||||
ls -la "$(APK_DIR)/app/src/main/assets/test" | head -20
|
||||
|
||||
install/android-debug:
|
||||
$(ANDROID_SDK)/platform-tools/adb install -r \
|
||||
$(APK_DIR)/app/build/outputs/apk/debug/app-debug.apk
|
||||
@@ -383,6 +402,46 @@ run/android-debug: install/android-debug
|
||||
log/android:
|
||||
$(ANDROID_SDK)/platform-tools/adb logcat -s EVEngineActivity:I SDL:V SDL/APP:V vulkan:V libc:F DEBUG:F
|
||||
|
||||
# ---- Android test app (zeroerr suite as libmain.so) ----
|
||||
# make build/android-debug-test # configure + deps + engine/tests + APK
|
||||
# make run/android-test-debug [FILTER=math.*] # install + launch on device
|
||||
# make log/android-test # stream test results from logcat
|
||||
build/android-debug-test: build/android-debug-test/build.ninja
|
||||
cmake --build $@ --target deps -j $(ANDROID_JOBS)
|
||||
cmake --build $@ -j $(ANDROID_JOBS)
|
||||
$(MAKE) sync/android-libs BUILD_DIR=build/android-debug-test
|
||||
$(MAKE) sync/android-test-assets
|
||||
cd $(APK_DIR) && JAVA_HOME="$(JAVA_HOME)" ANDROID_HOME="$(ANDROID_SDK)" ./gradlew assembleDebug
|
||||
|
||||
build/android-debug-test/build.ninja:
|
||||
cmake -G Ninja \
|
||||
-DCMAKE_TOOLCHAIN_FILE=$(ANDROID_NDK)/build/cmake/android.toolchain.cmake \
|
||||
-DANDROID_ABI=$(ANDROID_ABI) \
|
||||
-DANDROID_PLATFORM=$(ANDROID_PLATFORM) \
|
||||
-DANDROID_STL=$(ANDROID_STL) \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DBUILD_PLATFORM=android \
|
||||
-DBUILD_TESTING=ON \
|
||||
-DEVENGINE_ANDROID_TEST_APP=ON \
|
||||
-DEVENGINE_DOWNLOAD_CLASSIC_SCENES=OFF \
|
||||
-DEVENGINE_DOWNLOAD_SPINE_MODELS=OFF \
|
||||
-DEVENGINE_DOWNLOAD_SKINNED_CHARACTER=OFF \
|
||||
$(CMAKE_EXTRA_ARGS) \
|
||||
-B build/android-debug-test -S .
|
||||
|
||||
# Convenience: reuse the game APK's install/run with the test filter extra.
|
||||
FILTER ?=
|
||||
run/android-test-debug: install/android-debug
|
||||
@if [ -n "$(FILTER)" ]; then \
|
||||
$(ANDROID_SDK)/platform-tools/adb shell am start -n com.evengine.example/.EVTestActivity --es evengine.test.filter "$(FILTER)"; \
|
||||
else \
|
||||
$(ANDROID_SDK)/platform-tools/adb shell am start -n com.evengine.example/.EVTestActivity; \
|
||||
fi
|
||||
|
||||
log/android-test:
|
||||
$(ANDROID_SDK)/platform-tools/adb logcat -c
|
||||
$(ANDROID_SDK)/platform-tools/adb logcat -s EVEngineTest:V SDL:V vulkan:V libc:F DEBUG:F
|
||||
|
||||
# Install Debug .app to the first connected physical iOS/iPadOS device.
|
||||
install/ios-debug:
|
||||
@test -n "$$(security find-identity -v -p codesigning 2>/dev/null | sed -n '/Apple Development/p')" \
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
android:hardwareAccelerated="true">
|
||||
|
||||
<activity
|
||||
android:name=".EVEngineActivity"
|
||||
android:name=".EVTestActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:alwaysRetainTaskState="true"
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.evengine.example;
|
||||
|
||||
import android.content.res.AssetManager;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import org.libsdl.app.SDLActivity;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Android host for the unit_test suite.
|
||||
*
|
||||
* Unpacks the APK's {@code assets/test} + {@code assets/examples} into internal
|
||||
* storage ({@code <files>/evengine_test}) and launches the zeroerr runner built
|
||||
* into {@code libmain.so}. SDL_main chdirs to that tree, so the compile-time
|
||||
* {@code EVENGINE_SOURCE_DIR="."} / {@code EVENGINE_TEST_BINARY_DIR="."} paths
|
||||
* resolve on-device. A test filter can be passed as the
|
||||
* {@code evengine.test.filter} intent extra, e.g.
|
||||
* {@code adb shell am start -n com.evengine.example/.EVTestActivity --es evengine.test.filter "math.*"}.
|
||||
*/
|
||||
public class EVTestActivity extends SDLActivity {
|
||||
private static final String TAG = "EVTestActivity";
|
||||
private String testDir;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
testDir = new File(getFilesDir(), "evengine_test").getAbsolutePath();
|
||||
try {
|
||||
// Wipe first so assets removed from the package do not linger across upgrades.
|
||||
deleteRecursive(new File(testDir));
|
||||
copyAssetDir("test", testDir);
|
||||
copyAssetDir("examples", testDir + "/examples");
|
||||
Log.i(TAG, "Test assets ready at " + testDir);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to unpack test assets", e);
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getLibraries() {
|
||||
return new String[] {
|
||||
"SDL2",
|
||||
"c++_shared",
|
||||
"main"
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getArguments() {
|
||||
if (getIntent() != null) {
|
||||
String filter = getIntent().getStringExtra("evengine.test.filter");
|
||||
if (filter != null && !filter.isEmpty()) {
|
||||
return new String[] { "--testcase=" + filter };
|
||||
}
|
||||
}
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
private void copyAssetDir(String assetPath, String destPath) throws IOException {
|
||||
AssetManager am = getAssets();
|
||||
String[] children = am.list(assetPath);
|
||||
File dest = new File(destPath);
|
||||
if (!dest.exists() && !dest.mkdirs()) {
|
||||
throw new IOException("Cannot create " + destPath);
|
||||
}
|
||||
if (children == null || children.length == 0) {
|
||||
// Leaf file
|
||||
copyAssetFile(assetPath, destPath);
|
||||
return;
|
||||
}
|
||||
for (String child : children) {
|
||||
String childAsset = assetPath.isEmpty() ? child : assetPath + "/" + child;
|
||||
String childDest = destPath + "/" + child;
|
||||
String[] grand = am.list(childAsset);
|
||||
if (grand != null && grand.length > 0) {
|
||||
copyAssetDir(childAsset, childDest);
|
||||
} else {
|
||||
copyAssetFile(childAsset, childDest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void copyAssetFile(String assetPath, String destPath) throws IOException {
|
||||
File outFile = new File(destPath);
|
||||
File parent = outFile.getParentFile();
|
||||
if (parent != null && !parent.exists() && !parent.mkdirs()) {
|
||||
throw new IOException("Cannot create parent for " + destPath);
|
||||
}
|
||||
try (InputStream in = getAssets().open(assetPath);
|
||||
OutputStream out = new FileOutputStream(outFile)) {
|
||||
byte[] buf = new byte[16 * 1024];
|
||||
int n;
|
||||
while ((n = in.read(buf)) > 0) {
|
||||
out.write(buf, 0, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void deleteRecursive(File f) {
|
||||
if (f == null || !f.exists()) return;
|
||||
File[] children = f.listFiles();
|
||||
if (children != null) {
|
||||
for (File c : children) deleteRecursive(c);
|
||||
}
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -10,8 +10,12 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/common/config.h.in common/config.h)
|
||||
set(EVENGINE_IOS_DEPLOYMENT_TARGET "13.0" CACHE STRING "Minimum iOS/iPadOS deployment target")
|
||||
|
||||
if(ANDROID OR (CMAKE_SYSTEM_NAME STREQUAL "Android"))
|
||||
# SDLActivity loads libmain.so and calls SDL_main (via SDL_main.h macro on main).
|
||||
add_library(main SHARED main.cpp)
|
||||
if(NOT EVENGINE_ANDROID_TEST_APP)
|
||||
# SDLActivity loads libmain.so and calls SDL_main (via SDL_main.h macro on main).
|
||||
# In Android test-app mode the shared `main` library is built by test/CMakeLists.txt
|
||||
# from the zeroerr unit-test runner instead of the engine main.cpp.
|
||||
add_library(main SHARED main.cpp)
|
||||
endif()
|
||||
set(EVENGINE_NATIVE_TARGET main CACHE INTERNAL "Host target for SDK install")
|
||||
elseif(CMAKE_SYSTEM_NAME STREQUAL "iOS" OR EVENGINE_IOS)
|
||||
# UIKit SDL entry: MACOSX_BUNDLE .app with SDL_main.
|
||||
@@ -199,7 +203,8 @@ if(EVENGINE_PLATFORM_LIB)
|
||||
list(APPEND EVELIBS ${EVENGINE_PLATFORM_LIB})
|
||||
endif()
|
||||
|
||||
if(CMAKE_GENERATOR MATCHES "Xcode")
|
||||
if(NOT EVENGINE_ANDROID_TEST_APP)
|
||||
if(CMAKE_GENERATOR MATCHES "Xcode")
|
||||
# create_module() emits STATIC libraries here, so the linker would drop the
|
||||
# translation units whose only job is to self-register a module or a
|
||||
# subcommand at static-init time. Pull the archives in whole.
|
||||
@@ -208,8 +213,9 @@ if(CMAKE_GENERATOR MATCHES "Xcode")
|
||||
list(APPEND _eve_whole_archive_libs "$<LINK_LIBRARY:WHOLE_ARCHIVE,${_eve_lib}>")
|
||||
endforeach()
|
||||
target_link_libraries(${EVENGINE_NATIVE_TARGET} ${_eve_whole_archive_libs} zeroerr ${ThirdParty} ${EVENGINE_VULKAN_LIB})
|
||||
else()
|
||||
else()
|
||||
target_link_libraries(${EVENGINE_NATIVE_TARGET} ${EVELIBS} zeroerr ${ThirdParty} ${EVENGINE_VULKAN_LIB})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "iOS" OR EVENGINE_IOS)
|
||||
@@ -300,6 +306,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "iOS" OR EVENGINE_IOS)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT EVENGINE_ANDROID_TEST_APP)
|
||||
# Plugins resolve ModuleManager / ModuleRegister symbols from the host process.
|
||||
# Export surface is EVENGINE_API (__declspec(dllexport)) only — do not enable
|
||||
# WINDOWS_EXPORT_ALL_SYMBOLS: it pulls Poco/ImGui template instantiations into
|
||||
@@ -326,3 +333,4 @@ elseif(ANDROID OR (CMAKE_SYSTEM_NAME STREQUAL "Android"))
|
||||
VISIBILITY_INLINES_HIDDEN OFF
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
+68
-10
@@ -103,6 +103,16 @@ if(EVENGINE_BUILD_DEMO)
|
||||
list(APPEND all_test_cpp demo.cpp)
|
||||
endif()
|
||||
|
||||
# Desktop-only DevTools (DAP / MCP / debugger / callgraph / renderflow) are not
|
||||
# compiled into the Android runtime, so their test files cannot link there.
|
||||
# all_test_cpp holds absolute paths from file(GLOB), so filter by basename.
|
||||
if(ANDROID OR (CMAKE_SYSTEM_NAME STREQUAL "Android"))
|
||||
foreach(_eve_devtool_test IN ITEMS callgraph.cpp renderflow.cpp debugger.cpp dap.cpp mcp.cpp)
|
||||
list(FILTER all_test_cpp EXCLUDE REGEX "/${_eve_devtool_test}$")
|
||||
endforeach()
|
||||
message(STATUS "Android test: removed devtools test files, remaining=${all_test_cpp}")
|
||||
endif()
|
||||
|
||||
file(GLOB all_test_scripts
|
||||
filesystem.nut
|
||||
graphic.nut
|
||||
@@ -119,15 +129,36 @@ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/nut_scripts.cpp
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
add_executable(
|
||||
unit_test
|
||||
${all_test_cpp}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/nut_scripts.cpp
|
||||
)
|
||||
# On Android the zeroerr runner is packaged as the app's native entry point
|
||||
# (libmain.so loaded by SDLActivity), replacing the engine main.cpp.
|
||||
if(EVENGINE_ANDROID_TEST_APP)
|
||||
add_library(main SHARED
|
||||
${all_test_cpp}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/nut_scripts.cpp
|
||||
)
|
||||
else()
|
||||
add_executable(
|
||||
unit_test
|
||||
${all_test_cpp}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/nut_scripts.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
if(WIN32)
|
||||
set(SystemLibraries ${SystemLibraries} winmm version imm32 Setupapi ws2_32 iphlpapi shlwapi)
|
||||
elseif(ANDROID OR (CMAKE_SYSTEM_NAME STREQUAL "Android"))
|
||||
find_library(ANDROID_LOG_LIB log)
|
||||
find_library(ANDROID_LIB android)
|
||||
find_library(ANDROID_OPENSLES_LIB OpenSLES)
|
||||
set(SystemLibraries ${SystemLibraries}
|
||||
${ANDROID_LOG_LIB}
|
||||
${ANDROID_LIB}
|
||||
${ANDROID_OPENSLES_LIB}
|
||||
dl
|
||||
m
|
||||
c++_shared
|
||||
)
|
||||
elseif(APPLE)
|
||||
set(SystemLibraries ${SystemLibraries}
|
||||
dl pthread iconv
|
||||
@@ -191,6 +222,11 @@ else()
|
||||
set(ThirdParty ${ThirdParty} simplesquirrel_staticmd sqstdlib_staticmd squirrel_staticmd SDL2
|
||||
PocoDataSQLitemd PocoDatamd PocoFoundationmd PocoNetmd PocoJSONmd PocoXMLmd
|
||||
${SystemLibraries})
|
||||
elseif(ANDROID OR (CMAKE_SYSTEM_NAME STREQUAL "Android"))
|
||||
# Same unprefixed static names as the engine Android build; SDL2 stays shared.
|
||||
set(ThirdParty ${ThirdParty} simplesquirrel_static sqstdlib_static squirrel_static SDL2 SDL2main
|
||||
PocoDataSQLite PocoData PocoNet PocoXML PocoJSON PocoFoundation
|
||||
${SystemLibraries})
|
||||
else()
|
||||
set(ThirdParty ${ThirdParty} simplesquirrel_static sqstdlib_static squirrel_static SDL2
|
||||
PocoDataSQLite PocoData PocoNet PocoXML PocoJSON PocoFoundation
|
||||
@@ -209,7 +245,6 @@ set(EVELIBS
|
||||
EVTouch
|
||||
EVGraphics
|
||||
EVImage
|
||||
EVDevTools
|
||||
EVCommon
|
||||
EVEvent
|
||||
EVTimer
|
||||
@@ -245,6 +280,10 @@ set(EVELIBS
|
||||
EVStylize
|
||||
EVEditor)
|
||||
|
||||
if(NOT (ANDROID OR (CMAKE_SYSTEM_NAME STREQUAL "Android")))
|
||||
list(APPEND EVELIBS EVDevTools)
|
||||
endif()
|
||||
|
||||
if(EVENGINE_BUILD_DEMO)
|
||||
list(APPEND
|
||||
EVELIBS EVDemo)
|
||||
@@ -254,14 +293,30 @@ if(EVENGINE_PLATFORM_LIB)
|
||||
list(APPEND EVELIBS ${EVENGINE_PLATFORM_LIB})
|
||||
endif()
|
||||
|
||||
target_link_libraries(unit_test ${EVELIBS} zeroerr ${ThirdParty} ${EVENGINE_VULKAN_LIB})
|
||||
target_compile_definitions(unit_test PRIVATE
|
||||
EVENGINE_TEST_BINARY_DIR="${CMAKE_CURRENT_BINARY_DIR}"
|
||||
EVENGINE_SOURCE_DIR="${CMAKE_SOURCE_DIR}")
|
||||
if(EVENGINE_ANDROID_TEST_APP)
|
||||
target_link_libraries(main ${EVELIBS} zeroerr ${ThirdParty} ${EVENGINE_VULKAN_LIB})
|
||||
# Source/binary roots are resolved at runtime: the Android test runner chdirs to
|
||||
# the unpacked asset root before running, so "." means "repo root on device".
|
||||
target_compile_definitions(main PRIVATE
|
||||
EVENGINE_TEST_BINARY_DIR="."
|
||||
EVENGINE_SOURCE_DIR="."
|
||||
EVENGINE_ANDROID=1)
|
||||
# Keep SDL_main / module registration symbols visible for the dlopened SDLActivity.
|
||||
set_target_properties(main PROPERTIES
|
||||
CXX_VISIBILITY_PRESET default
|
||||
VISIBILITY_INLINES_HIDDEN OFF)
|
||||
else()
|
||||
target_link_libraries(unit_test ${EVELIBS} zeroerr ${ThirdParty} ${EVENGINE_VULKAN_LIB})
|
||||
target_compile_definitions(unit_test PRIVATE
|
||||
EVENGINE_TEST_BINARY_DIR="${CMAKE_CURRENT_BINARY_DIR}"
|
||||
EVENGINE_SOURCE_DIR="${CMAKE_SOURCE_DIR}")
|
||||
endif()
|
||||
|
||||
# Large classic scenes (Sponza / DamagedHelmet). Cornell Box is committed.
|
||||
# Enable with -DEVENGINE_DOWNLOAD_CLASSIC_SCENES=ON, or build target
|
||||
# `download_classic_scenes` before running ClassicScenes.* tests.
|
||||
# (Skipped on the Android test app: bash/python3 download steps do not run there.)
|
||||
if(NOT EVENGINE_ANDROID_TEST_APP)
|
||||
option(EVENGINE_DOWNLOAD_CLASSIC_SCENES
|
||||
"Download large classic render test scenes (Sponza, DamagedHelmet)" OFF)
|
||||
set(_eve_classic_scenes_script "${CMAKE_SOURCE_DIR}/scripts/download_classic_scenes.sh")
|
||||
@@ -326,6 +381,9 @@ if(EVENGINE_DOWNLOAD_SKINNED_CHARACTER)
|
||||
COMMENT "PRE_BUILD: download skinned character (CesiumMan)"
|
||||
VERBATIM)
|
||||
endif()
|
||||
endif() # NOT EVENGINE_ANDROID_TEST_APP
|
||||
|
||||
if(NOT EVENGINE_ANDROID_TEST_APP)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/ZeroErrDiscoverTests.cmake)
|
||||
zeroerr_discover_tests(unit_test)
|
||||
endif()
|
||||
@@ -46,12 +46,8 @@ using namespace eve::graphics;
|
||||
|
||||
namespace {
|
||||
|
||||
std::string pathBesideThisSource(const char *filename) {
|
||||
std::string here = __FILE__;
|
||||
auto slash = here.find_last_of("/\\");
|
||||
std::string dir = (slash == std::string::npos) ? std::string(".") : here.substr(0, slash);
|
||||
return dir + "/" + filename;
|
||||
}
|
||||
#include "PathBesideSource.h"
|
||||
EVE_DEFINE_PATH_BESIDE_SOURCE()
|
||||
|
||||
std::vector<char> readBinaryFile(const std::string &path) {
|
||||
std::ifstream in(path, std::ios::binary);
|
||||
|
||||
+2
-6
@@ -24,12 +24,8 @@ using namespace eve::graphics;
|
||||
|
||||
namespace {
|
||||
|
||||
std::string pathBesideThisSource(const char *filename) {
|
||||
std::string here = __FILE__;
|
||||
auto slash = here.find_last_of("/\\");
|
||||
std::string dir = (slash == std::string::npos) ? std::string(".") : here.substr(0, slash);
|
||||
return dir + "/" + filename;
|
||||
}
|
||||
#include "PathBesideSource.h"
|
||||
EVE_DEFINE_PATH_BESIDE_SOURCE()
|
||||
|
||||
std::vector<char> readBinaryFile(const std::string &path) {
|
||||
std::ifstream in(path, std::ios::binary);
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
// test/PathBesideSource.h
|
||||
//
|
||||
// Shared helper for resolving test asset paths relative to the test source file.
|
||||
// On desktop the path is dirname(__FILE__); on Android the build-host path baked
|
||||
// into __FILE__ is replaced with the on-device unpacked tree root so that
|
||||
// `pathBesideThisSource("assets/...")` resolves to <internal>/evengine_test/assets/...
|
||||
//
|
||||
// Deliberately uses its own `eve_test_path` namespace (NOT `eve`): this header is
|
||||
// included *inside* the anonymous namespaces of several test files, so introducing
|
||||
// `namespace eve` there would shadow the global ::eve and break every
|
||||
// `eve::window::...` / `eve::image::...` reference in those files.
|
||||
|
||||
#include <string>
|
||||
|
||||
#if defined(EVENGINE_ANDROID)
|
||||
#include <SDL2/SDL.h>
|
||||
#endif
|
||||
|
||||
namespace eve_test_path {
|
||||
|
||||
#if defined(EVENGINE_ANDROID)
|
||||
inline std::string androidTestRoot() {
|
||||
static const std::string root = [] {
|
||||
const char *internal = SDL_AndroidGetInternalStoragePath();
|
||||
return internal ? std::string(internal) + "/evengine_test" : std::string();
|
||||
}();
|
||||
return root;
|
||||
}
|
||||
|
||||
// Map a build-host source path (__FILE__) onto the on-device test layout.
|
||||
// All test sources are flat in test/, and the APK unpacks assets/test/* directly
|
||||
// into the device root (<internal>/evengine_test/*), so the basename is the only
|
||||
// meaningful part of __FILE__ here.
|
||||
inline std::string androidSourcePath(const char *file) {
|
||||
std::string s = file ? file : "";
|
||||
const std::string root = androidTestRoot();
|
||||
if (root.empty())
|
||||
return s;
|
||||
size_t slash = s.find_last_of("/\\");
|
||||
std::string rel = (slash == std::string::npos) ? s : s.substr(slash + 1);
|
||||
return root + "/" + rel;
|
||||
}
|
||||
#endif // EVENGINE_ANDROID
|
||||
|
||||
inline std::string pathBesideSourceImpl(const char *file, const char *relative) {
|
||||
#if defined(EVENGINE_ANDROID)
|
||||
std::string source = androidSourcePath(file);
|
||||
size_t slash = source.find_last_of('/');
|
||||
std::string dir = (slash == std::string::npos) ? std::string(".") : source.substr(0, slash);
|
||||
return dir + "/" + relative;
|
||||
#else
|
||||
std::string here = file ? file : "";
|
||||
auto slash = here.find_last_of("/\\");
|
||||
std::string dir = (slash == std::string::npos) ? std::string(".") : here.substr(0, slash);
|
||||
return dir + "/" + relative;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace eve_test_path
|
||||
|
||||
#define EVE_DEFINE_PATH_BESIDE_SOURCE() \
|
||||
static std::string pathBesideThisSource(const char *filename) { \
|
||||
return eve_test_path::pathBesideSourceImpl(__FILE__, filename); \
|
||||
}
|
||||
@@ -866,12 +866,8 @@ struct CloseWin {
|
||||
}
|
||||
};
|
||||
|
||||
std::string pathBesideThisSource(const char *relative) {
|
||||
std::string here = __FILE__;
|
||||
auto slash = here.find_last_of("/\\");
|
||||
std::string dir = (slash == std::string::npos) ? std::string(".") : here.substr(0, slash);
|
||||
return dir + "/" + relative;
|
||||
}
|
||||
#include "PathBesideSource.h"
|
||||
EVE_DEFINE_PATH_BESIDE_SOURCE()
|
||||
|
||||
Mesh *makeFloor(Graphics *gfx, float half = 4.5f) {
|
||||
const float pos[] = {
|
||||
|
||||
@@ -42,12 +42,8 @@ using namespace eve::graphics;
|
||||
|
||||
namespace {
|
||||
|
||||
std::string pathBesideThisSource(const char *relative) {
|
||||
std::string here = __FILE__;
|
||||
auto slash = here.find_last_of("/\\");
|
||||
std::string dir = (slash == std::string::npos) ? std::string(".") : here.substr(0, slash);
|
||||
return dir + "/" + relative;
|
||||
}
|
||||
#include "PathBesideSource.h"
|
||||
EVE_DEFINE_PATH_BESIDE_SOURCE()
|
||||
|
||||
bool fileExists(const std::string &path) {
|
||||
return std::filesystem::is_regular_file(path);
|
||||
|
||||
+2
-6
@@ -20,12 +20,8 @@
|
||||
|
||||
namespace {
|
||||
|
||||
std::string pathBesideThisSource(const char *filename) {
|
||||
std::string here = __FILE__;
|
||||
auto slash = here.find_last_of("/\\");
|
||||
std::string dir = (slash == std::string::npos) ? std::string(".") : here.substr(0, slash);
|
||||
return dir + "/" + filename;
|
||||
}
|
||||
#include "PathBesideSource.h"
|
||||
EVE_DEFINE_PATH_BESIDE_SOURCE()
|
||||
|
||||
std::vector<char> readBinaryFile(const std::string &path) {
|
||||
std::ifstream in(path, std::ios::binary);
|
||||
|
||||
+2
-6
@@ -18,12 +18,8 @@ namespace {
|
||||
// FontAwesome private-use icon with a non-empty bitmap in the fixture.
|
||||
constexpr int kIconCodepoint = 0xF000;
|
||||
|
||||
std::string pathBesideThisSource(const char *filename) {
|
||||
std::string here = __FILE__;
|
||||
auto slash = here.find_last_of("/\\");
|
||||
std::string dir = (slash == std::string::npos) ? std::string(".") : here.substr(0, slash);
|
||||
return dir + "/" + filename;
|
||||
}
|
||||
#include "PathBesideSource.h"
|
||||
EVE_DEFINE_PATH_BESIDE_SOURCE()
|
||||
|
||||
std::vector<char> readBinaryFile(const std::string &path) {
|
||||
std::ifstream in(path, std::ios::binary);
|
||||
|
||||
@@ -27,12 +27,8 @@ namespace {
|
||||
constexpr int kIconCodepoint = 0xF000;
|
||||
constexpr const char *kIconUtf8 = "\xEF\x80\x80"; // UTF-8 for U+F000
|
||||
|
||||
std::string pathBesideThisSource(const char *filename) {
|
||||
std::string here = __FILE__;
|
||||
auto slash = here.find_last_of("/\\");
|
||||
std::string dir = (slash == std::string::npos) ? std::string(".") : here.substr(0, slash);
|
||||
return dir + "/" + filename;
|
||||
}
|
||||
#include "PathBesideSource.h"
|
||||
EVE_DEFINE_PATH_BESIDE_SOURCE()
|
||||
|
||||
std::vector<char> readBinaryFile(const std::string &path) {
|
||||
std::ifstream in(path, std::ios::binary);
|
||||
|
||||
+86
-2
@@ -1,5 +1,89 @@
|
||||
#include "zeroerr/unittest.h"
|
||||
|
||||
int main(int argc, const char** argv) {
|
||||
return zeroerr::UnitTest().parseArgs(argc, argv).run();
|
||||
#if defined(EVENGINE_ANDROID)
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_main.h>
|
||||
#include <android/log.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <streambuf>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace {
|
||||
|
||||
// Route C++ iostream output to Android logcat so `adb logcat` can show the
|
||||
// zeroerr console reporter (stdout/stderr are not attached to anything on Android).
|
||||
class LogcatBuf : public std::streambuf {
|
||||
public:
|
||||
LogcatBuf() { setp(buffer_, buffer_ + sizeof(buffer_) - 1); }
|
||||
~LogcatBuf() override { sync(); }
|
||||
|
||||
protected:
|
||||
int overflow(int ch) override {
|
||||
if (ch != traits_type::eof()) {
|
||||
*pptr() = static_cast<char>(ch);
|
||||
pbump(1);
|
||||
}
|
||||
return sync() == 0 ? traits_type::not_eof(ch) : traits_type::eof();
|
||||
}
|
||||
|
||||
int sync() override {
|
||||
if (pptr() == pbase())
|
||||
return 0;
|
||||
*pptr() = '\0';
|
||||
__android_log_print(ANDROID_LOG_INFO, "EVEngineTest", "%s", pbase());
|
||||
setp(buffer_, buffer_ + sizeof(buffer_) - 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
char buffer_[1024];
|
||||
};
|
||||
|
||||
// Root of the unpacked test tree: <internal storage>/evengine_test. The Java
|
||||
// activity unpacks the APK's assets/test + assets/examples here before SDL_main runs.
|
||||
std::string testRoot() {
|
||||
const char *internal = SDL_AndroidGetInternalStoragePath();
|
||||
if (!internal)
|
||||
return {};
|
||||
return std::string(internal) + "/evengine_test";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
#endif // EVENGINE_ANDROID
|
||||
|
||||
// Signature must match the `extern "C" int SDL_main(int, char*[])` declared by
|
||||
// SDL_main.h so the symbol stays unmangled ("SDL_main" for the SDLActivity JNI
|
||||
// dlsym). A `const char**` parameter would become a C++ overload and be mangled.
|
||||
int main(int argc, char **argv) {
|
||||
#if defined(EVENGINE_ANDROID)
|
||||
static LogcatBuf logcatBuf;
|
||||
std::cout.rdbuf(&logcatBuf);
|
||||
std::cerr.rdbuf(&logcatBuf);
|
||||
std::clog.rdbuf(&logcatBuf);
|
||||
std::cout.setf(std::ios::unitbuf);
|
||||
std::cerr.setf(std::ios::unitbuf);
|
||||
|
||||
const std::string root = testRoot();
|
||||
if (root.empty()) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "EVEngineTest", "no internal storage path");
|
||||
} else if (chdir(root.c_str()) != 0) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "EVEngineTest", "chdir(%s) failed: %s",
|
||||
root.c_str(), strerror(errno));
|
||||
} else {
|
||||
__android_log_print(ANDROID_LOG_INFO, "EVEngineTest", "test root: %s", root.c_str());
|
||||
}
|
||||
|
||||
// SDLActivity already ran SDL_SetMainReady(); init the subsystems the tests use.
|
||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER | SDL_INIT_AUDIO) != 0) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "EVEngineTest", "SDL_Init failed: %s",
|
||||
SDL_GetError());
|
||||
}
|
||||
__android_log_print(ANDROID_LOG_INFO, "EVEngineTest", "unit_test android starting");
|
||||
#endif // EVENGINE_ANDROID
|
||||
|
||||
return zeroerr::UnitTest().parseArgs(argc, const_cast<const char **>(argv)).run();
|
||||
}
|
||||
+2
-6
@@ -43,12 +43,8 @@ using namespace eve::graphics;
|
||||
|
||||
namespace {
|
||||
|
||||
std::string pathBesideThisSource(const char *filename) {
|
||||
std::string here = __FILE__;
|
||||
auto slash = here.find_last_of("/\\");
|
||||
std::string dir = (slash == std::string::npos) ? std::string(".") : here.substr(0, slash);
|
||||
return dir + "/" + filename;
|
||||
}
|
||||
#include "PathBesideSource.h"
|
||||
EVE_DEFINE_PATH_BESIDE_SOURCE()
|
||||
|
||||
std::vector<char> readBinaryFile(const std::string &path) {
|
||||
std::ifstream in(path, std::ios::binary);
|
||||
|
||||
@@ -40,12 +40,8 @@ using namespace eve::ik;
|
||||
|
||||
namespace {
|
||||
|
||||
std::string pathBesideThisSource(const char *relative) {
|
||||
std::string here = __FILE__;
|
||||
auto slash = here.find_last_of("/\\");
|
||||
std::string dir = (slash == std::string::npos) ? std::string(".") : here.substr(0, slash);
|
||||
return dir + "/" + relative;
|
||||
}
|
||||
#include "PathBesideSource.h"
|
||||
EVE_DEFINE_PATH_BESIDE_SOURCE()
|
||||
|
||||
bool fileExists(const std::string &path) { return std::filesystem::is_regular_file(path); }
|
||||
|
||||
|
||||
@@ -38,12 +38,8 @@ using namespace eve::ik;
|
||||
|
||||
namespace {
|
||||
|
||||
std::string pathBesideThisSource(const char *relative) {
|
||||
std::string here = __FILE__;
|
||||
auto slash = here.find_last_of("/\\");
|
||||
std::string dir = (slash == std::string::npos) ? std::string(".") : here.substr(0, slash);
|
||||
return dir + "/" + relative;
|
||||
}
|
||||
#include "PathBesideSource.h"
|
||||
EVE_DEFINE_PATH_BESIDE_SOURCE()
|
||||
|
||||
bool fileExists(const std::string &path) { return std::filesystem::is_regular_file(path); }
|
||||
|
||||
|
||||
@@ -26,12 +26,8 @@ using namespace eve::animation;
|
||||
|
||||
namespace {
|
||||
|
||||
std::string pathBesideThisSource(const char *relative) {
|
||||
std::string here = __FILE__;
|
||||
auto slash = here.find_last_of("/\\");
|
||||
std::string dir = (slash == std::string::npos) ? std::string(".") : here.substr(0, slash);
|
||||
return dir + "/" + relative;
|
||||
}
|
||||
#include "PathBesideSource.h"
|
||||
EVE_DEFINE_PATH_BESIDE_SOURCE()
|
||||
|
||||
bool fileExists(const std::string &path) { return std::filesystem::is_regular_file(path); }
|
||||
|
||||
|
||||
+2
-6
@@ -18,12 +18,8 @@
|
||||
|
||||
namespace {
|
||||
|
||||
std::string pathBesideThisSource(const char *filename) {
|
||||
std::string here = __FILE__;
|
||||
auto slash = here.find_last_of("/\\");
|
||||
std::string dir = (slash == std::string::npos) ? std::string(".") : here.substr(0, slash);
|
||||
return dir + "/" + filename;
|
||||
}
|
||||
#include "PathBesideSource.h"
|
||||
EVE_DEFINE_PATH_BESIDE_SOURCE()
|
||||
|
||||
std::vector<char> readBinaryFile(const std::string &path) {
|
||||
std::ifstream in(path, std::ios::binary);
|
||||
|
||||
Reference in new issue
Block a user