-
Notifications
You must be signed in to change notification settings - Fork 271
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make transcripts idempotent #5406
Conversation
All serialization is now down via CMark (previously, all `api` and `ucm` blocks in the output were still being serialized directly, because of the interleaving of the input text and command/request output). This causes a few changes to the existing transcript outputs: - there is now always a blank line between Markdown block elements, - leading blank lines in UCM blocks are gone, - blank lines at the end of transcripts are gone (they still end with a final POSIX newline), and - some `ucm` blocks now have 4-backtick fences (because they contain triple-backticks). Transcript failures are now also handled as Markdown, rather than just being text appended to the document. This mostly doesn’t change the serialization, except that the failure message is now fenced, since they often contain newlines and indentation that is mangled otherwise.
This includes changes that get us closer to running transcript outputs as inputs - all `ProcessedBlock`s (`api`, `ucm`, and `unison`) now handle info tags the same way – previously `ucm` didn’t allow spaces between tags like `:hide :error`, and `api` didn’t support info tags at all. - preserve info tags in the output – `ucm :error` in the input results in `ucm :error` in the output - parser now supports `:added-by-unison` (only useful for parsing outputs as inputs)
- fix `:hide` on `ucm` and `api` blocks – previously it behaved the same as `:hide:all`, which prevents the outputs being used as transcripts - include `:added-by-ucm` to `ucm` blocks inserted after `unison` blocks - ensure output lines have 2-space indents This adds a couple transcripts testing the `:hide` behavior.
This provides the final pieces to make transcripts idempotent enough that we can use transcript outputs as inputs. This simplifies bug reporting, as now the GitHub issue can be written _as_ a transcript, even if you want to include some of the output in the bug report. No need to wrap the entire transcript in ` ```` markdown`. The parser changes here also improve error messages in some existing cases. There are some caveats: - failing transcripts are not _yet_ idempotent. There are issues with how errors are reported (especially parse failures) that make them not round-trip - `:hide:all` almost always breaks idempotency. This is intentional – to just hide the output, use `:hide` instead.
These were previously separated because transcripts previously couldn’t contain code blocks with triple backticks, but that is fixed now.
This produces the first output files, which should remain unchanged on subsequent runs.
Idempotent transcripts can get large, so using `:hide` more liberally is probably a good idea. This tries that out on a few transcripts.
Hrmm … the tests that are failing … they’re easy enough to fix (just need to update the output expectations), but it seems like there’s not an easy way to run these tests. I.e., they’re not part of ./scripts/checks.sh or anything else. Is that right? If so, we should probably add scripts for those jobs. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm really liking this overall, and I think @mitchellwrosen will like it too as he had previously raised concerns about :hide
hiding too much.
I'm just working through the long diff now.
unison-cli-integration/integration-tests/IntegrationTests/transcript.output.md
Show resolved
Hide resolved
some.ns.direct : Nat | ||
some.ns.direct.doc : Doc2 | ||
some.ns.pretty.deeply.nested : Nat | ||
some.ns.pretty.deeply.nested.doc : Doc2 | ||
some.outside : Nat | ||
some.outside.doc : Doc2 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure we want to lose this line 47? But I'm not sure where all it shows up. If it's the last one in a ```
, like the previous one on line 34 and like the one on line 49, then sure; but here it's bumping into the next command but without a vertical space.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, there are some differences with blank lines before and after this change. I tried to make it consistent in the code, but it definitely introduced some cases that are less than ideal. I’ll go back and review those changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking what you have is good, except that there should be a linebreak between result and the next prompt when there are multiple prompts in an output ucm
block. (I'm also wondering whether the linebreak would missing now in an interactive ucm
session?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you suggest a way we can be confident that all of the transcripts made it into idempotent/
and not accidentally deleted like I'd apparently done in the past?
So I tried to split up the commits to make this more obvious, but I don’t think it’s foolproof:
So, basically, verify that only output files were removed in that first commit. And I think no other commits should remove any transcripts. If they do, they should be spot-checkable (and I apologize for not isolating it better). |
Ah, perfect, thanks, I'll take another look. |
Checks that other transcripts in the source tree (currently just the GitHub bug-report template) are valid. This also fixes a bug where a transcript block like ` ``` ucm :hidec` would be parsed like ` ``` ucm :hide c`, rather than complaining that there is no `hidec` tag. This currently fails because of a typo introduced earlier in this PR.
This is caught by a test introduced in the previous commit.
This also makes a couple minor changes re: running the script: - removes the “belt and suspenders” `echo`ing that resulted in things being printed in triplicate - added `gettext` to the Nix environment, so `envsubst` is available - changed the #!, to not get stuck with Bash 3.2 on macOS.
c4dea22
to
2673620
Compare
unison-src/transcripts/project-outputs/.github/pull_request_template.output.md
Outdated
Show resolved
Hide resolved
Input: | ||
```` markdown |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm kinda thinking that we do want to continue wrapping these in ```` markdown
, to make it easier to copy them out? Otherwise we have to edit source on each issue to get the contents.
Also the formatting of the internal markdown may clash with the enclosing markdown.
An upside to the change though is that it avoids Github cropping long code blocks.
Agreed we don't need Input and Output sections.
I guess either way is fine and we can always change it back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm kinda thinking that we do want to continue wrapping these in
```` markdown
, to make it easier to copy them out? Otherwise we have to edit source on each issue to get the contents.
I was thinking it’s probably easy (for some Web person … probably not easy for me 😅) to make a bookmarklet or something to copy the raw issue description directly to the clipboard.
Longer-term, I could see a flow where you can do
scratch/main> transcript.run https://github.com/unisonweb/unison/issues/5451
Fetching transcript from topic description.
Saved transcript to ./unison-issue-5451.md.
Running transcript …
possibly even firing that off from a bookmarklet or browser extension or whatever.
IMO, there are two reasons to avoid nesting in markdown
:
- reporters will write most of their report outside of the
markdown
block to get formatting for their text, and only put the necessary code snippets in the block, which means either more work (copy/pasting text from the issue) getting an informative transcript out of it, or just that we end up with transcripts without info (a problem that exists historically, with transcripts not containing any context, just a chunk of code that didn’t work right at some point for some reason). - And now I forget the second one …
Also the formatting of the internal markdown may clash with the enclosing markdown.
Do you mean that the GitHub-flavored Markdown of the issue could be incompatible with the CommonMark used for the transcript?
I suppose this is possible, but as currently specified, the two are mutually-parseable. GFM is CommonMark with some extensions, but those extensions just end up as plain text in CommonMark (e.g., strikethrough).
One other consequence is that bug_report.md and pull_request_template.md wouldn’t be templates, just contain templates, and so I would remove the transcripts/project-outputs
tests.
I'm about ready to merge this, but still just trying to wrap my head around and document the new organization. Let me know if I have this right. It looks like:
I'm kinda thinking we can eventually make in-place the primary mode for running the transcripts, but we don't have to figure that out now. And this PR mainly is to let us reduce how many files we've got, and also to reduce friction in submitting transcripts with bug reports. |
And the errors aren’t currently handled idempotently because parse errors don’t preserve the transcript. Runtime errors do, and so those could be idempotent, but I’d rather just get the parse errors handled better, then make them all that way.
Correct about
Yeah, I didn’t have a great name for this directory, so feel free to suggest a different one, but these are meant to validate anything transcript-y that lives in the repo for some reason other than being a test. I.e., these test that those transcripts don’t change incompatibly rather than testing that the
Yep – maybe we split each type of transcript tests (“basic” (which currently doesn’t have an explicit directory), “using-base”, and “errors”) into “idempotent” and “non-idempotent” subdirs, or we can dynamically identify whether each transcript uses
There is one thing here that I want to call out: While the transcripts are idempotent (modulo parse errors & ``` ucm :hide
scratch/main> merge.builtins
```
``` unison
This should fail to parse, but pretend it’s a typo rather than this arbitrary text.
```
``` ucm
scratch/main> add
``` results in this output ``` ucm :hide
scratch/main> builtins.merge
```
``` unison
This should fail to parse, but pretend it’s a typo rather than this arbitrary text.
```
🛑
The transcript failed due to an error in the stanza above. The error is:
```
I found a ',' here, but I didn't see a list or tuple that it might be a separator for.
1 | This should fail to parse, but pretend it’s a typo rather than this arbitrary text.
``` So, running it in place by default can be annoying when writing a new transcript. What I currently do is stage my transcript, then run it and if anything is lost, just discard the unstaged changes. In future, transcript runs could be “non-destructive” – i.e., make sure that all of the original stanzas are included in the output, even if there was an error earlier in the run. |
Ooo, okay. A little dangerous. So I guess for now if you put it under |
Yeah, I would say “better bug reports” are the main thing here. What I like to do is run my transcript, paste the output.md into the issue description, then edit the output in the UCM blocks. There can be a lot of output and I can get the help for `alias.type`
``` ucm
scratch/main> help alias.type
alias.type
`alias.type Foo Bar` introduces `Bar` with the same definition as `Foo`.
```
but when I run just `help`, that command is missing
``` ucm
scratch/main> help
...
alias.many (or copy)
`alias.many <relative1> [relative2...] <namespace>` creates aliases `relative1`, `relative2`, ...
in the namespace `namespace`.
`alias.many foo.foo bar.bar .quux` creates aliases `.quux.foo.foo` and `.quux.bar.bar`.
alias.term
`alias.term foo bar` introduces `bar` with the same definition as `foo`.
api
`api` provides details about the API.
auth.login
Obtain an authentication session with Unison Share.
`auth.login`authenticates ucm with Unison Share.
...
``` where I’ve trimmed 90+% of the |
This avoids leading and trailing blanks, while ensuring they exist between commands & outputs.
I still have to resolve some conflicts, but I pushed a change that tidies up the blank lines in UCM blocks. The one thing I’m unsure of is that it inserts blank lines between commands even when ```ucm :hide
scratch/main> foo
scratch/main> bar
scratch/main> baz
``` or ```ucm :hide
scratch/main> foo
scratch/main> bar
scratch/main> baz
``` |
Probably having no blank lines is better in |
Ok, so I left the blank lines in for now, merged trunk, and ensured that no idempotent transcripts have been duplicated or left behind in transcripts/. I think this is good once the tests pass. |
Can you confirm what's the "right" way to add transcripts in new and open PRs now? I think I tried just throwing one If so, is it universal yet (both with |
Hrmm, creating
No. |
Yes — let's plan on doing this too. |
Disregard for now, I was in the middle of something else so I'm not sure what happened (which is why I was vague here) and may have misunderstood. Thanks for the clarification. |
Overview
This contains a lot of improvements to transcripts. The tl;dr is that now transcript outputs can be used as transcripts, so bug reports can contain just the output (and the output can be edited down to just the relevant bits), rather than including or attaching separate transcript and output.
Some more detail
api
anducm
blocks in the output were still being serialized directly, because of the interleaving of the input text and command/request output)ucm
blocks needed more backticks, but still only had three because of this, so that’s now fixed:added-by-ucm
ucm
andapi
outputs (api
outputs are now indented similarly toucm
outputs to make this easier):hide
(:all
),:error
, and:added-by-ucm
– previously each block time handled them with different semantics, including that forucm
,:hide
and:hide:all
had the same behavior1:hide
) are now preserved in the output``` markdown
blockCaveats:
:error
is):hide:all
is (intentionally) not idempotentFixes #5199.
Implementation notes
This changes a bunch of stuff in the transcript implementation. It tries to make some things clearer (e.g., breaking
awaitInput
into smaller functions, and removing the recursion from it), but there is still work to do there.Interesting/controversial decisions
This PR currently makes most of the transcript tests into “idempotent tests” – where the output is written back to the input file, and there should be no changes when re-run. It makes a nice impact with 11 kloc removed, but I don’t know that we actually want to do this.
One reason not to is that the transcripts are much noisier, since all the output is included. But maybe this should be handled by using
:hide
more liberally. I added a commit at the end that does that for a few transcripts.Test coverage
Every transcript is touched by this PR, I’m pretty sure.
Loose ends
There is related work here that doesn’t need to be in this PR
Footnotes
You’ll see in the diffs a bunch of “new”
``` ucm :hide
blocks. These were there before, but not copied to the output because of the bug where:hide
and:hide:all
were treated the same forucm
. ↩