diff --git a/release-version-sane/UpstreamRelease.cs b/release-version-sane/UpstreamRelease.cs new file mode 100644 index 0000000..71e516d --- /dev/null +++ b/release-version-sane/UpstreamRelease.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace ReleaseVersionSane +{ + public class UpstreamRelease + { + private readonly Uri RELEASE_INDEX = new Uri("https://raw.githubusercontent.com/dotnet/core/master/release-notes/releases-index.json"); + + public async Task<(List sdks, string runtime)> GetLatestRelease(HttpClient client, string majorMinor) + { + var releaseIndexRawJson = await client.GetStringAsync(RELEASE_INDEX); + var releaseInfoChannelJsonUrl = GetReleaseInfoChannelUrl(releaseIndexRawJson, majorMinor); + if (releaseInfoChannelJsonUrl == null) + { + return (null, null); + } + + var releaseInfoChannelRawJson = await client.GetStringAsync(releaseInfoChannelJsonUrl); + return GetLatestVersion(releaseInfoChannelRawJson); + } + + private (List sdks, string runtime) GetLatestVersion(string releaseRawJson) + { + dynamic releaseChannel = JsonConvert.DeserializeObject(releaseRawJson); + + string runtime = null; + var sdks = new List(); + var latestDate = new DateTime(2000, 1, 1); + + foreach (var release in releaseChannel["releases"]) + { + if (!DateTime.TryParse((string)release["release-date"], out DateTime releaseDate)) + { + continue; + } + + if (releaseDate > latestDate) + { + latestDate = releaseDate; + runtime = release["runtime"]["version"]; + sdks = new List(); + foreach (var sdk in release["sdks"]) + { + sdks.Add((string)sdk["version"]); + } + } + } + + return (sdks, runtime); + } + + private Uri GetReleaseInfoChannelUrl(string releaseIndexRawJson, string majorMinor) + { + dynamic releaseIndex = JsonConvert.DeserializeObject(releaseIndexRawJson); + + string releaseChannelJsonUrl = null; + foreach (var release in releaseIndex["releases-index"]) + { + if (release["channel-version"] == majorMinor) + { + releaseChannelJsonUrl = release["releases.json"]; + break; + } + } + + return new Uri(releaseChannelJsonUrl); + } + + } +} diff --git a/release-version-sane/VersionTest.cs b/release-version-sane/VersionTest.cs new file mode 100644 index 0000000..2b3161c --- /dev/null +++ b/release-version-sane/VersionTest.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Linq; +using System.Threading.Tasks; +using System.Diagnostics; +using Xunit; + +namespace ReleaseVersionSane +{ + public class VersionTest + { + [Fact] + public async Task VersionIsSane() + { + var upstream = new UpstreamRelease(); + var currentRuntimeVersion = GetRuntimeVersion(); + var currentSdkVersion = GetSdkVersion(); + + string majorMinor = $"{currentRuntimeVersion.Major}.{currentRuntimeVersion.Minor}"; + (List publicSdkVersions, string publicRuntimeVersion) = await upstream.GetLatestRelease(new HttpClient(), majorMinor); + + bool currentVersionNewerThanPublic = false; + + if (publicRuntimeVersion != currentRuntimeVersion.ToString()) + { + currentRuntimeVersion = new Version(currentRuntimeVersion.Major, + currentRuntimeVersion.Minor, + currentRuntimeVersion.Build - 1); + Assert.Equal(currentRuntimeVersion.ToString(), publicRuntimeVersion); + currentVersionNewerThanPublic = true; + } + + if (currentVersionNewerThanPublic) + { + currentSdkVersion = new Version(currentSdkVersion.Major, + currentSdkVersion.Minor, + currentSdkVersion.Build - 1); + } + + bool sdkMatched = false; + foreach (var sdk in publicSdkVersions) + { + if (sdk == currentSdkVersion.ToString()) + { + sdkMatched = true; + break; + } + } + + Assert.True(sdkMatched); + } + + private Version GetRuntimeVersion() + { + int exitCode = RunProcessAndGetOutput(new string[] { "dotnet" , "--list-runtimes" }, out string result); + if (exitCode != 0) + { + return null; + } + + return new Version(result + .Split(Environment.NewLine) + .Where(line => line.StartsWith("Microsoft.NETCore.App ")) + .Select(line => line.Split(' ')[1]) + .First()); + + } + + private Version GetSdkVersion() + { + int exitCode = RunProcessAndGetOutput(new string[] { "dotnet" , "--list-sdks" }, out string result); + if (exitCode != 0) + { + return null; + } + + return new Version(result + .Split(Environment.NewLine) + .Select(line => line.Split(' ')[0]) + .First()); + + } + + + private static int RunProcessAndGetOutput(string[] processAndArguments, out string standardOutput) + { + ProcessStartInfo startInfo = new ProcessStartInfo(); + startInfo.FileName = processAndArguments[0]; + startInfo.Arguments = string.Join(" ", processAndArguments.Skip(1)); + startInfo.RedirectStandardOutput = true; + + using (Process p = Process.Start(startInfo)) + { + p.WaitForExit(); + using (StreamReader r = p.StandardOutput) + { + standardOutput = r.ReadToEnd(); + } + return p.ExitCode; + } + } + + } +} diff --git a/release-version-sane/release-version-sane.csproj b/release-version-sane/release-version-sane.csproj new file mode 100644 index 0000000..d4dcc01 --- /dev/null +++ b/release-version-sane/release-version-sane.csproj @@ -0,0 +1,16 @@ + + + + true + netcoreapp3.1 + + false + + + + + + + + + diff --git a/release-version-sane/test.json b/release-version-sane/test.json new file mode 100644 index 0000000..96a8393 --- /dev/null +++ b/release-version-sane/test.json @@ -0,0 +1,12 @@ +{ + "name": "release-version-sane", + "enabled": true, + "requiresSdk": true, + "version": "3.1", + "versionSpecific": false, + "type": "xunit", + "cleanup": true, + "ignoredRIDs":[ + + ] +}