mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-11-07 11:48:19 +02:00
314 lines
13 KiB
Markdown
314 lines
13 KiB
Markdown
# Mochitest FAQ
|
|
|
|
## SSL and https-enabled tests
|
|
|
|
Mochitests must be run from http://mochi.test/ to succeed. However, some tests
|
|
may require use of additional protocols, hosts, or ports to test cross-origin
|
|
functionality.
|
|
|
|
The Mochitest harness addresses this need by mirroring all content of the
|
|
original server onto a variety of other servers through the magic of proxy
|
|
autoconfig and SSL tunneling. The full list of schemes, hosts, and ports on
|
|
which tests are served, is specified in `build/pgo/server-locations.txt`.
|
|
|
|
The origins described there are not the same, as some of them specify
|
|
particular SSL certificates for testing purposes, while some allow pages on
|
|
that server to request elevated privileges; read the file for full details.
|
|
|
|
It works as follows: The Mochitest harness includes preference values which
|
|
cause the browser to use proxy autoconfig to match requested URLs with servers.
|
|
The `network.proxy.autoconfig_url` preference is set to a data: URL that
|
|
encodes the JavaScript function, `FindProxyForURL`, which determines the host
|
|
of the given URL. In the case of SSL sites to be mirrored, the function maps
|
|
them to an SSL tunnel, which transparently forwards the traffic to the actual
|
|
server, as per the description of the CONNECT method given in RFC 2817. In this
|
|
manner a single HTTP server at http://127.0.0.1:8888 can successfully emulate
|
|
dozens of servers at distinct locations.
|
|
|
|
## What if my tests aren't done when onload fires?
|
|
|
|
Use `add_task()`, or call `SimpleTest.waitForExplicitFinish()` before onload
|
|
fires (and `SimpleTest.finish()` when you're done).
|
|
|
|
## How can I get the full log output for my test in automation for debugging?
|
|
|
|
Add the following to your test:
|
|
|
|
```
|
|
SimpleTest.requestCompleteLog();
|
|
```
|
|
|
|
## What if I need to change a preference to run my test?
|
|
|
|
The `SpecialPowers` object provides APIs to get and set preferences:
|
|
|
|
```js
|
|
await SpecialPowers.pushPrefEnv({ set: [["your-preference", "your-value" ]] });
|
|
// ...
|
|
await SpecialPowers.popPrefEnv(); // Implicit at the end of the test too.
|
|
```
|
|
|
|
You can also set prefs directly in the manifest:
|
|
|
|
```ini
|
|
[DEFAULT]
|
|
prefs =
|
|
browser.chrome.guess_favicon=true
|
|
```
|
|
|
|
If you need to change a pref when running a test locally, you can use the
|
|
`--setpref` flag:
|
|
|
|
```
|
|
./mach mochitest --setpref="javascript.options.jit.chrome=false" somePath/someTestFile.html
|
|
```
|
|
|
|
Equally, if you need to change a string pref:
|
|
|
|
```
|
|
./mach mochitest --setpref="webgl.osmesa=string with whitespace" somePath/someTestFile.html
|
|
```
|
|
|
|
## Can tests be run under a chrome URL?
|
|
|
|
Yes, use [mochitest-chrome](../chrome-tests/index.rst).
|
|
|
|
## How do I change the HTTP headers or status sent with a file used in a Mochitest?
|
|
|
|
Create a text file next to the file whose headers you want to modify. The name
|
|
of the text file should be the name of the file whose headers you're modifying
|
|
followed by `^headers^`. For example, if you have a file `foo.jpg`, the
|
|
text file should be named `foo.jpg^headers^`. (Don't try to actually use the
|
|
headers file in any other way in the test, because the HTTP server's
|
|
hidden-file functionality prevents any file ending in exactly one ^ from being
|
|
served.)
|
|
|
|
Edit the file to contain the headers and/or status you want to set, like so:
|
|
|
|
```
|
|
HTTP 404 Not Found
|
|
Content-Type: text/html
|
|
Random-Header-of-Doom: 17
|
|
```
|
|
|
|
The first line sets the HTTP status and a description (optional) associated
|
|
with the file. This line is optional; you don't need it if you're fine with the
|
|
normal response status and description.
|
|
|
|
Any other lines in the file describe additional headers which you want to add
|
|
or overwrite (most typically the Content-Type header, for the latter case) on
|
|
the response. The format follows the conventions of HTTP, except that you don't
|
|
need to have HTTP line endings and you can't use a header more than once (the
|
|
last line for a particular header wins). The file may end with at most one
|
|
blank line to match Unix text file conventions, but the trailing newline isn't
|
|
strictly necessary.
|
|
|
|
## How do I write tests that check header values, method types, etc. of HTTP requests?
|
|
|
|
To write such a test, you simply need to write an SJS (server-side JavaScript)
|
|
for it. See the [testing HTTP server](/networking/http_server_for_testing.rst)
|
|
docs for less mochitest-specific documentation of what you can do in SJS
|
|
scripts.
|
|
|
|
An SJS is simply a JavaScript file with the extension .sjs which is loaded in a
|
|
sandbox. Don't forget to reference it from your `mochitest.ini` file too!
|
|
|
|
```ini
|
|
[DEFAULT]
|
|
support-files =
|
|
test_file.sjs
|
|
```
|
|
|
|
The global property `handleRequest` defined by the script is then executed with
|
|
request and response objects, and the script populates the response based on the
|
|
information in the request.
|
|
|
|
Here's an example of a simple SJS:
|
|
|
|
```js
|
|
function handleRequest(request, response) {
|
|
// Allow cross-origin, so you can XHR to it!
|
|
response.setHeader("Access-Control-Allow-Origin", "*", false);
|
|
// Avoid confusing cache behaviors
|
|
response.setHeader("Cache-Control", "no-cache", false);
|
|
response.setHeader("Content-Type", "text/plain", false);
|
|
response.write("Hello world!");
|
|
}
|
|
```
|
|
|
|
The file is run, for example, at either
|
|
http://mochi.test:8888/tests/PATH/TO/YOUR/test_file.sjs,
|
|
http://{server-location}/tests/PATH/TO/YOUR/test_file.sjs - see
|
|
`build/pgo/server-locations.txt` for server locations!
|
|
|
|
If you want to actually execute the file, you need to reference it somehow. For
|
|
instance, you can XHR to it OR you could use a HTML element:
|
|
|
|
```js
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open("GET", "http://test/tests/dom/manifest/test/test_file.sjs");
|
|
xhr.onload = function(e){ console.log("loaded!", this.responseText)}
|
|
xhr.send();
|
|
```
|
|
|
|
The exact properties of the request and response parameters are defined in the
|
|
`nsIHttpRequestMetadata` and `nsIHttpResponse` interfaces in
|
|
`nsIHttpServer.idl`. However, here are a few useful ones:
|
|
|
|
|
|
* `.scheme` (string). The scheme of the request.
|
|
* `.host` (string). The scheme of the request.
|
|
* `.port` (string). The port of the request.
|
|
* `.method` (string). The HTTP method.
|
|
* `.httpVersion` (string). The protocol version, typically "1.1".
|
|
* `.path` (string). Path of the request,
|
|
* `.headers` (object). Name and values representing the headers.
|
|
* `.queryString` (string). The query string of the requested URL.
|
|
* `.bodyInputStream` ??
|
|
* `.getHeader(name)`. Gets a request header by name.
|
|
* `.hasHeader(name)` (boolean). Gets a request header by name.
|
|
|
|
**Note**: The browser is free to cache responses generated by your script. If
|
|
you ever want an SJS to return different data for multiple requests to the same
|
|
URL, you should add a `Cache-Control: no-cache` header to the response to
|
|
prevent the test from accidentally failing, especially if it's manually run
|
|
multiple times in the same Mochitest session.
|
|
|
|
## How do I keep state across loads of different server-side scripts?
|
|
|
|
Server-side scripts in Mochitest are run inside sandboxes, with a new sandbox
|
|
created for each new load. Consequently, any variables set in a handler don't
|
|
persist across loads. To support state storage, use the `getState(k)` and
|
|
`setState(k, v)` methods defined on the global object. These methods expose a
|
|
key-value storage mechanism for the server, with keys and values as strings.
|
|
(Use JSON to store objects and other structured data.) The myriad servers in
|
|
Mochitest are in reality a single server with some proxying and tunnelling
|
|
magic, so a stored state is the same in all servers at all times.
|
|
|
|
The `getState` and `setState` methods are scoped to the path being loaded. For
|
|
example, the absolute URLs `/foo/bar/baz, /foo/bar/baz?quux, and
|
|
/foo/bar/baz#fnord` all share the same state; the state for /foo/bar is entirely
|
|
separate.
|
|
|
|
You should use per-path state whenever possible to avoid inter-test dependencies
|
|
and bugs.
|
|
|
|
However, in rare cases it may be necessary for two scripts to collaborate in
|
|
some manner, and it may not be possible to use a custom query string to request
|
|
divergent behaviors from the script.
|
|
|
|
For this use case only you should use the `getSharedState(k, v)` and
|
|
`setSharedState(k, v)` methods defined on the global object. No restrictions
|
|
are placed on access to this whole-server shared state, and any script may add
|
|
new state that any other script may delete. To avoid conflicts, you should use
|
|
a key within a faux namespace so as to avoid accidental conflicts. For example,
|
|
if you needed shared state for an HTML5 video test, you might use a key like
|
|
`dom.media.video:sharedState`.
|
|
|
|
A further form of state storage is provided by the `getObjectState(k)` and
|
|
`setObjectState(k, v)` methods, which will store any `nsISupports` object.
|
|
These methods reside on the `nsIHttpServer` interface, but a limitation of
|
|
the sandbox object used by the server to process SJS responses means that the
|
|
former is present in the SJS request handler's global environment with the
|
|
signature `getObjectState(k, callback)`, where callback is a function to be
|
|
invoked by `getObjectState` with the object corresponding to the provided key
|
|
as the sole argument.
|
|
|
|
Note that this value mapping requires the value to be an XPCOM object; an
|
|
arbitrary JavaScript object with no `QueryInterface` method is insufficient.
|
|
If you wish to store a JavaScript object, you may find it useful
|
|
to provide the object with a `QueryInterface` implementation and then make
|
|
use of `wrappedJSObject` to reveal the actual JavaScript object through the
|
|
wrapping performed by XPConnect.
|
|
|
|
For further details on state-saving mechanisms provided by `httpd.js`, see
|
|
`netwerk/test/httpserver/nsIHttpServer.idl` and the
|
|
`nsIHttpServer.get(Shared|Object)?State` methods.
|
|
|
|
## How do I write a SJS script that responds asynchronously?
|
|
|
|
Sometimes you need to respond to a request asynchronously, for example after
|
|
waiting for a short period of time. You can do this by using the
|
|
`processAsync()` and `finish()` functions on the response object passed to the
|
|
`handleRequest()` function.
|
|
|
|
`processAsync()` must be called before returning from `handleRequest()`. Once
|
|
called, you can at any point call methods on the request object to send
|
|
more of the response. Once you are done, call the `finish()` function. For
|
|
example you can use the `setState()` / `getState()` functions described above to
|
|
store a request and later retrieve and finish it. However be aware that the
|
|
browser often reorders requests and so your code must be resilient to that to
|
|
avoid intermittent failures.
|
|
|
|
```js
|
|
let { setTimeout } = ChromeUtils.importESModule("resource://gre/modules/Timer.sys.mjs");
|
|
|
|
function handleRequest(request, response) {
|
|
response.processAsync();
|
|
response.setHeader("Content-Type", "text/plain", false);
|
|
response.write("hello...");
|
|
|
|
setTimeout(function() {
|
|
response.write("world!");
|
|
response.finish();
|
|
}, 5 * 1000);
|
|
}
|
|
```
|
|
|
|
For more details, see the `processAsync()` function documentation in
|
|
`netwerk/test/httpserver/nsIHttpServer.idl`.
|
|
|
|
## How do I get access to the files on the server as XPCOM objects from an SJS script?
|
|
|
|
If you need access to a file, because it's easier to store image data in a file
|
|
than directly in an SJS script, use the presupplied `SERVER_ROOT` object
|
|
state available to SJS scripts running in Mochitest:
|
|
|
|
```js
|
|
function handleRequest(req, res) {
|
|
var file;
|
|
getObjectState("SERVER_ROOT", function(serverRoot) {
|
|
file = serverRoot.getFile("tests/content/media/test/320x240.ogv");
|
|
});
|
|
// file is now an XPCOM object referring to the given file
|
|
res.write("file: " + file);
|
|
}
|
|
```
|
|
|
|
The path you specify is used as a path relative to the root directory served by
|
|
`httpd.js`, and an `nsIFile` corresponding to the file at that location is
|
|
returned.
|
|
|
|
Beware of typos: the file you specify doesn't actually have to exist
|
|
because file objects are mere encapsulations of string paths.
|
|
|
|
## Diagnosing and fixing leakcheck failures
|
|
|
|
Mochitests output a log of the windows and docshells that are created during the
|
|
test during debug builds. At the end of the test, the test runner runs a
|
|
leakcheck analysis to determine if any of them did not get cleaned up before the
|
|
test was ended.
|
|
|
|
Leaks can happen for a variety of reasons. One common one is that a JavaScript
|
|
event listener is retaining a reference that keeps the window alive.
|
|
|
|
```js
|
|
// Add an observer.
|
|
Services.obs.addObserver(myObserver, "event-name");
|
|
|
|
// Make sure and clean it up, or it may leak!
|
|
Services.obs.removeObserver(myObserver, "event-name");
|
|
```
|
|
|
|
Other sources of issues include accidentally leaving a window, or iframe
|
|
attached to the DOM, or setting an iframe's src to a blank string (creating an
|
|
about:blank page), rather than removing the iframe.
|
|
|
|
Finding the leak can be difficult, but the first step is to reproduce it
|
|
locally. Ensure you are on a debug build and the `MOZ_QUIET` environment flag
|
|
is not enabled. The leakcheck test analyzes the test output. After reproducing
|
|
the leak in the test, start commenting out code until the leak goes away. Then
|
|
once the leak stop reproducing, find the exact location where it is happening.
|
|
|
|
See [this post](https://crisal.io/words/2019/11/13/shutdown-leak-hunting.html)
|
|
for more advanced debugging techniques involving CC and GC logs.
|