Skip to content
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
4 changes: 4 additions & 0 deletions src/genie_python/testing_utils/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"""
This private module exists to allow testing utility code to be shared between genie_python's
unit tests, and the external system tests.
"""
63 changes: 63 additions & 0 deletions src/genie_python/testing_utils/script_checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import os
import tempfile
from types import TracebackType
from typing import IO

from genie_python.genie_script_checker import ScriptChecker


def write_to_temp_file(message: list[str], suffix: str = "", dir: str = "") -> IO[bytes]:
"""
write to temporary file for test check_script

Args:
message: message to write to file
suffix: filename suffix
dir: directory to write into

Returns:
temporary file
"""
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=suffix, dir=dir)
for line in message:
temp_file.write(line.encode("utf-8"))
temp_file.close()
return temp_file


class CreateTempScriptAndReturnErrors:
def __init__(
self,
script_checker: ScriptChecker,
machine: str,
script: list[str],
warnings_as_error: bool = True,
no_pyright: bool = False,
no_pylint: bool = False,
dir: str = "",
) -> None:
self.script = script
self.machine = machine
self.dir = dir
self.warnings_as_error = warnings_as_error
self.no_pyright = no_pyright
self.no_pylint = no_pylint
self.script_checker = script_checker

def __enter__(self) -> list[str]:
self.temp_script_file = write_to_temp_file(self.script, dir=self.dir)
return self.script_checker.check_script(
self.temp_script_file.name,
self.machine,
warnings_as_error=self.warnings_as_error,
no_pyright=self.no_pyright,
no_pylint=self.no_pylint,
)

def __exit__(
self,
exc_type: type[BaseException] | None,
exc_value: BaseException | None,
exc_traceback: TracebackType,
) -> None:
os.unlink(self.temp_script_file.name)
117 changes: 43 additions & 74 deletions tests/test_script_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,52 +26,13 @@

from genie_python.genie_epics_api import API
from genie_python.genie_script_checker import ScriptChecker


def write_to_file(message, suffix="", dir=""):
"""
write to temporary file for test check_script
:param message: message to write to file
:return: returns temporary file
"""
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=suffix, dir=dir)
for line in message:
temp_file.write(line.encode("utf-8"))
temp_file.close()
return temp_file
from genie_python.testing_utils.script_checker import (
CreateTempScriptAndReturnErrors,
write_to_temp_file,
)


class TestScriptChecker(unittest.TestCase):
class _CreateTempScriptAndReturnErrors:
def __init__(
self,
script_checker,
script,
warnings_as_error=True,
no_pyright=False,
no_pylint=False,
dir="",
):
self.script = script
self.dir = dir
self.warnings_as_error = warnings_as_error
self.no_pyright = no_pyright
self.no_pylint = no_pylint
self.script_checker = script_checker

def __enter__(self):
self.temp_script_file = write_to_file(self.script, dir=self.dir)
return self.script_checker.checker.check_script(
self.temp_script_file.name,
self.script_checker.machine,
warnings_as_error=self.warnings_as_error,
no_pyright=self.no_pyright,
no_pylint=self.no_pylint,
)

def __exit__(self, exc_type, exc_value, exc_traceback):
os.unlink(self.temp_script_file.name)

def setUp(self):
self.checker = ScriptChecker()
self.api = API("", None)
Expand All @@ -86,7 +47,7 @@ def tearDown(self):

def assertSymbolsDefined(self, script_lines, expected_symbols):
dir_path = tempfile.mkdtemp()
write_to_file(script_lines, suffix=".py", dir=dir_path)
write_to_temp_file(script_lines, suffix=".py", dir=dir_path)
result = self.checker.get_inst_attributes(dir_path)
shutil.rmtree(dir_path)
self.assertEqual(result, expected_symbols)
Expand All @@ -98,15 +59,15 @@ def test_GIVEN_end_without_brackets_WHEN_check_THEN_error_message(self):
" g.end\n",
]

with self._CreateTempScriptAndReturnErrors(self, script_lines) as errors:
with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors:
self.assertEqual(
errors, ["W: 4: Statement seems to have no effect (pointless-statement)"]
)

def test_GIVEN_end_as_start_of_another_word_WHEN_check_THEN_no_error_message(self):
script_lines = ["from genie_python import genie as g\n" "def test():\n", " endAngle = 1"]

with self._CreateTempScriptAndReturnErrors(self, script_lines) as errors:
with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors:
self.assertEqual(errors, [])

def test_GIVEN_end_as_end_of_another_word_WHEN_check_THEN_no_error_message(self):
Expand All @@ -115,13 +76,15 @@ def test_GIVEN_end_as_end_of_another_word_WHEN_check_THEN_no_error_message(self)
" angle_end = 1",
]

with self._CreateTempScriptAndReturnErrors(self, script_lines, no_pyright=True) as errors:
with CreateTempScriptAndReturnErrors(
self.checker, self.machine, script_lines, no_pyright=True
) as errors:
self.assertEqual(errors, [])

def test_GIVEN_end_without_brackets_at_start_of_line_WHEN_check_THEN_error_message(self):
script_lines = ["from genie_python import genie as g\n" "def test():\n" " g.end"]

with self._CreateTempScriptAndReturnErrors(self, script_lines) as errors:
with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors:
self.assertEqual(
errors, ["W: 3: Statement seems to have no effect (pointless-statement)"]
)
Expand All @@ -131,21 +94,21 @@ def test_GIVEN_end_without_brackets_on_line_with_fn_with_brackets_WHEN_check_THE
):
script_lines = ["from genie_python import genie as g\n" "g.begin(); g.end "]

with self._CreateTempScriptAndReturnErrors(self, script_lines) as errors:
with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors:
self.assertEqual(
errors, ["W: 2: Statement seems to have no effect (pointless-statement)"]
)

def test_GIVEN_end_in_string_without_brackets_WHEN_check_THEN_no_message(self):
script_lines = ["def test():\n" ' " a string containing end "']

with self._CreateTempScriptAndReturnErrors(self, script_lines) as errors:
with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors:
self.assertEqual(errors, [])

def test_GIVEN_end_in_comment_without_brackets_WHEN_check_THEN_no_message(self):
script_lines = ["def test():\n", ' "stuff" # end "']

with self._CreateTempScriptAndReturnErrors(self, script_lines) as errors:
with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors:
self.assertEqual(errors, [])

def test_GIVEN_g_assignment_WHEN_check_THEN_warning_message(self):
Expand Down Expand Up @@ -229,21 +192,21 @@ def test_GIVEN_g_assignment_with_space_between_assignment_and_value_WHEN_check_T
def test_GIVEN_g_assignment_with_2_symbols_before_number_WHEN_check_THEN_warning_message(self):
script_lines = ["from genie_python import genie as g\n", "def test():\n", " g+=12"]

with self._CreateTempScriptAndReturnErrors(self, script_lines) as errors:
with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors:
self.assertEqual(errors, ["W: 3: 'g' assignment in line 3"])

def test_GIVEN_variable_assignment_with_g__WHEN_check_THEN_no_message(self):
script_lines = ["going=13"]

with self._CreateTempScriptAndReturnErrors(self, script_lines) as errors:
with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors:
self.assertEqual(errors, [])

def test_GIVEN_function_with_g_WHEN_check_THEN_warn_user(self):
script_lines = [
"from genie_python import genie as g\n" "def test():\n" " g.test_function()\n"
]

with self._CreateTempScriptAndReturnErrors(self, script_lines) as errors:
with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors:
self.assertEqual(
errors,
["E: 3: Module 'genie_python.genie' has no 'test_function' member (no-member)"],
Expand All @@ -255,7 +218,7 @@ def test_GIVEN_2_g_assignments_WHEN_check_THEN_warning_message(self):
" g=17",
]

with self._CreateTempScriptAndReturnErrors(self, script_lines) as errors:
with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors:
self.assertEqual(
errors, ["W: 3: 'g' assignment in line 3", "W: 4: 'g' assignment in line 4"]
)
Expand All @@ -265,7 +228,7 @@ def test_GIVEN_g_non_existing_command_WHEN_call_THEN_error_message(self):
"from genie_python import genie as g\n" "def test():\n" " g.aitfor_time(10)"
]

with self._CreateTempScriptAndReturnErrors(self, script_lines) as errors:
with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors:
self.assertEqual(
errors,
[
Expand Down Expand Up @@ -409,7 +372,7 @@ def test_GIVEN_invalid_python_expr_WHEN_call_check_THEN_error(self):
script_lines = ["my_expr ="]
expected = "E: 1: Parsing failed: 'invalid syntax"

with self._CreateTempScriptAndReturnErrors(self, script_lines) as errors:
with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors:
self.assertTrue(
errors[0].startswith(expected),
f"Result was {errors}, expected first line to start with {expected}",
Expand All @@ -418,20 +381,20 @@ def test_GIVEN_invalid_python_expr_WHEN_call_check_THEN_error(self):
def test_GIVEN_valid_python_expr_WHEN_call_check_THEN_no_error(self):
script_lines = ["my_expr = {}"]

with self._CreateTempScriptAndReturnErrors(self, script_lines) as errors:
with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors:
self.assertEqual(errors, [])

def test_GIVEN_valid_python_class_WHEN_call_check_THEN_no_error(self):
script_lines = ["class MyClass():", "pass"]

with self._CreateTempScriptAndReturnErrors(self, script_lines) as errors:
with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors:
self.assertEqual(errors, [])

def test_GIVEN_invalid_python_class_WHEN_call_check_THEN_error(self):
script_lines = ["class MyClass():"]
expected = "E: 1: Parsing failed: 'expected an indented block"

with self._CreateTempScriptAndReturnErrors(self, script_lines) as errors:
with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors:
self.assertTrue(
errors[0].startswith(expected),
f'Result was "{errors[0]}", expected first line to start with "{expected}"',
Expand Down Expand Up @@ -476,15 +439,15 @@ def test_GIVEN_invalid_range_THEN_pyright_throws_exception(self):

expected = "E: 2: Argument of type"

with self._CreateTempScriptAndReturnErrors(self, script_lines) as errors:
with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors:
self.assertTrue(errors[0].startswith(expected))

def test_GIVEN_invalid_var_type_THEN_pyright_throws_exception(self):
script_lines = ["c: int | float = 3.4\n", "c = None\n"]

expected = "not assignable"

with self._CreateTempScriptAndReturnErrors(self, script_lines) as errors:
with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors:
self.assertTrue(expected in errors[0])

def test_GIVEN_new_directory_WHEN_pyright_script_checker_called_THEN_pyright_json_created_then_destroyed_after_use(
Expand Down Expand Up @@ -512,7 +475,7 @@ def test_GIVEN_invalid_genie_script_WHEN_pyright_script_checker_called_THEN_pyri

expected = "E: 3: Argument of type"

with self._CreateTempScriptAndReturnErrors(self, script_lines) as errors:
with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors:
self.assertTrue(errors[0].startswith(expected))

# Test that if linter fails then pyright does not run
Expand All @@ -530,7 +493,7 @@ def test_GIVEN_that_pylint_fails_THEN_pyright_is_not_ran(self):
]
expected = "E: 1: Parsing failed: 'invalid syntax"

with self._CreateTempScriptAndReturnErrors(self, script_lines) as errors:
with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors:
self.assertTrue(
errors[0].startswith(expected) and len(errors) == 1
) # Will be count of 2 if pyright still runs
Expand All @@ -546,15 +509,17 @@ def test_GIVEN_invalid_genie_script_WHEN_pyright_script_checker_called_with_no_p
" g.begin(1,2,3,4,5,6,7,8,9)\n",
]

with self._CreateTempScriptAndReturnErrors(self, script_lines, no_pyright=True) as errors:
with CreateTempScriptAndReturnErrors(
self.checker, self.machine, script_lines, no_pyright=True
) as errors:
self.assertEqual(errors, [])

# Pyright config checks

def test_GIVEN_unused_variable_in_script_WHEN_pyright_script_checker_called_THEN_no_error(self):
script_lines = ["a = 10\n"]

with self._CreateTempScriptAndReturnErrors(self, script_lines) as errors:
with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors:
self.assertEqual(errors, [])

def test_GIVEN_trying_to_access_member_of_optional_type_var_WHEN_pyright_script_checker_called_THEN_no_error(
Expand All @@ -566,7 +531,7 @@ def test_GIVEN_trying_to_access_member_of_optional_type_var_WHEN_pyright_script_
" a.upper()\n",
]

with self._CreateTempScriptAndReturnErrors(self, script_lines) as errors:
with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors:
self.assertEqual(errors, [])

def test_GIVEN_trying_to_index_var_of_optional_type_WHEN_pyright_script_checker_called_THEN_no_error(
Expand All @@ -578,7 +543,7 @@ def test_GIVEN_trying_to_index_var_of_optional_type_WHEN_pyright_script_checker_
" return elements[0]\n",
]

with self._CreateTempScriptAndReturnErrors(self, script_lines) as errors:
with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors:
self.assertEqual(errors, [])

def test_GIVEN_trying_to_call_var_of_optional_type_WHEN_pyright_script_checker_called_THEN_no_error(
Expand All @@ -590,7 +555,7 @@ def test_GIVEN_trying_to_call_var_of_optional_type_WHEN_pyright_script_checker_c
" callback()\n",
]

with self._CreateTempScriptAndReturnErrors(self, script_lines) as errors:
with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors:
self.assertEqual(errors, [])

def test_GIVEN_trying_to_iterate_over_var_of_optional_type_WHEN_pyright_script_checker_called_THEN_no_error(
Expand All @@ -602,35 +567,39 @@ def test_GIVEN_trying_to_iterate_over_var_of_optional_type_WHEN_pyright_script_c
" pass\n",
]

with self._CreateTempScriptAndReturnErrors(self, script_lines) as errors:
with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors:
self.assertEqual(errors, [])

def test_GIVEN_trying_to_define_function_with_none_type_args_type_WHEN_pyright_script_checker_called_THEN_no_error(
self,
):
script_lines = ["def none_func(arg: int = None):\n" " print(arg)\n"]

with self._CreateTempScriptAndReturnErrors(self, script_lines) as errors:
with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors:
self.assertEqual(errors, [])

def test_GIVEN_trying_to_use_optional_operand__WHEN_pyright_script_checker_called_THEN_no_error(
self,
):
script_lines = ["def none_func(arg1: int, arg2: int = None):\n" " print(arg2 + arg1)\n"]

with self._CreateTempScriptAndReturnErrors(self, script_lines) as errors:
with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors:
self.assertEqual(errors, [])

def test_GIVEN_trying_to_use_undefined_variable_WHEN_pyright_script_checker_called_THEN_no_error(
self,
):
script_lines = ["def func():\n" " print(arg)\n"]

with self._CreateTempScriptAndReturnErrors(self, script_lines, no_pylint=True) as errors:
with CreateTempScriptAndReturnErrors(
self.checker, self.machine, script_lines, no_pylint=True
) as errors:
self.assertEqual(errors, [])

def test_GIVEN_unused_import_WHEN_pyright_script_checker_called_THEN_no_error(self):
script_lines = ["import genie\n"]

with self._CreateTempScriptAndReturnErrors(self, script_lines, no_pylint=True) as errors:
with CreateTempScriptAndReturnErrors(
self.checker, self.machine, script_lines, no_pylint=True
) as errors:
self.assertEqual(errors, [])