The Launcher's XP11 port (8730b9b) now extends to everything: one net40
flavor across Console, vPOD, Contract, and SecureConfig (Newtonsoft.Json
everywhere; the net48/System.Text.Json legs and their #if splits are gone
since nothing consumed them).
Console (net40, single TFM like the Launcher):
- The ~31 BinaryFormatter bitmap blobs in the .resx files became raw
embedded files under assets/icons/ (extracted byte-faithfully via a
serialization surrogate — the animated square_throbber.gif survives),
loaded by Properties.Resources.EmbeddedBitmap/EmbeddedIcon. Reason:
System.Resources.Extensions' DeserializingResourceReader is net461+
and cannot load on net40. Strings stay in the .resx.
- IReadOnlyList -> IList in AppRegistry (net45+ interface).
vPOD (net40, single TFM):
- Zip extraction now shares the Launcher's MiniZip.cs (linked source), so
the diff-test install round-trip exercises it against ZipArchive zips.
- RPC args as JTokens; LaunchApps.json persistence via Newtonsoft;
Thread.VolatileRead instead of Volatile.Read.
Contract/SecureConfig: net40-only; Client/** (PodManagerConnection) now
ships in the one build. The Launcher package gains
TeslaSecureConfiguration.dll as a dependency of the client half.
Tests: the net48 xunit host loads the net40 assemblies (both CLR4), so
the suite exercises exactly what ships — 106/106 green. Also verified
live: net40 console provisioned, managed, and ran a full RP mission
against net40 vPOD (beacon/passphrase/RSA, 53290 RPC, egg load,
Run/Stop Mission).
Version: 4.11.4.3 across Launcher, Console, and vPOD (vPOD joins the
suite version line; was 1.0.0). Ship the dotNetFx40 redistributable in
Launcher/assets for XP-era pods.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
140 lines
6.5 KiB
C#
140 lines
6.5 KiB
C#
using System;
|
|
using Xunit;
|
|
|
|
namespace TeslaConsole.DiffTests
|
|
{
|
|
/// <summary>
|
|
/// For every input below, the original and the recovered build must return the
|
|
/// exact same result. Each [Theory] case runs the same method in both assemblies
|
|
/// (in their own AppDomains) and asserts byte-for-byte string equality.
|
|
/// </summary>
|
|
public class BehavioralEquivalenceTests : IClassFixture<DifferentialFixture>
|
|
{
|
|
private readonly DifferentialFixture _fx;
|
|
|
|
public BehavioralEquivalenceTests(DifferentialFixture fx) => _fx = fx;
|
|
|
|
private void AssertSame(string caseName, params string[] args)
|
|
{
|
|
var (original, recovered) = _fx.RunBoth(caseName, args);
|
|
Assert.Equal(original, recovered);
|
|
// Guard against both silently throwing the same way for a case that should succeed.
|
|
Assert.False(original == null,
|
|
$"{caseName}({string.Join(",", args)}) returned null from both assemblies");
|
|
}
|
|
|
|
[Fact]
|
|
public void Harness_Distinguishes_Different_Outputs()
|
|
{
|
|
// Negative control: a passing suite must be capable of seeing a difference.
|
|
// Same method, different inputs, must yield different results in each assembly.
|
|
string ninetySec = (90L * TimeSpan.TicksPerSecond).ToString();
|
|
var zero = _fx.Original.Run("GetTimeString", new[] { "0" });
|
|
var ninety = _fx.Original.Run("GetTimeString", new[] { ninetySec });
|
|
Assert.NotEqual(zero, ninety);
|
|
|
|
var rZero = _fx.Recovered.Run("GetTimeString", new[] { "0" });
|
|
var rNinety = _fx.Recovered.Run("GetTimeString", new[] { ninetySec });
|
|
Assert.NotEqual(rZero, rNinety);
|
|
}
|
|
|
|
[Fact]
|
|
public void Loaded_Assemblies_Are_The_Expected_Builds()
|
|
{
|
|
// The original is the untouched 4.11.3.37076 baseline; the recovered build is
|
|
// the modernized line, intentionally bumped to 4.11.4.x (same assembly name,
|
|
// new version). Both must be TeslaConsole — guards against testing the wrong files.
|
|
Assert.Contains("TeslaConsole", _fx.Original.AssemblyFullName);
|
|
Assert.Contains("TeslaConsole", _fx.Recovered.AssemblyFullName);
|
|
Assert.Contains("4.11.3.37076", _fx.Original.AssemblyFullName);
|
|
Assert.Contains("4.11.4.3", _fx.Recovered.AssemblyFullName);
|
|
}
|
|
|
|
// ---- RPStrings.GetTimeString: mm:ss formatting with 0.5s rounding ----
|
|
[Theory]
|
|
[InlineData(0L)] // 0s -> 00:00
|
|
[InlineData(4_000_000L)] // 0.4s -> rounds down
|
|
[InlineData(5_000_000L)] // 0.5s -> rounds up to 1s
|
|
[InlineData(6_000_000L)] // 0.6s
|
|
[InlineData(595_000_000L)] // 59.5s -> rolls to 01:00
|
|
[InlineData(600_000_000L)] // 60s -> 01:00
|
|
[InlineData(900_000_000L)] // 90s -> 01:30
|
|
[InlineData(5_990_000_000L)] // 599s
|
|
[InlineData(6_000_000_000L)] // 600s -> 10:00
|
|
[InlineData(36_000_000_000L)] // 3600s -> 60:00
|
|
[InlineData(-50_000_000L)] // negative
|
|
[InlineData(864_000_000_000L)] // 1 day
|
|
public void GetTimeString_Matches(long ticks)
|
|
=> AssertSame("GetTimeString", ticks.ToString());
|
|
|
|
// ---- HostTypeHelper.Parse(...).ToString() including invalid input ----
|
|
[Theory]
|
|
[InlineData("Game Machine")]
|
|
[InlineData("Mission Review")]
|
|
[InlineData("Console")]
|
|
[InlineData("GameMachineHostType")]
|
|
[InlineData("MissionReviewHostType")]
|
|
[InlineData("ConsoleHostType")]
|
|
[InlineData("console")] // case-sensitive: should throw in both
|
|
[InlineData("Nonsense")] // invalid: ArgumentException in both
|
|
[InlineData("")] // empty
|
|
public void HostTypeParse_Matches(string input)
|
|
=> AssertSame("HostTypeParse", input);
|
|
|
|
// ---- PlasmaBitmaps.ConvertBitmap: 1bpp packing of a known pixel pattern ----
|
|
[Theory]
|
|
[InlineData(8, 8, 0)]
|
|
[InlineData(8, 8, 1)]
|
|
[InlineData(8, 8, 2)]
|
|
[InlineData(16, 16, 0)]
|
|
[InlineData(16, 16, 3)]
|
|
[InlineData(32, 8, 5)]
|
|
[InlineData(7, 8, 0)] // width not multiple of 8 -> throws in both
|
|
[InlineData(8, 7, 0)] // height not multiple of 8 -> throws in both
|
|
public void ConvertBitmap_Matches(int width, int height, int seed)
|
|
=> AssertSame("ConvertBitmap", width.ToString(), height.ToString(), seed.ToString());
|
|
|
|
// ---- PlasmaBitmaps.GenerateString: full GDI text -> 1bpp pipeline ----
|
|
[Theory]
|
|
[InlineData(128, 32, "Microsoft Sans Serif", "RED")]
|
|
[InlineData(64, 16, "Microsoft Sans Serif", "RED")]
|
|
[InlineData(128, 32, "Microsoft Sans Serif", "1")]
|
|
[InlineData(128, 32, "Microsoft Sans Serif", "ABCDEFGH")]
|
|
[InlineData(128, 32, "Microsoft Sans Serif", "")]
|
|
[InlineData(128, 32, "Arial", "GO")]
|
|
public void GenerateString_Matches(int width, int height, string font, string text)
|
|
=> AssertSame("GenerateString", width.ToString(), height.ToString(), font, text);
|
|
|
|
// ---- RedPlanet RPMap / RPVehicle XML parsing ----
|
|
[Theory]
|
|
[InlineData("<map key=\"m1\" name=\"Blade's Edge\" image=\"images/x.bmp\" />")]
|
|
[InlineData("<map key=\"m2\" name=\"Lyz's Lane\" />")] // no image attr
|
|
[InlineData("<map key=\"\" name=\"\" />")] // empty values
|
|
public void RPMapParse_Matches(string xml)
|
|
=> AssertSame("RPMapParse", xml);
|
|
|
|
[Theory]
|
|
[InlineData("<vehicle key=\"v1\" name=\"Armadillo\" image=\"images/a.bmp\" />")]
|
|
[InlineData("<vehicle key=\"v2\" name=\"Skeeter\" />")]
|
|
public void RPVehicleParse_Matches(string xml)
|
|
=> AssertSame("RPVehicleParse", xml);
|
|
|
|
// ---- SiteManagement well-known application GUIDs (constants) ----
|
|
[Theory]
|
|
[InlineData("RPAppGuid")]
|
|
[InlineData("RPMRAppGuid")]
|
|
[InlineData("RPLCAppGuid")]
|
|
[InlineData("MW4AppGuid")]
|
|
[InlineData("MW4LCAppGuid")]
|
|
public void SiteManagementGuid_Matches(string fieldName)
|
|
=> AssertSame("SiteManagementGuid", fieldName);
|
|
|
|
// ---- Tuple.Create<int,string> generic factory ----
|
|
[Theory]
|
|
[InlineData("1", "alpha")]
|
|
[InlineData("-7", "")]
|
|
public void TupleCreate_Matches(string a, string b)
|
|
=> AssertSame("TupleCreate", a, b);
|
|
}
|
|
}
|