diff --git a/aliBuild b/aliBuild index 0994d6b6..030581d4 100755 --- a/aliBuild +++ b/aliBuild @@ -28,7 +28,7 @@ def doMain(args, parser): # do not get fooled when parsing localized messages. # We set ALIBUILD_ARCHITECTURE so that it's picked up by the external # command which does the reporting. - if not "architecture" in args: + if "architecture" not in args: args.architecture = detectArch() ENVIRONMENT_OVERRIDES = { "LANG": "C", @@ -57,7 +57,7 @@ def doMain(args, parser): error(e.message) exit(1) - if args.action == "version" or args.action == None: + if args.action == "version" or args.action is None: print("aliBuild version: {version} ({arch})".format( version=__version__ or "unknown", arch=args.architecture or "unknown")) sys.exit(0) @@ -111,13 +111,14 @@ if __name__ == "__main__": else: os.environ["ALIBUILD_ANALYTICS_USER_UUID"] = open(expanduser("~/.config/alibuild/analytics-uuid")).read().strip() try: - profiler = "--profile" in sys.argv - if profiler: + useProfiler = "--profile" in sys.argv + if useProfiler: print("profiler started") import cProfile, pstats from io import StringIO pr = cProfile.Profile() pr.enable() + def profiler(): pr.disable() print("profiler stopped") diff --git a/alibuild_helpers/__init__.py b/alibuild_helpers/__init__.py index 0692cfb5..150d430e 100644 --- a/alibuild_helpers/__init__.py +++ b/alibuild_helpers/__init__.py @@ -1,6 +1,9 @@ # This file is needed to package build_template.sh. # Single-source a PEP440-compliant version using setuptools_scm. + +from typing import Optional +__version__: Optional[str] try: # This is an sdist or wheel, and it's properly installed. from alibuild_helpers._version import version as __version__ diff --git a/alibuild_helpers/analytics.py b/alibuild_helpers/analytics.py index 2ecd83e3..cf0656cf 100644 --- a/alibuild_helpers/analytics.py +++ b/alibuild_helpers/analytics.py @@ -104,7 +104,7 @@ def report_exception(e): exd = e.__class__.__name__, exf = "1") -def enable_analytics(): +def enable_analytics() -> None: if exists(expanduser("~/.config/alibuild/disable-analytics")): unlink(expanduser("~/.config/alibuild/disable-analytics")) if not exists(expanduser("~/.config/alibuild/analytics-uuid")): diff --git a/alibuild_helpers/build.py b/alibuild_helpers/build.py index 08d4f547..72ec011c 100644 --- a/alibuild_helpers/build.py +++ b/alibuild_helpers/build.py @@ -35,7 +35,7 @@ import time -def writeAll(fn, txt): +def writeAll(fn: str, txt: str) -> None: f = open(fn, "w") f.write(txt) f.close() @@ -983,7 +983,7 @@ def doBuild(args, parser): fp.close() except: from pkg_resources import resource_string - cmd_raw = resource_string("alibuild_helpers", 'build_template.sh') + cmd_raw = resource_string("alibuild_helpers", 'build_template.sh').decode() if args.docker: cachedTarball = re.sub("^" + workDir, "/sw", spec["cachedTarball"]) @@ -1077,7 +1077,7 @@ def doBuild(args, parser): args.develPrefix if "develPrefix" in args and spec["is_devel_pkg"] else spec["version"]) ) err = execute(build_command, printer=progress) - progress.end("failed" if err else "done", err) + progress.end("failed" if err else "done", bool(err)) report_event("BuildError" if err else "BuildSuccess", spec["package"], " ".join(( args.architecture, spec["version"], diff --git a/alibuild_helpers/cmd.py b/alibuild_helpers/cmd.py index a0ea2db5..c0c8d8b7 100644 --- a/alibuild_helpers/cmd.py +++ b/alibuild_helpers/cmd.py @@ -5,10 +5,11 @@ from textwrap import dedent from subprocess import TimeoutExpired from shlex import quote +from typing import Union, Tuple, List from alibuild_helpers.log import debug, warning, dieOnError -def decode_with_fallback(data): +def decode_with_fallback(data : Union[bytes, str]) -> str: """Try to decode DATA as utf-8; if that doesn't work, fall back to latin-1. This combination should cover every possible byte string, as latin-1 covers @@ -23,7 +24,7 @@ def decode_with_fallback(data): return str(data) -def getoutput(command, timeout=None): +def getoutput(command:str, timeout: Union[int, None] = None) -> str: """Run command, check it succeeded, and return its stdout as a string.""" proc = Popen(command, shell=isinstance(command, str), stdout=PIPE, stderr=PIPE) try: @@ -37,16 +38,16 @@ def getoutput(command, timeout=None): return decode_with_fallback(stdout) -def getstatusoutput(command, timeout=None): +def getstatusoutput(command:str, timeout: Union[int, None] = None) -> Tuple[int, str]: """Run command and return its return code and output (stdout and stderr).""" proc = Popen(command, shell=isinstance(command, str), stdout=PIPE, stderr=STDOUT) try: - merged_output, _ = proc.communicate(timeout=timeout) + merged_output_bytes, _ = proc.communicate(timeout=timeout) except TimeoutExpired: warning("Process %r timed out; terminated", command) proc.terminate() - merged_output, _ = proc.communicate() - merged_output = decode_with_fallback(merged_output) + merged_output_bytes, _ = proc.communicate() + merged_output = decode_with_fallback(merged_output_bytes) # Strip a single trailing newline, if one exists, to match the behaviour of # subprocess.getstatusoutput. if merged_output.endswith("\n"): @@ -54,9 +55,10 @@ def getstatusoutput(command, timeout=None): return proc.returncode, merged_output -def execute(command, printer=debug, timeout=None): +def execute(command: Union[str, List[str]], printer=debug, timeout:Union[int, None] =None) -> int: popen = Popen(command, shell=isinstance(command, str), stdout=PIPE, stderr=STDOUT) start_time = time.time() + assert popen.stdout is not None, "Could not open stdout for command" for line in iter(popen.stdout.readline, b""): printer("%s", decode_with_fallback(line).strip("\n")) if timeout is not None and time.time() > start_time + timeout: diff --git a/alibuild_helpers/doctor.py b/alibuild_helpers/doctor.py index 8c0fa42c..8fa29030 100644 --- a/alibuild_helpers/doctor.py +++ b/alibuild_helpers/doctor.py @@ -7,7 +7,7 @@ from alibuild_helpers.utilities import getPackageList, parseDefaults, readDefaults, validateDefaults from alibuild_helpers.cmd import getstatusoutput, DockerRunner -def prunePaths(workDir): +def prunePaths(workDir: str) -> None: for x in ["PATH", "LD_LIBRARY_PATH", "DYLD_LIBRARY_PATH"]: if not x in os.environ: continue @@ -52,7 +52,7 @@ def checkRequirements(spec, cmd, homebrew_replacement, getstatusoutput_docker): spec.get("system_requirement_missing")) return (err, "") -def systemInfo(): +def systemInfo() -> None: _,out = getstatusoutput("env") debug("Environment:\n%s", out) _,out = getstatusoutput("uname -a") diff --git a/alibuild_helpers/init.py b/alibuild_helpers/init.py index d9d3b04e..5016baf4 100644 --- a/alibuild_helpers/init.py +++ b/alibuild_helpers/init.py @@ -2,13 +2,13 @@ from alibuild_helpers.utilities import getPackageList, parseDefaults, readDefaults, validateDefaults from alibuild_helpers.log import debug, error, warning, banner, info from alibuild_helpers.log import dieOnError -from alibuild_helpers.workarea import cleanup_git_log, updateReferenceRepoSpec +from alibuild_helpers.workarea import updateReferenceRepoSpec from os.path import join import os.path as path import os, sys -def parsePackagesDefinition(pkgname): +def parsePackagesDefinition(pkgname: str): return [ dict(zip(["name","ver"], y.split("@")[0:2])) for y in [ x+"@" for x in list(filter(lambda y: y, pkgname.split(","))) ] ] @@ -23,8 +23,10 @@ def doInit(args): "--dry-run / -n specified. Doing nothing.", ",".join(x["name"] for x in pkgs)) sys.exit(0) try: - path.exists(args.develPrefix) or os.mkdir(args.develPrefix) - path.exists(args.referenceSources) or os.makedirs(args.referenceSources) + if not path.exists(args.develPrefix): + os.mkdir(args.develPrefix) + if not path.exists(args.referenceSources): + os.makedirs(args.referenceSources) except OSError as e: error("%s", e) sys.exit(1) @@ -66,9 +68,10 @@ def doInit(args): for p in pkgs: spec = specs.get(p["name"]) + dieOnError(spec is None, "cannot find recipe for package %s" % p["name"]) + assert spec spec["is_devel_pkg"] = False spec["scm"] = Git() - dieOnError(spec is None, "cannot find recipe for package %s" % p["name"]) dest = join(args.develPrefix, spec["package"]) writeRepo = spec.get("write_repo", spec.get("source")) dieOnError(not writeRepo, "package %s has no source field and cannot be developed" % spec["package"]) diff --git a/alibuild_helpers/log.py b/alibuild_helpers/log.py index 690d2b64..9b8824c9 100644 --- a/alibuild_helpers/log.py +++ b/alibuild_helpers/log.py @@ -3,10 +3,9 @@ import re import time import datetime +from typing import Any -debug, error, warning, info, success = (None, None, None, None, None) - -def dieOnError(err, msg): +def dieOnError(err: Any, msg: str) -> None: if err: error("%s", msg) sys.exit(1) @@ -60,7 +59,7 @@ def __init__(self, begin_msg=""): self.lasttime = 0 self.STAGES = ".", "..", "...", "....", ".....", "....", "...", ".." self.begin_msg = begin_msg - self.percent = -1 + self.percent: float = -1 def __call__(self, txt, *args): if logger.level <= logging.DEBUG or not sys.stdout.isatty(): @@ -88,7 +87,7 @@ def __call__(self, txt, *args): self.lasttime = time.time() sys.stderr.flush() - def erase(self): + def erase(self) -> None: nerase = len(self.STAGES[self.count]) if self.count > -1 else 0 if self.percent > -1: nerase = nerase + 7 diff --git a/alibuild_helpers/sync.py b/alibuild_helpers/sync.py index ae9dafd5..8f10a9e7 100644 --- a/alibuild_helpers/sync.py +++ b/alibuild_helpers/sync.py @@ -12,30 +12,15 @@ from alibuild_helpers.cmd import execute from alibuild_helpers.log import debug, info, error, dieOnError, ProgressPrint from alibuild_helpers.utilities import resolve_store_path, resolve_links_path, symlink - - -def remote_from_url(read_url, write_url, architecture, work_dir, insecure=False): - """Parse remote store URLs and return the correct RemoteSync instance for them.""" - if read_url.startswith("http"): - return HttpRemoteSync(read_url, architecture, work_dir, insecure) - if read_url.startswith("s3://"): - return S3RemoteSync(read_url, write_url, architecture, work_dir) - if read_url.startswith("b3://"): - return Boto3RemoteSync(read_url, write_url, architecture, work_dir) - if read_url.startswith("cvmfs://"): - return CVMFSRemoteSync(read_url, None, architecture, work_dir) - if read_url: - return RsyncRemoteSync(read_url, write_url, architecture, work_dir) - return NoRemoteSync() - +from typing import Union class NoRemoteSync: """Helper class which does not do anything to sync""" - def fetch_symlinks(self, spec): + def fetch_symlinks(self, spec) -> None: pass - def fetch_tarball(self, spec): + def fetch_tarball(self, spec) -> None: pass - def upload_symlinks_and_tarball(self, spec): + def upload_symlinks_and_tarball(self, spec) -> None: pass class PartialDownloadError(Exception): @@ -47,7 +32,7 @@ def __str__(self): class HttpRemoteSync: - def __init__(self, remoteStore, architecture, workdir, insecure): + def __init__(self, remoteStore: str, architecture: str, workdir: str, insecure: bool): self.remoteStore = remoteStore self.writeStore = "" self.architecture = architecture @@ -486,14 +471,14 @@ class Boto3RemoteSync: time. """ - def __init__(self, remoteStore, writeStore, architecture, workdir): + def __init__(self, remoteStore, writeStore, architecture, workdir) -> None: self.remoteStore = re.sub("^b3://", "", remoteStore) self.writeStore = re.sub("^b3://", "", writeStore) self.architecture = architecture self.workdir = workdir self._s3_init() - def _s3_init(self): + def _s3_init(self) -> None: # This is a separate method so that we can patch it out for unit tests. # Import boto3 here, so that if we don't use this remote store, we don't # have to install it in the first place. @@ -530,7 +515,7 @@ def _s3_key_exists(self, key): raise return True - def fetch_tarball(self, spec): + def fetch_tarball(self, spec) -> None: debug("Updating remote store for package %s with hashes %s", spec["package"], ", ".join(spec["remote_hashes"])) @@ -568,7 +553,7 @@ def fetch_tarball(self, spec): debug("Remote has no tarballs for %s with hashes %s", spec["package"], ", ".join(spec["remote_hashes"])) - def fetch_symlinks(self, spec): + def fetch_symlinks(self, spec) -> None: from botocore.exceptions import ClientError links_path = resolve_links_path(self.architecture, spec["package"]) os.makedirs(os.path.join(self.workdir, links_path), exist_ok=True) @@ -691,3 +676,21 @@ def upload_symlinks_and_tarball(self, spec): self.s3.upload_file(Bucket=self.writeStore, Key=tar_path, Filename=os.path.join(self.workdir, tar_path)) + +RemoteSync = Union[HttpRemoteSync, S3RemoteSync, Boto3RemoteSync, CVMFSRemoteSync, RsyncRemoteSync, NoRemoteSync] + +def remote_from_url(read_url: str, write_url: str, architecture: str, work_dir: str, insecure=False) -> RemoteSync: + """Parse remote store URLs and return the correct RemoteSync instance for them.""" + if read_url.startswith("http"): + return HttpRemoteSync(read_url, architecture, work_dir, insecure) + if read_url.startswith("s3://"): + return S3RemoteSync(read_url, write_url, architecture, work_dir) + if read_url.startswith("b3://"): + return Boto3RemoteSync(read_url, write_url, architecture, work_dir) + if read_url.startswith("cvmfs://"): + return CVMFSRemoteSync(read_url, None, architecture, work_dir) + if read_url: + return RsyncRemoteSync(read_url, write_url, architecture, work_dir) + return NoRemoteSync() + + diff --git a/alibuild_helpers/templating_plugin.py b/alibuild_helpers/templating_plugin.py index 7c1c8847..94e6767d 100644 --- a/alibuild_helpers/templating_plugin.py +++ b/alibuild_helpers/templating_plugin.py @@ -11,7 +11,7 @@ from jinja2.sandbox import SandboxedEnvironment -def build_plugin(specs, args, build_order): +def build_plugin(specs, args, build_order) -> None: """Read a user-provided template from stdin and render it.""" print(SandboxedEnvironment(autoescape=False) .from_string(sys.stdin.read()) diff --git a/alibuild_helpers/utilities.py b/alibuild_helpers/utilities.py index a78c175e..88ca6077 100644 --- a/alibuild_helpers/utilities.py +++ b/alibuild_helpers/utilities.py @@ -567,7 +567,7 @@ def getPackageList(packages, specs, configDir, preferSystem, noSystem, class Hasher: - def __init__(self): + def __init__(self) -> None: self.h = hashlib.sha1() def __call__(self, txt): if not type(txt) == bytes: diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 00000000..3fa28b7e --- /dev/null +++ b/mypy.ini @@ -0,0 +1,30 @@ +[mypy] +python_version = 3.12 +platform = linux +plugins = pydantic.mypy +show_error_codes = true +follow_imports = normal +local_partial_types = true +strict_equality = true +no_implicit_optional = true +warn_incomplete_stub = true +warn_redundant_casts = true +warn_unused_configs = true +warn_unused_ignores = true +enable_error_code = ignore-without-code, redundant-self, truthy-iterable +disable_error_code = annotation-unchecked, import-not-found, import-untyped +extra_checks = false +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +warn_return_any = true +warn_unreachable = true + +[pydantic-mypy] +init_forbid_extra = true +init_typed = true +warn_required_dynamic_aliases = true +warn_untyped_fields = true diff --git a/tests/test_analytics.py b/tests/test_analytics.py index 2e5d9f3b..b6f11543 100644 --- a/tests/test_analytics.py +++ b/tests/test_analytics.py @@ -7,11 +7,11 @@ def noAnalytics(): def yesAnalytics(): return True -def notInvoked(): +def notInvoked() -> None: assert(False) class TestAnalytics(unittest.TestCase): - def test_analytics(self): + def test_analytics(self) -> None: self.assertEqual(False, decideAnalytics(hasDisableFile=False, hasUuid=False, isTty=False, diff --git a/tests/test_args.py b/tests/test_args.py index c70dd762..7791dd19 100644 --- a/tests/test_args.py +++ b/tests/test_args.py @@ -21,7 +21,7 @@ "build --force-unknown-architecture": [call(BUILD_MISSING_PKG_ERROR)], "build --force-unknown-architecture zlib --foo": [call('unrecognized arguments: --foo')], "init --docker-image": [call('unrecognized arguments: --docker-image')], - "builda --force-unknown-architecture zlib" : [call("argument action: invalid choice: 'builda' (choose from 'analytics', 'architecture', 'build', 'clean', 'deps', 'doctor', 'init', 'version')")], + "builda --force-unknown-architecture zlib" : [call("argument action: invalid choice: 'builda' (choose from analytics, architecture, build, clean, deps, doctor, init, version)")], "build --force-unknown-architecture zlib --no-system --always-prefer-system" : [call('argument --always-prefer-system: not allowed with argument --no-system')], "build zlib --architecture foo": ARCHITECTURE_ERROR, "build --force-unknown-architecture zlib --remote-store rsync://test1.local/::rw --write-store rsync://test2.local/::rw ": [call('cannot specify ::rw and --write-store at the same time')], @@ -95,15 +95,20 @@ def test_actionParsing(self, mock_commands): @mock.patch("alibuild_helpers.utilities.getoutput", new=lambda cmd: "x86_64") # for uname -m @mock.patch('alibuild_helpers.args.argparse.ArgumentParser.error') - def test_failingParsing(self, mock_print): + def test_failingParsing(self, mock_print) -> None: mock_print.side_effect = FakeExit("raised") for (cmd, calls) in PARSER_ERRORS.items(): mock_print.mock_calls = [] with patch.object(sys, "argv", ["alibuild"] + shlex.split(cmd)): self.assertRaises(FakeExit, doParseArgs) + if mock_print.mock_calls != calls: + import json + print('Failed test:', cmd) + print("Expected calls: ", json.dumps(calls, indent=2, default=str)) + print("Actual calls: ", json.dumps(mock_print.mock_calls, indent=2, default=str)) self.assertEqual(mock_print.mock_calls, calls) - def test_validArchitectures(self): + def test_validArchitectures(self) -> None: for arch in VALID_ARCHS: self.assertTrue(matchValidArch(arch)) for arch in INVALID_ARCHS: diff --git a/tests/test_build.py b/tests/test_build.py index a0f4ed72..ee42506f 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -9,6 +9,7 @@ from unittest.mock import call, patch, MagicMock, DEFAULT from io import StringIO from collections import OrderedDict +from typing import Dict from alibuild_helpers.utilities import parseRecipe, resolve_tag from alibuild_helpers.build import doBuild, storeHashes, generate_initdotsh @@ -130,7 +131,7 @@ def dummy_git(args, directory=".", check=True, prompt=True): }[(tuple(args), directory, check)] -TIMES_ASKED = {} +TIMES_ASKED : Dict[str, int] = {} def dummy_open(x, mode="r", encoding=None, errors=None): @@ -240,7 +241,7 @@ class BuildTestCase(unittest.TestCase): @patch("alibuild_helpers.workarea.is_writeable", new=MagicMock(return_value=True)) @patch("alibuild_helpers.build.basename", new=MagicMock(return_value="aliBuild")) @patch("alibuild_helpers.build.install_wrapper_script", new=MagicMock()) - def test_coverDoBuild(self, mock_debug, mock_listdir, mock_warning, mock_sys, mock_git_git): + def test_coverDoBuild(self, mock_debug, mock_listdir, mock_warning, mock_sys, mock_git_git) -> None: mock_git_git.side_effect = dummy_git mock_debug.side_effect = lambda *args: None mock_warning.side_effect = lambda *args: None @@ -339,7 +340,7 @@ def setup_spec(self, script): spec["tag"] = resolve_tag(spec) return spec - def test_hashing(self): + def test_hashing(self) -> None: """Check that the hashes assigned to packages remain constant.""" default = self.setup_spec(TEST_DEFAULT_RELEASE) zlib = self.setup_spec(TEST_ZLIB_RECIPE) @@ -387,7 +388,7 @@ def test_hashing(self): self.assertEqual(len(extra["remote_hashes"]), 3) self.assertEqual(extra["local_hashes"][0], TEST_EXTRA_BUILD_HASH) - def test_initdotsh(self): + def test_initdotsh(self) -> None: """Sanity-check the generated init.sh for a few variables.""" specs = { # Add some attributes that are normally set by doBuild(), but diff --git a/tests/test_git.py b/tests/test_git.py index f6af40be..3af937ee 100644 --- a/tests/test_git.py +++ b/tests/test_git.py @@ -15,33 +15,33 @@ class GitWrapperTestCase(unittest.TestCase): """Make sure the git() wrapper function is working.""" - def setUp(self): + def setUp(self) -> None: # Disable reading all git configuration files, including the user's, in # case they have access to PRIVATE_REPO. self._prev_git_config_global = os.environ.get("GIT_CONFIG_GLOBAL") os.environ["GIT_CONFIG_GLOBAL"] = os.devnull - def tearDown(self): + def tearDown(self) -> None: # Restore the original value of GIT_CONFIG_GLOBAL, if any. if self._prev_git_config_global is None: del os.environ["GIT_CONFIG_GLOBAL"] else: os.environ["GIT_CONFIG_GLOBAL"] = self._prev_git_config_global - def test_git_existing_repo(self): + def test_git_existing_repo(self) -> None: """Check git can read an existing repo.""" err, out = git(("ls-remote", "-ht", EXISTING_REPO), check=False, prompt=False) self.assertEqual(err, 0, "git output:\n" + out) self.assertTrue(out, "expected non-empty output from git") - def test_git_missing_repo(self): + def test_git_missing_repo(self) -> None: """Check we get the right exception when a repo doesn't exist.""" self.assertRaises(SCMError, git, ( "ls-remote", "-ht", MISSING_REPO, ), prompt=False) - def test_git_private_repo(self): + def test_git_private_repo(self) -> None: """Check we get the right exception when credentials are required.""" self.assertRaises(SCMError, git, ( "-c", "credential.helper=", "ls-remote", "-ht", PRIVATE_REPO, diff --git a/tests/test_hashing.py b/tests/test_hashing.py index e0e09573..56283c4c 100644 --- a/tests/test_hashing.py +++ b/tests/test_hashing.py @@ -28,7 +28,7 @@ class KnownGoodHashesTestCase(unittest.TestCase): @unittest.skipIf(not os.path.exists(LOGFILE), "Need a reference build log at path " + LOGFILE) - def test_hashes_match_build_log(self): + def test_hashes_match_build_log(self) -> None: checked = set() specs = {} with codecs.open(LOGFILE, encoding="utf-8") as logf: diff --git a/tests/test_init.py b/tests/test_init.py index 97b968c4..42e477d2 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -25,7 +25,7 @@ def dummy_exists(x): class InitTestCase(unittest.TestCase): - def test_packageDefinition(self): + def test_packageDefinition(self) -> None: self.assertEqual(parsePackagesDefinition("AliRoot@v5-08-16,AliPhysics@v5-08-16-01"), [{'ver': 'v5-08-16', 'name': 'AliRoot'}, {'ver': 'v5-08-16-01', 'name': 'AliPhysics'}]) @@ -36,7 +36,7 @@ def test_packageDefinition(self): @patch("alibuild_helpers.init.info") @patch("alibuild_helpers.init.path") @patch("alibuild_helpers.init.os") - def test_doDryRunInit(self, mock_os, mock_path, mock_info): + def test_doDryRunInit(self, mock_os, mock_path, mock_info) -> None: fake_dist = {"repo": "alisw/alidist", "ver": "master"} args = Namespace( develPrefix = ".", @@ -60,7 +60,7 @@ def test_doDryRunInit(self, mock_os, mock_path, mock_info): @patch("alibuild_helpers.init.updateReferenceRepoSpec") @patch("alibuild_helpers.utilities.open") @patch("alibuild_helpers.init.readDefaults") - def test_doRealInit(self, mock_read_defaults, mock_open, mock_update_reference, mock_git, mock_os, mock_path, mock_info, mock_banner): + def test_doRealInit(self, mock_read_defaults, mock_open, mock_update_reference, mock_git, mock_os, mock_path, mock_info, mock_banner) -> None: fake_dist = {"repo": "alisw/alidist", "ver": "master"} mock_open.side_effect = lambda x: { "/alidist/defaults-release.sh": StringIO("package: defaults-release\nversion: v1\n---"), diff --git a/tests/test_log.py b/tests/test_log.py index 06553f1b..3831bf80 100644 --- a/tests/test_log.py +++ b/tests/test_log.py @@ -20,7 +20,7 @@ def test_dieOnError(self, mock_sys, mock_error): @patch("sys.stdout.isatty", new=MagicMock(return_value=True)) @patch("sys.stderr", new=MagicMock(return_value=True)) - def test_ProgressPrint(self): + def test_ProgressPrint(self) -> None: """Make sure ProgressPrint updates correctly.""" # ProgressPrint only parses messages every 0.5s. Trick it into thinking # the last message was seconds ago. diff --git a/tests/test_packagelist.py b/tests/test_packagelist.py index 4f9cd93f..4813446c 100644 --- a/tests/test_packagelist.py +++ b/tests/test_packagelist.py @@ -71,7 +71,7 @@ class MockReader: - def __init__(self, url, dist=None): + def __init__(self, url, dist=None) -> None: self._contents = RECIPES[url] self.url = "mock://" + url @@ -109,7 +109,7 @@ def getPackageListWithDefaults(packages, force_rebuild=()): class ReplacementTestCase(unittest.TestCase): """Test that system package replacements are working.""" - def test_disable(self): + def test_disable(self) -> None: """Check that not specifying any replacement disables the package. This is was the only available behaviour in previous aliBuild versions @@ -121,7 +121,7 @@ def test_disable(self): self.assertNotIn("disable", ownPkgs) self.assertNotIn("disable", specs) - def test_replacement_given(self): + def test_replacement_given(self) -> None: """Check that specifying a replacement spec means it is used. This also checks that if no recipe is given, we report the package as @@ -138,7 +138,7 @@ def test_replacement_given(self): self.assertIn("with-replacement", systemPkgs) self.assertNotIn("with-replacement", ownPkgs) - def test_replacement_recipe_given(self): + def test_replacement_recipe_given(self) -> None: """Check that specifying a replacement recipe means it is used. Also check that we report to the user that a package will be compiled @@ -174,13 +174,13 @@ def side_effect(msg, *args, **kwargs): class ForceRebuildTestCase(unittest.TestCase): """Test that force_rebuild keys are applied properly.""" - def test_force_rebuild_recipe(self): + def test_force_rebuild_recipe(self) -> None: """If the recipe specifies force_rebuild, it must be applied.""" specs, _, _, _, _ = getPackageListWithDefaults(["force-rebuild"]) self.assertTrue(specs["force-rebuild"]["force_rebuild"]) self.assertFalse(specs["defaults-release"]["force_rebuild"]) - def test_force_rebuild_command_line(self): + def test_force_rebuild_command_line(self) -> None: """The --force-rebuild option must take precedence, if given.""" specs, _, _, _, _ = getPackageListWithDefaults( ["force-rebuild"], force_rebuild=["defaults-release", "force-rebuild"], diff --git a/tests/test_parseRecipe.py b/tests/test_parseRecipe.py index 82edbb0c..b6785737 100644 --- a/tests/test_parseRecipe.py +++ b/tests/test_parseRecipe.py @@ -51,7 +51,7 @@ ^""" class Recoder(object): - def __init__(self): + def __init__(self) -> None: self.buffer = "" def __call__(self, s, *a): self.buffer += s % a @@ -67,7 +67,7 @@ def __call__(self): return self.buffer class TestRecipes(unittest.TestCase): - def test_recipes(self): + def test_recipes(self) -> None: err, meta, body = parseRecipe(BufferReader("test1.sh", TEST1)) self.assertEqual(err, None) self.assertEqual(meta["package"], "foo") @@ -88,7 +88,7 @@ def test_recipes(self): err, meta, body = parseRecipe(BufferReader("test_broken_6.sh", TEST_BROKEN_6)) self.assertEqual(err.strip(), ERROR_MSG_6.strip()) - def test_getRecipeReader(self): + def test_getRecipeReader(self) -> None: f = getRecipeReader("foo") self.assertEqual(type(f), FileReader) f = getRecipeReader("dist:foo@master") @@ -107,7 +107,7 @@ def test_parseDefaults(self): self.assertEqual(overrides, {'defaults-release': {}, 'root': {'requires': 'GCC'}}) self.assertEqual(taps, {'root': 'dist:ROOT@master'}) - def test_validateDefault(self): + def test_validateDefault(self) -> None: ok, out, validDefaults = validateDefaults({"something": True}, "release") self.assertEqual(ok, True) ok, out, validDefaults = validateDefaults({"package": "foo","valid_defaults": ["o2", "o2-dataflow"]}, "release") diff --git a/tests/test_sync.py b/tests/test_sync.py index 76103742..c55f3bf3 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -44,7 +44,7 @@ def tarball_name(spec): class MockRequest: - def __init__(self, j, simulate_err=False): + def __init__(self, j, simulate_err=False) -> None: self.j = j self.simulate_err = simulate_err self.status_code = 200 if j else 404 @@ -233,7 +233,7 @@ def head_object(Bucket, Key): raise ClientError({"Error": {"Code": "404"}}, "head_object") return {} - def download_file(Bucket, Key, Filename, Callback=None): + def download_file(Bucket, Key, Filename, Callback=None) -> None: self.assertNotIn(NONEXISTENT_HASH, Key, "tried to fetch missing tarball") self.assertNotIn(BAD_HASH, Key, "tried to follow bad symlink") @@ -266,7 +266,7 @@ def get_paginator(method): @patch("os.path.isfile", new=MagicMock(return_value=False)) @patch("os.path.islink", new=MagicMock(return_value=False)) @patch("alibuild_helpers.sync.execute", new=MagicMock(return_value=0)) - def test_tarball_download(self): + def test_tarball_download(self) -> None: """Test boto3 behaviour when downloading tarballs from the remote.""" b3sync = sync.Boto3RemoteSync( remoteStore="b3://localhost", writeStore="b3://localhost", @@ -296,7 +296,7 @@ def test_tarball_download(self): )) @patch("os.readlink", new=MagicMock(return_value="dummy path")) @patch("os.path.islink", new=MagicMock(return_value=True)) - def test_tarball_upload(self): + def test_tarball_upload(self) -> None: """Test boto3 behaviour when building packages for upload locally.""" b3sync = sync.Boto3RemoteSync( remoteStore="b3://localhost", writeStore="b3://localhost", diff --git a/tests/test_utilities.py b/tests/test_utilities.py index 3fc2beef..dd1497ac 100644 --- a/tests/test_utilities.py +++ b/tests/test_utilities.py @@ -136,18 +136,18 @@ ] class TestUtilities(unittest.TestCase): - def test_osx(self): + def test_osx(self) -> None: for payload in architecturePayloads: result, hasOsRelease, osReleaseLines, platformTuple, platformSystem, platformProcessor = payload self.assertEqual(result, doDetectArch(hasOsRelease, osReleaseLines, platformTuple, platformSystem, platformProcessor)) # Test by mocking platform.processor - def test_osx_mock(self): + def test_osx_mock(self) -> None: for payload in macOSArchitecturePayloads: result, hasOsRelease, osReleaseLines, platformTuple, platformSystem, platformProcessor = payload with patch('platform.machine', return_value=platformProcessor): platformProcessor = None self.assertEqual(result, doDetectArch(hasOsRelease, osReleaseLines, platformTuple, platformSystem, None)) - def test_Hasher(self): + def test_Hasher(self) -> None: h = Hasher() h("foo") self.assertEqual("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33", h.hexdigest()) @@ -157,7 +157,7 @@ def test_Hasher(self): h("bar") self.assertEqual("8843d7f92416211de9ebb963ff4ce28125932878", h.hexdigest()) - def test_UTF8_Hasher(self): + def test_UTF8_Hasher(self) -> None: h1 = Hasher() h2 = Hasher() h3 = Hasher() @@ -169,12 +169,12 @@ def test_UTF8_Hasher(self): self.assertEqual(h3.hexdigest(), "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33") self.assertNotEqual(h1.hexdigest(), h2.hexdigest()) - def test_asList(self): + def test_asList(self) -> None: self.assertEqual(asList("a"), ["a"]) self.assertEqual(asList(["a"]), ["a"]) self.assertEqual(asList(None), [None]) - def test_filterByArchitecture(self): + def test_filterByArchitecture(self) -> None: self.assertEqual(["AliRoot"], list(filterByArchitecture("osx_x86-64", ["AliRoot"]))) self.assertEqual([], list(filterByArchitecture("osx_x86-64", ["AliRoot:(?!osx)"]))) self.assertEqual(["GCC"], list(filterByArchitecture("osx_x86-64", ["AliRoot:(?!osx)", "GCC"]))) @@ -213,7 +213,7 @@ def test_prunePaths(self): self.assertTrue(fake_env_copy["DYLD_LIBRARY_PATH"] == "/sw/lib") self.assertTrue(fake_env_copy["ALIBUILD_VERSION"] == "v1.0.0") - def test_resolver(self): + def test_resolver(self) -> None: spec = {"package": "test-pkg", "version": "%(tag_basename)s", "tag": "foo/bar", @@ -231,7 +231,7 @@ def test_resolver(self): class TestTopologicalSort(unittest.TestCase): """Check that various properties of topological sorting hold.""" - def test_resolve_dependency_chain(self): + def test_resolve_dependency_chain(self) -> None: """Test that topological sorting correctly sorts packages in a dependency chain.""" # Topological sorting only takes "requires" into account, since the # build/runtime distinction does not matter for resolving build order. @@ -241,7 +241,7 @@ def test_resolve_dependency_chain(self): "c": {"package": "c", "requires": []}, }))) - def test_diamond_dependency(self): + def test_diamond_dependency(self) -> None: """Test that a diamond dependency relationship is handled correctly.""" self.assertEqual(["base", "mid2", "mid1", "top"], list(topological_sort({ "top": {"package": "top", "requires": ["mid1", "mid2"]}, @@ -251,7 +251,7 @@ def test_diamond_dependency(self): "base": {"package": "base", "requires": []}, }))) - def test_dont_drop_packages(self): + def test_dont_drop_packages(self) -> None: """Check that topological sorting doesn't drop any packages.""" # For half the packages, depend on the first package, to make this a # little more than trivial.