Skip to content

Commit 83d8fcf

Browse files
committed
Backend(,Tests): stop using binary serialization
We had a need in the past to serialize exceptions (as they could happen in off-line mode, when running as a cold-storage device), so that they could be reported later when the device comes online, but exceptions can't be seralizated to JSON (as explained in [1]), so we ended up using binary serialization (hooking it up in this past commit[2]). However, binary serialization is going away in .NET9[3] because of its potential security risk. Even though we doubt that for our use case we would be affected by this security vector, we: - Want to be prepared for the future. - Know that there were anyway edge cases where binary serialization was not actually working (e.g. see bug 240), and was causing crashes. We explored the idea of contributing an IException interface to the 'sentry-dotnet' repo [4] (this library is the replacement of SharpRaven, see [5]), so that we can serialize exceptions easily in JSON, for later deserializing them and send them straight to Sentry's API for report purposes, however: * We found adding the IException overloads to be extremely complicated due to the sheer amount of unit tests and things that Sentry has, that would need to be modified. * Given the above, we thought it would be too much work, and too much risk of not being accepted upstream. * Even if the IException overloads were accepted, the approach would still be a leaky abstraction because the type of the exception cannot be properly represented in a hypothetical IException's property, so we were/would ending up with hacky things such as an IsAggregateException:bool property, for example. But why end here and not have more bool types for other exceptions? Instead of the above nightmare we have decided to go for the simplest approach of all (the one that I should have done 3ish years ago when I was initially solving this problem, to avoid any OVERENGINEERING): just use good old Exception.ToString() method! This method provides, not only the type of the exception and its .Message property, also all its inner exceptions recursively. This is GOOD ENOUGH. Fixes #240 Closes https://gitlab.com/nblockchain/geewallet/-/issues/174 [1] 403d5c7 [2] 1f7b3b7 [3] https://twitter.com/SitnikAdam/status/1746874459640811575 [4] https://github.com/getsentry/sentry-dotnet [5] #252
1 parent 417b4d1 commit 83d8fcf

17 files changed

+206
-380
lines changed
Lines changed: 94 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
namespace GWallet.Backend.Tests
22

33
open System
4-
open System.Runtime.Serialization
54

65
open NUnit.Framework
76

87
open GWallet.Backend
98

109

11-
type CustomExceptionWithoutSerializationCtor =
10+
type CustomExceptionWithoutInnerExceptionCtor =
1211
inherit Exception
1312

1413
new(message) =
@@ -17,8 +16,6 @@ type CustomExceptionWithoutSerializationCtor =
1716
type CustomException =
1817
inherit Exception
1918

20-
new(info: SerializationInfo, context: StreamingContext) =
21-
{ inherit Exception(info, context) }
2219
new(message: string, innerException: CustomException) =
2320
{ inherit Exception(message, innerException) }
2421
new(message) =
@@ -68,17 +65,17 @@ type ExceptionMarshalling () =
6865
cex
6966
Marshalling.Serialize ex
7067

71-
let msg = "Exceptions didn't match. Full binary form was "
68+
let msg = "Expected Exception.ToString() differs from actual"
7269
#if LEGACY_FRAMEWORK
73-
let legacyMsg = "(Legacy)Exceptions didn't match. Full binary form was "
70+
let legacyIgnoreMsg = "Mono or old .NETFramework might vary slightly; there's no need to really do any regression testing here"
7471
#endif
7572

7673
[<Test>]
7774
member __.``can serialize basic exceptions``() =
7875
let json = SerializeBasicException ()
7976
Assert.That(json, Is.Not.Null)
80-
Assert.That(json, Is.Not.Empty)
81-
Assert.That(MarshallingData.SerializedExceptionsAreSame json MarshallingData.BasicExceptionExampleInJson false msg)
77+
Assert.That(json.Trim(), Is.Not.Empty)
78+
Assert.That(MarshallingData.SerializedExceptionsAreSame json MarshallingData.BasicExceptionExampleInJson msg)
8279

8380
[<Test>]
8481
member __.``can deserialize basic exceptions``() =
@@ -90,31 +87,24 @@ type ExceptionMarshalling () =
9087
Assert.Inconclusive "Fix the serialization test first"
9188
failwith "unreachable"
9289

93-
let ex: Exception = Marshalling.Deserialize basicExSerialized
90+
let ex: MarshalledException = Marshalling.Deserialize basicExSerialized
9491
Assert.That(ex, Is.Not.Null)
95-
Assert.That(ex, Is.InstanceOf<Exception>())
96-
Assert.That(ex.Message, Is.EqualTo "msg")
97-
Assert.That(ex.InnerException, Is.Null)
98-
Assert.That(ex.StackTrace, Is.Null)
92+
Assert.That(ex, Is.InstanceOf<MarshalledException>())
93+
Assert.That(ex.FullDescription.Trim().Length, Is.GreaterThan 0)
94+
Assert.That(
95+
MarshallingData.Sanitize ex.FullDescription,
96+
Is.EqualTo (MarshallingData.Sanitize "System.Exception: msg")
97+
)
9998

10099
[<Test>]
101100
member __.``can serialize real exceptions``() =
102101
let json = SerializeRealException ()
103102
Assert.That(json, Is.Not.Null)
104-
Assert.That(json, Is.Not.Empty)
103+
Assert.That(json.Trim(), Is.Not.Empty)
105104
#if !LEGACY_FRAMEWORK
106-
Assert.That(MarshallingData.SerializedExceptionsAreSame json MarshallingData.RealExceptionExampleInJson false msg)
105+
Assert.That(MarshallingData.SerializedExceptionsAreSame json MarshallingData.RealExceptionExampleInJson msg)
107106
#else
108-
if Config.IsWindowsPlatform () then
109-
let serializedExceptionsAreSame =
110-
try
111-
MarshallingData.SerializedExceptionsAreSame json MarshallingData.RealExceptionExampleInJson false msg
112-
with
113-
| :? AssertionException ->
114-
MarshallingData.SerializedExceptionsAreSame json MarshallingData.RealExceptionWindowsLegacyExampleInJson false legacyMsg
115-
Assert.That serializedExceptionsAreSame
116-
else
117-
Assert.That(MarshallingData.SerializedExceptionsAreSame json MarshallingData.RealExceptionUnixLegacyExampleInJson false legacyMsg)
107+
Assert.Ignore legacyIgnoreMsg
118108
#endif
119109

120110
[<Test>]
@@ -127,20 +117,29 @@ type ExceptionMarshalling () =
127117
Assert.Inconclusive "Fix the serialization test first"
128118
failwith "unreachable"
129119

130-
let ex: Exception = Marshalling.Deserialize realExceptionSerialized
120+
let ex: MarshalledException = Marshalling.Deserialize realExceptionSerialized
131121
Assert.That(ex, Is.Not.Null)
132-
Assert.That(ex, Is.InstanceOf<Exception>())
133-
Assert.That(ex.Message, Is.EqualTo "msg")
134-
Assert.That(ex.InnerException, Is.Null)
135-
Assert.That(ex.StackTrace, Is.Not.Null)
136-
Assert.That(ex.StackTrace, Is.Not.Empty)
122+
Assert.That(ex, Is.InstanceOf<MarshalledException>())
123+
Assert.That(ex.FullDescription.Trim().Length, Is.GreaterThan 0)
124+
#if !LEGACY_FRAMEWORK
125+
let expected =
126+
sprintf
127+
"System.Exception: msg at GWallet.Backend.Tests.ExceptionMarshalling.SerializeRealException() in %s/ExceptionMarshalling.fs:line 38"
128+
MarshallingData.ThisProjPath
129+
Assert.That(
130+
MarshallingData.Sanitize ex.FullDescription,
131+
Is.EqualTo (MarshallingData.Sanitize expected)
132+
)
133+
#else
134+
Assert.Ignore legacyIgnoreMsg
135+
#endif
137136

138137
[<Test>]
139138
member __.``can serialize inner exceptions``() =
140139
let json = SerializeInnerException ()
141140
Assert.That(json, Is.Not.Null)
142-
Assert.That(json, Is.Not.Empty)
143-
Assert.That(MarshallingData.SerializedExceptionsAreSame json MarshallingData.InnerExceptionExampleInJson false msg)
141+
Assert.That(json.Trim(), Is.Not.Empty)
142+
Assert.That(MarshallingData.SerializedExceptionsAreSame json MarshallingData.InnerExceptionExampleInJson msg)
144143

145144
[<Test>]
146145
member __.``can deserialize inner exceptions``() =
@@ -152,31 +151,28 @@ type ExceptionMarshalling () =
152151
Assert.Inconclusive "Fix the serialization test first"
153152
failwith "unreachable"
154153

155-
let ex: Exception = Marshalling.Deserialize innerExceptionSerialized
154+
let ex: MarshalledException = Marshalling.Deserialize innerExceptionSerialized
156155
Assert.That (ex, Is.Not.Null)
157-
Assert.That (ex, Is.InstanceOf<Exception>())
158-
Assert.That (ex.Message, Is.EqualTo "msg")
159-
Assert.That (ex.StackTrace, Is.Null)
160-
Assert.That (ex.InnerException, Is.Not.Null)
161-
162-
Assert.That (ex.InnerException, Is.InstanceOf<Exception>())
163-
Assert.That (ex.InnerException.Message, Is.EqualTo "innerMsg")
164-
Assert.That (ex.InnerException.StackTrace, Is.Null)
156+
Assert.That (ex, Is.InstanceOf<MarshalledException>())
157+
Assert.That(ex.FullDescription.Trim().Length, Is.GreaterThan 0)
158+
Assert.That (
159+
MarshallingData.Sanitize ex.FullDescription,
160+
Is.EqualTo (MarshallingData.Sanitize "System.Exception: msg ---> System.Exception: innerMsg --- End of inner exception stack trace ---")
161+
)
165162

166163
[<Test>]
167164
member __.``can serialize custom exceptions``() =
168165
let json = SerializeCustomException ()
169166
Assert.That(json, Is.Not.Null)
170-
Assert.That(json, Is.Not.Empty)
171-
Assert.That(MarshallingData.SerializedExceptionsAreSame json MarshallingData.CustomExceptionExampleInJson false msg)
167+
Assert.That(json.Trim(), Is.Not.Empty)
168+
Assert.That(MarshallingData.SerializedExceptionsAreSame json MarshallingData.CustomExceptionExampleInJson msg)
172169

173170
[<Test>]
174-
member __.``serializing custom exception not prepared for binary serialization, throws``() =
175-
let exToSerialize = CustomExceptionWithoutSerializationCtor "msg"
176-
let ex: MarshallingCompatibilityException =
177-
Assert.Throws(fun _ -> Marshalling.Serialize exToSerialize |> ignore<string>)
178-
Assert.That(ex, Is.TypeOf<MarshallingCompatibilityException>())
179-
Assert.That(ex.Message, IsString.WhichContains "GWallet.Backend.Tests.CustomExceptionWithoutSerializationCtor")
171+
member __.``serializing custom exception without inner ex ctor does not crash``() =
172+
let exToSerialize = CustomExceptionWithoutInnerExceptionCtor "msg"
173+
let serializedEx = (Marshalling.Serialize exToSerialize).Trim()
174+
Assert.That(serializedEx, Is.Not.Null)
175+
Assert.That(serializedEx.Trim().Length, Is.GreaterThan 0)
180176

181177
[<Test>]
182178
member __.``can deserialize custom exceptions``() =
@@ -188,21 +184,24 @@ type ExceptionMarshalling () =
188184
Assert.Inconclusive "Fix the serialization test first"
189185
failwith "unreachable"
190186

191-
let ex: Exception = Marshalling.Deserialize customExceptionSerialized
187+
let ex: MarshalledException = Marshalling.Deserialize customExceptionSerialized
192188
Assert.That(ex, Is.Not.Null)
193-
Assert.That(ex, Is.InstanceOf<CustomException>())
194-
Assert.That(ex.Message, Is.EqualTo "msg")
195-
Assert.That(ex.InnerException, Is.Null)
196-
Assert.That(ex.StackTrace, Is.Null)
189+
Assert.That(ex, Is.InstanceOf<MarshalledException>())
190+
Assert.That(
191+
MarshallingData.Sanitize ex.FullDescription,
192+
Is.EqualTo (MarshallingData.Sanitize "GWallet.Backend.Tests.CustomException: msg")
193+
)
197194

198195
[<Test>]
199196
member __.``can serialize F# custom exceptions``() =
200197
let json = SerializeCustomFSharpException ()
201198
Assert.That(json, Is.Not.Null)
202-
Assert.That(json, Is.Not.Empty)
203-
204-
// strangely enough, message would be different between linux_vanilla_dotnet6 and other dotnet6 configs (e.g. Windows, macOS, Linux-github)
205-
Assert.That(MarshallingData.SerializedExceptionsAreSame json MarshallingData.CustomFSharpExceptionExampleInJson true msg)
199+
Assert.That(json.Trim(), Is.Not.Empty)
200+
#if !LEGACY_FRAMEWORK
201+
Assert.That(MarshallingData.SerializedExceptionsAreSame json MarshallingData.CustomFSharpExceptionExampleInJson msg)
202+
#else
203+
Assert.Ignore legacyIgnoreMsg
204+
#endif
206205

207206
[<Test>]
208207
member __.``can deserialize F# custom exceptions``() =
@@ -214,36 +213,34 @@ type ExceptionMarshalling () =
214213
Assert.Inconclusive "Fix the serialization test first"
215214
failwith "unreachable"
216215

217-
let ex: Exception = Marshalling.Deserialize customExceptionSerialized
216+
let ex: MarshalledException = Marshalling.Deserialize customExceptionSerialized
218217
Assert.That(ex, Is.Not.Null)
219-
Assert.That(ex, Is.InstanceOf<CustomFSharpException>())
220-
Assert.That(ex.Message, Is.Not.Null)
221-
Assert.That(ex.Message, Is.Not.Empty)
222-
Assert.That(ex.InnerException, Is.Null)
223-
Assert.That(ex.StackTrace, Is.Null)
218+
Assert.That(ex, Is.InstanceOf<MarshalledException>())
219+
Assert.That(ex.FullDescription.Trim().Length, Is.GreaterThan 0)
220+
221+
if ex.FullDescription.Contains "of type" then
222+
// old version of .NET6? (happens in stockdotnet6 CI lanes)
223+
Assert.That(
224+
MarshallingData.Sanitize ex.FullDescription,
225+
Is.EqualTo (MarshallingData.Sanitize "GWallet.Backend.Tests.CustomFSharpException: Exception of type 'GWallet.Backend.Tests.CustomFSharpException' was thrown.")
226+
)
227+
else
228+
Assert.That(
229+
MarshallingData.Sanitize ex.FullDescription,
230+
Is.EqualTo (MarshallingData.Sanitize "GWallet.Backend.Tests.CustomFSharpException: CustomFSharpException")
231+
)
224232

225-
// TODO: test marshalling custom exceptions with custom properties/fields, and custom F# exception with subtypes
226233

227234
[<Test>]
228235
member __.``can serialize full exceptions (all previous features combined)``() =
229236
let json = SerializeFullException ()
230237

231238
Assert.That(json, Is.Not.Null)
232-
Assert.That(json, Is.Not.Empty)
233-
239+
Assert.That(json.Trim(), Is.Not.Empty)
234240
#if !LEGACY_FRAMEWORK
235-
Assert.That(MarshallingData.SerializedExceptionsAreSame json MarshallingData.FullExceptionExampleInJson false msg)
241+
Assert.That(MarshallingData.SerializedExceptionsAreSame json MarshallingData.FullExceptionExampleInJson msg)
236242
#else
237-
if Config.IsWindowsPlatform () then
238-
let serializedExceptionsAreSame =
239-
try
240-
MarshallingData.SerializedExceptionsAreSame json MarshallingData.FullExceptionExampleInJson false msg
241-
with
242-
| :? AssertionException ->
243-
MarshallingData.SerializedExceptionsAreSame json MarshallingData.FullExceptionWindowsLegacyExampleInJson false legacyMsg
244-
Assert.That serializedExceptionsAreSame
245-
else
246-
Assert.That(MarshallingData.SerializedExceptionsAreSame json MarshallingData.FullExceptionUnixLegacyExampleInJson false legacyMsg)
243+
Assert.Ignore legacyIgnoreMsg
247244
#endif
248245

249246
[<Test>]
@@ -256,12 +253,23 @@ type ExceptionMarshalling () =
256253
Assert.Inconclusive "Fix the serialization test first"
257254
failwith "unreachable"
258255

