Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewritten the Native library loader to be more flexible and testable. #25

Merged
merged 10 commits into from
Dec 22, 2022
2 changes: 1 addition & 1 deletion Demos/Sharpie.Demos.Bounce/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
[assembly: ExcludeFromCodeCoverage]

// Create the main terminal instance.
using var terminal = new Terminal(NativeCursesProvider.Instance, new(CaretMode: CaretMode.Invisible));
using var terminal = new Terminal(CursesBackend.NCurses(), new(CaretMode: CaretMode.Invisible));

// Prepare styles
var styles = Enum.GetValues<StandardColor>()
Expand Down
4 changes: 3 additions & 1 deletion Demos/Sharpie.Demos.Events/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE

[assembly: ExcludeFromCodeCoverage]



// Create the terminal instance without any non-standard settings.
using var terminal = new Terminal(NativeCursesProvider.Instance, new());
using var terminal = new Terminal(CursesBackend.NCurses(), new());

// Set the main screen attributes for text and drawings.
terminal.Screen.ColorMixture = terminal.Colors.MixColors(StandardColor.Green, StandardColor.Blue);
Expand Down
2 changes: 1 addition & 1 deletion Demos/Sharpie.Demos.Font/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
[assembly: ExcludeFromCodeCoverage]

// Create a new terminal instance with an invisible cursor.
using var terminal = new Terminal(NativeCursesProvider.Instance, new(CaretMode: CaretMode.Invisible));
using var terminal = new Terminal(CursesBackend.NCurses(), new(CaretMode: CaretMode.Invisible));

// Setup the message and a number of rotating styles that will be applied for each letter of the message.
var message = "\x001 Let the ASCII fun begin! \x003";
Expand Down
2 changes: 1 addition & 1 deletion Demos/Sharpie.Demos.Slk/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
[assembly: ExcludeFromCodeCoverage]

