From 07789a23e9c513dba87b020fae2989a57955e8a6 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Sun, 6 Dec 2020 16:01:59 +0000 Subject: Tests: options moved to the separate class. This change is necessary to separate the logic and prevent possible circular dependency. --- test/unit/applications/lang/go.py | 2 +- test/unit/applications/lang/java.py | 2 +- test/unit/applications/lang/node.py | 2 +- test/unit/applications/lang/perl.py | 2 +- test/unit/applications/lang/php.py | 2 +- test/unit/applications/lang/python.py | 2 +- test/unit/applications/lang/ruby.py | 2 +- test/unit/applications/proto.py | 2 +- test/unit/applications/tls.py | 2 +- test/unit/control.py | 2 +- test/unit/feature/isolation.py | 2 +- test/unit/http.py | 2 +- test/unit/main.py | 2 +- test/unit/option.py | 16 ++++++++++++++++ 14 files changed, 29 insertions(+), 13 deletions(-) create mode 100644 test/unit/option.py (limited to 'test/unit') diff --git a/test/unit/applications/lang/go.py b/test/unit/applications/lang/go.py index 866dec47..70f9d58c 100644 --- a/test/unit/applications/lang/go.py +++ b/test/unit/applications/lang/go.py @@ -1,8 +1,8 @@ import os import subprocess -from conftest import option from unit.applications.proto import TestApplicationProto +from unit.option import option class TestApplicationGo(TestApplicationProto): diff --git a/test/unit/applications/lang/java.py b/test/unit/applications/lang/java.py index 0ff85187..b2e17f23 100644 --- a/test/unit/applications/lang/java.py +++ b/test/unit/applications/lang/java.py @@ -4,8 +4,8 @@ import shutil import subprocess import pytest -from conftest import option from unit.applications.proto import TestApplicationProto +from unit.option import option class TestApplicationJava(TestApplicationProto): diff --git a/test/unit/applications/lang/node.py b/test/unit/applications/lang/node.py index 98fd9ffc..baccef7e 100644 --- a/test/unit/applications/lang/node.py +++ b/test/unit/applications/lang/node.py @@ -1,9 +1,9 @@ import shutil from urllib.parse import quote -from conftest import option from conftest import public_dir from unit.applications.proto import TestApplicationProto +from unit.option import option class TestApplicationNode(TestApplicationProto): diff --git a/test/unit/applications/lang/perl.py b/test/unit/applications/lang/perl.py index 9dc24ace..58b867f0 100644 --- a/test/unit/applications/lang/perl.py +++ b/test/unit/applications/lang/perl.py @@ -1,5 +1,5 @@ -from conftest import option from unit.applications.proto import TestApplicationProto +from unit.option import option class TestApplicationPerl(TestApplicationProto): diff --git a/test/unit/applications/lang/php.py b/test/unit/applications/lang/php.py index 3dbb32f5..90c0078c 100644 --- a/test/unit/applications/lang/php.py +++ b/test/unit/applications/lang/php.py @@ -1,8 +1,8 @@ -from conftest import option import os import shutil from unit.applications.proto import TestApplicationProto +from unit.option import option class TestApplicationPHP(TestApplicationProto): diff --git a/test/unit/applications/lang/python.py b/test/unit/applications/lang/python.py index 792a86fa..287d23f0 100644 --- a/test/unit/applications/lang/python.py +++ b/test/unit/applications/lang/python.py @@ -3,8 +3,8 @@ import shutil from urllib.parse import quote import pytest -from conftest import option from unit.applications.proto import TestApplicationProto +from unit.option import option class TestApplicationPython(TestApplicationProto): diff --git a/test/unit/applications/lang/ruby.py b/test/unit/applications/lang/ruby.py index 82d66e65..02644584 100644 --- a/test/unit/applications/lang/ruby.py +++ b/test/unit/applications/lang/ruby.py @@ -1,5 +1,5 @@ -from conftest import option from unit.applications.proto import TestApplicationProto +from unit.option import option class TestApplicationRuby(TestApplicationProto): diff --git a/test/unit/applications/proto.py b/test/unit/applications/proto.py index 6e760c70..af05d071 100644 --- a/test/unit/applications/proto.py +++ b/test/unit/applications/proto.py @@ -2,8 +2,8 @@ import os import re import time -from conftest import option from unit.control import TestControl +from unit.option import option class TestApplicationProto(TestControl): diff --git a/test/unit/applications/tls.py b/test/unit/applications/tls.py index fb1b112c..b0cd5abb 100644 --- a/test/unit/applications/tls.py +++ b/test/unit/applications/tls.py @@ -2,8 +2,8 @@ import os import ssl import subprocess -from conftest import option from unit.applications.proto import TestApplicationProto +from unit.option import option class TestApplicationTLS(TestApplicationProto): diff --git a/test/unit/control.py b/test/unit/control.py index f05aa827..3008a64b 100644 --- a/test/unit/control.py +++ b/test/unit/control.py @@ -1,7 +1,7 @@ import json -from conftest import option from unit.http import TestHTTP +from unit.option import option def args_handler(conf_func): diff --git a/test/unit/feature/isolation.py b/test/unit/feature/isolation.py index 7877c03a..d8f68919 100644 --- a/test/unit/feature/isolation.py +++ b/test/unit/feature/isolation.py @@ -4,7 +4,7 @@ from unit.applications.lang.go import TestApplicationGo from unit.applications.lang.java import TestApplicationJava from unit.applications.lang.node import TestApplicationNode from unit.applications.proto import TestApplicationProto -from conftest import option +from unit.option import option class TestFeatureIsolation(TestApplicationProto): diff --git a/test/unit/http.py b/test/unit/http.py index 8d964978..ae74eac3 100644 --- a/test/unit/http.py +++ b/test/unit/http.py @@ -7,8 +7,8 @@ import select import socket import pytest -from conftest import option from unit.main import TestUnit +from unit.option import option class TestHTTP(TestUnit): diff --git a/test/unit/main.py b/test/unit/main.py index 488b3f4d..fce6a322 100644 --- a/test/unit/main.py +++ b/test/unit/main.py @@ -1,5 +1,5 @@ import pytest -from conftest import option +from unit.option import option class TestUnit(): diff --git a/test/unit/option.py b/test/unit/option.py new file mode 100644 index 00000000..677d806e --- /dev/null +++ b/test/unit/option.py @@ -0,0 +1,16 @@ +class Options(): + _options = { + 'skip_alerts': [], + 'skip_sanitizer': False, + } + + def __setattr__(self, name, value): + Options._options[name] = value + + def __getattr__(self, name): + if name in Options._options: + return Options._options[name] + + raise AttributeError + +option = Options() -- cgit From 8f916285639d7f9aac9ef03cace5e4dcbcca70cd Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Tue, 8 Dec 2020 14:37:25 +0000 Subject: Tests: utils module introduced. --- test/unit/applications/lang/node.py | 2 +- test/unit/utils.py | 50 +++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 test/unit/utils.py (limited to 'test/unit') diff --git a/test/unit/applications/lang/node.py b/test/unit/applications/lang/node.py index baccef7e..cc6d06ef 100644 --- a/test/unit/applications/lang/node.py +++ b/test/unit/applications/lang/node.py @@ -1,9 +1,9 @@ import shutil from urllib.parse import quote -from conftest import public_dir from unit.applications.proto import TestApplicationProto from unit.option import option +from unit.utils import public_dir class TestApplicationNode(TestApplicationProto): diff --git a/test/unit/utils.py b/test/unit/utils.py new file mode 100644 index 00000000..f24e9728 --- /dev/null +++ b/test/unit/utils.py @@ -0,0 +1,50 @@ +import os +import socket +import time + +import pytest + + +def public_dir(path): + os.chmod(path, 0o777) + + for root, dirs, files in os.walk(path): + for d in dirs: + os.chmod(os.path.join(root, d), 0o777) + for f in files: + os.chmod(os.path.join(root, f), 0o777) + + +def waitforfiles(*files): + for i in range(50): + wait = False + + for f in files: + if not os.path.exists(f): + wait = True + break + + if not wait: + return True + + time.sleep(0.1) + + return False + + +def waitforsocket(port): + for i in range(50): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + try: + sock.settimeout(5) + sock.connect(('127.0.0.1', port)) + return + + except ConnectionRefusedError: + time.sleep(0.1) + + except KeyboardInterrupt: + raise + + pytest.fail('Can\'t connect to the 127.0.0.1:' + port) + -- cgit From 4c846ae69308983050a55f6467c2d53e78120e0b Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Wed, 9 Dec 2020 16:15:50 +0000 Subject: Tests: isolation check moved to the pytest_sessionstart(). This change eliminates the need for some classes to run Unit one more time before running tests. --- test/unit/check/isolation.py | 158 ++++++++++++++++++++++++++++++++++++++++ test/unit/feature/isolation.py | 160 ----------------------------------------- test/unit/main.py | 46 ++++++------ test/unit/utils.py | 12 ++++ 4 files changed, 190 insertions(+), 186 deletions(-) create mode 100644 test/unit/check/isolation.py delete mode 100644 test/unit/feature/isolation.py (limited to 'test/unit') diff --git a/test/unit/check/isolation.py b/test/unit/check/isolation.py new file mode 100644 index 00000000..bb8feed1 --- /dev/null +++ b/test/unit/check/isolation.py @@ -0,0 +1,158 @@ +import json +import os + +from unit.applications.lang.go import TestApplicationGo +from unit.applications.lang.java import TestApplicationJava +from unit.applications.lang.node import TestApplicationNode +from unit.applications.proto import TestApplicationProto +from unit.http import TestHTTP +from unit.option import option +from unit.utils import getns + +allns = ['pid', 'mnt', 'ipc', 'uts', 'cgroup', 'net'] +http = TestHTTP() + +def check_isolation(): + test_conf = {"namespaces": {"credential": True}} + available = option.available + + conf = '' + if 'go' in available['modules']: + TestApplicationGo().prepare_env('empty', 'app') + + conf = { + "listeners": {"*:7080": {"pass": "applications/empty"}}, + "applications": { + "empty": { + "type": "external", + "processes": {"spare": 0}, + "working_directory": option.test_dir + "/go/empty", + "executable": option.temp_dir + "/go/app", + "isolation": {"namespaces": {"credential": True}}, + }, + }, + } + + elif 'python' in available['modules']: + conf = { + "listeners": {"*:7080": {"pass": "applications/empty"}}, + "applications": { + "empty": { + "type": "python", + "processes": {"spare": 0}, + "path": option.test_dir + "/python/empty", + "working_directory": option.test_dir + "/python/empty", + "module": "wsgi", + "isolation": {"namespaces": {"credential": True}}, + } + }, + } + + elif 'php' in available['modules']: + conf = { + "listeners": {"*:7080": {"pass": "applications/phpinfo"}}, + "applications": { + "phpinfo": { + "type": "php", + "processes": {"spare": 0}, + "root": option.test_dir + "/php/phpinfo", + "working_directory": option.test_dir + "/php/phpinfo", + "index": "index.php", + "isolation": {"namespaces": {"credential": True}}, + } + }, + } + + elif 'ruby' in available['modules']: + conf = { + "listeners": {"*:7080": {"pass": "applications/empty"}}, + "applications": { + "empty": { + "type": "ruby", + "processes": {"spare": 0}, + "working_directory": option.test_dir + "/ruby/empty", + "script": option.test_dir + "/ruby/empty/config.ru", + "isolation": {"namespaces": {"credential": True}}, + } + }, + } + + elif 'java' in available['modules']: + TestApplicationJava().prepare_env('empty') + + conf = { + "listeners": {"*:7080": {"pass": "applications/empty"}}, + "applications": { + "empty": { + "unit_jars": option.current_dir + "/build", + "type": "java", + "processes": {"spare": 0}, + "working_directory": option.test_dir + "/java/empty/", + "webapp": option.temp_dir + "/java", + "isolation": {"namespaces": {"credential": True}}, + } + }, + } + + elif 'node' in available['modules']: + TestApplicationNode().prepare_env('basic') + + conf = { + "listeners": {"*:7080": {"pass": "applications/basic"}}, + "applications": { + "basic": { + "type": "external", + "processes": {"spare": 0}, + "working_directory": option.temp_dir + "/node", + "executable": "app.js", + "isolation": {"namespaces": {"credential": True}}, + } + }, + } + + elif 'perl' in available['modules']: + conf = { + "listeners": {"*:7080": {"pass": "applications/body_empty"}}, + "applications": { + "body_empty": { + "type": "perl", + "processes": {"spare": 0}, + "working_directory": option.test_dir + + "/perl/body_empty", + "script": option.test_dir + "/perl/body_empty/psgi.pl", + "isolation": {"namespaces": {"credential": True}}, + } + }, + } + + else: + return + + resp = http.put( + url='/config', + sock_type='unix', + addr=option.temp_dir + '/control.unit.sock', + body=json.dumps(conf), + ) + + if 'success' not in resp: + return + + userns = getns('user') + if not userns: + return + + available['features']['isolation'] = {'user': userns} + + unp_clone_path = '/proc/sys/kernel/unprivileged_userns_clone' + if os.path.exists(unp_clone_path): + with open(unp_clone_path, 'r') as f: + if str(f.read()).rstrip() == '1': + available['features']['isolation'][ + 'unprivileged_userns_clone' + ] = True + + for ns in allns: + ns_value = getns(ns) + if ns_value: + available['features']['isolation'][ns] = ns_value diff --git a/test/unit/feature/isolation.py b/test/unit/feature/isolation.py deleted file mode 100644 index d8f68919..00000000 --- a/test/unit/feature/isolation.py +++ /dev/null @@ -1,160 +0,0 @@ -import os - -from unit.applications.lang.go import TestApplicationGo -from unit.applications.lang.java import TestApplicationJava -from unit.applications.lang.node import TestApplicationNode -from unit.applications.proto import TestApplicationProto -from unit.option import option - - -class TestFeatureIsolation(TestApplicationProto): - allns = ['pid', 'mnt', 'ipc', 'uts', 'cgroup', 'net'] - - def check(self, available, temp_dir): - test_conf = {"namespaces": {"credential": True}} - - conf = '' - if 'go' in available['modules']: - TestApplicationGo().prepare_env('empty', 'app') - - conf = { - "listeners": {"*:7080": {"pass": "applications/empty"}}, - "applications": { - "empty": { - "type": "external", - "processes": {"spare": 0}, - "working_directory": option.test_dir + "/go/empty", - "executable": option.temp_dir + "/go/app", - "isolation": {"namespaces": {"credential": True}}, - }, - }, - } - - elif 'python' in available['modules']: - conf = { - "listeners": {"*:7080": {"pass": "applications/empty"}}, - "applications": { - "empty": { - "type": "python", - "processes": {"spare": 0}, - "path": option.test_dir + "/python/empty", - "working_directory": option.test_dir + "/python/empty", - "module": "wsgi", - "isolation": {"namespaces": {"credential": True}}, - } - }, - } - - elif 'php' in available['modules']: - conf = { - "listeners": {"*:7080": {"pass": "applications/phpinfo"}}, - "applications": { - "phpinfo": { - "type": "php", - "processes": {"spare": 0}, - "root": option.test_dir + "/php/phpinfo", - "working_directory": option.test_dir + "/php/phpinfo", - "index": "index.php", - "isolation": {"namespaces": {"credential": True}}, - } - }, - } - - elif 'ruby' in available['modules']: - conf = { - "listeners": {"*:7080": {"pass": "applications/empty"}}, - "applications": { - "empty": { - "type": "ruby", - "processes": {"spare": 0}, - "working_directory": option.test_dir + "/ruby/empty", - "script": option.test_dir + "/ruby/empty/config.ru", - "isolation": {"namespaces": {"credential": True}}, - } - }, - } - - elif 'java' in available['modules']: - TestApplicationJava().prepare_env('empty') - - conf = { - "listeners": {"*:7080": {"pass": "applications/empty"}}, - "applications": { - "empty": { - "unit_jars": option.current_dir + "/build", - "type": "java", - "processes": {"spare": 0}, - "working_directory": option.test_dir + "/java/empty/", - "webapp": option.temp_dir + "/java", - "isolation": {"namespaces": {"credential": True}}, - } - }, - } - - elif 'node' in available['modules']: - TestApplicationNode().prepare_env('basic') - - conf = { - "listeners": {"*:7080": {"pass": "applications/basic"}}, - "applications": { - "basic": { - "type": "external", - "processes": {"spare": 0}, - "working_directory": option.temp_dir + "/node", - "executable": "app.js", - "isolation": {"namespaces": {"credential": True}}, - } - }, - } - - elif 'perl' in available['modules']: - conf = { - "listeners": {"*:7080": {"pass": "applications/body_empty"}}, - "applications": { - "body_empty": { - "type": "perl", - "processes": {"spare": 0}, - "working_directory": option.test_dir - + "/perl/body_empty", - "script": option.test_dir + "/perl/body_empty/psgi.pl", - "isolation": {"namespaces": {"credential": True}}, - } - }, - } - - else: - return - - if 'success' not in self.conf(conf): - return - - userns = self.getns('user') - if not userns: - return - - available['features']['isolation'] = {'user': userns} - - unp_clone_path = '/proc/sys/kernel/unprivileged_userns_clone' - if os.path.exists(unp_clone_path): - with open(unp_clone_path, 'r') as f: - if str(f.read()).rstrip() == '1': - available['features']['isolation'][ - 'unprivileged_userns_clone' - ] = True - - for ns in self.allns: - ns_value = self.getns(ns) - if ns_value: - available['features']['isolation'][ns] = ns_value - - def getns(self, nstype): - # read namespace id from symlink file: - # it points to: ':[]' - # # eg.: 'pid:[4026531836]' - nspath = '/proc/self/ns/' + nstype - data = None - - if os.path.exists(nspath): - data = int(os.readlink(nspath)[len(nstype) + 2 : -1]) - - return data diff --git a/test/unit/main.py b/test/unit/main.py index fce6a322..749ff3ab 100644 --- a/test/unit/main.py +++ b/test/unit/main.py @@ -4,39 +4,33 @@ from unit.option import option class TestUnit(): @classmethod - def setup_class(cls, complete_check=True): - def check(): - missed = [] + def setup_class(cls): + missed = [] - # check modules + # check modules - if 'modules' in cls.prerequisites: - available_modules = list(option.available['modules'].keys()) + if 'modules' in cls.prerequisites: + available_modules = list(option.available['modules'].keys()) - for module in cls.prerequisites['modules']: - if module in available_modules: - continue + for module in cls.prerequisites['modules']: + if module in available_modules: + continue - missed.append(module) + missed.append(module) - if missed: - pytest.skip('Unit has no ' + ', '.join(missed) + ' module(s)') + if missed: + pytest.skip('Unit has no ' + ', '.join(missed) + ' module(s)') - # check features + # check features - if 'features' in cls.prerequisites: - available_features = list(option.available['features'].keys()) + if 'features' in cls.prerequisites: + available_features = list(option.available['features'].keys()) - for feature in cls.prerequisites['features']: - if feature in available_features: - continue + for feature in cls.prerequisites['features']: + if feature in available_features: + continue - missed.append(feature) + missed.append(feature) - if missed: - pytest.skip(', '.join(missed) + ' feature(s) not supported') - - if complete_check: - check() - else: - return check + if missed: + pytest.skip(', '.join(missed) + ' feature(s) not supported') diff --git a/test/unit/utils.py b/test/unit/utils.py index f24e9728..1307a4f6 100644 --- a/test/unit/utils.py +++ b/test/unit/utils.py @@ -48,3 +48,15 @@ def waitforsocket(port): pytest.fail('Can\'t connect to the 127.0.0.1:' + port) + +def getns(nstype): + # read namespace id from symlink file: + # it points to: ':[]' + # # eg.: 'pid:[4026531836]' + nspath = '/proc/self/ns/' + nstype + data = None + + if os.path.exists(nspath): + data = int(os.readlink(nspath)[len(nstype) + 2 : -1]) + + return data -- cgit From 7be62c3c213c3da1da1a45c8db16192eb0ed14d8 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Wed, 9 Dec 2020 16:17:46 +0000 Subject: Tests: TestUnit class removed. Prerequisite checks moved to the fixture in conftest.py. --- test/unit/http.py | 3 +-- test/unit/main.py | 36 ------------------------------------ 2 files changed, 1 insertion(+), 38 deletions(-) delete mode 100644 test/unit/main.py (limited to 'test/unit') diff --git a/test/unit/http.py b/test/unit/http.py index ae74eac3..57e6ed3a 100644 --- a/test/unit/http.py +++ b/test/unit/http.py @@ -7,11 +7,10 @@ import select import socket import pytest -from unit.main import TestUnit from unit.option import option -class TestHTTP(TestUnit): +class TestHTTP(): def http(self, start_str, **kwargs): sock_type = kwargs.get('sock_type', 'ipv4') port = kwargs.get('port', 7080) diff --git a/test/unit/main.py b/test/unit/main.py deleted file mode 100644 index 749ff3ab..00000000 --- a/test/unit/main.py +++ /dev/null @@ -1,36 +0,0 @@ -import pytest -from unit.option import option - - -class TestUnit(): - @classmethod - def setup_class(cls): - missed = [] - - # check modules - - if 'modules' in cls.prerequisites: - available_modules = list(option.available['modules'].keys()) - - for module in cls.prerequisites['modules']: - if module in available_modules: - continue - - missed.append(module) - - if missed: - pytest.skip('Unit has no ' + ', '.join(missed) + ' module(s)') - - # check features - - if 'features' in cls.prerequisites: - available_features = list(option.available['features'].keys()) - - for feature in cls.prerequisites['features']: - if feature in available_features: - continue - - missed.append(feature) - - if missed: - pytest.skip(', '.join(missed) + ' feature(s) not supported') -- cgit From 66ac35e9b941500a95a069066d896793b5df3a2a Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Tue, 15 Dec 2020 11:06:49 +0000 Subject: Tests: fixed bug that disabled isolation tests. --- test/unit/check/isolation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test/unit') diff --git a/test/unit/check/isolation.py b/test/unit/check/isolation.py index bb8feed1..fe5a41f8 100644 --- a/test/unit/check/isolation.py +++ b/test/unit/check/isolation.py @@ -135,7 +135,7 @@ def check_isolation(): body=json.dumps(conf), ) - if 'success' not in resp: + if 'success' not in resp['body']: return userns = getns('user') -- cgit From 03436d2ec2ab485b4f3196690e9a267bf0d42d30 Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Mon, 21 Dec 2020 11:00:46 +0000 Subject: Tests: introduced a separate cache directory for Go builds. The Go compiler can't detect changes to C header files when compiling CGO applications, and then this leads to Go test samples being linked with wrong libunit. This patch creates a new cache directory reused throughout the test suite. --- test/unit/applications/lang/go.py | 1 + 1 file changed, 1 insertion(+) (limited to 'test/unit') diff --git a/test/unit/applications/lang/go.py b/test/unit/applications/lang/go.py index 70f9d58c..a17b1af4 100644 --- a/test/unit/applications/lang/go.py +++ b/test/unit/applications/lang/go.py @@ -12,6 +12,7 @@ class TestApplicationGo(TestApplicationProto): env = os.environ.copy() env['GOPATH'] = option.current_dir + '/build/go' + env['GOCACHE'] = option.cache_dir + '/go' if static: args = [ -- cgit From db9b70932b2e3e4df8b4bcdb24fedce042da15aa Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Wed, 13 Jan 2021 06:24:26 +0000 Subject: Tests: waitformount() and waitforunmount() introduced. --- test/unit/utils.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'test/unit') diff --git a/test/unit/utils.py b/test/unit/utils.py index 1307a4f6..7a0a3fe5 100644 --- a/test/unit/utils.py +++ b/test/unit/utils.py @@ -1,5 +1,6 @@ import os import socket +import subprocess import time import pytest @@ -49,6 +50,37 @@ def waitforsocket(port): pytest.fail('Can\'t connect to the 127.0.0.1:' + port) +def findmnt(): + try: + out = subprocess.check_output( + ['findmnt', '--raw'], stderr=subprocess.STDOUT + ).decode() + except FileNotFoundError: + pytest.skip('requires findmnt') + + return out + + +def waitformount(template, wait=50): + for i in range(wait): + if findmnt().find(template) != -1: + return True + + time.sleep(0.1) + + return False + + +def waitforunmount(template, wait=50): + for i in range(wait): + if findmnt().find(template) == -1: + return True + + time.sleep(0.1) + + return False + + def getns(nstype): # read namespace id from symlink file: # it points to: ':[]' -- cgit