Skip to content

Commit

Permalink
✨Add UnitsNetSetup to hold global static state (#1267)
Browse files Browse the repository at this point in the history
Added `UnitsNetSetup` to gather global state in a single place as a
singleton, with the possibility of passing instances of it to static
methods later to override the defaults.

This way, state is no longer scattered among multiple classes that
depend on each other in non-obvious ways and where initialization order
has caused issues up until now.

### Changes
- Add `UnitsNetSetup` to hold global state as a singleton property
`Default` that controls default state initialization.
- Add properties for all "services" that depend on global state:
`UnitConverter, UnitAbbreviationsCache, UnitParser, QuantityParser`
- Forward all other `Default` singleton properties from these services
to `UnitsNetSetup.Default` and mark obsolete
- Add some TODOs where it seems functionality is missing

### Testing
❌ Still running into a handful of flaky tests due to racing conditions. 
This was a regression in #1210, but still not fixed.
Will investigate and fix in separate PR.

```
UnitAbbreviationsCacheTests.AllUnitsImplementToStringForInvariantCulture
UnitAbbreviationsCacheTests.MapUnitToAbbreviation_AddCustomUnit_DoesNotOverrideDefaultAbbreviationForAlreadyMappedUnits
```
  • Loading branch information
angularsen committed Jul 11, 2023
1 parent 49f8864 commit 5f13d99
Show file tree
Hide file tree
Showing 13 changed files with 440 additions and 170 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public AbbreviatedUnitsConverter()
/// </summary>
/// <param name="comparer">The comparer used to compare the property/quantity names (e.g. StringComparer.OrdinalIgnoreCase) </param>
public AbbreviatedUnitsConverter(IEqualityComparer<string?> comparer)
: this(new Dictionary<string, QuantityInfo>(Quantity.ByName, comparer), UnitAbbreviationsCache.Default, comparer)
: this(new Dictionary<string, QuantityInfo>(Quantity.ByName, comparer), UnitsNetSetup.Default.UnitAbbreviations, comparer)
{
}

Expand Down
29 changes: 29 additions & 0 deletions UnitsNet.Tests/AlphabeticalOrderer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Licensed under MIT No Attribution, see LICENSE file at the root.
// Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet.

using System.Collections.Generic;
using System.Linq;
using Xunit.Abstractions;
using Xunit.Sdk;

namespace UnitsNet.Tests;

/// <summary>
/// Useful for debugging tests where a particular order of tests is required.
/// </summary>
/// <example>
/// Add the attribute to your test class:
/// <code>
/// <![CDATA[
/// [TestCaseOrderer(
/// ordererTypeName: "UnitsNet.Tests.AlphabeticalOrderer",
/// ordererAssemblyName: "UnitsNet.Tests")]
/// ]]>
/// </code>
/// </example>
public class AlphabeticalOrderer : ITestCaseOrderer
{
public IEnumerable<TTestCase> OrderTestCases<TTestCase>(
IEnumerable<TTestCase> testCases) where TTestCase : ITestCase =>
testCases.OrderBy(testCase => testCase.TestMethod.Method.Name);
}
15 changes: 8 additions & 7 deletions UnitsNet.Tests/BaseUnitsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,14 @@ public void ToStringGivesExpectedResult()
AmountOfSubstanceUnit.Mole,
LuminousIntensityUnit.Candela);

var m = UnitAbbreviationsCache.Default.GetDefaultAbbreviation(LengthUnit.Meter);
var kg = UnitAbbreviationsCache.Default.GetDefaultAbbreviation(MassUnit.Kilogram);
var s = UnitAbbreviationsCache.Default.GetDefaultAbbreviation(DurationUnit.Second);
var A = UnitAbbreviationsCache.Default.GetDefaultAbbreviation(ElectricCurrentUnit.Ampere);
var K = UnitAbbreviationsCache.Default.GetDefaultAbbreviation(TemperatureUnit.Kelvin);
var mol = UnitAbbreviationsCache.Default.GetDefaultAbbreviation(AmountOfSubstanceUnit.Mole);
var cd = UnitAbbreviationsCache.Default.GetDefaultAbbreviation(LuminousIntensityUnit.Candela);
UnitAbbreviationsCache cache = UnitsNetSetup.Default.UnitAbbreviations;
var m = cache.GetDefaultAbbreviation(LengthUnit.Meter);
var kg = cache.GetDefaultAbbreviation(MassUnit.Kilogram);
var s = cache.GetDefaultAbbreviation(DurationUnit.Second);
var A = cache.GetDefaultAbbreviation(ElectricCurrentUnit.Ampere);
var K = cache.GetDefaultAbbreviation(TemperatureUnit.Kelvin);
var mol = cache.GetDefaultAbbreviation(AmountOfSubstanceUnit.Mole);
var cd = cache.GetDefaultAbbreviation(LuminousIntensityUnit.Candela);

