SCAutolib 3.3.3__py3-none-any.whl → 3.4.0__py3-none-any.whl

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.

Potentially problematic release.


This version of SCAutolib might be problematic. Click here for more details.

SCAutolib/__init__.py CHANGED
@@ -7,10 +7,12 @@ from schema import Schema, Use, Or, And, Optional
7
7
 
8
8
  from SCAutolib.enums import CardType, UserType
9
9
 
10
- fmt = "%(name)s:%(module)s.%(funcName)s.%(lineno)d [%(levelname)s] %(message)s"
10
+ fmt = ("%(asctime)s %(name)s:%(module)s.%(funcName)s.%(lineno)d "
11
+ "[%(levelname)s] %(message)s")
11
12
  date_fmt = "%H:%M:%S"
12
13
  coloredlogs.install(level="DEBUG", fmt=fmt, datefmt=date_fmt,
13
- field_styles={'levelname': {'bold': True, 'color': 'blue'}})
14
+ field_styles={'levelname': {'bold': True, 'color': 'blue'},
15
+ 'asctime': {'color': 'green'}})
14
16
  logger = logging.getLogger(__name__)
15
17
  # Disable logs from imported packages
16
18
  logging.getLogger("paramiko").setLevel(logging.WARNING)
SCAutolib/cli_commands.py CHANGED
@@ -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()
SCAutolib/controller.py CHANGED
@@ -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
@@ -170,10 +167,6 @@ class Controller:
170
167
  for u in self.lib_conf["users"]]):
171
168
  packages += self._general_steps_for_ipa()
172
169
 
173
- # In RHEL-10 we need one extra policy for the pcsc-lite to work
174
- if isDistro(['rhel', 'centos'], version='10'):
175
- packages += ["sssd-polkit-rules"]
176
-
177
170
  # Check for installed packages
178
171
  missing = _check_packages(packages)
179
172
  if install_missing and missing:
@@ -185,21 +178,7 @@ class Controller:
185
178
  raise exceptions.SCAutolibException(msg)
186
179
 
187
180
  if graphical:
188
- if not isDistro('fedora'):
189
- run(['dnf', 'groupinstall', 'Server with GUI', '-y',
190
- '--allowerasing'])
191
- run(['pip', 'install', 'python-uinput'])
192
- else:
193
- # Fedora doesn't have server with GUI group so installed gdm
194
- # manually and also python3-uinput should be installed from RPM
195
- run(['dnf', 'install', 'gdm', 'python3-uinput', '-y'])
196
- # disable subscription message
197
- run(['systemctl', '--global', 'mask',
198
- 'org.gnome.SettingsDaemon.Subscription.target'])
199
- # disable welcome message
200
- self.dconf_file.create()
201
- self.dconf_file.save()
202
- run('dconf update')
181
+ self.setup_graphical(install_missing, gdm)
203
182
 
204
183
  if not isDistro('fedora'):
205
184
  run(['dnf', 'groupinstall', "Smart Card Support", '-y',
@@ -220,6 +199,38 @@ class Controller:
220
199
  dump_to_json(user.User(username="root",
221
200
  password=self.lib_conf["root_passwd"]))
222
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
+
223
234
  def setup_local_ca(self, force: bool = False):
224
235
  """
225
236
  Setup local CA based on configuration from the configuration file. All
@@ -453,6 +464,12 @@ class Controller:
453
464
  cache_file.unlink()
454
465
  logger.debug("Removed opensc file cache")
455
466
 
467
+ sssd_cache_dir = Path(os.path.expanduser('~sssd') + "/.cache/opensc/")
468
+ if sssd_cache_dir.exists():
469
+ for cache_file in sssd_cache_dir.iterdir():
470
+ cache_file.unlink()
471
+ logger.debug("Removed opensc file cache for sssd user")
472
+
456
473
  # file only created in graphical mode that is why it is removed.
457
474
  self.dconf_file.remove()
458
475
 
SCAutolib/models/file.py CHANGED
@@ -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
  """
SCAutolib/models/gui.py CHANGED
@@ -1,8 +1,6 @@
1
- import copy
2
1
  import inspect
3
- import os
4
- from os.path import join
5
2
  from time import sleep, time
3
+ from pathlib import Path
6
4
 
7
5
  import cv2
8
6
  import keyboard
@@ -15,28 +13,6 @@ from SCAutolib import run, logger
15
13
  from SCAutolib.isDistro import isDistro
16
14
 
17
15
 
18
- class HTMLFileHandler(logging.FileHandler):
19
- """Extending FileHandler to work with HTML files."""
20
-
21
- def __init__(
22
- self, filename, mode='a', encoding=None, delay=False, errors=None):
23
- """
24
- A handler class which writes formatted logging records to disk HTML
25
- files.
26
- """
27
- super(HTMLFileHandler, self).__init__(
28
- filename, mode, encoding, delay, errors
29
- )
30
-
31
- def emit(self, record: logging.LogRecord) -> None:
32
- """
33
- Do whatever it takes to actually log the specified logging record.
34
- """
35
- custom_record = copy.deepcopy(record)
36
- custom_record.msg = str(record.msg).rstrip().replace("\n", "<br>\n")
37
- return super().emit(custom_record)
38
-
39
-
40
16
  class Screen:
41
17
  """Captures the screenshots."""
42
18
 
@@ -47,7 +23,14 @@ class Screen:
47
23
  """
48
24
  self.directory = directory
49
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
+
50
31
  self.screenshot_num = 1
32
+ if len(taken_images) > 0:
33
+ self.screenshot_num = int(taken_images[0].split('.')[0]) + 1
51
34
 
52
35
  def screenshot(self, timeout: float = 30):
53
36
  """Runs ffmpeg to take a screenshot.
@@ -74,6 +57,9 @@ class Screen:
74
57
 
75
58
  if out.returncode == 0:
76
59
  captured = True
60
+ else:
61
+ logger.debug(f"ffmpeg failed with {out.returncode}. "
62
+ f"stdout: {out.stdout}, stderr: {out.stderr}")
77
63
 
78
64
  if not captured:
79
65
  raise Exception('Could not capture screenshot within timeout.')
@@ -257,7 +243,8 @@ def log_decorator(func):
257
243
  class GUI:
258
244
  """Represents the GUI and allows controlling the system under test."""
259
245
 
260
- 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):
261
248
  """Initializes the GUI of system under test.
262
249
 
263
250
  :param wait_time: Time to wait after each action
@@ -267,60 +254,95 @@ class GUI:
267
254
 
268
255
  self.wait_time = wait_time
269
256
  self.gdm_init_time = 10
257
+ self.from_cli = from_cli
270
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()
271
262
  if res_dir_name:
272
- 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')
273
281
  else:
274
282
  calling_func = inspect.stack()[1][3]
275
- self.html_directory = '/tmp/SC-tests/' + str(int(time()))
276
- self.html_directory += "_" + calling_func
283
+ self.html_directory = self.html_directory.joinpath(
284
+ str(int(time())) + '_' + calling_func)
277
285
 
278
- self.screenshot_directory = self.html_directory + "/screenshots"
286
+ self.screenshot_directory = self.html_directory.joinpath("screenshots")
279
287
  # will create both dirs
280
- os.makedirs(self.screenshot_directory, exist_ok=True)
288
+ self.screenshot_directory.mkdir(parents=True, exist_ok=True)
281
289
 
282
- self.html_file = join(self.html_directory, "index.html")
283
- with open(self.html_file, 'w') as fp:
284
- fp.write(
285
- "<html lang=\"en\">\n"
286
- "<head>\n"
287
- "<meta charset=\"UTF-8\">\n"
288
- "<meta name=\"viewport\" content=\"width=device-width, "
289
- "initial-scale=1.0\">\n"
290
- "<title>Test Results</title>\n"
291
- "</head>\n"
292
- "<body style=\"background-color:#000\">\n"
293
- )
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
+ )
294
303
 
295
- fmt = "<span style=\"color:white;\">"
304
+ fmt = "<span style=\"color:limegreen;\">"
305
+ fmt += "%(asctime)s</span> "
306
+ fmt += "<span style=\"color:white;\">"
296
307
  fmt += "%(name)s:%(module)s.%(funcName)s.%(lineno)d </span>"
297
308
  fmt += "<span style=\"color:royalblue;\">[%(levelname)s] </span>"
298
- fmt += "<span style=\"color:limegreen;\">%(message)s</span>"
299
- self.fileHandler = HTMLFileHandler(self.html_file)
309
+ fmt += "<pre style=\"color:limegreen;\">%(message)s</pre>"
310
+ self.fileHandler = logging.FileHandler(self.html_file)
300
311
  self.fileHandler.setLevel(logging.DEBUG)
301
312
  self.fileHandler.setFormatter(
302
313
  logging.Formatter("<p>" + fmt + "</p>")
303
314
  )
304
315
 
316
+ if self.from_cli:
317
+ logger.addHandler(self.fileHandler)
318
+
305
319
  self.mouse = Mouse()
306
320
 
307
321
  # workaround for keyboard library
308
322
  # otherwise the first character is not sent
309
323
  keyboard.send('enter')
310
324
 
311
- def __enter__(self):
325
+ # create screen object to use from calls
312
326
  self.screen = Screen(self.screenshot_directory, self.html_file)
327
+
328
+ def __enter__(self):
313
329
  # By restarting gdm, the system gets into defined state
314
330
  run(['systemctl', 'restart', 'gdm'], check=True)
315
331
  # Cannot screenshot before gdm starts displaying
316
332
  # This would break the display
317
333
  sleep(self.gdm_init_time)
318
334
 
319
- logger.addHandler(self.fileHandler)
335
+ if not self.from_cli:
336
+ logger.addHandler(self.fileHandler)
320
337
 
321
338
  return self
322
339
 
323
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
+
324
346
  run(['systemctl', 'stop', 'gdm'], check=True)
325
347
 
326
348
  with open(self.html_file, 'a') as fp:
@@ -329,7 +351,13 @@ class GUI:
329
351
  "</html>\n"
330
352
  )
331
353
 
332
- 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
+
333
361
  logger.info(f"HTML file with results created in {self.html_directory}.")
334
362
 
335
363
  @action_decorator
@@ -395,7 +423,18 @@ class GUI:
395
423
  def kb_write(self, *args, **kwargs):
396
424
  # delay is a workaround needed for keyboard library
397
425
  kwargs.setdefault('delay', 0.1)
398
- 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)
399
438
 
400
439
  @action_decorator
401
440
  @log_decorator
SCAutolib/utils.py CHANGED
@@ -145,6 +145,7 @@ def load_token(card_name: str = None, update_sssd: bool = False):
145
145
  key="matchrule",
146
146
  value=f"<SUBJECT>.*CN={card.CN}.*")
147
147
  sssd_conf.save()
148
+ run(["sss_cache", "-E"])
148
149
  run(["systemctl", "restart", "sssd"])
149
150
  return card
150
151
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: SCAutolib
3
- Version: 3.3.3
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
@@ -16,22 +16,32 @@ Classifier: Topic :: Software Development :: Testing :: Acceptance
16
16
  Requires-Python: >=3
17
17
  Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
- Requires-Dist: click >=8
20
- Requires-Dist: coloredlogs >=15
21
- Requires-Dist: paramiko >=2.10
22
- Requires-Dist: fabric >=2.7
23
- Requires-Dist: invoke >=1.7
24
- Requires-Dist: pytest >=7
25
- Requires-Dist: schema >=0.7
26
- Requires-Dist: python-freeipa >=1.0
27
- Requires-Dist: pexpect >=4
28
- Requires-Dist: distro >=1.5.0
19
+ Requires-Dist: click>=8
20
+ Requires-Dist: coloredlogs>=15
21
+ Requires-Dist: paramiko>=2.10
22
+ Requires-Dist: fabric>=2.7
23
+ Requires-Dist: invoke>=1.7
24
+ Requires-Dist: pytest>=7
25
+ Requires-Dist: schema>=0.7
26
+ Requires-Dist: python_freeipa>=1.0
27
+ Requires-Dist: pexpect>=4
28
+ Requires-Dist: distro>=1.5.0
29
29
  Provides-Extra: graphical
30
- Requires-Dist: opencv-python ; extra == 'graphical'
31
- Requires-Dist: pandas ; extra == 'graphical'
32
- Requires-Dist: numpy ; extra == 'graphical'
33
- Requires-Dist: pytesseract ; extra == 'graphical'
34
- Requires-Dist: keyboard ; extra == 'graphical'
30
+ Requires-Dist: opencv-python; extra == "graphical"
31
+ Requires-Dist: pandas; extra == "graphical"
32
+ Requires-Dist: numpy; extra == "graphical"
33
+ Requires-Dist: pytesseract; extra == "graphical"
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.
@@ -1,16 +1,16 @@
1
- SCAutolib/__init__.py,sha256=nC78DtNE09z1s-LglB2K_-iquLONQAOfg2qbK_6ay9g,5490
2
- SCAutolib/cli_commands.py,sha256=5G6JSGskB0igROUufRQ8xpKwoOv6tfzhWDo6o6nI-Ao,5888
3
- SCAutolib/controller.py,sha256=-SvoDp3qWRfpdoZmRl9MpKUvv1h4cwRKGAQLFxnoB2M,23384
1
+ SCAutolib/__init__.py,sha256=ysyjkZZ9rT08COAGbKZBlyu7PEminPAFWLEB_uotQ-4,5579
2
+ SCAutolib/cli_commands.py,sha256=LtpkVdFxnKmkazPX3UhB7eCTufNz5Uq0i4Naa4R7r2k,10014
3
+ SCAutolib/controller.py,sha256=k40WMW1_7mblgggCue7iSQOmfDJT6xQZW9Vxu5oWZDA,23976
4
4
  SCAutolib/enums.py,sha256=UviyFPIw6MPkJl-BwWd4eTtr47BRr_KMsDlHF-ErGcU,677
5
5
  SCAutolib/exceptions.py,sha256=-Jsj80CXOSXQacCI46PYXEIh6-tdHSOw3FE4itE_e5w,857
6
6
  SCAutolib/isDistro.py,sha256=nAbxa9q0LIWmxmwlZeKXd22M3s9_E6FbD9G5j5coLsY,1460
7
- SCAutolib/utils.py,sha256=dArD7_gVa9WA8N4GlP4PwfOaK_mm_HkwA-Z7dxM7IGY,7326
7
+ SCAutolib/utils.py,sha256=vtZ1bb-ngHCqDGnw_9qJ8A1avC3hXKCBGkR3kvFK9f0,7359
8
8
  SCAutolib/models/CA.py,sha256=vWXZNxUlula854MwcbVNSU1Eiy07bSH1dBr7Y2Zz20A,29633
9
9
  SCAutolib/models/__init__.py,sha256=8NZySkDbAn2sktD1L3__Y37kY9kEXM2o4TnN3hiIsfk,48
10
10
  SCAutolib/models/authselect.py,sha256=PqRcxB9RSAWmGSF1Z8u1YrE7OLrD9oj-sCzGJEAWHa8,3443
11
11
  SCAutolib/models/card.py,sha256=QhGn5hbdZaaEuH9T1jZNNwKTopTApJfQcjP5iSKP5Kk,16149
12
- SCAutolib/models/file.py,sha256=MCAyiB8rd9CEHgcjTaVRJBbv2i5Ig7M3uTWcosiS5hc,23323
13
- SCAutolib/models/gui.py,sha256=BIIHPXOiSDgBm3GIN8jFucQQHO-XeicGjVhECeTuEBk,16285
12
+ SCAutolib/models/file.py,sha256=h71HIO-zF4UJ8o_eG1P2p632eTQDEhUsXK39TJFmZEY,23460
13
+ SCAutolib/models/gui.py,sha256=9spdED8omO11UrJzQu2HEQvzuX2QCLD7FcmKZV_9ha4,17956
14
14
  SCAutolib/models/log.py,sha256=6EoiehIIJjCXZqbT_X3eQyKWCS-_yZ5RdJcX5HVTJXI,1499
15
15
  SCAutolib/models/user.py,sha256=VK4chlS7izTYiwmVuYjl_cknOe00FFbCbiQOfMOoZU4,6390
16
16
  SCAutolib/templates/ca.cnf,sha256=9oqUZUSy_lEtNLDViD8SwgJl1ZKCI1-DMri1feF6vjQ,1047
@@ -21,9 +21,9 @@ SCAutolib/templates/sssd.conf-8or9,sha256=eBQJu9AY7LG4OsHRxinUjUeQOIxSu_MksWPKfq
21
21
  SCAutolib/templates/user.cnf,sha256=pyyJhxFdOVlFqoVGVwjomOq-W4wEt3YWfRGZEXprwto,452
22
22
  SCAutolib/templates/virt_cacard.service,sha256=31NrSKUspYIKNOVhL4Usc62CImlu3heNZprJ8sdw11Y,299
23
23
  SCAutolib/templates/virtcacard.cil,sha256=TwxknjxnTtDK_KR3-MbKcLM0VrB76JVSlY-j84VaNZY,167
24
- SCAutolib-3.3.3.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
25
- SCAutolib-3.3.3.dist-info/METADATA,sha256=AnDOB83TI5EVhRjsOCS_cEGmVVoDXOjM6z-8_ZKE56s,2343
26
- SCAutolib-3.3.3.dist-info/WHEEL,sha256=Wyh-_nZ0DJYolHNn1_hMa4lM7uDedD_RGVwbmTjyItk,91
27
- SCAutolib-3.3.3.dist-info/entry_points.txt,sha256=SyEBTEHEsfYmYZ4L3mQ_RUkW_PRTEWurYgITxGkFLe4,54
28
- SCAutolib-3.3.3.dist-info/top_level.txt,sha256=z2XZ0S23vykXV_dZYNlLcgcSERgBDIWxmNsiiQBL-wQ,10
29
- SCAutolib-3.3.3.dist-info/RECORD,,
24
+ SCAutolib-3.4.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
25
+ SCAutolib-3.4.0.dist-info/METADATA,sha256=wT_80_w6642c4iUlm1GH43Drsy-dQvUGlHTsOid_5VE,2549
26
+ SCAutolib-3.4.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
27
+ SCAutolib-3.4.0.dist-info/entry_points.txt,sha256=SyEBTEHEsfYmYZ4L3mQ_RUkW_PRTEWurYgITxGkFLe4,54
28
+ SCAutolib-3.4.0.dist-info/top_level.txt,sha256=z2XZ0S23vykXV_dZYNlLcgcSERgBDIWxmNsiiQBL-wQ,10
29
+ SCAutolib-3.4.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (71.1.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5