-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Auto merge of #19417 - dsandeephegde:master, r=asajeffrey,jdm
Mutation Test: with more mutation strategies <!-- Please describe your changes on the following line: --> 1. Added following mutation strategies: - If True (make if always true) - If False(make if always false) - Modify Comparision (<= to <, >= to >) - Plus To Minus - Minus To Plus - Changing Atomic Strings (make string constant empty) - Duplicate Line - Delete If Block 2. Randomized the test order. 3. Introduced logging instead of print. 4. Added retry mechanism when mutation cannot be performed on a file by a strategy. --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes #18529 (github issue number if applicable). <!-- Either: --> - [X] These changes do not require tests because it is a python script to run mutation test and does not change any behavior. <!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.--> <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/19417) <!-- Reviewable:end -->
- Loading branch information
Showing
4 changed files
with
262 additions
and
65 deletions.
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
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,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) |
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