fix(ci): make release gates portable and deterministic
All checks were successful
CI / linux-portable (push) Successful in 3m46s
CI / windows-gate (push) Successful in 6m21s
CI / release (push) Successful in 2m15s

This commit is contained in:
Erik 2026-08-25 19:38:10 +02:00
parent af9327a17b
commit 0c699240e0
4 changed files with 25 additions and 15 deletions

View file

@ -2083,12 +2083,12 @@ public static class CharacterStatController
long skillCost = skill.AdvancementClass >= CharacterSkillAdvancementClass.Trained long skillCost = skill.AdvancementClass >= CharacterSkillAdvancementClass.Trained
? skill.RaiseCost ? skill.RaiseCost
: skill.TrainedCost; : skill.TrainedCost;
return skillCost > 0 ? skillCost.ToString("N0") : "Infinity!"; return skillCost > 0 ? FormatXp(skillCost) : "Infinity!";
} }
if (attrSel[0] < 0) return sheet.SkillCredits.ToString(); if (attrSel[0] < 0) return sheet.SkillCredits.ToString();
long cost = GetRaiseCost(sheet, attrSel[0]); long cost = GetRaiseCost(sheet, attrSel[0]);
return cost > 0 ? cost.ToString("N0") : "Infinity!"; return cost > 0 ? FormatXp(cost) : "Infinity!";
}); });
// Line-2 elements: pass null → keep dat font. // Line-2 elements: pass null → keep dat font.
@ -2114,7 +2114,7 @@ public static class CharacterStatController
if (skill is not null && skill.AdvancementClass < CharacterSkillAdvancementClass.Trained) if (skill is not null && skill.AdvancementClass < CharacterSkillAdvancementClass.Trained)
return sheet.SkillCredits.ToString(); return sheet.SkillCredits.ToString();
} }
return sheet.UnassignedXp.ToString("N0"); return FormatXp(sheet.UnassignedXp);
}); });
BindSelectedFooterState(stateB); BindSelectedFooterState(stateB);
@ -2166,12 +2166,12 @@ public static class CharacterStatController
long skillCost = skill.AdvancementClass >= CharacterSkillAdvancementClass.Trained long skillCost = skill.AdvancementClass >= CharacterSkillAdvancementClass.Trained
? skill.RaiseCost ? skill.RaiseCost
: skill.TrainedCost; : skill.TrainedCost;
return skillCost > 0 ? skillCost.ToString("N0") : "Infinity!"; return skillCost > 0 ? FormatXp(skillCost) : "Infinity!";
} }
if (attrSel[0] < 0) return sheet.SkillCredits.ToString(); if (attrSel[0] < 0) return sheet.SkillCredits.ToString();
long cost = GetRaiseCost(sheet, attrSel[0]); long cost = GetRaiseCost(sheet, attrSel[0]);
return cost > 0 ? cost.ToString("N0") : "Infinity!"; return cost > 0 ? FormatXp(cost) : "Infinity!";
}); });
LabelProvider(TextById(state, FooterLine2Label), null, Body, () => LabelProvider(TextById(state, FooterLine2Label), null, Body, () =>
@ -2194,7 +2194,7 @@ public static class CharacterStatController
if (skill is not null && skill.AdvancementClass < CharacterSkillAdvancementClass.Trained) if (skill is not null && skill.AdvancementClass < CharacterSkillAdvancementClass.Trained)
return sheet.SkillCredits.ToString(); return sheet.SkillCredits.ToString();
} }
return sheet.UnassignedXp.ToString("N0"); return FormatXp(sheet.UnassignedXp);
}); });
} }
} }

View file

