Reputation: 5827
If you write something like
this.MyMethod(this.MyMethod(myParam)).Should().BeNull();
using the FluentAssertions library, an exception is thrown when the assertion fails, with the message
Expected this.MyMethod(this.MyMethod(myParam)) to be <null>, but found "foo".
How does it manage to get the syntactic context of the method call in order to produce such a meaningful exception message? Obviously some kind of reflection is used and probably some stack trace inspection (indeed, it only works properly in Debug mode and when the expression is written in a single line), but how exactly?
More specifically, how to write a method with the signature
public static string GetSyntacticContext(object parameter);
satisfying (in Debug mode) the following?
GetSyntacticContext("foo")
.Should().Be("\"foo\"");
GetSyntacticContext(myParam + 1)
.Should().Be("myParam + 1");
GetSyntacticContext(this.MyMethod(this.MyMethod(myParam)))
.Should().Be("this.MyMethod(this.MyMethod(myParam))");
Upvotes: 3
Views: 54
Reputation: 66604
You mention that it only works in Debug mode and only when the expression is written in a single line. Indeed exception stacktraces in Debug mode contain the full path to the source file and the line number of every stack frame leading up to the place where the exception occurred.
The library can therefore read that line from the file and examine it. It might do a full expression parse of the line, but it’s more likely they went for the quick-and-dirty route of using regular expressions. All it really needs to do in all of the examples you quoted is to remove .Should()
and everything after it.
In your position I would modify the source files slightly (e.g. vary the spacing and/or add some /* comments */ in the middle of the line) to see if the exception message retains those. If it does, we know that it reads from the source file. If it doesn’t, I’m willing to bet that it still reads from the source file but performs a syntactic parse of the relevant line and reconstructs it.
Your hypothetical GetSyntacticContext
method might do something similar: obtain the current stack frame with its file name/line number information and read that line from the file. Something like this:
public static string GetSyntacticContext(object parameter)
{
var st = new StackTrace(fNeedFileInfo: true);
var frame = st.GetFrame(1);
return File.ReadLines(frame.GetFileName()).Skip(frame.GetFileLineNumber() - 1).FirstOrDefault();
}
Note that this example does not make any use of the parameter
parameter. In this example I have not attempted to detect the call to GetSyntacticContext
and extract the code inside the method call syntax; it just returns the whole line of code, indentation and all.
As an aside, the StackFrame
type has a GetMethod()
method too, so you can obtain a runtime reference to the method that contains the relevant call to GetSyntacticContext
. Not sure what you might do with that, but you could obtain the IL code for it, and/or analyze the contents of a PDB file accompanying the EXE file. This wouldn’t really give you actual C# source code though; at best the PDB file contains the same filename/line number information that you already have, and it would still not allow you to distinguish between multiple calls to GetSyntacticContext
occurring within the same statement.
Upvotes: 5