cleepcli 1.45.0__tar.gz → 1.46.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 (37) hide show
  1. {cleepcli-1.45.0/cleepcli.egg-info → cleepcli-1.46.0}/PKG-INFO +4 -4
  2. {cleepcli-1.45.0 → cleepcli-1.46.0}/README.md +3 -3
  3. {cleepcli-1.45.0 → cleepcli-1.46.0}/bin/ccli +6 -4
  4. {cleepcli-1.45.0 → cleepcli-1.46.0}/bin/cleep-cli +6 -4
  5. {cleepcli-1.45.0 → cleepcli-1.46.0}/cleepcli/console.py +45 -8
  6. {cleepcli-1.45.0 → cleepcli-1.46.0}/cleepcli/module.py +2 -0
  7. {cleepcli-1.45.0 → cleepcli-1.46.0}/cleepcli/test.py +59 -67
  8. {cleepcli-1.45.0 → cleepcli-1.46.0}/cleepcli/version.py +1 -1
  9. {cleepcli-1.45.0 → cleepcli-1.46.0/cleepcli.egg-info}/PKG-INFO +4 -4
  10. {cleepcli-1.45.0 → cleepcli-1.46.0}/LICENSE +0 -0
  11. {cleepcli-1.45.0 → cleepcli-1.46.0}/MANIFEST.in +0 -0
  12. {cleepcli-1.45.0 → cleepcli-1.46.0}/cleepcli/__init__.py +0 -0
  13. {cleepcli-1.45.0 → cleepcli-1.46.0}/cleepcli/check.py +0 -0
  14. {cleepcli-1.45.0 → cleepcli-1.46.0}/cleepcli/ci.py +0 -0
  15. {cleepcli-1.45.0 → cleepcli-1.46.0}/cleepcli/cleepapi.py +0 -0
  16. {cleepcli-1.45.0 → cleepcli-1.46.0}/cleepcli/config.py +0 -0
  17. {cleepcli-1.45.0 → cleepcli-1.46.0}/cleepcli/docs.py +0 -0
  18. {cleepcli-1.45.0 → cleepcli-1.46.0}/cleepcli/file.py +0 -0
  19. {cleepcli-1.45.0 → cleepcli-1.46.0}/cleepcli/git.py +0 -0
  20. {cleepcli-1.45.0 → cleepcli-1.46.0}/cleepcli/package.py +0 -0
  21. {cleepcli-1.45.0 → cleepcli-1.46.0}/cleepcli/tools.py +0 -0
  22. {cleepcli-1.45.0 → cleepcli-1.46.0}/cleepcli/watch.py +0 -0
  23. {cleepcli-1.45.0 → cleepcli-1.46.0}/cleepcli.egg-info/SOURCES.txt +0 -0
  24. {cleepcli-1.45.0 → cleepcli-1.46.0}/cleepcli.egg-info/dependency_links.txt +0 -0
  25. {cleepcli-1.45.0 → cleepcli-1.46.0}/cleepcli.egg-info/requires.txt +0 -0
  26. {cleepcli-1.45.0 → cleepcli-1.46.0}/cleepcli.egg-info/top_level.txt +0 -0
  27. {cleepcli-1.45.0 → cleepcli-1.46.0}/pyproject.toml +0 -0
  28. {cleepcli-1.45.0 → cleepcli-1.46.0}/requirements.txt +0 -0
  29. {cleepcli-1.45.0 → cleepcli-1.46.0}/setup.cfg +0 -0
  30. {cleepcli-1.45.0 → cleepcli-1.46.0}/setup.py +0 -0
  31. {cleepcli-1.45.0 → cleepcli-1.46.0}/tests/test_check.py +0 -0
  32. {cleepcli-1.45.0 → cleepcli-1.46.0}/tests/test_config.py +0 -0
  33. {cleepcli-1.45.0 → cleepcli-1.46.0}/tests/test_console.py +0 -0
  34. {cleepcli-1.45.0 → cleepcli-1.46.0}/tests/test_coverage.py +0 -0
  35. {cleepcli-1.45.0 → cleepcli-1.46.0}/tests/test_docs_breaking.py +0 -0
  36. {cleepcli-1.45.0 → cleepcli-1.46.0}/tests/test_module_scaffold.py +0 -0
  37. {cleepcli-1.45.0 → cleepcli-1.46.0}/tests/test_watch.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cleepcli
3
- Version: 1.45.0
3
+ Version: 1.46.0
4
4
  Summary: Cleep-cli helps developers to build great Cleep applications from command line.
5
5
  Home-page: https://github.com/CleepDevice/cleep-cli
6
6
  Author: Tanguy Bonneau
@@ -42,7 +42,7 @@ This utility helps developers to build Cleep applications providing some useful
42
42
  - `coreget` to clone or pull official Cleep repository.
43
43
  - `coredocs` to generate core documentation in HTML format.
44
44
  - `coresync` to synchronize sources from repository to execution folders.
45
- - `coretests` to perform core tests suite.
45
+ - `coretests` to perform core unit tests suite (`--functional` for real OS binary tests on device / OS migration).
46
46
  - `coretestscov` to display last core tests code coverage report.
47
47
  - `cpuprof` to run CPU profiler on application execution instance.
48
48
  - `memprof` to run memory profiler on application execution instance.
@@ -52,7 +52,7 @@ This utility helps developers to build Cleep applications providing some useful
52
52
  - `moddelete` to delete installed files of a module.
53
53
  - `moddocs` to generate module documentation.
54
54
  - `modsync` to synchronize sources from module folder to execution folders.
55
- - `modtests` to execute module tests.
55
+ - `modtests` to execute module unit tests (`--functional` for real OS/device tests).
56
56
  - `modtestscov` to display last module tests code coverage report.
57
57
  - `modbuild` to create application package (.zip)
58
58
  - `modcheckback` to check app backend (python files structure and content)
@@ -142,7 +142,7 @@ The [watchdog](https://pypi.org/project/watchdog/) python library is used to det
142
142
 
143
143
  ## CI (GitHub Actions)
144
144
 
145
- A tag `vX.Y.Z` (or a manual **workflow_dispatch**) builds a **py3** wheel + sdist on `ubuntu-latest` (Python 3.9) via `python -m build` and uploads them to PyPI.
145
+ A tag `vX.Y.Z` (or a manual **workflow_dispatch**) builds a **py3** wheel + sdist on `ubuntu-latest` (Python 3.12) via `python -m build` and uploads them to PyPI.
146
146
 
147
147
  GitHub **environment** `actions` (Settings → Environments), secret:
148
148
 
@@ -6,7 +6,7 @@ This utility helps developers to build Cleep applications providing some useful
6
6
  - `coreget` to clone or pull official Cleep repository.
7
7
  - `coredocs` to generate core documentation in HTML format.
8
8
  - `coresync` to synchronize sources from repository to execution folders.
9
- - `coretests` to perform core tests suite.
9
+ - `coretests` to perform core unit tests suite (`--functional` for real OS binary tests on device / OS migration).
10
10
  - `coretestscov` to display last core tests code coverage report.
11
11
  - `cpuprof` to run CPU profiler on application execution instance.
12
12
  - `memprof` to run memory profiler on application execution instance.
@@ -16,7 +16,7 @@ This utility helps developers to build Cleep applications providing some useful
16
16
  - `moddelete` to delete installed files of a module.
17
17
  - `moddocs` to generate module documentation.
18
18
  - `modsync` to synchronize sources from module folder to execution folders.
19
- - `modtests` to execute module tests.
19
+ - `modtests` to execute module unit tests (`--functional` for real OS/device tests).
20
20
  - `modtestscov` to display last module tests code coverage report.
21
21
  - `modbuild` to create application package (.zip)
22
22
  - `modcheckback` to check app backend (python files structure and content)
@@ -106,7 +106,7 @@ The [watchdog](https://pypi.org/project/watchdog/) python library is used to det
106
106
 
107
107
  ## CI (GitHub Actions)
108
108
 
109
- A tag `vX.Y.Z` (or a manual **workflow_dispatch**) builds a **py3** wheel + sdist on `ubuntu-latest` (Python 3.9) via `python -m build` and uploads them to PyPI.
109
+ A tag `vX.Y.Z` (or a manual **workflow_dispatch**) builds a **py3** wheel + sdist on `ubuntu-latest` (Python 3.12) via `python -m build` and uploads them to PyPI.
110
110
 
111
111
  GitHub **environment** `actions` (Settings → Environments), secret:
112
112
 
@@ -114,12 +114,13 @@ def coresync():
114
114
  @click.option('--xml', is_flag=True, default=False, help='Use xml reporting instead of default one (report).')
115
115
  @click.option('--quiet', is_flag=True, default=True, help='Display or not coverage warnings.')
116
116
  @click.option('-p', '--pattern', default=None, help='Run tests on specified file pattern only')
117
- def coretests(coverage, output, xml, quiet, pattern):
117
+ @click.option('--functional', is_flag=True, help='Run functional tests (real OS binaries) instead of unit tests.')
118
+ def coretests(coverage, output, xml, quiet, pattern, functional):
118
119
  """
119
120
  Execute core tests
120
121
  """
121
122
  m = Test()
122
- res = m.core_tests(coverage, output, xml, quiet, pattern)
123
+ res = m.core_tests(coverage, output, xml, quiet, pattern, functional=functional)
123
124
 
124
125
  if not res:
125
126
  sys.exit(1)
@@ -385,7 +386,8 @@ def test():
385
386
  @click.option('--coverage', is_flag=True, help='Display coverage report.')
386
387
  @click.option('--copyto', help='Copy .coverage file to specified dir.')
387
388
  @click.option('-p', '--pattern', help='Execute tests matching pattern.')
388
- def modtests(ctx, module, coverage, copyto, pattern):
389
+ @click.option('--functional', is_flag=True, help='Run functional tests (real OS/device) instead of unit tests.')
390
+ def modtests(ctx, module, coverage, copyto, pattern, functional):
389
391
  """
390
392
  Execute module tests
391
393
  """
@@ -393,7 +395,7 @@ def modtests(ctx, module, coverage, copyto, pattern):
393
395
  ctx.invoke(modsync, module=module)
394
396
 
395
397
  m = Test()
396
- res = m.module_tests(module, coverage, copyto, pattern)
398
+ res = m.module_tests(module, coverage, copyto, pattern, functional=functional)
397
399
 
398
400
  if not res:
399
401
  sys.exit(1)
@@ -114,12 +114,13 @@ def coresync():
114
114
  @click.option('--xml', is_flag=True, default=False, help='Use xml reporting instead of default one (report).')
115
115
  @click.option('--quiet', is_flag=True, default=True, help='Display or not coverage warnings.')
116
116
  @click.option('-p', '--pattern', default=None, help='Run tests on specified file pattern only')
117
- def coretests(coverage, output, xml, quiet, pattern):
117
+ @click.option('--functional', is_flag=True, help='Run functional tests (real OS binaries) instead of unit tests.')
118
+ def coretests(coverage, output, xml, quiet, pattern, functional):
118
119
  """
119
120
  Execute core tests
120
121
  """
121
122
  m = Test()
122
- res = m.core_tests(coverage, output, xml, quiet, pattern)
123
+ res = m.core_tests(coverage, output, xml, quiet, pattern, functional=functional)
123
124
 
124
125
  if not res:
125
126
  sys.exit(1)
@@ -385,7 +386,8 @@ def test():
385
386
  @click.option('--coverage', is_flag=True, help='Display coverage report.')
386
387
  @click.option('--copyto', help='Copy .coverage file to specified dir.')
387
388
  @click.option('-p', '--pattern', help='Execute tests matching pattern.')
388
- def modtests(ctx, module, coverage, copyto, pattern):
389
+ @click.option('--functional', is_flag=True, help='Run functional tests (real OS/device) instead of unit tests.')
390
+ def modtests(ctx, module, coverage, copyto, pattern, functional):
389
391
  """
390
392
  Execute module tests
391
393
  """
@@ -393,7 +395,7 @@ def modtests(ctx, module, coverage, copyto, pattern):
393
395
  ctx.invoke(modsync, module=module)
394
396
 
395
397
  m = Test()
396
- res = m.module_tests(module, coverage, copyto, pattern)
398
+ res = m.module_tests(module, coverage, copyto, pattern, functional=functional)
397
399
 
398
400
  if not res:
399
401
  sys.exit(1)
@@ -4,6 +4,7 @@
4
4
  import sys
5
5
  import subprocess
6
6
  import time
7
+ import atexit
7
8
  from threading import Timer, Thread
8
9
  try: # pragma: no cover
9
10
  from Queue import Queue, Empty
@@ -58,12 +59,40 @@ class EndlessConsole(Thread):
58
59
  self.__stderr_queue = Queue()
59
60
  self.__stdout_thread = None
60
61
  self.__stderr_thread = None
62
+ self.__proc = None
63
+ self.__pid = None
64
+ # Child runs in its own session (setsid). Reap it if the CLI exits via
65
+ # Ctrl-C before kill() — otherwise coverage/pytest can orphan and OOM a Pi.
66
+ atexit.register(self.__reap_child)
61
67
 
62
68
  def __del__(self):
63
69
  """
64
70
  Destructor
65
71
  """
66
72
  self.__stop()
73
+ self.__reap_child()
74
+
75
+ def __reap_child(self):
76
+ """Kill leftover process group if the subprocess is still running."""
77
+ pid = self.__pid
78
+ proc = self.__proc
79
+ if not pid or pid == 1:
80
+ return
81
+ try:
82
+ if proc is not None and proc.poll() is None:
83
+ self.logger.debug('Reaping leftover process group pid=%s', pid)
84
+ if ON_POSIX:
85
+ os.killpg(os.getpgid(pid), signal.SIGKILL)
86
+ else: # pragma: no cover
87
+ proc.kill()
88
+ self.killed = True
89
+ except Exception: # pragma: no cover
90
+ pass
91
+ finally:
92
+ try:
93
+ atexit.unregister(self.__reap_child)
94
+ except Exception: # pragma: no cover
95
+ pass
67
96
 
68
97
  def __enqueue_output(self, output, queue):
69
98
  for line in iter(output.readline, b''):
@@ -103,6 +132,7 @@ class EndlessConsole(Thread):
103
132
  self.logger.debug('Process killed manually')
104
133
  self.killed = True
105
134
  self.__stop()
135
+ self.__reap_child()
106
136
 
107
137
  def __send_stds(self):
108
138
  """
@@ -156,7 +186,9 @@ class EndlessConsole(Thread):
156
186
  close_fds=True,
157
187
  preexec_fn=os.setsid
158
188
  )
159
- pid = proc.pid
189
+ self.__proc = proc
190
+ self.__pid = proc.pid
191
+ pid = self.__pid
160
192
  self.logger.trace('PID=%d' % pid)
161
193
 
162
194
  if self.callback:
@@ -214,14 +246,19 @@ class EndlessConsole(Thread):
214
246
  except Exception: # pragma: no cover
215
247
  pass
216
248
 
217
- # make sure process (and child processes) is really killed
218
- if self.killed and pid != 1:
249
+ # Always reap the process group if still alive. The child is started in its
250
+ # own session (setsid): if the CLI exits on Ctrl-C without kill(), pytest/
251
+ # coverage would otherwise keep running and can OOM a Raspberry Pi.
252
+ if pid != 1:
219
253
  try:
220
- if ON_POSIX:
221
- os.killpg(os.getpgid(pid), signal.SIGKILL)
222
- else: # pragma: no cover
223
- proc.kill()
224
- except Exception: # pragma: no cover
254
+ if proc.poll() is None:
255
+ self.logger.debug('Terminating leftover process group pid=%s', pid)
256
+ if ON_POSIX:
257
+ os.killpg(os.getpgid(pid), signal.SIGKILL)
258
+ else: # pragma: no cover
259
+ proc.kill()
260
+ self.killed = True
261
+ except Exception: # pragma: no cover
225
262
  pass
226
263
 
227
264
  # process is over
@@ -488,6 +488,8 @@ pytest_plugins = ('cleep.libs.tests.fixtures',)
488
488
  pythonpath = .
489
489
  testpaths = tests
490
490
  python_files = test_*.py
491
+ markers =
492
+ functional: real OS/device tests (CLEEP_FUNCTIONAL_TESTS / modtests --functional)
491
493
  """
492
494
  DOCS_CONF_PY = """# -*- coding: utf-8 -*-
493
495
  import os, sys
@@ -116,11 +116,11 @@ class Test():
116
116
  self.__stderr.append(stderr)
117
117
 
118
118
  def __console_callback(self, stdout, stderr):
119
- if stdout and stdout.find('----------------------------------------------------------------------') < 0:
119
+ # Forward pytest/unittest output as-is so failure tracebacks stay readable in CI
120
+ if stdout:
120
121
  self.logger.info(" | " + stdout)
121
- if stderr and stderr.find('----------------------------------------------------------------------') < 0:
122
- self.logger.info(" | " + stderr)
123
122
  if stderr:
123
+ self.logger.info(" | " + stderr)
124
124
  self.__stderr.append(stderr)
125
125
 
126
126
  def __console_end_callback(self, return_code, killed):
@@ -243,7 +243,7 @@ cd "%(path)s"
243
243
 
244
244
  return res
245
245
 
246
- def module_tests(self, module_name, display_coverage=False, copy_to=None, pattern=None):
246
+ def module_tests(self, module_name, display_coverage=False, copy_to=None, pattern=None, functional=False):
247
247
  """
248
248
  Execute module unit tests and display process output on stdout
249
249
 
@@ -252,6 +252,7 @@ cd "%(path)s"
252
252
  display_coverage (bool): display coverage report (default False)
253
253
  copy_to (str): copy coverage file to specified path
254
254
  pattern (str): run testcase that match pattern (no coverage)
255
+ functional (bool): run functional tests (real OS / device) instead of unit tests
255
256
 
256
257
  Returns:
257
258
  bool: True if process succeed, False otherwise
@@ -267,11 +268,16 @@ cd "%(path)s"
267
268
  if module_version is None:
268
269
  return False
269
270
 
270
- self.logger.info('Running unit tests...')
271
+ if functional:
272
+ self.logger.info('Running module functional tests...')
273
+ else:
274
+ self.logger.info('Running unit tests...')
271
275
  coverage_file_path = self.__get_coverage_file(module_name, module_version)
272
276
  module_root_path = self.__get_module_root_path(module_name)
273
277
  if pattern:
274
- cmd = self.__get_tests_cmd_with_pattern(module_root_path, pattern)
278
+ cmd = self.__get_tests_cmd_with_pattern(module_root_path, pattern, functional=functional)
279
+ elif functional:
280
+ cmd = self.__get_functional_tests_cmd(module_root_path)
275
281
  else:
276
282
  cmd = self.__get_tests_cmd_with_coverage(module_root_path, coverage_file_path)
277
283
  self.logger.debug('Test cmd: %s' % cmd)
@@ -284,18 +290,22 @@ cd "%(path)s"
284
290
  time.sleep(0.25)
285
291
 
286
292
  # display coverage report
287
- if display_coverage:
293
+ if display_coverage and not functional:
288
294
  self.logger.debug('Display coverage')
289
295
  self.logger.info(self.module_tests_coverage(module_name))
290
296
 
291
297
  # copy coverage file to standart location (in module tests directory) for coverage report tools
292
- if copy_to:
298
+ if copy_to and not functional:
293
299
  if not os.path.isfile(coverage_file_path):
294
300
  self.logger.error('Coverage file missing: %s' % coverage_file_path)
295
301
  return False
296
302
  shutil.copyfile(coverage_file_path, os.path.join(copy_to, '.coverage'))
297
303
 
298
304
  self.logger.debug('Return code: %s' % self.__endless_command_return_code)
305
+ # pytest exit code 5 = no tests collected (app has no @pytest.mark.functional)
306
+ if functional and self.__endless_command_return_code == 5:
307
+ self.logger.info('No functional tests for module "%s" — OK' % module_name)
308
+ return True
299
309
  if self.__endless_command_return_code!=0:
300
310
  return False
301
311
 
@@ -309,16 +319,30 @@ cd "%(path)s"
309
319
  """
310
320
  return """
311
321
  cd "%s"
312
- PYTHONPATH=. COVERAGE_FILE=%s coverage run --omit="*/lib/python*/*","*/tests/*","test_*" --source=backend --concurrency=thread -m pytest tests
322
+ PYTHONPATH=. COVERAGE_FILE=%s coverage run --omit="*/lib/python*/*","*/tests/*","test_*" --source=backend --concurrency=thread -m pytest --tb=short -ra -m "not functional" tests
313
323
  """ % (module_root_path, coverage_file_path)
314
324
 
315
- def __get_tests_cmd_with_pattern(self, module_root_path, pattern):
325
+ def __get_functional_tests_cmd(self, module_root_path):
326
+ """
327
+ Return cmdline to run module functional tests only (no coverage).
328
+ """
329
+ return """
330
+ cd "%s"
331
+ CLEEP_FUNCTIONAL_TESTS=1 PYTHONPATH=. python3 -m pytest -v --tb=short -ra -m functional tests
332
+ """ % module_root_path
333
+
334
+ def __get_tests_cmd_with_pattern(self, module_root_path, pattern, functional=False):
316
335
  """
317
336
  Return cmdline to run tests matching specified pattern (no coverage).
318
337
  """
338
+ if functional:
339
+ return """
340
+ cd "%s"
341
+ CLEEP_FUNCTIONAL_TESTS=1 PYTHONPATH=. python3 -m pytest --tb=short -ra -m functional tests -k "%s"
342
+ """ % (module_root_path, pattern)
319
343
  return """
320
344
  cd "%s"
321
- PYTHONPATH=. python3 -m pytest tests -k "%s"
345
+ PYTHONPATH=. python3 -m pytest --tb=short -ra -m "not functional" tests -k "%s"
322
346
  """ % (module_root_path, pattern)
323
347
 
324
348
  def module_tests_coverage(self, module_name, missing=False, as_json=False, quiet=True):
@@ -379,57 +403,7 @@ PYTHONPATH=. python3 -m pytest tests -k "%s"
379
403
  """
380
404
  return config.REPO_DIR
381
405
 
382
- def __list_core_files(self, core_path, pattern=None):
383
- """
384
- Return all python files on core with their associated tests
385
-
386
- Args:
387
- core_path (string): core path
388
- pattern (str): custom pattern to filter files to process
389
-
390
- Returns:
391
- dict: list of files with test file path::
392
-
393
- {
394
- python file (string): test file (string)
395
- }
396
-
397
- """
398
- files = []
399
- tests_path = self.__get_core_tests_path()
400
-
401
- self.logger.info('Searching core files...')
402
- for root, _, filenames in os.walk(core_path):
403
- for filename in filenames:
404
- # build paths
405
- filepath = os.path.join(root, filename)
406
- core_relative_path = root.replace('%s' % core_path, '')
407
- core_relative_path = core_relative_path[1:] if core_relative_path.startswith('/') else core_relative_path
408
- test_filepath = os.path.join(tests_path, core_relative_path, 'test_%s' % filename)
409
-
410
- # drop useless files
411
- if not filename.endswith('.py'):
412
- self.logger.debug(' Drop file "%s": not python file', filepath)
413
- continue
414
- if filename.startswith('__init__'):
415
- self.logger.debug(' Drop file "%s": python init file', filepath)
416
- continue
417
- if filename.startswith('test_'):
418
- self.logger.debug(' Drop file "%s": test file', filepath)
419
- continue
420
- if root.endswith('libs/tests'):
421
- self.logger.debug(' Drop file "%s": test lib', filepath)
422
- continue
423
- if pattern and not re.search(pattern, test_filepath) and not re.search(pattern, filepath):
424
- self.logger.debug(' Drop file "%s": does not match pattern %s', filepath, pattern)
425
- continue
426
-
427
- self.logger.debug(' Found "%s" [%s]' % (filepath, test_filepath))
428
- files.append((filepath, test_filepath))
429
-
430
- return files
431
-
432
- def core_tests(self, display_coverage=False, display_test_output=False, xml=False, quiet=True, pattern=None):
406
+ def core_tests(self, display_coverage=False, display_test_output=False, xml=False, quiet=True, pattern=None, functional=False):
433
407
  """
434
408
  Execute core unit tests and display process output on stdout
435
409
 
@@ -439,6 +413,7 @@ PYTHONPATH=. python3 -m pytest tests -k "%s"
439
413
  xml (bool): use xml coverage command instead of report command
440
414
  quiet (bool): display or not coverage.py warnings
441
415
  pattern (str): custom pattern to filter tests (pytest -k)
416
+ functional (bool): run functional tests (real OS binaries) instead of unit tests
442
417
 
443
418
  Returns:
444
419
  bool: True if process succeed.
@@ -451,15 +426,25 @@ PYTHONPATH=. python3 -m pytest tests -k "%s"
451
426
  self.logger.error('Unable to clear previous tests results')
452
427
  return False
453
428
 
454
- self.logger.info('Running core unit tests (pytest)...')
429
+ if functional:
430
+ self.logger.info('Running core functional tests (pytest, real OS binaries)...')
431
+ marker_opt = '-m functional'
432
+ env_prefix = 'CLEEP_FUNCTIONAL_TESTS=1 '
433
+ else:
434
+ self.logger.info('Running core unit tests (pytest)...')
435
+ marker_opt = '-m "not functional"'
436
+ env_prefix = ''
455
437
  pattern_opt = '-k "%s"' % pattern if pattern else ''
456
- verbosity = '-v' if display_test_output else '-q'
438
+ # Always keep short failure tracebacks (+ failed/error summary) so CI logs are actionable
439
+ verbosity = '-v --tb=short -ra' if display_test_output else '-q --tb=short -ra'
457
440
  cmd = """
458
441
  cd "%(repo_dir)s"
459
- PYTHONPATH=. coverage run --omit="*/lib/python*/*","*/tests/*","*test_*.py" --source=cleep --concurrency=gevent -m pytest %(verbosity)s cleep/tests %(pattern_opt)s
442
+ %(env_prefix)sPYTHONPATH=. coverage run --omit="*/lib/python*/*","*/tests/*","*test_*.py" --source=cleep --concurrency=gevent -m pytest %(verbosity)s %(marker_opt)s cleep/tests %(pattern_opt)s
460
443
  """ % {
461
444
  'repo_dir': repo_dir,
445
+ 'env_prefix': env_prefix,
462
446
  'verbosity': verbosity,
447
+ 'marker_opt': marker_opt,
463
448
  'pattern_opt': pattern_opt,
464
449
  }
465
450
  self.logger.debug('Test cmd: %s' % cmd)
@@ -469,8 +454,15 @@ PYTHONPATH=. coverage run --omit="*/lib/python*/*","*/tests/*","*test_*.py" --so
469
454
  c = EndlessConsole(cmd, console_callback, self.__console_end_callback)
470
455
  c.start()
471
456
 
472
- while self.__endless_command_running:
473
- time.sleep(0.25)
457
+ try:
458
+ while self.__endless_command_running:
459
+ time.sleep(0.25)
460
+ except KeyboardInterrupt:
461
+ self.logger.warning('Interrupted — stopping tests...')
462
+ c.kill()
463
+ while self.__endless_command_running:
464
+ time.sleep(0.1)
465
+ raise
474
466
 
475
467
  success = self.__endless_command_return_code == 0
476
468
 
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env python
2
2
  # -*- coding: utf-8 -*-
3
3
 
4
- VERSION = "1.45.0"
4
+ VERSION = "1.46.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cleepcli
3
- Version: 1.45.0
3
+ Version: 1.46.0
4
4
  Summary: Cleep-cli helps developers to build great Cleep applications from command line.
5
5
  Home-page: https://github.com/CleepDevice/cleep-cli
6
6
  Author: Tanguy Bonneau
@@ -42,7 +42,7 @@ This utility helps developers to build Cleep applications providing some useful
42
42
  - `coreget` to clone or pull official Cleep repository.
43
43
  - `coredocs` to generate core documentation in HTML format.
44
44
  - `coresync` to synchronize sources from repository to execution folders.
45
- - `coretests` to perform core tests suite.
45
+ - `coretests` to perform core unit tests suite (`--functional` for real OS binary tests on device / OS migration).
46
46
  - `coretestscov` to display last core tests code coverage report.
47
47
  - `cpuprof` to run CPU profiler on application execution instance.
48
48
  - `memprof` to run memory profiler on application execution instance.
@@ -52,7 +52,7 @@ This utility helps developers to build Cleep applications providing some useful
52
52
  - `moddelete` to delete installed files of a module.
53
53
  - `moddocs` to generate module documentation.
54
54
  - `modsync` to synchronize sources from module folder to execution folders.
55
- - `modtests` to execute module tests.
55
+ - `modtests` to execute module unit tests (`--functional` for real OS/device tests).
56
56
  - `modtestscov` to display last module tests code coverage report.
57
57
  - `modbuild` to create application package (.zip)
58
58
  - `modcheckback` to check app backend (python files structure and content)
@@ -142,7 +142,7 @@ The [watchdog](https://pypi.org/project/watchdog/) python library is used to det
142
142
 
143
143
  ## CI (GitHub Actions)
144
144
 
145
- A tag `vX.Y.Z` (or a manual **workflow_dispatch**) builds a **py3** wheel + sdist on `ubuntu-latest` (Python 3.9) via `python -m build` and uploads them to PyPI.
145
+ A tag `vX.Y.Z` (or a manual **workflow_dispatch**) builds a **py3** wheel + sdist on `ubuntu-latest` (Python 3.12) via `python -m build` and uploads them to PyPI.
146
146
 
147
147
  GitHub **environment** `actions` (Settings → Environments), secret:
148
148
 
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
File without changes
File without changes
File without changes