Skip to content

Commit 830114d

Browse files
[TESTING] Implemented CEP-8 exit-code refactoring and updated test code (- WIP #117 & PR #175 -)
> [!NOTE] > Fixes commits d6fa180..f7f7777 Changes in file .coderabbit.yaml: - Improved review directions slightly for project. Changes in file .coveragerc: - ignore untested corner-case for import errors that are never thrown. Changes in file docs/Makefile: - minor changes to cleanup logic. Changes in file multicast/__init__.py: - related changes to implementing new exceptions components. - related changes to implementing new exit-code logic to [align with CEP-8](https://gist.github.com/reactive-firewall/b7ee98df9e636a51806e62ef9c4ab161#4-adhere-to-posix-standards) - minor improvements to mtool abstract class. Changes in file multicast/__main__.py: - now adheres to [CEP-8](https://gist.github.com/reactive-firewall/b7ee98df9e636a51806e62ef9c4ab161) - overhauled `doStep method` to implementing new exit-code logic to [align with CEP-8](https://gist.github.com/reactive-firewall/b7ee98df9e636a51806e62ef9c4ab161#4-adhere-to-posix-standards) - refactored `main` function to implementing new exit-code logic to [align with CEP-8](https://gist.github.com/reactive-firewall/b7ee98df9e636a51806e62ef9c4ab161#4-adhere-to-posix-standards) - refactored `useTool` method to implementing new exit-code logic to [align with CEP-8](https://gist.github.com/reactive-firewall/b7ee98df9e636a51806e62ef9c4ab161#4-adhere-to-posix-standards) - related changes to implementing new exceptions components. - formalized implementation style of returning tuple of (success bool, details or None) from `doStep` - other minor odd changes Changes in file multicast/exceptions.py: - improved class CommandExecutionError to allow more use-cases - other minor odd changes Changes in file multicast/hear.py: - formalized implementation style of returning tuple of (success bool, details or None) from `doStep` Changes in file multicast/recv.py: - related changes to implementing new exceptions components. - formalized implementation style of returning tuple of (success bool, details or None) from `doStep` Changes in file multicast/send.py: - related changes to implementing new exceptions components. - formalized implementation style of returning tuple of (success bool, details or None) from `doStep` - refactored SAY logic to use skt.py utilities for a more single responsibility style design Changes in file tests/__init__.py: - Fixed a few regressions caused by implementing new exit-code logic - other minor odd changes Changes in file tests/profiling.py: - Fixed a few regressions caused by implementing new exit-code logic - other minor odd changes Changes in file tests/test_fuzz.py: - identified and resolved several regressions related to implementing new exit-code logic Changes in file tests/test_hear_data_processing.py: - identified and resolved several regressions related to implementing new exit-code logic Changes in file tests/test_hear_keyboard_interrupt.py: - identified and resolved several regressions related to implementing new exit-code logic - properly stablized the `ctrl^C` testing (- WIP #53 -) - other minor odd changes Changes in file tests/test_usage.py: - Fixed dozens of regressions caused by implementing new exit-code logic - Fixed dozens of regressions caused by implementing consistant `tuple` return logic in `doStep` methods - Refactored some tests for new behavior like accepting `--groups None` - other minor odd changes ### ChangeLog: Changes in file .coderabbit.yaml: reviews: Changes in file .coveragerc: include = multicast/*,tests/* Changes in file docs/Makefile: init: ../docs/ Changes in file multicast/__init__.py: def __call__(self, *args, **kwargs): def buildArgs(cls, calling_parser_group): Changes in file multicast/__main__.py: def doStep(self, *args, **kwargs): def main(*argv): def useTool(tool, **kwargs): Changes in file multicast/exceptions.py: class CommandExecutionError(RuntimeError): def __init__(self, *args, **kwargs): def exit_on_exception(func): Changes in file multicast/hear.py: def doStep(self, *args, **kwargs): Changes in file multicast/recv.py: def setupArgs(cls, parser): Changes in file multicast/send.py: def _sayStep(group, port, data): def doStep(self, *args, **kwargs): Changes in file tests/__init__.py: Unknown Changes Changes in file tests/profiling.py: def nothing(*args, **kwargs): Changes in file tests/test_fuzz.py: def test_multicast_sender_with_random_data(self, data): def test_say_works_WHEN_using_stdin_GIVEN_alnum_of_any_size_fuzz_input(self, tex Changes in file tests/test_hear_data_processing.py: class RecvDataProcessingTestSuite(context.BasicUsageTestSuite): Changes in file tests/test_hear_keyboard_interrupt.py: def test_hear_keyboard_interrupt(self): Changes in file tests/test_usage.py: def test_Usage_Error_WHEN_the_help_command_is_called(self): def test_Usage_Error_WHEN_the_help_sub_command_is_called(self): def test_aborts_WHEN_calling_multicast_GIVEN_invalid_tools(self): def test_hear_is_stable_WHEN_calling_multicast_GIVEN_invalid_tool(self): def test_hear_works_WHEN_fuzzed_and_say_works(self): def test_hear_works_WHEN_say_works(self): def test_noop_stable_WHEN_calling_multicast_GIVEN_noop_args(self): def test_say_works_WHEN_using_stdin(self):
1 parent f7f7777 commit 830114d

File tree

15 files changed

+372
-182
lines changed

15 files changed

+372
-182
lines changed

.coderabbit.yaml

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,14 @@ reviews:
99
# Code Review Instructions
1010
1111
- Ensure the code follows best practices and coding standards.
12+
- Check for security vulnerabilities and potential issues.
13+
- Ensure the code follows the **DRY, AHA, and SOLID** principles.
14+
- Our "Code Review Checklist Guide" is documented in
15+
[CEP-4](https://gist.github.com/reactive-firewall/cc041f10aad1d43a5ef15f50a6bbd5a5)
1216
- For **Python** code, follow
13-
[PEP 20](https://www.python.org/dev/peps/pep-0020/) and
17+
[PEP 20](https://www.python.org/dev/peps/pep-0020/),
18+
[PEP 483](https://peps.python.org/pep-0483/),
19+
[PEP 729](https://peps.python.org/pep-0729/), and
1420
[CEP-8](https://gist.github.com/reactive-firewall/b7ee98df9e636a51806e62ef9c4ab161)
1521
standards.
1622
- For **BASH** and **Shellscript** code, follow
@@ -19,25 +25,27 @@ reviews:
1925
standards.
2026
- Check all **BASH** files start with an
2127
[extensive disclaimer](https://gist.github.com/reactive-firewall/866b42d175ae3ebefcb2a5878b30ea17).
22-
- Check for security vulnerabilities and potential issues.
23-
- Ensure the code follows the **DRY, AHA, and SOLID** principles.
2428
2529
# Documentation Review Instructions
2630
- Verify that documentation and comments are clear and comprehensive.
2731
- Verify that documentation and comments are free of spelling mistakes.
2832
- Verify that technical documentation includes a "References" section at
2933
the end of documentation, using the same format as actual RFCs, with
3034
both "Normative References" and "Informative References".
35+
- Ensure that that documentation and comments follow
36+
[CEP-7](https://gist.github.com/reactive-firewall/123b8a45f1bdeb064079e0524a29ec20)
3137
3238
# Test Code Review Instructions
3339
- Ensure that test code is automated, comprehensive, and follows testing best practices.
3440
- Verify that all critical functionality is covered by tests.
35-
- Ensure that test code follow
36-
[CEP-8](https://gist.github.com/reactive-firewall/d840ee9990e65f302ce2a8d78ebe73f6)
41+
- For **test** code, *also* follow
42+
[CEP-9](https://gist.github.com/reactive-firewall/d840ee9990e65f302ce2a8d78ebe73f6)
3743
3844
# Misc.
3945
- Confirm that the code meets the project's requirements and objectives.
4046
- Confirm that copyright years are up-to date whenever a file is changed.
47+
- For **Python** code, consider [PEP 290](https://peps.python.org/pep-0290/) whenever a python
48+
(e.g. has the extension '.py') file is changed.
4149
request_changes_workflow: true
4250
high_level_summary: true
4351
high_level_summary_placeholder: '@coderabbitai summary'
@@ -81,7 +89,7 @@ reviews:
8189
3. You may assume the file 'README.md' will contain GitHub flavor Markdown.
8290
- path: '**/*.py'
8391
instructions: >-
84-
When reviewing Python code for this project:
92+
When reviewing **Python** code for this project:
8593
8694
1. Prioritize portability over clarity, especially when dealing with cross-Python compatibility. However, with the priority in mind, do still consider improvements to clarity when relevant.
8795
@@ -96,7 +104,7 @@ reviews:
96104
6. Verify Flake8's configuration file is located at ".flake8.ini"
97105
- path: tests/*
98106
instructions: >-
99-
When reviewing test code:
107+
When reviewing **test** code:
100108
101109
1. Prioritize portability over clarity, especially when dealing with cross-Python compatibility. However, with the priority in mind, do still consider improvements to clarity when relevant.
102110
@@ -167,6 +175,7 @@ reviews:
167175
- 'CEP-8'
168176
- 'CEP-7'
169177
- 'CEP-5'
178+
- 'CEP-4'
170179
- 'Shellscript'
171180
- 'bash'
172181
disabled_rules:

.coveragerc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ exclude_lines =
1212
pragma: no cover
1313
from . import
1414
pass
15+
except ImportError
1516
except Exception
1617
except BaseException
1718
# Don't complain if tests don't hit defensive assertion code:

docs/Makefile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,9 @@ init: ../docs/
6868
@ln -sf ../LICENSE.md $(SRCDIR)/LICENSE.md
6969

7070
clean:
71-
@-rm -rf $(BUILDDIR)/* 2>/dev/null || true
72-
@-rm -rfRd $(SRCDIR)/apidocs/* 2>/dev/null || true
71+
@-rm -rf $(SRCDIR)/$(BUILDDIR)/* 2>/dev/null || true
72+
@-rm -fd $(SRCDIR)/$(BUILDDIR) 2>/dev/null || true
73+
@-rm -rfd $(SRCDIR)/apidocs/* 2>/dev/null || true
7374
@-unlink $(SRCDIR)/README.md 2>/dev/null || true
7475
@-unlink $(SRCDIR)/tests 2>/dev/null || true
7576
@-unlink $(SRCDIR)/LICENSE.md 2>/dev/null || true

multicast/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ def buildArgs(cls, calling_parser_group):
473473
return calling_parser_group
474474

475475
@classmethod
476-
def parseArgs(cls, arguments=None):
476+
def parseArgs(cls, arguments):
477477
"""
478478
Will attempt to parse the given CLI arguments.
479479
@@ -595,7 +595,7 @@ def setupArgs(cls, parser): # pragma: no cover
595595
pass # skipcq - abstract method
596596

597597
@abc.abstractmethod
598-
def doStep(self, *args): # pragma: no cover
598+
def doStep(self, *args, **kwargs): # pragma: no cover
599599
"""
600600
Abstracts the __call__ behavior for sub-classing the tool.
601601

multicast/__main__.py

Lines changed: 68 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
__all__ = [
6767
"""__package__""", """__module__""", """__name__""", """__doc__""",
6868
"""McastNope""", """McastRecvHearDispatch""", """McastDispatch""", """main""",
69+
"""TASK_OPTIONS""",
6970
]
7071

7172

@@ -79,52 +80,59 @@
7980

8081

8182
try:
82-
from . import sys as _sys
83-
except Exception as impErr:
83+
from . import sys
84+
except ImportError as impErr:
8485
# Throw more relevant Error
8586
raise ImportError(str("[CWE-440] Error Importing Python")) from impErr
8687

8788

88-
if 'multicast.__version__' not in _sys.modules:
89+
if 'multicast.__version__' not in sys.modules:
8990
from . import __version__ as __version__ # skipcq: PYL-C0414
9091
else: # pragma: no branch
91-
__version__ = _sys.modules["""multicast.__version__"""]
92+
__version__ = sys.modules["""multicast.__version__"""]
9293

9394

94-
if 'multicast._MCAST_DEFAULT_PORT' not in _sys.modules:
95+
if 'multicast._MCAST_DEFAULT_PORT' not in sys.modules:
9596
from . import _MCAST_DEFAULT_PORT as _MCAST_DEFAULT_PORT # skipcq: PYL-C0414
9697
else: # pragma: no branch
97-
_MCAST_DEFAULT_PORT = _sys.modules["""multicast._MCAST_DEFAULT_PORT"""]
98+
_MCAST_DEFAULT_PORT = sys.modules["""multicast._MCAST_DEFAULT_PORT"""]
9899

99100

100-
if 'multicast._MCAST_DEFAULT_GROUP' not in _sys.modules:
101+
if 'multicast._MCAST_DEFAULT_GROUP' not in sys.modules:
101102
from . import _MCAST_DEFAULT_GROUP as _MCAST_DEFAULT_GROUP # skipcq: PYL-C0414
102103
else: # pragma: no branch
103-
_MCAST_DEFAULT_GROUP = _sys.modules["""multicast._MCAST_DEFAULT_GROUP"""]
104+
_MCAST_DEFAULT_GROUP = sys.modules["""multicast._MCAST_DEFAULT_GROUP"""]
104105

105106

106-
if 'multicast.mtool' not in _sys.modules:
107+
if 'multicast.exceptions' not in sys.modules:
108+
# pylint: disable=useless-import-alias - skipcq: PYL-C0414
109+
from . import exceptions as exceptions # skipcq: PYL-C0414
110+
else: # pragma: no branch
111+
exceptions = sys.modules["""multicast.exceptions"""]
112+
113+
114+
if 'multicast.mtool' not in sys.modules:
107115
from . import mtool as mtool # skipcq: PYL-C0414
108116
else: # pragma: no branch
109-
mtool = _sys.modules["""multicast.mtool"""]
117+
mtool = sys.modules["""multicast.mtool"""]
110118

111119

112-
if 'multicast.recv' not in _sys.modules:
120+
if 'multicast.recv' not in sys.modules:
113121
from . import recv as recv # pylint: disable=useless-import-alias - skipcq: PYL-C0414
114122
else: # pragma: no branch
115-
recv = _sys.modules["""multicast.recv"""]
123+
recv = sys.modules["""multicast.recv"""]
116124

117125

118-
if 'multicast.send' not in _sys.modules:
126+
if 'multicast.send' not in sys.modules:
119127
from . import send as send # pylint: disable=useless-import-alias - skipcq: PYL-C0414
120128
else: # pragma: no branch
121-
send = _sys.modules["""multicast.send"""]
129+
send = sys.modules["""multicast.send"""]
122130

123131

124-
if 'multicast.hear' not in _sys.modules:
132+
if 'multicast.hear' not in sys.modules:
125133
from . import hear as hear # pylint: disable=useless-import-alias - skipcq: PYL-C0414
126134
else: # pragma: no branch
127-
hear = _sys.modules["""multicast.hear"""]
135+
hear = sys.modules["""multicast.hear"""]
128136

129137

130138
class McastNope(mtool):
@@ -256,7 +264,11 @@ def doStep(self, *args, **kwargs):
256264
tuple: A "tuple" set to None.
257265
258266
"""
259-
return self.NoOp(*args, **kwargs)
267+
_None_from_NoOp = self.NoOp(*args, **kwargs)
268+
return (
269+
True if _None_from_NoOp is None else False,
270+
None if _None_from_NoOp is None else _None_from_NoOp # noqa
271+
)
260272

261273

262274
class McastRecvHearDispatch(mtool):
@@ -530,17 +542,17 @@ def useTool(tool, **kwargs):
530542
_is_done = False
531543
if (tool is not None) and (tool in cached_list):
532544
try:
533-
theResult = TASK_OPTIONS[tool].__call__([], **kwargs)
534-
_is_done = True
545+
(_is_done, theResult) = TASK_OPTIONS[tool].__call__([], **kwargs)
535546
except Exception as e: # pragma: no branch
536547
theResult = str(
537548
"""CRITICAL - Attempted '[{t}]: {args}' just failed! :: {errs}"""
538549
).format(
539550
t=tool, args=kwargs, errs=e
540551
)
552+
_is_done = False
541553
return (_is_done, theResult) # noqa
542554

543-
def doStep(self, *args):
555+
def doStep(self, *args, **kwargs):
544556
"""
545557
Executes the multicast tool based on parsed arguments.
546558
@@ -553,36 +565,40 @@ def doStep(self, *args):
553565
Returns:
554566
A tuple containing the exit status and the result of the tool execution.
555567
"""
556-
__EXIT_MSG = (1, "Unknown")
568+
__EXIT_MSG = (64, exceptions.EXIT_CODES[64][1])
557569
try:
558-
try:
559-
(argz, _) = type(self).parseArgs(*args)
560-
service_cmd = argz.cmd_tool
561-
argz.__dict__.__delitem__("""cmd_tool""")
562-
_TOOL_MSG = (self.useTool(service_cmd, **argz.__dict__))
563-
if _TOOL_MSG[0]:
564-
__EXIT_MSG = (0, _TOOL_MSG)
565-
elif (_sys.stdout.isatty()): # pragma: no cover
566-
print(_TOOL_MSG)
567-
except Exception as inerr: # pragma: no branch
568-
w = str("WARNING - An error occurred while")
569-
w += str(" handling the arguments.")
570-
w += str(" Refused.")
571-
if (_sys.stdout.isatty()): # pragma: no cover
572-
print(w)
573-
print(str(inerr))
574-
print(str(inerr.args))
575-
del inerr # skipcq - cleanup any error leaks early
576-
__EXIT_MSG = (2, "NoOp")
577-
except BaseException: # pragma: no branch
578-
e = str("CRITICAL - An error occurred while handling")
579-
e += str(" the dispatch.")
580-
if (_sys.stdout.isatty()): # pragma: no cover
581-
print(str(e))
582-
__EXIT_MSG = (3, "STOP")
570+
(argz, _) = type(self).parseArgs(*args)
571+
service_cmd = argz.cmd_tool
572+
argz.__dict__.__delitem__("""cmd_tool""")
573+
_TOOL_MSG = (self.useTool(service_cmd, **argz.__dict__))
574+
if _TOOL_MSG[0]:
575+
__EXIT_MSG = (0, _TOOL_MSG)
576+
else:
577+
__EXIT_MSG = (70, _TOOL_MSG)
578+
if (sys.stdout.isatty()): # pragma: no cover
579+
print(str(_TOOL_MSG))
580+
except Exception as err: # pragma: no branch
581+
exit_code = exceptions.get_exit_code_from_exception(err)
582+
__EXIT_MSG = (
583+
False, str(
584+
"""CRITICAL - Attempted '[{t}]: {args}' just failed! :: {code} {errs}"""
585+
).format(
586+
t=__name__, args=args, code=exit_code, errs=err
587+
)
588+
)
589+
if (sys.stderr.isatty()):
590+
print(
591+
"WARNING - An error occurred while handling the arguments. Refused.",
592+
file=sys.stderr
593+
)
594+
print(
595+
f"{exceptions.EXIT_CODES[exit_code][1]}: {err}\n{err.args}",
596+
file=sys.stderr
597+
)
583598
return __EXIT_MSG # noqa
584599

585600

601+
@exceptions.exit_on_exception
586602
def main(*argv):
587603
"""
588604
Do main event stuff.
@@ -597,12 +613,11 @@ def main(*argv):
597613
The expected return codes are as follows:
598614
= 0: Any nominal state (i.e. no errors and possibly success)
599615
≥ 1: Any erroneous state (includes simple failure)
600-
= 2: Any failed state
601-
= 3: Any undefined (but assumed erroneous) state
616+
> 2: Any failed state
602617
< 0: Implicitly erroneous and treated the same as abs(exit_code) would be.
603618
604619
Args:
605-
*argv: the array of arguments. Usually _sys.argv[1:]
620+
*argv: the array of arguments. Usually sys.argv[1:]
606621
607622
Returns:
608623
tuple: the underlying exit code int, and optional detail string.
@@ -695,9 +710,9 @@ def main(*argv):
695710

696711

697712
if __name__ in '__main__':
698-
__EXIT_CODE = (2, "NoOp")
699-
if (_sys.argv is not None) and (len(_sys.argv) > 1):
700-
__EXIT_CODE = main(_sys.argv[1:])
701-
elif (_sys.argv is not None):
713+
__EXIT_CODE = (1, exceptions.EXIT_CODES[1][1])
714+
if (sys.argv is not None) and (len(sys.argv) > 1):
715+
__EXIT_CODE = main(sys.argv[1:])
716+
elif (sys.argv is not None):
702717
__EXIT_CODE = main([str(__name__), """-h"""])
703718
exit(__EXIT_CODE[0]) # skipcq: PYL-R1722 - intentionally allow overwriteing exit for testing

0 commit comments

Comments
 (0)