summaryrefslogtreecommitdiffhomepage
path: root/test/unit
diff options
context:
space:
mode:
Diffstat (limited to 'test/unit')
-rw-r--r--test/unit/applications/lang/go.py32
-rw-r--r--test/unit/applications/lang/java.py26
-rw-r--r--test/unit/applications/lang/node.py12
-rw-r--r--test/unit/applications/tls.py22
-rw-r--r--test/unit/applications/websockets.py46
-rw-r--r--test/unit/feature/isolation.py87
-rw-r--r--test/unit/http.py12
-rw-r--r--test/unit/main.py178
8 files changed, 283 insertions, 132 deletions
diff --git a/test/unit/applications/lang/go.py b/test/unit/applications/lang/go.py
index e4ab8ffa..15ac1cd9 100644
--- a/test/unit/applications/lang/go.py
+++ b/test/unit/applications/lang/go.py
@@ -4,12 +4,22 @@ from unit.applications.proto import TestApplicationProto
class TestApplicationGo(TestApplicationProto):
- def load(self, script, name='app'):
+ @classmethod
+ def setUpClass(cls, complete_check=True):
+ unit = super().setUpClass(complete_check=False)
- if not os.path.isdir(self.testdir + '/go'):
- os.mkdir(self.testdir + '/go')
+ # check go module
+
+ go_app = TestApplicationGo()
+ go_app.testdir = unit.testdir
+ if go_app.prepare_env('empty', 'app').returncode == 0:
+ cls.available['modules']['go'] = []
- go_app_path = self.current_dir + '/go/'
+ return unit if not complete_check else unit.complete()
+
+ def prepare_env(self, script, name):
+ if not os.path.exists(self.testdir + '/go'):
+ os.mkdir(self.testdir + '/go')
env = os.environ.copy()
env['GOPATH'] = self.pardir + '/go'
@@ -19,12 +29,18 @@ class TestApplicationGo(TestApplicationProto):
'build',
'-o',
self.testdir + '/go/' + name,
- go_app_path + script + '/' + name + '.go',
+ self.current_dir + '/go/' + script + '/' + name + '.go',
],
env=env,
)
+
process.communicate()
+ return process
+
+ def load(self, script, name='app'):
+ self.prepare_env(script, name)
+
self._load_conf(
{
"listeners": {"*:7080": {"pass": "applications/" + script}},
@@ -32,8 +48,10 @@ class TestApplicationGo(TestApplicationProto):
script: {
"type": "external",
"processes": {"spare": 0},
- "working_directory": go_app_path + script,
- "executable": self.testdir + '/go/' + name,
+ "working_directory": self.current_dir
+ + "/go/"
+ + script,
+ "executable": self.testdir + "/go/" + name,
}
},
}
diff --git a/test/unit/applications/lang/java.py b/test/unit/applications/lang/java.py
index ec1c95d9..40bf3662 100644
--- a/test/unit/applications/lang/java.py
+++ b/test/unit/applications/lang/java.py
@@ -1,4 +1,5 @@
import os
+import glob
import shutil
from subprocess import Popen
from unit.applications.proto import TestApplicationProto
@@ -6,11 +7,9 @@ from unit.applications.proto import TestApplicationProto
class TestApplicationJava(TestApplicationProto):
def load(self, script, name='app'):
-
app_path = self.testdir + '/java'
web_inf_path = app_path + '/WEB-INF/'
classes_path = web_inf_path + 'classes/'
-
script_path = self.current_dir + '/java/' + script + '/'
if not os.path.isdir(app_path):
@@ -19,39 +18,48 @@ class TestApplicationJava(TestApplicationProto):
src = []
for f in os.listdir(script_path):
+ file_path = script_path + f
+
if f.endswith('.java'):
- src.append(script_path + f)
+ src.append(file_path)
continue
if f.startswith('.') or f == 'Makefile':
continue
- if os.path.isdir(script_path + f):
+ if os.path.isdir(file_path):
if f == 'WEB-INF':
continue
- shutil.copytree(script_path + f, app_path + '/' + f)
+ shutil.copytree(file_path, app_path + '/' + f)
continue
if f == 'web.xml':
if not os.path.isdir(web_inf_path):
os.makedirs(web_inf_path)
- shutil.copy2(script_path + f, web_inf_path)
+ shutil.copy2(file_path, web_inf_path)
else:
- shutil.copy2(script_path + f, app_path)
+ shutil.copy2(file_path, app_path)
if src:
if not os.path.isdir(classes_path):
os.makedirs(classes_path)
- tomcat_jar = self.pardir + '/build/tomcat-servlet-api-9.0.13.jar'
+ classpath = self.pardir + '/build/tomcat-servlet-api-9.0.13.jar'
+
+ ws_jars = glob.glob(
+ self.pardir + '/build/websocket-api-java-*.jar'
+ )
+
+ if not ws_jars:
+ self.fail('websocket api jar not found.')
javac = [
'javac',
'-encoding', 'utf-8',
'-d', classes_path,
- '-classpath', tomcat_jar,
+ '-classpath', classpath + ':' + ws_jars[0],
]
javac.extend(src)
diff --git a/test/unit/applications/lang/node.py b/test/unit/applications/lang/node.py
index 931c6596..3cc72669 100644
--- a/test/unit/applications/lang/node.py
+++ b/test/unit/applications/lang/node.py
@@ -4,8 +4,18 @@ from unit.applications.proto import TestApplicationProto
class TestApplicationNode(TestApplicationProto):
- def load(self, script, name='app.js'):
+ @classmethod
+ def setUpClass(cls, complete_check=True):
+ unit = super().setUpClass(complete_check=False)
+
+ # check node module
+
+ if os.path.exists(unit.pardir + '/node/node_modules'):
+ cls.available['modules']['node'] = []
+ return unit if not complete_check else unit.complete()
+
+ def load(self, script, name='app.js'):
# copy application
shutil.copytree(
diff --git a/test/unit/applications/tls.py b/test/unit/applications/tls.py
index 6e8deefb..1290279d 100644
--- a/test/unit/applications/tls.py
+++ b/test/unit/applications/tls.py
@@ -1,4 +1,5 @@
import os
+import re
import ssl
import subprocess
from unit.applications.proto import TestApplicationProto
@@ -12,6 +13,27 @@ class TestApplicationTLS(TestApplicationProto):
self.context.check_hostname = False
self.context.verify_mode = ssl.CERT_NONE
+ @classmethod
+ def setUpClass(cls, complete_check=True):
+ unit = super().setUpClass(complete_check=False)
+
+ # check tls module
+
+ try:
+ subprocess.check_output(['which', 'openssl'])
+
+ output = subprocess.check_output(
+ [unit.unitd, '--version'], stderr=subprocess.STDOUT
+ )
+
+ if re.search('--openssl', output.decode()):
+ cls.available['modules']['openssl'] = []
+
+ except:
+ pass
+
+ return unit if not complete_check else unit.complete()
+
def certificate(self, name='default', load=True):
self.openssl_conf()
diff --git a/test/unit/applications/websockets.py b/test/unit/applications/websockets.py
index 417e9504..50ff2797 100644
--- a/test/unit/applications/websockets.py
+++ b/test/unit/applications/websockets.py
@@ -54,24 +54,16 @@ class TestApplicationWebsocket(TestApplicationProto):
def apply_mask(self, data, mask):
return bytes(b ^ m for b, m in zip(data, itertools.cycle(mask)))
- def serialize_close(self, code = 1000, reason = ''):
+ def serialize_close(self, code=1000, reason=''):
return struct.pack('!H', code) + reason.encode('utf-8')
- def frame_read(self, sock, read_timeout=10):
+ def frame_read(self, sock, read_timeout=30):
def recv_bytes(sock, bytes):
data = b''
while select.select([sock], [], [], read_timeout)[0]:
- try:
- if bytes < 65536:
- data = sock.recv(bytes)
- else:
- data = self.recvall(
- sock,
- read_timeout=read_timeout,
- buff_size=bytes,
- )
- break
- except:
+ data += sock.recv(bytes - len(data))
+
+ if len(data) == bytes:
break
return data
@@ -99,7 +91,11 @@ class TestApplicationWebsocket(TestApplicationProto):
if frame['mask']:
mask_bits = recv_bytes(sock, 4)
- data = recv_bytes(sock, length)
+ data = b''
+
+ if length != 0:
+ data = recv_bytes(sock, length)
+
if frame['mask']:
data = self.apply_mask(data, mask_bits)
@@ -175,14 +171,20 @@ class TestApplicationWebsocket(TestApplicationProto):
frame = self.frame_to_send(*args, **kwargs)
if chopsize is None:
- sock.sendall(frame)
+ try:
+ sock.sendall(frame)
+ except BrokenPipeError:
+ pass
else:
pos = 0
frame_len = len(frame)
- while (pos < frame_len):
+ while pos < frame_len:
end = min(pos + chopsize, frame_len)
- sock.sendall(frame[pos:end])
+ try:
+ sock.sendall(frame[pos:end])
+ except BrokenPipeError:
+ end = frame_len
pos = end
def message(self, sock, type, message, fragmention_size=None, **kwargs):
@@ -197,17 +199,19 @@ class TestApplicationWebsocket(TestApplicationProto):
pos = 0
op_code = type
- while(pos < message_len):
+ while pos < message_len:
end = min(pos + fragmention_size, message_len)
- fin = (end == message_len)
- self.frame_write(sock, op_code, message[pos:end], fin=fin, **kwargs)
+ fin = end == message_len
+ self.frame_write(
+ sock, op_code, message[pos:end], fin=fin, **kwargs
+ )
op_code = self.OP_CONT
pos = end
def message_read(self, sock, read_timeout=10):
frame = self.frame_read(sock, read_timeout=read_timeout)
- while(not frame['fin']):
+ while not frame['fin']:
temp = self.frame_read(sock, read_timeout=read_timeout)
frame['data'] += temp['data']
frame['fin'] = temp['fin']
diff --git a/test/unit/feature/isolation.py b/test/unit/feature/isolation.py
new file mode 100644
index 00000000..9b06ab3c
--- /dev/null
+++ b/test/unit/feature/isolation.py
@@ -0,0 +1,87 @@
+import os
+import json
+from unit.applications.proto import TestApplicationProto
+from unit.applications.lang.go import TestApplicationGo
+from unit.applications.lang.java import TestApplicationJava
+from unit.applications.lang.node import TestApplicationNode
+from unit.applications.lang.perl import TestApplicationPerl
+from unit.applications.lang.php import TestApplicationPHP
+from unit.applications.lang.python import TestApplicationPython
+from unit.applications.lang.ruby import TestApplicationRuby
+
+
+class TestFeatureIsolation(TestApplicationProto):
+ allns = ['pid', 'mnt', 'ipc', 'uts', 'cgroup', 'net']
+
+ def check(self, available, testdir):
+ test_conf = {"namespaces": {"credential": True}}
+
+ module = ''
+ app = 'empty'
+ if 'go' in available['modules']:
+ module = TestApplicationGo()
+
+ elif 'java' in available['modules']:
+ module = TestApplicationJava()
+
+ elif 'node' in available['modules']:
+ module = TestApplicationNode()
+ app = 'basic'
+
+ elif 'perl' in available['modules']:
+ module = TestApplicationPerl()
+ app = 'body_empty'
+
+ elif 'php' in available['modules']:
+ module = TestApplicationPHP()
+ app = 'phpinfo'
+
+ elif 'python' in available['modules']:
+ module = TestApplicationPython()
+
+ elif 'ruby' in available['modules']:
+ module = TestApplicationRuby()
+
+ if not module:
+ return
+
+ module.testdir = testdir
+ module.load(app)
+
+ resp = module.conf(test_conf, 'applications/' + app + '/isolation')
+ if 'success' not in resp:
+ 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: '<nstype>:[<ns id>]'
+ # # 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
+
+ def parsejson(self, data):
+ return json.loads(data.split('\n')[1])
diff --git a/test/unit/http.py b/test/unit/http.py
index c0af8a9e..82a6bd6a 100644
--- a/test/unit/http.py
+++ b/test/unit/http.py
@@ -12,6 +12,11 @@ class TestHTTP(TestUnit):
port = 7080 if 'port' not in kwargs else kwargs['port']
url = '/' if 'url' not in kwargs else kwargs['url']
http = 'HTTP/1.0' if 'http_10' in kwargs else 'HTTP/1.1'
+ read_buffer_size = (
+ 4096
+ if 'read_buffer_size' not in kwargs
+ else kwargs['read_buffer_size']
+ )
headers = (
{'Host': 'localhost', 'Connection': 'close'}
@@ -94,7 +99,9 @@ class TestHTTP(TestUnit):
read_timeout = (
30 if 'read_timeout' not in kwargs else kwargs['read_timeout']
)
- resp = self.recvall(sock, read_timeout=read_timeout).decode(enc)
+ resp = self.recvall(
+ sock, read_timeout=read_timeout, buff_size=read_buffer_size
+ ).decode(enc)
if TestUnit.detailed:
print('<<<')
@@ -118,6 +125,9 @@ class TestHTTP(TestUnit):
def get(self, **kwargs):
return self.http('GET', **kwargs)
+ def head(self, **kwargs):
+ return self.http('HEAD', **kwargs)
+
def post(self, **kwargs):
return self.http('POST', **kwargs)
diff --git a/test/unit/main.py b/test/unit/main.py
index 6a167a9e..873f1815 100644
--- a/test/unit/main.py
+++ b/test/unit/main.py
@@ -12,8 +12,6 @@ import subprocess
from multiprocessing import Process
-available_modules = {}
-
class TestUnit(unittest.TestCase):
current_dir = os.path.abspath(
@@ -28,6 +26,7 @@ class TestUnit(unittest.TestCase):
detailed = False
save_log = False
+ unsafe = False
def __init__(self, methodName='runTest'):
super().__init__(methodName)
@@ -41,10 +40,12 @@ class TestUnit(unittest.TestCase):
if not hasattr(self, 'application_type'):
return super().run(result)
+ # rerun test for each available module version
+
type = self.application_type
- for prerequisite in self.prerequisites:
- if prerequisite in available_modules:
- for version in available_modules[prerequisite]:
+ for module in self.prerequisites['modules']:
+ if module in self.available['modules']:
+ for version in self.available['modules'][module]:
self.application_type = type + ' ' + version
super().run(result)
@@ -63,8 +64,83 @@ class TestUnit(unittest.TestCase):
unittest.main()
@classmethod
- def setUpClass(cls):
- TestUnit().check_modules(*cls.prerequisites)
+ def setUpClass(cls, complete_check=True):
+ cls.available = {'modules': {}, 'features': {}}
+ unit = TestUnit()
+
+ unit._run()
+
+ # read unit.log
+
+ for i in range(50):
+ with open(unit.testdir + '/unit.log', 'r') as f:
+ log = f.read()
+ m = re.search('controller started', log)
+
+ if m is None:
+ time.sleep(0.1)
+ else:
+ break
+
+ if m is None:
+ unit.stop()
+ exit("Unit is writing log too long")
+
+ # discover available modules from unit.log
+
+ for module in re.findall(r'module: ([a-zA-Z]+) (.*) ".*"$', log, re.M):
+ if module[0] not in cls.available['modules']:
+ cls.available['modules'][module[0]] = [module[1]]
+ else:
+ cls.available['modules'][module[0]].append(module[1])
+
+ def check(available, prerequisites):
+ missed = []
+
+ # check modules
+
+ if 'modules' in prerequisites:
+ available_modules = list(available['modules'].keys())
+
+ for module in prerequisites['modules']:
+ if module in available_modules:
+ continue
+
+ missed.append(module)
+
+ if missed:
+ print('Unit has no ' + ', '.join(missed) + ' module(s)')
+ raise unittest.SkipTest()
+
+ # check features
+
+ if 'features' in prerequisites:
+ available_features = list(available['features'].keys())
+
+ for feature in prerequisites['features']:
+ if feature in available_features:
+ continue
+
+ missed.append(feature)
+
+ if missed:
+ print(', '.join(missed) + ' feature(s) not supported')
+ raise unittest.SkipTest()
+
+ def destroy():
+ unit.stop()
+ unit._check_alerts(log)
+ shutil.rmtree(unit.testdir)
+
+ def complete():
+ destroy()
+ check(cls.available, cls.prerequisites)
+
+ if complete_check:
+ complete()
+ else:
+ unit.complete = complete
+ return unit
def setUp(self):
self._run()
@@ -105,92 +181,6 @@ class TestUnit(unittest.TestCase):
else:
self._print_path_to_log()
- def check_modules(self, *modules):
- self._run()
-
- for i in range(50):
- with open(self.testdir + '/unit.log', 'r') as f:
- log = f.read()
- m = re.search('controller started', log)
-
- if m is None:
- time.sleep(0.1)
- else:
- break
-
- if m is None:
- self.stop()
- exit("Unit is writing log too long")
-
- # discover all available modules
-
- global available_modules
- available_modules = {}
- for module in re.findall(r'module: ([a-zA-Z]+) (.*) ".*"$', log, re.M):
- if module[0] not in available_modules:
- available_modules[module[0]] = [module[1]]
- else:
- available_modules[module[0]].append(module[1])
-
- missed_module = ''
- for module in modules:
- if module == 'go':
- env = os.environ.copy()
- env['GOPATH'] = self.pardir + '/go'
-
- try:
- process = subprocess.Popen(
- [
- 'go',
- 'build',
- '-o',
- self.testdir + '/go/check_module',
- self.current_dir + '/go/empty/app.go',
- ],
- env=env,
- )
- process.communicate()
-
- m = module if process.returncode == 0 else None
-
- except:
- m = None
-
- elif module == 'node':
- if os.path.isdir(self.pardir + '/node/node_modules'):
- m = module
- else:
- m = None
-
- elif module == 'openssl':
- try:
- subprocess.check_output(['which', 'openssl'])
-
- output = subprocess.check_output(
- [self.unitd, '--version'],
- stderr=subprocess.STDOUT,
- )
-
- m = re.search('--openssl', output.decode())
-
- except:
- m = None
-
- else:
- if module not in available_modules:
- m = None
-
- if m is None:
- missed_module = module
- break
-
- self.stop()
- self._check_alerts(log)
- shutil.rmtree(self.testdir)
-
- if missed_module:
- raise unittest.SkipTest('Unit has no ' + missed_module + ' module')
-
def stop(self):
if self._started:
self._stop()
@@ -350,6 +340,8 @@ class TestUnit(unittest.TestCase):
TestUnit.save_log = args.save_log
TestUnit.unsafe = args.unsafe
+ # set stdout to non-blocking
+
if TestUnit.detailed:
fcntl.fcntl(sys.stdout.fileno(), fcntl.F_SETFL, 0)