Saga – a better JavaScript code coverage tool aka my new toy
JS code coverage tools are a mess. So, finally, after damning my soul for having ever developed jscoverage-maven-plugin, I started thinking whether I should really write a proper JS code coverage tool, without any native shenanigans.
So, long story short, lo and behold – Saga, a JS code coverage tool that actually works. Well, at least I hope so.
P. S. it has a Maven plugin and all!..
ChromeDriver: extension loading fun
WebDriver is awesome.
I mean it: it works most of the time, and it’s being actively developed. And it’s nice to learn new tricks from time to time.
So, this was our problem: we had to load a browser extension into ChromeDriver. May sound simple enough, since loading extensions into FirefoxDriver is dead simple:
FirefoxProfile profile = new FirefoxProfile(); profile.addExtension(new File(extensionUri)); capabilities.setCapability(FirefoxDriver.PROFILE, profile);
Boom, done. Chrome, as usual, is a little trickier. I’ve seen code that basically looks for the Chrome installation directory, parses the version and then writes a JSON string into Chrome’s external_extensions.json file. It works, but it’s amazingly ugly. I mean, it just ugs in ugly, it’s extremely error-prone and it’s hard to do in a cross-platform way.
Fortunately, there’s another way: you can start Chrome with –load-extension flags (as per ChromeDriver docs)
DesiredCapabilities capabilities = DesiredCapabilities.chrome();
capabilities.setCapability("chrome.switches",
Arrays.asList("--load-extension=/path/to/extension/directory"));
WebDriver driver = new ChromeDriver(capabilities);
Awesome! Now, there’s a teeny-tiny problem with this approach: you cannot just point it to a *.crx file and expect it to magically work. However, if you think about it (and try it, if you dare), Chrome’s CRX files (as well as Firefox’s XPI files) are nothing more than glorified ZIP files with fancier extensions (and, of course, specific content). So, your mission becomes simpler: unzip the extension into, say, your temp directory, and then load it with the aforementioned technique.
Now, unzipping in Java is not fun – and we couldn’t find a good, simple library that would do the trick – so here’s a code snippet that does it:
public final class ZipUtils {
public static void unzip(final File sourceFile, final File targetDir) {
ZipFile zip = null;
try {
zip = new ZipFile(sourceFile);
Enumeration entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
File fileEntry = new File(targetDir, entry.getName());
if (entry.isDirectory()) {
fileEntry.mkdirs();
continue;
}
fileEntry.getParentFile().mkdirs();
InputStream inputStream = zip.getInputStream(entry);
FileOutputStream output = new FileOutputStream(fileEntry);
IOUtils.copy(inputStream, output);
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(output);
}
} catch (final Exception e) {
throw new RuntimeException(e);
} finally {
if (zip != null) {
try {
zip.close();
} catch (IOException e) {
}
}
}
}
}
And after this it’s simple:
final DesiredCapabilities capabilities = DesiredCapabilities.chrome();
final File unpackedExtensionDir;
try {
unpackedExtensionDir = File.createTempFile("webdriver", "my-extension");
} catch (final IOException e) {
throw new RuntimeException(e);
}
unpackedExtensionDir.mkdir();
ZipUtils.unzip(new File(getClass().getResource(
"/my-extension.crx").toURI()), unpackedExtensionDir);
capabilities.setCapability("chrome.switches",
"--load-extension=" + unpackedExtensionDir.getAbsolutePath());
Ugliness level: close to zero
Profit: max
JSCoverage (revisited)
I expected to be right, but it’s still interesting to realise that I was. This is an excerpt from the jscoverage source code:
void jscoverage_instrument(const char * source,
const char * destination,
int verbose,
char ** exclude,
int num_exclude,
char ** no_instrument,
int num_no_instrument)
{
// [...] omitted for brevity
/* check if it's on the exclude list */
for (int i = 0; i < num_exclude; i++) {
char * x = make_path(source, exclude[i]);
if (is_same_file(x, s) || contains_file(x, s)) {
free(x);
goto cleanup;
}
free(x);
}
// [...] omitted for brevity
}
And this is being done for every single file instrumented. And no, it does not have any kind of default excludes (like, VCS directories, other crap), so if you don’t want to wait forever for all the .svn folders and their contents to be copied, you have to compose a humongous –exclude string.
Which will mean that the char ** exclude array will have possibly thousands of records – and kaboom! – execution will take forever – believe me, I tried on a rather hefty i7 M620/8GB machine – I just Ctrl+C-ed before it could finish.
Correct me if I’m wrong, but a better approach would be to intersect the two sets (excludes and all the possible candidates for instrumentation) and then run instrumentation on the intersected area.
This would, of course, mean more iterations if there are many files but almost no excludes, but this shouldn’t cause any performance issues on modern machines, unlike the [files * excludes] situation we run into with the current approach.
I mean, it’s a great tool, because (as far as I’m concerned) there’s no other that does the same thing for JS. And it’s not like I’m bitching and moaning about free software which is distributed without any warranty.
I’m just really thinking if I should dive into C again (the horror!) or just develop a Groovy/Python app that would do the same thing (or even better). Anyone with me?
jscoverage-maven-plugin
Wrote a Maven plugin for headless JS file coverage calculation.
A bit too tired to write more about it, but just a few words: it uses JSCoverage behind the scenes and JSC is not as good as I thought it was. In short, it exhibits really bad performance when given a long list of excludes (and no, it doesn’t accept patterns). This is probably caused (and I’m just assuming here!) by the way that it excludes files – iterating through the whole list of exclusions for every file in the tree that it traverses.
I might be wrong, but I’ve just got this feeling
Anyway, the code is available here
Don’t use for…in loops for arrays
I’m sick and tired of reminding people not to do this and tracking bugs that’ve been caused by this malpractice.
If you do this:
var a = []; a[0] = 0; a[2] = 2; a[1] = 1;
Then, while looping through this array with a for…in loop you’ll get the following order in IE: 0, 2, 1. Sometimes this can be very dangerous and not so easy to debug.
In short: use for…in loops for associative arrays and regular for loops for your pure arrays.
JS trailing commas in object declarations vs IE
People doing that should burn in hell:
var foo = {
bar: 1,
baz: 2,
};
This is an infamous thing, I know, but I’d prefer that FF/Chrome reported the error as well, but rather pointing to the correct code snippet.
Anyway, eventually I found a regex, upgraded it to work more often (if not always) and it did help me big time:
,\s*(//.*|/\*.*\s*\*/)*\s*\n*(\s*\/\/.*\n)*\s*[\}\)\]]
JavaScript variable declarations
I know that many people (including Douglas Crockford) promote this style of JS var declarations:
var foo = 1,
bar = 2,
baz = 3;
which, IMHO, is a very, very bad idea. Why? Because it is extremely error-prone – it is very easy to miss a comma at the end of the line or put a semicolon instead, and then all following vars go global and you will be none the wiser.
I think you should always go with a proper
var foo = 1; var bar = 2; var baz = 3;
All of this ‘one var statement per function’ hassle is just a consequence of JS not having a block scope, and so they say that people coming from C/Java/whatever might not know that, and they might assume that if they declare a variable inside a for loop, for example, then it’s block-scoped.
I don’t care if people know it, really. If you are using a language and are ignorant to learn something about it, then, to quote Linus Torvalds, «I will just call you ugly and stupid».
Moreover, I think, mostly, if you have to care about a variable being block-scoped or not (which implies you are using it all over your function in different contexts), then, newsflash, I will most likely call you the same what I did before, because this, in turn, might imply that your function grows too big and it’s a good idea to start refactoring.