Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Execute output code in integration tests #68

Merged
merged 5 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
test:
name: Run pytest
runs-on: ubuntu-20.04
timeout-minutes: 10
timeout-minutes: 15
strategy:
matrix:
python-version: ['3.9', '3.10', '3.11']
Expand Down
4 changes: 3 additions & 1 deletion integration_tests/base_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from codemodder import __VERSION__
from codemodder import registry

from tests.validations import execute_code

SAMPLES_DIR = "tests/samples"

Expand Down Expand Up @@ -51,6 +51,7 @@ class BaseIntegrationTest(DependencyTestMixin, CleanRepoMixin):
num_changes = 1
_lines = []
num_changed_files = 1
allowed_exceptions = ()

@classmethod
def setup_class(cls):
Expand Down Expand Up @@ -121,6 +122,7 @@ def check_code_after(self):
with open(self.code_path, "r", encoding="utf-8") as f:
new_code = f.read()
assert new_code == self.expected_new_code
execute_code(path=self.code_path, allowed_exceptions=self.allowed_exceptions)

def test_file_rewritten(self):
"""
Expand Down
3 changes: 3 additions & 0 deletions integration_tests/test_harden_pyyaml.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import yaml
from core_codemods.harden_pyyaml import HardenPyyaml
from integration_tests.base_test import (
BaseIntegrationTest,
Expand All @@ -14,3 +15,5 @@ class TestHardenPyyaml(BaseIntegrationTest):
expected_diff = '--- \n+++ \n@@ -1,4 +1,4 @@\n import yaml\n \n data = b"!!python/object/apply:subprocess.Popen \\\\n- ls"\n-deserialized_data = yaml.load(data, Loader=yaml.Loader)\n+deserialized_data = yaml.load(data, yaml.SafeLoader)\n'
expected_line_change = "4"
change_description = HardenPyyaml.CHANGE_DESCRIPTION
# expected exception because the yaml.SafeLoader protects against unsafe code
allowed_exceptions = (yaml.constructor.ConstructorError,)
2 changes: 2 additions & 0 deletions integration_tests/test_limit_readline.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ class TestLimitReadline(BaseIntegrationTest):
expected_diff = '--- \n+++ \n@@ -1,2 +1,2 @@\n file = open("some_file.txt")\n-file.readline()\n+file.readline(5_000_000)\n'
expected_line_change = "2"
change_description = LimitReadline.CHANGE_DESCRIPTION
# expected because output code points to fake file
allowed_exceptions = (FileNotFoundError,)
1 change: 1 addition & 0 deletions integration_tests/test_lxml_safe_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ class TestLxmlSafeParsing(BaseIntegrationTest):
expected_line_change = "2"
num_changes = 2
change_description = LxmlSafeParsing.CHANGE_DESCRIPTION
allowed_exceptions = (OSError,)
33 changes: 17 additions & 16 deletions integration_tests/test_order_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,22 @@ class TestOrderImports(BaseIntegrationTest):
original_code, expected_new_code = original_and_expected_from_code_path(
code_path,
[
(1, "# comment b4\n"),
(2, "# comment b5\n"),
(3, "# comment b3\n"),
(4, "# comment b1\n"),
(5, "# comment b2\n"),
(6, "import b\n"),
(7, "import d\n"),
(8, "# comment a\n"),
(9, "from a import a1, a2\n"),
(10, "\n"),
(11, "a1\n"),
(12, "a2\n"),
(13, "b\n"),
(14, "c\n"),
(15, "d"),
(1, "# comment builtins4\n"),
(2, "# comment builtins5\n"),
(3, "# comment builtins3\n"),
(4, "# comment builtins1\n"),
(5, "# comment builtins2\n"),
(6, "import builtins\n"),
(7, "import collections\n"),
(8, "import datetime\n"),
(9, "# comment a\n"),
(10, "from abc import ABC, ABCMeta\n"),
(11, "\n"),
(12, "ABC\n"),
(13, "ABCMeta\n"),
(14, "builtins\n"),
(15, "collections\n"),
(16, ""),
(17, ""),
(18, ""),
(19, ""),
Expand All @@ -34,6 +35,6 @@ class TestOrderImports(BaseIntegrationTest):
],
)

expected_diff = "--- \n+++ \n@@ -1,19 +1,13 @@\n #!/bin/env python\n-from a import a2\n-\n+# comment b4\n+# comment b5\n+# comment b3\n # comment b1\n # comment b2\n import b\n-\n+import d\n # comment a\n-from a import a1\n-\n-# comment b3\n-import b, d\n-\n-# comment b4\n-# comment b5\n-import b\n+from a import a1, a2\n \n a1\n a2\n"
expected_diff = "--- \n+++ \n@@ -1,20 +1,14 @@\n #!/bin/env python\n-from abc import ABCMeta\n-\n+# comment builtins4\n+# comment builtins5\n+# comment builtins3\n # comment builtins1\n # comment builtins2\n import builtins\n-\n+import collections\n+import datetime\n # comment a\n-from abc import ABC\n-\n-# comment builtins3\n-import builtins, datetime\n-\n-# comment builtins4\n-# comment builtins5\n-import builtins\n-import collections\n+from abc import ABC, ABCMeta\n \n ABC\n ABCMeta\n"
expected_line_change = "2"
change_description = OrderImports.CHANGE_DESCRIPTION
4 changes: 2 additions & 2 deletions integration_tests/test_remove_unused_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ class TestRemoveUnusedImports(BaseIntegrationTest):
original_code, expected_new_code = original_and_expected_from_code_path(
code_path,
[
(1, """from b import c\n"""),
(1, """from builtins import complex\n"""),
],
)

expected_diff = "--- \n+++ \n@@ -1,5 +1,5 @@\n import a\n-from b import c, d\n+from b import c\n \n a\n c\n"
expected_diff = "--- \n+++ \n@@ -1,5 +1,5 @@\n import abc\n-from builtins import complex, dict\n+from builtins import complex\n \n abc\n complex\n"
expected_line_change = 2
change_description = RemoveUnusedImports.CHANGE_DESCRIPTION
9 changes: 6 additions & 3 deletions integration_tests/test_request_verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
BaseIntegrationTest,
original_and_expected_from_code_path,
)
from requests import exceptions


class TestRequestsVerify(BaseIntegrationTest):
Expand All @@ -11,14 +12,16 @@ class TestRequestsVerify(BaseIntegrationTest):
original_code, expected_new_code = original_and_expected_from_code_path(
code_path,
[
(2, """requests.get("www.google.com", verify=True)\n"""),
(2, """requests.get("https://www.google.com", verify=True)\n"""),
(
3,
"""requests.post("https/some-api/", json={"id": 1234, "price": 18}, verify=True)\n""",
"""requests.post("https://some-api/", json={"id": 1234, "price": 18}, verify=True)\n""",
),
],
)
expected_diff = '--- \n+++ \n@@ -1,5 +1,5 @@\n import requests\n \n-requests.get("www.google.com", verify=False)\n-requests.post("https/some-api/", json={"id": 1234, "price": 18}, verify=False)\n+requests.get("www.google.com", verify=True)\n+requests.post("https/some-api/", json={"id": 1234, "price": 18}, verify=True)\n var = "hello"\n'
expected_diff = '--- \n+++ \n@@ -1,5 +1,5 @@\n import requests\n \n-requests.get("https://www.google.com", verify=False)\n-requests.post("https://some-api/", json={"id": 1234, "price": 18}, verify=False)\n+requests.get("https://www.google.com", verify=True)\n+requests.post("https://some-api/", json={"id": 1234, "price": 18}, verify=True)\n var = "hello"\n'
expected_line_change = "3"
num_changes = 2
change_description = RequestsVerify.CHANGE_DESCRIPTION
# expected because when executing the output code it will make a request which fails, which is OK.
allowed_exceptions = (exceptions.ConnectionError,)
4 changes: 2 additions & 2 deletions integration_tests/test_url_sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ class TestUrlSandbox(BaseIntegrationTest):
code_path,
[
(0, """from security import safe_requests\n"""),
(2, """safe_requests.get("www.google.com")\n"""),
(2, """safe_requests.get("https://www.google.com")\n"""),
],
)

expected_diff = '--- \n+++ \n@@ -1,4 +1,4 @@\n-import requests\n+from security import safe_requests\n \n-requests.get("www.google.com")\n+safe_requests.get("www.google.com")\n var = "hello"\n'
expected_diff = '--- \n+++ \n@@ -1,4 +1,4 @@\n-import requests\n+from security import safe_requests\n \n-requests.get("https://www.google.com")\n+safe_requests.get("https://www.google.com")\n var = "hello"\n'
expected_line_change = "3"
change_description = UrlSandbox.CHANGE_DESCRIPTION
num_changed_files = 1
Expand Down
10 changes: 5 additions & 5 deletions integration_tests/test_use_walrus_if.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,22 @@ class TestUseWalrusIf(BaseIntegrationTest):
code_path = "tests/samples/use_walrus_if.py"
original_code, _ = original_and_expected_from_code_path(code_path, [])
expected_new_code = """
if (x := foo()) is not None:
if (x := sum([1, 2])) is not None:
print(x)

