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
6 changes: 4 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ jobs:

test:
uses: ./.github/workflows/test.yml
with:
steam-live-tests: true

pack:
name: Pack
Expand Down Expand Up @@ -51,10 +53,10 @@ jobs:
path: packages
- name: Push packages
run: dotnet nuget push "packages/*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json
- uses: dotnet/nbgv@v0.5.1
- uses: dotnet/nbgv@v0.5.2
id: nbgv
- name: Create GitHub release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v3
with:
name: v${{ steps.nbgv.outputs.SemVer2 }}
tag_name: v${{ steps.nbgv.outputs.SemVer2 }}
Expand Down
19 changes: 15 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,17 @@ name: Build & Test

on:
workflow_call:
inputs:
steam-live-tests:
description: 'Run live Steam-network tests (sets STEAM_LIVE_TESTS=1).'
type: boolean
default: false
workflow_dispatch:
inputs:
steam-live-tests:
description: 'Run live Steam-network tests (sets STEAM_LIVE_TESTS=1).'
type: boolean
default: false
push:
branches: [ develop ]
pull_request:
Expand All @@ -16,18 +26,19 @@ jobs:
matrix:
os: [windows-latest, ubuntu-latest]
runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: actions/setup-dotnet@v5
with:
dotnet-version: '10.0.x'

- name: Build & Test in Release Mode
run: dotnet test --configuration Release --report-github
# Ignore exit code 8 on Linux (zero tests are now allowed)
# Ignore exit code 8 on Linux (zero tests are now allowed)
# TODO: Remove this when linux is supported
env:
TESTINGPLATFORM_EXITCODE_IGNORE: ${{ matrix.os == 'ubuntu-latest' && '8' || '' }}
TESTINGPLATFORM_EXITCODE_IGNORE: ${{ matrix.os == 'ubuntu-latest' && '8' || '' }}
STEAM_LIVE_TESTS: ${{ inputs.steam-live-tests && '1' || '' }}
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
<Version>3.9.50</Version>
</PackageReference>
<PackageReference Include="SauceControl.InheritDoc" Version="2.0.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.201">
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.300">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<None Include="$(MSBuildThisFileDirectory)README.md" Pack="true" PackagePath="" />
Expand Down
2 changes: 1 addition & 1 deletion sampleApp/SampleApplication.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8" />
</ItemGroup>

<ItemGroup>
Expand Down
12 changes: 6 additions & 6 deletions src/AET.SteamAbstraction/AET.SteamAbstraction.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AnakinRaW.CommonUtilities.Registry" Version="13.0.18" />
<PackageReference Include="AnakinRaW.CommonUtilities" Version="13.0.18" />
<PackageReference Include="AnakinRaW.CommonUtilities.FileSystem" Version="13.0.18" />
<PackageReference Include="System.Text.Json" Version="10.0.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.5" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.5" />
<PackageReference Include="AnakinRaW.CommonUtilities.Registry" Version="13.0.23" />
<PackageReference Include="AnakinRaW.CommonUtilities" Version="13.0.23" />
<PackageReference Include="AnakinRaW.CommonUtilities.FileSystem" Version="13.0.23" />
<PackageReference Include="System.Text.Json" Version="10.0.8" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.8" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.8" />
<PackageReference Include="Microsoft.Bcl.HashCode" Version="6.0.0" Condition="$(TargetFramework) == 'netstandard2.0'" />
<PackageReference Include="Nullable" Version="1.3.1" Condition="$(TargetFramework) == 'netstandard2.0'">
<PrivateAssets>all</PrivateAssets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace PG.StarWarsGame.Infrastructure.Clients.Processes;

internal sealed class GameProcess : DisposableObject, IGameProcess
{
private volatile bool _closed;
private int _closedFlag;

private EventHandler? _closingHandler;

Expand All @@ -18,7 +18,7 @@ public event EventHandler? Closed
add
{
// Execute event right away if the process was already closed.
if (_closed)
if (Volatile.Read(ref _closedFlag) != 0)
value?.Invoke(this, EventArgs.Empty);
else
_closingHandler += value;
Expand All @@ -30,8 +30,8 @@ public event EventHandler? Closed

// To avoid complexity this property does not query the underlying process but just "trusts"
// on the OnClosed function to set the value when the process was terminated.
// This means this property has theoretically a race condition with the game's process.
public GameProcessState State => _closed ? GameProcessState.Closed : GameProcessState.Running;
// This means this property has theoretically a race condition with the game's process.
public GameProcessState State => Volatile.Read(ref _closedFlag) != 0 ? GameProcessState.Closed : GameProcessState.Running;

internal Process Process { get; }

Expand All @@ -51,10 +51,16 @@ public void Exit()
try
{
Process.Kill();
Process.WaitForExit();
}
catch (Exception e) when (e is InvalidOperationException or Win32Exception)
{
}

// Process.Exited is dispatched asynchronously on a ThreadPool work item, so
// WaitForExit can return before OnClosed has been invoked. Drive it synchronously
// here; OnClosed is idempotent via the Interlocked guard.
OnClosed(this, EventArgs.Empty);
}

public Task WaitForExitAsync(CancellationToken cancellationToken = default)
Expand All @@ -74,15 +80,13 @@ private void RegisterExitEvent(Process process)
Process.EnableRaisingEvents = true;
Process.Exited += OnClosed;
if (process.HasExited)
{
_closed = true;
Process.Exited -= OnClosed;
}
OnClosed(this, EventArgs.Empty);
}

private void OnClosed(object? sender, EventArgs e)
{
_closed = true;
if (Interlocked.Exchange(ref _closedFlag, 1) != 0)
return;
Process.Exited -= OnClosed;
_closingHandler?.Invoke(this, EventArgs.Empty);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,16 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AlamoEngineTools.Modinfo" Version="8.0.2" />
<PackageReference Include="AnakinRaW.CommonUtilities" Version="13.0.18" />
<PackageReference Include="AnakinRaW.CommonUtilities.FileSystem" Version="13.0.18" />
<PackageReference Include="AnakinRaW.CommonUtilities.Registry" Version="13.0.18" />
<PackageReference Include="AlamoEngineTools.Modinfo" Version="8.0.6" />
<PackageReference Include="AnakinRaW.CommonUtilities" Version="13.0.23" />
<PackageReference Include="AnakinRaW.CommonUtilities.FileSystem" Version="13.0.23" />
<PackageReference Include="AnakinRaW.CommonUtilities.Registry" Version="13.0.23" />
<PackageReference Include="semver" Version="3.0.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.12.4" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.5" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.8" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.8" />
<PackageReference Include="QuikGraph" Version="2.5.0" PrivateAssets="compile" />
<PackageReference Include="System.Text.Json" Version="10.0.5" />
<PackageReference Include="System.Text.Json" Version="10.0.8" />
<PackageReference Include="IsExternalInit" Version="1.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
12 changes: 6 additions & 6 deletions test/AET.SteamAbstraction.Test/AET.SteamAbstraction.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,23 @@

<ItemGroup>
<PackageReference Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
<PackageReference Include="GitHubActionsTestLogger" Version="3.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
<PackageReference Include="GitHubActionsTestLogger" Version="3.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.5.1" />
<PackageReference Include="xunit.v3.mtp-v2" Version="3.2.2" />
<PackageReference Include="Microsoft.Testing.Platform" Version="2.1.0" />
<PackageReference Include="Microsoft.Testing.Platform" Version="2.2.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="8.0.1">
<PackageReference Include="coverlet.collector" Version="10.0.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.5" />
<PackageReference Include="Testably.Abstractions.Testing" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8" />
<PackageReference Include="Testably.Abstractions.Testing" Version="6.4.0" />
<ProjectReference Include="..\..\src\AET.SteamAbstraction\AET.SteamAbstraction.csproj" />
<ProjectReference Include="..\AET.SteamAbstraction.Testing\AET.SteamAbstraction.Testing.csproj" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,16 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AnakinRaW.CommonUtilities.Testing" Version="13.0.18" />
<PackageReference Include="AnakinRaW.CommonUtilities.Testing" Version="13.0.23" />
<PackageReference Include="IsExternalInit" Version="1.0.3" Condition="$(TargetFramework) == 'netstandard2.0'">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.5" />
<PackageReference Include="Testably.Abstractions.Testing" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8" />
<PackageReference Include="Testably.Abstractions.Testing" Version="6.4.0" />
<ProjectReference Include="..\..\src\AET.SteamAbstraction\AET.SteamAbstraction.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="GitHubActionsTestLogger" Version="3.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
<PackageReference Include="Testably.Abstractions.Testing" Version="6.0.0" />
<PackageReference Include="GitHubActionsTestLogger" Version="3.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.5.1" />
<PackageReference Include="Testably.Abstractions.Testing" Version="6.4.0" />
<PackageReference Include="xunit.v3.mtp-v2" Version="3.2.2" />
<PackageReference Include="Microsoft.Testing.Platform" Version="2.1.0" />
<PackageReference Include="Microsoft.Testing.Platform" Version="2.2.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="8.0.1">
<PackageReference Include="coverlet.collector" Version="10.0.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,24 @@ private Process StartStableTestProcess()
return _testProcess;
}

