Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto

# Backend test inputs and golds must be LF everywhere. Some golds record document offsets (navigation/Find Usages
# RANGE: (78,88)), which shift if a Windows checkout turns the inputs into CRLF. Same rule as JetBrains' own plugins.
src/dotnet/ReSharperPlugin.RimworldDev.Tests/test/data/** text eol=lf

# Preserve line endings in gradle scripts
gradlew* -text diff
19 changes: 1 addition & 18 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches:
- main
pull_request:
workflow_dispatch:

jobs:
Build:
Expand All @@ -31,21 +32,3 @@ jobs:
with:
name: ${{ github.event.repository.name }}.CI.${{ github.head_ref || github.ref_name }}
path: output
Test:
runs-on: ubuntu-latest
steps:
- uses: jlumbroso/free-disk-space@main
- uses: actions/checkout@v4
with:
submodules: recursive
- name: configure_java
uses: actions/setup-java@v4
with:
distribution: 'corretto'
java-version: '21'
cache: 'gradle'
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- run: ./gradlew :testDotNet --no-daemon
11 changes: 8 additions & 3 deletions .github/workflows/Deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@ on:

jobs:
Publish:
runs-on: ubuntu-latest
# Windows so that :publishPlugin's dependency on :testDotNet runs the real backend test suite before anything is
# published (the ReSharper test shell only runs on Windows; on Linux every test reports as skipped). Releases are
# rare, so the Windows runner cost is acceptable here, unlike on every PR commit (see Tests.yml).
runs-on: windows-latest
environment: Deploy
defaults:
run:
# ./gradlew and the output/* globs below are written for bash; Windows runners ship Git Bash.
shell: bash
steps:
- uses: jlumbroso/free-disk-space@main
- uses: actions/checkout@v4
with:
submodules: recursive
Expand All @@ -36,6 +42,5 @@ jobs:
- name: Upload binaries to release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
gh release upload ${{ github.ref_name }} output/*
44 changes: 44 additions & 0 deletions .github/workflows/Tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Tests

# Separate from CI.yml so that adding or removing the 'feature-testing' label re-runs only the tests, on the other OS,
# without touching the Build check.
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
workflow_dispatch:
inputs:
windows:
description: Run the full backend test suite on Windows
type: boolean
default: false

jobs:
Test:
# The backend tests boot the ReSharper test shell, which only runs on Windows (docs/testing-research.md, "Platform").
# Windows runners cost more, so by default this runs on Linux, where the suite builds and every test reports as
# skipped (WindowsOnlyGuard). The 'feature-testing' PR label (or the workflow_dispatch checkbox) runs it for real.
# Other label changes don't re-run anything.
if: >-
(github.event.action != 'labeled' && github.event.action != 'unlabeled') ||
github.event.label.name == 'feature-testing'
runs-on: ${{ (contains(github.event.pull_request.labels.*.name, 'feature-testing') || inputs.windows) && 'windows-latest' || 'ubuntu-latest' }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- name: Cache NuGet packages
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: nuget-${{ runner.os }}-${{ hashFiles('Directory.Build.props', 'src/dotnet/**/*.csproj', 'src/dotnet/**/*.props') }}
restore-keys: nuget-${{ runner.os }}-
# Same command as Gradle's :testDotNet, without needing Java/Gradle on this runner.
- run: dotnet test ReSharperPlugin.RimworldDev.sln --logger GitHubActions
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ _dotTrace*

# Nuget
packages/
**/NuGetLocks/*
# NuGetLocks are committed on purpose: the lock files pin test-framework package versions

# Example Mod
example-mod/.idea
5 changes: 4 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,10 @@ tasks.runIde {
// part of a plugin, but there are dangers about keeping plugins in sync
autoReload = false

val exampleModSolution = layout.projectDirectory.file("example-mod/AshAndDust.sln").asFile.absolutePath
// What the sandbox opens. Defaults to the checked-in example mod; the highlighting fixtures under example-mods/
// are opened with e.g. -PrunIdeSolution=example-mods/XmlOnlyMod (a folder is fine, it doesn't have to be a .sln).
val solutionToOpen = providers.gradleProperty("runIdeSolution").getOrElse("example-mod/AshAndDust.sln")
val exampleModSolution = layout.projectDirectory.file(solutionToOpen).asFile.absolutePath

argumentProviders += CommandLineArgumentProvider {
listOf(exampleModSolution)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using JetBrains.ReSharper.FeaturesTestFramework.Completion;
using JetBrains.ReSharper.TestFramework;
using NUnit.Framework;
using ReSharperPlugin.RimworldDev.Tests.CompletionSuggestions;
using ReSharperPlugin.RimworldDev.Tests.TestBases;

namespace ReSharperPlugin.RimworldDev.Tests.AcceptCompletion;

[ProjectLayouts(ProjectLayout.XmlProject, ProjectLayout.CSharpProject)]
[TestFileExtension(".xml")]
public class RimworldXmlTests(ProjectLayout layout) : RimworldCompletionTestBase(layout)
{
protected override CodeCompletionTestType TestType => CodeCompletionTestType.Action;
protected override string RelativeTestDataPath => @"AcceptCompletion\Rimworld";

[Test] public void TestCompleteTag() => DoNamedTest();
[Test] public void TestCompleteEnumValue() => DoNamedTest();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using JetBrains.ReSharper.FeaturesTestFramework.Completion;
using JetBrains.ReSharper.TestFramework;
using NUnit.Framework;
using ReSharperPlugin.RimworldDev.Tests.TestBases;

namespace ReSharperPlugin.RimworldDev.Tests.CompletionSuggestions;

[TestFileExtension(".cs")]
[ProjectLayouts(ProjectLayout.CSharpProject)]
public class RimworldCSharpCompletionTests(ProjectLayout layout) : RimworldCompletionTestBase(layout)
{
protected override CodeCompletionTestType TestType => CodeCompletionTestType.ModernList;
protected override string RelativeTestDataPath => @"CompletionSuggestions\RimworldCSharp";

[Test] public void TestDefOfFieldWithPrefixAndSemicolon() => DoNamedTest("Defs.xml");
[Test] public void TestDefOfFieldWithPrefix() => DoNamedTest("Defs.xml");
[Test] public void TestDefDatabaseGetNamed() => DoNamedTest("Defs.xml");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using JetBrains.ReSharper.FeaturesTestFramework.Completion;
using JetBrains.ReSharper.TestFramework;
using NUnit.Framework;
using ReSharperPlugin.RimworldDev.Tests.TestBases;

namespace ReSharperPlugin.RimworldDev.Tests.CompletionSuggestions;

[ProjectLayouts(ProjectLayout.CSharpProject)]
[TestFileExtension(".xml")]
public class RimworldXmlCompletionTests(ProjectLayout layout) : RimworldCompletionTestBase(layout)
{
protected override CodeCompletionTestType TestType => CodeCompletionTestType.ModernList;
protected override string RelativeTestDataPath => @"CompletionSuggestions\Rimworld";

// Test Property Names
[Test] public void TestDefName() => DoNamedTest();
[Test] public void TestThingDefProperties() => DoNamedTest();
[Test] public void TestModDefClassProperties() => DoNamedTest("ModTypes.cs");
[Test] public void TestNestedFieldProperties() => DoNamedTest();
[Test] public void TestListItemProperties() => DoNamedTest();
[Test] public void TestListItemWithClassProperties() => DoNamedTest();
[Test] public void TestModListItemClassProperties() => DoNamedTest("ModTypes.cs");

// Test Property Values
[Test] public void TestBooleanPropertyValue() => DoNamedTest();
[Test] public void TestEnumValue() => DoNamedTest();

[Test] public void TestStructPropertyValue() => DoNamedTest();

// Test Def references
[Test] public void TestDefReferenceSameFile() => DoNamedTest();
[Test] public void TestDefReferenceOtherFile() => DoNamedTest("OtherDefs.xml");

[Test]
public void TestDefsFilterByType() => DoNamedTest("OtherDefs.xml", "StuffCategoryDefs.xml");
[Test] public void TestParentName() => DoNamedTest();

// When a property expects a specific DefType (like ThingDef), modded classes that extend that should be offered
[Test] public void TestModDefAsSuperclassReference() => DoNamedTest("ModTypes.cs");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using JetBrains.ReSharper.TestFramework;
using NUnit.Framework;
using ReSharperPlugin.RimworldDev.Tests.References;
using ReSharperPlugin.RimworldDev.Tests.TestBases;

namespace ReSharperPlugin.RimworldDev.Tests.FindUsages;

/// <summary>
/// Find Usages (and the other navigation providers) started from a def, with usages spread across XML and C# files.
/// XMLTagDeclaredElement + RimworldSearcherFactory/CustomSearcher is the known rough edge; see "Find Usages.md".
/// The golds record *current* behaviour: usages in XML are found, usages in C# (the [DefOf] field and the
/// DefDatabase string in CSharpUsages.cs) are not, because RimworldSearcherFactory.IsCompatibleWithLanguage only
/// accepts XML. Allowing C# there makes both appear (verified), so when that's fixed these golds should gain them.
/// </summary>
[ProjectLayouts(ProjectLayout.CSharpProject)]
[TestFileExtension(".xml")]
public class RimworldFindUsagesFromXmlTests(ProjectLayout layout) : RimworldNavigationTestBase(layout)
{
protected override string RelativeTestDataPath => "FindUsages";

[Test] public void TestFromDefName() => DoNamedTest("OtherUsages.xml", "CSharpUsages.cs");
[Test] public void TestFromNameAttribute() => DoNamedTest();
}

[ProjectLayouts(ProjectLayout.CSharpProject)]
[TestFileExtension(".cs")]
public class RimworldFindUsagesFromCSharpTests(ProjectLayout layout) : RimworldNavigationTestBase(layout)
{
protected override string RelativeTestDataPath => "FindUsages";

[Test] public void TestFromCSharpString() => DoNamedTest("Defs.xml", "OtherUsages.xml");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using JetBrains.ReSharper.TestFramework;
using NUnit.Framework;
using ReSharperPlugin.RimworldDev.Tests.TestBases;

namespace ReSharperPlugin.RimworldDev.Tests.Generate;

/// <summary>
/// This tests the `Alt+Insert` Generation menu inside a Def.
/// </summary>
[ProjectLayouts(ProjectLayout.XmlProject, ProjectLayout.CSharpProject)]
[TestFileExtension(".xml")]
public class RimworldGenerateTests(ProjectLayout layout) : RimworldGenerateTestBase(layout)
{
protected override string RelativeTestDataPath => @"Generate";

[ProjectLayouts(ProjectLayout.XmlProject)]
[Test] public void TestGenerateProperties() => DoNamedTest();

[Test] public void TestGenerateInListItem() => DoNamedTest();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using JetBrains.ReSharper.TestFramework;
using NUnit.Framework;
using ReSharperPlugin.RimworldDev.Tests.TestBases;

namespace ReSharperPlugin.RimworldDev.Tests.Highlighting;

[ProjectLayouts(ProjectLayout.XmlProject, ProjectLayout.CSharpProject)]
[TestFileExtension(".xml")]
public class RimworldXmlHighlightingTests(ProjectLayout layout) : RimworldHighlightingTestBase(layout)
{
protected override string RelativeTestDataPath => @"Highlighting";

[Test] public void TestValidValues() => DoNamedTest();

/// <summary>
/// Float values are only reported when loaded up XmlProjects, they don't actually get matched properly in CSharp
/// projects
/// </summary>
[ProjectLayouts(ProjectLayout.XmlProject)]
[Test] public void TestInvalidValues() => DoNamedTest();
}

[ProjectLayouts(ProjectLayout.CSharpProject)]
[TestFileExtension(".xml")]
public class RimworldXmlHighlightingWithoutRimworldTests(ProjectLayout layout) : RimworldHighlightingTestBase(layout)
{
protected override string RelativeTestDataPath => @"Highlighting";
protected override bool ReferenceRimworld => false;

[Test] public void TestWithoutRimworld() => DoNamedTest();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System.Linq;
using JetBrains.Lifetimes;
using JetBrains.ProjectModel;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.Files;
using JetBrains.ReSharper.Psi.Resolve;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.ReSharper.Resources.Shell;
using JetBrains.ReSharper.TestFramework;
using NUnit.Framework;
using ReSharperPlugin.RimworldDev.Tests.TestBases;

namespace ReSharperPlugin.RimworldDev.Tests.Navigation;

/// <summary>
/// We previously had an issue around keeping stale references to ITreeNodes in our Symbol Cache, which meant that if
/// Rider attempted to access it from our Symbol Cache (for navigation for example) after the file had been edited but
/// before we'd rebuilt our cache then we'd get an error about trying to read an uncommited PSI Document. The fix for
/// that was to store data that allows us to look up the real ITreeNode, and then do that lookup on demand rather than
/// storing the ITreeNode itself.
///
/// These tests exist to act as a regression test against that behavior coming back.
/// </summary>
[ProjectLayouts(ProjectLayout.CSharpProject)]
[TestFileExtension(".xml")]
public class RimworldNavigationAfterEditTests(ProjectLayout layout) : RimworldNavigationTestBase(layout)
{
private const string DefsFile = "MovingDefs.xml";
private const string ThingALine = " <ThingDef><defName>ThingA</defName></ThingDef>\n";

protected override string RelativeTestDataPath => "Navigation";

[Test] public void TestNavigateAfterDefsMove() => DoNamedTest(DefsFile);

protected override void DoTest(Lifetime lifetime, IProject testProject)
{
var files = Solution.GetPsiServices().Files;
var document = testProject.GetAllProjectFiles().Single(file => file.Name == DefsFile).ToSourceFiles().Single()
.Document;

files.CommitAllDocuments();
using (ReadLockCookie.Create())
{
var psiFile = testProject.GetAllProjectFiles().Single(file => file.Name != DefsFile).ToSourceFiles()
.Single().GetPrimaryPsiFile()!;

foreach (var node in psiFile.Descendants().ToEnumerable())
foreach (var reference in node.GetReferences<IReference>())
reference.Resolve();
}

using (WriteLockCookie.Create())
document.InsertText(document.GetText().IndexOf(" -->"), ThingALine);

Check warning on line 53 in src/dotnet/ReSharperPlugin.RimworldDev.Tests/Navigation/RimworldNavigationAfterEdits.cs

View workflow job for this annotation

GitHub Actions / Test

'IDocument.InsertText(int, string)' is obsolete: 'Use JetBrains.DocumentModel.DocumentExtensions.InsertText'

Check warning on line 53 in src/dotnet/ReSharperPlugin.RimworldDev.Tests/Navigation/RimworldNavigationAfterEdits.cs

View workflow job for this annotation

GitHub Actions / Test

'IDocument.InsertText(int, string)' is obsolete: 'Use JetBrains.DocumentModel.DocumentExtensions.InsertText'
files.CommitAllDocuments();

base.DoTest(lifetime, testProject);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using JetBrains.Lifetimes;
using JetBrains.ProjectModel;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.Files;
using JetBrains.ReSharper.Psi.Resolve;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.ReSharper.Resources.Shell;
using JetBrains.ReSharper.TestFramework;
using NUnit.Framework;
using ReSharperPlugin.RimworldDev.Tests.TestBases;
using System.Linq;

namespace ReSharperPlugin.RimworldDev.Tests.Navigation;

/// <summary>
/// These tests essentially emulate us doing a Ctrl+Click in the IDE. The gold files should show what options were
/// presented to the IDE (not the user) from what sources. The distinction is that the same reference/result may be
/// presented to the IDE form multiple sources and that'll be deduplicated to a single option for the IDE.
/// </summary>
[ProjectLayouts(ProjectLayout.CSharpProject)]
[TestFileExtension(".xml")]
public class RimworldNavigationTests(ProjectLayout layout) : RimworldNavigationTestBase(layout)
{
protected override string RelativeTestDataPath => "Navigation";

[Test] public void TestNavigatePropertyToCSharpField() => DoNamedTest();
[Test] public void TestNavigateValueToXmlDef() => DoNamedTest();
[Test] public void TestNavigateValueToCSharpEnum() => DoNamedTest();
}


Loading
Loading