1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
# Transcripts
A transcript is both the input and output of a successful session of a `cmd2`-based app which is saved to a text file. With no extra work on your part, your app can play back these transcripts as a unit test. Transcripts can contain regular expressions, which provide the flexibility to match responses from commands that produce dynamic or variable output.
## Creating From History
A transcript can automatically generated based upon commands previously executed in the _history_ using `history -t`:
```text
(Cmd) help
...
(Cmd) help history
...
(Cmd) history 1:2 -t transcript.txt
2 commands and outputs saved to transcript file 'transcript.txt'
```
This is by far the easiest way to generate a transcript.
!!! warning
Make sure you use the **poutput()** method in your `cmd2` application for generating command output. This method of the `cmd2.Cmd` class ensure that output is properly redirected when redirecting to a file, piping to a shell command, and when generating a transcript.
## Creating From A Script File
A transcript can also be automatically generated from a script file using `run_script -t`:
```text
(Cmd) run_script scripts/script.txt -t transcript.txt
2 commands and their outputs saved to transcript file 'transcript.txt'
(Cmd)
```
This is a particularly attractive option for automatically regenerating transcripts for regression testing as your `cmd2` application changes.
## Creating Manually
Here's a transcript created from `python examples/example.py`:
```text
(Cmd) say -r 3 Goodnight, Gracie
Goodnight, Gracie
Goodnight, Gracie
Goodnight, Gracie
(Cmd) mumble maybe we could go to lunch
like maybe we ... could go to hmmm lunch
(Cmd) mumble maybe we could go to lunch
well maybe we could like go to er lunch right?
```
This transcript has three commands: they are on the lines that begin with the prompt. The first command looks like this:
```text
(Cmd) say -r 3 Goodnight, Gracie
```
Following each command is the output generated by that command.
The transcript ignores all lines in the file until it reaches the first line that begins with the prompt. You can take advantage of this by using the first lines of the transcript as comments:
```text
# Lines at the beginning of the transcript that do not
; start with the prompt i.e. '(Cmd) ' are ignored.
/* You can use them for comments. */
All six of these lines before the first prompt are treated as comments.
(Cmd) say -r 3 Goodnight, Gracie
Goodnight, Gracie
Goodnight, Gracie
Goodnight, Gracie
(Cmd) mumble maybe we could go to lunch
like maybe we ... could go to hmmm lunch
(Cmd) mumble maybe we could go to lunch
maybe we could like go to er lunch right?
```
In this example I've used several different commenting styles, and even bare text. It doesn't matter what you put on those beginning lines. Everything before:
```text
(Cmd) say -r 3 Goodnight, Gracie
```
will be ignored.
## Regular Expressions
If we used the above transcript as-is, it would likely fail. As you can see, the `mumble` command doesn't always return the same thing: it inserts random words into the input.
Regular expressions can be included in the response portion of a transcript, and are surrounded by slashes:
```text
(Cmd) mumble maybe we could go to lunch
/.*\bmaybe\b.*\bcould\b.*\blunch\b.*/
(Cmd) mumble maybe we could go to lunch
/.*\bmaybe\b.*\bcould\b.*\blunch\b.*/
```
Without creating a tutorial on regular expressions, this one matches anything that has the words `maybe`, `could`, and `lunch` in that order. It doesn't ensure that `we` or `go` or `to` appear in the output, but it does work if mumble happens to add words to the beginning or the end of the output.
Since the output could be multiple lines long, `cmd2` uses multiline regular expression matching, and also uses the `DOTALL` flag. These two flags subtly change the behavior of commonly used special characters like `.`, `^` and `$`, so you may want to double check the [Python regular expression documentation](https://docs.python.org/3/library/re.html).
If your output has slashes in it, you will need to escape those slashes so the stuff between them is not interpred as a regular expression. In this transcript:
```text
(Cmd) say cd /usr/local/lib/python3.11/site-packages
/usr/local/lib/python3.11/site-packages
```
the output contains slashes. The text between the first slash and the second slash, will be interpreted as a regular expression, and those two slashes will not be included in the comparison. When replayed, this transcript would therefore fail. To fix it, we could either write a regular expression to match the path instead of specifying it verbatim, or we can escape the slashes:
```text
(Cmd) say cd /usr/local/lib/python3.11/site-packages
\/usr\/local\/lib\/python3.11\/site-packages
```
!!! warning
Be aware of trailing spaces and newlines. Your commands might output trailing spaces which are impossible to see. Instead of leaving them invisible, you can add a regular expression to match them, so that you can see where they are when you look at the transcript:
```text
(Cmd) set editor
editor: vim/ /
```
Some terminal emulators strip trailing space when you copy text from them. This could make the actual data generated by your app different than the text you pasted into the transcript, and it might not be readily obvious why the transcript is not passing. Consider using [redirection](./redirection.md) to the clipboard or to a file to ensure you accurately capture the output of your command.
If you aren't using regular expressions, make sure the newlines at the end of your transcript exactly match the output of your commands. A common cause of a failing transcript is an extra or missing newline.
If you are using regular expressions, be aware that depending on how you write your regex, the newlines after the regex may or may not matter. `\Z` matches _after_ the newline at the end of the string, whereas `$` matches the end of the string _or_ just before a newline.
## Running A Transcript
Once you have created a transcript, it's easy to have your application play it back and check the output. From within the `examples/` directory:
```text
$ python example.py --test transcript_regex.txt
.
----------------------------------------------------------------------
Ran 1 test in 0.013s
OK
```
The output will look familiar if you use `unittest`, because that's exactly what happens. Each command in the transcript is run, and we `assert` the output matches the expected result from the transcript.
!!! note
If you have passed an `allow_cli_args` parameter containing `False` to `cmd2.Cmd.__init__` in order to disable parsing of command line arguments at invocation, then the use of `-t` or `--test` to run transcript testing is automatically disabled. In this case, you can alternatively provide a value for the optional `transcript_files` when constructing the instance of your `cmd2.Cmd` derived class in order to cause a transcript test to run:
```py
from cmd2 import Cmd
class App(Cmd):
# customized attributes and methods here
if __name__ == '__main__':
app = App(transcript_files=['exampleSession.txt'])
app.cmdloop()
```
|