---
title: Scenario language changes
...

This document covers ideas we have for new scenario language changes, each can be fleshed out here and once ready for implementation, perhaps an issue can be raised to mark the work to be done.

# Continuation lines

Many languages permit continuation lines, to enable neater formatting of commands etc. There are a number of ways to achieve that but we want something consistent for Subplot which makes sense considering the way people use our scenario language.

## Options considered

### `C` style

In C-like languages, a _backslash newline_ combination is considered to effectively be a null input (escaped newlines do not break the logical line).

```
when I run \
  a command \
    arg1 \
    arg2 \
    arg3
when I run another command
```

### Mail style

In RFC822 headers and the like, a line which starts with any whitespace is considered to be a continuation of the previous line and the whitespace is folded.

```
when I run
  a command
    arg1
    arg2
    arg3
when I run another command
```

### Start of line style

In some systems, a specific token at the start of a line shows that it is a continuation — this is sometimes done in shell prompts, python repls, etc.

The newline, continuation token, and any subsequent whitespace is folded into a single space.

```
when I run
... a command
...   arg1
...   arg2
...   arg3
when I run another command
```

## Decisions

Currently we believe that mail-style is most likely to be familiar and comfortable for our users, so we wish to implement that.

# New keywords - subroutines and runtime conditionality

The context here is that sometimes a constructed test suite contains tests which you want to have prepared, or perhaps even run, but where running the test detects some runtime condition which means that it should not run (assuming). For example you have a suite which checks various things about your system but if your yubikey isn't plugged in, the SSH tests can't run.
While we do have labels and permit include/exclude based on labels, that is at construction time in Rust and so not ideal for runtime variation.


We have formulated the following use cases:

* ASSUMING: as someone using Subplot to test my SSH configuration, I want to run some scenarios only if my personal key is available, or my work key, or both
  - similar: as someone who is in a hurry, I want to run only scenarios that execute in a short time

* SUBROUTINE: as someone with many scenarios that all require the same several setup steps, I want to not have to repeat the same steps in every scenario
  - I also want to not hide what happens in the setup by doing everything in a big step where the step implementation does them, since it's not evident to a reader of the typeset subplot document what happens

* USING: as someone with many scenarios that use the same external resource, I want only a limited number of the scenarios to run concurrently
  - for example, it might be a GPU or a database

In our discussion we came to the following consensus:

* We want to be able to analyze statically which scenarios use other scenarios.

* We want a class on scenario headings to mark the scenario as "do not ever run", for use as examples and things that aren't implemented yet.

* Scenarios may need to check a condition for whether they should run. This changes things so that a scenario may result in a SUCCESS, a FAILURE, or a SKIPPED status. We want to add a new step kind "assuming", to extend the existing "given", "when", and "then. An "assuming" step can be bound to a step function as the others, or it can refer to another scenario. If this happens, the other scenario is executed before the scenario with the "assuming" step, and if the other scenario fails, the assuming scenario is skipped.

  - the "assuming" step can be at any point in the scenario, and is executed in order as the other step kinds; this is a change from Yarn

  - a scenario used in "assuming" MUST be tagged with the `.assumed` class in the heading that names the scenario; such scenarios only run if used in an "assuming" step and their results (SUCCESS/FAILURE) are cached so they only run at most once

  - the assumed scenario's heading MAY have an identifier, or we refer to it by an automatically constructed "slug" identifier by condensing the heading text; "assuming" may also use the heading text verbatim

  - docgen should indicate in the typeset output which scenarios only exist to be used in "assuming" statements

* For the "subroutine" case, we want to support two cases, both of which use another scenario. We choose the "calling" keyword for this. In one case, the called scenario is executed separately, for it side effects (e.g., setting up a database), in other other it's expanded inline into the calling scenario, as if it has been put there by the author of the subplot document.

  - we add a "calling" keyword

  - like "assuming", "calling" may refer to a scenario by id or slug or heading text

  - the called scenario MUST be tagged with one of the following:

    - `.called` is executed once for each call site, as a separate scenario, for its side-effects, e.g., to create an empty database for scenarios the modify the database

  - `.cached` is executed once for all call sites, as a separate scenario, for its side-effects, e.g., to create a read-only database and populate it with some test data

  - `.inlined` is expanded inline at each call site and becomes part of the calling scenario

  - a `.called` or `.cached` scenario runs independently with its own "context" object; if it fails, the calling scenario also fails

  - an `.inlined` scenario uses the calling scenario's context object

  - if a called scenario fails, the calling scenario fails

* We don't want to support parameters for called scenarios. It's hard to see how they could be made easy to understand for all stakeholders.

* We're don't think that the "USING" use case is worth tackling at this time, but we may return to it later. For now we assume that either scenarios are executed sequentially or they can be all executed concurrently.


## Example using "assuming"

~~~~~~markdown

# Personal SSH key is available {.assumed #personal_key}

~~~scenario
when I run ssh-add -l
then the output contains "SHA256:PDCzyQPpd9tiWsELM8LfxaBsMDMm42J8/eEfezNgabc"
~~~

# I can log in to my personal server

~~~scenario
assuming personal_key
when I run ssh personal.example.com echo hello world
then output contains "hello world"
~~~

# I can log in over localhost

~~~scenario
assuming personal ssh key is available
when I run ssh localhost echo hi there
then output contains "hi there"
~~~

# I can log in to my other server

~~~scenario
assuming personalsshkeyisavailable
when I run ssh home.local echo hola
then output contains "hola"
~~~

~~~~~~

## Example using "calling"

~~~~~~markdown

# Create read-only database with test data {.cached #create-tstdat}

~~~scenario
given database TSTDAT
when I create table DATA in TSTDAT
and I insert test data into DATA in TSTDAT
then data is committed
~~~

# Count lines in test data

~~~scenario
using create-tstdat
when I count rows in DATA in TSTDAT
then it is a very large number
~~~

# Create empty database {.called #create-rw}

~~~scenario
given empty database RWDB
~~~

# Create many tables

~~~scenario
calling create empty database
when I create ten million tables in RWDB
then it works
~~~

# Create many rows

~~~scenario
calling create empty database
when I create table ROWS in RWDB
then it works
and I create ten billion rows in ROWS in RWDB
then it works
~~~

~~~~~~

## Cleaning up

Each step binding can provide a cleanup function as well as a step function. Given that, cleaning up in these situations may be non-intuitive unless we are very careful. Current thinking can be encapsulated as:

1. For the `.inlined` `calling` use-case, since the statements are effectively inlined, the cleanups occur as though the statements were part of the calling scenario directly.
2. For the `.called` `calling` use-case, since the scenario is meant to be entirely independently executed, cleanups are executed before the called scenario completes.
3. For the `.cached` `calling` use-case, since the scenario will only be run once for the entire test suite run, upon 'completion' the scenario's context must be preserved and cleaned up only at the end of execution of the test suite.
4. For assumed scenarios, the same applies as for cached called scenarios. They must be cleaned up only at the end of execution of the full test suite.

We're fairly in agreement on 1 and 2, but 3 and 4 require more thought before we have a consistent view on things.

Also we had a thought about writing step implementations in the scenario language rather than Rust or Python, but that might be somewhat opaque to the reader of a rendered document which is less than ideal.
