SCAutolib 3.3.5__tar.gz → 3.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. {scautolib-3.3.5/SCAutolib.egg-info → scautolib-3.4.0}/PKG-INFO +12 -2
  2. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib/cli_commands.py +182 -35
  3. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib/controller.py +46 -31
  4. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib/models/file.py +6 -0
  5. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib/models/gui.py +81 -24
  6. {scautolib-3.3.5 → scautolib-3.4.0/SCAutolib.egg-info}/PKG-INFO +12 -2
  7. {scautolib-3.3.5 → scautolib-3.4.0}/setup.py +1 -1
  8. {scautolib-3.3.5 → scautolib-3.4.0}/LICENSE +0 -0
  9. {scautolib-3.3.5 → scautolib-3.4.0}/MANIFEST.in +0 -0
  10. {scautolib-3.3.5 → scautolib-3.4.0}/README.md +0 -0
  11. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib/__init__.py +0 -0
  12. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib/enums.py +0 -0
  13. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib/exceptions.py +0 -0
  14. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib/isDistro.py +0 -0
  15. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib/models/CA.py +0 -0
  16. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib/models/__init__.py +0 -0
  17. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib/models/authselect.py +0 -0
  18. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib/models/card.py +0 -0
  19. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib/models/log.py +0 -0
  20. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib/models/user.py +0 -0
  21. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib/templates/ca.cnf +0 -0
  22. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib/templates/gnome_disable_welcome +0 -0
  23. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib/templates/softhsm2.conf +0 -0
  24. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib/templates/sssd.conf-10 +0 -0
  25. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib/templates/sssd.conf-8or9 +0 -0
  26. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib/templates/user.cnf +0 -0
  27. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib/templates/virt_cacard.service +0 -0
  28. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib/templates/virtcacard.cil +0 -0
  29. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib/utils.py +0 -0
  30. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib.egg-info/SOURCES.txt +0 -0
  31. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib.egg-info/dependency_links.txt +0 -0
  32. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib.egg-info/entry_points.txt +0 -0
  33. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib.egg-info/requires.txt +0 -0
  34. {scautolib-3.3.5 → scautolib-3.4.0}/SCAutolib.egg-info/top_level.txt +0 -0
  35. {scautolib-3.3.5 → scautolib-3.4.0}/requirements.txt +0 -0
  36. {scautolib-3.3.5 → scautolib-3.4.0}/setup.cfg +0 -0
  37. {scautolib-3.3.5 → scautolib-3.4.0}/test/test_ca.py +0 -0
  38. {scautolib-3.3.5 → scautolib-3.4.0}/test/test_card.py +0 -0
  39. {scautolib-3.3.5 → scautolib-3.4.0}/test/test_cli.py +0 -0
  40. {scautolib-3.3.5 → scautolib-3.4.0}/test/test_controller.py +0 -0
  41. {scautolib-3.3.5 → scautolib-3.4.0}/test/test_file.py +0 -0
  42. {scautolib-3.3.5 → scautolib-3.4.0}/test/test_openssl_conf.py +0 -0
  43. {scautolib-3.3.5 → scautolib-3.4.0}/test/test_sssd_conf.py +0 -0
  44. {scautolib-3.3.5 → scautolib-3.4.0}/test/test_user.py +0 -0
  45. {scautolib-3.3.5 → scautolib-3.4.0}/test/test_utils.py +0 -0
  46. {scautolib-3.3.5 → scautolib-3.4.0}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: SCAutolib
3
- Version: 3.3.5
3
+ Version: 3.4.0
4
4
  Summary: Python library for automation tests of smart cards using virtualization.
5
5
  Home-page: https://github.com/redhat-qe-security/SCAutolib
6
6
  Author: Pavel Yadlouski
@@ -32,6 +32,16 @@ Requires-Dist: pandas; extra == "graphical"
32
32
  Requires-Dist: numpy; extra == "graphical"
33
33
  Requires-Dist: pytesseract; extra == "graphical"
34
34
  Requires-Dist: keyboard; extra == "graphical"
