diff options
author | Sandeep Hegde <dsandeephegde@gmail.com> | 2017-11-08 22:59:12 -0500 |
---|---|---|
committer | Sandeep Hegde <dsandeephegde@gmail.com> | 2017-12-06 15:17:28 -0500 |
commit | 1de6ab16e2004419e4795617fafba2b5fa7855e9 (patch) | |
tree | 65dcd63969ef9844642f62016ed3354805e2189c /python/servo/mutation/mutator.py | |
parent | 9d602a7bb9f452fc45f9367a09fbf2fe52d3e20e (diff) | |
download | servo-1de6ab16e2004419e4795617fafba2b5fa7855e9.tar.gz servo-1de6ab16e2004419e4795617fafba2b5fa7855e9.zip |
Mutation Testing
Introduced strategy design pattern
Added Strategies:
if true and if false
Replace String literals
Remove if blocks which do not have else.
Modify Comparision Statement - changing <= to < and changing >= to >
Duplicating statements
Added plus to minus and minus to plus mutaiton strategy
Classifying build failures for mutant as unexpected error - Skipping test run for mutant with compilation failure
Added logger messages instead of print
Randomized the mutation test order
Try new strategy on failure to mutate on a file
Updated Readme - Adding mutation strategy and mutation test execution flow
Diffstat (limited to 'python/servo/mutation/mutator.py')
-rw-r--r-- | python/servo/mutation/mutator.py | 195 |
1 files changed, 195 insertions, 0 deletions
diff --git a/python/servo/mutation/mutator.py b/python/servo/mutation/mutator.py new file mode 100644 index 00000000000..fd5e4ae4a1b --- /dev/null +++ b/python/servo/mutation/mutator.py @@ -0,0 +1,195 @@ +# Copyright 2013 The Servo Project Developers. See the COPYRIGHT +# file at the top-level directory of this distribution. +# +# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +# option. This file may not be copied, modified, or distributed +# except according to those terms. + +import fileinput +import re +import random + + +def is_comment(line): + return re.search(r"\/\/.*", line) + + +def init_variables(if_blocks): + random_index = random.randint(0, len(if_blocks) - 1) + start_counter = 0 + end_counter = 0 + lines_to_delete = [] + line_to_mutate = if_blocks[random_index] + return random_index, start_counter, end_counter, lines_to_delete, line_to_mutate + + +def deleteStatements(file_name, line_numbers): + for line in fileinput.input(file_name, inplace=True): + if fileinput.lineno() not in line_numbers: + print line.rstrip() + + +class Strategy: + def __init__(self): + self._strategy_name = "" + self._replace_strategy = {} + + def mutate(self, file_name): + line_numbers = [] + for line in fileinput.input(file_name): + if not is_comment(line) and re.search(self._replace_strategy['regex'], line): + line_numbers.append(fileinput.lineno()) + if len(line_numbers) == 0: + return -1 + else: + mutation_line_number = line_numbers[random.randint(0, len(line_numbers) - 1)] + for line in fileinput.input(file_name, inplace=True): + if fileinput.lineno() == mutation_line_number: + line = re.sub(self._replace_strategy['regex'], self._replace_strategy['replaceString'], line) + print line.rstrip() + return mutation_line_number + + +class AndOr(Strategy): + def __init__(self): + Strategy.__init__(self) + logical_and = r"(?<=\s)&&(?=\s)" + self._replace_strategy = { + 'regex': logical_and, + 'replaceString': '||' + } + + +class IfTrue(Strategy): + def __init__(self): + Strategy.__init__(self) + if_condition = r"(?<=if\s)(.*)(?=\s\{)" + self._replace_strategy = { + 'regex': if_condition, + 'replaceString': 'true' + } + + +class IfFalse(Strategy): + def __init__(self): + Strategy.__init__(self) + if_condition = r"(?<=if\s)(.*)(?=\s\{)" + self._replace_strategy = { + 'regex': if_condition, + 'replaceString': 'false' + } + + +class ModifyComparision(Strategy): + def __init__(self): + Strategy.__init__(self) + less_than_equals = r"(?<=\s)(\<)\=(?=\s)" + greater_than_equals = r"(?<=\s)(\<)\=(?=\s)" + self._replace_strategy = { + 'regex': (less_than_equals + '|' + greater_than_equals), + 'replaceString': r"\1" + } + + +class MinusToPlus(Strategy): + def __init__(self): + Strategy.__init__(self) + arithmetic_minus = r"(?<=\s)\-(?=\s.+)" + minus_in_shorthand = r"(?<=\s)\-(?=\=)" + self._replace_strategy = { + 'regex': (arithmetic_minus + '|' + minus_in_shorthand), + 'replaceString': '+' + } + + +class PlusToMinus(Strategy): + def __init__(self): + Strategy.__init__(self) + arithmetic_plus = r"(?<=[^\"]\s)\+(?=\s[^A-Z\'?\":\{]+)" + plus_in_shorthand = r"(?<=\s)\+(?=\=)" + self._replace_strategy = { + 'regex': (arithmetic_plus + '|' + plus_in_shorthand), + 'replaceString': '-' + } + + +class AtomicString(Strategy): + def __init__(self): + Strategy.__init__(self) + string_literal = r"(?<=\").+(?=\")" + self._replace_strategy = { + 'regex': string_literal, + 'replaceString': ' ' + } + + +class DuplicateLine(Strategy): + def __init__(self): + Strategy.__init__(self) + self._strategy_name = "duplicate" + append_statement = r".+?append\(.+?\).*?;" + remove_statement = r".+?remove\(.*?\).*?;" + push_statement = r".+?push\(.+?\).*?;" + pop_statement = r".+?pop\(.+?\).*?;" + plus_equals_statement = r".+?\s\+\=\s.*" + minus_equals_statement = r".+?\s\-\=\s.*" + self._replace_strategy = { + 'regex': (append_statement + '|' + remove_statement + '|' + push_statement + + '|' + pop_statement + '|' + plus_equals_statement + '|' + minus_equals_statement), + 'replaceString': r"\g<0>\n\g<0>", + } + + +class DeleteIfBlock(Strategy): + def __init__(self): + Strategy.__init__(self) + self.if_block = r"^\s+if\s(.+)\s\{" + self.else_block = r"\selse(.+)\{" + + def mutate(self, file_name): + code_lines = [] + if_blocks = [] + for line in fileinput.input(file_name): + code_lines.append(line) + if re.search(self.if_block, line): + if_blocks.append(fileinput.lineno()) + if len(if_blocks) == 0: + return -1 + random_index, start_counter, end_counter, lines_to_delete, line_to_mutate = init_variables(if_blocks) + while line_to_mutate <= len(code_lines): + current_line = code_lines[line_to_mutate - 1] + next_line = code_lines[line_to_mutate] + if re.search(self.else_block, current_line) is not None \ + or re.search(self.else_block, next_line) is not None: + if_blocks.pop(random_index) + if len(if_blocks) == 0: + return -1 + else: + random_index, start_counter, end_counter, lines_to_delete, line_to_mutate = \ + init_variables(if_blocks) + continue + lines_to_delete.append(line_to_mutate) + for ch in current_line: + if ch == "{": + start_counter += 1 + elif ch == "}": + end_counter += 1 + if start_counter and start_counter == end_counter: + deleteStatements(file_name, lines_to_delete) + return lines_to_delete[0] + line_to_mutate += 1 + + +def get_strategies(): + return AndOr, IfTrue, IfFalse, ModifyComparision, PlusToMinus, MinusToPlus, \ + AtomicString, DuplicateLine, DeleteIfBlock + + +class Mutator: + def __init__(self, strategy): + self._strategy = strategy + + def mutate(self, file_name): + return self._strategy.mutate(file_name) |