Skip to content

Add support for checking tests in Rust 1.23. #247

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

Merged
merged 1 commit into from
Feb 10, 2018
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions RustEnhanced.sublime-settings
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"rust_syntax_checking_method": "check",

// Enable checking of test code within #[cfg(test)] sections.
// `check` method requires Rust 1.23 or newer.
"rust_syntax_checking_include_tests": true,

// If true, will not display warning messages.
Expand Down
6 changes: 5 additions & 1 deletion SyntaxCheckPlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import sublime_plugin
import os
from .rust import (messages, rust_proc, rust_thread, util, target_detect,
cargo_settings)
cargo_settings, semver)
from pprint import pprint


Expand Down Expand Up @@ -152,6 +152,10 @@ def get_rustc_messages(self):
# It also disables the "main function not found" error for
# binaries.
cmd['command'].append('--test')
elif method == 'check':
if (util.get_setting('rust_syntax_checking_include_tests', True) and
semver.match(cmd['rustc_version'], '>=1.23.0')):
cmd['command'].append('--profile=test')
p = rust_proc.RustProc()
self.current_target_src = target_src
p.run(self.window, cmd['command'], self.cwd, self, env=cmd['env'])
Expand Down
8 changes: 5 additions & 3 deletions tests/error-tests/examples/no_main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

mod no_main_mod;
// Not sure why no-trans doesn't handle this properly.
// end-msg: ERR(check) main function not found
// end-msg: NOTE(check) the main function must be defined
// end-msg: MSG(check) Note: no_main_mod.rs:1
// When --profile=test is used with `cargo check`, this error will not happen
// due to the synthesized main created by the test harness.
// end-msg: ERR(rust_syntax_checking_include_tests=False) main function not found
// end-msg: NOTE(rust_syntax_checking_include_tests=False) the main function must be defined
// end-msg: MSG(rust_syntax_checking_include_tests=False) Note: no_main_mod.rs:1
6 changes: 3 additions & 3 deletions tests/error-tests/examples/no_main_mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*BEGIN*/fn main() {
// ^^^^^^^^^WARN(no-trans) function is never used
// ^^^^^^^^^NOTE(no-trans) #[warn(dead_code)]
// ^^^^^^^^^WARN(rust_syntax_checking_include_tests=True) function is never used
// ^^^^^^^^^NOTE(rust_syntax_checking_include_tests=True) #[warn(dead_code)]
// 1.24 nightly has changed how these no-trans messages are displayed (instead
// of encompassing the entire function).
}/*END*/
// ~NOTE(check) here is a function named 'main'
// ~NOTE(rust_syntax_checking_include_tests=False) here is a function named 'main'
8 changes: 4 additions & 4 deletions tests/error-tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ mod tests {
// ^^^^^^^^^^^^ERR(<1.16.0) undefined or not in scope
// ^^^^^^^^^^^^ERR(<1.16.0) type name
// ^^^^^^^^^^^^HELP(<1.16.0) no candidates
// ^^^^^^^^^^^^ERR(>=1.16.0,test) not found in this scope
// ^^^^^^^^^^^^ERR(>=1.16.0,test) cannot find type `DoesNotExist`
// ^^^^^^^^^^^^ERR(>=1.16.0,rust_syntax_checking_include_tests=True) not found in this scope
// ^^^^^^^^^^^^ERR(>=1.16.0,rust_syntax_checking_include_tests=True) cannot find type `DoesNotExist`
}

#[test]
fn it_works() {
asdf
// ^^^^ERR(<1.16.0) unresolved name
// ^^^^ERR(<1.16.0) unresolved name
// ^^^^ERR(>=1.16.0,test) not found in this scope
// ^^^^ERR(>=1.16.0,test) cannot find value
// ^^^^ERR(>=1.16.0,rust_syntax_checking_include_tests=True) not found in this scope
// ^^^^ERR(>=1.16.0,rust_syntax_checking_include_tests=True) cannot find value
}
}
3 changes: 3 additions & 0 deletions tests/rust_test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,6 @@ def __enter__(self):

def __exit__(self, type, value, traceback):
self.settings.set(self.name, self.orig)

def __str__(self):
return '%s=%s' % (self.name, self.value)
122 changes: 80 additions & 42 deletions tests/test_syntax_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"""


import contextlib
import itertools
import re
from rust_test_common import *

Expand Down Expand Up @@ -62,10 +64,15 @@ def test_messages(self):

The current restrictions are:

- `test`: This message only appears in a cfg(test) block.
- `no-trans`: This message only appears with no-trans method.
- `check`: This message only appears with check method.
- semver: Any semver string to match against the rustc version.
- `key=value`: Check a configuration value.

With multiple restrictions, they must all pass. You can separate
restrictions with " OR " to combine with Boolean OR:

// ^^^ERR(check OR test,<1.16.0) checks or test less than 1.16.

These tests are somewhat fragile, as new versions of Rust change the
formatting of messages. Hopefully these examples are relatively
Expand All @@ -92,11 +99,13 @@ def test_messages(self):
# message with suggestion
'error-tests/tests/cast-to-unsized-trait-object-suggestion.rs',
# error in a cfg(test) section
'error-tests/src/lib.rs',
([_altered_settings('rust_syntax_checking_include_tests', [True, False])],
'error-tests/src/lib.rs'),
'error-tests/tests/macro-expansion.rs',
'error-tests/tests/macro-backtrace-println.rs',
'error-tests/examples/SNAKE.rs',
('error-tests/examples/no_main.rs', 'error-tests/examples/no_main_mod.rs'),
([_altered_settings('rust_syntax_checking_include_tests', [True, False])],
'error-tests/examples/no_main.rs', 'error-tests/examples/no_main_mod.rs'),
('error-tests/tests/remote_note_1.rs', 'error-tests/tests/remote_note_1_mod.rs'),
'error-tests/tests/macro-expansion-outside-1.rs',
'error-tests/tests/macro-expansion-outside-2.rs',
Expand All @@ -111,11 +120,15 @@ def test_messages(self):
'workspace/workspace2/src/lib.rs',
'workspace/workspace2/src/somemod.rs',
]

# Configure different permutations of settings to test for each file.
methods = ['no-trans']
if semver.match(self.rustc_version, '>=1.16.0'):
methods.append('check')
else:
print('Skipping check, need rust >= 1.16.')
setups = [_altered_settings('rust_syntax_checking_method', methods)]

if semver.match(self.rustc_version, '>=1.19.0'):
# -Zno-trans now requires nightly
self._override_setting('cargo_build', {
Expand All @@ -125,23 +138,29 @@ def test_messages(self):
}
}
})

for paths in to_test:
if not isinstance(paths, tuple):
paths = (paths,)
if isinstance(paths[0], str):
extra_setups = []
else:
extra_setups = paths[0]
paths = paths[1:]
paths = [os.path.join('tests', p) for p in paths]
self._with_open_file(paths[0], self._test_messages,
methods=methods, extra_paths=paths[1:])
setups=setups + extra_setups, extra_paths=paths[1:])

def test_clippy_messages(self):
"""Test clippy messages."""
to_test = [
'tests/error-tests/examples/clippy_ex.rs',
]
setups = [[AlteredSetting('rust_syntax_checking_method', 'clippy')]]
for path in to_test:
self._with_open_file(path, self._test_messages, methods=['clippy'])

def _test_messages(self, view, methods=None, extra_paths=()):
self._with_open_file(path, self._test_messages, setups=setups)

def _test_messages(self, view, setups=None, extra_paths=()):
# Capture all calls to Sublime to add phantoms and regions.
# These are keyed by filename.
phantoms = {}
Expand All @@ -163,35 +182,37 @@ def collect_regions(v, key, regions, scope, icon, flags):

# Trigger the generation of messages.
try:
for method in methods:
with AlteredSetting('rust_syntax_checking_method', method):
self._test_messages2(view, phantoms, view_regions, method, extra_paths)
for setup in itertools.product(*setups):
with contextlib.ExitStack() as stack:
for ctx in setup:
stack.enter_context(ctx)
self._test_messages2(view, phantoms, view_regions, extra_paths, setup)
phantoms.clear()
view_regions.clear()
finally:
m._sublime_add_phantom = orig_add_phantom
m._sublime_add_regions = orig_add_regions

def _test_messages2(self, view, phantoms, regions, method, extra_paths):
def _test_messages2(self, view, phantoms, regions, extra_paths, setup):
e = plugin.SyntaxCheckPlugin.RustSyntaxCheckEvent()
# Force Cargo to recompile.
self._cargo_clean(view)
# os.utime(view.file_name()) 1 second resolution is not enough
e.on_post_save(view)
# Wait for it to finish.
self._get_rust_thread().join()
self._test_messages_check(view, phantoms, regions, method)
self._test_messages_check(view, phantoms, regions, setup)

def extra_check(view):
# on_load is disabled during tests, do it manually.
plugin.rust.messages.show_messages_for_view(view)
self._test_messages_check(view, phantoms, regions, method)
self._test_messages_check(view, phantoms, regions, setup)

# Load any other views that are expected to have messages.
for path in extra_paths:
self._with_open_file(path, extra_check)

def _test_messages_check(self, view, phantoms, regions, method):
def _test_messages_check(self, view, phantoms, regions, setup):
phantoms = phantoms.get(view.file_name(), [])
regions = regions.get(view.file_name(), [])
expected_messages = self._collect_expected_regions(view)
Expand All @@ -201,36 +222,45 @@ def _test_messages_check(self, view, phantoms, regions, method):
manifest_path = util.find_cargo_manifest(view.file_name())
cs = cargo_settings.CargoSettings(window)
cs.load()
method = util.get_setting('rust_syntax_checking_method')
toolchain = cs.get_computed(manifest_path, method, None, 'toolchain')
self.rustc_version = util.get_rustc_version(window, manifest_path,
toolchain=toolchain)

def do_check(check):
if check == 'check':
# This message only shows up in check.
return method == 'check'
elif check == 'no-trans':
# This message only shows up in no-trans.
return method == 'no-trans'
elif check == 'nightly':
# This message only shows on nightly.
return 'nightly' in self.rustc_version
elif re.match('[<>=!0-9]', check):
return semver.match(self.rustc_version, check)
elif '=' in check:
key, value = check.split('=')
if value == 'True':
value = True
elif value == 'False':
value = False
return util.get_setting(key) == value
else:
raise ValueError(check)

def restriction_check(restrictions):
if not restrictions:
return True
checks = restrictions[1:-1].split(',')
for check in checks:
if check == 'test':
if method == 'check':
# 'cargo check' currently does not handle cfg(test)
# blocks (see
# https://github.com/rust-lang/cargo/issues/3431)
return False
elif check == 'check':
# This message only shows up in check.
if method != 'check':
return False
elif check == 'no-trans':
# This message only shows up in no-trans.
if method != 'no-trans':
return False
elif check == 'nightly':
# This message only shows on nightly.
return 'nightly' in self.rustc_version
ors = restrictions[1:-1].split(' OR ')
for conj in ors:
checks = conj.split(',')
for check in checks:
if not do_check(check):
break
else:
if not semver.match(self.rustc_version, check):
return False
return True
return True
return False

region_set = {(r.begin(), r.end()) for r in regions}

Expand All @@ -255,14 +285,14 @@ def restriction_check(restrictions):
self.assertIn(emsg_info['level'], content)
break
else:
raise AssertionError('Did not find expected message "%s:%s" for region %r:%r for file %r method=%r\nAvailable phantoms=%r' % (
raise AssertionError('Did not find expected message "%s:%s" for region %r:%r for file %r\nsetup=%s\nversion=%s\nAvailable phantoms=%r' % (
emsg_info['level'], emsg_info['message'],
emsg_info['begin'], emsg_info['end'],
view.file_name(), method, phantoms))
view.file_name(), _setup_debug(setup), self.rustc_version, phantoms))
del phantoms[i]
if len(phantoms):
raise AssertionError('Got extra phantoms for %r (method=%s, version=%s): %r' % (
view.file_name(), method, self.rustc_version, phantoms))
raise AssertionError('Got extra phantoms for %r\nsetup=%s\nversion=%s\n%r' % (
view.file_name(), _setup_debug(setup), self.rustc_version, phantoms))

# Check regions.
found_regions = set()
Expand All @@ -273,9 +303,9 @@ def restriction_check(restrictions):
if r in region_set:
found_regions.add(r)
else:
raise AssertionError('Did not find expected region %r,%r for file %r method %r\nActual regions=%r' % (
raise AssertionError('Did not find expected region %r,%r for file %r\nsetup=%s\nversion=%s\nActual regions=%r' % (
emsg_info['begin'], emsg_info['end'], view.file_name(),
method, region_set))
_setup_debug(setup), self.rustc_version, region_set))
if len(region_set) != len(found_regions):
extra_regions = region_set - found_regions
raise AssertionError('Got extra regions for %r: %r' % (
Expand Down Expand Up @@ -373,3 +403,11 @@ def _collect_expected_regions(self, view):
})

return result


def _altered_settings(name, values):
return [AlteredSetting(name, value) for value in values]


def _setup_debug(setup):
return '\n' + '\n'.join([' ' + str(s) for s in setup])