diff --git a/AudioAnalysis.sln.DotSettings b/AudioAnalysis.sln.DotSettings index 80e2e3f51..b2ce57e8d 100644 --- a/AudioAnalysis.sln.DotSettings +++ b/AudioAnalysis.sln.DotSettings @@ -262,6 +262,7 @@ True True True + True TRACE 1000 True diff --git a/TestSettings.testsettings b/TestSettings.testsettings deleted file mode 100644 index 3ccf31369..000000000 --- a/TestSettings.testsettings +++ /dev/null @@ -1,30 +0,0 @@ - - - These are default test settings for a local test run. - - - - - - - - - - - - - - -
-
-
-
- - - - - - - - - \ No newline at end of file diff --git a/src/AcousticWorkbench/UrlGenerator.cs b/src/AcousticWorkbench/UrlGenerator.cs index 940bf5487..2d4ff4898 100644 --- a/src/AcousticWorkbench/UrlGenerator.cs +++ b/src/AcousticWorkbench/UrlGenerator.cs @@ -30,7 +30,7 @@ public static Uri GetLoginUri(this IApi api) public static Uri GetSessionValidateUri(this IApi api) { - return api.Base("security/new"); + return api.Base("security/user"); } public static Uri GetListenUri(this IApi api, long audioRecordingId, double startOffsetSeconds, double? endOffsetSeconds = null) diff --git a/src/Acoustics.Shared/Extensions/RangeExtensions.cs b/src/Acoustics.Shared/Extensions/RangeExtensions.cs index 3186f91b6..641046805 100644 --- a/src/Acoustics.Shared/Extensions/RangeExtensions.cs +++ b/src/Acoustics.Shared/Extensions/RangeExtensions.cs @@ -30,7 +30,8 @@ public static Range Grow( { var limitMagnitude = limits.Size(); - if (growAmount + range.Size() > limitMagnitude) + var newMagnitude = growAmount + range.Size(); + if (newMagnitude > limitMagnitude) { return limits; } @@ -38,6 +39,18 @@ public static Range Grow( var halfGrow = growAmount / 2.0; var newMin = range.Minimum - halfGrow; + var newMax = range.Maximum + halfGrow; + + if (newMin < limits.Minimum) + { + newMax = limits.Minimum + newMagnitude; + newMin = limits.Minimum; + } + else if (newMax > limits.Maximum) + { + newMin = limits.Maximum - newMagnitude; + newMax = limits.Maximum; + } // round the lower bound if (roundDigits.HasValue) @@ -45,26 +58,13 @@ public static Range Grow( newMin = Math.Round(newMin, roundDigits.Value, MidpointRounding.AwayFromZero); } - var newMax = range.Maximum + halfGrow; - // round the upper bound if (roundDigits.HasValue) { newMax = Math.Round(newMax, roundDigits.Value, MidpointRounding.AwayFromZero); } - if (newMin < limits.Minimum) - { - newMax += limits.Minimum - newMin; - newMin = limits.Minimum; - } - else if (newMax > limits.Maximum) - { - newMin -= newMax - limits.Maximum; - newMax = limits.Maximum; - } - - return new Range(newMin, newMax); + return new Range(newMin, newMax, range.Topology); } public static double Center(this Range range) @@ -89,12 +89,18 @@ public static TimeSpan Size(this Range range) public static Range Shift(this Range range, double shift) { - return new Range(range.Minimum + shift, range.Maximum + shift); + return new Range( + range.Minimum + shift, + range.Maximum + shift, + range.Topology); } public static Range Shift(this Range range, TimeSpan shift) { - return new Range(range.Minimum.Add(shift), range.Maximum.Add(shift)); + return new Range( + range.Minimum.Add(shift), + range.Maximum.Add(shift), + range.Topology); } public static Range Add(this Range rangeA, Range rangeB) @@ -103,7 +109,8 @@ public static Range Add(this Range rangeA, Range rangeB) return new Range( rangeA.Minimum + rangeB.Minimum, - rangeA.Maximum + rangeB.Maximum); + rangeA.Maximum + rangeB.Maximum, + rangeA.CombineTopology(rangeB)); } public static Range Subtract(this Range rangeA, Range rangeB) @@ -112,7 +119,8 @@ public static Range Subtract(this Range rangeA, Range ra return new Range( rangeA.Minimum - rangeB.Maximum, - rangeA.Maximum - rangeB.Minimum); + rangeA.Maximum - rangeB.Minimum, + rangeA.CombineTopology(rangeB)); } public static Range Multiply(this Range rangeA, Range rangeB) @@ -126,7 +134,8 @@ public static Range Multiply(this Range rangeA, Range ra return new Range( Maths.Min(a1b1, a1b2, a2b1, a2b2), - Maths.Max(a1b1, a1b2, a2b1, a2b2)); + Maths.Max(a1b1, a1b2, a2b1, a2b2), + rangeA.CombineTopology(rangeB)); } public static Range Divide(this Range rangeA, Range rangeB) @@ -149,25 +158,25 @@ public static Range Invert(this Range range) return new Range(1 / range.Maximum, double.PositiveInfinity); } - return new Range(1 / range.Maximum, 1 / range.Minimum); + return new Range(1 / range.Maximum, 1 / range.Minimum, range.Topology); } - public static Range AsRange(this (T Minimum, T Maximum) pair) + public static Range AsRange(this (T Minimum, T Maximum) pair, Topology topology = Topology.Default) where T : struct, IComparable { - return new Range(pair.Minimum, pair.Maximum); + return new Range(pair.Minimum, pair.Maximum, topology); } - public static Range To(this T Minimum, T Maximum) + public static Range To(this T minimum, T maximum, Topology topology = Topology.Default) where T : struct, IComparable { - return new Range(Minimum, Maximum); + return new Range(minimum, maximum, topology); } - public static Range AsRangeFromZero(this T maximum) + public static Range AsRangeFromZero(this T maximum, Topology topology = Topology.Default) where T : struct, IComparable { - return new Range(default(T), maximum); + return new Range(default(T), maximum, topology); } } } diff --git a/src/Acoustics.Shared/Range.cs b/src/Acoustics.Shared/Range.cs index b1ae4f3f2..7a36c3492 100644 --- a/src/Acoustics.Shared/Range.cs +++ b/src/Acoustics.Shared/Range.cs @@ -10,8 +10,30 @@ namespace Acoustics.Shared { using System; - using System.Collections.Generic; - using System.Diagnostics; + + public enum Topology : byte + { + /* + * Our flags are defined like this to ensure the default value is [a,b). + * The meaning of bit 1 is: Is the left exclusive? (1 yes, 0 no) + * The meaning of bit 2 is: Is the right inclusive? (1 yes, 0 no) + * + * Note bit 1 is on the right. + */ +#pragma warning disable SA1025 // Code should not contain multiple whitespace in a row + Open = 0b0_1, + LeftClosedRightOpen = 0b0_0, + LeftOpenRightClosed = 0b1_1, + Closed = 0b1_0, +#pragma warning restore SA1025 // Code should not contain multiple whitespace in a row + + Exclusive = Open, + MinimumInclusiveMaximumExclusive = LeftClosedRightOpen, + MinimumExclusiveMaximumInclusive = LeftOpenRightClosed, + Inclusive = Closed, + + Default = LeftClosedRightOpen, + } /// /// Represents a range between two points on the same dimenson. @@ -21,7 +43,7 @@ namespace Acoustics.Shared /// /// The type used to represent the points in this range. /// - public struct Range : IEquatable>, IComparable> + public readonly struct Range : IEquatable>, IComparable> where T : struct, IComparable { public Range(T minimum, T maximum) @@ -35,6 +57,21 @@ public Range(T minimum, T maximum) this.Minimum = minimum; this.Maximum = maximum; + this.Topology = Topology.Default; + } + + public Range(T minimum, T maximum, Topology topology) + { + if (minimum.CompareTo(maximum) == 1) + { + throw new ArgumentException( + $"Range's minimum ({minimum}) must be less than the maximum ({maximum})", + nameof(minimum)); + } + + this.Minimum = minimum; + this.Maximum = maximum; + this.Topology = topology; } /// @@ -47,6 +84,19 @@ public Range(T minimum, T maximum) /// public T Maximum { get; } + /// + /// Gets the type of topology this interval has. + /// + public Topology Topology { get; } + + public bool IsEmpty => this.Minimum.Equals(this.Maximum); + + public bool IsDefault => this.Equals(default(Range)); + + public bool IsMinimumInclusive => this.Topology == Topology.LeftClosedRightOpen || this.Topology == Topology.Closed; + + public bool IsMaximumInclusive => this.Topology == Topology.LeftOpenRightClosed || this.Topology == Topology.Closed; + /// /// Equals operator. /// @@ -77,23 +127,42 @@ public Range(T minimum, T maximum) public bool Contains(T scalar, Topology type = Topology.Default) { - return ScalarEqualOrGreaterThanAnchor(scalar, this.Minimum, type) && ScalarEqualOrLessThanAnchor(scalar, this.Maximum, type); + return ScalarEqualOrGreaterThanAnchor(scalar, this.Minimum, this.IsMinimumInclusive) && + ScalarEqualOrLessThanAnchor(scalar, this.Maximum, this.IsMaximumInclusive); + } + + public bool Contains(Range range) + { + return ScalarEqualOrGreaterThanAnchor( + range.Minimum, + this.Minimum, + this.IsMinimumInclusive || (!this.IsMinimumInclusive && !range.IsMinimumInclusive)) + && ScalarEqualOrLessThanAnchor( + range.Maximum, + this.Maximum, + this.IsMaximumInclusive || (!this.IsMaximumInclusive && !range.IsMaximumInclusive)); } - public bool IntersectsWith(Range range, Topology type = Topology.Default) + public bool IntersectsWith(Range range) { - return ScalarEqualOrGreaterThanAnchor(range.Maximum, this.Minimum, type) && - ScalarEqualOrLessThanAnchor(range.Minimum, this.Maximum, type); + return ScalarEqualOrGreaterThanAnchor( + range.Maximum, + this.Minimum, + range.IsMaximumInclusive || this.IsMinimumInclusive) && + ScalarEqualOrLessThanAnchor( + range.Minimum, + this.Maximum, + range.IsMinimumInclusive || this.IsMaximumInclusive); } public bool TryGetUnion(Range range, out Range union) { - if (this.IntersectsWith(range, Topology.Closed)) + if (this.IntersectsWith(range)) { T newMin = this.Minimum.CompareTo(range.Minimum) < 0 ? this.Minimum : range.Minimum; T newMax = this.Maximum.CompareTo(range.Maximum) > 0 ? this.Maximum : range.Maximum; - union = new Range(newMin, newMax); + union = new Range(newMin, newMax, this.CombineTopology(range)); return true; } @@ -101,10 +170,6 @@ public bool TryGetUnion(Range range, out Range union) return false; } - public bool IsEmpty => this.Minimum.Equals(this.Maximum); - - public bool IsDefault => this.Equals(default(Range)); - /// /// Indicates whether the current object is equal to another object of the same type. /// @@ -116,7 +181,7 @@ public bool TryGetUnion(Range range, out Range union) /// public bool Equals(Range other) { - return this.Minimum.Equals(other.Minimum) && this.Maximum.Equals(other.Maximum); + return this.Minimum.Equals(other.Minimum) && this.Maximum.Equals(other.Maximum) && this.Topology == other.Topology; } /// @@ -135,7 +200,7 @@ public override bool Equals(object obj) return false; } - return obj is Range && this.Equals((Range)obj); + return obj is Range range && this.Equals(range); } /// @@ -148,7 +213,10 @@ public override int GetHashCode() { unchecked { - return (this.Minimum.GetHashCode() * 397) ^ this.Maximum.GetHashCode(); + var hashCode = this.Minimum.GetHashCode(); + hashCode = (hashCode * 397) ^ this.Maximum.GetHashCode(); + hashCode = (hashCode * 397) ^ (int)this.Topology; + return hashCode; } } @@ -162,7 +230,9 @@ public override int GetHashCode() /// public override string ToString() { - return $"Range: [{this.Minimum}, {this.Maximum}]"; + var left = this.IsMinimumInclusive ? "[" : "("; + var right = this.IsMaximumInclusive ? "]" : ")"; + return $"Range: {left}{this.Minimum}, {this.Maximum}{right}"; } public int CompareTo(Range other) @@ -177,7 +247,33 @@ public int CompareTo(Range other) return this.Maximum.CompareTo(other.Maximum); } - private static bool ScalarEqualOrGreaterThanAnchor(T scalar, T anchor, Topology anchorTopology) + public Topology CombineTopology(Range second) + { + if (this.Topology == second.Topology) + { + return this.Topology; + } + + bool min = this.IsBothMinimumInclusive(second); + bool max = this.IsBothMaximumInclusive(second); + + if (min && max) + { + return Topology.Inclusive; + } + else if (min) + { + return Topology.MinimumInclusiveMaximumExclusive; + } + else if (max) + { + return Topology.MinimumExclusiveMaximumInclusive; + } + + return Topology.Exclusive; + } + + private static bool ScalarEqualOrGreaterThanAnchor(T scalar, T anchor, bool isMinimumInclusive) { int comparison = anchor.CompareTo(scalar); bool result = false; @@ -191,13 +287,12 @@ private static bool ScalarEqualOrGreaterThanAnchor(T scalar, T anchor, Topology case 0: { - result = (anchorTopology & Topology.LeftClosedRightOpen) == Topology.LeftClosedRightOpen; + result = isMinimumInclusive; break; } case 1: { - result = false; break; } } @@ -205,7 +300,7 @@ private static bool ScalarEqualOrGreaterThanAnchor(T scalar, T anchor, Topology return result; } - private static bool ScalarEqualOrLessThanAnchor(T scalar, T anchor, Topology anchorTopology) + private static bool ScalarEqualOrLessThanAnchor(T scalar, T anchor, bool isMaximumInclusive) { int comparison = anchor.CompareTo(scalar); bool result = false; @@ -213,13 +308,12 @@ private static bool ScalarEqualOrLessThanAnchor(T scalar, T anchor, Topology anc { case -1: { - result = false; break; } case 0: { - result = (anchorTopology & Topology.LeftOpenRightClosed) == Topology.LeftOpenRightClosed; + result = isMaximumInclusive; break; } @@ -232,21 +326,10 @@ private static bool ScalarEqualOrLessThanAnchor(T scalar, T anchor, Topology anc return result; } - } - [Flags] - public enum Topology - { - Open = 0x0, - LeftClosedRightOpen = 0x01, - LeftOpenRightClosed = 0x02, - Closed = 0x3, + private bool IsBothMinimumInclusive(Range other) => this.IsMinimumInclusive && other.IsMinimumInclusive; - Exclusive = Open, - MinimumInclusiveMaximumExclusive = LeftClosedRightOpen, - MinimumExclusiveMaximumInclusive = LeftOpenRightClosed, - Inclusive = Closed, + private bool IsBothMaximumInclusive(Range other) => this.IsMaximumInclusive && other.IsMaximumInclusive; - Default = LeftClosedRightOpen, } } diff --git a/src/AnalysisBase/FileSegment.cs b/src/AnalysisBase/FileSegment.cs index 04a9e0cf5..9ef433c11 100644 --- a/src/AnalysisBase/FileSegment.cs +++ b/src/AnalysisBase/FileSegment.cs @@ -35,6 +35,7 @@ namespace AnalysisBase public class FileSegment : ICloneable, ISegment { private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static readonly MasterAudioUtility DefaultMasterAudioUtility = new MasterAudioUtility(); private readonly FileDateBehavior dateBehavior; @@ -94,10 +95,32 @@ public FileSegment( this.Source = source; this.Alignment = alignment; - var basename = Path.GetFileNameWithoutExtension(this.Source.Name); var fileDate = this.ParseDate(null); - var info = (utility ?? new MasterAudioUtility()).Info(source); + var info = (utility ?? DefaultMasterAudioUtility).Info(source); + + var basename = Path.GetFileNameWithoutExtension(this.Source.Name); + this.SourceMetadata = new SourceMetadata(info.Duration.Value, info.SampleRate.Value, basename, fileDate); + + Contract.Ensures(this.Validate(), "FileSegment did not validate"); + } + + public FileSegment( + FileInfo source, + DateTimeOffset startDate, + IAudioUtility utility = null) + { + Contract.Requires(source != null); + + this.dateBehavior = FileDateBehavior.Required; + this.Source = source; + this.Alignment = TimeAlignment.None; + + var fileDate = this.ParseDate(startDate); + + var info = (utility ?? DefaultMasterAudioUtility).Info(source); + var basename = Path.GetFileNameWithoutExtension(this.Source.Name); + this.SourceMetadata = new SourceMetadata(info.Duration.Value, info.SampleRate.Value, basename, fileDate); Contract.Ensures(this.Validate(), "FileSegment did not validate"); diff --git a/src/AnalysisConfigFiles/Ecosounds.EventStatistics.yml b/src/AnalysisConfigFiles/Ecosounds.EventStatistics.yml new file mode 100644 index 000000000..2b436528a --- /dev/null +++ b/src/AnalysisConfigFiles/Ecosounds.EventStatistics.yml @@ -0,0 +1,5 @@ + + +FrameSize: 512 + +FrameStep: 512 \ No newline at end of file diff --git a/src/AnalysisPrograms/Production/Parsers/DirectoryInfoParser.cs b/src/AnalysisPrograms/Production/Parsers/DirectoryInfoParser.cs index 19ceabbb5..e6d497042 100644 --- a/src/AnalysisPrograms/Production/Parsers/DirectoryInfoParser.cs +++ b/src/AnalysisPrograms/Production/Parsers/DirectoryInfoParser.cs @@ -17,7 +17,15 @@ public class DirectoryInfoParser : IValueParser { public object Parse(string argName, string value) { - return value.IsNotWhitespace() ? new DirectoryInfo(value) : null; + try + { + return value.IsNotWhitespace() ? new DirectoryInfo(value) : null; + } + catch (ArgumentException aex) + { + var message = $"Argument `{argName}` with value `{value}` could not be converted to a directory. Reason: {aex.Message}"; + throw new ArgumentException(message, aex); + } } } } diff --git a/src/AnalysisPrograms/SourcePreparers/RemoteSourcePreparer.cs b/src/AnalysisPrograms/SourcePreparers/RemoteSourcePreparer.cs index cde829829..e9f76a2b3 100644 --- a/src/AnalysisPrograms/SourcePreparers/RemoteSourcePreparer.cs +++ b/src/AnalysisPrograms/SourcePreparers/RemoteSourcePreparer.cs @@ -270,7 +270,7 @@ public async Task PrepareFile( } // finally inspect the bit of audio we downloaded, extract the metadata, and return a file segment - var preparedFile = new FileSegment(destination, TimeAlignment.None); + var preparedFile = new FileSegment(destination, segment.Source.RecordedDate.AddSeconds(segment.StartOffsetSeconds)); // do some sanity checks var expectedDuration = segment.Offsets.Size().Seconds(); diff --git a/src/AudioAnalysisTools/EventStatistics/EventStatisticsCalculate.cs b/src/AudioAnalysisTools/EventStatistics/EventStatisticsCalculate.cs index 760ac28d2..c8b821157 100644 --- a/src/AudioAnalysisTools/EventStatistics/EventStatisticsCalculate.cs +++ b/src/AudioAnalysisTools/EventStatistics/EventStatisticsCalculate.cs @@ -80,8 +80,8 @@ public static EventStatistics AnalyzeAudioEvent( if (!recording .Duration - .AsRangeFromZero() - .IntersectsWith(localTemporalTarget, Topology.MinimumInclusiveMaximumExclusive)) + .AsRangeFromZero(Topology.Inclusive) + .Contains(localTemporalTarget)) { stats.Error = true; stats.ErrorMessage = diff --git a/tests/Acoustics.Test/Shared/RangeTests.cs b/tests/Acoustics.Test/Shared/RangeTests.cs index 8f089903e..ce72bad48 100644 --- a/tests/Acoustics.Test/Shared/RangeTests.cs +++ b/tests/Acoustics.Test/Shared/RangeTests.cs @@ -77,10 +77,14 @@ public void GetHashCodeWorks() public void ToStringWorks() { var a = new Range(5, 10); - var c = new Range(5, 10.004); + var b = new Range(5, 10, Topology.Open); + var c = new Range(5, 10.004, Topology.Closed); + var d = new Range(5, 10.004, Topology.LeftOpenRightClosed); - Assert.AreEqual("Range: [5, 10]", a.ToString()); + Assert.AreEqual("Range: [5, 10)", a.ToString()); + Assert.AreEqual("Range: (5, 10)", b.ToString()); Assert.AreEqual("Range: [5, 10.004]", c.ToString()); + Assert.AreEqual("Range: (5, 10.004]", d.ToString()); } [DataTestMethod] @@ -218,6 +222,11 @@ public void DoubleInvertWorks() [DataRow(60.0, 540, 00, 600, null, 30, 570)] [DataRow(37.123, 39.999, 0, 120, 0, 7, 70)] [DataRow(37.123, 39.999, 0, 120, 1, 7.1, 70)] + + // expected magnitude is size (1.232) plus growth, subtracted from end limit. +#pragma warning disable SA1139 // Use literal suffix notation instead of casting + [DataRow(3593.853, 3595.085, 0.0, 3595, 0, 3535 - (int)1.232, 3595)] +#pragma warning restore SA1139 // Use literal suffix notation instead of casting public void DoubleGrowWorks(double a1, double a2, double b1, double b2, int? roundDigits, double c1, double c2) { var target = a1.To(a2); @@ -244,7 +253,7 @@ public void DoubleGrowWorks(double a1, double a2, double b1, double b2, int? rou [DataRow(100, 300, 300, Topology.LeftOpenRightClosed, true)] public void DoubleContainsWorks(double a1, double a2, double scalar, Topology? type, bool result) { - var range = a1.To(a2); + var range = a1.To(a2, type ?? Topology.Default); var actual = type.HasValue ? range.Contains(scalar, type.Value) : range.Contains(scalar); @@ -253,7 +262,7 @@ public void DoubleContainsWorks(double a1, double a2, double scalar, Topology? t [DataTestMethod] [DataRow(100, 300, 150, 400, null, true)] - [DataRow(100, 300, 300, 400, null, false)] + [DataRow(100, 300, 300, 400, null, true)] [DataRow(100, 300, 400, 500, null, false)] [DataRow(100, 300, -600, 50, null, false)] [DataRow(300, 400, double.NegativeInfinity, double.PositiveInfinity, null, true)] @@ -264,27 +273,59 @@ public void DoubleContainsWorks(double a1, double a2, double scalar, Topology? t [DataRow(100, 300, 200, 400, Topology.Open, true)] [DataRow(400, 700, 300, 400, Topology.Open, false)] [DataRow(100, 300, -50, 100, Topology.LeftClosedRightOpen, true)] - [DataRow(100, 300, 300, 400, Topology.LeftClosedRightOpen, false)] + [DataRow(100, 300, 300, 400, Topology.LeftClosedRightOpen, true)] [DataRow(100, 300, -50, 100, Topology.LeftOpenRightClosed, false)] [DataRow(100, 300, 300, 400, Topology.LeftOpenRightClosed, true)] public void DoubleIntersectsWithWorks(double a1, double a2, double b1, double b2, Topology? type, bool result) { - var a = a1.To(a2); + var a = a1.To(a2, type ?? Topology.Default); var b = b1.To(b2); - var actual = type.HasValue ? a.IntersectsWith(b, type.Value) : a.IntersectsWith(b); + var actual = a.IntersectsWith(b); Assert.AreEqual(result, actual); // intersect is commutative - double check here if (type == Topology.Closed) { - var actualCommutative = type.HasValue ? b.IntersectsWith(a, type.Value) : b.IntersectsWith(a); + var actualCommutative = b.IntersectsWith(a); Assert.AreEqual(result, actualCommutative); } } + [DataTestMethod] + [DataRow(100, 300, 150, 400, null, false, false)] + [DataRow(100, 300, 300, 400, null, false, false)] + [DataRow(100, 300, 400, 500, null, false, false)] + [DataRow(100, 300, -600, 50, null, false, false)] + [DataRow(100, 300, 100, 300, null, true, true)] + [DataRow(100, 300, 100, 299, null, true, false)] + [DataRow(300, 400, double.NegativeInfinity, double.PositiveInfinity, null, false, true)] + [DataRow(100, 300, 300, 400, Topology.Closed, false, false)] + [DataRow(100, 200, 100, 200, Topology.Closed, true, false)] + [DataRow(200, 400, 300, 350, Topology.Closed, true, false)] + [DataRow(100 - double.Epsilon, 500 + double.Epsilon, 100, 500, Topology.Closed, true, false)] + [DataRow(200, 400, 200, 400, Topology.Open, false, true)] + [DataRow(199, 401, 200, 400, Topology.Open, true, false)] + [DataRow(100, 300, 100, 299, Topology.LeftClosedRightOpen, true, false)] + [DataRow(100, 300, 100, 300, Topology.LeftClosedRightOpen, true, true)] + [DataRow(100, 300, 100, 300, Topology.LeftOpenRightClosed, false, false)] + [DataRow(100, 300, 101, 300, Topology.LeftOpenRightClosed, true, false)] + public void DoubleContainsIntervalWorks(double a1, double a2, double b1, double b2, Topology? type, bool result, bool reverseResult) + { + var a = a1.To(a2, type ?? Topology.Default); + var b = b1.To(b2); + + var actual = a.Contains(b); + + Assert.AreEqual(result, actual); + + // contains should not be communitive unless closed + bool actualReverseResult = b.Contains(a); + Assert.AreEqual(reverseResult, actualReverseResult); + } + [DataTestMethod] [DataRow(100, 300, 150, 400, true, 100, 400)] [DataRow(-30, -10, -90, -20, true, -90, -10)] @@ -338,5 +379,17 @@ public void DoubleIsDefaultWithWorks(double a1, double a2, bool expected) Assert.AreEqual(expected, actual); } + + [TestMethod] + public void DefaultTopologyWorks() + { + var actual = default(Range); + Assert.AreEqual(0, actual.Minimum); + Assert.AreEqual(0, actual.Maximum); + Assert.AreEqual(Topology.Default, actual.Topology); + Assert.IsTrue(actual.IsEmpty); + Assert.IsTrue(actual.IsMinimumInclusive); + Assert.IsFalse(actual.IsMaximumInclusive); + } } }