Skip to content

Commit

Permalink
test: add type hinting to test_helpers (#1014)
Browse files Browse the repository at this point in the history
* Add type hints to tests/test_helpers
* For fake_script and fake_script_calls, there are a bunch of `type: ignore`s to work around not being able to state the type of `fake_script_path` - I think there will be a few of these through other test modules as well, as hints are added there. I feel it would be cleaner to have the fake script functionality in a class rather than dynamically added to TestCase instances, but [that is a much more substantial change](tonyandrewmeyer@46238ee)

Partially addresses #1007
  • Loading branch information
tonyandrewmeyer authored Sep 26, 2023
1 parent 76a5f15 commit b2c4a3e
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 19 deletions.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ include = ["ops/*.py", "ops/_private/*.py",
"test/test_infra.py",
"test/test_jujuversion.py",
"test/test_log.py",
"test/test_helpers.py",
"test/test_lib.py",
]
pythonVersion = "3.8" # check no python > 3.8 features are used
Expand Down
44 changes: 25 additions & 19 deletions test/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@
import shutil
import subprocess
import tempfile
import typing
import unittest

import ops
from ops.model import _ModelBackend
from ops.storage import SQLiteStorage


def fake_script(test_case, name, content):
def fake_script(test_case: unittest.TestCase, name: str, content: str):
if not hasattr(test_case, 'fake_script_path'):
fake_script_path = tempfile.mkdtemp('-fake_script')
old_path = os.environ["PATH"]
Expand All @@ -35,40 +36,42 @@ def cleanup():
os.environ['PATH'] = old_path

test_case.addCleanup(cleanup)
test_case.fake_script_path = pathlib.Path(fake_script_path)
test_case.fake_script_path = pathlib.Path(fake_script_path) # type: ignore

template_args = {
template_args: typing.Dict[str, str] = {
'name': name,
'path': test_case.fake_script_path.as_posix(),
'path': test_case.fake_script_path.as_posix(), # type: ignore
'content': content,
}

path = test_case.fake_script_path / name
with path.open('wt') as f:
path: pathlib.Path = test_case.fake_script_path / name # type: ignore
with path.open('wt') as f: # type: ignore
# Before executing the provided script, dump the provided arguments in calls.txt.
# ASCII 1E is RS 'record separator', and 1C is FS 'file separator', which seem appropriate.
f.write('''#!/bin/sh
f.write( # type: ignore
'''#!/bin/sh
{{ printf {name}; printf "\\036%s" "$@"; printf "\\034"; }} >> {path}/calls.txt
{content}'''.format_map(template_args))
os.chmod(str(path), 0o755)
os.chmod(str(path), 0o755) # type: ignore
# TODO: this hardcodes the path to bash.exe, which works for now but might
# need to be set via environ or something like that.
path.with_suffix(".bat").write_text(
path.with_suffix(".bat").write_text( # type: ignore
f'@"C:\\Program Files\\git\\bin\\bash.exe" {path} %*\n')


def fake_script_calls(test_case, clear=False):
calls_file = test_case.fake_script_path / 'calls.txt'
if not calls_file.exists():
def fake_script_calls(test_case: unittest.TestCase,
clear: bool = False) -> typing.List[typing.List[str]]:
calls_file: pathlib.Path = test_case.fake_script_path / 'calls.txt' # type: ignore
if not calls_file.exists(): # type: ignore
return []

# newline and encoding forced to linuxy defaults because on
# windows they're written from git-bash
with calls_file.open('r+t', newline='\n', encoding='utf8') as f:
calls = [line.split('\x1e') for line in f.read().split('\x1c')[:-1]]
with calls_file.open('r+t', newline='\n', encoding='utf8') as f: # type: ignore
calls = [line.split('\x1e') for line in f.read().split('\x1c')[:-1]] # type: ignore
if clear:
f.truncate(0)
return calls
f.truncate(0) # type: ignore
return calls # type: ignore


class FakeScriptTest(unittest.TestCase):
Expand Down Expand Up @@ -105,7 +108,10 @@ def test_fake_script_clear(self):

class BaseTestCase(unittest.TestCase):

def create_framework(self, *, model=None, tmpdir=None):
def create_framework(self,
*,
model: typing.Optional[ops.Model] = None,
tmpdir: typing.Optional[pathlib.Path] = None):
"""Create a Framework object.
By default operate in-memory; pass a temporary directory via the 'tmpdir'
Expand All @@ -122,8 +128,8 @@ def create_framework(self, *, model=None, tmpdir=None):
framework = ops.Framework(
SQLiteStorage(data_fpath),
charm_dir,
meta=None,
model=model)
meta=model._cache._meta if model else ops.CharmMeta(),
model=model) # type: ignore
self.addCleanup(framework.close)
return framework

Expand Down

0 comments on commit b2c4a3e

Please sign in to comment.