Merged upstream 4.7 into downstream 4.6
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
This commit is contained in:
@@ -27,8 +27,6 @@ allprojects {
|
||||
}
|
||||
|
||||
configurations {
|
||||
// Initializes a placeholder for the devImplementation dependency configuration.
|
||||
devImplementation {}
|
||||
// Initializes a placeholder for the monoImplementation dependency configuration.
|
||||
monoImplementation {}
|
||||
}
|
||||
@@ -43,6 +41,7 @@ dependencies {
|
||||
|
||||
implementation "androidx.fragment:fragment:$versions.fragmentVersion"
|
||||
implementation "androidx.core:core-splashscreen:$versions.splashscreenVersion"
|
||||
implementation "androidx.documentfile:documentfile:$versions.documentfileVersion"
|
||||
|
||||
if (rootProject.findProject(":lib")) {
|
||||
implementation project(":lib")
|
||||
@@ -52,15 +51,32 @@ dependencies {
|
||||
// Godot gradle build mode. In this scenario this project is the only one around and the Godot
|
||||
// library is available through the pre-generated godot-lib.*.aar android archive files.
|
||||
debugImplementation fileTree(dir: 'libs/debug', include: ['**/*.jar', '*.aar'])
|
||||
devImplementation fileTree(dir: 'libs/dev', include: ['**/*.jar', '*.aar'])
|
||||
releaseImplementation fileTree(dir: 'libs/release', include: ['**/*.jar', '*.aar'])
|
||||
}
|
||||
|
||||
// Godot user plugins remote dependencies
|
||||
String[] remoteDeps = getGodotPluginsRemoteBinaries()
|
||||
if (remoteDeps != null && remoteDeps.size() > 0) {
|
||||
def platformPattern = /^\s*(platform|enforcedPlatform)\s*\(\s*['"]*(\S+)['"]*\s*\)$/
|
||||
for (String dep : remoteDeps) {
|
||||
implementation dep
|
||||
def matcher = dep =~ platformPattern
|
||||
if (matcher) {
|
||||
switch (matcher[0][1]) {
|
||||
case "platform":
|
||||
implementation platform(matcher[0][2])
|
||||
break
|
||||
|
||||
case "enforcedPlatform":
|
||||
implementation enforcedPlatform(matcher[0][2])
|
||||
break
|
||||
|
||||
default:
|
||||
throw new GradleException("Invalid remote platform dependency: $dep")
|
||||
break
|
||||
}
|
||||
} else {
|
||||
implementation dep
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,18 +221,6 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
dev {
|
||||
initWith debug
|
||||
// Signing and zip-aligning are skipped for prebuilt builds, but
|
||||
// performed for Godot gradle builds.
|
||||
zipAlignEnabled shouldZipAlign()
|
||||
if (shouldSign()) {
|
||||
signingConfig signingConfigs.debug
|
||||
} else {
|
||||
signingConfig null
|
||||
}
|
||||
}
|
||||
|
||||
release {
|
||||
// Signing and zip-aligning are skipped for prebuilt builds, but
|
||||
// performed for Godot gradle builds.
|
||||
@@ -250,7 +254,6 @@ android {
|
||||
sourceSets {
|
||||
main.res.srcDirs += ['res']
|
||||
debug.jniLibs.srcDirs = ['libs/debug', 'libs/debug/vulkan_validation_layers']
|
||||
dev.jniLibs.srcDirs = ['libs/dev']
|
||||
release.jniLibs.srcDirs = ['libs/release']
|
||||
}
|
||||
|
||||
@@ -329,9 +332,6 @@ module, so we're ensuring the ':app:preBuild' task is set to run after those tas
|
||||
if (rootProject.tasks.findByPath("copyDebugAARToAppModule") != null) {
|
||||
preBuild.mustRunAfter(rootProject.tasks.named("copyDebugAARToAppModule"))
|
||||
}
|
||||
if (rootProject.tasks.findByPath("copyDevAARToAppModule") != null) {
|
||||
preBuild.mustRunAfter(rootProject.tasks.named("copyDevAARToAppModule"))
|
||||
}
|
||||
if (rootProject.tasks.findByPath("copyReleaseAARToAppModule") != null) {
|
||||
preBuild.mustRunAfter(rootProject.tasks.named("copyReleaseAARToAppModule"))
|
||||
}
|
||||
|
||||
@@ -1,28 +1,29 @@
|
||||
ext.versions = [
|
||||
androidGradlePlugin: '8.6.1',
|
||||
compileSdk : 35,
|
||||
compileSdk : 36,
|
||||
// Also update:
|
||||
// - 'platform/android/export/export_plugin.cpp#DEFAULT_MIN_SDK_VERSION'
|
||||
// - 'platform/android/detect.py#get_min_target_api()'
|
||||
minSdk : 24,
|
||||
// Also update 'platform/android/export/export_plugin.cpp#DEFAULT_TARGET_SDK_VERSION'
|
||||
targetSdk : 35,
|
||||
buildTools : '35.0.1',
|
||||
kotlinVersion : '2.1.20',
|
||||
targetSdk : 36,
|
||||
buildTools : '36.1.0',
|
||||
kotlinVersion : '2.1.21',
|
||||
fragmentVersion : '1.8.6',
|
||||
nexusPublishVersion: '1.3.0',
|
||||
javaVersion : JavaVersion.VERSION_17,
|
||||
// Also update 'platform/android/detect.py#get_ndk_version()' when this is updated.
|
||||
ndkVersion : '28.1.13356709',
|
||||
ndkVersion : '29.0.14206865',
|
||||
splashscreenVersion: '1.0.1',
|
||||
// 'openxrLoaderVersion' should be set to XR_CURRENT_API_VERSION, see 'thirdparty/openxr'
|
||||
openxrLoaderVersion: '1.1.53',
|
||||
openxrVendorsVersion: '4.2.2-stable',
|
||||
openxrVendorsVersion: '4.3.0-stable',
|
||||
junitVersion : '1.3.0',
|
||||
espressoCoreVersion: '3.7.0',
|
||||
kotlinTestVersion : '1.3.11',
|
||||
testRunnerVersion : '1.7.0',
|
||||
testOrchestratorVersion: '1.6.1',
|
||||
documentfileVersion: '1.1.0',
|
||||
]
|
||||
|
||||
ext.getExportPackageName = { ->
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
To add custom attributes, use the "gradle_build/custom_theme_attributes" Android export option. -->
|
||||
<style name="GodotAppSplashTheme" parent="Theme.SplashScreen">
|
||||
<item name="android:windowSplashScreenBackground">@mipmap/icon_background</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@mipmap/icon_foreground</item>
|
||||
<item name="android:windowSplashScreenBrandingImage">@drawable/splash_branding_image</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
|
||||
<item name="postSplashScreenTheme">@style/GodotAppMainTheme</item>
|
||||
<item name="android:windowIsTranslucent">false</item>
|
||||
</style>
|
||||
|
||||
+77
@@ -34,8 +34,11 @@ import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.espresso.Espresso
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.godot.game.test.GodotAppInstrumentedTestPlugin
|
||||
import org.godotengine.godot.Godot
|
||||
import org.godotengine.godot.GodotActivity.Companion.EXTRA_COMMAND_LINE_PARAMS
|
||||
import org.godotengine.godot.plugin.GodotPluginRegistry
|
||||
import org.junit.Test
|
||||
@@ -110,6 +113,28 @@ class GodotAppTest {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs test to validate android plugin signals.
|
||||
*/
|
||||
@Test
|
||||
fun runPluginSignalTests() {
|
||||
ActivityScenario.launch(GodotApp::class.java).use { scenario ->
|
||||
scenario.onActivity { activity ->
|
||||
val testPlugin = getTestPlugin()
|
||||
assertNotNull(testPlugin)
|
||||
|
||||
Log.d(TAG, "Waiting for the Godot main loop to start...")
|
||||
testPlugin.waitForGodotMainLoopStarted()
|
||||
|
||||
Log.d(TAG, "Running Android plugin signal tests...")
|
||||
val result = testPlugin.runPluginSignalTests()
|
||||
assertNotNull(result)
|
||||
result.exceptionOrNull()?.let { throw it }
|
||||
assertTrue(result.isSuccess)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test implicit launch of the Godot app, and validates this resolves to the `GodotAppLauncher` activity alias.
|
||||
*/
|
||||
@@ -169,4 +194,56 @@ class GodotAppTest {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the back press does not quit the game when 'quit_on_go_back' is disabled.
|
||||
*/
|
||||
@Test
|
||||
fun testGameNotQuittingOnBackPress() {
|
||||
ActivityScenario.launch(GodotApp::class.java).use { scenario ->
|
||||
val testPlugin = getTestPlugin()
|
||||
assertNotNull(testPlugin)
|
||||
|
||||
Log.d(TAG, "Waiting for the Godot main loop to start...")
|
||||
testPlugin.waitForGodotMainLoopStarted()
|
||||
|
||||
// Disable 'quit_on_go_back'.
|
||||
testPlugin.updateQuitOnGoBack(false)
|
||||
|
||||
// Trigger the back press event.
|
||||
Espresso.pressBackUnconditionally()
|
||||
|
||||
Log.d(TAG, "Waiting for the engine to terminate...")
|
||||
testPlugin.waitForEngineTermination(5_000L)
|
||||
|
||||
val godot = Godot.getInstance(InstrumentationRegistry.getInstrumentation().targetContext)
|
||||
assertTrue { godot.runStatus != Godot.RunStatus.TERMINATING }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the back press event quits the game when 'quit_on_go_back' is enabled.
|
||||
*/
|
||||
@Test
|
||||
fun testGameQuittingOnBackPress() {
|
||||
ActivityScenario.launch(GodotApp::class.java).use { scenario ->
|
||||
val testPlugin = getTestPlugin()
|
||||
assertNotNull(testPlugin)
|
||||
|
||||
Log.d(TAG, "Waiting for the Godot main loop to start...")
|
||||
testPlugin.waitForGodotMainLoopStarted()
|
||||
|
||||
// Enable 'quit_on_go_back'.
|
||||
testPlugin.updateQuitOnGoBack(true)
|
||||
|
||||
// Trigger the back press event.
|
||||
Espresso.pressBackUnconditionally()
|
||||
|
||||
Log.d(TAG, "Waiting for the engine to terminate...")
|
||||
testPlugin.waitForEngineTermination(5_000L)
|
||||
|
||||
val godot = Godot.getInstance(InstrumentationRegistry.getInstrumentation().targetContext)
|
||||
assertTrue { godot.runStatus == Godot.RunStatus.TERMINATING }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
<meta-data
|
||||
android:name="org.godotengine.plugin.v2.GodotAppInstrumentedTestPlugin"
|
||||
android:value="com.godot.game.test.GodotAppInstrumentedTestPlugin"/>
|
||||
|
||||
<meta-data
|
||||
android:name="org.godotengine.plugin.v2.SignalTestPlugin"
|
||||
android:value="com.godot.game.test.SignalTestPlugin"/>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
list=[{
|
||||
"base": &"BaseTest",
|
||||
"class": &"AndroidPluginSignalTests",
|
||||
"icon": "",
|
||||
"is_abstract": false,
|
||||
"is_tool": false,
|
||||
"language": &"GDScript",
|
||||
"path": "res://test/android_plugin/signal_tests.gd"
|
||||
}, {
|
||||
"base": &"RefCounted",
|
||||
"class": &"BaseTest",
|
||||
"icon": "",
|
||||
|
||||
Binary file not shown.
@@ -3,12 +3,22 @@ extends Node2D
|
||||
var _plugin_name = "GodotAppInstrumentedTestPlugin"
|
||||
var _android_plugin
|
||||
|
||||
func _ready():
|
||||
var _signal_test_plugin_name = "SignalTestPlugin"
|
||||
var _signal_test_plugin
|
||||
|
||||
func _init():
|
||||
# Verify plugin singleton in _init, since plugins should already be registered at this point.
|
||||
if Engine.has_singleton(_plugin_name):
|
||||
_android_plugin = Engine.get_singleton(_plugin_name)
|
||||
_android_plugin.connect("launch_tests", _launch_tests)
|
||||
else:
|
||||
printerr("Couldn't find plugin " + _plugin_name)
|
||||
_android_plugin.connect("update_quit_on_go_back", _update_quit_on_go_back)
|
||||
|
||||
if Engine.has_singleton(_signal_test_plugin_name):
|
||||
_signal_test_plugin = Engine.get_singleton(_signal_test_plugin_name)
|
||||
|
||||
func _ready() -> void:
|
||||
if not _android_plugin:
|
||||
printerr("ERROR: Couldn't find plugin " + _plugin_name)
|
||||
get_tree().quit()
|
||||
|
||||
func _launch_tests(test_label: String) -> void:
|
||||
@@ -18,16 +28,22 @@ func _launch_tests(test_label: String) -> void:
|
||||
test_instance = JavaClassWrapperTests.new()
|
||||
"file_access_tests":
|
||||
test_instance = FileAccessTests.new()
|
||||
"android_plugin_signal_tests":
|
||||
test_instance = AndroidPluginSignalTests.new(_signal_test_plugin)
|
||||
|
||||
if test_instance:
|
||||
test_instance.__reset_tests()
|
||||
test_instance.run_tests()
|
||||
await test_instance.run_tests()
|
||||
var incomplete_tests = test_instance._test_started - test_instance._test_completed
|
||||
_android_plugin.onTestsCompleted(test_label, test_instance._test_completed, test_instance._test_assert_failures + incomplete_tests)
|
||||
else:
|
||||
_android_plugin.onTestsFailed(test_label, "Unable to launch tests")
|
||||
|
||||
|
||||
func _update_quit_on_go_back(quit_on_go_back: bool) -> void:
|
||||
get_tree().quit_on_go_back = quit_on_go_back
|
||||
|
||||
|
||||
func _on_plugin_toast_button_pressed() -> void:
|
||||
if _android_plugin:
|
||||
_android_plugin.helloWorld()
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://cg3hylang5fxn"]
|
||||
[gd_scene format=3 uid="uid://cg3hylang5fxn"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bv6y7in6otgcm" path="res://main.gd" id="1_j0gfq"]
|
||||
|
||||
[node name="Main" type="Node2D"]
|
||||
[node name="Main" type="Node2D" unique_id=852911723]
|
||||
script = ExtResource("1_j0gfq")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=1839352715]
|
||||
offset_left = 68.0
|
||||
offset_top = 102.0
|
||||
offset_right = 506.0
|
||||
offset_bottom = 408.0
|
||||
theme_override_constants/separation = 25
|
||||
|
||||
[node name="PluginToastButton" type="Button" parent="VBoxContainer"]
|
||||
[node name="PluginToastButton" type="Button" parent="VBoxContainer" unique_id=1670434164]
|
||||
custom_minimum_size = Vector2(0, 50)
|
||||
layout_mode = 2
|
||||
text = "Plugin Toast
|
||||
"
|
||||
|
||||
[node name="VibrationButton" type="Button" parent="VBoxContainer"]
|
||||
[node name="VibrationButton" type="Button" parent="VBoxContainer" unique_id=648980813]
|
||||
custom_minimum_size = Vector2(0, 50)
|
||||
layout_mode = 2
|
||||
text = "Vibration"
|
||||
|
||||
[node name="GDScriptToastButton" type="Button" parent="VBoxContainer"]
|
||||
[node name="GDScriptToastButton" type="Button" parent="VBoxContainer" unique_id=95554078]
|
||||
custom_minimum_size = Vector2(0, 50)
|
||||
layout_mode = 2
|
||||
text = "GDScript Toast
|
||||
|
||||
@@ -8,11 +8,15 @@
|
||||
|
||||
config_version=5
|
||||
|
||||
[animation]
|
||||
|
||||
compatibility/default_parent_skeleton_in_mesh_instance_3d=true
|
||||
|
||||
[application]
|
||||
|
||||
config/name="Godot App Instrumentation Tests"
|
||||
run/main_scene="res://main.tscn"
|
||||
config/features=PackedStringArray("4.5", "GL Compatibility")
|
||||
config/features=PackedStringArray("4.7", "GL Compatibility")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[debug]
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
class_name AndroidPluginSignalTests
|
||||
extends BaseTest
|
||||
|
||||
var _plugin: JNISingleton
|
||||
const emission_test_signal = "emission_test_signal"
|
||||
const launch_test_signal = "launch_tests"
|
||||
|
||||
signal emission_test_signal_emitted
|
||||
|
||||
func _init(plugin: JNISingleton) -> void:
|
||||
_plugin = plugin
|
||||
|
||||
func run_tests():
|
||||
print("Android plugin signal tests starting...")
|
||||
|
||||
__exec_test(test_plugin_exists)
|
||||
__exec_test(test_signal_registration)
|
||||
__exec_test(test_signal_connection)
|
||||
await __exec_test(test_signal_emission)
|
||||
|
||||
print("Android plugin signal tests completed.")
|
||||
|
||||
func test_plugin_exists() -> bool:
|
||||
if _plugin == null:
|
||||
printerr("ERROR: Couldn't find SignalTestPlugin plugin; _plugin is null")
|
||||
return false
|
||||
|
||||
return true
|
||||
|
||||
func test_signal_registration() -> bool:
|
||||
var signal_registered = _plugin.has_signal(emission_test_signal)
|
||||
assert_true(signal_registered)
|
||||
|
||||
var launch_signal_registered = _plugin.has_signal(launch_test_signal)
|
||||
assert_true(launch_signal_registered)
|
||||
|
||||
return true
|
||||
|
||||
func test_signal_connection() -> bool:
|
||||
_plugin.connect(emission_test_signal, _on_emission_test_signal_emitted)
|
||||
assert_equal(_plugin.has_connections(emission_test_signal), true)
|
||||
|
||||
_plugin.disconnect(emission_test_signal, _on_emission_test_signal_emitted)
|
||||
assert_equal(_plugin.has_connections(emission_test_signal), false)
|
||||
|
||||
_plugin.emission_test_signal.connect(_on_emission_test_signal_emitted)
|
||||
assert_equal(_plugin.has_connections(emission_test_signal), true)
|
||||
|
||||
_plugin.emission_test_signal.disconnect(_on_emission_test_signal_emitted)
|
||||
assert_equal(_plugin.has_connections(emission_test_signal), false)
|
||||
|
||||
return true
|
||||
|
||||
func test_signal_emission() -> bool:
|
||||
var err1 = _plugin.connect(emission_test_signal, _on_emission_test_signal_emitted)
|
||||
assert_equal(err1, OK)
|
||||
_plugin.triggerTestSignal1()
|
||||
await emission_test_signal_emitted
|
||||
|
||||
# Test case: Same signal name, but different type and number of parameters
|
||||
# The "launch_tests" signal is registered by both GodotAppInstrumentedTestPlugin and SignalTestPlugin.
|
||||
# SignalTestPlugin emits it with a boolean and a string arguments, while GodotAppInstrumentedTestPlugin emits it with one string.
|
||||
var err2 = _plugin.connect(launch_test_signal, _on_launch_tests_emitted)
|
||||
assert_equal(err2, OK)
|
||||
_plugin.triggerLaunchTestSignal()
|
||||
await emission_test_signal_emitted
|
||||
|
||||
return true
|
||||
|
||||
func _on_emission_test_signal_emitted() -> void:
|
||||
emission_test_signal_emitted.emit()
|
||||
|
||||
func _on_launch_tests_emitted(param1: bool, param2: String) -> void:
|
||||
assert_true(param1)
|
||||
assert_equal(param2, "second message")
|
||||
emission_test_signal_emitted.emit()
|
||||
+1
@@ -0,0 +1 @@
|
||||
uid://cq0mxqlbbug6r
|
||||
@@ -9,8 +9,9 @@ var _test_assert_failures := 0
|
||||
|
||||
func __exec_test(test_func: Callable):
|
||||
_test_started += 1
|
||||
test_func.call()
|
||||
_test_completed += 1
|
||||
var ret = await test_func.call()
|
||||
if ret == true:
|
||||
_test_completed += 1
|
||||
|
||||
func __reset_tests():
|
||||
_test_started = 0
|
||||
|
||||
+20
-9
@@ -9,10 +9,14 @@ func run_tests():
|
||||
__exec_test(test_internal_app_dir_access)
|
||||
__exec_test(test_internal_cache_dir_access)
|
||||
__exec_test(test_external_app_dir_access)
|
||||
__exec_test(test_downloads_dir_access)
|
||||
__exec_test(test_documents_dir_access)
|
||||
|
||||
func _test_dir_access(dir_path: String, data_file_content: String) -> void:
|
||||
# Scoped storage: Testing access to Downloads and Documents directory.
|
||||
var version = JavaClassWrapper.wrap("android.os.Build$VERSION")
|
||||
if version.SDK_INT >= 30:
|
||||
__exec_test(test_downloads_dir_access)
|
||||
__exec_test(test_documents_dir_access)
|
||||
|
||||
func _test_dir_access(dir_path: String, data_file_content: String) -> bool:
|
||||
print("Testing access to " + dir_path)
|
||||
var data_file_path = dir_path.path_join("data.dat")
|
||||
|
||||
@@ -29,45 +33,52 @@ func _test_dir_access(dir_path: String, data_file_content: String) -> void:
|
||||
|
||||
var deletion_result = DirAccess.remove_absolute(data_file_path)
|
||||
assert_equal(deletion_result, OK)
|
||||
return true
|
||||
|
||||
func test_obb_dir_access() -> void:
|
||||
func test_obb_dir_access() -> bool:
|
||||
var android_runtime = Engine.get_singleton("AndroidRuntime")
|
||||
assert_true(android_runtime != null)
|
||||
|
||||
var app_context = android_runtime.getApplicationContext()
|
||||
var obb_dir: String = app_context.getObbDir().getCanonicalPath()
|
||||
_test_dir_access(obb_dir, FILE_CONTENT + "obb dir.")
|
||||
return true
|
||||
|
||||
func test_internal_app_dir_access() -> void:
|
||||
func test_internal_app_dir_access() -> bool:
|
||||
var android_runtime = Engine.get_singleton("AndroidRuntime")
|
||||
assert_true(android_runtime != null)
|
||||
|
||||
var app_context = android_runtime.getApplicationContext()
|
||||
var internal_app_dir: String = app_context.getFilesDir().getCanonicalPath()
|
||||
_test_dir_access(internal_app_dir, FILE_CONTENT + "internal app dir.")
|
||||
return true
|
||||
|
||||
func test_internal_cache_dir_access() -> void:
|
||||
func test_internal_cache_dir_access() -> bool:
|
||||
var android_runtime = Engine.get_singleton("AndroidRuntime")
|
||||
assert_true(android_runtime != null)
|
||||
|
||||
var app_context = android_runtime.getApplicationContext()
|
||||
var internal_cache_dir: String = app_context.getCacheDir().getCanonicalPath()
|
||||
_test_dir_access(internal_cache_dir, FILE_CONTENT + "internal cache dir.")
|
||||
return true
|
||||
|
||||
func test_external_app_dir_access() -> void:
|
||||
func test_external_app_dir_access() -> bool:
|
||||
var android_runtime = Engine.get_singleton("AndroidRuntime")
|
||||
assert_true(android_runtime != null)
|
||||
|
||||
var app_context = android_runtime.getApplicationContext()
|
||||
var external_app_dir: String = app_context.getExternalFilesDir("").getCanonicalPath()
|
||||
_test_dir_access(external_app_dir, FILE_CONTENT + "external app dir.")
|
||||
return true
|
||||
|
||||
func test_downloads_dir_access() -> void:
|
||||
func test_downloads_dir_access() -> bool:
|
||||
var EnvironmentClass = JavaClassWrapper.wrap("android.os.Environment")
|
||||
var downloads_dir = EnvironmentClass.getExternalStoragePublicDirectory(EnvironmentClass.DIRECTORY_DOWNLOADS).getCanonicalPath()
|
||||
_test_dir_access(downloads_dir, FILE_CONTENT + "downloads dir.")
|
||||
return true
|
||||
|
||||
func test_documents_dir_access() -> void:
|
||||
func test_documents_dir_access() -> bool:
|
||||
var EnvironmentClass = JavaClassWrapper.wrap("android.os.Environment")
|
||||
var documents_dir = EnvironmentClass.getExternalStoragePublicDirectory(EnvironmentClass.DIRECTORY_DOCUMENTS).getCanonicalPath()
|
||||
_test_dir_access(documents_dir, FILE_CONTENT + "documents dir.")
|
||||
return true
|
||||
|
||||
+58
-9
@@ -20,12 +20,16 @@ func run_tests():
|
||||
|
||||
__exec_test(test_callable)
|
||||
|
||||
__exec_test(test_interface_callable_proxy)
|
||||
|
||||
__exec_test(test_interface_object_proxy)
|
||||
|
||||
print("JavaClassWrapper tests finished.")
|
||||
print("Tests started: " + str(_test_started))
|
||||
print("Tests completed: " + str(_test_completed))
|
||||
|
||||
|
||||
func test_exceptions() -> void:
|
||||
func test_exceptions() -> bool:
|
||||
var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass')
|
||||
#print(TestClass.get_java_method_list())
|
||||
|
||||
@@ -36,7 +40,9 @@ func test_exceptions() -> void:
|
||||
|
||||
assert_equal(JavaClassWrapper.get_exception(), null)
|
||||
|
||||
func test_multiple_signatures() -> void:
|
||||
return true
|
||||
|
||||
func test_multiple_signatures() -> bool:
|
||||
var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass')
|
||||
|
||||
var ai := [1, 2]
|
||||
@@ -55,7 +61,9 @@ func test_multiple_signatures() -> void:
|
||||
]
|
||||
assert_equal(TestClass.testMethod(3, aobjl), "testObjects: 27 135")
|
||||
|
||||
func test_array_arguments() -> void:
|
||||
return true
|
||||
|
||||
func test_array_arguments() -> bool:
|
||||
var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass')
|
||||
|
||||
assert_equal(TestClass.testArgBoolArray([true, false, true]), "[true, false, true]")
|
||||
@@ -72,7 +80,9 @@ func test_array_arguments() -> void:
|
||||
assert_equal(TestClass.testArgDoubleArray(PackedFloat64Array([37.1, 38.2, 39.3])), "[37.1, 38.2, 39.3]")
|
||||
assert_equal(TestClass.testArgDoubleArray([37.1, 38.2, 39.3]), "[37.1, 38.2, 39.3]")
|
||||
|
||||
func test_array_return() -> void:
|
||||
return true
|
||||
|
||||
func test_array_return() -> bool:
|
||||
var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass')
|
||||
#print(TestClass.get_java_method_list())
|
||||
|
||||
@@ -107,14 +117,17 @@ func test_array_return() -> void:
|
||||
assert_equal(TestClass.testRetStringArray(), PackedStringArray(["I", "am", "String"]))
|
||||
assert_equal(TestClass.testRetCharSequenceArray(), PackedStringArray(["I", "am", "CharSequence"]))
|
||||
|
||||
func test_dictionary():
|
||||
return true
|
||||
|
||||
func test_dictionary() -> bool:
|
||||
var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass')
|
||||
assert_equal(TestClass.testDictionary({a = 1, b = 2}), "{a=1, b=2}")
|
||||
assert_equal(TestClass.testRetDictionary(), {a = 1, b = 2})
|
||||
assert_equal(TestClass.testRetDictionaryArray(), [{a = 1, b = 2}])
|
||||
assert_equal(TestClass.testDictionaryNested({a = 1, b = [2, 3], c = 4}), "{a: 1, b: [2, 3], c: 4}")
|
||||
return true
|
||||
|
||||
func test_object_overload():
|
||||
func test_object_overload() -> bool:
|
||||
var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass')
|
||||
var TestClass2: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass2')
|
||||
var TestClass3: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass3')
|
||||
@@ -130,22 +143,25 @@ func test_object_overload():
|
||||
|
||||
assert_equal(TestClass.testObjectOverloadArray(arr_of_t2), "TestClass2: [33, 34]")
|
||||
assert_equal(TestClass.testObjectOverloadArray(arr_of_t3), "TestClass3: [thirty three, thirty four]")
|
||||
return true
|
||||
|
||||
func test_variant_conversion_safe_from_stack_overflow():
|
||||
func test_variant_conversion_safe_from_stack_overflow() -> bool:
|
||||
var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass')
|
||||
var arr: Array = [42]
|
||||
var dict: Dictionary = {"arr": arr}
|
||||
arr.append(dict)
|
||||
# The following line will crash with stack overflow if not handled property:
|
||||
TestClass.testDictionary(dict)
|
||||
return true
|
||||
|
||||
func test_big_integers():
|
||||
func test_big_integers() -> bool:
|
||||
var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass')
|
||||
assert_equal(TestClass.testArgLong(4242424242), "4242424242")
|
||||
assert_equal(TestClass.testArgLong(-4242424242), "-4242424242")
|
||||
assert_equal(TestClass.testDictionary({a = 4242424242, b = -4242424242}), "{a=4242424242, b=-4242424242}")
|
||||
return true
|
||||
|
||||
func test_callable():
|
||||
func test_callable() -> bool:
|
||||
var android_runtime = Engine.get_singleton("AndroidRuntime")
|
||||
assert_true(android_runtime != null)
|
||||
|
||||
@@ -155,3 +171,36 @@ func test_callable():
|
||||
return null
|
||||
android_runtime.createRunnableFromGodotCallable(cb1).run()
|
||||
assert_equal(cb1_data['called'], true)
|
||||
|
||||
return true
|
||||
|
||||
func test_interface_callable_proxy() -> bool:
|
||||
var cb1_data := {called = false, content = ""}
|
||||
var cb1 = func (content: String) -> void:
|
||||
cb1_data['called'] = true
|
||||
cb1_data['content'] = content
|
||||
|
||||
var printer_proxy = JavaClassWrapper.create_sam_callback("android.util.Printer", cb1)
|
||||
assert_true(printer_proxy != null)
|
||||
|
||||
printer_proxy.println("This is a callback test")
|
||||
assert_equal(cb1_data['called'], true)
|
||||
assert_equal(cb1_data['content'], "This is a callback test")
|
||||
return true
|
||||
|
||||
class PrintProxy:
|
||||
var test_data := {called = false, content = ""}
|
||||
|
||||
func println(content: String) -> void:
|
||||
test_data['called'] = true
|
||||
test_data['content'] = content
|
||||
|
||||
func test_interface_object_proxy() -> bool:
|
||||
var print_object = PrintProxy.new()
|
||||
var proxy = JavaClassWrapper.create_proxy(print_object, ["android.util.Printer"])
|
||||
assert_true(proxy != null)
|
||||
|
||||
proxy.println("This is proxy test")
|
||||
assert_equal(print_object.test_data['called'], true)
|
||||
assert_equal(print_object.test_data['content'], "This is proxy test")
|
||||
return true
|
||||
|
||||
+31
-2
@@ -38,6 +38,7 @@ import org.godotengine.godot.plugin.UsedByGodot
|
||||
import org.godotengine.godot.plugin.SignalInfo
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* [GodotPlugin] used to drive instrumented tests.
|
||||
@@ -47,14 +48,18 @@ class GodotAppInstrumentedTestPlugin(godot: Godot) : GodotPlugin(godot) {
|
||||
companion object {
|
||||
private val TAG = GodotAppInstrumentedTestPlugin::class.java.simpleName
|
||||
private const val MAIN_LOOP_STARTED_LATCH_KEY = "main_loop_started_latch"
|
||||
private const val ENGINE_TERMINATING_LATCH_KEY = "engine_terminating_latch"
|
||||
|
||||
private const val JAVACLASSWRAPPER_TESTS = "javaclasswrapper_tests"
|
||||
private const val FILE_ACCESS_TESTS = "file_access_tests"
|
||||
private const val PLUGIN_SIGNAL_TESTS = "android_plugin_signal_tests"
|
||||
|
||||
private val LAUNCH_TESTS_SIGNAL = SignalInfo("launch_tests", String::class.java)
|
||||
private val UPDATE_QUIT_ON_GO_BACK_SIGNAL = SignalInfo("update_quit_on_go_back", java.lang.Boolean::class.java)
|
||||
|
||||
private val SIGNALS = setOf(
|
||||
LAUNCH_TESTS_SIGNAL
|
||||
LAUNCH_TESTS_SIGNAL,
|
||||
UPDATE_QUIT_ON_GO_BACK_SIGNAL
|
||||
)
|
||||
}
|
||||
|
||||
@@ -65,6 +70,8 @@ class GodotAppInstrumentedTestPlugin(godot: Godot) : GodotPlugin(godot) {
|
||||
// Add a countdown latch that is triggered when `onGodotMainLoopStarted` is fired.
|
||||
// This will be used by tests to wait until the engine is ready.
|
||||
latches[MAIN_LOOP_STARTED_LATCH_KEY] = CountDownLatch(1)
|
||||
// Add a countdown latch that is triggered when the engine terminates.
|
||||
latches[ENGINE_TERMINATING_LATCH_KEY] = CountDownLatch(1)
|
||||
}
|
||||
|
||||
override fun getPluginName() = "GodotAppInstrumentedTestPlugin"
|
||||
@@ -76,6 +83,11 @@ class GodotAppInstrumentedTestPlugin(godot: Godot) : GodotPlugin(godot) {
|
||||
latches.remove(MAIN_LOOP_STARTED_LATCH_KEY)?.countDown()
|
||||
}
|
||||
|
||||
override fun onGodotTerminating() {
|
||||
super.onGodotTerminating()
|
||||
latches.remove(ENGINE_TERMINATING_LATCH_KEY)?.countDown()
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by the instrumented test to wait until the Godot main loop is up and running.
|
||||
*/
|
||||
@@ -88,6 +100,19 @@ class GodotAppInstrumentedTestPlugin(godot: Godot) : GodotPlugin(godot) {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun waitForEngineTermination(timeoutInMs: Long) {
|
||||
// Wait on the CountDownLatch for `onGodotTerminating`.
|
||||
try {
|
||||
latches[ENGINE_TERMINATING_LATCH_KEY]?.await(timeoutInMs, TimeUnit.MILLISECONDS)
|
||||
} catch (e: InterruptedException) {
|
||||
Log.e(TAG, "Unable to wait for engine termination event.", e)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun updateQuitOnGoBack(quitOnGoBack: Boolean) {
|
||||
emitSignal(UPDATE_QUIT_ON_GO_BACK_SIGNAL, quitOnGoBack)
|
||||
}
|
||||
|
||||
/**
|
||||
* This launches the JavaClassWrapper tests, and wait until the tests are complete before returning.
|
||||
*/
|
||||
@@ -102,9 +127,13 @@ class GodotAppInstrumentedTestPlugin(godot: Godot) : GodotPlugin(godot) {
|
||||
return launchTests(FILE_ACCESS_TESTS)
|
||||
}
|
||||
|
||||
internal fun runPluginSignalTests(): Result<Any>? {
|
||||
return launchTests(PLUGIN_SIGNAL_TESTS)
|
||||
}
|
||||
|
||||
private fun launchTests(testLabel: String): Result<Any>? {
|
||||
val latch = latches.getOrPut(testLabel) { CountDownLatch(1) }
|
||||
emitSignal(LAUNCH_TESTS_SIGNAL.name, testLabel)
|
||||
emitSignal(LAUNCH_TESTS_SIGNAL, testLabel)
|
||||
return try {
|
||||
latch.await()
|
||||
val result = testResults.remove(testLabel)
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.godot.game.test
|
||||
|
||||
import android.util.Log
|
||||
import org.godotengine.godot.Dictionary
|
||||
import org.godotengine.godot.Godot
|
||||
import org.godotengine.godot.plugin.GodotPlugin
|
||||
import org.godotengine.godot.plugin.SignalInfo
|
||||
import org.godotengine.godot.plugin.UsedByGodot
|
||||
|
||||
class SignalTestPlugin(godot: Godot) : GodotPlugin(godot) {
|
||||
|
||||
companion object {
|
||||
private val EMISSION_TEST_SIGNAL = SignalInfo("emission_test_signal")
|
||||
private val LAUNCH_TESTS_SIGNAL = SignalInfo("launch_tests", java.lang.Boolean::class.java, String::class.java)
|
||||
}
|
||||
|
||||
override fun getPluginName() = "SignalTestPlugin"
|
||||
|
||||
override fun getPluginSignals(): Set<SignalInfo?> {
|
||||
return setOf(
|
||||
EMISSION_TEST_SIGNAL,
|
||||
LAUNCH_TESTS_SIGNAL
|
||||
)
|
||||
}
|
||||
|
||||
@UsedByGodot
|
||||
fun triggerTestSignal1() {
|
||||
emitSignal(EMISSION_TEST_SIGNAL)
|
||||
}
|
||||
|
||||
@UsedByGodot
|
||||
fun triggerLaunchTestSignal() {
|
||||
emitSignal(LAUNCH_TESTS_SIGNAL, true, "second message")
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@
|
||||
android:launchMode="singleInstancePerTask"
|
||||
android:excludeFromRecents="false"
|
||||
android:exported="false"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:screenOrientation="landscape"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:configChanges="layoutDirection|locale|orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
|
||||
|
||||
@@ -67,9 +67,14 @@ public class GodotApp extends GodotActivity {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
SplashScreen.installSplashScreen(this);
|
||||
SplashScreen splashScreen = SplashScreen.installSplashScreen(this);
|
||||
EdgeToEdge.enable(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Godot godot = getGodot();
|
||||
if (godot != null && godot.getDisableGodotSplash()) {
|
||||
splashScreen.setKeepOnScreenCondition(() -> godot.getRunStatus() != Godot.RunStatus.STARTED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -92,4 +97,9 @@ public class GodotApp extends GodotActivity {
|
||||
super.onGodotForceQuit(instance);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isPiPEnabled() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user