Skip to content

Instantly share code, notes, and snippets.

@pchiusano
Last active August 19, 2019 18:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pchiusano/b4607611089498b777c8e7f69c054765 to your computer and use it in GitHub Desktop.
Save pchiusano/b4607611089498b777c8e7f69c054765 to your computer and use it in GitHub Desktop.
Unison transcript files (RFC draft)

Unison transcript files

This is a Unison transcript file. It's used for creating typechecked documentation, test cases, and scripts.

It's just a markdown file that does some special interpretation of certain fenced code blocks. It can be passed to ucm as a command line argument. ucm will run through the file change events and commands in the file in order and output a new .md file with the responses to these inputs. It exits when it's done, so this can also be used for scripting.

Basics

---
filename: scratch.u
---
x = 1 + 1

The above will create the file scratch.u with the given contents, parse and typecheck it, and insert the ucm output into the resulting markdown, as a new fenced code block, immediately following the unison fenced block. If you leave off the frontmatter with the filename, that's okay too, no file will be created. If the file doesn't parse or typecheck, ucm exits with an error.

If you are instead expecting an error and want to show the resulting error message and continue, use unison:error as the code fence:

x = 1 + "hi" -- thankfully does not typecheck

Another fenced code type is ucm (ucm:error if you are expecting an error):

.> view base.List.map
.> execute IO.putText "hello, world!!"

This will run the ucm command and show its output in a new fenced code block immediately below. A ucm fenced block can have multiple commands, in which case the output is inserted after each command.

You can elide the cd/namespace commands and just directly indicate the namespace with the prompt, for instance:

.foo> find
.bar> find

This will output the command sequence cd .foo, find, cd .bar, find.

Creating tests cases

Sounds good, now what about creating test cases? We provide a couple new commands:

  • assert.execute expr exits ucm with failure if execute expr fails or evaluates to false.
  • assert.test exits ucm with failure if the test command returns a failing status
  • assert.todo exits ucm with failure if the todo command returns anything to do
  • assert.view foo exits ucm with failure if view command gives no results
  • ... can add to this as needed
.> assert true
.> assert.test

Examples

It might not be obvious how to use the above to construct test cases, so I thought I'd give a few examples. Suppose I'd like to check that fork creates a copy of a namespace. To make the transcript pretty self-contained, I'll create a namespace with some definitions in it, then fork that, cd into it, and verify that viewing one of the definitions works.

ns0.x = 42
ns0.y = 19
.> add
.> fork ns0 ns1
.ns1> assert.view x
.ns1> assert.view y

In the absence of a full-blown codebase API accessible from Unison programs, there may be some creativity needed to arrange things so that the transcript fails in a way that indicates what you want. The above test is a bit weak, it would be nice if we could assert that the x in the fork is the same as the x in ns0. How could we do that? Easy: just write a Unison test!

use .test
use .base

test> ns1.forkIsCopy1 = (.ns0.x == .ns1.x) |> expect |> run
test> ns1.forkIsCopy1 = (.ns0.y == .ns1.y) |> expect |> run
.> add
.> assert.test

Now we're actually testing that the values are as expected in the fork.

@atacratic
Copy link

assert.todo.empty, assert.view.succeeds ?

"I guess just if the expression fails then." OK, although not actually sure what you mean by 'fails'. Maybe that it can't parse expr?

"Just don't include the filename in the front-matter of the markdown." does that rule out adding it to the codebase? If not then fine.

"Or maybe a flag passed to ucm that drops you into the regular prompt once the transcript completes." I like this idea.

Allowing push from transcript possibly a bit fraught? parsing a transcript then has a side-effect? Maybe it's fine.

"No. Only state is just the codebase state. All the files are self contained." It's going to be a bit of a yawn if all the tiny fragments in the abiities tutorial have to repeat the use .base use .base.io incantation (or do without them).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment