Skip to content

Commit

Permalink
Add tests for LinesPass (#164)
Browse files Browse the repository at this point in the history
Add basic unit tests.
  • Loading branch information
emaxx-google authored Jan 22, 2025
1 parent cd145f0 commit 9f1e1d5
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 43 deletions.
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,12 @@ configure_file(
@ONLY
)

configure_file(
"${PROJECT_SOURCE_DIR}/cvise/utils/externalprograms.py"
"${PROJECT_BINARY_DIR}/cvise/utils/externalprograms.py"
@ONLY
)

configure_file(
"${PROJECT_SOURCE_DIR}/tests/test_cvise.py"
"${PROJECT_BINARY_DIR}/tests/test_cvise.py"
Expand Down
44 changes: 1 addition & 43 deletions cvise.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
import logging
import os
import os.path
import platform
import shutil
import sys
import tempfile
import time
Expand All @@ -26,6 +24,7 @@
from cvise.utils import misc, statistics, testing # noqa: E402
from cvise.utils.error import CViseError # noqa: E402
from cvise.utils.error import MissingPassGroupsError # noqa: E402
from cvise.utils.externalprograms import find_external_programs # noqa: E402
import psutil # noqa: E402


Expand Down Expand Up @@ -56,47 +55,6 @@ def get_share_dir():
raise CViseError('Cannot find cvise module directory!')


def find_external_programs():
programs = {
'clang_delta': 'clang_delta',
'clex': 'clex',
'topformflat': 'delta',
'unifdef': None,
'gcov-dump': None,
}

for prog, local_folder in programs.items():
path = None
if local_folder:
local_folder = os.path.join(script_path, local_folder)
if platform.system() == 'Windows':
for configuration in ['Debug', 'Release']:
new_local_folder = os.path.join(local_folder, configuration)
if os.path.exists(new_local_folder):
local_folder = new_local_folder
break

path = shutil.which(prog, path=local_folder)

if not path:
search = os.path.join('@CMAKE_INSTALL_FULL_LIBEXECDIR@', '@cvise_PACKAGE@')
path = shutil.which(prog, path=search)
if not path:
search = destdir + os.path.join('@CMAKE_INSTALL_FULL_LIBEXECDIR@', '@cvise_PACKAGE@')
path = shutil.which(prog, path=search)

if not path:
path = shutil.which(prog)

if path is not None:
programs[prog] = path

# Special case for clang-format
programs['clang-format'] = '@CLANG_FORMAT_PATH@'

return programs


def get_pass_group_path(name):
return os.path.join(get_share_dir(), 'pass_groups', name + '.json')

Expand Down
2 changes: 2 additions & 0 deletions cvise/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,15 @@ set(SOURCE_FILES
"tests/test_ifs.py"
"tests/test_ints.py"
"tests/test_line_markers.py"
"tests/test_lines.py"
"tests/test_nestedmatcher.py"
"tests/test_peep.py"
"tests/test_special.py"
"tests/test_ternary.py"
"tests/test_test_manager.py"
"utils/__init__.py"
"utils/error.py"
"utils/externalprograms.py"
"utils/misc.py"
"utils/nestedmatcher.py"
"utils/readkey.py"
Expand Down
97 changes: 97 additions & 0 deletions cvise/tests/test_lines.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import pytest

from cvise.passes.lines import LinesPass
from cvise.utils.externalprograms import find_external_programs


@pytest.fixture
def input_path(tmp_path):
return tmp_path / 'input.cc'


@pytest.fixture
def external_programs():
return find_external_programs()


def read_file(path):
with open(path) as f:
return f.read()


def write_file(path, data):
with open(path, 'w') as f:
f.write(data)


def collect_all_transforms(pass_, state, input_path):
all_outputs = set()
backup = read_file(input_path)
while state is not None:
pass_.transform(input_path, state, process_event_notifier=None)
all_outputs.add(read_file(input_path))
write_file(input_path, backup)
state = pass_.advance(input_path, state)
return all_outputs


def advance_until(pass_, state, input_path, predicate):
backup = read_file(input_path)
while True:
pass_.transform(input_path, state, process_event_notifier=None)
if predicate(read_file(input_path)):
return state
write_file(input_path, backup)
state = pass_.advance(input_path, state)
assert state is not None


def test_new_reformatting_arg0(input_path, external_programs):
write_file(input_path, 'int f() {\nchar x;\n}\nnamespace foo\n{\n}\n')
p = LinesPass('0', external_programs)
p.new(input_path, check_sanity=lambda: True)
assert read_file(input_path) == 'int f() { char x; }\n namespace foo { }\n'


def test_new_reformatting_arg1(input_path, external_programs):
write_file(input_path, 'int f() {\nchar x;\n}\nnamespace foo\n{\n}\n')
p = LinesPass('1', external_programs)
p.new(input_path, check_sanity=lambda: True)
assert read_file(input_path) == 'int f() {\n char x;\n }\n namespace foo {\n }\n'


def test_transform_deletes_individual_line(input_path, external_programs):
write_file(input_path, 'int f() { char x; }\nnamespace foo { }\n')
p = LinesPass('0', external_programs)
state = p.new(input_path)
all_transforms = collect_all_transforms(p, state, input_path)
assert 'int f() { char x; }\n' in all_transforms
assert ' namespace foo { }\n' in all_transforms


def test_transform_deletes_lines_range(input_path, external_programs):
write_file(input_path, 'A;\nB;\nC;\nD;\nE;\nF;\nG;\nH;\n')
p = LinesPass('0', external_programs)
state = p.new(input_path)
all_transforms = collect_all_transforms(p, state, input_path)
# deletion of a half:
assert 'A;\n B;\n C;\n D;\n' in all_transforms
assert ' E;\n F;\n G;\n H;\n' in all_transforms
# deletion of a quarter:
assert ' C;\n D;\n E;\n F;\n G;\n H;\n' in all_transforms
assert 'A;\n B;\n E;\n F;\n G;\n H;\n' in all_transforms
assert 'A;\n B;\n C;\n D;\n G;\n H;\n' in all_transforms
assert 'A;\n B;\n C;\n D;\n E;\n F;\n' in all_transforms


def test_advance_on_success(input_path, external_programs):
write_file(input_path, 'foo;\nbar;\nbaz;\n')
p = LinesPass('0', external_programs)
state = p.new(input_path)
# cut 'foo' first
state = advance_until(p, state, input_path, lambda s: 'bar' in s and 'baz' in s)
p.advance_on_success(input_path, state)
# cut 'baz' now
state = advance_until(p, state, input_path, lambda s: 'bar' in s)
p.advance_on_success(input_path, state)
assert read_file(input_path) == ' bar;\n'
47 changes: 47 additions & 0 deletions cvise/utils/externalprograms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import os
import platform
import shutil

SCRIPT_PATH = os.path.dirname(os.path.realpath(__file__))
DESTDIR = os.getenv('DESTDIR', '')


def find_external_programs():
programs = {
'clang_delta': 'clang_delta',
'clex': 'clex',
'topformflat': 'delta',
'unifdef': None,
'gcov-dump': None,
}

for prog, local_folder in programs.items():
path = None
if local_folder:
local_folder = os.path.join(SCRIPT_PATH, '..', '..', local_folder)
if platform.system() == 'Windows':
for configuration in ['Debug', 'Release']:
new_local_folder = os.path.join(local_folder, configuration)
if os.path.exists(new_local_folder):
local_folder = new_local_folder
break

path = shutil.which(prog, path=local_folder)

if not path:
search = os.path.join('@CMAKE_INSTALL_FULL_LIBEXECDIR@', '@cvise_PACKAGE@')
path = shutil.which(prog, path=search)
if not path:
search = DESTDIR + os.path.join('@CMAKE_INSTALL_FULL_LIBEXECDIR@', '@cvise_PACKAGE@')
path = shutil.which(prog, path=search)

if not path:
path = shutil.which(prog)

if path is not None:
programs[prog] = path

# Special case for clang-format
programs['clang-format'] = '@CLANG_FORMAT_PATH@'

return programs

0 comments on commit 9f1e1d5

Please sign in to comment.