Skip to content

Commit

Permalink
Merge pull request #23 from mutpy/break-down-operators
Browse files Browse the repository at this point in the history
Break down operators.py into multiple Python modules
  • Loading branch information
konradhalas committed Dec 27, 2017
2 parents 21302c8 + 0353e2d commit 14999c5
Show file tree
Hide file tree
Showing 10 changed files with 760 additions and 730 deletions.
730 changes: 0 additions & 730 deletions mutpy/operators.py

This file was deleted.

46 changes: 46 additions & 0 deletions mutpy/operators/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from .arithmetic import *
from .base import *
from .decorator import *
from .exception import *
from .inheritance import *
from .logical import *
from .loop import *
from .misc import *

SuperCallingInsert = utils.get_by_python_version([
SuperCallingInsertPython27,
SuperCallingInsertPython35,
])

standard_operators = {
ArithmeticOperatorDeletion,
ArithmeticOperatorReplacement,
AssignmentOperatorReplacement,
BreakContinueReplacement,
ConditionalOperatorDeletion,
ConditionalOperatorInsertion,
ConstantReplacement,
DecoratorDeletion,
ExceptionHandlerDeletion,
ExceptionSwallowing,
HidingVariableDeletion,
LogicalConnectorReplacement,
LogicalOperatorDeletion,
LogicalOperatorReplacement,
OverriddenMethodCallingPositionChange,
OverridingMethodDeletion,
RelationalOperatorReplacement,
SliceIndexRemove,
SuperCallingDeletion,
SuperCallingInsert,
}

experimental_operators = {
ClassmethodDecoratorInsertion,
OneIterationLoop,
ReverseIterationLoop,
SelfVariableDeletion,
StatementDeletion,
StaticmethodDecoratorInsertion,
ZeroIterationLoop,
}
79 changes: 79 additions & 0 deletions mutpy/operators/arithmetic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import ast

from mutpy.operators.base import MutationResign, MutationOperator, AbstractUnaryOperatorDeletion


class ArithmeticOperatorDeletion(AbstractUnaryOperatorDeletion):
def get_operator_type(self):
return ast.UAdd, ast.USub


class AbstractArithmeticOperatorReplacement(MutationOperator):
def should_mutate(self, node):
raise NotImplementedError()

def mutate_Add(self, node):
if self.should_mutate(node):
return ast.Sub()
raise MutationResign()

def mutate_Sub(self, node):
if self.should_mutate(node):
return ast.Add()
raise MutationResign()

def mutate_Mult_to_Div(self, node):
if self.should_mutate(node):
return ast.Div()
raise MutationResign()

def mutate_Mult_to_FloorDiv(self, node):
if self.should_mutate(node):
return ast.FloorDiv()
raise MutationResign()

def mutate_Mult_to_Pow(self, node):
if self.should_mutate(node):
return ast.Pow()
raise MutationResign()

def mutate_Div_to_Mult(self, node):
if self.should_mutate(node):
return ast.Mult()
raise MutationResign()

def mutate_Div_to_FloorDiv(self, node):
if self.should_mutate(node):
return ast.FloorDiv()
raise MutationResign()

def mutate_FloorDiv_to_Div(self, node):
if self.should_mutate(node):
return ast.Div()
raise MutationResign()

def mutate_FloorDiv_to_Mult(self, node):
if self.should_mutate(node):
return ast.Mult()
raise MutationResign()

def mutate_Mod(self, node):
if self.should_mutate(node):
return ast.Mult()
raise MutationResign()

def mutate_Pow(self, node):
if self.should_mutate(node):
return ast.Mult()
raise MutationResign()


class ArithmeticOperatorReplacement(AbstractArithmeticOperatorReplacement):
def should_mutate(self, node):
return not isinstance(node.parent, ast.AugAssign)

def mutate_USub(self, node):
return ast.UAdd()

def mutate_UAdd(self, node):
return ast.USub()
146 changes: 146 additions & 0 deletions mutpy/operators/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import ast
import copy
import re

from mutpy import utils


class MutationResign(Exception):
pass


class Mutation:
def __init__(self, operator, node, visitor=None):
self.operator = operator
self.node = node
self.visitor = visitor


def copy_node(mutate):
def f(self, node):
copied_node = copy.deepcopy(node, memo={
id(node.parent): node.parent,
})
return mutate(self, copied_node)

return f


class MutationOperator:
def mutate(self, node, to_mutate=None, sampler=None, coverage_injector=None, module=None, only_mutation=None):
self.to_mutate = to_mutate
self.sampler = sampler
self.only_mutation = only_mutation
self.coverage_injector = coverage_injector
self.module = module
for new_node in self.visit(node):
yield Mutation(operator=self.__class__, node=self.current_node, visitor=self.visitor), new_node

def visit(self, node):
if self.has_notmutate(node) or (self.coverage_injector and not self.coverage_injector.is_covered(node)):
return
if self.only_mutation and self.only_mutation.node != node and self.only_mutation.node not in node.children:
return
self.fix_lineno(node)
visitors = self.find_visitors(node)
if visitors:
for visitor in visitors:
try:
if self.sampler and not self.sampler.is_mutation_time():
raise MutationResign
if self.only_mutation and \
(self.only_mutation.node != node or self.only_mutation.visitor != visitor.__name__):
raise MutationResign
new_node = visitor(node)
self.visitor = visitor.__name__
self.current_node = node
self.fix_node_internals(node, new_node)
ast.fix_missing_locations(new_node)
yield new_node
except MutationResign:
pass
finally:
for new_node in self.generic_visit(node):
yield new_node
else:
for new_node in self.generic_visit(node):
yield new_node

def generic_visit(self, node):
for field, old_value in ast.iter_fields(node):
if isinstance(old_value, list):
generator = self.generic_visit_list(old_value)
elif isinstance(old_value, ast.AST):
generator = self.generic_visit_real_node(node, field, old_value)
else:
generator = []

for _ in generator:
yield node

def generic_visit_list(self, old_value):
old_values_copy = old_value[:]
for position, value in enumerate(old_values_copy):
if isinstance(value, ast.AST):
for new_value in self.visit(value):
if not isinstance(new_value, ast.AST):
old_value[position:position + 1] = new_value
elif value is None:
del old_value[position]
else:
old_value[position] = new_value

yield
old_value[:] = old_values_copy

def generic_visit_real_node(self, node, field, old_value):
for new_node in self.visit(old_value):
if new_node is None:
delattr(node, field)
else:
setattr(node, field, new_node)
yield
setattr(node, field, old_value)

def has_notmutate(self, node):
try:
for decorator in node.decorator_list:
if decorator.id == utils.notmutate.__name__:
return True
return False
except AttributeError:
return False

def fix_lineno(self, node):
if not hasattr(node, 'lineno') and getattr(node, 'parent', None) is not None and hasattr(node.parent, 'lineno'):
node.lineno = node.parent.lineno

def fix_node_internals(self, old_node, new_node):
if not hasattr(new_node, 'parent'):
new_node.children = old_node.children
new_node.parent = old_node.parent
if hasattr(old_node, 'marker'):
new_node.marker = old_node.marker

def find_visitors(self, node):
method_prefix = 'mutate_' + node.__class__.__name__
return self.getattrs_like(method_prefix)

def getattrs_like(ob, attr_like):
pattern = re.compile(attr_like + "($|(_\w+)+$)")
return [getattr(ob, attr) for attr in dir(ob) if pattern.match(attr)]

@classmethod
def name(cls):
return ''.join([c for c in cls.__name__ if str.isupper(c)])

@classmethod
def long_name(cls):
return ' '.join(map(str.lower, (re.split('([A-Z][a-z]*)', cls.__name__)[1::2])))


class AbstractUnaryOperatorDeletion(MutationOperator):
def mutate_UnaryOp(self, node):
if isinstance(node.op, self.get_operator_type()):
return node.operand
raise MutationResign()
50 changes: 50 additions & 0 deletions mutpy/operators/decorator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import ast

from mutpy.operators.base import MutationOperator, copy_node, MutationResign


class DecoratorDeletion(MutationOperator):
@copy_node
def mutate_FunctionDef(self, node):
if node.decorator_list:
node.decorator_list = []
return node
else:
raise MutationResign()

@classmethod
def name(cls):
return 'DDL'


class AbstractMethodDecoratorInsertionMutationOperator(MutationOperator):
@copy_node
def mutate_FunctionDef(self, node):
if not isinstance(node.parent, ast.ClassDef):
raise MutationResign()
for decorator in node.decorator_list:
if isinstance(decorator, ast.Call):
decorator_name = decorator.func.id
elif isinstance(decorator, ast.Attribute):
decorator_name = decorator.value.id
else:
decorator_name = decorator.id
if decorator_name == self.get_decorator_name():
raise MutationResign()

decorator = ast.Name(id=self.get_decorator_name(), ctx=ast.Load())
node.decorator_list.append(decorator)
return node

def get_decorator_name(self):
raise NotImplementedError()


class ClassmethodDecoratorInsertion(AbstractMethodDecoratorInsertionMutationOperator):
def get_decorator_name(self):
return 'classmethod'


class StaticmethodDecoratorInsertion(AbstractMethodDecoratorInsertionMutationOperator):
def get_decorator_name(self):
return 'staticmethod'
21 changes: 21 additions & 0 deletions mutpy/operators/exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import ast

from mutpy.operators.base import MutationOperator, MutationResign


class ExceptionHandlerDeletion(MutationOperator):
def mutate_ExceptHandler(self, node):
if node.body and isinstance(node.body[0], ast.Raise):
raise MutationResign()
return ast.ExceptHandler(type=node.type, name=node.name, body=[ast.Raise()])


class ExceptionSwallowing(MutationOperator):
def mutate_ExceptHandler(self, node):
if len(node.body) == 1 and isinstance(node.body[0], ast.Pass):
raise MutationResign()
return ast.ExceptHandler(type=node.type, name=node.name, body=[ast.Pass()])

@classmethod
def name(cls):
return 'EXS'
Loading

0 comments on commit 14999c5

Please sign in to comment.