dotnet run always evaluates the Aspire graph. A second launch after Ctrl+C should not wait on that. Co-authored-by: Cursor <cursoragent@cursor.com>
158 lines
5.5 KiB
C#
158 lines
5.5 KiB
C#
namespace HSchool.AppHost.Tests;
|
|
|
|
public class LaunchBuildStampTests
|
|
{
|
|
private static readonly DateTime Inputs = new(2026, 1, 1, 12, 0, 0, DateTimeKind.Utc);
|
|
private static readonly DateTime Outputs = new(2026, 1, 1, 12, 5, 0, DateTimeKind.Utc);
|
|
private static readonly DateTime Newer = new(2026, 1, 1, 12, 10, 0, DateTimeKind.Utc);
|
|
|
|
[Fact]
|
|
public void MissingDll_NeedsBuild()
|
|
{
|
|
using var repo = FreshRepo();
|
|
File.Delete(repo.Dll("HSchool.Server"));
|
|
|
|
Assert.True(LaunchBuildStamp.NeedsBuild(repo.Root));
|
|
}
|
|
|
|
[Fact]
|
|
public void FreshDlls_SkipBuild()
|
|
{
|
|
using var repo = FreshRepo();
|
|
|
|
Assert.False(LaunchBuildStamp.NeedsBuild(repo.Root));
|
|
}
|
|
|
|
[Fact]
|
|
public void NewerCs_NeedsBuild()
|
|
{
|
|
using var repo = FreshRepo();
|
|
repo.Touch("src/HSchool.Server/Program.cs", Newer);
|
|
|
|
Assert.True(LaunchBuildStamp.NeedsBuild(repo.Root));
|
|
}
|
|
|
|
[Fact]
|
|
public void NewerTsOrJsonc_SkipBuild()
|
|
{
|
|
using var repo = FreshRepo();
|
|
repo.Write("src/HSchool.Client/src/main.ts", "export {}");
|
|
repo.Write("src/HSchool.Server/mods/core/defs/foo.jsonc", "{}");
|
|
repo.Touch("src/HSchool.Client/src/main.ts", Newer);
|
|
repo.Touch("src/HSchool.Server/mods/core/defs/foo.jsonc", Newer);
|
|
|
|
Assert.False(LaunchBuildStamp.NeedsBuild(repo.Root));
|
|
}
|
|
|
|
[Fact]
|
|
public void FilesInBinObjNodeModules_AreNotInputs()
|
|
{
|
|
using var repo = FreshRepo();
|
|
repo.Write("src/HSchool.Server/bin/Debug/net10.0/Generated.cs", "class G {}");
|
|
repo.Write("src/HSchool.Server/obj/Debug/net10.0/Generated.cs", "class G {}");
|
|
repo.Write("src/HSchool.Client/node_modules/pkg/index.cs", "class N {}");
|
|
repo.Touch("src/HSchool.Server/bin/Debug/net10.0/Generated.cs", Newer);
|
|
repo.Touch("src/HSchool.Server/obj/Debug/net10.0/Generated.cs", Newer);
|
|
repo.Touch("src/HSchool.Client/node_modules/pkg/index.cs", Newer);
|
|
|
|
Assert.False(LaunchBuildStamp.NeedsBuild(repo.Root));
|
|
}
|
|
|
|
[Fact]
|
|
public void NewerDirectoryPackagesProps_NeedsBuild()
|
|
{
|
|
using var repo = FreshRepo();
|
|
repo.Touch("Directory.Packages.props", Newer);
|
|
|
|
Assert.True(LaunchBuildStamp.NeedsBuild(repo.Root));
|
|
}
|
|
|
|
[Fact]
|
|
public void ReleaseLooksAtReleaseBin()
|
|
{
|
|
using var repo = FreshRepo();
|
|
|
|
Assert.False(LaunchBuildStamp.NeedsBuild(repo.Root, "Debug"));
|
|
Assert.True(LaunchBuildStamp.NeedsBuild(repo.Root, "Release"));
|
|
|
|
repo.WriteDll("HSchool.AppHost", "Release");
|
|
repo.WriteDll("HSchool.Server", "Release");
|
|
repo.TouchDlls("Release", Outputs);
|
|
|
|
Assert.False(LaunchBuildStamp.NeedsBuild(repo.Root, "Release"));
|
|
}
|
|
|
|
private static TempRepo FreshRepo()
|
|
{
|
|
var repo = new TempRepo();
|
|
repo.Write("global.json", "{}");
|
|
repo.Write("Directory.Build.props", "<Project />");
|
|
repo.Write("Directory.Packages.props", "<Project />");
|
|
repo.Write("src/HSchool.AppHost/AppHost.cs", "class A {}");
|
|
repo.Write("src/HSchool.AppHost/HSchool.AppHost.csproj", "<Project />");
|
|
repo.Write("src/HSchool.Server/Program.cs", "class S {}");
|
|
repo.Write("src/HSchool.Server/HSchool.Server.csproj", "<Project />");
|
|
repo.WriteDll("HSchool.AppHost");
|
|
repo.WriteDll("HSchool.Server");
|
|
repo.Touch("global.json", Inputs);
|
|
repo.Touch("Directory.Build.props", Inputs);
|
|
repo.Touch("Directory.Packages.props", Inputs);
|
|
repo.Touch("src/HSchool.AppHost/AppHost.cs", Inputs);
|
|
repo.Touch("src/HSchool.AppHost/HSchool.AppHost.csproj", Inputs);
|
|
repo.Touch("src/HSchool.Server/Program.cs", Inputs);
|
|
repo.Touch("src/HSchool.Server/HSchool.Server.csproj", Inputs);
|
|
repo.TouchDlls("Debug", Outputs);
|
|
return repo;
|
|
}
|
|
|
|
private sealed class TempRepo : IDisposable
|
|
{
|
|
public string Root { get; } = Path.Combine(Path.GetTempPath(), "h-school-stamp-" + Guid.NewGuid().ToString("N"));
|
|
|
|
public TempRepo() => Directory.CreateDirectory(Root);
|
|
|
|
public string Write(string relative, string contents)
|
|
{
|
|
var path = PathUnder(relative);
|
|
var dir = Path.GetDirectoryName(path);
|
|
if (dir is not null)
|
|
{
|
|
Directory.CreateDirectory(dir);
|
|
}
|
|
|
|
File.WriteAllText(path, contents);
|
|
return path;
|
|
}
|
|
|
|
public string WriteDll(string project, string configuration = "Debug") =>
|
|
Write($"src/{project}/bin/{configuration}/{LaunchBuildStamp.TargetFramework}/{project}.dll", "dll");
|
|
|
|
public string Dll(string project, string configuration = "Debug") =>
|
|
PathUnder($"src/{project}/bin/{configuration}/{LaunchBuildStamp.TargetFramework}/{project}.dll");
|
|
|
|
public void Touch(string relative, DateTime utc) => File.SetLastWriteTimeUtc(PathUnder(relative), utc);
|
|
|
|
public void TouchDlls(string configuration, DateTime utc)
|
|
{
|
|
foreach (var project in LaunchBuildStamp.OutputProjects)
|
|
{
|
|
Touch($"src/{project}/bin/{configuration}/{LaunchBuildStamp.TargetFramework}/{project}.dll", utc);
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
try
|
|
{
|
|
Directory.Delete(Root, recursive: true);
|
|
}
|
|
catch (IOException)
|
|
{
|
|
}
|
|
}
|
|
|
|
private string PathUnder(string relative) =>
|
|
Path.Combine(Root, relative.Replace('/', Path.DirectorySeparatorChar));
|
|
}
|
|
}
|