├── .config └── dotnet-tools.json ├── .editorconfig ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── build.yml │ ├── publish.yml │ └── update.yml ├── .gitignore ├── CONTRIBUTING.md ├── GitVersion.yml ├── LICENSE ├── Machine.Specifications.Runner.ReSharper.sln ├── README.md ├── build.ps1 ├── build.sh ├── build ├── build.cs └── build.csproj ├── images ├── icon.png └── pluginIcon.svg ├── install-plugin.ps1 ├── plugin.xml └── src ├── Machine.Specifications.Runner.ReSharper.Adapters ├── Discovery │ ├── Discoverer.cs │ ├── DiscoveryCache.cs │ ├── IMspecController.cs │ ├── IMspecDiscoverySink.cs │ ├── MspecController.cs │ ├── MspecDiscoveryException.cs │ └── MspecDiscoverySink.cs ├── Elements │ ├── BehaviorElement.cs │ ├── ContextElement.cs │ ├── ContextInfoExtensions.cs │ ├── IBehaviorElement.cs │ ├── IContextElement.cs │ ├── IMspecElement.cs │ ├── ISpecificationElement.cs │ ├── SpecificationElement.cs │ └── SpecificationInfoExtensions.cs ├── ExceptionResultExtensions.cs ├── Execution │ ├── ConcurrentLookup.cs │ ├── ElementCache.cs │ ├── ExecutionAdapterRunListener.cs │ ├── Executor.cs │ ├── IExecutionListener.cs │ ├── RunContext.cs │ ├── RunTracker.cs │ ├── TaskWrapper.cs │ └── TestExecutionListener.cs ├── Listeners │ ├── AdapterListener.cs │ ├── AssemblyInfoExtensions.cs │ ├── ContextInfoExtensions.cs │ ├── ExceptionResultExtensions.cs │ ├── IRunListener.cs │ ├── LoggingRunListener.cs │ ├── ResultExtensions.cs │ ├── SpecificationInfoExtensions.cs │ ├── StatusExtensions.cs │ ├── TestAssemblyInfo.cs │ ├── TestContextInfo.cs │ ├── TestError.cs │ ├── TestRunResult.cs │ ├── TestSpecificationInfo.cs │ └── TestStatus.cs ├── Machine.Specifications.Runner.ReSharper.Adapters.csproj ├── MspecReSharperId.cs ├── MspecRunner.cs ├── RemoteTaskBuilder.cs └── RemoteTaskDepot.cs ├── Machine.Specifications.Runner.ReSharper.Tasks ├── Machine.Specifications.Runner.ReSharper.Tasks.csproj ├── MspecBehaviorSpecificationRemoteTask.cs ├── MspecContextRemoteTask.cs ├── MspecRemoteTask.cs ├── MspecSpecificationRemoteTask.cs └── MspecTestContainer.cs ├── Machine.Specifications.Runner.ReSharper.Tests ├── Adapters │ ├── Discovery │ │ └── MspecDiscoverySinkTests.cs │ ├── Elements │ │ ├── BehaviorElementTests.cs │ │ ├── ContextElementTests.cs │ │ └── SpecificationElementTests.cs │ ├── Execution │ │ ├── ConcurrentLookupTests.cs │ │ ├── ElementCacheTests.cs │ │ ├── ExecutionAdapterRunListenerTests.cs │ │ ├── RunContextTests.cs │ │ ├── TaskWrapperTests.cs │ │ └── TestExecutionListenerTests.cs │ └── RemoteTaskDepotTests.cs ├── Fixtures │ ├── ElementFixtures.cs │ └── RemoteTaskFixtures.cs ├── Machine.Specifications.Runner.ReSharper.Tests.csproj └── Tasks │ ├── MspecBehaviorSpecificationRemoteTaskTests.cs │ ├── MspecContextRemoteTaskTests.cs │ └── MspecSpecificationRemoteTaskTests.cs └── Machine.Specifications.Runner.ReSharper ├── Elements ├── IMspecTestElement.cs ├── MspecBehaviorSpecificationTestElement.cs ├── MspecContextTestElement.cs └── MspecSpecificationTestElement.cs ├── EnumerableExtensions.cs ├── Machine.Specifications.Runner.ReSharper.csproj ├── Mappings ├── MspecBehaviorSpecificationMapping.cs ├── MspecContextMapping.cs ├── MspecElementMapping.cs ├── MspecElementMappingKeys.cs └── MspecSpecificationMapping.cs ├── MspecProviderSettings.cs ├── MspecPsiFileExplorer.cs ├── MspecServiceProvider.cs ├── MspecTestExplorerFromArtifacts.cs ├── MspecTestExplorerFromFile.cs ├── MspecTestExplorerFromMetadata.cs ├── MspecTestExplorerFromTestRunner.cs ├── MspecTestMetadataExplorer.cs ├── MspecTestProvider.cs ├── MspecTestRunnerOrchestrator.cs ├── Options └── MspecPage.cs ├── Reflection ├── IAttributeInfo.cs ├── IFieldInfo.cs ├── ITypeInfo.cs ├── MetadataAttributeInfoAdapter.cs ├── MetadataExtensions.cs ├── MetadataFieldInfoAdapter.cs ├── MetadataTypeInfoAdapter.cs ├── PsiAttributeInfoAdapter.cs ├── PsiExtensions.cs ├── PsiFieldInfoAdapter.cs ├── PsiTypeInfoAdapter.cs ├── ReflectionExtensions.cs └── UnknownTypeInfoAdapter.cs ├── Resources ├── Machine.png └── MspecThemedIcons.cs ├── Rules └── EnsureAncestorsAddedToExecutedElementsRule.cs ├── Runner ├── AgentManagerHost.cs ├── IAgentManagerHost.cs └── MspecTestRunnerRunStrategy.cs ├── TypeInfoFactory.cs ├── UnitTestElementFactory.cs └── ZoneMarker.cs /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "gitversion.tool": { 6 | "version": "5.12.0", 7 | "commands": [ 8 | "dotnet-gitversion" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs. 2 | ; More information at http://EditorConfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.{cs,cake,ps1}] 11 | indent_style = space 12 | indent_size = 4 13 | 14 | [*.gold] 15 | insert_final_newline = false 16 | trim_trailing_whitespace = false 17 | 18 | [*.{csproj,targets,props}] 19 | indent_style = space 20 | indent_size = 2 21 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "nuget" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | 8 | - package-ecosystem: "nuget" 9 | directory: "/build" 10 | schedule: 11 | interval: "weekly" 12 | 13 | - package-ecosystem: "nuget" 14 | directory: "/.config" 15 | schedule: 16 | interval: "weekly" 17 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | env: 10 | DOTNET_NOLOGO: true 11 | 12 | jobs: 13 | build: 14 | name: build 15 | runs-on: windows-latest 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v3 20 | - name: Fetch all tags and branches 21 | run: git fetch --prune --unshallow 22 | - name: Build 23 | run: ./build.ps1 24 | - name: Upload artifacts 25 | uses: actions/upload-artifact@v4 26 | with: 27 | path: | 28 | artifacts/*.nupkg 29 | artifacts/*.zip 30 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | env: 8 | DOTNET_NOLOGO: true 9 | 10 | jobs: 11 | publish: 12 | name: publish 13 | runs-on: windows-latest 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v3 18 | - name: Fetch all tags and branches 19 | run: git fetch --prune --unshallow 20 | - name: Deploy 21 | env: 22 | JETBRAINS_API_KEY: ${{ secrets.JETBRAINS_API_KEY }} 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | run: ./build.ps1 publish 25 | - name: Upload artifacts 26 | uses: actions/upload-artifact@v4 27 | with: 28 | path: | 29 | artifacts/*.nupkg 30 | artifacts/*.zip 31 | - uses: NBTX/upload-release-assets@v1 32 | if: github.event_name == 'release' 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | with: 36 | targets: 'artifacts/*.*' 37 | upload_url: ${{ github.event.release.upload_url }} 38 | -------------------------------------------------------------------------------- /.github/workflows/update.yml: -------------------------------------------------------------------------------- 1 | name: update 2 | 3 | on: 4 | schedule: 5 | - cron: '0 8 * * 5' 6 | 7 | jobs: 8 | update: 9 | name: update 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | - run: dotnet restore 15 | - uses: MeilCli/nuget-update-check-action@v3 16 | id: outdated 17 | with: 18 | include_prerelease: true 19 | frameworks: net461 20 | - uses: juztcode/gitter-github-action@v1 21 | if: steps.outdated.outputs.has_nuget_update != 'false' && contains(steps.outdated.outputs.nuget_update_json, 'JetBrains.ReSharper.SDK') 22 | with: 23 | room-id: ${{ secrets.GITTER_ROOM_ID }} 24 | token: ${{ secrets.GITTER_API_KEY }} 25 | text: ${{ steps.outdated.outputs.nuget_update_text }} 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | [Aa][Rr][Mm]/ 24 | [Aa][Rr][Mm]64/ 25 | bld/ 26 | [Bb]in/ 27 | [Oo]bj/ 28 | [Ll]og/ 29 | 30 | # Visual Studio 2015/2017 cache/options directory 31 | .vs/ 32 | # Uncomment if you have tasks that create the project's static files in wwwroot 33 | #wwwroot/ 34 | 35 | # Visual Studio 2017 auto generated files 36 | Generated\ Files/ 37 | 38 | # MSTest test Results 39 | [Tt]est[Rr]esult*/ 40 | [Bb]uild[Ll]og.* 41 | _JetPackages/ 42 | 43 | # NUNIT 44 | *.VisualState.xml 45 | TestResult.xml 46 | 47 | # Build Results of an ATL Project 48 | [Dd]ebugPS/ 49 | [Rr]eleasePS/ 50 | dlldata.c 51 | 52 | # Benchmark Results 53 | BenchmarkDotNet.Artifacts/ 54 | 55 | # .NET Core 56 | project.lock.json 57 | project.fragment.lock.json 58 | artifacts/ 59 | 60 | # StyleCop 61 | StyleCopReport.xml 62 | 63 | # Files built by Visual Studio 64 | *_i.c 65 | *_p.c 66 | *_h.h 67 | *.ilk 68 | *.meta 69 | *.obj 70 | *.iobj 71 | *.pch 72 | *.pdb 73 | *.ipdb 74 | *.pgc 75 | *.pgd 76 | *.rsp 77 | *.sbr 78 | *.tlb 79 | *.tli 80 | *.tlh 81 | *.tmp 82 | *.tmp_proj 83 | *_wpftmp.csproj 84 | *.log 85 | *.vspscc 86 | *.vssscc 87 | .builds 88 | *.pidb 89 | *.svclog 90 | *.scc 91 | 92 | # Chutzpah Test files 93 | _Chutzpah* 94 | 95 | # Visual C++ cache files 96 | ipch/ 97 | *.aps 98 | *.ncb 99 | *.opendb 100 | *.opensdf 101 | *.sdf 102 | *.cachefile 103 | *.VC.db 104 | *.VC.VC.opendb 105 | 106 | # Visual Studio profiler 107 | *.psess 108 | *.vsp 109 | *.vspx 110 | *.sap 111 | 112 | # Visual Studio Trace Files 113 | *.e2e 114 | 115 | # TFS 2012 Local Workspace 116 | $tf/ 117 | 118 | # Guidance Automation Toolkit 119 | *.gpState 120 | 121 | # ReSharper is a .NET coding add-in 122 | _ReSharper*/ 123 | #*.[Rr]e[Ss]harper 124 | *.DotSettings.user 125 | 126 | # JustCode is a .NET coding add-in 127 | .JustCode 128 | 129 | # TeamCity is a build add-in 130 | _TeamCity* 131 | 132 | # DotCover is a Code Coverage Tool 133 | *.dotCover 134 | 135 | # AxoCover is a Code Coverage Tool 136 | .axoCover/* 137 | !.axoCover/settings.json 138 | 139 | # Visual Studio code coverage results 140 | *.coverage 141 | *.coveragexml 142 | 143 | # NCrunch 144 | _NCrunch_* 145 | .*crunch*.local.xml 146 | nCrunchTemp_* 147 | 148 | # MightyMoose 149 | *.mm.* 150 | AutoTest.Net/ 151 | 152 | # Web workbench (sass) 153 | .sass-cache/ 154 | 155 | # Installshield output folder 156 | [Ee]xpress/ 157 | 158 | # DocProject is a documentation generator add-in 159 | DocProject/buildhelp/ 160 | DocProject/Help/*.HxT 161 | DocProject/Help/*.HxC 162 | DocProject/Help/*.hhc 163 | DocProject/Help/*.hhk 164 | DocProject/Help/*.hhp 165 | DocProject/Help/Html2 166 | DocProject/Help/html 167 | 168 | # Click-Once directory 169 | publish/ 170 | 171 | # Publish Web Output 172 | *.[Pp]ublish.xml 173 | *.azurePubxml 174 | # Note: Comment the next line if you want to checkin your web deploy settings, 175 | # but database connection strings (with potential passwords) will be unencrypted 176 | *.pubxml 177 | *.publishproj 178 | 179 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 180 | # checkin your Azure Web App publish settings, but sensitive information contained 181 | # in these scripts will be unencrypted 182 | PublishScripts/ 183 | 184 | # NuGet Packages 185 | *.nupkg 186 | # The packages folder can be ignored because of Package Restore 187 | **/[Pp]ackages/* 188 | # except build/, which is used as an MSBuild target. 189 | !**/[Pp]ackages/build/ 190 | # Uncomment if necessary however generally it will be regenerated when needed 191 | #!**/[Pp]ackages/repositories.config 192 | # NuGet v3's project.json files produces more ignorable files 193 | *.nuget.props 194 | *.nuget.targets 195 | 196 | # Microsoft Azure Build Output 197 | csx/ 198 | *.build.csdef 199 | 200 | # Microsoft Azure Emulator 201 | ecf/ 202 | rcf/ 203 | 204 | # Windows Store app package directories and files 205 | AppPackages/ 206 | BundleArtifacts/ 207 | Package.StoreAssociation.xml 208 | _pkginfo.txt 209 | *.appx 210 | 211 | # Visual Studio cache files 212 | # files ending in .cache can be ignored 213 | *.[Cc]ache 214 | # but keep track of directories ending in .cache 215 | !?*.[Cc]ache/ 216 | 217 | # Others 218 | ClientBin/ 219 | ~$* 220 | *~ 221 | *.dbmdl 222 | *.dbproj.schemaview 223 | *.jfm 224 | *.pfx 225 | *.publishsettings 226 | orleans.codegen.cs 227 | 228 | # Including strong name files can present a security risk 229 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 230 | #*.snk 231 | 232 | # Since there are multiple workflows, uncomment next line to ignore bower_components 233 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 234 | #bower_components/ 235 | # ASP.NET Core default setup: bower directory is configured as wwwroot/lib/ and bower restore is true 236 | **/wwwroot/lib/ 237 | 238 | # RIA/Silverlight projects 239 | Generated_Code/ 240 | 241 | # Backup & report files from converting an old project file 242 | # to a newer Visual Studio version. Backup files are not needed, 243 | # because we have git ;-) 244 | _UpgradeReport_Files/ 245 | Backup*/ 246 | UpgradeLog*.XML 247 | UpgradeLog*.htm 248 | ServiceFabricBackup/ 249 | *.rptproj.bak 250 | 251 | # SQL Server files 252 | *.mdf 253 | *.ldf 254 | *.ndf 255 | 256 | # Business Intelligence projects 257 | *.rdl.data 258 | *.bim.layout 259 | *.bim_*.settings 260 | *.rptproj.rsuser 261 | 262 | # Microsoft Fakes 263 | FakesAssemblies/ 264 | 265 | # GhostDoc plugin setting file 266 | *.GhostDoc.xml 267 | 268 | # Node.js Tools for Visual Studio 269 | .ntvs_analysis.dat 270 | node_modules/ 271 | 272 | # Visual Studio 6 build log 273 | *.plg 274 | 275 | # Visual Studio 6 workspace options file 276 | *.opt 277 | 278 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 279 | *.vbw 280 | 281 | # Visual Studio LightSwitch build output 282 | **/*.HTMLClient/GeneratedArtifacts 283 | **/*.DesktopClient/GeneratedArtifacts 284 | **/*.DesktopClient/ModelManifest.xml 285 | **/*.Server/GeneratedArtifacts 286 | **/*.Server/ModelManifest.xml 287 | _Pvt_Extensions 288 | 289 | # Paket dependency manager 290 | .paket/paket.exe 291 | paket-files/ 292 | 293 | # FAKE - F# Make 294 | .fake/ 295 | 296 | # JetBrains Rider 297 | .idea/ 298 | *.sln.iml 299 | 300 | # CodeRush personal settings 301 | .cr/personal 302 | 303 | # Python Tools for Visual Studio (PTVS) 304 | __pycache__/ 305 | *.pyc 306 | 307 | # Cake - Uncomment if you are using it 308 | tools/** 309 | !tools/packages.config 310 | 311 | # Tabs Studio 312 | *.tss 313 | 314 | # Telerik's JustMock configuration file 315 | *.jmconfig 316 | 317 | # BizTalk build output 318 | *.btp.cs 319 | *.btm.cs 320 | *.odx.cs 321 | *.xsd.cs 322 | 323 | # OpenCover UI analysis results 324 | OpenCover/ 325 | 326 | # Azure Stream Analytics local run output 327 | ASALocalRun/ 328 | 329 | # MSBuild Binary and Structured Log 330 | *.binlog 331 | 332 | # NVidia Nsight GPU debugger configuration file 333 | *.nvuser 334 | 335 | # MFractors (Xamarin productivity tool) working folder 336 | .mfractor/ 337 | 338 | # Local History for Visual Studio 339 | .localhistory/ 340 | 341 | # BeatPulse healthcheck temp database 342 | healthchecksdb 343 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | First of all, thank you for wanting to contribute to Machine.Specifications! We really appreciate all the awesome support we get from our community. We want to keep it as easy as possible for you to contribute changes that make Machine.Specifications better for you. There are a few guidelines that we need contributors to follow so that we can all work together happily. 4 | 5 | ## Preparation 6 | 7 | Before starting work on a new bug, feature, etc. ensure that an [issue](https://github.com/machine/machine.specifications/issues) has been raised. Indicate your intention to work on the issue by writing a comment against it. This will prevent duplication of effort. If the issue is a new feature, it's usually best to propose a design in the issue comments. -------------------------------------------------------------------------------- /GitVersion.yml: -------------------------------------------------------------------------------- 1 | mode: ContinuousDelivery 2 | commit-message-incrementing: Disabled 3 | legacy-semver-padding: 0 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008 Machine Project 2 | Portions Copyright (c) 2008 Jacob Lewallen, Aaron Jensen 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | ***************************** 23 | Some parts licensed under MS-PL 24 | ***************************** 25 | 26 | This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software. 27 | 28 | 1. Definitions 29 | 30 | The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law. 31 | 32 | A "contribution" is the original software, or any additions or changes to the software. 33 | 34 | A "contributor" is any person that distributes its contribution under this license. 35 | 36 | "Licensed patents" are a contributor's patent claims that read directly on its contribution. 37 | 38 | 2. Grant of Rights 39 | 40 | (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. 41 | 42 | (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. 43 | 44 | 3. Conditions and Limitations 45 | 46 | (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. 47 | 48 | (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically. 49 | 50 | (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software. 51 | 52 | (D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license. 53 | 54 | (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. -------------------------------------------------------------------------------- /Machine.Specifications.Runner.ReSharper.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33110.190 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Machine.Specifications.Runner.ReSharper", "src\Machine.Specifications.Runner.ReSharper\Machine.Specifications.Runner.ReSharper.csproj", "{79A3A8CA-CAEF-4DC6-83C9-E19A0C720B0E}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{9725D2B9-232B-45A2-92CC-4F7CDE717C94}" 9 | ProjectSection(SolutionItems) = preProject 10 | .github\workflows\build.yml = .github\workflows\build.yml 11 | CONTRIBUTING.md = CONTRIBUTING.md 12 | .config\dotnet-tools.json = .config\dotnet-tools.json 13 | install-plugin.ps1 = install-plugin.ps1 14 | plugin.xml = plugin.xml 15 | .github\workflows\publish.yml = .github\workflows\publish.yml 16 | README.md = README.md 17 | .github\workflows\update.yml = .github\workflows\update.yml 18 | EndProjectSection 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Machine.Specifications.Runner.ReSharper.Adapters", "src\Machine.Specifications.Runner.ReSharper.Adapters\Machine.Specifications.Runner.ReSharper.Adapters.csproj", "{49062A8E-B51E-4DE6-AD81-9CEE62BBB25B}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Machine.Specifications.Runner.ReSharper.Tasks", "src\Machine.Specifications.Runner.ReSharper.Tasks\Machine.Specifications.Runner.ReSharper.Tasks.csproj", "{8788CA0C-44A2-40EF-9859-076313FE884E}" 23 | EndProject 24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Machine.Specifications.Runner.ReSharper.Tests", "src\Machine.Specifications.Runner.ReSharper.Tests\Machine.Specifications.Runner.ReSharper.Tests.csproj", "{64E9C141-E05B-4CBD-B8FC-75AF0EBAF907}" 25 | EndProject 26 | Global 27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 28 | Debug|Any CPU = Debug|Any CPU 29 | Release|Any CPU = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 32 | {79A3A8CA-CAEF-4DC6-83C9-E19A0C720B0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {79A3A8CA-CAEF-4DC6-83C9-E19A0C720B0E}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {79A3A8CA-CAEF-4DC6-83C9-E19A0C720B0E}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {79A3A8CA-CAEF-4DC6-83C9-E19A0C720B0E}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {49062A8E-B51E-4DE6-AD81-9CEE62BBB25B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {49062A8E-B51E-4DE6-AD81-9CEE62BBB25B}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {49062A8E-B51E-4DE6-AD81-9CEE62BBB25B}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {49062A8E-B51E-4DE6-AD81-9CEE62BBB25B}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {8788CA0C-44A2-40EF-9859-076313FE884E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {8788CA0C-44A2-40EF-9859-076313FE884E}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {8788CA0C-44A2-40EF-9859-076313FE884E}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {8788CA0C-44A2-40EF-9859-076313FE884E}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {64E9C141-E05B-4CBD-B8FC-75AF0EBAF907}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {64E9C141-E05B-4CBD-B8FC-75AF0EBAF907}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {64E9C141-E05B-4CBD-B8FC-75AF0EBAF907}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {64E9C141-E05B-4CBD-B8FC-75AF0EBAF907}.Release|Any CPU.Build.0 = Release|Any CPU 48 | EndGlobalSection 49 | GlobalSection(SolutionProperties) = preSolution 50 | HideSolutionNode = FALSE 51 | EndGlobalSection 52 | GlobalSection(ExtensibilityGlobals) = postSolution 53 | SolutionGuid = {96104B85-D662-42D9-8F61-E8D8C126C3A8} 54 | EndGlobalSection 55 | EndGlobal 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Machine.Specifications.Runner.Resharper 2 | Machine.Specifications provides a Resharper plugin to integrate with the ReSharper test runner. Search for `Machine.Specifications.Runner.Resharper` in the Resharper extension gallery. 3 | 4 | To open an issue, please visit the [core issue tracker](https://github.com/machine/machine.specifications/issues). 5 | 6 | ### Debugging 7 | 8 | 1) Run the `install-plugin.ps1` script the first time to install ReSharper to an experimental hive and deploy the plugin 9 | 2) Set the startup project to `Machine.Specifications.Runner.Resharper` 10 | 3) Run and debug from Visual Studio 11 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | 3 | Set-StrictMode -Version Latest 4 | $ErrorActionPreference = "Stop" 5 | 6 | if ($null -eq (Get-Command "dotnet" -ErrorAction Ignore)) { 7 | throw "Could not find 'dotnet', please install .NET SDK" 8 | } 9 | 10 | Push-Location (Split-Path $MyInvocation.MyCommand.Definition) 11 | 12 | try { 13 | & dotnet run --project build --no-launch-profile -- $args 14 | } 15 | finally { 16 | Pop-Location 17 | } 18 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | if which dotnet > /dev/null; then 6 | dotnet run --project build --no-launch-profile -- "$@" 7 | else 8 | echo "error(1): Could not find 'dotnet', please install .NET SDK" 9 | exit 1 10 | fi 11 | -------------------------------------------------------------------------------- /build/build.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Compression; 4 | using System.Linq; 5 | using System.Net.Http; 6 | using System.Text; 7 | using System.Text.Json; 8 | using System.Threading.Tasks; 9 | using System.Xml.Linq; 10 | using System.Xml.XPath; 11 | using GlobExpressions; 12 | using static Bullseye.Targets; 13 | using static SimpleExec.Command; 14 | 15 | var configuration = "Release"; 16 | var version = await GetGitVersion(); 17 | var waveVersion = GetWaveVersion(); 18 | var notes = GetReleaseNotes(); 19 | var apiKey = Environment.GetEnvironmentVariable("JETBRAINS_API_KEY"); 20 | 21 | Target("clean", () => 22 | { 23 | Run("dotnet", $"clean --configuration {configuration}"); 24 | 25 | if (Directory.Exists("artifacts")) 26 | { 27 | Directory.Delete("artifacts", true); 28 | } 29 | }); 30 | 31 | Target("restore", DependsOn("clean"), () => 32 | { 33 | Run("dotnet", "restore"); 34 | }); 35 | 36 | Target("build", DependsOn("restore"), () => 37 | { 38 | Run("dotnet", "build " + 39 | "--no-restore " + 40 | $"--configuration {configuration} " + 41 | "--property HostFullIdentifier= " + 42 | $"--property Version={version.SemVer} " + 43 | $"--property AssemblyVersion={version.AssemblySemVer} " + 44 | $"--property FileVersion={version.AssemblySemFileVer} " + 45 | $"--property InformationalVersion={version.InformationalVersion}"); 46 | }); 47 | 48 | Target("test", DependsOn("build"), () => 49 | { 50 | Run("dotnet", $"test --configuration {configuration} --no-restore --no-build"); 51 | }); 52 | 53 | Target("package", DependsOn("build", "test"), () => 54 | { 55 | Run("dotnet", $"pack --configuration {configuration} --no-restore --no-build --output artifacts --property Version={version.SemVer}"); 56 | }); 57 | 58 | Target("zip", DependsOn("package"), () => 59 | { 60 | var artifactPath = Path.Combine("artifacts", "machine-specifications"); 61 | var dotnetPath = Path.Combine(artifactPath, "dotnet"); 62 | var libPath = Path.Combine(artifactPath, "lib"); 63 | var metaPath = Path.Combine(libPath, "META-INF"); 64 | var jarPath = Path.Combine(libPath, $"machine-specifications-{version.SemVer}.jar"); 65 | var zipPath = Path.Combine("artifacts", $"machine-specifications-{version.SemVer}.zip"); 66 | 67 | Directory.CreateDirectory(dotnetPath); 68 | Directory.CreateDirectory(metaPath); 69 | 70 | var nupkg = Glob.Files("artifacts", "*.nupkg").First(); 71 | 72 | using var archive = ZipFile.OpenRead(Path.Combine("artifacts", nupkg)); 73 | 74 | foreach (var entry in archive.Entries.Where(x => x.FullName.Contains("DotFiles"))) 75 | { 76 | entry.ExtractToFile(Path.Combine(dotnetPath, entry.Name)); 77 | } 78 | 79 | var plugin = File.ReadAllText("plugin.xml") 80 | .Replace("${Version}", version.SemVer) 81 | .Replace("${SinceBuild}", waveVersion + ".0") 82 | .Replace("${UntilBuild}", waveVersion + ".*") 83 | .Replace("${ChangeNotes}", notes); 84 | 85 | var icon = File.ReadAllBytes(Path.Combine("images", "pluginIcon.svg")); 86 | 87 | File.WriteAllText(Path.Combine(metaPath, "plugin.xml"), plugin); 88 | File.WriteAllText(Path.Combine(metaPath, "MANIFEST.MF"), "Manifest-Version: 1.0"); 89 | File.WriteAllBytes(Path.Combine(metaPath, "pluginIcon.svg"), icon); 90 | 91 | ZipFile.CreateFromDirectory(metaPath, jarPath, CompressionLevel.Optimal, true, new UnixUTF8Encoding()); 92 | 93 | Directory.Delete(metaPath, true); 94 | 95 | ZipFile.CreateFromDirectory(artifactPath, zipPath, CompressionLevel.Optimal, true, new UnixUTF8Encoding()); 96 | 97 | Console.WriteLine($"Created {zipPath}"); 98 | }); 99 | 100 | Target("publish-nuget", DependsOn("package"), () => 101 | { 102 | var githubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN"); 103 | 104 | var packages = Glob.Files("artifacts", "*.nupkg") 105 | .ToArray(); 106 | 107 | Run("dotnet", $"nuget add source https://nuget.pkg.github.com/machine/index.json -n github -u machine -p {githubToken} --store-password-in-clear-text"); 108 | 109 | foreach (var package in packages) 110 | { 111 | var path = Path.Combine("artifacts", package); 112 | 113 | Run("dotnet", $"nuget push {path} --source https://plugins.jetbrains.com/ --api-key {apiKey}"); 114 | Run("dotnet", $"nuget push {path} --source github"); 115 | 116 | Console.WriteLine($"Published plugin {package} to JetBrains hub"); 117 | } 118 | }); 119 | 120 | Target("publish-zip", DependsOn("zip"), () => 121 | { 122 | using var client = new HttpClient(); 123 | 124 | client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}"); 125 | 126 | var plugins = Glob.Files("artifacts", "*.zip"); 127 | 128 | foreach (var file in plugins) 129 | { 130 | var filename = Path.GetFileName(file); 131 | 132 | var content = new MultipartFormDataContent 133 | { 134 | { new StringContent("com.intellij.resharper.machine.specifications"), "xmlId" }, 135 | { new ByteArrayContent(File.ReadAllBytes(Path.Combine("artifacts", file))), "file", filename }, 136 | { new StringContent(notes), "notes" } 137 | }; 138 | 139 | if (!string.IsNullOrEmpty(version.PreReleaseTag)) 140 | { 141 | content.Add(new StringContent("channel"), "Beta"); 142 | } 143 | 144 | var response = client.PostAsync("https://plugins.jetbrains.com/plugin/uploadPlugin", content); 145 | response.Wait(TimeSpan.FromMinutes(10)); 146 | response.Result.EnsureSuccessStatusCode(); 147 | 148 | Console.WriteLine($"Published plugin {filename} to JetBrains hub"); 149 | } 150 | }); 151 | 152 | Target("publish", DependsOn("publish-nuget", "publish-zip")); 153 | 154 | Target("default", DependsOn("zip")); 155 | 156 | await RunTargetsAndExitAsync(args); 157 | 158 | async Task GetGitVersion() 159 | { 160 | Run("dotnet", "tool restore"); 161 | 162 | var (value, _) = await ReadAsync("dotnet", "dotnet-gitversion"); 163 | 164 | return JsonSerializer.Deserialize(value); 165 | } 166 | 167 | string GetWaveVersion() 168 | { 169 | var value = GetXmlValue("SdkVersion"); 170 | 171 | if (string.IsNullOrEmpty(value)) 172 | { 173 | return string.Empty; 174 | } 175 | 176 | return $"{value.Substring(2,2)}{value.Substring(5,1)}"; 177 | } 178 | 179 | string GetReleaseNotes() 180 | { 181 | return GetXmlValue("PackageReleaseNotes"); 182 | } 183 | 184 | string GetXmlValue(string name) 185 | { 186 | var projects = Glob.Files(Environment.CurrentDirectory, "src/**/*.csproj"); 187 | 188 | foreach (var project in projects) 189 | { 190 | var document = XDocument.Load(project); 191 | var node = document.XPathSelectElement($"/Project/PropertyGroup/{name}"); 192 | 193 | if (!string.IsNullOrEmpty(node!.Value)) 194 | { 195 | return node.Value; 196 | } 197 | } 198 | 199 | return string.Empty; 200 | } 201 | 202 | class GitVersion 203 | { 204 | public string SemVer { get; set; } 205 | 206 | public string AssemblySemVer { get; set; } 207 | 208 | public string AssemblySemFileVer { get; set; } 209 | 210 | public string InformationalVersion { get; set; } 211 | 212 | public string PreReleaseTag { get; set; } 213 | } 214 | 215 | class UnixUTF8Encoding : UTF8Encoding 216 | { 217 | public override byte[] GetBytes(string s) 218 | { 219 | s = s.Replace("\\", "/"); 220 | 221 | return base.GetBytes(s); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /build/build.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machine/machine.specifications.runner.resharper/f7e86ca30bcc244ab85eaebcf4f94741fc253a2d/images/icon.png -------------------------------------------------------------------------------- /images/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /install-plugin.ps1: -------------------------------------------------------------------------------- 1 | Set-StrictMode -Version Latest 2 | $ErrorActionPreference = "Stop" 3 | 4 | $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent 5 | 6 | $PluginId = "Machine.Specifications.Runner.Resharper9" 7 | $RootSuffix = "ReSharper" 8 | $ResharperUrl = "https://data.services.jetbrains.com/products/releases?code=RSU&type=release" 9 | $NugetUrl = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" 10 | $Version = "0.1.0" 11 | $MainProject = "$PSScriptRoot\src\Machine.Specifications.Runner.ReSharper\Machine.Specifications.Runner.ReSharper.csproj" 12 | $NugetExe = "$PSScriptRoot\tools\nuget.exe" 13 | 14 | Function Invoke-Exe { 15 | param( 16 | [Parameter(Mandatory = $true, Position = 0)] 17 | [ValidateNotNullOrEmpty()] 18 | [String] 19 | $Executable, 20 | 21 | [Parameter(ValueFromRemainingArguments = $true)] 22 | [String[]] 23 | $Arguments 24 | ) 25 | 26 | Write-Host "> $Executable $Arguments" 27 | $result = Start-Process -FilePath $Executable -ArgumentList $Arguments -NoNewWindow -Wait -Passthru 28 | 29 | if (-Not $result.ExitCode -eq 0) { 30 | throw "'$Executable $Arguments' failed with exit code $($result.ExitCode)" 31 | } 32 | } 33 | 34 | Function Write-User-Settings { 35 | param( 36 | [Parameter(Mandatory = $true)] 37 | [String] 38 | $HostIdentifier, 39 | 40 | [Parameter(Mandatory = $true)] 41 | [String] 42 | $UserProjectFile 43 | ) 44 | 45 | if (!(Test-Path "$UserProjectFile")) { 46 | Set-Content -Path "$UserProjectFile" -Value "" 47 | } 48 | 49 | $xml = [xml] (Get-Content "$UserProjectFile") 50 | $node = $xml.SelectSingleNode(".//HostFullIdentifier") 51 | $node.InnerText = $HostIdentifier 52 | $xml.Save("$UserProjectFile") 53 | } 54 | 55 | Function Read-SdkVersion { 56 | $xml = [xml] (Get-Content "$MainProject") 57 | $node = $xml.SelectSingleNode(".//SdkVersion") 58 | 59 | if ($null -eq $node) { 60 | throw "SdkVersion not found in '$MainProject'" 61 | } 62 | 63 | $value = $node.InnerText 64 | 65 | return $value.Substring(2, 2) + $value.Substring(5, 1) 66 | } 67 | 68 | Function Write-Nuspec { 69 | param( 70 | [Parameter(Mandatory=$true)] 71 | [String] 72 | $NuspecFile 73 | ) 74 | 75 | $sdkVersion = Read-SdkVersion 76 | $nextSdkVersion = [int]$sdkVersion + 1 77 | 78 | $nuspec = @' 79 | 80 | 81 | 82 | Machine.Specifications.Runner.Resharper9 83 | {0} 84 | Machine Specifications 85 | Machine Specifications 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | '@ -f $Version,$sdkVersion,$nextSdkVersion 103 | 104 | Set-Content -Path "$NuspecFile" -Value $nuspec 105 | } 106 | 107 | Function Get-ToolPath { 108 | param( 109 | [Parameter(Mandatory = $true)] 110 | [String] 111 | $ToolUrl 112 | ) 113 | 114 | $file = $ToolUrl.Substring($ToolUrl.LastIndexOf("/") + 1) 115 | 116 | return "$PSScriptRoot\tools\" + $file 117 | } 118 | 119 | Function Get-Tool { 120 | param( 121 | [Parameter(Mandatory = $true)] 122 | [String] 123 | $ToolUrl 124 | ) 125 | 126 | $file = Get-ToolPath $ToolUrl 127 | 128 | if (!(Test-Path $file)) { 129 | mkdir -Force $(Split-Path $file -Parent) > $null 130 | Write-Output "Downloading from $ToolUrl" 131 | (New-Object System.Net.WebClient).DownloadFile($ToolUrl, $file) 132 | } else { 133 | $filename = $file.Substring($file.LastIndexOf("\") + 1) 134 | Write-Output "Using $($filename) from cache" 135 | } 136 | } 137 | 138 | Function Install-Hive { 139 | param( 140 | [Parameter(Mandatory = $true)] 141 | [String] 142 | $InstallerFile 143 | ) 144 | 145 | Write-Output "Installing experimental hive" 146 | Invoke-Exe $InstallerFile "/SpecificProductNames=ReSharper" "/Hive=$RootSuffix" "/VsVersion=15.0;16.0" "/Silent=True" 147 | } 148 | 149 | Function Get-InstallationPath { 150 | $version = Read-SdkVersion 151 | 152 | return $(Get-ChildItem "$env:APPDATA\JetBrains\ReSharperPlatformVs*\v$version`_*$RootSuffix\NuGet.Config" | Sort-Object | Select-Object -Last 1).Directory 153 | } 154 | 155 | Function Save-PackagesConfig { 156 | $installPath = Get-InstallationPath 157 | 158 | if (Test-Path "$installPath\packages.config") { 159 | $xml = [xml] (Get-Content "$installPath\packages.config") 160 | } else { 161 | $xml = [xml] ("") 162 | } 163 | 164 | if ($null -eq $xml.SelectSingleNode(".//package[@id='$PluginId']/@id")) { 165 | $pluginNode = $xml.CreateElement('package') 166 | $pluginNode.setAttribute("id", "$PluginId") 167 | $pluginNode.setAttribute("version", "$Version") 168 | 169 | $packagesNode = $xml.SelectSingleNode("//packages") 170 | $packagesNode.AppendChild($PluginNode) > $null 171 | 172 | $xml.Save("$installPath\packages.config") 173 | } 174 | } 175 | 176 | # Download tools 177 | $url = [uri] $(Invoke-WebRequest -UseBasicParsing $ResharperUrl | ConvertFrom-Json).RSU[0].downloads.windows.link 178 | $resharperTool = Get-ToolPath $url 179 | 180 | Get-Tool $url 181 | Get-Tool $NugetUrl 182 | 183 | # Build plugin 184 | $artifacts = "$PSScriptRoot\artifacts" 185 | 186 | Write-Nuspec "$artifacts\Package.nuspec" 187 | Invoke-Exe dotnet build "$PSScriptRoot\Machine.Specifications.Runner.Resharper.sln" /p:Version=$Version /p:HostFullIdentifier="" /p:UseSharedCompilation=false 188 | Invoke-Exe $NugetExe pack "$artifacts\Package.nuspec" -version $Version -outputDirectory "$artifacts" 189 | 190 | # Install hive 191 | Install-Hive $resharperTool 192 | 193 | # Set up environment 194 | Save-PackagesConfig 195 | 196 | # Install plugin 197 | Invoke-Exe $NugetExe install $PluginId -OutputDirectory "$env:LOCALAPPDATA\JetBrains\plugins" -Source "$artifacts" -DependencyVersion Ignore 198 | 199 | # Reinstall hive 200 | Install-Hive $resharperTool 201 | 202 | # Set user project settings 203 | $installPath = Get-InstallationPath 204 | $hostIdentifier = "$($installPath.Parent.Name)_$($installPath.Name.Split('_')[-1])" 205 | 206 | Write-User-Settings $hostIdentifier "$PSScriptRoot\src\Machine.Specifications.Runner.ReSharper\Machine.Specifications.Runner.ReSharper.csproj.user" 207 | Write-User-Settings $hostIdentifier "$PSScriptRoot\src\Machine.Specifications.Runner.ReSharper.Adapters\Machine.Specifications.Runner.ReSharper.Adapters.csproj.user" 208 | Write-User-Settings $hostIdentifier "$PSScriptRoot\src\Machine.Specifications.Runner.ReSharper.Tasks\Machine.Specifications.Runner.ReSharper.Tasks.csproj.user" 209 | 210 | Write-Output "Installed plugin to hive" 211 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | com.intellij.resharper.machine.specifications 3 | Machine.Specifications for Rider 4 | ${Version} 5 | Unit testing 6 | Machine 7 | 8 | 9 | com.intellij.modules.rider 10 | 11 | 12 | This plugin adds test discovery and runner support for the Machine.Specifications framework. Tests can be run from the unit test window or using the gutter marks. A sub-menu will give you options to run tests in debug mode or for different .NET framework targets.

14 | 15 |

For issues with this plugin, please head to GitHub.

16 | ]]> 17 |
18 | 19 | 20 | 23 | 24 |
25 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Discovery/Discoverer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Threading; 6 | using JetBrains.ReSharper.TestRunner.Abstractions; 7 | using JetBrains.ReSharper.TestRunner.Abstractions.Objects; 8 | using Machine.Specifications.Runner.ReSharper.Adapters.Elements; 9 | using Machine.Specifications.Runner.ReSharper.Tasks; 10 | 11 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Discovery; 12 | 13 | public class Discoverer( 14 | TestRequest request, 15 | IMspecController controller, 16 | ITestDiscoverySink sink, 17 | RemoteTaskDepot depot, 18 | CancellationToken token) 19 | { 20 | private readonly ILogger logger = Logger.GetLogger(); 21 | 22 | public void Discover() 23 | { 24 | logger.Info($"Discovering tests from {request.Container.Location}"); 25 | 26 | var source = new List(); 27 | 28 | try 29 | { 30 | var discoverySink = new MspecDiscoverySink(token); 31 | 32 | controller.Find(discoverySink, request.Container.Location); 33 | 34 | foreach (var element in discoverySink.Elements.Result) 35 | { 36 | var task = GetRemoteTask(element); 37 | var parent = GetParent(element); 38 | 39 | source.Add(task); 40 | 41 | if (parent != null && depot[element] == null && depot[parent] != null && depot[parent]!.RunAllChildren) 42 | { 43 | depot.Add(task); 44 | } 45 | 46 | depot.Bind(element, task); 47 | } 48 | 49 | if (source.Any()) 50 | { 51 | logger.Debug($"Sending {source.Count} discovery results to server"); 52 | 53 | sink.TestsDiscovered(source.ToArray()); 54 | } 55 | } 56 | catch (OperationCanceledException) 57 | { 58 | logger.Info("Discovery was aborted"); 59 | } 60 | catch (TargetInvocationException ex) when(ex.InnerException != null) 61 | { 62 | throw new MspecDiscoveryException(ex.InnerException.Message, ex.InnerException.StackTrace); 63 | } 64 | catch (Exception ex) when (ex.GetType().FullName.StartsWith("Machine.Specifications")) 65 | { 66 | throw new MspecDiscoveryException(ex.Message, ex.StackTrace); 67 | } 68 | 69 | logger.Info("Discovery completed"); 70 | } 71 | 72 | private MspecRemoteTask GetRemoteTask(IMspecElement element) 73 | { 74 | return RemoteTaskBuilder.GetRemoteTask(element); 75 | } 76 | 77 | private IMspecElement? GetParent(IMspecElement element) 78 | { 79 | return element switch 80 | { 81 | IContextElement => null, 82 | IBehaviorElement behavior => behavior.Context, 83 | ISpecificationElement {Behavior: null} specification => specification.Context, 84 | ISpecificationElement {Behavior: not null} specification => specification.Behavior, 85 | _ => throw new ArgumentOutOfRangeException(nameof(element)) 86 | }; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Discovery/DiscoveryCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using Machine.Specifications.Runner.ReSharper.Adapters.Elements; 6 | using Machine.Specifications.Runner.Utility; 7 | 8 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Discovery; 9 | 10 | public class DiscoveryCache(Assembly assembly) 11 | { 12 | private readonly Dictionary types = new(); 13 | 14 | private readonly Dictionary fields = new(); 15 | 16 | private readonly Dictionary behaviorFieldsByType = new(); 17 | 18 | private readonly Dictionary ignoreReasons = new(); 19 | 20 | private readonly Dictionary behaviors = new(); 21 | 22 | public IBehaviorElement? GetOrAddBehavior(IContextElement context, SpecificationInfo specification) 23 | { 24 | var behaviorField = GetOrAddBehaviorFieldByType(context.TypeName, specification.ContainingType); 25 | 26 | if (behaviorField == null) 27 | { 28 | return null; 29 | } 30 | 31 | var key = $"{context.TypeName}.{behaviorField.Name}"; 32 | 33 | if (!behaviors.TryGetValue(key, out var behavior)) 34 | { 35 | // MSpec behaviors don't seem to inherit an 'ignored' attribute from the context 36 | var ignoreReason = GetIgnoreReason(context.TypeName, behaviorField.Name, false); 37 | 38 | behavior = behaviors[key] = new BehaviorElement(context, specification.ContainingType, behaviorField.Name, ignoreReason); 39 | } 40 | 41 | return behavior; 42 | } 43 | 44 | public string? GetIgnoreReason(string typeName) 45 | { 46 | if (!ignoreReasons.TryGetValue(typeName, out var reason)) 47 | { 48 | var type = GetOrAddType(typeName); 49 | 50 | reason = type == null 51 | ? null 52 | : GetIgnoreReason(type); 53 | 54 | ignoreReasons[typeName] = reason; 55 | } 56 | 57 | return reason; 58 | } 59 | 60 | public string? GetIgnoreReason(string typeName, string fieldName, bool inherit) 61 | { 62 | var key = $"{typeName}.{fieldName}"; 63 | 64 | if (!ignoreReasons.TryGetValue(key, out var reason)) 65 | { 66 | var type = GetOrAddType(typeName); 67 | var field = GetOrAddField(type, fieldName); 68 | 69 | reason = field == null 70 | ? null 71 | : GetIgnoreReason(field); 72 | 73 | if (string.IsNullOrEmpty(reason) && inherit) 74 | { 75 | reason = GetIgnoreReason(typeName); 76 | } 77 | 78 | ignoreReasons[key] = reason; 79 | } 80 | 81 | return reason; 82 | } 83 | 84 | private FieldInfo? GetOrAddBehaviorFieldByType(string contextTypeName, string behaviorType) 85 | { 86 | var key = $"{contextTypeName}.{behaviorType}"; 87 | 88 | if (!behaviorFieldsByType.TryGetValue(key, out var field)) 89 | { 90 | var type = GetOrAddType(contextTypeName); 91 | 92 | field = GetBehaviorField(type, x => x.FieldType.GenericTypeArguments.First().FullName == behaviorType); 93 | 94 | behaviorFieldsByType[key] = field; 95 | 96 | if (field != null) 97 | { 98 | fields[$"{contextTypeName}.{field.Name}"] = field; 99 | } 100 | } 101 | 102 | return field; 103 | } 104 | 105 | private string? GetIgnoreReason(MemberInfo member) 106 | { 107 | var attribute = member.GetCustomAttributesData() 108 | .FirstOrDefault(x => x.AttributeType.FullName == FullNames.IgnoreAttribute); 109 | 110 | var attributeValue = attribute?.ConstructorArguments.FirstOrDefault(); 111 | 112 | return attributeValue?.Value as string; 113 | } 114 | 115 | private Type? GetOrAddType(string typeName) 116 | { 117 | if (!types.TryGetValue(typeName, out var type)) 118 | { 119 | type = types[typeName] = assembly.GetType(typeName); 120 | } 121 | 122 | return type; 123 | } 124 | 125 | private FieldInfo? GetOrAddField(Type? type, string fieldName, Func? predicate = null) 126 | { 127 | if (type == null) 128 | { 129 | return null; 130 | } 131 | 132 | var key = $"{type.FullName}.{fieldName}"; 133 | 134 | if (!fields.TryGetValue(key, out var field)) 135 | { 136 | field = type 137 | .GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) 138 | .Where(x => x.Name == fieldName) 139 | .FirstOrDefault(predicate ?? (_ => true)); 140 | 141 | fields[key] = field; 142 | } 143 | 144 | return field; 145 | } 146 | 147 | private FieldInfo? GetBehaviorField(Type? type, Func predicate) 148 | { 149 | return type? 150 | .GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) 151 | .Where(IsBehavesLikeField) 152 | .FirstOrDefault(predicate); 153 | } 154 | 155 | private bool IsBehavesLikeField(FieldInfo field) 156 | { 157 | return field.FieldType.IsGenericType && 158 | field.FieldType.GetCustomAttributes(false) 159 | .Any(x => x.GetType().FullName == FullNames.BehaviorDelegateAttribute); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Discovery/IMspecController.cs: -------------------------------------------------------------------------------- 1 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Discovery; 2 | 3 | public interface IMspecController 4 | { 5 | void Find(IMspecDiscoverySink sink, string assemblyPath); 6 | } 7 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Discovery/IMspecDiscoverySink.cs: -------------------------------------------------------------------------------- 1 | using Machine.Specifications.Runner.ReSharper.Adapters.Elements; 2 | 3 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Discovery; 4 | 5 | public interface IMspecDiscoverySink 6 | { 7 | void OnSpecification(ISpecificationElement specification); 8 | 9 | void OnDiscoveryCompleted(); 10 | } 11 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Discovery/MspecController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Reflection; 5 | using System.Threading; 6 | using System.Xml.Linq; 7 | using JetBrains.ReSharper.TestRunner.Abstractions.Objects; 8 | using Machine.Specifications.Runner.ReSharper.Adapters.Elements; 9 | using Machine.Specifications.Runner.Utility; 10 | 11 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Discovery; 12 | 13 | public class MspecController : IMspecController 14 | { 15 | private readonly TestRequest request; 16 | 17 | private readonly CancellationToken token; 18 | 19 | private readonly object controller; 20 | 21 | private readonly MethodInfo invoker; 22 | 23 | public MspecController(TestRequest request, CancellationToken token) 24 | { 25 | this.request = request; 26 | this.token = token; 27 | 28 | var controllerType = GetControllerType(); 29 | 30 | invoker = controllerType.GetMethod("DiscoverSpecs", BindingFlags.Instance | BindingFlags.Public)!; 31 | controller = Activator.CreateInstance(controllerType, (Action) Listener); 32 | } 33 | 34 | public void Find(IMspecDiscoverySink sink, string assemblyPath) 35 | { 36 | var assemblyName = AssemblyName.GetAssemblyName(assemblyPath); 37 | var assembly = Assembly.Load(assemblyName); 38 | 39 | var results = (string) invoker.Invoke(controller, [assembly]); 40 | 41 | var specifications = GetSpecifications(assembly, results); 42 | 43 | foreach (var specification in specifications) 44 | { 45 | sink.OnSpecification(specification); 46 | } 47 | 48 | sink.OnDiscoveryCompleted(); 49 | } 50 | 51 | private Type GetControllerType() 52 | { 53 | var type = Type.GetType("Machine.Specifications.Controller.Controller, Machine.Specifications"); 54 | 55 | if (type == null) 56 | { 57 | var path = Path.GetDirectoryName(request.Container.Location); 58 | var assemblyPath = Path.Combine(path!, "Machine.Specifications.dll"); 59 | 60 | var assembly = Assembly.LoadFrom(assemblyPath); 61 | 62 | type = assembly.GetType("Machine.Specifications.Controller.Controller"); 63 | } 64 | 65 | if (type == null) 66 | { 67 | throw new InvalidOperationException("Cannot find 'Machine.Specifications.dll' controller type"); 68 | } 69 | 70 | return type; 71 | } 72 | 73 | private IEnumerable GetSpecifications(Assembly assembly, string xml) 74 | { 75 | var document = XDocument.Parse(xml); 76 | 77 | var cache = new DiscoveryCache(assembly); 78 | 79 | foreach (var contextElement in document.Descendants("contextinfo")) 80 | { 81 | token.ThrowIfCancellationRequested(); 82 | 83 | var contextInfo = ContextInfo.Parse(contextElement.ToString()); 84 | var contextIgnore = cache.GetIgnoreReason(contextInfo.TypeName); 85 | 86 | var context = contextInfo.ToElement(contextIgnore); 87 | 88 | foreach (var specificationElement in contextElement.Descendants("specificationinfo")) 89 | { 90 | token.ThrowIfCancellationRequested(); 91 | 92 | var specification = SpecificationInfo.Parse(specificationElement.ToString()); 93 | 94 | var behavior = specification.IsBehavior(context.TypeName) 95 | ? cache.GetOrAddBehavior(context, specification) 96 | : null; 97 | 98 | var specificationIgnore = behavior != null 99 | ? cache.GetIgnoreReason(behavior.TypeName, specification.FieldName, false) ?? behavior.IgnoreReason 100 | : cache.GetIgnoreReason(context.TypeName, specification.FieldName, true); 101 | 102 | yield return SpecificationInfo.Parse(specificationElement.ToString()) 103 | .ToElement(context, specificationIgnore, behavior); 104 | } 105 | } 106 | } 107 | 108 | private void Listener(string _) 109 | { 110 | token.ThrowIfCancellationRequested(); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Discovery/MspecDiscoveryException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Discovery; 5 | 6 | [Serializable] 7 | public class MspecDiscoveryException : Exception 8 | { 9 | public MspecDiscoveryException(string message, string stackTrace) 10 | : base(message) 11 | { 12 | StackTrace = stackTrace; 13 | } 14 | 15 | protected MspecDiscoveryException(SerializationInfo info, StreamingContext context) 16 | : base(info, context) 17 | { 18 | StackTrace = info.GetString(nameof(StackTrace)); 19 | } 20 | 21 | public override string StackTrace { get; } 22 | 23 | public override void GetObjectData(SerializationInfo info, StreamingContext context) 24 | { 25 | base.GetObjectData(info, context); 26 | 27 | info.AddValue(nameof(StackTrace), StackTrace); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Discovery/MspecDiscoverySink.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Machine.Specifications.Runner.ReSharper.Adapters.Elements; 5 | 6 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Discovery; 7 | 8 | public class MspecDiscoverySink(CancellationToken token) : IMspecDiscoverySink 9 | { 10 | private readonly TaskCompletionSource source = new(); 11 | 12 | private readonly List elements = new(); 13 | 14 | private readonly HashSet handledContexts = new(); 15 | 16 | private readonly HashSet handledBehaviors = new(); 17 | 18 | private readonly HashSet handledSpecifications = new(); 19 | 20 | public Task Elements => source.Task; 21 | 22 | public void OnSpecification(ISpecificationElement specification) 23 | { 24 | token.ThrowIfCancellationRequested(); 25 | 26 | if (handledContexts.Add(specification.Context)) 27 | { 28 | elements.Add(specification.Context); 29 | } 30 | 31 | if (specification.Behavior != null && handledBehaviors.Add(specification.Behavior)) 32 | { 33 | elements.Add(specification.Behavior); 34 | } 35 | 36 | if (handledSpecifications.Add(specification)) 37 | { 38 | elements.Add(specification); 39 | } 40 | } 41 | 42 | public void OnDiscoveryCompleted() 43 | { 44 | source.SetResult(elements.ToArray()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Elements/BehaviorElement.cs: -------------------------------------------------------------------------------- 1 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Elements; 2 | 3 | public class BehaviorElement(IContextElement context, string typeName, string fieldName, string? ignoreReason) 4 | : IBehaviorElement 5 | { 6 | public string Id { get; } = $"{context.TypeName}.{fieldName}"; 7 | 8 | public string AggregateId { get; } = $"{context.TypeName}.{typeName}"; 9 | 10 | public string? IgnoreReason { get; } = ignoreReason; 11 | 12 | public IContextElement Context { get; } = context; 13 | 14 | public string TypeName { get; } = typeName; 15 | 16 | public string FieldName { get; } = fieldName; 17 | } 18 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Elements/ContextElement.cs: -------------------------------------------------------------------------------- 1 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Elements; 2 | 3 | public class ContextElement(string typeName, string subject, string? ignoreReason) : IContextElement 4 | { 5 | public string Id { get; } = typeName; 6 | 7 | public string AggregateId => Id; 8 | 9 | public string? IgnoreReason { get; } = ignoreReason; 10 | 11 | public string TypeName { get; } = typeName; 12 | 13 | public string Subject { get; } = subject; 14 | } 15 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Elements/ContextInfoExtensions.cs: -------------------------------------------------------------------------------- 1 | using Machine.Specifications.Runner.Utility; 2 | 3 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Elements; 4 | 5 | public static class ContextInfoExtensions 6 | { 7 | public static IContextElement ToElement(this ContextInfo context, string? ignoreReason) 8 | { 9 | return new ContextElement(context.TypeName, context.Concern, ignoreReason); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Elements/IBehaviorElement.cs: -------------------------------------------------------------------------------- 1 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Elements; 2 | 3 | public interface IBehaviorElement : IMspecElement 4 | { 5 | IContextElement Context { get; } 6 | 7 | string TypeName { get; } 8 | 9 | string FieldName { get; } 10 | } 11 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Elements/IContextElement.cs: -------------------------------------------------------------------------------- 1 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Elements; 2 | 3 | public interface IContextElement : IMspecElement 4 | { 5 | string TypeName { get; } 6 | 7 | string Subject { get; } 8 | } 9 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Elements/IMspecElement.cs: -------------------------------------------------------------------------------- 1 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Elements; 2 | 3 | public interface IMspecElement 4 | { 5 | string Id { get; } 6 | 7 | string AggregateId { get; } 8 | 9 | string? IgnoreReason { get; } 10 | } 11 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Elements/ISpecificationElement.cs: -------------------------------------------------------------------------------- 1 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Elements; 2 | 3 | public interface ISpecificationElement : IMspecElement 4 | { 5 | IContextElement Context { get; } 6 | 7 | string FieldName { get; } 8 | 9 | IBehaviorElement? Behavior { get; } 10 | } 11 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Elements/SpecificationElement.cs: -------------------------------------------------------------------------------- 1 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Elements; 2 | 3 | public class SpecificationElement( 4 | IContextElement context, 5 | string fieldName, 6 | string? ignoreReason = null, 7 | IBehaviorElement? behavior = null) 8 | : ISpecificationElement 9 | { 10 | public string Id { get; } = behavior != null 11 | ? $"{context.TypeName}.{behavior.FieldName}.{fieldName}" 12 | : $"{context.TypeName}.{fieldName}"; 13 | 14 | public string AggregateId { get; } = behavior != null 15 | ? $"{context.TypeName}.{behavior.TypeName}.{fieldName}" 16 | : $"{context.TypeName}.{fieldName}"; 17 | 18 | public string? IgnoreReason { get; } = ignoreReason; 19 | 20 | public IContextElement Context { get; } = context; 21 | 22 | public string FieldName { get; } = fieldName; 23 | 24 | public IBehaviorElement? Behavior { get; } = behavior; 25 | } 26 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Elements/SpecificationInfoExtensions.cs: -------------------------------------------------------------------------------- 1 | using Machine.Specifications.Runner.Utility; 2 | 3 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Elements; 4 | 5 | public static class SpecificationInfoExtensions 6 | { 7 | public static ISpecificationElement ToElement(this SpecificationInfo specification, IContextElement context, string? ignoreReason, IBehaviorElement? behavior = null) 8 | { 9 | return new SpecificationElement(context, specification.FieldName, ignoreReason, behavior); 10 | } 11 | 12 | public static bool IsBehavior(this SpecificationInfo specification, string contextTypeName) 13 | { 14 | return specification.ContainingType != contextTypeName; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/ExceptionResultExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using JetBrains.ReSharper.TestRunner.Abstractions.Objects; 6 | using Machine.Specifications.Runner.Utility; 7 | 8 | namespace Machine.Specifications.Runner.ReSharper.Adapters; 9 | 10 | internal static class ExceptionResultExtensions 11 | { 12 | private static IEnumerable Flatten(this ExceptionResult result) 13 | { 14 | var exception = result; 15 | 16 | if (exception.FullTypeName == typeof(TargetInvocationException).FullName && exception.InnerExceptionResult != null) 17 | { 18 | exception = exception.InnerExceptionResult; 19 | } 20 | 21 | for (var current = exception; current != null; current = current.InnerExceptionResult) 22 | { 23 | yield return current; 24 | } 25 | } 26 | 27 | public static ExceptionInfo[] GetExceptions(this ExceptionResult? result) 28 | { 29 | if (result == null) 30 | { 31 | return []; 32 | } 33 | 34 | return result.Flatten() 35 | .Select(x => new ExceptionInfo(x.FullTypeName, x.Message, x.StackTrace)) 36 | .ToArray(); 37 | } 38 | 39 | public static string GetExceptionMessage(this ExceptionResult? result) 40 | { 41 | if (result == null) 42 | { 43 | return string.Empty; 44 | } 45 | 46 | var exception = result.Flatten().FirstOrDefault(); 47 | 48 | return exception != null 49 | ? $"{exception.FullTypeName}: {exception.Message}" 50 | : string.Empty; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Execution/ConcurrentLookup.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using Machine.Specifications.Runner.ReSharper.Adapters.Elements; 3 | 4 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Execution; 5 | 6 | public class ConcurrentLookup 7 | where T : IMspecElement 8 | { 9 | private readonly ConcurrentDictionary> values = new(); 10 | 11 | public void Add(T? element) 12 | { 13 | if (element == null) 14 | { 15 | return; 16 | } 17 | 18 | var bag = values.GetOrAdd(element.AggregateId, _ => new ConcurrentBag()); 19 | 20 | bag.Add(element); 21 | } 22 | 23 | public T? Take(string id) 24 | { 25 | if (values.TryGetValue(id, out var bag) && bag.TryTake(out var value)) 26 | { 27 | return value; 28 | } 29 | 30 | return default; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Execution/ElementCache.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Machine.Specifications.Runner.ReSharper.Adapters.Elements; 4 | 5 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Execution; 6 | 7 | public class ElementCache 8 | { 9 | private readonly HashSet behaviorTypes; 10 | 11 | private readonly ILookup behaviorsByContext; 12 | 13 | private readonly ILookup specificationsByContext; 14 | 15 | private readonly ILookup specificationsByBehavior; 16 | 17 | public ElementCache(ISpecificationElement[] specifications) 18 | { 19 | var types = specifications 20 | .Where(x => x.Behavior != null) 21 | .Select(x => x.Behavior!.TypeName) 22 | .Distinct(); 23 | 24 | behaviorTypes = [..types]; 25 | 26 | behaviorsByContext = specifications 27 | .Where(x => x.Behavior != null) 28 | .Select(x => x.Behavior!) 29 | .Distinct() 30 | .ToLookup(x => x!.Context); 31 | 32 | specificationsByContext = specifications 33 | .ToLookup(x => x.Context); 34 | 35 | specificationsByBehavior = specifications 36 | .Where(x => x.Behavior != null) 37 | .ToLookup(x => x.Behavior!); 38 | } 39 | 40 | public bool IsBehavior(string type) 41 | { 42 | return behaviorTypes.Contains(type); 43 | } 44 | 45 | public IEnumerable GetBehaviors(IContextElement element) 46 | { 47 | return behaviorsByContext[element]; 48 | } 49 | 50 | public IEnumerable GetSpecifications(IContextElement element) 51 | { 52 | return specificationsByContext[element]; 53 | } 54 | 55 | public IEnumerable GetSpecifications(IBehaviorElement behavior) 56 | { 57 | return specificationsByBehavior[behavior]; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Execution/ExecutionAdapterRunListener.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Machine.Specifications.Runner.ReSharper.Adapters.Elements; 4 | using Machine.Specifications.Runner.ReSharper.Adapters.Listeners; 5 | 6 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Execution; 7 | 8 | public class ExecutionAdapterRunListener(IExecutionListener listener, ElementCache cache, RunTracker tracker) 9 | : IRunListener 10 | { 11 | private readonly HashSet behaviors = new(); 12 | 13 | private IContextElement? currentContext; 14 | 15 | public void OnAssemblyStart(TestAssemblyInfo assemblyInfo) 16 | { 17 | listener.OnAssemblyStart(assemblyInfo.Location); 18 | } 19 | 20 | public void OnAssemblyEnd(TestAssemblyInfo assemblyInfo) 21 | { 22 | listener.OnAssemblyEnd(assemblyInfo.Location); 23 | } 24 | 25 | public void OnRunStart() 26 | { 27 | listener.OnRunStart(); 28 | } 29 | 30 | public void OnRunEnd() 31 | { 32 | listener.OnRunEnd(); 33 | } 34 | 35 | public void OnContextStart(TestContextInfo contextInfo) 36 | { 37 | var element = tracker.StartContext(contextInfo.TypeName); 38 | 39 | currentContext = element; 40 | 41 | if (element != null) 42 | { 43 | listener.OnContextStart(element); 44 | } 45 | } 46 | 47 | public void OnContextEnd(TestContextInfo contextInfo) 48 | { 49 | var element = tracker.FinishContext(contextInfo.TypeName); 50 | 51 | if (element != null) 52 | { 53 | var runningBehaviors = cache.GetBehaviors(element) 54 | .Where(x => behaviors.Contains(x)); 55 | 56 | foreach (var behavior in runningBehaviors) 57 | { 58 | listener.OnBehaviorEnd(behavior, contextInfo.CapturedOutput); 59 | } 60 | 61 | listener.OnContextEnd(element, contextInfo.CapturedOutput); 62 | } 63 | 64 | currentContext = null; 65 | } 66 | 67 | public void OnSpecificationStart(TestSpecificationInfo specificationInfo) 68 | { 69 | var isBehavior = cache.IsBehavior(specificationInfo.ContainingType); 70 | 71 | if (isBehavior) 72 | { 73 | var key = $"{currentContext?.TypeName}.{specificationInfo.ContainingType}.{specificationInfo.FieldName}"; 74 | 75 | var element = tracker.StartSpecification(key); 76 | 77 | if (element?.Behavior != null && behaviors.Add(element.Behavior)) 78 | { 79 | listener.OnBehaviorStart(element.Behavior); 80 | } 81 | 82 | if (element != null) 83 | { 84 | listener.OnSpecificationStart(element); 85 | } 86 | } 87 | else 88 | { 89 | var key = $"{specificationInfo.ContainingType}.{specificationInfo.FieldName}"; 90 | 91 | var element = tracker.StartSpecification(key); 92 | 93 | if (element != null) 94 | { 95 | listener.OnSpecificationStart(element); 96 | } 97 | } 98 | } 99 | 100 | public void OnSpecificationEnd(TestSpecificationInfo specificationInfo, TestRunResult runResult) 101 | { 102 | var isBehavior = cache.IsBehavior(specificationInfo.ContainingType); 103 | 104 | if (isBehavior) 105 | { 106 | var key = $"{currentContext?.TypeName}.{specificationInfo.ContainingType}.{specificationInfo.FieldName}"; 107 | 108 | var element = tracker.FinishSpecification(key); 109 | 110 | if (element != null) 111 | { 112 | listener.OnSpecificationEnd(element, specificationInfo.CapturedOutput, runResult); 113 | } 114 | } 115 | else 116 | { 117 | var key = $"{specificationInfo.ContainingType}.{specificationInfo.FieldName}"; 118 | 119 | var element = tracker.FinishSpecification(key); 120 | 121 | if (element != null) 122 | { 123 | listener.OnSpecificationEnd(element, specificationInfo.CapturedOutput, runResult); 124 | } 125 | } 126 | } 127 | 128 | public void OnFatalError(TestError? error) 129 | { 130 | listener.OnFatalError(error); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Execution/Executor.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading; 3 | using JetBrains.ReSharper.TestRunner.Abstractions; 4 | using JetBrains.ReSharper.TestRunner.Abstractions.Objects; 5 | using Machine.Specifications.Runner.ReSharper.Adapters.Listeners; 6 | using Machine.Specifications.Runner.Utility; 7 | 8 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Execution; 9 | 10 | public class Executor( 11 | ITestExecutionSink executionSink, 12 | TestRunRequest request, 13 | RemoteTaskDepot depot, 14 | CancellationToken token) 15 | { 16 | private readonly RunContext context = new(depot, executionSink); 17 | 18 | public void Execute() 19 | { 20 | var assembly = request.Container.Location; 21 | 22 | var testsToRun = context.GetTestsToRun().ToArray(); 23 | 24 | var contexts = testsToRun 25 | .Select(x => x.Context.TypeName) 26 | .Distinct(); 27 | 28 | var cache = new ElementCache(testsToRun); 29 | var tracker = new RunTracker(testsToRun); 30 | 31 | var listener = new TestExecutionListener(context, cache, token); 32 | var adapter = new ExecutionAdapterRunListener(listener, cache, tracker); 33 | var loggingListener = new LoggingRunListener(adapter); 34 | var machineAdapter = new AdapterListener(loggingListener, assembly); 35 | 36 | var runOptions = RunOptions.Custom.FilterBy(contexts); 37 | 38 | var runner = new AppDomainRunner(machineAdapter, runOptions); 39 | runner.RunAssembly(new AssemblyPath(request.Container.Location)); 40 | 41 | listener.Finished.WaitOne(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Execution/IExecutionListener.cs: -------------------------------------------------------------------------------- 1 | using Machine.Specifications.Runner.ReSharper.Adapters.Elements; 2 | using Machine.Specifications.Runner.ReSharper.Adapters.Listeners; 3 | 4 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Execution; 5 | 6 | public interface IExecutionListener 7 | { 8 | void OnAssemblyStart(string assemblyLocation); 9 | 10 | void OnAssemblyEnd(string assemblyLocation); 11 | 12 | void OnRunStart(); 13 | 14 | void OnRunEnd(); 15 | 16 | void OnContextStart(IContextElement context); 17 | 18 | void OnContextEnd(IContextElement context, string capturedOutput); 19 | 20 | void OnBehaviorStart(IBehaviorElement behavior); 21 | 22 | void OnBehaviorEnd(IBehaviorElement behavior, string capturedOutput); 23 | 24 | void OnSpecificationStart(ISpecificationElement specification); 25 | 26 | void OnSpecificationEnd(ISpecificationElement specification, string capturedOutput, TestRunResult runResult); 27 | 28 | void OnFatalError(TestError? error); 29 | } 30 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Execution/RunContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Collections.Generic; 3 | using JetBrains.ReSharper.TestRunner.Abstractions; 4 | using Machine.Specifications.Runner.ReSharper.Adapters.Elements; 5 | using Machine.Specifications.Runner.ReSharper.Tasks; 6 | 7 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Execution; 8 | 9 | public class RunContext(RemoteTaskDepot depot, ITestExecutionSink sink) 10 | { 11 | private readonly ConcurrentDictionary tasks = new(); 12 | 13 | private readonly ConcurrentDictionary tasksById = new(); 14 | 15 | public IEnumerable GetTestsToRun() 16 | { 17 | return depot.GetTestsToRun(); 18 | } 19 | 20 | public TaskWrapper GetTask(IMspecElement element) 21 | { 22 | return tasks.GetOrAdd(element, key => 23 | { 24 | var task = depot[MspecReSharperId.Create(key)] ?? CreateTask(element); 25 | 26 | return tasksById.GetOrAdd(task.TestId, _ => new TaskWrapper(task, sink)); 27 | }); 28 | } 29 | 30 | private MspecRemoteTask CreateTask(IMspecElement element) 31 | { 32 | var task = RemoteTaskBuilder.GetRemoteTask(element); 33 | 34 | sink.DynamicTestDiscovered(task); 35 | 36 | depot.Add(task); 37 | 38 | return task; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Execution/RunTracker.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Machine.Specifications.Runner.ReSharper.Adapters.Elements; 3 | 4 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Execution; 5 | 6 | public class RunTracker 7 | { 8 | private readonly ConcurrentLookup pendingContexts = new(); 9 | 10 | private readonly ConcurrentLookup runningContexts = new(); 11 | 12 | private readonly ConcurrentLookup pendingSpecifications = new(); 13 | 14 | private readonly ConcurrentLookup runningSpecifications = new(); 15 | 16 | public RunTracker(ISpecificationElement[] specifications) 17 | { 18 | var contexts = specifications 19 | .Select(x => x.Context) 20 | .Distinct(); 21 | 22 | foreach (var context in contexts) 23 | { 24 | pendingContexts.Add(context); 25 | } 26 | 27 | foreach (var specification in specifications) 28 | { 29 | pendingSpecifications.Add(specification); 30 | } 31 | } 32 | 33 | public IContextElement? StartContext(string id) 34 | { 35 | var element = pendingContexts.Take(id); 36 | 37 | runningContexts.Add(element); 38 | 39 | return element; 40 | } 41 | 42 | public ISpecificationElement? StartSpecification(string id) 43 | { 44 | var element = pendingSpecifications.Take(id); 45 | 46 | runningSpecifications.Add(element); 47 | 48 | return element; 49 | } 50 | 51 | public IContextElement? FinishContext(string id) 52 | { 53 | return runningContexts.Take(id); 54 | } 55 | 56 | public ISpecificationElement? FinishSpecification(string id) 57 | { 58 | return runningSpecifications.Take(id); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Execution/TaskWrapper.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Threading; 3 | using JetBrains.ReSharper.TestRunner.Abstractions; 4 | using JetBrains.ReSharper.TestRunner.Abstractions.Objects; 5 | using Machine.Specifications.Runner.ReSharper.Tasks; 6 | 7 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Execution; 8 | 9 | public class TaskWrapper(MspecRemoteTask? task, ITestExecutionSink sink) 10 | { 11 | private readonly Stopwatch watch = new(); 12 | 13 | private int started; 14 | 15 | private int finished; 16 | 17 | private TestOutcome result; 18 | 19 | private string? message; 20 | 21 | public void Starting() 22 | { 23 | if (Interlocked.Exchange(ref started, 1) != 0) 24 | { 25 | return; 26 | } 27 | 28 | if (task != null) 29 | { 30 | watch.Restart(); 31 | 32 | sink.TestStarting(task); 33 | } 34 | } 35 | 36 | public void Output(string output) 37 | { 38 | if (string.IsNullOrEmpty(output)) 39 | { 40 | return; 41 | } 42 | 43 | if (task != null) 44 | { 45 | sink.TestOutput(task, output, TestOutputType.STDOUT); 46 | } 47 | } 48 | 49 | public void Skipped(string? reason = null) 50 | { 51 | result = TestOutcome.Ignored; 52 | message = reason ?? task?.IgnoreReason ?? "Ignored"; 53 | 54 | Finished(); 55 | } 56 | 57 | public void Passed() 58 | { 59 | result = TestOutcome.Success; 60 | } 61 | 62 | public void Failed(ExceptionInfo[] exceptions, string exceptionMessage) 63 | { 64 | result = TestOutcome.Failed; 65 | message = exceptionMessage; 66 | 67 | if (task != null) 68 | { 69 | sink.TestException(task, exceptions); 70 | } 71 | } 72 | 73 | public void Finished(bool childTestsFailed = false) 74 | { 75 | watch.Stop(); 76 | 77 | if (result == TestOutcome.Inconclusive) 78 | { 79 | result = childTestsFailed 80 | ? TestOutcome.Failed 81 | : TestOutcome.Success; 82 | 83 | message = childTestsFailed 84 | ? "One or more child tests failed" 85 | : string.Empty; 86 | } 87 | 88 | if (Interlocked.Exchange(ref finished, 1) != 0) 89 | { 90 | return; 91 | } 92 | 93 | if (task != null) 94 | { 95 | sink.TestFinished(task, result, message, watch.Elapsed); 96 | } 97 | } 98 | 99 | public void Reset() 100 | { 101 | finished = 0; 102 | result = TestOutcome.Inconclusive; 103 | message = string.Empty; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Execution/TestExecutionListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading; 6 | using JetBrains.ReSharper.TestRunner.Abstractions.Objects; 7 | using Machine.Specifications.Runner.ReSharper.Adapters.Elements; 8 | using Machine.Specifications.Runner.ReSharper.Adapters.Listeners; 9 | 10 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Execution; 11 | 12 | public class TestExecutionListener(RunContext runContext, ElementCache cache, CancellationToken token) 13 | : IExecutionListener 14 | { 15 | private readonly CancellationToken token = token; 16 | 17 | private readonly HashSet passed = []; 18 | 19 | private readonly HashSet failed = []; 20 | 21 | private readonly ManualResetEvent waitEvent = new(false); 22 | 23 | public WaitHandle Finished => waitEvent; 24 | 25 | public void OnAssemblyStart(string assemblyLocation) 26 | { 27 | if (string.IsNullOrEmpty(assemblyLocation)) 28 | { 29 | return; 30 | } 31 | 32 | var path = Path.GetDirectoryName(assemblyLocation); 33 | 34 | if (!string.IsNullOrEmpty(path)) 35 | { 36 | Environment.CurrentDirectory = path; 37 | } 38 | } 39 | 40 | public void OnAssemblyEnd(string assemblyLocation) 41 | { 42 | } 43 | 44 | public void OnRunStart() 45 | { 46 | } 47 | 48 | public void OnRunEnd() 49 | { 50 | waitEvent.Set(); 51 | } 52 | 53 | public void OnContextStart(IContextElement context) 54 | { 55 | if (token.IsCancellationRequested) 56 | { 57 | return; 58 | } 59 | 60 | runContext.GetTask(context).Starting(); 61 | } 62 | 63 | public void OnContextEnd(IContextElement context, string capturedOutput) 64 | { 65 | if (token.IsCancellationRequested) 66 | { 67 | return; 68 | } 69 | 70 | var task = runContext.GetTask(context); 71 | var tests = cache.GetSpecifications(context).ToArray(); 72 | var behaviors = cache.GetBehaviors(context).ToArray(); 73 | 74 | var testsPassed = tests.All(x => passed.Contains(x)); 75 | var testsFailed = tests.Any(x => failed.Contains(x)); 76 | 77 | if (testsPassed) 78 | { 79 | task.Passed(); 80 | } 81 | 82 | task.Output(capturedOutput); 83 | 84 | var ignoreReason = !behaviors.Any() || behaviors.Any(x => string.IsNullOrEmpty(x.IgnoreReason)) 85 | ? null 86 | : context.IgnoreReason; 87 | 88 | if (string.IsNullOrEmpty(ignoreReason)) 89 | { 90 | task.Finished(testsFailed); 91 | } 92 | else 93 | { 94 | task.Skipped(context.IgnoreReason); 95 | } 96 | } 97 | 98 | public void OnBehaviorStart(IBehaviorElement behavior) 99 | { 100 | if (token.IsCancellationRequested) 101 | { 102 | return; 103 | } 104 | 105 | runContext.GetTask(behavior).Starting(); 106 | } 107 | 108 | public void OnBehaviorEnd(IBehaviorElement behavior, string capturedOutput) 109 | { 110 | if (token.IsCancellationRequested) 111 | { 112 | return; 113 | } 114 | 115 | var task = runContext.GetTask(behavior); 116 | var tests = cache.GetSpecifications(behavior).ToArray(); 117 | 118 | var testsPassed = tests.All(x => passed.Contains(x)); 119 | var testsFailed = tests.Any(x => failed.Contains(x)); 120 | 121 | if (testsPassed) 122 | { 123 | task.Passed(); 124 | } 125 | 126 | task.Output(capturedOutput); 127 | 128 | if (string.IsNullOrEmpty(behavior.IgnoreReason)) 129 | { 130 | task.Finished(testsFailed); 131 | } 132 | else 133 | { 134 | task.Skipped(behavior.IgnoreReason); 135 | } 136 | } 137 | 138 | public void OnSpecificationStart(ISpecificationElement specification) 139 | { 140 | if (token.IsCancellationRequested) 141 | { 142 | return; 143 | } 144 | 145 | runContext.GetTask(specification).Starting(); 146 | } 147 | 148 | public void OnSpecificationEnd(ISpecificationElement specification, string capturedOutput, TestRunResult runResult) 149 | { 150 | if (token.IsCancellationRequested) 151 | { 152 | return; 153 | } 154 | 155 | var task = runContext.GetTask(specification); 156 | 157 | task.Output(capturedOutput); 158 | 159 | if (runResult.Status == TestStatus.Failing) 160 | { 161 | failed.Add(specification); 162 | 163 | task.Failed(runResult.Exception?.Exceptions ?? Array.Empty(), runResult.Exception?.ExceptionMessage ?? string.Empty); 164 | task.Finished(); 165 | } 166 | else if (runResult.Status == TestStatus.Passing) 167 | { 168 | passed.Add(specification); 169 | 170 | task.Passed(); 171 | task.Finished(); 172 | } 173 | else if (runResult.Status == TestStatus.NotImplemented) 174 | { 175 | task.Skipped("Not implemented"); 176 | } 177 | else if (runResult.Status == TestStatus.Ignored) 178 | { 179 | task.Skipped(); 180 | } 181 | } 182 | 183 | public void OnFatalError(TestError? error) 184 | { 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Listeners/AdapterListener.cs: -------------------------------------------------------------------------------- 1 | using Machine.Specifications.Runner.Utility; 2 | 3 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Listeners; 4 | 5 | internal class AdapterListener(IRunListener listener, string assemblyPath) : ISpecificationRunListener 6 | { 7 | public void OnAssemblyStart(AssemblyInfo assemblyInfo) 8 | { 9 | listener.OnAssemblyStart(new TestAssemblyInfo(assemblyPath)); 10 | } 11 | 12 | public void OnAssemblyEnd(AssemblyInfo assemblyInfo) 13 | { 14 | listener.OnAssemblyEnd(assemblyInfo.ToTestAssembly()); 15 | } 16 | 17 | public void OnRunStart() 18 | { 19 | listener.OnRunStart(); 20 | } 21 | 22 | public void OnRunEnd() 23 | { 24 | listener.OnRunEnd(); 25 | } 26 | 27 | public void OnContextStart(ContextInfo contextInfo) 28 | { 29 | listener.OnContextStart(contextInfo.ToTestContext()); 30 | } 31 | 32 | public void OnContextEnd(ContextInfo contextInfo) 33 | { 34 | listener.OnContextEnd(contextInfo.ToTestContext()); 35 | } 36 | 37 | public void OnSpecificationStart(SpecificationInfo specificationInfo) 38 | { 39 | listener.OnSpecificationStart(specificationInfo.ToTestSpecification()); 40 | } 41 | 42 | public void OnSpecificationEnd(SpecificationInfo specificationInfo, Result result) 43 | { 44 | listener.OnSpecificationEnd(specificationInfo.ToTestSpecification(), result.ToTestResult()); 45 | } 46 | 47 | public void OnFatalError(ExceptionResult exceptionResult) 48 | { 49 | listener.OnFatalError(exceptionResult.ToTestError()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Listeners/AssemblyInfoExtensions.cs: -------------------------------------------------------------------------------- 1 | using Machine.Specifications.Runner.Utility; 2 | 3 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Listeners; 4 | 5 | internal static class AssemblyInfoExtensions 6 | { 7 | public static TestAssemblyInfo ToTestAssembly(this AssemblyInfo assembly) 8 | { 9 | return new TestAssemblyInfo(assembly.Location); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Listeners/ContextInfoExtensions.cs: -------------------------------------------------------------------------------- 1 | using Machine.Specifications.Runner.Utility; 2 | 3 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Listeners; 4 | 5 | internal static class ContextInfoExtensions 6 | { 7 | public static TestContextInfo ToTestContext(this ContextInfo context) 8 | { 9 | return new TestContextInfo(context.TypeName, context.CapturedOutput); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Listeners/ExceptionResultExtensions.cs: -------------------------------------------------------------------------------- 1 | using Machine.Specifications.Runner.Utility; 2 | 3 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Listeners; 4 | 5 | internal static class ExceptionResultExtensions 6 | { 7 | public static TestError? ToTestError(this ExceptionResult? result) 8 | { 9 | if (result == null) 10 | { 11 | return null; 12 | } 13 | 14 | return new TestError(result.FullTypeName, result.GetExceptions(), result.GetExceptionMessage()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Listeners/IRunListener.cs: -------------------------------------------------------------------------------- 1 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Listeners; 2 | 3 | public interface IRunListener 4 | { 5 | void OnAssemblyStart(TestAssemblyInfo assemblyInfo); 6 | 7 | void OnAssemblyEnd(TestAssemblyInfo assemblyInfo); 8 | 9 | void OnRunStart(); 10 | 11 | void OnRunEnd(); 12 | 13 | void OnContextStart(TestContextInfo contextInfo); 14 | 15 | void OnContextEnd(TestContextInfo contextInfo); 16 | 17 | void OnSpecificationStart(TestSpecificationInfo specificationInfo); 18 | 19 | void OnSpecificationEnd(TestSpecificationInfo specificationInfo, TestRunResult runResult); 20 | 21 | void OnFatalError(TestError? error); 22 | } 23 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Listeners/LoggingRunListener.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.ReSharper.TestRunner.Abstractions; 2 | 3 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Listeners; 4 | 5 | public class LoggingRunListener(IRunListener listener) : IRunListener 6 | { 7 | private readonly ILogger logger = Logger.GetLogger(); 8 | 9 | public void OnAssemblyStart(TestAssemblyInfo assemblyInfo) 10 | { 11 | logger.Trace($"OnAssemblyStart: {assemblyInfo.Location}"); 12 | 13 | logger.Catch(() => listener.OnAssemblyStart(assemblyInfo)); 14 | } 15 | 16 | public void OnAssemblyEnd(TestAssemblyInfo assemblyInfo) 17 | { 18 | logger.Trace($"OnAssemblyEnd: {assemblyInfo.Location}"); 19 | 20 | logger.Catch(() => listener.OnAssemblyEnd(assemblyInfo)); 21 | } 22 | 23 | public void OnRunStart() 24 | { 25 | logger.Trace("OnRunStart:"); 26 | 27 | logger.Catch(listener.OnRunStart); 28 | } 29 | 30 | public void OnRunEnd() 31 | { 32 | logger.Trace("OnRunEnd:"); 33 | 34 | logger.Catch(listener.OnRunEnd); 35 | } 36 | 37 | public void OnContextStart(TestContextInfo contextInfo) 38 | { 39 | logger.Trace($"OnContextStart: {contextInfo.TypeName}"); 40 | 41 | logger.Catch(() => listener.OnContextStart(contextInfo)); 42 | } 43 | 44 | public void OnContextEnd(TestContextInfo contextInfo) 45 | { 46 | logger.Trace($"OnContextEnd: {contextInfo.TypeName}"); 47 | 48 | logger.Catch(() => listener.OnContextEnd(contextInfo)); 49 | } 50 | 51 | public void OnSpecificationStart(TestSpecificationInfo specificationInfo) 52 | { 53 | logger.Trace($"OnSpecificationStart: {specificationInfo.ContainingType}.{specificationInfo.FieldName}"); 54 | 55 | logger.Catch(() => listener.OnSpecificationStart(specificationInfo)); 56 | } 57 | 58 | public void OnSpecificationEnd(TestSpecificationInfo specificationInfo, TestRunResult runResult) 59 | { 60 | logger.Trace($"OnSpecificationEnd: {specificationInfo.ContainingType}.{specificationInfo.FieldName}"); 61 | 62 | logger.Catch(() => listener.OnSpecificationEnd(specificationInfo, runResult)); 63 | } 64 | 65 | public void OnFatalError(TestError? error) 66 | { 67 | logger.Trace($"OnFatalError: {error?.FullTypeName}"); 68 | 69 | logger.Catch(() => listener.OnFatalError(error)); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Listeners/ResultExtensions.cs: -------------------------------------------------------------------------------- 1 | using Machine.Specifications.Runner.Utility; 2 | 3 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Listeners; 4 | 5 | internal static class ResultExtensions 6 | { 7 | public static TestRunResult ToTestResult(this Result result) 8 | { 9 | return new TestRunResult(result.Status.ToTestStatus(), result.Exception.ToTestError()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Listeners/SpecificationInfoExtensions.cs: -------------------------------------------------------------------------------- 1 | using Machine.Specifications.Runner.Utility; 2 | 3 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Listeners; 4 | 5 | internal static class SpecificationInfoExtensions 6 | { 7 | public static TestSpecificationInfo ToTestSpecification(this SpecificationInfo specification) 8 | { 9 | return new TestSpecificationInfo(specification.ContainingType, specification.FieldName, specification.CapturedOutput); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Listeners/StatusExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Machine.Specifications.Runner.Utility; 3 | 4 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Listeners; 5 | 6 | internal static class StatusExtensions 7 | { 8 | public static TestStatus ToTestStatus(this Status status) 9 | { 10 | return status switch 11 | { 12 | Status.Ignored => TestStatus.Ignored, 13 | Status.Failing => TestStatus.Failing, 14 | Status.NotImplemented => TestStatus.NotImplemented, 15 | Status.Passing => TestStatus.Passing, 16 | _ => throw new ArgumentException() 17 | }; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Listeners/TestAssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Listeners; 2 | 3 | public class TestAssemblyInfo(string location) 4 | { 5 | public string Location { get; } = location; 6 | } 7 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Listeners/TestContextInfo.cs: -------------------------------------------------------------------------------- 1 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Listeners; 2 | 3 | public class TestContextInfo(string typeName, string capturedOutput) 4 | { 5 | public string TypeName { get; } = typeName; 6 | 7 | public string CapturedOutput { get; } = capturedOutput; 8 | } 9 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Listeners/TestError.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.ReSharper.TestRunner.Abstractions.Objects; 2 | 3 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Listeners; 4 | 5 | public class TestError(string fullTypeName, ExceptionInfo[] exceptions, string exceptionMessage) 6 | { 7 | public string FullTypeName { get; } = fullTypeName; 8 | 9 | public ExceptionInfo[] Exceptions { get; } = exceptions; 10 | 11 | public string ExceptionMessage { get; } = exceptionMessage; 12 | } 13 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Listeners/TestRunResult.cs: -------------------------------------------------------------------------------- 1 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Listeners; 2 | 3 | public class TestRunResult(TestStatus status, TestError? exception = null) 4 | { 5 | public TestStatus Status { get; } = status; 6 | 7 | public TestError? Exception { get; } = exception; 8 | } 9 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Listeners/TestSpecificationInfo.cs: -------------------------------------------------------------------------------- 1 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Listeners; 2 | 3 | public class TestSpecificationInfo(string containingType, string fieldName, string capturedOutput) 4 | { 5 | public string ContainingType { get; } = containingType; 6 | 7 | public string FieldName { get; } = fieldName; 8 | 9 | public string CapturedOutput { get; } = capturedOutput; 10 | } 11 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Listeners/TestStatus.cs: -------------------------------------------------------------------------------- 1 | namespace Machine.Specifications.Runner.ReSharper.Adapters.Listeners; 2 | 3 | public enum TestStatus 4 | { 5 | Failing, 6 | Passing, 7 | NotImplemented, 8 | Ignored, 9 | } 10 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/Machine.Specifications.Runner.ReSharper.Adapters.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;net461 5 | true 6 | Machine.Specifications.Runner.ReSharper.Adapters 7 | false 8 | latest 9 | enable 10 | 11 | Machine.Specifications.Runner.ReSharper.Adapters.net461 12 | Machine.Specifications.Runner.ReSharper.Adapters.netstandard20 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | "$(ILRepack)" --internalize --norepackres --out:"$(OutputPath)$(AssemblyName).dll" "$(TargetPath)" "$(OutputPath)Machine.Specifications.Runner.Utility.dll" 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/MspecReSharperId.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Machine.Specifications.Runner.ReSharper.Adapters.Elements; 3 | 4 | namespace Machine.Specifications.Runner.ReSharper.Adapters; 5 | 6 | public class MspecReSharperId : IEquatable 7 | { 8 | public MspecReSharperId(IContextElement context) 9 | { 10 | Id = context.TypeName; 11 | } 12 | 13 | public MspecReSharperId(IBehaviorElement behavior) 14 | { 15 | Id = $"{behavior.Context.TypeName}.{behavior.FieldName}"; 16 | } 17 | 18 | public MspecReSharperId(ISpecificationElement specification) 19 | { 20 | Id = specification.Behavior != null 21 | ? $"{specification.Context.TypeName}.{specification.Behavior.FieldName}.{specification.FieldName}" 22 | : $"{specification.Context.TypeName}.{specification.FieldName}"; 23 | } 24 | 25 | public string Id { get; } 26 | 27 | public bool Equals(MspecReSharperId? other) 28 | { 29 | return other != null && Id == other.Id; 30 | } 31 | 32 | public override bool Equals(object? obj) 33 | { 34 | return Equals(obj as MspecReSharperId); 35 | } 36 | 37 | public override int GetHashCode() 38 | { 39 | return Id.GetHashCode(); 40 | } 41 | 42 | public override string ToString() 43 | { 44 | return $"ReSharperId({Id})"; 45 | } 46 | 47 | public static string Self(IContextElement context) 48 | { 49 | return new MspecReSharperId(context).Id; 50 | } 51 | 52 | public static string Self(IBehaviorElement behavior) 53 | { 54 | return new MspecReSharperId(behavior).Id; 55 | } 56 | 57 | public static string Self(ISpecificationElement specification) 58 | { 59 | return new MspecReSharperId(specification).Id; 60 | } 61 | 62 | public static string Self(IMspecElement element) 63 | { 64 | return Create(element).Id; 65 | } 66 | 67 | public static MspecReSharperId Create(IMspecElement element) 68 | { 69 | return element switch 70 | { 71 | IContextElement context => new MspecReSharperId(context), 72 | ISpecificationElement specification => new MspecReSharperId(specification), 73 | IBehaviorElement behavior => new MspecReSharperId(behavior), 74 | _ => throw new ArgumentOutOfRangeException(nameof(element)) 75 | }; 76 | } 77 | 78 | public static string? Parent(IMspecElement element) 79 | { 80 | return element switch 81 | { 82 | IContextElement => null, 83 | IBehaviorElement behavior => Self(behavior.Context), 84 | ISpecificationElement {Behavior: not null} specification => Self(specification.Behavior), 85 | ISpecificationElement {Behavior: null} specification => Self(specification.Context), 86 | _ => throw new ArgumentOutOfRangeException(nameof(element)) 87 | }; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/MspecRunner.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using JetBrains.ReSharper.TestRunner.Abstractions; 3 | using JetBrains.ReSharper.TestRunner.Abstractions.Objects; 4 | using Machine.Specifications.Runner.ReSharper.Adapters.Discovery; 5 | using Machine.Specifications.Runner.ReSharper.Adapters.Execution; 6 | 7 | namespace Machine.Specifications.Runner.ReSharper.Adapters; 8 | 9 | public class MspecRunner : LongLivedMarshalByRefObject, ITestDiscoverer, ITestExecutor 10 | { 11 | private readonly ILogger logger = Logger.GetLogger(); 12 | 13 | private readonly CancellationTokenSource discoveryToken = new(); 14 | 15 | private readonly CancellationTokenSource executionToken = new(); 16 | 17 | public void DiscoverTests(TestDiscoveryRequest request, ITestDiscoverySink discoverySink) 18 | { 19 | logger.Info("Exploration started"); 20 | 21 | var depot = new RemoteTaskDepot([]); 22 | var controller = new MspecController(request, discoveryToken.Token); 23 | 24 | var discoverer = new Discoverer(request, controller, discoverySink, depot, discoveryToken.Token); 25 | discoverer.Discover(); 26 | 27 | logger.Info("Exploration completed"); 28 | } 29 | 30 | public void AbortDiscovery() 31 | { 32 | discoveryToken.Cancel(); 33 | } 34 | 35 | public void RunTests(TestRunRequest request, ITestDiscoverySink discoverySink, ITestExecutionSink executionSink) 36 | { 37 | logger.Info("Execution started"); 38 | 39 | var depot = new RemoteTaskDepot(request.Selection); 40 | var controller = new MspecController(request, discoveryToken.Token); 41 | 42 | var discoverer = new Discoverer(request, controller, discoverySink, depot, discoveryToken.Token); 43 | discoverer.Discover(); 44 | 45 | var executor = new Executor(executionSink, request, depot, executionToken.Token); 46 | executor.Execute(); 47 | 48 | logger.Info("Execution completed"); 49 | } 50 | 51 | public void AbortRun() 52 | { 53 | executionToken.Cancel(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/RemoteTaskBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Machine.Specifications.Runner.ReSharper.Adapters.Elements; 3 | using Machine.Specifications.Runner.ReSharper.Tasks; 4 | 5 | namespace Machine.Specifications.Runner.ReSharper.Adapters; 6 | 7 | public static class RemoteTaskBuilder 8 | { 9 | public static MspecRemoteTask GetRemoteTask(IMspecElement element) 10 | { 11 | return element switch 12 | { 13 | IContextElement context => FromContext(context), 14 | IBehaviorElement behavior => FromBehavior(behavior), 15 | ISpecificationElement specification => FromSpecification(specification), 16 | _ => throw new ArgumentOutOfRangeException(nameof(element)) 17 | }; 18 | } 19 | 20 | private static MspecRemoteTask FromContext(IContextElement context) 21 | { 22 | return MspecContextRemoteTask.ToServer(MspecReSharperId.Self(context), context.Subject, null, context.IgnoreReason); 23 | } 24 | 25 | private static MspecRemoteTask FromBehavior(IBehaviorElement behavior) 26 | { 27 | return MspecSpecificationRemoteTask.ToServer(behavior.Context.TypeName, behavior.FieldName, behavior.TypeName, null, null, behavior.IgnoreReason); 28 | } 29 | 30 | private static MspecRemoteTask FromSpecification(ISpecificationElement specification) 31 | { 32 | if (specification.Behavior != null) 33 | { 34 | return MspecBehaviorSpecificationRemoteTask.ToServer( 35 | $"{specification.Context.TypeName}.{specification.Behavior.FieldName}", 36 | specification.Context.TypeName, 37 | specification.FieldName, 38 | specification.IgnoreReason); 39 | } 40 | 41 | return MspecSpecificationRemoteTask.ToServer(specification.Context.TypeName, specification.FieldName, null, null, null, specification.IgnoreReason); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Adapters/RemoteTaskDepot.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using JetBrains.ReSharper.TestRunner.Abstractions.Objects; 4 | using Machine.Specifications.Runner.ReSharper.Adapters.Elements; 5 | using Machine.Specifications.Runner.ReSharper.Tasks; 6 | 7 | namespace Machine.Specifications.Runner.ReSharper.Adapters; 8 | 9 | public class RemoteTaskDepot 10 | { 11 | private readonly Dictionary tasksByReSharperId = new(); 12 | 13 | private readonly List testsToRun = []; 14 | 15 | public RemoteTaskDepot(RemoteTask[] tasks) 16 | { 17 | foreach (var task in tasks.OfType()) 18 | { 19 | tasksByReSharperId[task.TestId] = task; 20 | } 21 | } 22 | 23 | public MspecRemoteTask? this[IMspecElement element] 24 | { 25 | get 26 | { 27 | tasksByReSharperId.TryGetValue(MspecReSharperId.Self(element), out var value); 28 | 29 | return value; 30 | } 31 | } 32 | 33 | public MspecRemoteTask? this[MspecReSharperId id] 34 | { 35 | get 36 | { 37 | tasksByReSharperId.TryGetValue(id.Id, out var value); 38 | 39 | return value; 40 | } 41 | } 42 | 43 | public void Add(MspecRemoteTask task) 44 | { 45 | tasksByReSharperId[task.TestId] = task; 46 | } 47 | 48 | public void Bind(IMspecElement element, MspecRemoteTask task) 49 | { 50 | if (tasksByReSharperId.ContainsKey(task.TestId) && element is ISpecificationElement specification) 51 | { 52 | testsToRun.Add(specification); 53 | } 54 | } 55 | 56 | public IEnumerable GetTestsToRun() 57 | { 58 | return testsToRun; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Tasks/Machine.Specifications.Runner.ReSharper.Tasks.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;net461 5 | Machine.Specifications.Runner.ReSharper.Tasks 6 | false 7 | latest 8 | enable 9 | 10 | Machine.Specifications.Runner.ReSharper.Tasks.net461 11 | Machine.Specifications.Runner.ReSharper.Tasks.netstandard20 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Tasks/MspecBehaviorSpecificationRemoteTask.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Machine.Specifications.Runner.ReSharper.Tasks; 4 | 5 | [Serializable] 6 | public class MspecBehaviorSpecificationRemoteTask : MspecRemoteTask 7 | { 8 | private MspecBehaviorSpecificationRemoteTask(string testId, string? ignoreReason, bool runExplicitly) 9 | : base(testId, ignoreReason, true, runExplicitly) 10 | { 11 | } 12 | 13 | private MspecBehaviorSpecificationRemoteTask(string parentId, string contextTypeName, string fieldName, string? ignoreReason) 14 | : base($"{parentId}.{fieldName}", ignoreReason) 15 | { 16 | ParentId = parentId; 17 | ContextTypeName = contextTypeName; 18 | FieldName = fieldName; 19 | } 20 | 21 | public string? ParentId { get; set; } 22 | 23 | public string? ContextTypeName { get; set; } 24 | 25 | public string? FieldName { get; set; } 26 | 27 | public static MspecBehaviorSpecificationRemoteTask ToClient(string testId, string? ignoreReason, bool runExplicitly) 28 | { 29 | return new(testId, ignoreReason, runExplicitly); 30 | } 31 | 32 | public static MspecBehaviorSpecificationRemoteTask ToServer(string parentId, string contextTypeName, string fieldName, string? ignoreReason) 33 | { 34 | return new(parentId, contextTypeName, fieldName, ignoreReason); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Tasks/MspecContextRemoteTask.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Machine.Specifications.Runner.ReSharper.Tasks; 4 | 5 | [Serializable] 6 | public class MspecContextRemoteTask : MspecRemoteTask 7 | { 8 | private MspecContextRemoteTask(string testId, string? ignoreReason, bool runAllChildren, bool runExplicitly) 9 | : base(testId, ignoreReason, runAllChildren, runExplicitly) 10 | { 11 | } 12 | 13 | private MspecContextRemoteTask(string typeName, string? subject, string[]? tags, string? ignoreReason) 14 | : base(typeName, ignoreReason) 15 | { 16 | Subject = subject; 17 | Tags = tags; 18 | } 19 | 20 | public string ContextTypeName => TestId; 21 | 22 | public string? Subject { get; set; } 23 | 24 | public string[]? Tags { get; set; } 25 | 26 | public static MspecContextRemoteTask ToClient(string testId, string? ignoreReason, bool runAllChildren, bool runExplicitly) 27 | { 28 | return new(testId, ignoreReason, runAllChildren, runExplicitly); 29 | } 30 | 31 | public static MspecContextRemoteTask ToServer(string typeName, string? subject, string[]? tags, string? ignoreReason) 32 | { 33 | return new(typeName, subject, tags, ignoreReason); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Tasks/MspecRemoteTask.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.ReSharper.TestRunner.Abstractions.Objects; 3 | 4 | namespace Machine.Specifications.Runner.ReSharper.Tasks; 5 | 6 | [Serializable] 7 | public abstract class MspecRemoteTask( 8 | string testId, 9 | string? ignoreReason, 10 | bool runAllChildren = true, 11 | bool runExplicitly = false) 12 | : RemoteTask 13 | { 14 | public string TestId { get; } = testId; 15 | 16 | public string? IgnoreReason { get; } = ignoreReason; 17 | 18 | public bool RunAllChildren { get; } = runAllChildren; 19 | 20 | public bool RunExplicitly { get; } = runExplicitly; 21 | } 22 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Tasks/MspecSpecificationRemoteTask.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Machine.Specifications.Runner.ReSharper.Tasks; 4 | 5 | [Serializable] 6 | public class MspecSpecificationRemoteTask : MspecRemoteTask 7 | { 8 | private MspecSpecificationRemoteTask(string testId, string? ignoreReason, bool runAllChildren, bool runExplicitly) 9 | : base(testId, ignoreReason, runAllChildren, runExplicitly) 10 | { 11 | } 12 | 13 | private MspecSpecificationRemoteTask( 14 | string contextTypeName, 15 | string fieldName, 16 | string? behaviorType, 17 | string? subject, 18 | string[]? tags, 19 | string? ignoreReason) 20 | : base($"{contextTypeName}.{fieldName}", ignoreReason) 21 | { 22 | ContextTypeName = contextTypeName; 23 | FieldName = fieldName; 24 | BehaviorType = behaviorType; 25 | Subject = subject; 26 | Tags = tags; 27 | } 28 | 29 | public string? ContextTypeName { get; set; } 30 | 31 | public string? BehaviorType { get; set; } 32 | 33 | public string? FieldName { get; set; } 34 | 35 | public string? Subject { get; } 36 | 37 | public string[]? Tags { get; set; } 38 | 39 | public static MspecSpecificationRemoteTask ToClient( 40 | string testId, 41 | string? ignoreReason, 42 | bool runAllChildren, 43 | bool runExplicitly) 44 | { 45 | return new(testId, ignoreReason, runAllChildren, runExplicitly); 46 | } 47 | 48 | public static MspecSpecificationRemoteTask ToServer( 49 | string contextTypeName, 50 | string fieldName, 51 | string? behaviorType, 52 | string? subject, 53 | string[]? tags, 54 | string? ignoreReason) 55 | { 56 | return new(contextTypeName, fieldName, behaviorType, subject, tags, ignoreReason); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Tasks/MspecTestContainer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.ReSharper.TestRunner.Abstractions.Objects; 3 | 4 | namespace Machine.Specifications.Runner.ReSharper.Tasks; 5 | 6 | [Serializable] 7 | public class MspecTestContainer(string location, ShadowCopy shadowCopy) : TestContainer(location, shadowCopy); 8 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Tests/Adapters/Discovery/MspecDiscoverySinkTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Machine.Specifications.Runner.ReSharper.Adapters.Discovery; 4 | using Machine.Specifications.Runner.ReSharper.Adapters.Elements; 5 | using NUnit.Framework; 6 | 7 | namespace Machine.Specifications.Runner.ReSharper.Tests.Adapters.Discovery; 8 | 9 | [TestFixture] 10 | public class MspecDiscoverySinkTests 11 | { 12 | [Test] 13 | public async Task AddsSingleContext() 14 | { 15 | var context = new ContextElement("Namespace.ContextType", "subject", null); 16 | 17 | var specification1 = new SpecificationElement(context, "should_be"); 18 | var specification2 = new SpecificationElement(context, "should_not_be"); 19 | 20 | var sink = new MspecDiscoverySink(CancellationToken.None); 21 | sink.OnSpecification(specification1); 22 | sink.OnSpecification(specification2); 23 | sink.OnDiscoveryCompleted(); 24 | 25 | var results = await sink.Elements; 26 | 27 | Assert.That(results.Length, Is.EqualTo(3)); 28 | Assert.That(results, Has.Member(context)); 29 | Assert.That(results, Has.Member(specification1)); 30 | Assert.That(results, Has.Member(specification2)); 31 | } 32 | 33 | [Test] 34 | public async Task AddsSingleBehavior() 35 | { 36 | var context = new ContextElement("Namespace.ContextType", "subject", null); 37 | 38 | var behavior = new BehaviorElement(context, "Namespace.ABehavior", "a_vehicle_that_is_started", null); 39 | 40 | var specification1 = new SpecificationElement(context, "should_be", null, behavior); 41 | var specification2 = new SpecificationElement(context, "should_not_be", null, behavior); 42 | 43 | var sink = new MspecDiscoverySink(CancellationToken.None); 44 | sink.OnSpecification(specification1); 45 | sink.OnSpecification(specification2); 46 | sink.OnDiscoveryCompleted(); 47 | 48 | var results = await sink.Elements; 49 | 50 | Assert.That(results.Length, Is.EqualTo(4)); 51 | Assert.That(results, Has.Member(context)); 52 | Assert.That(results, Has.Member(behavior)); 53 | Assert.That(results, Has.Member(specification1)); 54 | Assert.That(results, Has.Member(specification2)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Tests/Adapters/Elements/BehaviorElementTests.cs: -------------------------------------------------------------------------------- 1 | using Machine.Specifications.Runner.ReSharper.Adapters.Elements; 2 | using NSubstitute; 3 | using NUnit.Framework; 4 | 5 | namespace Machine.Specifications.Runner.ReSharper.Tests.Adapters.Elements; 6 | 7 | [TestFixture] 8 | public class BehaviorElementTests 9 | { 10 | [Test] 11 | public void SetsCorrectId() 12 | { 13 | var context = Substitute.For(); 14 | context.TypeName.Returns("Namespace.ContextType"); 15 | 16 | var element = new BehaviorElement(context, "Namespace.BehaviorType", "a_vehicle_that_is_started", null); 17 | 18 | Assert.That(element.Id, Is.EqualTo("Namespace.ContextType.a_vehicle_that_is_started")); 19 | } 20 | 21 | [Test] 22 | public void SetsCorrectAggregateId() 23 | { 24 | var context = Substitute.For(); 25 | context.TypeName.Returns("Namespace.ContextType"); 26 | 27 | var element = new BehaviorElement(context, "Namespace.BehaviorType", "a_vehicle_that_is_started", null); 28 | 29 | Assert.That(element.AggregateId, Is.EqualTo("Namespace.ContextType.Namespace.BehaviorType")); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Tests/Adapters/Elements/ContextElementTests.cs: -------------------------------------------------------------------------------- 1 | using Machine.Specifications.Runner.ReSharper.Adapters.Elements; 2 | using NUnit.Framework; 3 | 4 | namespace Machine.Specifications.Runner.ReSharper.Tests.Adapters.Elements; 5 | 6 | [TestFixture] 7 | public class ContextElementTests 8 | { 9 | [Test] 10 | public void SetsCorrectId() 11 | { 12 | var context = new ContextElement("Namespace.ContextType", "subject", null); 13 | 14 | Assert.That(context.Id, Is.EqualTo("Namespace.ContextType")); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Tests/Adapters/Elements/SpecificationElementTests.cs: -------------------------------------------------------------------------------- 1 | using Machine.Specifications.Runner.ReSharper.Adapters.Elements; 2 | using NSubstitute; 3 | using NUnit.Framework; 4 | 5 | namespace Machine.Specifications.Runner.ReSharper.Tests.Adapters.Elements; 6 | 7 | [TestFixture] 8 | public class SpecificationElementTests 9 | { 10 | [Test] 11 | public void SetsCorrectIdForSpecification() 12 | { 13 | var context = Substitute.For(); 14 | context.TypeName.Returns("Namespace.ContextType"); 15 | 16 | var specification = new SpecificationElement(context, "should_be"); 17 | 18 | Assert.That(specification.Id, Is.EqualTo("Namespace.ContextType.should_be")); 19 | } 20 | 21 | [Test] 22 | public void SetsCorrectIdForBehaviorSpecification() 23 | { 24 | var context = Substitute.For(); 25 | context.TypeName.Returns("Namespace.ContextType"); 26 | 27 | var behavior = Substitute.For(); 28 | behavior.FieldName.Returns("behaves_like"); 29 | 30 | var specification = new SpecificationElement(context, "should_be", null, behavior); 31 | 32 | Assert.That(specification.Id, Is.EqualTo("Namespace.ContextType.behaves_like.should_be")); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Tests/Adapters/Execution/ConcurrentLookupTests.cs: -------------------------------------------------------------------------------- 1 | using Machine.Specifications.Runner.ReSharper.Adapters.Elements; 2 | using Machine.Specifications.Runner.ReSharper.Adapters.Execution; 3 | using NUnit.Framework; 4 | 5 | namespace Machine.Specifications.Runner.ReSharper.Tests.Adapters.Execution; 6 | 7 | public class ConcurrentLookupTests 8 | { 9 | [Test] 10 | public void CanAddWithSameAggregateId() 11 | { 12 | var lookup = new ConcurrentLookup(); 13 | 14 | lookup.Add(new ContextElement("Type", "Subject", null)); 15 | lookup.Add(new ContextElement("Type", "SubjectS", null)); 16 | } 17 | 18 | [Test] 19 | public void CanTakeWhenUsingDuplicateIds() 20 | { 21 | var lookup = new ConcurrentLookup(); 22 | 23 | lookup.Add(new ContextElement("Type", "Subject", null)); 24 | lookup.Add(new ContextElement("Type", "SubjectS", null)); 25 | 26 | Assert.That(lookup.Take("Type"), Is.Not.Null); 27 | Assert.That(lookup.Take("Type"), Is.Not.Null); 28 | } 29 | 30 | [Test] 31 | public void ReturnsNullWhenAllTaken() 32 | { 33 | var lookup = new ConcurrentLookup(); 34 | 35 | lookup.Add(new ContextElement("Type", "Subject", null)); 36 | lookup.Add(new ContextElement("Type", "SubjectS", null)); 37 | 38 | Assert.That(lookup.Take("Type"), Is.Not.Null); 39 | Assert.That(lookup.Take("Type"), Is.Not.Null); 40 | Assert.That(lookup.Take("Type"), Is.Null); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Tests/Adapters/Execution/ElementCacheTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Machine.Specifications.Runner.ReSharper.Adapters.Execution; 3 | using Machine.Specifications.Runner.ReSharper.Tests.Fixtures; 4 | using NUnit.Framework; 5 | 6 | namespace Machine.Specifications.Runner.ReSharper.Tests.Adapters.Execution; 7 | 8 | public class ElementCacheTests 9 | { 10 | [Test] 11 | public void CanGetBehaviorType() 12 | { 13 | var cache = new ElementCache([ 14 | ElementFixtures.Specification1, 15 | ElementFixtures.Behavior1Specification1, 16 | ElementFixtures.Behavior2Specification1 17 | ]); 18 | 19 | Assert.That(cache.IsBehavior(ElementFixtures.Behavior1.TypeName), Is.True); 20 | Assert.That(cache.IsBehavior(ElementFixtures.Context.TypeName), Is.False); 21 | } 22 | 23 | [Test] 24 | public void CanGetBehaviors() 25 | { 26 | var cache = new ElementCache([ 27 | ElementFixtures.Specification1, 28 | ElementFixtures.Behavior1Specification1, 29 | ElementFixtures.Behavior1Specification2, 30 | ElementFixtures.Behavior2Specification1 31 | ]); 32 | 33 | var behaviors = cache.GetBehaviors(ElementFixtures.Context).ToArray(); 34 | 35 | Assert.That(behaviors, Has.Length.EqualTo(2)); 36 | Assert.That(behaviors, Contains.Item(ElementFixtures.Behavior1)); 37 | Assert.That(behaviors, Contains.Item(ElementFixtures.Behavior2)); 38 | } 39 | 40 | [Test] 41 | public void CanGetSpecificationsByContext() 42 | { 43 | var cache = new ElementCache([ 44 | ElementFixtures.Specification1, 45 | ElementFixtures.Behavior1Specification1, 46 | ElementFixtures.Behavior1Specification2, 47 | ElementFixtures.Behavior2Specification1 48 | ]); 49 | 50 | var specifications = cache.GetSpecifications(ElementFixtures.Context).ToArray(); 51 | 52 | Assert.That(specifications, Has.Length.EqualTo(4)); 53 | Assert.That(specifications, Contains.Item(ElementFixtures.Specification1)); 54 | Assert.That(specifications, Contains.Item(ElementFixtures.Behavior1Specification1)); 55 | Assert.That(specifications, Contains.Item(ElementFixtures.Behavior1Specification2)); 56 | Assert.That(specifications, Contains.Item(ElementFixtures.Behavior2Specification1)); 57 | } 58 | 59 | [Test] 60 | public void CanGetSpecificationsByBehavior() 61 | { 62 | var cache = new ElementCache([ 63 | ElementFixtures.Specification1, 64 | ElementFixtures.Behavior1Specification1, 65 | ElementFixtures.Behavior1Specification2, 66 | ElementFixtures.Behavior2Specification1 67 | ]); 68 | 69 | var specifications1 = cache.GetSpecifications(ElementFixtures.Behavior1).ToArray(); 70 | var specifications2 = cache.GetSpecifications(ElementFixtures.Behavior2).ToArray(); 71 | 72 | Assert.That(specifications1, Has.Length.EqualTo(2)); 73 | Assert.That(specifications1, Contains.Item(ElementFixtures.Behavior1Specification1)); 74 | Assert.That(specifications1, Contains.Item(ElementFixtures.Behavior1Specification2)); 75 | 76 | Assert.That(specifications2, Has.Length.EqualTo(1)); 77 | Assert.That(specifications2, Contains.Item(ElementFixtures.Behavior2Specification1)); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Tests/Adapters/Execution/ExecutionAdapterRunListenerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Machine.Specifications.Runner.ReSharper.Adapters.Elements; 3 | using Machine.Specifications.Runner.ReSharper.Adapters.Execution; 4 | using Machine.Specifications.Runner.ReSharper.Adapters.Listeners; 5 | using Machine.Specifications.Runner.ReSharper.Tests.Fixtures; 6 | using NSubstitute; 7 | using NUnit.Framework; 8 | 9 | namespace Machine.Specifications.Runner.ReSharper.Tests.Adapters.Execution; 10 | 11 | [TestFixture] 12 | public class ExecutionAdapterRunListenerTests 13 | { 14 | [Test] 15 | public void CanNotifyRun() 16 | { 17 | var sink = Substitute.For(); 18 | var cache = new ElementCache([]); 19 | var tracker = new RunTracker([]); 20 | 21 | var listener = new ExecutionAdapterRunListener(sink, cache, tracker); 22 | 23 | listener.OnRunStart(); 24 | listener.OnRunEnd(); 25 | 26 | sink.Received().OnRunStart(); 27 | sink.Received().OnRunEnd(); 28 | } 29 | 30 | [Test] 31 | public void CanNotifyAssembly() 32 | { 33 | const string path = "path/to/assembly.dll"; 34 | 35 | var sink = Substitute.For(); 36 | var cache = new ElementCache([]); 37 | var tracker = new RunTracker([]); 38 | 39 | var listener = new ExecutionAdapterRunListener(sink, cache, tracker); 40 | 41 | var assembly = new TestAssemblyInfo(path); 42 | 43 | listener.OnAssemblyStart(assembly); 44 | listener.OnAssemblyEnd(assembly); 45 | 46 | sink.Received().OnAssemblyStart(path); 47 | sink.Received().OnAssemblyEnd(path); 48 | } 49 | 50 | [Test] 51 | public void CanStartAndEndContext() 52 | { 53 | var sink = Substitute.For(); 54 | var elements = new ISpecificationElement[] 55 | { 56 | ElementFixtures.Specification1, 57 | ElementFixtures.Behavior1Specification1, 58 | ElementFixtures.Behavior1Specification2 59 | }; 60 | 61 | var cache = new ElementCache(elements); 62 | var tracker = new RunTracker(elements); 63 | 64 | var listener = new ExecutionAdapterRunListener(sink, cache, tracker); 65 | 66 | var context = new TestContextInfo(ElementFixtures.Context.TypeName, string.Empty); 67 | 68 | listener.OnContextStart(context); 69 | listener.OnContextEnd(context); 70 | 71 | sink.Received().OnContextStart(ElementFixtures.Context); 72 | sink.Received().OnContextEnd(ElementFixtures.Context, Arg.Any()); 73 | } 74 | 75 | [Test] 76 | public void CanStartAndEndSpecification() 77 | { 78 | var sink = Substitute.For(); 79 | var elements = new ISpecificationElement[] 80 | { 81 | ElementFixtures.Specification1, 82 | ElementFixtures.Behavior1Specification1, 83 | ElementFixtures.Behavior1Specification2 84 | }; 85 | 86 | var cache = new ElementCache(elements); 87 | var tracker = new RunTracker(elements); 88 | 89 | var listener = new ExecutionAdapterRunListener(sink, cache, tracker); 90 | 91 | var context = new TestContextInfo(ElementFixtures.Context.TypeName, string.Empty); 92 | var specification = new TestSpecificationInfo(ElementFixtures.Context.TypeName, ElementFixtures.Specification1.FieldName, string.Empty); 93 | 94 | listener.OnContextStart(context); 95 | listener.OnSpecificationStart(specification); 96 | listener.OnSpecificationEnd(specification, new TestRunResult(TestStatus.Passing)); 97 | listener.OnContextEnd(context); 98 | 99 | sink.Received().OnContextStart(ElementFixtures.Context); 100 | sink.Received().OnSpecificationStart(ElementFixtures.Specification1); 101 | sink.Received().OnSpecificationEnd(ElementFixtures.Specification1, Arg.Any(), Arg.Any()); 102 | sink.Received().OnContextEnd(ElementFixtures.Context, Arg.Any()); 103 | } 104 | 105 | [Test] 106 | public void CanStartAndEndBehaviorSpecifications() 107 | { 108 | var sink = Substitute.For(); 109 | var elements = new ISpecificationElement[] 110 | { 111 | ElementFixtures.Behavior1Specification1, 112 | ElementFixtures.Behavior1Specification2 113 | }; 114 | 115 | var cache = new ElementCache(elements); 116 | var tracker = new RunTracker(elements); 117 | 118 | var listener = new ExecutionAdapterRunListener(sink, cache, tracker); 119 | 120 | var context = new TestContextInfo(ElementFixtures.Context.TypeName, string.Empty); 121 | var specification1 = new TestSpecificationInfo(ElementFixtures.Behavior1.TypeName, ElementFixtures.Behavior1Specification1.FieldName, string.Empty); 122 | var specification2 = new TestSpecificationInfo(ElementFixtures.Behavior1.TypeName, ElementFixtures.Behavior1Specification2.FieldName, string.Empty); 123 | 124 | listener.OnContextStart(context); 125 | listener.OnSpecificationStart(specification1); 126 | listener.OnSpecificationEnd(specification1, new TestRunResult(TestStatus.Passing)); 127 | listener.OnSpecificationStart(specification2); 128 | listener.OnSpecificationEnd(specification2, new TestRunResult(TestStatus.Passing)); 129 | listener.OnContextEnd(context); 130 | 131 | sink.Received().OnContextStart(ElementFixtures.Context); 132 | sink.Received().OnBehaviorStart(ElementFixtures.Behavior1); 133 | sink.Received().OnSpecificationStart(ElementFixtures.Behavior1Specification1); 134 | sink.Received().OnSpecificationEnd(ElementFixtures.Behavior1Specification1, Arg.Any(), Arg.Any()); 135 | sink.Received().OnSpecificationStart(ElementFixtures.Behavior1Specification2); 136 | sink.Received().OnSpecificationEnd(ElementFixtures.Behavior1Specification2, Arg.Any(), Arg.Any()); 137 | sink.Received().OnBehaviorEnd(ElementFixtures.Behavior1, Arg.Any()); 138 | sink.Received().OnContextEnd(ElementFixtures.Context, Arg.Any()); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Tests/Adapters/Execution/RunContextTests.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.ReSharper.TestRunner.Abstractions; 2 | using JetBrains.ReSharper.TestRunner.Abstractions.Objects; 3 | using Machine.Specifications.Runner.ReSharper.Adapters; 4 | using Machine.Specifications.Runner.ReSharper.Adapters.Execution; 5 | using Machine.Specifications.Runner.ReSharper.Tests.Fixtures; 6 | using NSubstitute; 7 | using NUnit.Framework; 8 | 9 | namespace Machine.Specifications.Runner.ReSharper.Tests.Adapters.Execution; 10 | 11 | [TestFixture] 12 | public class RunContextTests 13 | { 14 | [Test] 15 | public void TaskIsNotReportedToSink() 16 | { 17 | var sink = Substitute.For(); 18 | 19 | var depot = new RemoteTaskDepot([ 20 | RemoteTaskFixtures.Context, 21 | RemoteTaskFixtures.Behavior1, 22 | RemoteTaskFixtures.Specification1, 23 | RemoteTaskFixtures.Behavior1Specification1 24 | ]); 25 | 26 | var context = new RunContext(depot, sink); 27 | 28 | Assert.That(context.GetTask(ElementFixtures.Specification1), Is.Not.Null); 29 | sink.DidNotReceive().DynamicTestDiscovered(Arg.Any()); 30 | } 31 | 32 | [Test] 33 | public void TaskNotInDepotIsReportedToSink() 34 | { 35 | var sink = Substitute.For(); 36 | 37 | var depot = new RemoteTaskDepot([ 38 | RemoteTaskFixtures.Context 39 | ]); 40 | 41 | var context = new RunContext(depot, sink); 42 | 43 | Assert.That(context.GetTask(ElementFixtures.Specification1), Is.Not.Null); 44 | sink.Received().DynamicTestDiscovered(Arg.Any()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Tests/Adapters/Execution/TaskWrapperTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.ReSharper.TestRunner.Abstractions; 3 | using JetBrains.ReSharper.TestRunner.Abstractions.Objects; 4 | using Machine.Specifications.Runner.ReSharper.Adapters.Execution; 5 | using Machine.Specifications.Runner.ReSharper.Tasks; 6 | using NSubstitute; 7 | using NUnit.Framework; 8 | 9 | namespace Machine.Specifications.Runner.ReSharper.Tests.Adapters.Execution; 10 | 11 | [TestFixture] 12 | public class TaskWrapperTests 13 | { 14 | [Test] 15 | public void StartsOnce() 16 | { 17 | var sink = Substitute.For(); 18 | var task = MspecContextRemoteTask.ToServer("ContextType", null, null, null); 19 | 20 | var wrapper = new TaskWrapper(task, sink); 21 | 22 | wrapper.Starting(); 23 | wrapper.Starting(); 24 | 25 | sink.Received(1).TestStarting(task); 26 | } 27 | 28 | [Test] 29 | public void SetsOutput() 30 | { 31 | var sink = Substitute.For(); 32 | var task = MspecContextRemoteTask.ToServer("ContextType", null, null, null); 33 | 34 | var wrapper = new TaskWrapper(task, sink); 35 | 36 | wrapper.Output("my result"); 37 | 38 | sink.Received().TestOutput(task, "my result", TestOutputType.STDOUT); 39 | } 40 | 41 | [Test] 42 | public void CallsFinishedOnce() 43 | { 44 | var sink = Substitute.For(); 45 | var task = MspecContextRemoteTask.ToServer("ContextType", null, null, null); 46 | 47 | var wrapper = new TaskWrapper(task, sink); 48 | 49 | wrapper.Finished(); 50 | wrapper.Finished(); 51 | 52 | sink.Received(1).TestFinished(task, TestOutcome.Success, Arg.Any(), Arg.Any()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Tests/Adapters/RemoteTaskDepotTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Machine.Specifications.Runner.ReSharper.Adapters; 3 | using Machine.Specifications.Runner.ReSharper.Tests.Fixtures; 4 | using NUnit.Framework; 5 | 6 | namespace Machine.Specifications.Runner.ReSharper.Tests.Adapters; 7 | 8 | [TestFixture] 9 | public class RemoteTaskDepotTests 10 | { 11 | [Test] 12 | public void CanGetContextByElement() 13 | { 14 | var depot = new RemoteTaskDepot([ 15 | RemoteTaskFixtures.Context 16 | ]); 17 | 18 | Assert.That(depot[ElementFixtures.Context], Is.Not.Null); 19 | } 20 | 21 | [Test] 22 | public void CanGetSpecificationByElement() 23 | { 24 | var depot = new RemoteTaskDepot([ 25 | RemoteTaskFixtures.Context, 26 | RemoteTaskFixtures.Specification1 27 | ]); 28 | 29 | Assert.That(depot[ElementFixtures.Specification1], Is.Not.Null); 30 | } 31 | 32 | [Test] 33 | public void CanGetBehaviorByElement() 34 | { 35 | var depot = new RemoteTaskDepot([ 36 | RemoteTaskFixtures.Context, 37 | RemoteTaskFixtures.Behavior1 38 | ]); 39 | 40 | Assert.That(depot[ElementFixtures.Behavior1], Is.Not.Null); 41 | } 42 | 43 | [Test] 44 | public void CanGetBehaviorSpecificationByElement() 45 | { 46 | var depot = new RemoteTaskDepot([ 47 | RemoteTaskFixtures.Context, 48 | RemoteTaskFixtures.Behavior1, 49 | RemoteTaskFixtures.Behavior1Specification1 50 | ]); 51 | 52 | Assert.That(depot[ElementFixtures.Behavior1Specification1], Is.Not.Null); 53 | } 54 | 55 | [Test] 56 | public void CanGetBehaviorWhenThereIsSpecificationWithSameName() 57 | { 58 | var depot = new RemoteTaskDepot([ 59 | RemoteTaskFixtures.Context, 60 | RemoteTaskFixtures.Behavior1, 61 | RemoteTaskFixtures.Specification1, 62 | RemoteTaskFixtures.Behavior1Specification1 63 | ]); 64 | 65 | Assert.That(depot[ElementFixtures.Specification1], Is.Not.Null); 66 | } 67 | 68 | [Test] 69 | public void BoundElementsAreRunnable() 70 | { 71 | var depot = new RemoteTaskDepot([ 72 | RemoteTaskFixtures.Context, 73 | RemoteTaskFixtures.Behavior1, 74 | RemoteTaskFixtures.Specification1, 75 | RemoteTaskFixtures.Behavior1Specification1 76 | ]); 77 | 78 | depot.Bind(ElementFixtures.Context, RemoteTaskFixtures.Context); 79 | depot.Bind(ElementFixtures.Behavior1, RemoteTaskFixtures.Behavior1); 80 | depot.Bind(ElementFixtures.Specification1, RemoteTaskFixtures.Specification1); 81 | depot.Bind(ElementFixtures.Behavior1Specification1, RemoteTaskFixtures.Behavior1Specification1); 82 | 83 | var selected = depot.GetTestsToRun().ToArray(); 84 | 85 | Assert.That(selected, Has.Member(ElementFixtures.Specification1)); 86 | Assert.That(selected, Has.Member(ElementFixtures.Behavior1Specification1)); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Tests/Fixtures/ElementFixtures.cs: -------------------------------------------------------------------------------- 1 | using Machine.Specifications.Runner.ReSharper.Adapters.Elements; 2 | 3 | namespace Machine.Specifications.Runner.ReSharper.Tests.Fixtures; 4 | 5 | public static class ElementFixtures 6 | { 7 | public static ContextElement Context { get; } = new("Namespace.Context", string.Empty, null); 8 | 9 | public static BehaviorElement Behavior1 { get; } = new(Context, "Namespace.Behavior1", "behaves_like_1", null); 10 | 11 | public static BehaviorElement Behavior2 { get; } = new(Context, "Namespace.Behavior2", "behaves_like_2", null); 12 | 13 | public static SpecificationElement Specification1 { get; } = new(Context, "should_1"); 14 | 15 | public static SpecificationElement Specification2 { get; } = new(Context, "should_2"); 16 | 17 | public static SpecificationElement Behavior1Specification1 { get; } = new(Context, "should_behave_1", null, Behavior1); 18 | 19 | public static SpecificationElement Behavior1Specification2 { get; } = new(Context, "should_behave_2", null, Behavior1); 20 | 21 | public static SpecificationElement Behavior2Specification1 { get; } = new(Context, "should_behave_1", null, Behavior2); 22 | } 23 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Tests/Fixtures/RemoteTaskFixtures.cs: -------------------------------------------------------------------------------- 1 | using Machine.Specifications.Runner.ReSharper.Tasks; 2 | 3 | namespace Machine.Specifications.Runner.ReSharper.Tests.Fixtures; 4 | 5 | public static class RemoteTaskFixtures 6 | { 7 | public static MspecContextRemoteTask Context { get; } = 8 | MspecContextRemoteTask.ToServer( 9 | ElementFixtures.Context.TypeName, 10 | null, 11 | null, 12 | null); 13 | 14 | public static MspecSpecificationRemoteTask Specification1 { get; } = 15 | MspecSpecificationRemoteTask.ToServer( 16 | ElementFixtures.Context.TypeName, 17 | ElementFixtures.Specification1.FieldName, 18 | null, 19 | null, 20 | null, 21 | null); 22 | 23 | public static MspecSpecificationRemoteTask Specification2 { get; } = 24 | MspecSpecificationRemoteTask.ToServer( 25 | ElementFixtures.Context.TypeName, 26 | ElementFixtures.Specification2.FieldName, 27 | null, 28 | null, 29 | null, 30 | null); 31 | 32 | public static MspecSpecificationRemoteTask Behavior1 { get; } = 33 | MspecSpecificationRemoteTask.ToServer( 34 | ElementFixtures.Context.TypeName, 35 | ElementFixtures.Behavior1.FieldName, 36 | ElementFixtures.Behavior1.TypeName, 37 | null, 38 | null, 39 | null); 40 | 41 | public static MspecBehaviorSpecificationRemoteTask Behavior1Specification1 { get; } = 42 | MspecBehaviorSpecificationRemoteTask.ToServer( 43 | ElementFixtures.Behavior1.Id, 44 | ElementFixtures.Context.TypeName, 45 | ElementFixtures.Behavior1Specification1.FieldName, 46 | null); 47 | 48 | public static MspecBehaviorSpecificationRemoteTask Behavior1Specification2 { get; } = 49 | MspecBehaviorSpecificationRemoteTask.ToServer( 50 | ElementFixtures.Behavior1.Id, 51 | ElementFixtures.Context.TypeName, 52 | ElementFixtures.Behavior1Specification2.FieldName, 53 | null); 54 | } 55 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Tests/Machine.Specifications.Runner.ReSharper.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net472 5 | latest 6 | enable 7 | false 8 | MSB3277 9 | None 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | all 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | Always 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Tests/Tasks/MspecBehaviorSpecificationRemoteTaskTests.cs: -------------------------------------------------------------------------------- 1 | using Machine.Specifications.Runner.ReSharper.Tasks; 2 | using NUnit.Framework; 3 | 4 | namespace Machine.Specifications.Runner.ReSharper.Tests.Tasks; 5 | 6 | [TestFixture] 7 | public class MspecBehaviorSpecificationRemoteTaskTests 8 | { 9 | [Test] 10 | public void ServerTaskHasCorrectId() 11 | { 12 | var task = MspecBehaviorSpecificationRemoteTask.ToServer("Namespace.Context.behaves_like", "Namespace.Context", "should", null); 13 | 14 | Assert.That(task.TestId, Is.EqualTo("Namespace.Context.behaves_like.should")); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Tests/Tasks/MspecContextRemoteTaskTests.cs: -------------------------------------------------------------------------------- 1 | using Machine.Specifications.Runner.ReSharper.Tasks; 2 | using NUnit.Framework; 3 | 4 | namespace Machine.Specifications.Runner.ReSharper.Tests.Tasks; 5 | 6 | [TestFixture] 7 | public class MspecContextRemoteTaskTests 8 | { 9 | [Test] 10 | public void ServerTaskHasCorrectId() 11 | { 12 | var task = MspecContextRemoteTask.ToServer("Namespace.Context", null, null, null); 13 | 14 | Assert.That(task.TestId, Is.EqualTo("Namespace.Context")); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper.Tests/Tasks/MspecSpecificationRemoteTaskTests.cs: -------------------------------------------------------------------------------- 1 | using Machine.Specifications.Runner.ReSharper.Tasks; 2 | using NUnit.Framework; 3 | 4 | namespace Machine.Specifications.Runner.ReSharper.Tests.Tasks; 5 | 6 | [TestFixture] 7 | public class MspecSpecificationRemoteTaskTests 8 | { 9 | [Test] 10 | public void SpecificationServerTaskHasCorrectId() 11 | { 12 | var task = MspecSpecificationRemoteTask.ToServer("Namespace.Context", "should", null, null, null, null); 13 | 14 | Assert.That(task.TestId, Is.EqualTo("Namespace.Context.should")); 15 | } 16 | 17 | [Test] 18 | public void BehaviorServerTaskHasCorrectId() 19 | { 20 | var task = MspecSpecificationRemoteTask.ToServer("Namespace.Context", "behaves_like", "Namespace.ABehavior", null, null, null); 21 | 22 | Assert.That(task.TestId, Is.EqualTo("Namespace.Context.behaves_like")); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Elements/IMspecTestElement.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.ReSharper.UnitTestFramework.Elements; 2 | 3 | namespace Machine.Specifications.Runner.ReSharper.Elements; 4 | 5 | public interface IMspecTestElement : IUnitTestElement 6 | { 7 | } 8 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Elements/MspecBehaviorSpecificationTestElement.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using JetBrains.Annotations; 3 | using JetBrains.Metadata.Reader.Impl; 4 | using JetBrains.ProjectModel; 5 | using JetBrains.ReSharper.Psi; 6 | using JetBrains.ReSharper.Psi.Util; 7 | using JetBrains.ReSharper.UnitTestFramework; 8 | using JetBrains.ReSharper.UnitTestFramework.Elements; 9 | using JetBrains.ReSharper.UnitTestFramework.Persistence; 10 | using Machine.Specifications.Runner.Utility; 11 | 12 | namespace Machine.Specifications.Runner.ReSharper.Elements; 13 | 14 | public class MspecBehaviorSpecificationTestElement : ClrUnitTestElement.Row, ITestCase, IMspecTestElement 15 | { 16 | [UsedImplicitly] 17 | public MspecBehaviorSpecificationTestElement() 18 | { 19 | } 20 | 21 | public MspecBehaviorSpecificationTestElement(MspecSpecificationTestElement parent, string fieldName, string? ignoreReason) 22 | : base($"{parent.Context.TypeName.FullName}.{parent.FieldName}.{fieldName}", parent) 23 | { 24 | FieldName = fieldName; 25 | DisplayName = fieldName.ToFormat(); 26 | IgnoreReason = ignoreReason; 27 | } 28 | 29 | public override string Kind => "Behavior Specification"; 30 | 31 | public bool IsNotRunnableStandalone => Origin == UnitTestElementOrigin.Dynamic; 32 | 33 | public MspecSpecificationTestElement Specification => (MspecSpecificationTestElement) Parent!; 34 | 35 | [Persist] 36 | [UsedImplicitly] 37 | public string FieldName { get; set; } = null!; 38 | 39 | [Persist] 40 | [UsedImplicitly] 41 | public string DisplayName { get; set; } = null!; 42 | 43 | public override string ShortName => DisplayName; 44 | 45 | [Persist] 46 | [UsedImplicitly] 47 | public string? IgnoreReason { get; set; } 48 | 49 | public override IDeclaredElement? GetDeclaredElement() 50 | { 51 | if (Specification.BehaviorType == null) 52 | { 53 | return null; 54 | } 55 | 56 | using (CompilationContextCookie.OverrideOrCreate(Project.GetResolveContext(TargetFrameworkId))) 57 | { 58 | var behaviorType = UT.Facade.TypeCache.GetTypeElement( 59 | Project, 60 | TargetFrameworkId, 61 | new ClrTypeName(Specification.BehaviorType), 62 | false, 63 | true); 64 | 65 | return behaviorType? 66 | .EnumerateMembers(FieldName, behaviorType.CaseSensitiveName) 67 | .FirstOrDefault(); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Elements/MspecContextTestElement.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using JetBrains.Metadata.Reader.API; 3 | using JetBrains.ReSharper.UnitTestFramework.Elements; 4 | using JetBrains.ReSharper.UnitTestFramework.Persistence; 5 | using Machine.Specifications.Runner.Utility; 6 | 7 | namespace Machine.Specifications.Runner.ReSharper.Elements; 8 | 9 | public class MspecContextTestElement : ClrUnitTestElement.FromClass, IMspecTestElement 10 | { 11 | [UsedImplicitly] 12 | public MspecContextTestElement() 13 | { 14 | } 15 | 16 | public MspecContextTestElement(IClrTypeName typeName, string? subject, string? ignoreReason) 17 | : base(typeName.FullName, typeName, GetDisplayName(typeName, subject)) 18 | { 19 | Subject = subject; 20 | IgnoreReason = ignoreReason; 21 | } 22 | 23 | public override string Kind => "Context"; 24 | 25 | [Persist] 26 | [UsedImplicitly] 27 | public string? Subject { get; set; } 28 | 29 | [Persist] 30 | [UsedImplicitly] 31 | public string? IgnoreReason { get; set; } 32 | 33 | private static string GetDisplayName(IClrTypeName typeName, string? subject) 34 | { 35 | var display = typeName.ShortName.ToFormat(); 36 | 37 | return string.IsNullOrEmpty(subject) 38 | ? display 39 | : $"{subject}, {display}"; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Elements/MspecSpecificationTestElement.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using JetBrains.Annotations; 3 | using JetBrains.ReSharper.Psi; 4 | using JetBrains.ReSharper.Psi.Util; 5 | using JetBrains.ReSharper.UnitTestFramework.Elements; 6 | using JetBrains.ReSharper.UnitTestFramework.Persistence; 7 | using Machine.Specifications.Runner.Utility; 8 | 9 | namespace Machine.Specifications.Runner.ReSharper.Elements; 10 | 11 | public class MspecSpecificationTestElement : ClrUnitTestElement.FromMethod, IMspecTestElement 12 | { 13 | [UsedImplicitly] 14 | public MspecSpecificationTestElement() 15 | { 16 | } 17 | 18 | public MspecSpecificationTestElement(MspecContextTestElement parent, string fieldName, string? behaviorType, string? declaredInTypeShortName, string? ignoreReason) 19 | : base($"{parent.TypeName.FullName}.{fieldName}", parent, fieldName, declaredInTypeShortName) 20 | { 21 | FieldName = fieldName; 22 | BehaviorType = behaviorType; 23 | DisplayName = fieldName.ToFormat(); 24 | IgnoreReason = ignoreReason; 25 | } 26 | 27 | public MspecContextTestElement Context => Parent; 28 | 29 | public override string Kind => "Specification"; 30 | 31 | [Persist] 32 | [UsedImplicitly] 33 | public string FieldName { get; set; } = null!; 34 | 35 | [Persist] 36 | [UsedImplicitly] 37 | public string? BehaviorType { get; set; } 38 | 39 | [Persist] 40 | [UsedImplicitly] 41 | public string DisplayName { get; set; } = null!; 42 | 43 | public override string ShortName => DisplayName; 44 | 45 | [Persist] 46 | [UsedImplicitly] 47 | public string? IgnoreReason { get; set; } 48 | 49 | protected override IDeclaredElement? GetTypeMember(ITypeElement declaredType) 50 | { 51 | return declaredType 52 | .EnumerateMembers(FieldName, declaredType.CaseSensitiveName) 53 | .FirstOrDefault(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Machine.Specifications.Runner.ReSharper; 6 | 7 | public static class EnumerableExtensions 8 | { 9 | public static IEnumerable Flatten(this IEnumerable source, Func> selector) 10 | { 11 | var items = source.ToArray(); 12 | 13 | return items 14 | .SelectMany(c => selector(c).Flatten(selector)) 15 | .Concat(items); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Machine.Specifications.Runner.ReSharper.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 0.1.0 5 | net472 6 | Machine.Specifications.Runner.Resharper9 7 | latest 8 | enable 9 | false 10 | NU5128,NU5100 11 | None 12 | 13 | 14 | 15 | Program 16 | $(DevEnvDir)devenv.exe 17 | /RootSuffix ReSharper /ReSharper.Internal 18 | 19 | 20 | 21 | Machine.Specifications for ReSharper 22 | A ReSharper runner for the Context/Specification framework Machine.Specifications 23 | Machine 24 | Machine 25 | test;unit;testing;context;specification;bdd;tdd;mspec;runner;resharper 26 | Adds support for R# and Rider 2025.1 27 | Machine.png 28 | http://github.com/machine/machine.specifications.runner.resharper/raw/master/images/icon.png 29 | https://github.com/machine/machine.specifications.runner.resharper 30 | http://github.com/machine/machine.specifications.runner.resharper 31 | MIT 32 | 33 | 34 | 35 | 2025.1.0 36 | $(SdkVersion.Substring(2,2))$(SdkVersion.Substring(5,1)) 37 | $(WaveVersionBase).0.0 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | $(TargetsForTfmSpecificContentInPackage);PackageItems 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Mappings/MspecBehaviorSpecificationMapping.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Application.Parts; 2 | using JetBrains.ProjectModel; 3 | using JetBrains.ReSharper.UnitTestFramework.Execution.TestRunner; 4 | using JetBrains.ReSharper.UnitTestFramework.Exploration; 5 | using JetBrains.Util; 6 | using Machine.Specifications.Runner.ReSharper.Elements; 7 | using Machine.Specifications.Runner.ReSharper.Tasks; 8 | 9 | namespace Machine.Specifications.Runner.ReSharper.Mappings; 10 | 11 | [SolutionComponent(Instantiation.DemandAnyThreadUnsafe)] 12 | public class MspecBehaviorSpecificationMapping(MspecServiceProvider services) 13 | : MspecElementMapping(services) 14 | { 15 | protected override MspecBehaviorSpecificationRemoteTask ToRemoteTask(MspecBehaviorSpecificationTestElement element, ITestRunnerExecutionContext context) 16 | { 17 | var task = MspecBehaviorSpecificationRemoteTask.ToClient( 18 | element.NaturalId.TestId, 19 | element.IgnoreReason, 20 | context.IsRunExplicitly(element)); 21 | 22 | task.ContextTypeName = element.Specification.Context.TypeName.FullName; 23 | task.FieldName = element.FieldName; 24 | task.ParentId = $"{element.Specification.Context.TypeName.FullName}.{element.Specification.FieldName}"; 25 | 26 | return task; 27 | } 28 | 29 | protected override MspecBehaviorSpecificationTestElement? ToElement(MspecBehaviorSpecificationRemoteTask task, ITestRunnerDiscoveryContext context, IUnitTestElementObserver observer) 30 | { 31 | if (task.ParentId == null) 32 | { 33 | context.Logger.Warn($"Cannot create element for MspecBehaviorSpecificationTestElement '{task.TestId}': ParentId is missing"); 34 | 35 | return null; 36 | } 37 | 38 | var specificationElement = observer.GetElementById(task.ParentId); 39 | 40 | if (specificationElement == null) 41 | { 42 | context.Logger.Warn("Cannot create element for MspecBehaviorSpecificationTestElement '" + task.TestId + "': Specification is missing"); 43 | 44 | return null; 45 | } 46 | 47 | var factory = GetFactory(context); 48 | 49 | return factory.GetOrCreateBehaviorSpecification( 50 | specificationElement, 51 | task.FieldName!, 52 | task.IgnoreReason); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Mappings/MspecContextMapping.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using JetBrains.Application.Parts; 4 | using JetBrains.Metadata.Reader.Impl; 5 | using JetBrains.ProjectModel; 6 | using JetBrains.ReSharper.UnitTestFramework.Execution.TestRunner; 7 | using JetBrains.ReSharper.UnitTestFramework.Exploration; 8 | using Machine.Specifications.Runner.ReSharper.Elements; 9 | using Machine.Specifications.Runner.ReSharper.Tasks; 10 | 11 | namespace Machine.Specifications.Runner.ReSharper.Mappings; 12 | 13 | [SolutionComponent(Instantiation.DemandAnyThreadUnsafe)] 14 | public class MspecContextMapping(MspecServiceProvider services) 15 | : MspecElementMapping(services) 16 | { 17 | protected override MspecContextRemoteTask ToRemoteTask(MspecContextTestElement element, ITestRunnerExecutionContext context) 18 | { 19 | var task = MspecContextRemoteTask.ToClient( 20 | element.NaturalId.TestId, 21 | element.IgnoreReason, 22 | context.RunAllChildren(element), 23 | context.IsRunExplicitly(element)); 24 | 25 | task.Subject = element.Subject; 26 | task.Tags = element.OwnCategories?.Select(x => x.Name).ToArray() ?? Array.Empty(); 27 | 28 | return task; 29 | } 30 | 31 | protected override MspecContextTestElement ToElement(MspecContextRemoteTask task, ITestRunnerDiscoveryContext context, IUnitTestElementObserver observer) 32 | { 33 | var factory = GetFactory(context); 34 | 35 | return factory.GetOrCreateContext( 36 | new ClrTypeName(task.ContextTypeName), 37 | task.Subject, 38 | task.Tags, 39 | task.IgnoreReason); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Mappings/MspecElementMapping.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.ReSharper.TestRunner.Abstractions.Objects; 2 | using JetBrains.ReSharper.UnitTestFramework.Elements; 3 | using JetBrains.ReSharper.UnitTestFramework.Execution.TestRunner; 4 | using JetBrains.ReSharper.UnitTestFramework.Exploration; 5 | 6 | namespace Machine.Specifications.Runner.ReSharper.Mappings; 7 | 8 | public abstract class MspecElementMapping(MspecServiceProvider services) 9 | : IUnitTestElementToRemoteTaskMapping, IRemoteTaskToUnitTestElementMapping 10 | where TElement : IUnitTestElement 11 | where TTask : RemoteTask 12 | { 13 | protected MspecServiceProvider Services { get; } = services; 14 | 15 | protected abstract TTask ToRemoteTask(TElement element, ITestRunnerExecutionContext context); 16 | 17 | protected abstract TElement? ToElement(TTask task, ITestRunnerDiscoveryContext context, IUnitTestElementObserver observer); 18 | 19 | public RemoteTask GetRemoteTask(TElement element, ITestRunnerExecutionContext context) 20 | { 21 | return ToRemoteTask(element, context); 22 | } 23 | 24 | public IUnitTestElement? GetElement(TTask task, ITestRunnerDiscoveryContext context, IUnitTestElementObserver observer) 25 | { 26 | return ToElement(task, context, observer); 27 | } 28 | 29 | protected UnitTestElementFactory GetFactory(ITestRunnerDiscoveryContext context) 30 | { 31 | return context.GetOrCreateDataUnderLock( 32 | MspecElementMappingKeys.ElementFactoryKey, static () => new UnitTestElementFactory()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Mappings/MspecElementMappingKeys.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Util; 2 | 3 | namespace Machine.Specifications.Runner.ReSharper.Mappings; 4 | 5 | public static class MspecElementMappingKeys 6 | { 7 | public static readonly Key ElementFactoryKey = new("MspecElementMapping.MspecElementFactory"); 8 | } 9 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Mappings/MspecSpecificationMapping.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Application.Parts; 2 | using JetBrains.ProjectModel; 3 | using JetBrains.ReSharper.UnitTestFramework.Execution.TestRunner; 4 | using JetBrains.ReSharper.UnitTestFramework.Exploration; 5 | using JetBrains.Util; 6 | using Machine.Specifications.Runner.ReSharper.Elements; 7 | using Machine.Specifications.Runner.ReSharper.Tasks; 8 | 9 | namespace Machine.Specifications.Runner.ReSharper.Mappings; 10 | 11 | [SolutionComponent(Instantiation.DemandAnyThreadUnsafe)] 12 | public class MspecSpecificationMapping(MspecServiceProvider services) 13 | : MspecElementMapping(services) 14 | { 15 | protected override MspecSpecificationRemoteTask ToRemoteTask(MspecSpecificationTestElement element, ITestRunnerExecutionContext context) 16 | { 17 | var task = MspecSpecificationRemoteTask.ToClient( 18 | element.NaturalId.TestId, 19 | element.IgnoreReason, 20 | context.RunAllChildren(element), 21 | context.IsRunExplicitly(element)); 22 | 23 | task.ContextTypeName = element.Context.TypeName.FullName; 24 | task.FieldName = element.FieldName; 25 | task.BehaviorType = element.BehaviorType; 26 | 27 | return task; 28 | } 29 | 30 | protected override MspecSpecificationTestElement? ToElement(MspecSpecificationRemoteTask task, ITestRunnerDiscoveryContext context, IUnitTestElementObserver observer) 31 | { 32 | if (task.ContextTypeName == null) 33 | { 34 | context.Logger.Warn($"Cannot create element for MspecSpecificationTestElement '{task.TestId}': ContextTypeName is missing"); 35 | 36 | return null; 37 | } 38 | 39 | var factory = GetFactory(context); 40 | 41 | var contextElement = observer.GetElementById(task.ContextTypeName); 42 | 43 | if (contextElement == null) 44 | { 45 | context.Logger.Warn($"Cannot create element for MspecSpecificationTestElement '{task.TestId}': Context is missing"); 46 | 47 | return null; 48 | } 49 | 50 | return factory.GetOrCreateSpecification( 51 | contextElement, 52 | task.FieldName!, 53 | task.BehaviorType, 54 | task.IgnoreReason); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/MspecProviderSettings.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Application; 2 | using JetBrains.Application.Settings; 3 | using JetBrains.Application.Settings.Extentions; 4 | using JetBrains.Lifetimes; 5 | using JetBrains.ReSharper.UnitTestFramework.Exploration.Artifacts; 6 | using JetBrains.ReSharper.UnitTestFramework.Settings; 7 | 8 | namespace Machine.Specifications.Runner.ReSharper; 9 | 10 | [SettingsKey(typeof(UnitTestingSettings), "Settings for MSpec unit test provider")] 11 | public class MspecProviderSettings 12 | { 13 | [SettingsEntry(DiscoveryMethod.Metadata, "Discovery method for discovery tests from artifacts")] 14 | public DiscoveryMethod TestDiscoveryFromArtifactsMethod; 15 | 16 | [ShellComponent] 17 | public class Reader(ISettingsStore settingsStore, ISettingsOptimization settingsOptimization) 18 | : ICachedSettingsReader 19 | { 20 | public ISettingsOptimization SettingsOptimization { get; } = settingsOptimization; 21 | 22 | public SettingsKey KeyExposed { get; } = settingsStore.Schema.GetKey(); 23 | 24 | public MspecProviderSettings ReadData(Lifetime lifetime, IContextBoundSettingsStore store) 25 | { 26 | return (MspecProviderSettings)store.GetKey(KeyExposed, null, SettingsOptimization)!; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/MspecPsiFileExplorer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using JetBrains.Metadata.Reader.Impl; 5 | using JetBrains.ReSharper.Psi; 6 | using JetBrains.ReSharper.Psi.Tree; 7 | using JetBrains.ReSharper.UnitTestFramework.Elements; 8 | using JetBrains.ReSharper.UnitTestFramework.Exploration; 9 | using JetBrains.ReSharper.UnitTestFramework.Exploration.Daemon; 10 | using Machine.Specifications.Runner.ReSharper.Elements; 11 | using Machine.Specifications.Runner.ReSharper.Reflection; 12 | 13 | namespace Machine.Specifications.Runner.ReSharper; 14 | 15 | public class MspecPsiFileExplorer(IUnitTestElementObserverOnFile observer, Func interrupted) 16 | : UnitTestElementRecursivePsiProcessor(interrupted) 17 | { 18 | private readonly UnitTestElementFactory factory = new(); 19 | 20 | private readonly Dictionary recentContexts = new(); 21 | 22 | public override bool InteriorShouldBeProcessed(ITreeNode element) 23 | { 24 | if (element is ITypeMemberDeclaration) 25 | { 26 | return element is ITypeDeclaration; 27 | } 28 | 29 | return true; 30 | } 31 | 32 | public override void ProcessBeforeInterior(ITreeNode element) 33 | { 34 | if (element is not IDeclaration declaration) 35 | { 36 | return; 37 | } 38 | 39 | var declaredElement = declaration.DeclaredElement; 40 | 41 | if (declaredElement is IClass type) 42 | { 43 | ProcessType(type.AsTypeInfo(), declaration); 44 | } 45 | else if (declaredElement is IField field) 46 | { 47 | ProcessField(field.AsFieldInfo(), declaration); 48 | } 49 | } 50 | 51 | public override void ProcessAfterInterior(ITreeNode element) 52 | { 53 | } 54 | 55 | private void ProcessType(ITypeInfo type, IDeclaration declaration) 56 | { 57 | if (type.IsContext()) 58 | { 59 | ProcessContext(type, declaration, true); 60 | } 61 | } 62 | 63 | private void ProcessContext(ITypeInfo type, IDeclaration declaration, bool isClear) 64 | { 65 | var name = new ClrTypeName(type.FullyQualifiedName); 66 | 67 | var context = factory.GetOrCreateContext( 68 | name, 69 | type.GetSubject(), 70 | type.GetTags().ToArray(), 71 | type.GetIgnoreReason()); 72 | 73 | recentContexts[name] = context; 74 | 75 | if (isClear) 76 | { 77 | OnUnitTestElement(context, declaration); 78 | } 79 | } 80 | 81 | private void ProcessField(IFieldInfo field, IDeclaration? declaration = null) 82 | { 83 | if (field.IsSpecification()) 84 | { 85 | ProcessSpecificationField(field, declaration); 86 | } 87 | else if (field.IsBehavior()) 88 | { 89 | ProcessBehaviorField(field, declaration); 90 | } 91 | } 92 | 93 | private void ProcessSpecificationField(IFieldInfo field, IDeclaration? declaration = null) 94 | { 95 | var containingType = new ClrTypeName(field.DeclaringType); 96 | 97 | if (recentContexts.TryGetValue(containingType, out var context)) 98 | { 99 | var specification = factory.GetOrCreateSpecification( 100 | context, 101 | field.ShortName, 102 | null, 103 | field.GetIgnoreReason() ?? context.IgnoreReason); 104 | 105 | OnUnitTestElement(specification, declaration); 106 | } 107 | } 108 | 109 | private void ProcessBehaviorField(IFieldInfo field, IDeclaration? declaration = null) 110 | { 111 | var behaviorType = field.FieldType.GetGenericArguments().FirstOrDefault(); 112 | var containingType = new ClrTypeName(field.DeclaringType); 113 | 114 | if (recentContexts.TryGetValue(containingType, out var context) && behaviorType != null && behaviorType.IsBehaviorContainer()) 115 | { 116 | var specification = factory.GetOrCreateSpecification( 117 | context, 118 | field.ShortName, 119 | behaviorType.FullyQualifiedName, 120 | field.GetIgnoreReason()); 121 | 122 | OnUnitTestElement(specification, declaration); 123 | } 124 | } 125 | 126 | private void OnUnitTestElement(IUnitTestElement element, IDeclaration? declaration = null) 127 | { 128 | if (declaration == null) 129 | { 130 | return; 131 | } 132 | 133 | var navigationRange = declaration.GetNameDocumentRange().TextRange; 134 | var containingRange = declaration.GetDocumentRange().TextRange; 135 | 136 | observer.OnUnitTestElement(element); 137 | observer.OnUnitTestElementDisposition(element, navigationRange, containingRange); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/MspecServiceProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Application.Parts; 3 | using JetBrains.ProjectModel; 4 | using JetBrains.ReSharper.UnitTestFramework.Execution; 5 | using JetBrains.Util; 6 | using Machine.Specifications.Runner.ReSharper.Runner; 7 | 8 | namespace Machine.Specifications.Runner.ReSharper; 9 | 10 | [SolutionComponent(Instantiation.ContainerAsyncPrimaryThread)] 11 | public class MspecServiceProvider(ISolution solution) 12 | { 13 | private readonly Lazy runner = Lazy.Of(solution.GetComponent, true); 14 | 15 | public IUnitTestRunStrategy GetRunStrategy() 16 | { 17 | return runner.Value; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/MspecTestExplorerFromArtifacts.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Application.Parts; 2 | using JetBrains.Application.Settings; 3 | using JetBrains.ProjectModel; 4 | using JetBrains.ReSharper.UnitTestFramework.Exploration.Artifacts; 5 | 6 | namespace Machine.Specifications.Runner.ReSharper; 7 | 8 | [SolutionComponent(Instantiation.ContainerAsyncPrimaryThread)] 9 | public class MspecTestExplorerFromArtifacts( 10 | ISettingsStore settingsStore, 11 | MspecTestExplorerFromMetadata metadataExplorer, 12 | MspecTestExplorerFromTestRunner testRunnerExplorer) 13 | : UnitTestExplorerFrom.Switching(settingsStore, x => x.TestDiscoveryFromArtifactsMethod, metadataExplorer, testRunnerExplorer); 14 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/MspecTestExplorerFromFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Application.Parts; 3 | using JetBrains.ProjectModel; 4 | using JetBrains.ReSharper.Psi; 5 | using JetBrains.ReSharper.Psi.Tree; 6 | using JetBrains.ReSharper.UnitTestFramework; 7 | using JetBrains.ReSharper.UnitTestFramework.Exploration; 8 | using JetBrains.ReSharper.UnitTestFramework.Exploration.Daemon; 9 | 10 | namespace Machine.Specifications.Runner.ReSharper; 11 | 12 | [SolutionComponent(Instantiation.DemandAnyThreadUnsafe)] 13 | public class MspecTestExplorerFromFile(MspecTestProvider provider) : IUnitTestExplorerFromFile 14 | { 15 | public IUnitTestProvider Provider { get; } = provider; 16 | 17 | public void ProcessFile(IFile psiFile, IUnitTestElementObserverOnFile observer, Func interrupted) 18 | { 19 | if (!IsProjectFile(psiFile)) 20 | { 21 | return; 22 | } 23 | 24 | var explorer = new MspecPsiFileExplorer(observer, interrupted); 25 | 26 | psiFile.ProcessDescendants(explorer); 27 | } 28 | 29 | private static bool IsProjectFile(IFile psiFile) 30 | { 31 | return psiFile.GetSourceFile().ToProjectFile() != null; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/MspecTestExplorerFromMetadata.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using JetBrains.Application.Parts; 4 | using JetBrains.Metadata.Reader.API; 5 | using JetBrains.ProjectModel; 6 | using JetBrains.ProjectModel.Assemblies.AssemblyToAssemblyResolvers; 7 | using JetBrains.ProjectModel.Assemblies.Impl; 8 | using JetBrains.ProjectModel.NuGet.Packaging; 9 | using JetBrains.ReSharper.UnitTestFramework.Exploration; 10 | using JetBrains.ReSharper.UnitTestFramework.Exploration.Artifacts; 11 | using JetBrains.Util; 12 | using JetBrains.Util.Dotnet.TargetFrameworkIds; 13 | 14 | namespace Machine.Specifications.Runner.ReSharper; 15 | 16 | [SolutionComponent(Instantiation.ContainerAsyncPrimaryThread)] 17 | public class MspecTestExplorerFromMetadata( 18 | MspecTestProvider provider, 19 | AssemblyToAssemblyReferencesResolveManager resolveManager, 20 | ResolveContextManager resolveContextManager, 21 | NuGetInstalledPackageChecker installedPackageChecker, 22 | ILogger logger) 23 | : UnitTestExplorerFrom.Metadata(provider, resolveManager, resolveContextManager, installedPackageChecker, logger) 24 | { 25 | private readonly ILogger logger = logger; 26 | 27 | protected override IEnumerable GetRequiredNuGetDependencies(IProject project, TargetFrameworkId targetFrameworkId) 28 | { 29 | foreach (var dependency in base.GetRequiredNuGetDependencies(project, targetFrameworkId)) 30 | { 31 | yield return dependency; 32 | } 33 | 34 | yield return "Machine.Specifications.Runner.VisualStudio"; 35 | } 36 | 37 | protected override void ProcessProject( 38 | MetadataLoader loader, 39 | IUnitTestElementObserver observer, 40 | CancellationToken token) 41 | { 42 | MetadataElementsSource.ExploreProject(observer.Source.Project, observer.Source.Output, loader, logger, token, assembly => 43 | { 44 | var explorer = new MspecTestMetadataExplorer(observer); 45 | 46 | explorer.ExploreAssembly(assembly, token); 47 | }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/MspecTestExplorerFromTestRunner.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Application.Parts; 2 | using JetBrains.ProjectModel; 3 | using JetBrains.ProjectModel.NuGet.Packaging; 4 | using JetBrains.ReSharper.UnitTestFramework.Execution.TestRunner; 5 | using JetBrains.ReSharper.UnitTestFramework.Exploration.Artifacts; 6 | using JetBrains.Util.Collections; 7 | 8 | namespace Machine.Specifications.Runner.ReSharper; 9 | 10 | [SolutionComponent(Instantiation.ContainerAsyncPrimaryThread)] 11 | public class MspecTestExplorerFromTestRunner( 12 | MspecTestProvider provider, 13 | ITestRunnerAgentManager agentManager, 14 | MspecTestRunnerOrchestrator adapter, 15 | NuGetInstalledPackageChecker installedPackageChecker) 16 | : UnitTestExplorerFrom.TestRunner(provider, agentManager, adapter, installedPackageChecker, 1.Minute()); 17 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/MspecTestMetadataExplorer.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading; 3 | using JetBrains.Metadata.Reader.API; 4 | using JetBrains.Metadata.Reader.Impl; 5 | using JetBrains.ReSharper.UnitTestFramework.Exploration; 6 | using Machine.Specifications.Runner.ReSharper.Elements; 7 | using Machine.Specifications.Runner.ReSharper.Reflection; 8 | 9 | namespace Machine.Specifications.Runner.ReSharper; 10 | 11 | public class MspecTestMetadataExplorer(IUnitTestElementObserver observer) 12 | { 13 | private readonly UnitTestElementFactory factory = new(); 14 | 15 | public void ExploreAssembly(IMetadataAssembly assembly, CancellationToken token) 16 | { 17 | var types = assembly.GetTypes() 18 | .Flatten(x => x.GetNestedTypes()); 19 | 20 | foreach (var type in types) 21 | { 22 | if (token.IsCancellationRequested) 23 | { 24 | break; 25 | } 26 | 27 | ExploreType(type.AsTypeInfo()); 28 | } 29 | } 30 | 31 | private void ExploreType(ITypeInfo type) 32 | { 33 | if (!type.IsContext()) 34 | { 35 | return; 36 | } 37 | 38 | ExploreContext(type); 39 | } 40 | 41 | private void ExploreContext(ITypeInfo type) 42 | { 43 | var contextElement = factory.GetOrCreateContext( 44 | new ClrTypeName(type.FullyQualifiedName), 45 | type.GetSubject(), 46 | type.GetTags().ToArray(), 47 | type.GetIgnoreReason()); 48 | 49 | observer.OnUnitTestElement(contextElement); 50 | 51 | var fields = type.GetFields().ToArray(); 52 | 53 | var specifications = fields.Where(x => x.IsSpecification()); 54 | var behaviors = fields.Where(x => x.IsBehavior()); 55 | 56 | foreach (var specification in specifications) 57 | { 58 | ExploreSpecification(contextElement, specification); 59 | } 60 | 61 | foreach (var behavior in behaviors) 62 | { 63 | ExploreBehavior(contextElement, behavior); 64 | } 65 | 66 | observer.OnUnitTestElement(contextElement); 67 | } 68 | 69 | private void ExploreSpecification(MspecContextTestElement contextElement, IFieldInfo field) 70 | { 71 | var specificationElement = factory.GetOrCreateSpecification( 72 | contextElement, 73 | field.ShortName, 74 | null, 75 | field.GetIgnoreReason() ?? contextElement.IgnoreReason); 76 | 77 | observer.OnUnitTestElement(specificationElement); 78 | } 79 | 80 | private void ExploreBehavior(MspecContextTestElement contextElement, IFieldInfo field) 81 | { 82 | var behaviorType = field.FieldType.GetGenericArguments() 83 | .FirstOrDefault(); 84 | 85 | var specificationElement = factory.GetOrCreateSpecification( 86 | contextElement, 87 | field.ShortName, 88 | behaviorType?.FullyQualifiedName, 89 | field.GetIgnoreReason()); 90 | 91 | observer.OnUnitTestElement(specificationElement); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/MspecTestProvider.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Metadata.Utils; 2 | using JetBrains.ProjectModel; 3 | using JetBrains.ReSharper.Psi; 4 | using JetBrains.ReSharper.Resources.Shell; 5 | using JetBrains.ReSharper.UnitTestFramework; 6 | using JetBrains.ReSharper.UnitTestFramework.Elements; 7 | using JetBrains.ReSharper.UnitTestFramework.Execution; 8 | using JetBrains.ReSharper.UnitTestFramework.Execution.Hosting; 9 | using JetBrains.Util.Dotnet.TargetFrameworkIds; 10 | using JetBrains.Util.Reflection; 11 | using Machine.Specifications.Runner.ReSharper.Elements; 12 | using Machine.Specifications.Runner.ReSharper.Reflection; 13 | 14 | namespace Machine.Specifications.Runner.ReSharper; 15 | 16 | [UnitTestProvider] 17 | public class MspecTestProvider : IDotNetArtifactBasedUnitTestProvider 18 | { 19 | public const string Id = "Machine.Specifications"; 20 | 21 | private static readonly AssemblyNameInfo MspecReferenceName = AssemblyNameInfoFactory.Create2(Id, null); 22 | 23 | public string ID => Id; 24 | 25 | public string Name => Id; 26 | 27 | public IUnitTestRunStrategy GetRunStrategy(IUnitTestElement element, IHostProvider hostProvider) 28 | { 29 | return UT.Facade.Get().GetRunStrategy(); 30 | } 31 | 32 | public bool IsElementOfKind(IUnitTestElement element, UnitTestElementKind elementKind) 33 | { 34 | switch (elementKind) 35 | { 36 | case UnitTestElementKind.Test: 37 | return element is MspecSpecificationTestElement or MspecBehaviorSpecificationTestElement; 38 | 39 | case UnitTestElementKind.TestContainer: 40 | return element is MspecContextTestElement; 41 | 42 | case UnitTestElementKind.TestStuff: 43 | return element is MspecContextTestElement or MspecSpecificationTestElement or MspecBehaviorSpecificationTestElement; 44 | 45 | case UnitTestElementKind.Unknown: 46 | return element is not MspecContextTestElement && 47 | element is not MspecSpecificationTestElement && 48 | element is not MspecBehaviorSpecificationTestElement; 49 | } 50 | 51 | return false; 52 | } 53 | 54 | public bool IsElementOfKind(IDeclaredElement element, UnitTestElementKind elementKind) 55 | { 56 | switch (elementKind) 57 | { 58 | case UnitTestElementKind.Test: 59 | return element.IsSpecification(); 60 | 61 | case UnitTestElementKind.TestContainer: 62 | return element.IsContext(); 63 | 64 | case UnitTestElementKind.TestStuff: 65 | return element.IsSpecification() || element.IsContext() || element.IsBehavior(); 66 | 67 | case UnitTestElementKind.Unknown: 68 | return !(element.IsSpecification() || element.IsContext() || element.IsBehavior()); 69 | } 70 | 71 | return false; 72 | } 73 | 74 | public bool IsSupported(IHostProvider hostProvider, IProject project, TargetFrameworkId targetFrameworkId) 75 | { 76 | return IsSupported(project, targetFrameworkId); 77 | } 78 | 79 | public bool IsSupported(IProject project, TargetFrameworkId targetFrameworkId) 80 | { 81 | using (ReadLockCookie.Create()) 82 | { 83 | return ReferencedAssembliesService.IsProjectReferencingAssemblyByName(project, targetFrameworkId, MspecReferenceName, out _); 84 | } 85 | } 86 | 87 | public bool SupportsResultEventsForParentOf(IUnitTestElement element) 88 | { 89 | return element is not MspecContextTestElement || element.Parent is not MspecContextTestElement; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/MspecTestRunnerOrchestrator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Reflection; 3 | using JetBrains.Application.Parts; 4 | using JetBrains.ProjectModel; 5 | using JetBrains.ReSharper.TestRunner.Abstractions; 6 | using JetBrains.ReSharper.TestRunner.Abstractions.Objects; 7 | using JetBrains.ReSharper.UnitTestFramework.Execution.TestRunner; 8 | using JetBrains.ReSharper.UnitTestFramework.Execution.TestRunner.Extensions; 9 | using JetBrains.Util; 10 | using Machine.Specifications.Runner.ReSharper.Tasks; 11 | 12 | namespace Machine.Specifications.Runner.ReSharper; 13 | 14 | [SolutionComponent(Instantiation.DemandAnyThreadSafe)] 15 | public class MspecTestRunnerOrchestrator : ITestRunnerAdapter 16 | { 17 | private const string Namespace = "Machine.Specifications.Runner.ReSharper"; 18 | 19 | private static readonly FileSystemPath Root = Assembly.GetExecutingAssembly().GetPath().Directory; 20 | 21 | public Assembly InProcessAdapterAssembly => typeof (MspecTestContainer).Assembly; 22 | 23 | public int Priority => 10; 24 | 25 | public TestAdapterLoader GetTestAdapterLoader(ITestRunnerContext context) 26 | { 27 | var framework = context.RuntimeDescriptor.TargetFrameworkId.IsNetCoreSdk() 28 | ? "netstandard20" 29 | : "net461"; 30 | 31 | var adapters = Root.Combine($"{Namespace}.Adapters.{framework}.dll"); 32 | var tasks = Root.Combine($"{Namespace}.Tasks.{framework}.dll"); 33 | 34 | var type = TypeInfoFactory.Create($"{Namespace}.Adapters.MspecRunner", adapters.FullPath); 35 | 36 | return new TestAdapterInfo(type, type) 37 | { 38 | AdditionalAssemblies = new[] 39 | { 40 | tasks.FullPath 41 | } 42 | }; 43 | } 44 | 45 | public TestContainer GetTestContainer(ITestRunnerContext context) 46 | { 47 | return new MspecTestContainer(context.GetOutputPath().FullPath, context.Settings.TestRunner.ToShadowCopy()); 48 | } 49 | 50 | public IEnumerable GetMessageHandlers(ITestRunnerContext context) 51 | { 52 | yield break; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Options/MspecPage.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Application.Environment; 2 | using JetBrains.Application.Environment.Helpers; 3 | using JetBrains.Application.UI.Options; 4 | using JetBrains.Application.UI.Options.OptionsDialog; 5 | using JetBrains.Application.UI.Options.OptionsDialog.SimpleOptions.ViewModel; 6 | using JetBrains.IDE.UI.Options; 7 | using JetBrains.Lifetimes; 8 | using JetBrains.ReSharper.UnitTestFramework.Exploration.Artifacts; 9 | using JetBrains.ReSharper.UnitTestFramework.UI.Options; 10 | using Machine.Specifications.Runner.ReSharper.Resources; 11 | 12 | namespace Machine.Specifications.Runner.ReSharper.Options; 13 | 14 | [OptionsPage("MSpecPage", "MSpec", typeof(MspecThemedIcons.Mspec), ParentId = UnitTestingPages.Frameworks)] 15 | public class MspecPage : BeSimpleOptionsPage 16 | { 17 | public MspecPage( 18 | Lifetime lifetime, 19 | OptionsPageContext optionsPageContext, 20 | OptionsSettingsSmartContext optionsSettingsSmartContext, 21 | MspecTestProvider provider, 22 | RunsProducts.ProductConfigurations productConfigurations) 23 | : base(lifetime, optionsPageContext, optionsSettingsSmartContext) 24 | { 25 | var supportEnabledOption = UnitTestProviderOptionsPage.GetTestSupportEnabledOption(lifetime, optionsSettingsSmartContext, provider.ID); 26 | 27 | if (productConfigurations.IsInternalMode()) 28 | { 29 | AddBoolOption(supportEnabledOption, "_Enable " + provider.Name + " support", string.Empty); 30 | } 31 | 32 | AddHeader("Test discovery"); 33 | 34 | AddRadioOption( 35 | x => x.TestDiscoveryFromArtifactsMethod, 36 | "When running tests discovery from artifacts, use:", 37 | new RadioOptionPoint( 38 | DiscoveryMethod.Metadata, 39 | "Metadata", 40 | "Fast, but might not be able to find certain entities, such as custom categories or dynamic tests. They will appear after the first execution."), 41 | new RadioOptionPoint( 42 | DiscoveryMethod.TestRunner, 43 | "Test Runner", 44 | "Slower, finds all tests or categories")); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Reflection/IAttributeInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Machine.Specifications.Runner.ReSharper.Reflection; 4 | 5 | public interface IAttributeInfo 6 | { 7 | IEnumerable GetParameters(); 8 | } 9 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Reflection/IFieldInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Machine.Specifications.Runner.ReSharper.Reflection; 4 | 5 | public interface IFieldInfo 6 | { 7 | string DeclaringType { get; } 8 | 9 | string ShortName { get; } 10 | 11 | ITypeInfo FieldType { get; } 12 | 13 | IEnumerable GetCustomAttributes(string typeName, bool inherit); 14 | } 15 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Reflection/ITypeInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Machine.Specifications.Runner.ReSharper.Reflection; 4 | 5 | public interface ITypeInfo 6 | { 7 | string FullyQualifiedName { get; } 8 | 9 | bool IsAbstract { get; } 10 | 11 | ITypeInfo? GetContainingType(); 12 | 13 | IEnumerable GetFields(); 14 | 15 | IEnumerable GetCustomAttributes(string typeName, bool inherit); 16 | 17 | IEnumerable GetGenericArguments(); 18 | } 19 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Reflection/MetadataAttributeInfoAdapter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using JetBrains.Metadata.Reader.API; 4 | using JetBrains.Metadata.Reader.Impl; 5 | 6 | namespace Machine.Specifications.Runner.ReSharper.Reflection; 7 | 8 | public class MetadataAttributeInfoAdapter(IMetadataCustomAttribute attribute) : IAttributeInfo 9 | { 10 | public IEnumerable GetParameters() 11 | { 12 | var arguments = attribute.ConstructorArguments 13 | .Where(x => !x.IsBadValue()) 14 | .ToArray(); 15 | 16 | var types = arguments 17 | .Select(x => x.Value) 18 | .OfType() 19 | .Select(x => new ClrTypeName(x.Type.FullyQualifiedName)) 20 | .Select(x => x.ShortName); 21 | 22 | var values = arguments 23 | .Select(x => x.Value) 24 | .OfType(); 25 | 26 | var arrayValues = arguments 27 | .Where(x => x.ValuesArray != null) 28 | .SelectMany(x => x.ValuesArray) 29 | .Select(x => x.Value) 30 | .OfType(); 31 | 32 | return types 33 | .Concat(values) 34 | .Concat(arrayValues); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Reflection/MetadataExtensions.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Metadata.Reader.API; 2 | 3 | namespace Machine.Specifications.Runner.ReSharper.Reflection; 4 | 5 | public static class MetadataExtensions 6 | { 7 | public static ITypeInfo AsTypeInfo(this IMetadataTypeInfo type, IMetadataClassType? classType = null) 8 | { 9 | return new MetadataTypeInfoAdapter(type, classType); 10 | } 11 | 12 | public static IAttributeInfo AsAttributeInfo(this IMetadataCustomAttribute attribute) 13 | { 14 | return new MetadataAttributeInfoAdapter(attribute); 15 | } 16 | 17 | public static IFieldInfo AsFieldInfo(this IMetadataField field) 18 | { 19 | return new MetadataFieldInfoAdapter(field); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Reflection/MetadataFieldInfoAdapter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using JetBrains.Metadata.Reader.API; 4 | 5 | namespace Machine.Specifications.Runner.ReSharper.Reflection; 6 | 7 | public class MetadataFieldInfoAdapter(IMetadataField field) : IFieldInfo 8 | { 9 | public string DeclaringType => field.DeclaringType.FullyQualifiedName; 10 | 11 | public string ShortName => field.Name; 12 | 13 | public ITypeInfo FieldType 14 | { 15 | get 16 | { 17 | if (field.Type is IMetadataClassType classType) 18 | { 19 | return classType.Type.AsTypeInfo(classType); 20 | } 21 | 22 | return UnknownTypeInfoAdapter.Default; 23 | } 24 | } 25 | 26 | public IEnumerable GetCustomAttributes(string typeName, bool inherit) 27 | { 28 | return field.GetCustomAttributes(typeName) 29 | .Select(x => x.AsAttributeInfo()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Reflection/MetadataTypeInfoAdapter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using JetBrains.Metadata.Reader.API; 4 | 5 | namespace Machine.Specifications.Runner.ReSharper.Reflection; 6 | 7 | public class MetadataTypeInfoAdapter(IMetadataTypeInfo type, IMetadataClassType? classType = null) 8 | : ITypeInfo 9 | { 10 | public string FullyQualifiedName => type.FullyQualifiedName; 11 | 12 | public bool IsAbstract => type.IsAbstract; 13 | 14 | public ITypeInfo? GetContainingType() 15 | { 16 | return type.DeclaringType?.AsTypeInfo(); 17 | } 18 | 19 | public IEnumerable GetFields() 20 | { 21 | return type.GetFields() 22 | .Where(x => !x.IsStatic) 23 | .Select(x => x.AsFieldInfo()); 24 | } 25 | 26 | public IEnumerable GetCustomAttributes(string typeName, bool inherit) 27 | { 28 | var attributes = type.GetCustomAttributes(typeName) 29 | .Select(x => x.AsAttributeInfo()); 30 | 31 | if (!inherit) 32 | { 33 | return attributes; 34 | } 35 | 36 | var baseAttributes = GetBaseTypes() 37 | .SelectMany(x => x.GetCustomAttributes(typeName, false)); 38 | 39 | return attributes.Concat(baseAttributes); 40 | } 41 | 42 | public IEnumerable GetGenericArguments() 43 | { 44 | if (classType == null) 45 | { 46 | return type.TypeParameters.Select(_ => UnknownTypeInfoAdapter.Default); 47 | } 48 | 49 | return classType.Arguments 50 | .OfType() 51 | .Select(x => x.Type.AsTypeInfo()); 52 | } 53 | 54 | private IEnumerable GetBaseTypes() 55 | { 56 | var current = type; 57 | 58 | while (current.Base != null) 59 | { 60 | yield return current.Base.Type.AsTypeInfo(); 61 | 62 | current = current.Base.Type; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Reflection/PsiAttributeInfoAdapter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using JetBrains.ReSharper.Psi; 4 | 5 | namespace Machine.Specifications.Runner.ReSharper.Reflection; 6 | 7 | public class PsiAttributeInfoAdapter(IAttributeInstance attribute) : IAttributeInfo 8 | { 9 | public IEnumerable GetParameters() 10 | { 11 | var parameters = attribute.PositionParameters() 12 | .Where(x => !x.IsBadValue) 13 | .ToArray(); 14 | 15 | var typeItems = parameters 16 | .Where(x => x.IsType) 17 | .Select(x => x.TypeValue) 18 | .OfType() 19 | .Where(x => x.IsValid()) 20 | .Select(x => x.GetClrName().ShortName); 21 | 22 | var arrayItems = parameters 23 | .Where(x => x.IsArray) 24 | .SelectMany(x => x.ArrayValue); 25 | 26 | var stringItems = parameters 27 | .Where(x => x.IsConstant) 28 | .Concat(arrayItems) 29 | .Select(x => x.ConstantValue.AsString()) 30 | .Where(x => x != null) 31 | .Select(x => x!); 32 | 33 | return typeItems.Concat(stringItems); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Reflection/PsiExtensions.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.ReSharper.Psi; 2 | 3 | namespace Machine.Specifications.Runner.ReSharper.Reflection; 4 | 5 | public static class PsiExtensions 6 | { 7 | public static ITypeInfo AsTypeInfo(this ITypeElement type, IDeclaredType? declaredType = null) 8 | { 9 | return new PsiTypeInfoAdapter(type, declaredType); 10 | } 11 | 12 | public static IAttributeInfo AsAttributeInfo(this IAttributeInstance attribute) 13 | { 14 | return new PsiAttributeInfoAdapter(attribute); 15 | } 16 | 17 | public static IFieldInfo AsFieldInfo(this IField field) 18 | { 19 | return new PsiFieldInfoAdapter(field); 20 | } 21 | 22 | public static bool IsContext(this IDeclaredElement element) 23 | { 24 | return element is IClass type && type.AsTypeInfo().IsContext(); 25 | } 26 | 27 | public static bool IsSpecification(this IDeclaredElement element) 28 | { 29 | return element is IField field && field.AsFieldInfo().IsSpecification(); 30 | } 31 | 32 | public static bool IsBehavior(this IDeclaredElement element) 33 | { 34 | return element is IField field && field.AsFieldInfo().IsBehavior(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Reflection/PsiFieldInfoAdapter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using JetBrains.Metadata.Reader.Impl; 4 | using JetBrains.ReSharper.Psi; 5 | 6 | namespace Machine.Specifications.Runner.ReSharper.Reflection; 7 | 8 | public class PsiFieldInfoAdapter(IField field) : IFieldInfo 9 | { 10 | public string DeclaringType => field.ContainingType!.GetClrName().FullName; 11 | 12 | public string ShortName => field.ShortName; 13 | 14 | public ITypeInfo FieldType 15 | { 16 | get 17 | { 18 | if (field.Type is IDeclaredType {IsResolved: true} type) 19 | { 20 | return type.GetTypeElement()!.AsTypeInfo(type); 21 | } 22 | 23 | return UnknownTypeInfoAdapter.Default; 24 | } 25 | } 26 | 27 | public IEnumerable GetCustomAttributes(string typeName, bool inherit) 28 | { 29 | return field.GetAttributeInstances(new ClrTypeName(typeName), inherit) 30 | .Select(x => x.AsAttributeInfo()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Reflection/PsiTypeInfoAdapter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using JetBrains.Metadata.Reader.Impl; 4 | using JetBrains.ReSharper.Psi; 5 | 6 | namespace Machine.Specifications.Runner.ReSharper.Reflection; 7 | 8 | public class PsiTypeInfoAdapter(ITypeElement type, IDeclaredType? declaredType = null) : ITypeInfo 9 | { 10 | public string FullyQualifiedName => type.GetClrName().FullName; 11 | 12 | public bool IsAbstract => type is IModifiersOwner {IsAbstract: true}; 13 | 14 | public ITypeInfo? GetContainingType() 15 | { 16 | return type.GetContainingType()?.AsTypeInfo(); 17 | } 18 | 19 | public IEnumerable GetFields() 20 | { 21 | if (type is not IClass classType) 22 | { 23 | return Enumerable.Empty(); 24 | } 25 | 26 | return classType.Fields 27 | .Where(x => !x.IsStatic) 28 | .Select(x => x.AsFieldInfo()); 29 | } 30 | 31 | public IEnumerable GetCustomAttributes(string typeName, bool inherit) 32 | { 33 | return type.GetAttributeInstances(new ClrTypeName(typeName), inherit) 34 | .Select(x => x.AsAttributeInfo()); 35 | } 36 | 37 | public IEnumerable GetGenericArguments() 38 | { 39 | if (declaredType != null) 40 | { 41 | var substitution = declaredType.GetSubstitution(); 42 | 43 | return substitution.Domain 44 | .Select(x => substitution.Apply(x).GetScalarType()) 45 | .Where(x => x != null) 46 | .Select(x => x?.GetTypeElement()) 47 | .OfType() 48 | .Select(x => x.AsTypeInfo()); 49 | } 50 | 51 | if (type is ITypeParametersOwner owner) 52 | { 53 | return owner.TypeParameters.Select(x => x.AsTypeInfo()); 54 | } 55 | 56 | return Enumerable.Empty(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Reflection/ReflectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using JetBrains.Util; 4 | using Machine.Specifications.Runner.Utility; 5 | 6 | namespace Machine.Specifications.Runner.ReSharper.Reflection; 7 | 8 | public static class ReflectionExtensions 9 | { 10 | public static IEnumerable GetTags(this ITypeInfo type) 11 | { 12 | return type.GetCustomAttributes(FullNames.TagsAttribute, false) 13 | .SelectMany(x => x.GetParameters()) 14 | .Distinct(); 15 | } 16 | 17 | public static string GetSubject(this ITypeInfo type) 18 | { 19 | var attributes = type.GetCustomAttributes(FullNames.SubjectAttribute, true) 20 | .ToArray(); 21 | 22 | if (!attributes.Any()) 23 | { 24 | return type.GetContainingType()?.GetSubject() ?? string.Empty; 25 | } 26 | 27 | return attributes.First() 28 | .GetParameters() 29 | .Join(" "); 30 | } 31 | 32 | public static bool IsSpecification(this IFieldInfo field) 33 | { 34 | return field.FieldType.GetCustomAttributes(FullNames.AssertDelegateAttribute, false).Any(); 35 | } 36 | 37 | public static bool IsBehavior(this IFieldInfo field) 38 | { 39 | var type = field.FieldType; 40 | var behaviorType = type.GetGenericArguments().FirstOrDefault(); 41 | 42 | return type.GetCustomAttributes(FullNames.BehaviorDelegateAttribute, false).Any() && 43 | behaviorType?.GetFields().Any(x => x.IsSpecification()) == true; 44 | } 45 | 46 | public static string? GetIgnoreReason(this ITypeInfo type) 47 | { 48 | return type.GetCustomAttributes(FullNames.IgnoreAttribute, false) 49 | .SelectMany(x => x.GetParameters()) 50 | .FirstOrDefault(); 51 | } 52 | 53 | public static string? GetIgnoreReason(this IFieldInfo field) 54 | { 55 | return field.GetCustomAttributes(FullNames.IgnoreAttribute, false) 56 | .SelectMany(x => x.GetParameters()) 57 | .FirstOrDefault(); 58 | } 59 | 60 | public static bool IsBehaviorContainer(this ITypeInfo type) 61 | { 62 | return !type.IsAbstract && 63 | type.GetCustomAttributes(FullNames.BehaviorsAttribute, false).Any() && 64 | type.GetFields().Any(x => x.IsSpecification()); 65 | } 66 | 67 | public static bool IsContext(this ITypeInfo type) 68 | { 69 | return !type.IsAbstract && 70 | !type.GetCustomAttributes(FullNames.BehaviorsAttribute, false).Any() && 71 | !type.GetGenericArguments().Any() && 72 | type.GetFields().Any(x => x.IsSpecification() || x.IsBehavior()); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Reflection/UnknownTypeInfoAdapter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Machine.Specifications.Runner.ReSharper.Reflection; 4 | 5 | public class UnknownTypeInfoAdapter : ITypeInfo 6 | { 7 | public static readonly UnknownTypeInfoAdapter Default = new(); 8 | 9 | public string FullyQualifiedName => string.Empty; 10 | 11 | public bool IsAbstract => false; 12 | 13 | public ITypeInfo? GetContainingType() 14 | { 15 | return null; 16 | } 17 | 18 | public IEnumerable GetFields() 19 | { 20 | return []; 21 | } 22 | 23 | public IEnumerable GetCustomAttributes(string typeName, bool inherit) 24 | { 25 | return []; 26 | } 27 | 28 | public IEnumerable GetGenericArguments() 29 | { 30 | return []; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Resources/Machine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machine/machine.specifications.runner.resharper/f7e86ca30bcc244ab85eaebcf4f94741fc253a2d/src/Machine.Specifications.Runner.ReSharper/Resources/Machine.png -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Resources/MspecThemedIcons.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Application.Icons.CompiledIconsCs; 2 | using JetBrains.UI.Icons; 3 | using JetBrains.Util.Icons; 4 | 5 | namespace Machine.Specifications.Runner.ReSharper.Resources; 6 | 7 | public static class MspecThemedIcons 8 | { 9 | [CompiledIconCs] 10 | public sealed class Mspec : CompiledIconCsClass 11 | { 12 | public static IconId Id = new CompiledIconCsId(typeof(Mspec)); 13 | 14 | public TiImage Load() 15 | { 16 | return TiImageConverter.FromTiSvg( 17 | ""); 66 | } 67 | 68 | public override CompiledIconCsIdOwner.ThemedIconThemeImage[] GetThemeImages() 69 | { 70 | return 71 | [ 72 | new("Default", Load) 73 | ]; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Rules/EnsureAncestorsAddedToExecutedElementsRule.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using JetBrains.ReSharper.UnitTestFramework.Criteria; 4 | using JetBrains.ReSharper.UnitTestFramework.Elements; 5 | using JetBrains.ReSharper.UnitTestFramework.Execution.Hosting; 6 | using JetBrains.ReSharper.UnitTestFramework.Execution.Launch.Rules; 7 | using JetBrains.ReSharper.UnitTestFramework.Session; 8 | using JetBrains.Util; 9 | using Machine.Specifications.Runner.ReSharper.Elements; 10 | 11 | namespace Machine.Specifications.Runner.ReSharper.Rules; 12 | 13 | [UnitTestElementsTransformationRule(Priority = 90)] 14 | public class EnsureAncestorsAddedToExecutedElementsRule : IUnitTestElementsTransformationRule 15 | { 16 | public IUnitTestElementCriterion Apply( 17 | IUnitTestElementCriterion criterion, 18 | IUnitTestSession session, 19 | IHostProvider hostProvider) 20 | { 21 | return criterion; 22 | } 23 | 24 | public void Apply(ISet elements, IUnitTestSession session, IHostProvider hostProvider) 25 | { 26 | var ancestors = new HashSet(); 27 | 28 | foreach (var element in elements.OfType()) 29 | { 30 | var parent = element.Parent; 31 | 32 | while (parent != null && ancestors.Add(parent)) 33 | { 34 | parent = parent.Parent; 35 | } 36 | } 37 | 38 | elements.AddRange(ancestors); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Runner/AgentManagerHost.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Application.Parts; 2 | using JetBrains.ProjectModel; 3 | using JetBrains.ReSharper.UnitTestFramework.Execution.TestRunner; 4 | 5 | namespace Machine.Specifications.Runner.ReSharper.Runner; 6 | 7 | [SolutionComponent(Instantiation.ContainerAsyncPrimaryThread)] 8 | public class AgentManagerHost(ITestRunnerAgentManager testRunnerAgentManager) : IAgentManagerHost 9 | { 10 | public ITestRunnerAgentManager AgentManager { get; } = testRunnerAgentManager; 11 | } 12 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Runner/IAgentManagerHost.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.ReSharper.UnitTestFramework.Execution.TestRunner; 2 | 3 | namespace Machine.Specifications.Runner.ReSharper.Runner; 4 | 5 | public interface IAgentManagerHost 6 | { 7 | ITestRunnerAgentManager AgentManager { get; } 8 | } 9 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/Runner/MspecTestRunnerRunStrategy.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Application.Parts; 2 | using JetBrains.ProjectModel; 3 | using JetBrains.ReSharper.UnitTestFramework.Execution.TestRunner; 4 | using JetBrains.ReSharper.UnitTestFramework.Execution.TestRunner.DataCollection; 5 | using JetBrains.ReSharper.UnitTestFramework.Exploration.Artifacts; 6 | 7 | namespace Machine.Specifications.Runner.ReSharper.Runner; 8 | 9 | [SolutionComponent(Instantiation.DemandAnyThreadUnsafe)] 10 | public class MspecTestRunnerRunStrategy( 11 | IDataCollectorFactory dataCollectorFactory, 12 | IAgentManagerHost agentManagerHost, 13 | MspecTestRunnerOrchestrator adapter, 14 | IUnitTestProjectArtifactResolver artifactResolver) 15 | : TestRunnerRunStrategy(dataCollectorFactory, agentManagerHost.AgentManager, adapter, artifactResolver); 16 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/TypeInfoFactory.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.ReSharper.TestRunner.Abstractions.Objects; 2 | 3 | namespace Machine.Specifications.Runner.ReSharper; 4 | 5 | public static class TypeInfoFactory 6 | { 7 | public static TypeInfo Create(string typeName, string assemblyLocation) 8 | { 9 | return new TypeInfo(typeName, assemblyLocation); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/UnitTestElementFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using JetBrains.Metadata.Reader.API; 4 | using JetBrains.ReSharper.UnitTestFramework.Elements; 5 | using Machine.Specifications.Runner.ReSharper.Elements; 6 | 7 | namespace Machine.Specifications.Runner.ReSharper; 8 | 9 | public class UnitTestElementFactory 10 | { 11 | private readonly JetHashSet elements = new(UnitTestElement.Comparer.ByNaturalId); 12 | 13 | public MspecContextTestElement GetOrCreateContext( 14 | IClrTypeName typeName, 15 | string? subject, 16 | string[]? tags, 17 | string? ignoreReason) 18 | { 19 | var context = new MspecContextTestElement(typeName, subject, ignoreReason); 20 | 21 | if (tags != null) 22 | { 23 | context.OwnCategories = tags.Select(x => new UnitTestElementCategory(x)).ToJetHashSet(); 24 | } 25 | 26 | return (MspecContextTestElement) elements.Intern(context); 27 | } 28 | 29 | public MspecSpecificationTestElement GetOrCreateSpecification( 30 | MspecContextTestElement context, 31 | string fieldName, 32 | string? behaviorType, 33 | string? ignoreReason) 34 | { 35 | var specification = new MspecSpecificationTestElement(context, fieldName, behaviorType, null, ignoreReason); 36 | 37 | return (MspecSpecificationTestElement) elements.Intern(specification); 38 | } 39 | 40 | public MspecBehaviorSpecificationTestElement GetOrCreateBehaviorSpecification( 41 | MspecSpecificationTestElement parent, 42 | string fieldName, 43 | string? ignoreReason) 44 | { 45 | var specification = new MspecBehaviorSpecificationTestElement(parent, fieldName, ignoreReason ?? parent.IgnoreReason); 46 | 47 | return (MspecBehaviorSpecificationTestElement) elements.Intern(specification); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Machine.Specifications.Runner.ReSharper/ZoneMarker.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Application.BuildScript.Application.Zones; 2 | using JetBrains.ReSharper.UnitTestFramework; 3 | 4 | namespace Machine.Specifications.Runner.ReSharper; 5 | 6 | [ZoneMarker] 7 | public class ZoneMarker : IRequire 8 | { 9 | } 10 | --------------------------------------------------------------------------------