35
+ Dynamic: author
36
+ Dynamic: author-email
37
+ Dynamic: classifier
38
+ Dynamic: description
39
+ Dynamic: description-content-type
40
+ Dynamic: home-page
41
+ Dynamic: provides-extra
42
+ Dynamic: requires-dist
43
+ Dynamic: requires-python
44
+ Dynamic: summary
35
45
 
36
46
  # Smart Card Automation library (SCAutolib)
37
47
  Test automation library for Smart Cards.
@@ -6,15 +6,53 @@ import click
6
6
  from pathlib import Path
7
7
  from sys import exit
8
8
 
9
+ from collections import OrderedDict
10
+
9
11
  from SCAutolib import logger, exceptions, schema_user
10
12
  from SCAutolib.controller import Controller
11
13
  from SCAutolib.enums import ReturnCode
12
14
 
13
15
 
14
- @click.group()
16
+ def check_conf_path(conf):
17
+ return click.Path(exists=True, resolve_path=True)(conf)
18
+
19
+
20
+ # In Help output, force the subcommand list to match the order
21
+ # listed in this file. Solution was found here:
22
+ # https://github.com/pallets/click/issues/513#issuecomment-301046782
23
+ class NaturalOrderGroup(click.Group):
24
+ """
25
+ Command group trying to list subcommands in the order they were added.
26
+ Example use::
27
+
28
+ @click.group(cls=NaturalOrderGroup)
29
+
30
+ If passing dict of commands from other sources, ensure they are of type
31
+ OrderedDict and properly ordered, otherwise order of them will be random
32
+ and newly added will come to the end.
33
+ """
34
+ def __init__(self, name=None, commands=None, **attrs):
35
+ if commands is None:
36
+ commands = OrderedDict()
37
+ elif not isinstance(commands, OrderedDict):
38
+ commands = OrderedDict(commands)
39
+ click.Group.__init__(self, name=name,
40
+ commands=commands,
41
+ **attrs)
42
+
43
+ def list_commands(self, ctx):
44
+ """
45
+ List command names as they are in commands dict.
46
+
47
+ If the dict is OrderedDict, it will preserve the order commands
48
+ were added.
49
+ """
50
+ return self.commands.keys()
51
+
52
+
53
+ @click.group(cls=NaturalOrderGroup)
15
54
  @click.option("--conf", "-c",
16
55
  default="./conf.json",
17
- type=click.Path(exists=True, resolve_path=True),
18
56
  show_default=True,
19
57
  help="Path to JSON configuration file.")
20
58
  @click.option('--force', "-f", is_flag=True, default=False, show_default=True,
@@ -29,35 +67,13 @@ def cli(ctx, force, verbose, conf):
29
67
  logger.setLevel(verbose)
30
68
  ctx.ensure_object(dict) # Create a dict to store the context
31
69
  ctx.obj["FORCE"] = force # Store the force option in the context
32
- ctx.obj["CONTROLLER"] = Controller(conf)
33
-
34
-
35
- @click.command()
36
- @click.option("--ca-type", "-t",
37
- required=False,
38
- default='all',
39
- type=click.Choice(['all', 'local', 'ipa'], case_sensitive=False),
40
- show_default=True,
41
- help="Type of the CA to be configured. If not set, all CA's "
42
- "from the config file would be configured")
43
- @click.pass_context
44
- def setup_ca(ctx, ca_type):
45
- """
46
- Configure the CA's in the config file. If more than one CA is
47
- specified, specified CA type would be configured.
48
- """
49
- cnt = ctx.obj["CONTROLLER"]
50
- if ca_type == 'all':
51
- cnt.setup_local_ca(force=ctx.obj["FORCE"])
52
- cnt.setup_ipa_client(force=ctx.obj["FORCE"])
53
- elif ca_type == 'local':
54
- cnt.setup_local_ca(force=ctx.obj["FORCE"])
55
- elif ca_type == 'ipa':
56
- cnt.setup_ipa_client(force=ctx.obj["FORCE"])
57
- exit(ReturnCode.SUCCESS.value)
70
+ parsed_conf = None
71
+ if ctx.invoked_subcommand != "gui":
72
+ parsed_conf = check_conf_path(conf)
73
+ ctx.obj["CONTROLLER"] = Controller(parsed_conf)
58
74
 
59
75
 
60
- @click.command()
76
+ @cli.command()
61
77
  @click.option("--gdm", "-g",
62
78
  required=False,
63
79
  default=False,
@@ -85,7 +101,32 @@ def prepare(ctx, gdm, install_missing, graphical):
85
101
  exit(ReturnCode.SUCCESS.value)
86
102
 
87
103
 
88
- @click.command()
104
+ @cli.command()
105
+ @click.option("--ca-type", "-t",
106
+ required=False,
107
+ default='all',
108
+ type=click.Choice(['all', 'local', 'ipa'], case_sensitive=False),
109
+ show_default=True,
110
+ help="Type of the CA to be configured. If not set, all CA's "
111
+ "from the config file would be configured")
112
+ @click.pass_context
113
+ def setup_ca(ctx, ca_type):
114
+ """
115
+ Configure the CA's in the config file. If more than one CA is
116
+ specified, specified CA type would be configured.
117
+ """
118
+ cnt = ctx.obj["CONTROLLER"]
119
+ if ca_type == 'all':
120
+ cnt.setup_local_ca(force=ctx.obj["FORCE"])
121
+ cnt.setup_ipa_client(force=ctx.obj["FORCE"])
122
+ elif ca_type == 'local':
123
+ cnt.setup_local_ca(force=ctx.obj["FORCE"])
124
+ elif ca_type == 'ipa':
125
+ cnt.setup_ipa_client(force=ctx.obj["FORCE"])
126
+ exit(ReturnCode.SUCCESS.value)
127
+
128
+
129
+ @cli.command()
89
130
  @click.argument("name",
90
131
  required=True,
91
132
  default=None)
@@ -154,7 +195,7 @@ def setup_user(ctx, name, card_dir, card_type, passwd, pin, user_type):
154
195
  exit(ReturnCode.SUCCESS.value)
155
196
 
156
197
 
157
- @click.command()
198
+ @cli.command()
158
199
  @click.pass_context
159
200
  def cleanup(ctx):
160
201
  """
@@ -165,7 +206,113 @@ def cleanup(ctx):
165
206
  exit(ReturnCode.SUCCESS.value)
166
207
 
167
208
 
168
- cli.add_command(setup_ca)
169
- cli.add_command(prepare)
170
- cli.add_command(setup_user)
171
- cli.add_command(cleanup)
209
+ @cli.group(cls=NaturalOrderGroup, chain=True)
210
+ @click.option("--install-missing", "-i",
211
+ required=False,
212
+ default=False,
213
+ is_flag=True,
214
+ help="Install missing packages")
215
+ @click.pass_context
216
+ def gui(ctx, install_missing):
217
+ """ Run GUI Test commands """
218
+ pass
219
+
220
+
221
+ @gui.command()
222
+ def init():
223
+ """ Initialize GUI for testing """
224
+ return "init"
225
+
226
+
227
+ @gui.command()
228
+ @click.option("--no",
229
+ required=False,
230
+ default=False,
231
+ is_flag=True,
232
+ help="Reverse the action")
233
+ @click.argument("name")
234
+ def assert_text(name, no):
235
+ """ Check if a word is found on the screen """
236
+ if no:
237
+ return f"assert_no_text:{name}"
238
+ return f"assert_text:{name}"
239
+
240
+
241
+ @gui.command()
242
+ @click.argument("name")
243
+ def click_on(name):
244
+ """ Click on object containing word """
245
+ return f"click_on:{name}"
246
+
247
+
248
+ @gui.command()
249
+ @click.option("--no",
250
+ required=False,
251
+ default=False,
252
+ is_flag=True,
253
+ help="Reverse the action")
254
+ def check_home_screen(no):
255
+ """ Check if screen appears to be the home screen """
256
+ if no:
257
+ return "check_no_home_screen"
258
+ return "check_home_screen"
259
+
260
+
261
+ @gui.command()
262
+ @click.argument("keys")
263
+ def kb_send(keys):
264
+ """ Send key(s) to keyboard """
265
+ return f"kb_send:{keys}"
266
+
267
+
268
+ @gui.command()
269
+ @click.argument("keys")
270
+ def kb_write(keys):
271
+ """ Send string to keyboard """
272
+ return f"kb_write:{keys}"
273
+
274
+
275
+ @gui.command()
276
+ def done():
277
+ """ cleanup after testing """
278
+ return "done"
279
+
280
+
281
+ @gui.result_callback()
282
+ @click.pass_context
283
+ def run_all(ctx, actions, install_missing):
284
+ """ Run all cli actions in order """
285
+ ctx.obj["CONTROLLER"].setup_graphical(install_missing, True)
286
+
287
+ from SCAutolib.models.gui import GUI
288
+ gui = GUI(from_cli=True)
289
+ for action in actions:
290
+ if "init" in action:
291
+ gui.__enter__()
292
+ if "assert_text" in action:
293
+ assert_text = action.split(":", 1)[1]
294
+ gui.assert_text(assert_text)
295
+ if "assert_no_text" in action:
296
+ assert_text = action.split(":", 1)[1]
297
+ gui.assert_no_text(assert_text)
298
+ if "click_on" in action:
299
+ click_on = action.split(":", 1)[1]
300
+ gui.click_on(click_on)
301
+ if "check_home_screen" in action:
302
+ gui.check_home_screen()
303
+ if "check_no_home_screen" in action:
304
+ gui.check_home_screen(False)
305
+ if "kb_send" in action:
306
+ params = action.split(":", 1)[1].split()[0]
307
+ gui.kb_send(params)
308
+ if "kb_write" in action:
309
+ params = action.split(":", 1)[1].split()[0]
310
+ gui.kb_write(params)
311
+ gui.kb_send('enter')
312
+ if "done" in action:
313
+ gui.__exit__(None, None, None)
314
+ ctx.obj["CONTROLLER"].cleanup()
315
+
316
+
317
+ if __name__ == "__main__":
318
+ cli()
@@ -34,7 +34,7 @@ class Controller:
34
34
  def conf_path(self):
35
35
  return self._lib_conf_path
36
36
 
37
- def __init__(self, config: Union[Path, str], params: {} = None):
37
+ def __init__(self, config: Union[Path, str] = None, params: {} = None):
38
38
  """
39
39
  Constructor will parse and check input configuration file. If some
40
40
  required fields in the configuration are missing, CLI parameters
@@ -55,15 +55,18 @@ class Controller:
55
55
  # Check params
56
56
 
57
57
  # Parse config file
58
- self._lib_conf_path = config.absolute() if isinstance(config, Path) \
59
- else Path(config).absolute()
60
-
61
- with self._lib_conf_path.open("r") as f:
62
- tmp_conf = json.load(f)
63
- if tmp_conf is None:
64
- raise exceptions.SCAutolibException(
65
- "Data are not loaded correctly.")
66
- self.lib_conf = self._validate_configuration(tmp_conf, params)
58
+ self.lib_conf = None
59
+ if config:
60
+ self._lib_conf_path = config.absolute() if isinstance(config, Path) \
61
+ else Path(config).absolute()
62
+
63
+ with self._lib_conf_path.open("r") as f:
64
+ tmp_conf = json.load(f)
65
+ if tmp_conf is None:
66
+ raise exceptions.SCAutolibException(
67
+ "Data are not loaded correctly.")
68
+ self.lib_conf = self._validate_configuration(tmp_conf, params)
69
+
67
70
  self.users = []
68
71
  for d in (LIB_DIR, LIB_BACKUP, LIB_DUMP, LIB_DUMP_USERS, LIB_DUMP_CAS,
69
72
  LIB_DUMP_CARDS, LIB_DUMP_CONFS):
@@ -147,12 +150,6 @@ class Controller:
147
150
 
148
151
  packages = ["opensc", "httpd", "sssd", "sssd-tools", "gnutls-utils",
149
152
  "openssl", "nss-tools"]
150
- if gdm:
151
- packages.append("gdm")
152
-
153
- if graphical:
154
- # ffmpeg-free is in EPEL repo
155
- packages += ["tesseract", "ffmpeg-free"]
156
153
 
157
154
  # Prepare for virtual cards
158
155
  if any(c["card_type"] == CardType.virtual
@@ -181,21 +178,7 @@ class Controller:
181
178
  raise exceptions.SCAutolibException(msg)
182
179
 
183
180
  if graphical:
184
- if not isDistro('fedora'):
185
- run(['dnf', 'groupinstall', 'Server with GUI', '-y',
186
- '--allowerasing'])
187
- run(['pip', 'install', 'python-uinput'])
188
- else:
189
- # Fedora doesn't have server with GUI group so installed gdm
190
- # manually and also python3-uinput should be installed from RPM
191
- run(['dnf', 'install', 'gdm', 'python3-uinput', '-y'])
192
- # disable subscription message
193
- run(['systemctl', '--global', 'mask',
194
- 'org.gnome.SettingsDaemon.Subscription.target'])
195
- # disable welcome message
196
- self.dconf_file.create()
197
- self.dconf_file.save()
198
- run('dconf update')
181
+ self.setup_graphical(install_missing, gdm)
199
182
 
200
183
  if not isDistro('fedora'):
201
184
  run(['dnf', 'groupinstall', "Smart Card Support", '-y',
@@ -216,6 +199,38 @@ class Controller:
216
199
  dump_to_json(user.User(username="root",
217
200
  password=self.lib_conf["root_passwd"]))
218
201
 
202
+ def setup_graphical(self, install_missing: bool, gdm: bool):
203
+ packages = ["gcc", "tesseract", "ffmpeg-free"]
204
+
205
+ if gdm:
206
+ packages.append("gdm")
207
+
208
+ missing = _check_packages(packages)
209
+ if install_missing and missing:
210
+ _install_packages(missing)
211
+ elif missing:
212
+ msg = "Can't continue with graphical. Some packages are missing: " \
213
+ f"{', '.join(missing)}"
214
+ logger.critical(msg)
215
+ raise exceptions.SCAutolibException(msg)
216
+
217
+ if not isDistro('fedora'):
218
+ run(['dnf', 'groupinstall', 'Server with GUI', '-y',
219
+ '--allowerasing'])
220
+ run(['pip', 'install', 'python-uinput'])
221
+ else:
222
+ # Fedora doesn't have server with GUI group so installed gdm
223
+ # manually and also python3-uinput should be installed from RPM
224
+ run(['dnf', 'install', 'gdm', 'python3-uinput', '-y'])
225
+ # disable subscription message
226
+ run(['systemctl', '--global', 'mask',
227
+ 'org.gnome.SettingsDaemon.Subscription.target'])
228
+ # disable welcome message
229
+ if not self.dconf_file.exists():
230
+ self.dconf_file.create()
231
+ self.dconf_file.save()
232
+ run('dconf update')
233
+
219
234
  def setup_local_ca(self, force: bool = False):
220
235
  """
221
236
  Setup local CA based on configuration from the configuration file. All
@@ -92,6 +92,12 @@ class File:
92
92
  f"Removed file {self._conf_file}."
93
93
  )
94
94
 
95
+ def exists(self):
96
+ """
97
+ Checks if a file exists. Returns boolean.
98
+ """
99
+ return self._conf_file.exists()
100
+
95
101
  def set(self, key: str, value: Union[int, str, bool], section: str = None,
96
102
  separator: str = "="):
97
103
  """
@@ -1,7 +1,6 @@
1
1
  import inspect
2
- import os
3
- from os.path import join
4
2
  from time import sleep, time
3
+ from pathlib import Path
5
4
 
6
5
  import cv2
7
6
  import keyboard
@@ -24,7 +23,14 @@ class Screen:
24
23
  """
25
24
  self.directory = directory
26
25
  self.html_file = html_file
26
+
27
+ taken_images = [str(image).split('/')[-1]
28
+ for image in Path(directory).iterdir()]
29
+ taken_images.sort(reverse=True)
30
+
27
31
  self.screenshot_num = 1
32
+ if len(taken_images) > 0:
33
+ self.screenshot_num = int(taken_images[0].split('.')[0]) + 1
28
34
 
29
35
  def screenshot(self, timeout: float = 30):
30
36
  """Runs ffmpeg to take a screenshot.
@@ -237,7 +243,8 @@ def log_decorator(func):
237
243
  class GUI:
238
244
  """Represents the GUI and allows controlling the system under test."""
239
245
 
240
- def __init__(self, wait_time: float = 5, res_dir_name: str = None):
246
+ def __init__(self, wait_time: float = 5, res_dir_name: str = None,
247
+ from_cli: bool = False):
241
248
  """Initializes the GUI of system under test.
242
249
 
243
250
  :param wait_time: Time to wait after each action
@@ -247,30 +254,52 @@ class GUI:
247
254
 
248
255
  self.wait_time = wait_time
249
256
  self.gdm_init_time = 10
257
+ self.from_cli = from_cli
250
258
  # Create the directory for screenshots
259
+ self.html_directory = Path("/tmp/SC-tests")
260
+ if not self.html_directory.exists():
261
+ self.html_directory.mkdir()
251
262
  if res_dir_name:
252
- self.html_directory = '/tmp/SC-tests/' + res_dir_name
263
+ self.html_directory = self.html_directory.joinpath(res_dir_name)
264
+ elif from_cli:
265
+ run_dirs = [str(run_dir).split('/')[-1]
266
+ for run_dir in self.html_directory.iterdir()
267
+ if "cli_gui" in str(run_dir)]
268
+ run_dirs.sort(reverse=True)
269
+
270
+ last_run_dir = Path(run_dirs[0]) if len(run_dirs) > 0 else None
271
+ if last_run_dir and not last_run_dir.joinpath('done').exists():
272
+ # Use the old run directory
273
+ logger.debug("Using HTML logging file from last time.")
274
+ self.html_directory = self.html_directory.joinpath(
275
+ last_run_dir)
276
+ else:
277
+ # Create new run directory
278
+ logger.debug("Creating new HTML logging file.")
279
+ self.html_directory = self.html_directory.joinpath(
280
+ str(int(time())) + '_cli_gui')
253
281
  else:
254
282
  calling_func = inspect.stack()[1][3]
255
- self.html_directory = '/tmp/SC-tests/' + str(int(time()))
256
- self.html_directory += "_" + calling_func
283
+ self.html_directory = self.html_directory.joinpath(
284
+ str(int(time())) + '_' + calling_func)
257
285
 
258
- self.screenshot_directory = self.html_directory + "/screenshots"
286
+ self.screenshot_directory = self.html_directory.joinpath("screenshots")
259
287
  # will create both dirs
260
- os.makedirs(self.screenshot_directory, exist_ok=True)
288
+ self.screenshot_directory.mkdir(parents=True, exist_ok=True)
261
289
 
262
- self.html_file = join(self.html_directory, "index.html")
263
- with open(self.html_file, 'w') as fp:
264
- fp.write(
265
- "<html lang=\"en\">\n"
266
- "<head>\n"
267
- "<meta charset=\"UTF-8\">\n"
268
- "<meta name=\"viewport\" content=\"width=device-width, "
269
- "initial-scale=1.0\">\n"
270
- "<title>Test Results</title>\n"
271
- "</head>\n"
272
- "<body style=\"background-color:#000;\">\n"
273
- )
290
+ self.html_file = self.html_directory.joinpath("index.html")
291
+ if not self.html_file.exists():
292
+ with open(self.html_file, 'w') as fp:
293
+ fp.write(
294
+ "<html lang=\"en\">\n"
295
+ "<head>\n"
296
+ "<meta charset=\"UTF-8\">\n"
297
+ "<meta name=\"viewport\" content=\"width=device-width, "
298
+ "initial-scale=1.0\">\n"
299
+ "<title>Test Results</title>\n"
300
+ "</head>\n"
301
+ "<body style=\"background-color:#000;\">\n"
302
+ )
274
303
 
275
304
  fmt = "<span style=\"color:limegreen;\">"
276
305
  fmt += "%(asctime)s</span> "
@@ -284,25 +313,36 @@ class GUI:
284
313
  logging.Formatter("<p>" + fmt + "</p>")
285
314
  )
286
315
 
316
+ if self.from_cli:
317
+ logger.addHandler(self.fileHandler)
318
+
287
319
  self.mouse = Mouse()
288
320
 
289
321
  # workaround for keyboard library
290
322
  # otherwise the first character is not sent
291
323
  keyboard.send('enter')
292
324
 
293
- def __enter__(self):
325
+ # create screen object to use from calls
294
326
  self.screen = Screen(self.screenshot_directory, self.html_file)
327
+
328
+ def __enter__(self):
295
329
  # By restarting gdm, the system gets into defined state
296
330
  run(['systemctl', 'restart', 'gdm'], check=True)
297
331
  # Cannot screenshot before gdm starts displaying
298
332
  # This would break the display
299
333
  sleep(self.gdm_init_time)
300
334
 
301
- logger.addHandler(self.fileHandler)
335
+ if not self.from_cli:
336
+ logger.addHandler(self.fileHandler)
302
337
 
303
338
  return self
304
339
 
305
340
  def __exit__(self, type, value, traceback):
341
+ done_file = self.html_directory.joinpath('done')
342
+ print(done_file)
343
+ if done_file.exists():
344
+ return
345
+
306
346
  run(['systemctl', 'stop', 'gdm'], check=True)
307
347
 
308
348
  with open(self.html_file, 'a') as fp:
@@ -311,7 +351,13 @@ class GUI:
311
351
  "</html>\n"
312
352
  )
313
353
 
314
- logger.removeHandler(self.fileHandler)
354
+ print(done_file)
355
+ with open(done_file, 'w') as fp:
356
+ fp.write("done")
357
+
358
+ if not self.from_cli:
359
+ logger.removeHandler(self.fileHandler)
360
+
315
361
  logger.info(f"HTML file with results created in {self.html_directory}.")
316
362
 
317
363
  @action_decorator
@@ -377,7 +423,18 @@ class GUI:
377
423
  def kb_write(self, *args, **kwargs):
378
424
  # delay is a workaround needed for keyboard library
379
425
  kwargs.setdefault('delay', 0.1)
380
- keyboard.write(*args, **kwargs)
426
+
427
+ word = args[0]
428
+ last = ""
429
+ for char in word:
430
+ if char.isupper():
431
+ if last != "":
432
+ keyboard.write(*[last], **kwargs)
433
+ last = ""
434
+ keyboard.send(f"shift+{char.lower()}")
435
+ else:
436
+ last = f"{last}{char}"
437
+ keyboard.write(*[last], **kwargs)
381
438
 
382
439
  @action_decorator
383
440
  @log_decorator
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: SCAutolib
3
- Version: 3.3.5
3
+ Version: 3.4.0
4
4
  Summary: Python library for automation tests of smart cards using virtualization.
5
5
  Home-page: https://github.com/redhat-qe-security/SCAutolib
6
6
  Author: Pavel Yadlouski
@@ -32,6 +32,16 @@ Requires-Dist: pandas; extra == "graphical"
32
32
  Requires-Dist: numpy; extra == "graphical"
33
33
  Requires-Dist: pytesseract; extra == "graphical"
34
34
  Requires-Dist: keyboard; extra == "graphical"
35
+ Dynamic: author
36
+ Dynamic: author-email
37
+ Dynamic: classifier
38
+ Dynamic: description
39
+ Dynamic: description-content-type
40
+ Dynamic: home-page
41
+ Dynamic: provides-extra
42
+ Dynamic: requires-dist
43
+ Dynamic: requires-python
44
+ Dynamic: summary
35
45
 
36
46
  # Smart Card Automation library (SCAutolib)
37
47
  Test automation library for Smart Cards.
@@ -24,7 +24,7 @@ graphical_reqs = [
24
24
 
25
25
  setup(
26
26
  name="SCAutolib",
27
- version="3.3.5",
27
+ version="3.4.0",
28
28
  description=description,
29
29
  long_description=long_description,
30
30
  long_description_content_type='text/markdown',
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes