cleepcli 1.46.0__tar.gz → 1.48.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.
- {cleepcli-1.46.0 → cleepcli-1.48.0}/MANIFEST.in +1 -0
- {cleepcli-1.46.0/cleepcli.egg-info → cleepcli-1.48.0}/PKG-INFO +20 -4
- {cleepcli-1.46.0 → cleepcli-1.48.0}/README.md +19 -3
- {cleepcli-1.46.0 → cleepcli-1.48.0}/bin/ccli +59 -4
- {cleepcli-1.46.0 → cleepcli-1.48.0}/bin/cleep-cli +59 -4
- {cleepcli-1.46.0 → cleepcli-1.48.0}/cleepcli/check.py +44 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/cleepcli/file.py +45 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/cleepcli/package.py +10 -0
- cleepcli-1.48.0/cleepcli/systemd/cleep-watch.service +18 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/cleepcli/version.py +1 -1
- {cleepcli-1.46.0 → cleepcli-1.48.0}/cleepcli/watch.py +24 -2
- cleepcli-1.48.0/cleepcli/watchsvc.py +214 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0/cleepcli.egg-info}/PKG-INFO +20 -4
- {cleepcli-1.46.0 → cleepcli-1.48.0}/cleepcli.egg-info/SOURCES.txt +5 -1
- {cleepcli-1.46.0 → cleepcli-1.48.0}/setup.py +1 -0
- cleepcli-1.48.0/tests/test_file_sync_all.py +91 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/tests/test_watch.py +7 -0
- cleepcli-1.48.0/tests/test_watchsvc.py +57 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/LICENSE +0 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/cleepcli/__init__.py +0 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/cleepcli/ci.py +0 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/cleepcli/cleepapi.py +0 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/cleepcli/config.py +0 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/cleepcli/console.py +0 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/cleepcli/docs.py +0 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/cleepcli/git.py +0 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/cleepcli/module.py +0 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/cleepcli/test.py +0 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/cleepcli/tools.py +0 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/cleepcli.egg-info/dependency_links.txt +0 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/cleepcli.egg-info/requires.txt +0 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/cleepcli.egg-info/top_level.txt +0 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/pyproject.toml +0 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/requirements.txt +0 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/setup.cfg +0 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/tests/test_check.py +0 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/tests/test_config.py +0 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/tests/test_console.py +0 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/tests/test_coverage.py +0 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/tests/test_docs_breaking.py +0 -0
- {cleepcli-1.46.0 → cleepcli-1.48.0}/tests/test_module_scaffold.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cleepcli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.48.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
|
|
@@ -61,7 +61,9 @@ This utility helps developers to build Cleep applications providing some useful
|
|
|
61
61
|
- `modcheckscripts` to check app scripts files
|
|
62
62
|
- `modcheckcode` to check backend code quality (run pylint)
|
|
63
63
|
- Watch commands:
|
|
64
|
-
- `watch` to watch for repository filesystem changes and automatically update files on execution folders. It also restart backend or/and frontend according to changes.
|
|
64
|
+
- `watch` to watch for repository filesystem changes and automatically update files on execution folders. It also restart backend or/and frontend according to changes (catch-up sync on start).
|
|
65
|
+
- `watchsvc` to install/enable/disable/restart the durable `cleep-watch` systemd unit (runs `watch` outside `cleep.service`).
|
|
66
|
+
- `syncall` to force a one-shot sync of core + all modules (same catch-up as watch start).
|
|
65
67
|
- Misc commands:
|
|
66
68
|
- `version` to get cleep-cli version
|
|
67
69
|
|
|
@@ -85,9 +87,23 @@ Execute this command to get help on this cli:
|
|
|
85
87
|
|
|
86
88
|
## Watch usage
|
|
87
89
|
|
|
88
|
-
|
|
90
|
+
Preferred: let the CLI own the durable systemd unit:
|
|
89
91
|
|
|
90
|
-
|
|
92
|
+
```
|
|
93
|
+
cleep-cli watchsvc install
|
|
94
|
+
cleep-cli watchsvc enable
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The unit runs `cleep-cli watch`, survives `systemctl restart cleep`, and on each start performs a catch-up `syncall` so files written while the watcher was down are applied.
|
|
98
|
+
|
|
99
|
+
Manual one-shot resync (when the UI looks stale):
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
cleep-cli syncall
|
|
103
|
+
cleep-cli watchsvc restart # catch-up again + resume watch
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
You can still run `cleep-cli watch` in a terminal for debugging. Push sources to `/root/cleep-dev` (CleepDesktop developer sync or SFTP); the watcher copies them into the execution folders and restarts backend/frontend as needed.
|
|
91
107
|
|
|
92
108
|
### Paths
|
|
93
109
|
|
|
@@ -25,7 +25,9 @@ This utility helps developers to build Cleep applications providing some useful
|
|
|
25
25
|
- `modcheckscripts` to check app scripts files
|
|
26
26
|
- `modcheckcode` to check backend code quality (run pylint)
|
|
27
27
|
- Watch commands:
|
|
28
|
-
- `watch` to watch for repository filesystem changes and automatically update files on execution folders. It also restart backend or/and frontend according to changes.
|
|
28
|
+
- `watch` to watch for repository filesystem changes and automatically update files on execution folders. It also restart backend or/and frontend according to changes (catch-up sync on start).
|
|
29
|
+
- `watchsvc` to install/enable/disable/restart the durable `cleep-watch` systemd unit (runs `watch` outside `cleep.service`).
|
|
30
|
+
- `syncall` to force a one-shot sync of core + all modules (same catch-up as watch start).
|
|
29
31
|
- Misc commands:
|
|
30
32
|
- `version` to get cleep-cli version
|
|
31
33
|
|
|
@@ -49,9 +51,23 @@ Execute this command to get help on this cli:
|
|
|
49
51
|
|
|
50
52
|
## Watch usage
|
|
51
53
|
|
|
52
|
-
|
|
54
|
+
Preferred: let the CLI own the durable systemd unit:
|
|
53
55
|
|
|
54
|
-
|
|
56
|
+
```
|
|
57
|
+
cleep-cli watchsvc install
|
|
58
|
+
cleep-cli watchsvc enable
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The unit runs `cleep-cli watch`, survives `systemctl restart cleep`, and on each start performs a catch-up `syncall` so files written while the watcher was down are applied.
|
|
62
|
+
|
|
63
|
+
Manual one-shot resync (when the UI looks stale):
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
cleep-cli syncall
|
|
67
|
+
cleep-cli watchsvc restart # catch-up again + resume watch
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
You can still run `cleep-cli watch` in a terminal for debugging. Push sources to `/root/cleep-dev` (CleepDesktop developer sync or SFTP); the watcher copies them into the execution folders and restarts backend/frontend as needed.
|
|
55
71
|
|
|
56
72
|
### Paths
|
|
57
73
|
|
|
@@ -17,6 +17,7 @@ from cleepcli.git import Git
|
|
|
17
17
|
from cleepcli.module import Module
|
|
18
18
|
from cleepcli.file import File
|
|
19
19
|
from cleepcli.watch import CleepWatchdog
|
|
20
|
+
from cleepcli.watchsvc import WatchService
|
|
20
21
|
from cleepcli.test import Test, is_file_coverage_valid
|
|
21
22
|
from cleepcli.package import Package
|
|
22
23
|
from cleepcli.docs import Docs
|
|
@@ -108,6 +109,18 @@ def coresync():
|
|
|
108
109
|
sys.exit(1)
|
|
109
110
|
logging.info('Done')
|
|
110
111
|
|
|
112
|
+
@core.command()
|
|
113
|
+
def syncall():
|
|
114
|
+
"""
|
|
115
|
+
Synchronize core and all modules from /root/cleep-dev to execution folders.
|
|
116
|
+
|
|
117
|
+
Same catch-up used when cleep-watch.service starts. Safe one-shot resync when
|
|
118
|
+
the UI looks stale: run this, then reload the device page.
|
|
119
|
+
"""
|
|
120
|
+
f = File()
|
|
121
|
+
if not f.sync_all():
|
|
122
|
+
sys.exit(1)
|
|
123
|
+
|
|
111
124
|
@core.command()
|
|
112
125
|
@click.option('--coverage', is_flag=True, help='Display coverage report.')
|
|
113
126
|
@click.option('--output', is_flag=True, help='Display tests output.')
|
|
@@ -358,9 +371,13 @@ def watchdog():
|
|
|
358
371
|
@click.option('--quiet', is_flag=True, help='Disable logging.')
|
|
359
372
|
@click.option('--loglevel', default=logging.INFO, help='Logging level (10=DEBUG, 20=INFO, 30=WARN, 40=ERROR).')
|
|
360
373
|
@click.option('--rpc-url', default='https://127.0.0.1:443', help='Cleep RPC url (ex https://ip|host:port')
|
|
361
|
-
|
|
374
|
+
@click.option('--skip-catchup', is_flag=True, help='Do not syncall before watching (not recommended).')
|
|
375
|
+
def watch(quiet, loglevel, rpc_url, skip_catchup):
|
|
362
376
|
"""
|
|
363
|
-
Start watchdog that monitors filesystem changes on Cleep sources
|
|
377
|
+
Start watchdog that monitors filesystem changes on Cleep sources.
|
|
378
|
+
|
|
379
|
+
Prefer managing the durable unit with watchsvc (survives Cleep restarts).
|
|
380
|
+
On start, syncs core + all modules once unless --skip-catchup is set.
|
|
364
381
|
"""
|
|
365
382
|
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
|
|
366
383
|
# do not overwrite root logger level if configured to DEBUG (dev mode)
|
|
@@ -369,13 +386,51 @@ def watch(quiet, loglevel, rpc_url):
|
|
|
369
386
|
logging.disable(logging.CRITICAL)
|
|
370
387
|
else:
|
|
371
388
|
logging.getLogger().setLevel(loglevel)
|
|
372
|
-
|
|
373
|
-
w = CleepWatchdog(rpc_url)
|
|
389
|
+
|
|
390
|
+
w = CleepWatchdog(rpc_url, catch_up=not skip_catchup)
|
|
374
391
|
res = w.watch()
|
|
375
392
|
|
|
376
393
|
if not res:
|
|
377
394
|
sys.exit(1)
|
|
378
395
|
|
|
396
|
+
@watchdog.command('watchsvc')
|
|
397
|
+
@click.argument(
|
|
398
|
+
'action',
|
|
399
|
+
type=click.Choice(
|
|
400
|
+
['install', 'uninstall', 'enable', 'disable', 'start', 'stop', 'restart', 'status']
|
|
401
|
+
),
|
|
402
|
+
)
|
|
403
|
+
def watchsvc_cmd(action):
|
|
404
|
+
"""
|
|
405
|
+
Manage the durable cleep-watch systemd unit.
|
|
406
|
+
|
|
407
|
+
ACTION is one of: install, uninstall, enable, disable, start, stop, restart, status.
|
|
408
|
+
|
|
409
|
+
The unit runs ``cleep-cli watch`` outside of cleep.service. Typical setup::
|
|
410
|
+
|
|
411
|
+
cleep-cli watchsvc install
|
|
412
|
+
cleep-cli watchsvc enable
|
|
413
|
+
"""
|
|
414
|
+
svc = WatchService()
|
|
415
|
+
handlers = {
|
|
416
|
+
'install': svc.install,
|
|
417
|
+
'uninstall': svc.uninstall,
|
|
418
|
+
'enable': svc.enable,
|
|
419
|
+
'disable': svc.disable,
|
|
420
|
+
'start': svc.start,
|
|
421
|
+
'stop': svc.stop,
|
|
422
|
+
'restart': svc.restart,
|
|
423
|
+
}
|
|
424
|
+
if action == 'status':
|
|
425
|
+
status = svc.status()
|
|
426
|
+
logging.info(status)
|
|
427
|
+
if status != 'active':
|
|
428
|
+
sys.exit(1)
|
|
429
|
+
return
|
|
430
|
+
|
|
431
|
+
if not handlers[action]():
|
|
432
|
+
sys.exit(1)
|
|
433
|
+
|
|
379
434
|
@click.group()
|
|
380
435
|
def test():
|
|
381
436
|
pass
|
|
@@ -17,6 +17,7 @@ from cleepcli.git import Git
|
|
|
17
17
|
from cleepcli.module import Module
|
|
18
18
|
from cleepcli.file import File
|
|
19
19
|
from cleepcli.watch import CleepWatchdog
|
|
20
|
+
from cleepcli.watchsvc import WatchService
|
|
20
21
|
from cleepcli.test import Test, is_file_coverage_valid
|
|
21
22
|
from cleepcli.package import Package
|
|
22
23
|
from cleepcli.docs import Docs
|
|
@@ -108,6 +109,18 @@ def coresync():
|
|
|
108
109
|
sys.exit(1)
|
|
109
110
|
logging.info('Done')
|
|
110
111
|
|
|
112
|
+
@core.command()
|
|
113
|
+
def syncall():
|
|
114
|
+
"""
|
|
115
|
+
Synchronize core and all modules from /root/cleep-dev to execution folders.
|
|
116
|
+
|
|
117
|
+
Same catch-up used when cleep-watch.service starts. Safe one-shot resync when
|
|
118
|
+
the UI looks stale: run this, then reload the device page.
|
|
119
|
+
"""
|
|
120
|
+
f = File()
|
|
121
|
+
if not f.sync_all():
|
|
122
|
+
sys.exit(1)
|
|
123
|
+
|
|
111
124
|
@core.command()
|
|
112
125
|
@click.option('--coverage', is_flag=True, help='Display coverage report.')
|
|
113
126
|
@click.option('--output', is_flag=True, help='Display tests output.')
|
|
@@ -358,9 +371,13 @@ def watchdog():
|
|
|
358
371
|
@click.option('--quiet', is_flag=True, help='Disable logging.')
|
|
359
372
|
@click.option('--loglevel', default=logging.INFO, help='Logging level (10=DEBUG, 20=INFO, 30=WARN, 40=ERROR).')
|
|
360
373
|
@click.option('--rpc-url', default='https://127.0.0.1:443', help='Cleep RPC url (ex https://ip|host:port')
|
|
361
|
-
|
|
374
|
+
@click.option('--skip-catchup', is_flag=True, help='Do not syncall before watching (not recommended).')
|
|
375
|
+
def watch(quiet, loglevel, rpc_url, skip_catchup):
|
|
362
376
|
"""
|
|
363
|
-
Start watchdog that monitors filesystem changes on Cleep sources
|
|
377
|
+
Start watchdog that monitors filesystem changes on Cleep sources.
|
|
378
|
+
|
|
379
|
+
Prefer managing the durable unit with watchsvc (survives Cleep restarts).
|
|
380
|
+
On start, syncs core + all modules once unless --skip-catchup is set.
|
|
364
381
|
"""
|
|
365
382
|
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
|
|
366
383
|
# do not overwrite root logger level if configured to DEBUG (dev mode)
|
|
@@ -369,13 +386,51 @@ def watch(quiet, loglevel, rpc_url):
|
|
|
369
386
|
logging.disable(logging.CRITICAL)
|
|
370
387
|
else:
|
|
371
388
|
logging.getLogger().setLevel(loglevel)
|
|
372
|
-
|
|
373
|
-
w = CleepWatchdog(rpc_url)
|
|
389
|
+
|
|
390
|
+
w = CleepWatchdog(rpc_url, catch_up=not skip_catchup)
|
|
374
391
|
res = w.watch()
|
|
375
392
|
|
|
376
393
|
if not res:
|
|
377
394
|
sys.exit(1)
|
|
378
395
|
|
|
396
|
+
@watchdog.command('watchsvc')
|
|
397
|
+
@click.argument(
|
|
398
|
+
'action',
|
|
399
|
+
type=click.Choice(
|
|
400
|
+
['install', 'uninstall', 'enable', 'disable', 'start', 'stop', 'restart', 'status']
|
|
401
|
+
),
|
|
402
|
+
)
|
|
403
|
+
def watchsvc_cmd(action):
|
|
404
|
+
"""
|
|
405
|
+
Manage the durable cleep-watch systemd unit.
|
|
406
|
+
|
|
407
|
+
ACTION is one of: install, uninstall, enable, disable, start, stop, restart, status.
|
|
408
|
+
|
|
409
|
+
The unit runs ``cleep-cli watch`` outside of cleep.service. Typical setup::
|
|
410
|
+
|
|
411
|
+
cleep-cli watchsvc install
|
|
412
|
+
cleep-cli watchsvc enable
|
|
413
|
+
"""
|
|
414
|
+
svc = WatchService()
|
|
415
|
+
handlers = {
|
|
416
|
+
'install': svc.install,
|
|
417
|
+
'uninstall': svc.uninstall,
|
|
418
|
+
'enable': svc.enable,
|
|
419
|
+
'disable': svc.disable,
|
|
420
|
+
'start': svc.start,
|
|
421
|
+
'stop': svc.stop,
|
|
422
|
+
'restart': svc.restart,
|
|
423
|
+
}
|
|
424
|
+
if action == 'status':
|
|
425
|
+
status = svc.status()
|
|
426
|
+
logging.info(status)
|
|
427
|
+
if status != 'active':
|
|
428
|
+
sys.exit(1)
|
|
429
|
+
return
|
|
430
|
+
|
|
431
|
+
if not handlers[action]():
|
|
432
|
+
sys.exit(1)
|
|
433
|
+
|
|
379
434
|
@click.group()
|
|
380
435
|
def test():
|
|
381
436
|
pass
|
|
@@ -1497,6 +1497,50 @@ overgeneral-exceptions=Exception
|
|
|
1497
1497
|
"""
|
|
1498
1498
|
return self._check_module_tree(module_name, "asset")
|
|
1499
1499
|
|
|
1500
|
+
def check_firmwares(self, module_name):
|
|
1501
|
+
"""
|
|
1502
|
+
Validate optional ``firmwares/`` zip+sidecar ``.manifest`` pairs and list
|
|
1503
|
+
files for packaging (see ``cleep.libs.internals.firmware_manifest``).
|
|
1504
|
+
|
|
1505
|
+
Returns:
|
|
1506
|
+
dict: errors / warnings / files (same shape as check_scripts)
|
|
1507
|
+
"""
|
|
1508
|
+
if not os.path.exists(os.path.join(config.MODULES_DST, module_name)):
|
|
1509
|
+
raise Exception('Module "%s" does not exist' % module_name)
|
|
1510
|
+
|
|
1511
|
+
firmwares_path = os.path.join(config.MODULES_SRC, module_name, "firmwares")
|
|
1512
|
+
out = {
|
|
1513
|
+
"errors": [],
|
|
1514
|
+
"warnings": [],
|
|
1515
|
+
"files": [],
|
|
1516
|
+
}
|
|
1517
|
+
if not os.path.isdir(firmwares_path):
|
|
1518
|
+
return out
|
|
1519
|
+
|
|
1520
|
+
try:
|
|
1521
|
+
from cleep.libs.internals.firmware_manifest import (
|
|
1522
|
+
FirmwareManifestError,
|
|
1523
|
+
scan_firmwares_dir,
|
|
1524
|
+
)
|
|
1525
|
+
except ImportError:
|
|
1526
|
+
out["errors"].append(
|
|
1527
|
+
"Unable to import cleep.libs.internals.firmware_manifest "
|
|
1528
|
+
"(is Cleep core available on PYTHONPATH?)"
|
|
1529
|
+
)
|
|
1530
|
+
return out
|
|
1531
|
+
|
|
1532
|
+
try:
|
|
1533
|
+
scan_firmwares_dir(firmwares_path)
|
|
1534
|
+
except FirmwareManifestError as error:
|
|
1535
|
+
out["errors"].append(str(error))
|
|
1536
|
+
return out
|
|
1537
|
+
|
|
1538
|
+
# Include valid pairs in the package file list
|
|
1539
|
+
tree = self._check_module_tree(module_name, "firmwares")
|
|
1540
|
+
out["files"] = tree["files"]
|
|
1541
|
+
out["warnings"] = tree["warnings"]
|
|
1542
|
+
return out
|
|
1543
|
+
|
|
1500
1544
|
def check_tests(self, module_name):
|
|
1501
1545
|
"""
|
|
1502
1546
|
Check test files
|
|
@@ -60,6 +60,51 @@ rsync -av "%(REPO_DIR)s/medias/sounds" "%(MEDIA_DST)s/sounds/" --delete
|
|
|
60
60
|
|
|
61
61
|
return True
|
|
62
62
|
|
|
63
|
+
def list_modules(self):
|
|
64
|
+
"""
|
|
65
|
+
List module folder names under MODULES_SRC (skips hidden dirs).
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
list: sorted module names
|
|
69
|
+
"""
|
|
70
|
+
if not os.path.isdir(config.MODULES_SRC):
|
|
71
|
+
return []
|
|
72
|
+
names = []
|
|
73
|
+
try:
|
|
74
|
+
for name in os.listdir(config.MODULES_SRC):
|
|
75
|
+
if name.startswith("."):
|
|
76
|
+
continue
|
|
77
|
+
path = os.path.join(config.MODULES_SRC, name)
|
|
78
|
+
if os.path.isdir(path):
|
|
79
|
+
names.append(name)
|
|
80
|
+
except OSError as error:
|
|
81
|
+
self.logger.error("Unable to list modules: %s", error)
|
|
82
|
+
return []
|
|
83
|
+
return sorted(names)
|
|
84
|
+
|
|
85
|
+
def sync_all(self):
|
|
86
|
+
"""
|
|
87
|
+
Synchronize core and every module from REPO_DIR to execution folders.
|
|
88
|
+
|
|
89
|
+
Used as watch catch-up on start and by the ``syncall`` CLI command so a
|
|
90
|
+
full resync is always one explicit action away.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
bool: True if every sync succeeded
|
|
94
|
+
"""
|
|
95
|
+
self.logger.info("Synchronizing core sources...")
|
|
96
|
+
if not self.core_sync():
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
modules = self.list_modules()
|
|
100
|
+
for module_name in modules:
|
|
101
|
+
self.logger.info('Synchronizing module "%s" sources...', module_name)
|
|
102
|
+
if not self.module_sync(module_name):
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
self.logger.info("Sync all done (%d module(s))", len(modules))
|
|
106
|
+
return True
|
|
107
|
+
|
|
63
108
|
def module_sync(self, module_name, backend=True, frontend=True, scripts=True):
|
|
64
109
|
"""
|
|
65
110
|
Copy specified module content to valid execution location
|
|
@@ -516,6 +516,12 @@ sha256sum $DEB > $SHA256
|
|
|
516
516
|
"Error in asset/. Fix it before packaging application: %s"
|
|
517
517
|
% data_asset["errors"]
|
|
518
518
|
)
|
|
519
|
+
data_firmwares = check.check_firmwares(module_name)
|
|
520
|
+
if len(data_firmwares["errors"]) > 0:
|
|
521
|
+
raise Exception(
|
|
522
|
+
"Error in firmwares/. Fix it before packaging application: %s"
|
|
523
|
+
% data_firmwares["errors"]
|
|
524
|
+
)
|
|
519
525
|
data_tests = check.check_tests(module_name)
|
|
520
526
|
if len(data_tests["errors"]) > 0:
|
|
521
527
|
raise Exception(
|
|
@@ -629,6 +635,10 @@ sha256sum $DEB > $SHA256
|
|
|
629
635
|
for a_file in data_asset["files"]:
|
|
630
636
|
archive.write(a_file["fullpath"], a_file["path"])
|
|
631
637
|
|
|
638
|
+
# add firmwares/ (zip + sidecar .manifest; installer copies to APP_FIRMWARE)
|
|
639
|
+
for a_file in data_firmwares["files"]:
|
|
640
|
+
archive.write(a_file["fullpath"], a_file["path"])
|
|
641
|
+
|
|
632
642
|
# add module.json
|
|
633
643
|
archive.write(module_json, "module.json")
|
|
634
644
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=Cleep development source watcher (cleep-cli watch)
|
|
3
|
+
Documentation=https://github.com/CleepDevice/cleep-cli
|
|
4
|
+
# Independent from cleep.service so backend restarts do not kill the watcher
|
|
5
|
+
# mid-sync (Desktop writes would otherwise be missed until the next edit).
|
|
6
|
+
After=network-online.target
|
|
7
|
+
Wants=network-online.target
|
|
8
|
+
|
|
9
|
+
[Service]
|
|
10
|
+
Type=simple
|
|
11
|
+
ExecStart=/usr/local/bin/cleep-cli watch --loglevel=20
|
|
12
|
+
Restart=always
|
|
13
|
+
RestartSec=3
|
|
14
|
+
# Allow a long catch-up sync on start before the main loop
|
|
15
|
+
TimeoutStartSec=180
|
|
16
|
+
|
|
17
|
+
[Install]
|
|
18
|
+
WantedBy=multi-user.target
|
|
@@ -18,6 +18,7 @@ from watchdog.events import (
|
|
|
18
18
|
import watchdog.events as events
|
|
19
19
|
from .cleepapi import CleepApi
|
|
20
20
|
from .file import File
|
|
21
|
+
from .watchsvc import WATCH_SERVICE
|
|
21
22
|
|
|
22
23
|
_WATCHED_EVENT_TYPES = (
|
|
23
24
|
EVENT_TYPE_CREATED,
|
|
@@ -206,6 +207,11 @@ class CleepHandler(FileSystemEventHandler):
|
|
|
206
207
|
REJECTED_FILENAMES = [
|
|
207
208
|
"4913", # vim furtive temp file to check user permissions
|
|
208
209
|
".gitignore",
|
|
210
|
+
# Module-root docs are not synced by module_sync; ignore to avoid noise.
|
|
211
|
+
"CHANGELOG.md",
|
|
212
|
+
"README.md",
|
|
213
|
+
"LICENSE",
|
|
214
|
+
"COPYING",
|
|
209
215
|
]
|
|
210
216
|
REJECTED_EXTENSIONS = [
|
|
211
217
|
".swp", # vim
|
|
@@ -521,22 +527,24 @@ class CleepWatchdog:
|
|
|
521
527
|
Cleep core and module watchdog
|
|
522
528
|
"""
|
|
523
529
|
|
|
524
|
-
def __init__(self, rpc_url):
|
|
530
|
+
def __init__(self, rpc_url, catch_up=True):
|
|
525
531
|
"""
|
|
526
532
|
Constructor
|
|
527
533
|
|
|
528
534
|
Args:
|
|
529
535
|
rpc_url (str): Cleep RPC url
|
|
536
|
+
catch_up (bool): when True, sync core + all modules before watching
|
|
537
|
+
so files written while the watcher was down are not missed
|
|
530
538
|
"""
|
|
531
539
|
self.logger = logging.getLogger(self.__class__.__name__)
|
|
532
540
|
self.actions_executor = ActionsExecutor(rpc_url)
|
|
541
|
+
self.catch_up = catch_up
|
|
533
542
|
|
|
534
543
|
def watch(self):
|
|
535
544
|
"""
|
|
536
545
|
Start watchdog that monitors filesystem changes on Cleep sources
|
|
537
546
|
"""
|
|
538
547
|
path = "%s/" % config.REPO_DIR
|
|
539
|
-
self.actions_executor.start()
|
|
540
548
|
|
|
541
549
|
if not os.path.exists(path):
|
|
542
550
|
self.logger.error(
|
|
@@ -544,6 +552,20 @@ class CleepWatchdog:
|
|
|
544
552
|
)
|
|
545
553
|
return False
|
|
546
554
|
|
|
555
|
+
# Catch-up before inotify: Desktop (or editors) may have written while
|
|
556
|
+
# this process was stopped (cleep restart, create-app pause, boot).
|
|
557
|
+
if self.catch_up:
|
|
558
|
+
self.logger.info("Catch-up sync before watching...")
|
|
559
|
+
if not File().sync_all():
|
|
560
|
+
self.logger.error(
|
|
561
|
+
"Catch-up sync failed; fix errors then retry "
|
|
562
|
+
"(cleep-cli syncall / systemctl restart %s)",
|
|
563
|
+
WATCH_SERVICE,
|
|
564
|
+
)
|
|
565
|
+
return False
|
|
566
|
+
|
|
567
|
+
self.actions_executor.start()
|
|
568
|
+
|
|
547
569
|
event_handler = CleepHandler(self.actions_executor)
|
|
548
570
|
observer = Observer()
|
|
549
571
|
event_handler.observer = observer
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Manage the durable cleep-watch systemd unit.
|
|
6
|
+
|
|
7
|
+
The unit runs ``cleep-cli watch`` outside of cleep.service so backend restarts
|
|
8
|
+
do not kill the watcher mid-sync.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
import os
|
|
13
|
+
import shutil
|
|
14
|
+
|
|
15
|
+
from .console import Console
|
|
16
|
+
|
|
17
|
+
WATCH_SERVICE = "cleep-watch.service"
|
|
18
|
+
WATCH_UNIT_DST = "/lib/systemd/system/%s" % WATCH_SERVICE
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def bundled_unit_path():
|
|
22
|
+
"""
|
|
23
|
+
Path to the unit file shipped inside the cleepcli package.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
str: absolute path to cleep-watch.service
|
|
27
|
+
"""
|
|
28
|
+
return os.path.join(os.path.dirname(__file__), "systemd", WATCH_SERVICE)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class WatchService:
|
|
32
|
+
"""
|
|
33
|
+
Install / enable / control cleep-watch.service via systemctl.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self):
|
|
37
|
+
self.logger = logging.getLogger(self.__class__.__name__)
|
|
38
|
+
self.console = Console()
|
|
39
|
+
|
|
40
|
+
def _systemctl(self, *args, timeout=60.0):
|
|
41
|
+
cmd = "/bin/systemctl " + " ".join(args)
|
|
42
|
+
self.logger.debug("systemctl cmd: %s", cmd)
|
|
43
|
+
return self.console.command(cmd, timeout)
|
|
44
|
+
|
|
45
|
+
def _ok(self, resp):
|
|
46
|
+
return not resp.get("killed") and resp.get("returncode", 1) == 0
|
|
47
|
+
|
|
48
|
+
def _daemon_reload(self):
|
|
49
|
+
return self._ok(self._systemctl("daemon-reload"))
|
|
50
|
+
|
|
51
|
+
def install(self):
|
|
52
|
+
"""
|
|
53
|
+
Copy the bundled unit to systemd and reload the daemon.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
bool: True on success
|
|
57
|
+
"""
|
|
58
|
+
src = bundled_unit_path()
|
|
59
|
+
if not os.path.isfile(src):
|
|
60
|
+
self.logger.error("Bundled unit not found: %s", src)
|
|
61
|
+
return False
|
|
62
|
+
try:
|
|
63
|
+
os.makedirs(os.path.dirname(WATCH_UNIT_DST), exist_ok=True)
|
|
64
|
+
shutil.copyfile(src, WATCH_UNIT_DST)
|
|
65
|
+
except OSError as error:
|
|
66
|
+
self.logger.error("Unable to install %s: %s", WATCH_UNIT_DST, error)
|
|
67
|
+
return False
|
|
68
|
+
if not self._daemon_reload():
|
|
69
|
+
self.logger.error("systemctl daemon-reload failed after install")
|
|
70
|
+
return False
|
|
71
|
+
self.logger.info("Installed %s", WATCH_UNIT_DST)
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
def uninstall(self):
|
|
75
|
+
"""
|
|
76
|
+
Disable the unit, remove the unit file, reload systemd.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
bool: True on success (missing unit is OK)
|
|
80
|
+
"""
|
|
81
|
+
self.disable()
|
|
82
|
+
try:
|
|
83
|
+
if os.path.isfile(WATCH_UNIT_DST):
|
|
84
|
+
os.remove(WATCH_UNIT_DST)
|
|
85
|
+
self.logger.info("Removed %s", WATCH_UNIT_DST)
|
|
86
|
+
except OSError as error:
|
|
87
|
+
self.logger.error("Unable to remove %s: %s", WATCH_UNIT_DST, error)
|
|
88
|
+
return False
|
|
89
|
+
if not self._daemon_reload():
|
|
90
|
+
self.logger.error("systemctl daemon-reload failed after uninstall")
|
|
91
|
+
return False
|
|
92
|
+
return True
|
|
93
|
+
|
|
94
|
+
def enable(self):
|
|
95
|
+
"""
|
|
96
|
+
Enable and start the unit (installs the unit file first if missing).
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
bool: True on success
|
|
100
|
+
"""
|
|
101
|
+
if not os.path.isfile(WATCH_UNIT_DST):
|
|
102
|
+
if not self.install():
|
|
103
|
+
return False
|
|
104
|
+
resp = self._systemctl("enable", "--now", WATCH_SERVICE)
|
|
105
|
+
if not self._ok(resp):
|
|
106
|
+
self.logger.error(
|
|
107
|
+
"Unable to enable %s: %s %s",
|
|
108
|
+
WATCH_SERVICE,
|
|
109
|
+
resp.get("stdout"),
|
|
110
|
+
resp.get("stderr"),
|
|
111
|
+
)
|
|
112
|
+
return False
|
|
113
|
+
return self.is_active()
|
|
114
|
+
|
|
115
|
+
def disable(self):
|
|
116
|
+
"""
|
|
117
|
+
Disable and stop the unit.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
bool: True on success (or if unit was not installed)
|
|
121
|
+
"""
|
|
122
|
+
if not os.path.isfile(WATCH_UNIT_DST):
|
|
123
|
+
return True
|
|
124
|
+
resp = self._systemctl("disable", "--now", WATCH_SERVICE)
|
|
125
|
+
if not self._ok(resp):
|
|
126
|
+
# Unit may already be inactive/disabled
|
|
127
|
+
self.logger.debug(
|
|
128
|
+
"disable --now %s: %s %s",
|
|
129
|
+
WATCH_SERVICE,
|
|
130
|
+
resp.get("stdout"),
|
|
131
|
+
resp.get("stderr"),
|
|
132
|
+
)
|
|
133
|
+
return True
|
|
134
|
+
|
|
135
|
+
def start(self):
|
|
136
|
+
"""
|
|
137
|
+
Start the unit (catch-up sync runs on ExecStart).
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
bool: True on success
|
|
141
|
+
"""
|
|
142
|
+
if not os.path.isfile(WATCH_UNIT_DST):
|
|
143
|
+
if not self.install():
|
|
144
|
+
return False
|
|
145
|
+
resp = self._systemctl("start", WATCH_SERVICE)
|
|
146
|
+
if not self._ok(resp):
|
|
147
|
+
self.logger.error(
|
|
148
|
+
"Unable to start %s: %s %s",
|
|
149
|
+
WATCH_SERVICE,
|
|
150
|
+
resp.get("stdout"),
|
|
151
|
+
resp.get("stderr"),
|
|
152
|
+
)
|
|
153
|
+
return False
|
|
154
|
+
return True
|
|
155
|
+
|
|
156
|
+
def stop(self):
|
|
157
|
+
"""
|
|
158
|
+
Stop the unit.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
bool: True on success
|
|
162
|
+
"""
|
|
163
|
+
if not os.path.isfile(WATCH_UNIT_DST):
|
|
164
|
+
return True
|
|
165
|
+
resp = self._systemctl("stop", WATCH_SERVICE)
|
|
166
|
+
if not self._ok(resp):
|
|
167
|
+
self.logger.error(
|
|
168
|
+
"Unable to stop %s: %s %s",
|
|
169
|
+
WATCH_SERVICE,
|
|
170
|
+
resp.get("stdout"),
|
|
171
|
+
resp.get("stderr"),
|
|
172
|
+
)
|
|
173
|
+
return False
|
|
174
|
+
return True
|
|
175
|
+
|
|
176
|
+
def restart(self):
|
|
177
|
+
"""
|
|
178
|
+
Restart the unit (re-runs catch-up sync on start).
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
bool: True on success
|
|
182
|
+
"""
|
|
183
|
+
if not os.path.isfile(WATCH_UNIT_DST):
|
|
184
|
+
return self.enable()
|
|
185
|
+
resp = self._systemctl("restart", WATCH_SERVICE, timeout=180.0)
|
|
186
|
+
if not self._ok(resp):
|
|
187
|
+
self.logger.error(
|
|
188
|
+
"Unable to restart %s: %s %s",
|
|
189
|
+
WATCH_SERVICE,
|
|
190
|
+
resp.get("stdout"),
|
|
191
|
+
resp.get("stderr"),
|
|
192
|
+
)
|
|
193
|
+
return False
|
|
194
|
+
return True
|
|
195
|
+
|
|
196
|
+
def is_active(self):
|
|
197
|
+
"""
|
|
198
|
+
Return True if the unit is active.
|
|
199
|
+
"""
|
|
200
|
+
resp = self._systemctl("is-active", WATCH_SERVICE, timeout=5.0)
|
|
201
|
+
active = "".join(resp.get("stdout") or []).strip()
|
|
202
|
+
return active == "active"
|
|
203
|
+
|
|
204
|
+
def status(self):
|
|
205
|
+
"""
|
|
206
|
+
Print / return a short status string for the unit.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
str: systemctl is-active output (or "not-installed")
|
|
210
|
+
"""
|
|
211
|
+
if not os.path.isfile(WATCH_UNIT_DST):
|
|
212
|
+
return "not-installed"
|
|
213
|
+
resp = self._systemctl("is-active", WATCH_SERVICE, timeout=5.0)
|
|
214
|
+
return "".join(resp.get("stdout") or []).strip() or "unknown"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cleepcli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.48.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
|
|
@@ -61,7 +61,9 @@ This utility helps developers to build Cleep applications providing some useful
|
|
|
61
61
|
- `modcheckscripts` to check app scripts files
|
|
62
62
|
- `modcheckcode` to check backend code quality (run pylint)
|
|
63
63
|
- Watch commands:
|
|
64
|
-
- `watch` to watch for repository filesystem changes and automatically update files on execution folders. It also restart backend or/and frontend according to changes.
|
|
64
|
+
- `watch` to watch for repository filesystem changes and automatically update files on execution folders. It also restart backend or/and frontend according to changes (catch-up sync on start).
|
|
65
|
+
- `watchsvc` to install/enable/disable/restart the durable `cleep-watch` systemd unit (runs `watch` outside `cleep.service`).
|
|
66
|
+
- `syncall` to force a one-shot sync of core + all modules (same catch-up as watch start).
|
|
65
67
|
- Misc commands:
|
|
66
68
|
- `version` to get cleep-cli version
|
|
67
69
|
|
|
@@ -85,9 +87,23 @@ Execute this command to get help on this cli:
|
|
|
85
87
|
|
|
86
88
|
## Watch usage
|
|
87
89
|
|
|
88
|
-
|
|
90
|
+
Preferred: let the CLI own the durable systemd unit:
|
|
89
91
|
|
|
90
|
-
|
|
92
|
+
```
|
|
93
|
+
cleep-cli watchsvc install
|
|
94
|
+
cleep-cli watchsvc enable
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The unit runs `cleep-cli watch`, survives `systemctl restart cleep`, and on each start performs a catch-up `syncall` so files written while the watcher was down are applied.
|
|
98
|
+
|
|
99
|
+
Manual one-shot resync (when the UI looks stale):
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
cleep-cli syncall
|
|
103
|
+
cleep-cli watchsvc restart # catch-up again + resume watch
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
You can still run `cleep-cli watch` in a terminal for debugging. Push sources to `/root/cleep-dev` (CleepDesktop developer sync or SFTP); the watcher copies them into the execution folders and restarts backend/frontend as needed.
|
|
91
107
|
|
|
92
108
|
### Paths
|
|
93
109
|
|
|
@@ -22,15 +22,19 @@ cleepcli/test.py
|
|
|
22
22
|
cleepcli/tools.py
|
|
23
23
|
cleepcli/version.py
|
|
24
24
|
cleepcli/watch.py
|
|
25
|
+
cleepcli/watchsvc.py
|
|
25
26
|
cleepcli.egg-info/PKG-INFO
|
|
26
27
|
cleepcli.egg-info/SOURCES.txt
|
|
27
28
|
cleepcli.egg-info/dependency_links.txt
|
|
28
29
|
cleepcli.egg-info/requires.txt
|
|
29
30
|
cleepcli.egg-info/top_level.txt
|
|
31
|
+
cleepcli/systemd/cleep-watch.service
|
|
30
32
|
tests/test_check.py
|
|
31
33
|
tests/test_config.py
|
|
32
34
|
tests/test_console.py
|
|
33
35
|
tests/test_coverage.py
|
|
34
36
|
tests/test_docs_breaking.py
|
|
37
|
+
tests/test_file_sync_all.py
|
|
35
38
|
tests/test_module_scaffold.py
|
|
36
|
-
tests/test_watch.py
|
|
39
|
+
tests/test_watch.py
|
|
40
|
+
tests/test_watchsvc.py
|
|
@@ -39,6 +39,7 @@ setup(
|
|
|
39
39
|
maintainer_email="tanguy.bonneau@gmail.com",
|
|
40
40
|
url="https://github.com/CleepDevice/cleep-cli",
|
|
41
41
|
packages=["cleepcli"],
|
|
42
|
+
package_data={"cleepcli": ["systemd/cleep-watch.service"]},
|
|
42
43
|
include_package_data=True,
|
|
43
44
|
install_requires=get_requirements(),
|
|
44
45
|
python_requires=">=3.7",
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
import tempfile
|
|
7
|
+
|
|
8
|
+
from unittest.mock import Mock, patch
|
|
9
|
+
|
|
10
|
+
from cleepcli import config
|
|
11
|
+
from cleepcli.file import File
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_list_modules_sorted():
|
|
15
|
+
repo = tempfile.mkdtemp()
|
|
16
|
+
try:
|
|
17
|
+
modules = os.path.join(repo, "modules")
|
|
18
|
+
os.makedirs(os.path.join(modules, "zeta"))
|
|
19
|
+
os.makedirs(os.path.join(modules, "alpha"))
|
|
20
|
+
os.makedirs(os.path.join(modules, ".hidden"))
|
|
21
|
+
open(os.path.join(modules, "file.txt"), "w", encoding="utf-8").close()
|
|
22
|
+
|
|
23
|
+
with patch.object(config, "MODULES_SRC", modules):
|
|
24
|
+
assert File().list_modules() == ["alpha", "zeta"]
|
|
25
|
+
finally:
|
|
26
|
+
shutil.rmtree(repo)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_sync_all_runs_core_then_modules():
|
|
30
|
+
f = File()
|
|
31
|
+
f.core_sync = Mock(return_value=True)
|
|
32
|
+
f.module_sync = Mock(return_value=True)
|
|
33
|
+
f.list_modules = Mock(return_value=["audio", "developer"])
|
|
34
|
+
|
|
35
|
+
assert f.sync_all() is True
|
|
36
|
+
f.core_sync.assert_called_once_with()
|
|
37
|
+
assert f.module_sync.call_args_list[0].args[0] == "audio"
|
|
38
|
+
assert f.module_sync.call_args_list[1].args[0] == "developer"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_sync_all_stops_on_core_failure():
|
|
42
|
+
f = File()
|
|
43
|
+
f.core_sync = Mock(return_value=False)
|
|
44
|
+
f.module_sync = Mock(return_value=True)
|
|
45
|
+
f.list_modules = Mock(return_value=["audio"])
|
|
46
|
+
|
|
47
|
+
assert f.sync_all() is False
|
|
48
|
+
f.module_sync.assert_not_called()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_watch_catch_up_calls_sync_all():
|
|
52
|
+
from cleepcli.watch import CleepWatchdog
|
|
53
|
+
|
|
54
|
+
with patch("cleepcli.watch.File") as file_cls, patch(
|
|
55
|
+
"cleepcli.watch.os.path.exists", return_value=True
|
|
56
|
+
), patch("cleepcli.watch.Observer"), patch(
|
|
57
|
+
"cleepcli.watch.CleepHandler"
|
|
58
|
+
) as handler_cls, patch(
|
|
59
|
+
"cleepcli.watch.ActionsExecutor"
|
|
60
|
+
), patch(
|
|
61
|
+
"cleepcli.watch.time.sleep", side_effect=KeyboardInterrupt
|
|
62
|
+
):
|
|
63
|
+
file_cls.return_value.sync_all.return_value = True
|
|
64
|
+
handler = handler_cls.return_value
|
|
65
|
+
handler._watched = {"/tmp"}
|
|
66
|
+
handler.schedule_all = Mock()
|
|
67
|
+
|
|
68
|
+
watchdog = CleepWatchdog("https://127.0.0.1:443", catch_up=True)
|
|
69
|
+
assert watchdog.watch() is True
|
|
70
|
+
file_cls.return_value.sync_all.assert_called_once_with()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_watch_skip_catch_up():
|
|
74
|
+
from cleepcli.watch import CleepWatchdog
|
|
75
|
+
|
|
76
|
+
with patch("cleepcli.watch.File") as file_cls, patch(
|
|
77
|
+
"cleepcli.watch.os.path.exists", return_value=True
|
|
78
|
+
), patch("cleepcli.watch.Observer"), patch(
|
|
79
|
+
"cleepcli.watch.CleepHandler"
|
|
80
|
+
) as handler_cls, patch(
|
|
81
|
+
"cleepcli.watch.ActionsExecutor"
|
|
82
|
+
), patch(
|
|
83
|
+
"cleepcli.watch.time.sleep", side_effect=KeyboardInterrupt
|
|
84
|
+
):
|
|
85
|
+
handler = handler_cls.return_value
|
|
86
|
+
handler._watched = {"/tmp"}
|
|
87
|
+
handler.schedule_all = Mock()
|
|
88
|
+
|
|
89
|
+
watchdog = CleepWatchdog("https://127.0.0.1:443", catch_up=False)
|
|
90
|
+
assert watchdog.watch() is True
|
|
91
|
+
file_cls.return_value.sync_all.assert_not_called()
|
|
@@ -131,6 +131,13 @@ def test_module_frontend_is_queued(handler):
|
|
|
131
131
|
)
|
|
132
132
|
|
|
133
133
|
|
|
134
|
+
def test_module_changelog_is_ignored(handler):
|
|
135
|
+
h, executor = handler
|
|
136
|
+
path = os.path.join(config.MODULES_SRC, "audio", "CHANGELOG.md")
|
|
137
|
+
h.on_any_event(FileModifiedEvent(path))
|
|
138
|
+
executor.note_change.assert_not_called()
|
|
139
|
+
|
|
140
|
+
|
|
134
141
|
def test_created_git_directory_is_not_watched(handler):
|
|
135
142
|
h, executor = handler
|
|
136
143
|
h.observer = Mock()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
from unittest.mock import Mock, patch
|
|
6
|
+
|
|
7
|
+
from cleepcli.watchsvc import WatchService, WATCH_UNIT_DST, bundled_unit_path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_bundled_unit_path_exists():
|
|
11
|
+
assert os.path.isfile(bundled_unit_path())
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_install_copies_unit_and_reloads():
|
|
15
|
+
svc = WatchService()
|
|
16
|
+
svc.console = Mock()
|
|
17
|
+
svc.console.command.return_value = {"returncode": 0, "stdout": "", "stderr": ""}
|
|
18
|
+
|
|
19
|
+
with patch("cleepcli.watchsvc.shutil.copyfile") as copyfile, patch(
|
|
20
|
+
"cleepcli.watchsvc.os.makedirs"
|
|
21
|
+
), patch("cleepcli.watchsvc.os.path.isfile", return_value=True):
|
|
22
|
+
assert svc.install() is True
|
|
23
|
+
copyfile.assert_called_once()
|
|
24
|
+
assert copyfile.call_args.args[1] == WATCH_UNIT_DST
|
|
25
|
+
svc.console.command.assert_called_with(
|
|
26
|
+
"/bin/systemctl daemon-reload", 60.0
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_enable_installs_when_missing():
|
|
31
|
+
svc = WatchService()
|
|
32
|
+
svc.install = Mock(return_value=True)
|
|
33
|
+
svc.is_active = Mock(return_value=True)
|
|
34
|
+
svc.console = Mock()
|
|
35
|
+
svc.console.command.return_value = {"returncode": 0, "stdout": "", "stderr": ""}
|
|
36
|
+
|
|
37
|
+
with patch("cleepcli.watchsvc.os.path.isfile", return_value=False):
|
|
38
|
+
assert svc.enable() is True
|
|
39
|
+
svc.install.assert_called_once_with()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_status_not_installed():
|
|
43
|
+
svc = WatchService()
|
|
44
|
+
with patch("cleepcli.watchsvc.os.path.isfile", return_value=False):
|
|
45
|
+
assert svc.status() == "not-installed"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_status_active():
|
|
49
|
+
svc = WatchService()
|
|
50
|
+
svc.console = Mock()
|
|
51
|
+
svc.console.command.return_value = {
|
|
52
|
+
"returncode": 0,
|
|
53
|
+
"stdout": ["active"],
|
|
54
|
+
"stderr": "",
|
|
55
|
+
}
|
|
56
|
+
with patch("cleepcli.watchsvc.os.path.isfile", return_value=True):
|
|
57
|
+
assert svc.status() == "active"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|