Skip to content
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

Machine-readable output of tests #1284

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 145 additions & 0 deletions text/0000-machine-readable-tests-output.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
- Feature Name: machine\_readable\_tests\_output
- Start Date: 2015-09-17
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)

# Summary

Replace current test output with machine-readable one.

# Motivation

Currently when testing Rust provide only human-readable output which is nice
in most cases as we are humans or other humanoids. But from time to time we need
to feed machine with results of our tests (i.e. some kind of CI that will report
which tests failed or something like that) and there is no way to do that in
"civilised" way. We need to parse non-machine-readable output which isn't nice.

# Detailed design

Replace current, human-readable, output with JSON-based output based on
[TAP-J][tap-j]. For compatibility with existing work flow there should be added
built-in parser for that protocol which will output in current format.

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be
interpreted as described in [RFC 2119][rfc2119].

## Protocol description

Output of test MUST be stream of JSON objects that can be parsed by external
tools.

### Structure

Output is stream of lines containing JSON objects separated by new line. Each
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would make sense to agree on something like http://dataprotocols.org/ndjson/ or http://jsonlines.org/ for that or is that up to TAP-J to define?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could also make usage of \1E ASCII code which refers to "Record Separator". This is still under consideration, but 1 record per line is simple enough.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd take '\n' or '\r\n', it's a pretty common format in the NoSQL-world (e.g. Elasticsearch bulk uses it) and JSON can be written newline-free very well.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But is inconsistent between platform as Windows still uses \r\n as a line separator and I believe that println! macro respect that. Although this needs reconsideration as I chosen new line as it was simple enough to implement in first draft of specs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That inconsistency doesn't matter in that case as '{"test": "foo"}\r' is the same JSON value as '{"test": "foo"}'. Even if your IO isn't prepared for that, this is not an issue.

object MUST contain `type` field which designates `suite`, `test`, `bench` or
`final`. Any document MAY have `extra` field which contains an open mapping
for additional, unstandardized fields.

### Suite

```json
{
"type": "suite",
"name": "Doc-Test",
"build": "2015-08-21T10:03:20+0200",
"count": 13,
"rustc": "2a89bb6ba033b236c79a90486e2e3ee04d0e66f9"
}
```

Describes tests suite. It MUST appear only once at the beginning of each stream.

Fields:

| Name | Description |
| ---- | -------------------------------------------------------------- |
| `type` | MUST be a `suite` |
| `build` | MUST be ISO8601 timestamp of build. |
| `name` | OPTIONAL suite name i.e. "Tests", "Benchmarks" or "Doc-Tests". |
| `count` | MUST be count of all tests (including ignored). |
| `rustc` | MUST be version of Rust compiler used to build test suite. |

### Test

```json
{
"type": "test",
"subtype": "should_panic",
"status": "ok",
"label": "octavo::digest::md5::tests::test_md5",
"file": "src/digest/md5.rs",
"line": 684,
"stdout": "",
"stderr": "",
"duration": 100
}
```

Describes test. Each test MUST produce only one test object.

Fields:

| Name | Description |
| ---- | ----------- |
| `type` | MUST be `test`. |
| `subtype` | SHOULD be one of `test`, `bench` or `should_panic`. Defaults to `test`. |
| `status` | MUST be one of `ok`, `fail` or `ignore`. |
| `reason` | MUST describe of failure if `status` is `fail`. Otherwise MUST NOT be present. |
| `label` | MUST be unique identifier of test. |
| `file` | OPTIONAL file name containing test. |
| `line` | OPTIONAL line in `file` that contains test. |
| `stdout` | OPTIONAL output of standard output for given test. |
| `stderr` | OPTIONAL output of standard error for given test. |
| `duration` | OPTIONAL test execution duration in nanoseconds. MUST be present in benchmark. |
| `throughput` | OPTIONAL test throughput in case of benchmark test. |

### Final

```json
{
"type": "final",
"results": {
"ok": 10,
"fail": 0,
"ignore": 2
}
}
```

Finish test suite and closes stream. Parser MUST ignore all other input unless
it is new suite.

Fields:

| Name | Description |
| ---- | ----------- |
| `type` | MUST be `final`. |
| `results` | MUST be object containing only 3 fields: `ok`, `fail` and `ignore` which describe how many test passed, failed or was ignored respectfully. |


# Drawbacks

This is breaking change in tooling and will require new tool that will provide
current functionality for compatibility reasons, but IMHO small pain for big gain.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the test output a committed API? Using the test API directly requires #[feature(test)], so it is not breaking a committed API.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, test isn't commited. But testing is available in stable Rust and change in that matter will be visible to non-compiler-devs also so I marked it as a breaking change.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not make the machine-readable output an option and keep the human-readable output the default? Machine-readable output by default makes for a dreadful interactive experience.

FWIW pytest outputs a human-readable format to stdout and optionally a machine-readable one to a specified file.

The ability to output both human-readable and machine-readable is actually convenient in CI, you can send the human-readable output to a human-readable log and the machine-readable output to the CI's parser.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's the point of drawbacks. That this should be written, but IMHO isn't point of this RFC. I can rewrite it to provide flag that will print out machine-readable style.

About writing to file machine-readable and on stdout human-readable then I think that one should use Unix tools:

cargo test | tee output.log | formatter

Where formatter is tool that change machine-readable input into desired human-readable output.


# Alternatives

Do nothing.

# Unresolved questions

Test object fields:

- Should there be any additional fields? How should benchmark test description
look like?
- Maybe there should be additional, optional field `nspi` containing times
for each iteration that would help with statistical analysis of benchmarks.

Object separator: is newline enough? Maybe something that will be simpler to parse.

Format: should additional formats be available? Is support only for JSON enough?

[tap-j]: https://github.com/rubyworks/tapout/wiki/TAP-Y-J-Specification
[rfc2119]: https://www.ietf.org/rfc/rfc2119.txt