In `System.SysUtils`, `class function TCardinalHelper.Parse` throws errors for valid Cardinal values
Posted by jpluimers on 2016/10/05
Oh nice System.SysUtils.TCardinalHelper.Parse:
class function TCardinalHelper.Parse(const S: string): Cardinal; begin Result := StrToInt(S); end;
Which means you get this nice EConvertError with message ''4294967295' is not a valid integer value'. with this simple test (which doesn’t even reach the Assert):
uses System.SysUtils; procedure Cardinal_Parse_High_Cardinal_Succeeds(); var Expected: Cardinal; Value: string; Actual: Cardinal; begin Expected := High(Cardinal); Value := Expected.ToString(); Actual := Cardinal.Parse(Value); Assert(Expected = Actual); end;
So I write some unit tests (see below) of which helpers for these types fail in one way or the other:
- Cardinal
- NativeUInt
- Single
- Double
- Extended
These work for the boundary cases:
- SmallInt
- ShortInt
- Integer
- Int64
- NativeInt
- Byte
- Word
- UInt64
- Boolean
- ByteBool
- WordBool
- LongBool
–jeroen
via: Oh nice, in System.SysUtils: “` class function TCardinalHelper.Parse(const…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| unit SysUtilsParseTests; | |
| interface | |
| uses | |
| DUnitX.TestFramework; | |
| type | |
| [TestFixture] | |
| TSmallIntParseTests = class(TObject) | |
| strict private | |
| type | |
| TNumber = SmallInt; | |
| public | |
| [Test] | |
| procedure ParseMaxValueSucceeds; | |
| [Test] | |
| procedure ParseMinValueSucceeds; | |
| end; | |
| type | |
| [TestFixture] | |
| TShortIntParseTests = class(TObject) | |
| strict private | |
| type | |
| TNumber = ShortInt; | |
| public | |
| [Test] | |
| procedure ParseMaxValueSucceeds; | |
| [Test] | |
| procedure ParseMinValueSucceeds; | |
| end; | |
| type | |
| [TestFixture] | |
| TIntegerParseTests = class(TObject) | |
| strict private | |
| type | |
| TNumber = Integer; | |
| public | |
| [Test] | |
| procedure ParseMaxValueSucceeds; | |
| [Test] | |
| procedure ParseMinValueSucceeds; | |
| end; | |
| type | |
| [TestFixture] | |
| TInt64ParseTests = class(TObject) | |
| strict private | |
| type | |
| TNumber = Int64; | |
| public | |
| [Test] | |
| procedure ParseMaxValueSucceeds; | |
| [Test] | |
| procedure ParseMinValueSucceeds; | |
| end; | |
| type | |
| [TestFixture] | |
| TNativeIntParseTests = class(TObject) | |
| strict private | |
| type | |
| TNumber = NativeInt; | |
| public | |
| [Test] | |
| procedure ParseMaxValueSucceeds; | |
| [Test] | |
| procedure ParseMinValueSucceeds; | |
| end; | |
| type | |
| [TestFixture] | |
| TByteParseTests = class(TObject) | |
| strict private | |
| type | |
| TNumber = Byte; | |
| public | |
| [Test] | |
| procedure ParseMaxValueSucceeds; | |
| [Test] | |
| procedure ParseMinValueSucceeds; | |
| end; | |
| type | |
| [TestFixture] | |
| TWordParseTests = class(TObject) | |
| strict private | |
| type | |
| TNumber = Word; | |
| public | |
| [Test] | |
| procedure ParseMaxValueSucceeds; | |
| [Test] | |
| procedure ParseMinValueSucceeds; | |
| end; | |
| type | |
| [TestFixture] | |
| TCardinalParseTests = class(TObject) | |
| strict private | |
| type | |
| TNumber = Cardinal; | |
| public | |
| [Test] | |
| procedure ParseMaxValueSucceeds; | |
| [Test] | |
| procedure ParseMinValueSucceeds; | |
| end; | |
| type | |
| [TestFixture] | |
| TUInt64ParseTests = class(TObject) | |
| strict private | |
| type | |
| TNumber = UInt64; | |
| public | |
| [Test] | |
| procedure ParseMaxValueSucceeds; | |
| [Test] | |
| procedure ParseMinValueSucceeds; | |
| end; | |
| type | |
| [TestFixture] | |
| TNativeUIntParseTests = class(TObject) | |
| strict private | |
| type | |
| TNumber = NativeUInt; | |
| public | |
| [Test] | |
| procedure ParseMaxValueSucceeds; | |
| [Test] | |
| procedure ParseMinValueSucceeds; | |
| end; | |
| type | |
| [TestFixture] | |
| TBooleanParseTests = class(TObject) | |
| strict private | |
| type | |
| TNumber = Boolean; | |
| public | |
| [Test] | |
| procedure ParseHighSucceeds; | |
| [Test] | |
| procedure ParseLowSucceeds; | |
| end; | |
| type | |
| [TestFixture] | |
| TByteBoolParseTests = class(TObject) | |
| strict private | |
| type | |
| TNumber = ByteBool; | |
| public | |
| [Test] | |
| procedure ParseHighSucceeds; | |
| [Test] | |
| procedure ParseLowSucceeds; | |
| end; | |
| type | |
| [TestFixture] | |
| TWordBoolParseTests = class(TObject) | |
| strict private | |
| type | |
| TNumber = WordBool; | |
| public | |
| [Test] | |
| procedure ParseHighSucceeds; | |
| [Test] | |
| procedure ParseLowSucceeds; | |
| end; | |
| type | |
| [TestFixture] | |
| TLongBoolParseTests = class(TObject) | |
| strict private | |
| type | |
| TNumber = LongBool; | |
| public | |
| [Test] | |
| procedure ParseHighSucceeds; | |
| [Test] | |
| procedure ParseLowSucceeds; | |
| end; | |
| type | |
| [TestFixture] | |
| TSingleParseTests = class(TObject) | |
| strict private | |
| type | |
| TNumber = Single; | |
| public | |
| [Test] | |
| procedure ParseMaxValueSucceeds; | |
| [Test] | |
| procedure ParseMinValueSucceeds; | |
| end; | |
| type | |
| [TestFixture] | |
| TDoubleParseTests = class(TObject) | |
| strict private | |
| type | |
| TNumber = Double; | |
| public | |
| [Test] | |
| procedure ParseMaxValueSucceeds; | |
| [Test] | |
| procedure ParseMinValueSucceeds; | |
| end; | |
| type | |
| [TestFixture] | |
| TExtendedParseTests = class(TObject) | |
| strict private | |
| type | |
| TNumber = Extended; | |
| public | |
| [Test] | |
| procedure ParseMaxValueSucceeds; | |
| [Test] | |
| procedure ParseMinValueSucceeds; | |
| end; | |
| implementation | |
| uses | |
| System.SysConst, | |
| System.SysUtils; | |
| procedure Cardinal_Parse_High_Cardinal_Succeeds(); | |
| var | |
| Expected: Cardinal; | |
| Value: string; | |
| Actual: Cardinal; | |
| begin | |
| Expected := High(Cardinal); | |
| Value := Expected.ToString(); | |
| Actual := Cardinal.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| { TSmallIntParseTests } | |
| procedure TSmallIntParseTests.ParseMaxValueSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := TNumber.MaxValue; | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| procedure TSmallIntParseTests.ParseMinValueSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := TNumber.MinValue; | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| { TShortIntParseTests } | |
| procedure TShortIntParseTests.ParseMaxValueSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := TNumber.MaxValue; | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| procedure TShortIntParseTests.ParseMinValueSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := TNumber.MinValue; | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| { TIntegerParseTests } | |
| procedure TIntegerParseTests.ParseMaxValueSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := TNumber.MaxValue; | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| procedure TIntegerParseTests.ParseMinValueSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := TNumber.MinValue; | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| { TInt64ParseTests } | |
| procedure TInt64ParseTests.ParseMaxValueSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := TNumber.MaxValue; | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| procedure TInt64ParseTests.ParseMinValueSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := TNumber.MinValue; | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| { TNativeIntParseTests } | |
| procedure TNativeIntParseTests.ParseMaxValueSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := TNumber.MaxValue; | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| procedure TNativeIntParseTests.ParseMinValueSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := TNumber.MinValue; | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| { TByteParseTests } | |
| procedure TByteParseTests.ParseMaxValueSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := TNumber.MaxValue; | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| procedure TByteParseTests.ParseMinValueSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := TNumber.MinValue; | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| { TWordParseTests } | |
| procedure TWordParseTests.ParseMaxValueSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := TNumber.MaxValue; | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| procedure TWordParseTests.ParseMinValueSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := TNumber.MinValue; | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| { TCardinalParseTests } | |
| procedure TCardinalParseTests.ParseMaxValueSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := TNumber.MaxValue; | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| procedure TCardinalParseTests.ParseMinValueSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := TNumber.MinValue; | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| { TUInt64ParseTests } | |
| procedure TUInt64ParseTests.ParseMaxValueSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := TNumber.MaxValue; | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| procedure TUInt64ParseTests.ParseMinValueSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := TNumber.MinValue; | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| { TNativeUIntParseTests } | |
| procedure TNativeUIntParseTests.ParseMaxValueSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := TNumber.MaxValue; | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| procedure TNativeUIntParseTests.ParseMinValueSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := TNumber.MinValue; | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| { TBooleanParseTests } | |
| procedure TBooleanParseTests.ParseHighSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := High(TNumber); | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| procedure TBooleanParseTests.ParseLowSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := Low(TNumber); | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| { TByteBoolParseTests } | |
| procedure TByteBoolParseTests.ParseHighSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := High(TNumber); | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| procedure TByteBoolParseTests.ParseLowSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := Low(TNumber); | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| { TWordBoolParseTests } | |
| procedure TWordBoolParseTests.ParseHighSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := High(TNumber); | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| procedure TWordBoolParseTests.ParseLowSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := Low(TNumber); | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| { TLongBoolParseTests } | |
| procedure TLongBoolParseTests.ParseHighSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := High(TNumber); | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| procedure TLongBoolParseTests.ParseLowSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := Low(TNumber); | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| { TSingleParseTests } | |
| procedure TSingleParseTests.ParseMaxValueSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := TNumber.MaxValue; | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| procedure TSingleParseTests.ParseMinValueSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := TNumber.MinValue; | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| { TDoubleParseTests } | |
| procedure TDoubleParseTests.ParseMaxValueSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := TNumber.MaxValue; | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| procedure TDoubleParseTests.ParseMinValueSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := TNumber.MinValue; | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| { TExtendedParseTests } | |
| procedure TExtendedParseTests.ParseMaxValueSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := TNumber.MaxValue; | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| procedure TExtendedParseTests.ParseMinValueSucceeds; | |
| var | |
| Expected: TNumber; | |
| Value: string; | |
| Actual: TNumber; | |
| begin | |
| Expected := TNumber.MinValue; | |
| Value := Expected.ToString(); | |
| Actual := TNumber.Parse(Value); | |
| Assert.AreEqual(Expected, Actual); | |
| end; | |
| initialization | |
| TDUnitX.RegisterTestFixture(TSmallIntParseTests); | |
| TDUnitX.RegisterTestFixture(TShortIntParseTests); | |
| TDUnitX.RegisterTestFixture(TIntegerParseTests); | |
| TDUnitX.RegisterTestFixture(TInt64ParseTests); | |
| TDUnitX.RegisterTestFixture(TNativeIntParseTests); | |
| TDUnitX.RegisterTestFixture(TByteParseTests); | |
| TDUnitX.RegisterTestFixture(TWordParseTests); | |
| TDUnitX.RegisterTestFixture(TCardinalParseTests); | |
| TDUnitX.RegisterTestFixture(TUInt64ParseTests); | |
| TDUnitX.RegisterTestFixture(TNativeUIntParseTests); | |
| TDUnitX.RegisterTestFixture(TBooleanParseTests); | |
| TDUnitX.RegisterTestFixture(TByteBoolParseTests); | |
| TDUnitX.RegisterTestFixture(TWordBoolParseTests); | |
| TDUnitX.RegisterTestFixture(TLongBoolParseTests); | |
| TDUnitX.RegisterTestFixture(TSingleParseTests); | |
| TDUnitX.RegisterTestFixture(TDoubleParseTests); | |
| TDUnitX.RegisterTestFixture(TExtendedParseTests); | |
| end. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| DUnitX – [SysUtilsParseMethodsFailWithBoundaryValuesProject.exe] – Starting Tests. | |
| .E.E.E.E………………………..F.F………E………..E…… | |
| Tests Found : 34 | |
| Tests Ignored : 0 | |
| Tests Passed : 26 | |
| Tests Leaked : 0 | |
| Tests Failed : 2 | |
| Tests Errored : 6 | |
| Failing Tests | |
| SysUtilsParseTests.TExtendedParseTests.ParseMaxValueSucceeds | |
| Message: Expected 1,18973149535723E4932 but got 1,18973149535723E4932 | |
| SysUtilsParseTests.TExtendedParseTests.ParseMinValueSucceeds | |
| Message: Expected -1,18973149535723E4932 but got -1,18973149535723E4932 | |
| Tests With Errors | |
| SysUtilsParseTests.TSingleParseTests.ParseMaxValueSucceeds | |
| Message: '3,40282346638529E38' is not a valid floating point value | |
| SysUtilsParseTests.TSingleParseTests.ParseMinValueSucceeds | |
| Message: '-3,40282346638529E38' is not a valid floating point value | |
| SysUtilsParseTests.TDoubleParseTests.ParseMaxValueSucceeds | |
| Message: '1,79769313486232E308' is not a valid floating point value | |
| SysUtilsParseTests.TDoubleParseTests.ParseMinValueSucceeds | |
| Message: '-1,79769313486232E308' is not a valid floating point value | |
| SysUtilsParseTests.TCardinalParseTests.ParseMaxValueSucceeds | |
| Message: '4294967295' is not a valid integer value | |
| SysUtilsParseTests.TNativeUIntParseTests.ParseMaxValueSucceeds | |
| Message: '4294967295' is not a valid integer value | |
| Done.. press <Enter> key to quit. |






Rudy Velthuis said
If you look at the code for TCardinalHelper.Parse, it is not really surprise that anything above MaxInt gets rejected:
jpluimers said
Indeed. It is too bad that there are way more of those “not really surprising” things in the RTL, let alone VCL and FMX.
Wodzu said
Amazing, Embarcadero haven’t wrote tests for their own stuff… good job Jeroen!
Remy Lebeau said
Reported to QualityPortal as RSP-16026 “Parse() methods of Integral helper classes raise errors for valid values”, https://quality.embarcadero.com/browse/RSP-16026
jpluimers said
Thanks. Note it’s not just Integral helper classes: floating point classes suffer too.
Unit tests are in https://bitbucket.org/jeroenp/besharp.net/commits/65b089e9d458458579ed253d227de344c917da31