@ -363,6 +363,8 @@ public sealed class LauncherContentStateStore
{ {
if (string.IsNullOrWhiteSpace(path) if (string.IsNullOrWhiteSpace(path)
|| Path.IsPathFullyQualified(path) || Path.IsPathFullyQualified(path)
|| path.Contains('/')
|| path.Contains('\\')
|| !string.Equals(path, Path.GetFileName(path), StringComparison.Ordinal) || !string.Equals(path, Path.GetFileName(path), StringComparison.Ordinal)
|| path is "." or ".." || path is "." or ".."
|| !path.EndsWith(".pak", StringComparison.OrdinalIgnoreCase)) || !path.EndsWith(".pak", StringComparison.OrdinalIgnoreCase))

View file

@ -1,5 +1,6 @@
using AcDream.App.UI; using AcDream.App.UI;
using AcDream.App.UI.Layout; using AcDream.App.UI.Layout;
using System.Globalization;
using System.Numerics; using System.Numerics;
namespace AcDream.App.Tests.UI.Layout; namespace AcDream.App.Tests.UI.Layout;
@ -184,8 +185,8 @@ public class CharacterStatControllerTests
Assert.Equal("Non-Player Killer", visiblePk.LinesProvider()[0].Text); Assert.Equal("Non-Player Killer", visiblePk.LinesProvider()[0].Text);
Assert.Equal("126", visibleLevel.LinesProvider()[0].Text); Assert.Equal("126", visibleLevel.LinesProvider()[0].Text);
Assert.Equal("Total Experience (XP):", visibleTotalXpLabel.LinesProvider()[0].Text); Assert.Equal("Total Experience (XP):", visibleTotalXpLabel.LinesProvider()[0].Text);
Assert.Equal((1_250_000_000L).ToString("N0"), visibleTotalXp.LinesProvider()[0].Text); Assert.Equal((1_250_000_000L).ToString("N0", CultureInfo.InvariantCulture), visibleTotalXp.LinesProvider()[0].Text);
Assert.Equal((42_000_000L).ToString("N0"), visibleXpNext.LinesProvider()[0].Text); Assert.Equal((42_000_000L).ToString("N0", CultureInfo.InvariantCulture), visibleXpNext.LinesProvider()[0].Text);
Assert.Empty(hiddenName.LinesProvider()); Assert.Empty(hiddenName.LinesProvider());
Assert.Empty(hiddenXpNext.LinesProvider()); Assert.Empty(hiddenXpNext.LinesProvider());
Assert.Empty(hiddenPk.LinesProvider()); Assert.Empty(hiddenPk.LinesProvider());
@ -682,7 +683,7 @@ public class CharacterStatControllerTests
CharacterStatController.Bind(layout, SampleData.SampleCharacter); CharacterStatController.Bind(layout, SampleData.SampleCharacter);
var expected = (87_757_321_741L).ToString("N0"); var expected = (87_757_321_741L).ToString("N0", CultureInfo.InvariantCulture);
Assert.Equal(expected, val.LinesProvider()[0].Text); Assert.Equal(expected, val.LinesProvider()[0].Text);
} }
@ -737,7 +738,7 @@ public class CharacterStatControllerTests
Descendants(list).OfType<UiClickablePanel>().ToList()[4].OnClick!(); Descendants(list).OfType<UiClickablePanel>().ToList()[4].OnClick!();
// Focus raise cost = 110 (SampleData fixture). // Focus raise cost = 110 (SampleData fixture).
Assert.Equal((110L).ToString("N0"), val.LinesProvider()[0].Text); Assert.Equal((110L).ToString("N0", CultureInfo.InvariantCulture), val.LinesProvider()[0].Text);
} }
[Fact] [Fact]
@ -768,7 +769,7 @@ public class CharacterStatControllerTests
Descendants(list).OfType<UiClickablePanel>().ToList()[4].OnClick!(); Descendants(list).OfType<UiClickablePanel>().ToList()[4].OnClick!();
// UnassignedXp = 87_757_321_741L // UnassignedXp = 87_757_321_741L
var expected = (87_757_321_741L).ToString("N0"); var expected = (87_757_321_741L).ToString("N0", CultureInfo.InvariantCulture);
Assert.Equal(expected, val.LinesProvider()[0].Text); Assert.Equal(expected, val.LinesProvider()[0].Text);
} }
@ -1443,9 +1444,9 @@ public class CharacterStatControllerTests
// which was previously computed but never surfaced in the title text. // which was previously computed but never surfaced in the title text.
Assert.Equal("War Magic: 285 (+5)", title.LinesProvider()[0].Text); Assert.Equal("War Magic: 285 (+5)", title.LinesProvider()[0].Text);
Assert.Equal("Experience To Raise:", l1Label.LinesProvider()[0].Text); Assert.Equal("Experience To Raise:", l1Label.LinesProvider()[0].Text);
Assert.Equal((11_100_000L).ToString("N0"), l1Value.LinesProvider()[0].Text); Assert.Equal((11_100_000L).ToString("N0", CultureInfo.InvariantCulture), l1Value.LinesProvider()[0].Text);
Assert.Equal("Unassigned Experience:", l2Label.LinesProvider()[0].Text); Assert.Equal("Unassigned Experience:", l2Label.LinesProvider()[0].Text);
Assert.Equal((87_757_321_741L).ToString("N0"), l2Value.LinesProvider()[0].Text); Assert.Equal((87_757_321_741L).ToString("N0", CultureInfo.InvariantCulture), l2Value.LinesProvider()[0].Text);
// CT5: RowHighlightSprite corrected to 0x06000F93 (see the sealed // CT5: RowHighlightSprite corrected to 0x06000F93 (see the sealed
// verdict on RowClick_WithSpriteResolve_SelectedRowHasHighlightSprite // verdict on RowClick_WithSpriteResolve_SelectedRowHasHighlightSprite
// above); UseSelectionBars was retired outright (retail's actual // above); UseSelectionBars was retired outright (retail's actual
@ -2113,7 +2114,7 @@ public class CharacterStatControllerTests
CharacterStatController.Bind(layout, SampleData.SampleCharacter); CharacterStatController.Bind(layout, SampleData.SampleCharacter);
Assert.Equal((1_250_000_000L).ToString("N0"), value.LinesProvider()[0].Text); Assert.Equal((1_250_000_000L).ToString("N0", CultureInfo.InvariantCulture), value.LinesProvider()[0].Text);
Assert.False(value.Centered); Assert.False(value.Centered);
Assert.True(value.RightAligned); Assert.True(value.RightAligned);
} }
@ -2257,7 +2258,7 @@ public class CharacterStatControllerTests
var lines = xpValue.LinesProvider(); var lines = xpValue.LinesProvider();
Assert.Single(lines); Assert.Single(lines);
// XpToNextLevel from SampleData = 42_000_000L formatted as "42,000,000" // XpToNextLevel from SampleData = 42_000_000L formatted as "42,000,000"
Assert.Equal((42_000_000L).ToString("N0"), lines[0].Text); Assert.Equal((42_000_000L).ToString("N0", CultureInfo.InvariantCulture), lines[0].Text);
} }
/// <summary> /// <summary>

View file

@ -238,11 +238,18 @@ public sealed class PreparedAssetVerificationCacheTests : IDisposable
// Crash shape: the verified package was moved aside and what sits in // Crash shape: the verified package was moved aside and what sits in
// its place is wrong. The cache still describes the ORIGINAL bytes, so // its place is wrong. The cache still describes the ORIGINAL bytes, so
// if recovery trusted it the launcher would accept a bad package. // if recovery trusted it the launcher would accept a bad package.
DateTime verifiedWriteTime = File.GetLastWriteTimeUtc(store.PreparedAssetPath);
string backup = LauncherInstallRecordStore.GetBackupPath(store.PreparedAssetPath); string backup = LauncherInstallRecordStore.GetBackupPath(store.PreparedAssetPath);
File.Move(store.PreparedAssetPath, backup); File.Move(store.PreparedAssetPath, backup);
await File.WriteAllTextAsync( await File.WriteAllTextAsync(
store.PreparedAssetPath, store.PreparedAssetPath,
new string('z', PackageContent.Length)); new string('z', PackageContent.Length));
File.SetLastWriteTimeUtc(
store.PreparedAssetPath,
verifiedWriteTime.AddMinutes(1));
Assert.NotEqual(
verifiedWriteTime,
File.GetLastWriteTimeUtc(store.PreparedAssetPath));
InstallRecordVerification verification = await store.LoadAndVerifyAsync(); InstallRecordVerification verification = await store.LoadAndVerifyAsync();