Thursday, September 5, 2013

Regression Testing of JavaScript Load Order (with Dart)

Among the many nice things about Dart is that programmers no longer need to worry about latency and script load order. There is one main() entry point, which fires when all code is loaded and the DOM is ready. It really is wonderful… except when you have to rely on legacy JavaScript code that still does this kind of thing.

Unfortunately, the ICE Code Editor is just such a beast. I already have Future code that handles loading the JavaScript files for the editor part. But I do not handle the loading of Gzip JavaScript libraries nearly as well (Gzip is available in Dart, but not in the browser).

As I found last night, it is quite hard to reliably reproduce circumstances in which the Gzip JavaScript libraries load order breaks ICE. But I was finally able to do it with a combination of HTML5's application cache and artificial latency. The general idea is to place the JavaScript file or files that need to load sooner than others into application cache. By the time these are requested in the natural <script> load order, they are already in cache. This effectively simulates loading these files over a warm HTTP tube (see SPDY Book for more info on warm HTTP tubes).

Today, I would like to build on that by writing a test that exercises the bug in question. The first thing that I am going to need is a way to run my test from a web server—the application cache stuff will not work with the usual file:// based tests. It just so happens that Pub, the package management tool for Dart, contains a minimal web server that ough to be sufficient.

Instead of starting pub serve on the default port of 8080, I start it on port 8008 to avoid other servers that I might be running:
# Start the pub web server
pub serve --port=8008 &
I grab the PID of the server so that I can kill it when my tests have completed.

Next, I need some latency. This is all running on localhost which has no latency at all. Without a pretty decent amount of latency, I am unable to see the desired behavior. The Linux tc utility to the rescue:
# Add ridiculous latency
sudo tc qdisc add dev lo root netem delay 100ms
With the setup out of the way, I can run my actual test. That is just doing the usual with Chrome's content_shell:
# Run a set of Dart Unit tests
results=$(content_shell --dump-render-tree http://localhost:8008/test/latency.html)
echo -e "$results"
Well, not quite usual—now I am running against the web server running on port 8008.

After running the test—and before checking the results (and possibly exiting)—I remove the latency and shutdown the server:
# Never forget to remove latency!
sudo tc qdisc del dev lo root

# Stop the pub web server
kill $pub_serve_pid
With that, I am ready to run my latency test:
➜  ice-code-editor git:(master) ✗ ./test/
Serving ice_code_editor on http://localhost:8008
Build completed successfully
GET /test/latency.html -> File "/home/chris/repos/ice-code-editor/web/test/latency.html"
GET /test/latency.appcache -> File "/home/chris/repos/ice-code-editor/web/test/latency.appcache"
GET /test/packages/ice_code_editor/js/ace/ace.js -> File "/home/chris/repos/ice-code-editor/lib/js/ace/ace.js"
CONSOLE MESSAGE: FAIL: Opening Shared Link shared content is opened in the editor
  Expected: 'Howdy, Bob!'
    Actual: ''
     Which: is different. Both strings start the same, but the given value is missing the following trailing characters: Howdy, Bob ...
CONSOLE MESSAGE: Exception: Exception: Some tests failed.
CONSOLE MESSAGE: Uncaught Error: NoSuchMethodError : method not found: 'RawDeflate'
Receiver: top-level
Arguments: []
#0      Proxy._forward (package:js/js.dart:1038:20)
#1      Proxy.noSuchMethod (package:js/js.dart:1027:20)
#2      Gzip.decode (package:ice_code_editor/gzip.dart:12:23)
#3      Full._openProject (package:ice_code_editor/full.dart:239:28)
#4      Full.Full.<anonymous closure> (package:ice_code_editor/full.dart:25:32)
That is exactly what I wanted to see. It is the same error that I am seeing in production. In production, I only see that error on occasion and only on first load and only with certain browsers. But in test, I have successfully got the error showing up every time.

From there, it is just a matter of fixing the bug.

Day #865

No comments:

Post a Comment