forked from MetroRobots/ros_glint
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
314 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
[report] | ||
omit = | ||
*_version.py | ||
*finder.py | ||
*terminal.py | ||
*diff.py | ||
*config.py | ||
*/package.py | ||
*/ros_resources.py | ||
*/util.py | ||
*/main.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from .core import get_linters | ||
|
||
__all__ = ['get_linters'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import collections | ||
import pathlib | ||
|
||
|
||
_glinter_functions = collections.OrderedDict() | ||
|
||
root = pathlib.Path(__file__).parent.parent.parent | ||
|
||
|
||
# Decorator function for gathering all the functions | ||
def glinter(f): | ||
_glinter_functions[f.__name__] = f | ||
return f | ||
|
||
|
||
def get_linters(): | ||
return _glinter_functions |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import difflib | ||
from .terminal import color_diff | ||
|
||
|
||
def get_diff_string(contents0, contents1, filename): | ||
s = '=' * 5 + str(filename) + '=' * 45 + '\n' | ||
d = difflib.Differ() | ||
result = d.compare(contents0.split('\n'), contents1.split('\n')) | ||
s += '\n'.join(color_diff(result)) | ||
return s |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
from colorama import Fore, Style, init | ||
|
||
init() | ||
|
||
|
||
def color_diff(diff, buffer_size=5): | ||
# Buffer size is the number of lines before and after diffs to print | ||
buffer = [] | ||
started = False | ||
|
||
for line in diff: | ||
color = '' | ||
if line.startswith('+'): | ||
color = Fore.GREEN | ||
elif line.startswith('-'): | ||
color = Fore.RED | ||
elif line.startswith('^'): | ||
color = Fore.BLUE | ||
elif line.startswith('?'): | ||
continue | ||
|
||
if color: | ||
if buffer: | ||
if len(buffer) > buffer_size: | ||
yield '...' | ||
yield from buffer[-buffer_size:] | ||
started = True | ||
yield color + line + Fore.RESET | ||
buffer = [] | ||
else: | ||
if started and len(buffer) >= buffer_size: | ||
yield from buffer[:buffer_size] | ||
buffer = [] | ||
started = False | ||
buffer.append(line) | ||
if buffer: | ||
yield from buffer[-buffer_size:] | ||
yield '...' | ||
|
||
|
||
def color_text(s, fore='YELLOW', bright=False): | ||
return (Style.BRIGHT if bright else '') + getattr(Fore, fore) + s + Style.RESET_ALL |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import os | ||
import stat | ||
|
||
|
||
def set_executable(fn, state): | ||
existing_permissions = stat.S_IMODE(os.lstat(fn).st_mode) | ||
if state: | ||
flags = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH | ||
else: | ||
flags = ~stat.S_IXUSR | ~stat.S_IXGRP | ~stat.S_IXOTH | ||
os.chmod(fn, existing_permissions | flags) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import inspect | ||
import pooch | ||
import pytest | ||
from ros_glint import get_linters | ||
from ros_glint.diff import get_diff_string | ||
from ros_glint.terminal import color_text | ||
from zip_interface import get_test_cases | ||
from betsy_ros import ROSInterface | ||
|
||
from ros_introspect import Package, ROSResources | ||
|
||
URL_TEMPLATE = 'https://github.com/DLu/roscompile_test_data/raw/{}/test_data.zip' | ||
TEST_DATA = [ | ||
# (branch, known_hash) | ||
('ros1', '2100c19c912c6044194e7a77dad3d002e3b22f5206b171c3f127069b87dbc662'), | ||
('ros2', '97fa59fdc742a60e57cf17643b70eb21aa26a6967cc64d364371480665fb5635'), | ||
] | ||
|
||
linters = get_linters() | ||
|
||
|
||
def files_match(pkg_in, pkg_out, filename): | ||
"""Return true if the contents of the given file are the same in each package. Otherwise maybe show the diff.""" | ||
generated_contents = pkg_in.get_contents(filename).rstrip() | ||
canonical_contents = pkg_out.get_contents(filename).rstrip() | ||
ret = generated_contents == canonical_contents | ||
if not ret: | ||
print(get_diff_string(generated_contents, canonical_contents, filename)) | ||
return ret | ||
|
||
|
||
def run_case(test_config, cases): | ||
resources = ROSResources.get() | ||
|
||
with cases[test_config['in']] as pkg_in: | ||
pkg_out = cases[test_config['out']] | ||
root = pkg_in.root | ||
pkg_obj = Package(root) | ||
local_config = test_config.get('config', {}) | ||
|
||
# Initialize ROS Resources | ||
resources.packages = set(test_config.get('pkgs', [])) | ||
resources.messages = set() | ||
for msg in test_config.get('msgs', []): | ||
parts = msg.split('/') | ||
resources.messages.add(ROSInterface(parts[0], 'msg', parts[1])) | ||
|
||
# Run Functions | ||
for function_name in test_config['functions']: | ||
if function_name not in linters: | ||
pytest.skip(f'Missing linter: {function_name}') | ||
|
||
fne = linters[function_name] | ||
if 'config' in inspect.getfullargspec(fne).args: | ||
fne(pkg_obj, config=local_config) | ||
else: | ||
fne(pkg_obj) | ||
pkg_obj.save() | ||
|
||
folder_diff = pkg_in.compare_filesets(pkg_out) | ||
|
||
s = '{:25} >> {:25} {}'.format(test_config['in'], test_config['out'], | ||
','.join(test_config['functions'])) | ||
print(color_text(s, 'BLUE', bright=True)) | ||
|
||
def jp(paths): | ||
# Join Paths | ||
return ', '.join(map(str, paths)) | ||
|
||
assert len(folder_diff['deleted']) == 0, \ | ||
f'These files should have been deleted but weren\'t: {jp(folder_diff["deleted"])}' | ||
assert len(folder_diff['added']) == 0, \ | ||
f'These files should have been generated but weren\'t: {jp(folder_diff["added"])}' | ||
for filename in folder_diff['matches']: | ||
assert files_match(pkg_in, pkg_out, filename), 'The contents of {} do not match!'.format(filename) | ||
|
||
|
||
parameters = [] | ||
test_ids = [] | ||
|
||
for branch, known_hash in TEST_DATA: | ||
file_path = pooch.retrieve(URL_TEMPLATE.format(branch), known_hash=known_hash) | ||
config, test_data = get_test_cases(file_path) | ||
|
||
for i, test_config in enumerate(config): | ||
parameters.append((test_config, test_data)) | ||
test_ids.append(f'{branch}_test_{i:03d}') | ||
|
||
|
||
@pytest.mark.parametrize('test_config, test_data', parameters, ids=test_ids) | ||
def test_from_zip(test_config, test_data): | ||
run_case(test_config, test_data) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import collections | ||
import os | ||
import pathlib | ||
import shutil | ||
import tempfile | ||
import yaml | ||
import zipfile | ||
|
||
from ros_introspect.finder import walk | ||
from ros_glint.util import set_executable | ||
|
||
|
||
class ROSCompilePackageFiles: | ||
def __init__(self, package_name, pkg_files, executables): | ||
self.package_name = package_name | ||
self.is_written = False | ||
self.root = self.get_input_root() | ||
self.pkg_files = pkg_files | ||
self.executables = executables | ||
|
||
def copy(self): | ||
return ROSCompilePackageFiles(self.package_name, self.pkg_files, self.executables) | ||
|
||
def get_input_root(self): | ||
return pathlib.Path(tempfile.gettempdir()) / self.package_name | ||
|
||
def __enter__(self): | ||
self.write() | ||
self.is_written = True | ||
return self | ||
|
||
def __exit__(self, a_type, value, traceback): | ||
self.is_written = False | ||
self.clear() | ||
|
||
def get_filenames(self): | ||
if self.is_written: | ||
the_files = [] | ||
for subpath in walk(self.root): | ||
the_files.append(subpath) | ||
return set(the_files) | ||
else: | ||
return set(self.pkg_files.keys()) | ||
|
||
def get_contents(self, filename): | ||
if self.is_written: | ||
full_path = self.root / filename | ||
if full_path.exists(): | ||
return open(full_path).read().replace('\r\n', '\n') | ||
elif filename in self.pkg_files: | ||
return self.pkg_files[filename].replace('\r\n', '\n') | ||
|
||
def compare_filesets(self, other_package): | ||
in_keys = self.get_filenames() | ||
out_keys = other_package.get_filenames() | ||
matches = in_keys.intersection(out_keys) | ||
missed_deletes = in_keys - out_keys | ||
missed_generations = out_keys - in_keys | ||
return {'matches': sorted(matches), 'deleted': sorted(missed_deletes), 'added': sorted(missed_generations)} | ||
|
||
def write(self): | ||
self.clear() | ||
self.root.mkdir() | ||
for fn, contents in self.pkg_files.items(): | ||
outfile = self.root / fn | ||
outfile.parent.mkdir(exist_ok=True, parents=True) | ||
with open(outfile, 'w') as f: | ||
f.write(contents) | ||
if fn in self.executables: | ||
set_executable(outfile, True) | ||
|
||
def clear(self): | ||
if self.root.exists(): | ||
shutil.rmtree(self.root) | ||
|
||
def __repr__(self): | ||
return self.package_name | ||
|
||
|
||
def get_test_cases(zip_filename): | ||
file_data = collections.defaultdict(dict) | ||
zf = zipfile.ZipFile(zip_filename) | ||
config = None | ||
executables = set() | ||
for file in zf.filelist: | ||
if file.filename[-1] == '/': | ||
continue | ||
if file.filename == 'list_o_tests.yaml': | ||
config = yaml.safe_load(zf.read(file)) | ||
continue | ||
parts = file.filename.split(os.path.sep) | ||
package = parts[0] | ||
path = pathlib.Path(os.path.join(*parts[1:])) | ||
file_data[package][path] = zf.read(file).decode() | ||
if (file.external_attr >> 16) & 0o111: | ||
executables.add(path) | ||
|
||
test_data = {} | ||
for package, pkg_data in file_data.items(): | ||
test_data[package] = ROSCompilePackageFiles(package, pkg_data, executables) | ||
for pkg_data in config: | ||
if 'function' in pkg_data: | ||
pkg_data['functions'] = [pkg_data.pop('function')] | ||
|
||
return config, test_data |