if y := bar():
if y := max([1, 2]):
print(y)

z = baz()
z = min([1, 2])
print(z)


def whatever():
if (b := biz()) == 10:
if (b := int("2")) == 10:
print(b)
""".lstrip()

expected_diff = "--- \n+++ \n@@ -1,9 +1,7 @@\n-x = foo()\n-if x is not None:\n+if (x := foo()) is not None:\n print(x)\n \n-y = bar()\n-if y:\n+if y := bar():\n print(y)\n \n z = baz()\n@@ -11,6 +9,5 @@\n \n \n def whatever():\n- b = biz()\n- if b == 10:\n+ if (b := biz()) == 10:\n print(b)\n"
expected_diff = '--- \n+++ \n@@ -1,9 +1,7 @@\n-x = sum([1, 2])\n-if x is not None:\n+if (x := sum([1, 2])) is not None:\n print(x)\n \n-y = max([1, 2])\n-if y:\n+if y := max([1, 2]):\n print(y)\n \n z = min([1, 2])\n@@ -11,6 +9,5 @@\n \n \n def whatever():\n- b = int("2")\n- if b == 10:\n+ if (b := int("2")) == 10:\n print(b)\n'

num_changes = 3
expected_line_change = 1
Expand Down
4 changes: 4 additions & 0 deletions requirements/test.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
coverage==7.3.*
GitPython<4
Jinja2~=3.1.2
lxml~=4.9.3
mock==5.1.*
pre-commit<4
Pyjwt~=2.8.0
pytest==7.4.*
pytest-cov~=4.1.0
pytest-mock~=3.11.1
pytest-xdist==3.*
security~=1.2.0
types-mock==5.1.*
2 changes: 1 addition & 1 deletion tests/samples/make_request.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import requests

requests.get("www.google.com")
requests.get("https://www.google.com")
var = "hello"
31 changes: 16 additions & 15 deletions tests/samples/unordered_imports.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
#!/bin/env python
from a import a2
from abc import ABCMeta

# comment b1
# comment b2
import b
# comment builtins1
# comment builtins2
import builtins

# comment a
from a import a1
from abc import ABC

# comment b3
import b, d
# comment builtins3
import builtins, datetime

# comment b4
# comment b5
import b
# comment builtins4
# comment builtins5
import builtins
import collections

a1
a2
b
c
d
ABC
ABCMeta
builtins
collections
datetime
8 changes: 4 additions & 4 deletions tests/samples/unused_imports.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import a
from b import c, d
import abc
from builtins import complex, dict

a
c
abc
complex
4 changes: 2 additions & 2 deletions tests/samples/unverified_request.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import requests

requests.get("www.google.com", verify=False)
requests.post("https/some-api/", json={"id": 1234, "price": 18}, verify=False)
requests.get("https://www.google.com", verify=False)
requests.post("https://some-api/", json={"id": 1234, "price": 18}, verify=False)
var = "hello"
8 changes: 4 additions & 4 deletions tests/samples/use_walrus_if.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
x = foo()
x = sum([1, 2])
if x is not None:
print(x)

y = bar()
y = max([1, 2])
if y:
print(y)

z = baz()
z = min([1, 2])
print(z)


def whatever():
b = biz()
b = int("2")
if b == 10:
print(b)
30 changes: 30 additions & 0 deletions tests/validations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import importlib.util
import tempfile


def execute_code(*, path=None, code=None, allowed_exceptions=None):
"""
Ensure that code written in `path` or in `code` str is executable.
"""
assert (path is None) != (
code is None
), "Must pass either path to code or code as a str."

if path:
_run_code(path, allowed_exceptions)
return
with tempfile.NamedTemporaryFile(suffix=".py", mode="w+t") as temp:
temp.write(code)
_run_code(temp.name, allowed_exceptions)


def _run_code(path, allowed_exceptions=None):
"""Execute the code in `path` in its own namespace."""
allowed_exceptions = allowed_exceptions or ()

spec = importlib.util.spec_from_file_location("output_code", path)
module = importlib.util.module_from_spec(spec)
try:
spec.loader.exec_module(module)
except allowed_exceptions:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is super cool.

pass