1 Introduction
Subplot is software to help capture and communicate acceptance criteria for software and systems, and how they are verified, in a way that's understood by all project stakeholders. The current document contains the acceptance criteria for Subplot itself, and its architecture.
The acceptance criteria are expressed as scenarios, which roughly correspond to use cases. The scenario as accompanied by explanatory text to explain things to the reader. Scenarios use a given/when/then sequence of steps, where each step is implemented by code provided by the developers of the system under test. This is very similar to the Cucumber tool, but with more emphasis on producing a standalone document.
1.1 Acceptance criteria and acceptance tests
We define the various concepts relevant to Subplot as follows:
-
Acceptance criteria: What the stakeholders require of the system for them to be happy with it and use it.
-
Stakeholder: Someone with a keen interest in the success of a system. They might be a paying client, someone who uses the system, or someone involved in developing the system. Depending on the system and project, some stakeholders may have a bigger say than others.
-
Acceptance test: How stakeholders verify that the system fulfills the acceptance criteria, in an automated way. Some criteria may not be possible to verify automatically.
-
Scenario: In Subplot, the acceptance criteria are written as freeform prose, with diagrams, etc. The scenarios, which are embedded blocks of Subplot scenario language, capture the mechanisms of verifying that criteria are met - the acceptance tests - showing step by step how to determine that the software system is acceptable to the stakeholders.
1.2 A basic workflow for using Subplot
We recommend the following initial approach to using Subplot, which you can vary based on your particular needs and circumstances.
- Start with a small acceptance document that you think expresses some useful requirements.
- Write some acceptance criteria and have them agreed among the stakeholders.
- Write scenarios to verify that the criteria are met, and have those scenarios agreed by the stakeholders.
- Write bindings and test functions, so that as the code is written it can be tested against the acceptance criteria.
- Iterate on this in short cycles to maximise discussion and stakeholder buy-in.
You definitely want to keep the subplot document source code in version control. You certainly need to have people who can write technical text that's aimed at all your stakeholders.
1.3 Subplot architecture
Subplot reads an input document, in Markdown, and generates a typeset output document, as HTML, for all stakeholders to understand. Subplot also generates a test program, in Python, that verifies the acceptance criteria are met, for developers and testers and auditors to verify the system under test meets its acceptance criteria. The generated program uses code written by the Subplot user to implement the verification steps. The diagram below illustrates this and shows how data flows through the system.
Subplot generated HTML itself.
Subplot actually consists mainly of two separate programs: subplot docgen for generating output documents, and subplot codegen for generating the test program. There are a couple of additional tools (subplot metadata for reporting meta data about a Subplot document, and subplot extract for extracting embedded files from a subplot document.
Thus a more detailed architecture view is shown below.
1.4 A fairy tale of acceptance testing
The king was upset. This naturally meant the whole court was in a tizzy and chattering excitedly at each other, while trying to avoid the royal wrath.
"Who will rid me of this troublesome chore?" shouted the king, and quaffed a flagon of wine. "And no killing of priests, this time!"
The grand hall's doors were thrown open. The grand wizard stood in the doorway, robe, hat, and staff everything, but quite still. After the court became silent, the wizard strode confidently to stand before the king.
"What ails you, my lord?"
The king looked upon the wizard, and took a deep breath. It does not do to shout at wizards, for they control dragons, and even kings are tasty morsels to the great beasts.
"I am tired of choosing what to wear every day. Can't you do something?"
The wizard stoked his long, grey beard. He turned around, looked at the magnificent outfits worn by members of the court. He turned back, and looked at the king.
"I believe I can fix this. Just to be clear, your beef is with having to choose clothing, yes?"
"Yes", said the king, "that's what I said. When will you be done?"
The wizard raised his staff and brought it back down again, with a loud bang.
"Done" said the wizard, smugly.
The king was amazed and started smiling, until he noticed that everyone, including himself, was wearing identical burlap sacks and nothing on their feet. His voice was high, whiny, like that of a little child.
"Oh no, that's not at all what I wanted! Change it back! Change it back now!"
The morale of this story is to be clear and precise in your acceptance criteria, or you might get something other than what you really, really wanted.
1.5 Motivation for Subplot
Keeping track of requirements and acceptance criteria is necessary for all but the simplest of software projects. Having all stakeholders in a project agree to them is crucial, as is that all agree how it is verified that the software meets the acceptance criteria. Subplot provides a way for documenting the shared understanding of what the acceptance criteria are and how they can be checked automatically.
Stakeholders in a project may include:
- those who pay for the work to be done; this may be the employer of the developers for in-house projects ("customer")
- those who use the resulting systems, whether they pay for it or not ("user")
- those who install and configure the systems and keep them functional ("sysadmin")
- those who support the users ("support")
- those who test the project for acceptability ("tester")
- those who develop the system in the first place ("developer")
The above list is incomplete and simplistic, but suffices as an example.
All stakeholders need to understand the acceptance criteria, and how the system is evaluated against the criteria. In the simplest case, the customer and the developer need to both understand and agree so that the developer knows when the job is done, and the customer knows when they need to pay their bill.
However, even when the various stakeholder roles all fall upon the same person, or only on people who act as developers, the Subplot tooling can be useful. A developer would understand acceptance criteria expressed only in code, but doing so may take time and energy that are not always available. The Subplot approach aims to encourage hiding unnecessary detail and documenting things in a way that is easy to understand with little effort.
Unfortunately, this does mean that for a Subplot output document to be good and helpful, writing it will require effort and skill. No tool can replace that.
1.6 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 ./check
script 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.md -o test.py $ python3 test.py ... 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.md -o test.py $ 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.md -o test.py $ 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 Subplot input language
Subplot reads three input files, each in a different format:
- The document file in GitHub Flavored Markdown.
- The bindings file, in YAML.
- The functions file, in Bash or Python.
Subplot interprets marked parts of the input document
specially. These are fenced code blocks tagged with the sceanrio
,
file
, or example
classes.
3.1 Scenario language
The scenarios are core to Subplot. They express what the detailed acceptance criteria are and how they're verified. The scenarios are meant to be understood by both all human stakeholders and the Subplot software. As such, they are expressed in a somewhat stilted language that resembles English, but is just formal enough that it can also be understood by a computer.
A scenario is a sequence of steps. A step can be setup to prepare for an action, an action, or an examination of the effect an action had. For example, a scenario to verify that a backup system works might look like the following:
~~~scenario given a backup server when I make a backup and I restore the backup then the restored data is identical to the original data ~~~
This is not magic. The three kinds of steps are each identified by the first word in the step.
given
means it's a step to set up the environment for the scenariowhen
means it's a step with the action that the scenario verifiesthen
means it's a step to examine the results of the action
The and
keyword is special in that it means the step is the same
kind as the previous step. In the example, on line 4, it means the
step is a when
step.
Each step is implemented by a bit of code, provided by the author of the subplot document. The step is bound to the code via a binding file, via the text of the step: if the text is like this, then call that function. Bindings files are described in detail shortly below.
The three kinds of steps exist to make scenarios easier to understand by humans. Subplot itself does not actually care if a step is setup, action, or examination, but it's easier for humans reading the scenario, or writing the corresponding code, if each step only does the kind of work that is implied by the kind of step it's bound to.
3.1.1 Using Subplot's language effectively
Your subplot scenarios will be best understood when they use the subplot
language in a consistent fashion, within and even across different projects.
As with programming languages, it's possible to place your own style on your
subplots. Indeed, there is no inherent internal implementation difference between
how given
, when
and then
steps are processed (other than that given
steps often also have cleanup functions associated with them).
Nonetheless we have some recommendations about using the Subplot language, which reflect how we use it in Subplot and related projects.
When you are formulating your scenarios, it is common to try and use phraseology along the lines of if this happens then that is the case but this is not language which works well with subplot. Scenarios describe what will happen in the success case. As such we don't construct scenarios which say if foo happens then the case fails, instead we say when I do the thing then foo does not happen. This is a subtle but critical shift in the construction of your test cases which will mean that they map more effectively to scenarios.
Scenarios work best when they describe how some entity (human or otherwise)
actually goes about successfully achieving their goal. They start out by setting
the scene for the goal (given
) they go on to describe the actions/activity
undertaken in order for the goal to be achieved (when
) and they describe how
the entity knows that the goal has been achieved (then
). By writing in this
active goal-oriented fashion, your scenarios will flow better and be easier for
all stakeholders to understand.
In general you should use given
statements where you do not wish to go into
the detail of what it means for the statement to have been run, you simply wish
to inform the reader that some precondition is met. These statements are often
best along the lines of given a setup which works
or given a development enviroment
or somesuch.
The when
statements are best used to denote active steps. These are
the steps which your putative actors or personae use to achieve their goals.
These often work best in the form when I do the thing
or
when the user does the thing
.
The then
statements are the crux of the scenario, they are the validation
steps. These are the steps which tell the reader of the scenario how the actor
knows that their action (the when
steps) has had the desired outcome. This
could be of the form then some output is present
or then it exits successfully
.
With all that in mind, a good scenario looks like
given the necessary starting conditions when I do the required actions then the desired outcome is achieved
Given all that, however, it's worth considering some pitfalls to avoid when writing your scenarios.
It's best to avoid overly precise or overly technical details in your scenario
language (unless that's necessary to properly describe your goal etc.) So
it's best to say things like then the output file is valid JSON
rather than
then the output file contains {"foo": "bar", "baz": 7}
. Obviously if the
actual values are important then again, statements such as then the output file has a key "foo" which contains the value "bar"
or similar.
Try not to change "person" or voice in your scenarios unless there are multiple
entities involved in telling your stories. For example, if you have a scenario
statement of when I run fooprogram
do not also have statements in the passive
such as when fooprogram is run
. It's reasonable to switch between when
and
then
statements (then the output is good
) but try not to have multiple
then
statements which switch it up, such as then I have an output file
,
and the output file is ok
.
If you're likely to copy-paste your scenario statements around, do not use and
as a scenario keyword, even though it's valid to do so. Instead start all your
scenario statements with the correct given
, when
, or then
. The typesetter
will deal with formatting that nicely for you.
3.2 Document markup
Subplot parses Markdown input files using GitHub-flavored Markdown.
Subplot extends Markdown by treating certain certain tags for fenced code blocks specially. A scenario, for example, would look like this:
```scenario given a standard setup when peace happens then everything is OK ```
The scenario
tag on the code block is recognized by Subplot, which
will typeset the scenario (in output documents) or generate code (for
the test program) accordingly. Scenario blocks do not need to be
complete scenario. Subplot will collect all the snippets into one
block for the test program. Snippets under the same heading belong
together; the next heading of the same or a higher level ends the
scenario.
For scenario
blocks you may not use any attributes. All attributes
are reserved for Subplot. Subplot doesn't define any attributes yet,
but by reserving all of them, it can add them later without it being
a breaking change.
For embedding test data files in the Markdown document, Subplot
understands the file
tag:
~~~{#filename .file} This data is accessible to the test program as 'filename'. ~~~
The .file
attribute is necessary, as is the identifier, here
#filename
. The generated test program can access the data using the
identifier (without the #).
Subplot also understands the dot
and roadmap
tags, and can use the
Graphviz dot program, or the roadmap Rust crate, to produce
diagrams. These can useful for describing things visually.
When typesetting files, Subplot will automatically number the lines in
the file so that documentation prose can refer to sections of embedded
files without needing convoluted expressions of positions. However if
you do not want that, you can annotate the file with .noNumberLines
.
For example…
~~~{#numbered-lines.txt .file} This file has numbered lines. This is line number three. ~~~ ~~~{#not-numbered-lines.txt .file .noNumberLines} This file does not have numbered lines. This is still line number three, but would it be obvious? ~~~
…renders as:
This file has numbered lines. This is line number three.
This file does not have numbered lines. This is still line number three, but would it be obvious?
3.2.1 Use embedded file
This scenario makes sure the sample files are used in a scenario so that they don't cause warnings.
3.3 Document metadata
Document metadata is read from a YAML file. This can used to set the document title, authors, date (version), and more. Crucially for Subplot, the bindings and functions files are named in the metadata block, rather than Subplot deriving them from the input file name.
title: "Subplot" authors: - The Subplot project date: work in progress markdowns: - subplot.md bindings: - subplot.yaml impls: python: - subplot.py
There can be more than one bindings or functions file: use a YAML list.
3.4 Bindings file
The bindings file binds scenario steps to code functions that implement the steps. The YAML file is a list of objects (also known as dicts or hashmaps or key/value pairs), specifying a step kind (given, when, then), a pattern matching the text of the step and optionally capturing interesting parts of the text. Each binding may contain a type map which tells subplot the types of the captures in the patterns so that they can be validated to some extent, and a binding will list some number of implementations, each of which is specified by the name of the language (template) it is for, and then the name of a function that implements the step, optionally with the name of a function to call to clean up a scenario which includes that step.
There are some flexibilities in bindings, futher details can be found below:
- Patterns can be simple or full-blown Perl-compatible regular expresssions (PCRE).
- Bindings may have type maps. Without a type map, all captures are considered to be short strings (words).
- Bindings may have as many or as few implementations as needed. A zero
impl
binding will work fordocgen
but will fail tocodegen
. This can permit document authors to prepare bindings without knowing how an engineer might implement it.
- given: "a standard setup" impl: python: function: create_standard_setup - when: "{thing} happens" impl: python: function: make_thing_happen types: thing: word - when: "I say (?P<sentence>.+) with a smile" regex: true impl: python: function: speak - then: "everything is OK" impl: python: function: check_everything_is_ok
In the example above, there are four bindings and they all provide Python implementation functions:
- A binding for a "given a standard setup" step. The binding captures
no part of the text, and causes the
create_standard_setup
function to be called. - A binding for a "when" step consisting of one word followed by
"happens". For example, "peace", as in "then peace happens". The
word is captured as "thing", and given to the
make_thing_happen
function as an argument when it is called. - A binding for a "when" followed by "I say", an arbitrary sentence,
and then "with a smile", as in "when I say good morning to you with
a smile". The function
speak
is then called with capture named "sentence" as "good morning to you". - A binding for a "then everything is OK" step, which captures nothing,
and calls the
check_everything_is_ok
function.
3.5 Step functions and cleanup
A step function must be atomic: either it completes successfully, or it cleans up any changes it made before returning an indication of failure.
A cleanup function is only called for successfully executed step functions.
For example, consider a step that creates and starts a virtual machine. The step function creates the VM, then starts it, and if both actions succeeds, the step succeeds. A cleanup function for that step will stop and delete the VM. The cleanup is only called if the step succeeded. If the step function manages to create the VM, but not start it, it's the step function's responsibility to delete the VM, before it signals failure. The cleanup function won't be called in that case.
3.5.1 Simple patterns
The simple patterns are of the form {name}
and match a single word
consisting of printable characters. This can be varied by adding a
suffix, such as {name:text}
which matches any text. The following
kinds of simple patterns are supported:
{name}
or{name:word}
– a single word. As a special case, the form{name:file}
is also supported. It is also a single word, but has the added constraint that it must match an embedded file's name.{name:text}
– any text{name:int}
– any whole number, including negative{name:uint}
– any unsigned whole number{name:number}
– any number
A pattern uses simple patterns by default, or if the regex
field is
set to false. To use regular expressions, regex
must be set to true.
Subplot complains if typical regular expression characters are used,
when simple patterns are expected, unless regex
is explicitly set to
false.
3.5.2 Regular expression patterns
Regular expression patterns are used only if the binding regex
field
is set to true
.
The regular expressions use PCRE syntax as implemented by the Rust
regex crate. The (?P<name>pattern)
syntax is used to capture
parts of the step. The captured parts are given to the bound function
as arguments, when it's called.
3.5.3 The type map
Bindings may also contain a type map. This is a dictionary called types
and contains a key-value mapping from capture name to the type of the capture.
Valid types are listed above in the simple patterns section. In addition to
simple patterns, the type map can be used for regular expression bindings as
well.
When using simple patterns, if the capture is given a type in the type map, and also in the pattern, then the types must match, otherwise subplot will refuse to load the binding.
Typically the type map is used by the code generators to, for example, distinguish
between "12"
and 12
(i.e. between a string and what should be a number). This
permits the generated test suites to use native language types directly. The
file
type, if used, must refer to an embedded file in the document; subplot docgen
will emit a warning if the file is not found, and subplot codegen will emit an error.
3.5.4 The implementation map
Bindings can contain an impl
map which connects the binding with zero or more
language templates. If a binding has no impl
entries then it can still be
used to docgen
a HTML document from a subplot document. This permits a
workflow where requirements owners / architects design the validations for a
project and then engineers implement the step functions to permit the
validations to work.
Shipped with subplot are a number of libraries such as files
or runcmd
and
these libraries are polyglot in that they provide bindings for all supported
templates provided by subplot.
Here is an example of a binding from one of those libraries:
- given: file {embedded_file} impl: rust: function: subplotlib::steplibrary::files::create_from_embedded python: function: files_create_from_embedded types: embedded_file: file
3.5.5 Embedded file name didn't match
title: Bad filenames in matched steps do not permit codegen markdowns: [badfilename.md] bindings: [b.yaml] impls: python: [f.py]
# Bad filename ```scenario given file missing.md ```
3.5.6 Bindings file strictness - given when then
The bindings file is semi-strict. For example you must have only one
of given
, when
, or then
in your binding.
title: Bad bindings cause everything to fail markdowns: [badbindingsgwt.md] bindings: [badbindingsgwt.yaml]
# Bad bindings ```scenario given we won't reach here ```
- given: we won't reach here then: we won't reach here
3.5.7 Bindings file strictness - unknown field
The bindings file is semi-strict. For example, you must not have keys in the bindings file which are not known to Subplot.
title: Bad bindings cause everything to fail markdowns: [badbindingsuf.md] bindings: [badbindingsuf.yaml]
# Bad bindings ```scenario given we won't reach here ```
- given: we won't reach here function: old_school_function
3.6 Functions file
Functions implementing steps are supported in Bash and Python. The
language is chosen by setting the template
field in the document
YAML metadata to bash
or python
.
The functions files are not parsed by Subplot at all. Subplot merely copies them to the output. All parsing and validation of the file is done by the programming language being used.
The conventions for calling step functions vary by language. All languages support a "dict" abstraction of some sort. This is most importantly used to implement a "context" to store state in a controlled manner between calls to step functions. A step function can set a key to a value in the context, or retrieve the value for a key.
Typically, a "when" step does something, and records the results into the context, and a "then" step checks the results by inspecting the context. This decouples functions from each other, and avoids having them use global variables for state.
3.6.1 Bash
The step functions are called without any arguments.
The context is managed using shell functions provided by the Bash template:
ctx_set key value
ctx_get key
Captured values from scenario steps are passed in via another dict and accessed using another function:
cap_get key
Similarly, there's a dict for the contents of embedded data files:
files_get filename
The template provides assertion functions: assert_eq
, assert_contains
.
Example:
_run() { if "$@" < /dev/null > stdout 2> stderr then ctx_set exit 0 else ctx_set exit "$?" fi ctx_set stdout "$(cat stdout)" ctx_set stderr "$(cat stderr)" } run_echo_without_args() { _run echo } run_echo_with_args() { args="$(cap_get args)" _run echo "$args" } exit_code_is() { actual_exit="$(ctx_get exit)" wanted_exit="$(cap_get exit_code)" assert_eq "$actual_exit" "$wanted_exit" } stdout_is_a_newline() { stdout="$(ctx_get stdout)" assert_eq "$stdout" "$(printf '\n')" } stdout_is_text() { stdout="$(ctx_get stdout)" text="$(cap_get text)" assert_contains "$stdout" "$text" } stderr_is_empty() { stderr="$(ctx_get stderr)" assert_eq "$stderr" "" }
3.6.2 Python
The context is implemented by a dict-like class.
The step functions are called with a ctx
argument that has the
current state of the context, and each capture from a step as a
keyword argument. The keyword argument name is the same as the capture
name in the pattern in the bindings file.
The contents of embedded files are accessed using a function:
get_file(filename)
Example:
import json def exit_code_is(ctx, wanted=None): assert_eq(ctx.get("exit"), wanted) def json_output_matches_file(ctx, filename=None): actual = json.loads(ctx["stdout"]) expected = json.load(open(filename)) assert_dict_eq(actual, expected) def file_ends_in_zero_newlines(ctx, filename=None): content = open(filename, "r").read() assert_ne(content[-1], "\n")
3.7 Comparing the scenario runners
Currently Subplot ships with three scenario runner templates. The Bash, Python, and Rust templates. The first two are fully self-contained and have a set of features dictated by the Subplot version. The latter is tied to how Cargo runs tests. Given that, this comparison is only considered correct against the version of Rust at the time of publishing a Subplot release. Newer versions of Rust may introduce additional functionality which we do not list here. Finally, we do not list features here which are considered fundamental, such as "runs all the scenarios" or "supports embedded files" since no template would be considered for release if it did not do these things. These are the differentiation points.
Feature | Bash | Python | Rust |
---|---|---|---|
Isolation model | Subprocess | Subprocess | Threads |
Parallelism | None | None | Threading |
Passing environment variables | CLI | CLI | Prefixed env vars |
Execution order | Fixed order | Randomised | Fixed order plus threading peturbation |
Run specific scenarios | Simple substring check | Simple substring check | Either exact or simple substring check |
Diagnostic logging | Writes to stdout/stderr per normal shell | Supports comprehensive log file | Writes captured output to stdout/stderr on failure |
Stop-on-failure | Stops on first failure | Stops on first failure unless told not to | Runs all tests unless told not to |
Data dir integration | Cleans up only on full success | Cleans up each scenario unless told to save it | Cleans up each scenario with no option to save failure state |
4 Acceptance criteria for Subplot
Add the acceptance criteria test scenarios for Subplot here.
4.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 bash: function: precond_foo - when: I do bar impl: python: function: do_bar bash: function: do_bar - when: I do foobar impl: python: function: do_foobar bash: function: do_foobar - then: bar was done impl: python: function: bar_was_done bash: function: bar_was_done - then: foobar was done impl: python: function: foobar_was_done bash: function: foobar_was_done - given: file {filename} impl: python: function: provide_file bash: 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)
4.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.
4.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.
title: Indented scenario step markdowns: - indented-step.md bindings: - b.yaml
# This is a title ~~~scenario given precondition ~~~
4.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.
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 ~~~
4.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.
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.
4.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.
title: No templates in here markdowns: [notemplate.md] impls: { }
# This is a title ```scenario then failure ensues ```
4.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.
4.6.1 All the keywords
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 ```
4.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.
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 ```
4.7 Title markup
It is OK to use markup in document titles, in the YAML metadata section. This scenario verifies that all markup works.
title: This _uses_ ~~all~~ **most** inline `markup` subtitle: H~2~O is not 2^10^ markdowns: [title-markup.md] impls: { python: [] }
# Introduction
4.8 Scenario titles
A scenario gets its title from the lowest level of section heading that applies to it. The heading can use markup.
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 ```
4.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.
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 ```
4.10 Empty lines in scenarios
This scenario verifies that empty lines in scenarios are OK.
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 ```
4.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 bash: function: foo cleanup: foo_cleanup - given: bar impl: python: function: bar cleanup: bar_cleanup bash: function: bar cleanup: bar_cleanup - given: failure impl: python: function: failure cleanup: failure_cleanup bash: 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
foo() { true } foo_cleanup() { true } bar() { true } bar_cleanup() { true } failure() { return 1 } failure_cleanup() { true }
4.11.1 Cleanup functions gets called on success (Python)
title: Cleanup markdowns: - cleanup-success-python.md bindings: [cleanup.yaml] impls: python: [cleanup.py]
# Cleanup ~~~scenario given foo given bar ~~~
4.11.2 Cleanup functions get called on failure (Python)
title: Cleanup markdowns: - cleanup-fail-python.md bindings: [cleanup.yaml] impls: python: [cleanup.py]
# Cleanup ~~~scenario given foo given bar given failure ~~~
4.11.3 Cleanup functions gets called on success (Bash)
title: Cleanup markdowns: - cleanup-success-bash.md bindings: [cleanup.yaml] impls: bash: [cleanup.sh]
--- title: Cleanup bindings: [cleanup.yaml] impls: bash: [cleanup.sh] ... # Cleanup ~~~scenario given foo given bar ~~~
4.11.4 Cleanup functions get called on failure (Bash)
If a step fails, all the cleanups for the preceding steps are still called, in reverse order.
title: Cleanup markdowns: - cleanup-fail-bash.md bindings: [cleanup.yaml] impls: bash: [cleanup.sh]
# Cleanup ~~~scenario given foo given bar given failure ~~~
4.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.
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())
4.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".
4.13.1 Capture using simple patterns
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)
4.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.
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
4.13.3 Simple patterns with regex metacharacters: allowed case
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
4.13.4 Capture using regular expressions
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
4.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.
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)
4.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
.
There is currently no equivalent functionality for the generated Bash test program. Patches for that are welcome.
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
4.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.
4.16.1 Lowest level heading is name of scenario
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 ```
4.16.2 Subheadings don't start new scenario
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
4.16.3 Next heading at same level starts new scenario
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 ```
4.16.4 Next heading at higher level starts new scenario
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 ```
4.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.
4.16.5.1 Document generator gives an error if input document lacks title
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
4.16.5.2 Code generator gives an error if input document lacks title
4.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.
title: Plain *emph* **strong** ~~strikeout~~ superscript^10^ subscript~10~ 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 ~~~~
4.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.
4.16.7 Running only chosen scenarios with Python
This verifies that the generated Python test program can run only chosen scenarios.
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 ```
4.16.8 Running only chosen scenarios with Bash
This verifies that the generated Bash test program can run only chosen scenarios.
title: Test scenario markdowns: - twoscenarios-bash.md bindings: [b.yaml] impls: bash: [f.sh]
# 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 ```
precond_foo() { ctx_set bar_done 0 ctx_set foobar_done 0 } do_bar() { ctx_set bar_done 1 } do_foobar() { ctx_set foobar_done 1 } bar_was_done() { actual="$(ctx_get bar_done)" assert_eq "$actual" 1 } foobar_was_done() { actual="$(ctx_get foobar_done)" assert_eq "$actual" 1 }
4.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
- maintaining the
- a finished, reviewed, officially stamped document needs a fixed date
- Subplot allows this to be written as the
date
metadata field
- Subplot allows this to be written as the
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
4.17.1 Date given in metadata
This scenario tests that the date
field in metadata is used if
specified.
title: The Fabulous Title authors: - Alfred Pennyworth - Geoffrey Butler date: WIP markdowns: - metadate.md
# Introduction This is a test document. That's all.
4.17.2 Date given on command line
This scenario tests that the --date
command line option is used.
title: The Fabulous Title authors: - Alfred Pennyworth - Geoffrey Butler markdowns: - dateless.md
# Introduction This is a test document. It has no date metadata.
4.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.
4.17.4 Missing bindings file
If a bindings file is missing, the error message should name the missing file.
title: Missing binding markdowns: - missing-binding.md bindings: [missing-binding.yaml]
This is a markdown file.
4.17.5 Missing functions file
If a functions file is missing, the error message should name the missing file.
--- title: Missing functions markdowns: - missing-functions.md bindings: [b.yaml] impls: python: [missing-functions.py] ...
This file is empty.
4.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.
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": [] }
4.17.7 Multiple markdown files
This scenario tests that the markdowns
field in metadata can specify
more than one markdown file.
title: The Fabulous Title authors: - Alfred Pennyworth - Geoffrey Butler date: WIP markdowns: - md1.md - md2.md
First markdown file.
Second markdown file.
4.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 asadd-newline=auto
add-newline=auto
—add a newline, if one isn't thereadd-newline=no
—never add a newline, but keep one if it's thereadd-newline=yes
—always add a newline, even if one is already there
The scenarios below test the various cases.
4.18.1 Extract embedded file
This scenario checks that an embedded file can be extracted, and used in a subplot.
title: One embedded file markdowns: - embedded.md
~~~{#embedded.txt .file} This is the embedded file. ~~~
4.18.2 Extract embedded file, by default add missing newline
This scenario checks the default handling: add a newline if one is missing.
This file does not end in a newline.
4.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.
This file ends in a newline.
4.18.4 Extract embedded file, automatically add missing newline
Explicitly request automatic newlines, when the file does not end in one.
This file does not end in a newline.
4.18.5 Extract embedded file, do not automatically add second newline
Explicitly request automatic newlines, when the file already ends in one.
This file ends in a newline.
4.18.6 Extract embedded file, explicitly add missing newline
Explicitly request automatic newlines, when the file doesn't end with one.
This file does not end in a newline.
4.18.7 Extract embedded file, explicitly add second newline
Explicitly request automatic newlines, when the file already ends with one.
This file ends in a newline.
4.18.8 Extract embedded file, do not add missing newline
Explicitly ask for no newline to be added.
This file does not end in a newline.
4.18.9 Fail if the same filename is used twice
--- 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. ```
4.18.10 Fail if two filenames only differ in case
--- 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. ```
4.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.
title: Embedded file is not used by a scenario markdowns: - unusedfile.md
```{#thisisnotused.txt .file} This is the embedded file. ```
4.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).
4.19.1 Examples may be unused
title: Example is not an embedded file markdowns: - unusedexample.md
```{#thisisnotused.txt .example} This is the embedded example. ```
4.19.2 Examples are not files
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 ```
4.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
4.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 ```
4.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 ```
4.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
4.20.4 List embedded files
The subplot metadata
command lists embedded files in its output.
title: Two embedded files markdowns: - two-embedded.md
~~~{#foo.txt .file} ~~~ ~~~{#bar.yaml. .file} ~~~
4.21 Embedded diagrams
Subplot allows embedding markup to generate diagrams into the Markdown document.
4.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.
The scenario checks that a diagram is generated and embedded into the HTML output, and is not referenced as an external image.
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
4.21.2 Dot
Dot is a program from the Graphviz suite to generate directed diagrams, such as this one.
The scenario checks that a diagram is generated and embedded into the HTML output, not referenced as an external image.
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
4.21.3 PlantUML
PlantUML is a program to generate various kinds of diagrams for describing software, such as this one:
The scenario below checks that a diagram is generated and embedded into the HTML output, not referenced as an external image.
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
4.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:
This scenario checks that a diagram is generated and embedded into the HTML output, not referenced as an external image.
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
4.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.
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 ```
4.22 Extract embedded files
subplot extract
extracts embedded files from a subplot file.
title: Embedded file markdowns: - embedded-file.md
~~~{#foo.txt .file} This is a test file. ~~~
This is a test file.
4.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.
4.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.
title: Foo markdowns: - scenario-before-heading.md
~~~scenario ~~~
4.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.
title: Foo markdowns: - dl.md
# Foo Some term : Definition of term.
4.23.3 Bad "add-newline" value
Requirement: Only specific values for the "add-newline" attribute are allowed for an embedded file.
title: Foo markdowns: - add-newline.md
~~~{#foo.txt .file add-newline=xyzzy} ~~~
4.24 HTML output
4.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.
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; }
4.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.
title: Embedded CSS markdowns: - css-urls.md bindings: - b.yaml css_urls: - https://example.com/flushing.css
# This is a title ~~~scenario given precondition ~~~
4.25 Running Subplot
The scenarios in this section verify that the Subplot tool can be run in various specific ways.
4.25.1 Files not in current working directory
Requirement: Subplot can process a subplot that is not in the current working directory.