#! /usr/bin/env python3
# -*- coding: utf-8 -*-
# Multicast Python Module
# ..................................
# Copyright (c) 2017-2025, Mr. Walls
# ..................................
# Licensed under MIT (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# ..........................................
# https://github.com/reactive-firewall-org/multicast/tree/HEAD/LICENSE.md
# ..........................................
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Main entry point for the multicast package.
This module provides the command-line interface and core functionalities for multicast
communication. It handles command dispatching and execution of multicast operations.
Classes:
McastNope: No-operation implementation for testing and validation.
McastRecvHearDispatch: Handler for receiving multicast messages.
McastDispatch: Main dispatcher for multicast operations.
Functions:
main(*argv): Main entry point for CLI operations.
Example:
>>> import multicast.__main__ as main
>>> exit_code, _ = main.main(['NOOP'])
>>> isinstance(exit_code, int)
True
Caution: See details regarding dynamic imports [documented](../__init__.py) in this module.
Minimal Acceptance Testing:
First set up test fixtures by importing multicast.
>>> import multicast as _multicast
>>>
>>> import _multicast.__main__
>>>
>>> _multicast.__doc__ is not None
True
>>>
>>> _multicast.__main__.__doc__ is not None
True
>>>
>>> _multicast.__version__ is not None
True
>>>
Testcase 0: multicast.__main__ should have a doctests.
>>> import _multicast.__main__
>>>
>>> _multicast.__main__.__module__ is not None
True
>>>
>>> _multicast.__main__.__doc__ is not None
True
>>>
"""
# skipcq
__all__ = [
"""McastNope""",
"""McastRecvHearDispatch""",
"""McastDispatch""",
"""main""",
"""TASK_OPTIONS""",
]
__package__ = "multicast" # skipcq: PYL-W0622
"""
The package of this component.
Minimal Acceptance Testing:
First set up test fixtures by importing multicast.
Testcase 0: Multicast should be importable.
>>> import multicast
>>>
Testcase 1: __main__ should be automatically imported.
>>> multicast.__main__.__package__ is not None
True
>>>
>>> multicast.__main__.__package__ == multicast.__package__
True
>>>
"""
__module__ = "multicast.__main__" # skipcq: PYL-W0622
"""
The module of this component.
Minimal Acceptance Testing:
First set up test fixtures by importing multicast.
Testcase 0: Multicast should be importable.
>>> import multicast
>>>
Testcase 1: __main__ should be automatically imported.
>>> multicast.__main__.__module__ is not None
True
>>>
"""
__file__ = "multicast/__main__.py"
"""The file of this component."""
try:
from . import sys
except ImportError as baton:
# Throw more relevant Error
raise ImportError("[CWE-440] Error Importing Python") from baton
if "multicast.__version__" not in sys.modules:
from . import __version__ as __version__ # skipcq: PYL-C0414
else: # pragma: no branch
__version__ = sys.modules["multicast.__version__"]
if "multicast._MCAST_DEFAULT_PORT" not in sys.modules:
from . import _MCAST_DEFAULT_PORT as _MCAST_DEFAULT_PORT # skipcq: PYL-C0414
else: # pragma: no branch
_MCAST_DEFAULT_PORT = sys.modules["multicast._MCAST_DEFAULT_PORT"]
if "multicast._MCAST_DEFAULT_GROUP" not in sys.modules:
from . import _MCAST_DEFAULT_GROUP as _MCAST_DEFAULT_GROUP # skipcq: PYL-C0414
else: # pragma: no branch
_MCAST_DEFAULT_GROUP = sys.modules["multicast._MCAST_DEFAULT_GROUP"]
if "multicast.exceptions" not in sys.modules:
# pylint: disable=useless-import-alias - skipcq: PYL-C0414
from . import exceptions as exceptions # skipcq: PYL-C0414
else: # pragma: no branch
exceptions = sys.modules["multicast.exceptions"]
if "multicast.mtool" not in sys.modules:
from . import mtool as mtool # skipcq: PYL-C0414
else: # pragma: no branch
mtool = sys.modules["multicast.mtool"]
if "multicast.recv" not in sys.modules:
from . import recv as recv # pylint: disable=useless-import-alias - skipcq: PYL-C0414
else: # pragma: no branch
recv = sys.modules["multicast.recv"]
if "multicast.send" not in sys.modules:
from . import send as send # pylint: disable=useless-import-alias - skipcq: PYL-C0414
else: # pragma: no branch
send = sys.modules["multicast.send"]
if "multicast.hear" not in sys.modules:
from . import hear as hear # pylint: disable=useless-import-alias - skipcq: PYL-C0414
else: # pragma: no branch
hear = sys.modules["multicast.hear"]
[docs]
class McastNope(mtool):
"""
The trivial implementation of mtool.
Testing:
Testcase 0: First set up test fixtures by importing multicast.
>>> import multicast.__main__ as _multicast
>>> _multicast.McastNope is not None
True
>>>
Testcase 1: McastNope should be detailed with some metadata.
A: Test that the __MAGIC__ variables are initialized.
B: Test that the __MAGIC__ variables are strings.
>>> _multicast.McastNope is not None
True
>>> _multicast.McastNope is not None
True
>>> _multicast.McastNope.__module__ is not None
True
>>> _multicast.McastNope.__proc__ is not None
True
>>> _multicast.McastNope.__prologue__ is not None
True
>>>
Testcase 2: parseArgs should return a namespace.
A: Test that the multicast.mtool component is initialized.
B: Test that the multicast.mtool.parseArgs component is initialized.
>>> multicast.mtool is not None
True
>>> _multicast.McastNope.parseArgs is not None
True
>>> tst_fxtr_args = ['''NOOP''', '''--port=1234''', '''--iface=127.0.0.1''']
>>> test_fixture = _multicast.McastNope.parseArgs(tst_fxtr_args)
>>> test_fixture is not None
True
>>> type(test_fixture) #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS
<...tuple...>
>>> tst_args_2 = ['''NOOP''', '''--junk''', '''--more-trash=stuff''']
>>> (test_fixture_2, test_ignore_extras) = _multicast.McastNope.parseArgs(tst_args_2)
>>> test_fixture_2 is not None
True
>>> type(test_fixture_2) #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS
<...Namespace...>
>>>
"""
__module__ = "multicast.__main__"
__name__ = "multicast.__main__.McastNope"
__proc__ = "NOOP" # NOT "Nope" rather "NoOp"
__prologue__ = "No Operation."
[docs]
@classmethod
def setupArgs(cls, parser):
"""
Sets up command-line arguments for the trivial subclass implementation of mtool.
This method is intended to be overridden by subclasses to define
specific command-line arguments. It takes a parser object as an
argument, which is typically an instance of `argparse.ArgumentParser`.
Args:
parser (argparse.ArgumentParser): The argument parser to which
the arguments should be added.
Returns:
None: This method does not return a value.
Note:
This is trivial implementation make this an optional abstract method. Subclasses may
choose to implement it or leave it as a no-op.
"""
pass # skipcq: PTC-W0049, PYL-W0107 - optional abstract method
[docs]
@staticmethod
def NoOp(*args, **kwargs):
"""
Do Nothing.
The meaning of Nothing. This function should be self-explanitory;
it does 'no operation' i.e. nothing.
This serves as a placeholder when no specific operation is required.
Args:
*args: Variable length argument list (unused).
**kwargs: Arbitrary keyword arguments (unused).
Minimal Acceptance Testing:
First set up test fixtures by importing multicast.
>>> import multicast.__main__
>>>
Testcase 0: multicast.__main__ should have a McastNope class.
>>> import multicast.__main__
>>> multicast.__main__.McastNope is not None
True
>>>
Testcase 1: multicast.NoOp should return None.
>>> import multicast.__main__
>>> multicast.__main__.McastNope.NoOp() is None
True
>>> multicast.__main__.McastNope.NoOp() is not None
False
>>>
>>> multicast.__main__.McastNope.NoOp("Junk")
None
>>>
"""
return None # noqa
[docs]
def doStep(self, *args, **kwargs):
"""
Overrides the `doStep` method from `mtool` to perform no action.
This method calls the `NoOp` function with the provided arguments and returns the result.
This serves as a placeholder or default action when no specific operation is required.
Args:
*args: Positional arguments passed to `NoOp`.
**kwargs: Keyword arguments passed to `NoOp`.
Args:
*args: Variable length argument list (unused).
**kwargs: Arbitrary keyword arguments (unused).
Returns:
tuple: A "tuple" set to None.
"""
_None_from_NoOp = self.NoOp(*args, **kwargs)
return (
_None_from_NoOp is None,
_None_from_NoOp # noqa
)
[docs]
class McastRecvHearDispatch(mtool):
"""
The `McastRecvHearDispatch` class handles receiving and dispatching multicast messages.
This class listens for multicast messages on a specified group and port, and dispatches them
to the appropriate handler. It is designed to work with both command-line tools and
programmatic interfaces.
Testing:
Testcase 0: First set up test fixtures by importing multicast.
>>> import multicast.__main__ as _multicast
>>> _multicast.McastNope is not None
True
>>>
Testcase 1: Recv should be detailed with some metadata.
A: Test that the __MAGIC__ variables are initialized.
B: Test that the __MAGIC__ variables are strings.
>>> multicast.__main__ is not None
True
>>> multicast.__main__.McastNope is not None
True
>>> multicast.recv.McastRECV.__module__ is not None
True
>>> multicast.recv.McastRECV.__proc__ is not None
True
>>> multicast.recv.McastRECV.__epilogue__ is not None
True
>>> multicast.recv.McastRECV.__prologue__ is not None
True
>>>
Testcase 2: parseArgs should return a namespace.
A: Test that the multicast.mtool component is initialized.
B: Test that the multicast.mtool.parseArgs component is initialized.
>>> multicast.mtool is not None
True
>>> _multicast.McastRecvHearDispatch.parseArgs is not None
True
>>> tst_fxtr_args = ['''NOOP''', '''--port=1234''', '''--iface=127.0.0.1''']
>>> test_fixture = _multicast.McastRecvHearDispatch.parseArgs(tst_fxtr_args)
>>> test_fixture is not None
True
>>> type(test_fixture) #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS
<...tuple...>
>>> tst_args_2 = ['''NOOP''', '''--junk''', '''--more-trash=stuff''']
>>> (test_fixture_2, t_ig_ext) = _multicast.McastRecvHearDispatch.parseArgs(tst_args_2)
>>> test_fixture_2 is not None
True
>>> type(test_fixture_2) #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS
<...Namespace...>
>>>
"""
__module__ = "multicast.__main__"
__name__ = "multicast.__main__.McastRecvHearDispatch"
__proc__ = "HEAR"
__epilogue__ = """Generally speaking you want to bind to one of the groups you joined in
this module/instance, but it is also possible to bind to group which
is added by some other programs (like another python program instance of this)
"""
__prologue__ = "Python Multicast Receiver. Primitives for a listener for multicast data."
[docs]
@classmethod
def setupArgs(cls, parser):
"""
Will attempt to add hear args.
Both HEAR and RECV use the same arguments, and are differentiated only by the global,
'--daemon' argument during dispatch. The remaining arguments are:
| Arguments | Notes |
|-----------|------------------------------------------------------------|
| --deamon | Enable use of HEAR, otherwise use RECV if omitted |
| --group | multicast group (ip address) to bind-to for the udp socket |
| --groups | multicast groups to join (should include the bind group) |
| --port | The UDP port number to listen/filter on for the udp socket |
Testing:
Testcase 0: First set up test fixtures by importing multicast.
>>> import multicast
>>> multicast.hear is not None
True
>>> multicast.hear.McastHEAR is not None
True
>>>
Testcase 1: main should return an int.
A: Test that the multicast component is initialized.
B: Test that the hear component is initialized.
C: Test that the main(hear) function is initialized.
D: Test that the main(hear) function returns an int 0-3.
>>> multicast.hear is not None
True
>>> multicast.__main__.main is not None
True
>>> tst_fxtr_args = ['''HEAR''', '''--daemon''', '''--port=1234''']
>>> (test_fixture, junk_ignore) = multicast.__main__.main(tst_fxtr_args)
>>> test_fixture is not None
True
>>> type(test_fixture) #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS
<...int...>
>>> int(test_fixture) >= int(0)
True
>>> int(test_fixture) < int(4)
True
>>>
Testcase 2: setupArgs should not error given valid input.
A: Test that the multicast component is initialized.
B: Test that the __main__ component is initialized.
C: Test that the McastRecvHearDispatch class is initialized.
D: Test that the setupArgs function returns without error.
>>> multicast.__main__ is not None
True
>>> multicast.__main__.McastRecvHearDispatch is not None
True
>>> tst_fxtr_args = argparse.ArgumentParser(prog="testcase")
>>> multicast.__main__.McastRecvHearDispatch.setupArgs(parser=tst_fxtr_args)
>>>
Testcase 3: setupArgs should return None untouched.
A: Test that the multicast component is initialized.
B: Test that the __main__ component is initialized.
C: Test that the McastRecvHearDispatch class is initialized.
D: Test that the McastRecvHearDispatch.setupArgs() function yields None.
>>> multicast.__main__ is not None
True
>>> multicast.__main__.McastRecvHearDispatch is not None
True
>>> multicast.__main__.McastRecvHearDispatch.setupArgs is not None
True
>>> tst_fxtr_N = None
>>> test_fixture = multicast.__main__.McastRecvHearDispatch.setupArgs(tst_fxtr_N)
>>> test_fixture is not None
False
>>> type(test_fixture) #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS
<...None...>
>>> tst_fxtr_N == test_fixture
True
>>> tst_fxtr_N is None
True
>>>
>>> test_fixture is None
True
>>>
"""
if parser is not None: # pragma: no branch
parser.add_argument(
"--port",
type=int,
default=_MCAST_DEFAULT_PORT, # skipcq: PYL-W0212 - module ok
)
__tmp_help = "local interface to use for listening to multicast data; "
__tmp_help += "if unspecified, any one interface may be chosen."
parser.add_argument("--iface", default=None, help=__tmp_help)
__tmp_help = "multicast group (ip address) to bind-to for the udp socket; "
__tmp_help += "should be one of the multicast groups joined globally "
__tmp_help += "(not necessarily joined in this python program) "
__tmp_help += "in the interface specified by --iface. "
__tmp_help += f"If unspecified, bind-to {_MCAST_DEFAULT_GROUP} "
__tmp_help += "(all addresses (all multicast addresses) of that interface)"
parser.add_argument(
"--group",
default=_MCAST_DEFAULT_GROUP, # skipcq: PYL-W0212 - module ok
help=__tmp_help,
)
__tmp_help = "multicast groups (ip addresses) to join globally; "
__tmp_help += "should be one of the multicast groups joined globally "
__tmp_help += "by the interface specified by --iface. "
__tmp_help += "If unspecified, or supplied an empty list, the default "
__tmp_help += "implementation will join "
__tmp_help += f"{_MCAST_DEFAULT_GROUP} (all addresses (all multicast addresses) "
__tmp_help += "of that interface) instead of not joining. NOTE: If you really need "
__tmp_help += "to NOT join the multicast group you should instead use the sockets "
__tmp_help += "module directly, as this module does not support such a use-case."
parser.add_argument("--groups", default=[], nargs="*", help=__tmp_help)
[docs]
@staticmethod
def _help_daemon_dispatch(*args, **kwargs):
"""
Helps checking flags for daemon dispatching.
Internal method to check the `--daemon` option
and interpret how it affects the dispatching of sub-commands.
Args:
*args: Additional positional arguments.
**kwargs: Parsed command-line arguments.
Returns:
boolean: True if daemon mode is to be used, otherwise False.
"""
_useHear = kwargs.get("is_daemon", False)
return _useHear
[docs]
def doStep(self, *args, **kwargs):
"""
Executes a multicast step based on the daemon dispatch.
Overrides the `doStep` method from `mtool` to determine and execute
the correct sub-command based on provided arguments.
This method selects either the `McastHEAR` or `McastRECV` class based on the daemon
dispatch flag and executes the corresponding step.
The RECV (via McastRECV) is the primitive sub-command to receive a single multicast hunk.
The HEAR (via McastHEAR) is equivalent to running RECV in a loop to continually receive
multiple hunks. Most use-case will probably want to use HEAR instead of RECV.
Args:
*args: Variable length argument list containing command-line arguments.
**kwargs: Arbitrary keyword arguments.
Returns:
tuple: The result of the dispatched sub-command's `doStep` method.
"""
if self._help_daemon_dispatch(*args, **kwargs):
__stub_class = hear.McastHEAR
else:
__stub_class = recv.McastRECV
return __stub_class().doStep(*args, **kwargs)
# More boiler-plate-code
TASK_OPTIONS = {
"NOOP": McastNope(),
"RECV": McastRecvHearDispatch(),
"SAY": send.McastSAY(),
"HEAR": McastRecvHearDispatch(),
}
"""The callable function tasks of this program. will add."""
[docs]
class McastDispatch(mtool):
"""
The `McastDispatch` class is the main entry point for dispatching multicast tasks.
It provides a command-line interface for sending, receiving, and listening to multicast
messages. The class handles argument parsing and dispatches the appropriate multicast
tool based on the provided command.
"""
__proc__: str = "multicast"
__prologue__: str = "The Main Entrypoint."
__epilogue__: str = "Called from the command line, this main component handles the CLI dispatch."
[docs]
@classmethod
def setupArgs(cls, parser) -> None:
"""
Will attempt to add each subcomponent's args.
As the dispatch tool, this is more of a proxy implementation to call the sub-components
own setupArgs.
"""
if parser is not None: # pragma: no branch
for sub_tool in sorted(TASK_OPTIONS.keys()):
sub_parser = parser.add_parser(sub_tool, help="...")
type(TASK_OPTIONS[sub_tool]).setupArgs(sub_parser)
[docs]
def doStep(self, *args, **kwargs) -> tuple:
"""
Executes the multicast tool based on parsed arguments.
This method parses the command-line arguments, selects the appropriate multicast tool, and
executes it. If an error occurs during argument handling, it prints a warning message.
Args:
*args: Command-line arguments for the multicast tool.
Returns:
A tuple containing the exit status and the result of the tool execution.
"""
_response: tuple = (64, exceptions.EXIT_CODES[64][1])
try:
(argz, _) = type(self).parseArgs(*args)
service_cmd = argz.cmd_tool
argz.__dict__.__delitem__("cmd_tool")
_TOOL_MSG = (self.useTool(service_cmd, **argz.__dict__)) # skipcq: PYL-C0325
if _TOOL_MSG[0]:
_response = (0, _TOOL_MSG)
else:
_response = (70, _TOOL_MSG)
if sys.stdout.isatty(): # pragma: no cover
print(str(_TOOL_MSG))
except Exception as _cause: # pragma: no branch
exit_code = exceptions.get_exit_code_from_exception(_cause)
_response = (
1,
f"CRITICAL - Attempted '[{__name__}]: {args}' just failed! :: {exit_code} {_cause}" # noqa
)
if sys.stderr.isatty():
print(
"WARNING - An error occurred while handling the arguments. Refused.",
file=sys.stderr,
)
print(f"{exceptions.EXIT_CODES[exit_code][1]}: {_cause}\n{_cause.args}", file=sys.stderr)
return _response # noqa
[docs]
@exceptions.exit_on_exception
def main(*argv) -> tuple:
"""
Do main event stuff.
Executes the multicast command-line interface, by parsing command-line arguments and dispatching
the appropriate multicast operations.
The main(*args) function in multicast is expected to return a POSIX compatible exit code and
optional detail message. Regardless of errors the result as an 'exit code' (int) is returned,
even if the optional details are not (e.g., `tuple(int(exit_code), None)`).
The only exception is when the error results in exiting the process, which will exit the
python runtime with the underlying return codes, instead of returning directly to the then
unreachable caller. See `multicast.exceptions.exit_on_exception` for the mechanisms involved.
The expected return codes are as follows:
= 0: Any nominal state (i.e. no errors and possibly success)
≥ 1: Any erroneous state (includes simple failure)
> 2: Any failed state
< 0: Implicitly erroneous and treated the same as abs(exit_code) would be.
Args:
*argv: the array of arguments. Usually sys.argv[1:]
Returns:
tuple: the underlying exit code int, and optional detail string.
Minimal Acceptance Testing:
First set up test fixtures by importing multicast.
>>> import multicast
>>> multicast.send is not None
True
>>>
Testcase 0: main should return an int.
A: Test that the multicast component is initialized.
B: Test that the send component is initialized.
C: Test that the send.main function is initialized.
D: Test that the send.main function returns an int 0-3.
>>> multicast.send is not None
True
>>> multicast.__main__.main is not None
True
>>> tst_fxtr_args = ['''SAY''', '''--port=1234''', '''--message''', '''is required''']
>>> (test_fixture, junk_ignore) = multicast.__main__.main(tst_fxtr_args)
>>> test_fixture is not None
True
>>> type(test_fixture) #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS
<...int...>
>>> int(test_fixture) >= int(0)
True
>>> int(test_fixture) < int(4)
True
>>>
Testcase 1: main should return an int.
A: Test that the multicast component is initialized.
B: Test that the recv component is initialized.
C: Test that the main(recv) function is initialized.
D: Test that the main(recv) function returns an int 0-3.
>>> multicast.recv is not None
True
>>> multicast.__main__.main is not None
True
>>> tst_fxtr_args = ['''RECV''', '''--port=1234''', '''--group''', '''224.0.0.1''']
>>> (test_fixture, junk_ignore) = multicast.__main__.main(tst_fxtr_args)
>>> test_fixture is not None
True
>>> type(test_fixture) #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS
<...int...>
>>> int(test_fixture) >= int(0)
True
>>> int(test_fixture) < int(4)
True
>>>
Testcase 2: main should error with usage.
A: Test that the multicast component is initialized.
B: Test that the recv component is initialized.
C: Test that the main(recv) function is initialized.
D: Test that the main(recv) function errors with a usage hint by default.
>>> multicast.recv is not None
True
>>> multicast.__main__.main is not None
True
>>> (test_fixture, junk_ignore) = multicast.__main__.main() #doctest: +ELLIPSIS
usage: multicast [-h | -V] [--use-std] [--daemon] CMD ...
multicast...
CRITICAL...
>>> type(test_fixture) #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS
<...int...>
>>> int(test_fixture) >= int(0)
True
>>> int(test_fixture) < int(4)
True
>>> type(junk_ignore)
<...str...>
>>> junk_ignore in "STOP"
True
>>>
"""
dispatch: McastDispatch = McastDispatch()
return dispatch(*argv)
[docs]
def cli() -> int:
"""
Do main console script stuff.
Along with `main()`, `cli()` provides a main entrypoint for console usage of multicast.
`cli()` just calls `main()` with arguments from `sys.argv` and returns only the exit-code.
Through calling `multicast.__main__.main(sys.argv[1:])`, `cli()` ...
> Executes the multicast command-line interface, by parsing command-line arguments and
> dispatching the appropriate multicast operations.
>
> The main(*args) function in multicast is expected to return a POSIX compatible exit code...
`cli()` versus `main(*args)`:
- The primary difference is the return types, whereas `main(*args)` returns a `tuple`,
`cli()` returns only the first element as an `int`.
- The secondary difference between `cli()` and `main(*args)` is that `main(*args)` requires
arguments to be passed, whereas `cli()` will use `sys.argv` instead.
__Except__ in the case of errors, the result as an 'exit code' (int) is returned by `cli()`.
The expected return codes are the same as those from `main(*args)`.
Args:
None: Uses `sys.argv` instead.
Returns:
int: the underlying exit code int
Minimal Acceptance Testing:
First set up test fixtures by importing multicast.
>>> import multicast
>>> multicast.__main__.main is not None
True
>>>
Testcase 0: calls to cli should return an int.
A: Test that the call to the `cli` function returns an int 0-3.
>>> tst_argv_args = ['''SAY''', '''--port=1234''', '''--message''', '''is required''']
>>> sys.argv = tst_argv_args # normally arguments are automatically already in argv
>>> test_code = multicast.__main__.cli()
>>> test_code is not None
True
>>> type(test_code) #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS
<...int...>
>>> int(test_code) >= int(0)
True
>>> int(test_code) < int(4)
True
>>>
Testcase 1: main should error with usage.
A: Test that the multicast component is initialized.
B: Test that the recv component is initialized.
C: Test that the main(recv) function is initialized.
D: Test that the main(recv) function errors with a usage hint by default.
>>> multicast.__main__.main is not None
True
>>> test_code = multicast.__main__.cli() #doctest: +ELLIPSIS
usage: multicast [-h | -V] [--use-std] [--daemon] CMD ...
multicast...
CRITICAL...
>>> type(test_code) #doctest: -DONT_ACCEPT_BLANKLINE, +ELLIPSIS
<...int...>
>>> int(test_code) >= int(0)
True
>>> int(test_code) < int(4)
True
>>>
"""
__EXIT_CODE: tuple = (1, exceptions.EXIT_CODES[1][1])
if sys.argv is not None:
if len(sys.argv) > 1:
__EXIT_CODE = main(sys.argv[1:])
else: # pragma: no branch
__EXIT_CODE = main([__name__, "-h"])
return __EXIT_CODE[0]
if __name__ in '__main__':
exit(cli()) # skipcq: PYL-R1722 - intentionally allow overwriting exit for testing