I got started with the errata for Beta 1.0 of Dart for
One of my tests is failing. Or maybe not.
I had all of my tests passing with Dart 1.5.8, but with 1.6.0, I find:
CONSOLE MESSAGE: FAIL: [bad invocation from noSuchMethod] cannot supply invocation mirror arguments Expected: throws 'Yikes _ajaxSave' Actual: <Closure: () => dynamic> Which: threw 'Yikes _ajaxSave")' package:unittest/src/simple_configuration.dart 119:7 SimpleConfiguration.onExpectFailure package:unittest/src/simple_configuration.dart 15:28 _ExpectFailureHandler.fail package:matcher/src/expect.dart 113:9 DefaultFailureHandler.failMatch package:matcher/src/expect.dart 73:29 expect ../varying_the_behavior/test/calling_methods_from_no_such_method.dart 38:13 run.<fn>.<fn> package:unittest/src/test_case.dart 102:37 _run.<fn>Which is just weird, right? I expect this code throws
'Yikes _ajaxSave'
. I find that, when run under test, it threw 'Yikes _ajaxSave"
. And this is counted as a test failure?!I actually first noticed this error back when Dart 1.6 was still in beta. I spent a little time investigating any changes to throwing errors in 1.6, but could not find any. In the end, I thought to wait on the possibility that the language had a bug that would get sorted. That turns out not to be the case.
Since 1.6.0 arrived, I have been ignoring this as best I can so that I can focus on changes to the text of the book. It helps knowing that I will always have time to investigate in depth during these chain posts. So I get down to it.
The test is for some of Dart's dynamic code capabilities. In this test case, I have a method that reaches
noSuchMethod()
in a call. In this particular noSuchMethod()
, I handle calls to #save
, but any other calls should result in an error:class HipsterModel {
noSuchMethod(args) {
if (args.memberName != #save) {
var method_name = args.memberName.toString().
replaceAll('Symbol("', '').
replaceAll(new RegExp(r'@.+'), '');
throw "Yikes ${method_name}";
}
}
}
The problem turns out not to be a bug, but a change to the way that symbols are converted to strings. Had I looked closer at the failure, I might have noticed that the message includes trailing double quotes. In other words, the output of toString()
on a Symbol
is different and I am no longer handling it properly.I believe that the old format of
Symbol
toString()
looked like Symbol("_ajaxSave@XXX")
. I don't recall what information was embedded in the XXX
part of the string, but the following code would try to strip away the data so that a “clean” failure could be thrown (and ultimately tested): if (args.memberName != #save) {
var method_name = args.memberName.toString().
replaceAll('Symbol("', '').
replaceAll(new RegExp(r'@.+'), '');
throw "Yikes ${method_name}";
}
In Dart 1.6, calling toString()
on a Symbol
results in something like: Symbol("_ajaxSave")
. I cannot argue with the change—this seems cleaner. And easier to convert into a string. In fact, I can use replaceAllMapped()
to extract a regular expression placeholder to do it: if (args.memberName != #save) {
var method_name = args.memberName.toString().
replaceAllMapped(
new RegExp(r'Symbol\("(.+)"\)'),
(m)=> m[1]
);
throw "Yikes ${method_name}";
}
The language designers make converting Symbol
objects to strings intentionally awkward so that developers do not break symbol lookup when code is converted to JavaScript. If this were production code, I might just embed the full Symbol#toString()
output in the error. Or allow Dart's normal noSuchMethod()
handling to kick in. But, since this helps to test book code, I leave it.With that, I have all my tests passing and
dartanalyzer
static analysis passing as well. So I can back to fixing errata.Day #176
You should use MirrorSystem.getName(), which will work when compiled with dart2js (though it will bloat your output by including the symbol map).
ReplyDeleteAh, good point. I had meant to point to the SO article on this (http://stackoverflow.com/questions/16058505/converting-a-symbol-into-a-string) which recommends MirrorSystem.getName(), but just forgot. So thanks for reminding me.
DeleteThe only reason I didn't use that solution was fear that it would make my book acceptance tests slow (~2 seconds wall time currently). I didn't even try it -- I just assume that any mirrors code will automatically make things slow (bad developer). I just tried this and my fears were unfounded. The tests run just as fast and now this one is future proofed against future changes to Symbol's string representation. Someday, I won't assume.