def effective_package(self, template): """ :param str template: Template describing how to name delivered files, example: {meta}/{name}-{version} """ folder = os.path.join( self.dist_folder, template.format(name=self.name, version=self.version)) runez.delete(folder, logger=None) runez.ensure_folder(folder, folder=True, logger=None) vrun(self.name, "virtualenv", folder) bin_folder = os.path.join(folder, "bin") pip = os.path.join(bin_folder, "pip") spec = self.source_folder if self.source_folder else "%s==%s" % ( self.name, self.version) runez.run(pip, "install", "-i", system.SETTINGS.index, "-f", self.build_folder, spec) if self.relocatable: python = system.target_python(package_name=self.name).executable vrun(self.name, "virtualenv", "--relocatable", "--python=%s" % python, folder) self.packaged.append(folder) self.executables = [ os.path.join(bin_folder, name) for name in self.entry_points ]
def pex_build(self, name, destination): """ Run pex build :param str name: Name of entry point :param str destination: Path to file where to produce pex :return str: None if successful, error message otherwise """ runez.ensure_folder(self.build_folder, folder=True) runez.delete(destination) args = ["--cache-dir", self.build_folder, "--repo", self.build_folder] args.extend([ "-c%s" % name, "-o%s" % destination, "%s==%s" % (self.name, self.version) ]) python = system.target_python(package_name=self.name) shebang = python.shebang( universal=system.is_universal(self.build_folder)) if shebang: args.append("--python-shebang") args.append(shebang) vrun(self.name, self.specced_command(), *args, path_env=C_COMPILATION_HELP)
def __exit__(self, *_): """ Release lock """ if not self._should_keep(): runez.delete(self.folder, logger=LOG.debug if self.keep else None) runez.delete(self.lock, logger=None)
def package(pspec, build_folder, dist_folder, requirements, run_compile_all): runez.ensure_folder(build_folder, clean=True) if pspec.python.major < 3: # pragma: no cover abort("Packaging with pex is not supported any more with python2") pex_root = os.path.join(build_folder, "pex-root") tmp = os.path.join(build_folder, "pex-tmp") wheels = os.path.join(build_folder, "wheels") runez.ensure_folder(tmp, logger=False) runez.ensure_folder(wheels, logger=False) pex_venv = PythonVenv(pspec, folder=os.path.join(build_folder, "pex-venv")) pex_venv.pip_install("pex==2.1.75", *requirements) pex_venv.pip_wheel("--cache-dir", wheels, "--wheel-dir", wheels, *requirements) contents = PackageContents(pex_venv, pspec) if contents.entry_points: wheel_path = pspec.find_wheel(wheels) result = [] for name in contents.entry_points: target = os.path.join(dist_folder, name) runez.delete(target) pex_venv.run_python( "-mpex", "-o%s" % target, "--pex-root", pex_root, "--tmpdir", tmp, "--no-index", "--find-links", wheels, # resolver options None if run_compile_all else "--no-compile", # output options "-c%s" % name, # entry point options "--python-shebang", "/usr/bin/env python%s" % pspec.python.major, wheel_path, ) result.append(target) return result
def __init__(self, lock, venv_python): """ :param SoftLock lock: Acquired lock """ self.venv_python = venv_python self.lock = lock self.folder = lock.folder self.bin = os.path.join(self.folder, "bin") self.python = os.path.join(self.bin, "python") self._frozen = None if runez.file.is_younger(self.python, self.lock.keep): return runez.delete(self.folder) is_py2 = runez.PY2 if venv_python: is_py2 = runez.to_int(venv_python.major, default=2) < 3 if is_py2: venv = virtualenv_path() if not venv: runez.abort("Can't determine path to virtualenv.py") runez.run(self.venv_python.executable, venv, self.folder) else: runez.run(self.venv_python.executable, "-mvenv", self.folder) runez.run(self.python, "-mpip", "install", "wheel")
def clean_samples(cls, verbose=False): cleanable = [] for root, dirs, files in os.walk(cls.SAMPLE_FOLDER): if not dirs and not files: cleanable.append(root) if os.path.basename(root).startswith("_xpct-"): for fname in files: ypath = os.path.dirname(root) ypath = os.path.join(ypath, "%s.yml" % runez.basename(fname)) if not os.path.isfile(ypath): # Delete _xpct-* files that correspond to moved samples cleanable.append(os.path.join(root, fname)) if not cleanable: if verbose: print("No cleanable _xpct- files found") return for path in cleanable: runez.delete(path, logger=logging.info) for root, dirs, files in os.walk(cls.SAMPLE_FOLDER): if not dirs and not files: cleanable.append(root) runez.delete(root, logger=logging.info) print("%s cleaned" % runez.plural(cleanable, "file"))
def clean_folder(folder): """Clean contents of 'folder', if any""" if os.path.isdir(folder): for fname in os.listdir(folder): runez.delete(os.path.join(folder, fname)) else: runez.ensure_folder(folder, folder=True)
def _install(self, pspec, target, source): runez.delete(target, logger=False) if os.path.isabs(source) and os.path.isabs(target): parent = runez.parent_folder(target) if runez.parent_folder(source).startswith(parent): # Use relative path if source is under target source = os.path.relpath(source, parent) os.symlink(source, target)
def cleanup(self): """Cleanup older installs""" cutoff = time.time() - system.SETTINGS.install_timeout * 60 folder = system.SETTINGS.meta.full_path(self.name) removed_entry_points = runez.read_json(self.removed_entry_points_path, default=[], fatal=False) prefixes = {None: [], self.name: []} for name in self.entry_points: prefixes[name] = [] for name in removed_entry_points: prefixes[name] = [] if os.path.isdir(folder): for name in os.listdir(folder): if name.startswith("."): continue target = find_prefix(prefixes, name) if target in prefixes: fpath = os.path.join(folder, name) prefixes[target].append((os.path.getmtime(fpath), fpath)) # Sort each by last modified timestamp for target, cleanable in prefixes.items(): prefixes[target] = sorted(cleanable, reverse=True) rem_cleaned = 0 for target, cleanable in prefixes.items(): if not cleanable: if target in removed_entry_points: # No cleanable found for a removed entry-point -> count as cleaned rem_cleaned += 1 continue if target not in removed_entry_points: if cleanable[0][0] <= cutoff: # Latest is old enough now, cleanup all except latest cleanable = cleanable[1:] else: # Latest is too young, keep the last 2 cleanable = cleanable[2:] elif cleanable[0][0] <= cutoff: # Delete all removed entry points when old enough rem_cleaned += 1 else: # Removed entry point still too young, keep latest cleanable = cleanable[1:] for _, path in cleanable: runez.delete(path) if rem_cleaned >= len(removed_entry_points): runez.delete(self.removed_entry_points_path)
def required_entry_points(self): """ :return list: Entry points, abort execution if there aren't any """ ep = self.entry_points if not ep: runez.delete(system.SETTINGS.meta.full_path(self.name)) runez.abort( "'%s' is not a CLI, it has no console_scripts entry points", self.name) return ep
def test_package_venv(cli): # Verify that "debian mode" works as expected, with -droot/tmp <-> /tmp runez.delete("/tmp/pickley") cli.run("package", cli.project_folder, "-droot/tmp", "--no-compile", "--sanity-check=--version", "-sroot:root/usr/local/bin") assert cli.succeeded assert "--version" in cli.logged assert runez.is_executable("/tmp/pickley/bin/pickley") r = runez.run("/tmp/pickley/bin/pickley", "--version") assert r.succeeded runez.delete("/tmp/pickley")
def __exit__(self, *_): """Release lock""" if runez.DRYRUN: print("Would release %s" % runez.short(self.lock_path)) else: runez.log.trace("Released %s" % runez.short(self.lock_path)) if CFG.base: runez.Anchored.pop(CFG.base.path) runez.delete(self.lock_path, logger=False)
def uninstall(all, packages): """Uninstall packages""" if packages and all: abort("Either specify packages to uninstall, or --all (but not both)") if not packages and not all: abort("Specify packages to uninstall, or --all") if packages and PICKLEY in packages: abort( "Run 'uninstall --all' if you wish to uninstall pickley itself (and everything it installed)" ) setup_audit_log() for pspec in CFG.package_specs(packages): manifest = pspec.get_manifest() if not manifest or not manifest.version: abort("%s was not installed with pickley" % runez.bold(pspec.dashed)) if manifest.entrypoints: for ep in manifest.entrypoints: runez.delete(pspec.exe_path(ep)) runez.delete(pspec.meta_path) action = "Would uninstall" if runez.DRYRUN else "Uninstalled" inform("%s %s" % (action, pspec.dashed)) if all: runez.delete(CFG.base.full_path(PICKLEY)) runez.delete(CFG.meta.path) inform("pickley is now %s" % runez.red("uninstalled"))
def test_lock(temp_base): folder = os.path.join(temp_base, "foo") with SoftLock(folder, timeout=10) as lock: assert lock._locked() with pytest.raises(SoftLockException): with SoftLock(folder, timeout=0.01): pass assert str(lock) == folder + ".lock" runez.delete(str(lock)) assert not lock._locked() with patch("pickley.system.virtualenv_path", return_value=None): assert "Can't determine path to virtualenv.py" in verify_abort( SharedVenv, lock, None)
def internal_install(self, force=False, verbose=True): """ :param bool force: If True, re-install even if package is already installed :param bool verbose: If True, show more extensive info """ with SoftLock(self.dist_folder, timeout=system.SETTINGS.install_timeout): self.refresh_desired(force=force) self.version = self.desired.version if not self.desired.valid: return runez.abort("Can't install %s: %s", self.name, self.desired.problem) if not force and self.current.equivalent(self.desired): system.inform( self.desired.representation(verbose=verbose, note="is already installed")) self.cleanup() return prev_entry_points = self.entry_points self.effective_install() new_entry_points = self.entry_points removed = set(prev_entry_points).difference(new_entry_points) if removed: old_removed = runez.read_json(self.removed_entry_points_path, default=[], fatal=False) removed = sorted(removed.union(old_removed)) runez.save_json(removed, self.removed_entry_points_path, fatal=False) # Delete wrapper/symlinks of removed entry points immediately for name in removed: runez.delete(system.SETTINGS.base.full_path(name)) self.cleanup() self.current.set_from(self.desired) self.current.save(fatal=False) msg = "Would install" if runez.DRYRUN else "Installed" system.inform("%s %s" % (msg, self.desired.representation(verbose=verbose)))
def __enter__(self): """ Acquire lock """ cutoff = time.time() + self.timeout while self._locked(): if time.time() >= cutoff: raise SoftLockException(self.folder) time.sleep(1) # We got the soft lock runez.write(self.lock, "%s\n" % os.getpid()) if not self._should_keep(): runez.delete(self.folder, logger=LOG.debug if self.keep else None) return self
def install(self, pspec, venv, entry_points): """ Args: pspec (pickley.PackageSpec): Package spec this installation is for venv (pickley.package.PythonVenv): Virtual env where executables reside (DOT_META/<package>/...) entry_points (dict | list): Full path of executable to deliver (<base>/<entry_point>) """ if not pspec.is_clear_for_installation(): auto_uninstall(pspec.exe_path(pspec.dashed)) try: prev_manifest = pspec.get_manifest() for name in entry_points: src = venv.bin_path(name) dest = pspec.exe_path(name) if runez.DRYRUN: print("Would %s %s -> %s" % (self.short_name, short(dest), short(src))) continue if not os.path.exists(src): abort( "Can't %s %s -> %s: source does not exist" % (self.short_name, short(dest), runez.red(short(src)))) LOG.debug("%s %s -> %s" % (self.action, short(dest), short(src))) self._install(pspec, dest, src) manifest = pspec.save_manifest(entry_points) if not runez.DRYRUN and prev_manifest and prev_manifest.entrypoints: for old_ep in prev_manifest.entrypoints: if old_ep and old_ep not in entry_points: # Remove old entry points that are not in new manifest any more runez.delete(pspec.exe_path(old_ep)) if self.ping: # Touch the .ping file since this is a fresh install (no need to check for upgrades right away) runez.touch(pspec.ping_path) return manifest except Exception as e: abort("Failed to %s %s: %s" % (self.short_name, short(pspec), runez.red(e)))
def install(pspec, ping=True): delivery = DeliveryMethod.delivery_method_by_name(pspec.settings.delivery) delivery.ping = ping args = [pspec.specced] if pspec.folder: args = [pspec.folder] elif pspec._pickley_dev_mode: args = ["-e", pspec._pickley_dev_mode] # pragma: no cover, convenience case for running pickley from .venv/ venv = PythonVenv(pspec) venv.pip_install(*args) contents = PackageContents(venv, pspec) if not contents.entry_points: runez.delete(pspec.meta_path) abort("Can't install '%s', it is %s" % (runez.bold(pspec.dashed), runez.red("not a CLI"))) return delivery.install(pspec, venv, contents.entry_points)
def __init__(self, lock, venv_python): """ :param SoftLock lock: Acquired lock """ self.venv_python = venv_python self.lock = lock self.folder = lock.folder self.bin = os.path.join(self.folder, "bin") self.python = os.path.join(self.bin, "python") self.pip = os.path.join(self.bin, "pip") self._frozen = None if runez.is_younger(self.python, self.lock.keep): return runez.delete(self.folder) venv = system.virtualenv_path() if not venv: runez.abort("Can't determine path to virtualenv.py") runez.run(self.venv_python.executable, venv, self.folder)
def test_wrapper(temp_base): repeater = os.path.join(temp_base, "repeat.sh") target = os.path.join(temp_base, system.PICKLEY) runez.write(repeater, "#!/bin/bash\n\necho :: $*\n") runez.make_executable(repeater) # Actual wrapper d = DeliveryMethodWrap(system.PackageSpec(system.PICKLEY)) d.install(target, repeater) assert runez.run(target, "auto-upgrade", "foo") == RunResult(":: auto-upgrade foo", "", 0) assert runez.run(target, "--debug", "auto-upgrade", "foo") == RunResult(":: --debug auto-upgrade foo", "", 0) assert runez.run(target, "settings", "-d") == RunResult(":: settings -d", "", 0) # Verify that we're triggering background auto-upgrade as expected d.hook = "echo " d.bg = "" d.install(target, repeater) result = runez.run(target, "settings", "-d") assert "nohup" in result.output assert "repeat.sh settings -d" in result.output result = runez.run(target, "auto-upgrade", "foo") assert "nohup" not in result.output assert "repeat.sh auto-upgrade foo" in result.output result = runez.run(target, "--debug", "auto-upgrade", "foo") assert "nohup" not in result.output assert "repeat.sh --debug auto-upgrade foo" in result.output runez.delete(repeater) result = runez.run(target, "foo", fatal=False) assert result.failed assert "Please reinstall with" in result.full_output assert os.path.exists(target) assert uninstall_existing(target, fatal=False) == 1 assert not os.path.exists(target)
def _install(self, pspec, target, source): pickley = pspec.cfg.base.full_path(PICKLEY) if pspec.dashed == PICKLEY: wrapper = PICKLEY_WRAPPER else: wrapper = GENERIC_WRAPPER if runez.DEV.project_folder and not os.path.exists(pickley): # We're running from development venv pickley = pspec.cfg.program_path contents = wrapper.lstrip().format( hook=self.hook, bg=self.bg, name=runez.quoted(pspec.dashed, adapter=None), pickley=runez.quoted(pickley, adapter=None), source=runez.quoted(source, adapter=None), ) runez.delete(target, logger=False) runez.write(target, contents, logger=False) runez.make_executable(target, logger=False)
def test_edge_cases(): # Don't crash for no-ops assert runez.copy(None, None) == 0 assert runez.move(None, None) == 0 assert runez.symlink(None, None) == 0 assert runez.copy("some-file", "some-file") == 0 assert runez.move("some-file", "some-file") == 0 assert runez.symlink("some-file", "some-file") == 0 assert runez.delete("non-existing") == 0 assert runez.touch(None) == 0 assert not runez.file.is_younger("", None) assert not runez.file.is_younger("", 1) assert not runez.file.is_younger("/dev/null/not-there", 1)
def install(self, target, source): """ :param str target: Full path of executable to deliver (<base>/<entry_point>) :param str source: Path to original executable being delivered (.pickley/<package>/...) """ runez.delete(target) if runez.DRYRUN: LOG.debug("Would %s %s (source: %s)", self.implementation_name, short(target), short(source)) return if not os.path.exists(source): runez.abort("Can't %s, source %s does not exist", self.implementation_name, short(source)) try: LOG.debug("Delivering %s %s -> %s", self.implementation_name, short(target), short(source)) self._install(target, source) except Exception as e: runez.abort("Failed %s %s: %s", self.implementation_name, short(target), e)
def test_executable(temp_folder): with runez.CaptureOutput(dryrun=True) as logged: assert runez.make_executable("some-file") == 1 assert "Would make some-file executable" in logged.pop() assert runez.make_executable("some-file", logger=False) == 1 assert not logged with runez.CaptureOutput() as logged: assert runez.touch("some-file") == 1 assert "Touched some-file" in logged.pop() assert runez.delete("some-file") == 1 assert "Deleted some-file" in logged.pop() assert runez.touch("some-file", logger=logging.debug) == 1 assert "Touched some-file" in logged.pop() assert runez.make_executable("some-file", logger=logging.debug) == 1 assert "Made 'some-file' executable" in logged.pop() assert runez.is_executable("some-file") assert runez.make_executable("some-file") == 0 assert not logged assert runez.touch("some-file", logger=False) == 1 assert runez.delete("some-file", logger=False) == 1 assert not runez.is_executable("some-file") assert not logged assert runez.make_executable("/dev/null/some-file", fatal=False) == -1 assert "does not exist, can't make it executable" in logged.pop() assert runez.make_executable("/dev/null/some-file", fatal=False, logger=None) == -1 # Don't log anything assert not logged assert runez.make_executable("/dev/null/some-file", fatal=False, logger=False) == -1 # Log errors only assert "does not exist, can't make it executable" in logged.pop()
def test_file_operations(temp_folder): runez.symlink("foo", "dangling-symlink", must_exist=False) runez.move("dangling-symlink", "dangling-symlink2") assert os.path.islink("dangling-symlink2") runez.write("README.md", "hello") runez.copy("README.md", "sample1/README.md") runez.copy("sample1", "sample2") runez.move("sample1/README.md", "sample1/foo") # overwrite=None "merges" dir contents runez.copy("sample1", "sample2", overwrite=None) assert dir_contents("sample2") == {"README.md": ["hello"], "foo": ["hello"]} # overwrite=True replaces dir runez.copy("sample1", "sample2", overwrite=True) assert dir_contents("sample2") == {"foo": ["hello"]} # overwrite=None, source is a dir, existing destination file gets replaced by source directory runez.copy("sample1", "sample2/foo", overwrite=None) assert dir_contents("sample2") == {"foo": {"foo": ["hello"]}} with runez.CaptureOutput(dryrun=True) as logged: assert runez.ensure_folder("some-folder", fatal=False) == 1 assert "Would create" in logged.pop() assert runez.touch("some-file", logger=logging.debug) == 1 assert "Would touch some-file" in logged.pop() assert runez.copy("some-file", "bar") == 1 assert "Would copy some-file -> bar" in logged.pop() assert runez.move("some-file", "bar") == 1 assert "Would move some-file -> bar" in logged.pop() assert runez.symlink("some-file", "bar") == 1 assert "Would symlink some-file <- bar" in logged.pop() assert runez.delete(temp_folder) == 1 assert "Would delete" in logged.pop() assert runez.copy("some-folder/bar", "some-folder", fatal=False) == -1 assert "source contained in destination" in logged.pop() assert runez.move("some-folder/bar/baz", "some-folder", fatal=False) == -1 assert "source contained in destination" in logged.pop() assert runez.symlink("some-folder/bar/baz", "some-folder", fatal=False) == -1 assert "source contained in destination" in logged.pop()
def groom_installation(self, keep_for=60): """ Args: keep_for (int): Minimum time in minutes for how long to keep the previous latest version """ if self.cfg.cache.contains(self.folder): runez.delete(self.folder, logger=False) current = self.get_manifest() meta_path = self.meta_path current_age = None if current and os.path.isdir(meta_path): now = time.time() candidates = [] for fname in os.listdir(meta_path): if fname.startswith("."): # Pickley meta files start with '.' continue fpath = os.path.join(meta_path, fname) vpart = fname[len(self.dashed) + 1:] age = now - os.path.getmtime(fpath) if vpart == current.version: current_age = age else: version = Version(vpart) if not version.is_valid: # Not a proper installation runez.delete(fpath, fatal=False) continue # Different version, previously installed candidates.append((age, version, fpath)) if not candidates: return candidates = sorted(candidates) youngest = candidates[0] for candidate in candidates[1:]: runez.delete(candidate[2], fatal=False) if current_age: current_age = min(current_age, youngest[0]) if current_age > (keep_for * runez.date.SECONDS_IN_ONE_MINUTE): runez.delete(youngest[2], fatal=False)
def test_executable(temp_folder): with runez.CaptureOutput(dryrun=True) as logged: assert runez.make_executable("some-file") == 1 assert "Would make some-file executable" in logged assert runez.touch("some-file") == 1 assert runez.make_executable("some-file") == 1 assert runez.is_executable("some-file") assert runez.make_executable("some-file") == 0 assert runez.delete("some-file") == 1 assert not runez.is_executable("some-file") with runez.CaptureOutput() as logged: assert runez.make_executable("/dev/null/some-file", fatal=False) == -1 assert "does not exist, can't make it executable" in logged
def uninstall(force, packages): """ Uninstall packages """ packages = system.SETTINGS.resolved_packages(packages) errors = 0 for name in packages: p = PACKAGERS.resolved(name) if not force and not p.current.file_exists: errors += 1 LOG.error("%s was not installed with pickley", name) continue eps = p.entry_points ep_uninstalled = 0 ep_missed = 0 meta_deleted = runez.delete(system.SETTINGS.meta.full_path(name), fatal=False) if not eps and force: eps = {name: ""} if eps and meta_deleted >= 0: for entry_point in eps: path = system.SETTINGS.base.full_path(entry_point) handler = runez.delete if meta_deleted > 0 else uninstall_existing r = handler(path, fatal=False) if r < 0: ep_missed += 1 elif r > 0: ep_uninstalled += 1 if ep_missed or meta_deleted < 0: # Error was already reported errors += 1 continue if ep_uninstalled + meta_deleted == 0: system.inform("Nothing to uninstall for %s" % name) continue message = "Would uninstall" if runez.DRYRUN else "Uninstalled" message = "%s %s" % (message, name) if ep_uninstalled > 1: message += " (%s entry points)" % ep_uninstalled system.inform(message) if errors: sys.exit(1)
def test_failure(monkeypatch): monkeypatch.setattr(io, "open", runez.conftest.exception_raiser()) monkeypatch.setattr(os, "unlink", runez.conftest.exception_raiser("bad unlink")) monkeypatch.setattr(shutil, "copy", runez.conftest.exception_raiser()) monkeypatch.setattr(os.path, "exists", lambda _: True) monkeypatch.setattr(os.path, "isfile", lambda _: True) monkeypatch.setattr(os.path, "getsize", lambda _: 10) with runez.CaptureOutput() as logged: with patch("runez.file._do_delete"): with patch("pathlib.Path.exists", return_value=True): assert runez.copy("some-file", "bar", fatal=False) == -1 assert "Can't copy" in logged.pop() assert runez.delete("some-file", fatal=False) == -1 assert "Can't delete" in logged assert "bad unlink" in logged.pop() assert runez.write("bar", "some content", fatal=False) assert "Can't write" in logged.pop() if not runez.SYS_INFO.platform_id.is_windows: assert runez.make_executable("some-file", fatal=False) == -1 assert "Can't chmod" in logged.pop()
def test_install(cli): cli.expect_success("--dryrun --delivery wrap install tox", "Would wrap", "Would install tox") cli.expect_success("--dryrun --delivery symlink install tox", "Would symlink", "Would install tox") assert not os.path.exists(".pickley/audit.log") cli.expect_failure("check tox", "is not installed") cli.expect_failure("install six", "'six' is not a CLI") # Install tox, but add a few files + a bogus previous entry point to test cleanup runez.write(".pickley/tox/.entry-points.json", '["tox-old1", "tox-old2"]\n') runez.touch("tox-old1") runez.touch(".pickley/tox/tox-0.1/bin") runez.touch(".pickley/tox/tox-0.2/bin") runez.touch(".pickley/tox/tox-0.3/bin") runez.touch(".pickley/tox/tox-old1-0.1") runez.touch(".pickley/tox/tox-old1-0.2") cli.expect_success("--delivery wrap install tox", "Installed tox") # Old entry point removed immediately assert not os.path.exists("tox-old1") # Only 1 cleaned up immediately (latest + 1 kept) assert not os.path.exists(".pickley/tox/tox-0.1") assert not os.path.exists(".pickley/tox/tox-0.2") assert os.path.exists(".pickley/tox/tox-0.3") assert not os.path.exists(".pickley/tox/tox-old1-0.1") assert os.path.exists(".pickley/tox/tox-old1-0.2") assert runez.is_executable("tox") result = run_program("tox", "--version") assert "tox" in result.output cli.expect_success("auto-upgrade tox", "Skipping auto-upgrade") runez.delete(system.SETTINGS.meta.full_path("tox", ".ping")) cli.expect_success("auto-upgrade tox", "already installed") cli.expect_success("copy .pickley/tox tox-copy", "Copied") cli.expect_success("move tox-copy tox-relocated", "Moved") runez.delete("tox-relocated") # Verify that older versions and removed entry-points do get cleaned up runez.save_json({"install_timeout": 0}, "custom-timeout.json") cli.expect_success("-ccustom-timeout.json install tox", "already installed") # All cleaned up when enough time went by assert not os.path.exists(".pickley/tox/tox-0.3") assert not os.path.exists(".pickley/tox/tox-old1-0.2") cli.expect_success("check", "tox", "is installed") cli.expect_success( "check --verbose", "tox", "is installed (as %s wrap, channel: " % system.VENV_PACKAGER) # Simulate new version available latest = runez.read_json(".pickley/tox/.latest.json") latest["version"] = "10000.0" runez.save_json(latest, ".pickley/tox/.latest.json") cli.expect_failure("check", "tox", "can be upgraded to 10000.0") # Latest twine 2.0 requires py3 cli.expect_success("-ppex install twine==1.14.0", "Installed twine") cli.expect_success("list", "tox", "twine") cli.expect_success("list --verbose", "tox", "twine") assert find_uninstaller("tox") assert find_uninstaller("twine") cli.expect_success("uninstall twine", "Uninstalled twine") runez.write(".pickley/tox/.current.json", "") cli.expect_failure("check", "tox", "Couldn't read", "is not installed") cli.expect_success("uninstall --all", "Uninstalled tox", "entry points") assert not os.path.exists("tox") assert not os.path.exists(".pickley")