]> begriffs open source - cmsis-freertos/blob - Test/litani/lib/validation.py
Updated pack to FreeRTOS 10.4.6
[cmsis-freertos] / Test / litani / lib / validation.py
1 # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License").
4 # You may not use this file except in compliance with the License.
5 # A copy of the License is located at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # or in the "license" file accompanying this file. This file is distributed
10 # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 # express or implied. See the License for the specific language governing
12 # permissions and limitations under the License.
13
14
15 import datetime
16 import logging
17
18 import lib.litani
19
20
21 _NO_VALIDATE = False
22
23
24 def _ms_time_str(string):
25     try:
26         datetime.datetime.strptime(string, lib.litani.TIME_FORMAT_MS)
27     except RuntimeError as e:
28         raise ValueError(
29             "Date '%s' was not in the right format (expected '%s')" %
30             (string, lib.litani.TIME_FORMAT_MS)) from e
31
32
33 def _time_str(string):
34     try:
35         datetime.datetime.strptime(string, lib.litani.TIME_FORMAT_R)
36     except RuntimeError as e:
37         raise ValueError(
38             "Date '%s' was not in the right format (expected '%s')" %
39             (string, lib.litani.TIME_FORMAT_R)) from e
40
41
42 # doc-gen
43 # {
44 #   "page": "litani-run.json",
45 #   "order": 1,
46 #   "title": "Schema for 'wrapper_arguments' key"
47 # }
48 def _single_job_schema():
49     import voluptuous
50
51     # The *wrapper_arguments* key to run.json maps to the following dict. None
52     # of the values in this dict change at any point during the run; they are
53     # mostly the same as the flags passed to *litani-add-job(1)* for this job.
54     return {
55         "job_id": str,
56         # A globally-unique ID for this job.
57
58         "command": str,
59         # The command that litani will execute in a subshell.
60
61         "ci_stage": str,
62         # The name of the 'stage' that this job will execute in, used for
63         # organizing the HTML dashboard.
64
65         "verbose": bool,
66
67         "timeout_ok": bool,
68         # If true, then if this job times out, the outcome will be set to
69         # 'success'.
70
71         "pipeline_name": str,
72         # The name of the 'pipeline' that this job will execute in, used for
73         # organizing the HTML dashboard.
74
75         "very_verbose": bool,
76
77         "timeout_ignore": bool,
78         # If true, then if this job times out, the outcome will be set to
79         # 'fail_ignored'.
80
81         "profile_memory": bool,
82         # If true, then litani will regularly sample the memory usage of this
83         # job's command while it runs. Samples are stored in the job's
84         # *memory_trace*.
85
86         "profile_memory_interval": int,
87         # How frequently (in seconds) litani will profile the command's memory
88         # use, if *profile_memory* is true.
89
90         "cwd": voluptuous.Any(str, None),
91         # The directory that litani will run the command in.
92
93         "interleave_stdout_stderr": bool,
94         # Whether the command's stderr will be sent to the stdout stream. If
95         # true, the job's *stderr* key will be None and the *stdout* key will
96         # contain lines from both the command's stdout and stderr.
97
98         "pool": voluptuous.Any(str, None),
99         # The pool that this job will execute in; if not null, then it must be a
100         # key in the *pools* dict of the overall run.
101
102         "tags": voluptuous.Any([str], None),
103         # A list of user-specified tags. Litani mostly doesn't interpret these,
104         # although the HTML dashboard generator does use some of them. Tags are
105         # intended to help users find particular jobs for data analysis and can
106         # contain arbitrary data.
107
108         "timeout": voluptuous.Any(int, None),
109         # The number of seconds that Litani will allow the job to run for before
110         # sending SIGTERM followed by SIGKILL (see *signal(3)*).
111
112         "inputs": voluptuous.Any([str], None),
113         # The list of files that should be made up-to-date before the job will
114         # run
115
116         "outputs": voluptuous.Any([str], None),
117         # The list of files that this job will make up-to-date after it
118         # completes
119
120         "description": voluptuous.Any(str, None),
121         # A human-readable description of this job
122
123         "status_file": voluptuous.Any(str, None),
124
125         "stderr_file": voluptuous.Any(str, None),
126         # A file to redirect stderr to, as well as buffering it internally
127
128         "stdout_file": voluptuous.Any(str, None),
129         # A file to redirect stdout to, as well as buffering it internally
130
131         "ok_returns": voluptuous.Any([str], None),
132         # A list of return codes. If the command exits with any of these return
133         # codes (or 0), then the outcome will be set to 'success'.
134
135         "outcome_table": voluptuous.Any(str, None),
136         # A file to load an outcome table from.
137
138         "phony_outputs": voluptuous.Any([str], None),
139         # A list of outputs that Litani will not warn about if they were not
140         # created by the job.
141
142         "ignore_returns": voluptuous.Any([str], None),
143         # A list of return codes. If the command exits with any of these return
144         # codes (or 0), then the outcome will be set to 'fail_ignored'.
145
146         "subcommand": voluptuous.Any("exec", "add-job"),
147     }
148
149
150 def validate_single_job(job):
151     global _NO_VALIDATE
152
153     if _NO_VALIDATE:
154         return
155     try:
156         import voluptuous
157         import voluptuous.humanize
158         schema = voluptuous.Schema(_single_job_schema(), required=True)
159         voluptuous.humanize.validate_with_humanized_errors(job, schema)
160     except (ImportError, ModuleNotFoundError):
161         logging.debug("voluptuous not installed; not validating schema")
162         _NO_VALIDATE = True
163
164
165 def validate_run(run):
166     global _NO_VALIDATE
167
168     if _NO_VALIDATE:
169         return
170     try:
171         import voluptuous
172         import voluptuous.humanize
173         outcome = _outcome()
174         schema = voluptuous.Schema(_run_schema(), required=True)
175         voluptuous.humanize.validate_with_humanized_errors(run, schema)
176     except (ImportError, ModuleNotFoundError):
177         logging.debug("voluptuous not installed; not validating schema")
178         _NO_VALIDATE = True
179
180
181 # doc-gen
182 # {
183 #   "page": "litani-run.json",
184 #   "order": 3,
185 #   "title": "Schema for a job or ci_stage outcome"
186 # }
187 def _outcome():
188     import voluptuous
189
190     # Outcomes and ci_stages have an *"outcome"* (though, confusingly, the key
191     # is *"status"* for ci_stages). "fail_ignored" means that the job failed but
192     # the user specified that the job's dependencies should run anyway. If a
193     # pipeline contains a job whose outcome is "fail_ignored", then the status
194     # of the pipeline will be "fail" after all of its jobs complete.
195
196     return voluptuous.Any("success", "fail", "fail_ignored")
197 # end-doc-gen
198
199 # doc-gen
200 # {
201 #   "page": "litani-run.json",
202 #   "order": 2,
203 #   "title": "Schema for a pipeline or run status"
204 # }
205 def _status():
206     import voluptuous
207
208     # pipelines and runs have a *"status"*. The status is "in_progress" when some
209     # of the jobs are incomplete and either "success" or "fail" once all jobs
210     # complete.
211
212     return voluptuous.Any("success", "fail", "in_progress")
213 # end-doc-gen
214
215
216 # doc-gen
217 # {
218 #   "page": "litani-run.json",
219 #   "order": 0,
220 #   "title": "Schema for entire run.json file"
221 # }
222 def _run_schema():
223     import voluptuous
224
225     return {
226         "run_id": str,
227         # A globally-unique ID for the run.
228
229         "project": str,
230         # A name for the project that this run is part of. This name is used by
231         # the HTML report generator and can be used to group related sets of
232         # runs, but is otherwise not used by litani.
233
234         "stages": [str],
235         # The CI stages that each job can be a member of. Stage names can
236         # be provided through the --stages flag of *litani-init(1)*. Default
237         # stages "build", "test" and "report" are used if the flag is not used.
238
239         "pools": {voluptuous.Optional(str): int},
240         # A mapping from pool names to the depth of the pool. Jobs can be a
241         # member of zero or one pool. The depth of a pool that a set of jobs
242         # belong to limits the number of those jobs that litani will run in
243         # parallel.
244
245         "start_time": _time_str,
246         # The time at which the run started.
247
248         "version": str,
249         # The version string of the Litani binary that ran this run.
250
251         "version_major": int,
252         # Litani's major version number.
253
254         "version_minor": int,
255         # Litani's minor version number.
256
257         "version_patch": int,
258         # Litani's patch version number.
259
260         "release_candidate": bool,
261         # false if this version of Litani is a tagged release.
262
263         voluptuous.Optional("end_time"): _time_str,
264         # The time at which the run ended. This key will only exist if *status*
265         # is not equal to "in_progress".
266
267         "status": _status(),
268         # The state of this run, see the status schema below.
269
270         "aux": dict,
271         # A free-form dict that users can add custom information into. There are
272         # no constraints on the format of this dict, but it is recommended that
273         # users add their information to a sub-dict with a key that indicates
274         # its function. For example, to add information pertaining to a CI run,
275         # users might add a key called "continuous_integration_data" whose value
276         # is a sub-dict containing all required fields.
277
278         "parallelism": voluptuous.Any({
279         # This dict contains information about the parallelism level of the jobs
280         # that litani runs. This is to measure whether the run is using as many
281         # processor cores as possible over the duration of the run.
282
283             voluptuous.Optional("trace"): [{
284             # A list of samples of the run's concurrency level.
285
286                 "time": _ms_time_str,
287                 # The time at which the sample was taken.
288
289                 "finished": int,
290                 # How many jobs have finished
291
292                 "running": int,
293                 # How many jobs are running
294
295                 "total": int,
296                 # The total number of jobs
297
298             }],
299
300             voluptuous.Optional("max_parallelism"): int,
301             # The maximum parallelism attained over the run
302
303             voluptuous.Optional("n_proc"): voluptuous.Any(None, int),
304             # The number of processors detected on this machine
305
306         }),
307
308         "pipelines": [{
309         # Each pipeline contains ci_stages which contain jobs.
310
311             "url": str,
312
313             "name": str,
314             # The pipeline name. The set of pipeline names are all the names
315             # passed to the --pipeline-name flag of *litani-add-job(1)*.
316
317             "status": _status(),
318             # The pipeline's state, see the status schema below.
319
320             "ci_stages": [{
321             # Each ci_stage contains a list of jobs.
322
323                 "url": str,
324                 "complete": bool,
325                 # Whether all the jobs in this stage are complete.
326
327                 "name": str,
328                 # The stage's name. This is any of the *stages* of
329                 # the project.
330
331                 "status": _outcome(),
332                 # The stage's state, see the outcome schema below.
333
334                 "progress": voluptuous.All(int, voluptuous.Range(min=0, max=100)),
335
336                 "jobs": [voluptuous.Any({
337                 # The list of all the jobs in this ci_stage in this pipeline.
338                 # There are three different forms the value of this key can
339                 # take.
340
341                     "complete": False,
342                     # If *complete* is false and no *start_time* key exists,
343                     # then this job has not yet started.
344
345                     "duration_str": None,
346
347                     "wrapper_arguments": _single_job_schema(),
348                     # The arguments passed to this job, see the
349                     # single_job_schema schema below.
350
351                 }, {
352                     "complete": False,
353                     # If *complete* is false but the *start_time* key exists,
354                     # then the job has started running but has not yet finished.
355
356                     "start_time": _time_str,
357                     # The time at which the job started running.
358
359                     "duration_str": None,
360
361                     "wrapper_arguments": _single_job_schema(),
362                     # The arguments passed to this job, see the
363                     # single_job_schema schema below.
364
365                 }, {
366                     "duration": int,
367                     # How long the job ran for.
368
369                     "complete": True,
370                     # If *complete* is true, then the job has terminated.
371
372                     "outcome": _outcome(),
373                     # The job's outcome, see the outcome schema below.
374
375                     "end_time": _time_str,
376                     # The time at which the job completed.
377
378                     "start_time": _time_str,
379                     # The time at which the job started running.
380
381                     "timeout_reached": bool,
382                     # Whether the job reached its timeout limit.
383
384                     "command_return_code": int,
385                     # The command's return code.
386
387                     "wrapper_return_code": int,
388
389                     "stderr": voluptuous.Any([str], None),
390                     # A list of strings that the command printed to its stderr.
391
392                     "stdout": voluptuous.Any([str], None),
393                     # A list of strings that the command printed to its stdout.
394
395                     "duration_str": voluptuous.Any(str, None),
396                     # A human-readable duration of this job (HH:MM:SS).
397
398                     "wrapper_arguments": _single_job_schema(),
399                     # The arguments passed to this job, see the
400                     # single_job_schema schema below.
401
402                     "loaded_outcome_dict": voluptuous.Any(dict, None),
403                     # If *wrapper_arguments["outcome_table"]* is not null, the
404                     # value of this key will be the deserialized data loaded
405                     # from the outcome table file.
406
407                     "memory_trace": {
408                     # If *profile_memory* was set to true in the wrapper
409                     # arguments for this job, this dict will contain samples of
410                     # the command's memory usage.
411
412                         voluptuous.Optional("peak"): {
413                         # The command's peak memory usage.
414
415                             "rss": int,
416                             # Peak resident set
417
418                             "vsz": int,
419                             # Peak virtual memory size
420
421                             "human_readable_rss": str,
422                             # Peak resident set
423
424                             "human_readable_vsz": str,
425                             # Peak virtual memory size
426
427                         },
428
429                         voluptuous.Optional("trace"): [{
430                         # A list of samples of memory usage.
431
432                             "rss": int,
433                             # Resident set
434
435                             "vsz": int,
436                             # Virtual memory
437
438                             "time": _time_str,
439                             # The time at which the sample was taken
440
441                         }],
442                     },
443                 })],
444             }],
445         }],
446
447         "latest_symlink": voluptuous.Any(str, None),
448         # The symbolic link to the report advertised to users
449     }
450 # end-doc-gen
451
452
453 def validate_outcome_table(table):
454     try:
455         import voluptuous
456     except ImportError:
457         logging.debug("Skipping outcome table validation as voluptuous is not installed")
458         return
459
460     schema = voluptuous.Schema(_outcome_table_schema())
461     voluptuous.humanize.validate_with_humanized_errors(table, schema)
462
463
464 # doc-gen
465 # {
466 #   "page": "litani-outcome-table.json",
467 #   "order": 0,
468 #   "title": "Schema for user-provided outcome table"
469 # }
470 def _outcome_table_schema():
471     import voluptuous
472
473     return {
474         voluptuous.Optional("comment"): str,
475         # A description of the outcome table as a whole.
476
477         "outcomes": [
478         # The outcome of the job will be the first item in this list that
479         # matches.
480
481             voluptuous.Any({
482                 "type": "return-code",
483                 # If the return code of the job matches the value of *value*,
484                 # the outcome will be set to the value of *action*. The value
485                 # of the optional *comment* key can contain a human-readable
486                 # explanation for this outcome.
487
488                 "value": int,
489
490                 "action": _outcome(),
491
492                 voluptuous.Optional("comment"): str,
493
494             }, {
495                 "type": "timeout",
496                 # If this job timed out, the outcome will be set to the value of
497                 # *action*. The value of the optional *comment* key can contain
498                 # a human-readable explanation for this outcome.
499
500                 "action": _outcome(),
501
502                 voluptuous.Optional("comment"): str,
503
504             }, {
505                 "type": "wildcard",
506                 # The *"wildcard"* action type matches any job and sets its
507                 # outcome to the value of *action*. It is recommended to place a
508                 # *wildcard* action as the last element of the list of
509                 # *outcomes* to catch all jobs that were not matched by a
510                 # previous rule.
511
512                 "action": _outcome(),
513
514                 voluptuous.Optional("comment"): str,
515
516         })]
517     }