diff --git a/source/scripts/csharp/CMakeLists.txt b/source/scripts/csharp/CMakeLists.txt index b2044aaa1..b5d339a24 100644 --- a/source/scripts/csharp/CMakeLists.txt +++ b/source/scripts/csharp/CMakeLists.txt @@ -15,4 +15,6 @@ include(CSharpProject) add_subdirectory(hello) add_subdirectory(static) +add_subdirectory(class) add_subdirectory(function) + diff --git a/source/scripts/csharp/class/CMakeLists.txt b/source/scripts/csharp/class/CMakeLists.txt new file mode 100644 index 000000000..e2e6383f4 --- /dev/null +++ b/source/scripts/csharp/class/CMakeLists.txt @@ -0,0 +1,20 @@ +# +# External dependencies +# + +find_package(DotNET) + +if(NOT DOTNET_FOUND) + message(SEND_ERROR "DotNET command not found") + return() +endif() + +if(DOTNET_VERSION VERSION_LESS "5.0") + return() +endif() + +# +# Configure csharp project +# + +cs_project(class 0.1.0) diff --git a/source/scripts/csharp/class/source/class.cs b/source/scripts/csharp/class/source/class.cs new file mode 100644 index 000000000..7f918f1ec --- /dev/null +++ b/source/scripts/csharp/class/source/class.cs @@ -0,0 +1,21 @@ +public class Counter +{ + public int count; // attribute + public static int total; // static attribute + + public Counter(int start) // constructor + { + this.count = start; + } + + public int Add(int value) // instance method + { + this.count += value; + return this.count; + } + + public static int Twice(int value) // static method + { + return value * 2; + } +} diff --git a/source/tests/CMakeLists.txt b/source/tests/CMakeLists.txt index 84d0f85fe..42a03f4ef 100644 --- a/source/tests/CMakeLists.txt +++ b/source/tests/CMakeLists.txt @@ -219,6 +219,7 @@ add_subdirectory(metacall_llvm_test) add_subdirectory(metacall_ruby_test) add_subdirectory(metacall_cs_test) add_subdirectory(metacall_csharp_static_class_test) +add_subdirectory(metacall_csharp_class_test) add_subdirectory(metacall_csharp_function_test) add_subdirectory(metacall_csharp_empty_configuration_test) add_subdirectory(metacall_csharp_invalid_configuration_test) diff --git a/source/tests/metacall_csharp_class_test/CMakeLists.txt b/source/tests/metacall_csharp_class_test/CMakeLists.txt new file mode 100644 index 000000000..75cbd66d5 --- /dev/null +++ b/source/tests/metacall_csharp_class_test/CMakeLists.txt @@ -0,0 +1,192 @@ +# Check if this loader is enabled +if(NOT OPTION_BUILD_LOADERS OR NOT OPTION_BUILD_LOADERS_CS OR NOT OPTION_BUILD_SCRIPTS OR NOT OPTION_BUILD_SCRIPTS_CS) + return() +endif() + +# +# External dependencies +# + +find_package(DotNET) + +if(NOT DOTNET_FOUND) + message(SEND_ERROR "DotNET command not found") + return() +endif() + +if(DOTNET_VERSION VERSION_LESS "5.0") + return() +endif() + +# +# Executable name and options +# + +# Target name +set(target metacall-csharp-class-test) +message(STATUS "Test ${target}") + +# +# Compiler warnings +# + +include(Warnings) + +# +# Compiler security +# + +include(SecurityFlags) + +# +# Sources +# + +set(include_path "${CMAKE_CURRENT_SOURCE_DIR}/include/${target}") +set(source_path "${CMAKE_CURRENT_SOURCE_DIR}/source") + +set(sources + ${source_path}/main.cpp + ${source_path}/metacall_csharp_class_test.cpp +) + +# Group source files +set(header_group "Header Files (API)") +set(source_group "Source Files") +source_group_by_path(${include_path} "\\\\.h$|\\\\.hpp$" + ${header_group} ${headers}) +source_group_by_path(${source_path} "\\\\.cpp$|\\\\.c$|\\\\.h$|\\\\.hpp$" + ${source_group} ${sources}) + +# +# Create executable +# + +# Build executable +add_executable(${target} + ${sources} +) + +# Create namespaced alias +add_executable(${META_PROJECT_NAME}::${target} ALIAS ${target}) + +# +# Project options +# + +set_target_properties(${target} + PROPERTIES + ${DEFAULT_PROJECT_OPTIONS} + FOLDER "${IDE_FOLDER}" +) + +# +# Include directories +# + +target_include_directories(${target} + PRIVATE + ${DEFAULT_INCLUDE_DIRECTORIES} + ${PROJECT_BINARY_DIR}/source/include +) + +# +# Libraries +# + +target_link_libraries(${target} + PRIVATE + ${DEFAULT_LIBRARIES} + + GTest + + ${META_PROJECT_NAME}::metacall +) + +# +# Compile definitions +# + +target_compile_definitions(${target} + PRIVATE + ${DEFAULT_COMPILE_DEFINITIONS} +) + +# +# Compile options +# + +target_compile_options(${target} + PRIVATE + ${DEFAULT_COMPILE_OPTIONS} +) + +# +# Compile features +# + +target_compile_features(${target} + PRIVATE + cxx_std_17 +) + +# +# Linker options +# + +target_link_options(${target} + PRIVATE + ${DEFAULT_LINKER_OPTIONS} +) + +# +# Define test +# + +if(OPTION_BUILD_THREAD_SANITIZER) + find_package(DotNET) + check_tsan_executable("${DOTNET_CORE_LIBRARY}" DotNET_TSAN) + if(NOT DotNET_TSAN) + # This test fails when run with thread sanitizer due to C# when CoreCLR is not compiled with TSAN: + # coreclr_initialize status (0x8007ff0b) + # For solving this, we should enable C# support for sanitizers and debug it properly + return() + endif() +endif() + +if(OPTION_TEST_MEMORYCHECK) + # C# NetCore instrumentation for Valgrind is not supported right now on MetaCall + # and we do not have a way to check if is instrumented or not checking the library + # so we skip directly the test to avoid false positives + return() +endif() + +add_test(NAME ${target} + COMMAND $ +) + +# +# Define dependencies +# + +add_dependencies(${target} + cs_loader +) + +# +# Define test properties +# + +set_property(TEST ${target} + PROPERTY LABELS ${target} + + # TODO: Valgrind also fails with C# + MEMCHECK_IGNORE +) + +include(TestEnvironmentVariables) + +test_environment_variables(${target} + "" + ${TESTS_ENVIRONMENT_VARIABLES} +) diff --git a/source/tests/metacall_csharp_class_test/source/main.cpp b/source/tests/metacall_csharp_class_test/source/main.cpp new file mode 100644 index 000000000..ae0fb5321 --- /dev/null +++ b/source/tests/metacall_csharp_class_test/source/main.cpp @@ -0,0 +1,28 @@ +/* + * MetaCall Library by Parra Studios + * A library for providing a foreign function interface calls. + * + * Copyright (C) 2016 - 2026 Vicente Eduardo Ferrer Garcia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include + +int main(int argc, char *argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +} diff --git a/source/tests/metacall_csharp_class_test/source/metacall_csharp_class_test.cpp b/source/tests/metacall_csharp_class_test/source/metacall_csharp_class_test.cpp new file mode 100644 index 000000000..31eaa6cd1 --- /dev/null +++ b/source/tests/metacall_csharp_class_test/source/metacall_csharp_class_test.cpp @@ -0,0 +1,130 @@ +/* + * MetaCall Library by Parra Studios + * A library for providing a foreign function interface calls. + * + * Copyright (C) 2016 - 2026 Vicente Eduardo Ferrer Garcia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include + +#include +#include +#include + +#include +#include + +class metacall_csharp_class_test : public testing::Test +{ +public: +}; + +TEST_F(metacall_csharp_class_test, DefaultConstructor) +{ + metacall_print_info(); + + ASSERT_EQ((int)0, (int)metacall_initialize()); + + /* C# Netcore */ +#if defined(OPTION_BUILD_LOADERS_CS) + { + const char *cs_scripts[] = { + "class.cs" + }; + + ASSERT_EQ((int)0, (int)metacall_load_from_file("cs", cs_scripts, sizeof(cs_scripts) / sizeof(cs_scripts[0]), NULL)); + + /* Get the class handle */ + void *myclass = metacall_class("Counter"); + ASSERT_NE((void *)NULL, (void *)myclass); + + /* Construct: new Counter(5) */ + void *constructor_params[] = { + metacall_value_create_int(5) + }; + + void *new_object_v = metacall_class_new(myclass, "Counter", constructor_params, sizeof(constructor_params) / sizeof(constructor_params[0])); + ASSERT_NE((void *)NULL, (void *)new_object_v); + + void *new_object = metacall_value_to_object(new_object_v); + + /* Read the instance attribute: count == 5 */ + { + void *count = metacall_object_get(new_object, "count"); + EXPECT_EQ((int)5, (int)metacall_value_to_int(count)); + metacall_value_destroy(count); + } + + /* Invoke the instance method: Add(7) == 12 */ + { + void *args[] = { + metacall_value_create_int(7) + }; + + void *ret = metacallt_object(new_object, "Add", METACALL_INT, args, sizeof(args) / sizeof(args[0])); + EXPECT_EQ((int)12, (int)metacall_value_to_int(ret)); + metacall_value_destroy(ret); + } + + /* Invoke the static method: Counter.Twice(21) == 42 */ + { + void *args[] = { + metacall_value_create_int(21) + }; + + void *ret = metacallt_class(myclass, "Twice", METACALL_INT, args, sizeof(args) / sizeof(args[0])); + EXPECT_EQ((int)42, (int)metacall_value_to_int(ret)); + metacall_value_destroy(ret); + } + + /* Static attribute: Counter.total set/get == 99 */ + { + void *val = metacall_value_create_int(99); + ASSERT_EQ((int)0, (int)metacall_class_static_set(myclass, "total", val)); + metacall_value_destroy(val); + + void *ret = metacall_class_static_get(myclass, "total"); + EXPECT_EQ((int)99, (int)metacall_value_to_int(ret)); + metacall_value_destroy(ret); + } + + metacall_value_destroy(new_object_v); + } +#endif /* OPTION_BUILD_LOADERS_CS */ + + /* Print inspect information */ + { + size_t size = 0; + + struct metacall_allocator_std_type std_ctx = { &std::malloc, &std::realloc, &std::free }; + + void *allocator = metacall_allocator_create(METACALL_ALLOCATOR_STD, (void *)&std_ctx); + + char *inspect_str = metacall_inspect(&size, allocator); + + EXPECT_NE((char *)NULL, (char *)inspect_str); + + EXPECT_GT((size_t)size, (size_t)0); + + std::cout << inspect_str << std::endl; + + metacall_allocator_free(allocator, inspect_str); + + metacall_allocator_destroy(allocator); + } + + metacall_destroy(); +}