Subplot

By: The Subplot project

2026-01-02 09:24

Table of Contents

1 Introduction

This document explains the acceptance criteria for Subplot. See the user guide for an explanation of Subplot.

1.1 Using this document to verify Subplot works

This document ("subplot") can be used to verify Subplot itself from its source tree or an installed Subplot. The default is to test Subplot from the source tree, and the Makefile does that. You can run this in the source tree to build Subplot and then verify it using itself:

$ cargo build -q
$ cargo run --bin subplot -- codegen subplot.subplot -o test.py -t python
$ python3 test.py --env SUBPLOT_DIR=target/debug
... much output
OK, all scenarios finished successfully
$

To test an installed Subplot, generate the test program, and tell the test program where Subplot is installed. Again, in the Subplot source tree:

$ cargo build -q
$ cargo run --bin subplot -- codegen subplot.subplot -o test.py -t python
$ python3 test.py  --env SUBPLOT_DIR=/usr/local/bin
... much output
OK, all scenarios finished successfully
$

You can do this with an installed Subplot as well:

$ cargo clean
$ /usr/local/bin/subplot codegen subplot.subplot -o test.py -t python
$ python3 test.py --env SUBPLOT_DIR=/usr/local/bin
... much output
OK, all scenarios finished successfully
$

The generated test program is self-standing, and can be run from anywhere. However, to generate it you need to be in the Subplot source tree. You can move it elsewhere after generating it, you if you prefer.

2 Requirements

This chapter lists requirements for Subplot. These requirements are not meant to be automatically verifiable. For specific, automatically testable acceptance criteria, see the later chapter with acceptance tests for Subplot.

Each requirement here is given a unique mnemonic id for easier reference in discussions.

  • UnderstandableTests

    Acceptance tests should be possible to express in a way that's easily understood by all stakeholders, including those who are not software developers.

    Done but requires the Subplot document to be written with care.

  • EasyToWriteDocs

    The markup language for writing documentation should be easy to write.

    Done by using Markdown.

  • AidsComprehension

    The formatted human-readable documentation should use good layout and typography to enhance comprehension.

    In progress — we currently only output HTML, but may add PDF output back later.

  • CodeSeparately

    The code to implement the acceptance criteria should not be embedded in the documentation source, but be in separate files. This makes it easier to edit without specialised tooling.

    Done by keeping scenario step implementations in a separate file.

  • AnyProgammingLanguage

    The developers implementing the acceptance tests should be free to use a language they're familiar and comfortable with. Subplot should not require them to use a specific language.

    Not done — only Python supported at the moment.

  • FastTestExecution

    Executing the acceptance tests should be fast.

    Not done — the generated Python test program is simplistic and linear.

  • NoDeployment

    The acceptance test tooling should assume the system under test is already deployed and available. Deploying is too big of a problem space to bring into the scope of acceptance testing, and there are already good tools for deployment.

    Done by virtue of letting those who implement the scenario steps worry about it.

  • MachineParseableResults

    The tests should produce a machine parseable result that can be archived, post-processed, and analyzed in ways that are of interest to the project using Subplot. For example, to see trends in how long tests take, how often tests fail, to find regressions, and to find tests that don't provide value.

    Not done — the generated test program is simplistic.

3 Acceptance criteria for Subplot

Add the acceptance criteria test scenarios for Subplot here.

3.1 Test data shared between scenarios

The 'smoke-test' scenarios below test Subplot by running it against specific input files. This section specifies the bindings and functions files, which are generated and cleaned up on the fly. They're separate from the scenarios so that the scenarios are shorter and clearer, but also so that the input files do not need to be duplicated for each scenario.

title: Test scenario
markdowns:
- simple.md
bindings: [b.yaml]
impls:
  python: [f.py]
# Simple
This is the simplest possible test scenario

```scenario
given precondition foo
when I do bar
then bar was done
```
- given: precondition foo
  impl:
    python:
      function: precond_foo
- when: I do bar
  impl:
    python:
      function: do_bar
- when: I do foobar
  impl:
    python:
      function: do_foobar
- then: bar was done
  impl:
    python:
      function: bar_was_done
- then: foobar was done
  impl:
    python:
      function: foobar_was_done
- given: file {filename}
  impl:
    python:
      function: provide_file
  types:
    filename: file
def precond_foo(ctx):
    ctx['bar_done'] = False
    ctx['foobar_done'] = False
def do_bar(ctx):
    ctx['bar_done'] = True
def bar_was_done(ctx):
    assert_eq(ctx['bar_done'], True)
def do_foobar(ctx):
    ctx['foobar_done'] = True
def foobar_was_done(ctx):
    assert_eq(ctx['foobar_done'], True)

3.1.1 Smoke test

The scenario below uses the input files defined above to run some tests to verify that Subplot can build an HTML document, and execute a simple scenario successfully. The test is based on generating the test program from an input file, running the test program, and examining the output.

codegendocgen
1 given file simple.subplot
2 given file simple.md
3 given file b.yaml
4 given file f.py
5 given an installed subplot
6 when I run subplot docgen simple.subplot -o simple.html
7 then file simple.html exists
8 when I run subplot codegen --run simple.subplot -o test.py
9 then scenario "Simple" was run
10 then step "givenprecondition foo" was run
11 then step "whenI do bar" was run
12 then step "thenbar was done" was run
13 then command is successful

3.2 Indented scenario steps are not allowed

Requirement: A scenario step starts at the beginning of the line.

Justification: We may want to allow continuing a step to the next line, but as of June, 2023, we haven't settled on a syntax for this. However, whatever syntax we do eventually choose, it will be easier to add that if scenario steps start at the beginning of a line, without making a breaking change.

docgenfailures
1 given file indented-step.subplot
2 given file indented-step.md
3 given file b.yaml
4 given an installed subplot
5 when I try to run subplot docgen indented-step.subplot -o foo.html
6 then command fails
7 then stderr contains "indented"
title: Indented scenario step
markdowns:
  - indented-step.md
bindings:
  - b.yaml
# This is a title

~~~scenario
  given precondition
~~~

3.3 Named code blocks must have an appropriate class

Requirement: Named code blocks must carry an appropriate class such as file or example

Justification: Eventually we may want to add other meanings to named blocks, currently the identifier cannot be used except to refer to the block as a named file, but we may want to in the future so this is here to try and prevent any future incompatibilities.

docgenclasses
1 given file named-code-blocks-appropriate.subplot
2 given file named-code-blocks-appropriate.md
3 given file b.yaml
4 given an installed subplot
5 when I try to run subplot docgen named-code-blocks-appropriate.subplot -o foo.html
6 then command fails
7 then stderr contains "#example-1 at named-code-blocks-appropriate.md:7:1"
8 then stderr doesn't contain "example-2"
9 then stderr doesn't contain "example-3"
title: Named code blocks carry appropriate classes step
markdowns:
  - named-code-blocks-appropriate.md
bindings:
  - b.yaml
# This is a title

~~~scenario
given precondition
~~~

~~~{#example-1 .numberLines}
This example is bad
~~~

~~~{#example-2 .file .numberLines}
This example is OK because of .file
~~~

~~~{#example-3 .example .numberLines}
This example is OK because of .example
~~~

3.4 No scenarios means codegen fails

If you attempt to subplot codegen on a document which contains no scenarios, the tool will fail to execute with a reasonable error message.

codegenfailures
1 given file noscenarios.subplot
2 given file noscenarios.md
3 given an installed subplot
4 when I try to run subplot codegen noscenarios.subplot -o test.py
5 then command fails
6 then stderr contains "no scenarios were found"
title: No scenarios in here
markdowns: [noscenarios.md]
impls: { python: [] }
# This is a title

But there are no scenarios in this file, and thus nothing can be generated in a test suite.

3.5 No template means you can docgen but not codegen

When running docgen you do not need a template to have been defined in the subplot input document. If you have template-specific bindings then you should provide one, but if not, then it is unnecessary. This means you can use docgen to build documents before you have any inkling of the implementation language necessary to validate the scenarios.

docgencodegenfailures
1 given file notemplate.subplot
2 given file notemplate.md
3 given an installed subplot
4 when I run subplot docgen notemplate.subplot -o notemplate.html
5 then file notemplate.html exists
6 when I try to run subplot codegen notemplate.subplot -o test.py
7 then command fails
8 then stderr contains "document has no template"
title: No templates in here
markdowns: [notemplate.md]
impls: { }
# This is a title

```scenario
then failure ensues
```

3.6 Keywords

Subplot supports the keywords given, when, and then, and the aliases and and but. The aliases stand for the same (effective) keyword as the previous step in the scenario. This chapter has scenarios to check the keywords and aliases in various combinations.

3.6.1 All the keywords

codegen
1 given file allkeywords.subplot
2 given file allkeywords.md
3 given file b.yaml
4 given file f.py
5 given an installed subplot
6 when I run subplot codegen --run allkeywords.subplot -o test.py
7 then scenario "All keywords" was run
8 then step "givenprecondition foo" was run
9 then step "whenI do bar" was run
10 then step "thenbar was done" was run
11 then command is successful
title: All the keywords scenario
markdowns:
- allkeywords.md
bindings: [b.yaml]
impls:
  python: [f.py]
# All keywords

This uses all the keywords.

```scenario
given precondition foo
when I do bar
and I do foobar
then bar was done
but foobar was done
```

3.6.2 Misuse of continuation keywords

When continuation keywords (and and but) are used, they have to not be the first keyword in a scenario. Any such scenario will fail to parse because subplot will be unable to determine what kind of keyword they are meant to be continuing.

codegenfailures
1 given file continuationmisuse.subplot
2 given file continuationmisuse.md
3 given file b.yaml
4 given file f.py
5 given an installed subplot
6 when I try to run subplot codegen --run continuationmisuse.subplot -o test.py
7 then command fails
title: Continuation keyword misuse
markdowns:
- continuationmisuse.subplot
bindings: [b.yaml]
impls:
  python: [f.py]
# Continuation keyword misuse

This scenario should fail to parse because we misuse a
continuation keyword at the start.

```scenario
and precondition foo
when I do bar
then bar was done
```

3.7 Title markup

It is OK to use markup in document titles, in the YAML metadata section. This scenario verifies that all markup works.

docgen
1 given file title-markup.subplot
2 given file title-markup.md
3 given an installed subplot
4 when I run subplot docgen title-markup.subplot -o foo.html
5 then file foo.html exists
title: This _uses_ ~~all~~ **most** inline `markup`
subtitle: H~2~O is not 2^10^
markdowns: [title-markup.md]
impls: { python: [] }
# Introduction

3.8 Scenario titles

A scenario gets its title from the lowest level of section heading that applies to it. The heading can use markup.

1 given file scenario-titles.subplot
2 given file scenario-titles.md
3 given file b.yaml
4 given file f.py
5 given an installed subplot
6 when I run subplot metadata scenario-titles.subplot
7 then stdout contains "My fun scenario title"
title: Test scenario
markdowns:
- scenario-titles.md
bindings: [b.yaml]
impls:
  python: [f.py]
# My **fun** _scenario_ `title`

```scenario
given precondition foo
when I do bar
then bar was done
```

3.9 Duplicate scenario titles

Requirement: Subplot treats it as an error if two scenarios have the same title.

Justification: the title is how a scenario is identified, and the user needs to be able to do so unambiguously.

failures
1 given file duplicate-scenario-titles.subplot
2 given file duplicate-scenario-titles.md
3 given file b.yaml
4 given file f.py
5 given an installed subplot
6 when I try to run subplot metadata duplicate-scenario-titles.subplot
7 then command fails
8 then stderr contains "duplicate"
title: Test scenario
markdowns:
- duplicate-scenario-titles.md
bindings: [b.yaml]
impls:
  python: [f.py]
# My sceanrio

```scenario
when I do bar
```

# My sceanrio

```scenario
when I do bar
```

3.10 Empty lines in scenarios

This scenario verifies that empty lines in scenarios are OK.

docgencodegen
1 given file emptylines.subplot
2 given file emptylines.md
3 given file b.yaml
4 given file f.py
5 given an installed subplot
6 when I run subplot docgen emptylines.subplot -o emptylines.html
7 then file emptylines.html exists
8 when I run subplot codegen --run emptylines.subplot -o test.py
9 then scenario "Simple" was run
10 then step "givenprecondition foo" was run
11 then step "whenI do bar" was run
12 then step "thenbar was done" was run
13 then command is successful
title: Test scenario
markdowns:
- emptylines.md
bindings: [b.yaml]
impls:
  python: [f.py]
# Simple
This is the simplest possible test scenario

```scenario
given precondition foo

when I do bar

then bar was done

```

3.11 Automatic cleanup in scenarios

A binding can define a cleanup function, which gets called at the end of the scenario in reverse order for the successful steps. If a step fails, all the cleanups for the successful steps are still called. We test this for every language template we support.

- given: foo
  impl:
    python:
      function: foo
      cleanup: foo_cleanup
- given: bar
  impl:
    python:
      function: bar
      cleanup: bar_cleanup
- given: failure
  impl:
    python:
      function: failure
      cleanup: failure_cleanup
def foo(ctx):
   pass
def foo_cleanup(ctx):
   pass
def bar(ctx):
   pass
def bar_cleanup(ctx):
   pass
def failure(ctx):
   assert 0
def failure_cleanup(ctx):
   pass

3.11.1 Cleanup functions gets called on success (Python)

codegenpython
1 given file cleanup-success-python.subplot
2 given file cleanup-success-python.md
3 given file cleanup.yaml
4 given file cleanup.py
5 given an installed subplot
6 when I run subplot codegen --run cleanup-success-python.subplot -o test.py
7 then scenario "Cleanup" was run
8 then step "givenfoo" was run, and then step "givenbar"
9 then cleanup for "givenbar" was run, and then for "givenfoo"
10 then command is successful
title: Cleanup
markdowns:
- cleanup-success-python.md
bindings: [cleanup.yaml]
impls:
  python: [cleanup.py]
# Cleanup

~~~scenario
given foo
given bar
~~~

3.11.2 Cleanup functions get called on failure (Python)

codegenpython
1 given file cleanup-fail-python.subplot
2 given file cleanup-fail-python.md
3 given file cleanup.yaml
4 given file cleanup.py
5 given an installed subplot
6 when I try to run subplot codegen --run cleanup-fail-python.subplot -o test.py
7 then scenario "Cleanup" was run
8 then step "givenfoo" was run, and then step "givenbar"
9 then cleanup for "givenbar" was run, and then for "givenfoo"
10 then cleanup for "givenfailure" was not run
11 then command fails
title: Cleanup
markdowns:
- cleanup-fail-python.md
bindings: [cleanup.yaml]
impls:
  python: [cleanup.py]
# Cleanup

~~~scenario
given foo
given bar
given failure
~~~

3.12 Temporary files in scenarios in Python

The Python template for generating test programs supports the --save-on-failure option. If the test program fails, it produces a dump of the data directories of all the scenarios it has run. Any temporary files created by the scenario using the usual mechanisms need to be in that dump. For this to happen, the test runner must set the TMPDIR environment variable to point at the data directory. This scenario verifies that it happens.

codegenpython
1 given file tmpdir.subplot
2 given file tmpdir.md
3 given file tmpdir.yaml
4 given file tmpdir.py
5 given an installed subplot
6 when I run subplot codegen --run tmpdir.subplot -o test.py
7 then command is successful
8 then scenario "TMPDIR" was run
9 then step "thenTMPDIR is set" was run
title: TMPDIR
markdowns: [tmpdir.md]
bindings: [tmpdir.yaml]
impls:
  python: [tmpdir.py]
# TMPDIR

~~~scenario
then TMPDIR is set
~~~
- then: TMPDIR is set
  impl:
    python:
      function: tmpdir_is_set
import os
def tmpdir_is_set(ctx):
	assert_eq(os.environ.get("TMPDIR"), os.getcwd())

3.13 Capturing parts of steps for functions

A scenario step binding can capture parts of a scenario step, to be passed to the function implementing the step as an argument. Captures can be done using regular expressions or "simple patterns".

3.13.1 Capture using simple patterns

codegen
1 given file simplepattern.subplot
2 given file simplepattern.md
3 given file simplepattern.yaml
4 given file capture.py
5 given an installed subplot
6 when I run subplot codegen --run simplepattern.subplot -o test.py
7 then scenario "Simple pattern" was run
8 then step "givenI am Tomjon" was run
9 then stdout contains "function got argument name as Tomjon"
10 then command is successful
title: Simple pattern capture
markdowns:
- simplepattern.md
bindings: [simplepattern.yaml]
impls:
  python: [capture.py]
# Simple pattern

~~~scenario
given I am Tomjon
~~~
- given: I am {name}
  impl:
    python:
      function: func
def func(ctx, name=None):
    print('function got argument name as', name)

3.13.2 Simple patterns with regex metacharacters: forbidden case

Help users to avoid accidental regular expression versus simple pattern confusion. The rule is that a simple pattern mustn't contain regular expression meta characters unless the rule is explicitly marked as not being a regular expression pattern.

codegenfailures
1 given file confusedpattern.subplot
2 given file confusedpattern.md
3 given file confusedpattern.yaml
4 given file capture.py
5 given an installed subplot
6 when I try to run subplot codegen --run confusedpattern.subplot -o test.py
7 then command fails
8 then stderr contains "simple pattern contains regex"
title: Simple pattern capture
markdowns:
- confusedpattern.md
bindings: [confusedpattern.yaml]
impls:
  python: [capture.py]
# Simple pattern

~~~scenario
given I* am Tomjon
~~~
- given: I* am {name}
  impl:
    python:
      function: func

3.13.3 Simple patterns with regex metacharacters: allowed case

codegen
1 given file confusedbutok.subplot
2 given file confusedbutok.md
3 given file confusedbutok.yaml
4 given file capture.py
5 given an installed subplot
6 when I run subplot codegen --run confusedbutok.subplot -o test.py
7 then command is successful
title: Simple pattern capture
markdowns:
- confusedbutok.md
bindings: [confusedbutok.yaml]
impls:
  python: [capture.py]
# Simple pattern

~~~scenario
given I* am Tomjon
~~~
- given: I* am {name}
  impl:
    python:
      function: func
  regex: false

3.13.4 Capture using regular expressions

codegen
1 given file regex.subplot
2 given file regex.md
3 given file regex.yaml
4 given file capture.py
5 given an installed subplot
6 when I run subplot codegen --run regex.subplot -o test.py
7 then scenario "Regex" was run
8 then step "givenI am Tomjon" was run
9 then stdout contains "function got argument name as Tomjon"
10 then command is successful
title: Regex capture
markdowns:
- regex.md
bindings: [regex.yaml]
impls:
  python: [capture.py]
# Regex

~~~scenario
given I am Tomjon
~~~
- given: I am (?P<name>\S+)
  impl:
    python:
      function: func
  regex: true

3.14 Recall values for use in later steps

It's sometimes useful to use a value remembered in a previous step. For example, if one step creates a resource with a random number as its name, a later step should be able to use it. This happens in enough projects that Subplot's Python template has support for it.

The Python template has a Context class, with methods remember_value, recall_value, and expand_values. These values are distinct from the other values that can be stored in a context. Only explicitly remembered values may be recalled or expanded so that expansions don't accidentally refer to values meant for another purpose.

codegenpython
1 given file values.subplot
2 given file values.md
3 given file values.yaml
4 given file values.py
5 given an installed subplot
6 when I run subplot codegen values.subplot -o test.py
7 when I run python3 test.py
8 then command is successful
title: Values
markdowns:
- values.md
bindings: [values.yaml]
impls:
  python: [values.py]
# Values

~~~scenario
when I remember foo as bar
then expanded "${foo}" is bar
~~~
- when: I remember {name} as {value}
  impl:
    python:
      function: remember

- then: expanded "{actual}" is {expected}
  impl:
    python:
      function: check
def remember(ctx, name=None, value=None):
	ctx.remember_value(name, value)

def check(ctx, expected=None, actual=None):
    assert_eq(ctx.expand_values(actual), expected)

3.15 Set environment variables in generated test programs

The generated test programs run each scenario with a fixed, almost empty set of environment variables. This is so that tests are more repeatable and less dependent on any values accidentally set by the developers.

However, sometimes it's helpful for the user to be able to set environment variables for the scenarios. For example, if the scenarios test locally built binaries that may be installed anywhere, the installation directory should be added to the PATH variable so that scenarios can invoke the scripts easily.

The scenario in this section verifies that the Python test program generated by subplot codegen accepts the option --env NAME=VALUE.

codegenpython
1 given file env.subplot
2 given file env.md
3 given file env.yaml
4 given file env.py
5 given an installed subplot
6 when I run subplot codegen env.subplot -o test.py
7 when I try to run python3 test.py
8 then command fails
9 when I try to run python3 test.py --env FOO=foo
10 then command fails
11 when I try to run python3 test.py --env FOO=bar
12 then command is successful
title: Environment variables
markdowns:
- env.md
bindings: [env.yaml]
impls:
  python: [env.py]
# Test
~~~scenario
then environment variable FOO is set to "bar"
~~~
- then: environment variable {name} is set to "{value:text}"
  impl:
    python:
      function: is_set_to
import os, sys
def is_set_to(ctx, name=None, value=None):
  sys.stderr.write(f"{name}={os.environ.get(name)!r}\n")
  assert os.environ.get(name) == value

3.16 Document structure

Subplot uses chapters and sections to keep together scenario snippets that form a complete scenario. The lowest level heading before a snippet starts a scenario and is the name of the scenario. If there are subheadings, they divide the description of the scenario into parts, but don't start a new scenario. The next heading at the same or a higher level starts a new scenario.

3.16.1 Lowest level heading is name of scenario

codegen
1 given file scenarioislowest.subplot
2 given file scenarioislowest.md
3 given file b.yaml
4 given file f.py
5 given an installed subplot
6 when I run subplot codegen --run scenarioislowest.subplot -o test.py
7 then scenario "heading 1.1.1" was run
8 then command is successful
title: Test scenario
markdowns:
- scenarioislowest.md
bindings: [b.yaml]
impls:
  python: [f.py]
# heading 1
## heading 1.1
### heading 1.1.1

```scenario
given precondition foo
```

3.16.2 Subheadings don't start new scenario

codegen
1 given file subisnotnewscenario.subplot
2 given file subisnotnewscenario.md
3 given file b.yaml
4 given file f.py
5 given an installed subplot
6 when I run subplot codegen --run subisnotnewscenario.subplot -o test.py
7 then scenario "heading 1.1a" was run
8 then command is successful
title: Test scenario
markdowns:
- subisnotnewscenario.md
bindings: [b.yaml]
impls:
  python: [f.py]
# heading 1
## heading 1.1a

```scenario
given precondition foo
```

### heading 1.1.1
### heading 1.1.2

3.16.3 Next heading at same level starts new scenario

codegen
1 given file samelevelisnewscenario.subplot
2 given file samelevelisnewscenario.md
3 given file b.yaml
4 given file f.py
5 given an installed subplot
6 when I run subplot codegen --run samelevelisnewscenario.subplot -o test.py
7 then scenario "heading 1.1.1" was run
8 then scenario "heading 1.1.2" was run
9 then command is successful
title: Test scenario
markdowns:
- samelevelisnewscenario.md
bindings: [b.yaml]
impls:
  python: [f.py]
# heading 1
## heading 1.1
### heading 1.1.1

```scenario
given precondition foo
```
### heading 1.1.2

```scenario
given precondition foo
```

3.16.4 Next heading at higher level starts new scenario

codegen
1 given file higherisnewscenario.subplot
2 given file higherisnewscenario.md
3 given file b.yaml
4 given file f.py
5 given an installed subplot
6 when I run subplot codegen --run higherisnewscenario.subplot -o test.py
7 then scenario "heading 1.1.1" was run
8 then scenario "heading 1.2" was run
9 then command is successful
title: Test scenario
markdowns:
- higherisnewscenario.md
bindings: [b.yaml]
impls:
  python: [f.py]
# heading 1
## heading 1.1
### heading 1.1.1

```scenario
given precondition foo
```
## heading 1.2

```scenario
given precondition foo
```

3.16.5 Document titles

The document and code generators require a document title, because it's a common user error to not have one, and Subplot should help make good documents.

3.16.5.1 Document generator gives an error if input document lacks title

docgenfailures
1 given file notitle.subplot
2 given file notitle.md
3 given an installed subplot
4 when I try to run subplot docgen notitle.subplot -o foo.md
5 then command fails
markdowns:
- notitle.md
bindings: [b.yaml]
functions: [f.py]
# Introduction

This is a very simple Markdown file without a document title.

```scenario
given precondition foo
when I do bar
then bar was done

3.16.5.2 Code generator gives an error if input document lacks title

codegenfailures
1 given file notitle.subplot
2 given file notitle.md
3 given an installed subplot
4 when I try to run subplot codegen --run notitle.subplot -o test.py
5 then command fails

3.16.5.3 Subplot accepts title and headings with inline markup

Markdown allows using any inline markup in document titles and chapter and section headings. Verify that Subplot accepts them.

docgencodegen
1 given file fancytitle.subplot
2 given file fancytitle.md
3 given file b.yaml
4 given file f.py
5 given an installed subplot
6 when I try to run subplot docgen fancytitle.subplot -o foo.md
7 then command is successful
8 when I try to run subplot codegen fancytitle.subplot -o foo.md
9 then command is successful
title: Plain *emph* **strong** ~~strikeout~~ ^superscript10^ ~subscript10~
markdowns:
- fancytitle.md
bindings: [b.yaml]
impls:
  python: [f.py]
# `code` [smallcaps]{.smallcaps} $$2^10$$

## "double quoted"
## 'single quoted'
## <b>raw inline</b>
## <span>span</span>
## ![alt](image.jpg)
## footnote[^1]

[^1]: footnote

This is a very simple Markdown file that uses every kind of inline
markup in the title and chapter heading.

To satisfy codegen, we *MUST* have a scenario here

~~~~scenario
when I do bar
then bar was done
~~~~

3.16.6 Running only chosen scenarios

To make the edit-test loop more convenient for the test programs generated by Subplot, we allow the user to specify patterns for scenarios to run. Default is to run all scenarios.

3.16.7 Running only chosen scenarios with Python

This verifies that the generated Python test program can run only chosen scenarios.

codegenpython
1 given file twoscenarios-python.subplot
2 given file twoscenarios-python.md
3 given file b.yaml
4 given file f.py
5 given an installed subplot
6 when I run subplot codegen twoscenarios-python.subplot -o test.py
7 when I run python3 test.py on
8 then scenario "One" was run
9 then scenario "Two" was not run
10 then command is successful
title: Test scenario
markdowns:
- twoscenarios-python.md
bindings: [b.yaml]
impls:
  python: [f.py]
# One

```scenario
given precondition foo
when I do bar
then bar was done
```

# Two

```scenario
given precondition foo
when I do bar
then bar was done
```

3.17 Document metadata

Some document metadata should end up in the typeset document, especially the title, authors. The document date is more complicated, to cater to different use cases:

  • a work-in-progress document needs a new date for each revision
    • maintaining the date metadata field manually is quite tedious, so Subplot provides it automatically using the document source file modification time
    • some people would prefer a git describe or similar method for indicating the document revision, so Subplot allows the date to be specified via the command line
  • a finished, reviewed, officially stamped document needs a fixed date
    • Subplot allows this to be written as the date metadata field

The rules for what Subplot uses as the date or document revision information are, then:

  • if there is date metadata field, that is used
  • otherwise, if the user gives the --date command line option, that is used
  • otherwise, the markdown file's modification time is used

3.17.1 Date given in metadata

This scenario tests that the date field in metadata is used if specified.

docgen
1 given file metadate.subplot
2 given file metadate.md
3 given an installed subplot
4 when I run subplot docgen metadate.subplot -o metadate.html
5 when I run cat metadate.html
6 then file metadate.html exists
7 then file metadate.html contains "<title>The Fabulous Title</title>"
8 then file metadate.html contains "Alfred Pennyworth"
9 then file metadate.html contains "Geoffrey Butler"
10 then file metadate.html contains "WIP"
title: The Fabulous Title
authors:
- Alfred Pennyworth
- Geoffrey Butler
date: WIP
markdowns:
- metadate.md
# Introduction
This is a test document. That's all.

3.17.2 Date given on command line

This scenario tests that the --date command line option is used.

docgen
1 given file dateless.subplot
2 given file dateless.md
3 given an installed subplot
4 when I run subplot docgen dateless.subplot -o dateoption.html --date=FANCYDATE
5 then file dateoption.html exists
6 then file dateoption.html contains "<title>The Fabulous Title</title>"
7 then file dateoption.html contains "Alfred Pennyworth"
8 then file dateoption.html contains "Geoffrey Butler"
9 then file dateoption.html contains "FANCYDATE"
title: The Fabulous Title
authors:
- Alfred Pennyworth
- Geoffrey Butler
markdowns:
- dateless.md
# Introduction
This is a test document. It has no date metadata.

3.17.3 No date anywhere

This scenario tests the case of no metadata date and no command line option, either. The date in the typeset document shall come from the modification time of the input file, and shall have the date in ISO 8601 format, with time to the minute.

docgen
1 given file dateless.subplot
2 given file dateless.md
3 given file dateless.md has modification time 2020-02-26 07:53:17
4 given an installed subplot
5 when I run subplot docgen dateless.subplot -o mtime.html
6 then file mtime.html exists
7 then file mtime.html contains "<title>The Fabulous Title</title>"
8 then file mtime.html contains "Alfred Pennyworth"
9 then file mtime.html contains "Geoffrey Butler"
10 then file mtime.html contains "2020-02-26 07:53"

3.17.4 Missing bindings file

If a bindings file is missing, the error message should name the missing file.

docgenfailures
1 given file missing-binding.subplot
2 given file missing-binding.md
3 given an installed subplot
4 when I try to run subplot docgen missing-binding.subplot -o foo.html
5 then command fails
6 then stderr contains "could not be found"
7 then stderr contains "missing-binding.yaml"
title: Missing binding
markdowns:
- missing-binding.md
bindings: [missing-binding.yaml]
This is a markdown file.

3.17.5 Missing functions file

If a functions file is missing, the error message should name the missing file.

codegenfailures
1 given file missing-functions.subplot
2 given file missing-functions.md
3 given file b.yaml
4 given an installed subplot
5 when I try to run subplot codegen --run missing-functions.subplot -o foo.py
6 then command fails
7 then stderr contains "could not be found"
8 then stderr contains "missing-functions.py"
---
title: Missing functions
markdowns:
- missing-functions.md
bindings: [b.yaml]
impls:
  python: [missing-functions.py]
...
This file is empty.

3.17.6 Extracting metadata from a document

The subplot metadata program extracts metadata from a document. It is useful to see the scenarios, for example. For example, given a document like this:

subplot metadata would extract this information from the simple.md example:

title: Test scenario
bindings: [b.yaml]
impls:
  python: [f.py]
scenario Simple

This scenario check subplot metadata works. Note that it requires the bindings or functions files.

1 given file images.subplot
2 given file images.md
3 given file b.yaml
4 given file other.yaml
5 given file f.py
6 given file other.py
7 given file expected.json
8 given an installed subplot
9 when I run subplot metadata images.subplot
10 then stdout contains "source: images.md"
11 then stdout contains "source: b.yaml"
12 then stdout contains "source: other.yaml"
13 then stdout contains "source: f.py"
14 then stdout contains "source: other.py"
15 then stdout contains "source: image.gif"
16 then stdout contains "bindings: b.yaml"
17 then stdout contains "bindings: other.yaml"
18 then stdout contains "functions[python]: f.py"
19 when I run subplot metadata images.subplot -o json
20 then JSON output matches expected.json
title: Document refers to external images
markdowns:
- images.md
bindings:
- b.yaml
- other.yaml
impls:
  python:
    - f.py
    - other.py
![alt text](image.gif)
[]
pass
{
	"title": "Document refers to external images",
    "sources": [
	  "b.yaml",
	  "f.py",
	  "image.gif",
	  "images.md",
      "images.subplot",
	  "other.py",
	  "other.yaml"
    ],
	"binding_files": [
	  "b.yaml",
	  "other.yaml"
	],
	"impls": {
    "python": [
	    "f.py",
	    "other.py"
	  ]
  },
	"files": [],
	"scenarios": []
}

3.17.7 Multiple markdown files

This scenario tests that the markdowns field in metadata can specify more than one markdown file.

docgen
1 given file multimd.subplot
2 given file md1.md
3 given file md2.md
4 given an installed subplot
5 when I run subplot docgen multimd.subplot -o multimd.html
6 when I run cat multimd.html
7 then file multimd.html exists
8 then file multimd.html contains "<title>The Fabulous Title</title>"
9 then file multimd.html contains "First markdown file."
10 then file multimd.html contains "Second markdown file."
title: The Fabulous Title
authors:
- Alfred Pennyworth
- Geoffrey Butler
date: WIP
markdowns:
- md1.md
- md2.md
First markdown file.
Second markdown file.

3.18 Embedded files

Subplot allows data files to be embedded in the input document. This is handy for small test files and the like.

Handling of a newline character on the last line is tricky. The block ends in a newline on the last line. Sometimes one is needed—but sometimes it's not wanted. Subplot helps the situation by allowing a add-newline= class to be added to the code blocks, with one of three allowed cases:

  • no add-newline class—default handling: same as add-newline=auto
  • add-newline=auto—add a newline, if one isn't there
  • add-newline=no—never add a newline, but keep one if it's there
  • add-newline=yes—always add a newline, even if one is already there

The scenarios below test the various cases.

3.18.1 Extract embedded file

This scenario checks that an embedded file can be extracted, and used in a subplot.

docgen
1 given file embedded.subplot
2 given file embedded.md
3 given an installed subplot
4 when I run subplot docgen --merciful embedded.subplot -o foo.html
5 then file foo.html exists
6 then file foo.html matches regex /embedded\.txt/
title: One embedded file
markdowns:
- embedded.md
~~~{#embedded.txt .file}
This is the embedded file.
~~~

3.18.2 Extract embedded file, by default add missing newline

This scenario checks the default handling: add a newline if one is missing.

embedded-files
1 given file default-without-newline.txt
2 then default-without-newline.txt ends in one newline
This file does not end in a newline.

3.18.3 Extract embedded file, by default do not add a second newline

This scenario checks the default handling: if content already ends in a newline, do not add another newline.

embedded-files
1 given file default-has-newline.txt
2 then default-has-newline.txt ends in one newline
This file ends in a newline.

3.18.4 Extract embedded file, automatically add missing newline

Explicitly request automatic newlines, when the file does not end in one.

embedded-files
1 given file auto-without-newline.txt
2 then auto-without-newline.txt ends in one newline
This file does not end in a newline.

3.18.5 Extract embedded file, do not automatically add second newline

Explicitly request automatic newlines, when the file already ends in one.

embedded-files
1 given file auto-has-newline.txt
2 then auto-has-newline.txt ends in one newline
This file ends in a newline.

3.18.6 Extract embedded file, explicitly add missing newline

Explicitly request automatic newlines, when the file doesn't end with one.

embedded-files
1 given file add-without-newline.txt
2 then add-without-newline.txt ends in one newline
This file does not end in a newline.

3.18.7 Extract embedded file, explicitly add second newline

Explicitly request automatic newlines, when the file already ends with one.

embedded-files
1 given file add-has-newline.txt
2 then add-has-newline.txt ends in two newlines
This file ends in a newline.

3.18.8 Extract embedded file, do not add missing newline

Explicitly ask for no newline to be added.

embedded-files
1 given file no-adding-without-newline.txt
2 then no-adding-without-newline.txt does not end in a newline
This file does not end in a newline.

3.18.9 Fail if the same filename is used twice

embedded-filesfailures
1 given file onefiletwice.md
2 given an installed subplot
3 when I try to run subplot docgen onefiletwice.md -o onefiletwice.html
4 then command fails
5 then file onefiletwice.html does not exist
---
title: Two embedded files with the same name
...

```{#filename .file}
This is the embedded file.
```

```{#filename .file}
This is another embedded file, and has the same name.
```

3.18.10 Fail if two filenames only differ in case

docgenembedded-filesfailures
1 given file casediff.md
2 given an installed subplot
3 when I try to run subplot docgen casediff.md -o casediff.html
4 then command fails
5 then file casediff.html does not exist
---
title: Two embedded files with names differing only in case
...

```{#filename .file}
This is the embedded file.
```

```{#FILENAME .file}
This is another embedded file, and has the same name in uppercase.
```

3.18.11 Fail if embedded file isn't used

This scenario checks that we get warnings, when using a subplot with embedded files that aren't used.

docgenfailuresembedded-files
1 given file unusedfile.subplot
2 given file unusedfile.md
3 given an installed subplot
4 when I try to run subplot docgen --merciful unusedfile.subplot -o unusedfile.html
5 then command is successful
6 then file unusedfile.html exists
7 then stderr contains "thisisnotused.txt"
title: Embedded file is not used by a scenario
markdowns:
- unusedfile.md
```{#thisisnotused.txt .file}
This is the embedded file.
```

3.19 Example blocks

Similar to embedded files, Subplot permits you to mark blocks as examples. Example blocks are formatted just like file blocks, but they may not be used by scenarios and their names are separated from files, and are not subject to the same naming constraints (caseless uniqueness).

3.19.1 Examples may be unused

docgenexamples
1 given file unusedexample.subplot
2 given file unusedexample.md
3 given an installed subplot
4 when I try to run subplot docgen --merciful unusedexample.subplot -o unusedexample.html
5 then command is successful
6 then file unusedexample.html exists
7 then stderr doesn't contain "thisisnotused.txt"
title: Example is not an embedded file
markdowns:
- unusedexample.md
```{#thisisnotused.txt .example}
This is the embedded example.
```

3.19.2 Examples are not files

codegenexamplesembedded-filesfailures
1 given file examplesnotfiles.subplot
2 given file examplesnotfiles.md
3 given an installed subplot
4 when I try to run subplot codegen examplesnotfiles.subplot -t python -o examplesnotfiles.html
5 then command fails
6 then file examplesnotfiles.html does not exist
7 then stderr contains "thisisanexample.txt"
title: Examples are not files
markdowns:
- examplesnotfiles.md
impls:
  python: []
# Try and use an example as a file

```scenario
given file thisisanexample.txt
```

```{#thisisanexample.txt .example}
This is an embedded example
```

3.20 Steps must match bindings

Subplot permits the binding author to define arbitrarily complex regular expressions for binding matches. In order to ensure that associating steps to bindings is both reliable and tractable, a step must match exactly one binding.

- given: a binding
  impl:
    python:
      function: a_binding
- given: a (?:broken)? binding
  impl:
    python:
      function: a_broken_binding
  regex: true
- given: a capitalised Binding
  impl:
    python:
      function: os.getcwd
  case_sensitive: true

3.20.1 Steps which do not match bindings do not work

title: No bindings available
markdowns:
- nobinding.md
bindings:
- badbindings.yaml
# Broken scenario because step has no binding

```scenario
given a missing binding
then nothing works
```
codegenbindingsfailures
1 given file nobinding.subplot
2 given file nobinding.md
3 given file badbindings.yaml
4 given an installed subplot
5 when I try to run subplot codegen --run nobinding.subplot -o test.py
6 then command fails

3.20.2 Steps which do not case-sensitively match sensitive bindings do not work

title: Case sensitivity mismatch
markdowns:
- casemismatch.md
impls: { python: [] }
bindings:
- badbindings.yaml
# Broken scenario because step has a case mismatch with sensitive binding

```scenario
given a capitalised binding
```
codegenbindingsfailures
1 given file casemismatch.subplot
2 given file casemismatch.md
3 given file badbindings.yaml
4 given an installed subplot
5 when I try to run subplot codegen --run casemismatch.subplot -o test.py
6 then command fails

3.20.3 Steps which match more than one binding do not work

title: Two bindings match
markdowns:
- twobindings.md
bindings:
- twobindings.yaml
impls:
  python: [a_function.py]
# Broken scenario because step has two possible bindings

```scenario
given a binding
```
- given: a {xyzzy}
  impl:
    python:
      function: a_function
- given: a {plugh}
  impl:
    python:
      function: a_function
def a_function(ctx):
    assert 0
codegenfailuresbindings
1 given file twobindings.subplot
2 given file twobindings.md
3 given file twobindings.yaml
4 given file a_function.py
5 given an installed subplot
6 when I try to run subplot codegen --run twobindings.subplot -o test.py
7 then command fails
8 then stderr contains "xyzzy"
9 then stderr contains "plugh"

3.20.4 List embedded files

The subplot metadata command lists embedded files in its output.

1 given file two-embedded.subplot
2 given file two-embedded.md
3 given an installed subplot
4 when I run subplot metadata --merciful two-embedded.subplot
5 then stdout contains "foo.txt"
6 then stdout contains "bar.yaml"
title: Two embedded files
markdowns:
- two-embedded.md
~~~{#foo.txt .file}
~~~

~~~{#bar.yaml. .file}
~~~

3.21 Embedded diagrams

Subplot allows embedding markup to generate diagrams into the Markdown document.

3.21.1 Pikchr

Pikchr is a diagramming library which implements a Pic-like diagram language. It allows the conversion of textual descriptions of arbitrarily complex diagrams into SVGs such as this one.

Pikchr diagram

The scenario checks that a diagram is generated and embedded into the HTML output, and is not referenced as an external image.

docgendiagramspikchr
1 given file pikchr.subplot
2 given file pikchr.md
3 given an installed subplot
4 when I run subplot docgen pikchr.subplot -o pikchr.html
5 then file pikchr.html matches regex /src="data:image/svg\+xml;base64,/

The sample input file pikchr.md:

---
This is an example markdown file that embeds a simple Pikchr diagram.

~~~pikchr
arrow right 200% "Markdown" "Source"
box rad 10px "Markdown" "Formatter" "(docs.rs/markdown)" fit
arrow right 200% "HTML+SVG" "Output"
arrow <-> down 70% from last box.s
box same "Pikchr" "Formatter" "(docs.rs/pikchr)" fit
~~~

title: Pikchr test
markdowns:
- pikchr.md

3.21.2 Dot

Dot is a program from the Graphviz suite to generate directed diagrams, such as this one.

Dot diagram

The scenario checks that a diagram is generated and embedded into the HTML output, not referenced as an external image.

docgendiagramsdot
1 given file dot.subplot
2 given file dot.md
3 given file b.yaml
4 given an installed subplot
5 when I run subplot docgen dot.subplot -o dot.html
6 then file dot.html matches regex /src="data:image/svg\+xml;base64,/

The sample input file dot.md:

This is an example Markdown file, which embeds a diagram using dot markup.

~~~dot
digraph "example" {
thing -> other
}
~~~
title: Dot test
markdowns:
- dot.md

3.21.3 PlantUML

PlantUML is a program to generate various kinds of diagrams for describing software, such as this one:

UML diagram

The scenario below checks that a diagram is generated and embedded into the HTML output, not referenced as an external image.

docgendiagramsplantuml
1 given file plantuml.subplot
2 given file plantuml.md
3 given file b.yaml
4 given an installed subplot
5 when I run subplot docgen plantuml.subplot -o plantuml.html
6 then file plantuml.html matches regex /src="data:image/svg\+xml;base64,/

The sample input file plantuml.md:

This is an example Markdown file, which embeds a diagram using
PlantUML markup.

~~~plantuml
@startuml
Alice -> Bob: Authentication Request
Bob --> Alice: Authentication Response

Alice -> Bob: Another authentication Request
Alice <-- Bob: Another authentication Response
@enduml
~~~
title: Plantuml test
markdowns:
- plantuml.md

3.21.4 Roadmap

Subplot supports visual roadmaps using a YAML based markup language, implemented by the [roadmap][] Rust library. The library converts the roadmap into dot, and that gets rendered as SVG and embedded in the output document by Subplot.

An example:

Road map

This scenario checks that a diagram is generated and embedded into the HTML output, not referenced as an external image.

docgendiagramsroadmap
1 given file roadmap.subplot
2 given file roadmap.md
3 given file b.yaml
4 given an installed subplot
5 when I run subplot docgen roadmap.subplot -o roadmap.html
6 then file roadmap.html matches regex /src="data:image/svg\+xml;base64,/

The sample input file roadmap.md:

This is an example Markdown file, which embeds a roadmap.

~~~roadmap
goal:
  label: |
    This is the end goal:
    if we reach here, there
    is nothing more to be
    done in the project
  depends:
  - finished
  - blocked

finished:
  status: finished
  label: |
    This task is finished;
    the arrow indicates what
    follows this task (unless
    it's blocked)

ready:
  status: ready
  label: |
    This task is ready
    to be done: it is not
    blocked by anything

next:
  status: next
  label: |
    This task is chosen
    to be done next

blocked:
  status: blocked
  label: |
    This task is blocked
    and can't be done until
    something happens
  depends:
  - ready
  - next
~~~
title: Roadmap test
markdowns:
- roadmap.md

3.21.5 Class name validation

When Subplot loads a document it will validate that the block classes match a known set. Subplot has a built-in set which it treats as special, and it knows some custom classes and a number of file type classes.

If the author of a document wishes to use additional class names then they can include a classes list in the document metadata which subplot will treat as valid.

docgenfailures
1 given file unknown-class-name.subplot
2 given file unknown-class-name.md
3 given file known-class-name.subplot
4 given file known-class-name.md
5 given file b.yaml
6 given an installed subplot
7 when I try to run subplot docgen unknown-class-name.subplot -o unknown-class-name.html
8 then command fails
9 then file unknown-class-name.html does not exist
10 then stderr contains "Unknown classes found in the document: foobar"
11 when I run subplot docgen known-class-name.subplot -o known-class-name.html
12 then file known-class-name.html exists
title: A document with an unknown class name
markdowns:
- unknown-class-name.md
```foobar
This content is foobarish
```
title: A document with a previously unknown class name
markdowns:
- known-class-name.md
classes:
- foobar
```foobar
This content is foobarish
```

3.22 Extract embedded files

subplot extract extracts embedded files from a subplot file.

extract
1 given file embedded-file.subplot
2 given file embedded-file.md
3 given file expected.txt
4 given an installed subplot
5 when I run subplot extract --merciful embedded-file.subplot foo.txt -d .
6 then files foo.txt and expected.txt match
title: Embedded file
markdowns:
- embedded-file.md
~~~{#foo.txt .file}
This is a test file.
~~~
This is a test file.

3.23 Mistakes in markdown

When there are mistakes in the markdown input, Subplot should report the location (filename, line, column) where the mistake is, and what the mistake is. The scenarios in this section verify that.

3.23.1 Scenario before the first heading

Requirement: A scenario must follow a heading.

Justification: the heading can be used as the title for the scenario.

docgenfailures
1 given an installed subplot
2 given file scenario-before-heading.subplot
3 given file scenario-before-heading.md
4 when I try to run subplot docgen scenario-before-heading.subplot -o /dev/null
5 then command fails
6 then stderr contains "ERROR scenario-before-heading.md:1:1: first scenario is before first heading"
title: Foo
markdowns:
  - scenario-before-heading.md
~~~scenario
~~~

3.23.2 Attempt to use definition list

Requirement: Attempt to use definition lists is reported.

Justification: the markdown parser we use in Subplot doesn't support them, and it would be unhelpful to not tell the user if they try to use them.

docgenfailures
1 given an installed subplot
2 given file dl.subplot
3 given file dl.md
4 when I try to run subplot docgen dl.subplot -o /dev/null
5 then command fails
6 then stderr contains "ERROR dl.md:3:1: attempt to use definition lists in Markdown"
title: Foo
markdowns:
  - dl.md
# Foo

Some term
: Definition of term.

3.23.3 Bad "add-newline" value

Requirement: Only specific values for the "add-newline" attribute are allowed for an embedded file.

docgenfailures
1 given an installed subplot
2 given file add-newline.subplot
3 given file add-newline.md
4 when I try to run subplot docgen add-newline.subplot -o /dev/null
5 then command fails
6 then stderr contains "ERROR add-newline.md:1:1: value of add-newline attribute is not understood: xyzzy"
title: Foo
markdowns:
  - add-newline.md
~~~{#foo.txt .file add-newline=xyzzy}
~~~

3.24 HTML output

3.24.1 Embedded CSS

Requirement: The user can specify CSS files to embed in the HTML output.

Justification: We want to allow production of self-standing output with user-defined styling.

docgen
1 given file embedded-css.subplot
2 given file embedded-css.md
3 given file embedded-css.css
4 given file b.yaml
5 given an installed subplot
6 when I run subplot docgen embedded-css.subplot -o foo.html
7 then file foo.html contains "silly: property;"
title: Embedded CSS
markdowns:
  - embedded-css.md
bindings:
  - b.yaml
css_embed:
 - embedded-css.css
# This is a title

~~~scenario
given precondition
~~~
html {
    silly: property;
}

3.24.2 CSS URLs

Requirement: The user can specify CSS URLs to add in the HTML output.

Justification: We want to allow users to specify non-embedded CSS.

docgen
1 given file css-urls.subplot
2 given file css-urls.md
3 given file b.yaml
4 given an installed subplot
5 when I run subplot docgen css-urls.subplot -o foo.html
6 then file foo.html contains "https://example.com/flushing.css"
title: Embedded CSS
markdowns:
  - css-urls.md
bindings:
  - b.yaml
css_urls:
  - https://example.com/flushing.css
# This is a title

~~~scenario
given precondition
~~~

3.25 Running Subplot

The scenarios in this section verify that the Subplot tool can be run in various specific ways.

3.25.1 Files not in current working directory

Requirement: Subplot can process a subplot that is not in the current working directory.

codegendocgen
1 given file x/simple.subplot from simple.subplot
2 given file x/simple.md from simple.md
3 given file x/b.yaml from b.yaml
4 given file x/f.py from f.py
5 given an installed subplot
6 when I run subplot metadata x/simple.subplot
7 then command is successful
8 when I run subplot codegen x/simple.subplot -o test.py
9 then file test.py exists
10 when I run subplot docgen x/simple.subplot -o simple.html
11 then file simple.html exists