Saturday, December 19, 2009

XPCShell Testing for Firefox Addons (Extensions)

A quick bit of info on what I've done to get xpcshell unit tests working for my add-on.
Here's my folder structure:To the .mozconfig (not in the above diagram, as mine is located in my home folder), I add the following to enable tests:
ac_add_options --enable-tests

To the Makefile.in in the bittorrent folder, I take the existing lines:
# list of subfolders to process
DIRS = public src

And add the following (based on http://zenit.senecac.on.ca/wiki/index.php/Dive_into_Mozilla_Unit_Testing_Lab)
# Adding xpcshell unit tests
ifdef ENABLE_TESTS
DIRS += test
endif

This means in addition to Make processing the public & src subfolders, it should also process the test folder, if tests are enabled.
The Makefile.in in the test folder is terribly boring. My Add-ons module name is bittorrentwikiscraps, I can perhaps specify the module for this Makefile as that also, however the pattern seems to be test_modulename. (My notes say I was basing this on this Makefile.in, take a look if you need platform specific tests)
DEPTH        = ../../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@

include $(DEPTH)/config/autoconf.mk

MODULE = test_bittorrentwikiscraps

XPCSHELL_TESTS = \
unit \
$(NULL)

include $(topsrcdir)/config/rules.mk

The XPCSHELL_TESTS defines the folders where the tests are stored, and MODULE defines where the tests will be copied, and run from. The tests will be copied to OBJDIR/_tests/xpcshell/test_bittorrentwikiscraps/unit/ when running 'make', and when running 'make xpcshell-tests' the tests in these folders will be executed.
The above is pretty normal for xpcshell tests. The next bit is what's been described to me by ted a couple of times, it's what crashreporter does to circumvent an issue with xpcshell (with a little hack of it's own^).
xpcshell doesn't know about add-ons, so the components I want to test won't be available when the tests run. So, I copy the components (JS files, in bittorrent/src/) to the testing area, by adding the following to bittorrent/src/Makefile.in (at the end of the file):
libs:: 
$(NSINSTALL) -D $(DEPTH)/_tests/xpcshell/test_$(MODULE)/unit/components

libs:: $(EXTRA_COMPONENTS)
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/xpcshell/test_$(MODULE)/unit/components

With JavaScript components, the files are listed in EXTRA_COMPONENTS (you list them! It's not automatic!). What we're doing here is first creating the components subfolder in the testing area (the first use of libs::) and then copying every item in EXTRA_COMPONENTS to this new folder (second use of libs::). Yay! Now the component files are there. But how will xpcshell know about them when the tests are run?
This is acheived by using nsIComponentRegistrar to register the components during the test. Yes, every time a test is run the components are registered first. We do this by creating head_regComps.js in the unit folder. When tests are run, for example test_sample.js, all the head_*.js files are executed before the contents of test_sample.js to do any setup. So, head_regComps.js will be run first, which ensures the components are loaded before the testing code. The contents of head_regComps.js are from the crashreporter code:
// load components from test subfolder
// Get current working directory
let cwd = Components.classes["@mozilla.org/file/directory_service;1"]
.getService(Components.interfaces.nsIProperties)
.get("CurWorkD", Components.interfaces.nsILocalFile);
// load subfolder
let cd = cwd.clone();
cd.append("components");
Components.manager instanceof Components.interfaces.nsIComponentRegistrar;
Components.manager.autoRegister(cd);

The code gets the current working directory (the unit folder in the testing area) and then registers all the components in the components subfolder.
For tests that just involve new components, this is enough. My tests can now see the components (in test_sample.js):
function run_test() {
var btPresent = ("@wikiscraps.com/transport/wireInStream;1" in Components.classes);
do_check_true(btPresent);
}

However, my Add-on also has new component interfaces (see the IDL files in the public folder). This issue isn't tackled in the current Mozilla build setup, and the above approach based on crashreporter can't handle interfaces. So, to recap: the issue of xpcshell not being aware of Add-ons prevents the interface being loaded; and unlike components, interfaces can't be loaded dynamically. My hack is just to copy the interfaces to OBJDIR/dist/bin/components/. I'm fine with this, my Firefox build has an interface that belongs in the extension folder. This will probably cause an issue when the Add-on interface in a profile and this components folder are out of sync, but oh well. This build is fine for testing and generating the Add-on's XPI file.
Anyway, the copying of the XPT file is achieved by adding the following in the bittorrent/public/Makefile.in (at the end)
libs:: 
$(INSTALL) $(XPIDL_GEN_DIR)/$(XPIDL_MODULE).xpt $(DIST)/bin/components

Usually the xpt files are copied in a similar manner to the folder '${FINAL_TARGET}/components', however because Add-ons are built for an XPI that will mean FINAL_TARGET will point to the xpi-staging area, so instead '$(DIST)/bin/components' must be used. Now, when xpcshell is run the interface will be ready to be loaded.
Just a note, I had removed OBJDIR/dist/bin/components/components.list, OBJDIR/dist/bin/components/compreg.dat & OBJDIR/dist/bin/components/xpti.dat earlier during my development; I'm unsure if I hadn't done this if the interfaces would be loaded.
Anyway, we can now test and see the interface has been loaded, by adjusting test_sample.js:
function run_test() {
var btPresent = ("@wikiscraps.com/transport/wireInStream;1" in Components.classes);
do_check_true(btPresent);
var btiPresent = ("btIWireInputStream" in Components.interfaces);
do_check_true(btiPresent);
}

Yay. I can test my Add-on components using xpcshell unit tests.

No comments:

Post a Comment