diff --git a/README.md b/README.md index 4ebae3aa..529d8ba5 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ ioxide hands you raw bytes and stays out of HTTP; when you want a framework on t `ioxide.Kestrel` swaps the transport under an existing ASP.NET Core app with your endpoints unchanged. -> Linux 6.1+ · .NET 10 / .NET 11 · `0.14.236` - experimental +> Linux 6.1+ · .NET 10 / .NET 11 · experimental **[Documentation](https://mda2av.github.io/ioxide/)** - architecture, guides, and every example as runnable code side by side. diff --git a/src/ioxide/IoxideRuntime.cs b/src/ioxide/IoxideRuntime.cs index b7dbb96d..27e16916 100644 --- a/src/ioxide/IoxideRuntime.cs +++ b/src/ioxide/IoxideRuntime.cs @@ -7,7 +7,20 @@ namespace ioxide; /// public static class IoxideRuntime { - public const string Version = "0.14.236"; + /// + /// The version of the NuGet package this assembly shipped in. + /// + /// + /// Generated from the Version property in ioxide.csproj by the GenerateVersionSource target + /// there, not written here. As a hand-kept literal it had to be remembered on every release and + /// was not: it still read "0.0.17" - untouched since the 0.0.x days - against packages on + /// 0.13.233, one of four statements of the same number with all four disagreeing (#224). + /// + /// Generated rather than read off the assembly at run time: AssemblyInformationalVersionAttribute + /// would be the obvious source, but reflecting over assembly attributes is what Native AOT + /// trims. A const costs nothing and survives trimming. + /// + public const string Version = IoxideVersion.Value; // Wiring (a builder API will eventually wrap this): // var reactor = new Reactor(id, config); // implements IRingHost diff --git a/src/ioxide/ioxide.csproj b/src/ioxide/ioxide.csproj index f4d525b8..c4d36256 100644 --- a/src/ioxide/ioxide.csproj +++ b/src/ioxide/ioxide.csproj @@ -23,4 +23,39 @@ + + + + <_IoxideVersionFile>$(IntermediateOutputPath)IoxideVersion.g.cs + + + + <_IoxideVersionLines Include="// <auto-generated />" /> + <_IoxideVersionLines Include="// Written by the GenerateVersionSource target in ioxide.csproj, from its Version property." /> + <_IoxideVersionLines Include="// Do not edit, and do not commit: it lives under obj/." /> + <_IoxideVersionLines Include="namespace ioxide%3B" /> + <_IoxideVersionLines Include="internal static class IoxideVersion" /> + <_IoxideVersionLines Include="{" /> + <_IoxideVersionLines Include=" public const string Value = "$(Version)"%3B" /> + <_IoxideVersionLines Include="}" /> + + + + + + + + + diff --git a/tests/Ioxide.Tests.Unit/Program.cs b/tests/Ioxide.Tests.Unit/Program.cs index b6205aae..f8636f77 100644 --- a/tests/Ioxide.Tests.Unit/Program.cs +++ b/tests/Ioxide.Tests.Unit/Program.cs @@ -11,6 +11,7 @@ private static int Main() { var runner = new Runner(); + VersionTests.Register(runner); DemuxParseTests.Register(runner); MessageTests.Register(runner); ResponseCapTests.Register(runner); diff --git a/tests/Ioxide.Tests.Unit/VersionTests.cs b/tests/Ioxide.Tests.Unit/VersionTests.cs new file mode 100644 index 00000000..dc91fead --- /dev/null +++ b/tests/Ioxide.Tests.Unit/VersionTests.cs @@ -0,0 +1,75 @@ +using System.Text.RegularExpressions; +using ioxide; + +namespace Ioxide.Tests; + +/// +/// reports the version the package was built as. +/// +/// +/// It used to be a hand-kept literal, and literals rot: it read "0.0.17" against packages on +/// 0.13.233, untouched since the 0.0.x days while three other statements of the same number moved +/// without it (#224). It is now generated from ioxide.csproj's Version property, so the test worth +/// having compares it against that property - if the generator stops running, or someone puts the +/// literal back, these fail. +/// +/// No reflection anywhere here, deliberately: the whole reason the version is generated rather than +/// read off AssemblyInformationalVersionAttribute is that Native AOT trims attribute reflection, and +/// a test that reached for it would be testing something the shipped configuration cannot do. +/// +internal static class VersionTests +{ + public static void Register(Runner runner) + { + runner.Test("version: IoxideRuntime.Version matches ioxide.csproj", () => + { + string csproj = FindCsproj(); + Match declared = Regex.Match(File.ReadAllText(csproj), @"([^<]+)"); + + Assert.True(declared.Success, $"no element in {csproj}"); + Assert.Equal(declared.Groups[1].Value, IoxideRuntime.Version); + }); + + runner.Test("version: it is a real version, not a placeholder", () => + { + Assert.True(IoxideRuntime.Version.Length > 0, "Version is empty"); + + Assert.True(Version.TryParse(IoxideRuntime.Version, out Version? parsed) && parsed is not null, + $"Version is not parseable: '{IoxideRuntime.Version}'"); + + Assert.True(parsed!.Major > 0 || parsed.Minor > 0 || parsed.Build > 0, + $"Version is all zeroes: '{IoxideRuntime.Version}'"); + }); + + runner.Test("version: no build metadata leaks into it", () => + { + // The SDK appends "+" to the informational version from the repository's git + // metadata - this build's assembly attribute reads "0.14.236+75c9bf6...". Generating the + // const from the Version property sidesteps that entirely, and this pins it: callers + // compare the value against a package version, and no package is named with a sha on it. + Assert.True(!IoxideRuntime.Version.Contains('+'), + $"build metadata reached the reported version: '{IoxideRuntime.Version}'"); + }); + } + + /// + /// Walks up from the test binary to the repo root, identified by the solution file beside it. + /// Plain file system, no reflection - see the note on the class. + /// + private static string FindCsproj() + { + for (DirectoryInfo? at = new(AppContext.BaseDirectory); at is not null; at = at.Parent) + { + if (File.Exists(Path.Combine(at.FullName, "ioxide.slnx"))) + { + string csproj = Path.Combine(at.FullName, "src", "ioxide", "ioxide.csproj"); + + Assert.True(File.Exists(csproj), $"found the repo root at {at.FullName} but no {csproj}"); + return csproj; + } + } + + throw new Exception( + $"no ioxide.slnx above {AppContext.BaseDirectory} - this test reads the version out of the repo"); + } +}