Assert.Equal($"[Length]: {m}, [Mass]: {kg}, [Time]: {s}, [Current]: {A}, [Temperature]: {K}, [Amount]: {mol}, [LuminousIntensity]: {cd}", siBaseUnits.ToString());
}
Expand Down
9 changes: 5 additions & 4 deletions UnitsNet.Tests/QuantityIFormattableTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ public void EmptyOrNullFormatStringEqualsGFormat()
[Fact]
public void AFormatGetsAbbreviations()
{
Assert.Equal(UnitAbbreviationsCache.Default.GetDefaultAbbreviation(MyLength.Unit, CultureInfo.InvariantCulture), MyLength.ToString("a", CultureInfo.InvariantCulture));
Assert.Equal(UnitAbbreviationsCache.Default.GetDefaultAbbreviation(MyLength.Unit, CultureInfo.InvariantCulture), MyLength.ToString("a0", CultureInfo.InvariantCulture));
UnitAbbreviationsCache cache = UnitsNetSetup.Default.UnitAbbreviations;
Assert.Equal(cache.GetDefaultAbbreviation(MyLength.Unit, CultureInfo.InvariantCulture), MyLength.ToString("a", CultureInfo.InvariantCulture));
Assert.Equal(cache.GetDefaultAbbreviation(MyLength.Unit, CultureInfo.InvariantCulture), MyLength.ToString("a0", CultureInfo.InvariantCulture));

Assert.Equal(UnitAbbreviationsCache.Default.GetUnitAbbreviations(MyLength.Unit, CultureInfo.InvariantCulture)[1], MyLength.ToString("a1", CultureInfo.InvariantCulture));
Assert.Equal(UnitAbbreviationsCache.Default.GetUnitAbbreviations(MyLength.Unit, CultureInfo.InvariantCulture)[2], MyLength.ToString("a2", CultureInfo.InvariantCulture));
Assert.Equal(cache.GetUnitAbbreviations(MyLength.Unit, CultureInfo.InvariantCulture)[1], MyLength.ToString("a1", CultureInfo.InvariantCulture));
Assert.Equal(cache.GetUnitAbbreviations(MyLength.Unit, CultureInfo.InvariantCulture)[2], MyLength.ToString("a2", CultureInfo.InvariantCulture));
}

[Fact]
Expand Down
18 changes: 18 additions & 0 deletions UnitsNet.Tests/UnitAbbreviationsCacheTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,24 @@ public void GetDefaultAbbreviationFallsBackToUsEnglishCulture()
Assert.Equal("US english abbreviation for Unit1", abbreviation);
}

[Fact]
public void MapUnitToAbbreviation_DoesNotAffectOtherCacheInstances()
{
var culture = AmericanCulture;
var unit = AreaUnit.SquareMeter;

var cache1 = new UnitAbbreviationsCache();
cache1.MapUnitToAbbreviation(unit, culture, "m^2");

var cache2 = new UnitAbbreviationsCache();
cache2.MapUnitToAbbreviation(unit, culture, "m2");

Assert.Equal(new[] { "", "m^2" }, cache1.GetUnitAbbreviations(unit, culture));
Assert.Equal(new[] { "", "m2" }, cache2.GetUnitAbbreviations(unit, culture));
Assert.Equal("", cache1.GetDefaultAbbreviation(unit, culture));
Assert.Equal("", cache2.GetDefaultAbbreviation(unit, culture));
}

[Fact]
public void MapUnitToAbbreviation_AddCustomUnit_DoesNotOverrideDefaultAbbreviationForAlreadyMappedUnits()
{
Expand Down
10 changes: 1 addition & 9 deletions UnitsNet/CustomCode/Quantity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,7 @@ namespace UnitsNet
{
public partial class Quantity
{
static Quantity()
{
Default = new QuantityInfoLookup();
}

private static QuantityInfoLookup Default
{
get;
}
private static QuantityInfoLookup Default => UnitsNetSetup.Default.QuantityInfoLookup;

/// <summary>
/// All enum value names of <see cref="Infos"/>, such as "Length" and "Mass".
Expand Down
8 changes: 2 additions & 6 deletions UnitsNet/CustomCode/QuantityParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ public class QuantityParser
/// <summary>
/// The default instance of <see cref="QuantityParser"/>, which uses <see cref="UnitAbbreviationsCache.Default"/> unit abbreviations.
/// </summary>
public static QuantityParser Default { get; }
[Obsolete("Use UnitsNetSetup.Default.QuantityParser instead.")]
public static QuantityParser Default => UnitsNetSetup.Default.QuantityParser;

/// <summary>
/// Creates an instance of <see cref="QuantityParser"/>, optionally specifying an <see cref="UnitAbbreviationsCache"/>
Expand All @@ -50,11 +51,6 @@ public QuantityParser(UnitAbbreviationsCache? unitAbbreviationsCache = null)
_unitParser = new UnitParser(_unitAbbreviationsCache);
}

static QuantityParser()
{
Default = new QuantityParser(UnitAbbreviationsCache.Default);
}

/// <summary>
/// Parses a quantity from a string, such as "1.2 kg" to <see cref="Length"/> or "100 cm" to <see cref="Mass"/>.
/// </summary>
Expand Down
Loading

0 comments on commit 5f13d99

Please sign in to comment.