1 # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
24 def _ms_time_str(string):
26 datetime.datetime.strptime(string, lib.litani.TIME_FORMAT_MS)
27 except RuntimeError as e:
29 "Date '%s' was not in the right format (expected '%s')" %
30 (string, lib.litani.TIME_FORMAT_MS)) from e
33 def _time_str(string):
35 datetime.datetime.strptime(string, lib.litani.TIME_FORMAT_R)
36 except RuntimeError as e:
38 "Date '%s' was not in the right format (expected '%s')" %
39 (string, lib.litani.TIME_FORMAT_R)) from e
44 # "page": "litani-run.json",
46 # "title": "Schema for 'wrapper_arguments' key"
48 def _single_job_schema():
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.
56 # A globally-unique ID for this job.
59 # The command that litani will execute in a subshell.
62 # The name of the 'stage' that this job will execute in, used for
63 # organizing the HTML dashboard.
68 # If true, then if this job times out, the outcome will be set to
72 # The name of the 'pipeline' that this job will execute in, used for
73 # organizing the HTML dashboard.
77 "timeout_ignore": bool,
78 # If true, then if this job times out, the outcome will be set to
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
86 "profile_memory_interval": int,
87 # How frequently (in seconds) litani will profile the command's memory
88 # use, if *profile_memory* is true.
90 "cwd": voluptuous.Any(str, None),
91 # The directory that litani will run the command in.
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.
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.
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.
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)*).
112 "inputs": voluptuous.Any([str], None),
113 # The list of files that should be made up-to-date before the job will
116 "outputs": voluptuous.Any([str], None),
117 # The list of files that this job will make up-to-date after it
120 "description": voluptuous.Any(str, None),
121 # A human-readable description of this job
123 "status_file": voluptuous.Any(str, None),
125 "stderr_file": voluptuous.Any(str, None),
126 # A file to redirect stderr to, as well as buffering it internally
128 "stdout_file": voluptuous.Any(str, None),
129 # A file to redirect stdout to, as well as buffering it internally
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'.
135 "outcome_table": voluptuous.Any(str, None),
136 # A file to load an outcome table from.
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.
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'.
146 "subcommand": voluptuous.Any("exec", "add-job"),
150 def validate_single_job(job):
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")
165 def validate_run(run):
172 import voluptuous.humanize
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")
183 # "page": "litani-run.json",
185 # "title": "Schema for a job or ci_stage outcome"
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.
196 return voluptuous.Any("success", "fail", "fail_ignored")
201 # "page": "litani-run.json",
203 # "title": "Schema for a pipeline or run status"
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
212 return voluptuous.Any("success", "fail", "in_progress")
218 # "page": "litani-run.json",
220 # "title": "Schema for entire run.json file"
227 # A globally-unique ID for the run.
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.
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.
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
245 "start_time": _time_str,
246 # The time at which the run started.
249 # The version string of the Litani binary that ran this run.
251 "version_major": int,
252 # Litani's major version number.
254 "version_minor": int,
255 # Litani's minor version number.
257 "version_patch": int,
258 # Litani's patch version number.
260 "release_candidate": bool,
261 # false if this version of Litani is a tagged release.
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".
268 # The state of this run, see the status schema below.
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.
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.
283 voluptuous.Optional("trace"): [{
284 # A list of samples of the run's concurrency level.
286 "time": _ms_time_str,
287 # The time at which the sample was taken.
290 # How many jobs have finished
293 # How many jobs are running
296 # The total number of jobs
300 voluptuous.Optional("max_parallelism"): int,
301 # The maximum parallelism attained over the run
303 voluptuous.Optional("n_proc"): voluptuous.Any(None, int),
304 # The number of processors detected on this machine
309 # Each pipeline contains ci_stages which contain jobs.
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)*.
318 # The pipeline's state, see the status schema below.
321 # Each ci_stage contains a list of jobs.
325 # Whether all the jobs in this stage are complete.
328 # The stage's name. This is any of the *stages* of
331 "status": _outcome(),
332 # The stage's state, see the outcome schema below.
334 "progress": voluptuous.All(int, voluptuous.Range(min=0, max=100)),
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
342 # If *complete* is false and no *start_time* key exists,
343 # then this job has not yet started.
345 "duration_str": None,
347 "wrapper_arguments": _single_job_schema(),
348 # The arguments passed to this job, see the
349 # single_job_schema schema below.
353 # If *complete* is false but the *start_time* key exists,
354 # then the job has started running but has not yet finished.
356 "start_time": _time_str,
357 # The time at which the job started running.
359 "duration_str": None,
361 "wrapper_arguments": _single_job_schema(),
362 # The arguments passed to this job, see the
363 # single_job_schema schema below.
367 # How long the job ran for.
370 # If *complete* is true, then the job has terminated.
372 "outcome": _outcome(),
373 # The job's outcome, see the outcome schema below.
375 "end_time": _time_str,
376 # The time at which the job completed.
378 "start_time": _time_str,
379 # The time at which the job started running.
381 "timeout_reached": bool,
382 # Whether the job reached its timeout limit.
384 "command_return_code": int,
385 # The command's return code.
387 "wrapper_return_code": int,
389 "stderr": voluptuous.Any([str], None),
390 # A list of strings that the command printed to its stderr.
392 "stdout": voluptuous.Any([str], None),
393 # A list of strings that the command printed to its stdout.
395 "duration_str": voluptuous.Any(str, None),
396 # A human-readable duration of this job (HH:MM:SS).
398 "wrapper_arguments": _single_job_schema(),
399 # The arguments passed to this job, see the
400 # single_job_schema schema below.
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.
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.
412 voluptuous.Optional("peak"): {
413 # The command's peak memory usage.
419 # Peak virtual memory size
421 "human_readable_rss": str,
424 "human_readable_vsz": str,
425 # Peak virtual memory size
429 voluptuous.Optional("trace"): [{
430 # A list of samples of memory usage.
439 # The time at which the sample was taken
447 "latest_symlink": voluptuous.Any(str, None),
448 # The symbolic link to the report advertised to users
453 def validate_outcome_table(table):
457 logging.debug("Skipping outcome table validation as voluptuous is not installed")
460 schema = voluptuous.Schema(_outcome_table_schema())
461 voluptuous.humanize.validate_with_humanized_errors(table, schema)
466 # "page": "litani-outcome-table.json",
468 # "title": "Schema for user-provided outcome table"
470 def _outcome_table_schema():
474 voluptuous.Optional("comment"): str,
475 # A description of the outcome table as a whole.
478 # The outcome of the job will be the first item in this list that
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.
490 "action": _outcome(),
492 voluptuous.Optional("comment"): str,
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.
500 "action": _outcome(),
502 voluptuous.Optional("comment"): str,
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
512 "action": _outcome(),
514 voluptuous.Optional("comment"): str,