From 14d526d95c1da4150710c76a7c7331b13e731742 Mon Sep 17 00:00:00 2001 From: Phanes Date: Mon, 4 Dec 2017 23:25:48 -0500 Subject: [PATCH] fixed logic flow bug in Task execution, cleaned up exceptions, improved exception verbosity, and added a logic tree for task execution flow --- conf/units/all_test.units | 10 +- design/Target Execution Flow.txt | 60 ++++++++ examplar.cpp | 2 +- src/loaders/Suite.h | 2 - src/loaders/Task.cpp | 238 +++++++++++++++++++++++-------- src/loaders/Task.h | 1 + 6 files changed, 244 insertions(+), 69 deletions(-) create mode 100644 design/Target Execution Flow.txt diff --git a/conf/units/all_test.units b/conf/units/all_test.units index 2fb5ac4..41fac62 100644 --- a/conf/units/all_test.units +++ b/conf/units/all_test.units @@ -2,10 +2,10 @@ "units": [ { "name": "independent test 1", - "target": "/usr/bin/false", + "target": "/usr/bin/true", "rectifier": "/usr/bin/true", "active": true, - "required": false, + "required": true, "rectify": true }, { @@ -26,10 +26,10 @@ }, { "name": "dependent test", - "target": "/usr/bin/true", - "rectifier": "/usr/bin/false", + "target": "/usr/bin/false", + "rectifier": "/usr/bin/true", "active": true, - "required": false, + "required": true, "rectify": true } ] diff --git a/design/Target Execution Flow.txt b/design/Target Execution Flow.txt new file mode 100644 index 0000000..bf52b0e --- /dev/null +++ b/design/Target Execution Flow.txt @@ -0,0 +1,60 @@ +# Logic Tree for Examplar Task Execution + + Examplar - An automation and testing framework. + + © SURRO INDUSTRIES and Chris Punches, 2017. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +## Key: +-Actions are labled by indices in a zero-indexed array 'a'. +-Decisions are labeled by indices in a zero-indexed array 'd'. + -Decisions have two possible states, TRUE or FALSE (0 or 1 respectively). + -Decision determinations are labeled with the decision label and a subspecifier. + +## Diagram: + +a[0] Execute Target +d[0] Error Code Check + d[0].0 ZERO + a[1] NEXT + d[0].1 NON-ZERO + d[1] Rectify Check + d[1].0 FALSE + d[2] Required Check + d[2].0 FALSE + a[2] NEXT + d[2].1 TRUE + a[3] EXCEPTION + d[1].1 TRUE + a[4] Execute Rectifier + d[3] Error Code Check + d[3].1 NON-ZERO + d[4] Required Check + d[4].0 FALSE + a[5] NEXT + d[4].1 TRUE + a[6] EXCEPTION + d[3].0 ZERO + a[7] Re-Execute Target + d[5] Error Code Check + d[5].0 ZERO + a[8] NEXT + d[5].1 NON-ZERO + d[6] Required Check + d[6].0 FALSE + a[9] NEXT + d[6].1 TRUE + a[10] EXCEPTION + diff --git a/examplar.cpp b/examplar.cpp index a0ec0eb..5221bda 100644 --- a/examplar.cpp +++ b/examplar.cpp @@ -17,11 +17,11 @@ along with this program. If not, see . */ + #include #include "src/json/json.h" #include "src/loaders/loaders.h" - int main( ) { bool verbose = true; diff --git a/src/loaders/Suite.h b/src/loaders/Suite.h index 671bff6..d8aa78e 100644 --- a/src/loaders/Suite.h +++ b/src/loaders/Suite.h @@ -27,8 +27,6 @@ #include "Unit.h" - - class Suite: public JSON_Loader { protected: diff --git a/src/loaders/Task.cpp b/src/loaders/Task.cpp index a799f9c..afd201b 100644 --- a/src/loaders/Task.cpp +++ b/src/loaders/Task.cpp @@ -35,18 +35,49 @@ public: Task_NotReady(): std::runtime_error("Task: Attempted to access a unit of a Task that is not defined.") {} }; -/// Task_RequiredButFailedTask - Exception thrown when a Task is failed but required, and rectification also failed. -class Task_RequiredButFailedTask: public std::runtime_error { + +/// Task_RequiredButFailedTask - Exception thrown when a Task fails but should not. +class TaskException: public std::exception +{ public: - Task_RequiredButFailedTask(): std::runtime_error("Task: Attempted to execute a Task that failed and was required.") {} + /** Constructor (C strings). + * @param message C-style string error message. + * The string contents are copied upon construction. + * Hence, responsibility for deleting the char* lies + * with the caller. + */ + explicit TaskException(const char* message): + msg_(message) + { + } + + /** Constructor (C++ STL strings). + * @param message The error message. + */ + explicit TaskException(const std::string& message): + msg_(message) + {} + + /** Destructor. + * Virtual to allow for subclassing. + */ + virtual ~TaskException() throw (){} + + /** Returns a pointer to the (constant) error description. + * @return A pointer to a const char*. The underlying memory + * is in posession of the Exception object. Callers must + * not attempt to free the memory. + */ + virtual const char* what() const throw (){ + return msg_.c_str(); + } + +protected: + /** Error message. + */ + std::string msg_; }; -/// Task_RequiredButFailedTask - Exception thrown when a Task is failed but required, and rectification also failed but -/// returned with a zero exit code (dont try to fool the check). -class Task_RequiredButRectifierDoesNotHeal: public std::runtime_error { -public: - Task_RequiredButRectifierDoesNotHeal(): std::runtime_error("Task: The rectification script was executed and reported success, but did not actually heal the faulty condition of the Task target.") {} -}; /// Task::Task() - Constructor for the Task class. The Task is the building block of a Plan indicating of which Unit to /// execute, and its dependencies on other units to have already been completed successfully. @@ -134,6 +165,11 @@ bool Task::has_definition() return this->defined; } + + + + + /// Task::execute - execute a task's unit definition. /// See the design document for what flow control needs to look like here. /// \param verbose - Verbosity level - not implemented yet. @@ -143,7 +179,8 @@ void Task::execute( bool verbose ) // PREWORK // throw if unit not coupled to all necessary values since Task is stateful (yes, stateful is okay) - if (! this->has_definition() ) { + if ( ! this->has_definition() ) + { throw Task_NotReady(); } @@ -156,81 +193,160 @@ void Task::execute( bool verbose ) std::string target_command = this->definition.get_target(); // if we're in verbose mode, do some verbose things - if ( verbose ) { + if ( verbose ) + { std::cout << "\tUsing unit \"" << task_name << "\"." << std::endl; std::cout << "\tExecuting target \"" << target_command << "\"." << std::endl; } - // execute target + // a[0] execute target int return_code = Sproc::execute( target_command ); - // d[0] check exit code of target - if (return_code == 0) { - // Zero d[0] return from target execution, good to return + // ********************************************** + // d[0] Error Code Check + // ********************************************** + if ( return_code == 0 ) + { + // d[0].0 ZERO + if ( verbose ) { std::cout << "\tTarget " << task_name << " succeeded." << std::endl; } this->mark_complete(); - // next - } else { - // Non-Zero d[0] from initial target execution, get to d[1] + + // a[1] NEXT + return; + } + + if ( return_code != 0 ) + { + // d[0].1 NON-ZERO + std::cout << "\tTarget \"" << task_name << "\" failed with exit code " << return_code << "." << std::endl; - // check if rectify pattern is enabled d[1] - if ( this->definition.get_rectify() ) { - // yes d[1] + // ********************************************** + // d[1] Rectify Check + // ********************************************** + if (! this->definition.get_rectify() ) + { + // d[1].0 FALSE + + // ********************************************** + // d[2] Required Check + // ********************************************** + if (! this->definition.get_required() ) + { + // d[2].0 FALSE + // a[2] NEXT + std::cout << "\tThis task is not required to continue the plan. Moving on." << std::endl; + return; + } + + if ( this->definition.get_required() ) + { + // d[2].1 TRUE + // a[3] EXCEPTION + throw TaskException("Task \"" + task_name + "\" is required, and failed, and rectification is not enabled."); + } + // ********************************************** + // end - d[2] Required Check + // ********************************************** + } + + + if ( this->definition.get_rectify() ) + { + // d[1].1 TRUE (Rectify Check) std::cout << "\tRectification pattern is enabled for \"" << task_name << "\"." << std::endl; - // execute RECTIFIER + + // a[4] Execute RECTIFIER std::string rectifier_command = this->definition.get_rectifier(); std::cout << "\tExecuting rectification: " << rectifier_command << "." << std::endl; int rectifier_error = Sproc::execute( rectifier_command ); - // d[3] check exit code of rectifier - if (rectifier_error) { - //d[3] non-zero - + // ********************************************** + // d[3] Error Code Check for Rectifier + // ********************************************** + if ( rectifier_error != 0 ) + { + // d[3].1 Non-Zero std::cout << "\tRectification of \"" << task_name << "\" failed with exit code " << rectifier_error << "." << std::endl; - // d[2] check if REQUIRED - if ( this->definition.get_required() ) { - // d[2] yes - // halt/exception - throw Task_RequiredButFailedTask(); + // ********************************************** + // d[4] Required Check + // ********************************************** + if ( ! this->definition.get_required() ) { + // d[4].0 FALSE + // a[5] NEXT + std::cout << "\tThis task is not required to continue the plan. Moving on." << std::endl; + return; } - // d[2] no - // next - } - // d[3] zero - // execute target - std::cout << "\tRe-Executing target \"" << this->definition.get_target() << "\"." << std::endl; - int retry_code = Sproc::execute( target_command ); + if ( this->definition.get_required() ) + { + // d[4].1 TRUE + // a[6] EXCEPTION + throw TaskException("Task \"" + task_name + "\" is required, and failed, then rectified but rectification failed."); + } + // ********************************************** + // end - d[4] Required Check + // ********************************************** + } + + // d[3] check exit code of rectifier + if ( rectifier_error == 0 ) + { + // d[3].0 Zero + std::cout << "\tRectification returned successfully." << std::endl; + + // a[7] Re-execute Target + std::cout << "\tRe-Executing target \"" << this->definition.get_target() << "\"." << std::endl; + int retry_code = Sproc::execute( target_command ); + + // ********************************************** + // d[5] Error Code Check + // ********************************************** + if ( retry_code == 0 ) + { + // d[5].0 ZERO + // a[8] NEXT + std::cout << "\tRe-execution was successful." << std::endl; + return; + } + + if ( retry_code != 0 ) + { + // d[5].1 NON-ZERO + std::cout << "\tRe-execution failed with exit code " << retry_code << "." << std::endl; + + + // ********************************************** + // d[6] Required Check + // ********************************************** + if ( ! this->definition.get_required() ) + { + // d[6].0 FALSE + // a[9] NEXT + std::cout << "\tThis task is not required to continue the plan. Moving on." << std::endl; + return; + } + + if ( this->definition.get_required() ) + { + // d[6].1 TRUE + // a[10] EXCEPTION + throw TaskException("Task \"" + task_name + "\" is required, and failed, then rectified but rectifier did not heal the condition causing the target to fail. Cannot proceed with Plan."); + } + // ********************************************** + // end - d[6] Required Check + // ********************************************** + } - // d[4] exit code of target retry - if (retry_code == 0) { - // d[4] zero } - // d[4] non-zero - // d[5] required check - if ( this->definition.get_required() ) { - // d[5] yes - std::cout << "\tTask \"" << task_name << "\" is required but rectification did not heal." << std::endl; - throw Task_RequiredButRectifierDoesNotHeal(); - } - // d[5] no - // next } - // no d[1] - std::cout << "\tRectification is not enabled for \"" << task_name << "\"." << std::endl; - // required d[2] - if ( this->definition.get_required() ) { - // d[2] yes - // This is executing..... - std::cout << "\tThis task is required to continue the plan." << std::endl; - // but these are NOT executing????? - throw Task_RequiredButFailedTask(); - } // d[2] no - std::cout << "\tThis task is not required to continue the plan." << std::endl; + // ********************************************** + // end d[1] Rectify Check + // ********************************************** } } \ No newline at end of file diff --git a/src/loaders/Task.h b/src/loaders/Task.h index cf57a15..d1f010a 100644 --- a/src/loaders/Task.h +++ b/src/loaders/Task.h @@ -27,6 +27,7 @@ class Task { + protected: // the name of this task std::string name;