.. _developing:
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 :doc:`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:
.. code-block:: console
$ 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 :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 :command:`pre-commit run`. [#f1]_
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 :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
:command:`pytest --fast` as a quick check, remembering to run the full :command:`pytest`
before making any final commits. You can also run specific parts of the test suite with a
command like :command:`pytest tests/syntax/test_specifiers.py`, or use pytest's ``-k``
option to filter by test name, e.g. :command:`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:
.. code-block:: none
Using --randomly-seed=344295085
Adding this as an option, i.e. running :command:`pytest --randomly-seed=344295085`, will
reproduce the same sequence of tests with the same Python/Scenic random seed. As a
shortcut, you can use :command:`--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 :command:`--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 :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:
* :command:`tox -p` will run the various combinations in parallel.
* :command:`tox -m basic` skips testing installations with the optional dependencies.
* :command:`tox -- --fast` only runs the "fast" tests. In general, any arguments after the :command:`--` will get passed to ``pytest``. For example,
* :command:`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:
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 :option:`-b` will cause the
backtraces printed from uncaught exceptions to include Scenic's internals; you can also
use the :option:`--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 :option:`--verbosity` option to print out the reason for each
rejection. If the problem doesn't become clear, you can use the :option:`--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:
.. autofunction:: scenic.setDebuggingOptions
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 :ref:`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 :scenic:`print(x)` will
not print out the actual value of :scenic:`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 :scenic:`x` (and which is bound to :scenic:`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 :file:`docs`
folder, written in the :ref:`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 :file:`docs` folder and run :command:`make html`. The output
will be placed in the :file:`docs/_build/html` folder, so the root page will be at
:file:`docs/_build/html/index.html`. If your changes do not appear, it's possible that Sphinx
has not detected them; you can run :command:`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 :file:`docs/conf.py` for full details. Some of the most
commonly-used features are:
* a ``scenic`` `role `_
which extends the standard Sphinx :rst:role:`samp` role with Scenic syntax highlighting;
* a ``sampref`` role which makes a cross-reference like :rst:role:`keyword` but allows
emphasizing variables like :rst:role:`samp`;
* the :rst:role:`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.
Benchmarking and Profiling
--------------------------
The :file:`tools/benchmarking` folder contains tools to help measure Scenic's performance
and resource usage. In particular, the :file:`tools/benchmarking/ci` folder contains a set
of benchmarks available to run as a CI workflow to assess the performance impact of a PR.
Those with write or triage permissions in the Scenic repository can trigger benchmarking
of a commit by leaving a comment on the PR of the form ``!benchmark HASH``, specifying the
hash of the desired commit. You can also run benchmarks
locally using the :file:`run_benchmarks.py` script: use its ``-h`` option for
instructions. The script uses `pyperf `_, which runs the
benchmarks many times to improve the stability of the results, so benchmarking is slow.
You may want to use the ``--fast`` option for quick-and-dirty results. Note that
even with the slow default options, many of the Scenic benchmarks have substantial
variability, so speed differences on the order of 10% or less are probably not
significant.
When working on optimizing a particular part of Scenic, it is much faster to focus on one
or a few suitable Scenic programs rather than constantly re-running the whole benchmark
suite. Scenic's :option:`--gather-stats` option is convenient for measuring the time and
sampling iterations required for a single Scenic program. For example:
.. code:: console
scenic examples/webots/vacuum/vacuum_simple.scenic -v 0 --gather-stats 100
This option is also ideal for running Scenic in a profiler to investigate where time is
being spent. Using `Pyinstrument `_, for example:
.. code:: console
pyinstrument -r html -m scenic examples/webots/vacuum/vacuum_simple.scenic -v 0 --gather-stats 100
.. rubric:: Footnotes
.. [#f1] To run the formatters on *all* files, changed or otherwise, use :command:`make format` in the root directory of the repository. But this should not be necessary if you installed the pre-commit hooks and so all files already committed are clean.