diff --git a/README.md b/README.md index acb38c7..10151fa 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ * [New cFS Project Configuration](#new-cfs-project-configuration) * [Developing New Test Scripts](#developing-new-test-scripts) * [Developing or Extending CTF Plugins](#developing-or-extending-ctf-plugins) + * [Updating CTF](#updating-ctf) * [Release Notes](#release-notes) - * [Contact](#contact) * [License](#license) # cFS Test Framework @@ -254,30 +254,39 @@ Plugin READMEs include documentation of their own instructions and configuration Refer to the [plugin guide](docs/ctf/ctf_plugin_guide.md) for information on creating custom CTF plugins. -## Release Notes -### v1.0 -10/30/2020 +## Updating CTF -Major Release +CTF versions can be checked out by running `git checkout `. This updates the CTF submodule/repository to the selected version. -This is the first open source release for CTF. Note that CTF remains in active development. +In addition, the configuration (and test script) files may need to be updated with new configuration fields, or test script additions/changes. -Existing users should review the change logs below and ensure current configurations and scripts are updated. +The sections below describe the changes needed to quickly update to a specific version. Note that the release notes contain a more complete set of changes. -* Release Additions - * Add `external/` directory with a self contained example CFS project. This project can be configured to utilize CTF out of the box. Refer to `external/README.md` for more information. +### Updating to v1.X -* CTF Core Changes - * Add an option to provide an additional plugin path within the configuration file. Custom plugins are loaded from that directory, in addition to CTF default plugins. - * Add the `additional_plugin_path = ` field to the config ini. - * Note - Give the custom plugin directory a unique name (do not use `plugins`, `lib`, etc...), so as to not shadow any modules within the CTF repo. - * Add an option to ignore specific CTF instructions within the configuration file. This is useful for CI or specific-configurations that may not have the ability to run certain instructions. - * Add the `ignored_instructions = , , ...` field to the config ini. Specify the instructions to ignore (comma-seperated). - * Minor improvements and bug-fixes. - -* CCSDS Plugin Changes +CTF v1.0 introduces major additions to the configuration file, as well as changes to the test script schema and instructions. + +* Existing CTF Test Scripts + * Using a find & replace tool, update all references within a test script as follows + * All Instances + * `"commands"` -> `"instructions"` + * `"command` -> `"instruction"` + * CFS Plugin Instructions + * `"name"` -> `"target"` + * `"set_length"` to `"payload_length"` + +* Config changes (reference `configs/default_config.ini` for descriptions and examples) + * Add fields + * core:additional_plugin_path + * core:ignored_instructions + * core:delay_between_scripts + * cfs:CCSDS_target + * cfs:evs_event_mid_name + * Delete fields + * cfs:evs_event_msg_mid + +* CCSDS Message Definitions * **Update to CCSDS Definition Schema (JSON) to support multiple MID values for the same message definitions** - * Move the "mid" field from each message definition to a separate MID map file, mapping between MID names and raw MID values per cFS target. * For command structure definitions * Rename `command_id_name` to `cmd_mid_name` * Remove `command_message_id`. Now maintained in a separate MID map file @@ -289,6 +298,7 @@ Existing users should review the change logs below and ensure current configurat * Add `cc_data_type` * Rename `args` to `cc_parameters` * No changes needed within `cc_parameters` (previously `args`) + * For telemetry structure definitions * Rename `name` to `tlm_mid_name` * Remove `mid`. Now maintained in a separate MID map file @@ -296,7 +306,16 @@ Existing users should review the change logs below and ensure current configurat * Add `tlm_description` * Rename `parameters` to `tlm_parameters` * No changes to `name`, `array_size`, `description`, `bit_length`, `parameters` + + * For alias and constant definitions + * Rename `cfs_type_name` to `alias_name` + * Rename `c_type` to `actual_name` + * Rename `cfs_macro_name` to `constant_name` + * Rename `c_macro` to `constant_value` + * MID Map JSON + * Move the "mid" field from each message definition to a separate MIDs map file, mapping between MID names and raw MID values per cFS target. + * An array of objects mapping target to MID value as follows ``` [ @@ -309,12 +328,86 @@ Existing users should review the change logs below and ensure current configurat } ] ``` - * **Please update the CCSDS JSON Definitions according to the changes above. Refer to the sample_cfs_workspace/ccdd/json for reference (after extracting the workspace from `external/`)** - * Change config field `evs_event_msg_mid` to `evs_event_mid_name` which should now be set to the MID name for the Event Messages (for example `CFE_EVS_LONG_EVENT_MSG_MID`). The actual MID value will be retrieved from the CCSDS message definitions. + * Refer to the `sample_cfs_workspace/ccdd/json` for reference (after extracting the workspace from `external/`) + +## Release Notes +### v1.1 +12/18/2020 + +* CTF Core Changes + * Add support for a `disable` field to CTF instructions within a test script to temporarily disable that instruction. + + * Add support for a `description` field to CTF instructions within a test script to capture comments. + + * Add `end_test_on_fail_commands` attribute to the `Plugin` class, allowing plugins to define a list of critical commands that end the current test script on failure. + + * Minor improvements and bug fixes. + +* CFS Plugin Changes + * Add the `RegisterCfs` and `StartCfs` instructions to the `end_test_on_fail_commands` such that test execution is halted if registering or starting cFS fails. + + * Add support for Short Event Messages by setting the `cfs:evs_short_event_mid_name` field in the config to match the MID name of the `CFE_EVS_ShortEventTlm_t` within the CCSDS JSON definitions. + + * Add the CheckNoEvent instruction to check that an event message was *not* sent during the verification timeout. + + * Ensure args do not get malformed while sending a command to multiple cFS targets + + * Resolve CheckTlmValue passing if one or more variables fails to be evaluated from the telemetry packet. + + * Resolve connection and deployment issues with cFS targets running with the SP0 protocol (WIP) + + * Minor improvements and bug fixes. + + +* CTF Editor Changes + * Add support for disabling/enabling test instructions or test cases + + * Add support for viewing/editing descriptions (comments) within the test script + + * Add the ability to rename folders or test scripts within the editor + + * Minor improvements and bug fixes. + +### v1.0 +10/30/2020 + +Major Release + +This is the first open source release for CTF. Note that CTF remains in active development. + +Existing users should review the change logs below and ensure current configurations and scripts are updated. + +* Release Additions + * Add `external/` directory with a self contained example CFS project. This project can be configured to utilize CTF out of the box. Refer to `external/README.md` for more information. + +* CTF Core Changes + + * **Update test script JSON schema with more accurate field names** + * Update `commands` array to `instructions` + * Update `command` object to `instruction` + * Refer to [Updating to v1.0](updating-to-v10) + + * Add an option to provide an additional plugin path within the configuration file. Custom plugins are loaded from that directory, in addition to CTF default plugins. + * Add the `additional_plugin_path = ` field to the config ini. + * Note - Give the custom plugin directory a unique name (do not use `plugins`, `lib`, etc...), so as to not shadow any modules within the CTF repo. + + * Add an option to ignore specific CTF instructions within the configuration file. This is useful for CI or specific-configurations that may not have the ability to run certain instructions. + * Add the `ignored_instructions = , , ...` field to the config ini. Specify the instructions to ignore (comma-seperated). + + * Minor improvements and bug-fixes. + +* CCSDS Plugin Changes + * **Update to CCSDS Definition Schema (JSON) to support multiple MID values for the same message definitions** + * Refer to [Updating to v1.0](updating-to-v10) + + * **Change config field `evs_event_msg_mid` to `evs_event_mid_name` which should now be set to the MID name for the Event Messages (for example `CFE_EVS_LONG_EVENT_MSG_MID`).** + * The actual MID value will be retrieved from the CCSDS message definitions. + * CFS Plugin Changes * **Update `name` field to `target`** * The `name` field is used for *all* cFS Plugin Instructions. This field is now renamed to `target` for clarity, and specifies the cFS target to apply the instruction to. * **Please change all instances of `"name":` to `"target"` within existing test scripts. Example scripts have been updated as part of the release.** + * No longer validate `cfs_run_dir` on registration. Previously, if the cFS instance was not built (i.e the run directory does not exist), validation would fail. * Rename `set_length` to `payload_length` for the `SendInvalidLengthCfsCommand` test instruction. @@ -327,6 +420,7 @@ Existing users should review the change logs below and ensure current configurat * CTF Editor Changes * Improvements to the Run Status View * Instructions can now be expanded while/after a test run to inspect instruction arguments and data. + * Updates to support CTF and CCSDS definition changes. ### v0.6 @@ -506,16 +600,6 @@ Major backend updates to improve reliability/maintainability of CTF. * Initial CTF Editor Release -## Contact - -Aly Shehata - -Software, Robotics and Simulation (ER6) - -NASA - Johnson Space Center (B32/224) - -Email - aly.shehata@nasa.gov - ## License MSC-26646-1, "Core Flight System Test Framework (CTF)" diff --git a/configs/ci_config.ini b/configs/ci_config.ini index 6587d94..efc347f 100644 --- a/configs/ci_config.ini +++ b/configs/ci_config.ini @@ -210,9 +210,12 @@ CCSDS_target = set1 # as new events packets evs_messages_clear_after_time = 5 -# Name of MID to collect cfe EVS messages +# Name of MID to collect cfe EVS long messages evs_event_mid_name = CFE_EVS_LONG_EVENT_MSG_MID +# Name of MID to collect cfe EVS short messages +evs_short_event_mid_name = CFE_EVS_SHORT_EVENT_MSG_MID + ################################# # ################################# diff --git a/configs/default_config.ini b/configs/default_config.ini index 4f108e0..260fe39 100644 --- a/configs/default_config.ini +++ b/configs/default_config.ini @@ -201,9 +201,12 @@ ccsds_ver = 2 # as new events packets evs_messages_clear_after_time = 5 -# Name of MID to collect cfe EVS messages +# Name of MID to collect cfe EVS long messages evs_event_mid_name = CFE_EVS_LONG_EVENT_MSG_MID +# Name of MID to collect cfe EVS short messages +evs_short_event_mid_name = CFE_EVS_SHORT_EVENT_MSG_MID + ################################# # ################################# diff --git a/configs/example_configs/cfe_6_7_config_examples.ini b/configs/example_configs/cfe_6_7_config_examples.ini index 9f26598..cb6172b 100644 --- a/configs/example_configs/cfe_6_7_config_examples.ini +++ b/configs/example_configs/cfe_6_7_config_examples.ini @@ -157,9 +157,12 @@ ccsds_ver = 2 # as new events packets evs_messages_clear_after_time = 5 -# Name of MID to collect cfe EVS messages +# Name of MID to collect cfe EVS long messages evs_event_mid_name = CFE_EVS_LONG_EVENT_MSG_MID +# Name of MID to collect cfe EVS short messages +evs_short_event_mid_name = CFE_EVS_SHORT_EVENT_MSG_MID + ################################# # Example settings for LX1 ################################# @@ -243,9 +246,12 @@ tlm_udp_port = 5011 # What CCSDS version ccsds_ver = 2 -# Name of MID to collect cfe EVS messages +# Name of MID to collect cfe EVS long messages evs_event_mid_name = CFE_EVS_LONG_EVENT_MSG_MID +# Name of MID to collect cfe EVS short messages +evs_short_event_mid_name = CFE_EVS_SHORT_EVENT_MSG_MID + ################################# # Example settings for LX2 ################################# @@ -268,9 +274,12 @@ tlm_udp_port = 5021 # What CCSDS version ccsds_ver = 2 -# Name of MID to collect cfe EVS messages +# Name of MID to collect cfe EVS long messages evs_event_mid_name = CFE_EVS_LONG_EVENT_MSG_MID +# Name of MID to collect cfe EVS short messages +evs_short_event_mid_name = CFE_EVS_SHORT_EVENT_MSG_MID + ################################# # Example settings for SP01 ################################# @@ -351,9 +360,12 @@ tlm_udp_port = 5031 # What CCSDS version ccsds_ver = 2 -# Name of MID to collect cfe EVS messages +# Name of MID to collect cfe EVS long messages evs_event_mid_name = CFE_EVS_LONG_EVENT_MSG_MID +# Name of MID to collect cfe EVS short messages +evs_short_event_mid_name = CFE_EVS_SHORT_EVENT_MSG_MID + ################################# # Example setting for SP02 ################################# @@ -435,9 +447,12 @@ tlm_udp_port = 5041 # What CCSDS version ccsds_ver = 2 -# Name of MID to collect cfe EVS messages +# Name of MID to collect cfe EVS long messages evs_event_mid_name = CFE_EVS_LONG_EVENT_MSG_MID +# Name of MID to collect cfe EVS short messages +evs_short_event_mid_name = CFE_EVS_SHORT_EVENT_MSG_MID + ################################# # Base settings for local SSH ################################# diff --git a/configs/example_configs/cfe_6_7_config_lx1.ini b/configs/example_configs/cfe_6_7_config_lx1.ini index 4033c54..a913f22 100644 --- a/configs/example_configs/cfe_6_7_config_lx1.ini +++ b/configs/example_configs/cfe_6_7_config_lx1.ini @@ -154,9 +154,12 @@ ccsds_ver = 2 # as new events packets evs_messages_clear_after_time = 5 -# Name of MID to collect cfe EVS messages +# Name of MID to collect cfe EVS long messages evs_event_mid_name = CFE_EVS_LONG_EVENT_MSG_MID +# Name of MID to collect cfe EVS short messages +evs_short_event_mid_name = CFE_EVS_SHORT_EVENT_MSG_MID + ################################# # Example settings for LX1 ################################# @@ -240,9 +243,12 @@ tlm_udp_port = 5011 # What CCSDS version ccsds_ver = 2 -# Name of MID to collect cfe EVS messages +# Name of MID to collect cfe EVS long messages evs_event_mid_name = CFE_EVS_LONG_EVENT_MSG_MID +# Name of MID to collect cfe EVS short messages +evs_short_event_mid_name = CFE_EVS_SHORT_EVENT_MSG_MID + ################################# # ################################# diff --git a/configs/example_configs/example_config_sp0.ini b/configs/example_configs/example_config_sp0.ini index 14386e2..dc71597 100644 --- a/configs/example_configs/example_config_sp0.ini +++ b/configs/example_configs/example_config_sp0.ini @@ -174,9 +174,12 @@ ccsds_ver = 2 # as new events packets evs_messages_clear_after_time = 5 -# Name of MID to collect cfe EVS messages +# Name of MID to collect cfe EVS long messages evs_event_mid_name = CFE_EVS_LONG_EVENT_MSG_MID +# Name of MID to collect cfe EVS short messages +evs_short_event_mid_name = CFE_EVS_SHORT_EVENT_MSG_MID + ################################# # ################################# @@ -262,5 +265,8 @@ tlm_udp_port = 5011 # What CCSDS version ccsds_ver = 2 -# Name of MID to collect cfe EVS messages -evs_event_mid_name = CFE_EVS_LONG_EVENT_MSG_MID \ No newline at end of file +# Name of MID to collect cfe EVS long messages +evs_event_mid_name = CFE_EVS_LONG_EVENT_MSG_MID + +# Name of MID to collect cfe EVS short messages +evs_short_event_mid_name = CFE_EVS_SHORT_EVENT_MSG_MID diff --git a/configs/example_configs/example_config_sp01_sp02.ini b/configs/example_configs/example_config_sp01_sp02.ini index 330bbea..07de24f 100644 --- a/configs/example_configs/example_config_sp01_sp02.ini +++ b/configs/example_configs/example_config_sp01_sp02.ini @@ -174,9 +174,12 @@ ccsds_ver = 2 # as new events packets evs_messages_clear_after_time = 5 -# Name of MID to collect cfe EVS messages +# Name of MID to collect cfe EVS long messages evs_event_mid_name = CFE_EVS_LONG_EVENT_MSG_MID +# Name of MID to collect cfe EVS short messages +evs_short_event_mid_name = CFE_EVS_SHORT_EVENT_MSG_MID + ################################# # Example settings for SP01 ################################# @@ -263,9 +266,12 @@ tlm_udp_port = 5011 # What CCSDS version ccsds_ver = 2 -# Name of MID to collect cfe EVS messages +# Name of MID to collect cfe EVS long messages evs_event_mid_name = CFE_EVS_LONG_EVENT_MSG_MID +# Name of MID to collect cfe EVS short messages +evs_short_event_mid_name = CFE_EVS_SHORT_EVENT_MSG_MID + ################################# # Example setting for SP02 ################################# @@ -353,9 +359,12 @@ tlm_udp_port = 5041 # What CCSDS version ccsds_ver = 2 -# Name of MID to collect cfe EVS messages +# Name of MID to collect cfe EVS long messages evs_event_mid_name = CFE_EVS_LONG_EVENT_MSG_MID +# Name of MID to collect cfe EVS short messages +evs_short_event_mid_name = CFE_EVS_SHORT_EVENT_MSG_MID + ################################# # ################################# diff --git a/configs/template_config.ini b/configs/template_config.ini index a6ba4cb..484e8dc 100644 --- a/configs/template_config.ini +++ b/configs/template_config.ini @@ -170,9 +170,12 @@ ccsds_ver = 2 # as new events packets evs_messages_clear_after_time = 5 -# Name of MID to collect cfe EVS messages +# Name of MID to collect cfe EVS long messages evs_event_mid_name = CFE_EVS_LONG_EVENT_MSG_MID +# Name of MID to collect cfe EVS short messages +evs_short_event_mid_name = CFE_EVS_SHORT_EVENT_MSG_MID + [execution] # Command timeout for the execution plugin diff --git a/ctf b/ctf index 703a7f8..b457d2b 100755 --- a/ctf +++ b/ctf @@ -14,6 +14,7 @@ # License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, # either expressed or implied. +CTF_VERSION = "v1.1" import traceback @@ -44,6 +45,7 @@ def main(): status_manager = StatusManager(port=args.port) # Load plugins and determine list of commands they support. + log.info("cFS Test Framework ({}) Starting...".format(CTF_VERSION)) log.info("Loading Plugins...") plugin_paths = ['plugins'] additional_plugin_path = expand_path(Config.get("core", "additional_plugins_path", fallback="")) diff --git a/external/sample_cfs_workspace.tgz b/external/sample_cfs_workspace.tgz index ad4163f..b3c6993 100644 Binary files a/external/sample_cfs_workspace.tgz and b/external/sample_cfs_workspace.tgz differ diff --git a/lib/args_validation.py b/lib/args_validation.py index 89c98be..76964d1 100644 --- a/lib/args_validation.py +++ b/lib/args_validation.py @@ -69,7 +69,7 @@ def verify_symbol(self, file, symbol): def validate_symbol(self, symbol, file_path): new_symbol = None - file_path = self.validate_directory(file_path) + file_path = self.validate_file(file_path) if file_path is None: return new_symbol if self.verify_symbol(file_path, symbol): diff --git a/lib/event_types.py b/lib/event_types.py index e12db3c..9a3d6e6 100644 --- a/lib/event_types.py +++ b/lib/event_types.py @@ -19,12 +19,14 @@ def __init__(self, time, commands, test): self.commands = commands self.test = test + class Command: - def __init__(self, delay, command, test, command_index): + def __init__(self, delay, command, test, command_index, disabled): self.delay = delay self.command = command self.test = test self.command_index = command_index + self.is_disabled = disabled class Verify: diff --git a/lib/plugin_manager.py b/lib/plugin_manager.py index 4971de4..d946317 100644 --- a/lib/plugin_manager.py +++ b/lib/plugin_manager.py @@ -131,7 +131,7 @@ def process_command(self, **kwargs): ret = func(**data) except Exception as e: log.error("Error Applying Function {}".format(func)) - traceback.format_exc(e) + log.debug(traceback.format_exc()) elif (len(data) == 0 and (req_args + optional_args) == 0): try: diff --git a/lib/readers/JSONScriptReader.py b/lib/readers/JSONScriptReader.py index 08db3d7..0b48541 100644 --- a/lib/readers/JSONScriptReader.py +++ b/lib/readers/JSONScriptReader.py @@ -202,6 +202,8 @@ def process_tests(self): function_call_delay = command["wait"] else: function_call_delay = 1.0 + + disabled = bool(command.get('disabled', False)) for c_index, c in enumerate(inline_commands): # Set the default delay value of 0 # then obtain wait value from test script @@ -210,12 +212,14 @@ def process_tests(self): delay = c["wait"] if c_index == 0: delay += function_call_delay - event_list.append(Command(delay, c, len(test_list), -1)) + disabled |= bool(c.get('disabled', False)) + event_list.append(Command(delay, c, len(test_list), -1, disabled)) else: delay = 0 if "wait" in command.keys(): delay = command["wait"] - event_list.append(Command(delay, command, len(test_list), -1)) + disabled = bool(command.get('disabled', False)) + event_list.append(Command(delay, command, len(test_list), -1, disabled)) for i in range(len(event_list)): event_list[i].command_index = i diff --git a/lib/status.py b/lib/status.py index 9c5810d..341e959 100644 --- a/lib/status.py +++ b/lib/status.py @@ -21,6 +21,8 @@ class StatusDefs: failed = 'failed' error = 'error' timeout = 'timeout' + aborted = 'aborted' + disabled = 'disabled' InstructionStatus = { "instruction": "", @@ -28,7 +30,8 @@ class StatusDefs: "data": {}, "status": StatusDefs.waiting, "details": "", - "comment": "" + "comment": "", + "description": "" } TestStatus = { @@ -36,7 +39,8 @@ class StatusDefs: "status": StatusDefs.waiting, "details": "", "instructions": [], - "comment": "" + "comment": "", + "description": "" } ScriptStatus = { diff --git a/lib/status_manager.py b/lib/status_manager.py index 4cc8fa8..42858f1 100644 --- a/lib/status_manager.py +++ b/lib/status_manager.py @@ -53,7 +53,7 @@ def blank_status_msg(self, scripts): test_status["case_number"] = test.test_info["test_case"] test_status["status"] = StatusDefs.waiting test_status["details"] = "" - test_status["comment"] = test.test_info.get("comment", "") + test_status["description"] = test.test_info.get("description", "") for c in test.event_list: command_status = deepcopy(InstructionStatus) command_status["instruction"] = c.command["instruction"] @@ -66,7 +66,7 @@ def blank_status_msg(self, scripts): command_status["status"] = StatusDefs.waiting command_status["details"] = "" - command_status["comment"] = c.command.get("comment", "") + command_status["description"] = c.command.get("description", "") test_status["instructions"].append(command_status) script_status["tests"].append(test_status) status["scripts"].append(script_status) diff --git a/lib/test.py b/lib/test.py index 9fa3469..6da852a 100644 --- a/lib/test.py +++ b/lib/test.py @@ -28,6 +28,10 @@ def __init__(self): self.test_info = None self.event_list = None self.test_result = True + self.test_aborted = False + self.test_run = False + self.num_skipped = 0 + self.num_ran = 0 # Scheduler for test events self.event_schedule = sched.scheduler(time.time, time.sleep) @@ -164,10 +168,23 @@ def run_commands(self): instruction_index = i.command_index instruction = i.command["instruction"] + if i.is_disabled: + status = StatusDefs.disabled + details = "Instruction is disabled. Skipping..." + self.status_manager.update_command_status(status, details, index=instruction_index) + self.status_manager.end_command() + log.info("Skipping disabled test instruction {} ".format(instruction)) + self.num_skipped += 1 + continue + if instruction in self.ignored_instructions: log.info("Ignoring test instruction {} ".format(instruction)) + self.num_skipped += 1 continue + self.test_run = True + self.num_ran += 1 + # process the command delay log.info("Waiting {} time-units before executing {}".format(delay, instruction)) try: @@ -201,15 +218,14 @@ def run_commands(self): self.test_result = False log.test(False, False, "CtfConditionError: Condition not satisfied: {}".format(e)) - if instruction in self.end_test_on_fail_commands and not self.test_result: + if (instruction in self.end_test_on_fail_commands or self.end_test_on_fail) and not self.test_result: # instruction is already executed in execute_event above # the test result is updated in self.test_result - log.error("{} Failed.".format(self.test_info["test_case"])) - break - - # If configured to exit test of fail - if not self.test_result and self.end_test_on_fail: - log.error("{} Failed.".format(self.test_info["test_case"])) + log.error("Instruction: {} Failed. Aborting test...".format(instruction)) + if self.end_test_on_fail: + log.warning("Configuration field \"end_test_on_fail\" enabled. Ending testing.") + log.error("Test Case: {} Failed.".format(self.test_info["test_case"])) + self.test_aborted = True break def run_test(self, status_manager): @@ -229,6 +245,8 @@ def run_test(self, status_manager): else: test_status = StatusDefs.failed self.status_manager.update_test_status(test_status, "") + if self.test_aborted: + test_status = StatusDefs.aborted except Exception as e: self.test_result = False @@ -236,5 +254,9 @@ def run_test(self, status_manager): self.status_manager.update_test_status(test_status, str(e)) raise + log.info("Test %s: %s" % (self.test_info["test_case"], test_status)) + log.info("Number instructions To Run: %s" % len(self.event_list)) + log.info("Number instructions Ran: %s" % self.num_ran) + log.info("Number instructions Skipped: %s" % self.num_skipped) self.status_manager.end_test() return test_status diff --git a/lib/test_script.py b/lib/test_script.py index 78acd4e..538b5f9 100644 --- a/lib/test_script.py +++ b/lib/test_script.py @@ -76,7 +76,11 @@ def run_script(self, status_manager): for test in self.tests: if self.verify_timeout: test.ctf_verification_timeout = self.verify_timeout - test.run_test(status_manager) + test_status = test.run_test(status_manager) + if test_status == StatusDefs.aborted: + log.error("Aborted Test Script: {}".format(self.input_file)) + break + self.time_taken = time.time() - self.start_time self.generateTestResults() @@ -103,13 +107,15 @@ def generateTestResults(self): self.num_passed = 0 self.num_failed = 0 for test in self.tests: - if test.test_result: - self.num_passed += 1 - else: - self.num_failed += 1 + if test.test_run: + if test.test_result: + self.num_passed += 1 + else: + self.num_failed += 1 self.logTestProlog() - log.info("Number tests Run: %s" % self.num_tests) + log.info("Number tests To Run: %s" % self.num_tests) + log.info("Number tests Ran: %s" % (self.num_passed+self.num_failed)) log.info("Number tests Passed: %s" % self.num_passed) log.info("Number tests Failed: %s" % self.num_failed) # if self.SkippedCommands > 0: diff --git a/plugins/ccsds_plugin/readers/ccdd_export_reader.py b/plugins/ccsds_plugin/readers/ccdd_export_reader.py index 6601f99..b9d907f 100644 --- a/plugins/ccsds_plugin/readers/ccdd_export_reader.py +++ b/plugins/ccsds_plugin/readers/ccdd_export_reader.py @@ -197,7 +197,9 @@ def _create_parameterized_type(self, type_dict, type_id=None, arg_id=None, subty if type_name is None: if self.config.log_ccsds_imports: log.debug("{} has no fields.".format(data_type_name)) - return create_type_class(data_type_name, self.ctype_structure, []), type_enums + data_type = create_type_class(data_type_name, self.ctype_structure, []) + self.type_dict[data_type_name] = data_type + return data_type, type_enums else: data_type = self._build_data_type_and_field(type_dict, fields) if data_type is None: @@ -205,6 +207,7 @@ def _create_parameterized_type(self, type_dict, type_id=None, arg_id=None, subty return data_type = create_type_class(data_type_name, self.ctype_structure, fields) + self.type_dict[data_type_name] = data_type return data_type, type_enums def process_telemetry(self, json_dict): @@ -240,27 +243,58 @@ def process_telemetry(self, json_dict): def process_types(self, json_list): for typedef in json_list: - if 'alias_name' in typedef: - cfs_type_name = typedef['alias_name'] - c_type_name = typedef['actual_name'] - if c_type_name in ctypes.__dict__: - c_type = ctypes.__dict__[c_type_name] - self.type_dict[cfs_type_name] = c_type + try: + if 'alias_name' in typedef and 'actual_name' in typedef: + # Aliases are type names that evaluate to ctype or custom types. Custom types must be mapped last. + alias_name = typedef['alias_name'] + actual_name = typedef['actual_name'] + if alias_name not in self.type_dict: + if actual_name in ctypes.__dict__: + c_type = ctypes.__dict__[actual_name] + self.type_dict[alias_name] = c_type + elif self.config.log_ccsds_imports: + log.debug("Alias {} is not a ctype, skipping...".format(alias_name)) + elif self.config.log_ccsds_imports: + log.debug("Alias {} already defined, skipping...".format(alias_name)) + elif 'constant_name' in typedef and 'constant_value' in typedef: + # Constants are CFS macros that evaluate to literal values + constant_name = typedef['constant_name'] + constant_value = typedef['constant_value'] + if constant_name not in self.type_dict: + self.type_dict[constant_name] = constant_value + elif self.config.log_ccsds_imports: + log.debug("Alias {} already defined, skipping...".format(constant_name)) + elif 'target' in typedef and 'mids' in typedef: + # Targets are CFS target names that map MID names to values + if typedef['target'] == self.config.CCSDS_target: + if self.config.log_ccsds_imports: + log.info("Found {} MIDs for {}".format(len(typedef['mids']), self.config.CCSDS_target)) + self.mids.update({mid['mid_name']: int(mid['mid_value'], 0) for mid in typedef['mids']}) else: - log.error("Unknown ctype name {} in {}".format(c_type_name, self.current_file_name)) - elif 'constant_name' in typedef: - cfs_macro_type = typedef['constant_name'] - c_macro = typedef['constant_value'] - self.type_dict[cfs_macro_type] = c_macro - elif 'target' in typedef: - if typedef['target'] == self.config.CCSDS_target: - if self.config.log_ccsds_imports: - log.info("Found {} MIDs for {}".format(len(typedef['mids']), self.config.CCSDS_target)) - self.mids.update({mid['mid_name']: int(mid['mid_value'], 0) for mid in typedef['mids']}) - else: - log.error("Invalid type definition in {}".format(self.current_file_name)) + log.error("Invalid type definition in {}".format(self.current_file_name)) + except Exception as e: + log.error("Unable to parse type definition in {}: {}".format(self.current_file_name, e)) - def process_ccsds_json_file(self, filename, file_filter=None): + def process_types_second_pass(self, json_list): + for typedef in json_list: + try: + if 'alias_name' in typedef and 'actual_name' in typedef: + # Any aliases left undefined should now evaluate to a custom type + alias_name = typedef['alias_name'] + actual_name = typedef['actual_name'] + if alias_name not in self.type_dict: + if actual_name in self.type_dict: + self.type_dict[alias_name] = self.type_dict[actual_name] + if self.config.log_ccsds_imports: + log.debug("Mapped alias {} to type {}".format(actual_name, alias_name)) + else: + log.error("Unknown alias name {} in {}".format(actual_name, self.current_file_name)) + elif self.config.log_ccsds_imports: + log.debug("Alias {} already defined, skipping...".format(alias_name)) + except Exception as e: + log.error("Unable to parse type definition in {}: {}".format(self.current_file_name, e)) + + def process_ccsds_json_file(self, filename, file_filter=None, second_pass=False): with open(filename) as file: try: json_dict = json.load(file) @@ -274,6 +308,8 @@ def process_ccsds_json_file(self, filename, file_filter=None): self.process_command(json_dict) elif self.is_telemetry_msg(json_dict): self.process_telemetry(json_dict) + elif second_pass: + self.process_types_second_pass(json_dict) else: self.process_types(json_dict) @@ -285,13 +321,16 @@ def get_ccsds_messages_from_dir(self, directory, common_command_code_file=None): if fnmatch.fnmatch(basename, "*.json"): filename = os.path.join(root, basename) files.append(filename) - # Process only types & macros first, then others + # Process only types & macros first, then custom types, then macros again for custom type aliases for file in files: self.current_file_name = os.path.basename(file) self.process_ccsds_json_file(file, self.is_types_macros) for file in files: self.current_file_name = os.path.basename(file) self.process_ccsds_json_file(file, self.is_command_tlm) + for file in files: + self.current_file_name = os.path.basename(file) + self.process_ccsds_json_file(file, self.is_types_macros, True) # Collect and convert integer values from enum_map and type_dict macro_map = dict(filter(lambda item: isinstance(item[1], int), self.type_dict.items())) diff --git a/plugins/cfs/README.md b/plugins/cfs/README.md index 34a0282..0f94086 100644 --- a/plugins/cfs/README.md +++ b/plugins/cfs/README.md @@ -7,8 +7,8 @@ The CFS plugin draws many default values from the CTF config file. The section ` If multiple CFS targets are to be registered, for each target name, the plugin will load values from a correspondingly named section. -If no targets are explicitly registered by name by the time `StartCfs` is first executed, the plugin will automatically configure targets for each config section beginning with `cfs_`. If no such sections are found, the plugin will configure a single *local* -target using the `[cfs]` config section. +If no targets are explicitly registered by name by the time `StartCfs` is first executed, the plugin will automatically configure targets for each config section beginning with `cfs_`. If no such sections are found, the plugin will configure a single +target using the `[cfs]` config section. Note that if the `cfs_protocol` field is not found in the `cfs` section, a local target will be registered. The precedence of values is first the named config section, if any, and then the `[cfs]` config section. A target cannot be registered, explicitly nor automatically, without a correspondingly named config section. @@ -56,7 +56,7 @@ CTF supports resolving macros from the ccsds_data_dir and replacing macros in th ###### Example ``` { - "command": "CheckTlmValue", + "instruction": "CheckTlmValue", "data": { "mid": "CFE_EVS_HK_TLM_MID", "args": [ @@ -83,7 +83,7 @@ override any value specified in the `[cfs]` section. Example: ```javascript { - "command": "RegisterCfs", + "instruction": "RegisterCfs", "data": { "target": "cfs_workstation" } @@ -104,7 +104,7 @@ Builds a CFS target. Example: ```javascript { - "command": "BuildCfs", + "instruction": "BuildCfs", "data": { "target": "cfs_workstation" } @@ -121,7 +121,7 @@ Starts a CFS target. Example: ```javascript { - "command": "StartCfs", + "instruction": "StartCfs", "data": { "target": "cfs_workstation", "run_args": "-R PO" @@ -137,7 +137,7 @@ Enables CFS output. No parameters. Example: ```javascript { - "command": "EnableCfsOutput", + "instruction": "EnableCfsOutput", "data": { "target": "cfs_workstation" } @@ -158,7 +158,7 @@ Example: Example: ```javascript { - "command":"SendCfsCommand", + "instruction":"SendCfsCommand", "data":{ "target": "cfs_workstation", "mid":"TO_CMD_MID", @@ -184,7 +184,7 @@ Checks that an event message matching the given parameters has been received fro Example: ```javascript { - "command":"CheckEvent", + "instruction":"CheckEvent", "data":{ "target": "cfs_workstation", "app":"BEX", @@ -196,7 +196,30 @@ Example: "wait": 1 } ``` +### CheckNoEvent +Checks that an event message matching the given parameters is no longer valid in received messages from the CFS target. +- **target:** (Optional) A previously registered target name. If no name is given, applies to all registered targets. +- **app**: The app that sent the event message. +- **id**: The Event ID, taken from an EVS enum, to represent the criticality level of a message. 13 is information, 14 is error, and anything else should be updated into this wiki as you find it. +- **msg**: (Optional) The expected message of the event. If blank, the msg field is not verified. +- **is_regex**: (Optional) True if `msg` is to be used for a regex match instead of string comparison +- **msg_args**: (optional) arguments that will be inserted into `msg`, similar to printf() functions +Example: +```javascript +{ + "instruction": "CheckNoEvent", + "data": { + "app": "TO", + "id": "3", + "msg": "TO - ENABLE_OUTPUT cmd succesful for routeMask:0x00000001", + "msg_args": "", + "target": "cfs_workstation" + }, + "wait": 4, + "description": "ENABLE_OUTPUT cmd message is no longer valid in received messages" +} +``` ### CheckTlmValue Checks that a telemetry message matching the given parameters has been received from the CFS target. - **target:** (Optional) A previously registered target name. If no name is given, applies to all registered targets. @@ -217,7 +240,7 @@ can be listed here to check multiple attributes of a given packet at once. Example: ```javascript { - "command": "CheckTlmValue", + "instruction": "CheckTlmValue", "data": { "target": "cfs_workstation", "mid": "TO_HK_TLM_MID", @@ -265,7 +288,7 @@ can be listed here to check multiple attributes of a given packet at once. Example: ```javascript { - "command": "CheckTlmContinuous", + "instruction": "CheckTlmContinuous", "data": { "target": "cfs_workstation", "verification_id": "TO_no_errors", @@ -291,7 +314,7 @@ Cancels a continuous telemetry check by ID so that it is no longer performed. Example: ```javascript { - "command": "RemoveCheckTlmContinuous", + "instruction": "RemoveCheckTlmContinuous", "data": { "verification_id": "TO_no_errors" } @@ -307,7 +330,7 @@ Copies files from a directory that have been modified during the current test ru Example: ```javascript { - "command": "ArchiveCfsFiles", + "instruction": "ArchiveCfsFiles", "data":{ "target": "cfs_workstation", "source_path": "../../build/exe/lx1/cf/" @@ -325,7 +348,7 @@ Note, the CFS plugin will automatically shutdown all CFS targets on test complet Example: ```javascript { - "command": "ShutdownCfs", + "instruction": "ShutdownCfs", "data": { "target": "cfs_workstation" } diff --git a/plugins/cfs/cfs_config.py b/plugins/cfs/cfs_config.py index e3ebcb3..c0e22a8 100644 --- a/plugins/cfs/cfs_config.py +++ b/plugins/cfs/cfs_config.py @@ -64,7 +64,8 @@ def __init__(self, name): self.cfs_run_in_xterm = None self.tlm_app_choice = None self.ccsds_ver = None - self.evs_event_mid_name = None + self.evs_long_event_mid_name = None + self.evs_short_event_mid_name = None self.evs_messages_clear_after_time = None # Logging fields self.evs_log_file = None @@ -171,7 +172,9 @@ def load_config_data(self, section_name): self.evs_messages_clear_after_time = self.load_field(section_name, "evs_messages_clear_after_time", Config.getint, self.validation.validate_number) - self.evs_event_mid_name = self.load_field(section_name, "evs_event_mid_name", Config.get) + self.evs_long_event_mid_name = self.load_field(section_name, "evs_event_mid_name", Config.get) + + self.evs_short_event_mid_name = self.load_field(section_name, "evs_short_event_mid_name", Config.get) self.endianess_of_target = self.load_field(section_name, "endianess_of_target", Config.get) # endianness value should be lower-case diff --git a/plugins/cfs/cfs_plugin.py b/plugins/cfs/cfs_plugin.py index 97cd108..15b4861 100644 --- a/plugins/cfs/cfs_plugin.py +++ b/plugins/cfs/cfs_plugin.py @@ -128,6 +128,19 @@ def __init__(self): "CheckEvent": (self.check_event, [ArgTypes.string, ArgTypes.string, ArgTypes.string, ArgTypes.boolean, ArgTypes.cmd_arg, ArgTypes.string]), + # CheckNoEvent: Checks that an event message matching the given parameters is not received + # user needs to ensure the previous messages are cleared from buffer before calling CheckNoEvent instruction + # - app: The app that sent the event message + # - id: The Event ID, taken from an EVS enum, to represent the criticality level of a message + # - msg: The expected message of the event + # - is_regex: (Optional) True if msg is to be used for a regex match instead of string comparison + # - msg_args: (Optional) Arguments that will be inserted into msg, similar to printf() functions + # - target: (Optional) A previously registered target name, or empty for all registered targets + "CheckNoEvent": + (self.check_noevent, + [ArgTypes.string, ArgTypes.string, ArgTypes.string, ArgTypes.boolean, ArgTypes.cmd_arg, + ArgTypes.string]), + # ArchiveCfsFiles: Copies files from a directory that have been modified # during the current test run into the test run's log directory # - source_path: A directory path from which to copy files @@ -140,11 +153,13 @@ def __init__(self): (self.shutdown_cfs, [ArgTypes.string]), } - self.verify_required_commands = ["CheckTlmValue", "CheckEvent"] + self.verify_required_commands = ["CheckTlmValue", "CheckEvent", "CheckNoEvent"] # TODO - Utilize the commands below in the time manager, so that continuous instructions can be # implemented here, and utilized by the time manager. self.continuous_commands = ["CheckTlmContinuous"] + self.end_test_on_fail_commands = ["RegisterCfs", "StartCfs"] + def initialize(self) -> bool: """Initializes the plugin by creating the CfsTimeManager. This method is intended to be called by the plugin manager before the test script runs. @@ -176,7 +191,7 @@ def register_cfs(self, target: str) -> bool: return False if target == self.FALLBACK_TARGET_NAME: - cfs_protocol = 'local' + cfs_protocol = Config.get(target, "cfs_protocol", fallback="local") elif target not in Config.sections(): log.error("No CFS configuration defined in config file for {}.".format(target)) return False @@ -286,11 +301,9 @@ def send_cfs_command(self, mid: str, cc: int, args: dict, target: str = None, log.debug("SendCfsCommand - Target: {}, MID: {}, CC: {}, Args: {}, Set Length: {}, CType Args: {}" .format(target, mid, cc, json.dumps(args), payload_length, ctype_args)) - # Make a copy of arguments since send_cfs_command may change arguments structure - args_copy = deepcopy(args) - # Collect the results of send_cfs_command on each specified target, and check that all passed - status = [t.send_cfs_command(mid, cc, args_copy, payload_length, ctype_args) for t in self.get_cfs_targets(target)] + # Make a copy of arguments since send_cfs_command may change arguments structure + status = [t.send_cfs_command(mid, cc, deepcopy(args), payload_length, ctype_args) for t in self.get_cfs_targets(target)] return all(status) if status else False @@ -320,7 +333,7 @@ def remove_check_tlm_continuous(self, verification_id: str, target: str = None) status = [t.remove_check_tlm_continuous(verification_id) for t in self.get_cfs_targets(target)] return all(status) if status else False - def check_event(self, app: str, id: str, msg: str, is_regex: bool = False, + def check_event(self, app: str, id: str, msg: str = None, is_regex: bool = False, msg_args: str = None, target: str = None) -> bool: """Implements the instruction CheckEvent. 'id' shadows the built-in function target but is kept because it exists in legacy test scripts.""" @@ -331,6 +344,29 @@ def check_event(self, app: str, id: str, msg: str, is_regex: bool = False, status = [t.check_event(app, id, msg, is_regex, msg_args) for t in self.get_cfs_targets(target)] return all(status) if status else False + def check_noevent(self, app: str, id: str, msg: str, is_regex: bool = False, + msg_args: str = None, target: str = None) -> bool: + """Implements the instruction CheckNoEvent. + 'id' shadows the built-in function target but is kept because it exists in legacy test scripts.""" + log.info("CheckNoEvent for target - {}, APP {}, ID {}, MSG {}, Msg Args {}" + .format(target, app, id, msg, json.dumps(msg_args))) + + # Collect the results of check_event on each specified target, and check that all passed + status = [t.check_event(app, id, msg, is_regex, msg_args) for t in self.get_cfs_targets(target)] + + if any(status): + log.info("CheckNoEvent found the event with status = {} !".format(status)) + return False + else: + # Check_noevent is to verify No event happens during the CtfVerificationStage. + # This function will be called a few times by test.py, it only returns True at the end of verification stage + if Global.current_verification_stage == CtfVerificationStage.last_ver: + log.info("CheckNoEvent did not find event") + return True + else: + return False + + def shutdown_cfs(self, target: str = None) -> bool: """Implements the instruction ShutdownCfs. This is non-destructive; the target still exists and can be restarted. diff --git a/plugins/cfs/pycfs/cfs_controllers.py b/plugins/cfs/pycfs/cfs_controllers.py index fae7303..367421e 100644 --- a/plugins/cfs/pycfs/cfs_controllers.py +++ b/plugins/cfs/pycfs/cfs_controllers.py @@ -360,7 +360,7 @@ def remove_check_tlm_continuous(self, v_id): log.info("Removing continuous telemetry check {} on {}".format(v_id, self.config.name)) return self.cfs.remove_tlm_condition(v_id) - def check_event(self, app, id, msg, is_regex=False, msg_args=None): + def check_event(self, app, id, msg=None, is_regex=False, msg_args=None): """Checks for an EVS event message in the telemetry packet history, assuming a particular structure for CFE_EVS_LongEventTlm_t. This can be generified in the future to determine the structure from the MID map. @@ -386,13 +386,18 @@ def check_event(self, app, id, msg, is_regex=False, msg_args=None): {"compare": "==", "variable": "Payload.PacketID.EventID", "value": id} ] - if msg: - compare = "regex" if is_regex else "streq" - args.append({"compare": compare, "variable": "Payload.Message", "value": msg}) + result = self.cfs.check_tlm_value(self.cfs.evs_short_event_msg_mid, args, discard_old_packets=False) + if result: + log.info("Received EVS_ShortEventTlm_t. Ignoring 'Message' field...") else: - log.warn("No msg provided; any message for App {} and Event ID {} will be matched.".format(app, id)) + if msg: + compare = "regex" if is_regex else "streq" + args.append({"compare": compare, "variable": "Payload.Message", "value": msg}) + result = self.cfs.check_tlm_value(self.cfs.evs_long_event_msg_mid, args, discard_old_packets=False) + else: + log.warn("No msg provided; any message for App {} and Event ID {} will be matched.".format(app, id)) + result = self.cfs.check_tlm_value(self.cfs.evs_long_event_msg_mid, args, discard_old_packets=False) - result = self.cfs.check_tlm_value(self.cfs.evs_event_msg_mid, args, discard_old_packets=False) return result def archive_cfs_files(self, source_path): @@ -604,19 +609,34 @@ def archive_cfs_files(self, source_path): return self.sp0_plugin.get_files(source_path, artifacts_path, self.config.name) def shutdown_cfs(self): - log.info("Shutting down controller for {}".format(self.config.name)) + log.info("Shutting down CFS on {}".format(self.config.name)) + + # TODO - Pull CFS stdout from SP0. Requires run_application to pipe output of process... + # stdout_final_path = os.path.join(Global.current_script_log_dir, + # os.path.basename(self.cfs.cfs_std_out_path)) + # + # if not os.path.exists(stdout_final_path): + # if not self.sp0_plugin.get_file(self.cfs.cfs_std_out_path, stdout_final_path): + # log.info("Cannot move CFS stdout file to script log directory.") + # if self.sp0_plugin.last_result[self.config.name]: + # log.debug(self.sp0_plugin.last_result[self.config.name].stdout.strip()) + if self.cfs: if self.cfs_running: - self.shutdown_cfs() - # Wait 2 time units for shutdown to complete - Global.time_manager.wait_seconds(2) + log.info("Sending SP0 Reboot Command...") + self.sp0_plugin.send_command("reboot()\n", timeout=2, name=self.config.name) + self.cfs.stop_cfs() - stdout_final_path = os.path.join(Global.current_script_log_dir, - os.path.basename(self.cfs.cfs_std_out_path)) - if not os.path.exists(stdout_final_path): - if not self.sp0_plugin.get_file(self.cfs.cfs_std_out_path, stdout_final_path): - log.info("Cannot move CFS stdout file to script log directory.") - if self.sp0_plugin.last_result[self.config.name]: - log.debug(self.sp0_plugin.last_result[self.config.name].stdout.strip()) + # Wait 2 time units for shutdown to complete + Global.time_manager.wait_seconds(2) + self.cfs = None + + # This function will shut down the CFS application being tested even if the JSON test file does not + # include the shutdown test command + def shutdown(self): + log.info("Shutting down controller for {}".format(self.config.name)) + if self.cfs: + if self.cfs_running: + self.shutdown_cfs() self.cfs = None diff --git a/plugins/cfs/pycfs/cfs_interface.py b/plugins/cfs/pycfs/cfs_interface.py index 16864ce..e7cfdd2 100644 --- a/plugins/cfs/pycfs/cfs_interface.py +++ b/plugins/cfs/pycfs/cfs_interface.py @@ -43,11 +43,19 @@ class CfsInterface(object): def __init__(self, config, telemetry, command, mid_map, ccsds): self.config = config - if self.config.evs_event_mid_name in mid_map: - self.evs_event_msg_mid = mid_map[self.config.evs_event_mid_name]["MID"] + if self.config.evs_long_event_mid_name in mid_map: + self.evs_long_event_msg_mid = mid_map[self.config.evs_long_event_mid_name]["MID"] else: - self.evs_event_msg_mid = -1 - log.error("{} not found in MID map! EVS event messages will not be captured.") + self.evs_long_event_msg_mid = -1 + log.error("{} not found in MID map! EVS long event messages will not be captured." + .format(self.config.evs_long_event_mid_name)) + + if self.config.evs_short_event_mid_name in mid_map: + self.evs_short_event_msg_mid = mid_map[self.config.evs_short_event_mid_name]["MID"] + else: + self.evs_short_event_msg_mid = -1 + log.error("{} not found in MID map! EVS short event messages will not be captured." + .format(self.config.evs_short_event_mid_name)) self.init_passed = False @@ -138,9 +146,9 @@ def write_evs_log(self, payload): self.evs_log_file.write("%s/%s/%s %s: %s\n" % (payload.PacketID.SpacecraftID, payload.PacketID.ProcessorID, - payload.PacketID.AppName, + payload.PacketID.AppName.decode(), payload.PacketID.EventID, - payload.Message)) + payload.Message.decode() if hasattr(payload, "Message") else "")) except UnicodeDecodeError: log.error("Failed to write event packet to EVS Log file for Event Payload: {}".format(str(payload))) traceback.format_exc() @@ -240,7 +248,7 @@ def read_sb_packets(self): Global.get_time_manager().exec_time)) self.tlm_has_been_received = True self.unchecked_packet_mids.append(mid) - if mid == self.evs_event_msg_mid: + if mid in [self.evs_long_event_msg_mid, self.evs_short_event_msg_mid]: # Write this packet to the CFS EVS Log File self.write_evs_log(payload) except socket.timeout: @@ -318,9 +326,6 @@ def check_strings(actual, expected, equal): return False def check_value(self, actual, expected, compare, mask, mask_value): - if isinstance(actual, bytes): - actual = actual.decode() - if compare == "streq": return self.check_strings(actual, expected, True) if compare == "strneq": @@ -381,10 +386,10 @@ def clear_received_msgs_before_verification_start(self, mid): if mid not in self.received_mid_packets_dic: log.error("No messages received for MID: {} to clear.".format(hex(mid))) return - time_offset = 0 - if mid == self.evs_event_msg_mid: - time_offset += self.config.evs_messages_clear_after_time - start_time = Global.current_verification_start_time - time_offset + if mid in [self.evs_long_event_msg_mid, self.evs_short_event_msg_mid]: + start_time = Global.time_manager.exec_time - self.config.evs_messages_clear_after_time + else: + start_time = Global.current_verification_start_time log.debug("Clearing received packets for MID: {} before time = {}" .format(hex(mid), start_time)) self.received_mid_packets_dic[mid] = [ @@ -485,7 +490,11 @@ def check_tlm_packet(self, payload, args): except (SyntaxError, AttributeError) as e: log.error("Failed to Evaluate: {}".format(eval_string)) log.debug(traceback.format_exc()) - continue + packet_passed = False + break + + if isinstance(actual, bytes): + actual = actual.decode() initial_result = self.check_value(actual, expected_value, arg["compare"], mask, mask_value) @@ -512,3 +521,17 @@ def check_tlm_packet(self, payload, args): packet_passed = packet_passed and arg_result return packet_passed + + # Send a command to enable output and check if we receive a response + def enable_output(self): + count = 0 + while True: + count += 1 + self.output_manager.enable_output() + Global.time_manager.wait(1) + + if self.tlm_has_been_received: + return True + if count > 60: + log.error("Unable to connect to CFS mission") + return False diff --git a/plugins/cfs/pycfs/local_cfs_interface.py b/plugins/cfs/pycfs/local_cfs_interface.py index c5f8a47..9f2c6a5 100644 --- a/plugins/cfs/pycfs/local_cfs_interface.py +++ b/plugins/cfs/pycfs/local_cfs_interface.py @@ -8,6 +8,7 @@ from io import StringIO from subprocess import Popen, PIPE, STDOUT from distutils.spawn import find_executable +import time # module dependencies from plugins.cfs.pycfs.cfs_interface import CfsInterface @@ -46,12 +47,13 @@ def get_start_string(self, run_args): # TODO - Test use of debug when running embedded if not self.config.cfs_run_in_xterm or find_executable('xterm') is None: - start_string = "./{} | tee -a {}".format(target, self.cfs_std_out_path) + start_string = "./{} >> {} 2>&1".format(target, self.cfs_std_out_path) if self.config.cfs_run_in_xterm: log.error("Dependency 'xterm' not found. Attempting to run in an embedded terminal window instead.") else: - start_string = "xterm -l -hold -geometry 130X24+800+0 -e \"%s ./%s |& tee -a %s\"; wait &" % \ + start_string = "xterm -l -geometry 130X24+800+0 -e \"script -c \'%s ./%s; wait &\' -q -f -e %s\"" % \ (debug, target, self.cfs_std_out_path) + log.info("Starting CFS with command: {}".format(start_string)) return start_string def build_cfs(self): @@ -102,7 +104,7 @@ def start_cfs(self, run_args): # Report an error if given a bad directory if not os.path.exists(self.config.cfs_run_dir): - log.error("Couldn't find CFS run directory") + log.error("Couldn't find CFS run directory {}".format(self.config.cfs_run_dir)) return_values["result"] = False return return_values @@ -123,6 +125,8 @@ def start_cfs(self, run_args): return_values["result"] = False return return_values + time.sleep(2) + # Check the status of the CFS application if cfs_process.poll() is not None: log.error("Error Launching CFS Process.\nAttempted to launch: {}, returned: {}" @@ -134,16 +138,4 @@ def start_cfs(self, run_args): return return_values - # Send a command to enable output and check if we receive a response - def enable_output(self): - count = 0 - while True: - count += 1 - self.output_manager.enable_output() - Global.time_manager.wait(1) - - if self.tlm_has_been_received: - return True - if count > 60: - log.error("Unable to connect to CFS mission") - return False + diff --git a/plugins/info/cfs_plugin.json b/plugins/info/cfs_plugin.json index 6cd6ed0..f9dc90e 100644 --- a/plugins/info/cfs_plugin.json +++ b/plugins/info/cfs_plugin.json @@ -210,6 +210,43 @@ } ] }, + { + "name": "CheckNoEvent", + "description": "", + "parameters": [ + { + "name": "app", + "description": "", + "type": "string" + }, + { + "name": "id", + "description": "", + "type": "string" + }, + { + "name": "msg", + "description": "", + "type": "string" + }, + { + "name": "is_regex", + "description": "", + "type": "boolean" + }, + { + "name": "msg_args", + "description": "", + "type": "cmd_arg", + "isArray": true + }, + { + "name": "target", + "description": "", + "type": "string" + } + ] + }, { "name": "ArchiveCfsFiles", "description": "", diff --git a/scripts/cfe_6_7_tests/cfe_tests/CfeEsTest.json b/scripts/cfe_6_7_tests/cfe_tests/CfeEsTest.json index e2c07b9..ef8098f 100644 --- a/scripts/cfe_6_7_tests/cfe_tests/CfeEsTest.json +++ b/scripts/cfe_6_7_tests/cfe_tests/CfeEsTest.json @@ -111,7 +111,7 @@ { "function": "SendCheckCfeEsRestartCmd", "wait": 1, - "comment": "Testing Off Nominal Case, CFS Outputs Invalid Restart Type", + "description": "Testing Off Nominal Case, CFS Outputs Invalid Restart Type", "params": { "expectedCmdCnt": 0, "expectedErrCnt": 1, @@ -140,7 +140,7 @@ { "function": "SendCheckCfeEsStopPerfDataAppCmd", "wait": 1, - "comment": "Off-nominal Case due to output not being written", + "description": "Off-nominal Case due to output not being written", "params": { "expectedCmdCnt": 1, "expectedErrCnt": 3, @@ -150,7 +150,7 @@ { "function": "SendCheckCfeEsStartAppCmd", "wait": 1, - "comment": "Off-nominal Case due to mismatch between fileName size on CFS vs JSON", + "description": "Off-nominal Case due to mismatch between fileName size on CFS vs JSON", "params": { "expectedCmdCnt": 1, "expectedErrCnt": 4, diff --git a/scripts/cfe_6_7_tests/cfe_tests/CfeEvsTest.json b/scripts/cfe_6_7_tests/cfe_tests/CfeEvsTest.json index 4be64a8..4c116b4 100644 --- a/scripts/cfe_6_7_tests/cfe_tests/CfeEvsTest.json +++ b/scripts/cfe_6_7_tests/cfe_tests/CfeEvsTest.json @@ -97,7 +97,7 @@ { "function": "SendCheckCfeEvsResetCmd", "wait": 1, - "comment": "Reset will set the counters to 0.", + "description": "Reset will set the counters to 0.", "params": { "expectedCmdCnt": 0, "expectedErrCnt": 0 @@ -116,7 +116,7 @@ { "function": "SendCheckCfeEvsSetLogModeCmd", "wait": 1, - "comment": "Off-Nominal Case - 'Set Log Mode Command Error: Log Mode = 2'", + "description": "Off-Nominal Case - 'Set Log Mode Command Error: Log Mode = 2'", "params": { "expectedCmdCnt": 1, "expectedErrCnt": 1, @@ -126,7 +126,7 @@ { "function": "SendCheckCfeEvsWriteLogDataFileCmd", "wait": 1, - "comment": "Off-Nominal Case - 'Write Log File Command Error: OS_creat = 0xFFFFFF98, filename = CfeEvsWriteLogDataFileCmdOutput.txt'", + "description": "Off-Nominal Case - 'Write Log File Command Error: OS_creat = 0xFFFFFF98, filename = CfeEvsWriteLogDataFileCmdOutput.txt'", "params": { "expectedCmdCnt": 1, "expectedErrCnt": 2, diff --git a/scripts/cfe_6_7_tests/cfe_tests/CfeSbTest.json b/scripts/cfe_6_7_tests/cfe_tests/CfeSbTest.json index 4988037..c34e187 100644 --- a/scripts/cfe_6_7_tests/cfe_tests/CfeSbTest.json +++ b/scripts/cfe_6_7_tests/cfe_tests/CfeSbTest.json @@ -77,7 +77,7 @@ { "function": "SendCheckCfeSbResetCounterCmd", "wait": 1, - "comment": "Reset will set the counters to 0.", + "description": "Reset will set the counters to 0.", "params": { "expectedCmdCnt": 0, "expectedErrCnt": 0 @@ -86,7 +86,7 @@ { "function": "SendCheckCfeSbSendRoutingInfoCmd", "wait": 1, - "comment": "Off-Nominal Case - 'Error creating file SendCheckCfeSbSendRoutingInfoCmdParam.txt, stat=0xffffff98'", + "description": "Off-Nominal Case - 'Error creating file SendCheckCfeSbSendRoutingInfoCmdParam.txt, stat=0xffffff98'", "params": { "expectedCmdCnt": 0, "expectedErrCnt": 1, @@ -96,7 +96,7 @@ { "function": "SendCheckCfeSbSendMapInfoCmd", "wait": 1, - "comment": "Off-Nominal Case - 'Error creating file SendCheckCfeSbSendMapInfoCmdParam.txt, stat=0xffffff98'", + "description": "Off-Nominal Case - 'Error creating file SendCheckCfeSbSendMapInfoCmdParam.txt, stat=0xffffff98'", "params": { "expectedCmdCnt": 0, "expectedErrCnt": 2, @@ -106,7 +106,7 @@ { "function": "SendCheckCfeSbEnableRouteCmd", "wait": 1, - "comment": "Off-Nominal Case - 'Enbl Route Cmd:Invalid Param.Msg 0x2006,Pipe 112'", + "description": "Off-Nominal Case - 'Enbl Route Cmd:Invalid Param.Msg 0x2006,Pipe 112'", "params": { "expectedCmdCnt": 0, "expectedErrCnt": 3, diff --git a/scripts/cfe_6_7_tests/cfe_tests/CfeTblTest.json b/scripts/cfe_6_7_tests/cfe_tests/CfeTblTest.json index cc050b7..13f89d6 100644 --- a/scripts/cfe_6_7_tests/cfe_tests/CfeTblTest.json +++ b/scripts/cfe_6_7_tests/cfe_tests/CfeTblTest.json @@ -82,7 +82,7 @@ { "function": "SendCheckCfsTblLoadCmd", "wait": 1, - "comment": "Off-nominal check due to invalid table filename", + "description": "Off-nominal check due to invalid table filename", "params": { "expectedCmdCnt": 0, "expectedErrCnt": 1, @@ -92,7 +92,7 @@ { "function": "SendCheckCfsTblDumpCmd", "wait": 1, - "comment": "Off-nominal check due to invalid output filename", + "description": "Off-nominal check due to invalid output filename", "params": { "expectedCmdCnt": 0, "expectedErrCnt": 2, diff --git a/scripts/cfe_6_7_tests/cfe_tests/CfeTimeTest.json b/scripts/cfe_6_7_tests/cfe_tests/CfeTimeTest.json index 4c49053..63fc953 100644 --- a/scripts/cfe_6_7_tests/cfe_tests/CfeTimeTest.json +++ b/scripts/cfe_6_7_tests/cfe_tests/CfeTimeTest.json @@ -92,7 +92,7 @@ { "function": "SendCheckCfsTimeSubDelayCmd", "wait": 1, - "comment": "Off-nominal Case. Command gets accepted, but data is invalid", + "description": "Off-nominal Case. Command gets accepted, but data is invalid", "params": { "expectedCmdCnt": 0, "expectedErrCnt": 1, diff --git a/scripts/example_failing_tests/test_continuous_verification_failing.json b/scripts/example_failing_tests/test_continuous_verification_failing.json index 09b3f81..97d03c6 100644 --- a/scripts/example_failing_tests/test_continuous_verification_failing.json +++ b/scripts/example_failing_tests/test_continuous_verification_failing.json @@ -154,7 +154,7 @@ { "function": "SendCheckCfeEsRestartCmd", "wait": 1, - "comment": "Testing Off Nominal Case, CFS Outputs Invalid Restart Type", + "description": "Testing Off Nominal Case, CFS Outputs Invalid Restart Type", "params": { "expectedCmdCnt": 0, "expectedErrCnt": 1, @@ -183,7 +183,7 @@ { "function": "SendCheckCfeEsStopPerfDataAppCmd", "wait": 1, - "comment": "Off-nominal Case due to output not being written", + "description": "Off-nominal Case due to output not being written", "params": { "expectedCmdCnt": 1, "expectedErrCnt": 3, @@ -193,7 +193,7 @@ { "function": "SendCheckCfeEsStartAppCmd", "wait": 1, - "comment": "Off-nominal Case due to mismatch between fileName size on CFS vs JSON", + "description": "Off-nominal Case due to mismatch between fileName size on CFS vs JSON", "params": { "expectedCmdCnt": 1, "expectedErrCnt": 4, diff --git a/scripts/example_tests/test_continuous_verification_passing.json b/scripts/example_tests/test_continuous_verification_passing.json index 3d6ec2d..bc5a2ab 100644 --- a/scripts/example_tests/test_continuous_verification_passing.json +++ b/scripts/example_tests/test_continuous_verification_passing.json @@ -36,7 +36,7 @@ }, { "instruction": "CheckTlmContinuous", - "comment": "Change 'name' to 'cfs_name' for all instructions", + "description": "Change 'name' to 'cfs_name' for all instructions", "data": { "target": "", "verification_id": "no_error", @@ -60,7 +60,7 @@ }, "wait": 1, - "comment": "Need this to enable the telemetry thread. Enable Output counts as 1 TO cmd" + "description": "Need this to enable the telemetry thread. Enable Output counts as 1 TO cmd" }, { "instruction": "CheckEvent", @@ -73,7 +73,7 @@ }, "wait": 1, - "comment": "Need this to enable the telemetry thread. Enable Output counts as 1 TO cmd" + "description": "Need this to enable the telemetry thread. Enable Output counts as 1 TO cmd" }, { "instruction": "SendCfsCommand", diff --git a/scripts/example_tests/test_ctf_advanced_example.json b/scripts/example_tests/test_ctf_advanced_example.json index 15e8b3a..f4b5ec5 100644 --- a/scripts/example_tests/test_ctf_advanced_example.json +++ b/scripts/example_tests/test_ctf_advanced_example.json @@ -115,10 +115,10 @@ }, "wait": 0, - "comment": "Need this to enable the telemetry thread. Enable Output counts as 1 TO cmd" + "description": "Need this to enable the telemetry thread. Enable Output counts as 1 TO cmd" }, { - "comment": "This will format the message with msg_args and then check for a regex match", + "description": "This will format the message with msg_args and then check for a regex match", "instruction": "CheckEvent", "data": { "app": "TO", @@ -132,7 +132,7 @@ "wait": 0 }, { - "comment": "This will format the message with msg_args and then check for a regex match", + "description": "This will format the message with msg_args and then check for a regex match", "instruction": "CheckEvent", "data": { "app": "TO", @@ -146,7 +146,7 @@ "wait": 0 }, { - "comment": "This will format the message with msg_args and then check for a regex match", + "description": "This will format the message with msg_args and then check for a regex match", "instruction": "CheckEvent", "data": { "app": "TO", diff --git a/scripts/example_tests/test_ctf_basic_example.json b/scripts/example_tests/test_ctf_basic_example.json index 6bfb409..ddc1cbc 100644 --- a/scripts/example_tests/test_ctf_basic_example.json +++ b/scripts/example_tests/test_ctf_basic_example.json @@ -42,7 +42,7 @@ }, "wait": 1, - "comment": "Need this to enable the telemetry thread. Enable Output counts as 1 TO cmd" + "description": "Need this to enable the telemetry thread. Enable Output counts as 1 TO cmd" }, { "instruction": "CheckEvent", @@ -55,8 +55,20 @@ }, "wait": 1, - "comment": "Need this to enable the telemetry thread. Enable Output counts as 1 TO cmd" - }, + "description": "Need this to enable the telemetry thread. Enable Output counts as 1 TO cmd" + }, + { + "instruction": "CheckNoEvent", + "data": { + "app": "TO", + "id": "3", + "msg": "TO - ENABLE_OUTPUT cmd succesful for routeMask:0x00000001", + "msg_args": "", + "target": "" + }, + "wait": 4, + "description": "ENABLE_OUTPUT cmd message is no longer valid in received messages" + }, { "instruction": "SendCfsCommand", "data": { @@ -69,7 +81,7 @@ }, { "instruction": "CheckTlmContinuous", - "comment": "Change 'name' to 'cfs_name' for all instructions", + "description": "Change 'name' to 'cfs_name' for all instructions", "data": { "target": "", "verification_id": "no_error", @@ -86,6 +98,8 @@ }, "wait": 1 }, + + { "instruction": "CheckTlmValue", "data": { @@ -130,6 +144,7 @@ }, "wait": 1 }, + { "instruction": "RemoveCheckTlmContinuous", "data": { @@ -141,4 +156,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/scripts/example_tests/test_ctf_cfe_basic_example.json b/scripts/example_tests/test_ctf_cfe_basic_example.json index 9780435..1ac9535 100644 --- a/scripts/example_tests/test_ctf_cfe_basic_example.json +++ b/scripts/example_tests/test_ctf_cfe_basic_example.json @@ -34,7 +34,7 @@ "target": "", "run_args": "-RPO" }, - "comment": "The above run_args are used as command-line arguments when starting the CFS instance.", + "description": "The above run_args are used as command-line arguments when starting the CFS instance.", "wait": 1 }, { @@ -43,7 +43,7 @@ "target": "" }, "wait": 1, - "comment": "Need this to enable the telemetry thread. Enable Output counts as 1 TO cmd" + "description": "Need this to enable the telemetry thread. Enable Output counts as 1 TO cmd" }, { diff --git a/scripts/example_tests/test_ctf_intermediate_example.json b/scripts/example_tests/test_ctf_intermediate_example.json index ca5def8..490b6bd 100644 --- a/scripts/example_tests/test_ctf_intermediate_example.json +++ b/scripts/example_tests/test_ctf_intermediate_example.json @@ -140,6 +140,17 @@ }, "wait": 1 }, + { + "instruction": "SendCfsCommand", + "disabled": true, + "data": { + "target": "", + "mid": "BAD_DATA", + "cc": "BAD_DATA", + "args": {} + }, + "wait": 1 + }, { "instruction": "CheckTlmValue", "data": { diff --git a/scripts/example_tests/test_ctf_verify_commands.json b/scripts/example_tests/test_ctf_verify_commands.json index 99f103b..a78cf86 100644 --- a/scripts/example_tests/test_ctf_verify_commands.json +++ b/scripts/example_tests/test_ctf_verify_commands.json @@ -41,11 +41,11 @@ "target": "" }, "wait": 1, - "comment": "Need this to enable the telemetry thread. Enable Output counts as 1 TO cmd" + "description": "Need this to enable the telemetry thread. Enable Output counts as 1 TO cmd" }, { "instruction": "SendCfsCommand", - "comment": "This command won't actually succeed without valid arguments, so will count as an error.", + "description": "This command won't actually succeed without valid arguments, so will count as an error.", "data": { "target": "", "mid": "CFE_ES_CMD_MID", diff --git a/scripts/validation_tests/validate_ccsds_example.json b/scripts/validation_tests/validate_ccsds_example.json index 9b0e7e0..2cf316e 100644 --- a/scripts/validation_tests/validate_ccsds_example.json +++ b/scripts/validation_tests/validate_ccsds_example.json @@ -28,7 +28,7 @@ { "instruction": "EnableCfsOutput", "wait": 1, - "comment": "Need this to enable the telemetry thread. Enable Output counts as 1 TO cmd" + "description": "Need this to enable the telemetry thread. Enable Output counts as 1 TO cmd" }, { "instruction": "ValidateCfsCcsdsData", diff --git a/setup_ctf_env.sh b/setup_ctf_env.sh index 0eef98e..9932169 100644 --- a/setup_ctf_env.sh +++ b/setup_ctf_env.sh @@ -136,6 +136,7 @@ conda install -c conda-forge pycparser -y conda install -c conda-forge PyNaCl -y conda install -c anaconda six -y conda install -c conda-forge pyelftools -y +conda install -c conda-forge coverage -y if [[ "$platform" == 'linux' ]]; then diff --git a/tools/ctf_ui/package.json b/tools/ctf_ui/package.json index c28f9d0..0cfb4de 100644 --- a/tools/ctf_ui/package.json +++ b/tools/ctf_ui/package.json @@ -59,6 +59,7 @@ "webpack-dev-server": "^3.7.2" }, "dependencies": { + "@ant-design/icons": "^4.3.0", "antd": "^3.20.3", "electron-localshortcut": "^3.1.0", "electron-react-titlebar": "^0.8.1", diff --git a/tools/ctf_ui/src/app/ui/FilePane.tsx b/tools/ctf_ui/src/app/ui/FilePane.tsx index 006a854..41eeb65 100644 --- a/tools/ctf_ui/src/app/ui/FilePane.tsx +++ b/tools/ctf_ui/src/app/ui/FilePane.tsx @@ -18,6 +18,8 @@ import { Dropdown, Empty, Tree, Menu, Popover, Tooltip } from 'antd'; import * as React from 'react'; import { FileTree } from '../../model/tree'; import { PaneHeader } from './PaneHeader'; +import * as fs from 'fs'; + const { TreeNode, DirectoryTree } = Tree; export interface SelectedItem { @@ -62,19 +64,20 @@ export class FilePane extends React.Component { this.setState(props); } - renderContextMenu = (text: string): React.ReactNode => { + renderContextMenu = (text: string, isfolder: boolean): React.ReactNode => { return ( {this.state.contextMenu.map(item => { - return ( - - {item.icon} - {item.title} - + if ( isfolder == true ) { + return ( {item.icon} {item.title} ) + } else if ( item.key != 'new-folder') + return ( + {item.icon} {item.title} ); - })} + }) + } } trigger={['contextMenu']} @@ -87,10 +90,10 @@ export class FilePane extends React.Component { renderTreeNode = (itemId: string) => { if (this.state.tree) { const node = this.state.tree.items[itemId]; - if (node.children.length > 0) { + if (node.children.length > 0) { return ( {node.children.map(this.renderTreeNode)} @@ -101,7 +104,7 @@ export class FilePane extends React.Component {

{node.title}

)}> - {this.renderContextMenu(node.title)} + {this.renderContextMenu(node.title, node.isDirectory)} } key={node.id} diff --git a/tools/ctf_ui/src/app/ui/editor/CtfFileEditor.tsx b/tools/ctf_ui/src/app/ui/editor/CtfFileEditor.tsx index d5cfee6..aa67da5 100644 --- a/tools/ctf_ui/src/app/ui/editor/CtfFileEditor.tsx +++ b/tools/ctf_ui/src/app/ui/editor/CtfFileEditor.tsx @@ -31,6 +31,7 @@ import { Tooltip, Popover, Input, + Checkbox, Form } from 'antd'; import Text from 'antd/lib/typography/Text'; @@ -57,6 +58,8 @@ import { TestCase } from './components/TestCase'; import { AutoCompleteField } from './components/AutoCompleteField'; import { FormProps, FormComponentProps } from 'antd/lib/form'; import { RefObject } from 'react'; +import { CheckCircleTwoTone , CloseCircleTwoTone, ShrinkOutlined } from '@ant-design/icons'; + const { Panel } = Collapse; const { Content, Sider } = Layout; @@ -187,6 +190,7 @@ class CtfFileEditor extends React.Component { }; onTestCaseChanged = (index: number, testCase: CtfTest) => { + const newFile = jsonclone(this.state.ctfFile); newFile.tests[index] = testCase; this.setState({ ctfFile: newFile }); @@ -255,6 +259,206 @@ class CtfFileEditor extends React.Component { if (this.state.onChange) this.state.onChange(newFile); }; + + checkallinstructionsdisabled = (case_number: string) => { + + let alltestsdisabled = false; + + if (this.state.ctfFile) { + const ctfFile = this.state.ctfFile; + + if (ctfFile.tests) { + ctfFile.tests.map((test: CtfTest, testindex) => { + + if (test.case_number == case_number) { + + let i = 0; + for (i = 0; i < test.instructions.length; i++) { + + if (!test.instructions[i].hasOwnProperty("disabled")) break; + if (test.instructions[i].hasOwnProperty("disabled") && test.instructions[i].disabled != true) + break; + } + + if ( i == test.instructions.length) { + alltestsdisabled = true; + } + + } + + + } ); + + } + } + + return alltestsdisabled; + } + + instructionsdisabled = (case_number: string) => { + + let alltestsdisabled = this.checkallinstructionsdisabled(case_number); + + if (alltestsdisabled) { + return 'Enable test case'; + } else { + return 'Disable test case'; + } + + } + + getbuttonicon = (case_number: string) => { + + let alltestsdisabled = this.checkallinstructionsdisabled(case_number); + + if (alltestsdisabled) { + return 'close-circle-o'; + } else { + return 'check-circle-o'; + } + + } + + getstyle = (case_number: string) => { + + let alltestsdisabled = this.checkallinstructionsdisabled(case_number); + + if (alltestsdisabled) { + return { color: "red", flex: "0 0 auto" }; + } else { + return { color: "green", flex: "0 0 auto" }; + } + + } + + + + + checkalltestdisabled = (case_number: string) => { + + let checked = false; + + if (this.state.ctfFile) { + const ctfFile = this.state.ctfFile; + + if (ctfFile.tests) { + ctfFile.tests.map((test: CtfTest, testindex) => { + + if (test.case_number == case_number) { + + let i = 0; + for (i = 0; i < test.instructions.length; i++) { + + if (!test.instructions[i].hasOwnProperty("disabled")) break; + if (test.instructions[i].hasOwnProperty("disabled") && test.instructions[i].disabled != true) + break; + } + + if ( i == test.instructions.length) { + checked = true; + } + + } + + + } ); + + } + } + + return checked; + + } + + onClickIcon = e => { + + if (this.state.ctfFile) { + const ctfFile = this.state.ctfFile; + + if (ctfFile.tests) { + ctfFile.tests.map((test: CtfTest, testindex) => { + + if (test.case_number == e.target.id) { + + let alltestsdisabled = this.checkallinstructionsdisabled(test.case_number); + let newdisable = !alltestsdisabled; + + const newtest = Object.assign( {}, test ); + + + + test.instructions.map((cmd, index) => { //instruction + + if (cmd.hasOwnProperty("instruction") || cmd.hasOwnProperty("function") ) { + const newcmd = Object.assign({}, cmd, {disabled: newdisable}); + + newtest.instructions[index] = newcmd; + } + + } + ); + + + + this.onTestCaseChanged( testindex, newtest ); + + } + + } + ); + + } + + e.stopPropagation(); + } + + } + + onChangeCheckbox = e => { + + if (this.state.ctfFile) { + const ctfFile = this.state.ctfFile; + + if (ctfFile.tests) { + ctfFile.tests.map((test: CtfTest, testindex) => { + + if (test.case_number == e.target.name) { + + const newtest = Object.assign( {}, test ); + + + + test.instructions.map((cmd, index) => { + + if (cmd.hasOwnProperty("instruction")) { + + const newcmd = Object.assign({}, cmd, {disabled: e.target.checked}); + + newtest.instructions[index] = newcmd; + } + else { // function + + } + + } + ); + + + + this.onTestCaseChanged( testindex, newtest ); + + } + + } + ); + + } + + e.stopPropagation(); + } + + }; + reloadImports = () =>{ const newFunctionData = this.getFunctionData() return newFunctionData @@ -380,7 +584,7 @@ class CtfFileEditor extends React.Component { {data.requirements ? Object.keys(data.requirements).map((req, index) => ( -
+
{req} {data.requirements[req]} { } renderCtfFilePane() { + if (this.state.ctfFile) { const ctfFile = this.state.ctfFile; return ( @@ -467,11 +672,12 @@ class CtfFileEditor extends React.Component { Imports {ctfFile.import && this.state.context ? ( - Object.keys(ctfFile.import).map(path => ( + Object.keys(ctfFile.import).map( (path, index) => ( {this.state.context.import[path] ? ( { (test: CtfTest, index) => ( +
+ this.onTestCaseChanged( @@ -576,9 +786,35 @@ class CtfFileEditor extends React.Component { ) ) }} - > + > {test.case_number} - + +
+ + + + +
+ + +
+ + + + +
} key={test.id} > @@ -619,7 +855,9 @@ class CtfFileEditor extends React.Component { DELETE +
+ ) ) diff --git a/tools/ctf_ui/src/app/ui/editor/components/Command.tsx b/tools/ctf_ui/src/app/ui/editor/components/Command.tsx index f5cd932..ec615bd 100644 --- a/tools/ctf_ui/src/app/ui/editor/components/Command.tsx +++ b/tools/ctf_ui/src/app/ui/editor/components/Command.tsx @@ -36,6 +36,12 @@ import { Comparison } from './Comparison'; import { ParamArray } from './ctf-file-editor-utils'; import { CascaderOptionType } from 'antd/lib/cascader'; +import { CheckCircleTwoTone, MessageOutlined,MessageTwoTone } from '@ant-design/icons'; + +const { TextArea } = Input; + + + const CommandStyle: React.CSSProperties = { flex: '1 1 auto', whiteSpace: 'nowrap', @@ -547,8 +553,8 @@ export class Command extends React.Component { onChange(param, cmd_arg_obj, index) }} /> - - + + ); } else if (param.type === 'tlm_arg') { return ( @@ -630,14 +636,33 @@ export class Command extends React.Component { } }; + + renderDescription(description: string) { + + return ( +