Skip to content

Instantly share code, notes, and snippets.

@Ladsgroup
Last active October 6, 2019 16:49
Show Gist options
  • Save Ladsgroup/bfb4f4b66384efb8b8d3efea338b6e6b to your computer and use it in GitHub Desktop.
Save Ladsgroup/bfb4f4b66384efb8b8d3efea338b6e6b to your computer and use it in GitHub Desktop.
phpunit4 killer
# License: MIT
# Run like: "python phpunit4_killer.py /var/lib/mediawiki/extensions/Wikibase/"
import os
import sys
import re
import subprocess
def find_files(path):
files = []
for r, d, f in os.walk(path):
for file_name in f:
if file_name.endswith('.php'):
files.append(os.path.join(r, file_name))
return files
def check_file(path):
with open(path, 'r', encoding="utf-8") as f:
content = f.read()
old_content = content
cases = re.findall(r'\n\t*?use \\?PHPUnit4And6Compat;', content)
literal = '\'(?:[^\'\\\\]|\\\\\')+\'|"(?:[^"\\\\]|\\\\")+"'
clname = '(?:[a-zA-Z\\\\]+::class|\$\w+|' + literal + ')'
content = re.sub(r'(\$this->)setExpected(Exception\(\s+' + clname + '\s+\);)', r'\1expect\2', content)
content = re.sub(r'^(\\t*)(\$this->)setExpectedException\(\s+(' + clname + '),\s+(' + clname + ')\s+\);', r'\1\2expectException( \3 );\n\1\$this->expectExceptionMessage( \4 );', content)
content = re.sub(r'(\$this->)get(Mock\(\s+' + clname + '\s+\))', r'\1create\2', content)
content = re.sub(r'(\$this->)getMock\(\s+(' + clname + '),\s+\[\],\s+\[\],\s+[\'"]{2},\s+false\s+\)', r'\1createMock( \2 )', content)
content = re.sub(r'^(\\t*)(\$this->)getMock\(\s+(' + clname + '),\s+(\[(?:\s*' + literal + ',?)+\])\s+\);', r'\1\2createMock( \3 )\n\1\t->setMethods( \4 )\n\1\t->getMock();', content)
if '$this->getMock(' in content or '$this->setExpectedException(' in content:
print('Nooooo /o\\')
with open(path, 'w', encoding="utf-8") as f:
print(path)
f.write(content)
return
for case in cases:
content = content.replace(case, '')
content = content.replace(', PHPUnit4And6Compat, ', ', ')
content = content.replace('use PHPUnit4And6Compat, ', 'use ')
content = content.replace(', PHPUnit4And6Compat;', ';')
if content == old_content:
return
with open(path, 'w', encoding="utf-8") as f:
print(path)
f.write(content)
for f in find_files(sys.argv[1]):
check_file(f)
subprocess.run(["composer", "update"], cwd=sys.argv[1], shell=True)
subprocess.run(["composer", "fix"], cwd=sys.argv[1], shell=True)
@Daimona
Copy link

Daimona commented Oct 6, 2019

Some other improvements (includes the ones suggested above).

Before line 21, add:

literal = '\'(?:[^\'\\\\]|\\\\\')+\'|"(?:[^"\\\\]|\\\\")+"'
clname = '(?:[a-zA-Z\\\\]+::class|\$\w+|' + literal + ')'

Line21 -->content = re.sub(r'(\$this->)setExpected(Exception\(\s+' + clname + '\s+\);)', r'\1expect\2', content)

Line22 -->content = re.sub(r'^(\\t*)(\$this->)setExpectedException\(\s+(' + clname + '),\s+(' + clname + ')\s+\);', r'\1\2expectException( \3 );\n\1\$this->expectExceptionMessage( \4 );', content)

Line23 -->content = re.sub(r'(\$this->)get(Mock\(\s+' + clname + '\s+\))', r'\1create\2', content)

And after line 23:

content = re.sub(r'(\$this->)get(Mock\(\s+' + clname + '),\s+\[\],\s+\[\],\s+[\'"]{2},\s+false\s+\)', r'\1create\2 )', content)
content = re.sub(r'^(\\t*)(\$this->)get(Mock\(\s+' + clname + '),\s+(\[(?:\s*' + literal + ',?)+\])\s+\);', r'\1\2create\3 )\n\1\t->setMethods( \4 )\n\1\t->getMock();', content)

These will still leave some edge cases (e.g.: func call, arrays, class members, string concat), but we can fix them manually. We should also replace literal class names with ::class, but I didn't add it because you'd also have to use the classes etc.

Note: Untested

@Ladsgroup
Copy link
Author

Applied your suggestions, regarding removing phpunit trait, I will explain it further in the ticket.

@Daimona
Copy link

Daimona commented Oct 6, 2019

Cool, thanks :) I'm gonna (ab)use it then

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment