diff --git a/src/MSDIAL5/MsdialCore/Parameter/ParameterBase.cs b/src/MSDIAL5/MsdialCore/Parameter/ParameterBase.cs index ec3186eef..895fdd811 100644 --- a/src/MSDIAL5/MsdialCore/Parameter/ParameterBase.cs +++ b/src/MSDIAL5/MsdialCore/Parameter/ParameterBase.cs @@ -1221,8 +1221,25 @@ public class AlignmentBaseParameter { public bool TogetherWithAlignment { get; set; } = true; [Key(9)] public bool UseRefMatchedPeaksOnly { get; set; } = false; + /// + /// Key 10 was added after projects with keys 0-9 were saved. Such a project has no element here, and MessagePack + /// then assigns null rather than the initializer, so the getter hands out the defaults instead of null: an older + /// project opens, exports its parameters and aligns with automatic RT correction off, as it was saved. The + /// defaults are put in place once, atomically, as for . + /// [Key(10)] - public AutomaticAlignmentRetentionTimeCorrectionParameter AutomaticRtCorrection { get; set; } = new AutomaticAlignmentRetentionTimeCorrectionParameter(); + public AutomaticAlignmentRetentionTimeCorrectionParameter AutomaticRtCorrection { + get { + var parameter = _automaticRtCorrection; + if (parameter is null) { + System.Threading.Interlocked.CompareExchange(ref _automaticRtCorrection, new AutomaticAlignmentRetentionTimeCorrectionParameter(), null); + parameter = _automaticRtCorrection; + } + return parameter; + } + set => _automaticRtCorrection = value; + } + private AutomaticAlignmentRetentionTimeCorrectionParameter _automaticRtCorrection = new AutomaticAlignmentRetentionTimeCorrectionParameter(); } /// diff --git a/tests/MSDIAL5/MsdialCoreTests/Parameter/AlignmentBaseParameterTests.cs b/tests/MSDIAL5/MsdialCoreTests/Parameter/AlignmentBaseParameterTests.cs new file mode 100644 index 000000000..253fbf461 --- /dev/null +++ b/tests/MSDIAL5/MsdialCoreTests/Parameter/AlignmentBaseParameterTests.cs @@ -0,0 +1,85 @@ +using CompMs.Common.MessagePack; +using MessagePack; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.IO; +using System.Linq; + +namespace CompMs.MsdialCore.Parameter.Tests +{ + /// + /// AutomaticRtCorrection is MessagePack key 10, added after projects with keys 0-9 were saved. MessagePack 1.x + /// gives an absent key its type's default, not the member initialiser, so the nested parameter loaded as null and + /// the parameter export (and the GUI's alignment view) threw on an older project. + /// + [TestClass()] + public class AlignmentBaseParameterTests + { + [TestMethod()] + public void AProjectSavedBeforeAutomaticRtCorrectionExistedLoadsWithItsDefaults() { + var saved = new ParameterWithoutAutomaticRtCorrection { AlignmentReferenceFileID = 3, RetentionTimeAlignmentTolerance = 0.2F, TogetherWithAlignment = false, }; + + var loaded = SaveAndLoadAsProjectsDo(saved); + + Assert.IsNotNull(loaded.AutomaticRtCorrection); + Assert.IsFalse(loaded.AutomaticRtCorrection.Execute, "an older project aligns without automatic RT correction, as it was saved"); + Assert.AreEqual(new AutomaticAlignmentRetentionTimeCorrectionParameter().LocalSupportRtWindow, loaded.AutomaticRtCorrection.LocalSupportRtWindow); + Assert.AreSame(loaded.AutomaticRtCorrection, loaded.AutomaticRtCorrection, "the defaults are created once and kept"); + Assert.AreEqual(3, loaded.AlignmentReferenceFileID); + Assert.AreEqual(0.2F, loaded.RetentionTimeAlignmentTolerance); + Assert.IsFalse(loaded.TogetherWithAlignment); + } + + [TestMethod()] + public void AnOlderProjectExportsItsParameters() { + var loaded = SaveAndLoadAsProjectsDo(new ParameterWithoutAutomaticRtCorrection()); + var parameter = new ParameterBase { AlignmentBaseParam = loaded, }; + + var text = parameter.ParametersAsText(); + + Assert.IsTrue(text.Contains("Execute automatic RT correction for alignment: False")); + } + + [TestMethod()] + public void AStoredAutomaticRtCorrectionIsKept() { + var saved = new AlignmentBaseParameter(); + saved.AutomaticRtCorrection.Execute = true; + saved.AutomaticRtCorrection.ReferenceFileId = 5; + + var loaded = SaveAndLoadAsProjectsDo(saved); + + Assert.IsTrue(loaded.AutomaticRtCorrection.Execute); + Assert.AreEqual(5, loaded.AutomaticRtCorrection.ReferenceFileId); + } + + [TestMethod()] + public void SettingNullHandsOutTheDefaults() { + var parameter = new AlignmentBaseParameter { AutomaticRtCorrection = null, }; + + Assert.IsNotNull(parameter.AutomaticRtCorrection); + Assert.IsFalse(parameter.AutomaticRtCorrection.Execute); + } + + private static TLoad SaveAndLoadAsProjectsDo(TSave saved) { + using var stream = new MemoryStream(); + MessagePackDefaultHandler.SaveToStream(saved, stream); + stream.Position = 0; + return MessagePackDefaultHandler.LoadFromStream(stream); + } + + /// The parameter as it was serialised before key 10 (AutomaticRtCorrection) existed. + [MessagePackObject] + public sealed class ParameterWithoutAutomaticRtCorrection + { + [Key(0)] public int AlignmentReferenceFileID { get; set; } = 0; + [Key(1)] public float RetentionTimeAlignmentFactor { get; set; } = 0.5F; + [Key(2)] public float Ms1AlignmentFactor { get; set; } = 0.5F; + [Key(3)] public float SpectrumSimilarityAlignmentFactor { get; set; } = 0.5F; + [Key(4)] public float Ms1AlignmentTolerance { get; set; } = 0.015F; + [Key(5)] public float RetentionTimeAlignmentTolerance { get; set; } = 0.10F; + [Key(6)] public float SpectrumSimilarityAlignmentTolerance { get; set; } = 0.8F; + [Key(7)] public float AlignmentScoreCutOff { get; set; } = 50; + [Key(8)] public bool TogetherWithAlignment { get; set; } = true; + [Key(9)] public bool UseRefMatchedPeaksOnly { get; set; } = false; + } + } +}