259-
let ex: Exception = Marshalling.Deserialize fullExceptionSerialized
256+
let ex: MarshalledException = Marshalling.Deserialize fullExceptionSerialized
260257
Assert.That(ex, Is.Not.Null)
261-
Assert.That(ex, Is.InstanceOf<CustomException> ())
262-
Assert.That(ex.Message, Is.Not.Null)
263-
Assert.That(ex.Message, Is.Not.Empty)
264-
Assert.That(ex.InnerException, Is.Not.Null)
265-
Assert.That(ex.StackTrace, Is.Not.Null)
266-
Assert.That(ex.StackTrace, Is.Not.Empty)
258+
Assert.That(ex, Is.InstanceOf<MarshalledException> ())
259+
Assert.That(ex.FullDescription.Trim().Length, Is.GreaterThan 0)
260+
261+
#if !LEGACY_FRAMEWORK
262+
Assert.That(
263+
MarshallingData.Sanitize ex.FullDescription,
264+
Is.EqualTo (
265+
MarshallingData.Sanitize
266+
<| sprintf
267+
"GWallet.Backend.Tests.CustomException: msg ---> GWallet.Backend.Tests.CustomException: innerMsg --- End of inner exception stack trace --- at GWallet.Backend.Tests.ExceptionMarshalling.SerializeFullException() in %s/ExceptionMarshalling.fs:line 61"
268+
MarshallingData.ThisProjPath
269+
)
270+
)
271+
#else
272+
Assert.Ignore legacyIgnoreMsg
273+
#endif
274+
267275

src/GWallet.Backend.Tests/GWallet.Backend.Tests-legacy.fsproj

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,15 +75,10 @@
7575
<EmbeddedResource Include="data\unsignedAndFormattedEtherTransaction.json" />
7676
<EmbeddedResource Include="data\basicException.json" />
7777
<EmbeddedResource Include="data\realException.json" />
78-
<EmbeddedResource Include="data\realException_unixLegacy.json" />
79-
<EmbeddedResource Include="data\realException_windowsLegacy.json" />
8078
<EmbeddedResource Include="data\innerException.json" />
8179
<EmbeddedResource Include="data\customException.json" />
8280
<EmbeddedResource Include="data\customFSharpException.json" />
83-
<EmbeddedResource Include="data\customFSharpException_legacy.json" />
8481
<EmbeddedResource Include="data\fullException.json" />
85-
<EmbeddedResource Include="data\fullException_unixLegacy.json" />
86-
<EmbeddedResource Include="data\fullException_windowsLegacy.json" />
8782
<Compile Include="ElectrumIntegrationTests.fs" />
8883
<Compile Include="WarpWallet.fs" />
8984
<Compile Include="CompoundBalanceCaching.fs" />

src/GWallet.Backend.Tests/GWallet.Backend.Tests.fsproj

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,13 @@
3939
<ItemGroup>
4040
<EmbeddedResource Include="data\basicException.json" />
4141
<EmbeddedResource Include="data\customFSharpException.json" />
42-
<EmbeddedResource Include="data\customFSharpException_legacy.json" />
4342
<EmbeddedResource Include="data\realException.json" />
44-
<EmbeddedResource Include="data\realException_unixLegacy.json" />
45-
<EmbeddedResource Include="data\realException_windowsLegacy.json" />
4643
<EmbeddedResource Include="data\signedAndFormattedEtherTransaction.json" />
4744
<EmbeddedResource Include="data\customException.json" />
4845
<EmbeddedResource Include="data\unsignedAndFormattedSaiTransaction.json" />
4946
<EmbeddedResource Include="data\unsignedAndFormattedBtcTransaction.json" />
5047
<EmbeddedResource Include="data\unsignedAndFormattedEtherTransaction.json" />
5148
<EmbeddedResource Include="data\fullException.json" />
52-
<EmbeddedResource Include="data\fullException_unixLegacy.json" />
53-
<EmbeddedResource Include="data\fullException_windowsLegacy.json" />
5449
<EmbeddedResource Include="data\signedAndFormattedBtcTransaction.json" />
5550
<EmbeddedResource Include="data\signedAndFormattedSaiTransaction.json" />
5651
<EmbeddedResource Include="data\innerException.json" />

0 commit comments

Comments
 (0)