874
edits
Dandonkulous (talk | contribs) No edit summary |
(Added sections on testing, module unloading, module instantiation, memory leak prevention) |
||
| Line 1: | Line 1: | ||
The following are best practices for reboot development. Nothing is (yet) set in stone, so propose and debate and stuff. | The following are best practices for reboot development. Nothing is (yet) set in stone, so propose and debate and stuff. | ||
= Style Guidelines = | |||
See [[Labs/Jetpack/Reboot/Style_Guide]] for official guidelines for general coding style and structure. | |||
= Testing = | |||
Testing has been designed from the ground-up to be as easy as possible to get into. Just make a directory called <tt>tests</tt> in your package, make a file called <tt>test-something.js</tt> and have it export functions that take a single argument called <tt>test</tt>. Running <tt>cfx test</tt> will automatically find and call all these functions, passing in a [[Labs/Jetpack/JEP/28#Test_Runner_Objects|Test Runner Object]] as the argument. | |||
More notes: | |||
* Unit tests should be written so that all dependencies—i.e., what's ''not'' being tested—are stubbed/faked/mocked. This also ensures that the tests run as quickly as possible, which makes the test-driven-development process of "write a little bit of code, run the test suite" as easy as possible. | |||
* At the same time, integration tests with the Mozilla platform are important because the platform is constantly evolving and we need to sure that our code doesn't break as it changes underneath us; we also need to be able to ensure that our code works fine under different Mozilla applications. | |||
* If your module is going to be used by lots and lots of other modules, as is often the case with e.g. <tt>xhr</tt>, consider actually writing a mock class and making it available to other modules. This way others won't have to constantly re-create such objects for their tests. | |||
= Module Unloading = | |||
All code that touches the Mozilla platform needs to properly manage its resource use and unload all resources currently in-use when an unload signal is sent to it. See the [http://hg.mozilla.org/users/avarma_mozilla.com/jep-28/file/tip/packages/cuddlefish/lib/xhr.js xhr module source code] for a great example of this. | |||
Testing to ensure that you're unloading your resources properly is also important, and generally consists of using the test runner's (currently undocumented) <tt>makeSandboxedLoader()</tt> method, which creates a new module loader. Your test can then use the new instance of your module to allocate resources, and then unload them, and finally test to make sure there aren't any leaks. See the [http://hg.mozilla.org/users/avarma_mozilla.com/jep-28/file/tip/packages/cuddlefish/tests/test-xhr.js xhr module test suite] for an example of this. | |||
= Module Instantiation = | |||
Aside from having a lifecycle that's independent of the host application, a module can actually have multiple instances of itself running inside an application at once (sometimes even different ''versions'' of different instances of itself). | |||
This means that you shouldn't mutate your outside environment in ways that could collide with other instances of your module: for example, don't add a property to every browser window's global namespace called <tt>__myModule_secret_id</tt>. | |||
= Memory Leak Prevention = | |||
* The <tt>--times</tt> (or <tt>-x</tt>) option to <tt>cfx</tt> is useful in detecting leaks. This causes multiple iterations of your test suite to be run, with memory statistics displayed after each iteration. It's difficult to tell from pure heap information whether your code has a leak from one iteration to the next, but if you run e.g. 5000 iterations and you eventually eat up gobs of memory, then you know you have a leak. | |||
* Use <tt>memory.track(this)</tt> on the first line of an object constructor to make sure you don't create extra instances of it from one test suite iteration to the next. The number of tracked objects in the testing sandbox is displayed as part of the memory statistics at the end of each iteration, and this should not increase from one run to the next. | |||
* The test runner automatically keeps track of the global scope of all loaded modules and makes sure that they're garbage collected when your test suite is finished. If they're not, the test runner will display a warning at the end of the test run. This could means that e.g. a function belonging to a "leaking" module has been registered as a callback to Mozilla platform code and not been unregistered. However, in some cases the leak warnings actually appear to be erroneous, and running e.g. an even number of iterations of the test suite reports leaks while running an odd number doesn't. | |||
* Add [http://hg.mozilla.org/users/avarma_mozilla.com/atul-packages/ atul-packages] to your <tt>packages</tt> directory and then pass <tt>--extra-packages=nsjetpack</tt> to <tt>cfx</tt> when you run your tests. This will cause the test runner to enable optional [[Labs/Jetpack/Binary_Components#Memory_Profiling|JS memory profiling functionality]] and display extra JS object statistics between iterations. In particular, a "diff" of the JS heap from one iteration to the next is displayed, which can be helpful in pinpointing where a leak is occurring. | |||
= More Stuff = | |||
We also need to cover: | We also need to cover: | ||
| Line 7: | Line 45: | ||
* how to namespace packages (e.g., <tt>require("foo/bar/baz")</tt>) | * how to namespace packages (e.g., <tt>require("foo/bar/baz")</tt>) | ||
* out-of-code documentation (e.g., tutorials and guides) | * out-of-code documentation (e.g., tutorials and guides) | ||
* security | * security | ||
* localization | * localization | ||
* API design | * API design | ||
* Some Cuddlefish modules, like file.js, take pains to be broadly compatible as JS modules and loadable as-is in Web pages, in addition to being SecurableModules used in Jetpack. Do we really want to go that route for all modules? | * Some Cuddlefish modules, like file.js, take pains to be broadly compatible as JS modules and loadable as-is in Web pages, in addition to being SecurableModules used in Jetpack. Do we really want to go that route for all modules? | ||
* When creating objects, do we want <tt>jetpack.thing()</tt> or <tt>new jetpack.Thing()</tt>? | * When creating objects, do we want <tt>jetpack.thing()</tt> or <tt>new jetpack.Thing()</tt>? | ||
** Atul has found the case of forgetting the "new" operator to be unforgivingly difficult to debug. | ** Atul has found the case of forgetting the "new" operator to be unforgivingly difficult to debug. | ||
** Atul has also found that "new" operator problems are hard to debug when the operator associates w/ a different operand than one intends, e.g. <tt>new require("foo").Bar()</tt>. | ** Atul has also found that "new" operator problems are hard to debug when the operator associates w/ a different operand than one intends, e.g. <tt>new require("foo").Bar()</tt>. | ||
edits