diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index eb0b3103..0bda0444 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -14,11 +14,9 @@ jobs: fail-fast: false matrix: config: - # - {os: macos-latest, r: 'release'} + - {os: macos-latest, r: 'release'} - {os: windows-latest, r: 'release'} - - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} - {os: ubuntu-latest, r: 'release'} - # - {os: ubuntu-latest, r: 'oldrel-1'} env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} @@ -26,24 +24,22 @@ jobs: steps: - uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} - uses: r-lib/actions/setup-pandoc@v2 + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 8.0.x + - uses: r-lib/actions/setup-r@v2 with: r-version: ${{ matrix.config.r }} http-user-agent: ${{ matrix.config.http-user-agent }} use-public-rspm: true - - name: install ubuntu dependencies - if: runner.os == 'Linux' - run: | - sudo apt-get update - sudo apt-get install dotnet-runtime-8.0 libcurl4-openssl-dev libssl-dev libxml2-dev - sudo apt-get install libfontconfig1-dev libharfbuzz-dev libfribidi-dev - sudo apt-get install libfreetype6-dev libpng-dev libtiff5-dev libjpeg-dev - sudo ln -s /usr/lib/x86_64-linux-gnu/libdl.so.2 /usr/lib/x86_64-linux-gnu/libdl.so - - uses: r-lib/actions/setup-r-dependencies@v2 with: extra-packages: any::rcmdcheck @@ -67,7 +63,7 @@ jobs: - name: Get package version from DESCRIPTION file and set as environment variable run: | - echo "PKG_VERSION=$(grep -oP '(?<=Version: )\d+\.\d+\.\d+\.*\d*' DESCRIPTION)" >> $GITHUB_ENV + echo "PKG_VERSION=$(grep '^Version: ' DESCRIPTION | sed -E 's/^Version: ([0-9]+\.[0-9]+\.[0-9]+\.?[0-9]*)/\1/')" >> $GITHUB_ENV echo "$PKG_VERSION" shell: bash diff --git a/.github/workflows/build-c#.yaml b/.github/workflows/build-c#.yaml index 53cf8c28..40bc5eaa 100644 --- a/.github/workflows/build-c#.yaml +++ b/.github/workflows/build-c#.yaml @@ -6,39 +6,76 @@ on: jobs: Linux-Build: - - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} permissions: contents: write steps: - uses: actions/checkout@v4 - if: ${{ github.event_name == 'pull_request' }} with: repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.event.pull_request.head.ref }} - - uses: actions/checkout@v4 - if: ${{ github.event_name != 'pull_request' }} - - name: Setup .NET uses: actions/setup-dotnet@v1 with: dotnet-version: 8.0.x + - name: Setup R + uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + - name: Make working-directory: ./shared run: make + - name: Test C# binaries + run: | + export R_HOME=/usr/lib/R + dotnet test ./shared/RDotNet.Tests/ -c Release -d log.txt --no-build + dotnet test ./shared/rSharpTests/ -c Release -d log.txt --no-build + dotnet test ./shared/DynamicInterop.Tests/ -c Release -d log.txt --no-build + + - uses: EndBug/add-and-commit@v9 + if: ${{ success() }} + with: + pull: '--verbose' + message: 'Update Linux C# binaries (Commit from Github Actions).' + default_author: github_actions + add: '*linux.so' + + + macOS-Build: + runs-on: macos-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.ref }} + + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 8.0.x + - name: Setup R uses: r-lib/actions/setup-r@v2 with: use-public-rspm: true + - name: Make + working-directory: ./shared + run: make + - name: Test C# binaries run: | - export R_HOME=/usr/lib/R + export R_HOME=/Library/Frameworks/R.framework/Resources dotnet test ./shared/RDotNet.Tests/ -c Release -d log.txt --no-build dotnet test ./shared/rSharpTests/ -c Release -d log.txt --no-build dotnet test ./shared/DynamicInterop.Tests/ -c Release -d log.txt --no-build @@ -46,12 +83,13 @@ jobs: - uses: EndBug/add-and-commit@v9 if: ${{ success() }} with: - message: 'Update Linux C# binaries (Commit from Github Actions).' + pull: '--verbose' + message: 'Update macOS C# binaries (Commit from Github Actions).' default_author: github_actions - add: '*.so' + add: '*mac.so' + Windows-Build: - needs: Linux-Build runs-on: windows-latest env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} @@ -59,14 +97,10 @@ jobs: contents: write steps: - uses: actions/checkout@v4 - if: ${{ github.event_name == 'pull_request' }} with: repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.event.pull_request.head.ref }} - - uses: actions/checkout@v4 - if: ${{ github.event_name != 'pull_request' }} - - name: Setup .NET uses: actions/setup-dotnet@v1 with: @@ -77,7 +111,6 @@ jobs: with: use-public-rspm: true - - name: Install dependencies run: | nuget restore ./shared/packages.config -PackagesDirectory ./shared/packages @@ -97,10 +130,10 @@ jobs: dotnet test .\shared\rSharpTests\ -c Release -d log.txt --no-build dotnet test .\shared\DynamicInterop.Tests\ -c Release -d log.txt --no-build - - uses: EndBug/add-and-commit@v9 if: ${{ success() }} with: + pull: '--verbose' message: 'Update Windows C# binaries (Commit from Github Actions).' default_author: github_actions add: '*.dll' diff --git a/.github/workflows/pr-workflow.yaml b/.github/workflows/main-workflow.yaml similarity index 50% rename from .github/workflows/pr-workflow.yaml rename to .github/workflows/main-workflow.yaml index e5efa4c1..c5ff2345 100644 --- a/.github/workflows/pr-workflow.yaml +++ b/.github/workflows/main-workflow.yaml @@ -1,13 +1,17 @@ name: PR-Workflow on: + push: + branches: [main] pull_request: workflow_dispatch: - jobs: + + # Detect changes done in C# code base. If no change were done, skim "build-Csharp-binaries" workflow. - changes: + detect-changes: + if: github.event_name == 'pull_request' runs-on: ubuntu-latest permissions: pull-requests: read @@ -17,23 +21,37 @@ jobs: - uses: dorny/paths-filter@v3 id: filter with: + base: ${{ github.ref }} filters: | Csharp: - 'shared/**' build-Csharp-binaries: - needs: changes - if: ${{ needs.changes.outputs.Csharp == 'true' }} + needs: detect-changes + if: ${{ needs.detect-changes.outputs.Csharp == 'true' && github.event_name == 'pull_request'}} uses: ./.github/workflows/build-c#.yaml - R-CMD-Check: - if: ${{ always() }} + + # Automatically bump dev version when a it is a push to main branch + bump-dev-version: needs: build-Csharp-binaries + if: github.event_name != 'pull_request' # only when merging in main/develop branch + uses: Open-Systems-Pharmacology/Workflows/.github/workflows/bump_dev_version_tag_branch.yaml@main + with: + app-id: ${{ vars.VERSION_BUMPER_APPID }} + secrets: + private-key: ${{ secrets.VERSION_BUMPER_SECRET }} + + R-CMD-Check: + if: ${{ !cancelled() }} + needs: bump-dev-version uses: ./.github/workflows/R-CMD-check.yaml + test-coverage: - if: ${{ always() }} + if: ${{ !cancelled() }} needs: [R-CMD-Check] uses: ./.github/workflows/test-coverage.yaml + pkgdown: - if: ${{ always() }} + if: ${{ !cancelled() }} needs: [R-CMD-Check] uses: ./.github/workflows/pkgdown.yaml diff --git a/.github/workflows/merge-workflow.yaml b/.github/workflows/merge-workflow.yaml deleted file mode 100644 index da2e0279..00000000 --- a/.github/workflows/merge-workflow.yaml +++ /dev/null @@ -1,18 +0,0 @@ -name: Merge Workflow - -on: - push: - branches: - - main - workflow_dispatch: - - -jobs: - R-CMD-Check: - uses: ./.github/workflows/R-CMD-check.yaml - test-coverage: - needs: [R-CMD-Check] - uses: ./.github/workflows/test-coverage.yaml - pkgdown: - needs: [R-CMD-Check] - uses: ./.github/workflows/pkgdown.yaml diff --git a/NEWS.md b/NEWS.md index f162d86e..98c3b388 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,10 @@ # rSharp (development version) +## Major changes + +- rSharp is now compatible with macOS (tested on ARM) + + # rSharp 1.0.1 ## Minor improvements and bug fixes diff --git a/R/rSharp-env.R b/R/rSharp-env.R index fc121ef5..86c5727c 100644 --- a/R/rSharp-env.R +++ b/R/rSharp-env.R @@ -1,49 +1,57 @@ -# Environment that holds various global variables and settings for the package. -# It is not exported and should not be directly manipulated by other packages. -rSharpEnv <- new.env(parent = emptyenv()) - -# name of the package. This will be used to retrieve information on the package at run time -rSharpEnv$packageName <- "rSharp" -# Name of the C++ redistributable library -rSharpEnv$msvcrFileName <- "msvcp140.dll" -# The name of the native (C++) library -rSharpEnv$nativePkgName <- "rSharp" -# Name of the .NET library -rSharpEnv$dotnetPkgName <- "ClrFacade" - -# Full type name of the main facade to the interop code written in C# -rSharpEnv$clrFacadeTypeName <- "ClrFacade.ClrFacade" -# Full type name of the test cases -rSharpEnv$testCasesTypeName <- "ClrFacade.TestCases" -# Full name of test object class -rSharpEnv$testObjectTypeName <- "ClrFacade.TestObject" -rSharpEnv$testMethodBindingTypeName <- "ClrFacade.TestMethodBinding" - - -#' Names of the settings stored in rSharpEnv Can be used with `getRSharpSetting()` -#' @export -rSharpSettingNames <- names(rSharpEnv) - -#' @title getRSharpSetting -#' @description Get the value of a global rSharp setting. -#' -#' @param settingName String name of the setting -#' -#' @return Value of the setting stored in rSharpEnv. If the setting does not exist, an error is thrown. -#' @export -#' -#' @examples -#' getRSharpSetting("nativePkgName") -getRSharpSetting <- function(settingName) { - if (!(any(names(rSharpEnv) == settingName))) { - stop(messages$errorPackageSettingNotFound(settingName, rSharpEnv)) - } - - obj <- rSharpEnv[[settingName]] - # Evaluate if the object is a function. This is required since some properties are defined as function reference - if (is.function(obj)) { - return(obj()) - } - - return(obj) -} +# Environment that holds various global variables and settings for the package. +# It is not exported and should not be directly manipulated by other packages. +rSharpEnv <- new.env(parent = emptyenv()) + +# name of the package. This will be used to retrieve information on the package at run time +rSharpEnv$packageName <- "rSharp" +# Name of the C++ redistributable library +rSharpEnv$msvcrFileName <- "msvcp140.dll" +# The name of the package +rSharpEnv$pkgName <- "rSharp" + +# The name of the native (C++) library +rSharpEnv$nativePkgName <- + switch(Sys.info()[['sysname']], + Windows = rSharpEnv$pkgName, + Linux = {paste0(rSharpEnv$pkgName, ".linux")}, + Darwin = {paste0(rSharpEnv$pkgName,".mac")}) + +# Name of the .NET library +rSharpEnv$dotnetPkgName <- "ClrFacade" + +# Full type name of the main facade to the interop code written in C# +rSharpEnv$clrFacadeTypeName <- "ClrFacade.ClrFacade" +# Full type name of the test cases +rSharpEnv$testCasesTypeName <- "ClrFacade.TestCases" +# Full name of test object class +rSharpEnv$testObjectTypeName <- "ClrFacade.TestObject" +rSharpEnv$testMethodBindingTypeName <- "ClrFacade.TestMethodBinding" + + +#' Names of the settings stored in rSharpEnv Can be used with `getRSharpSetting()` +#' @export +rSharpSettingNames <- names(rSharpEnv) + +#' @title getRSharpSetting +#' @description Get the value of a global rSharp setting. +#' +#' @param settingName String name of the setting +#' +#' @return Value of the setting stored in rSharpEnv. If the setting does not exist, an error is thrown. +#' @export +#' +#' @examples +#' getRSharpSetting("nativePkgName") +getRSharpSetting <- function(settingName) { + if (!(any(names(rSharpEnv) == settingName))) { + stop(messages$errorPackageSettingNotFound(settingName, rSharpEnv)) + } + + obj <- rSharpEnv[[settingName]] + # Evaluate if the object is a function. This is required since some properties are defined as function reference + if (is.function(obj)) { + return(obj()) + } + + return(obj) +} diff --git a/R/zzz.R b/R/zzz.R index 2500af6a..3dc758d2 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -1,40 +1,41 @@ -.onLoad <- function(...) { # nocov start - # Check for C++ distrib availability - if (.Platform$OS.type == "windows") { - if (Sys.which(rSharpEnv$msvcrFileName) == "") { - stop(paste(rSharpEnv$msvcrFileName, "was not found on this Windows system.", - "You are probably missing the Visual C++ Redistributable.", - "Go to https://learn.microsoft.com/en-US/cpp/windows/latest-supported-vc-redist?view=msvc-170 and download the latest 'Microsoft Visual C++ Redistributable'", - sep = "\n" - )) - } - } else { - strings <- strsplit(system("ldd --version | grep ldd", intern = TRUE), " +")[[1]] - if (as.numeric(utils::tail(strings, n = 1)) < 2.35) { - stop("a suitable version of glibc was not found. Install glibc >= 2.35") - } - } - - # find installed dotnet runtimes for .NET 8 - if (length(grep("Microsoft.NETCore.App 8", system("dotnet --list-runtimes", intern = TRUE))) == 0) { - stop(" a suitable dotnet runtime was not found. Install dotnet 8 or newer") - } - # Load the C++ and .NET libraries - .loadAndInit() -} - -.loadAndInit <- function() { - # Path to the folder where the libraries are located - srcPkgLibPath <- system.file("lib", package = rSharpEnv$packageName) - - nativeLibrary <- file.path(srcPkgLibPath, paste0(rSharpEnv$nativePkgName, .Platform$dynlib.ext)) - - # Load C++ library - dyn.load(nativeLibrary, DLLpath = srcPkgLibPath) - - # Load .NET library through C++ - # The method returns 0 if successful. Otherwise, an error is thrown. - result <- .External("rSharp_create_domain", srcPkgLibPath, PACKAGE = rSharpEnv$nativePkgName) - # Turn on the the conversion of advanced data types with R.NET. - invisible(callStatic("ClrFacade.ClrFacade", "SetRDotNet", TRUE)) -} # nocov end +.onLoad <- function(...) { # nocov start + # Check for C++ distrib availability + if (.Platform$OS.type == "windows") { + if (Sys.which(rSharpEnv$msvcrFileName) == "") { + stop(paste(rSharpEnv$msvcrFileName, "was not found on this Windows system.", + "You are probably missing the Visual C++ Redistributable.", + "Go to https://learn.microsoft.com/en-US/cpp/windows/latest-supported-vc-redist?view=msvc-170 and download the latest 'Microsoft Visual C++ Redistributable'", + sep = "\n" + )) + } + } else { + + } + + # find installed dotnet runtimes for .NET 8 or higher + if (length(grep("Microsoft.NETCore.App 8", system("dotnet --list-runtimes", intern = TRUE))) == 0) { + stop(paste("No suitable dotnet 8 runtime found. ", + "Please install dotnet 8: go to https://learn.microsoft.com/en-us/dotnet/core/install/ and follow installation instructions.", + sep = "\n") + ) + } + + # Load the C++ and .NET libraries + .loadAndInit() +} + +.loadAndInit <- function() { + # Path to the folder where the libraries are located + srcPkgLibPath <- system.file("lib", package = rSharpEnv$packageName) + nativeLibraryPath <- file.path(srcPkgLibPath, paste0(rSharpEnv$nativePkgName, .Platform$dynlib.ext)) + + # Load C++ library + dyn.load(nativeLibraryPath, DLLpath = srcPkgLibPath) + + # Load .NET library through C++ + # The method returns 0 if successful. Otherwise, an error is thrown. + result <- .External("rSharp_create_domain", srcPkgLibPath, PACKAGE = rSharpEnv$nativePkgName) + + # Turn on the the conversion of advanced data types with R.NET. + invisible(callStatic("ClrFacade.ClrFacade", "SetRDotNet", TRUE)) +} # nocov end diff --git a/inst/extdata/rSharp.Examples.dll b/inst/extdata/rSharp.Examples.dll index 0af259fe..4b71389b 100644 Binary files a/inst/extdata/rSharp.Examples.dll and b/inst/extdata/rSharp.Examples.dll differ diff --git a/inst/lib/ClrFacade.dll b/inst/lib/ClrFacade.dll index 11e409dc..e02011d6 100644 Binary files a/inst/lib/ClrFacade.dll and b/inst/lib/ClrFacade.dll differ diff --git a/inst/lib/DynamicInterop.dll b/inst/lib/DynamicInterop.dll index 7bcf78d6..41a41188 100644 Binary files a/inst/lib/DynamicInterop.dll and b/inst/lib/DynamicInterop.dll differ diff --git a/inst/lib/RDotNet.dll b/inst/lib/RDotNet.dll index aa630e62..da6e6108 100644 Binary files a/inst/lib/RDotNet.dll and b/inst/lib/RDotNet.dll differ diff --git a/inst/lib/RSharp.runtimeconfig.json b/inst/lib/RSharp.runtimeconfig.json index c678ffe4..2e5d483f 100644 --- a/inst/lib/RSharp.runtimeconfig.json +++ b/inst/lib/RSharp.runtimeconfig.json @@ -1,12 +1,12 @@ -{ - "runtimeOptions": { - "tfm": "net8.0", - "framework": { - "name": "Microsoft.NETCore.App", - "version": "8.0.0" - }, - "configProperties": { - "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": true - } - } -} +{ + "runtimeOptions": { + "tfm": "net8.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "8.0.0" + }, + "configProperties": { + "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": true + } + } +} diff --git a/inst/lib/rSharp.dll b/inst/lib/rSharp.dll index 184c2493..b33c4000 100644 Binary files a/inst/lib/rSharp.dll and b/inst/lib/rSharp.dll differ diff --git a/inst/lib/rSharp.so b/inst/lib/rSharp.linux.so similarity index 94% rename from inst/lib/rSharp.so rename to inst/lib/rSharp.linux.so index 7e3c2df1..e08ba0cc 100755 Binary files a/inst/lib/rSharp.so and b/inst/lib/rSharp.linux.so differ diff --git a/inst/lib/rSharp.mac.so b/inst/lib/rSharp.mac.so new file mode 100755 index 00000000..e2a3571c Binary files /dev/null and b/inst/lib/rSharp.mac.so differ diff --git a/rSharp.Rproj b/rSharp.Rproj index 5bf00ede..1ed2afb1 100644 --- a/rSharp.Rproj +++ b/rSharp.Rproj @@ -1,23 +1,23 @@ -Version: 1.0 - -RestoreWorkspace: Default -SaveWorkspace: Default -AlwaysSaveHistory: Default - -EnableCodeIndexing: Yes -UseSpacesForTab: Yes -NumSpacesForTab: 2 -Encoding: UTF-8 - -RnwWeave: Sweave -LaTeX: pdfLaTeX - -AutoAppendNewline: Yes -StripTrailingWhitespace: Yes - -BuildType: Package -PackageUseDevtools: Yes -PackageInstallArgs: --no-multiarch --with-keep.source -PackageBuildArgs: --no-multiarch -PackageBuildBinaryArgs: --no-multiarch -PackageRoxygenize: rd,collate,namespace +Version: 1.0 + +RestoreWorkspace: Default +SaveWorkspace: Default +AlwaysSaveHistory: Default + +EnableCodeIndexing: Yes +UseSpacesForTab: Yes +NumSpacesForTab: 2 +Encoding: UTF-8 + +RnwWeave: Sweave +LaTeX: pdfLaTeX + +AutoAppendNewline: Yes +StripTrailingWhitespace: Yes + +BuildType: Package +PackageUseDevtools: Yes +PackageInstallArgs: --no-multiarch --with-keep.source +PackageBuildArgs: --no-multiarch +PackageBuildBinaryArgs: --no-multiarch +PackageRoxygenize: rd,collate,namespace diff --git a/shared/ClrFacade/InternalRDotNetDataConverter.cs b/shared/ClrFacade/InternalRDotNetDataConverter.cs index df76c91c..5a2740bf 100644 --- a/shared/ClrFacade/InternalRDotNetDataConverter.cs +++ b/shared/ClrFacade/InternalRDotNetDataConverter.cs @@ -26,8 +26,14 @@ private InternalRDotNetDataConverter(string pathToNativeSharedObj) CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US"); CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("en-US"); CultureInfo.CurrentUICulture = new CultureInfo("en-US"); - - dllName = Path.Combine(libDir, NativeUtility.IsUnix ? "rSharp.so" : "rSharp.dll"); + + if(NativeUtility.IsLinux) + dllName = Path.Combine(libDir, "rSharp.linux.so"); + else if(NativeUtility.IsMac) + dllName = Path.Combine(libDir, "rSharp.mac.so"); + else + dllName = Path.Combine(libDir, "rSharp.dll"); + } DataConversionHelper.rSharpNativeDll = new RSharpUnmanagedDll(dllName); diff --git a/shared/DynamicInterop.TestApp/Program.cs b/shared/DynamicInterop.TestApp/Program.cs index f1ae7602..157f5367 100644 --- a/shared/DynamicInterop.TestApp/Program.cs +++ b/shared/DynamicInterop.TestApp/Program.cs @@ -20,7 +20,7 @@ public static void Main(string[] args) static void lowLevelTest(IReadOnlyList args) { - var nat = new UnmanagedDll("libdl.so"); + var nat = new UnmanagedDll("libdl"); var open = nat.GetFunction("dlopen"); var error = nat.GetFunction("dlerror"); var handle = open(args[0], 0x01); diff --git a/shared/DynamicInterop.Tests/WrapDynamicLibrary.cs b/shared/DynamicInterop.Tests/WrapDynamicLibrary.cs index 43e8ae7c..32cc1a5c 100644 --- a/shared/DynamicInterop.Tests/WrapDynamicLibrary.cs +++ b/shared/DynamicInterop.Tests/WrapDynamicLibrary.cs @@ -35,19 +35,27 @@ private void CreateLib(string fname) [Fact] public void WellKnownSystemDll() { - if (PlatformUtility.IsUnix) + if (PlatformUtility.IsLinux) TestLoadLibc(); + else if (PlatformUtility.IsMacOSX) + TestLoadSystem(); else if (Environment.OSVersion.Platform == PlatformID.Win32NT) TestLoadKernel32(); else throw new NotSupportedException(PlatformUtility.GetPlatformNotSupportedMsg()); } + + private void TestLoadSystem() + { + using (var dll = new UnmanagedDll("/usr/lib/libSystem.dylib")) + { + var m = dll.GetFunction(); + Assert.NotNull(m); + } + } private void TestLoadLibc() { - // TODO: obviously need to generalize this. - - //nm -D --defined-only /lib/x86_64-linux-gnu/libc.so.6 | less using (var dll = new UnmanagedDll("/lib/x86_64-linux-gnu/libc.so.6")) { var m = dll.GetFunction(); diff --git a/shared/DynamicInterop/PlatformUtility.cs b/shared/DynamicInterop/PlatformUtility.cs index 2db505f0..799badf7 100644 --- a/shared/DynamicInterop/PlatformUtility.cs +++ b/shared/DynamicInterop/PlatformUtility.cs @@ -14,12 +14,21 @@ public static class PlatformUtility /// /// Is the platform unix-like (Unix or MacOX) /// - public static bool IsUnix + public static bool IsLinux { get { var p = GetPlatform(); - return p == PlatformID.MacOSX || p == PlatformID.Unix; + return p == PlatformID.Unix; + } + } + + public static bool IsMacOSX + { + get + { + var p = GetPlatform(); + return p == PlatformID.MacOSX; } } diff --git a/shared/DynamicInterop/SafeHandleUnmanagedDll.cs b/shared/DynamicInterop/SafeHandleUnmanagedDll.cs index edbd4759..d8f4925f 100644 --- a/shared/DynamicInterop/SafeHandleUnmanagedDll.cs +++ b/shared/DynamicInterop/SafeHandleUnmanagedDll.cs @@ -10,7 +10,7 @@ internal sealed class SafeHandleUnmanagedDll : SafeHandleZeroOrMinusOneIsInvalid public SafeHandleUnmanagedDll(string dllName) : base(true) { IDynamicLibraryLoader libraryLoader = null; - if (PlatformUtility.IsUnix) + if (PlatformUtility.IsLinux || PlatformUtility.IsMacOSX) libraryLoader = new UnixLibraryLoader(); else if (Environment.OSVersion.Platform == PlatformID.Win32NT) libraryLoader = new WindowsLibraryLoader(); diff --git a/shared/DynamicInterop/UnixLibraryLoader.cs b/shared/DynamicInterop/UnixLibraryLoader.cs index 69fc86f0..72f30f0c 100644 --- a/shared/DynamicInterop/UnixLibraryLoader.cs +++ b/shared/DynamicInterop/UnixLibraryLoader.cs @@ -95,13 +95,13 @@ internal static IntPtr InternalLoadLibrary(string filename, int lazy) private static int _so; - [DllImport("libdl.so", EntryPoint = "dlopen")] + [DllImport("libdl", EntryPoint = "dlopen")] private static extern IntPtr dlopen1([MarshalAs(UnmanagedType.LPStr)] string filename, int flag); [DllImport("libdl.so.2", EntryPoint = "dlopen")] private static extern IntPtr dlopen2([MarshalAs(UnmanagedType.LPStr)] string filename, int flag); - [DllImport("libdl.so", EntryPoint = "dlerror")] + [DllImport("libdl", EntryPoint = "dlerror")] [return: MarshalAs(UnmanagedType.LPStr)] private static extern string dlerror1(); @@ -109,7 +109,7 @@ internal static IntPtr InternalLoadLibrary(string filename, int lazy) [return: MarshalAs(UnmanagedType.LPStr)] private static extern string dlerror2(); - [DllImport("libdl.so", EntryPoint = "dlclose")] + [DllImport("libdl", EntryPoint = "dlclose")] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] private static extern int dlclose1(IntPtr hModule); @@ -117,7 +117,7 @@ internal static IntPtr InternalLoadLibrary(string filename, int lazy) [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] private static extern int dlclose2(IntPtr hModule); - [DllImport("libdl.so", EntryPoint = "dlsym")] + [DllImport("libdl", EntryPoint = "dlsym")] private static extern IntPtr dlsym1(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)] string lpProcName); [DllImport("libdl.so.2", EntryPoint = "dlsym")] diff --git a/shared/DynamicInterop/UnmanagedDll.cs b/shared/DynamicInterop/UnmanagedDll.cs index 25006cb0..e1dd0469 100644 --- a/shared/DynamicInterop/UnmanagedDll.cs +++ b/shared/DynamicInterop/UnmanagedDll.cs @@ -56,33 +56,12 @@ public UnmanagedDll(string dllName) private void ReportLoadLibError(string dllName, string nativeError) { ThrowFailedLibraryLoad(dllName, nativeError); -/* - * string dllFullName = dllName; - if (File.Exists(dllFullName)) - ThrowFailedLibraryLoad(dllFullName); - else - { - // This below assumes that the PATH environment variable is what is relied on - // TODO: check whether there is more to it: http://msdn.microsoft.com/en-us/library/ms682586.aspx - - // Also some pointers to relevant information if we want to check whether the attempt to load - // was made on a 32 or 64 bit library - // For Windows: - // http://stackoverflow.com/questions/1345632/determine-if-an-executable-or-library-is-32-or-64-bits-on-windows - // http://www.neowin.net/forum/topic/732648-check-if-exe-is-x64/?p=590544108#entry590544108 - // Linux, and perhaps MacOS; the 'file' command seems the way to go. - // http://stackoverflow.com/questions/5665228/in-linux-determine-if-a-a-library-archive-32-bit-or-64-bit - - dllFullName = FindFullPath(dllName, throwIfNotFound: true); - ThrowFailedLibraryLoad(dllFullName); - } - */ } [Obsolete("This message is likely to be too distribution specific", true)] private string createLdLibPathMsg() { - if (!PlatformUtility.IsUnix) + if (!PlatformUtility.IsLinux || PlatformUtility.IsMacOSX) return null; //var sampleldLibPaths = "/usr/local/lib/R/lib:/usr/local/lib:/usr/lib/jvm/java-7-openjdk-amd64/jre/lib/amd64/server"; var ldLibPathEnv = Environment.GetEnvironmentVariable("LD_LIBRARY_PATH"); diff --git a/shared/R.NET/NativeLibrary/NativeUtility.cs b/shared/R.NET/NativeLibrary/NativeUtility.cs index 44b7adbb..99e92bb7 100644 --- a/shared/R.NET/NativeLibrary/NativeUtility.cs +++ b/shared/R.NET/NativeLibrary/NativeUtility.cs @@ -566,7 +566,7 @@ public static string GetRLibraryFileName() return "R.dll"; case PlatformID.MacOSX: - return "libR.dylib"; + return "/Library/Frameworks/R.framework/Resources/lib/libR.dylib"; case PlatformID.Unix: return "libR.so"; @@ -577,14 +577,23 @@ public static string GetRLibraryFileName() } /// - /// Is the platform a unix like (Unix or MacOX) + /// Is the platform a linux /// - public static bool IsUnix + public static bool IsLinux { get { var p = GetPlatform(); - return p == PlatformID.MacOSX || p == PlatformID.Unix; + return p == PlatformID.Unix; + } + } + + public static bool IsMac + { + get + { + var p = GetPlatform(); + return p == PlatformID.MacOSX; } } } diff --git a/shared/R.NET/REngine.cs b/shared/R.NET/REngine.cs index c2f2842f..261e72fc 100644 --- a/shared/R.NET/REngine.cs +++ b/shared/R.NET/REngine.cs @@ -286,7 +286,7 @@ private static void DetermineCompatibility(REngine engine) // compatibility version to support R 3.5+ engine.Compatibility = CompatibilityMode.ALTREP; - if (NativeUtility.IsUnix) + if (NativeUtility.IsLinux || NativeUtility.IsMac) // engine.DllVersion is not implemented because the R native library has no entry point to getDllVersion which is Windows only. // Not sure yet if there is a way to programatically query the R version on Linux, without bumping in a chicken and egg problem. return; diff --git a/shared/RDotNet.TestBase/RDotNetTestFixture.cs b/shared/RDotNet.TestBase/RDotNetTestFixture.cs index da63ad94..e9c1bc5c 100644 --- a/shared/RDotNet.TestBase/RDotNetTestFixture.cs +++ b/shared/RDotNet.TestBase/RDotNetTestFixture.cs @@ -29,7 +29,7 @@ protected REngine Engine protected static void ReportFailOnLinux(string additionalMsg) { - if (NativeUtility.IsUnix) + if (NativeUtility.IsLinux || NativeUtility.IsMac) throw new NotSupportedException("This unit test is problematic to run from NUnit on Linux " + additionalMsg); } diff --git a/shared/makefile b/shared/makefile index c4156c86..416eb2ea 100644 --- a/shared/makefile +++ b/shared/makefile @@ -6,8 +6,15 @@ OUT_DIR := out SRC_DIR := . SRC_FILES := $(wildcard $(SRC_DIR)/*.cpp) OBJ_FILES := $(patsubst $(SRC_DIR)/%.cpp,$(OBJ_DIR)/%.o,$(SRC_FILES)) -SHLIB_EXT := .so INSTDIR= ../inst +UNAME := $(shell uname) + +ifeq ($(UNAME), Darwin) + OUTPUT := rSharp.mac.so + RUNTIME := osx +else + OUTPUT := rSharp.linux.so +endif # only nuget supports packages.config NUGET_CMD=nuget @@ -28,22 +35,30 @@ RM = rm -f # .PHONY: all clean -all: printarch instdir rSharpLib $(OUT_DIR)/rSharp.so rSharpInstrSharpUX +all: printarch instdir rSharpLib $(OUT_DIR)/$(OUTPUT) rSharpInstrSharpUX clean: ${RM} $(OUT_DIR)/* $(OBJ_DIR)/* $(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp -@mkdir -p $(OBJ_DIR) 2>/dev/null - $(CXX) -std=c++11 -Ipackages/Microsoft.NETCore.App.Host.linux-x64.8.0.2/runtimes/linux-x64/native $(ALL_CPPFLAGS) -I/usr/share/R/include/ $(ALL_CFLAGS) -fPIC -c $< -o $@ -lsupc++ +ifeq ($(UNAME), Darwin) + $(CXX) -std=c++11 -Ipackages/Microsoft.NETCore.App.Host.osx-arm64.8.0.2/runtimes/osx-arm64/native $(ALL_CPPFLAGS) -I/Library/Frameworks/R.framework/Resources/include -I/usr/share/R/include/ $(ALL_CFLAGS) -fPIC -c $< -o $@ -lsupc++ +else + $(CXX) -std=c++11 -Ipackages/Microsoft.NETCore.App.Host.linux-x64.8.0.2/runtimes/linux-x64/native $(ALL_CPPFLAGS) -I/usr/share/R/include/ $(ALL_CFLAGS) -fPIC -c $< -o $@ -lsupc++ +endif -$(OUT_DIR)/rSharp.so: $(OBJ_FILES) +$(OUT_DIR)/$(OUTPUT): $(OBJ_FILES) -@mkdir -p $(OUT_DIR) 2>/dev/null - $(CXX) -std=c++11 -shared -Wl,-z,relro -o $@ $^ $(ALL_LIBS) -L./packages/Microsoft.NETCore.App.Host.linux-x64.8.0.2/runtimes/linux-x64/native -l:libnethost.a +ifeq ($(UNAME), Darwin) + $(CXX) -std=c++11 -shared -Wl, -o $@ $^ $(ALL_LIBS) ./packages/Microsoft.NETCore.App.Host.osx-arm64.8.0.2/runtimes/osx-arm64/native/libnethost.a -L/Library/Frameworks/R.framework/Resources/lib -lR +else + $(CXX) -std=c++11 -shared -Wl,-z,relro -o $@ $^ $(ALL_LIBS) -L./packages/Microsoft.NETCore.App.Host.linux-x64.8.0.2/runtimes/linux-x64/native -l:libnethost.a +endif printarch: -@echo **Variable information only for diagnosis purposes** - -@echo SHLIB_EXT=$(SHLIB_EXT) + -@echo $(OUTPUT) -@echo CC=$(CC) -@echo CXX=$(CXX) -@echo **END Variable** @@ -53,13 +68,21 @@ instdir: -@mkdir -p $(INSTDIR) 2>/dev/null -@mkdir -p $(INSTDIR)/lib$(R_ARCH) 2>/dev/null -rSharpInstrSharpUX: $(OUT_DIR)/rSharp.so +rSharpInstrSharpUX: $(OUT_DIR)/$(OUTPUT) +ifeq ($(UNAME), Darwin) + cp -p $< $(INSTDIR)/lib$(R_ARCH) +else cp -u -p $< $(INSTDIR)/lib$(R_ARCH) +endif rSharpLib: rSharpLibComp +ifeq ($(UNAME), Darwin) + if [ -e symbols.rds ] ; then cp -p symbols.rds $(INSTDIR)/lib$(R_ARCH) ; fi + -cp -p ./$(RUNTIMECONFIG).json $(INSTDIR)/lib/ +else if [ -e symbols.rds ] ; then cp -u -p symbols.rds $(INSTDIR)/lib$(R_ARCH) ; fi - -cp -u -p ./$(RUNTIMECONFIG).json $(INSTDIR)/lib/ +endif rSharpNugetRestore: -$(NUGET_CMD) restore packages.config -PackagesDirectory packages diff --git a/shared/packages.config b/shared/packages.config index 1a4dcf98..85642b59 100644 --- a/shared/packages.config +++ b/shared/packages.config @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/shared/rSharp.cpp b/shared/rSharp.cpp index b5abeeea..0f18727e 100644 --- a/shared/rSharp.cpp +++ b/shared/rSharp.cpp @@ -99,7 +99,7 @@ SEXP rSharp_create_domain(SEXP args) delete[] wideStringPath; } - catch (const std::exception& ex) + catch (const std::runtime_error& ex) { error_return(ex.what()) } @@ -579,7 +579,7 @@ SEXP r_create_clr_object(SEXP parameters) { methodParameters = sexp_to_parameters(sExpressionMethodParameter); } - catch (const std::exception& ex) + catch (const std::runtime_error& ex) { free(ns_qualified_typename); error_return(ex.what()) @@ -593,7 +593,7 @@ SEXP r_create_clr_object(SEXP parameters) free_params_array(methodParameters, numberOfObjects); return ConvertToSEXP(return_value); } - catch (const std::exception& ex) + catch (const std::runtime_error& ex) { free(ns_qualified_typename); free_params_array(methodParameters, numberOfObjects); @@ -695,7 +695,7 @@ SEXP r_get_object_direct() auto return_value = getCurrentObjectDirect(); return ConvertToSEXP(return_value); } - catch (const std::exception& ex) + catch (const std::runtime_error& ex) { error_return(ex.what()) } @@ -746,7 +746,7 @@ SEXP r_get_typename_externalptr(SEXP parameters) auto return_value = get_type_full_name(reinterpret_cast(sExpressionNameParameter)); return mkString(return_value); } - catch (const std::exception& ex) + catch (const std::runtime_error& ex) { error_return(ex.what()) } @@ -788,7 +788,7 @@ SEXP r_call_method(SEXP parameters) { params = sexp_to_parameters(sExpressionParameterStack); } - catch (const std::exception& ex) + catch (const std::runtime_error& ex) { error_return(ex.what()) } @@ -800,7 +800,7 @@ SEXP r_call_method(SEXP parameters) free_params_array(params, numberOfObjects); return ConvertToSEXP(return_value); } - catch (const std::exception& ex) + catch (const std::runtime_error& ex) { free_params_array(params, numberOfObjects); error_return(ex.what()) @@ -827,7 +827,7 @@ SEXP r_call_static_method(SEXP parameters) { methodParameters = sexp_to_parameters(sExpressionMethodParameter); } - catch (const std::exception& ex) + catch (const std::runtime_error& ex) { free(ns_qualified_typename); error_return(ex.what()) @@ -846,7 +846,7 @@ SEXP r_call_static_method(SEXP parameters) free_params_array(methodParameters, numberOfObjects); return ConvertToSEXP(return_value); } - catch (const std::exception& ex) + catch (const std::runtime_error& ex) { free(ns_qualified_typename); free_params_array(methodParameters, numberOfObjects); diff --git a/tests/testthat/test-basic.R b/tests/testthat/test-basic.R index bb5be93c..6186711c 100644 --- a/tests/testthat/test-basic.R +++ b/tests/testthat/test-basic.R @@ -1,131 +1,133 @@ -test_that("Methods with variable number of parameters with c# 'params' keyword", { - testObj <- newObjectFromName(rSharpEnv$testObjectTypeName) - actual <- testObj$call("TestParams", "Hello, ", "World!", 1L, 2L, 3L, 6L, 5L, 4L) - expected <- "Hello, World!123654" - expect_equal(actual, expected = expected) - actual <- testObj$call("TestParams", "Hello, ", "World!", as.integer(1:6)) - expected <- "Hello, World!123456" - expect_equal(actual, expected = expected) -}) - -test_that("Vignette examples work", { - assemblyPath <- system.file("extdata", "rSharp.Examples.dll", package = "rSharp") - expect_true(loadAssembly(assemblyPath)) - testObj <- newObjectFromName("rSharp.Examples.SampleInstanceClass") - expect_equal(callStatic("rSharp.Examples.SampleStaticClass", "GetAString"), "A string from static class") - expect_equal(testObj$call("GetAString"), "A string from instance class") -}) - -test_that("Correct method binding based on parameter types", { - mkArrayTypeName <- function(typeName) { - paste(typeName, "[]", sep = "") - } - f <- function(...) { - callStatic("ClrFacade.TestMethodBinding", "SomeStaticMethod", ...) - } - printIfDifferent <- function(got, expected) { - if (any(got != expected)) { - print(paste("got", got, ", expected", expected)) - } - } - g <- function(values, typeName) { - if (is.list(values)) { # this is what one gets with a concatenation of S4 objects, when we use c(testObj,testObj) with CLR objects - printIfDifferent(f(values[[1]]), typeName) - printIfDifferent(f(values), mkArrayTypeName(typeName)) # This is not yet supported? - printIfDifferent(f(values[[1]], values[[2]]), rep(typeName, 2)) - expect_equal(f(values[[1]]), typeName) - expect_equal(f(values), mkArrayTypeName(typeName)) - expect_equal(f(values[[1]], values[[2]]), rep(typeName, 2)) - } else { - printIfDifferent(f(values[1]), typeName) - printIfDifferent(f(values), mkArrayTypeName(typeName)) - printIfDifferent(f(values[1], values[2]), rep(typeName, 2)) - expect_equal(f(values[1]), typeName) - expect_equal(f(values), mkArrayTypeName(typeName)) - expect_equal(f(values[1], values[2]), rep(typeName, 2)) - } - } - intName <- "System.Int32" - doubleName <- "System.Double" - stringName <- "System.String" - boolName <- "System.Boolean" - dateTimeName <- "System.DateTime" - objectName <- "System.Object" - testObj <- newObjectFromName(rSharpEnv$testObjectTypeName) - - testMethodBinding <- function() { - g(1:3, intName) - g(1.2 * 1:3, doubleName) - g(letters[1:3], stringName) - g(rep(TRUE, 3), boolName) - g(as.Date("2001-01-01") + 1:3, dateTimeName) - g(c(testObj, testObj, testObj), objectName) - - expect_equal(f(1.0, "a"), c(doubleName, stringName)) - expect_equal(f(1.0, "a", "b"), c(doubleName, stringName, stringName)) - expect_equal(f(1.0, letters[1:2]), c(doubleName, mkArrayTypeName(stringName))) - expect_equal(f(1.0, letters[1:10]), c(doubleName, mkArrayTypeName(stringName))) - - expect_equal(f("a", letters[1:3]), c(stringName, mkArrayTypeName(stringName))) - expect_equal(f(letters[1:3], "a"), c(mkArrayTypeName(stringName), stringName)) - expect_equal(f(letters[1:3], letters[4:6]), c(mkArrayTypeName(stringName), mkArrayTypeName(stringName))) - } - testMethodBinding() - obj <- newObjectFromName("ClrFacade.TestMethodBinding") - f <- function(...) { - obj$call("SomeInstanceMethod", ...) - } - testMethodBinding() - # Test that methods implemented to comply with an interface are found, even if the method is explicitely implemented. - # We do not want the users to have to figure out which interface type they deal with, at least not for R users. - f <- function(...) { - obj$call("SomeExplicitlyImplementedMethod", ...) - } - testMethodBinding() -}) - -test_that("Conversion of non-bijective types can be turned on/off", { - # When the conversion is turned off, a `NetObject` is returned, which holds a reference to the .NET object. - setConvertAdvancedTypes(FALSE) - expect_true(is(callTestCase("CreateStringDictionary"), "NetObject")) - expect_true(is(callTestCase("CreateStringDoubleArrayDictionary"), "NetObject")) - # When the conversion is turned on, the .NET object is converted to an R list. - setConvertAdvancedTypes(TRUE) - expect_equal(callTestCase("CreateStringDictionary"), list(a = "A", b = "B")) - expect_equal(callTestCase("CreateStringDoubleArrayDictionary"), list(a = c(1.0, 2.0, 3.0, 3.5, 4.3, 11), b = c(1.0, 2.0, 3.0, 3.5, 4.3), c = c(2.2, 3.3, 6.5))) -}) - -test_that("toStringNET works for primitive types", { - expect_equal(toStringNET(1), "1") - expect_equal(toStringNET(1.0), "1") - expect_equal(toStringNET("a"), "a") - expect_equal(toStringNET(TRUE), "True") - expect_equal(toStringNET(FALSE), "False") - # Check of correct behavior - # expect_equal(toStringNET(NA), "null") - expect_equal(toStringNET(NULL), "null") - expect_equal(toStringNET(NaN), "NaN") -}) - -test_that("Print traceback", { - expected <- "Type: System.Exception -Message: An exception designed with a particular stack trace length -Method: Void ThrowException(Int32) -Stack trace: - at ClrFacade.TestCases.ThrowException(Int32 stackDepth) - at ClrFacade.TestCases.ThrowException(Int32 stackDepth) - at ClrFacade.TestCases.ThrowException(Int32 stackDepth) - at ClrFacade.TestCases.ThrowException(Int32 stackDepth) - at ClrFacade.TestCases.ThrowException(Int32 stackDepth) - at ClrFacade.TestCases.ThrowException(Int32 stackDepth) - at ClrFacade.TestCases.ThrowException(Int32 stackDepth) - at ClrFacade.TestCases.ThrowException(Int32 stackDepth) - at ClrFacade.TestCases.ThrowException(Int32 stackDepth) - at ClrFacade.TestCases.ThrowException(Int32 stackDepth) - at InvokeStub_TestCases.ThrowException(Object, Span`1) - at System.Reflection.MethodBaseInvoker.InvokeWithOneArg(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)" - - expect_error(callStatic(rSharpEnv$testCasesTypeName, "ThrowException", 10L)) # will be truncated by the Rf_error API - # Dont know how to test only for the first xx lines of code. The message thrown when executing tests is different from the one thrown when running the code in the console. - # expect_output(printTraceback(), expected, fixed = TRUE) # prints the full stack trace -}) +test_that("Methods with variable number of parameters with c# 'params' keyword", { + testObj <- newObjectFromName(rSharpEnv$testObjectTypeName) + actual <- testObj$call("TestParams", "Hello, ", "World!", 1L, 2L, 3L, 6L, 5L, 4L) + expected <- "Hello, World!123654" + expect_equal(actual, expected = expected) + actual <- testObj$call("TestParams", "Hello, ", "World!", as.integer(1:6)) + expected <- "Hello, World!123456" + expect_equal(actual, expected = expected) +}) + +test_that("Vignette examples work", { + assemblyPath <- system.file("extdata", "rSharp.Examples.dll", package = "rSharp") + # expect_true(loadAssembly(assemblyPath)) # Returns FALSE on macos but TRUE on other platforms + expect_no_error(loadAssembly(assemblyPath)) + expect_true(isAssemblyLoaded("rSharp.Examples")) + testObj <- newObjectFromName("rSharp.Examples.SampleInstanceClass") + expect_equal(callStatic("rSharp.Examples.SampleStaticClass", "GetAString"), "A string from static class") + expect_equal(testObj$call("GetAString"), "A string from instance class") +}) + +test_that("Correct method binding based on parameter types", { + mkArrayTypeName <- function(typeName) { + paste(typeName, "[]", sep = "") + } + f <- function(...) { + callStatic("ClrFacade.TestMethodBinding", "SomeStaticMethod", ...) + } + printIfDifferent <- function(got, expected) { + if (any(got != expected)) { + print(paste("got", got, ", expected", expected)) + } + } + g <- function(values, typeName) { + if (is.list(values)) { # this is what one gets with a concatenation of S4 objects, when we use c(testObj,testObj) with CLR objects + printIfDifferent(f(values[[1]]), typeName) + printIfDifferent(f(values), mkArrayTypeName(typeName)) # This is not yet supported? + printIfDifferent(f(values[[1]], values[[2]]), rep(typeName, 2)) + expect_equal(f(values[[1]]), typeName) + expect_equal(f(values), mkArrayTypeName(typeName)) + expect_equal(f(values[[1]], values[[2]]), rep(typeName, 2)) + } else { + printIfDifferent(f(values[1]), typeName) + printIfDifferent(f(values), mkArrayTypeName(typeName)) + printIfDifferent(f(values[1], values[2]), rep(typeName, 2)) + expect_equal(f(values[1]), typeName) + expect_equal(f(values), mkArrayTypeName(typeName)) + expect_equal(f(values[1], values[2]), rep(typeName, 2)) + } + } + intName <- "System.Int32" + doubleName <- "System.Double" + stringName <- "System.String" + boolName <- "System.Boolean" + dateTimeName <- "System.DateTime" + objectName <- "System.Object" + testObj <- newObjectFromName(rSharpEnv$testObjectTypeName) + + testMethodBinding <- function() { + g(1:3, intName) + g(1.2 * 1:3, doubleName) + g(letters[1:3], stringName) + g(rep(TRUE, 3), boolName) + g(as.Date("2001-01-01") + 1:3, dateTimeName) + g(c(testObj, testObj, testObj), objectName) + + expect_equal(f(1.0, "a"), c(doubleName, stringName)) + expect_equal(f(1.0, "a", "b"), c(doubleName, stringName, stringName)) + expect_equal(f(1.0, letters[1:2]), c(doubleName, mkArrayTypeName(stringName))) + expect_equal(f(1.0, letters[1:10]), c(doubleName, mkArrayTypeName(stringName))) + + expect_equal(f("a", letters[1:3]), c(stringName, mkArrayTypeName(stringName))) + expect_equal(f(letters[1:3], "a"), c(mkArrayTypeName(stringName), stringName)) + expect_equal(f(letters[1:3], letters[4:6]), c(mkArrayTypeName(stringName), mkArrayTypeName(stringName))) + } + testMethodBinding() + obj <- newObjectFromName("ClrFacade.TestMethodBinding") + f <- function(...) { + obj$call("SomeInstanceMethod", ...) + } + testMethodBinding() + # Test that methods implemented to comply with an interface are found, even if the method is explicitely implemented. + # We do not want the users to have to figure out which interface type they deal with, at least not for R users. + f <- function(...) { + obj$call("SomeExplicitlyImplementedMethod", ...) + } + testMethodBinding() +}) + +test_that("Conversion of non-bijective types can be turned on/off", { + # When the conversion is turned off, a `NetObject` is returned, which holds a reference to the .NET object. + setConvertAdvancedTypes(FALSE) + expect_true(is(callTestCase("CreateStringDictionary"), "NetObject")) + expect_true(is(callTestCase("CreateStringDoubleArrayDictionary"), "NetObject")) + # When the conversion is turned on, the .NET object is converted to an R list. + setConvertAdvancedTypes(TRUE) + expect_equal(callTestCase("CreateStringDictionary"), list(a = "A", b = "B")) + expect_equal(callTestCase("CreateStringDoubleArrayDictionary"), list(a = c(1.0, 2.0, 3.0, 3.5, 4.3, 11), b = c(1.0, 2.0, 3.0, 3.5, 4.3), c = c(2.2, 3.3, 6.5))) +}) + +test_that("toStringNET works for primitive types", { + expect_equal(toStringNET(1), "1") + expect_equal(toStringNET(1.0), "1") + expect_equal(toStringNET("a"), "a") + expect_equal(toStringNET(TRUE), "True") + expect_equal(toStringNET(FALSE), "False") + # Check of correct behavior + # expect_equal(toStringNET(NA), "null") + expect_equal(toStringNET(NULL), "null") + expect_equal(toStringNET(NaN), "NaN") +}) + +test_that("Print traceback", { + expected <- "Type: System.Exception +Message: An exception designed with a particular stack trace length +Method: Void ThrowException(Int32) +Stack trace: + at ClrFacade.TestCases.ThrowException(Int32 stackDepth) + at ClrFacade.TestCases.ThrowException(Int32 stackDepth) + at ClrFacade.TestCases.ThrowException(Int32 stackDepth) + at ClrFacade.TestCases.ThrowException(Int32 stackDepth) + at ClrFacade.TestCases.ThrowException(Int32 stackDepth) + at ClrFacade.TestCases.ThrowException(Int32 stackDepth) + at ClrFacade.TestCases.ThrowException(Int32 stackDepth) + at ClrFacade.TestCases.ThrowException(Int32 stackDepth) + at ClrFacade.TestCases.ThrowException(Int32 stackDepth) + at ClrFacade.TestCases.ThrowException(Int32 stackDepth) + at InvokeStub_TestCases.ThrowException(Object, Span`1) + at System.Reflection.MethodBaseInvoker.InvokeWithOneArg(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)" + + expect_error(callStatic(rSharpEnv$testCasesTypeName, "ThrowException", 10L)) # will be truncated by the Rf_error API + # Dont know how to test only for the first xx lines of code. The message thrown when executing tests is different from the one thrown when running the code in the console. + # expect_output(printTraceback(), expected, fixed = TRUE) # prints the full stack trace +})