@andrian.yablonskyy/thub-common 1.1.3 → 1.1.5
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.
- package/package.json +1 -1
- package/src/client-config.js +27 -4
- package/src/index.js +1 -0
- package/src/job-spec.schema.js +16 -1
- package/src/power.js +46 -0
- package/src/validate-job-spec.js +4 -0
package/package.json
CHANGED
package/src/client-config.js
CHANGED
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const Ajv = require('ajv'),
|
|
19
|
-
addFormats = require('ajv-formats')
|
|
19
|
+
addFormats = require('ajv-formats'),
|
|
20
|
+
{ MAX_POWER_PORTS } = require('./power');
|
|
20
21
|
|
|
21
22
|
// The config file's key for an HW Client's devices. Older files call it
|
|
22
23
|
// `hw`, still read (and rewritten as `hw-devices` on the next save).
|
|
@@ -46,7 +47,29 @@ const HW_DEVICES = 'hw-devices',
|
|
|
46
47
|
properties: {
|
|
47
48
|
stlinks: list(device({ serial: { type: 'string', pattern: '^[A-Za-z0-9]{1,64}$' } })),
|
|
48
49
|
uarts: list(device({ baudRate: { type: 'integer', minimum: 50, maximum: 4000000 } })),
|
|
49
|
-
usbs: list(device())
|
|
50
|
+
usbs: list(device()),
|
|
51
|
+
// USB port power switched with uhubctl (README §8.7): each port by its
|
|
52
|
+
// hub's location and port number, as `uhubctl` lists them
|
|
53
|
+
// ("Current status for hub 1-1.4" … "Port 2").
|
|
54
|
+
usbPower: {
|
|
55
|
+
type: 'object',
|
|
56
|
+
additionalProperties: false,
|
|
57
|
+
properties: {
|
|
58
|
+
ports: {
|
|
59
|
+
type: 'array',
|
|
60
|
+
maxItems: MAX_POWER_PORTS,
|
|
61
|
+
items: {
|
|
62
|
+
type: 'object',
|
|
63
|
+
additionalProperties: false,
|
|
64
|
+
required: ['hub', 'port'],
|
|
65
|
+
properties: {
|
|
66
|
+
hub: { type: 'string', pattern: '^[0-9]+(-[0-9]+(\\.[0-9]+)*)?$', maxLength: 64 },
|
|
67
|
+
port: { type: 'integer', minimum: 1, maximum: 255 }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
50
73
|
}
|
|
51
74
|
},
|
|
52
75
|
// An SW Client has no settings of its own: its editable section is empty.
|
|
@@ -62,8 +85,8 @@ function hwDevicesOf(file){
|
|
|
62
85
|
return file?.[HW_DEVICES] !== undefined ? file[HW_DEVICES] : file?.hw;
|
|
63
86
|
}
|
|
64
87
|
|
|
65
|
-
// An hw-devices section without the power control Clients no longer
|
|
66
|
-
// (relays, power — relay boards and uhubctl), so a config file that still
|
|
88
|
+
// An hw-devices section without the older power control Clients no longer
|
|
89
|
+
// have (relays, power — relay boards, and uhubctl before usbPower), so a config file that still
|
|
67
90
|
// has them reports, saves and imports without them. Returns
|
|
68
91
|
// { section, dropped } (dropped: the names left out).
|
|
69
92
|
function withoutPowerControl(hw){
|
package/src/index.js
CHANGED
package/src/job-spec.schema.js
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
'use strict';
|
|
15
15
|
|
|
16
|
+
const { POWER_ACTIONS, MAX_RESET_DELAY_SEC } = require('./power');
|
|
17
|
+
|
|
16
18
|
// An environment variable name a job may set (`--env NAME=value`).
|
|
17
19
|
const ENV_NAME_PATTERN = '^[A-Za-z_][A-Za-z0-9_]*$',
|
|
18
20
|
|
|
@@ -56,7 +58,7 @@ const ENV_NAME_PATTERN = '^[A-Za-z_][A-Za-z0-9_]*$',
|
|
|
56
58
|
// Client runs with `sh -c`, `args` as "$@" (`--arg`, repeatable).
|
|
57
59
|
command: { type: 'string', minLength: 1, maxLength: 4096 },
|
|
58
60
|
args: { type: 'array', items: { type: 'string' }, default: [] },
|
|
59
|
-
// Passed to the command as
|
|
61
|
+
// Passed to the command as JOB_SUITE (`--suite`).
|
|
60
62
|
suite: { type: 'string', default: 'default' },
|
|
61
63
|
// Files the Client downloads into the task's work directory before
|
|
62
64
|
// running the command (`--download-file`, repeatable).
|
|
@@ -92,6 +94,19 @@ const ENV_NAME_PATTERN = '^[A-Za-z_][A-Za-z0-9_]*$',
|
|
|
92
94
|
// not an identity: nothing authenticates or enforces it.
|
|
93
95
|
user: { type: 'string', minLength: 1 },
|
|
94
96
|
meta: { type: 'object' },
|
|
97
|
+
// USB port power on the Client (uhubctl, README §8.7) at the job's
|
|
98
|
+
// start (before the DUT is prepared) and end (whatever the verdict):
|
|
99
|
+
// `--power-on-start` / `--power-on-end` on|off|reset, and the reset's
|
|
100
|
+
// off time `--power-reset-delay` (seconds; 1 when not given). HW only.
|
|
101
|
+
power: {
|
|
102
|
+
type: 'object',
|
|
103
|
+
additionalProperties: false,
|
|
104
|
+
properties: {
|
|
105
|
+
onStart: { enum: POWER_ACTIONS },
|
|
106
|
+
onEnd: { enum: POWER_ACTIONS },
|
|
107
|
+
resetDelaySec: { type: 'number', minimum: 0, maximum: MAX_RESET_DELAY_SEC }
|
|
108
|
+
}
|
|
109
|
+
},
|
|
95
110
|
// Exercises the full pipeline (schedule, accept, state transitions,
|
|
96
111
|
// logs, artifact, result) without flashing/running anything for real —
|
|
97
112
|
// see README §7.1 "Dry-run the pipeline".
|
package/src/power.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file packages/shared/src/power.js
|
|
3
|
+
* @description USB port power (uhubctl on the Client): the actions, the reset delay and the checks on a power
|
|
4
|
+
* request — shared by the Agent, the Coordinator and the Client (README §8.7)
|
|
5
|
+
*
|
|
6
|
+
* @author Andrian Yablonskyy
|
|
7
|
+
* @copyright Copyright (c) 2026 Andrian Yablonskyy. All rights reserved.
|
|
8
|
+
*
|
|
9
|
+
* This file is part of TestHub and is proprietary and confidential.
|
|
10
|
+
* Unauthorized copying, modification, distribution, or use of this file,
|
|
11
|
+
* via any medium, is strictly prohibited without prior written permission
|
|
12
|
+
* from AdSystem.PRO.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
// `reset`: off, wait the reset delay, on.
|
|
18
|
+
const POWER_ACTIONS = Object.freeze(['on', 'off', 'reset']),
|
|
19
|
+
DEFAULT_RESET_DELAY_SEC = 1,
|
|
20
|
+
MAX_RESET_DELAY_SEC = 60,
|
|
21
|
+
// A Client's hw-devices.usbPower.ports holds at most this many.
|
|
22
|
+
MAX_POWER_PORTS = 8;
|
|
23
|
+
|
|
24
|
+
// Errors (strings) for a request to switch a Client's ports: `action` one of
|
|
25
|
+
// POWER_ACTIONS, `delaySec` (reset only, optional) 0..MAX_RESET_DELAY_SEC,
|
|
26
|
+
// `port` (optional) a 1-based position in the Client's usbPower.ports.
|
|
27
|
+
function powerRequestErrors({ action, delaySec, port } = {}){
|
|
28
|
+
const errors = [];
|
|
29
|
+
if (!POWER_ACTIONS.includes(action)){
|
|
30
|
+
errors.push(`action must be one of ${POWER_ACTIONS.join(', ')}`);
|
|
31
|
+
}
|
|
32
|
+
if (delaySec !== undefined && delaySec !== null){
|
|
33
|
+
if (typeof delaySec !== 'number' || !Number.isFinite(delaySec) || delaySec < 0 || delaySec > MAX_RESET_DELAY_SEC){
|
|
34
|
+
errors.push(`the reset delay (delaySec) must be a number of seconds, 0-${MAX_RESET_DELAY_SEC}`);
|
|
35
|
+
}
|
|
36
|
+
else if (action !== 'reset'){
|
|
37
|
+
errors.push('the reset delay (delaySec) applies to reset only');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (port !== undefined && port !== null && (!Number.isInteger(port) || port < 1 || port > MAX_POWER_PORTS)){
|
|
41
|
+
errors.push(`port must be a usbPower port number, 1-${MAX_POWER_PORTS}`);
|
|
42
|
+
}
|
|
43
|
+
return errors;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = { POWER_ACTIONS, DEFAULT_RESET_DELAY_SEC, MAX_RESET_DELAY_SEC, MAX_POWER_PORTS, powerRequestErrors };
|
package/src/validate-job-spec.js
CHANGED
|
@@ -69,6 +69,10 @@ function crossFieldErrors(spec){
|
|
|
69
69
|
if (reserved.length){
|
|
70
70
|
errors.push(`/env ${reserved.join(', ')}: set by the Client itself (THUB_*, JOB_*) — use other names`);
|
|
71
71
|
}
|
|
72
|
+
// USB power is an HW Client's (its hw-devices.usbPower ports).
|
|
73
|
+
if (spec.power && spec.target?.type !== 'hw'){
|
|
74
|
+
errors.push('/power USB port power is for HW jobs only (--type hw)');
|
|
75
|
+
}
|
|
72
76
|
return errors;
|
|
73
77
|
}
|
|
74
78
|
|