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

(#8) Add Linux support #25

Merged
merged 8 commits into from
Mar 16, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,8 @@

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="Microsoft.Reactive.Testing" Version="6.0.0" />
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="xunit" Version="2.6.4"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ProcessDoctor.TestFramework\ProcessDoctor.TestFramework.csproj" />
</ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion ProcessDoctor.Backend.Core/Interfaces/IProcessProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace ProcessDoctor.Backend.Core.Interfaces;

public interface IProcessProvider
{
IObservable<SystemProcess> ObserveProcesses(ObservationTarget targetState);
IObservable<SystemProcess> ObserveProcesses(ObservationTarget observationTarget);

IProcessListSnapshot CreateSnapshot();
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<PackageReference Include="JetBrains.Lifetimes" Version="2023.3.3" />
<PackageReference Include="SkiaSharp" Version="2.88.7" />
<PackageReference Include="System.Reactive" Version="6.0.0" />
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
</ItemGroup>

</Project>
37 changes: 37 additions & 0 deletions ProcessDoctor.Backend.Linux.Tests/Fakes/FakeProcessEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using ProcessDoctor.Backend.Linux.Proc.Interfaces;

namespace ProcessDoctor.Backend.Linux.Tests.Fakes;

public sealed class FakeProcessEntry : IProcessEntry
{
public static FakeProcessEntry Create(
uint id,
string? commandLine = null,
string? executablePath = null,
IProcessStatus? status = null)
=> new(
id,
commandLine,
executablePath,
status ?? FakeProcessStatus.Create());

public uint Id { get; }

public string? CommandLine { get; }

public string? ExecutablePath { get; }

public IProcessStatus Status { get; }

public FakeProcessEntry(
uint id,
string? commandLine,
string? executablePath,
IProcessStatus status)
{
Id = id;
CommandLine = commandLine;
ExecutablePath = executablePath;
Status = status;
}
}
32 changes: 32 additions & 0 deletions ProcessDoctor.Backend.Linux.Tests/Fakes/FakeProcessStatus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using ProcessDoctor.Backend.Linux.Proc.Interfaces;
using ProcessDoctor.Backend.Linux.Proc.StatusFile.Enums;

namespace ProcessDoctor.Backend.Linux.Tests.Fakes;

public sealed class FakeProcessStatus : IProcessStatus
{
public static FakeProcessStatus Create(
string? name = null,
uint? parentId = null,
ProcessState? state = null)
=> new(
name ?? "ProcessDoctor",
parentId,
state ?? ProcessState.Running);

public string Name { get; }

public uint? ParentId { get; }

public ProcessState State { get; }

private FakeProcessStatus(
string name,
uint? parentId,
ProcessState state)
{
Name = name;
ParentId = parentId;
State = state;
}
}
13 changes: 13 additions & 0 deletions ProcessDoctor.Backend.Linux.Tests/Fixtures/ProcFolderFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.IO.Abstractions.TestingHelpers;
using JetBrains.Annotations;

namespace ProcessDoctor.Backend.Linux.Tests.Fixtures;

[UsedImplicitly]
public sealed class ProcFolderFixture
{
public MockFileSystem FileSystem { get; } = new();

public ProcessFixture CreateProcess(uint id)
=> new(FileSystem, id);
}
40 changes: 40 additions & 0 deletions ProcessDoctor.Backend.Linux.Tests/Fixtures/ProcessFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.IO.Abstractions;
using System.IO.Abstractions.TestingHelpers;
using ProcessDoctor.Backend.Linux.Proc;

namespace ProcessDoctor.Backend.Linux.Tests.Fixtures;

public sealed class ProcessFixture
{
public IDirectoryInfo Directory { get; }

public IFileInfo CommandLineFile { get; }

public IFileInfo ExecutablePathFile { get; }

public IFileInfo StatusFile { get; }

public ProcessFixture(MockFileSystem fileSystem, uint id)
{
var directoryPath = fileSystem.Path.Combine(ProcPaths.Path, id.ToString());
var processDirectory = fileSystem.DirectoryInfo.New(directoryPath);
fileSystem.AddDirectory(directoryPath);

var exePath = fileSystem.Path.Combine(processDirectory.FullName, ProcPaths.ExecutablePath.FileName);
var exeFile = fileSystem.FileInfo.New(exePath);
fileSystem.AddEmptyFile(exeFile);

var commandLinePath = fileSystem.Path.Combine(processDirectory.FullName, ProcPaths.CommandLine.FileName);
var commandLineFile = fileSystem.FileInfo.New(commandLinePath);
fileSystem.AddEmptyFile(commandLineFile);

var statusPath = fileSystem.Path.Combine(processDirectory.FullName, ProcPaths.Status.FileName);
var statusFile = fileSystem.FileInfo.New(statusPath);
fileSystem.AddEmptyFile(statusFile);

Directory = processDirectory;
CommandLineFile = commandLineFile;
ExecutablePathFile = exeFile;
StatusFile = statusFile;
}
}
1 change: 1 addition & 0 deletions ProcessDoctor.Backend.Linux.Tests/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using Xunit;
46 changes: 46 additions & 0 deletions ProcessDoctor.Backend.Linux.Tests/LinuxProcessTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using FluentAssertions;
using ProcessDoctor.Backend.Linux.Tests.Fakes;

namespace ProcessDoctor.Backend.Linux.Tests;

public sealed class LinuxProcessTests
{
[SkippableTheory]
[InlineData("/usr/bin/htop")]
public void Should_extract_icon(string executablePath)
{
Skip.IfNot(OperatingSystem.IsLinux());

// Arrange
var processEntry = FakeProcessEntry.Create(id: 123u, executablePath: executablePath);
var sut = LinuxProcess.Create(processEntry);

// Act
using var bitmap = sut.ExtractIcon();

// Assert
bitmap
.Bytes
.Should()
.NotBeEmpty();
}

[SkippableFact]
public void Should_extract_stock_icon()
{
Skip.IfNot(OperatingSystem.IsLinux());

// Arrange
var processEntry = FakeProcessEntry.Create(id: 123u);
var sut = LinuxProcess.Create(processEntry);

// Act
using var bitmap = sut.ExtractIcon();

// Assert
bitmap
.Bytes
.Should()
.NotBeEmpty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System.IO.Abstractions.TestingHelpers;
using FluentAssertions;
using ProcessDoctor.Backend.Linux.Proc;
using ProcessDoctor.Backend.Linux.Proc.Extensions;

namespace ProcessDoctor.Backend.Linux.Tests.ProcTests;

public sealed class DirectoryInfoExtensionsTests
{
[Theory]
[InlineData("124152")]
[InlineData("245")]
[InlineData("245612")]
[InlineData("67")]
public void Should_return_true_if_directory_is_process(string expectedProcessId)
{
// Arrange
var fileSystem = new MockFileSystem();
var path = fileSystem.Path.Combine(ProcPaths.Path, expectedProcessId);
var sut = fileSystem.DirectoryInfo.New(path);

// Act & Assert
sut.IsProcess()
.Should()
.BeTrue();
}

[Theory]
[InlineData("-1")]
[InlineData("-523")]
[InlineData("status")]
[InlineData("cmdline")]
[InlineData("123exe")]
public void Should_return_false_if_directory_is_not_process(string expectedProcessId)
{
// Arrange
var fileSystem = new MockFileSystem();
var path = fileSystem.Path.Combine(ProcPaths.Path, expectedProcessId);
var sut = fileSystem.DirectoryInfo.New(path);

// Act & Assert
sut.IsProcess()
.Should()
.BeFalse();
}
}
102 changes: 102 additions & 0 deletions ProcessDoctor.Backend.Linux.Tests/ProcTests/ProcessEntryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using FluentAssertions;
using ProcessDoctor.Backend.Linux.Proc;
using ProcessDoctor.Backend.Linux.Proc.Exceptions;
using ProcessDoctor.Backend.Linux.Proc.StatusFile;
using ProcessDoctor.Backend.Linux.Tests.Fixtures;

namespace ProcessDoctor.Backend.Linux.Tests.ProcTests;

public sealed class ProcessEntryTests(ProcFolderFixture procFolderFixture) : IClassFixture<ProcFolderFixture>
{
[Theory]
[InlineData("dir")]
[InlineData("123dir")]
[InlineData("dir123")]
[InlineData("-123")]
public void Should_throw_exception_if_directory_is_not_process(string directoryName)
{
// Arrange
var processDirectory = procFolderFixture
.FileSystem
.DirectoryInfo
.New(directoryName);

// Act & Assert
this.Invoking(_ => ProcessEntry.Create(processDirectory))
.Should()
.Throw<InvalidProcessDirectoryException>();
}

[Theory]
[InlineData(123u)]
[InlineData(1234u)]
[InlineData(0u)]
public void Should_read_process_id_properly(uint expectedId)
{
// Arrange & Act
var process = procFolderFixture.CreateProcess(expectedId);
var sut = ProcessEntry.Create(process.Directory);

// Assert
sut.Id
.Should()
.Be(expectedId);
}

[Theory]
[InlineData("cmdline")]
[InlineData(@"C:\NET\ProcessDoctor\ProcessDoctor.exe")]
public void Should_read_process_command_line_properly(string expectedCommandLine)
{
// Arrange
var process = procFolderFixture.CreateProcess(123u);
using (var writer = process.CommandLineFile.CreateText())
writer.Write(expectedCommandLine);

// Act
var sut = ProcessEntry.Create(process.Directory);

// Assert
sut.CommandLine
.Should()
.Be(expectedCommandLine);
}

[Fact]
public void Command_line_should_be_null_if_file_is_empty()
{
// Arrange
var process = procFolderFixture.CreateProcess(123u);

// Act
var sut = ProcessEntry.Create(process.Directory);

// Assert
sut.CommandLine
.Should()
.BeNull();
}

[Fact]
public void Should_read_process_status_section_properly()
{
// Arrange & Act
var process = procFolderFixture.CreateProcess(123u);
using (var writer = process.StatusFile.CreateText())
writer.Write(
"""
Name: ProcessDoctor
...
...
""");

var sut = ProcessEntry.Create(process.Directory);

// Assert
sut.Status
.Should()
.NotBeNull()
.And
.BeOfType<ProcessStatus>();
}
}
Loading
Loading