Compiled-code automated tests

  • Revision slug: Compiled-code_automated_tests
  • Revision title: Compiled-code automated tests
  • Revision id: 136827
  • Created:
  • Creator: Waldo
  • Is current revision? No
  • Comment docs on compiled-code tests

Revision Content

Compiled-code tests written in C++ are capable of testing anything in Mozilla, making them perhaps the most powerful testing method available. However, this power imposes a high cost on performing many such tests. For web-visible testing, you are nearly always better off with either a Mochitest or a reftest; for unit testing, you should write an xpcshell test if the functionality being tested is available through scriptable methods and interfaces.

If the functionality you wish to test is only available to compiled code, however, a compiled-code test is the right choice.

The structure of a compiled-code test

Using XPCOM requires including a number of headers and writing some tedious initialization code, under normal circumstances. For a test, however, most of this nonsense can be eliminated by including the test-support header {{template.Source("xpcom/tests/TestHarness.h")}}. This header provides a scoped-XPCOM class, the smart pointer classes, do_CreateInstance, and stdio.h/stdlib.h. With that, the basic structure of a compiled-code test is reduced to the following:

// standard Mozilla tri-license

#include "TestHarness.h"

nsresult TestFoo()
{
  // do your stuff and either fail in there or fall through
  printf("TestFoo PASSED!\n");
  return NS_OK;
}

int main(int argc, char** argv)
{
  ScopedXPCOM xpcom(;
  if (xpcom.failed())
    return 1;

  // XPCOM is now started up, and you can access components/services/etc.

  int rv = 0;

  if (NS_FAILED(TestFoo()))
    rv = 1;
  // ...

  return rv;
}

If you're fixing bugs in compiled code, you can probably figure out how to fill out the rest of this skeleton.

There are a couple things to note that aren't demonstrated by this skeleton. First, note that a test failure doesn't cause the test to immediately fail. While it's possible to write tests that fail as early as possible, usually it's possible to run multiple tests and only report failure at the end, which can save the frustrated developer who breaks your test the trouble of fixing each failure individually. Second, a test which fails should do so by printing an error message and returning a failure code. When possible, the error should be printed as early as possible and propagated up the call stack without printing further errors (note how in the example the TestFoo() doesn't have a corresponding printed message, because TestFoo's definition should do so before returning a failure code), but if an error must be printed multiple times, that's fine. Error messages should begin with the string "FAIL " (one trailing space) to enable better future processing of errors in, for example, tinderbox logs. Third, it's fine to print out pass messages such as the one at the end of TestFoo(), but if you're printing more than 10-15 you should consider being more selective in what you print. The intent is to give information about test progress but not to overwhelm the screen or tinderbox logs.

Making the test compile and link

Once you've written your test, you need to hook it up to the build system. You'll first need to make sure {{template.Source("xpcom/tests/TestHarness.h")}} is found correctly. You can do this by adding the following lines to the Makefile.in in the directory containing your test:

LOCAL_INCLUDES += -I$(topsrcdir)/xpcom/tests
CPPSRCS += TestFoo.cpp

You can hook up any other headers you need through the standard methods like REQUIRES. Note that because your test will be run in non-debug trees and tinderboxen, you should ensure your code builds in both debug and optimized configurations so that you don't rely on non-exported symbols libxul hides from you.

Hooking your test up to run automatically

Now that you have the test, it's time to make sure it works. We may eventually have Makefile magic to run the tests automatically, but for now, just add the following lines to the Makefile.in:

# anywhere in the file...
CPP_UNIT_TESTS += TestFoo

# to the check:: target in the file, making sure to preserve the tab-indents...
check::
	@$(EXIT_ON_ERROR) \
	for f in $(CPP_UNIT_TESTS); do \
	  XPCOM_DEBUG_BREAK=stack-and-abort $(RUN_TEST_PROGRAM) $(DIST)/bin/$$f; \
	done

Again, there are a couple things to note here. First, the EXIT_ON_ERROR causes a non-zero return value to break make check; without it the failure is ignored. Second, the tests are run with XPCOM_DEBUG_BREAK set so that assertions are fatal. You must ensure any tests you commit run correctly, without generating any assertions, so that your tests don't break the build. (Currently the tests aren't run in debug builds where the assertions would be tested, but this will hopefully change sometime soon.)

Functionality provided by TestHarness.h

Currently the only thing beyond useful includes is the ScopedXPCOM class; the functionality it provides is minimal enough (and is fully demonstrated above) that just reading the header is easier than describing it. More functionality for printing error messages may be added at some later time.

Running the tests

Just run them using the standard make -C $OBJDIR/path/to/containing/folder check command.

Revision Source

<p>
</p><p>Compiled-code tests written in C++ are capable of testing anything in Mozilla, making them perhaps the most powerful testing method available.  However, this power imposes a high cost on performing many such tests.  For web-visible testing, you are nearly always better off with either a <a href="en/Mochitest">Mochitest</a> or a <a class="external" href="http://mxr.mozilla.org/mozilla/source/layout/tools/reftest/README.txt">reftest</a>; for unit testing, you should write an <a href="en/Writing_xpcshell-based_unit_tests">xpcshell test</a> if the functionality being tested is available through scriptable methods and interfaces.
</p><p>If the functionality you wish to test is only available to compiled code, however, a compiled-code test is the right choice.
</p>
<h3 name="The_structure_of_a_compiled-code_test"> The structure of a compiled-code test </h3>
<p>Using XPCOM requires including a number of headers and writing some tedious initialization code, under normal circumstances.  For a test, however, most of this nonsense can be eliminated by including the test-support header {{template.Source("xpcom/tests/TestHarness.h")}}.  This header provides a scoped-XPCOM class, the smart pointer classes, <code>do_CreateInstance</code>, and <code>stdio.h</code>/<code>stdlib.h</code>.  With that, the basic structure of a compiled-code test is reduced to the following:
</p>
<pre class="eval">// standard Mozilla tri-license

#include "TestHarness.h"

nsresult TestFoo()
{
  // do your stuff and either fail in there or fall through
  printf("TestFoo PASSED!\n");
  return NS_OK;
}

int main(int argc, char** argv)
{
  ScopedXPCOM xpcom(;
  if (xpcom.failed())
    return 1;

  // XPCOM is now started up, and you can access components/services/etc.

  int rv = 0;

  if (NS_FAILED(TestFoo()))
    rv = 1;
  // ...

  return rv;
}
</pre>
<p>If you're fixing bugs in compiled code, you can probably figure out how to fill out the rest of this skeleton.
</p><p>There are a couple things to note that aren't demonstrated by this skeleton.  First, note that a test failure doesn't cause the test to immediately fail.  While it's possible to write tests that fail as early as possible, usually it's possible to run multiple tests and only report failure at the end, which can save the frustrated developer who breaks your test the trouble of fixing each failure individually.  Second, a test which fails should do so by printing an error message and returning a failure code.  When possible, the error should be printed as early as possible and propagated up the call stack without printing further errors (note how in the example the <code>TestFoo()</code> doesn't have a corresponding printed message, because <code>TestFoo</code>'s definition should do so before returning a failure code), but if an error must be printed multiple times, that's fine.  Error messages should begin with the string <code>"FAIL "</code> (one trailing space) to enable better future processing of errors in, for example, tinderbox logs.  Third, it's fine to print out pass messages such as the one at the end of <code>TestFoo()</code>, but if you're printing more than 10-15 you should consider being more selective in what you print.  The intent is to give information about test progress but not to overwhelm the screen or tinderbox logs.
</p>
<h3 name="Making_the_test_compile_and_link"> Making the test compile and link </h3>
<p>Once you've written your test, you need to hook it up to the build system.  You'll first need to make sure {{template.Source("xpcom/tests/TestHarness.h")}} is found correctly.  You can do this by adding the following lines to the <code>Makefile.in</code> in the directory containing your test:
</p>
<pre class="eval">LOCAL_INCLUDES += -I$(topsrcdir)/xpcom/tests
CPPSRCS += TestFoo.cpp
</pre>
<p>You can hook up any other headers you need through the standard methods like <code>REQUIRES</code>.  Note that because your test will be run in non-debug trees and tinderboxen, you should ensure your code builds in both debug and optimized configurations so that you don't rely on non-exported symbols libxul hides from you.
</p>
<h3 name="Hooking_your_test_up_to_run_automatically"> Hooking your test up to run automatically </h3>
<p>Now that you have the test, it's time to make sure it works.  We may eventually have Makefile magic to run the tests automatically, but for now, just add the following lines to the <code>Makefile.in</code>:
</p>
<pre class="eval"># anywhere in the file...
CPP_UNIT_TESTS += TestFoo

# to the check:: target in the file, making sure to preserve the tab-indents...
check::
	@$(EXIT_ON_ERROR) \
	for f in $(CPP_UNIT_TESTS); do \
	  XPCOM_DEBUG_BREAK=stack-and-abort $(RUN_TEST_PROGRAM) $(DIST)/bin/$$f; \
	done
</pre>
<p>Again, there are a couple things to note here.  First, the <code>EXIT_ON_ERROR</code> causes a non-zero return value to break <code>make check</code>; without it the failure is ignored.  Second, the tests are run with <code>XPCOM_DEBUG_BREAK</code> set so that assertions are fatal.  You must ensure any tests you commit run correctly, without generating any assertions, so that your tests don't break the build.  (Currently the tests aren't run in debug builds where the assertions would be tested, but this will hopefully change sometime soon.)
</p>
<h3 name="Functionality_provided_by_TestHarness.h"> Functionality provided by <code>TestHarness.h</code> </h3>
<p>Currently the only thing beyond useful includes is the <code>ScopedXPCOM</code> class; the functionality it provides is minimal enough (and is fully demonstrated above) that just reading the header is easier than describing it.  More functionality for printing error messages may be added at some later time.
</p>
<h3 name="Running_the_tests"> Running the tests </h3>
<p>Just run them using the standard <code>make -C $OBJDIR/path/to/containing/folder check</code> command.
</p>
Revert to this revision