From 3ba7529891c5c5614e38955eb6fdbf4e68170412 Mon Sep 17 00:00:00 2001 From: Mikhail Filippov Date: Sun, 9 Oct 2022 13:22:19 +0400 Subject: [PATCH] Add basic Rider plugin structure --- settings.gradle | 33 --- settings.gradle.kts | 34 +++ utbot-rider/.gitignore | 2 + utbot-rider/build.gradle.kts | 57 ++++ utbot-rider/dotnet-sdk.cmd | 265 ++++++++++++++++++ utbot-rider/src/dotnet/UtBot/UtBot.sln | 16 ++ .../UtBot/UtBot/GenerateUnitTestAction.cs | 12 + .../UtBot/GenerateUnitTestElementProvider.cs | 39 +++ .../UtBot/UtBot/GenerateUnitTestWorkflow.cs | 23 ++ .../UtBot/GenerateUnitTestWorkflowProvider.cs | 13 + .../src/dotnet/UtBot/UtBot/UnitTestBuilder.cs | 222 +++++++++++++++ .../UtBot/UtBot/UnitTestMethodDescriptor.cs | 7 + .../src/dotnet/UtBot/UtBot/UtBot.csproj | 15 + .../src/dotnet/UtBot/UtBot/ZoneMarker.cs | 22 ++ .../src/main/resources/META-INF/plugin.xml | 60 ++++ .../main/resources/META-INF/pluginIcon.svg | 111 ++++++++ .../resources/META-INF/pluginIcon_dark.svg | 111 ++++++++ 17 files changed, 1009 insertions(+), 33 deletions(-) delete mode 100644 settings.gradle create mode 100644 settings.gradle.kts create mode 100644 utbot-rider/.gitignore create mode 100644 utbot-rider/build.gradle.kts create mode 100755 utbot-rider/dotnet-sdk.cmd create mode 100644 utbot-rider/src/dotnet/UtBot/UtBot.sln create mode 100644 utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestAction.cs create mode 100644 utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestElementProvider.cs create mode 100644 utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestWorkflow.cs create mode 100644 utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestWorkflowProvider.cs create mode 100644 utbot-rider/src/dotnet/UtBot/UtBot/UnitTestBuilder.cs create mode 100644 utbot-rider/src/dotnet/UtBot/UtBot/UnitTestMethodDescriptor.cs create mode 100644 utbot-rider/src/dotnet/UtBot/UtBot/UtBot.csproj create mode 100644 utbot-rider/src/dotnet/UtBot/UtBot/ZoneMarker.cs create mode 100644 utbot-rider/src/main/resources/META-INF/plugin.xml create mode 100644 utbot-rider/src/main/resources/META-INF/pluginIcon.svg create mode 100644 utbot-rider/src/main/resources/META-INF/pluginIcon_dark.svg diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 7e7cd525dc..0000000000 --- a/settings.gradle +++ /dev/null @@ -1,33 +0,0 @@ -pluginManagement { - resolutionStrategy { - eachPlugin { - if (requested.id.name == "rdgen") { - useModule("com.jetbrains.rd:rd-gen:${requested.version}") - } - } - } -} - -rootProject.name = 'utbot' - -include 'utbot-core' -include 'utbot-framework' -include 'utbot-framework-api' -include 'utbot-intellij' -include 'utbot-sample' -include 'utbot-fuzzers' -include 'utbot-junit-contest' -include 'utbot-analytics' -include 'utbot-analytics-torch' -include 'utbot-cli' -include 'utbot-api' -include 'utbot-instrumentation' -include 'utbot-instrumentation-tests' - -include 'utbot-summary' -include 'utbot-gradle' -include 'utbot-maven' -include 'utbot-summary-tests' -include 'utbot-framework-test' -include 'utbot-rd' - diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000000..470c1a16ab --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,34 @@ +pluginManagement { + resolutionStrategy { + eachPlugin { + if (requested.id.name == "rdgen") { + useModule("com.jetbrains.rd:rd-gen:${requested.version}") + } + } + } +} + +rootProject.name = "utbot" + +include("utbot-core") +include("utbot-framework") +include("utbot-framework-api") +include("utbot-intellij") +include("utbot-rider") +include("utbot-sample") +include("utbot-fuzzers") +include("utbot-junit-contest") +include("utbot-analytics") +include("utbot-analytics-torch") +include("utbot-cli") +include("utbot-api") +include("utbot-instrumentation") +include("utbot-instrumentation-tests") + +include("utbot-summary") +include("utbot-gradle") +include("utbot-maven") +include("utbot-summary-tests") +include("utbot-framework-test") +include("utbot-rd") + diff --git a/utbot-rider/.gitignore b/utbot-rider/.gitignore new file mode 100644 index 0000000000..cd42ee34e8 --- /dev/null +++ b/utbot-rider/.gitignore @@ -0,0 +1,2 @@ +bin/ +obj/ diff --git a/utbot-rider/build.gradle.kts b/utbot-rider/build.gradle.kts new file mode 100644 index 0000000000..ae4e7ebec4 --- /dev/null +++ b/utbot-rider/build.gradle.kts @@ -0,0 +1,57 @@ +val semVer: String? by rootProject + +plugins { + id("org.jetbrains.intellij") version "1.7.0" +} + +intellij { + type.set("RD") + version.set("2022.2") +} + +tasks { + val dotNetSdkCmdPath = projectDir.resolve("dotnet-sdk.cmd").toString() + + compileKotlin { + kotlinOptions { + jvmTarget = "11" + freeCompilerArgs = freeCompilerArgs + listOf("-Xallow-result-return-type", "-Xsam-conversions=class") + allWarningsAsErrors = false + } + } + + java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_11 + } + + runIde { + jvmArgs("-Xmx2048m") + } + + patchPluginXml { + sinceBuild.set("222") + untilBuild.set("222.*") + version.set(semVer) + } + + val publishDotNet = create("publishDotNet") { + group = "build" + doLast { + exec { + commandLine = listOf( + dotNetSdkCmdPath, + "publish", + projectDir.resolve("src/dotnet/UtBot/UtBot.sln").toString() + ) + } + } + } + + prepareSandbox { + dependsOn(publishDotNet) + from("src/dotnet/UtBot/UtBot/bin/Debug/net6.0/publish") { + into("${intellij.pluginName.get()}/dotnet") } + } + +} diff --git a/utbot-rider/dotnet-sdk.cmd b/utbot-rider/dotnet-sdk.cmd new file mode 100755 index 0000000000..23339d35e0 --- /dev/null +++ b/utbot-rider/dotnet-sdk.cmd @@ -0,0 +1,265 @@ +:<<"::CMDLITERAL" +@ECHO OFF +GOTO :CMDSCRIPT +::CMDLITERAL + +set -eu + +SCRIPT_VERSION=dotnet-cmd-v2 +COMPANY_DIR="UtBot" +TARGET_DIR="${TEMPDIR:-$HOME/.local/share}/$COMPANY_DIR/dotnet-cmd" +KEEP_ROSETTA2=false +export DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true +export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=true +export DOTNET_CLI_TELEMETRY_OPTOUT=true + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +retry_on_error () { + local n="$1" + shift + + for i in $(seq 2 "$n"); do + "$@" 2>&1 && return || echo "WARNING: Command '$1' returned non-zero exit status $?, try again" + done + "$@" +} + +is_linux_musl () { + (ldd --version 2>&1 || true) | grep -q musl +} + +case $(uname) in +Darwin) + DOTNET_ARCH=$(uname -m) + if ! $KEEP_ROSETTA2 && [ "$(sysctl -n sysctl.proc_translated 2>/dev/null || true)" = "1" ]; then + DOTNET_ARCH=arm64 + fi + case $DOTNET_ARCH in + x86_64) + DOTNET_HASH_URL=cf3e1c73-a9a9-4e08-8607-8f9edae5f3f2/40a021a98a6b6e430a1f170037735f6f + DOTNET_FILE_NAME=dotnet-sdk-6.0.301-osx-x64 + ;; + arm64) + DOTNET_HASH_URL=3859fff3-f8a9-4e05-87cd-bd6db75833f5/64ec1099d45f85d14099da3c1f92a5c3 + DOTNET_FILE_NAME=dotnet-sdk-6.0.301-osx-arm64 + ;; + *) echo "Unknown architecture $DOTNET_ARCH" >&2; exit 1;; + esac;; +Linux) + DOTNET_ARCH=$(linux$(getconf LONG_BIT) uname -m) + case $DOTNET_ARCH in + x86_64) + if is_linux_musl; then + DOTNET_HASH_URL=206aebda-126f-484f-af02-051a17c1ec54/2ec559cb69cec83ffa64dba5441a1b2d + DOTNET_FILE_NAME=dotnet-sdk-6.0.301-linux-musl-x64 + else + DOTNET_HASH_URL=77d472e5-194c-421e-992d-e4ca1d08e6cc/56c61ac303ddf1b12026151f4f000a2b + DOTNET_FILE_NAME=dotnet-sdk-6.0.301-linux-x64 + fi + ;; + aarch64) + if is_linux_musl; then + DOTNET_HASH_URL=4bd2399a-e0e9-43a6-9767-ac15dd430b1c/3dd4307a1ce811e31943d80eee638bc1 + DOTNET_FILE_NAME=dotnet-sdk-6.0.301-linux-musl-arm64 + else + DOTNET_HASH_URL=06c4ee8e-bf2c-4e46-ab1c-e14dd72311c1/f7bc6c9677eaccadd1d0e76c55d361ea + DOTNET_FILE_NAME=dotnet-sdk-6.0.301-linux-arm64 + fi + ;; + armv7l | armv8l) + if is_linux_musl; then + DOTNET_HASH_URL=952c468c-ac70-46b0-9274-4cb9c270950c/f0cd4c8392158547c2fa38674bfd56fd + DOTNET_FILE_NAME=dotnet-sdk-6.0.301-linux-musl-arm + else + DOTNET_HASH_URL=a218e3b9-941b-43be-bfb1-615862777457/80954de34ab68729981ed372a8d25b46 + DOTNET_FILE_NAME=dotnet-sdk-6.0.301-linux-arm + fi + ;; + *) echo "Unknown architecture $DOTNET_ARCH" >&2; exit 1;; + esac;; +*) echo "Unknown platform: $(uname)" >&2; exit 1;; +esac + +DOTNET_URL=https://cache-redirector.jetbrains.com/download.visualstudio.microsoft.com/download/pr/$DOTNET_HASH_URL/$DOTNET_FILE_NAME.tar.gz +DOTNET_TARGET_DIR=$TARGET_DIR/$DOTNET_FILE_NAME-$SCRIPT_VERSION +DOTNET_TEMP_FILE=$TARGET_DIR/dotnet-sdk-temp.tar.gz + +if grep -q -x "$DOTNET_URL" "$DOTNET_TARGET_DIR/.flag" 2>/dev/null; then + # Everything is up-to-date in $DOTNET_TARGET_DIR, do nothing + true +else +while true; do # Note(k15tfu): for goto + mkdir -p "$TARGET_DIR" + + LOCK_FILE="$TARGET_DIR/.dotnet-cmd-lock.pid" + TMP_LOCK_FILE="$TARGET_DIR/.tmp.$$.pid" + echo $$ >"$TMP_LOCK_FILE" + + while ! ln "$TMP_LOCK_FILE" "$LOCK_FILE" 2>/dev/null; do + LOCK_OWNER=$(cat "$LOCK_FILE" 2>/dev/null || true) + while [ -n "$LOCK_OWNER" ] && ps -p $LOCK_OWNER >/dev/null; do + warn "Waiting for the process $LOCK_OWNER to finish bootstrap dotnet.cmd" + sleep 1 + LOCK_OWNER=$(cat "$LOCK_FILE" 2>/dev/null || true) + + # Hurry up, bootstrap is ready.. + if grep -q -x "$DOTNET_URL" "$DOTNET_TARGET_DIR/.flag" 2>/dev/null; then + break 3 # Note(k15tfu): goto out of the outer if-else block. + fi + done + + if [ -n "$LOCK_OWNER" ] && grep -q -x $LOCK_OWNER "$LOCK_FILE" 2>/dev/null; then + die "ERROR: The lock file $LOCK_FILE still exists on disk after the owner process $LOCK_OWNER exited" + fi + done + + trap "rm -f \"$LOCK_FILE\"" EXIT + rm "$TMP_LOCK_FILE" + + if ! grep -q -x "$DOTNET_URL" "$DOTNET_TARGET_DIR/.flag" 2>/dev/null; then + warn "Downloading $DOTNET_URL to $DOTNET_TEMP_FILE" + + rm -f "$DOTNET_TEMP_FILE" + if command -v curl >/dev/null 2>&1; then + if [ -t 1 ]; then CURL_PROGRESS="--progress-bar"; else CURL_PROGRESS="--silent --show-error"; fi + retry_on_error 5 curl -L $CURL_PROGRESS --output "${DOTNET_TEMP_FILE}" "$DOTNET_URL" + elif command -v wget >/dev/null 2>&1; then + if [ -t 1 ]; then WGET_PROGRESS=""; else WGET_PROGRESS="-nv"; fi + retry_on_error 5 wget $WGET_PROGRESS -O "${DOTNET_TEMP_FILE}" "$DOTNET_URL" + else + die "ERROR: Please install wget or curl" + fi + + warn "Extracting $DOTNET_TEMP_FILE to $DOTNET_TARGET_DIR" + rm -rf "$DOTNET_TARGET_DIR" + mkdir -p "$DOTNET_TARGET_DIR" + + tar -x -f "$DOTNET_TEMP_FILE" -C "$DOTNET_TARGET_DIR" + rm -f "$DOTNET_TEMP_FILE" + + echo "$DOTNET_URL" >"$DOTNET_TARGET_DIR/.flag" + fi + + rm "$LOCK_FILE" + break +done +fi + +if [ ! -x "$DOTNET_TARGET_DIR/dotnet" ]; then + die "Unable to find dotnet under $DOTNET_TARGET_DIR" +fi + +exec "$DOTNET_TARGET_DIR/dotnet" "$@" + +:CMDSCRIPT + +setlocal +set SCRIPT_VERSION=v2 +set COMPANY_NAME=UtBot +set TARGET_DIR=%LOCALAPPDATA%\%COMPANY_NAME%\dotnet-cmd\ + +for /f "tokens=3 delims= " %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v "PROCESSOR_ARCHITECTURE"') do set ARCH=%%a + +if "%ARCH%"=="ARM64" ( + set DOTNET_HASH_URL=656c8345-6661-409e-871d-00ca93cec542/cae3dcdc5c668c0e0abcf12d838348f1 + set DOTNET_FILE_NAME=dotnet-sdk-6.0.301-win-arm64 +) else ( + +if "%ARCH%"=="AMD64" ( + set DOTNET_HASH_URL=333eba0c-3242-48f3-a923-fdac5f219f77/342a4595101e3b4616360a7666459236 + set DOTNET_FILE_NAME=dotnet-sdk-6.0.301-win-x64 +) else ( + +if "%ARCH%"=="x86" ( + set DOTNET_HASH_URL=0a9cabcb-cb52-4f5e-bb79-1298f9ff9e22/c306c5cc940a9bb9a39ffe6619a255e6 + set DOTNET_FILE_NAME=dotnet-sdk-6.0.301-win-x86 +) else ( + +echo Unknown Windows architecture +goto fail + +))) + +set DOTNET_URL=https://cache-redirector.jetbrains.com/download.visualstudio.microsoft.com/download/pr/%DOTNET_HASH_URL%/%DOTNET_FILE_NAME%.zip +set DOTNET_TARGET_DIR=%TARGET_DIR%%DOTNET_FILE_NAME%-%SCRIPT_VERSION%\ +set DOTNET_TEMP_FILE=%TARGET_DIR%dotnet-sdk-temp.zip +set DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true +set DOTNET_SKIP_FIRST_TIME_EXPERIENCE=true +set DOTNET_CLI_TELEMETRY_OPTOUT=true + +set POWERSHELL=%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe + +if not exist "%DOTNET_TARGET_DIR%.flag" goto downloadAndExtractDotNet + +set /p CURRENT_FLAG=<"%DOTNET_TARGET_DIR%.flag" +if "%CURRENT_FLAG%" == "%DOTNET_URL%" goto continueWithDotNet + +:downloadAndExtractDotNet + +set DOWNLOAD_AND_EXTRACT_DOTNET_PS1= ^ +Set-StrictMode -Version 3.0; ^ +$ErrorActionPreference = 'Stop'; ^ + ^ +$createdNew = $false; ^ +$lock = New-Object System.Threading.Mutex($true, 'Global\dotnet-cmd-lock', [ref]$createdNew); ^ +if (-not $createdNew) { ^ + Write-Host 'Waiting for the other process to finish bootstrap dotnet.cmd'; ^ + [void]$lock.WaitOne(); ^ +} ^ + ^ +try { ^ + if ((Get-Content '%DOTNET_TARGET_DIR%.flag' -ErrorAction Ignore) -ne '%DOTNET_URL%') { ^ + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; ^ + Write-Host 'Downloading %DOTNET_URL% to %DOTNET_TEMP_FILE%'; ^ + [void](New-Item '%TARGET_DIR%' -ItemType Directory -Force); ^ + (New-Object Net.WebClient).DownloadFile('%DOTNET_URL%', '%DOTNET_TEMP_FILE%'); ^ + ^ + Write-Host 'Extracting %DOTNET_TEMP_FILE% to %DOTNET_TARGET_DIR%'; ^ + if (Test-Path '%DOTNET_TARGET_DIR%') { ^ + Remove-Item '%DOTNET_TARGET_DIR%' -Recurse; ^ + } ^ + Add-Type -A 'System.IO.Compression.FileSystem'; ^ + [IO.Compression.ZipFile]::ExtractToDirectory('%DOTNET_TEMP_FILE%', '%DOTNET_TARGET_DIR%'); ^ + Remove-Item '%DOTNET_TEMP_FILE%'; ^ + ^ + Set-Content '%DOTNET_TARGET_DIR%.flag' -Value '%DOTNET_URL%'; ^ + } ^ +} ^ +finally { ^ + $lock.ReleaseMutex(); ^ +} + +"%POWERSHELL%" -nologo -noprofile -Command %DOWNLOAD_AND_EXTRACT_DOTNET_PS1% +if errorlevel 1 goto fail + +:continueWithDotNet + +if not exist "%DOTNET_TARGET_DIR%\dotnet.exe" ( + echo Unable to find dotnet.exe under %DOTNET_TARGET_DIR% + goto fail +) + +REM Prevent globally installed .NET Core from leaking into this runtime's lookup +SET DOTNET_MULTILEVEL_LOOKUP=0 + +for /f "tokens=2 delims=:." %%c in ('chcp') do set /a PREV_CODE_PAGE=%%c +chcp 65001 >nul && call "%DOTNET_TARGET_DIR%\dotnet.exe" %* +set /a DOTNET_EXIT_CODE=%ERRORLEVEL% +chcp %PREV_CODE_PAGE% >nul + +exit /B %DOTNET_EXIT_CODE% +endlocal + +:fail +echo "FAIL" +exit /b 1 \ No newline at end of file diff --git a/utbot-rider/src/dotnet/UtBot/UtBot.sln b/utbot-rider/src/dotnet/UtBot/UtBot.sln new file mode 100644 index 0000000000..7515c53f43 --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UtBot", "UtBot\UtBot.csproj", "{573A2CF1-56F0-4350-A018-E25A52A86E63}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {573A2CF1-56F0-4350-A018-E25A52A86E63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {573A2CF1-56F0-4350-A018-E25A52A86E63}.Debug|Any CPU.Build.0 = Debug|Any CPU + {573A2CF1-56F0-4350-A018-E25A52A86E63}.Release|Any CPU.ActiveCfg = Release|Any CPU + {573A2CF1-56F0-4350-A018-E25A52A86E63}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestAction.cs b/utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestAction.cs new file mode 100644 index 0000000000..6c9d8c290f --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestAction.cs @@ -0,0 +1,12 @@ +using JetBrains.Application.UI.ActionsRevised.Menu; +using JetBrains.ReSharper.Feature.Services.Generate.Actions; +using JetBrains.ReSharper.Resources.Resources.Icons; +using JetBrains.UI.RichText; + +namespace UtBot; + +[Action("Generate.UnitTest", "Generate Unit Test", Icon = typeof(PsiFeaturesUnsortedThemedIcons.FuncZoneGenerate))] +internal class GenerateUnitTestAction : GenerateActionBase +{ + protected override RichText Caption => "Generate Unit Test"; +} \ No newline at end of file diff --git a/utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestElementProvider.cs b/utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestElementProvider.cs new file mode 100644 index 0000000000..2218aa8073 --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestElementProvider.cs @@ -0,0 +1,39 @@ +using JetBrains.Annotations; +using JetBrains.ReSharper.Feature.Services.CSharp.Generate; +using JetBrains.ReSharper.Feature.Services.Generate; +using JetBrains.ReSharper.Psi; +using JetBrains.ReSharper.Psi.CSharp; +using JetBrains.ReSharper.Psi.Resolve; +using JetBrains.ReSharper.Psi.Tree; + +namespace UtBot; + +[GeneratorElementProvider(GenerateUnitTestWorkflow.Kind, typeof(CSharpLanguage))] +public class GenerateUnitTestElementProvider : GeneratorProviderBase +{ + public override void Populate(CSharpGeneratorContext context) + { + var memberSource = context.ExternalElementsSource?.GetTypeElement() ?? context.ClassDeclaration.DeclaredElement; + if (memberSource == null) return; + + var substitution = context.ExternalElementsSource?.GetSubstitution() ?? memberSource.IdSubstitution; + + var usageContext = (ITreeNode)context.ClassDeclaration.Body ?? context.ClassDeclaration; + + foreach (var method in memberSource.Methods) + if (MethodFilter(method, substitution, usageContext)) + { + var element = new GeneratorDeclaredElement(method, substitution); + context.ProvidedElements.Add(element); + context.InputElements.Add(element); + } + } + + protected virtual bool MethodFilter([NotNull] IMethod method, ISubstitution substitution, + [NotNull] ITreeNode context) + { + if (method.IsSynthetic()) return false; + if (method.GetAccessRights() != AccessRights.PUBLIC) return false; + return true; + } +} \ No newline at end of file diff --git a/utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestWorkflow.cs b/utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestWorkflow.cs new file mode 100644 index 0000000000..0daa5f1237 --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestWorkflow.cs @@ -0,0 +1,23 @@ +using JetBrains.ReSharper.Feature.Services.Generate.Actions; +using JetBrains.ReSharper.Feature.Services.Generate.Workflows; +using JetBrains.ReSharper.Psi.Resources; + +namespace UtBot; + +public class GenerateUnitTestWorkflow : GenerateCodeWorkflowBase +{ + public const string Kind = "UnitTest"; + + public GenerateUnitTestWorkflow() : base( + Kind, + PsiSymbolsThemedIcons.SymbolUnitTest.Id, + "Tests with UnitTestBot", + GenerateActionGroup.CLR_LANGUAGE, + "Generate tests with UnitTestBot", + "Select methods for generation", + "Generate.UnitTest") + { + } + + public override double Order => 10; +} \ No newline at end of file diff --git a/utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestWorkflowProvider.cs b/utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestWorkflowProvider.cs new file mode 100644 index 0000000000..7d4a8aacce --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestWorkflowProvider.cs @@ -0,0 +1,13 @@ +using JetBrains.Application.DataContext; +using JetBrains.ReSharper.Feature.Services.Generate.Actions; + +namespace UtBot; + +[GenerateProvider] +public class GenerateUnitTestWorkflowProvider : IGenerateWorkflowProvider +{ + public IEnumerable CreateWorkflow(IDataContext dataContext) + { + return new[] { new GenerateUnitTestWorkflow() }; + } +} \ No newline at end of file diff --git a/utbot-rider/src/dotnet/UtBot/UtBot/UnitTestBuilder.cs b/utbot-rider/src/dotnet/UtBot/UtBot/UnitTestBuilder.cs new file mode 100644 index 0000000000..1001dda77e --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot/UnitTestBuilder.cs @@ -0,0 +1,222 @@ +using System.Runtime.Loader; +using JetBrains.Application.Progress; +using JetBrains.Application.Threading; +using JetBrains.Application.Threading.Tasks; +using JetBrains.Application.UI.Controls; +using JetBrains.DataFlow; +using JetBrains.Lifetimes; +using JetBrains.ProjectModel; +using JetBrains.ProjectModel.Features.SolutionBuilders; +using JetBrains.ProjectModel.ProjectsHost; +using JetBrains.RdBackend.Common.Features; +using JetBrains.ReSharper.Feature.Services.CSharp.Generate; +using JetBrains.ReSharper.Feature.Services.Generate; +using JetBrains.ReSharper.Psi; +using JetBrains.ReSharper.Psi.CSharp; +using JetBrains.ReSharper.UnitTestFramework; +using JetBrains.ReSharper.UnitTestFramework.Criteria; +using JetBrains.ReSharper.UnitTestFramework.Execution.Hosting; +using JetBrains.ReSharper.UnitTestFramework.Exploration.Artifacts; +using JetBrains.Rider.Model; +using JetBrains.Util; + +namespace UtBot; + +[GeneratorBuilder(GenerateUnitTestWorkflow.Kind, typeof(CSharpLanguage))] +internal sealed class UnitTestBuilder : GeneratorBuilderBase +{ + private readonly IBackgroundProgressIndicatorManager _backgroundProgressIndicatorManager; + private readonly Lifetime _lifetime; + private readonly ILogger _logger; + private readonly IShellLocks _shellLocks; + private readonly ISolutionBuilder _solutionBuilder; + private readonly IUnitTestArtifactExplorationProcess _unitTestArtifactExplorationProcess; + private readonly IUnitTestingFacade _unitTestingFacade; + + public UnitTestBuilder( + Lifetime lifetime, + IShellLocks shellLocks, + IBackgroundProgressIndicatorManager backgroundProgressIndicatorManager, + ISolutionBuilder solutionBuilder, + IUnitTestArtifactExplorationProcess unitTestArtifactExplorationProcess, + IUnitTestingFacade unitTestingFacade, + ILogger logger) + { + _lifetime = lifetime; + _shellLocks = shellLocks; + _backgroundProgressIndicatorManager = backgroundProgressIndicatorManager; + _solutionBuilder = solutionBuilder; + _unitTestArtifactExplorationProcess = unitTestArtifactExplorationProcess; + _unitTestingFacade = unitTestingFacade; + _logger = logger; + } + + protected override void Process(CSharpGeneratorContext context, IProgressIndicator progress) + { + if (context.PsiModule.ContainingProjectModule is not IProject project) return; + var typeElement = context.ClassDeclaration.DeclaredElement; + if (typeElement == null) return; + if (!(typeElement is IClass) && !(typeElement is IStruct)) return; + var assembly = project.GetOutputFilePath(context.PsiModule.TargetFrameworkId); + var descriptors = new List(); + foreach (var inputElement in context.InputElements.WithProgress(progress, "Generating Unit tests") + .OfType>()) + descriptors.Add(new UnitTestMethodDescriptor + { MethodName = inputElement.DeclaredElement.ShortName, TypeName = typeElement.ShortName }); + + var progressLifetimeDef = _lifetime.CreateNested(); + var indicator = + _backgroundProgressIndicatorManager.CreateIndicator(progressLifetimeDef.Lifetime, true, true, + "Generating Unit Tests"); + _shellLocks.Tasks.StartNew(_lifetime, Scheduling.FreeThreaded, () => + { + try + { + Generate(indicator, project, assembly, descriptors); + } + finally + { + progressLifetimeDef.Terminate(); + } + }); + } + + private void Generate(IBackgroundProgressIndicator progressIndicator, IProject project, + VirtualFileSystemPath assemblyPath, List descriptors) + { + SolutionBuilderRequest buildRequest; + var contextUnloaded = false; + using (_shellLocks.UsingReadLock()) + { + if (!project.IsValid()) return; + buildRequest = _solutionBuilder.CreateBuildRequest(BuildSessionTarget.Build, + new[] { project }, + SolutionBuilderRequestSilentMode.Silent, + new SolutionBuilderRequestAdvancedSettings(null, + false, verbosityLevel: LoggerVerbosityLevel.Normal, isRestoreRequest: true)); + + _solutionBuilder.ExecuteBuildRequest(buildRequest); + } + + buildRequest.State.WaitForValue(_lifetime, state => state.HasFlag(BuildRunState.Completed)); + + var assemblyLoadContext = new AssemblyLoadContext("UnitTestGeneration"); + //var renderer = new NunitTestRenderer(assemblyLoadContext); + try + { + var fs = File.OpenRead(assemblyPath.FullPath); + var ass = assemblyLoadContext.LoadFromStream(fs); + fs.Close(); + fs.Dispose(); + var solution = project.GetSolution(); + var solutionMark = solution.GetSolutionMark(); + if (solutionMark == null) return; + string unitTestProjectLocation = null; + foreach (var descriptor in descriptors) + foreach (var type in ass.GetTypes()) + { + if (type.Name != descriptor.TypeName) continue; + foreach (var methodInfo in type.GetMethods()) + { + if (methodInfo.Name != descriptor.MethodName) continue; + progressIndicator.Header.SetValue($"{descriptor.TypeName}.{descriptor.MethodName}"); + var solutionPath = solutionMark.Location.Parent.FullPath; + _logger.Verbose($"Solution path: {solutionPath}"); + _logger.Verbose("Start Generation"); + _logger.Catch(() => + { + MessageBox.ShowError("Put test renderer call here"); + unitTestProjectLocation = ""; /* renderer.Generate( + project.ProjectFileLocation.FullPath, + solutionPath, + methodInfo, + path => + { + _shellLocks.ExecuteOrQueue(_lifetime, "UnitTestBuilder::Generate", () => + { + if (solution.IsValid()) + { + solution.GetProtocolSolution().GetFileSystemModel().RefreshPaths + .Start(_lifetime, + new RdFsRefreshRequest(new List { path }, true)); + } + }); + });*/ + }); + _logger.Verbose("Generation finished"); + } + } + + //assemblyLoadContext.Unload(); + contextUnloaded = true; + _shellLocks.ExecuteOrQueue(_lifetime, "UnitTestBuilder::Generate", () => + { + if (project.IsValid()) + solution.GetProtocolSolution().GetFileSystemModel().RefreshPaths + .Start(_lifetime, + new RdFsRefreshRequest(new List { solutionMark.Location.FullPath }, true)); + }); + + if (unitTestProjectLocation == null) return; + IProject unitTestProject = null; + var manualResetEvent = new ManualResetEvent(false); + var unitTestProjectPath = VirtualFileSystemPath.Parse(unitTestProjectLocation, InteractionContext.Local); + + while (unitTestProject == null && _lifetime.IsAlive && project.IsValid()) + { + _shellLocks.ExecuteOrQueueReadLock(_lifetime, "UnitTestBuilder::WaitingForProject", + () => + { + _logger.Warn("Try to get project"); + unitTestProject = TryToFindProject(solution, unitTestProjectPath); + manualResetEvent.Set(); + }); + if (!manualResetEvent.WaitOne(TimeSpan.FromSeconds(10))) + { + _logger.Warn("Exit by timeout"); + return; + } + + manualResetEvent.Reset(); + Thread.Sleep(1000); + } + + if (unitTestProject != null && unitTestProject.IsValid()) + { + using (_shellLocks.UsingReadLock()) + { + if (!unitTestProject.IsValid()) return; + buildRequest = _solutionBuilder.CreateBuildRequest(BuildSessionTarget.Build, + new[] { unitTestProject }, + SolutionBuilderRequestSilentMode.Silent, + new SolutionBuilderRequestAdvancedSettings(null, + false, verbosityLevel: LoggerVerbosityLevel.Normal, isRestoreRequest: true)); + + _solutionBuilder.ExecuteBuildRequest(buildRequest); + } + + buildRequest.State.WaitForValue(_lifetime, state => state.HasFlag(BuildRunState.Completed)); + + _unitTestArtifactExplorationProcess.ExploreProject(project).ContinueWith(_ => + { + _unitTestingFacade + .Run(new ProjectCriterion(unitTestProject)) + .Using(UnitTestHost.Instance.GetProvider("Cover")) + .In.CurrentOrNewSession(); + }, _lifetime.ToCancellationToken()); + } + } + finally + { + if (!contextUnloaded) + { + //assemblyLoadContext.Unload(); + } + } + } + + private IProject TryToFindProject(ISolution solution, VirtualFileSystemPath unitTestProjectPath) + { + return solution.FindProjectItemsByLocation(unitTestProjectPath).SingleItem() as IProject; + } +} \ No newline at end of file diff --git a/utbot-rider/src/dotnet/UtBot/UtBot/UnitTestMethodDescriptor.cs b/utbot-rider/src/dotnet/UtBot/UtBot/UnitTestMethodDescriptor.cs new file mode 100644 index 0000000000..cc53f4edfe --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot/UnitTestMethodDescriptor.cs @@ -0,0 +1,7 @@ +namespace UtBot; + +public class UnitTestMethodDescriptor +{ + public string MethodName; + public string TypeName; +} \ No newline at end of file diff --git a/utbot-rider/src/dotnet/UtBot/UtBot/UtBot.csproj b/utbot-rider/src/dotnet/UtBot/UtBot/UtBot.csproj new file mode 100644 index 0000000000..0f1fb946df --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot/UtBot.csproj @@ -0,0 +1,15 @@ + + + + net6.0 + enable + enable + UtBot + + + + + + + + \ No newline at end of file diff --git a/utbot-rider/src/dotnet/UtBot/UtBot/ZoneMarker.cs b/utbot-rider/src/dotnet/UtBot/UtBot/ZoneMarker.cs new file mode 100644 index 0000000000..927016ff1c --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot/ZoneMarker.cs @@ -0,0 +1,22 @@ +using JetBrains.Application.BuildScript.Application.Zones; +using JetBrains.ProjectModel; +using JetBrains.ReSharper.Psi.CSharp; +using JetBrains.ReSharper.UnitTestFramework; +using JetBrains.Rider.Model; + +namespace UtBot; + +[ZoneDefinition(ZoneFlags.AutoEnable)] +public interface IUtBotPluginZone : + IZone, + IRequire, + IRequire, + IRequire, + IRequire +{ +} + +[ZoneMarker] +public class ZoneMarker : IRequire +{ +} \ No newline at end of file diff --git a/utbot-rider/src/main/resources/META-INF/plugin.xml b/utbot-rider/src/main/resources/META-INF/plugin.xml new file mode 100644 index 0000000000..801161f0e1 --- /dev/null +++ b/utbot-rider/src/main/resources/META-INF/plugin.xml @@ -0,0 +1,60 @@ + + + + org.utbot.intellij.plugin.id + UnitTestBot + utbot.org + com.intellij.modules.rider + + + + unit tests with a single action! +
+
+ The UTBot engine goes through your code instructions and generates regression tests. +
+
+ The engine finds potential problems in your code: +
+
+
    +
  • exceptions
  • +
  • hangs
  • +
  • overflows
  • +
  • and even native crashes
  • +
