Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package fuse-oscfs for openSUSE:Factory checked in at 2023-10-20 23:18:46 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/fuse-oscfs (Old) and /work/SRC/openSUSE:Factory/.fuse-oscfs.new.1945 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "fuse-oscfs" Fri Oct 20 23:18:46 2023 rev:6 rq:1119201 version:0.9.0 Changes: -------- --- /work/SRC/openSUSE:Factory/fuse-oscfs/fuse-oscfs.changes 2022-02-23 16:26:59.455509991 +0100 +++ /work/SRC/openSUSE:Factory/.fuse-oscfs.new.1945/fuse-oscfs.changes 2023-10-20 23:21:55.889825592 +0200 @@ -1,0 +2,24 @@ +Fri Oct 20 09:27:57 UTC 2023 - matthias.gerst...@suse.com + +- Update to version 0.9.0: + * NEWS: changes for next release + * regtest: compatibility with newer OSC module versions + * obs: implement transparent retry to prevent HTTP 503 woes + * obs: remove extraneous newlines from project and package meta output + * error handling: support full exception backtrace report + * debugging: support writing stdout and stderr to a logfile for analysis + * regtest: fix unmount in early auth error failure case + * regtest: make sure also to remove cookiejar for early auth error test + * Revert "regtest: add some extra robustness against potential umount races" + * regtest: add some extra robustness against potential umount races + * fix regressions introduced by commit cd692a1d2f9fabe4ac5451832c3b7df109302c07 + * urlopenwrapper: fix sshkey check + * Package: fix symlink path to update package + * urlopen wrapper: support disabling the wrapper on the command line + * obs.getBinaries(): fix strange osc behaviour if zero length files appear + * add command line option to disable build artifacts caching + * module_helper: insert local modules directory at the front of the path + * oscfs wrapper script: use explicitly python3 by default + * bugfix: fix early auth check on non-OBS instance + +------------------------------------------------------------------- Old: ---- oscfs-v0.8.1.tar.xz New: ---- oscfs-0.9.0.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ fuse-oscfs.spec ++++++ --- /var/tmp/diff_new_pack.XlfGXf/_old 2023-10-20 23:21:56.433845442 +0200 +++ /var/tmp/diff_new_pack.XlfGXf/_new 2023-10-20 23:21:56.433845442 +0200 @@ -1,7 +1,7 @@ # # spec file for package fuse-oscfs # -# Copyright (c) 2022 SUSE LLC +# Copyright (c) 2023 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,13 +17,13 @@ Name: fuse-oscfs -Version: 0.8.1 +Version: 0.9.0 Release: 0 Summary: A FUSE file system for accessing Open Build Service instances License: GPL-2.0-or-later Group: System/Filesystems URL: https://github.com/mgerstner/oscfs -Source: oscfs-v%{version}.tar.xz +Source: oscfs-%{version}.tar.xz BuildRequires: python-rpm-macros BuildRequires: python3-setuptools Requires: osc @@ -40,7 +40,7 @@ access for inspecting packages and their metadata. %prep -%setup -q -n oscfs-v%{version} +%setup -q -n oscfs-%{version} %build python3 setup.py build ++++++ _service ++++++ --- /var/tmp/diff_new_pack.XlfGXf/_old 2023-10-20 23:21:56.461846463 +0200 +++ /var/tmp/diff_new_pack.XlfGXf/_new 2023-10-20 23:21:56.465846609 +0200 @@ -2,7 +2,7 @@ <service name="tar_scm" mode="disabled"> <param name="url">https://github.com/mgerstner/oscfs.git</param> <param name="scm">git</param> - <param name="revision">v0.8.1</param> + <param name="revision">0.9.0</param> <param name="versionformat">@PARENT_TAG@</param> <param name="changesgenerate">enable</param> </service> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.XlfGXf/_old 2023-10-20 23:21:56.481847192 +0200 +++ /var/tmp/diff_new_pack.XlfGXf/_new 2023-10-20 23:21:56.485847339 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/mgerstner/oscfs.git</param> - <param name="changesrevision">1153b097df63f57552bce553ddba3944a55e065d</param></service></servicedata> + <param name="changesrevision">efa4a91b6fbe54a33eb51c04728706555e5106c6</param></service></servicedata> (No newline at EOF) ++++++ oscfs-v0.8.1.tar.xz -> oscfs-0.9.0.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/oscfs-v0.8.1/.gitignore new/oscfs-0.9.0/.gitignore --- old/oscfs-v0.8.1/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/oscfs-0.9.0/.gitignore 2023-10-20 11:03:57.000000000 +0200 @@ -0,0 +1,3 @@ +*.pyc +__pycache__ +*.swp diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/oscfs-v0.8.1/NEWS.rst new/oscfs-0.9.0/NEWS.rst --- old/oscfs-v0.8.1/NEWS.rst 2022-02-23 11:16:38.000000000 +0100 +++ new/oscfs-0.9.0/NEWS.rst 2023-10-20 11:03:57.000000000 +0200 @@ -1,3 +1,22 @@ +0.9.0 +===== + +- use Python3 by default now. +- various small bugfixes: + - fix error if a binary build artifact has zero file size + - fix symlink target in package update links (symlink to maintenance + incidents). +- new --no-urlopen-wrapper command line option to disable the HTTP connection + reuse hack. This is helpful when working against build.suse.de, where + two-factor-authentication is now required and the hack doesn't work with + that anymore. +- new --no-bin-cache command line option to disable caching of large binary files. +- new --use-logfile command line option to write errors and debugging + information to file. This is helpful when oscfs is running as a daemon and + stdout/stderr are discarded. +- implements transparent retry if the OBS server responds with HTTP status 503 + (service unavailable). + 0.8.1 ===== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/oscfs-v0.8.1/bin/module_helper.py new/oscfs-0.9.0/bin/module_helper.py --- old/oscfs-v0.8.1/bin/module_helper.py 2022-02-23 11:16:38.000000000 +0100 +++ new/oscfs-0.9.0/bin/module_helper.py 2023-10-20 11:03:57.000000000 +0200 @@ -9,7 +9,7 @@ """Adds .. to the current module path, tries to import $module and exits if it is not found.""" # get the full path of the parent directory and append the name parent_dir = os.path.realpath(os.path.dirname(os.path.dirname(__file__))) - sys.path.append(parent_dir) + sys.path.insert(0, parent_dir) # check if the module can be loaded now if pkgutil.find_loader(module) is None: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/oscfs-v0.8.1/bin/oscfs new/oscfs-0.9.0/bin/oscfs --- old/oscfs-v0.8.1/bin/oscfs 2022-02-23 11:16:38.000000000 +0100 +++ new/oscfs-0.9.0/bin/oscfs 2023-10-20 11:03:57.000000000 +0200 @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 try: import module_helper # noqa: F401 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/oscfs-v0.8.1/oscfs/fs.py new/oscfs-0.9.0/oscfs/fs.py --- old/oscfs-v0.8.1/oscfs/fs.py 2022-02-23 11:16:38.000000000 +0100 +++ new/oscfs-0.9.0/oscfs/fs.py 2023-10-20 11:03:57.000000000 +0200 @@ -6,9 +6,16 @@ # third party modules import fuse +# explicitly parse this option since we need to import the wrapper before +# oscfs.obs is imported +if "--no-urlopen-wrapper" not in sys.argv: + # urllib2 replacement, needs to be imported before osc.core + import oscfs.urlopenwrapper + # local modules import oscfs.obs import oscfs.root +from oscfs.package import BinaryFileNode class OscFs(fuse.LoggingMixIn, fuse.Operations): @@ -53,6 +60,16 @@ help="If set then PTF projects will be included which is not the default" ) self.m_parser.add_argument( + "--no-bin-cache", action='store_true', + help="If set then binary files like RPMs and other build artifacts will not be cached. This prevents linearly increasing memory usage in case a lot of these files are accessed over time." + ) + self.m_parser.add_argument( + "--no-urlopen-wrapper", action='store_true', + help="Disable use of a hack to replace urllib's urlopen function to improve performance." + ) + self.m_parser.add_argument( + "--use-logfile", nargs='?', const=f'/run/user/{os.getuid()}/oscfs.log.{os.getpid()}') + self.m_parser.add_argument( "mountpoint", type=str, help="Path where to mount the file system" ) @@ -91,10 +108,10 @@ print("HTTP error occured trying to access the remote server:") print(e) except Exception as e: - print( - "Accessing the remote server failed:", - e, file=sys.stderr - ) + if str(e).find("unknown_project") != -1: + # 404 on XML level + return + print("Accessing the remote server failed:", e, file=sys.stderr) raise sys.exit(1) @@ -109,13 +126,29 @@ except KeyError: raise fuse.FuseOSError(errno.ENOENT) + def _setupLogfile(self): + + logfile = self.m_args.use_logfile + + if not logfile: + return + + print("Redirecting output to logfile in", logfile) + + lf = open(logfile, 'a') + sys.stdout = lf + sys.stderr = lf + def run(self): self.m_args = self.m_parser.parse_args() + self._setupLogfile() self.m_obs.configure(self.m_args.apiurl) self.m_root = oscfs.root.Root(self.m_obs, self.m_args) if self.m_args.cache_time is not None: oscfs.types.Node.setMaxCacheTime(self.m_args.cache_time) + if self.m_args.no_bin_cache: + BinaryFileNode.cache_binaries = False self._checkAuth() @@ -176,10 +209,13 @@ self.m_handles[fd] = node + node.incUsers() + return fd def _freeFileHandle(self, fh): + self.m_handles[fh].decUsers() self.m_handles[fh] = None self.m_free_handles.append(fh) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/oscfs-v0.8.1/oscfs/misc.py new/oscfs-0.9.0/oscfs/misc.py --- old/oscfs-v0.8.1/oscfs/misc.py 2022-02-23 11:16:38.000000000 +0100 +++ new/oscfs-0.9.0/oscfs/misc.py 2023-10-20 11:03:57.000000000 +0200 @@ -34,6 +34,27 @@ return "Exception in {}:{}: {}".format(fn, ln, str(ex)) +def getExceptionTrace(ex): + import sys + import traceback + _, _, tb = sys.exc_info() + first = True + ret = [] + for frame in traceback.extract_tb(tb): + fn, ln, func, ln_text = frame + + ln = str(ln).rjust(4) + + if first: + line = f"{fn}:{ln} {func}(): {str(ex)}" + ret.append(line) + first = False + else: + ret.append(f"{fn}:{ln} {func}()") + + return '\n'.join(ret) + + def printException(ex): """Prints the currently active exception in a friendly, compact way to stderr.""" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/oscfs-v0.8.1/oscfs/obs.py new/oscfs-0.9.0/oscfs/obs.py --- old/oscfs-v0.8.1/oscfs/obs.py 2022-02-23 11:16:38.000000000 +0100 +++ new/oscfs-0.9.0/oscfs/obs.py 2023-10-20 11:03:57.000000000 +0200 @@ -2,12 +2,11 @@ from xml.etree import cElementTree as et import datetime -# urllib2 replacement, needs to be imported before osc.core -import oscfs.urlopenwrapper - # third party modules import osc.core +from oscfs.retry_decorator import transparent_retry + class Obs: """Wrapper around the osc python module for the purposes of this file @@ -21,49 +20,47 @@ import osc.conf self.m_apiurl = apiurl try: - osc.conf.get_config() + osc.conf.get_config(override_apiurl=apiurl) except osc.oscerr.NoConfigfile: raise Exception("No .oscrc config file found. Please configure OSC first.") except osc.oscerr.ConfigError as e: raise Exception(f"No valid configuration found in .oscrc: {e}. Please configure OSC first.") - osc.conf.config["apiurl"] = apiurl - def getUser(self): return osc.conf.config["user"] + @transparent_retry() def getProjectList(self): """Returns a list of the top-level projects of the current OBS instance. It's a list of plain strings.""" - ret = osc.core.meta_get_project_list(self.m_apiurl) - - return ret + return osc.core.meta_get_project_list(self.m_apiurl) + @transparent_retry(expect_xml=True) def getProjectMeta(self, project): """Returns a string consisting of the XML that makes up the specified project's metadata.""" xml_lines = osc.core.show_project_meta(self.m_apiurl, project) - - return '\n'.join([line.decode() for line in xml_lines]) + return ''.join([line.decode() for line in xml_lines]) def getProjectInfo(self, project): """Returns an object of type ProjectInfo for the given project.""" + xml = self.getProjectMeta(project) return ProjectInfo(xml) + @transparent_retry() def getPackageList(self, project): """Returns a list of all the packages within a top-level project. It's a list of plain strings.""" - ret = osc.core.meta_get_packagelist(self.m_apiurl, project) - - return ret + return osc.core.meta_get_packagelist(self.m_apiurl, project) + @transparent_retry(expect_xml=True) def _getPackageFileTree(self, project, package, revision=None): - xml = osc.core.show_files_meta( + return osc.core.show_files_meta( self.m_apiurl, project, package, @@ -71,19 +68,17 @@ meta=False ) - tree = et.fromstring(xml) - return tree - def getPackageFileList(self, project, package, revision=None): """Returns a list of the files belonging to a package. The list is comprised of tuples of the form (name, size, modtime).""" - tree = self._getPackageFileTree( + xml = self._getPackageFileTree( project, package, revision=revision ) + tree = et.fromstring(xml) ret = [] # if this is a linked package then we find two nodes @@ -126,6 +121,7 @@ return ret + @transparent_retry() def getPackageRequestList(self, project, package, states=None): """Returns a list of osc.core.Request instances representing the existing requests for the given project/package. @@ -164,6 +160,7 @@ comps = ['build', project, repo, arch, package, _file] return self._download(comps) + @transparent_retry() def _download(self, urlcomps, query=dict()): import urllib.parse @@ -182,6 +179,7 @@ return f.read() + @transparent_retry() def _getPackageRevisions(self, project, package, fmt): """Returns the list of revisions for the given project/package path in the given fmt. @fmt can be any of ('text, 'csv', @@ -237,6 +235,7 @@ key=lambda r: r.getRevision() ) + @transparent_retry(expect_xml=True) def getPackageMeta(self, project, package): """Returns a string consisting of the XML that makes up the specified package's metadata.""" @@ -247,7 +246,7 @@ package ) - return '\n'.join([line.decode() for line in xml_lines]) + return ''.join([line.decode() for line in xml_lines]) def getPackageInfo(self, project, package): """Returns an object of type PackageInfo for the given @@ -255,6 +254,7 @@ xml = self.getPackageMeta(project, package) return PackageInfo(xml) + @transparent_retry() def getBuildlog(self, project, package, repo, arch): """Returns the plaintext build log for the given build configuration. This can be empty if the build is not @@ -268,16 +268,17 @@ "_log?start=0&nostream=1" ]) - ret = b"" - # NOTE: streamfile supports bufsize="line" to read line wise # and supports yield semantics. This breaks with our - # urlopenwrapper hack, however so we don't use it. + # urlopenwrapper hack, however, so we don't use it. + ret = b"" + for line in osc.core.streamfile(url): ret += line return ret + @transparent_retry(expect_xml=True) def getBuildResultsMeta(self, project, package, repo=[], arch=[]): """Returns XML data describing the build status of the given project/package combination. If repo and arch are given then @@ -304,6 +305,7 @@ return BuildResultList(xml) + @transparent_retry() def getBinaryList(self, project, package, repo, arch): """Returns a list of tuples representing the binary artifacts of the given build configuration. Each tuple @@ -315,7 +317,8 @@ verbose=True ) - return [(f.name, f.mtime, f.size) for f in ret] + # NOTE: this can return None if the size is zero, this is fixing that + return [(f.name, f.mtime, 0 if f.size is None else f.size) for f in ret] class CommitInfo: @@ -714,6 +717,9 @@ self.reset() tree = et.fromstring(meta_xml) + if tree.tag == "status": + raise Exception(f"failed to retrieve project info: {tree.attrib['code']}") + name = tree.attrib["name"] self.setName(name) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/oscfs-v0.8.1/oscfs/package.py new/oscfs-0.9.0/oscfs/package.py --- old/oscfs-v0.8.1/oscfs/package.py 2022-02-23 11:16:38.000000000 +0100 +++ new/oscfs-0.9.0/oscfs/package.py 2023-10-20 11:03:57.000000000 +0200 @@ -209,7 +209,7 @@ for rev in range(len(infos)): info = infos[rev] - commit = "{rev + 1}" + commit = f"{rev + 1}" self.m_entries[commit] = CommitNode(self, info, commit) @@ -523,6 +523,8 @@ class BinaryFileNode(oscfs.types.FileNode): """This type returns a certain binary artifact's data upon read.""" + cache_binaries = True + def __init__(self, parent, project, package, repo, arch, binary): super(BinaryFileNode, self).__init__(parent, binary[0]) @@ -551,6 +553,10 @@ content = obs.getBinaryFileContent(*args) self.setContent(content) + def noUsersLeft(self): + if not self.cache_binaries: + self.dropCache() + class BinariesDir(oscfs.types.DirNode): """This type provides access to a repository/arch hierarchy that diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/oscfs-v0.8.1/oscfs/retry_decorator.py new/oscfs-0.9.0/oscfs/retry_decorator.py --- old/oscfs-v0.8.1/oscfs/retry_decorator.py 1970-01-01 01:00:00.000000000 +0100 +++ new/oscfs-0.9.0/oscfs/retry_decorator.py 2023-10-20 11:03:57.000000000 +0200 @@ -0,0 +1,58 @@ +import urllib + + +def _logRetry(ex): + from oscfs.misc import getExceptionTrace + from sys import stderr + trace = getExceptionTrace(ex) + print(f"HTTP status 503 service unavailable transparent retry occured:\n{trace}\n", file=stderr) + + +# OBS randomly fails with HTTP 503 "service unavailable". +# +# This can happen both via HTTPError exceptions or via a document of +# this form returned instead of XML: +# +# <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">\n<html><head>\n<title>503 Service Unavailable</title>\n</head><body>\n<h1>Service Unavailable</h1>\n<p>The server is temporarily unable to service your\nrequest due to maintenance downtime or capacity\nproblems. Please try again later.</p>\n<p>Additionally, a 503 Service Unavailable\nerror was encountered while trying to use an ErrorDocument to handle the request.</p>\n</body></html> +# +# There currently seems no easy way to solve this on the server end, so +# we are required to transparently retry in this case. Ugly. +# +# This is a function decorator that easily allows to add transparent retry +# behaviour to affected OBS API calls. +def transparent_retry(expect_xml=False): + + def inner_decorator(func): + + def retry_loop(*args, **kwargs): + while True: + try: + ret = func(*args, **kwargs) + except urllib.error.HTTPError as e: + if e.code == 503: + _logRetry(e) + continue + # on any other error simply re-raise the exception to the + # original caller + raise + + if expect_xml: + try: + text = ret.decode() + except Exception: + text = ret + + # heuristic to detect this, the osc module seems to fail to + # detect the error status, or there is none sent by the server. + if text.lower().find("503 service unavailable") != -1: + try: + raise Exception("503 transparent retry (xml)") + except Exception as e: + _logRetry(e) + continue + + return ret + + return retry_loop + + return inner_decorator diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/oscfs-v0.8.1/oscfs/types.py new/oscfs-0.9.0/oscfs/types.py --- old/oscfs-v0.8.1/oscfs/types.py 2022-02-23 11:16:38.000000000 +0100 +++ new/oscfs-0.9.0/oscfs/types.py 2023-10-20 11:03:57.000000000 +0200 @@ -115,11 +115,26 @@ self.setType(_type) self.m_last_updated = None self.m_auto_clear_on_update = True + self.m_num_users = 0 @classmethod def setMaxCacheTime(cls, seconds): cls.max_cache_time = datetime.timedelta(seconds=seconds) + def incUsers(self): + self.m_num_users += 1 + + def decUsers(self): + self.m_num_users -= 1 + + if self.m_num_users == 0: + self.noUsersLeft() + + def noUsersLeft(self): + """Can be overriden by child classes to react on the last open file + description being gone for this node.""" + pass + def getStat(self): return self.m_stat @@ -174,7 +189,7 @@ def isRoot(self): """Returns whether this node is the root node of the file system.""" - return self.m_parent is not None + return self.m_parent is None def getRoot(self): """Returns the root node of the file system.""" @@ -224,9 +239,9 @@ try: self.update() except Exception as e: - print("Failed to update {}: {}".format( + print("Failed to update {}:\n{}".format( self.getName(), - oscfs.misc.getFriendlyException(e), + oscfs.misc.getExceptionTrace(e), ), file=sys.stderr) raise fuse.FuseOSError(errno.EFAULT) self.setCacheFresh() @@ -255,6 +270,9 @@ if date: stat.setModTime(date) + def dropCache(self): + self.m_content = None + def setUseCache(self, on_off): self.m_use_cache = on_off diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/oscfs-v0.8.1/oscfs/urlopenwrapper.py new/oscfs-0.9.0/oscfs/urlopenwrapper.py --- old/oscfs-v0.8.1/oscfs/urlopenwrapper.py 2022-02-23 11:16:38.000000000 +0100 +++ new/oscfs-0.9.0/oscfs/urlopenwrapper.py 2023-10-20 11:03:57.000000000 +0200 @@ -55,13 +55,25 @@ # add user/password, but only if it's an encrypted connection if proto == 'https' and api_conf: - userpw = "{}:{}".format( - api_conf["user"], api_conf["pass"] - ) - auth = "Basic {}".format( - base64.b64encode(userpw.encode()).decode() - ) - req.add_unredirected_header("Authorization", auth) + if "sshkey" in api_conf and api_conf["sshkey"] is not None: + raise Exception("Cannot use urlopenwrapper hack, because sshkey usage is configured for authentication. Consider using --no-urlopen-wrapper.") + else: + # the reason that we have to mimic the header authorization + # code from the osc module is that the osc module uses a + # global urllib2 opener handler mechanism that is pretty + # complicated ... normally after getting a HTTP 401 error this + # opener handler would only lazily add the required + # authorization parameters. I didn't find a simple way to call + # into this logic from this wrapper here yet. This also means + # that the new SSH key 2 factor authentication breaks when + # using this wrapper. + userpw = "{}:{}".format( + api_conf["user"], api_conf["pass"] + ) + auth = "Basic {}".format( + base64.b64encode(userpw.encode()).decode() + ) + req.add_unredirected_header("Authorization", auth) connection = self._getConnection(proto, host) retries = 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/oscfs-v0.8.1/tests/regtest.py new/oscfs-0.9.0/tests/regtest.py --- old/oscfs-v0.8.1/tests/regtest.py 2022-02-23 11:16:38.000000000 +0100 +++ new/oscfs-0.9.0/tests/regtest.py 2023-10-20 11:03:57.000000000 +0200 @@ -37,6 +37,13 @@ import osc.conf # determines the active oscrc configuration file self.m_oscrc_config = os.path.expanduser(osc.conf.identify_conf()) + try: + options = osc.conf.Options() + # determines the active cookie jar file which caches authentication + self.m_oscrc_cookiejar = os.path.expanduser(options.cookiejar) + except AttributeError: + # in older OSC versions this was only available this way + self.m_oscrc_cookiejar = os.path.expanduser(osc.conf._identify_osccookiejar()) def _lookupOscFsBin(self): @@ -102,6 +109,12 @@ # could cause a blocking child process when the pipe is full # ... + def _fuserumount(self): + """Uses fusermount to unmount a potential existing oscfs mount. This + is useful for background instances of oscfs. Errors are not reported""" + + subprocess.call(["fusermount", "-u", self.m_mnt_dir]) + def umount(self): if not self.m_oscfs_proc: @@ -217,6 +230,12 @@ """, file=oscrc) + # make sure any cached authentication is also removed for this test + try: + os.remove(self.m_oscrc_cookiejar) + except Exception: + pass + # this is actually more complex, osc chokes on various other # conditions like config file not being there, config for apiurl # missing etc. @@ -240,6 +259,9 @@ self.m_oscfs_proc = None if res != 1 or not found_auth_error: + # it seems oscfs runs in the background now, explicitly + # unmount via fusermount + self._fuserumount() raise Exception("No authentication error code or message was reported: code = {}".format(res)) finally: self._restoreOscConfig()