Developing Scenic

This page covers information useful if you will be developing Scenic, either changing the language itself or adding new built-in libraries or simulator interfaces.

To find documentation (and code) for specific parts of Scenic’s implementation, see our page on Scenic Internals.

Getting Started

Start by cloning our repository on GitHub and setting up your virtual environment. Then to install Scenic and its development dependencies in your virtual environment run:

$ python -m pip install -e ".[dev]"

This will perform an “editable” install, so that any changes you make to Scenic’s code will take effect immediately when running Scenic in your virtual environment.

Scenic uses the isort and black tools to automatically sort import statements and enforce a consistent code style. Run the command pre-commit install to set up hooks which will run every time you commit and correct any formatting problems (you can then inspect the files and try committing again). You can also manually run the formatters on the files changed since the last commit with pre-commit run. [1]

Running the Test Suite

Scenic has an extensive test suite exercising most of the features of the language. We use the pytest Python testing tool. To run the entire test suite, run the command pytest inside the virtual environment from the root directory of the repository.

Some of the tests are quite slow, e.g. those which test the parsing and construction of road networks. We add a --fast option to pytest which skips such tests, while still covering all of the core features of the language. So it is convenient to often run pytest --fast as a quick check, remembering to run the full pytest before making any final commits. You can also run specific parts of the test suite with a command like pytest tests/syntax/test_specifiers.py, or use pytest’s -k option to filter by test name, e.g. pytest -k specifiers.

Note that many of Scenic’s tests are probabilistic, so in order to reproduce a test failure you may need to set the random seed. We use the pytest-randomly plugin to help with this: at the beginning of each run of pytest, it prints out a line like:

Using --randomly-seed=344295085

Adding this as an option, i.e. running pytest --randomly-seed=344295085, will reproduce the same sequence of tests with the same Python/Scenic random seed. As a shortcut, you can use --randomly-seed=last to use the seed from the previous testing run.

If you’re running the test suite on a headless server or just want to stop windows from popping up during testing, use the --no-graphics option to skip graphical tests.

Prior to finalizing a PR or other substantial changes, it’s a good idea to run the test suite under all major versions of Python that Scenic supports, in fresh virtual environments. You can do this automatically with the command tox, which by default will test all supported major versions both with and without optional dependencies (this will take a long time). Some variations:

  • tox -p will run the various combinations in parallel.

  • tox -m basic skips testing installations with the optional dependencies.

  • tox -- --fast only runs the “fast” tests. In general, any arguments after the -- will get passed to pytest. For example,

  • tox -- tests/syntax/test_specifiers.py only runs the tests in the given file.

See the Tox website for more information about the available options and how to configure Tox.

Debugging

You can use Python’s built-in debugger pdb to debug the parsing, compilation, sampling, and simulation of Scenic programs. The Scenic command-line option -b will cause the backtraces printed from uncaught exceptions to include Scenic’s internals; you can also use the --pdb option to automatically enter the debugger on such exceptions. If you’re trying to figure out why a scenario is taking many iterations of rejection sampling, first use the --verbosity option to print out the reason for each rejection. If the problem doesn’t become clear, you can use the --pdb-on-reject option to automatically enter the debugger when a scene or simulation is rejected.

If you’re using the Python API instead of invoking Scenic from the command line, these debugging features can be enabled using the following function from the scenic module:

setDebuggingOptions(*, verbosity=0, fullBacktrace=False, debugExceptions=False, debugRejections=False)[source]

Configure Scenic’s debugging options.

Parameters:
  • verbosity (int) – Verbosity level. Zero by default, although the command-line interface uses 1 by default. See the --verbosity option for the allowed values.

  • fullBacktrace (bool) – Whether to include Scenic’s innards in backtraces (like the -b command-line option).

  • debugExceptions (bool) – Whether to use pdb for post-mortem debugging of uncaught exceptions (like the --pdb option).

  • debugRejections (bool) – Whether to enter pdb when a scene or simulation is rejected (like the --pdb-on-reject option).

It is possible to put breakpoints into a Scenic program using the Python built-in function breakpoint. Note however that since code in a Scenic program is not always executed the way you might expect (e.g. top-level code is only run once, whereas code in requirements can run every time we generate a sample: see How Scenic is Compiled), some care is needed when interpreting what you see in the debugger. The same consideration applies when adding print statements to a Scenic program. For example, a top-level print(x) will not print out the actual value of x every time a sample is generated: instead, you will get a single print at compile time, showing the Distribution object which represents the distribution of x (and which is bound to x in the Python namespace used internally for the Scenic module).

Building the Documentation

Scenic’s documentation is built using Sphinx. The freestanding documentation pages (like this one) are found under the docs folder, written in the reStructuredText format. The detailed documentation of Scenic’s internal classes, functions, etc. is largely auto-generated from their docstrings, which are written in a variant of Google’s style understood by the Napoleon Sphinx extension (see the docstring of Scenario.generate for a simple example: click the [source] link to the right of the function signature to see the code).

If you modify the documentation, you should build a copy of it locally to make sure everything looks good before you push your changes to GitHub (where they will be picked up automatically by ReadTheDocs). To compile the documentation, enter the docs folder and run make html. The output will be placed in the docs/_build/html folder, so the root page will be at docs/_build/html/index.html. If your changes do not appear, it’s possible that Sphinx has not detected them; you can run make clean to delete all the files from the last compilation and start from a clean slate.

Scenic extends Sphinx in a number of ways to improve the presentation of Scenic code and add various useful features: see docs/conf.py for full details. Some of the most commonly-used features are:

  • a scenic role which extends the standard Sphinx samp role with Scenic syntax highlighting;

  • a sampref role which makes a cross-reference like keyword but allows emphasizing variables like samp;

  • the term role for glossary terms is extended so that the cross-reference will work even if the link is plural but the glossary entry is singular or vice versa.

Footnotes