+
+ They are not a surprise for you anymore. The engine will find the problems and generate tests for them. +
+
+ The engine carefully selects tests to maximize statement and branch coverage. Our credo is to maximize test coverage and minimize tests number. +
+
+ You can try the engine online without installation. +
+
+ Got ideas? Let us know or become a contributor on our GitHub page +
+
+ Found an issue? Please, submit it here. + ]]> +
+ + +
  • Java 11 support.
  • +
  • Smart Fuzzer significantly improves test generation results.
  • +
  • Generated tests have become even more human-readable and user-friendly.
  • +
  • We have enabled Mac OS X platform, give it a try.
  • +
  • The UnitTestBot engine generates SARIF reports.
  • +
  • We have polished plugin UX.
  • +
  • Mocking support is enhanced.
  • +
  • Java Streams, better Java Optional support, Java String support is improved, package-private constructors now are used for the test generation.
  • + + Discover everything mentioned above and much more in this release. + ]]> +
    + +
    diff --git a/utbot-rider/src/main/resources/META-INF/pluginIcon.svg b/utbot-rider/src/main/resources/META-INF/pluginIcon.svg new file mode 100644 index 0000000000..08d64eb8d1 --- /dev/null +++ b/utbot-rider/src/main/resources/META-INF/pluginIcon.svg @@ -0,0 +1,111 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/utbot-rider/src/main/resources/META-INF/pluginIcon_dark.svg b/utbot-rider/src/main/resources/META-INF/pluginIcon_dark.svg new file mode 100644 index 0000000000..3cf0a92370 --- /dev/null +++ b/utbot-rider/src/main/resources/META-INF/pluginIcon_dark.svg @@ -0,0 +1,111 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + +