Repository files navigation

AST based mutations

The following is a proof of concept that generates mutations (see Mutation Testing) based on the Abstract Syntax Tree of a given file. The idea is to use nikic/PHP-Parser to generate an AST for a given file. By using mutation operators that act on a Node of the AST we can create specific mutations for that Node.

An advantage of using nikic/PHP-Parser over a custom tokenizer is that the mutations will always generate valid PHP.

Creating mutations for a given source code can be done in 3 steps,

  1. Convert the source code to an AST
  2. Generate mutations based on mutation operators
  3. Apply the mutations one at a time and take action (e.g. store the mutated AST or run tests on the mutated AST)

The following example illustrates these steps,

usePhpParser\Lexer;
usePhpParser\ParserFactory;
usePhpParser\PrettyPrinter\Standard;
useRenamed\ApplyMutation;
useRenamed\GenerateMutations;
useRenamed\Mutation;
useRenamed\MutationOperator;
useRenamed\Mutations;
// The orginal source code for which we want to create mutations$source = "<?php echo 1 * 2 * 3;";
// Setup a parser to generate the AST$parser = (newParserFactory)->create(ParserFactory::PREFER_PHP7, newLexer);
$ast = $paser->parse($source);
$applyMutation = newApplyMutation($ast);
$generator = newGenerateMutations(
function(Mutation$mutation) use ($applyMutation) {
$applyMutation->apply($mutation, function($mutation, $ast) {
echo (newStandard)->prettyPrint($ast);
})
},
newMutations\Multiplication,
newMutations\DateTimeFromFormat// ... you can add as many operators as you want
);
// Generate and store the applied mutations$generator->generate($ast);

You may also use the Renamed\MutateSourceCode class to create mutations.

usePhpParser\PrettyPrinter\Standard;
useRenamed\MutateSourceCode;
useRenamed\Mutations;
$source = file_get_contents("SomeFile.php");
$mutate = newMutateSourceCode(
newMutations\Multiplication
);
// After a mutation has been applied, echo the pretty printed code$mutate->mutate($source, function ($mutation, $ast) {
echo (newStandard)->prettyPrint($ast);
});

Example

Suppose we have the following code,

<?phpecho1 * 2 * 3;";

Running the mutation tester with a ChangeMultiplicationToDivision operator will result in the following mutations

<?phpecho1 / 2 * 3;";
<?phpecho1 * 2 / 3;";

The code of the operator is quite simple (though it does require some understanding of nikic/PHP-Parser).

finalclass ChangeMultiplicationToDivision implements MutationOperator
{
/** * Replace (*) with (/) * @param Node $node */publicfunctionmutate(Node$node)
{
if (! $nodeinstanceofBinaryOp\Mul) {
return;
}
yieldnewBinaryOp\Div($node->left, $node->right, $node->getAttributes());
}
}

Anatomy of a mutation operator

A mutation operator implements the MutationOperator interface. Note that its only input is a Node and it does not have a return value, instead each operator will yield one or more nodes.

interface MutationOperator {
publicfunctionmutate(Node$node);
}

The advantage of yielding a mutation over returning (an array of) mutation(s) is that the latter gives us more opportunities to improve performance (see Synchronous vs asynchronous mutation testing). If you want to test your mutation operator be sure to extend the abstract Renamed\Tests\MutationOperatorTest testcase.

Issues / notes

Synchronous vs asynchronous mutation testing

The current implementation of GenerateMutations uses a Closure that is called after each mutation is generated. It is up to the user to decide what he or she wants to do with it. In general a Closure is given that calls the apply method on an ApplyMutation instance. Another approach would be to store the mutations and apply the mutations after all mutations have been generated. The later approach will probably be more memory intensive.

Performance when applying a mutation

Traverse twice, or clone during traversal

When applying a mutation on an ASt we have two options. We can traverse the AST twice, where in the first time we apply the mutation and in the second we reverse the mutation.

Another option would be to clone each node during traversal. This will probably be less efficient since it requires copying a whole AST.

✔ DONE Stop traversing after mutation has been applied

Once a mutation has been applied we can stop traversing the AST.

A simple fix could be to add a second visitor that is visited after the first one and returns NodeTraverserInterface::DONT_TRAVERSE_CHILDREN.

An even simpler fix could be returning NodeTraverserInterface::DONT_TRAVERSE_CHILDREN in enterNode of each mutation, however this will require each mutation to extend a base class.

I don’t like extending a mutation since that makes writing a mutation somewhat more complex.

In the end I would prefer to combine the traverser and visitor.

Combine traverser and visitors

Currently we require both a AST traverser and an NodeVisitor for both generating and applying a mutation. Since in both cases we only need 1 visitor it does not make much sense to have a traverser that can have multiple visitors. Moreover applying and generating mutations could probably use different traversers since applying won’t have to traverse the whole AST.

Higher order mutants

Higher order mutants are mutants that have been created by more than one mutation. It would be nice to have support for higher order mutations, and I don’t think it will require a lot of effort to implement this. But higher order mutations will result in a lot more mutations, making the overall process slower. I should probably read some research papers about HOMs.

About

A proof of concept which generates mutations based on an abstract syntax tree

Resources

Stars

15 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

AST based mutations

The following is a proof of concept that generates mutations (see Mutation Testing) based on the Abstract Syntax Tree of a given file. The idea is to use nikic/PHP-Parser to generate an AST for a given file. By using mutation operators that act on a Node of the AST we can create specific mutations for that Node.

An advantage of using nikic/PHP-Parser over a custom tokenizer is that the mutations will always generate valid PHP.

Creating mutations for a given source code can be done in 3 steps,

  1. Convert the source code to an AST
  2. Generate mutations based on mutation operators
  3. Apply the mutations one at a time and take action (e.g. store the mutated AST or run tests on the mutated AST)

The following example illustrates these steps,

usePhpParser\Lexer;
usePhpParser\ParserFactory;
usePhpParser\PrettyPrinter\Standard;
useRenamed\ApplyMutation;
useRenamed\GenerateMutations;
useRenamed\Mutation;
useRenamed\MutationOperator;
useRenamed\Mutations;
// The orginal source code for which we want to create mutations$source = "<?php echo 1 * 2 * 3;";
// Setup a parser to generate the AST$parser = (newParserFactory)->create(ParserFactory::PREFER_PHP7, newLexer);
$ast = $paser->parse($source);
$applyMutation = newApplyMutation($ast);
$generator = newGenerateMutations(
function(Mutation$mutation) use ($applyMutation) {
$applyMutation->apply($mutation, function($mutation, $ast) {
echo (newStandard)->prettyPrint($ast);
})
},
newMutations\Multiplication,
newMutations\DateTimeFromFormat// ... you can add as many operators as you want
);
// Generate and store the applied mutations$generator->generate($ast);

You may also use the Renamed\MutateSourceCode class to create mutations.

usePhpParser\PrettyPrinter\Standard;
useRenamed\MutateSourceCode;
useRenamed\Mutations;
$source = file_get_contents("SomeFile.php");
$mutate = newMutateSourceCode(
newMutations\Multiplication
);
// After a mutation has been applied, echo the pretty printed code$mutate->mutate($source, function ($mutation, $ast) {
echo (newStandard)->prettyPrint($ast);
});

Example

Suppose we have the following code,

<?phpecho1 * 2 * 3;";

Running the mutation tester with a ChangeMultiplicationToDivision operator will result in the following mutations

<?phpecho1 / 2 * 3;";
<?phpecho1 * 2 / 3;";

The code of the operator is quite simple (though it does require some understanding of nikic/PHP-Parser).

finalclass ChangeMultiplicationToDivision implements MutationOperator
{
/** * Replace (*) with (/) * @param Node $node */publicfunctionmutate(Node$node)
{
if (! $nodeinstanceofBinaryOp\Mul) {
return;
}
yieldnewBinaryOp\Div($node->left, $node->right, $node->getAttributes());
}
}

Anatomy of a mutation operator

A mutation operator implements the MutationOperator interface. Note that its only input is a Node and it does not have a return value, instead each operator will yield one or more nodes.

interface MutationOperator {
publicfunctionmutate(Node$node);
}

The advantage of yielding a mutation over returning (an array of) mutation(s) is that the latter gives us more opportunities to improve performance (see Synchronous vs asynchronous mutation testing). If you want to test your mutation operator be sure to extend the abstract Renamed\Tests\MutationOperatorTest testcase.

Issues / notes

Synchronous vs asynchronous mutation testing

The current implementation of GenerateMutations uses a Closure that is called after each mutation is generated. It is up to the user to decide what he or she wants to do with it. In general a Closure is given that calls the apply method on an ApplyMutation instance. Another approach would be to store the mutations and apply the mutations after all mutations have been generated. The later approach will probably be more memory intensive.

Performance when applying a mutation

Traverse twice, or clone during traversal

When applying a mutation on an ASt we have two options. We can traverse the AST twice, where in the first time we apply the mutation and in the second we reverse the mutation.

Another option would be to clone each node during traversal. This will probably be less efficient since it requires copying a whole AST.

✔ DONE Stop traversing after mutation has been applied

Once a mutation has been applied we can stop traversing the AST.

A simple fix could be to add a second visitor that is visited after the first one and returns NodeTraverserInterface::DONT_TRAVERSE_CHILDREN.

An even simpler fix could be returning NodeTraverserInterface::DONT_TRAVERSE_CHILDREN in enterNode of each mutation, however this will require each mutation to extend a base class.

I don’t like extending a mutation since that makes writing a mutation somewhat more complex.

In the end I would prefer to combine the traverser and visitor.

Combine traverser and visitors

Currently we require both a AST traverser and an NodeVisitor for both generating and applying a mutation. Since in both cases we only need 1 visitor it does not make much sense to have a traverser that can have multiple visitors. Moreover applying and generating mutations could probably use different traversers since applying won’t have to traverse the whole AST.

Higher order mutants

Higher order mutants are mutants that have been created by more than one mutation. It would be nice to have support for higher order mutations, and I don’t think it will require a lot of effort to implement this. But higher order mutations will result in a lot more mutations, making the overall process slower. I should probably read some research papers about HOMs.

About

A proof of concept which generates mutations based on an abstract syntax tree

Resources

Stars

15 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

AST based mutations

The following is a proof of concept that generates mutations (see Mutation Testing) based on the Abstract Syntax Tree of a given file. The idea is to use nikic/PHP-Parser to generate an AST for a given file. By using mutation operators that act on a Node of the AST we can create specific mutations for that Node.

An advantage of using nikic/PHP-Parser over a custom tokenizer is that the mutations will always generate valid PHP.

Creating mutations for a given source code can be done in 3 steps,

  1. Convert the source code to an AST
  2. Generate mutations based on mutation operators
  3. Apply the mutations one at a time and take action (e.g. store the mutated AST or run tests on the mutated AST)

The following example illustrates these steps,

usePhpParser\Lexer;
usePhpParser\ParserFactory;
usePhpParser\PrettyPrinter\Standard;
useRenamed\ApplyMutation;
useRenamed\GenerateMutations;
useRenamed\Mutation;
useRenamed\MutationOperator;
useRenamed\Mutations;
// The orginal source code for which we want to create mutations$source = "<?php echo 1 * 2 * 3;";
// Setup a parser to generate the AST$parser = (newParserFactory)->create(ParserFactory::PREFER_PHP7, newLexer);
$ast = $paser->parse($source);
$applyMutation = newApplyMutation($ast);
$generator = newGenerateMutations(
function(Mutation$mutation) use ($applyMutation) {
$applyMutation->apply($mutation, function($mutation, $ast) {
echo (newStandard)->prettyPrint($ast);
})
},
newMutations\Multiplication,
newMutations\DateTimeFromFormat// ... you can add as many operators as you want
);
// Generate and store the applied mutations$generator->generate($ast);

You may also use the Renamed\MutateSourceCode class to create mutations.

usePhpParser\PrettyPrinter\Standard;
useRenamed\MutateSourceCode;
useRenamed\Mutations;
$source = file_get_contents("SomeFile.php");
$mutate = newMutateSourceCode(
newMutations\Multiplication
);
// After a mutation has been applied, echo the pretty printed code$mutate->mutate($source, function ($mutation, $ast) {
echo (newStandard)->prettyPrint($ast);
});

Example

Suppose we have the following code,

<?phpecho1 * 2 * 3;";

Running the mutation tester with a ChangeMultiplicationToDivision operator will result in the following mutations

<?phpecho1 / 2 * 3;";
<?phpecho1 * 2 / 3;";

The code of the operator is quite simple (though it does require some understanding of nikic/PHP-Parser).

finalclass ChangeMultiplicationToDivision implements MutationOperator
{
/** * Replace (*) with (/) * @param Node $node */publicfunctionmutate(Node$node)
{
if (! $nodeinstanceofBinaryOp\Mul) {
return;
}
yieldnewBinaryOp\Div($node->left, $node->right, $node->getAttributes());
}
}

Anatomy of a mutation operator

A mutation operator implements the MutationOperator interface. Note that its only input is a Node and it does not have a return value, instead each operator will yield one or more nodes.

interface MutationOperator {
publicfunctionmutate(Node$node);
}

The advantage of yielding a mutation over returning (an array of) mutation(s) is that the latter gives us more opportunities to improve performance (see Synchronous vs asynchronous mutation testing). If you want to test your mutation operator be sure to extend the abstract Renamed\Tests\MutationOperatorTest testcase.

Issues / notes

Synchronous vs asynchronous mutation testing

The current implementation of GenerateMutations uses a Closure that is called after each mutation is generated. It is up to the user to decide what he or she wants to do with it. In general a Closure is given that calls the apply method on an ApplyMutation instance. Another approach would be to store the mutations and apply the mutations after all mutations have been generated. The later approach will probably be more memory intensive.

Performance when applying a mutation

Traverse twice, or clone during traversal

When applying a mutation on an ASt we have two options. We can traverse the AST twice, where in the first time we apply the mutation and in the second we reverse the mutation.

Another option would be to clone each node during traversal. This will probably be less efficient since it requires copying a whole AST.

✔ DONE Stop traversing after mutation has been applied

Once a mutation has been applied we can stop traversing the AST.

A simple fix could be to add a second visitor that is visited after the first one and returns NodeTraverserInterface::DONT_TRAVERSE_CHILDREN.

An even simpler fix could be returning NodeTraverserInterface::DONT_TRAVERSE_CHILDREN in enterNode of each mutation, however this will require each mutation to extend a base class.

I don’t like extending a mutation since that makes writing a mutation somewhat more complex.

In the end I would prefer to combine the traverser and visitor.

Combine traverser and visitors

Currently we require both a AST traverser and an NodeVisitor for both generating and applying a mutation. Since in both cases we only need 1 visitor it does not make much sense to have a traverser that can have multiple visitors. Moreover applying and generating mutations could probably use different traversers since applying won’t have to traverse the whole AST.

Higher order mutants

Higher order mutants are mutants that have been created by more than one mutation. It would be nice to have support for higher order mutations, and I don’t think it will require a lot of effort to implement this. But higher order mutations will result in a lot more mutations, making the overall process slower. I should probably read some research papers about HOMs.

About

A proof of concept which generates mutations based on an abstract syntax tree

Resources

Stars

