Skip to content

Commit 268138a

Browse files
authored
Merge pull request #40 from jscotka/fixes2
fix several issues caused by splitting to more files
2 parents 1e32a11 + c6c82b2 commit 268138a

File tree

9 files changed

+279
-248
lines changed

9 files changed

+279
-248
lines changed

docs/example-config-minimal.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
document: modularity-testing
22
version: 1
33
name: bash
4-
modulemd-url: https://src.fedoraproject.org/cgit/modules/memcached.git/plain/memcached.yaml
4+
modulemd-url: https://src.fedoraproject.org/modules/memcached/raw/master/f/memcached.yaml
55
packages:
66
rpms:
77
- bash

docs/example-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ name: memcached
1212
# link to default moduleMD file (usefull for local testing wihtout any repo)
1313
# env var: MODULEMDURL
1414
# MANDATORY (or compose-url)
15-
modulemd-url: https://src.fedoraproject.org/cgit/modules/memcached.git/plain/memcached.yaml
15+
modulemd-url: https://src.fedoraproject.org/modules/memcached/raw/master/f/memcached.yaml
1616
# final compose done by pungi (contain also modulemd files for modules) can suppy also previous part
1717
# env var: COMPOSEURL
1818
compose-url: url_to_compose_in done in fedora

examples/testing-module/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
CMD=avocado run --filter-by-tags=-WIP
2-
TESTS=$(shell ls *.py *.sh modulelint/*.py)
2+
TESTS=$(shell ls *.py *.sh ../../tools/modulelint/*.py)
33
export MTF_REMOTE_REPOS=yes
44
export DEBUG=yes
55
export DOCKERFILE=./Dockerfile

examples/testing-module/moduleframework

Lines changed: 0 additions & 1 deletion
This file was deleted.

examples/testing-module/modulelint

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
#
4+
# Meta test family (MTF) is a tool to test components of a modular Fedora:
5+
# https://docs.pagure.org/modularity/
6+
# Copyright (C) 2017 Red Hat, Inc.
7+
#
8+
# This program is free software; you can redistribute it and/or modify
9+
# it under the terms of the GNU General Public License as published by
10+
# he Free Software Foundation; either version 2 of the License, or
11+
# (at your option) any later version.
12+
#
13+
# This program is distributed in the hope that it will be useful,
14+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
# GNU General Public License for more details.
17+
#
18+
# You should have received a copy of the GNU General Public License along
19+
# with this program; if not, write to the Free Software Foundation, Inc.,
20+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21+
#
22+
# Authors: Jan Scotka <[email protected]>
23+
#
24+
25+
"""
26+
main module provides all helpers for various module types and AVOCADO(unittest) classes
27+
what you should use for your tests (inherited)
28+
"""
29+
30+
31+
from avocado import Test
32+
from avocado.core import exceptions
33+
34+
from moduleframework.common import *
35+
from moduleframework.helpers.container_helper import ContainerHelper
36+
from moduleframework.helpers.nspawn_helper import NspawnHelper
37+
from moduleframework.helpers.rpm_helper import RpmHelper
38+
39+
40+
# INTERFACE CLASS FOR GENERAL TESTS OF MODULES
41+
class AvocadoTest(Test):
42+
"""
43+
MAIN class for inheritance what should be used for tests based on this framework.
44+
It is intended for tests what fits all module types, what does not have specific usecases for some module type.
45+
Class is derived from AVOCADO TEST class.
46+
47+
This class is interface to *HELPER classed and use them as backend
48+
49+
It is not allowed to do instances of this class!!!
50+
Instance is done when test is executed by test scheduler like avocado/unittest
51+
52+
:avocado: disable
53+
"""
54+
55+
def __init__(self, *args, **kwargs):
56+
super(AvocadoTest, self).__init__(*args, **kwargs)
57+
58+
(self.backend, self.moduleType) = get_backend()
59+
self.moduleProfile = get_profile()
60+
print_info(
61+
"Module Type: %s; Profile: %s" %
62+
(self.moduleType, self.moduleProfile))
63+
64+
def cancel(self, *args, **kwargs):
65+
try:
66+
super(AvocadoTest, self).cancel(*args, **kwargs)
67+
except AttributeError:
68+
raise exceptions.TestDecoratorSkip(*args, **kwargs)
69+
70+
def setUp(self):
71+
"""
72+
Unittest setUp method. It prepares environment for selected module type like NSPAWN, DOCKER, RPM
73+
It is called when instance of test is created.
74+
75+
When you redefine this method in your class, don't forget to call super(self.__class__,self).setUp()
76+
77+
:return: None
78+
"""
79+
return self.backend.setUp()
80+
81+
def tearDown(self, *args, **kwargs):
82+
"""
83+
Unittest tearDown method. It clean environment for selected module type like NSPAWN, DOCKER, RPM after test is done
84+
It is called when instance of test is finished.
85+
86+
When you redefine this method in your class, don't forget to call super(self.__class__,self).tearDown()
87+
88+
:return: None
89+
"""
90+
return self.backend.tearDown(*args, **kwargs)
91+
92+
def start(self, *args, **kwargs):
93+
"""
94+
Start the module, it uses start action from config file for selected module or it calls default start
95+
in case start action is not defined in config file
96+
97+
:param args: Do not use it directly (It is defined in config.yaml)
98+
:param kwargs: Do not use it directly (It is defined in config.yaml)
99+
:return: None
100+
"""
101+
return self.backend.start(*args, **kwargs)
102+
103+
def stop(self, *args, **kwargs):
104+
"""
105+
Stop the module, it uses stop action from config file for selected module or it calls default stop
106+
in case stop action is not defined in config file (for some module type, stop action does not have sense,
107+
like docker, stop is done via docker stop dockerID)
108+
109+
:param args: Do not use it directly (It is defined in config.yaml)
110+
:param kwargs: Do not use it directly (It is defined in config.yaml)
111+
:return: None
112+
"""
113+
return self.backend.stop(*args, **kwargs)
114+
115+
def run(self, *args, **kwargs):
116+
"""
117+
Run command inside module, parametr command and others are passed to proper module Helper
118+
119+
:param args: command
120+
:param kwargs: shell, ignore_status, verbose
121+
:return: object avocado.process.run
122+
"""
123+
return self.backend.run(*args, **kwargs)
124+
125+
def runCheckState(self, command="ls /", expected_state=0,
126+
output_text=None, *args, **kwargs):
127+
"""
128+
derived from self.run method but allows to add also to pass expected return code.
129+
130+
:param command: str Command to run
131+
:param expected_state: int expected value of return code of command or last command in case of shell
132+
:param output_text: str Description of commands, what it does (in case of empty, command is default)
133+
:param args: pass thru
134+
:param kwargs: pass thru
135+
:return: None
136+
"""
137+
cmd = self.run(command, ignore_status=True, *args, **kwargs)
138+
output_text = command if not output_text else output_text
139+
if cmd.exit_status == expected_state:
140+
self.log.info(
141+
"command (RC=%d, expected=%d): %s" %
142+
(cmd.exit_status, expected_state, output_text))
143+
else:
144+
self.fail(
145+
"command (RC=%d, expected=%d): %s" %
146+
(cmd.exit_status, expected_state, output_text))
147+
148+
def getConfig(self):
149+
"""
150+
Return dict object of loaded config file
151+
152+
:return: dict
153+
"""
154+
return self.backend.config
155+
156+
def getConfigModule(self):
157+
"""
158+
Return just part specific for this module type (module section in config file)
159+
160+
:return: dict
161+
"""
162+
return self.backend.info
163+
164+
def runHost(self, *args, **kwargs):
165+
"""
166+
Run command on host (local machine). all parameters are passed inside. the most important is command
167+
what contains command to run
168+
169+
:param args: pass thru
170+
:param kwargs: pass thru
171+
:return: object of avocado.process.run
172+
"""
173+
return self.backend.runHost(*args, **kwargs)
174+
175+
def getModulemdYamlconfig(self, *args, **kwargs):
176+
"""
177+
Return dict of actual moduleMD file
178+
179+
:param args: pass thru
180+
:param kwargs: pass thru
181+
:return: dict
182+
"""
183+
return self.backend.getModulemdYamlconfig(*args, **kwargs)
184+
185+
def getActualProfile(self):
186+
"""
187+
Return actual profile set profile via env variable PROFILE, could be used for filtering tests with skipIf method
188+
Actually it returns list of packages, because profiles are not defined well
189+
190+
:return: str
191+
"""
192+
self.start()
193+
allpackages = self.run(r'rpm -qa --qf="%{{name}}\n"', verbose=is_not_silent()).stdout.split('\n')
194+
return allpackages
195+
196+
def copyTo(self, *args, **kwargs):
197+
"""
198+
Copy file from host machine to module
199+
200+
:param src: source file from host
201+
:param dest: destination file inside module
202+
:return: None
203+
"""
204+
return self.backend.copyTo(*args, **kwargs)
205+
206+
def copyFrom(self, *args, **kwargs):
207+
"""
208+
Copy file from module to host machine
209+
210+
:param src: source file from host
211+
:param dest: destination file inside module
212+
:return: None
213+
"""
214+
return self.backend.copyFrom(*args, **kwargs)
215+
216+
def getIPaddr(self, *args, **kwargs):
217+
"""
218+
Return ip addr string of guest machine
219+
In many cases it should be same as host machine and port should be forwarded to host
220+
221+
:return: str
222+
"""
223+
return self.backend.getIPaddr(*args, **kwargs)
224+
225+
def getArch(self):
226+
"""
227+
get system architecture string
228+
229+
:return: str
230+
"""
231+
return self.backend.getArch()
232+
233+
def getModuleDependencies(self):
234+
"""
235+
get list of module dependencies dictionary, there is structure like:
236+
{module_name: {stream: master, urls=[repo_url1, repo_url2]},
237+
dependent_module_name: {stream: f26, urls=[repo_url3]}}
238+
239+
:return: dict
240+
"""
241+
return self.backend.getModuleDependencies()
242+
243+
244+
def get_backend():
245+
"""
246+
Return proper module type, set by config by default_module section, or defined via
247+
env variable "MODULE"
248+
249+
:return: tuple (specific module object, str)
250+
"""
251+
amodule = os.environ.get('MODULE')
252+
readconfig = CommonFunctions()
253+
readconfig.loadconfig()
254+
if "default_module" in readconfig.config and readconfig.config[
255+
"default_module"] is not None and amodule is None:
256+
amodule = readconfig.config["default_module"]
257+
if amodule == 'docker':
258+
return ContainerHelper(), amodule
259+
elif amodule == 'rpm':
260+
return RpmHelper(), amodule
261+
elif amodule == 'nspawn':
262+
return NspawnHelper(), amodule
263+
else:
264+
raise ModuleFrameworkException("Unsupported MODULE={0}".format(amodule), "supproted are: docker, rpm, nspawn")
265+
266+
# To keep backward compatibility. This method could be used by pure avocado tests and is already used
267+
get_correct_backend = get_backend

moduleframework/common.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ def get_if_module():
178178
return not bool(disable_module)
179179

180180

181-
def sanitize_text(text, replacement="_"):
181+
def sanitize_text(text, replacement="_", invalid_chars=["/", ";", "&", ">", "<", "|"]):
182182

183183
"""
184184
Replace invalid characters in a string.
@@ -189,7 +189,6 @@ def sanitize_text(text, replacement="_"):
189189
:param (str): replacement char, default: "_"
190190
:return: str
191191
"""
192-
invalid_chars=["/", ";", "&", ">", "<", "|"]
193192
for char in invalid_chars:
194193
if char in text:
195194
text = text.replace(char, replacement)
@@ -203,9 +202,8 @@ def sanitize_cmd(cmd):
203202
:param (str): command to sanitize
204203
:return: str
205204
"""
206-
char_to_escape = '"'
207-
if char_to_escape in cmd:
208-
cmd = cmd.replace(char_to_escape, '\\'.join(char_to_escape))
205+
if '"' in cmd:
206+
cmd = cmd.replace('"', r'\"')
209207
return cmd
210208

211209

@@ -347,8 +345,8 @@ def runHost(self, command="ls /", **kwargs):
347345
raise ModuleFrameworkException(
348346
"Command is formatted by using trans_dict. If you want to use "
349347
"brackets { } in your code, please use {{ }}. Possible values "
350-
"in trans_dict are: %s"
351-
% trans_dict)
348+
"in trans_dict are: %s. \nBAD COMMAND: %s"
349+
% (trans_dict, command))
352350
return process.run("%s" % formattedcommand, **kwargs)
353351

354352
def installTestDependencies(self, packages=None):

0 commit comments

Comments
 (0)