codecraftai-testplus 1.2.0__py3-none-any.whl

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.
@@ -0,0 +1,57 @@
1
+ """
2
+ .. module:: testplus
3
+ :platform: Darwin, Linux, Unix, Windows
4
+ :synopsis: Top level package for the TestPlus framework which exposes
5
+ commonly used APIs and decorators from the underlying
6
+ ``ccai.testbase`` package.
7
+
8
+ .. moduleauthor:: Myron Walker <myron.walker@gmail.com>
9
+ """
10
+
11
+ __author__ = "Myron Walker"
12
+ __copyright__ = "Copyright 2025, Code Craft AI LLC"
13
+ __credits__ = []
14
+
15
+
16
+ from ccai.xmodules.xlogging.enhancedloggers import (
17
+ EnhancedLogger,
18
+ getEnhancedLogger
19
+ )
20
+ logger: EnhancedLogger = getEnhancedLogger()
21
+
22
+
23
+ from ccai.testbase.declaration import registrations as register
24
+ from ccai.testbase.declaration import originations as originate
25
+ from ccai.testbase.declaration import attachments as attach
26
+
27
+ from ccai.testbase.verification import (
28
+ assert_dict_response_has_keys,
29
+ assert_dict_response_has_paths,
30
+ assert_equal,
31
+ assert_expression,
32
+ assert_greater,
33
+ assert_lessthan,
34
+ assert_list_length,
35
+ assert_list_length_greater,
36
+ assert_not_equal,
37
+ assert_type
38
+ )
39
+
40
+ from ccai.testbase.markers import (
41
+ mark_categories,
42
+ mark_keywords,
43
+ mark_priority,
44
+ mark_descendent_categories,
45
+ mark_descendent_keywords,
46
+ mark_descendent_priority
47
+ )
48
+
49
+ from ccai.testbase.results.model.taskingresult import (
50
+ verify_tasking_results
51
+ )
52
+
53
+ from ccai.testbase.constraints import (
54
+ Constraints,
55
+ ConstraintsCatalog,
56
+ FeatureConstraints
57
+ )
@@ -0,0 +1,13 @@
1
+ """
2
+ .. module:: cli
3
+ :platform: Darwin, Linux, Unix, Windows
4
+ :synopsis: Package containing the command line interface implementation for
5
+ the ``testplus`` command.
6
+
7
+ .. moduleauthor:: Myron Walker <myron.walker@gmail.com>
8
+ """
9
+
10
+ __author__ = "Myron Walker"
11
+ __copyright__ = "Copyright 2025, Code Craft AI LLC"
12
+ __credits__ = []
13
+
@@ -0,0 +1,13 @@
1
+ """
2
+ .. module:: cmdtree
3
+ :platform: Darwin, Linux, Unix, Windows
4
+ :synopsis: Package defining the command tree structure for the ``testplus``
5
+ CLI.
6
+
7
+ .. moduleauthor:: Myron Walker <myron.walker@gmail.com>
8
+ """
9
+
10
+ __author__ = "Myron Walker"
11
+ __copyright__ = "Copyright 2025, Code Craft AI LLC"
12
+ __credits__ = []
13
+
@@ -0,0 +1,33 @@
1
+ """
2
+ .. module:: testing
3
+ :platform: Darwin, Linux, Unix, Windows
4
+ :synopsis: CLI group providing commands for interacting with tests and test
5
+ results.
6
+
7
+ .. moduleauthor:: Myron Walker <myron.walker@gmail.com>
8
+ """
9
+
10
+ __author__ = "Myron Walker"
11
+ __copyright__ = "Copyright 2025, Code Craft AI LLC"
12
+ __credits__ = []
13
+ __version__ = "1.0.0"
14
+
15
+ import click
16
+
17
+ from ccai.testplus.cli.cmdtree.testing.jobs import group_testplus_testing_jobs
18
+ from ccai.testplus.cli.cmdtree.testing.query import command_testplus_testing_query
19
+ from ccai.testplus.cli.cmdtree.testing.run import command_testplus_testing_run
20
+
21
+ @click.group("testing", help="Contains command for working with tests and test results.")
22
+ def group_testplus_testing():
23
+ """
24
+ Root command group for TestPlus testing related operations.
25
+
26
+ :returns: ``None``
27
+ """
28
+
29
+ return
30
+
31
+ group_testplus_testing.add_command(group_testplus_testing_jobs)
32
+ group_testplus_testing.add_command(command_testplus_testing_query)
33
+ group_testplus_testing.add_command(command_testplus_testing_run)
@@ -0,0 +1,62 @@
1
+ """
2
+ .. module:: constants
3
+ :platform: Darwin, Linux, Unix, Windows
4
+ :synopsis: Constants used by the TestPlus testing command tree.
5
+
6
+ .. moduleauthor:: Myron Walker <myron.walker@gmail.com>
7
+ """
8
+
9
+ __author__ = "Myron Walker"
10
+ __copyright__ = "Copyright 2025, Code Craft AI LLC"
11
+ __credits__ = []
12
+
13
+
14
+ from ccai.configuration.normalize import SEPARATOR
15
+
16
+ CONFIGURATION_CHOICES = [
17
+ "all",
18
+ "credentials",
19
+ "landscape",
20
+ "topology",
21
+ "runtime"
22
+ ]
23
+
24
+ HELP_ROOT = "The root directory to use when scanning for tests."
25
+ HELP_INCLUDES = "Add a test inclusion expression."
26
+ HELP_EXCLUDES = "Add a test exclusion expression."
27
+ HELP_OUTPUT = "The output directory where results and artifacts are collected."
28
+ HELP_START = r"A time stamp to associate with the start of the run. Example: 2020-10-17T15:30:11.989120 Bash: date +%Y-%m-%dT%H:%M:%S.%N"
29
+ HELP_BRANCH = "The name of the branch to associate with the test run results."
30
+ HELP_BUILD = "The name of the build to associate with the test run results."
31
+ HELP_FLAVOR = "The name of the flavor to associate with the test run results."
32
+ HELP_JOB_ID = "A unique identifier for the job with respect to the job runner."
33
+ HELP_JOB_INITIATOR = "The name of the initiator of the job."
34
+ HELP_JOB_LABEL = "The label associated with the job."
35
+ HELP_JOB_NAME = "The name of the job."
36
+ HELP_JOB_OWNER = "The ownerid and possibly appended ';(display name)' of the test run result owner."
37
+ HELP_DEFAULT_CONFIGS = "Indicates that one or more default configurations should be used."
38
+ HELP_CONFIG_NAME = "A configuration common base name to use for both the 'landscape' and 'topology' configurations."
39
+ HELP_CREDENTIAL = "The full path or URI of the credentials file to use for the testrun."
40
+ HELP_CREDENTIAL_NAMES = "The base name of the credential files to search for in the credential path."
41
+ HELP_CREDENTIAL_SOURCES = f"A '{SEPARATOR}' seperated list of source uris to search for credential documents."
42
+ HELP_LANDSCAPE = "The full path of the landscape file to use for the testrun."
43
+ HELP_LANDSCAPE_NAMES = "The base name of the landscape files to search for in the landscape path."
44
+ HELP_LANDSCAPE_SOURCES = f"A '{SEPARATOR}' seperated list of source uris to search for landscape documents."
45
+ HELP_RUNTIME = "The full path of the runtime file to use for the testrun."
46
+ HELP_RUNTIME_NAMES = "The base name of the runtime files to search for in the runtime path."
47
+ HELP_RUNTIME_SOURCES = f"A '{SEPARATOR}' seperated list of source uris to search for runtime documents."
48
+ HELP_TOPOLOGY = "The full path of the topology file to use for the testrun."
49
+ HELP_TOPOLOGY_NAMES = "The base name of the topology files to search for in the topology path."
50
+ HELP_TOPOLOGY_SOURCES = f"A '{SEPARATOR}' seperated list of source uris to search for topology documents."
51
+ HELP_RUNID = "A uuid to use for the run id for the testrun."
52
+ HELP_CONSOLE_LOG_LEVEL = "The logging level for console output."
53
+ HELP_FILE_LOG_LEVEL = "The logging level for logfile output."
54
+ HELP_DEBUG = "Output debug information to the console."
55
+ HELP_DEBUGGER = "Debugger to active during the test run."
56
+ HELP_BREAKPOINT = "The breakpoint to activate for the test run."
57
+ HELP_TIMETRAVEL = "Enables tracing for Time-Travel-Debugging."
58
+ HELP_TIMEPORTAL = "The name of a time portal to open for Time-Travel-Debugging."
59
+ HELP_PRERUN_DIAGNOSTIC = "Flag indicating that a pre run diagnostic should be performed."
60
+ HELP_POSTRUN_DIAGNOSTIC = "Flag indicating that a post run diagnostic should be performed."
61
+ HELP_INCLUDE_MARKER_EXP = "Allows the specification of a marker expression that if matched will include a test."
62
+ HELP_EXCLUDE_MARKER_EXP = "Allows the specification of a marker expression that if matched will exclude a test."
@@ -0,0 +1,195 @@
1
+ """
2
+ .. module:: jobs
3
+ :platform: Darwin, Linux, Unix, Windows
4
+ :synopsis: CLI commands for listing, showing and running TestPlus jobs.
5
+
6
+ .. moduleauthor:: Myron Walker <myron.walker@gmail.com>
7
+ """
8
+
9
+ __author__ = "Myron Walker"
10
+ __copyright__ = "Copyright 2025, Code Craft AI LLC"
11
+ __credits__ = []
12
+
13
+ import os
14
+ import sys
15
+
16
+ import click
17
+
18
+ from ccai.xmodules.xlogging.levels import LOG_LEVEL_NAMES
19
+ from ccai.xmodules.xlogging.enhancedloggers import getEnhancedLogger
20
+
21
+ @click.group("jobs", help="Contains commands for working with test jobs.")
22
+ def group_testplus_testing_jobs():
23
+ """
24
+ CLI command group for interacting with TestPlus jobs.
25
+
26
+ :returns: ``None``
27
+ """
28
+
29
+ return
30
+
31
+ HELP_ROOT = "The root directory to use when scanning for tests."
32
+ HELP_JOB = "The identifier of the job to run."
33
+ HELP_FILTER = "Add a job filter expression."
34
+ HELP_OUTPUT = "The output directory where results and artifacts are collected."
35
+ HELP_START = r"A time stamp to associate with the start of the run. Example: 2020-10-17T15:30:11.989120 Bash: date +%Y-%m-%dT%H:%M:%S.%N"
36
+ HELP_BRANCH = "The name of the branch to associate with the test run results."
37
+ HELP_BUILD = "The name of the build to associate with the test run results."
38
+ HELP_FLAVOR = "The name of the flavor to associate with the test run results."
39
+ HELP_CONSOLE_LOG_LEVEL = "The logging level for console output."
40
+ HELP_FILE_LOG_LEVEL = "The logging level for logfile output."
41
+ HELP_DEBUG = "Output debug information to the console."
42
+
43
+ @click.command("list")
44
+ @click.option("--root", default=".", type=str, help=HELP_ROOT)
45
+ @click.option("--filter", "-f", required=False, help=HELP_FILTER)
46
+ @click.option("--debug", default=False, type=bool, help=HELP_DEBUG)
47
+ def command_testing_jobs_list(root, filter, debug):
48
+ """
49
+ Lists available TestPlus jobs.
50
+
51
+ :param root: Root directory used when scanning for jobs.
52
+ :param filter: Optional job filter expression.
53
+ :param debug: Whether to emit debug output.
54
+
55
+ :returns: ``None``
56
+ """
57
+
58
+ click.echo("The 'jobs list' command is not yet implemented.", err=True)
59
+
60
+ return
61
+
62
+ @click.command("show")
63
+ @click.option("--root", default=".", type=str, help=HELP_ROOT)
64
+ @click.option("--job", "-j", required=True, help=HELP_JOB)
65
+ @click.option("--debug", default=False, type=bool, help=HELP_DEBUG)
66
+ def command_testplus_testing_jobs_show(root, job, debug):
67
+ """
68
+ Displays information about a specific TestPlus job.
69
+
70
+ :param root: Root directory used when scanning for jobs.
71
+ :param job: The identifier of the job to show.
72
+ :param debug: Whether to emit debug output.
73
+
74
+ :returns: ``None``
75
+ """
76
+
77
+ click.echo("The 'jobs show' command is not yet implemented.", err=True)
78
+
79
+ return
80
+
81
+ @click.command("run")
82
+ @click.option("--root", default=".", type=str, help=HELP_ROOT)
83
+ @click.option("--job", "-j", required=True, help=HELP_JOB)
84
+ @click.option("--output", "-o", required=False, help=HELP_OUTPUT)
85
+ @click.option("--start", default=None, required=False, help=HELP_START)
86
+ @click.option("--branch", default=None, required=False, help=HELP_BRANCH)
87
+ @click.option("--build", default=None, required=False, help=HELP_BUILD)
88
+ @click.option("--flavor", default=None, required=False, help=HELP_FLAVOR)
89
+ @click.option("--console-level", default=None, required=False, type=click.Choice(LOG_LEVEL_NAMES, case_sensitive=False), help=HELP_CONSOLE_LOG_LEVEL)
90
+ @click.option("--logfile-level", default=None, required=False, type=click.Choice(LOG_LEVEL_NAMES, case_sensitive=False), help=HELP_FILE_LOG_LEVEL)
91
+ def command_testplus_testing_jobs_run(root, job, output, start, branch, build, flavor, console_level, logfile_level):
92
+ """
93
+ Executes a specified TestPlus job.
94
+
95
+ :param root: Root directory used when scanning for tests.
96
+ :param job: Fully qualified job identifier.
97
+ :param output: Directory for test results.
98
+
99
+ :returns: ``None``
100
+ """
101
+
102
+ # pylint: disable=unused-import,import-outside-toplevel
103
+
104
+ # We do the imports of the automation framework code inside the action functions because
105
+ # we don't want to startup loggin and the processing of inputs and environment variables
106
+ # until we have entered an action method. Thats way we know how to setup the environment.
107
+
108
+ # IMPORTANT: We need to load the context first because it will trigger the loading
109
+ # of the default user configuration
110
+
111
+ from ccai.xmodules.ximport import import_by_name
112
+ from ccai.xmodules.xpython import prepend_python_path
113
+
114
+ # STEP 1 - Initialize the Runtime (Performed by the root command)
115
+
116
+ # STEP 2 - Resolve the configuration variables (Performed by the root command)
117
+
118
+ # STEP 3 - Apply any 'Configuration' related option overrides
119
+
120
+
121
+
122
+ # STEP 4 - Resolve the Configuration Maps
123
+ #from ccai.configuration.configurationmaps import resolve_configuration_maps
124
+ #resolve_configuration_maps(use_credentials=use_credentials, use_landscape=use_landscape,
125
+ # use_runtime=use_runtime, use_topology=use_topology)
126
+
127
+ # STEP 5 - The application of the remaining options must happen after we have resolved configuration
128
+ # maps because some of those settings will get overridden by the options. So the runtime
129
+ # needs to be loaded and set first before we apply runtime options
130
+
131
+ # STEP 6 - Initialize the Results Output
132
+
133
+ # STEP 7 - Create and kick off the job
134
+ from ccai.thisapp.activation import activate_profile_job, JobType
135
+ activate_profile_job(JobType.TestRun)
136
+
137
+
138
+ test_root = os.path.abspath(os.path.expandvars(os.path.expanduser(root)))
139
+ if not os.path.isdir(test_root):
140
+ errmsg = "The specified root folder does not exist. root=%s" % root
141
+ if test_root != root:
142
+ errmsg += " expanded=%s" % test_root
143
+ raise click.BadParameter(errmsg, param_hint="--root")
144
+
145
+ # Make sure we extend PATH to include the test root
146
+ prepend_python_path(test_root)
147
+
148
+ # Activation of the runtime happens when we initialize test results
149
+ from ccai.testplus.initialize import initialize_testplus_results
150
+ initialize_testplus_results()
151
+
152
+ logger = getEnhancedLogger()
153
+
154
+ tpmod = sys.modules["ccai.testplus"]
155
+ tpmod.logger = logger
156
+
157
+ from ccai.thisapp.wellknown import ConfiguredSuperFactorySingleton
158
+ from ccai.testbase.factoryprotocols import TestBaseFactoryProtocol
159
+
160
+ # At this point in the code, we either lookup an existing test job or we create a test job
161
+ # from the includes, excludes or test_module
162
+ sfactory = ConfiguredSuperFactorySingleton()
163
+ TestJobType = sfactory.get_override_types_by_order(TestBaseFactoryProtocol.get_testbase_default_job_type)
164
+
165
+ if job is not None:
166
+ job_parts = job.split("@")
167
+ if len(job_parts) != 2:
168
+ errmsg = "A --job parameter must be of the form 'package.module@JobClass'"
169
+ raise click.UsageError(errmsg)
170
+
171
+ job_package, job_class = job_parts
172
+
173
+ try:
174
+ job_mod = import_by_name(job_package)
175
+ except ImportError as imperr:
176
+ errmsg = "Failure while importing job package %r" % job_package
177
+ raise click.BadParameter(errmsg, param_hint="--job") from imperr
178
+
179
+ if not hasattr(job_mod, job_class):
180
+ errmsg = "The job package %r does not have a job type %r." % (job_package, job_class)
181
+ raise click.BadParameter(errmsg, param_hint="--job")
182
+
183
+ TestJobType = getattr(job_mod, job_class)
184
+
185
+ result_code = 0
186
+ with TestJobType(logger, test_root) as tjob:
187
+ result_code = tjob.execute()
188
+
189
+ sys.exit(result_code)
190
+
191
+ return
192
+
193
+ group_testplus_testing_jobs.add_command(command_testing_jobs_list)
194
+ group_testplus_testing_jobs.add_command(command_testplus_testing_jobs_show)
195
+ group_testplus_testing_jobs.add_command(command_testplus_testing_jobs_run)
@@ -0,0 +1,165 @@
1
+ """
2
+ .. module:: query
3
+ :platform: Darwin, Linux, Unix, Windows
4
+ :synopsis: CLI command used to query tests available in a TestPlus
5
+ environment.
6
+
7
+ .. moduleauthor:: Myron Walker <myron.walker@gmail.com>
8
+ """
9
+
10
+ __author__ = "Myron Walker"
11
+ __copyright__ = "Copyright 2025, Code Craft AI LLC"
12
+ __credits__ = []
13
+
14
+
15
+ from typing import List
16
+
17
+ from ccai.xmodules.xlogging.enhancedloggers import getEnhancedLogger
18
+ import os
19
+ import sys
20
+
21
+ import click
22
+
23
+ from ccai.testplus.cli.cmdtree.testing.constants import (
24
+ HELP_ROOT,
25
+ HELP_EXCLUDES,
26
+ HELP_INCLUDES,
27
+ HELP_EXCLUDE_MARKER_EXP,
28
+ HELP_INCLUDE_MARKER_EXP
29
+ )
30
+
31
+ @click.command("query", help="Command used to query information about tests.")
32
+ @click.option("--root", default=None, type=str, help=HELP_ROOT)
33
+ @click.option("--excludes", "-x", multiple=True, help=HELP_EXCLUDES)
34
+ @click.option("--includes", "-i", multiple=True, help=HELP_INCLUDES)
35
+ @click.option("--include-marker-exp", required=False, multiple=True, help=HELP_INCLUDE_MARKER_EXP)
36
+ @click.option("--exclude-marker-exp", required=False, multiple=True, help=HELP_EXCLUDE_MARKER_EXP)
37
+ def command_testplus_testing_query(root, includes, excludes,
38
+ include_marker_exp, exclude_marker_exp):
39
+ """
40
+ Queries tests available within a TestPlus environment.
41
+
42
+ :param root: Root directory used when scanning for tests.
43
+ :param includes: Inclusion expressions for tests.
44
+ :param excludes: Exclusion expressions for tests.
45
+
46
+ :returns: ``None``
47
+ """
48
+
49
+ # pylint: disable=unused-import,import-outside-toplevel
50
+
51
+ # We do the imports of the automation framework code inside the action functions because
52
+ # we don't want to startup loggin and the processing of inputs and environment variables
53
+ # until we have entered an action method. Thats way we know how to setup the environment.
54
+
55
+ # IMPORTANT: We need to load the context first because it will trigger the loading
56
+ # of the default user configuration
57
+
58
+ from ccai.thisapp.contextpaths import ContextPaths
59
+ from ccai.thisapp.wellknown import ThisApplicationSingleton
60
+
61
+ from ccai.xmodules.xpython import prepend_python_path
62
+
63
+ from ccai.thisapp.runtimevariables import RuntimeVariables
64
+
65
+ # STEP 2 - Resolve the configuration variables (performed by the root command)
66
+
67
+ # STEP 3 - Apply the configuration related options
68
+
69
+ # Instantiate the application singleton so the application context and the default user
70
+ # configuration are loaded before the configuration maps are resolved.
71
+ ThisApplicationSingleton()
72
+
73
+ # STEP 4 - Resolve the Configuration Maps
74
+ from ccai.configuration.configurationmaps import resolve_configuration_maps
75
+ resolve_configuration_maps(use_credentials=False, use_landscape=False,
76
+ use_runtime=False, use_topology=False)
77
+
78
+ # STEP 5 - Apply any Option Overrides
79
+
80
+ from ccai.runtime.optionoverrides import AM_RUNTIME_OPTION_OVERRIDES
81
+
82
+ from ccai.testbase.markers import MetaFilter, parse_marker_expression
83
+
84
+ # Setup the Test Root
85
+ if root is None:
86
+ if RuntimeVariables.AM_TESTROOT is not None:
87
+ root = RuntimeVariables.AM_TESTROOT
88
+ else:
89
+ root = "."
90
+
91
+ test_root = os.path.abspath(os.path.expandvars(os.path.expanduser(root)))
92
+ if not os.path.isdir(test_root):
93
+ errmsg = "The specified root folder does not exist. root=%s" % root
94
+ if test_root != root:
95
+ errmsg += " expanded=%s" % test_root
96
+ raise click.BadParameter(errmsg)
97
+
98
+ AM_RUNTIME_OPTION_OVERRIDES.override_testroot(root)
99
+
100
+ # Make sure we extend PATH to include the test roots parent folder so imports will
101
+ # work properly.
102
+ test_root_parent = os.path.dirname(test_root)
103
+ prepend_python_path(test_root_parent)
104
+
105
+ metafilters: List[MetaFilter] = []
106
+ for imexp in include_marker_exp:
107
+ imexp = imexp.strip("\"")
108
+ imexp = imexp.strip("'")
109
+ mfilter = parse_marker_expression("+" + imexp)
110
+ metafilters.append(mfilter)
111
+
112
+ for exmexp in exclude_marker_exp:
113
+ exmexp = exmexp.strip("\"")
114
+ exmexp = exmexp.strip("'")
115
+ mfilter = parse_marker_expression("-" + exmexp)
116
+ metafilters.append(mfilter)
117
+
118
+
119
+ # STEP 6 - Initialize the Results Output
120
+
121
+ # Initialize testplus results and logging
122
+ from ccai.testplus.initialize import initialize_testplus_results
123
+
124
+ initialize_testplus_results()
125
+
126
+
127
+ # STEP 7 - Kick off the query
128
+
129
+ logger = getEnhancedLogger()
130
+
131
+ from ccai.thisapp.wellknown import ConfiguredSuperFactorySingleton
132
+ from ccai.testbase.factoryprotocols import TestBaseFactoryProtocol
133
+
134
+ sfactory = ConfiguredSuperFactorySingleton()
135
+ TestJobType = sfactory.get_override_types_by_order(TestBaseFactoryProtocol.get_testbase_default_job_type)
136
+
137
+ # At this point in the code, we either lookup an existing test job or we create a test job
138
+ # from the includes, excludes or test_module
139
+ with TestJobType(logger, test_root, includes=includes, excludes=excludes, metafilters=metafilters) as tjob:
140
+ query_results = tjob.query()
141
+
142
+ test_names = [tn for tn in query_results.keys()]
143
+ test_names.sort()
144
+ print()
145
+ print("Tests:")
146
+ for tname in test_names:
147
+ tref = query_results[tname]
148
+ print(" " + tname)
149
+
150
+ param_names = [pn for pn in tref.subscriptions.keys()]
151
+ param_names.sort()
152
+ for pname in param_names:
153
+ pinfo = tref.subscriptions[pname]
154
+ print (" {}: {}".format(pname, pinfo.describe_source()))
155
+
156
+ print()
157
+
158
+ if len(tjob.import_errors) > 0:
159
+ print("IMPORT ERRORS:", file=sys.stderr)
160
+ for ifilename in tjob.import_errors:
161
+ imperr_msg = ifilename
162
+ print(" " + imperr_msg, file=sys.stderr)
163
+ print("", file=sys.stderr)
164
+
165
+ return