From 16bdc8c4f1fe630d8222bbd2b9d6695815be5d3c Mon Sep 17 00:00:00 2001 From: Fredia Huya-Kouadio Date: Tue, 23 Sep 2025 12:06:40 -0400 Subject: [PATCH] Add Android instrumented tests to the `app` module This builds on the work from @dsnopek in https://github.com/dsnopek/javaclasswrapper-test, by importing the set of JavaClassWrapper tests from that repo within the Godot core repo in order to bootstrap and standardize how we write and run Android instrumented tests. The approach used here should serve as a base to build upon to expand the set of instrumented tests used to validate the project's stability. Co-authored-by: David Snopek --- platform/android/java/app/build.gradle | 16 ++ .../java/com/godot/game/GodotAppTest.kt | 76 +++++ .../app/src/instrumented/AndroidManifest.xml | 9 + .../src/instrumented/assets/.gitattributes | 2 + .../app/src/instrumented/assets/.gitignore | 3 + .../src/instrumented/assets/.godot/.gdignore | 1 + .../.godot/global_script_class_cache.cfg | 17 ++ ....svg-218a8f2b3041327d8a5756f3a245f83b.ctex | Bin 0 -> 3434 bytes ...n.svg-218a8f2b3041327d8a5756f3a245f83b.md5 | 2 + .../assets/.godot/scene_groups_cache.cfg | 0 .../instrumented/assets/.godot/uid_cache.bin | Bin 0 -> 184 bytes .../java/app/src/instrumented/assets/icon.svg | 1 + .../src/instrumented/assets/icon.svg.import | 43 +++ .../java/app/src/instrumented/assets/main.gd | 60 ++++ .../app/src/instrumented/assets/main.gd.uid | 1 + .../app/src/instrumented/assets/main.tscn | 34 +++ .../app/src/instrumented/assets/project.godot | 26 ++ .../src/instrumented/assets/test/base_test.gd | 44 +++ .../instrumented/assets/test/base_test.gd.uid | 1 + .../java_class_wrapper_tests.gd | 136 +++++++++ .../java_class_wrapper_tests.gd.uid | 1 + .../test/GodotAppInstrumentedTestPlugin.kt | 144 ++++++++++ .../game/test/javaclasswrapper/TestClass.kt | 261 ++++++++++++++++++ .../game/test/javaclasswrapper/TestClass2.kt | 40 +++ .../game/test/javaclasswrapper/TestClass3.kt | 40 +++ .../src/instrumented/res/values/strings.xml | 4 + .../main/java/com/godot/game/GodotApp.java | 9 + 27 files changed, 971 insertions(+) create mode 100644 platform/android/java/app/src/androidTestInstrumented/java/com/godot/game/GodotAppTest.kt create mode 100644 platform/android/java/app/src/instrumented/AndroidManifest.xml create mode 100644 platform/android/java/app/src/instrumented/assets/.gitattributes create mode 100644 platform/android/java/app/src/instrumented/assets/.gitignore create mode 100644 platform/android/java/app/src/instrumented/assets/.godot/.gdignore create mode 100644 platform/android/java/app/src/instrumented/assets/.godot/global_script_class_cache.cfg create mode 100644 platform/android/java/app/src/instrumented/assets/.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex create mode 100644 platform/android/java/app/src/instrumented/assets/.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.md5 create mode 100644 platform/android/java/app/src/instrumented/assets/.godot/scene_groups_cache.cfg create mode 100644 platform/android/java/app/src/instrumented/assets/.godot/uid_cache.bin create mode 100644 platform/android/java/app/src/instrumented/assets/icon.svg create mode 100644 platform/android/java/app/src/instrumented/assets/icon.svg.import create mode 100644 platform/android/java/app/src/instrumented/assets/main.gd create mode 100644 platform/android/java/app/src/instrumented/assets/main.gd.uid create mode 100644 platform/android/java/app/src/instrumented/assets/main.tscn create mode 100644 platform/android/java/app/src/instrumented/assets/project.godot create mode 100644 platform/android/java/app/src/instrumented/assets/test/base_test.gd create mode 100644 platform/android/java/app/src/instrumented/assets/test/base_test.gd.uid create mode 100644 platform/android/java/app/src/instrumented/assets/test/javaclasswrapper/java_class_wrapper_tests.gd create mode 100644 platform/android/java/app/src/instrumented/assets/test/javaclasswrapper/java_class_wrapper_tests.gd.uid create mode 100644 platform/android/java/app/src/instrumented/java/com/godot/game/test/GodotAppInstrumentedTestPlugin.kt create mode 100644 platform/android/java/app/src/instrumented/java/com/godot/game/test/javaclasswrapper/TestClass.kt create mode 100644 platform/android/java/app/src/instrumented/java/com/godot/game/test/javaclasswrapper/TestClass2.kt create mode 100644 platform/android/java/app/src/instrumented/java/com/godot/game/test/javaclasswrapper/TestClass3.kt create mode 100644 platform/android/java/app/src/instrumented/res/values/strings.xml diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle index 5d90a495254..d75a63f101a 100644 --- a/platform/android/java/app/build.gradle +++ b/platform/android/java/app/build.gradle @@ -34,6 +34,11 @@ configurations { } dependencies { + // Android instrumented test dependencies + androidTestImplementation "androidx.test.ext:junit:1.3.0" + androidTestImplementation "androidx.test.espresso:espresso-core:3.7.0" + androidTestImplementation "org.jetbrains.kotlin:kotlin-test:1.3.11" + implementation "androidx.fragment:fragment:$versions.fragmentVersion" implementation "androidx.core:core-splashscreen:$versions.splashscreenVersion" @@ -114,6 +119,8 @@ android { targetSdkVersion getExportTargetSdkVersion() missingDimensionStrategy 'products', 'template' + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { @@ -214,10 +221,19 @@ android { flavorDimensions 'edition' productFlavors { + // Product flavor for the standard (no .net support) builds. standard { getIsDefault().set(true) } + + // Product flavor for the Mono (.net) builds. mono {} + + // Product flavor used for running instrumented tests. + instrumented { + applicationIdSuffix ".instrumented" + versionNameSuffix "-instrumented" + } } sourceSets { diff --git a/platform/android/java/app/src/androidTestInstrumented/java/com/godot/game/GodotAppTest.kt b/platform/android/java/app/src/androidTestInstrumented/java/com/godot/game/GodotAppTest.kt new file mode 100644 index 00000000000..f55954fe59b --- /dev/null +++ b/platform/android/java/app/src/androidTestInstrumented/java/com/godot/game/GodotAppTest.kt @@ -0,0 +1,76 @@ +/**************************************************************************/ +/* GodotAppTest.kt */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +package com.godot.game + +import android.util.Log +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.godot.game.test.GodotAppInstrumentedTestPlugin +import org.godotengine.godot.plugin.GodotPluginRegistry +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +/** + * This instrumented test will launch the `instrumented` version of GodotApp and run a set of tests against it. + */ +@RunWith(AndroidJUnit4::class) +class GodotAppTest { + + companion object { + private val TAG = GodotAppTest::class.java.simpleName + } + + @get:Rule + val godotAppRule = ActivityScenarioRule(GodotApp::class.java) + + /** + * Runs the JavaClassWrapper tests via the GodotAppInstrumentedTestPlugin. + */ + @Test + fun runJavaClassWrapperTests() { + val testPlugin = GodotPluginRegistry.getPluginRegistry() + .getPlugin("GodotAppInstrumentedTestPlugin") as GodotAppInstrumentedTestPlugin? + assertNotNull(testPlugin) + + Log.d(TAG, "Waiting for the Godot main loop to start...") + testPlugin.waitForGodotMainLoopStarted() + + Log.d(TAG, "Running JavaClassWrapper tests...") + val result = testPlugin.runJavaClassWrapperTests() + assertNotNull(result) + result.exceptionOrNull()?.let { throw it } + assertTrue(result.isSuccess) + Log.d(TAG, "Passed ${result.getOrNull()} tests") + } +} diff --git a/platform/android/java/app/src/instrumented/AndroidManifest.xml b/platform/android/java/app/src/instrumented/AndroidManifest.xml new file mode 100644 index 00000000000..863ec4c09fa --- /dev/null +++ b/platform/android/java/app/src/instrumented/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + + + + + diff --git a/platform/android/java/app/src/instrumented/assets/.gitattributes b/platform/android/java/app/src/instrumented/assets/.gitattributes new file mode 100644 index 00000000000..8ad74f78d9c --- /dev/null +++ b/platform/android/java/app/src/instrumented/assets/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/platform/android/java/app/src/instrumented/assets/.gitignore b/platform/android/java/app/src/instrumented/assets/.gitignore new file mode 100644 index 00000000000..a9e5f7dcd00 --- /dev/null +++ b/platform/android/java/app/src/instrumented/assets/.gitignore @@ -0,0 +1,3 @@ +# Godot 4+ specific ignores +/android/ +/.godot/editor diff --git a/platform/android/java/app/src/instrumented/assets/.godot/.gdignore b/platform/android/java/app/src/instrumented/assets/.godot/.gdignore new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/platform/android/java/app/src/instrumented/assets/.godot/.gdignore @@ -0,0 +1 @@ + diff --git a/platform/android/java/app/src/instrumented/assets/.godot/global_script_class_cache.cfg b/platform/android/java/app/src/instrumented/assets/.godot/global_script_class_cache.cfg new file mode 100644 index 00000000000..07389a50ae3 --- /dev/null +++ b/platform/android/java/app/src/instrumented/assets/.godot/global_script_class_cache.cfg @@ -0,0 +1,17 @@ +list=[{ +"base": &"RefCounted", +"class": &"BaseTest", +"icon": "", +"is_abstract": true, +"is_tool": false, +"language": &"GDScript", +"path": "res://test/base_test.gd" +}, { +"base": &"BaseTest", +"class": &"JavaClassWrapperTests", +"icon": "", +"is_abstract": false, +"is_tool": false, +"language": &"GDScript", +"path": "res://test/javaclasswrapper/java_class_wrapper_tests.gd" +}] diff --git a/platform/android/java/app/src/instrumented/assets/.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex b/platform/android/java/app/src/instrumented/assets/.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex new file mode 100644 index 0000000000000000000000000000000000000000..4650606ff3559f355c476a28843b29c1b9a0b015 GIT binary patch literal 3434 zcmV-w4VCgoQ&chm0001h0001h00000000gD|Ns900000000000000020001h0Du4h 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 Ma(2Mk0cDv50A|{t*#H0l literal 0 HcmV?d00001 diff --git a/platform/android/java/app/src/instrumented/assets/.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.md5 b/platform/android/java/app/src/instrumented/assets/.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.md5 new file mode 100644 index 00000000000..9346d82667c --- /dev/null +++ b/platform/android/java/app/src/instrumented/assets/.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.md5 @@ -0,0 +1,2 @@ +source_md5="4cdc64b13a9af63279c486903c9b54cc" +dest_md5="ddbdfc47e6405ad8d8e9e6a88a32824e" diff --git a/platform/android/java/app/src/instrumented/assets/.godot/scene_groups_cache.cfg b/platform/android/java/app/src/instrumented/assets/.godot/scene_groups_cache.cfg new file mode 100644 index 00000000000..e69de29bb2d diff --git a/platform/android/java/app/src/instrumented/assets/.godot/uid_cache.bin b/platform/android/java/app/src/instrumented/assets/.godot/uid_cache.bin new file mode 100644 index 0000000000000000000000000000000000000000..08174116434a39e575da2495602805d798f3d90c GIT binary patch literal 184 zcmZQ&U|?`?*c7yibDy+1kX@8oY^ASXl3HA%pOsjan4FVXTwGq1SWu8!1Qv(~i^M~P z;z0_F_0m(m`qVnz5SQQ)M>QfTu{ag12B_rZy^j;c#5sic;7T%+^YiqI%hDUnKBeVP ft2X0>%j70z<^i?HKVP*ad*@>hepHE);^aI4uCzbB literal 0 HcmV?d00001 diff --git a/platform/android/java/app/src/instrumented/assets/icon.svg b/platform/android/java/app/src/instrumented/assets/icon.svg new file mode 100644 index 00000000000..1640be71db9 --- /dev/null +++ b/platform/android/java/app/src/instrumented/assets/icon.svg @@ -0,0 +1 @@ + diff --git a/platform/android/java/app/src/instrumented/assets/icon.svg.import b/platform/android/java/app/src/instrumented/assets/icon.svg.import new file mode 100644 index 00000000000..2b8ba2993c8 --- /dev/null +++ b/platform/android/java/app/src/instrumented/assets/icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://srnrli5m8won" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/platform/android/java/app/src/instrumented/assets/main.gd b/platform/android/java/app/src/instrumented/assets/main.gd new file mode 100644 index 00000000000..a18811ba100 --- /dev/null +++ b/platform/android/java/app/src/instrumented/assets/main.gd @@ -0,0 +1,60 @@ +extends Node2D + +var _plugin_name = "GodotAppInstrumentedTestPlugin" +var _android_plugin + +func _ready(): + 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) + get_tree().quit() + +func _launch_tests(test_label: String) -> void: + var test_instance: BaseTest = null + match test_label: + "javaclasswrapper_tests": + test_instance = JavaClassWrapperTests.new() + + if test_instance: + test_instance.__reset_tests() + 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 _on_plugin_toast_button_pressed() -> void: + if _android_plugin: + _android_plugin.helloWorld() + +func _on_vibration_button_pressed() -> void: + var android_runtime = Engine.get_singleton("AndroidRuntime") + if android_runtime: + print("Checking if the device supports vibration") + var vibrator_service = android_runtime.getApplicationContext().getSystemService("vibrator") + if vibrator_service: + if vibrator_service.hasVibrator(): + print("Vibration is supported on device! Vibrating now...") + var VibrationEffect = JavaClassWrapper.wrap("android.os.VibrationEffect") + var effect = VibrationEffect.createOneShot(500, VibrationEffect.DEFAULT_AMPLITUDE) + vibrator_service.vibrate(effect) + else: + printerr("Vibration is not supported on device") + else: + printerr("Unable to retrieve the vibrator service") + else: + printerr("Couldn't find AndroidRuntime singleton") + +func _on_gd_script_toast_button_pressed() -> void: + var android_runtime = Engine.get_singleton("AndroidRuntime") + if android_runtime: + var activity = android_runtime.getActivity() + + var toastCallable = func (): + var ToastClass = JavaClassWrapper.wrap("android.widget.Toast") + ToastClass.makeText(activity, "Toast from GDScript", ToastClass.LENGTH_LONG).show() + + activity.runOnUiThread(android_runtime.createRunnableFromGodotCallable(toastCallable)) diff --git a/platform/android/java/app/src/instrumented/assets/main.gd.uid b/platform/android/java/app/src/instrumented/assets/main.gd.uid new file mode 100644 index 00000000000..396335dbdce --- /dev/null +++ b/platform/android/java/app/src/instrumented/assets/main.gd.uid @@ -0,0 +1 @@ +uid://bv6y7in6otgcm diff --git a/platform/android/java/app/src/instrumented/assets/main.tscn b/platform/android/java/app/src/instrumented/assets/main.tscn new file mode 100644 index 00000000000..848845bab62 --- /dev/null +++ b/platform/android/java/app/src/instrumented/assets/main.tscn @@ -0,0 +1,34 @@ +[gd_scene load_steps=2 format=3 uid="uid://cg3hylang5fxn"] + +[ext_resource type="Script" uid="uid://bv6y7in6otgcm" path="res://main.gd" id="1_j0gfq"] + +[node name="Main" type="Node2D"] +script = ExtResource("1_j0gfq") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +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"] +custom_minimum_size = Vector2(0, 50) +layout_mode = 2 +text = "Plugin Toast +" + +[node name="VibrationButton" type="Button" parent="VBoxContainer"] +custom_minimum_size = Vector2(0, 50) +layout_mode = 2 +text = "Vibration" + +[node name="GDScriptToastButton" type="Button" parent="VBoxContainer"] +custom_minimum_size = Vector2(0, 50) +layout_mode = 2 +text = "GDScript Toast +" + +[connection signal="pressed" from="VBoxContainer/PluginToastButton" to="." method="_on_plugin_toast_button_pressed"] +[connection signal="pressed" from="VBoxContainer/VibrationButton" to="." method="_on_vibration_button_pressed"] +[connection signal="pressed" from="VBoxContainer/GDScriptToastButton" to="." method="_on_gd_script_toast_button_pressed"] diff --git a/platform/android/java/app/src/instrumented/assets/project.godot b/platform/android/java/app/src/instrumented/assets/project.godot new file mode 100644 index 00000000000..f4ec2997925 --- /dev/null +++ b/platform/android/java/app/src/instrumented/assets/project.godot @@ -0,0 +1,26 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="Godot App Instrumentation Tests" +run/main_scene="res://main.tscn" +config/features=PackedStringArray("4.5", "GL Compatibility") +config/icon="res://icon.svg" + +[debug] + +settings/stdout/verbose_stdout=true + +[rendering] + +renderer/rendering_method="gl_compatibility" +renderer/rendering_method.mobile="gl_compatibility" +textures/vram_compression/import_etc2_astc=true diff --git a/platform/android/java/app/src/instrumented/assets/test/base_test.gd b/platform/android/java/app/src/instrumented/assets/test/base_test.gd new file mode 100644 index 00000000000..17a253ec974 --- /dev/null +++ b/platform/android/java/app/src/instrumented/assets/test/base_test.gd @@ -0,0 +1,44 @@ +@abstract class_name BaseTest + +var _test_started := 0 +var _test_completed := 0 +var _test_assert_passes := 0 +var _test_assert_failures := 0 + +@abstract func run_tests() + +func __exec_test(test_func: Callable): + _test_started += 1 + test_func.call() + _test_completed += 1 + +func __reset_tests(): + _test_started = 0 + _test_completed = 0 + _test_assert_passes = 0 + _test_assert_failures = 0 + +func __get_stack_frame(): + for s in get_stack(): + if not s.function.begins_with('__') and s.function != "assert_equal": + return s + return null + +func __assert_pass(): + _test_assert_passes += 1 + pass + +func __assert_fail(): + _test_assert_failures += 1 + var s = __get_stack_frame() + if s != null: + print_rich ("[color=red] == FAILURE: In function %s() from '%s' on line %s[/color]" % [s.function, s.source, s.line]) + else: + print_rich ("[color=red] == FAILURE (run with --debug to get more information!) ==[/color]") + +func assert_equal(actual, expected): + if actual == expected: + __assert_pass() + else: + __assert_fail() + print (" |-> Expected '%s' but got '%s'" % [expected, actual]) diff --git a/platform/android/java/app/src/instrumented/assets/test/base_test.gd.uid b/platform/android/java/app/src/instrumented/assets/test/base_test.gd.uid new file mode 100644 index 00000000000..e073901130e --- /dev/null +++ b/platform/android/java/app/src/instrumented/assets/test/base_test.gd.uid @@ -0,0 +1 @@ +uid://mofa8j0d801f diff --git a/platform/android/java/app/src/instrumented/assets/test/javaclasswrapper/java_class_wrapper_tests.gd b/platform/android/java/app/src/instrumented/assets/test/javaclasswrapper/java_class_wrapper_tests.gd new file mode 100644 index 00000000000..e43cda069a9 --- /dev/null +++ b/platform/android/java/app/src/instrumented/assets/test/javaclasswrapper/java_class_wrapper_tests.gd @@ -0,0 +1,136 @@ +class_name JavaClassWrapperTests +extends BaseTest + +func run_tests(): + print("JavaClassWrapper tests starting..") + + __exec_test(test_exceptions) + + __exec_test(test_multiple_signatures) + __exec_test(test_array_arguments) + __exec_test(test_array_return) + + __exec_test(test_dictionary) + + __exec_test(test_object_overload) + + __exec_test(test_variant_conversion_safe_from_stack_overflow) + + print("JavaClassWrapper tests finished.") + print("Tests started: " + str(_test_started)) + print("Tests completed: " + str(_test_completed)) + + +func test_exceptions() -> void: + var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass') + #print(TestClass.get_java_method_list()) + + assert_equal(JavaClassWrapper.get_exception(), null) + + assert_equal(TestClass.testExc(27), 0) + assert_equal(str(JavaClassWrapper.get_exception()), '') + + assert_equal(JavaClassWrapper.get_exception(), null) + +func test_multiple_signatures() -> void: + var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass') + + var ai := [1, 2] + assert_equal(TestClass.testMethod(1, ai), "IntArray: [1, 2]") + + var astr := ["abc"] + assert_equal(TestClass.testMethod(2, astr), "IntArray: [0]") + + var atstr: Array[String] = ["abc"] + assert_equal(TestClass.testMethod(3, atstr), "StringArray: [abc]") + + var TestClass2: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass2') + var aobjl: Array[Object] = [ + TestClass2.TestClass2(27), + TestClass2.TestClass2(135), + ] + assert_equal(TestClass.testMethod(3, aobjl), "testObjects: 27 135") + +func test_array_arguments() -> void: + var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass') + + assert_equal(TestClass.testArgBoolArray([true, false, true]), "[true, false, true]") + assert_equal(TestClass.testArgByteArray(PackedByteArray([1, 2, 3])), "[1, 2, 3]") + assert_equal(TestClass.testArgCharArray("abc".to_utf16_buffer()), "abc"); + assert_equal(TestClass.testArgShortArray(PackedInt32Array([27, 28, 29])), "[27, 28, 29]") + assert_equal(TestClass.testArgShortArray([27, 28, 29]), "[27, 28, 29]") + assert_equal(TestClass.testArgIntArray(PackedInt32Array([7, 8, 9])), "[7, 8, 9]") + assert_equal(TestClass.testArgIntArray([7, 8, 9]), "[7, 8, 9]") + assert_equal(TestClass.testArgLongArray(PackedInt64Array([17, 18, 19])), "[17, 18, 19]") + assert_equal(TestClass.testArgLongArray([17, 18, 19]), "[17, 18, 19]") + assert_equal(TestClass.testArgFloatArray(PackedFloat32Array([17.1, 18.2, 19.3])), "[17.1, 18.2, 19.3]") + assert_equal(TestClass.testArgFloatArray([17.1, 18.2, 19.3]), "[17.1, 18.2, 19.3]") + 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: + var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass') + #print(TestClass.get_java_method_list()) + + assert_equal(TestClass.testRetBoolArray(), [true, false, true]) + assert_equal(TestClass.testRetWrappedBoolArray(), [true, false, true]) + + assert_equal(TestClass.testRetByteArray(), PackedByteArray([1, 2, 3])) + assert_equal(TestClass.testRetWrappedByteArray(), PackedByteArray([1, 2, 3])) + + assert_equal(TestClass.testRetCharArray().get_string_from_utf16(), "abc") + assert_equal(TestClass.testRetWrappedCharArray().get_string_from_utf16(), "abc") + + assert_equal(TestClass.testRetShortArray(), PackedInt32Array([11, 12, 13])) + assert_equal(TestClass.testRetWrappedShortArray(), PackedInt32Array([11, 12, 13])) + + assert_equal(TestClass.testRetIntArray(), PackedInt32Array([21, 22, 23])) + assert_equal(TestClass.testRetWrappedIntArray(), PackedInt32Array([21, 22, 23])) + + assert_equal(TestClass.testRetLongArray(), PackedInt64Array([41, 42, 43])) + assert_equal(TestClass.testRetWrappedLongArray(), PackedInt64Array([41, 42, 43])) + + assert_equal(TestClass.testRetFloatArray(), PackedFloat32Array([31.1, 32.2, 33.3])) + assert_equal(TestClass.testRetWrappedFloatArray(), PackedFloat32Array([31.1, 32.2, 33.3])) + + assert_equal(TestClass.testRetDoubleArray(), PackedFloat64Array([41.1, 42.2, 43.3])) + assert_equal(TestClass.testRetWrappedDoubleArray(), PackedFloat64Array([41.1, 42.2, 43.3])) + + var obj_array = TestClass.testRetObjectArray() + assert_equal(str(obj_array[0]), '') + assert_equal(str(obj_array[1]), '') + + assert_equal(TestClass.testRetStringArray(), PackedStringArray(["I", "am", "String"])) + assert_equal(TestClass.testRetCharSequenceArray(), PackedStringArray(["I", "am", "CharSequence"])) + +func test_dictionary(): + 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}") + +func test_object_overload(): + 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') + + var t2 = TestClass2.TestClass2(33) + var t3 = TestClass3.TestClass3("thirty three") + + assert_equal(TestClass.testObjectOverload(t2), "TestClass2: 33") + assert_equal(TestClass.testObjectOverload(t3), "TestClass3: thirty three") + + var arr_of_t2 = [t2, TestClass2.TestClass2(34)] + var arr_of_t3 = [t3, TestClass3.TestClass3("thirty four")] + + assert_equal(TestClass.testObjectOverloadArray(arr_of_t2), "TestClass2: [33, 34]") + assert_equal(TestClass.testObjectOverloadArray(arr_of_t3), "TestClass3: [thirty three, thirty four]") + +func test_variant_conversion_safe_from_stack_overflow(): + 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) diff --git a/platform/android/java/app/src/instrumented/assets/test/javaclasswrapper/java_class_wrapper_tests.gd.uid b/platform/android/java/app/src/instrumented/assets/test/javaclasswrapper/java_class_wrapper_tests.gd.uid new file mode 100644 index 00000000000..93a2abe0e43 --- /dev/null +++ b/platform/android/java/app/src/instrumented/assets/test/javaclasswrapper/java_class_wrapper_tests.gd.uid @@ -0,0 +1 @@ +uid://3ql82ggk41xc diff --git a/platform/android/java/app/src/instrumented/java/com/godot/game/test/GodotAppInstrumentedTestPlugin.kt b/platform/android/java/app/src/instrumented/java/com/godot/game/test/GodotAppInstrumentedTestPlugin.kt new file mode 100644 index 00000000000..2fc9eecfe65 --- /dev/null +++ b/platform/android/java/app/src/instrumented/java/com/godot/game/test/GodotAppInstrumentedTestPlugin.kt @@ -0,0 +1,144 @@ +/**************************************************************************/ +/* GodotAppInstrumentedTestPlugin.kt */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +package com.godot.game.test + +import android.util.Log +import android.widget.Toast +import org.godotengine.godot.Godot +import org.godotengine.godot.plugin.GodotPlugin +import org.godotengine.godot.plugin.UsedByGodot +import org.godotengine.godot.plugin.SignalInfo +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.CountDownLatch + +/** + * [GodotPlugin] used to drive instrumented tests. + */ +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 JAVACLASSWRAPPER_TESTS = "javaclasswrapper_tests" + + private val LAUNCH_TESTS_SIGNAL = SignalInfo("launch_tests", String::class.java) + + private val SIGNALS = setOf( + LAUNCH_TESTS_SIGNAL + ) + } + + private val testResults = ConcurrentHashMap>() + private val latches = ConcurrentHashMap() + + init { + // 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) + } + + override fun getPluginName() = "GodotAppInstrumentedTestPlugin" + + override fun getPluginSignals() = SIGNALS + + override fun onGodotMainLoopStarted() { + super.onGodotMainLoopStarted() + latches.remove(MAIN_LOOP_STARTED_LATCH_KEY)?.countDown() + } + + /** + * Used by the instrumented test to wait until the Godot main loop is up and running. + */ + internal fun waitForGodotMainLoopStarted() { + // Wait on the CountDownLatch for `onGodotMainLoopStarted` + try { + latches[MAIN_LOOP_STARTED_LATCH_KEY]?.await() + } catch (e: InterruptedException) { + Log.e(TAG, "Unable to wait for Godot main loop started event.", e) + } + } + + /** + * This launches the JavaClassWrapper tests, and wait until the tests are complete before returning. + */ + internal fun runJavaClassWrapperTests(): Result? { + return launchTests(JAVACLASSWRAPPER_TESTS) + } + + private fun launchTests(testLabel: String): Result? { + val latch = latches.getOrPut(testLabel) { CountDownLatch(1) } + emitSignal(LAUNCH_TESTS_SIGNAL.name, testLabel) + return try { + latch.await() + val result = testResults.remove(testLabel) + result + } catch (e: InterruptedException) { + Log.e(TAG, "Unable to wait for completion for $testLabel", e) + null + } + } + + /** + * Callback invoked from gdscript when the tests are completed. + */ + @UsedByGodot + fun onTestsCompleted(testLabel: String, passes: Int, failures: Int) { + Log.d(TAG, "$testLabel tests completed") + val result = if (failures == 0) { + Result.success(passes) + } else { + Result.failure(AssertionError("$failures tests failed!")) + } + + completeTest(testLabel, result) + } + + @UsedByGodot + fun onTestsFailed(testLabel: String, failureMessage: String) { + Log.d(TAG, "$testLabel tests failed") + val result: Result = Result.failure(AssertionError(failureMessage)) + completeTest(testLabel, result) + } + + private fun completeTest(testKey: String, result: Result) { + testResults[testKey] = result + latches.remove(testKey)?.countDown() + } + + @UsedByGodot + fun helloWorld() { + runOnHostThread { + Toast.makeText(activity, "Toast from Android plugin", Toast.LENGTH_LONG).show() + Log.v(pluginName, "Hello World") + } + } +} diff --git a/platform/android/java/app/src/instrumented/java/com/godot/game/test/javaclasswrapper/TestClass.kt b/platform/android/java/app/src/instrumented/java/com/godot/game/test/javaclasswrapper/TestClass.kt new file mode 100644 index 00000000000..8bb677939a9 --- /dev/null +++ b/platform/android/java/app/src/instrumented/java/com/godot/game/test/javaclasswrapper/TestClass.kt @@ -0,0 +1,261 @@ +/**************************************************************************/ +/* TestClass.kt */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +package com.godot.game.test.javaclasswrapper + +import org.godotengine.godot.Dictionary +import kotlin.collections.contentToString +import kotlin.collections.joinToString + +class TestClass { + companion object { + @JvmStatic + fun stringify(value: Any?): String { + return when (value) { + null -> "null" + is Map<*, *> -> { + val entries = value.entries.joinToString(", ") { (k, v) -> "${stringify(k)}: ${stringify(v)}" } + "{$entries}" + } + + is List<*> -> value.joinToString(prefix = "[", postfix = "]") { stringify(it) } + is Array<*> -> value.joinToString(prefix = "[", postfix = "]") { stringify(it) } + is IntArray -> value.joinToString(prefix = "[", postfix = "]") + is LongArray -> value.joinToString(prefix = "[", postfix = "]") + is FloatArray -> value.joinToString(prefix = "[", postfix = "]") + is DoubleArray -> value.joinToString(prefix = "[", postfix = "]") + is BooleanArray -> value.joinToString(prefix = "[", postfix = "]") + is CharArray -> value.joinToString(prefix = "[", postfix = "]") + else -> value.toString() + } + } + + @JvmStatic + fun testDictionary(d: Dictionary): String { + return d.toString() + } + + @JvmStatic + fun testDictionaryNested(d: Dictionary): String { + return stringify(d) + } + + @JvmStatic + fun testRetDictionary(): Dictionary { + var d = Dictionary() + d.putAll(mapOf("a" to 1, "b" to 2)) + return d + } + + @JvmStatic + fun testRetDictionaryArray(): Array { + var d = Dictionary() + d.putAll(mapOf("a" to 1, "b" to 2)) + return arrayOf(d) + } + + @JvmStatic + fun testMethod(int: Int, array: IntArray): String { + return "IntArray: " + array.contentToString() + } + + @JvmStatic + fun testMethod(int: Int, vararg args: String): String { + return "StringArray: " + args.contentToString() + } + + @JvmStatic + fun testMethod(int: Int, objects: Array): String { + return "testObjects: " + objects.joinToString(separator = " ") { it.getValue().toString() } + } + + @JvmStatic + fun testExc(i: Int): Int { + val s: String? = null + s!!.length + return i + } + + @JvmStatic + fun testArgBoolArray(a: BooleanArray): String { + return a.contentToString(); + } + + @JvmStatic + fun testArgByteArray(a: ByteArray): String { + return a.contentToString(); + } + + @JvmStatic + fun testArgCharArray(a: CharArray): String { + return a.joinToString("") + } + + @JvmStatic + fun testArgShortArray(a: ShortArray): String { + return a.contentToString(); + } + + @JvmStatic + fun testArgIntArray(a: IntArray): String { + return a.contentToString(); + } + + @JvmStatic + fun testArgLongArray(a: LongArray): String { + return a.contentToString(); + } + + @JvmStatic + fun testArgFloatArray(a: FloatArray): String { + return a.contentToString(); + } + + @JvmStatic + fun testArgDoubleArray(a: DoubleArray): String { + return a.contentToString(); + } + + @JvmStatic + fun testRetBoolArray(): BooleanArray { + return booleanArrayOf(true, false, true) + } + + @JvmStatic + fun testRetByteArray(): ByteArray { + return byteArrayOf(1, 2, 3) + } + + @JvmStatic + fun testRetCharArray(): CharArray { + return "abc".toCharArray() + } + + @JvmStatic + fun testRetShortArray(): ShortArray { + return shortArrayOf(11, 12, 13) + } + + @JvmStatic + fun testRetIntArray(): IntArray { + return intArrayOf(21, 22, 23) + } + + @JvmStatic + fun testRetLongArray(): LongArray { + return longArrayOf(41, 42, 43) + } + + @JvmStatic + fun testRetFloatArray(): FloatArray { + return floatArrayOf(31.1f, 32.2f, 33.3f) + } + + @JvmStatic + fun testRetDoubleArray(): DoubleArray { + return doubleArrayOf(41.1, 42.2, 43.3) + } + + @JvmStatic + fun testRetWrappedBoolArray(): Array { + return arrayOf(true, false, true) + } + + @JvmStatic + fun testRetWrappedByteArray(): Array { + return arrayOf(1, 2, 3) + } + + @JvmStatic + fun testRetWrappedCharArray(): Array { + return arrayOf('a', 'b', 'c') + } + + @JvmStatic + fun testRetWrappedShortArray(): Array { + return arrayOf(11, 12, 13) + } + + @JvmStatic + fun testRetWrappedIntArray(): Array { + return arrayOf(21, 22, 23) + } + + @JvmStatic + fun testRetWrappedLongArray(): Array { + return arrayOf(41, 42, 43) + } + + @JvmStatic + fun testRetWrappedFloatArray(): Array { + return arrayOf(31.1f, 32.2f, 33.3f) + } + + @JvmStatic + fun testRetWrappedDoubleArray(): Array { + return arrayOf(41.1, 42.2, 43.3) + } + + @JvmStatic + fun testRetObjectArray(): Array { + return arrayOf(TestClass2(51), TestClass2(52)); + } + + @JvmStatic + fun testRetStringArray(): Array { + return arrayOf("I", "am", "String") + } + + @JvmStatic + fun testRetCharSequenceArray(): Array { + return arrayOf("I", "am", "CharSequence") + } + + @JvmStatic + fun testObjectOverload(a: TestClass2): String { + return "TestClass2: $a" + } + + @JvmStatic + fun testObjectOverload(a: TestClass3): String { + return "TestClass3: $a" + } + + @JvmStatic + fun testObjectOverloadArray(a: Array): String { + return "TestClass2: " + a.contentToString() + } + + @JvmStatic + fun testObjectOverloadArray(a: Array): String { + return "TestClass3: " + a.contentToString() + } + } +} diff --git a/platform/android/java/app/src/instrumented/java/com/godot/game/test/javaclasswrapper/TestClass2.kt b/platform/android/java/app/src/instrumented/java/com/godot/game/test/javaclasswrapper/TestClass2.kt new file mode 100644 index 00000000000..a44a3e4e302 --- /dev/null +++ b/platform/android/java/app/src/instrumented/java/com/godot/game/test/javaclasswrapper/TestClass2.kt @@ -0,0 +1,40 @@ +/**************************************************************************/ +/* TestClass2.kt */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +package com.godot.game.test.javaclasswrapper + +class TestClass2(private val value: Int) { + fun getValue(): Int { + return value + } + override fun toString(): String { + return value.toString() + } +} diff --git a/platform/android/java/app/src/instrumented/java/com/godot/game/test/javaclasswrapper/TestClass3.kt b/platform/android/java/app/src/instrumented/java/com/godot/game/test/javaclasswrapper/TestClass3.kt new file mode 100644 index 00000000000..b7ea77bfd84 --- /dev/null +++ b/platform/android/java/app/src/instrumented/java/com/godot/game/test/javaclasswrapper/TestClass3.kt @@ -0,0 +1,40 @@ +/**************************************************************************/ +/* TestClass3.kt */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +package com.godot.game.test.javaclasswrapper + +class TestClass3(private val value: String) { + fun getValue(): String { + return value + } + override fun toString(): String { + return value + } +} diff --git a/platform/android/java/app/src/instrumented/res/values/strings.xml b/platform/android/java/app/src/instrumented/res/values/strings.xml new file mode 100644 index 00000000000..326dc5dc64b --- /dev/null +++ b/platform/android/java/app/src/instrumented/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Godot App Instrumented Tests + diff --git a/platform/android/java/app/src/main/java/com/godot/game/GodotApp.java b/platform/android/java/app/src/main/java/com/godot/game/GodotApp.java index dac80282726..4bd7359b8a8 100644 --- a/platform/android/java/app/src/main/java/com/godot/game/GodotApp.java +++ b/platform/android/java/app/src/main/java/com/godot/game/GodotApp.java @@ -83,4 +83,13 @@ public class GodotApp extends GodotActivity { super.onGodotMainLoopStarted(); runOnUiThread(updateWindowAppearance); } + + @Override + public void onGodotForceQuit(Godot instance) { + if (!BuildConfig.FLAVOR.equals("instrumented")) { + // For instrumented builds, we disable force-quitting to allow the instrumented tests to complete + // successfully, otherwise they fail when the process crashes. + super.onGodotForceQuit(instance); + } + } }