15 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

AST based mutations

The following is a proof of concept that generates mutations (see Mutation Testing) based on the Abstract Syntax Tree of a given file. The idea is to use nikic/PHP-Parser to generate an AST for a given file. By using mutation operators that act on a Node of the AST we can create specific mutations for that Node.

An advantage of using nikic/PHP-Parser over a custom tokenizer is that the mutations will always generate valid PHP.

Creating mutations for a given source code can be done in 3 steps,

  1. Convert the source code to an AST
  2. Generate mutations based on mutation operators
  3. Apply the mutations one at a time and take action (e.g. store the mutated AST or run tests on the mutated AST)

The following example illustrates these steps,

usePhpParser\Lexer;
usePhpParser\ParserFactory;
usePhpParser\PrettyPrinter\Standard;
useRenamed\ApplyMutation;
useRenamed\GenerateMutations;
useRenamed\Mutation;
useRenamed\MutationOperator;
useRenamed\Mutations;
// The orginal source code for which we want to create mutations$source = "<?php echo 1 * 2 * 3;";
// Setup a parser to generate the AST$parser = (newParserFactory)->create(ParserFactory::PREFER_PHP7, newLexer);
$ast = $paser->parse($source);
$applyMutation = newApplyMutation($ast);
$generator = newGenerateMutations(
function(Mutation$mutation) use ($applyMutation) {
$applyMutation->apply($mutation, function($mutation, $ast) {
echo (newStandard)->prettyPrint($ast);
})
},
newMutations\Multiplication,
newMutations\DateTimeFromFormat// ... you can add as many operators as you want
);
// Generate and store the applied mutations$generator->generate($ast);

You may also use the Renamed\MutateSourceCode class to create mutations.

usePhpParser\PrettyPrinter\Standard;
useRenamed\MutateSourceCode;
useRenamed\Mutations;
$source = file_get_contents("SomeFile.php");
$mutate = newMutateSourceCode(
newMutations\Multiplication
);
// After a mutation has been applied, echo the pretty printed code$mutate->mutate($source, function ($mutation, $ast) {
echo (newStandard)->prettyPrint($ast);
});

Example

Suppose we have the following code,

<?phpecho1 * 2 * 3;";

Running the mutation tester with a ChangeMultiplicationToDivision operator will result in the following mutations

<?phpecho1 / 2 * 3;";
<?phpecho1 * 2 / 3;";

The code of the operator is quite simple (though it does require some understanding of nikic/PHP-Parser).

finalclass ChangeMultiplicationToDivision implements MutationOperator
{
/** * Replace (*) with (/) * @param Node $node */publicfunctionmutate(Node$node)
{
if (! $nodeinstanceofBinaryOp\Mul) {
return;
}
yieldnewBinaryOp\Div($node->left, $node->right, $node->getAttributes());
}
}

Anatomy of a mutation operator

A mutation operator implements the MutationOperator interface. Note that its only input is a Node and it does not have a return value, instead each operator will yield one or more nodes.

interface MutationOperator {
publicfunctionmutate(Node$node);
}

The advantage of yielding a mutation over returning (an array of) mutation(s) is that the latter gives us more opportunities to improve performance (see Synchronous vs asynchronous mutation testing). If you want to test your mutation operator be sure to extend the abstract Renamed\Tests\MutationOperatorTest testcase.

Issues / notes

Synchronous vs asynchronous mutation testing

The current implementation of GenerateMutations uses a Closure that is called after each mutation is generated. It is up to the user to decide what he or she wants to do with it. In general a Closure is given that calls the apply method on an ApplyMutation instance. Another approach would be to store the mutations and apply the mutations after all mutations have been generated. The later approach will probably be more memory intensive.

Performance when applying a mutation

Traverse twice, or clone during traversal

When applying a mutation on an ASt we have two options. We can traverse the AST twice, where in the first time we apply the mutation and in the second we reverse the mutation.

Another option would be to clone each node during traversal. This will probably be less efficient since it requires copying a whole AST.

✔ DONE Stop traversing after mutation has been applied

Once a mutation has been applied we can stop traversing the AST.

A simple fix could be to add a second visitor that is visited after the first one and returns NodeTraverserInterface::DONT_TRAVERSE_CHILDREN.

An even simpler fix could be returning NodeTraverserInterface::DONT_TRAVERSE_CHILDREN in enterNode of each mutation, however this will require each mutation to extend a base class.

I don’t like extending a mutation since that makes writing a mutation somewhat more complex.

In the end I would prefer to combine the traverser and visitor.

Combine traverser and visitors

Currently we require both a AST traverser and an NodeVisitor for both generating and applying a mutation. Since in both cases we only need 1 visitor it does not make much sense to have a traverser that can have multiple visitors. Moreover applying and generating mutations could probably use different traversers since applying won’t have to traverse the whole AST.

Higher order mutants

Higher order mutants are mutants that have been created by more than one mutation. It would be nice to have support for higher order mutations, and I don’t think it will require a lot of effort to implement this. But higher order mutations will result in a lot more mutations, making the overall process slower. I should probably read some research papers about HOMs.

About

A proof of concept which generates mutations based on an abstract syntax tree

Resources

Stars

15 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

AST based mutations

The following is a proof of concept that generates mutations (see Mutation Testing) based on the Abstract Syntax Tree of a given file. The idea is to use nikic/PHP-Parser to generate an AST for a given file. By using mutation operators that act on a Node of the AST we can create specific mutations for that Node.

An advantage of using nikic/PHP-Parser over a custom tokenizer is that the mutations will always generate valid PHP.

Creating mutations for a given source code can be done in 3 steps,

  1. Convert the source code to an AST
  2. Generate mutations based on mutation operators
  3. Apply the mutations one at a time and take action (e.g. store the mutated AST or run tests on the mutated AST)

The following example illustrates these steps,

usePhpParser\Lexer;
usePhpParser\ParserFactory;
usePhpParser\PrettyPrinter\Standard;
useRenamed\ApplyMutation;
useRenamed\GenerateMutations;
useRenamed\Mutation;
useRenamed\MutationOperator;
useRenamed\Mutations;
// The orginal source code for which we want to create mutations$source = "<?php echo 1 * 2 * 3;";
// Setup a parser to generate the AST$parser = (newParserFactory)->create(ParserFactory::PREFER_PHP7, newLexer);
$ast = $paser->parse($source);
$applyMutation = newApplyMutation($ast);
$generator = newGenerateMutations(
function(Mutation$mutation) use ($applyMutation) {
$applyMutation->apply($mutation, function($mutation, $ast) {
echo (newStandard)->prettyPrint($ast);
})
},
newMutations\Multiplication,
newMutations\DateTimeFromFormat// ... you can add as many operators as you want
);
// Generate and store the applied mutations$generator->generate($ast);

You may also use the Renamed\MutateSourceCode class to create mutations.

usePhpParser\PrettyPrinter\Standard;
useRenamed\MutateSourceCode;
useRenamed\Mutations;
$source = file_get_contents("SomeFile.php");
$mutate = newMutateSourceCode(
newMutations\Multiplication
);
// After a mutation has been applied, echo the pretty printed code$mutate->mutate($source, function ($mutation, $ast) {
echo (newStandard)->prettyPrint($ast);
});

Example

Suppose we have the following code,

<?phpecho1 * 2 * 3;";

Running the mutation tester with a ChangeMultiplicationToDivision operator will result in the following mutations

<?phpecho1 / 2 * 3;";
<?phpecho1 * 2 / 3;";

The code of the operator is quite simple (though it does require some understanding of nikic/PHP-Parser).

finalclass ChangeMultiplicationToDivision implements MutationOperator
{
/** * Replace (*) with (/) * @param Node $node */publicfunctionmutate(Node$node)
{
if (! $nodeinstanceofBinaryOp\Mul) {
return;
}
yieldnewBinaryOp\Div($node->left, $node->right, $node->getAttributes());
}
}

Anatomy of a mutation operator

A mutation operator implements the MutationOperator interface. Note that its only input is a Node and it does not have a return value, instead each operator will yield one or more nodes.

interface MutationOperator {
publicfunctionmutate(Node$node);
}

The advantage of yielding a mutation over returning (an array of) mutation(s) is that the latter gives us more opportunities to improve performance (see Synchronous vs asynchronous mutation testing). If you want to test your mutation operator be sure to extend the abstract Renamed\Tests\MutationOperatorTest testcase.

Issues / notes

Synchronous vs asynchronous mutation testing

The current implementation of GenerateMutations uses a Closure that is called after each mutation is generated. It is up to the user to decide what he or she wants to do with it. In general a Closure is given that calls the apply method on an ApplyMutation instance. Another approach would be to store the mutations and apply the mutations after all mutations have been generated. The later approach will probably be more memory intensive.

Performance when applying a mutation

Traverse twice, or clone during traversal

When applying a mutation on an ASt we have two options. We can traverse the AST twice, where in the first time we apply the mutation and in the second we reverse the mutation.

Another option would be to clone each node during traversal. This will probably be less efficient since it requires copying a whole AST.

✔ DONE Stop traversing after mutation has been applied

Once a mutation has been applied we can stop traversing the AST.

A simple fix could be to add a second visitor that is visited after the first one and returns NodeTraverserInterface::DONT_TRAVERSE_CHILDREN.

An even simpler fix could be returning NodeTraverserInterface::DONT_TRAVERSE_CHILDREN in enterNode of each mutation, however this will require each mutation to extend a base class.

I don’t like extending a mutation since that makes writing a mutation somewhat more complex.

In the end I would prefer to combine the traverser and visitor.

Combine traverser and visitors

Currently we require both a AST traverser and an NodeVisitor for both generating and applying a mutation. Since in both cases we only need 1 visitor it does not make much sense to have a traverser that can have multiple visitors. Moreover applying and generating mutations could probably use different traversers since applying won’t have to traverse the whole AST.

Higher order mutants

Higher order mutants are mutants that have been created by more than one mutation. It would be nice to have support for higher order mutations, and I don’t think it will require a lot of effort to implement this. But higher order mutations will result in a lot more mutations, making the overall process slower. I should probably read some research papers about HOMs.

About

A proof of concept which generates mutations based on an abstract syntax tree

Resources

Stars

15 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

AST based mutations

The following is a proof of concept that generates mutations (see Mutation Testing) based on the Abstract Syntax Tree of a given file. The idea is to use nikic/PHP-Parser to generate an AST for a given file. By using mutation operators that act on a Node of the AST we can create specific mutations for that Node.

An advantage of using nikic/PHP-Parser over a custom tokenizer is that the mutations will always generate valid PHP.

Creating mutations for a given source code can be done in 3 steps,

  1. Convert the source code to an AST
  2. Generate mutations based on mutation operators
  3. Apply the mutations one at a time and take action (e.g. store the mutated AST or run tests on the mutated AST)

The following example illustrates these steps,

usePhpParser\Lexer;
usePhpParser\ParserFactory;
usePhpParser\PrettyPrinter\Standard;
useRenamed\ApplyMutation;
useRenamed\GenerateMutations;
useRenamed\Mutation;
useRenamed\MutationOperator;
useRenamed\Mutations;
// The orginal source code for which we want to create mutations$source = "<?php echo 1 * 2 * 3;";
// Setup a parser to generate the AST$parser = (newParserFactory)->create(ParserFactory::PREFER_PHP7, newLexer);
$ast = $paser->parse($source);
$applyMutation = newApplyMutation($ast);
$generator = newGenerateMutations(
function(Mutation$mutation) use ($applyMutation) {
$applyMutation->apply($mutation, function($mutation, $ast) {
echo (newStandard)->prettyPrint($ast);
})
},
newMutations\Multiplication,
newMutations\DateTimeFromFormat// ... you can add as many operators as you want
);
// Generate and store the applied mutations$generator->generate($ast);

You may also use the Renamed\MutateSourceCode class to create mutations.

usePhpParser\PrettyPrinter\Standard;
useRenamed\MutateSourceCode;
useRenamed\Mutations;
$source = file_get_contents("SomeFile.php");
$mutate = newMutateSourceCode(
newMutations\Multiplication
);
// After a mutation has been applied, echo the pretty printed code$mutate->mutate($source, function ($mutation, $ast) {
echo (newStandard)->prettyPrint($ast);
});

Example

Suppose we have the following code,

<?phpecho1 * 2 * 3;";

Running the mutation tester with a ChangeMultiplicationToDivision operator will result in the following mutations

<?phpecho1 / 2 * 3;";
<?phpecho1 * 2 / 3;";

The code of the operator is quite simple (though it does require some understanding of nikic/PHP-Parser).

finalclass ChangeMultiplicationToDivision implements MutationOperator
{
/** * Replace (*) with (/) * @param Node $node */publicfunctionmutate(Node$node)
{
if (! $nodeinstanceofBinaryOp\Mul) {
return;
}
yieldnewBinaryOp\Div($node->left, $node->right, $node->getAttributes());
}
}

Anatomy of a mutation operator

A mutation operator implements the MutationOperator interface. Note that its only input is a Node and it does not have a return value, instead each operator will yield one or more nodes.

interface MutationOperator {
publicfunctionmutate(Node$node);
}

The advantage of yielding a mutation over returning (an array of) mutation(s) is that the latter gives us more opportunities to improve performance (see Synchronous vs asynchronous mutation testing). If you want to test your mutation operator be sure to extend the abstract Renamed\Tests\MutationOperatorTest testcase.

Issues / notes

Synchronous vs asynchronous mutation testing

The current implementation of GenerateMutations uses a Closure that is called after each mutation is generated. It is up to the user to decide what he or she wants to do with it. In general a Closure is given that calls the apply method on an ApplyMutation instance. Another approach would be to store the mutations and apply the mutations after all mutations have been generated. The later approach will probably be more memory intensive.

Performance when applying a mutation

Traverse twice, or clone during traversal

When applying a mutation on an ASt we have two options. We can traverse the AST twice, where in the first time we apply the mutation and in the second we reverse the mutation.

Another option would be to clone each node during traversal. This will probably be less efficient since it requires copying a whole AST.

✔ DONE Stop traversing after mutation has been applied

Once a mutation has been applied we can stop traversing the AST.

A simple fix could be to add a second visitor that is visited after the first one and returns NodeTraverserInterface::DONT_TRAVERSE_CHILDREN.

An even simpler fix could be returning NodeTraverserInterface::DONT_TRAVERSE_CHILDREN in enterNode of each mutation, however this will require each mutation to extend a base class.

I don’t like extending a mutation since that makes writing a mutation somewhat more complex.

In the end I would prefer to combine the traverser and visitor.

Combine traverser and visitors

Currently we require both a AST traverser and an NodeVisitor for both generating and applying a mutation. Since in both cases we only need 1 visitor it does not make much sense to have a traverser that can have multiple visitors. Moreover applying and generating mutations could probably use different traversers since applying won’t have to traverse the whole AST.

Higher order mutants

Higher order mutants are mutants that have been created by more than one mutation. It would be nice to have support for higher order mutations, and I don’t think it will require a lot of effort to implement this. But higher order mutations will result in a lot more mutations, making the overall process slower. I should probably read some research papers about HOMs.

About

A proof of concept which generates mutations based on an abstract syntax tree

Resources

Stars

15 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

AST based mutations

The following is a proof of concept that generates mutations (see Mutation Testing) based on the Abstract Syntax Tree of a given file. The idea is to use nikic/PHP-Parser to generate an AST for a given file. By using mutation operators that act on a Node of the AST we can create specific mutations for that Node.

An advantage of using nikic/PHP-Parser over a custom tokenizer is that the mutations will always generate valid PHP.

Creating mutations for a given source code can be done in 3 steps,

  1. Convert the source code to an AST
  2. Generate mutations based on mutation operators
  3. Apply the mutations one at a time and take action (e.g. store the mutated AST or run tests on the mutated AST)

The following example illustrates these steps,

usePhpParser\Lexer;
usePhpParser\ParserFactory;
usePhpParser\PrettyPrinter\Standard;
useRenamed\ApplyMutation;
useRenamed\GenerateMutations;
useRenamed\Mutation;
useRenamed\MutationOperator;
useRenamed\Mutations;
// The orginal source code for which we want to create mutations$source = "<?php echo 1 * 2 * 3;";
// Setup a parser to generate the AST$parser = (newParserFactory)->create(ParserFactory::PREFER_PHP7, newLexer);
$ast = $paser->parse($source);
$applyMutation = newApplyMutation($ast);
$generator = newGenerateMutations(
function(Mutation$mutation) use ($applyMutation) {
$applyMutation->apply($mutation, function($mutation, $ast) {
echo (newStandard)->prettyPrint($ast);
})
},
newMutations\Multiplication,
newMutations\DateTimeFromFormat// ... you can add as many operators as you want
);
// Generate and store the applied mutations$generator->generate($ast);

You may also use the Renamed\MutateSourceCode class to create mutations.

usePhpParser\PrettyPrinter\Standard;
useRenamed\MutateSourceCode;
useRenamed\Mutations;
$source = file_get_contents("SomeFile.php");
$mutate = newMutateSourceCode(
newMutations\Multiplication
);
// After a mutation has been applied, echo the pretty printed code$mutate->mutate($source, function ($mutation, $ast) {
echo (newStandard)->prettyPrint($ast);
});

Example

Suppose we have the following code,

<?phpecho1 * 2 * 3;";

Running the mutation tester with a ChangeMultiplicationToDivision operator will result in the following mutations

<?phpecho1 / 2 * 3;";
<?phpecho1 * 2 / 3;";

The code of the operator is quite simple (though it does require some understanding of nikic/PHP-Parser).

finalclass ChangeMultiplicationToDivision implements MutationOperator
{
/** * Replace (*) with (/) * @param Node $node */publicfunctionmutate(Node$node)
{
if (! $nodeinstanceofBinaryOp\Mul) {
return;
}
yieldnewBinaryOp\Div($node->left, $node->right, $node->getAttributes());
}
}

Anatomy of a mutation operator

A mutation operator implements the MutationOperator interface. Note that its only input is a Node and it does not have a return value, instead each operator will yield one or more nodes.

interface MutationOperator {
publicfunctionmutate(Node$node);
}

The advantage of yielding a mutation over returning (an array of) mutation(s) is that the latter gives us more opportunities to improve performance (see Synchronous vs asynchronous mutation testing). If you want to test your mutation operator be sure to extend the abstract Renamed\Tests\MutationOperatorTest testcase.

Issues / notes

Synchronous vs asynchronous mutation testing

The current implementation of GenerateMutations uses a Closure that is called after each mutation is generated. It is up to the user to decide what he or she wants to do with it. In general a Closure is given that calls the apply method on an ApplyMutation instance. Another approach would be to store the mutations and apply the mutations after all mutations have been generated. The later approach will probably be more memory intensive.

Performance when applying a mutation

Traverse twice, or clone during traversal

When applying a mutation on an ASt we have two options. We can traverse the AST twice, where in the first time we apply the mutation and in the second we reverse the mutation.

Another option would be to clone each node during traversal. This will probably be less efficient since it requires copying a whole AST.

✔ DONE Stop traversing after mutation has been applied

Once a mutation has been applied we can stop traversing the AST.

A simple fix could be to add a second visitor that is visited after the first one and returns NodeTraverserInterface::DONT_TRAVERSE_CHILDREN.

An even simpler fix could be returning NodeTraverserInterface::DONT_TRAVERSE_CHILDREN in enterNode of each mutation, however this will require each mutation to extend a base class.

I don’t like extending a mutation since that makes writing a mutation somewhat more complex.

In the end I would prefer to combine the traverser and visitor.

Combine traverser and visitors

Currently we require both a AST traverser and an NodeVisitor for both generating and applying a mutation. Since in both cases we only need 1 visitor it does not make much sense to have a traverser that can have multiple visitors. Moreover applying and generating mutations could probably use different traversers since applying won’t have to traverse the whole AST.

Higher order mutants

Higher order mutants are mutants that have been created by more than one mutation. It would be nice to have support for higher order mutations, and I don’t think it will require a lot of effort to implement this. But higher order mutations will result in a lot more mutations, making the overall process slower. I should probably read some research papers about HOMs.

About

A proof of concept which generates mutations based on an abstract syntax tree

Resources

Stars

15 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

AST based mutations

The following is a proof of concept that generates mutations (see Mutation Testing) based on the Abstract Syntax Tree of a given file. The idea is to use nikic/PHP-Parser to generate an AST for a given file. By using mutation operators that act on a Node of the AST we can create specific mutations for that Node.

An advantage of using nikic/PHP-Parser over a custom tokenizer is that the mutations will always generate valid PHP.

Creating mutations for a given source code can be done in 3 steps,

  1. Convert the source code to an AST
  2. Generate mutations based on mutation operators
  3. Apply the mutations one at a time and take action (e.g. store the mutated AST or run tests on the mutated AST)

The following example illustrates these steps,

usePhpParser\Lexer;
usePhpParser\ParserFactory;
usePhpParser\PrettyPrinter\Standard;
useRenamed\ApplyMutation;
useRenamed\GenerateMutations;
useRenamed\Mutation;
useRenamed\MutationOperator;
useRenamed\Mutations;
// The orginal source code for which we want to create mutations$source = "<?php echo 1 * 2 * 3;";
// Setup a parser to generate the AST$parser = (newParserFactory)->create(ParserFactory::PREFER_PHP7, newLexer);
$ast = $paser->parse($source);
$applyMutation = newApplyMutation($ast);
$generator = newGenerateMutations(
function(Mutation$mutation) use ($applyMutation) {
$applyMutation->apply($mutation, function($mutation, $ast) {
echo (newStandard)->prettyPrint($ast);
})
},
newMutations\Multiplication,
newMutations\DateTimeFromFormat// ... you can add as many operators as you want
);
// Generate and store the applied mutations$generator->generate($ast);

You may also use the Renamed\MutateSourceCode class to create mutations.

usePhpParser\PrettyPrinter\Standard;
useRenamed\MutateSourceCode;
useRenamed\Mutations;
$source = file_get_contents("SomeFile.php");
$mutate = newMutateSourceCode(
newMutations\Multiplication
);
// After a mutation has been applied, echo the pretty printed code$mutate->mutate($source, function ($mutation, $ast) {
echo (newStandard)->prettyPrint($ast);
});

Example

Suppose we have the following code,

<?phpecho1 * 2 * 3;";

Running the mutation tester with a ChangeMultiplicationToDivision operator will result in the following mutations

<?phpecho1 / 2 * 3;";
<?phpecho1 * 2 / 3;";

The code of the operator is quite simple (though it does require some understanding of nikic/PHP-Parser).

finalclass ChangeMultiplicationToDivision implements MutationOperator
{
/** * Replace (*) with (/) * @param Node $node */publicfunctionmutate(Node$node)
{
if (! $nodeinstanceofBinaryOp\Mul) {
return;
}
yieldnewBinaryOp\Div($node->left, $node->right, $node->getAttributes());
}
}

Anatomy of a mutation operator

A mutation operator implements the MutationOperator interface. Note that its only input is a Node and it does not have a return value, instead each operator will yield one or more nodes.

interface MutationOperator {
publicfunctionmutate(Node$node);
}

The advantage of yielding a mutation over returning (an array of) mutation(s) is that the latter gives us more opportunities to improve performance (see Synchronous vs asynchronous mutation testing). If you want to test your mutation operator be sure to extend the abstract Renamed\Tests\MutationOperatorTest testcase.

Issues / notes

Synchronous vs asynchronous mutation testing

The current implementation of GenerateMutations uses a Closure that is called after each mutation is generated. It is up to the user to decide what he or she wants to do with it. In general a Closure is given that calls the apply method on an ApplyMutation instance. Another approach would be to store the mutations and apply the mutations after all mutations have been generated. The later approach will probably be more memory intensive.

Performance when applying a mutation

Traverse twice, or clone during traversal

When applying a mutation on an ASt we have two options. We can traverse the AST twice, where in the first time we apply the mutation and in the second we reverse the mutation.

Another option would be to clone each node during traversal. This will probably be less efficient since it requires copying a whole AST.

✔ DONE Stop traversing after mutation has been applied

Once a mutation has been applied we can stop traversing the AST.

A simple fix could be to add a second visitor that is visited after the first one and returns NodeTraverserInterface::DONT_TRAVERSE_CHILDREN.

An even simpler fix could be returning NodeTraverserInterface::DONT_TRAVERSE_CHILDREN in enterNode of each mutation, however this will require each mutation to extend a base class.

I don’t like extending a mutation since that makes writing a mutation somewhat more complex.

In the end I would prefer to combine the traverser and visitor.

Combine traverser and visitors

Currently we require both a AST traverser and an NodeVisitor for both generating and applying a mutation. Since in both cases we only need 1 visitor it does not make much sense to have a traverser that can have multiple visitors. Moreover applying and generating mutations could probably use different traversers since applying won’t have to traverse the whole AST.

Higher order mutants

Higher order mutants are mutants that have been created by more than one mutation. It would be nice to have support for higher order mutations, and I don’t think it will require a lot of effort to implement this. But higher order mutations will result in a lot more mutations, making the overall process slower. I should probably read some research papers about HOMs.

About

A proof of concept which generates mutations based on an abstract syntax tree

Resources

Stars

15 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages