Skip to content

Commit

Permalink
0.9.0-dev if statements tested
Browse files Browse the repository at this point in the history
  • Loading branch information
EvanKepner committed Mar 9, 2019
1 parent ee6adce commit 2b44ede
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 26 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ Changelog
not guaranteed while under development.


0.9.0
-----

- Added new :code:`If` mutation:
1. Original statements are represented by :code:`If_Statement` and mutated to be either
:code:`If_True` where the statement always passes, or :code:`If_False` where the statement
is never passed.


0.8.0
-----

Expand Down
29 changes: 29 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ Supported operations:
- :code:`Compare` mutations e.g. :code:`== >= < <= !=`.
- :code:`Compare In` mutations e.g. :code:`in, not in`.
- :code:`Compare Is` mutations e.g. :code:`is, is not`.
- :code:`If` mutations e.g. :code:`If x > y` becomes :code:`If True` or :code:`If False`.
- :code:`Index` mutations e.g. :code:`i[0]` becomes :code:`i[1]` and :code:`i[-1]`.
- :code:`NameConstant` mutations e.g. :code:`True`, :code:`False`, and :code:`None`.
- :code:`Slice` mutations e.g. changing :code:`x[:2]` to :code:`x[2:]`
Expand Down Expand Up @@ -529,6 +530,34 @@ Example:
x is not None
If
--
If mutations change :code:`if` statements to always be :code:`True` or :code:`False`. The original
statement is represented by the class :code:`If_Statement` in reporting.
Members:
- :code:`If_False`
- :code:`If_Statement`
- :code:`If_True`
Example:
.. code-block:: python
# source code
if a > b: # If_Statement
...
# Mutations
if True: # If_True
...
if False: # If_False
...
Index
-----
Expand Down
2 changes: 1 addition & 1 deletion mutatest/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Mutation initialization.
"""
__version__ = "0.8.1-dev"
__version__ = "0.9.0-dev"

__title__ = "mutatest"
__description__ = "Python mutation testing: test your tests!"
Expand Down
12 changes: 6 additions & 6 deletions mutatest/_devtools.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ def visit_Compare(self, node: ast.Compare) -> None:
print(ast.dump(node))
self.generic_visit(node)

def visit_If(self, node: ast.If) -> None:
# have is, is not already, not clear If directly is a valuable mutation
print(f"If: {node}")
print(ast.dump(node))
self.generic_visit(node)

def visit_Index(self, node: ast.Index) -> None:
# actual slicing
print(f"Index: {node}")
Expand All @@ -73,12 +79,6 @@ def visit_Subscript(self, node: ast.Subscript) -> None:
# IN DEVELOPMENT
################################################################################################

def visit_If(self, node: ast.If) -> None:
# have is, is not already, not clear If directly is a valuable mutation
# print(f"If: {node}")
print(ast.dump(node))
self.generic_visit(node)

# FUTURE DEVELOPMENT
################################################################################################

Expand Down
28 changes: 11 additions & 17 deletions mutatest/transformers.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,18 +200,14 @@ def visit_Compare(self, node: ast.Compare) -> ast.AST:
return node

def visit_If(self, node: ast.If) -> ast.AST:

# TODO: DETERMINE IF_CONSTANT TARGET TRANSFORMTION USAGE
# TODO: CURRENTLY ONLY TRUE/FALSE WORK

"""If statements e.g. If x == y transformed to If True and If False."""
self.generic_visit(node)
log_header = f"visit_If: {self.src_file}:"

# default for a comparison is "If_NotConstant" which will be changed to True/False
# if_NotConstand if not set as a mutation target, controlled in get_mutations function
# default for a comparison is "If_Statement" which will be changed to True/False
# If_Statement is not set as a mutation target, controlled in get_mutations function

if_type = "If_NotConstant"
if_type = "If_Statement"

if_mutations = {
"If_True": ast.NameConstant(value=True),
Expand All @@ -224,10 +220,6 @@ def visit_If(self, node: ast.If) -> ast.AST:
idx = LocIndex("If", node.lineno, node.col_offset, if_type)
self.locs.add(idx)

if type(node) == ast.If:
LOGGER.info(self.target_idx)
LOGGER.info(self.mutation)

if idx == self.target_idx and self.mutation and not self.readonly:
LOGGER.debug("%s mutating idx: %s with %s", log_header, self.target_idx, self.mutation)
return ast.fix_missing_locations(
Expand Down Expand Up @@ -418,7 +410,7 @@ def get_compatible_operation_sets() -> List[MutationOpSet]:

# Custom references for If statement substitutions
# only If_True and If_False will be applied as mutations
if_types: Set[str] = {"If_True", "If_False", "If_Compare"}
if_types: Set[str] = {"If_True", "If_False", "If_Statement"}

# Custom references for subscript substitutions for slice mutations
slice_bounded_types: Set[str] = {"Slice_UnboundUpper", "Slice_UnboundLower", "Slice_Unbounded"}
Expand Down Expand Up @@ -455,7 +447,9 @@ def get_compatible_operation_sets() -> List[MutationOpSet]:
name="Compare Is", desc="Comapre identity e.g. is, is not", operations=cmpop_is_types
),
MutationOpSet(
name="If", desc="If statement tests e.g. Compare, True, False", operations=if_types
name="If",
desc="If statement tests e.g. original statement, True, False",
operations=if_types,
),
MutationOpSet(
name="Index",
Expand Down Expand Up @@ -505,10 +499,10 @@ def get_mutations_for_target(target: LocIndex) -> Set[Any]:
if target.op_type in ["Slice_UPosToZero", "Slice_UNegToZero"]:
mutation_ops = {target.op_type}

# Special case for If-compare since that is a default to transform to True or False
# but not a validation mutation target by iself
if "If_Compare" in mutation_ops:
mutation_ops.remove("If_Compare")
# Special case for If_Statement since that is a default to transform to True or False
# but not a validation mutation target by itself
if "If_Statement" in mutation_ops:
mutation_ops.remove("If_Statement")

break

Expand Down
46 changes: 46 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,52 @@ def compare_expected_loc():
return LocIndex(ast_class="Compare", lineno=2, col_offset=11, op_type=ast.Eq)


####################################################################################################
# TRANSFORMERS: IF FIXTURES
####################################################################################################


@pytest.fixture(scope="session")
def if_file(tmp_path_factory):
"""Simple file for if-statement mutations."""
contents = dedent(
"""\
def equal_test(a, b):
if a == b:
print("Equal")
elif a < b:
print("LT")
else:
print("Else!")
def second():
if True:
print("true")
if False:
print("false")
"""
)

fn = tmp_path_factory.mktemp("if_statement") / "if_statement.py"

with open(fn, "w") as output_fn:
output_fn.write(contents)

return fn


@pytest.fixture(scope="session")
def if_expected_locs():
"""Exepected locations in the if_statement."""
return [
LocIndex(ast_class="If", lineno=2, col_offset=4, op_type="If_Statement"),
LocIndex(ast_class="If", lineno=4, col_offset=9, op_type="If_Statement"),
LocIndex(ast_class="If", lineno=10, col_offset=4, op_type="If_True"),
LocIndex(ast_class="If", lineno=13, col_offset=4, op_type="If_False"),
]


####################################################################################################
# TRANSFORMERS: INDEX FIXTURES
####################################################################################################
Expand Down
35 changes: 33 additions & 2 deletions tests/test_transformers.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,35 @@ def test_MutateAST_visit_compare(compare_file, compare_expected_loc):
assert l.op_type == test_mutation


def test_MutateAST_visit_if(if_file, if_expected_locs):
"""Test mutation for nameconst: True, False, None."""
tree = get_ast_from_src(if_file)
test_mutation = "If_True"

testing_tree = deepcopy(tree)
# change from If_Statement to If_True
mutated_tree = MutateAST(target_idx=if_expected_locs[0], mutation=test_mutation).visit(
testing_tree
)

mast = MutateAST(readonly=True)
mast.visit(mutated_tree)

# named constants will also be picked up, filter just to if_ operations
if_locs = [l for l in mast.locs if l.ast_class == "If"]
assert len(if_locs) == 4

for l in if_locs:
# spot check on mutation from True to False
if l.lineno == 2 and l.col_offset == 4:
print(l)
assert l.op_type == test_mutation

# spot check on not-mutated location still being None
if l.lineno == 13 and l.col_offset == 4:
assert l.op_type == "If_False"


INDEX_SETS = [
# idx order, lineno, col_offset, mutation to apply
# change NumNeg to Pos and Zero
Expand Down Expand Up @@ -232,9 +261,11 @@ def test_MutateAST_visit_nameconst(nameconst_file, nameconst_expected_locs):
mast = MutateAST(readonly=True)
mast.visit(mutated_tree)

assert len(mast.locs) == 4
# if statement is included with this file that will be picked up
nc_locs = [l for l in mast.locs if l.ast_class == "NameConstant"]
assert len(nc_locs) == 4

for l in mast.locs:
for l in nc_locs:
# spot check on mutation from True to False
if l.lineno == 1 and l.col_offset == 14:
assert l.op_type == test_mutation
Expand Down

0 comments on commit 2b44ede

Please sign in to comment.