-
Notifications
You must be signed in to change notification settings - Fork 20
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
Add support for cargo test #39
Changes from all commits
47b475e
7177b40
3bab48f
5565edd
77fdbdd
1d07204
a80c60f
83a9c3a
50563f6
1389764
bdbbf81
1becab3
1dda2c4
418612b
223f490
6a22fe5
fe867be
bf3637e
dc8d9c9
5a4067a
e3e9ac8
d2ea503
b3ef3cd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -2,6 +2,8 @@ | |||||||||||||||||||||||||||||||||||||||||||||||
# Licensed under the Apache License, Version 2.0 | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
import os | ||||||||||||||||||||||||||||||||||||||||||||||||
from xml.dom import minidom | ||||||||||||||||||||||||||||||||||||||||||||||||
import xml.etree.ElementTree as eTree | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
from colcon_cargo.task.cargo import CARGO_EXECUTABLE | ||||||||||||||||||||||||||||||||||||||||||||||||
from colcon_core.event.test import TestFailure | ||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -22,38 +24,119 @@ def __init__(self): # noqa: D107 | |||||||||||||||||||||||||||||||||||||||||||||||
satisfies_version(TaskExtensionPoint.EXTENSION_POINT_VERSION, '^1.0') | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
def add_arguments(self, *, parser): # noqa: D102 | ||||||||||||||||||||||||||||||||||||||||||||||||
pass | ||||||||||||||||||||||||||||||||||||||||||||||||
parser.add_argument( | ||||||||||||||||||||||||||||||||||||||||||||||||
'--cargo-args', | ||||||||||||||||||||||||||||||||||||||||||||||||
nargs='*', metavar='*', type=str.lstrip, | ||||||||||||||||||||||||||||||||||||||||||||||||
help='Pass arguments to Cargo projects. ' | ||||||||||||||||||||||||||||||||||||||||||||||||
'Arguments matching other options must be prefixed by a space,\n' | ||||||||||||||||||||||||||||||||||||||||||||||||
'e.g. --cargo-args " --help"') | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
async def test(self, *, additional_hooks=None): # noqa: D102 | ||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I don't think we need the |
||||||||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||||||
Runs tests and style checks for the requested package. | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
Results are compiled into a single result `cargo_test.xml` file | ||||||||||||||||||||||||||||||||||||||||||||||||
with two test results, one for all the tests (cargo test) and one for | ||||||||||||||||||||||||||||||||||||||||||||||||
style (`cargo fmt --check`). | ||||||||||||||||||||||||||||||||||||||||||||||||
Documentation tests (`cargo test --doc`) are not implemented | ||||||||||||||||||||||||||||||||||||||||||||||||
since it is not possible to distinguish between a test that failed | ||||||||||||||||||||||||||||||||||||||||||||||||
because of a failing case and one that failed because the crate | ||||||||||||||||||||||||||||||||||||||||||||||||
contains no library target. | ||||||||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||||||
pkg = self.context.pkg | ||||||||||||||||||||||||||||||||||||||||||||||||
args = self.context.args | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
logger.info( | ||||||||||||||||||||||||||||||||||||||||||||||||
"Testing Cargo package in '{args.path}'".format_map(locals())) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
assert os.path.exists(args.build_base) | ||||||||||||||||||||||||||||||||||||||||||||||||
assert os.path.exists(args.build_base), \ | ||||||||||||||||||||||||||||||||||||||||||||||||
'Has this package been built before?' | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
test_results_path = os.path.join(args.build_base, 'test_results') | ||||||||||||||||||||||||||||||||||||||||||||||||
os.makedirs(test_results_path, exist_ok=True) | ||||||||||||||||||||||||||||||||||||||||||||||||
test_results_path = os.path.join(args.build_base, 'cargo_test.xml') | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||||||||||||||||||||||||
env = await get_command_environment( | ||||||||||||||||||||||||||||||||||||||||||||||||
'test', args.build_base, self.context.dependencies) | ||||||||||||||||||||||||||||||||||||||||||||||||
except RuntimeError as e: | ||||||||||||||||||||||||||||||||||||||||||||||||
# TODO(luca) log this as error in the test result file | ||||||||||||||||||||||||||||||||||||||||||||||||
logger.error(str(e)) | ||||||||||||||||||||||||||||||||||||||||||||||||
return 1 | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
if CARGO_EXECUTABLE is None: | ||||||||||||||||||||||||||||||||||||||||||||||||
raise RuntimeError("Could not find 'cargo' executable") | ||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Any reason we shouldn't just log this and return early like we do with the RuntimeError directly above? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair point, I just left it unchanged to avoid touching up too much. This behavior is also what the colcon-cargo/colcon_cargo/task/cargo/build.py Lines 47 to 69 in 9fdb14f
cargo test going to touch on the build task!What the TODO documents is that I was pondering whether we should create a test-result file for this case or just exit early with an error. I suspect we should still create a test result file hence I noted that down as a TODO. For reference making it a return code would produce this result: While currently ( My recommendation here would be to decide which looks best and do a followup PR that fixes it for both the build and test task. It will be a choice between verbosity (raise exception, get full backtrace) and simplicity (return error code, simple and concise error message). But as a spoiler I do agree with you that the concise error code looks a lot better, most people probably don't want a backtrace of colcon's task spawning There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think raising the runtime error is fine. It's what There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed the TODO b3ef3cd |
||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
cargo_args = args.cargo_args | ||||||||||||||||||||||||||||||||||||||||||||||||
if cargo_args is None: | ||||||||||||||||||||||||||||||||||||||||||||||||
cargo_args = [] | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
# invoke cargo test | ||||||||||||||||||||||||||||||||||||||||||||||||
rc = await run( | ||||||||||||||||||||||||||||||||||||||||||||||||
unit_rc = await run( | ||||||||||||||||||||||||||||||||||||||||||||||||
self.context, | ||||||||||||||||||||||||||||||||||||||||||||||||
[CARGO_EXECUTABLE, 'test', '-q', | ||||||||||||||||||||||||||||||||||||||||||||||||
'--target-dir', test_results_path], | ||||||||||||||||||||||||||||||||||||||||||||||||
cwd=args.path, env=env) | ||||||||||||||||||||||||||||||||||||||||||||||||
self._test_cmd(cargo_args), | ||||||||||||||||||||||||||||||||||||||||||||||||
cwd=args.path, env=env, capture_output=True) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
fmt_rc = await run( | ||||||||||||||||||||||||||||||||||||||||||||||||
self.context, | ||||||||||||||||||||||||||||||||||||||||||||||||
self._fmt_cmd(), | ||||||||||||||||||||||||||||||||||||||||||||||||
cwd=args.path, env=env, capture_output=True) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
error_report = self._create_error_report(unit_rc, fmt_rc) | ||||||||||||||||||||||||||||||||||||||||||||||||
with open(test_results_path, 'wb') as result_file: | ||||||||||||||||||||||||||||||||||||||||||||||||
xmlstr = minidom.parseString(eTree.tostring(error_report)) | ||||||||||||||||||||||||||||||||||||||||||||||||
xmlstr = xmlstr.toprettyxml(indent=' ', encoding='utf-8') | ||||||||||||||||||||||||||||||||||||||||||||||||
result_file.write(xmlstr) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
if rc.returncode: | ||||||||||||||||||||||||||||||||||||||||||||||||
if unit_rc.returncode or fmt_rc.returncode: | ||||||||||||||||||||||||||||||||||||||||||||||||
self.context.put_event_into_queue(TestFailure(pkg.name)) | ||||||||||||||||||||||||||||||||||||||||||||||||
# the return code should still be 0 | ||||||||||||||||||||||||||||||||||||||||||||||||
return 0 | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
def _test_cmd(self, cargo_args): | ||||||||||||||||||||||||||||||||||||||||||||||||
args = self.context.args | ||||||||||||||||||||||||||||||||||||||||||||||||
return [ | ||||||||||||||||||||||||||||||||||||||||||||||||
CARGO_EXECUTABLE, | ||||||||||||||||||||||||||||||||||||||||||||||||
'test', | ||||||||||||||||||||||||||||||||||||||||||||||||
'--quiet', | ||||||||||||||||||||||||||||||||||||||||||||||||
'--target-dir', | ||||||||||||||||||||||||||||||||||||||||||||||||
args.build_base, | ||||||||||||||||||||||||||||||||||||||||||||||||
] + cargo_args + [ | ||||||||||||||||||||||||||||||||||||||||||||||||
'--', | ||||||||||||||||||||||||||||||||||||||||||||||||
'--color=never', | ||||||||||||||||||||||||||||||||||||||||||||||||
] | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
# Ignore cargo args for rustfmt | ||||||||||||||||||||||||||||||||||||||||||||||||
def _fmt_cmd(self): | ||||||||||||||||||||||||||||||||||||||||||||||||
return [ | ||||||||||||||||||||||||||||||||||||||||||||||||
CARGO_EXECUTABLE, | ||||||||||||||||||||||||||||||||||||||||||||||||
'fmt', | ||||||||||||||||||||||||||||||||||||||||||||||||
'--check', | ||||||||||||||||||||||||||||||||||||||||||||||||
'--', | ||||||||||||||||||||||||||||||||||||||||||||||||
'--color=never', | ||||||||||||||||||||||||||||||||||||||||||||||||
] | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
def _create_error_report(self, unit_rc, fmt_rc) -> eTree.Element: | ||||||||||||||||||||||||||||||||||||||||||||||||
# TODO(luca) revisit when programmatic output from cargo test is | ||||||||||||||||||||||||||||||||||||||||||||||||
# stabilized, for now just have a suite for unit, and fmt tests | ||||||||||||||||||||||||||||||||||||||||||||||||
failures = 0 | ||||||||||||||||||||||||||||||||||||||||||||||||
testsuites = eTree.Element('testsuites') | ||||||||||||||||||||||||||||||||||||||||||||||||
# TODO(luca) add time | ||||||||||||||||||||||||||||||||||||||||||||||||
testsuite = eTree.SubElement(testsuites, | ||||||||||||||||||||||||||||||||||||||||||||||||
'testsuite', {'name': 'cargo_test'}) | ||||||||||||||||||||||||||||||||||||||||||||||||
unit_testcase = eTree.SubElement(testsuite, 'testcase', | ||||||||||||||||||||||||||||||||||||||||||||||||
{'name': 'unit'}) | ||||||||||||||||||||||||||||||||||||||||||||||||
if unit_rc.returncode: | ||||||||||||||||||||||||||||||||||||||||||||||||
unit_failure = eTree.SubElement(unit_testcase, 'failure', | ||||||||||||||||||||||||||||||||||||||||||||||||
{'message': 'cargo test failed'}) | ||||||||||||||||||||||||||||||||||||||||||||||||
unit_failure.text = unit_rc.stdout.decode('utf-8') | ||||||||||||||||||||||||||||||||||||||||||||||||
failures += 1 | ||||||||||||||||||||||||||||||||||||||||||||||||
fmt_testcase = eTree.SubElement(testsuite, 'testcase', {'name': 'fmt'}) | ||||||||||||||||||||||||||||||||||||||||||||||||
if fmt_rc.returncode: | ||||||||||||||||||||||||||||||||||||||||||||||||
fmt_failure = eTree.SubElement(fmt_testcase, 'failure', | ||||||||||||||||||||||||||||||||||||||||||||||||
{'message': 'cargo fmt failed'}) | ||||||||||||||||||||||||||||||||||||||||||||||||
fmt_failure.text = fmt_rc.stdout.decode('utf-8') | ||||||||||||||||||||||||||||||||||||||||||||||||
failures += 1 | ||||||||||||||||||||||||||||||||||||||||||||||||
testsuite.attrib['errors'] = str(0) | ||||||||||||||||||||||||||||||||||||||||||||||||
testsuite.attrib['failures'] = str(failures) | ||||||||||||||||||||||||||||||||||||||||||||||||
testsuite.attrib['skipped'] = str(0) | ||||||||||||||||||||||||||||||||||||||||||||||||
testsuite.attrib['tests'] = str(2) | ||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps I'm misunderstanding, but it seems like we're hard coding the report to always list 2 tests run? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes that is the case, all the output from |
||||||||||||||||||||||||||||||||||||||||||||||||
return testsuites |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
/// Failing doctest example | ||
/// ``` | ||
/// invalid_syntax | ||
/// ``` | ||
pub struct Type; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,17 @@ | ||
fn main() { | ||
println!("Hello, world!"); | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
|
||
#[test] | ||
fn ok() -> Result<(), ()> { | ||
Ok(()) | ||
} | ||
|
||
#[test] | ||
fn err() -> Result<(), ()> { | ||
Err(()) | ||
} | ||
} |
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 we actually add a docstring here? Perhaps just mentioning at a high level the two cargo commands running and why?
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 not sure what to say about the why we run tests / fmt 😅 but very valid, I added a docstring saying what we run (test and fmt) and what we don't and why we don't (docs) bf3637e