// Create the main terminal instance and enable 4 * 4 SLK mode,
using var terminal = new Terminal(NativeCursesProvider.Instance,
using var terminal = new Terminal(CursesBackend.NCurses(),
new(CaretMode: CaretMode.Invisible, UseMouse: true, SoftLabelKeyMode: SoftLabelKeyMode.FourFour,
AllocateHeader: true));

Expand Down
2 changes: 1 addition & 1 deletion Demos/Sharpie.Demos.Snake/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
[assembly: ExcludeFromCodeCoverage]

// Create the main terminal instance.
using var terminal = new Terminal(NativeCursesProvider.Instance,
using var terminal = new Terminal(CursesBackend.NCurses(),
new(CaretMode: CaretMode.Invisible, UseMouse: false, AllocateHeader: true));

// Configure the header.
Expand Down
2 changes: 1 addition & 1 deletion Demos/Sharpie.Demos.Windows/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE

[assembly: ExcludeFromCodeCoverage]
using var terminal =
new Terminal(NativeCursesProvider.Instance, new(CaretMode: CaretMode.Visible, ManagedWindows: true));
new Terminal(CursesBackend.NCurses(), new(CaretMode: CaretMode.Visible, ManagedWindows: true));

var rnd = new Random();

Expand Down
5 changes: 3 additions & 2 deletions Sharpie.Tests/CanvasTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,13 @@ public void DrawOnto_DrawsTheAdjustedArea()
_canvas2X2.Glyph(new(1, 0), new('B'), _style1);
_canvas2X2.Glyph(new(0, 1), new('C'), _style1);
_canvas2X2.Glyph(new(1, 1), new('D'), _style1);

_canvas2X2.DrawOnto(_drawSurfaceMock.Object, new(0, 0, 2, 2), new(9, 8));

_drawSurfaceMock.Verify(v => v.DrawCell(new(9, 8), new('A'), _style1), Times.Once);
_drawSurfaceMock.Verify(v => v.DrawCell(new(9, 9), new('C'), _style1), Times.Once);
_drawSurfaceMock.Verify(v => v.DrawCell(It.IsAny<Point>(), It.IsAny<Rune>(), It.IsAny<Style>()), Times.Exactly(2));
_drawSurfaceMock.Verify(v => v.DrawCell(It.IsAny<Point>(), It.IsAny<Rune>(), It.IsAny<Style>()),
Times.Exactly(2));
}

[TestMethod]
Expand Down
2 changes: 1 addition & 1 deletion Sharpie.Tests/ColorManagerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ namespace Sharpie.Tests;
public class ColorManagerTests
{
private ColorManager _colorManager = null!;
private Mock<ICursesProvider> _cursesMock = null!;
private Mock<ICursesBackend> _cursesMock = null!;
private Terminal _terminal = null!;

[TestInitialize]
Expand Down
149 changes: 149 additions & 0 deletions Sharpie.Tests/CursesBackendTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
Copyright (c) 2022, Alexandru Ciobanu
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

namespace Sharpie.Tests;

using System.Reflection;
using System.Runtime.InteropServices;

[TestClass]
public class CursesBackendTests
{
private Mock<IDotNetSystemAdapter> _dotNetSystemAdapterMock = null!;

private void MockLoadResult(string lib, bool result)
{
_dotNetSystemAdapterMock.Setup(s => s.TryLoadNativeLibrary(lib, It.IsAny<Assembly>(),
It.IsAny<DllImportSearchPath?>(), out It.Ref<IntPtr>.IsAny))
.Returns((string _, Assembly _, DllImportSearchPath? _, out IntPtr handle) =>
{
handle = new(100);
return result;
});
}

[TestInitialize]
public void TestInitialize()
{
_dotNetSystemAdapterMock = new();

_dotNetSystemAdapterMock
.Setup(s => s.TryGetNativeLibraryExport(It.IsAny<IntPtr>(), It.IsAny<string>(), out It.Ref<IntPtr>.IsAny))
.Returns(true);
}

[TestMethod]
public void NCurses1_Throws_IfFailedToLoadNCurses()
{
Should.Throw<CursesInitializationException>(() =>
CursesBackend.NCurses(_dotNetSystemAdapterMock.Object, s => new[] { s }));
}

[TestMethod]
public void NCurses1_Throws_IfFailedToLoadLibC()
{
_dotNetSystemAdapterMock.Setup(s => s.IsUnixLike)
.Returns(true);

MockLoadResult("ncurses", true);

Should.Throw<CursesInitializationException>(() =>
CursesBackend.NCurses(_dotNetSystemAdapterMock.Object, s => new[] { s }));
}

[TestMethod]
public void NCurses1_ForUnixLikeSystems_LoadsNCursesAndLibC()
{
_dotNetSystemAdapterMock.Setup(s => s.IsUnixLike)
.Returns(true);

MockLoadResult("ncurses", true);
MockLoadResult("libc", true);

var requests = new List<string>();

if (_dotNetSystemAdapterMock.Object.IsUnixLike)
{
CursesBackend.NCurses(_dotNetSystemAdapterMock.Object, s =>
{
requests.Add(s);
return new[] { s };
})
.ShouldBeOfType<UnixNCursesBackend>();
}

requests.ShouldBe(new[] { "ncurses", "libc" });
}

[TestMethod]
public void NCurses1_ForNonUnixLikeSystems_LoadsNCurses()
{
_dotNetSystemAdapterMock.Setup(s => s.IsUnixLike)
.Returns(false);

MockLoadResult("ncurses", true);

var requests = new List<string>();
CursesBackend.NCurses(_dotNetSystemAdapterMock.Object, s =>
{
requests.Add(s);
return new[] { s };
})
.ShouldBeOfType<NCursesBackend>();

requests.ShouldBe(new[] { "ncurses" });
}

[TestMethod]
public void NCurses2_Throws_LibPathResolverIsNull()
{
Should.Throw<ArgumentNullException>(() => CursesBackend.NCurses((Func<string, IEnumerable<string>>) null!));
}

[TestMethod]
public void NCurses2_CallsNCurses1()
{
_dotNetSystemAdapterMock.Setup(s => s.IsUnixLike)
.Returns(true);

MockLoadResult("ncurses", true);
MockLoadResult("libc", true);

CursesBackend.NCurses(_dotNetSystemAdapterMock.Object);

_dotNetSystemAdapterMock.Verify(
s => s.TryLoadNativeLibrary("ncurses", It.IsAny<Assembly>(), It.IsAny<DllImportSearchPath?>(),
out It.Ref<IntPtr>.IsAny), Times.Once);

_dotNetSystemAdapterMock.Verify(
s => s.TryLoadNativeLibrary("libc", It.IsAny<Assembly>(), It.IsAny<DllImportSearchPath?>(),
out It.Ref<IntPtr>.IsAny), Times.Once);
}
}
2 changes: 1 addition & 1 deletion Sharpie.Tests/CursesInitializationExceptionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,6 @@ public class CursesInitializationExceptionTests
public void HasTheCorrectMessage()
{
var ex = new CursesInitializationException();
ex.Message.ShouldBe("Failed to load or initialize the Curses library.");
ex.Message.ShouldBe("Failed to load or initialize the Curses backend.");
}
}
37 changes: 37 additions & 0 deletions Sharpie.Tests/DotNetSystemAdapterInterfaceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
Copyright (c) 2022, Alexandru Ciobanu
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

namespace Sharpie.Tests;

[TestClass]
public class DotNetSystemAdapterInterfaceTests
{
[TestMethod] public void Instance_IsNotNull() { IDotNetSystemAdapter.Instance.ShouldNotBeNull(); }
}
2 changes: 1 addition & 1 deletion Sharpie.Tests/EventPumpTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public class EventPumpTests
{
private const int Timeout = 10000;

private Mock<ICursesProvider> _cursesMock = null!;
private Mock<ICursesBackend> _cursesMock = null!;
private EventPump _pump = null!;
private CancellationTokenSource _source = null!;
private Terminal _terminal = null!;
Expand Down
44 changes: 1 addition & 43 deletions Sharpie.Tests/HelpersTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ namespace Sharpie.Tests;
[TestClass]
public class HelpersTests
{
private Mock<ICursesProvider> _cursesMock = null!;
private Mock<ICursesBackend> _cursesMock = null!;

[TestInitialize] public void TestInitialize() { _cursesMock = new(); }

Expand Down Expand Up @@ -324,48 +324,6 @@ public void ConvertMouseActionEvent_MapsModifiers(int evt, ModifierKey expMod)
result.modifierKey.ShouldBe(expMod);
}

[TestMethod]
public void ValidOrNull_Throws_IfCursesIsNull()
{
Should.Throw<ArgumentNullException>(() => Helpers.ValidOrNull(null!));
}

[TestMethod]
public void ValidOrNull_ReturnsNull_IfTermNameFailsWithDllNotFoundException()
{
_cursesMock.Setup(s => s.termname())
.Throws<DllNotFoundException>();

_cursesMock.Object.ValidOrNull()
.ShouldBeNull();
}

[TestMethod]
public void ValidOrNull_ReturnsNull_IfTermNameFailsWithEntryPointNotFoundException()
{
_cursesMock.Setup(s => s.termname())
.Throws<EntryPointNotFoundException>();

_cursesMock.Object.ValidOrNull()
.ShouldBeNull();
}

[TestMethod]
public void ValidOrNull_Throws_IfUnexpectedErrorOccurs()
{
_cursesMock.Setup(s => s.termname())
.Throws<ArgumentOutOfRangeException>();

Should.Throw<ArgumentOutOfRangeException>(() => _cursesMock.Object.ValidOrNull());
}

[TestMethod]
public void ValidOrNull_ReturnsCurses_IfTermNameDoesNotFail()
{
_cursesMock.Object.ValidOrNull()
.ShouldBe(_cursesMock.Object);
}

[TestMethod]
public void EnumerateInHalves_Throws_IfCountIsNegative()
{
Expand Down
Loading