// Long-running process that does not read stdin, so closing the redirected
// stdin handle (e.g. via Process.Dispose) cannot terminate it early. Used by
// tests that need a process which stays alive across a Dispose() call.
private Process StartUnattendedTestProcess()
{
var processStartInfo = new ProcessStartInfo
{
FileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "cmd.exe" : "/bin/sh",
Arguments = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? "/c ping -n 10 127.0.0.1 > nul"
: "-c \"sleep 10\"",
CreateNoWindow = true,
UseShellExecute = false,
};
_testProcess = Process.Start(processStartInfo)!;
return _testProcess;
}

public void Dispose()
{
try
Expand Down Expand Up @@ -296,7 +314,10 @@ public void Dispose_ClosesProcessAndCleansResources()
[Fact]
public async Task Dispose_CompletesWaitForExitAsyncSilently()
{
var process = StartStableTestProcess();
// Must not use StartStableTestProcess here: `cmd /c pause` reads from redirected
// stdin and exits on EOF, so disposing the Process (which closes the pipe) would
// race the assertion.
var process = StartUnattendedTestProcess();
var processInfo = new GameProcessInfo(Game, GameBuildType.Release, ArgumentCollection.Empty);
var gameProcess = new GameProcess(process, processInfo);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ namespace PG.StarWarsGame.Infrastructure.Test.GameServices;

public class SteamWorkshopWebpageDownloaderTest
{
[Fact]
[Fact(
Skip = TestEnvironment.SteamLiveTestsSkipReason,
SkipUnless = nameof(TestEnvironment.SteamLiveTestsEnabled),
SkipType = typeof(TestEnvironment))]
public async Task GetSteamWorkshopsPageHtmlAsync()
{
var downloader = new SteamWorkshopWebpageDownloader();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
using AET.Modinfo.Model;
using AET.Modinfo.Spec;
using AET.Modinfo.Utilities;
using Microsoft.Extensions.DependencyInjection;
using PG.StarWarsGame.Infrastructure.Services.Name;
using PG.StarWarsGame.Infrastructure.Services.Steam;
using PG.StarWarsGame.Infrastructure.Testing.TestBases;
using Xunit;

Expand All @@ -25,6 +27,12 @@ public void Ctor_NullArgs_Throws()

public class OnlineModNameResolverTest : ModNameResolverTestBase
{
protected override void SetupServices(IServiceCollection serviceCollection)
{
base.SetupServices(serviceCollection);
serviceCollection.AddSingleton<ISteamWorkshopWebpageDownloader>(new FakeSteamWorkshopWebpageDownloader());
}

public override ModNameResolverBase CreateResolver()
{
return new OnlineModNameResolver(ServiceProvider);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Globalization;
using System.Threading.Tasks;
using HtmlAgilityPack;
using PG.StarWarsGame.Infrastructure.Services.Steam;

namespace PG.StarWarsGame.Infrastructure.Test.ModServices;

internal sealed class FakeSteamWorkshopWebpageDownloader : ISteamWorkshopWebpageDownloader
{
private const string FixtureNamespace = "PG.StarWarsGame.Infrastructure.Test.ModServices.Fixtures";

public Task<HtmlDocument> GetSteamWorkshopsPageHtmlAsync(ulong workshopId, CultureInfo? culture)
{
var doc = new HtmlDocument();
var resourceName = $"{FixtureNamespace}.workshop_{workshopId}.html";
var assembly = typeof(FakeSteamWorkshopWebpageDownloader).Assembly;
using var stream = assembly.GetManifestResourceStream(resourceName);
if (stream is null)
return Task.FromResult(doc);
doc.Load(stream);
return Task.FromResult(doc);
}
}
Loading
Loading