From eb6404d0d2900fa7453e3a6a5721acf9f431f5c7 Mon Sep 17 00:00:00 2001 From: mnasie Date: Mon, 29 Jun 2026 19:28:19 +0300 Subject: [PATCH 01/20] Rename config file and update log file naming convention --- cli/src/main.cpp | 2 +- src/config_manager.cpp | 4 ++-- src/task_runner.cpp | 2 +- tests/unit/test_config_manager.cpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cli/src/main.cpp b/cli/src/main.cpp index 0880089..66236a2 100644 --- a/cli/src/main.cpp +++ b/cli/src/main.cpp @@ -80,7 +80,7 @@ int main(int argc, char **argv) { if(_fh.getErrCode() == ErrorCode::CONFIG_PARSE_FAILED) { std::cerr << "Config file is corrupted." << std::endl; - fs::path config_path = get_home_directory() / ".config" / "flowhook"/ "tasks.json"; + fs::path config_path = get_home_directory() / ".config" / "flowhook"/ "config.json"; std::cout << "Path : " << config_path << std::endl; std::cerr << "Options: " << std::endl; std::cout << " [1] clear all data and start fresh" << std::endl; diff --git a/src/config_manager.cpp b/src/config_manager.cpp index b7085fd..98c9fe0 100644 --- a/src/config_manager.cpp +++ b/src/config_manager.cpp @@ -25,9 +25,9 @@ namespace flowhook if (override) { - return std::filesystem::path(override) / "tasks.json"; + return std::filesystem::path(override) / "config.json"; } - return std::filesystem::path(home) / ".config" / "flowhook" / "tasks.json"; + return std::filesystem::path(home) / ".config" / "flowhook" / "config.json"; } Result ensure_config_dir() diff --git a/src/task_runner.cpp b/src/task_runner.cpp index 75afae4..a67aefe 100644 --- a/src/task_runner.cpp +++ b/src/task_runner.cpp @@ -38,7 +38,7 @@ Result TaskRunner::init(const string &task_name, working_directory + ". ✗")); } - string file_name = task_name + ".log"; + string file_name = task_name + "-flowhook.log"; fs::path _file_path = fs::path(working_directory) / file_name; sl = TRY(SessionLogger::create(_file_path.string()), void); diff --git a/tests/unit/test_config_manager.cpp b/tests/unit/test_config_manager.cpp index 75bb392..e123cff 100644 --- a/tests/unit/test_config_manager.cpp +++ b/tests/unit/test_config_manager.cpp @@ -63,7 +63,7 @@ UTEST_F_TEARDOWN(ConfigManagerFixture) UTEST_F(ConfigManagerFixture, create) { ASSERT_NE(utest_fixture->cm, nullptr); - EXPECT_TRUE(fs::exists("/tmp/cm_test/tasks.json")); + EXPECT_TRUE(fs::exists("/tmp/cm_test/config.json")); EXPECT_TRUE(ConfigManagerTest::get_config(utest_fixture->cm)["version"] == FLOWHOOK_VERSION); } From 72f801c3c244834da5d16f0dfd33ff2da32ad7d9 Mon Sep 17 00:00:00 2001 From: mnasie Date: Mon, 29 Jun 2026 20:10:14 +0300 Subject: [PATCH 02/20] Update test configuration and execution workflow Update the CI trigger branches and the test runner command. Modify unit tests to align with the updated Task schema and correct the JSON configuration filename. --- .github/workflows/tests.yml | 4 ++-- tests/unit/test_config_manager.cpp | 16 ++++++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f2fe89d..aaead53 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,7 +2,7 @@ name: Tests on: push: - branches: [core, main] + branches: [dev, main, test/all] pull_request: branches: [main] @@ -31,4 +31,4 @@ jobs: run: cmake --build build - name: Run tests - run: ctest --test-dir build --output-on-failure + run: cd build && ./test_config_manager diff --git a/tests/unit/test_config_manager.cpp b/tests/unit/test_config_manager.cpp index e123cff..d6e9762 100644 --- a/tests/unit/test_config_manager.cpp +++ b/tests/unit/test_config_manager.cpp @@ -37,13 +37,13 @@ UTEST_F_SETUP(ConfigManagerFixture) fs::create_directories("/tmp/cm_test2"); utest_fixture->t = new Task("test_task", "/tmp/cm_test", 3, {"ls"}, - {"/tmp/cm_test_file.txt"}, vector{"ls"}, vector{"ls"}, {}, {}, true, true); + {"/tmp/cm_test_file.txt"}, vector{"ls"}, vector{"ls"}, {"*.o"}, {".git"}, true, true); utest_fixture->t2 = new Task("test_task2", "/tmp/cm_test2", 3, {"ls"}, - {"/tmp/cm_test_file.txt"}, vector{"ls"}, vector{"cd, ls"}, {}, {}, true, true); + {"/tmp/cm_test_file.txt"}, vector{"ls"}, vector{"cd, ls"}, {"*.o"}, {".git"}, true, true); - utest_fixture->t3 = new Task("test_task", "/tmp/cm_test3", 3, {"cd", "make"}, - {"/tmp/cm_test_file.txt"}, vector{"ls"}, vector{"cd, ls"}, {}, {}, true, true); + utest_fixture->t3 = new Task("test_task1", "/tmp/cm_test", 3, {"cd", "make"}, + {"/tmp/cm_test_file.txt"}, vector{"ls"}, vector{"cd, ls"}, {"*.o"}, {".git"}, true, true); } UTEST_F_TEARDOWN(ConfigManagerFixture) @@ -132,6 +132,10 @@ UTEST_F(ConfigManagerFixture, update_task) auto c2 = utest_fixture->cm->update_task(*utest_fixture->t3); EXPECT_TRUE(c2.isOk()); + if(c2.isErr()) + { + cout << c2.getErrMessage() << endl; + } } @@ -154,7 +158,7 @@ UTEST_F(ConfigManagerFixture, flush) } json test_json = utest_fixture->cm->getjson(); - ifstream file("/tmp/cm_test/tasks.json"); + ifstream file("/tmp/cm_test/config.json"); json content = json::parse(file); EXPECT_EQ(test_json.size(), content.size()); } @@ -171,7 +175,7 @@ UTEST_F(ConfigManagerFixture, purge_config) auto c2 = utest_fixture->cm->purge_config(); EXPECT_TRUE(c2.isOk()); - auto config_path = "/tmp/cm_test/tasks.json"; + auto config_path = "/tmp/cm_test/config.json"; EXPECT_TRUE(fs::file_size(config_path) == 0); } From 9f1e6c297c67bd15e77a9534ef8962f7043b8aad Mon Sep 17 00:00:00 2001 From: mnasie Date: Mon, 29 Jun 2026 22:03:45 +0300 Subject: [PATCH 03/20] updated test for the session logger --- src/session_logger.cpp | 12 ++++++++- tests/unit/test_session_logger.cpp | 42 +++++++++++++++++------------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/session_logger.cpp b/src/session_logger.cpp index 83d872d..2c91500 100644 --- a/src/session_logger.cpp +++ b/src/session_logger.cpp @@ -47,6 +47,11 @@ namespace flowhook Result SessionLogger::start() { + if (is_running) + { + return Result::Ok(); // idempotent + } + FW_LOG("[DEBUG] Starting a new log session ..."); file.open(file_path, ios::out | ios::app); if (!file.is_open()) @@ -73,6 +78,11 @@ namespace flowhook Result SessionLogger::log_execution(const ExecutionResult &execution_result) { + if(!is_running) + { + return Result::Err(FWError::make( + ErrorCode::SESSION_LOGGER_NOT_RUNNING, "Error: session logger not initialized. ✗")); + } if (session.empty()) { return Result::Err(FWError::make( @@ -126,7 +136,7 @@ namespace flowhook { if (!is_running) { - return Result::Err(FWError::make(ErrorCode::SESSION_LOGGER_NOT_RUNNING, "Error: session logger not running. ✗")); + return Result::Ok(); // idempotent } if (!file.is_open()) diff --git a/tests/unit/test_session_logger.cpp b/tests/unit/test_session_logger.cpp index c186356..c926bbb 100644 --- a/tests/unit/test_session_logger.cpp +++ b/tests/unit/test_session_logger.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -11,6 +12,10 @@ namespace fs = std::filesystem; using namespace flowhook; using namespace std; +bool FLOWHOOK_DEBUG = false; +bool FLOWHOOK_VERBOSE = false; +bool FLOWHOOK_QUIET = false; + // --------------------------------------------------------------------------- // Fixture // --------------------------------------------------------------------------- @@ -23,10 +28,17 @@ struct SessionLoggerFixture UTEST_F_SETUP(SessionLoggerFixture) { - WatchEvent e(IN_MODIFY, "file", "/tmp/sl_test_file.txt", 0); + WatchEvent e(0, "file", "/tmp/sl_test_file.txt", IN_MODIFY); utest_fixture->er = new ExecutionResult(1, 0, e, "log", vector{"ls"}); utest_fixture->er2 = new ExecutionResult(2, 0, e, "log", vector{"ls"}); - utest_fixture->sl = new SessionLogger(); + auto s = SessionLogger::create("/tmp/sl_test_file.txt"); + if(s.isErr()) + { + cout << s.getErrMessage() << endl; + exit(1); + } + + utest_fixture->sl = s.unwrap(); fs::create_directories("/tmp/sl_test"); } @@ -44,20 +56,12 @@ UTEST_F_TEARDOWN(SessionLoggerFixture) // --------------------------------------------------------------------------- UTEST_F(SessionLoggerFixture, start) { - EXPECT_TRUE(utest_fixture->sl->start("/tmp/sl_test").isOk()); + EXPECT_TRUE(utest_fixture->sl->start().isOk()); } UTEST_F(SessionLoggerFixture, start_twice) { - EXPECT_TRUE(utest_fixture->sl->start("/tmp/sl_test").isOk()); - EXPECT_TRUE(utest_fixture->sl->start("/tmp/sl_test").isErr()); -} -UTEST_F(SessionLoggerFixture, start_nonexistent) -{ - EXPECT_TRUE(utest_fixture->sl->start("/tmp/sl_nonexistent").isErr()); -} -UTEST_F(SessionLoggerFixture, start_empty) -{ - EXPECT_TRUE(utest_fixture->sl->start("").isErr()); + EXPECT_TRUE(utest_fixture->sl->start().isOk()); + EXPECT_TRUE(utest_fixture->sl->start().isOk()); // idempotent } // --------------------------------------------------------------------------- @@ -65,7 +69,7 @@ UTEST_F(SessionLoggerFixture, start_empty) // --------------------------------------------------------------------------- UTEST_F(SessionLoggerFixture, log_execution) { - ASSERT_TRUE(utest_fixture->sl->start("/tmp/sl_test").isOk()); + ASSERT_TRUE(utest_fixture->sl->start().isOk()); auto r = utest_fixture->sl->log_execution(*utest_fixture->er); EXPECT_TRUE(r.isOk()); } @@ -80,18 +84,20 @@ UTEST_F(SessionLoggerFixture, log_execution_no_running) // --------------------------------------------------------------------------- UTEST_F(SessionLoggerFixture, stop) { - ASSERT_TRUE(utest_fixture->sl->start("/tmp/sl_test").isOk()); + ASSERT_TRUE(utest_fixture->sl->start().isOk()); EXPECT_TRUE(utest_fixture->sl->stop().isOk()); } + UTEST_F(SessionLoggerFixture, stop_without_start) { - EXPECT_TRUE(utest_fixture->sl->stop().isErr()); + EXPECT_TRUE(utest_fixture->sl->stop().isOk()); // idempotent } + UTEST_F(SessionLoggerFixture, stop_twice) { - ASSERT_TRUE(utest_fixture->sl->start("/tmp/sl_test").isOk()); + ASSERT_TRUE(utest_fixture->sl->start().isOk()); EXPECT_TRUE(utest_fixture->sl->stop().isOk()); - EXPECT_TRUE(utest_fixture->sl->stop().isErr()); + EXPECT_TRUE(utest_fixture->sl->stop().isOk()); // idempotent } UTEST_MAIN() From 84af010dcfbdc00e4199ecbe089d9fb57738f599 Mon Sep 17 00:00:00 2001 From: mnasie Date: Tue, 30 Jun 2026 14:25:40 +0300 Subject: [PATCH 04/20] Refactor path handling and improve task reporting Split path management into separate file and directory paths, implement idempotent path addition, and update CLI list command to display resolved files. Update test runner configuration to include missing test suites. --- .github/workflows/tests.yml | 2 +- cli/src/commands/list.cpp | 31 ++++++++---- src/config_manager.cpp | 33 +++++++++---- src/display.cpp | 47 ------------------ src/filewatcher.cpp | 12 +++-- src/flowhook_core.cpp | 17 ++++++- src/include/error/result.h | 2 - src/include/filewatcher.h | 3 -- src/include/flowhook_core.h | 2 + src/include/task_runner.h | 4 ++ src/include/types.h | 5 +- src/task_runner.cpp | 61 ++++++++++++++++++++---- tests/integration/test_flowhook_core.cpp | 5 ++ tests/unit/test_filewatcher.cpp | 5 ++ tests/unit/test_task_runner.cpp | 57 ++++++++++++++++++---- 15 files changed, 188 insertions(+), 98 deletions(-) delete mode 100644 src/display.cpp diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index aaead53..e9095c3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -31,4 +31,4 @@ jobs: run: cmake --build build - name: Run tests - run: cd build && ./test_config_manager + run: cd build && ./test_config_manager && ./test_task_runner && ./test_session_logger diff --git a/cli/src/commands/list.cpp b/cli/src/commands/list.cpp index 0c41480..3622970 100644 --- a/cli/src/commands/list.cpp +++ b/cli/src/commands/list.cpp @@ -48,8 +48,8 @@ namespace flowhook_cli { if (list_tasks) { std::cout << "All registered Tasks:\n" << std::endl; for (const auto& task : tasks) { - std::cout << "Name: " << task.name << std::endl; - std::cout << "Working Directory: " << task.id << "\n" << std::endl; + std::cout << " Name: " << task.name << std::endl; + std::cout << " Working Directory: " << task.id << "\n" << std::endl; } } @@ -60,10 +60,21 @@ namespace flowhook_cli { return; } - std::cout << "Paths:\n" << std::endl; - for(const auto& path : ts->paths) { - std::cout << path << std::endl; + std::cout << "Watched Directories:\n" << std::endl; + for(const auto& path : ts->dir_paths) { + std::cout << " " << path << std::endl; } + + std::cout << "Standalone File:\n" << std::endl; + for(const auto& path : ts->file_paths) { + std::cout << " " << path << std::endl; + } + + std::cout << "All Currently Watched Files:\n" << std::endl; + for(const auto& path : fh->get_resolved_files(ts->id)) { + std::cout << " " << path << std::endl; + } + } if (list_commands) { @@ -74,7 +85,7 @@ namespace flowhook_cli { std::cout << "Commands:\n" << std::endl; for(const auto& command : ts->commands) { - std::cout << command << std::endl; + std::cout << " " << command << std::endl; } } @@ -86,12 +97,12 @@ namespace flowhook_cli { std::cout << "Ignored paths:\n" << std::endl; for(const auto& ignored_paths : ts->ignored_paths) { - std::cout << ignored_paths << std::endl; + std::cout << " " << ignored_paths << std::endl; } std::cout << std::endl; std::cout << "Ignored patterns:\n" << std::endl; for(const auto& ignored_pattern : ts->ignored_patterns) { - std::cout << ignored_pattern << std::endl; + std::cout << " " << ignored_pattern << std::endl; } std::cout << std::endl; } @@ -104,7 +115,7 @@ namespace flowhook_cli { std::cout << "On success:\n" << std::endl; for(const auto& command : ts->on_success) { - std::cout << command << std::endl; + std::cout << " " << command << std::endl; } std::cout << std::endl; } @@ -117,7 +128,7 @@ namespace flowhook_cli { std::cout << "On failure:\n" << std::endl; for(const auto& command : ts->on_failure) { - std::cout << command << std::endl; + std::cout << " " << command << std::endl; } std::cout << std::endl; } diff --git a/src/config_manager.cpp b/src/config_manager.cpp index 98c9fe0..5a2e70a 100644 --- a/src/config_manager.cpp +++ b/src/config_manager.cpp @@ -117,12 +117,19 @@ namespace flowhook } _task.commands = _commands; - vector _paths; - for (auto &cmd : json_task.at("paths")) + vector _file_paths; + for (auto &cmd : json_task.at("file_paths")) { - _paths.push_back(cmd); + _file_paths.push_back(cmd); } - _task.paths = _paths; + _task.file_paths = _file_paths; + + vector _dir_paths; + for (auto &cmd : json_task.at("dir_paths")) + { + _dir_paths.push_back(cmd); + } + _task.dir_paths = _dir_paths; vector _on_success; for (auto &cmd : json_task.at("on_success")) @@ -136,6 +143,7 @@ namespace flowhook { _on_failure.push_back(cmd); } + _task.on_failure = _on_failure; vector _ignored_paths; for (auto &path : json_task.at("ignored_paths")) @@ -151,7 +159,6 @@ namespace flowhook } _task.ignored_patterns = _ignored_patterns; - _task.on_failure = _on_failure; _task.isActive = json_task.at("isActive"); return Result::Ok(_task); } @@ -166,11 +173,18 @@ namespace flowhook { _json_task["commands"].push_back(cmd); } - _json_task["paths"] = json::array(); - for (auto &path : task.paths) + _json_task["file_paths"] = json::array(); + for (auto &path : task.file_paths) { - _json_task["paths"].push_back(path); + _json_task["file_paths"].push_back(path); } + + _json_task["dir_paths"] = json::array(); + for (auto &path : task.dir_paths) + { + _json_task["dir_paths"].push_back(path); + } + _json_task["on_success"] = json::array(); for (auto &cmd : task.on_success) { @@ -279,7 +293,8 @@ namespace flowhook _task.name = it->at("task_name"); _task.id = it->at("working_directory"); _task.commands = it->at("commands"); - _task.paths = it->at("paths"); + _task.file_paths = it->at("file_paths"); + _task.dir_paths = it->at("dir_paths"); _task.on_success = it->at("on_success"); _task.on_failure = it->at("on_failure"); _task.ignored_paths = it->at("ignored_paths"); diff --git a/src/display.cpp b/src/display.cpp deleted file mode 100644 index 4517c7a..0000000 --- a/src/display.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include -#include - -#include "./include/display.h" -#include "./include/filewatcher.h" - -using namespace std; - -namespace flowhook -{ - void print_header() - { - cout << "===================================================" << endl; - cout << "------------ FLOWHOOK v0.0.1 -------------------" << endl; - cout << "===================================================" << endl; - } - - string receive_input(const string &msg) - { - cout << msg; - string text_buffer; - getline(cin, text_buffer); - return text_buffer; - } - - void print_list(const string &msg, const vector &list) - { - cout << "__________________________________________________" << endl; - cout << msg << endl; - for (auto &path : list) - { - cout << path << endl; - } - } - - void print_numbered_list(const string msg, const vector &list) - { - cout << "__________________________________________________" << endl; - cout << msg << endl; - int i = 1; - for (auto &path : list) - { - cout << "[" << i << "] " << path << endl; - i++; - } - } -} diff --git a/src/filewatcher.cpp b/src/filewatcher.cpp index 1817e32..019769e 100644 --- a/src/filewatcher.cpp +++ b/src/filewatcher.cpp @@ -109,6 +109,14 @@ Result FileWatcher::handle_events(int fd, vector wd, Result FileWatcher::add_path(const string &arg) { lock_guard lock(registry_mutex); + for(auto [wd, path]: watch_registry) + { + if(path == arg){ + return Result::Err( + ErrorCode::PATH_ALREADY_EXISTS, + "Error: path " + arg + " already exists ✗"); + } + } int wd = inotify_add_watch(inotify_fd, arg.c_str(), IN_MOVED_TO | IN_MOVED_FROM | IN_MODIFY | IN_CLOSE_WRITE); @@ -125,10 +133,6 @@ Result FileWatcher::add_path(const string &arg) { Result FileWatcher::remove_path(const string &arg) { lock_guard lock(registry_mutex); - return remove_path_internal(arg); -} - -Result FileWatcher::remove_path_internal(const string &arg) { // check if path exists bool path_exists = false; for (auto [w, p] : watch_registry) { diff --git a/src/flowhook_core.cpp b/src/flowhook_core.cpp index c236cb6..18902a9 100644 --- a/src/flowhook_core.cpp +++ b/src/flowhook_core.cpp @@ -56,7 +56,9 @@ namespace flowhook { for(auto c: task.commands) tr->add_command(c); - for(auto p: task.paths) + for(auto p: task.file_paths) + tr->add_path(p); + for(auto p: task.dir_paths) tr->add_path(p); for(auto s: task.on_success) tr->add_on_success(s); @@ -142,6 +144,19 @@ namespace flowhook { return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); } + + std::vector FlowHookCore::get_resolved_files(const std::string task_id) + { + for(auto it = task_runners.begin(); it != task_runners.end(); it++) + { + if((*it)->get_task_id() == task_id) + { + return (*it)->get_resolved_files(); + } + } + return std::vector(); + } + Result FlowHookCore::activate_task(const std::string &task_name) { for(auto it = task_runners.begin(); it != task_runners.end(); it++) diff --git a/src/include/error/result.h b/src/include/error/result.h index ccf2204..eeabd1e 100644 --- a/src/include/error/result.h +++ b/src/include/error/result.h @@ -1,8 +1,6 @@ #pragma once -#include #include -#include #include #include diff --git a/src/include/filewatcher.h b/src/include/filewatcher.h index 92e2b19..0f13ea4 100644 --- a/src/include/filewatcher.h +++ b/src/include/filewatcher.h @@ -61,11 +61,8 @@ namespace flowhook bool is_running() const { return isWatching; } Result add_path(const std::string &arg); - Result add_path_internal(const std::string &arg, int MAX_DEPTH, int CURRENT_DEPTH); Result remove_path(const std::string &arg); - Result remove_path_internal(const std::string &arg); - bool isIgnored(const std::string &path); Result start(int timeout); Result stop(); diff --git a/src/include/flowhook_core.h b/src/include/flowhook_core.h index 0386bf2..9656538 100644 --- a/src/include/flowhook_core.h +++ b/src/include/flowhook_core.h @@ -30,6 +30,8 @@ namespace flowhook { Result create_task(const std::string &task_name, const std::string &task_id); Result delete_task(const std::string &task_id); + std::vector get_resolved_files(const std::string task_id); + Result start_task(const std::string &task_id); Result stop_task(const std::string &task_id); diff --git a/src/include/task_runner.h b/src/include/task_runner.h index a4202ec..e2e1465 100644 --- a/src/include/task_runner.h +++ b/src/include/task_runner.h @@ -25,10 +25,12 @@ namespace flowhook bool flushed; bool is_init = false; int execution_id = 0; + std::vector resolved_files; TaskRunner() = default; Result init(const std::string &task_name, const std::string &working_directory); Result add_path_internal(const std::string &path, int MAX_DEPTH, int CURRENT_DEPTH); + bool check_path_existence(const std::string &path); public: static Result create(const std::string &task_name, const std::string &working_directory); ~TaskRunner(); @@ -36,6 +38,8 @@ namespace flowhook std::string get_task_name() const { return task.name; } std::string get_task_id() const { return task.id; } Task get_task() const { return task; } + std::vector get_resolved_files() const { return resolved_files; } + bool isIgnored(const std::string &path); diff --git a/src/include/types.h b/src/include/types.h index 1be8cc4..cc9f147 100644 --- a/src/include/types.h +++ b/src/include/types.h @@ -68,7 +68,8 @@ namespace flowhook std::string name = ""; // user assigned name can be duplicated, defaults to the filename of the cwd int watching_depth = 1; std::vector commands = {}; - std::vector paths = {}; + std::vector file_paths = {}; + std::vector dir_paths = {}; std::vector on_success = {}; std::vector on_failure = {}; @@ -79,7 +80,7 @@ namespace flowhook bool isRunning = false; bool isNull() const { - return id.empty() && name.empty() && commands.empty() && paths.empty() && + return id.empty() && name.empty() && commands.empty() && file_paths.empty() && dir_paths.empty() && on_success.empty() && on_failure.empty() && !isActive && !isRunning && watching_depth == 1 && ignored_paths.empty() && ignored_patterns.empty(); } diff --git a/src/task_runner.cpp b/src/task_runner.cpp index a67aefe..7e566ad 100644 --- a/src/task_runner.cpp +++ b/src/task_runner.cpp @@ -222,7 +222,29 @@ Result TaskRunner::delete_command(const string &command) { FWError::make(ErrorCode::COMMAND_NOT_FOUND, "Error: command not found")); } +bool TaskRunner::check_path_existence(const string &path) { + for (auto &p : task.file_paths) { + if (p == path) { + return true; + } + } + for (auto &p : task.dir_paths) { + if (p == path) { + return true; + } + } + return false; +} + Result TaskRunner::add_path(const string &path, int MAX_DEPTH) { + if(check_path_existence(path)) return Result::Ok(); // idempotent + if(!fs::exists(path)) + { + return Result::Err(FWError::make( + ErrorCode::PATH_NOT_FOUND, + "Error: provided path " + path + " does not exist! ✗")); + } + TEST(add_path_internal(path, MAX_DEPTH, 0)); if(fs::is_directory(path)) { @@ -234,6 +256,7 @@ Result TaskRunner::add_path(const string &path, int MAX_DEPTH) { Result TaskRunner::add_path_internal(const string &path, int MAX_DEPTH, int CURRENT_DEPTH) { + if(check_path_existence(path)) return Result::Ok(); // idempotent // if path is directory add each files inside it iteratively if (isIgnored(path)) { FW_LOG("[DEBUG] Path " + path + " matches ignored paths and patterns."); @@ -242,6 +265,7 @@ Result TaskRunner::add_path_internal(const string &path, int MAX_DEPTH, } if (fs::is_directory(path)) { + task.dir_paths.push_back(path); FW_LOG("[DEBUG] Path " + path + " is a directory adding child files recursively..."); for (auto &entry : fs::directory_iterator(path)) { @@ -258,7 +282,7 @@ Result TaskRunner::add_path_internal(const string &path, int MAX_DEPTH, " to task failed. ✗"); continue; } - task.paths.push_back(entry.path().string()); + resolved_files.push_back(entry.path().string()); fw->add_path(entry.path().string()); FW_LOG("[DEBUG] Adding path " + entry.path().string() + @@ -275,7 +299,8 @@ Result TaskRunner::add_path_internal(const string &path, int MAX_DEPTH, } else if (!fs::is_directory(path)) { FW_LOG("[DEBUG] Path " + path + " is a file. Adding to task..."); - task.paths.push_back(path); + task.file_paths.push_back(path); + resolved_files.push_back(path); fw->add_path(path); } return Result::Ok(); @@ -283,15 +308,32 @@ Result TaskRunner::add_path_internal(const string &path, int MAX_DEPTH, Result TaskRunner::delete_path(const string &path) { - for (auto it = task.paths.begin(); it != task.paths.end(); it++) { - if (*it == path) { - task.paths.erase(it); - return Result::Ok(); + if(!fs::exists(path)) + { + return Result::Err(FWError::make( + ErrorCode::PATH_NOT_FOUND, + "Error: provided path " + path + " does not exist! ✗")); + } + + if(!fs::is_directory(path)){ + for (auto it = task.file_paths.begin(); it != task.file_paths.end(); it++) { + if (*it == path) { + task.file_paths.erase(it); + return Result::Ok(); + } + } + } else { + for (auto it = task.dir_paths.begin(); it != task.dir_paths.end(); it++) { + if (*it == path) { + task.dir_paths.erase(it); + return Result::Ok(); + } + } } - } TEST(fw->remove_path(path)); return Result::Err( - FWError::make(ErrorCode::EVENT_NOT_FOUND, "Error: path not found")); + FWError::make(ErrorCode::PATH_NOT_FOUND, "Error: path not found") + ); } Result TaskRunner::add_on_success(const string &command) { @@ -472,8 +514,7 @@ Result TaskRunner::delete_callback(const WatchCallback &callback) { Result TaskRunner::start() { FW_LOG("[DEBUG] Starting TaskRunner..."); if (task.isRunning) { - return Result::Err(FWError::make( - ErrorCode::TASK_ALREADY_RUNNING, "Error: task runner already running")); + return Result::Ok(); // idempotent } task.isRunning = true; diff --git a/tests/integration/test_flowhook_core.cpp b/tests/integration/test_flowhook_core.cpp index 658ee30..3ff5f8c 100644 --- a/tests/integration/test_flowhook_core.cpp +++ b/tests/integration/test_flowhook_core.cpp @@ -10,6 +10,11 @@ using namespace std; using namespace flowhook; namespace fs = std::filesystem; +bool FLOWHOOK_DEBUG = false; +bool FLOWHOOK_VERBOSE = false; +bool FLOWHOOK_QUIET = false; + + struct FlowHookFixture { FlowHookCore* fh; diff --git a/tests/unit/test_filewatcher.cpp b/tests/unit/test_filewatcher.cpp index 2b5b747..aeb3046 100644 --- a/tests/unit/test_filewatcher.cpp +++ b/tests/unit/test_filewatcher.cpp @@ -9,6 +9,11 @@ namespace fs = std::filesystem; using namespace flowhook; using namespace std; +bool FLOWHOOK_DEBUG = false; +bool FLOWHOOK_VERBOSE = false; +bool FLOWHOOK_QUIET = false; + + // --------------------------------------------------------------------------- // Callbacks // --------------------------------------------------------------------------- diff --git a/tests/unit/test_task_runner.cpp b/tests/unit/test_task_runner.cpp index 46882df..a783221 100644 --- a/tests/unit/test_task_runner.cpp +++ b/tests/unit/test_task_runner.cpp @@ -10,6 +10,11 @@ namespace fs = std::filesystem; using namespace flowhook; using namespace std; +bool FLOWHOOK_DEBUG = false; +bool FLOWHOOK_VERBOSE = false; +bool FLOWHOOK_QUIET = false; + + // --------------------------------------------------------------------------- // Callbacks // --------------------------------------------------------------------------- @@ -106,10 +111,6 @@ UTEST_F(TaskRunnerFixture, add_command) { auto r = utest_fixture->tr->add_command("ls"); EXPECT_TRUE(r.isOk()); - if(r.isErr()) - { - cerr << r.unwrapErr().message << endl; - } } UTEST_F(TaskRunnerFixture, add_command_twice) { @@ -152,7 +153,7 @@ UTEST_F(TaskRunnerFixture, add_path) UTEST_F(TaskRunnerFixture, add_path_twice) { ASSERT_TRUE(utest_fixture->tr->add_path("/tmp/tr_test").isOk()); - EXPECT_TRUE(utest_fixture->tr->add_path("/tmp/tr_test").isErr()); + EXPECT_TRUE(utest_fixture->tr->add_path("/tmp/tr_test").isOk()); // idempotent } UTEST_F(TaskRunnerFixture, add_path_empty) { @@ -175,7 +176,8 @@ UTEST_F(TaskRunnerFixture, add_path_file) UTEST_F(TaskRunnerFixture, delete_path) { ASSERT_TRUE(utest_fixture->tr->add_path("/tmp/tr_test").isOk()); - EXPECT_TRUE(utest_fixture->tr->delete_path("/tmp/tr_test").isOk()); + auto result = utest_fixture->tr->delete_path("/tmp/tr_test"); + EXPECT_TRUE(result.isOk()); } UTEST_F(TaskRunnerFixture, delete_path_twice) { @@ -313,6 +315,42 @@ UTEST_F(TaskRunnerFixture, delete_callback_empty) EXPECT_TRUE(utest_fixture->tr->delete_callback(WatchCallback()).isErr()); } +// --------------------------------------------------------------------------- +// Ignored Path/ Ignored Pattern +// --------------------------------------------------------------------------- + +UTEST_F(TaskRunnerFixture, ignored_path) +{ + ASSERT_TRUE(utest_fixture->tr->add_path("/tmp/tr_test").isOk()); + ofstream("/tmp/tr_test_file.txt").close(); + ASSERT_TRUE(utest_fixture->tr->add_ignored_path("/tmp/tr_test/tr_test_file.txt").isOk()); + EXPECT_TRUE(utest_fixture->tr->isIgnored("/tmp/tr_test/tr_test_file.txt")); +} + +UTEST_F(TaskRunnerFixture, ignored_pattern) +{ + ASSERT_TRUE(utest_fixture->tr->add_path("/tmp/tr_test").isOk()); + ASSERT_TRUE(utest_fixture->tr->add_ignored_pattern("*.txt").isOk()); + ofstream("/tmp/tr_test_file.txt").close(); + EXPECT_TRUE(utest_fixture->tr->isIgnored("*.txt")); +} + +UTEST_F(TaskRunnerFixture, remove_ignored) +{ + ASSERT_TRUE(utest_fixture->tr->add_path("/tmp/tr_test").isOk()); + ofstream("/tmp/tr_test_file.txt").close(); + ASSERT_TRUE(utest_fixture->tr->add_ignored_path("/tmp/tr_test/tr_test_file.txt").isOk()); + ASSERT_TRUE(utest_fixture->tr->isIgnored("/tmp/tr_test/tr_test_file.txt")); + ASSERT_TRUE(utest_fixture->tr->remove_ignored_path("/tmp/tr_test/tr_test_file.txt").isOk()); + EXPECT_FALSE(utest_fixture->tr->isIgnored("/tmp/tr_test/tr_test_file.txt")); + + ASSERT_TRUE(utest_fixture->tr->add_ignored_pattern("*.txt").isOk()); + ASSERT_TRUE(utest_fixture->tr->isIgnored("*.txt")); + ASSERT_TRUE(utest_fixture->tr->remove_ignored_pattern("*.txt").isOk()); + EXPECT_FALSE(utest_fixture->tr->isIgnored("*.txt")); +} + + // --------------------------------------------------------------------------- // Execute // --------------------------------------------------------------------------- @@ -326,6 +364,7 @@ UTEST_F(TaskRunnerFixture, execute) ASSERT_TRUE(utest_fixture->tr->add_on_success("ls").isOk()); ASSERT_TRUE(utest_fixture->tr->add_on_failure("ls").isOk()); ASSERT_TRUE(utest_fixture->tr->add_callback(utest_fixture->cb).isOk()); + ASSERT_TRUE(utest_fixture->tr->start().isOk()); WatchEvent e(2, "file", "/tmp/tr_test_file.txt", 0); auto r = utest_fixture->tr->execute(e); EXPECT_TRUE(r.isOk()); @@ -367,7 +406,7 @@ UTEST_F(TaskRunnerFixture, start_twice) ASSERT_TRUE(utest_fixture->tr->add_on_failure("ls").isOk()); ASSERT_TRUE(utest_fixture->tr->add_callback(utest_fixture->cb).isOk()); ASSERT_TRUE(utest_fixture->tr->start().isOk()); - EXPECT_TRUE(utest_fixture->tr->start().isErr()); + EXPECT_TRUE(utest_fixture->tr->start().isOk());// idempotent } // --------------------------------------------------------------------------- @@ -393,7 +432,7 @@ UTEST_F(TaskRunnerFixture, stop) } UTEST_F(TaskRunnerFixture, stop_without_start) { - EXPECT_TRUE(utest_fixture->tr->stop().isErr()); + EXPECT_TRUE(utest_fixture->tr->stop().isOk()); // idempotent } -UTEST_MAIN() \ No newline at end of file +UTEST_MAIN() From d2186766bf7027edaa8d66329e833a12a6ba9632 Mon Sep 17 00:00:00 2001 From: mnasie Date: Tue, 30 Jun 2026 14:32:36 +0300 Subject: [PATCH 05/20] Update Task constructor arguments in unit tests --- tests/unit/test_config_manager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/test_config_manager.cpp b/tests/unit/test_config_manager.cpp index d6e9762..b18c003 100644 --- a/tests/unit/test_config_manager.cpp +++ b/tests/unit/test_config_manager.cpp @@ -37,13 +37,13 @@ UTEST_F_SETUP(ConfigManagerFixture) fs::create_directories("/tmp/cm_test2"); utest_fixture->t = new Task("test_task", "/tmp/cm_test", 3, {"ls"}, - {"/tmp/cm_test_file.txt"}, vector{"ls"}, vector{"ls"}, {"*.o"}, {".git"}, true, true); + {"/tmp/cm_test_file.txt"}, {"/tmp/cm_test"}, vector{"ls"}, vector{"ls"}, {"*.o"}, {".git"}, true, true); utest_fixture->t2 = new Task("test_task2", "/tmp/cm_test2", 3, {"ls"}, - {"/tmp/cm_test_file.txt"}, vector{"ls"}, vector{"cd, ls"}, {"*.o"}, {".git"}, true, true); + {"/tmp/cm_test_file.txt"}, {"/tmp/cm_test"}, vector{"ls"}, vector{"cd, ls"}, {"*.o"}, {".git"}, true, true); utest_fixture->t3 = new Task("test_task1", "/tmp/cm_test", 3, {"cd", "make"}, - {"/tmp/cm_test_file.txt"}, vector{"ls"}, vector{"cd, ls"}, {"*.o"}, {".git"}, true, true); + {"/tmp/cm_test_file.txt"}, {"/tmp/cm_test"}, vector{"ls"}, vector{"cd, ls"}, {"*.o"}, {".git"}, true, true); } UTEST_F_TEARDOWN(ConfigManagerFixture) From 00aec7652dcccde4b885fb58ba7937a3a9b181bf Mon Sep 17 00:00:00 2001 From: mnasie Date: Tue, 30 Jun 2026 15:58:00 +0300 Subject: [PATCH 06/20] Refactor task identification to use paths Standardize task identification by replacing task names with file paths across the API. Update internal logic to ensure idempotency for task activation and start operations. --- .github/workflows/tests.yml | 2 +- src/flowhook_core.cpp | 14 +- tests/integration/test_flowhook_core.cpp | 178 +++++++++++------------ 3 files changed, 93 insertions(+), 101 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e9095c3..d520791 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -31,4 +31,4 @@ jobs: run: cmake --build build - name: Run tests - run: cd build && ./test_config_manager && ./test_task_runner && ./test_session_logger + run: cd build && ./test_config_manager && ./test_task_runner && ./test_session_logger && ./test_flowhook_core diff --git a/src/flowhook_core.cpp b/src/flowhook_core.cpp index 18902a9..45e615f 100644 --- a/src/flowhook_core.cpp +++ b/src/flowhook_core.cpp @@ -157,21 +157,21 @@ namespace flowhook { return std::vector(); } - Result FlowHookCore::activate_task(const std::string &task_name) + Result FlowHookCore::activate_task(const std::string &task_id) { for(auto it = task_runners.begin(); it != task_runners.end(); it++) { - string name = (*it)->get_task_name(); - if(name == task_name) + string id = (*it)->get_task_id(); + if(id == task_id) { (*it)->activate(); config_manager->update_task((*it)->get_task()); - FW_VERBOSE("[FLOWHOOK] Task activated: " + task_name + " ✓"); + FW_VERBOSE("[FLOWHOOK] Task activated: " + task_id + " ✓"); return Result::Ok(); } } - return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_name + " ✗")); + return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); } Result FlowHookCore::deactivate_task(const std::string &task_id) @@ -489,7 +489,7 @@ namespace flowhook { { if(task_runners.empty()) { - return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: no tasks to start ✗")); + return Result::Ok(); //idempotent } for(auto it = task_runners.begin(); it != task_runners.end(); it++) @@ -523,5 +523,7 @@ namespace flowhook { watched_paths = TRY((*it)->get_watch_list(), vector); } return Result>::Ok(watched_paths); + + // activate } } diff --git a/tests/integration/test_flowhook_core.cpp b/tests/integration/test_flowhook_core.cpp index 3ff5f8c..7384b01 100644 --- a/tests/integration/test_flowhook_core.cpp +++ b/tests/integration/test_flowhook_core.cpp @@ -23,6 +23,7 @@ struct FlowHookFixture UTEST_F_SETUP(FlowHookFixture) { fs::create_directories("/tmp/fh_test"); + fs::create_directories("/tmp/fh_test2"); fs::create_directories("/tmp/fh_test/task_test"); setenv("FLOWHOOK_CONFIG_DIR_TEST", "/tmp/fh_test", 1); @@ -62,7 +63,9 @@ UTEST_F(FlowHookFixture, create_task_with_invalid_path) UTEST_F(FlowHookFixture, delete_task) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->delete_task("test_task").isOk()); + auto r = utest_fixture->fh->delete_task("/tmp/fh_test"); + EXPECT_TRUE(r.isOk()); + if(r.isErr()) cout << r.getErrMessage() << endl; } UTEST_F(FlowHookFixture, delete_non_existent_task) { @@ -71,8 +74,8 @@ UTEST_F(FlowHookFixture, delete_non_existent_task) UTEST_F(FlowHookFixture, delete_task_twice) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->delete_task("test_task").isOk()); - EXPECT_TRUE(utest_fixture->fh->delete_task("test_task").isErr()); + EXPECT_TRUE(utest_fixture->fh->delete_task("/tmp/fh_test").isOk()); + EXPECT_TRUE(utest_fixture->fh->delete_task("/tmp/fh_test").isErr()); } // ---------------------------------------------------------------------------------- @@ -82,46 +85,41 @@ UTEST_F(FlowHookFixture, delete_task_twice) UTEST_F(FlowHookFixture, set_task_path) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->set_task_path("test_task", "/tmp/fh_test/task_test").isOk()); + auto r = utest_fixture->fh->set_task_path("/tmp/fh_test", "/tmp/fh_test/task_test"); + EXPECT_TRUE(r.isOk()); } UTEST_F(FlowHookFixture, set_task_path_non_existent_task) { EXPECT_TRUE(utest_fixture->fh->set_task_path("non_existent_task", "/tmp/fh_test/task_test").isErr()); } -UTEST_F(FlowHookFixture, set_task_path_twice) -{ - ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - ASSERT_TRUE(utest_fixture->fh->set_task_path("test_task", "/tmp/fh_test/task_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->set_task_path("test_task", "/tmp/fh_test/task_test").isErr()); -} UTEST_F(FlowHookFixture, set_task_path_invalid_path) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->set_task_path("test_task", "/invalid/path").isErr()); + EXPECT_TRUE(utest_fixture->fh->set_task_path("/tmp/fh_test", "/invalid/path").isErr()); } UTEST_F(FlowHookFixture, set_empty_task_path) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->set_task_path("test_task", "").isErr()); + EXPECT_TRUE(utest_fixture->fh->set_task_path("/tmp/fh_test", "").isErr()); } UTEST_F(FlowHookFixture, delete_task_path) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - ASSERT_TRUE(utest_fixture->fh->set_task_path("test_task", "/tmp/fh_test/task_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->delete_task_path("test_task", "/tmp/fh_test/task_test").isOk()); + ASSERT_TRUE(utest_fixture->fh->set_task_path("/tmp/fh_test", "/tmp/fh_test/task_test").isOk()); + EXPECT_TRUE(utest_fixture->fh->delete_task_path("/tmp/fh_test", "/tmp/fh_test/task_test").isOk()); } UTEST_F(FlowHookFixture, delete_non_existent_task_path) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->delete_task_path("non_existent_task", "/tmp/fh_test/task_test").isErr()); + EXPECT_TRUE(utest_fixture->fh->delete_task_path("/non_existent_task", "/tmp/fh_test/task_test").isErr()); } UTEST_F(FlowHookFixture, delete_task_path_with_wrong_path) { - EXPECT_TRUE(utest_fixture->fh->delete_task_path("test_task", "/wrong/path").isErr()); + EXPECT_TRUE(utest_fixture->fh->delete_task_path("/tmp/fh_test", "/wrong/path").isErr()); } UTEST_F(FlowHookFixture, delete_empty_task_path) { - EXPECT_TRUE(utest_fixture->fh->delete_task_path("test_task", "").isErr()); + EXPECT_TRUE(utest_fixture->fh->delete_task_path("/tmp/fh_test", "").isErr()); } // ---------------------------------------------------------------------------------- @@ -131,7 +129,7 @@ UTEST_F(FlowHookFixture, delete_empty_task_path) UTEST_F(FlowHookFixture, set_task_command) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->set_task_command("test_task", "ls").isOk()); + EXPECT_TRUE(utest_fixture->fh->set_task_command("/tmp/fh_test", "ls").isOk()); } UTEST_F(FlowHookFixture, set_task_command_non_existent_task) { @@ -140,31 +138,31 @@ UTEST_F(FlowHookFixture, set_task_command_non_existent_task) UTEST_F(FlowHookFixture, set_task_command_empty_command) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->set_task_command("test_task", "").isErr()); + EXPECT_TRUE(utest_fixture->fh->set_task_command("/tmp/fh_test", "").isErr()); } UTEST_F(FlowHookFixture, set_task_command_twice) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->set_task_command("test_task", "ls").isOk()); - EXPECT_TRUE(utest_fixture->fh->set_task_command("test_task", "ls").isErr()); + EXPECT_TRUE(utest_fixture->fh->set_task_command("/tmp/fh_test", "ls").isOk()); + EXPECT_TRUE(utest_fixture->fh->set_task_command("/tmp/fh_test", "ls").isErr()); } UTEST_F(FlowHookFixture, delete_task_command) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->set_task_command("test_task", "ls").isOk()); - EXPECT_TRUE(utest_fixture->fh->delete_task_command("test_task", "ls").isOk()); + EXPECT_TRUE(utest_fixture->fh->set_task_command("/tmp/fh_test", "ls").isOk()); + EXPECT_TRUE(utest_fixture->fh->delete_task_command("/tmp/fh_test", "ls").isOk()); } UTEST_F(FlowHookFixture, delete_task_command_twice) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->set_task_command("test_task", "ls").isOk()); - EXPECT_TRUE(utest_fixture->fh->delete_task_command("test_task", "ls").isOk()); - EXPECT_TRUE(utest_fixture->fh->delete_task_command("test_task", "ls").isErr()); + EXPECT_TRUE(utest_fixture->fh->set_task_command("/tmp/fh_test", "ls").isOk()); + EXPECT_TRUE(utest_fixture->fh->delete_task_command("/tmp/fh_test", "ls").isOk()); + EXPECT_TRUE(utest_fixture->fh->delete_task_command("/tmp/fh_test", "ls").isErr()); } UTEST_F(FlowHookFixture, delete_nonexistent_task_command) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->delete_task_command("test_task", "ls").isErr()); + EXPECT_TRUE(utest_fixture->fh->delete_task_command("/tmp/fh_test", "ls").isErr()); } // ---------------------------------------------------------------------------------- @@ -174,8 +172,8 @@ UTEST_F(FlowHookFixture, delete_nonexistent_task_command) UTEST_F(FlowHookFixture, delete_task_on_success) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - ASSERT_TRUE(utest_fixture->fh->set_task_on_success("test_task", "ls").isOk()); - EXPECT_TRUE(utest_fixture->fh->delete_task_on_success("test_task", "ls").isOk()); + ASSERT_TRUE(utest_fixture->fh->set_task_on_success("/tmp/fh_test", "ls").isOk()); + EXPECT_TRUE(utest_fixture->fh->delete_task_on_success("/tmp/fh_test", "ls").isOk()); } UTEST_F(FlowHookFixture, delete_task_on_success_non_existent_task) { @@ -184,26 +182,26 @@ UTEST_F(FlowHookFixture, delete_task_on_success_non_existent_task) UTEST_F(FlowHookFixture, delete_task_on_success_non_existent_command) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->delete_task_on_success("test_task", "non_existent_command").isErr()); + EXPECT_TRUE(utest_fixture->fh->delete_task_on_success("/tmp/fh_test", "non_existent_command").isErr()); } UTEST_F(FlowHookFixture, delete_task_on_success_twice) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - ASSERT_TRUE(utest_fixture->fh->set_task_on_success("test_task", "ls").isOk()); - EXPECT_TRUE(utest_fixture->fh->delete_task_on_success("test_task", "ls").isOk()); + ASSERT_TRUE(utest_fixture->fh->set_task_on_success("/tmp/fh_test", "ls").isOk()); + EXPECT_TRUE(utest_fixture->fh->delete_task_on_success("/tmp/fh_test", "ls").isOk()); EXPECT_TRUE(utest_fixture->fh->delete_task_on_success("test_task", "ls").isErr()); } UTEST_F(FlowHookFixture, delete_task_on_success_empty_command) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->delete_task_on_success("test_task", "").isErr()); + EXPECT_TRUE(utest_fixture->fh->delete_task_on_success("/tmp/fh_test", "").isErr()); } UTEST_F(FlowHookFixture, delete_task_on_failure) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - ASSERT_TRUE(utest_fixture->fh->set_task_on_failure("test_task", "ls").isOk()); - EXPECT_TRUE(utest_fixture->fh->delete_task_on_failure("test_task", "ls").isOk()); + ASSERT_TRUE(utest_fixture->fh->set_task_on_failure("/tmp/fh_test", "ls").isOk()); + EXPECT_TRUE(utest_fixture->fh->delete_task_on_failure("/tmp/fh_test", "ls").isOk()); } UTEST_F(FlowHookFixture, delete_task_on_failure_non_existent_task) { @@ -212,19 +210,19 @@ UTEST_F(FlowHookFixture, delete_task_on_failure_non_existent_task) UTEST_F(FlowHookFixture, delete_task_on_failure_non_existent_command) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->delete_task_on_failure("test_task", "non_existent_command").isErr()); + EXPECT_TRUE(utest_fixture->fh->delete_task_on_failure("/tmp/fh_test", "non_existent_command").isErr()); } UTEST_F(FlowHookFixture, delete_task_on_failure_twice) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - ASSERT_TRUE(utest_fixture->fh->set_task_on_failure("test_task", "ls").isOk()); - EXPECT_TRUE(utest_fixture->fh->delete_task_on_failure("test_task", "ls").isOk()); - EXPECT_TRUE(utest_fixture->fh->delete_task_on_failure("test_task", "ls").isErr()); + ASSERT_TRUE(utest_fixture->fh->set_task_on_failure("/tmp/fh_test", "ls").isOk()); + EXPECT_TRUE(utest_fixture->fh->delete_task_on_failure("/tmp/fh_test", "ls").isOk()); + EXPECT_TRUE(utest_fixture->fh->delete_task_on_failure("/tmp/fh_test", "ls").isErr()); } UTEST_F(FlowHookFixture, delete_task_on_failure_empty_command) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->delete_task_on_failure("test_task", "").isErr()); + EXPECT_TRUE(utest_fixture->fh->delete_task_on_failure("/tmp/fh_test", "").isErr()); } // ---------------------------------------------------------------------------------- @@ -234,11 +232,11 @@ UTEST_F(FlowHookFixture, delete_task_on_failure_empty_command) UTEST_F(FlowHookFixture, start_task) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->start_task("test_task").isOk()); + EXPECT_TRUE(utest_fixture->fh->start_task("/tmp/fh_test").isOk()); // For now, we assume that the `start` call on TaskRunner succeeds. // In a real integration test, one might want to verify the task is actually running. // This is a minimal test to cover the FlowHookCore API. - utest_fixture->fh->stop_task("test_task").isOk(); // Clean up + utest_fixture->fh->stop_task("/tmp/fh_test").isOk(); // Clean up } UTEST_F(FlowHookFixture, start_task_non_existent) { @@ -247,15 +245,15 @@ UTEST_F(FlowHookFixture, start_task_non_existent) UTEST_F(FlowHookFixture, start_task_twice) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - ASSERT_TRUE(utest_fixture->fh->start_task("test_task").isOk()); - EXPECT_TRUE(utest_fixture->fh->start_task("test_task").isErr()); - utest_fixture->fh->stop_task("test_task").isOk(); // Clean up + ASSERT_TRUE(utest_fixture->fh->start_task("/tmp/fh_test").isOk()); + EXPECT_TRUE(utest_fixture->fh->start_task("/tmp/fh_test").isErr()); + utest_fixture->fh->stop_task("/tmp/fh_test").isOk(); // Clean up } UTEST_F(FlowHookFixture, stop_task) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - ASSERT_TRUE(utest_fixture->fh->start_task("test_task").isOk()); - EXPECT_TRUE(utest_fixture->fh->stop_task("test_task").isOk()); + ASSERT_TRUE(utest_fixture->fh->start_task("/tmp/fh_test").isOk()); + EXPECT_TRUE(utest_fixture->fh->stop_task("/tmp/fh_test").isOk()); } UTEST_F(FlowHookFixture, stop_task_non_existent) { @@ -264,9 +262,9 @@ UTEST_F(FlowHookFixture, stop_task_non_existent) UTEST_F(FlowHookFixture, stop_task_twice) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - ASSERT_TRUE(utest_fixture->fh->start_task("test_task").isOk()); - ASSERT_TRUE(utest_fixture->fh->stop_task("test_task").isOk()); - EXPECT_TRUE(utest_fixture->fh->stop_task("test_task").isErr()); // TaskRunner::stop() might return error if not running + ASSERT_TRUE(utest_fixture->fh->start_task("/tmp/fh_test").isOk()); + ASSERT_TRUE(utest_fixture->fh->stop_task("/tmp/fh_test").isOk()); + EXPECT_TRUE(utest_fixture->fh->stop_task("/tmp/fh_test").isErr()); // TaskRunner::stop() might return error if not running } // ---------------------------------------------------------------------------------- @@ -276,7 +274,9 @@ UTEST_F(FlowHookFixture, stop_task_twice) UTEST_F(FlowHookFixture, activate_task) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->activate_task("test_task").isOk()); + auto r = utest_fixture->fh->activate_task("/tmp/fh_test"); + EXPECT_TRUE(r.isOk()); + if(r.isErr()) cout << r.getErrMessage() << endl; } UTEST_F(FlowHookFixture, activate_task_non_existent) { @@ -285,14 +285,14 @@ UTEST_F(FlowHookFixture, activate_task_non_existent) UTEST_F(FlowHookFixture, activate_task_twice) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - ASSERT_TRUE(utest_fixture->fh->activate_task("test_task").isOk()); - EXPECT_TRUE(utest_fixture->fh->activate_task("test_task").isOk()); // TaskRunner::activate can be called multiple times + ASSERT_TRUE(utest_fixture->fh->activate_task("/tmp/fh_test").isOk()); + EXPECT_TRUE(utest_fixture->fh->activate_task("/tmp/fh_test").isOk()); // idempotent } UTEST_F(FlowHookFixture, deactivate_task) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - ASSERT_TRUE(utest_fixture->fh->activate_task("test_task").isOk()); - EXPECT_TRUE(utest_fixture->fh->deactivate_task("test_task").isOk()); + ASSERT_TRUE(utest_fixture->fh->activate_task("/tmp/fh_test").isOk()); + EXPECT_TRUE(utest_fixture->fh->deactivate_task("/tmp/fh_test").isOk()); } UTEST_F(FlowHookFixture, deactivate_task_non_existent) { @@ -301,9 +301,9 @@ UTEST_F(FlowHookFixture, deactivate_task_non_existent) UTEST_F(FlowHookFixture, deactivate_task_twice) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - ASSERT_TRUE(utest_fixture->fh->activate_task("test_task").isOk()); - ASSERT_TRUE(utest_fixture->fh->deactivate_task("test_task").isOk()); - EXPECT_TRUE(utest_fixture->fh->deactivate_task("test_task").isOk()); // TaskRunner::deactivate can be called multiple times + ASSERT_TRUE(utest_fixture->fh->activate_task("/tmp/fh_test").isOk()); + ASSERT_TRUE(utest_fixture->fh->deactivate_task("/tmp/fh_test").isOk()); + EXPECT_TRUE(utest_fixture->fh->deactivate_task("/tmp/fh_test").isOk()); // idempotent } // ---------------------------------------------------------------------------------- @@ -323,19 +323,10 @@ UTEST_F(FlowHookFixture, start_all_one_task) UTEST_F(FlowHookFixture, start_all_multiple_tasks) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task_1", "/tmp/fh_test").isOk()); - ASSERT_TRUE(utest_fixture->fh->create_task("test_task_2", "/tmp/fh_test").isOk()); + ASSERT_TRUE(utest_fixture->fh->create_task("test_task_2", "/tmp/fh_test2").isOk()); EXPECT_TRUE(utest_fixture->fh->start_all().isOk()); utest_fixture->fh->stop_all().isOk(); // Clean up } -UTEST_F(FlowHookFixture, start_all_some_already_running) -{ - ASSERT_TRUE(utest_fixture->fh->create_task("test_task_1", "/tmp/fh_test").isOk()); - ASSERT_TRUE(utest_fixture->fh->create_task("test_task_2", "/tmp/fh_test").isOk()); - ASSERT_TRUE(utest_fixture->fh->start_task("test_task_1").isOk()); - EXPECT_TRUE(utest_fixture->fh->start_all().isErr()); // Expect error because task_1 is already running - utest_fixture->fh->stop_all().isOk(); // Clean up -} - UTEST_F(FlowHookFixture, stop_all_no_tasks) { EXPECT_TRUE(utest_fixture->fh->stop_all().isErr()); @@ -343,20 +334,20 @@ UTEST_F(FlowHookFixture, stop_all_no_tasks) UTEST_F(FlowHookFixture, stop_all_one_task) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task_1", "/tmp/fh_test").isOk()); - ASSERT_TRUE(utest_fixture->fh->start_task("test_task_1").isOk()); + ASSERT_TRUE(utest_fixture->fh->start_task("/tmp/fh_test").isOk()); EXPECT_TRUE(utest_fixture->fh->stop_all().isOk()); } UTEST_F(FlowHookFixture, stop_all_multiple_tasks) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task_1", "/tmp/fh_test").isOk()); - ASSERT_TRUE(utest_fixture->fh->create_task("test_task_2", "/tmp/fh_test").isOk()); + ASSERT_TRUE(utest_fixture->fh->create_task("test_task_2", "/tmp/fh_test2").isOk()); ASSERT_TRUE(utest_fixture->fh->start_all().isOk()); EXPECT_TRUE(utest_fixture->fh->stop_all().isOk()); } UTEST_F(FlowHookFixture, start_active_no_tasks) { - EXPECT_TRUE(utest_fixture->fh->start_active().isErr()); + EXPECT_TRUE(utest_fixture->fh->start_active().isOk()); // idempotent } UTEST_F(FlowHookFixture, start_active_no_active_tasks) { @@ -368,28 +359,27 @@ UTEST_F(FlowHookFixture, start_active_no_active_tasks) UTEST_F(FlowHookFixture, start_active_one_active_task) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task_1", "/tmp/fh_test").isOk()); - ASSERT_TRUE(utest_fixture->fh->create_task("test_task_2", "/tmp/fh_test").isOk()); - ASSERT_TRUE(utest_fixture->fh->activate_task("test_task_1").isOk()); + ASSERT_TRUE(utest_fixture->fh->activate_task("/tmp/fh_test").isOk()); EXPECT_TRUE(utest_fixture->fh->start_active().isOk()); - utest_fixture->fh->stop_task("test_task_1").isOk(); // Clean up + utest_fixture->fh->stop_task("/tmp/fh_test").isOk(); // Clean up } UTEST_F(FlowHookFixture, start_active_multiple_active_tasks) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task_1", "/tmp/fh_test").isOk()); - ASSERT_TRUE(utest_fixture->fh->create_task("test_task_2", "/tmp/fh_test").isOk()); - ASSERT_TRUE(utest_fixture->fh->activate_task("test_task_1").isOk()); - ASSERT_TRUE(utest_fixture->fh->activate_task("test_task_2").isOk()); + ASSERT_TRUE(utest_fixture->fh->create_task("test_task_2", "/tmp/fh_test2").isOk()); + ASSERT_TRUE(utest_fixture->fh->activate_task("/tmp/fh_test").isOk()); + ASSERT_TRUE(utest_fixture->fh->activate_task("/tmp/fh_test2").isOk()); EXPECT_TRUE(utest_fixture->fh->start_active().isOk()); utest_fixture->fh->stop_all().isOk(); // Clean up } UTEST_F(FlowHookFixture, start_active_some_active_some_inactive) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task_1", "/tmp/fh_test").isOk()); - ASSERT_TRUE(utest_fixture->fh->create_task("test_task_2", "/tmp/fh_test").isOk()); - ASSERT_TRUE(utest_fixture->fh->activate_task("test_task_1").isOk()); + ASSERT_TRUE(utest_fixture->fh->create_task("test_task_2", "/tmp/fh_test2").isOk()); + ASSERT_TRUE(utest_fixture->fh->activate_task("/tmp/fh_test").isOk()); // test_task_2 remains inactive EXPECT_TRUE(utest_fixture->fh->start_active().isOk()); - utest_fixture->fh->stop_task("test_task_1").isOk(); // Clean up + utest_fixture->fh->stop_task("/tmp/fh_test").isOk(); // Clean up } // ---------------------------------------------------------------------------------- @@ -413,22 +403,22 @@ UTEST_F(FlowHookFixture, get_tasks_one_task) UTEST_F(FlowHookFixture, get_tasks_multiple_tasks) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task_1", "/tmp/fh_test").isOk()); - ASSERT_TRUE(utest_fixture->fh->create_task("test_task_2", "/tmp/fh_test").isOk()); + ASSERT_TRUE(utest_fixture->fh->create_task("test_task_2", "/tmp/fh_test2").isOk()); std::vector tasks = utest_fixture->fh->get_tasks(); EXPECT_EQ(tasks.size(), 2); // Tasks should be in the order they were created or added to task_runners - EXPECT_EQ(tasks[0].name, "test_task_1"); - EXPECT_EQ(tasks[1].name, "test_task_2"); + EXPECT_EQ(tasks[0].id, "/tmp/fh_test"); + EXPECT_EQ(tasks[1].id, "/tmp/fh_test2"); } UTEST_F(FlowHookFixture, get_tasks_after_deletion) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task_1", "/tmp/fh_test").isOk()); - ASSERT_TRUE(utest_fixture->fh->create_task("test_task_2", "/tmp/fh_test").isOk()); - ASSERT_TRUE(utest_fixture->fh->delete_task("test_task_1").isOk()); + ASSERT_TRUE(utest_fixture->fh->create_task("test_task_2", "/tmp/fh_test2").isOk()); + ASSERT_TRUE(utest_fixture->fh->delete_task("/tmp/fh_test2").isOk()); std::vector tasks = utest_fixture->fh->get_tasks(); EXPECT_EQ(tasks.size(), 1); - EXPECT_EQ(tasks[0].name, "test_task_2"); + EXPECT_EQ(tasks[0].id, "/tmp/fh_test"); } @@ -439,34 +429,34 @@ UTEST_F(FlowHookFixture, get_tasks_after_deletion) UTEST_F(FlowHookFixture, set_task_on_success) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->set_task_on_success("test_task", "ls").isOk()); + EXPECT_TRUE(utest_fixture->fh->set_task_on_success("/tmp/fh_test", "ls").isOk()); } UTEST_F(FlowHookFixture, set_task_on_failure) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->set_task_on_failure("test_task", "ls").isOk()); + EXPECT_TRUE(utest_fixture->fh->set_task_on_failure("/tmp/fh_test", "ls").isOk()); } UTEST_F(FlowHookFixture, set_task_on_success_twice) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->set_task_on_success("test_task", "ls").isOk()); - EXPECT_TRUE(utest_fixture->fh->set_task_on_success("test_task", "ls").isErr()); + EXPECT_TRUE(utest_fixture->fh->set_task_on_success("/tmp/fh_test", "ls").isOk()); + EXPECT_TRUE(utest_fixture->fh->set_task_on_success("/tmp/fh_test", "ls").isErr()); } UTEST_F(FlowHookFixture, set_task_on_failure_twice) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->set_task_on_failure("test_task", "ls").isOk()); - EXPECT_TRUE(utest_fixture->fh->set_task_on_failure("test_task", "ls").isErr()); + EXPECT_TRUE(utest_fixture->fh->set_task_on_failure("/tmp/fh_test", "ls").isOk()); + EXPECT_TRUE(utest_fixture->fh->set_task_on_failure("/tmp/fh_test", "ls").isErr()); } UTEST_F(FlowHookFixture, set_empty_task_on_success) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->set_task_on_success("test_task", "").isErr()); + EXPECT_TRUE(utest_fixture->fh->set_task_on_success("/tmp/fh_test", "").isErr()); } UTEST_F(FlowHookFixture, set_empty_task_on_failure) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task", "/tmp/fh_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->set_task_on_failure("test_task", "").isErr()); + EXPECT_TRUE(utest_fixture->fh->set_task_on_failure("/tmp/fh_test", "").isErr()); } UTEST_F(FlowHookFixture, set_task_on_success_nonexistent_task) { From 8071f3974116a5523a0a51c2101415bee5e01b79 Mon Sep 17 00:00:00 2001 From: mnasie Date: Tue, 30 Jun 2026 16:00:55 +0300 Subject: [PATCH 07/20] Increase default max depth for path watching --- src/include/task_runner.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/include/task_runner.h b/src/include/task_runner.h index e2e1465..e4ab35e 100644 --- a/src/include/task_runner.h +++ b/src/include/task_runner.h @@ -60,7 +60,7 @@ namespace flowhook Result add_on_failure(const std::string &command); Result delete_on_failure(const std::string &command); - Result add_path(const std::string &path, int MAX_DEPTH = 1); + Result add_path(const std::string &path, int MAX_DEPTH = 3); Result delete_path(const std::string &path); Result add_ignored_path(const std::string &path); From 254f2b8d666805529d07d44df396bf6f1a2dd91f Mon Sep 17 00:00:00 2001 From: mnasie Date: Tue, 30 Jun 2026 16:07:22 +0300 Subject: [PATCH 08/20] Persist task state and update default watching depth --- src/config_manager.cpp | 6 ++++++ src/include/task_runner.h | 2 +- src/include/types.h | 4 ++-- src/task_runner.cpp | 4 ++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/config_manager.cpp b/src/config_manager.cpp index 5a2e70a..bed3cf9 100644 --- a/src/config_manager.cpp +++ b/src/config_manager.cpp @@ -160,6 +160,8 @@ namespace flowhook _task.ignored_patterns = _ignored_patterns; _task.isActive = json_task.at("isActive"); + _task.watching_depth = json_task.at("watching_depth"); + _task.isRunning = json_task.at("isRunning"); return Result::Ok(_task); } @@ -204,6 +206,8 @@ namespace flowhook _json_task["ignored_patterns"].push_back(pattern); } _json_task["isActive"] = task.isActive; + _json_task["watching_depth"] = task.watching_depth; + _json_task["isRunning"] = task.isRunning; return Result::Ok(_json_task); } @@ -300,6 +304,8 @@ namespace flowhook _task.ignored_paths = it->at("ignored_paths"); _task.ignored_patterns = it->at("ignored_patterns"); _task.isActive = it->at("isActive"); + _task.watching_depth = it->at("watching_depth"); + _task.isRunning = it->at("isRunning"); tasks.push_back(_task); } return Result>::Ok(tasks); diff --git a/src/include/task_runner.h b/src/include/task_runner.h index e4ab35e..b75b150 100644 --- a/src/include/task_runner.h +++ b/src/include/task_runner.h @@ -60,7 +60,7 @@ namespace flowhook Result add_on_failure(const std::string &command); Result delete_on_failure(const std::string &command); - Result add_path(const std::string &path, int MAX_DEPTH = 3); + Result add_path(const std::string &path); Result delete_path(const std::string &path); Result add_ignored_path(const std::string &path); diff --git a/src/include/types.h b/src/include/types.h index cc9f147..d340f3a 100644 --- a/src/include/types.h +++ b/src/include/types.h @@ -66,7 +66,7 @@ namespace flowhook { std::string id = ""; // this is a name that will be used internally and always unique, not assigned by the user. Mostly the absolute path of the cwd std::string name = ""; // user assigned name can be duplicated, defaults to the filename of the cwd - int watching_depth = 1; + int watching_depth = 3; std::vector commands = {}; std::vector file_paths = {}; std::vector dir_paths = {}; @@ -81,7 +81,7 @@ namespace flowhook bool isNull() const { return id.empty() && name.empty() && commands.empty() && file_paths.empty() && dir_paths.empty() && - on_success.empty() && on_failure.empty() && !isActive && !isRunning && watching_depth == 1 && + on_success.empty() && on_failure.empty() && !isActive && !isRunning && watching_depth == 3 && ignored_paths.empty() && ignored_patterns.empty(); } }; diff --git a/src/task_runner.cpp b/src/task_runner.cpp index 7e566ad..d45e560 100644 --- a/src/task_runner.cpp +++ b/src/task_runner.cpp @@ -236,7 +236,7 @@ bool TaskRunner::check_path_existence(const string &path) { return false; } -Result TaskRunner::add_path(const string &path, int MAX_DEPTH) { +Result TaskRunner::add_path(const string &path) { if(check_path_existence(path)) return Result::Ok(); // idempotent if(!fs::exists(path)) { @@ -245,7 +245,7 @@ Result TaskRunner::add_path(const string &path, int MAX_DEPTH) { "Error: provided path " + path + " does not exist! ✗")); } - TEST(add_path_internal(path, MAX_DEPTH, 0)); + TEST(add_path_internal(path, task.watching_depth, 0)); if(fs::is_directory(path)) { FW_VERBOSE("[FLOWHOOK] Adding " + path + "'s children to task runner completed. ✓"); From e0af4f386146a707bb09e6c30ab1c582e04142fe Mon Sep 17 00:00:00 2001 From: mnasie Date: Tue, 30 Jun 2026 17:18:34 +0300 Subject: [PATCH 09/20] Add set command and implement task settings - Added `set` CLI command to configure active status and watching depth. - Updated core logic to persist task settings and validation. - Enabled running tasks by active status. - Cleaned up output formatting in list command. - Removed unused `isRunning` property from task configuration. --- CMakeLists.txt | 3 +- cli/src/commands/list.cpp | 18 ++++---- cli/src/commands/remove.cpp | 4 +- cli/src/commands/run.cpp | 90 +++++++++++++++++++++---------------- cli/src/commands/set.cpp | 60 +++++++++++++++++++++++++ cli/src/main.cpp | 2 + src/config_manager.cpp | 3 -- src/flowhook_core.cpp | 35 ++++++++++----- src/include/flowhook_core.h | 1 + src/task_runner.cpp | 10 ++--- 10 files changed, 156 insertions(+), 70 deletions(-) create mode 100644 cli/src/commands/set.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 424b37e..bdd60fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,7 +51,8 @@ add_executable(flowhook_cli cli/src/main.cpp cli/src/commands/run.cpp cli/src/commands/init.cpp cli/src/commands/list.cpp - cli/src/commands/remove.cpp) + cli/src/commands/remove.cpp + cli/src/commands/set.cpp) target_link_libraries(flowhook_cli PRIVATE flowhook_lib) target_include_directories(flowhook_cli PRIVATE cli/src) diff --git a/cli/src/commands/list.cpp b/cli/src/commands/list.cpp index 3622970..b230277 100644 --- a/cli/src/commands/list.cpp +++ b/cli/src/commands/list.cpp @@ -46,7 +46,7 @@ namespace flowhook_cli { if (list_tasks) { - std::cout << "All registered Tasks:\n" << std::endl; + std::cout << "\nAll registered Tasks:\n" << std::endl; for (const auto& task : tasks) { std::cout << " Name: " << task.name << std::endl; std::cout << " Working Directory: " << task.id << "\n" << std::endl; @@ -60,12 +60,12 @@ namespace flowhook_cli { return; } - std::cout << "Watched Directories:\n" << std::endl; + std::cout << "\nWatched Directories:\n" << std::endl; for(const auto& path : ts->dir_paths) { std::cout << " " << path << std::endl; } - std::cout << "Standalone File:\n" << std::endl; + std::cout << "\nStandalone File:\n" << std::endl; for(const auto& path : ts->file_paths) { std::cout << " " << path << std::endl; } @@ -83,7 +83,7 @@ namespace flowhook_cli { return; } - std::cout << "Commands:\n" << std::endl; + std::cout << "\nCommands:\n" << std::endl; for(const auto& command : ts->commands) { std::cout << " " << command << std::endl; } @@ -95,16 +95,14 @@ namespace flowhook_cli { return; } - std::cout << "Ignored paths:\n" << std::endl; + std::cout << "\nIgnored paths:\n" << std::endl; for(const auto& ignored_paths : ts->ignored_paths) { std::cout << " " << ignored_paths << std::endl; } - std::cout << std::endl; - std::cout << "Ignored patterns:\n" << std::endl; + std::cout << "\nIgnored patterns:\n" << std::endl; for(const auto& ignored_pattern : ts->ignored_patterns) { std::cout << " " << ignored_pattern << std::endl; } - std::cout << std::endl; } if (list_on_success) { @@ -113,7 +111,7 @@ namespace flowhook_cli { return; } - std::cout << "On success:\n" << std::endl; + std::cout << "\nOn success:\n" << std::endl; for(const auto& command : ts->on_success) { std::cout << " " << command << std::endl; } @@ -126,7 +124,7 @@ namespace flowhook_cli { return; } - std::cout << "On failure:\n" << std::endl; + std::cout << "\nOn failure:\n" << std::endl; for(const auto& command : ts->on_failure) { std::cout << " " << command << std::endl; } diff --git a/cli/src/commands/remove.cpp b/cli/src/commands/remove.cpp index a0d055b..fbae21b 100644 --- a/cli/src/commands/remove.cpp +++ b/cli/src/commands/remove.cpp @@ -10,15 +10,13 @@ void register_remove(CLI::App *app, flowhook::FlowHookCore *fh) { // add subcommand auto *remove = - app->add_subcommand("remove", "remove an element from a task\n"); + app->add_subcommand("remove", "remove an element from a task\n // removes the task itself if no OPTION is provided"); // this is a purely ceremonial flag(so that CLI11 won't say there is unrecognized flag) // the actual parsing of verbose and setting of the environment variable is done in main.cpp remove->add_flag("--debug", FLOWHOOK_DEBUG, "Enable debug output. \n// is an extremely detailed output that shows every step of the process."); remove->add_flag("--verbose", FLOWHOOK_VERBOSE, "Enable verbose output. \n// shows a summary of the process."); - - static std::string task_id = ""; static std::string removed_path = ""; remove->add_option("--path", removed_path, "Path to the task [optional] \n// use to add a new path to be watched.\n"); diff --git a/cli/src/commands/run.cpp b/cli/src/commands/run.cpp index da401d8..9c1c02a 100644 --- a/cli/src/commands/run.cpp +++ b/cli/src/commands/run.cpp @@ -1,47 +1,61 @@ #include "../../src/include/flowhook_core.h" -#include "../include/CLI11.hpp" #include "../../src/include/macros.hpp" +#include "../include/CLI11.hpp" +#include namespace fs = std::filesystem; namespace flowhook_cli { - void register_run(CLI::App* app, flowhook::FlowHookCore* fh) - { - auto* run = app->add_subcommand("run", "running a task"); - - // this is a purely ceremonial flag(so that CLI11 won't say there is unrecognized flag) - // the actual parsing of verbose and setting of the environment variable is done in main.cpp - run->add_flag("--debug", FLOWHOOK_DEBUG, "Enable debug output. \n// is an extremely detailed output that shows every step of the process."); - run->add_flag("--verbose", FLOWHOOK_VERBOSE, "Enable verbose output. \n// shows a summary of the process."); - run->add_flag("--quiet", FLOWHOOK_QUIET, "Removes build output from terminal"); - - - static bool run_all = false; - run->add_flag("--all", run_all, "Run all task instances"); - - run->callback([=]() mutable { - std::string run_task_id = fs::current_path().string(); - - if (run_all) { - auto r = fh->start_all(); - if (r.isErr()) { - std::cerr << "Failed to run all tasks: " << r.getErrMessage() - << std::endl; - return; - } - pause(); - std::cout << "Watching all tasks..." << std::endl; - return; - } else if (!run_task_id.empty()) { - auto r = fh->start_task(run_task_id); - if (r.isErr()) { - std::cerr << "Failed to run task: " << r.getErrMessage() - << std::endl; - return; - } - std::cout << "Watching " << run_task_id << " (use --quiet to suppress build output)" << std::endl; } - pause(); - }); +void register_run(CLI::App *app, flowhook::FlowHookCore *fh) { + auto *run = app->add_subcommand("run", "running a task"); + + // this is a purely ceremonial flag(so that CLI11 won't say there is + // unrecognized flag) the actual parsing of verbose and setting of the + // environment variable is done in main.cpp + run->add_flag("--debug", FLOWHOOK_DEBUG, + "Enable debug output. \n// is an extremely detailed output " + "that shows every step of the process."); + run->add_flag("--verbose", FLOWHOOK_VERBOSE, + "Enable verbose output. \n// shows a summary of the process."); + run->add_flag("--quiet", FLOWHOOK_QUIET, + "Removes build output from terminal"); + + static bool run_all = false; + run->add_flag("--all", run_all, "Run all task instances labeled as active."); + + static bool run_active = false; + run->add_flag("--active", run_active, "Run active task instances"); + + run->callback([=]() mutable { + std::string run_task_id = fs::current_path().string(); + + if (run_all) { + auto r = fh->start_all(); + if (r.isErr()) { + std::cerr << "Failed to run all tasks: " << r.getErrMessage() + << std::endl; + return; + } + pause(); + } else if (run_active) { + auto r = fh->start_active(); + if (r.isErr()) { + std::cerr << "Failed to run active tasks: " << r.getErrMessage() + << std::endl; + return; + } + pause(); + } else if (!run_task_id.empty()) { + auto r = fh->start_task(run_task_id); + if (r.isErr()) { + std::cerr << "Failed to run task: " << r.getErrMessage() << std::endl; + return; + } } + std::cout << "Watching " << run_task_id + << " (use --quiet to suppress build output)" << std::endl; + pause(); + }); } +} // namespace flowhook_cli diff --git a/cli/src/commands/set.cpp b/cli/src/commands/set.cpp new file mode 100644 index 0000000..84ef5cf --- /dev/null +++ b/cli/src/commands/set.cpp @@ -0,0 +1,60 @@ +#include "../../src/include/flowhook_core.h" +#include "../include/CLI11.hpp" +#include "../../src/include/macros.hpp" + + +namespace fs = std::filesystem; + +namespace flowhook_cli { + void register_set(CLI::App* app, flowhook::FlowHookCore* fh) + { + auto* set = app->add_subcommand("set", "setting a property for the task"); + + // this is a purely ceremonial flag(so that CLI11 won't say there is unrecognized flag) + // the actual parsing of verbose and setting of the environment variable is done in main.cpp + set->add_flag("--debug", FLOWHOOK_DEBUG, "Enable debug output. \n// is an extremely detailed output that shows every step of the process."); + set->add_flag("--verbose", FLOWHOOK_VERBOSE, "Enable verbose output. \n// shows a summary of the process."); + + + static bool active = false; + set->add_flag("--active", active, "Set task as active \n // enables you to run specified tasks with the `run active` command"); + + static bool deactive = false; + set->add_flag("--deactive", deactive, "Set a task enables active as deactive"); + + static int depth = 0; + set->add_option("--depth", depth, "Set the depth to watch from your working directory."); + + set->callback([=]() mutable { + std::string set_task_id = fs::current_path().string(); + + if (active) { + auto r = fh->activate_task(set_task_id); + if (r.isErr()) { + std::cerr << "Failed to set task as active: " << r.getErrMessage() + << std::endl; + return; + } + std::cout << "Task " << set_task_id << " is now active" << std::endl; + } + if(deactive) { + auto r = fh->deactivate_task(set_task_id); + if (r.isErr()) { + std::cerr << "Failed to set task as deactive: " << r.getErrMessage() + << std::endl; + return; + } + std::cout << "Task " << set_task_id << " is now deactive" << std::endl; + } + if(depth > 0) { + auto r = fh->set_depth(set_task_id, depth); + if (r.isErr()) { + std::cerr << "Failed to set depth: " << r.getErrMessage() + << std::endl; + return; + } + std::cout << "Depth set to " << depth << std::endl; + } + }); + } +} diff --git a/cli/src/main.cpp b/cli/src/main.cpp index 66236a2..4494286 100644 --- a/cli/src/main.cpp +++ b/cli/src/main.cpp @@ -15,6 +15,7 @@ namespace flowhook_cli { void register_init(CLI::App*, flowhook::FlowHookCore*); void register_list(CLI::App*, flowhook::FlowHookCore*); void register_remove(CLI::App*, flowhook::FlowHookCore*); + void register_set(CLI::App*, flowhook::FlowHookCore*); } namespace fs = std::filesystem; @@ -118,6 +119,7 @@ int main(int argc, char **argv) { flowhook_cli::register_run(&app, fh); flowhook_cli::register_list(&app, fh); flowhook_cli::register_remove(&app, fh); + flowhook_cli::register_set(&app, fh); CLI11_PARSE(app, argc, argv); diff --git a/src/config_manager.cpp b/src/config_manager.cpp index bed3cf9..2c2b86a 100644 --- a/src/config_manager.cpp +++ b/src/config_manager.cpp @@ -161,7 +161,6 @@ namespace flowhook _task.isActive = json_task.at("isActive"); _task.watching_depth = json_task.at("watching_depth"); - _task.isRunning = json_task.at("isRunning"); return Result::Ok(_task); } @@ -207,7 +206,6 @@ namespace flowhook } _json_task["isActive"] = task.isActive; _json_task["watching_depth"] = task.watching_depth; - _json_task["isRunning"] = task.isRunning; return Result::Ok(_json_task); } @@ -305,7 +303,6 @@ namespace flowhook _task.ignored_patterns = it->at("ignored_patterns"); _task.isActive = it->at("isActive"); _task.watching_depth = it->at("watching_depth"); - _task.isRunning = it->at("isRunning"); tasks.push_back(_task); } return Result>::Ok(tasks); diff --git a/src/flowhook_core.cpp b/src/flowhook_core.cpp index 45e615f..56a8400 100644 --- a/src/flowhook_core.cpp +++ b/src/flowhook_core.cpp @@ -33,7 +33,7 @@ namespace flowhook { default_ignored_patterns = { "*.o", "*.a", "*.so", "*.out", "*.exe", "*.swp", "*.swo", "*~", ".#*", - "*.class", "*.pyc", "*.log", ".git" + "*.class", "*.pyc", "*.log", "*.git" }; default_ignored_paths = { ".git" @@ -53,22 +53,22 @@ namespace flowhook { for(auto task: tasks) { TaskRunner* tr = TRY(TaskRunner::create(task.name, task.id), void); + for(auto i: task.ignored_paths) + TEST(tr->add_ignored_path(i)); + for(auto ip: task.ignored_patterns) + TEST(tr->add_ignored_pattern(ip)); for(auto c: task.commands) - tr->add_command(c); + TEST(tr->add_command(c)); for(auto p: task.file_paths) - tr->add_path(p); + TEST(tr->add_path(p)); for(auto p: task.dir_paths) - tr->add_path(p); + TEST(tr->add_path(p)); for(auto s: task.on_success) - tr->add_on_success(s); + TEST(tr->add_on_success(s)); for(auto f: task.on_failure) - tr->add_on_failure(f); + TEST(tr->add_on_failure(f)); - for(auto i: task.ignored_paths) - tr->add_ignored_path(i); - for(auto ip: task.ignored_patterns) - tr->add_ignored_pattern(ip); if(task.isActive) { tr->activate(); @@ -144,6 +144,20 @@ namespace flowhook { return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); } + Result FlowHookCore::set_depth(const std::string &task_id, int depth) + { + for(auto it = task_runners.begin(); it != task_runners.end(); it++) + { + if((*it)->get_task_id() == task_id) + { + TEST((*it)->set_depth(depth)); + config_manager->update_task((*it)->get_task()); + return Result::Ok(); + } + } + return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); + } + std::vector FlowHookCore::get_resolved_files(const std::string task_id) { @@ -182,6 +196,7 @@ namespace flowhook { if(id == task_id) { (*it)->deactivate(); + config_manager->update_task((*it)->get_task()); FW_VERBOSE("[FLOWHOOk] Task deactivated: " + task_id + " ✓"); return Result::Ok(); } diff --git a/src/include/flowhook_core.h b/src/include/flowhook_core.h index 9656538..ad2a505 100644 --- a/src/include/flowhook_core.h +++ b/src/include/flowhook_core.h @@ -27,6 +27,7 @@ namespace flowhook { ~FlowHookCore(); Result set_default_ignored(); + Result set_depth(const std::string &task_id, int depth); Result create_task(const std::string &task_name, const std::string &task_id); Result delete_task(const std::string &task_id); diff --git a/src/task_runner.cpp b/src/task_runner.cpp index d45e560..31810b1 100644 --- a/src/task_runner.cpp +++ b/src/task_runner.cpp @@ -63,14 +63,14 @@ TaskRunner::~TaskRunner() { } Result TaskRunner::set_depth(int num) { - if (num > 6) { + if (num > 10) { return Result::Err( FWError::make(ErrorCode::INVALID_DEPTH, - "Error: invalid depth set - depth too much ✗")); + "Error: invalid depth set - depth too much ✗, use a depth between 1 and 10")); } else if (num < 1) { return Result::Err( FWError::make(ErrorCode::INVALID_DEPTH, - "Error: invalid depth set - depth set too low ✗")); + "Error: invalid depth set - depth set too low ✗, use a depth between 1 and 10")); } task.watching_depth = num; @@ -272,7 +272,7 @@ Result TaskRunner::add_path_internal(const string &path, int MAX_DEPTH, if (entry.is_regular_file()) { FW_LOG("[DEBUG] Adding path " + entry.path().string() + " to filewatcher..."); - FW_LOG("[DEBUG] Checking if Path " + entry.path().string() + + FW_LOG("[DEBUG] Checking if resolved file path " + entry.path().string() + " matches ignored paths and patterns ..."); if (isIgnored(entry.path().string())) { @@ -289,7 +289,7 @@ Result TaskRunner::add_path_internal(const string &path, int MAX_DEPTH, " to task completed. ✓"); } else if (entry.is_directory()) { if (MAX_DEPTH > CURRENT_DEPTH) - TEST(add_path_internal(entry.path(), MAX_DEPTH, CURRENT_DEPTH + 1)); + TEST(add_path_internal(entry.path().string(), MAX_DEPTH, CURRENT_DEPTH + 1)); else FW_LOG("[DEBUG] Path " + entry.path().string() + " is a directory. But MAX_DEPTH=" + to_string(MAX_DEPTH) + From 3332f3c5286322e13729b31c749510e6dab57198 Mon Sep 17 00:00:00 2001 From: mnasie Date: Tue, 30 Jun 2026 19:37:56 +0300 Subject: [PATCH 10/20] Added bash script to test the CLI layer - add tests to test the add, run, init and list subcommands - refactored existing code to make the tests pass --- cli/src/commands/add.cpp | 2 +- cli/src/commands/run.cpp | 2 +- cli/tests/test_add.sh | 190 +++++++++++++++++++++++ cli/tests/test_cli.sh | 57 +++++++ cli/tests/test_helpers.sh | 46 ++++++ cli/tests/test_init.sh | 33 ++++ cli/tests/test_list.sh | 65 ++++++++ cli/tests/test_run.sh | 114 ++++++++++++++ src/config_manager.cpp | 1 + src/flowhook_core.cpp | 8 +- src/task_runner.cpp | 104 ++++++------- tests/integration/test_flowhook_core.cpp | 6 +- 12 files changed, 569 insertions(+), 59 deletions(-) create mode 100644 cli/tests/test_add.sh create mode 100644 cli/tests/test_cli.sh create mode 100644 cli/tests/test_helpers.sh create mode 100644 cli/tests/test_init.sh create mode 100644 cli/tests/test_list.sh create mode 100644 cli/tests/test_run.sh diff --git a/cli/src/commands/add.cpp b/cli/src/commands/add.cpp index 206c736..5d8bf78 100644 --- a/cli/src/commands/add.cpp +++ b/cli/src/commands/add.cpp @@ -44,7 +44,7 @@ void register_add(CLI::App *app, flowhook::FlowHookCore *fh) { if (add_n_path.empty() && add_command.empty() && command_on_success.empty() && command_on_failure.empty() && ignored_path.empty() && ignored_pattern.empty()) { - std::cerr << "At least one of --path, --command, --on-success, --on-failure, --ignored-path, or --ignored-pattern is required\n" + std::cerr << "Error: At least one of --path, --command, --on-success, --on-failure, --ignored-path, or --ignored-pattern is required\n" << std::endl; return; } diff --git a/cli/src/commands/run.cpp b/cli/src/commands/run.cpp index 9c1c02a..b3634c8 100644 --- a/cli/src/commands/run.cpp +++ b/cli/src/commands/run.cpp @@ -54,7 +54,7 @@ void register_run(CLI::App *app, flowhook::FlowHookCore *fh) { } } std::cout << "Watching " << run_task_id - << " (use --quiet to suppress build output)" << std::endl; + << " (use --quiet to suppress build output, press Ctrl+C to stop)" << std::endl; pause(); }); } diff --git a/cli/tests/test_add.sh b/cli/tests/test_add.sh new file mode 100644 index 0000000..5fcd32f --- /dev/null +++ b/cli/tests/test_add.sh @@ -0,0 +1,190 @@ +#!/usr/bin/env bash +set -uo pipefail + +PASS=0 +FAIL=0 + +source test_helpers.sh + +# --- add --path --- + +test_add_path_appears_in_list() { + fresh_dir + flowhook init >/dev/null 2>&1 + mkdir -p subdir + flowhook add --path ./subdir >/dev/null 2>&1 + local out + out=$(flowhook list --paths 2>&1) + assert_contains "$out" "subdir" "add --path shows up in list --paths" +} + +test_add_path_nonexistent_fails() { + fresh_dir + flowhook init >/dev/null 2>&1 + local out + out=$(flowhook add --path ./does_not_exist 2>&1) + assert_contains "$out" "Error:" "add --path on nonexistent path errors" +} + +test_add_duplicate_path_no_duplicate() { + fresh_dir + flowhook init >/dev/null 2>&1 + mkdir -p dup + flowhook add --path ./dup >/dev/null 2>&1 + flowhook add --path ./dup >/dev/null 2>&1 + local out count + out=$(flowhook list --paths 2>&1) + count=$(grep -o "dup" <<< "$out" | wc -l) + assert_exit 2 "$count" "adding the same path twice does not duplicate" +} +# --- add --command --- + +test_add_command_appears_in_list() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook add --command "echo hi" >/dev/null 2>&1 + local out + out=$(flowhook list --commands 2>&1) + assert_contains "$out" "echo hi" "add --command shows up in list --commands" +} + +test_add_duplicate_command_fails() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook add --command "echo hi" >/dev/null 2>&1 + local out + out=$(flowhook add --command "echo hi" 2>&1) + assert_contains "$out" "Error:" "adding the same command twice errors" +} + +# --- add --ignored-path --- + +test_add_ignored_path_appears_in_list() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook add --ignored-path .git >/dev/null 2>&1 + local out + out=$(flowhook list --ignored 2>&1) + assert_contains "$out" ".git" "add --ignored-path shows up in list --ignored" +} + +test_add_duplicate_ignored_path_fails() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook add --ignored-path .git >/dev/null 2>&1 + local out + out=$(flowhook add --ignored-path .git 2>&1) + assert_contains "$out" "Error:" "adding the same ignored-path twice errors" +} + +# --- add --ignored-pattern --- + +test_add_ignored_pattern_appears_in_list() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook add --ignored-pattern "*.o" >/dev/null 2>&1 + local out + out=$(flowhook list --ignored 2>&1) + assert_contains "$out" "*.o" "add --ignored-pattern shows up in list --ignored" +} + +test_add_duplicate_ignored_pattern_fails() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook add --ignored-pattern "*.o" >/dev/null 2>&1 + local out + out=$(flowhook add --ignored-pattern "*.o" 2>&1) + assert_contains "$out" "Error:" "adding the same ignored-pattern twice errors" +} + +# --- ignore-before-add interaction --- +# A directory ignored by name should not appear in list --paths once added, +# since add_path should respect already-registered ignores. + +test_ignored_path_prevents_add_from_listing() { + fresh_dir + flowhook init >/dev/null 2>&1 + mkdir -p ignoreme + flowhook add --ignored-path ignoreme >/dev/null 2>&1 + flowhook add --path ./ignoreme >/dev/null 2>&1 + local out + out=$(flowhook list --paths 2>&1) + assert_not_contains "$out" "ignoreme" "ignored path should not appear in list --paths" +} + +# --- add --on-success --- + +test_add_on_success_appears_in_list() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook add --on-success "echo success" >/dev/null 2>&1 + local out + out=$(flowhook list --on-success 2>&1) + assert_contains "$out" "echo success" "add --on-success shows up in list --on-success" +} + +test_add_duplicate_on_success_no_duplicate() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook add --on-success "echo success" >/dev/null 2>&1 + flowhook add --on-success "echo success" >/dev/null 2>&1 + local out count + out=$(flowhook list --on-success 2>&1) + count=$(grep -o "echo success" <<< "$out" | wc -l) + assert_exit 1 "$count" "adding the same on-success command twice does not duplicate" +} + +# --- add --on-failure --- + +test_add_on_failure_appears_in_list() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook add --on-failure "echo failure" >/dev/null 2>&1 + local out + out=$(flowhook list --on-failure 2>&1) + assert_contains "$out" "echo failure" "add --on-failure shows up in list --on-failure" +} + +test_add_duplicate_on_failure_no_duplicate() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook add --on-failure "echo failure" >/dev/null 2>&1 + flowhook add --on-failure "echo failure" >/dev/null 2>&1 + local out count + out=$(flowhook list --on-failure 2>&1) + count=$(grep -o "echo failure" <<< "$out" | wc -l) + assert_exit 1 "$count" "adding the same on-failure command twice does not duplicate" +} + +# add with no flags at all +test_add_with_no_flags() { + fresh_dir + flowhook init >/dev/null 2>&1 + local out + out=$(flowhook add 2>&1) + assert_contains "$out" "Error:" "add with no flags errors" +} + +# add --command with an empty +test_add_empty_command() { + fresh_dir + flowhook init >/dev/null 2>&1 + local out + out=$(flowhook add --command "" 2>&1) + assert_contains "$out" "Error:" "add --command empty string errors" +} + +# multiple flags in one add call — path + command + ignored-pattern together +test_add_multiple_flags_at_once() { + fresh_dir + flowhook init >/dev/null 2>&1 + mkdir -p combo + flowhook add --path ./combo --command "echo combo" --ignored-pattern "*.tmp" >/dev/null 2>&1 + local out + out=$(flowhook list --paths 2>&1) + assert_contains "$out" "combo" "multi-flag add: path registered" + out=$(flowhook list --commands 2>&1) + assert_contains "$out" "echo combo" "multi-flag add: command registered" + out=$(flowhook list --ignored 2>&1) + assert_contains "$out" "*.tmp" "multi-flag add: ignored-pattern registered" +} diff --git a/cli/tests/test_cli.sh b/cli/tests/test_cli.sh new file mode 100644 index 0000000..b59222c --- /dev/null +++ b/cli/tests/test_cli.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +set -uo pipefail +cd "$(dirname "$0")" || exit 1 + +PASS=0 +FAIL=0 + +source ./test_add.sh +source ./test_init.sh +source ./test_list.sh +source ./test_run.sh + + + +# -- TEST INIT -- +test_init_creates_task +test_init_twice_fails + +# -- TEST ADD -- +test_add_path_appears_in_list +test_add_path_nonexistent_fails +test_add_duplicate_path_no_duplicate +test_add_command_appears_in_list +test_add_duplicate_command_fails +test_add_ignored_path_appears_in_list +test_add_duplicate_ignored_path_fails +test_add_ignored_pattern_appears_in_list +test_add_duplicate_ignored_pattern_fails +test_ignored_path_prevents_add_from_listing +test_add_with_no_flags +test_add_empty_command +test_add_on_success_appears_in_list +test_add_duplicate_on_success_no_duplicate +test_add_on_failure_appears_in_list +test_add_duplicate_on_failure_no_duplicate +test_add_multiple_flags_at_once + +# -- TEST LIST -- + +test_list_no_flags_is_noop +test_list_tasks_works_without_init_in_cwd +test_list_paths_fails_without_init_in_cwd +test_list_ignored_shows_defaults +test_list_combined_flags_when_initialized + +# -- TEST RUN -- + +test_run_default_starts_and_exits_on_sigint +test_run_all_starts_and_exits_on_sigint +test_run_active_with_no_active_tasks_errors +test_run_active_with_active_task_starts_and_exits +test_run_quiet_suppresses_command_output + + +echo "DEBUG config dir: $FLOWHOOK_CONFIG_DIR_TEST" +echo "Passed: $PASS, Failed: $FAIL" +[[ "$FAIL" -eq 0 ]] || exit 1 diff --git a/cli/tests/test_helpers.sh b/cli/tests/test_helpers.sh new file mode 100644 index 0000000..4dd419f --- /dev/null +++ b/cli/tests/test_helpers.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +set -uo pipefail + +PASS=0 +FAIL=0 + +# --helper functions-- +assert_exit() { + local expected="$1" actual="$2" desc="$3" + if [[ "$expected" == "$actual" ]]; then + PASS=$((PASS+1)) + else + FAIL=$((FAIL+1)) + echo "FAIL: $desc (expected $expected, got $actual)" + fi +} + +assert_contains() { + local haystack="$1" needle="$2" desc="$3" + if [[ "$haystack" == *"$needle"* ]]; then + PASS=$((PASS+1)) + else + FAIL=$((FAIL+1)) + echo "FAIL: $desc (expected output to contain: $needle)" + echo " actual output: $haystack" + fi +} + +assert_not_contains() { + local haystack="$1" needle="$2" desc="$3" + if [[ "$haystack" != *"$needle"* ]]; then + PASS=$((PASS+1)) + else + FAIL=$((FAIL+1)) + echo "FAIL: $desc (output should NOT contain: $needle)" + echo " actual output: $haystack" + fi +} + +fresh_dir() { + local d + d=$(mktemp -d) + export FLOWHOOK_CONFIG_DIR_TEST="$d/config" + mkdir -p "$FLOWHOOK_CONFIG_DIR_TEST" + cd "$d" || exit 1 +} diff --git a/cli/tests/test_init.sh b/cli/tests/test_init.sh new file mode 100644 index 0000000..62592e3 --- /dev/null +++ b/cli/tests/test_init.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -uo pipefail + +PASS=0 +FAIL=0 + +source test_helpers.sh + +# --- init --- + +test_init_creates_task() { + fresh_dir + flowhook init >/dev/null 2>&1 + local code=$? + assert_exit 0 "$code" "init creates a task" + + local files + files=$(ls "$FLOWHOOK_CONFIG_DIR_TEST" 2>/dev/null) + if [[ -z "$files" ]]; then + FAIL=$((FAIL+1)) + echo "FAIL: init creates a task (no config file written)" + else + PASS=$((PASS+1)) + fi +} + +test_init_twice_fails() { + fresh_dir + flowhook init >/dev/null 2>&1 + local out + out=$(flowhook init 2>&1) + assert_contains "$out" "Error:" "init twice should print an Error" +} diff --git a/cli/tests/test_list.sh b/cli/tests/test_list.sh new file mode 100644 index 0000000..1c7ee1b --- /dev/null +++ b/cli/tests/test_list.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +set -uo pipefail + +PASS=0 +FAIL=0 + +source ./test_helpers.sh + +# test_list.sh + +test_list_no_flags_is_noop() { + fresh_dir + flowhook init >/dev/null 2>&1 + local out code + out=$(flowhook list 2>&1) + code=$? + assert_exit 0 "$code" "list with no flags exits 0" + if [[ -z "$out" ]]; then + PASS=$((PASS+1)) + else + FAIL=$((FAIL+1)) + echo "FAIL: list with no flags should print nothing" + echo " actual output: $out" + fi +} + +test_list_tasks_works_without_init_in_cwd() { + fresh_dir + # init in one dir, then list --tasks from a different, uninitialized dir + flowhook init >/dev/null 2>&1 + local task_dir + task_dir=$(pwd) + mkdir -p ../other_dir + cd ../other_dir || exit 1 + local out + out=$(flowhook list --tasks 2>&1) + assert_contains "$out" "$task_dir" "list --tasks shows tasks regardless of cwd" +} + +test_list_paths_fails_without_init_in_cwd() { + fresh_dir + # no init at all in this fresh dir + local out + out=$(flowhook list --paths --commands 2>&1) + assert_contains "$out" "Error: No flowhook task found" "list --paths/--commands errors when cwd has no task" +} + +test_list_ignored_shows_defaults() { + fresh_dir + flowhook init >/dev/null 2>&1 + local out + out=$(flowhook list --ignored 2>&1) + assert_contains "$out" ".git" "list --ignored shows default ignores even with no user additions" +} + +test_list_combined_flags_when_initialized() { + fresh_dir + flowhook init >/dev/null 2>&1 + mkdir -p combo + flowhook add --path ./combo --command "echo combo" >/dev/null 2>&1 + local out + out=$(flowhook list --paths --commands 2>&1) + assert_contains "$out" "combo" "combined list flags: path shown" + assert_contains "$out" "echo combo" "combined list flags: command shown" +} diff --git a/cli/tests/test_run.sh b/cli/tests/test_run.sh new file mode 100644 index 0000000..c93056d --- /dev/null +++ b/cli/tests/test_run.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash +set -uo pipefail +cd "$(dirname "$0")" || exit 1 + +PASS=0 +FAIL=0 + +source ./test_helpers.sh +# test_run.sh + +test_run_default_starts_and_exits_on_sigint() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook add --command "echo hi" >/dev/null 2>&1 + + local outfile + outfile=$(mktemp) + + flowhook run > "$outfile" 2>&1 & + local pid=$! + + sleep 1 + kill -INT "$pid" + wait "$pid" 2>/dev/null + local code=$? + + local out + out=$(cat "$outfile") + assert_contains "$out" "Watching" "run prints watching message" + assert_contains "$out" "Exiting safely" "run prints clean exit message on SIGINT" + + if [[ "$code" -ne 139 && "$code" -ne 134 ]]; then + PASS=$((PASS+1)) + else + FAIL=$((FAIL+1)) + echo "FAIL: run should not crash on SIGINT (got exit $code)" + fi + + rm -f "$outfile" +} + +test_run_all_starts_and_exits_on_sigint() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook add --command "echo hi" >/dev/null 2>&1 + + local outfile + outfile=$(mktemp) + + flowhook run --all > "$outfile" 2>&1 & + local pid=$! + + sleep 1 + kill -INT "$pid" + wait "$pid" 2>/dev/null + + local out + out=$(cat "$outfile") + assert_contains "$out" "Exiting safely" "run --all exits cleanly on SIGINT" + rm -f "$outfile" +} + +test_run_active_with_no_active_tasks_errors() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook add --command "echo hi" >/dev/null 2>&1 + # never set --active + + local out code + out=$(flowhook run --active 2>&1) + code=$? + assert_contains "$out" "Error: no active tasks to start" "run --active with no active tasks errors" +} + +test_run_active_with_active_task_starts_and_exits() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook add --command "echo hi" >/dev/null 2>&1 + flowhook set --active >/dev/null 2>&1 + + local outfile + outfile=$(mktemp) + + flowhook run --active > "$outfile" 2>&1 & + local pid=$! + + sleep 1 + kill -INT "$pid" + wait "$pid" 2>/dev/null + + local out + out=$(cat "$outfile") + assert_contains "$out" "Exiting safely" "run --active with an active task exits cleanly on SIGINT" + rm -f "$outfile" +} + +test_run_quiet_suppresses_command_output() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook add --command "echo SHOULD_NOT_APPEAR" >/dev/null 2>&1 + + local outfile + outfile=$(mktemp) + flowhook run --quiet > "$outfile" 2>&1 & + local pid=$! + sleep 1 + kill -INT "$pid" + wait "$pid" 2>/dev/null + + local out + out=$(cat "$outfile") + assert_not_contains "$out" "SHOULD_NOT_APPEAR" "run --quiet suppresses build output" + rm -f "$outfile" +} diff --git a/src/config_manager.cpp b/src/config_manager.cpp index 2c2b86a..2b3c405 100644 --- a/src/config_manager.cpp +++ b/src/config_manager.cpp @@ -243,6 +243,7 @@ namespace flowhook { json _json_task = TRY(convert_task_to_json(task), void); *it = _json_task; + isflushed = false; flush(); return Result::Ok(); } diff --git a/src/flowhook_core.cpp b/src/flowhook_core.cpp index 56a8400..5eec5fa 100644 --- a/src/flowhook_core.cpp +++ b/src/flowhook_core.cpp @@ -504,17 +504,23 @@ namespace flowhook { { if(task_runners.empty()) { - return Result::Ok(); //idempotent + return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: no tasks to start ✗")); } + bool found_active = false; for(auto it = task_runners.begin(); it != task_runners.end(); it++) { if((*it)->is_active()) { + found_active = true; FW_LOG("[DEBUG] Starting task " + (*it)->get_task_id() + " ..."); TEST((*it)->start()); } } + if(!found_active) + { + return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: no active tasks to start ✗")); + } FW_VERBOSE("[FLOWHOOK] All active tasks started ✓"); return Result::Ok(); } diff --git a/src/task_runner.cpp b/src/task_runner.cpp index 31810b1..7e72e3f 100644 --- a/src/task_runner.cpp +++ b/src/task_runner.cpp @@ -64,13 +64,13 @@ TaskRunner::~TaskRunner() { Result TaskRunner::set_depth(int num) { if (num > 10) { - return Result::Err( - FWError::make(ErrorCode::INVALID_DEPTH, - "Error: invalid depth set - depth too much ✗, use a depth between 1 and 10")); + return Result::Err(FWError::make( + ErrorCode::INVALID_DEPTH, "Error: invalid depth set - depth too much " + "✗, use a depth between 1 and 10")); } else if (num < 1) { - return Result::Err( - FWError::make(ErrorCode::INVALID_DEPTH, - "Error: invalid depth set - depth set too low ✗, use a depth between 1 and 10")); + return Result::Err(FWError::make( + ErrorCode::INVALID_DEPTH, "Error: invalid depth set - depth set too " + "low ✗, use a depth between 1 and 10")); } task.watching_depth = num; @@ -117,15 +117,17 @@ TaskRunner::change_working_directory(const string &working_directory) { } bool TaskRunner::isIgnored(const string &path) { + string filename = fs::path(path).filename().string(); for (auto &p : task.ignored_paths) { - if (p == path) + if (p == path || p == filename) return true; } - string filename = fs::path(path).filename().string(); for (auto &p : task.ignored_patterns) { if (fnmatch(p.c_str(), filename.c_str(), 0) == 0) return true; + if (fnmatch(p.c_str(), path.c_str(), 0) == 0) + return true; } return false; } @@ -142,8 +144,7 @@ Result TaskRunner::add_ignored_path(const string &path) { } } task.ignored_paths.push_back(path); - FW_LOG("[DEBUG] Adding ignored path " + path + - " to Task completed. ✓"); + FW_LOG("[DEBUG] Adding ignored path " + path + " to Task completed. ✓"); return Result::Ok(); } @@ -159,8 +160,7 @@ Result TaskRunner::add_ignored_pattern(const string &pattern) { } } task.ignored_patterns.push_back(pattern); - FW_LOG("[DEBUG] Adding ignored pattern " + pattern + - " to Task completed. ✓"); + FW_LOG("[DEBUG] Adding ignored pattern " + pattern + " to Task completed. ✓"); return Result::Ok(); } @@ -237,26 +237,28 @@ bool TaskRunner::check_path_existence(const string &path) { } Result TaskRunner::add_path(const string &path) { - if(check_path_existence(path)) return Result::Ok(); // idempotent - if(!fs::exists(path)) - { - return Result::Err(FWError::make( - ErrorCode::PATH_NOT_FOUND, - "Error: provided path " + path + " does not exist! ✗")); - } + if (check_path_existence(path)) + return Result::Ok(); // idempotent + if (!fs::exists(path)) { + return Result::Err( + FWError::make(ErrorCode::PATH_NOT_FOUND, + "Error: provided path " + path + " does not exist! ✗")); + } TEST(add_path_internal(path, task.watching_depth, 0)); - if(fs::is_directory(path)) - { - FW_VERBOSE("[FLOWHOOK] Adding " + path + "'s children to task runner completed. ✓"); + if (fs::is_directory(path)) { + FW_VERBOSE("[FLOWHOOK] Adding " + path + + "'s children to task runner completed. ✓"); } else - FW_VERBOSE("[FLOWHOOK] Adding path " + path + " to task runner completed. ✓"); + FW_VERBOSE("[FLOWHOOK] Adding path " + path + + " to task runner completed. ✓"); return Result::Ok(); } Result TaskRunner::add_path_internal(const string &path, int MAX_DEPTH, int CURRENT_DEPTH) { - if(check_path_existence(path)) return Result::Ok(); // idempotent + if (check_path_existence(path)) + return Result::Ok(); // idempotent // if path is directory add each files inside it iteratively if (isIgnored(path)) { FW_LOG("[DEBUG] Path " + path + " matches ignored paths and patterns."); @@ -265,14 +267,15 @@ Result TaskRunner::add_path_internal(const string &path, int MAX_DEPTH, } if (fs::is_directory(path)) { - task.dir_paths.push_back(path); + task.dir_paths.push_back(path); FW_LOG("[DEBUG] Path " + path + " is a directory adding child files recursively..."); for (auto &entry : fs::directory_iterator(path)) { if (entry.is_regular_file()) { FW_LOG("[DEBUG] Adding path " + entry.path().string() + " to filewatcher..."); - FW_LOG("[DEBUG] Checking if resolved file path " + entry.path().string() + + FW_LOG("[DEBUG] Checking if resolved file path " + + entry.path().string() + " matches ignored paths and patterns ..."); if (isIgnored(entry.path().string())) { @@ -289,15 +292,15 @@ Result TaskRunner::add_path_internal(const string &path, int MAX_DEPTH, " to task completed. ✓"); } else if (entry.is_directory()) { if (MAX_DEPTH > CURRENT_DEPTH) - TEST(add_path_internal(entry.path().string(), MAX_DEPTH, CURRENT_DEPTH + 1)); + TEST(add_path_internal(entry.path().string(), MAX_DEPTH, + CURRENT_DEPTH + 1)); else FW_LOG("[DEBUG] Path " + entry.path().string() + " is a directory. But MAX_DEPTH=" + to_string(MAX_DEPTH) + " have been reached. Child files won't be watched."); } } - } - else if (!fs::is_directory(path)) { + } else if (!fs::is_directory(path)) { FW_LOG("[DEBUG] Path " + path + " is a file. Adding to task..."); task.file_paths.push_back(path); resolved_files.push_back(path); @@ -306,34 +309,31 @@ Result TaskRunner::add_path_internal(const string &path, int MAX_DEPTH, return Result::Ok(); } - Result TaskRunner::delete_path(const string &path) { - if(!fs::exists(path)) - { - return Result::Err(FWError::make( - ErrorCode::PATH_NOT_FOUND, - "Error: provided path " + path + " does not exist! ✗")); - } + if (!fs::exists(path)) { + return Result::Err( + FWError::make(ErrorCode::PATH_NOT_FOUND, + "Error: provided path " + path + " does not exist! ✗")); + } - if(!fs::is_directory(path)){ - for (auto it = task.file_paths.begin(); it != task.file_paths.end(); it++) { - if (*it == path) { - task.file_paths.erase(it); - return Result::Ok(); - } - } - } else { - for (auto it = task.dir_paths.begin(); it != task.dir_paths.end(); it++) { - if (*it == path) { - task.dir_paths.erase(it); - return Result::Ok(); - } - } + if (!fs::is_directory(path)) { + for (auto it = task.file_paths.begin(); it != task.file_paths.end(); it++) { + if (*it == path) { + task.file_paths.erase(it); + return Result::Ok(); + } } + } else { + for (auto it = task.dir_paths.begin(); it != task.dir_paths.end(); it++) { + if (*it == path) { + task.dir_paths.erase(it); + return Result::Ok(); + } + } + } TEST(fw->remove_path(path)); return Result::Err( - FWError::make(ErrorCode::PATH_NOT_FOUND, "Error: path not found") - ); + FWError::make(ErrorCode::PATH_NOT_FOUND, "Error: path not found")); } Result TaskRunner::add_on_success(const string &command) { diff --git a/tests/integration/test_flowhook_core.cpp b/tests/integration/test_flowhook_core.cpp index 7384b01..d74fab6 100644 --- a/tests/integration/test_flowhook_core.cpp +++ b/tests/integration/test_flowhook_core.cpp @@ -347,14 +347,12 @@ UTEST_F(FlowHookFixture, stop_all_multiple_tasks) UTEST_F(FlowHookFixture, start_active_no_tasks) { - EXPECT_TRUE(utest_fixture->fh->start_active().isOk()); // idempotent + EXPECT_TRUE(utest_fixture->fh->start_active().isErr()); // idempotent } UTEST_F(FlowHookFixture, start_active_no_active_tasks) { ASSERT_TRUE(utest_fixture->fh->create_task("test_task_1", "/tmp/fh_test").isOk()); - EXPECT_TRUE(utest_fixture->fh->start_active().isOk()); // No active tasks, so no error. - // The implementation of start_active in flowhook_core.cpp checks if tasks are active before starting. - // If no tasks are active, it will simply iterate and return Ok without starting anything. + EXPECT_TRUE(utest_fixture->fh->start_active().isErr()); } UTEST_F(FlowHookFixture, start_active_one_active_task) { From 0ed4881011104bff524bb88fe5c4fdce8d8d6ae1 Mon Sep 17 00:00:00 2001 From: mnasie Date: Wed, 1 Jul 2026 12:00:29 +0300 Subject: [PATCH 11/20] Add -f flag to remove command and add unit tests - Added a `-f` flag to bypass the confirmation dialog when removing a task. - Added a suite of unit tests for the remove command. - Improved SIGINT handling in `test_run.sh` to ensure clean process cleanup. --- cli/src/commands/remove.cpp | 25 ++++-- cli/tests/test_cli.sh | 20 ++++- cli/tests/test_remove.sh | 163 ++++++++++++++++++++++++++++++++++++ cli/tests/test_run.sh | 27 +++--- 4 files changed, 213 insertions(+), 22 deletions(-) create mode 100644 cli/tests/test_remove.sh diff --git a/cli/src/commands/remove.cpp b/cli/src/commands/remove.cpp index fbae21b..d714425 100644 --- a/cli/src/commands/remove.cpp +++ b/cli/src/commands/remove.cpp @@ -36,29 +36,36 @@ void register_remove(CLI::App *app, flowhook::FlowHookCore *fh) { remove->add_option("--ignored-path", ignored_path, "Ignored path [optional] \n// if not provided, all files will be watched in the working directory\n"); remove->add_option("--ignored-pattern", ignored_pattern, "Ignored pattern [optional] \n// if not provided, no pattern will be ignored in the working directory\n"); + static bool force = false; + remove->add_flag("-f", force, "removes flowhook instance without providing a dialog to check certainity"); + remove->callback([=]() mutable { fs::path cwd = fs::current_path(); task_id = cwd.string(); bool remove_task = false; if(removed_path.empty() && removed_command.empty() && - command_on_success.empty() && command_on_failure.empty() && - ignored_path.empty() && ignored_pattern.empty()) { + command_on_success.empty() && command_on_failure.empty() && + ignored_path.empty() && ignored_pattern.empty()) { remove_task = true; } if (remove_task) { - std::cout << "Are you sure you want to remove the flowhook instance from the current directory?" << std::endl; - std::cout << "Type 'yes' to confirm: "; - std::string confirmation; - std::cin >> confirmation; - if (confirmation != "yes") { - std::cout << "Removal cancelled." << std::endl; - return; + if(!force){ + std::cout << "Are you sure you want to remove the flowhook instance from the current directory?" << std::endl; + std::cout << "Type 'yes' to confirm: "; + std::string confirmation; + std::cin >> confirmation; + if (confirmation != "yes") { + std::cout << "Removal cancelled." << std::endl; + return; + } } auto r = fh->delete_task(task_id); if (r.isErr()) { std::cerr << "Failed to remove task: " << r.getErrMessage() << std::endl; + } else { + std::cout << "Task removed successfully." << std::endl; } return; } diff --git a/cli/tests/test_cli.sh b/cli/tests/test_cli.sh index b59222c..d114f91 100644 --- a/cli/tests/test_cli.sh +++ b/cli/tests/test_cli.sh @@ -9,7 +9,7 @@ source ./test_add.sh source ./test_init.sh source ./test_list.sh source ./test_run.sh - +source ./test_remove.sh # -- TEST INIT -- @@ -51,6 +51,24 @@ test_run_active_with_no_active_tasks_errors test_run_active_with_active_task_starts_and_exits test_run_quiet_suppresses_command_output +# -- TEST REMOVE -- + +test_remove_path_removes_from_list +test_remove_nonexistent_path_errors +test_remove_command_removes_from_list +test_remove_nonexistent_command_errors +test_remove_on_success_removes_from_list +test_remove_nonexistent_on_success_errors +test_remove_on_failure_removes_from_list +test_remove_nonexistent_on_failure_errors +test_remove_ignored_path_removes_from_list +test_remove_nonexistent_ignored_path_errors +test_remove_ignored_pattern_removes_from_list +test_remove_nonexistent_ignored_pattern_errors +test_remove_whole_task_with_force_flag +test_remove_whole_task_confirm_yes_deletes +test_remove_whole_task_decline_keeps_task + echo "DEBUG config dir: $FLOWHOOK_CONFIG_DIR_TEST" echo "Passed: $PASS, Failed: $FAIL" diff --git a/cli/tests/test_remove.sh b/cli/tests/test_remove.sh new file mode 100644 index 0000000..68c2435 --- /dev/null +++ b/cli/tests/test_remove.sh @@ -0,0 +1,163 @@ +#!/usr/bin/env bash +set -uo pipefail +cd "$(dirname "$0")" || exit 1 + +PASS=0 +FAIL=0 + +# test_remove.sh + +# --- remove --path --- + +test_remove_path_removes_from_list() { + fresh_dir + flowhook init >/dev/null 2>&1 + local outside_dir + outside_dir=$(mktemp -d) + flowhook add --path "$outside_dir" >/dev/null 2>&1 + flowhook remove --path "$outside_dir" >/dev/null 2>&1 + local out + out=$(flowhook list --paths 2>&1) + assert_not_contains "$out" "$outside_dir" "remove --path removes externally-added dir from list --paths" + rm -rf "$outside_dir" +} +test_remove_nonexistent_path_errors() { + fresh_dir + flowhook init >/dev/null 2>&1 + local out + out=$(flowhook remove --path "nonexistent path" 2>&1) + assert_contains "$out" "Error:" "removing a path never added errors" +} + +# --- remove --command --- + +test_remove_command_removes_from_list() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook add --command "echo bye" >/dev/null 2>&1 + flowhook remove --command "echo bye" >/dev/null 2>&1 + local out + out=$(flowhook list --commands 2>&1) + assert_not_contains "$out" "echo bye" "remove --command removes it from list --commands" +} + +test_remove_nonexistent_command_errors() { + fresh_dir + flowhook init >/dev/null 2>&1 + local out + out=$(flowhook remove --command "nonexistent command" 2>&1) + assert_contains "$out" "Error:" "removing a command never added errors" +} + +# --- remove --on-success --- + +test_remove_on_success_removes_from_list() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook add --on-success "echo success" >/dev/null 2>&1 + flowhook remove --on-success "echo success" >/dev/null 2>&1 + local out + out=$(flowhook list --on-success 2>&1) + assert_not_contains "$out" "echo success" "remove --on-success removes it from list --on-success" +} + +test_remove_nonexistent_on_success_errors() { + fresh_dir + flowhook init >/dev/null 2>&1 + local out + out=$(flowhook remove --on-success "nonexistent" 2>&1) + assert_contains "$out" "Error:" "removing on-success never added errors" +} + +# --- remove --on-failure --- + +test_remove_on_failure_removes_from_list() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook add --on-failure "echo failure" >/dev/null 2>&1 + flowhook remove --on-failure "echo failure" >/dev/null 2>&1 + local out + out=$(flowhook list --on-failure 2>&1) + assert_not_contains "$out" "echo failure" "remove --on-failure removes it from list --on-failure" +} + +test_remove_nonexistent_on_failure_errors() { + fresh_dir + flowhook init >/dev/null 2>&1 + local out + out=$(flowhook remove --on-failure "nonexistent" 2>&1) + assert_contains "$out" "Error:" "removing on-failure never added errors" +} + +# --- remove --ignored-path --- + +test_remove_ignored_path_removes_from_list() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook add --ignored-path "myignoredpath" >/dev/null 2>&1 + flowhook remove --ignored-path "myignoredpath" >/dev/null 2>&1 + local out + out=$(flowhook list --ignored 2>&1) + assert_not_contains "$out" "myignoredpath" "remove --ignored-path removes it from list --ignored" +} + +test_remove_nonexistent_ignored_path_errors() { + fresh_dir + flowhook init >/dev/null 2>&1 + local out + out=$(flowhook remove --ignored-path "nonexistent" 2>&1) + assert_contains "$out" "Error:" "removing ignored-path never added errors" +} + +# --- remove --ignored-pattern --- + +test_remove_ignored_pattern_removes_from_list() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook add --ignored-pattern "*.jpeg" >/dev/null 2>&1 + flowhook remove --ignored-pattern "*.jpeg" >/dev/null 2>&1 + local out + out=$(flowhook list --ignored 2>&1) + assert_not_contains "$out" "*.jpeg" "remove --ignored-pattern removes it from list --ignored" +} + +test_remove_nonexistent_ignored_pattern_errors() { + fresh_dir + flowhook init >/dev/null 2>&1 + local out + out=$(flowhook remove --ignored-pattern "*.nonexistent" 2>&1) + assert_contains "$out" "Error:" "removing ignored-pattern never added errors" +} + +test_remove_whole_task_with_force_flag() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook remove -f >/dev/null 2>&1 + local out + out=$(flowhook list --tasks 2>&1) + local task_dir + task_dir=$(pwd) + assert_not_contains "$out" "$task_dir" "remove -f deletes the whole task" +} + +test_remove_whole_task_confirm_yes_deletes() { + fresh_dir + flowhook init >/dev/null 2>&1 + local task_dir + task_dir=$(pwd) + echo "yes" | flowhook remove >/dev/null 2>&1 + local out + out=$(flowhook list --tasks 2>&1) + assert_not_contains "$out" "$task_dir" "remove with 'yes' confirmation deletes the task" +} + +test_remove_whole_task_decline_keeps_task() { + fresh_dir + flowhook init >/dev/null 2>&1 + local task_dir + task_dir=$(pwd) + echo "no" | flowhook remove >/dev/null 2>&1 + local out + out=$(flowhook list --tasks 2>&1) + assert_contains "$out" "$task_dir" "remove declining confirmation keeps the task" +} diff --git a/cli/tests/test_run.sh b/cli/tests/test_run.sh index c93056d..8136f66 100644 --- a/cli/tests/test_run.sh +++ b/cli/tests/test_run.sh @@ -20,22 +20,25 @@ test_run_default_starts_and_exits_on_sigint() { local pid=$! sleep 1 - kill -INT "$pid" - wait "$pid" 2>/dev/null - local code=$? + kill -INT "$pid" 2>/dev/null + + # Give it up to 3 seconds to exit gracefully; force-kill if it doesn't. + local waited=0 + while kill -0 "$pid" 2>/dev/null && [[ $waited -lt 3 ]]; do + sleep 1 + waited=$((waited+1)) + done + if kill -0 "$pid" 2>/dev/null; then + kill -9 "$pid" 2>/dev/null + FAIL=$((FAIL+1)) + echo "FAIL: run did not exit on SIGINT within 3s, force-killed" + else + PASS=$((PASS+1)) + fi local out out=$(cat "$outfile") - assert_contains "$out" "Watching" "run prints watching message" assert_contains "$out" "Exiting safely" "run prints clean exit message on SIGINT" - - if [[ "$code" -ne 139 && "$code" -ne 134 ]]; then - PASS=$((PASS+1)) - else - FAIL=$((FAIL+1)) - echo "FAIL: run should not crash on SIGINT (got exit $code)" - fi - rm -f "$outfile" } From d6b6e0f7a540a877d090cff41a6569920391d625 Mon Sep 17 00:00:00 2001 From: mnasie Date: Wed, 1 Jul 2026 12:35:17 +0300 Subject: [PATCH 12/20] Add check command to CLI Implement the `check` subcommand to verify task status (active/inactive) and retrieve the watching depth for the current directory. --- CMakeLists.txt | 3 +- cli/src/commands/check.cpp | 68 +++++++++++++++++++++++++++++++++++++ cli/src/main.cpp | 2 ++ cli/tests/test_set.sh | 0 src/flowhook_core.cpp | 25 ++++++++++++++ src/include/flowhook_core.h | 2 ++ 6 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 cli/src/commands/check.cpp create mode 100644 cli/tests/test_set.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index bdd60fd..cf0c90b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,7 +52,8 @@ add_executable(flowhook_cli cli/src/main.cpp cli/src/commands/init.cpp cli/src/commands/list.cpp cli/src/commands/remove.cpp - cli/src/commands/set.cpp) + cli/src/commands/set.cpp + cli/src/commands/check.cpp) target_link_libraries(flowhook_cli PRIVATE flowhook_lib) target_include_directories(flowhook_cli PRIVATE cli/src) diff --git a/cli/src/commands/check.cpp b/cli/src/commands/check.cpp new file mode 100644 index 0000000..de39db3 --- /dev/null +++ b/cli/src/commands/check.cpp @@ -0,0 +1,68 @@ +#include "../../src/include/flowhook_core.h" +#include "../include/CLI11.hpp" +#include "../../src/include/macros.hpp" + + +namespace fs = std::filesystem; + +namespace flowhook_cli { + void register_check(CLI::App* app, flowhook::FlowHookCore* fh) + { + auto* check = app->add_subcommand("check", "check the status of the task"); + + // this is a purely ceremonial flag(so that CLI11 won't say there is unrecognized flag) + // the actual parsing of verbose and setting of the environment variable is done in main.cpp + check->add_flag("--debug", FLOWHOOK_DEBUG, "Enable debug output. \n// is an extremely detailed output that shows every step of the process."); + check->add_flag("--verbose", FLOWHOOK_VERBOSE, "Enable verbose output. \n// shows a summary of the process."); + + + static bool active = false; + check->add_flag("--active", active, "Check if task is labeled as active"); + + static bool deactive = false; + check->add_flag("--deactive", deactive, "Check if task is labeled as deactive"); + + static bool depth; + check->add_flag("--depth", depth, "Outputs the watching depth set from your working directory."); + + check->callback([=]() mutable { + std::string check_task_id = fs::current_path().string(); + + if (active) { + auto r = fh->is_task_active(check_task_id); + if (r.isErr()) { + std::cerr << "Failed to check if task is active: " << r.getErrMessage() + << std::endl; + return; + } + if (r.unwrap()) { + std::cout << "Task " << check_task_id << " is active" << std::endl; + } else { + std::cout << "Task " << check_task_id << " is not active" << std::endl; + } + } + if(deactive) { + auto r = fh->is_task_active(check_task_id); + if (r.isErr()) { + std::cerr << "Failed to check if task is deactive: " << r.getErrMessage() + << std::endl; + return; + } + if (r.unwrap()) { + std::cout << "Task " << check_task_id << " is deactive" << std::endl; + } else { + std::cout << "Task " << check_task_id << " is not deactive" << std::endl; + } + } + if(depth) { + auto r = fh->get_task_depth(check_task_id); + if (r.isErr()) { + std::cerr << "Failed to check task depth: " << r.getErrMessage() + << std::endl; + return; + } + std::cout << "Task depth is " << r.unwrap() << std::endl; + } + }); + } +} diff --git a/cli/src/main.cpp b/cli/src/main.cpp index 4494286..fef22d3 100644 --- a/cli/src/main.cpp +++ b/cli/src/main.cpp @@ -16,6 +16,7 @@ namespace flowhook_cli { void register_list(CLI::App*, flowhook::FlowHookCore*); void register_remove(CLI::App*, flowhook::FlowHookCore*); void register_set(CLI::App*, flowhook::FlowHookCore*); + void register_check(CLI::App*, flowhook::FlowHookCore*); } namespace fs = std::filesystem; @@ -120,6 +121,7 @@ int main(int argc, char **argv) { flowhook_cli::register_list(&app, fh); flowhook_cli::register_remove(&app, fh); flowhook_cli::register_set(&app, fh); + flowhook_cli::register_check(&app, fh); CLI11_PARSE(app, argc, argv); diff --git a/cli/tests/test_set.sh b/cli/tests/test_set.sh new file mode 100644 index 0000000..e69de29 diff --git a/src/flowhook_core.cpp b/src/flowhook_core.cpp index 5eec5fa..eddad12 100644 --- a/src/flowhook_core.cpp +++ b/src/flowhook_core.cpp @@ -171,6 +171,31 @@ namespace flowhook { return std::vector(); } + Result FlowHookCore::get_task_depth(const std::string &task_id) + { + for(auto it = task_runners.begin(); it != task_runners.end(); it++) + { + if((*it)->get_task_id() == task_id) + { + auto t = (*it)->get_task(); + return Result::Ok(t.watching_depth); + } + } + return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); + } + + Result FlowHookCore::is_task_active(const std::string &task_id) + { + for(auto it = task_runners.begin(); it != task_runners.end(); it++) + { + if((*it)->get_task_id() == task_id) + { + return Result::Ok((*it)->is_active()); + } + } + return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); + } + Result FlowHookCore::activate_task(const std::string &task_id) { for(auto it = task_runners.begin(); it != task_runners.end(); it++) diff --git a/src/include/flowhook_core.h b/src/include/flowhook_core.h index ad2a505..6a541cd 100644 --- a/src/include/flowhook_core.h +++ b/src/include/flowhook_core.h @@ -32,6 +32,8 @@ namespace flowhook { Result delete_task(const std::string &task_id); std::vector get_resolved_files(const std::string task_id); + Result is_task_active(const std::string &task_id); + Result get_task_depth(const std::string &task_id); Result start_task(const std::string &task_id); Result stop_task(const std::string &task_id); From bc8b238094ed0bcc9171205385fd027b7766be56 Mon Sep 17 00:00:00 2001 From: mnasie Date: Wed, 1 Jul 2026 13:47:49 +0300 Subject: [PATCH 13/20] Implement task configuration settings - Add `set` command support for --active, --deactive, and --depth - Update FlowHookCore to manage task depth and activation status - Normalize output streams to standard output across CLI commands - Add unit tests for configuration persistence and validation --- cli/src/commands/add.cpp | 14 +- cli/src/commands/check.cpp | 20 +- cli/src/commands/init.cpp | 4 +- cli/src/commands/list.cpp | 6 +- cli/src/commands/remove.cpp | 2 +- cli/src/commands/run.cpp | 6 +- cli/src/commands/set.cpp | 12 +- cli/tests/test_cli.sh | 13 + cli/tests/test_set.sh | 84 ++++ src/flowhook_core.cpp | 969 +++++++++++++++++------------------- src/include/flowhook_core.h | 1 + src/task_runner.cpp | 26 +- 12 files changed, 614 insertions(+), 543 deletions(-) diff --git a/cli/src/commands/add.cpp b/cli/src/commands/add.cpp index 5d8bf78..933d319 100644 --- a/cli/src/commands/add.cpp +++ b/cli/src/commands/add.cpp @@ -44,7 +44,7 @@ void register_add(CLI::App *app, flowhook::FlowHookCore *fh) { if (add_n_path.empty() && add_command.empty() && command_on_success.empty() && command_on_failure.empty() && ignored_path.empty() && ignored_pattern.empty()) { - std::cerr << "Error: At least one of --path, --command, --on-success, --on-failure, --ignored-path, or --ignored-pattern is required\n" + std::cout << "Error: At least one of --path, --command, --on-success, --on-failure, --ignored-path, or --ignored-pattern is required\n" << std::endl; return; } @@ -52,7 +52,7 @@ void register_add(CLI::App *app, flowhook::FlowHookCore *fh) { if (!add_n_path.empty()) { auto r = fh->set_task_path(task_id, add_n_path); if (r.isErr()) { - std::cerr << "Failed to set task path: " << r.getErrMessage() + std::cout << "Failed to set task path: " << r.getErrMessage() << std::endl; return; } @@ -61,7 +61,7 @@ void register_add(CLI::App *app, flowhook::FlowHookCore *fh) { if (!add_command.empty()) { auto r = fh->set_task_command(task_id, add_command); if (r.isErr()) { - std::cerr << "Failed to set task command: " << r.getErrMessage() + std::cout << "Failed to set task command: " << r.getErrMessage() << std::endl; return; } @@ -70,7 +70,7 @@ void register_add(CLI::App *app, flowhook::FlowHookCore *fh) { if (!command_on_success.empty()) { auto r = fh->set_task_on_success(task_id, command_on_success); if (r.isErr()) { - std::cerr << "Failed to set task on success: " << r.getErrMessage() + std::cout << "Failed to set task on success: " << r.getErrMessage() << std::endl; return; } @@ -79,7 +79,7 @@ void register_add(CLI::App *app, flowhook::FlowHookCore *fh) { if (!command_on_failure.empty()) { auto r = fh->set_task_on_failure(task_id, command_on_failure); if (r.isErr()) { - std::cerr << "Failed to set task on failure: " << r.getErrMessage() + std::cout << "Failed to set task on failure: " << r.getErrMessage() << std::endl; return; } @@ -88,7 +88,7 @@ void register_add(CLI::App *app, flowhook::FlowHookCore *fh) { if (!ignored_path.empty()) { auto r = fh->set_ignored_path(task_id, ignored_path); if (r.isErr()) { - std::cerr << "Failed to set ignored path: " << r.getErrMessage() + std::cout << "Failed to set ignored path: " << r.getErrMessage() << std::endl; return; } @@ -97,7 +97,7 @@ void register_add(CLI::App *app, flowhook::FlowHookCore *fh) { if (!ignored_pattern.empty()) { auto r = fh->set_ignored_pattern(task_id, ignored_pattern); if (r.isErr()) { - std::cerr << "Failed to set ignored pattern: " << r.getErrMessage() + std::cout << "Failed to set ignored pattern: " << r.getErrMessage() << std::endl; return; } diff --git a/cli/src/commands/check.cpp b/cli/src/commands/check.cpp index de39db3..e8f4e8f 100644 --- a/cli/src/commands/check.cpp +++ b/cli/src/commands/check.cpp @@ -28,10 +28,20 @@ namespace flowhook_cli { check->callback([=]() mutable { std::string check_task_id = fs::current_path().string(); + if(!active && !deactive && !depth) { + if(!fh->is_task(check_task_id)) { + std::cout << "FlowHook task has not been initialized in the current directory." << std::endl; + return; + }else { + std::cout << "FlowHook task is initialized in the current directory." << std::endl; + return; + } + } + if (active) { auto r = fh->is_task_active(check_task_id); if (r.isErr()) { - std::cerr << "Failed to check if task is active: " << r.getErrMessage() + std::cout << "Failed to check if task is active: " << r.getErrMessage() << std::endl; return; } @@ -44,20 +54,20 @@ namespace flowhook_cli { if(deactive) { auto r = fh->is_task_active(check_task_id); if (r.isErr()) { - std::cerr << "Failed to check if task is deactive: " << r.getErrMessage() + std::cout << "Failed to check if task is deactive: " << r.getErrMessage() << std::endl; return; } if (r.unwrap()) { - std::cout << "Task " << check_task_id << " is deactive" << std::endl; - } else { std::cout << "Task " << check_task_id << " is not deactive" << std::endl; + } else { + std::cout << "Task " << check_task_id << " is deactive" << std::endl; } } if(depth) { auto r = fh->get_task_depth(check_task_id); if (r.isErr()) { - std::cerr << "Failed to check task depth: " << r.getErrMessage() + std::cout << "Failed to check task depth: " << r.getErrMessage() << std::endl; return; } diff --git a/cli/src/commands/init.cpp b/cli/src/commands/init.cpp index f45042b..fdc51ea 100644 --- a/cli/src/commands/init.cpp +++ b/cli/src/commands/init.cpp @@ -30,12 +30,12 @@ namespace flowhook_cli { auto r = fh->create_task(task_name, n_path); if (r.isErr()) { - std::cerr << "Failed to create task: " << r.getErrMessage() << std::endl; + std::cout << "Failed to create task: " << r.getErrMessage() << std::endl; return; } auto p = fh->set_task_path(n_path, n_path); if (p.isErr()) { - std::cerr << "Failed to set task path: " << p.getErrMessage() << std::endl; + std::cout << "Failed to set task path: " << p.getErrMessage() << std::endl; return; } diff --git a/cli/src/commands/list.cpp b/cli/src/commands/list.cpp index b230277..7f1fbe2 100644 --- a/cli/src/commands/list.cpp +++ b/cli/src/commands/list.cpp @@ -56,7 +56,7 @@ namespace flowhook_cli { if (list_paths) { if(ts == nullptr) { - std::cerr << "Error: No flowhook task found for the current directory." << std::endl; + std::cout << "Error: No flowhook task found for the current directory." << std::endl; return; } @@ -79,7 +79,7 @@ namespace flowhook_cli { if (list_commands) { if(ts == nullptr) { - std::cerr << "Error: No flowhook task found for the current directory." << std::endl; + std::cout << "Error: No flowhook task found for the current directory." << std::endl; return; } @@ -91,7 +91,7 @@ namespace flowhook_cli { if (list_ignored) { if(ts == nullptr) { - std::cerr << "Error: No flowhook task found for the current directory." << std::endl; + std::cout << "Error: No flowhook task found for the current directory." << std::endl; return; } diff --git a/cli/src/commands/remove.cpp b/cli/src/commands/remove.cpp index d714425..27ce824 100644 --- a/cli/src/commands/remove.cpp +++ b/cli/src/commands/remove.cpp @@ -63,7 +63,7 @@ void register_remove(CLI::App *app, flowhook::FlowHookCore *fh) { } auto r = fh->delete_task(task_id); if (r.isErr()) { - std::cerr << "Failed to remove task: " << r.getErrMessage() << std::endl; + std::cout << "Failed to remove task: " << r.getErrMessage() << std::endl; } else { std::cout << "Task removed successfully." << std::endl; } diff --git a/cli/src/commands/run.cpp b/cli/src/commands/run.cpp index b3634c8..825bc87 100644 --- a/cli/src/commands/run.cpp +++ b/cli/src/commands/run.cpp @@ -33,7 +33,7 @@ void register_run(CLI::App *app, flowhook::FlowHookCore *fh) { if (run_all) { auto r = fh->start_all(); if (r.isErr()) { - std::cerr << "Failed to run all tasks: " << r.getErrMessage() + std::cout << "Failed to run all tasks: " << r.getErrMessage() << std::endl; return; } @@ -41,7 +41,7 @@ void register_run(CLI::App *app, flowhook::FlowHookCore *fh) { } else if (run_active) { auto r = fh->start_active(); if (r.isErr()) { - std::cerr << "Failed to run active tasks: " << r.getErrMessage() + std::cout << "Failed to run active tasks: " << r.getErrMessage() << std::endl; return; } @@ -49,7 +49,7 @@ void register_run(CLI::App *app, flowhook::FlowHookCore *fh) { } else if (!run_task_id.empty()) { auto r = fh->start_task(run_task_id); if (r.isErr()) { - std::cerr << "Failed to run task: " << r.getErrMessage() << std::endl; + std::cout << "Failed to run task: " << r.getErrMessage() << std::endl; return; } } diff --git a/cli/src/commands/set.cpp b/cli/src/commands/set.cpp index 84ef5cf..609b07f 100644 --- a/cli/src/commands/set.cpp +++ b/cli/src/commands/set.cpp @@ -2,6 +2,8 @@ #include "../include/CLI11.hpp" #include "../../src/include/macros.hpp" +#include + namespace fs = std::filesystem; @@ -22,7 +24,7 @@ namespace flowhook_cli { static bool deactive = false; set->add_flag("--deactive", deactive, "Set a task enables active as deactive"); - static int depth = 0; + static int depth = INT_MIN; set->add_option("--depth", depth, "Set the depth to watch from your working directory."); set->callback([=]() mutable { @@ -31,7 +33,7 @@ namespace flowhook_cli { if (active) { auto r = fh->activate_task(set_task_id); if (r.isErr()) { - std::cerr << "Failed to set task as active: " << r.getErrMessage() + std::cout << "Failed to set task as active: " << r.getErrMessage() << std::endl; return; } @@ -40,16 +42,16 @@ namespace flowhook_cli { if(deactive) { auto r = fh->deactivate_task(set_task_id); if (r.isErr()) { - std::cerr << "Failed to set task as deactive: " << r.getErrMessage() + std::cout << "Failed to set task as deactive: " << r.getErrMessage() << std::endl; return; } std::cout << "Task " << set_task_id << " is now deactive" << std::endl; } - if(depth > 0) { + if(depth != INT_MIN) { auto r = fh->set_depth(set_task_id, depth); if (r.isErr()) { - std::cerr << "Failed to set depth: " << r.getErrMessage() + std::cout << "Failed to set depth: " << r.getErrMessage() << std::endl; return; } diff --git a/cli/tests/test_cli.sh b/cli/tests/test_cli.sh index d114f91..4871ad6 100644 --- a/cli/tests/test_cli.sh +++ b/cli/tests/test_cli.sh @@ -10,6 +10,7 @@ source ./test_init.sh source ./test_list.sh source ./test_run.sh source ./test_remove.sh +source ./test_set.sh # -- TEST INIT -- @@ -70,6 +71,18 @@ test_remove_whole_task_confirm_yes_deletes test_remove_whole_task_decline_keeps_task +# -- TEST SET -- + +test_set_active +test_set_deactive +test_set_active_then_check_not_deactive +test_set_deactive_then_check_not_active +test_set_depth +test_set_depth_overwrite +test_set_active_and_deactive_together_last_wins +test_set_no_flags_is_noop + + echo "DEBUG config dir: $FLOWHOOK_CONFIG_DIR_TEST" echo "Passed: $PASS, Failed: $FAIL" [[ "$FAIL" -eq 0 ]] || exit 1 diff --git a/cli/tests/test_set.sh b/cli/tests/test_set.sh index e69de29..d3dc66d 100644 --- a/cli/tests/test_set.sh +++ b/cli/tests/test_set.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +set -uo pipefail +cd "$(dirname "$0")" || exit 1 + +PASS=0 +FAIL=0 + +source ./test_helpers.sh +# test_set.sh + +test_set_active() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook set --active >/dev/null 2>&1 + local out + out=$(flowhook check --active 2>&1) + assert_contains "$out" "is active" "set --active makes task active" +} + +test_set_deactive() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook set --active >/dev/null 2>&1 + flowhook set --deactive >/dev/null 2>&1 + local out + out=$(flowhook check --deactive 2>&1) + assert_contains "$out" "is deactive" "set --deactive makes task deactive" +} + +test_set_active_then_check_not_deactive() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook set --active >/dev/null 2>&1 + local out + out=$(flowhook check --deactive 2>&1) + assert_contains "$out" "not deactive" "active task is not deactive" +} + +test_set_deactive_then_check_not_active() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook set --deactive >/dev/null 2>&1 + local out + out=$(flowhook check --active 2>&1) + assert_contains "$out" "not active" "deactive task is not active" +} + +test_set_depth() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook set --depth 7 >/dev/null 2>&1 + local out + out=$(flowhook check --depth 2>&1) + assert_contains "$out" "7" "set --depth 7 reflected in check --depth" +} + +test_set_depth_overwrite() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook set --depth 3 >/dev/null 2>&1 + flowhook set --depth 9 >/dev/null 2>&1 + local out + out=$(flowhook check --depth 2>&1) + assert_contains "$out" "9" "set --depth overwrites previous depth" + assert_not_contains "$out" "3" "old depth not present after overwrite" +} + +test_set_active_and_deactive_together_last_wins() { + fresh_dir + flowhook init >/dev/null 2>&1 + flowhook set --active --deactive >/dev/null 2>&1 + local out + out=$(flowhook check --deactive 2>&1) + assert_contains "$out" "is deactive" "set --active --deactive together: deactive wins (last processed)" +} + +test_set_no_flags_is_noop() { + fresh_dir + flowhook init >/dev/null 2>&1 + local code + flowhook set >/dev/null 2>&1 + code=$? + assert_exit 0 "$code" "set with no flags does not crash" +} diff --git a/src/flowhook_core.cpp b/src/flowhook_core.cpp index eddad12..a1005e4 100644 --- a/src/flowhook_core.cpp +++ b/src/flowhook_core.cpp @@ -1,575 +1,534 @@ -#include -#include #include +#include +#include -#include "include/flowhook_core.h" -#include "include/error/result.h" #include "include/error/error.h" -#include "include/task_runner.h" +#include "include/error/result.h" +#include "include/flowhook_core.h" #include "include/macros.hpp" +#include "include/task_runner.h" using namespace std; namespace fs = std::filesystem; namespace flowhook { - Result FlowHookCore::create(){ - FlowHookCore* core = new FlowHookCore(); - TEST_OVERLOADED(core->init(), FlowHookCore*); - return Result::Ok(core); - } - - FlowHookCore::~FlowHookCore() - { - config_manager->flush(); - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - delete (*it); - } - } +Result FlowHookCore::create() { + FlowHookCore *core = new FlowHookCore(); + TEST_OVERLOADED(core->init(), FlowHookCore *); + return Result::Ok(core); +} - Result FlowHookCore::set_default_ignored() - { - default_ignored_patterns = { - "*.o", "*.a", "*.so", "*.out", "*.exe", - "*.swp", "*.swo", "*~", ".#*", - "*.class", "*.pyc", "*.log", "*.git" - }; - default_ignored_paths = { - ".git" - }; - return Result::Ok(); - } +FlowHookCore::~FlowHookCore() { + config_manager->flush(); + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + delete (*it); + } +} - Result FlowHookCore::init() - { - config_manager = TRY(ConfigManager::create(), void); - FW_LOG("[DEBUG] initializing a FlowHook core instance ...."); - - vector tasks = TRY(config_manager->get_tasks(), void); - set_default_ignored(); - - FW_LOG("[DEBUG] loading tasks from config file ...."); - for(auto task: tasks) - { - TaskRunner* tr = TRY(TaskRunner::create(task.name, task.id), void); - for(auto i: task.ignored_paths) - TEST(tr->add_ignored_path(i)); - for(auto ip: task.ignored_patterns) - TEST(tr->add_ignored_pattern(ip)); - - for(auto c: task.commands) - TEST(tr->add_command(c)); - for(auto p: task.file_paths) - TEST(tr->add_path(p)); - for(auto p: task.dir_paths) - TEST(tr->add_path(p)); - for(auto s: task.on_success) - TEST(tr->add_on_success(s)); - for(auto f: task.on_failure) - TEST(tr->add_on_failure(f)); - - if(task.isActive) - { - tr->activate(); - }else { - tr->deactivate(); - } - task_runners.push_back(tr); - } - FW_LOG("[DEBUG] loading tasks from config file completed. ✓"); - FW_VERBOSE("[FLOWHOOK] Flowhook core initialized."); - return Result::Ok(); - } +Result FlowHookCore::set_default_ignored() { + default_ignored_patterns = {"*.o", "*.a", "*.so", "*.out", "*.exe", + "*.swp", "*.swo", "*~", ".#*", "*.class", + "*.pyc", "*.log", "*.git"}; + default_ignored_paths = {".git"}; + return Result::Ok(); +} - Result FlowHookCore::create_task(const std::string &task_name, const std::string &task_id) - { - FW_LOG("[DEBUG] Creating task..."); - if(task_name.empty()) - { - return Result::Err(FWError::make(ErrorCode::EMPTY_VALUE, "Error: task name cannot be empty ✗")); - } - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - if((*it) == nullptr) continue; - string id = (*it)->get_task_id(); - if(id == task_id) - { - return Result::Err(FWError::make(ErrorCode::DUPLICATE_ENTRY, "Error: task already exists ✗")); - } - } - - if(!fs::exists(task_id) || !fs::is_directory(task_id)) - { - return Result::Err(FWError::make(ErrorCode::PATH_NOT_FOUND, "Error: working directory not found ✗")); - } - - if(task_runners.size() >= 100) - { - return Result::Err(FWError::make(ErrorCode::TASK_FULL, "Error: task limit reached ✗")); - } - auto result = TRY(TaskRunner::create(task_name, task_id), void); - - for(auto i: default_ignored_paths) - result->add_ignored_path(i); - for(auto ip: default_ignored_patterns) - result->add_ignored_pattern(ip); - - - task_runners.push_back(result); - TEST(config_manager->log_task(result->get_task())); - - FW_LOG("[DEBUG] logging task to config file completed. ✓"); - FW_VERBOSE("[FLOWHOOK] Task created: name='" + task_name + "' path='" + task_id + "' ✓"); - return Result::Ok(); +Result FlowHookCore::init() { + config_manager = TRY(ConfigManager::create(), void); + FW_LOG("[DEBUG] initializing a FlowHook core instance ...."); + + vector tasks = TRY(config_manager->get_tasks(), void); + set_default_ignored(); + + FW_LOG("[DEBUG] loading tasks from config file ...."); + for (auto task : tasks) { + TaskRunner *tr = TRY(TaskRunner::create(task.name, task.id), void); + for (auto i : task.ignored_paths) + TEST(tr->add_ignored_path(i)); + for (auto ip : task.ignored_patterns) + TEST(tr->add_ignored_pattern(ip)); + if (task.isActive) { + tr->activate(); + } else { + tr->deactivate(); } + tr->set_depth(task.watching_depth); + + for (auto c : task.commands) + TEST(tr->add_command(c)); + for (auto p : task.file_paths) + TEST(tr->add_path(p)); + for (auto p : task.dir_paths) + TEST(tr->add_path(p)); + for (auto s : task.on_success) + TEST(tr->add_on_success(s)); + for (auto f : task.on_failure) + TEST(tr->add_on_failure(f)); + + task_runners.push_back(tr); + } + FW_LOG("[DEBUG] loading tasks from config file completed. ✓"); + FW_VERBOSE("[FLOWHOOK] Flowhook core initialized."); + return Result::Ok(); +} - Result FlowHookCore::delete_task(const std::string &task_id) - { - FW_LOG("[DEBUG] Deleting task..."); - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - string name = (*it)->get_task_id(); - if(name == task_id) - { - Task _task_to_be_deleted = (*it)->get_task(); - TEST(config_manager->delete_task(_task_to_be_deleted)); - task_runners.erase(it); - FW_VERBOSE("[FLOWHOOK] Task deleted: " + task_id + "' ✓"); - return Result::Ok(); - } - } - - - return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); +Result FlowHookCore::create_task(const std::string &task_name, + const std::string &task_id) { + FW_LOG("[DEBUG] Creating task..."); + if (task_name.empty()) { + return Result::Err(FWError::make( + ErrorCode::EMPTY_VALUE, "Error: task name cannot be empty ✗")); + } + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + if ((*it) == nullptr) + continue; + string id = (*it)->get_task_id(); + if (id == task_id) { + return Result::Err(FWError::make(ErrorCode::DUPLICATE_ENTRY, + "Error: task already exists ✗")); } + } + + if (!fs::exists(task_id) || !fs::is_directory(task_id)) { + return Result::Err(FWError::make( + ErrorCode::PATH_NOT_FOUND, "Error: working directory not found ✗")); + } + + if (task_runners.size() >= 200) { + return Result::Err( + FWError::make(ErrorCode::TASK_FULL, "Error: task limit reached ✗")); + } + auto result = TRY(TaskRunner::create(task_name, task_id), void); + + for (auto i : default_ignored_paths) + result->add_ignored_path(i); + for (auto ip : default_ignored_patterns) + result->add_ignored_pattern(ip); + + task_runners.push_back(result); + TEST(config_manager->log_task(result->get_task())); + + FW_LOG("[DEBUG] logging task to config file completed. ✓"); + FW_VERBOSE("[FLOWHOOK] Task created: name='" + task_name + "' path='" + + task_id + "' ✓"); + return Result::Ok(); +} - Result FlowHookCore::set_depth(const std::string &task_id, int depth) - { - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - if((*it)->get_task_id() == task_id) - { - TEST((*it)->set_depth(depth)); - config_manager->update_task((*it)->get_task()); - return Result::Ok(); - } - } - return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); +Result FlowHookCore::delete_task(const std::string &task_id) { + FW_LOG("[DEBUG] Deleting task..."); + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + string name = (*it)->get_task_id(); + if (name == task_id) { + Task _task_to_be_deleted = (*it)->get_task(); + TEST(config_manager->delete_task(_task_to_be_deleted)); + task_runners.erase(it); + FW_VERBOSE("[FLOWHOOK] Task deleted: " + task_id + "' ✓"); + return Result::Ok(); } + } + return Result::Err(FWError::make( + ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); +} - std::vector FlowHookCore::get_resolved_files(const std::string task_id) - { - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - if((*it)->get_task_id() == task_id) - { - return (*it)->get_resolved_files(); - } - } - return std::vector(); +bool FlowHookCore::is_task(const std::string &task_id) { + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + if ((*it)->get_task_id() == task_id) { + return true; } + } + return false; +} - Result FlowHookCore::get_task_depth(const std::string &task_id) - { - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - if((*it)->get_task_id() == task_id) - { - auto t = (*it)->get_task(); - return Result::Ok(t.watching_depth); - } - } - return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); +Result FlowHookCore::set_depth(const std::string &task_id, int depth) { + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + if ((*it)->get_task_id() == task_id) { + TEST((*it)->set_depth(depth)); + config_manager->update_task((*it)->get_task()); + return Result::Ok(); } + } + return Result::Err(FWError::make( + ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); +} - Result FlowHookCore::is_task_active(const std::string &task_id) - { - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - if((*it)->get_task_id() == task_id) - { - return Result::Ok((*it)->is_active()); - } - } - return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); +std::vector +FlowHookCore::get_resolved_files(const std::string task_id) { + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + if ((*it)->get_task_id() == task_id) { + return (*it)->get_resolved_files(); } + } + return std::vector(); +} - Result FlowHookCore::activate_task(const std::string &task_id) - { - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - string id = (*it)->get_task_id(); - if(id == task_id) - { - (*it)->activate(); - config_manager->update_task((*it)->get_task()); - FW_VERBOSE("[FLOWHOOK] Task activated: " + task_id + " ✓"); - return Result::Ok(); - } - } - - return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); +Result FlowHookCore::get_task_depth(const std::string &task_id) { + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + if ((*it)->get_task_id() == task_id) { + auto t = (*it)->get_task(); + return Result::Ok(t.watching_depth); } + } + return Result::Err(FWError::make( + ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); +} - Result FlowHookCore::deactivate_task(const std::string &task_id) - { - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - string id = (*it)->get_task_id(); - if(id == task_id) - { - (*it)->deactivate(); - config_manager->update_task((*it)->get_task()); - FW_VERBOSE("[FLOWHOOk] Task deactivated: " + task_id + " ✓"); - return Result::Ok(); - } - } - - return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); +Result FlowHookCore::is_task_active(const std::string &task_id) { + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + if ((*it)->get_task_id() == task_id) { + return Result::Ok((*it)->is_active()); } + } + return Result::Err(FWError::make( + ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); +} - Result FlowHookCore::set_task_path(const std::string &task_id, const std::string &path) - { - if(!fs::exists(path)) - { - return Result::Err(FWError::make(ErrorCode::PATH_NOT_FOUND, "Error: path not found " + path + " ✗")); - } - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - string id = (*it)->get_task_id(); - if(id == task_id) - { - TEST((*it)->add_path(path)); - config_manager->update_task((*it)->get_task()); - FW_LOG("[DEBUG] Task path set: " + task_id + " ✓"); - return Result::Ok(); - } - } - - return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); +Result FlowHookCore::activate_task(const std::string &task_id) { + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + string id = (*it)->get_task_id(); + if (id == task_id) { + (*it)->activate(); + config_manager->update_task((*it)->get_task()); + FW_VERBOSE("[FLOWHOOK] Task activated: " + task_id + " ✓"); + return Result::Ok(); } + } - Result FlowHookCore::delete_task_path(const std::string &task_id, const std::string &path) - { - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - string id = (*it)->get_task_id(); - if(id == task_id) - { - TEST((*it)->delete_path(path)); - config_manager->update_task((*it)->get_task()); - FW_LOG("[DEBUG] Task path deleted: " + task_id + " ✓"); - return Result::Ok(); - } - } - - return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); - } + return Result::Err(FWError::make( + ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); +} - Result FlowHookCore::set_ignored_path(const std::string &task_id, const std::string &path) - { - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - string id = (*it)->get_task_id(); - if(id == task_id) - { - TEST((*it)->add_ignored_path(path)); - config_manager->update_task((*it)->get_task()); - FW_LOG("[DEBUG] Task ignore path set: " + task_id + " ✓"); - return Result::Ok(); - } - } - - return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); +Result FlowHookCore::deactivate_task(const std::string &task_id) { + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + string id = (*it)->get_task_id(); + if (id == task_id) { + (*it)->deactivate(); + config_manager->update_task((*it)->get_task()); + FW_VERBOSE("[FLOWHOOk] Task deactivated: " + task_id + " ✓"); + return Result::Ok(); } + } + + return Result::Err(FWError::make( + ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); +} - Result FlowHookCore::set_ignored_pattern(const std::string &task_id, const std::string &pattern) - { - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - string id = (*it)->get_task_id(); - if(id == task_id) - { - TEST((*it)->add_ignored_pattern(pattern)); - config_manager->update_task((*it)->get_task()); - FW_LOG("[DEBUG] Task ignore pattern set: " + task_id + " ✓"); - return Result::Ok(); - } - } - - return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗ ")); +Result FlowHookCore::set_task_path(const std::string &task_id, + const std::string &path) { + if (!fs::exists(path)) { + return Result::Err(FWError::make( + ErrorCode::PATH_NOT_FOUND, "Error: path not found " + path + " ✗")); + } + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + string id = (*it)->get_task_id(); + if (id == task_id) { + TEST((*it)->add_path(path)); + config_manager->update_task((*it)->get_task()); + FW_LOG("[DEBUG] Task path set: " + task_id + " ✓"); + return Result::Ok(); } + } + return Result::Err(FWError::make( + ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); +} - Result FlowHookCore::remove_ignored_path(const std::string &task_id, const std::string &path) - { - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - string id = (*it)->get_task_id(); - if(id == task_id) - { - TEST((*it)->remove_ignored_path(path)); - config_manager->update_task((*it)->get_task()); - FW_LOG("[DEBUG] Ignored path removed: " + task_id + " ✓"); - return Result::Ok(); - } - } - - return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); +Result FlowHookCore::delete_task_path(const std::string &task_id, + const std::string &path) { + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + string id = (*it)->get_task_id(); + if (id == task_id) { + TEST((*it)->delete_path(path)); + config_manager->update_task((*it)->get_task()); + FW_LOG("[DEBUG] Task path deleted: " + task_id + " ✓"); + return Result::Ok(); } + } + + return Result::Err(FWError::make( + ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); +} - Result FlowHookCore::remove_ignored_pattern(const std::string &task_id, const std::string &pattern) - { - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - string id = (*it)->get_task_id(); - if(id == task_id) - { - TEST((*it)->remove_ignored_pattern(pattern)); - config_manager->update_task((*it)->get_task()); - FW_LOG("[DEBUG] Ignored pattern removed: " + task_id + " ✓"); - return Result::Ok(); - } - } - - return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗ ")); +Result FlowHookCore::set_ignored_path(const std::string &task_id, + const std::string &path) { + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + string id = (*it)->get_task_id(); + if (id == task_id) { + TEST((*it)->add_ignored_path(path)); + config_manager->update_task((*it)->get_task()); + FW_LOG("[DEBUG] Task ignore path set: " + task_id + " ✓"); + return Result::Ok(); } + } + return Result::Err(FWError::make( + ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); +} - Result FlowHookCore::set_task_command(const std::string &task_id, const std::string &command) - { - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - string id = (*it)->get_task_id(); - if(id == task_id) - { - TEST((*it)->add_command(command)); - config_manager->update_task((*it)->get_task()); - FW_LOG("[DEBUG] Task command set: " + task_id + " ✓"); - return Result::Ok(); - } - } - return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗ ")); +Result FlowHookCore::set_ignored_pattern(const std::string &task_id, + const std::string &pattern) { + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + string id = (*it)->get_task_id(); + if (id == task_id) { + TEST((*it)->add_ignored_pattern(pattern)); + config_manager->update_task((*it)->get_task()); + FW_LOG("[DEBUG] Task ignore pattern set: " + task_id + " ✓"); + return Result::Ok(); } + } - Result FlowHookCore::delete_task_command(const std::string &task_id, const std::string &command) - { - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - string id = (*it)->get_task_id(); - if(id == task_id) - { - TEST((*it)->delete_command(command)); - config_manager->update_task((*it)->get_task()); - FW_LOG("[DEBUG] Task command deleted: " + task_id + " ✓"); - return Result::Ok(); - } - } - return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗ ")); + return Result::Err(FWError::make( + ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗ ")); +} + +Result FlowHookCore::remove_ignored_path(const std::string &task_id, + const std::string &path) { + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + string id = (*it)->get_task_id(); + if (id == task_id) { + TEST((*it)->remove_ignored_path(path)); + config_manager->update_task((*it)->get_task()); + FW_LOG("[DEBUG] Ignored path removed: " + task_id + " ✓"); + return Result::Ok(); } + } + + return Result::Err(FWError::make( + ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); +} - Result FlowHookCore::set_task_on_success(const std::string &task_id, const std::string &command) - { - if(task_id.empty() || command.empty()) - { - return Result::Err(FWError::make(ErrorCode::EMPTY_VALUE, "Error: task id and command cannot be empty " + task_id + " ✗ ")); - } - - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - string id = (*it)->get_task_id(); - if(id == task_id) - { - TEST((*it)->add_on_success(command)); - config_manager->update_task((*it)->get_task()); - FW_LOG("[DEBUG] Task on success set: " + task_id + " ✓"); - return Result::Ok(); - } - } - return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗ ")); +Result FlowHookCore::remove_ignored_pattern(const std::string &task_id, + const std::string &pattern) { + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + string id = (*it)->get_task_id(); + if (id == task_id) { + TEST((*it)->remove_ignored_pattern(pattern)); + config_manager->update_task((*it)->get_task()); + FW_LOG("[DEBUG] Ignored pattern removed: " + task_id + " ✓"); + return Result::Ok(); } + } - Result FlowHookCore::delete_task_on_success(const std::string &task_id, const std::string &command) - { - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - string id = (*it)->get_task_id(); - if(id == task_id) - { - TEST((*it)->delete_on_success(command)); - config_manager->update_task((*it)->get_task()); - FW_LOG("[DEBUG] Task on success deleted: " + task_id + " ✓"); - return Result::Ok(); - } - } - - return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗ ")); + return Result::Err(FWError::make( + ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗ ")); +} + +Result FlowHookCore::set_task_command(const std::string &task_id, + const std::string &command) { + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + string id = (*it)->get_task_id(); + if (id == task_id) { + TEST((*it)->add_command(command)); + config_manager->update_task((*it)->get_task()); + FW_LOG("[DEBUG] Task command set: " + task_id + " ✓"); + return Result::Ok(); } + } + return Result::Err(FWError::make( + ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗ ")); +} - Result FlowHookCore::set_task_on_failure(const std::string &task_id, const std::string &command) - { - if(task_id.empty() || command.empty()) - { - return Result::Err(FWError::make(ErrorCode::EMPTY_VALUE, "Error: task id and command cannot be empty " + task_id + " ✗ ")); - } - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - string id = (*it)->get_task_id(); - if(id == task_id) - { - TEST((*it)->add_on_failure(command)); - config_manager->update_task((*it)->get_task()); - FW_LOG("[DEBUG] Task on failure set: " + task_id + " ✓"); - return Result::Ok(); - } - } - return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗ ")); +Result FlowHookCore::delete_task_command(const std::string &task_id, + const std::string &command) { + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + string id = (*it)->get_task_id(); + if (id == task_id) { + TEST((*it)->delete_command(command)); + config_manager->update_task((*it)->get_task()); + FW_LOG("[DEBUG] Task command deleted: " + task_id + " ✓"); + return Result::Ok(); } + } + return Result::Err(FWError::make( + ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗ ")); +} - Result FlowHookCore::delete_task_on_failure(const std::string &task_id, const std::string &command) - { - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - string id = (*it)->get_task_id(); - if(id == task_id) - { - TEST((*it)->delete_on_failure(command)); - config_manager->update_task((*it)->get_task()); - FW_LOG("[DEBUG] Task on failure deleted: " + task_id + " ✓"); - return Result::Ok(); - } - } - - return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗ ")); +Result FlowHookCore::set_task_on_success(const std::string &task_id, + const std::string &command) { + if (task_id.empty() || command.empty()) { + return Result::Err(FWError::make( + ErrorCode::EMPTY_VALUE, + "Error: task id and command cannot be empty " + task_id + " ✗ ")); + } + + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + string id = (*it)->get_task_id(); + if (id == task_id) { + TEST((*it)->add_on_success(command)); + config_manager->update_task((*it)->get_task()); + FW_LOG("[DEBUG] Task on success set: " + task_id + " ✓"); + return Result::Ok(); } + } + return Result::Err(FWError::make( + ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗ ")); +} +Result FlowHookCore::delete_task_on_success(const std::string &task_id, + const std::string &command) { + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + string id = (*it)->get_task_id(); + if (id == task_id) { + TEST((*it)->delete_on_success(command)); + config_manager->update_task((*it)->get_task()); + FW_LOG("[DEBUG] Task on success deleted: " + task_id + " ✓"); + return Result::Ok(); + } + } + return Result::Err(FWError::make( + ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗ ")); +} - Result FlowHookCore::start_task(const std::string &task_id) - { - FW_LOG("[DEBUG] Starting task: " + task_id + " ..."); - FW_LOG("[DEBUG] Looping through tasks to find the correct one ..."); - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - string id = (*it)->get_task_id(); - if(id == task_id) - { - FW_LOG("[DEBUG] Task found: " + task_id + " ✓"); - if((*it)->is_running()) - { - return Result::Err(FWError::make(ErrorCode::TASK_ALREADY_RUNNING, "Error: task already running " + task_id + " ✗")); - } - FW_LOG("[DEBUG] Starting task_runner..."); - (*it)->start(); - FW_VERBOSE("[FLOWHOOK] Task started: " + task_id + " ✓"); - return Result::Ok(); - } - } - return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); +Result FlowHookCore::set_task_on_failure(const std::string &task_id, + const std::string &command) { + if (task_id.empty() || command.empty()) { + return Result::Err(FWError::make( + ErrorCode::EMPTY_VALUE, + "Error: task id and command cannot be empty " + task_id + " ✗ ")); + } + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + string id = (*it)->get_task_id(); + if (id == task_id) { + TEST((*it)->add_on_failure(command)); + config_manager->update_task((*it)->get_task()); + FW_LOG("[DEBUG] Task on failure set: " + task_id + " ✓"); + return Result::Ok(); } + } + return Result::Err(FWError::make( + ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗ ")); +} - Result FlowHookCore::stop_task(const std::string &task_id) - { - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - string id = (*it)->get_task_id(); - if(id == task_id) - { - FW_LOG("[DEBUG] Stopping task: " + task_id + " ✗"); - if(!(*it)->is_running()) - { - return Result::Err(FWError::make(ErrorCode::TASK_NOT_RUNNING, "Error: task not running " + task_id + " ✗")); - } - (*it)->stop(); - FW_VERBOSE("[FLOWHOOK] Task stopped: " + task_id + " ✓"); - return Result::Ok(); - } - } - return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); +Result FlowHookCore::delete_task_on_failure(const std::string &task_id, + const std::string &command) { + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + string id = (*it)->get_task_id(); + if (id == task_id) { + TEST((*it)->delete_on_failure(command)); + config_manager->update_task((*it)->get_task()); + FW_LOG("[DEBUG] Task on failure deleted: " + task_id + " ✓"); + return Result::Ok(); } + } - Result FlowHookCore::start_all() - { - if(task_runners.empty()) - { - return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: no tasks to start ✗")); - } - - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - if((*it)->is_running()){ - return Result::Err(FWError::make(ErrorCode::TASK_ALREADY_RUNNING, "Error: task already running ✗")); - } - FW_LOG("[DEBUG] Starting task: " + (*it)->get_task_id() + " ..."); - TEST((*it)->start()); - } - FW_VERBOSE("[FLOWHOOK] All tasks started ✓"); - return Result::Ok(); - } + return Result::Err(FWError::make( + ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗ ")); +} - Result FlowHookCore::stop_all() - { - if(task_runners.empty()) - { - return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: no tasks to stop ✗")); - } - - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - FW_LOG("[DEBUG] Stopping task " + (*it)->get_task_id() + " ..."); - TEST((*it)->stop()); - } - FW_VERBOSE("[FLOWHOOK] All tasks stopped ✓"); - return Result::Ok(); +Result FlowHookCore::start_task(const std::string &task_id) { + FW_LOG("[DEBUG] Starting task: " + task_id + " ..."); + FW_LOG("[DEBUG] Looping through tasks to find the correct one ..."); + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + string id = (*it)->get_task_id(); + if (id == task_id) { + FW_LOG("[DEBUG] Task found: " + task_id + " ✓"); + if ((*it)->is_running()) { + return Result::Err( + FWError::make(ErrorCode::TASK_ALREADY_RUNNING, + "Error: task already running " + task_id + " ✗")); + } + FW_LOG("[DEBUG] Starting task_runner..."); + (*it)->start(); + FW_VERBOSE("[FLOWHOOK] Task started: " + task_id + " ✓"); + return Result::Ok(); } + } + return Result::Err(FWError::make( + ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); +} - Result FlowHookCore::start_active() - { - if(task_runners.empty()) - { - return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: no tasks to start ✗")); - } - - bool found_active = false; - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - if((*it)->is_active()) - { - found_active = true; - FW_LOG("[DEBUG] Starting task " + (*it)->get_task_id() + " ..."); - TEST((*it)->start()); - } - } - if(!found_active) - { - return Result::Err(FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: no active tasks to start ✗")); - } - FW_VERBOSE("[FLOWHOOK] All active tasks started ✓"); - return Result::Ok(); +Result FlowHookCore::stop_task(const std::string &task_id) { + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + string id = (*it)->get_task_id(); + if (id == task_id) { + FW_LOG("[DEBUG] Stopping task: " + task_id + " ✗"); + if (!(*it)->is_running()) { + return Result::Err( + FWError::make(ErrorCode::TASK_NOT_RUNNING, + "Error: task not running " + task_id + " ✗")); + } + (*it)->stop(); + FW_VERBOSE("[FLOWHOOK] Task stopped: " + task_id + " ✓"); + return Result::Ok(); } + } + return Result::Err(FWError::make( + ErrorCode::TASK_NOT_FOUND, "Error: task not found " + task_id + " ✗")); +} + +Result FlowHookCore::start_all() { + if (task_runners.empty()) { + return Result::Err( + FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: no tasks to start ✗")); + } - std::vector FlowHookCore::get_tasks() const - { - vector tasks; - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - tasks.push_back((*it)->get_task()); - } - return tasks; + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + if ((*it)->is_running()) { + return Result::Err(FWError::make(ErrorCode::TASK_ALREADY_RUNNING, + "Error: task already running ✗")); } + FW_LOG("[DEBUG] Starting task: " + (*it)->get_task_id() + " ..."); + TEST((*it)->start()); + } + FW_VERBOSE("[FLOWHOOK] All tasks started ✓"); + return Result::Ok(); +} - Result> FlowHookCore::get_watch_list(const std::string &task_id) - { - vector watched_paths; - for(auto it = task_runners.begin(); it != task_runners.end(); it++) - { - if((*it)->get_task_id() == task_id) - watched_paths = TRY((*it)->get_watch_list(), vector); - } - return Result>::Ok(watched_paths); - - // activate +Result FlowHookCore::stop_all() { + if (task_runners.empty()) { + return Result::Err( + FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: no tasks to stop ✗")); + } + + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + FW_LOG("[DEBUG] Stopping task " + (*it)->get_task_id() + " ..."); + TEST((*it)->stop()); + } + FW_VERBOSE("[FLOWHOOK] All tasks stopped ✓"); + return Result::Ok(); +} + +Result FlowHookCore::start_active() { + if (task_runners.empty()) { + return Result::Err( + FWError::make(ErrorCode::TASK_NOT_FOUND, "Error: no tasks to start ✗")); + } + + bool found_active = false; + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + if ((*it)->is_active()) { + found_active = true; + FW_LOG("[DEBUG] Starting task " + (*it)->get_task_id() + " ..."); + TEST((*it)->start()); } + } + if (!found_active) { + return Result::Err(FWError::make( + ErrorCode::TASK_NOT_FOUND, "Error: no active tasks to start ✗")); + } + FW_VERBOSE("[FLOWHOOK] All active tasks started ✓"); + return Result::Ok(); +} + +std::vector FlowHookCore::get_tasks() const { + vector tasks; + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + tasks.push_back((*it)->get_task()); + } + return tasks; +} + +Result> +FlowHookCore::get_watch_list(const std::string &task_id) { + vector watched_paths; + for (auto it = task_runners.begin(); it != task_runners.end(); it++) { + if ((*it)->get_task_id() == task_id) + watched_paths = TRY((*it)->get_watch_list(), vector); + } + return Result>::Ok(watched_paths); + + // activate } +} // namespace flowhook diff --git a/src/include/flowhook_core.h b/src/include/flowhook_core.h index 6a541cd..d9adec4 100644 --- a/src/include/flowhook_core.h +++ b/src/include/flowhook_core.h @@ -30,6 +30,7 @@ namespace flowhook { Result set_depth(const std::string &task_id, int depth); Result create_task(const std::string &task_name, const std::string &task_id); Result delete_task(const std::string &task_id); + bool is_task(const std::string &task_id); std::vector get_resolved_files(const std::string task_id); Result is_task_active(const std::string &task_id); diff --git a/src/task_runner.cpp b/src/task_runner.cpp index 7e72e3f..63b5f99 100644 --- a/src/task_runner.cpp +++ b/src/task_runner.cpp @@ -66,14 +66,15 @@ Result TaskRunner::set_depth(int num) { if (num > 10) { return Result::Err(FWError::make( ErrorCode::INVALID_DEPTH, "Error: invalid depth set - depth too much " - "✗, use a depth between 1 and 10")); - } else if (num < 1) { + "✗, use a depth between 0 and 10")); + } else if (num < -1) { return Result::Err(FWError::make( ErrorCode::INVALID_DEPTH, "Error: invalid depth set - depth set too " - "low ✗, use a depth between 1 and 10")); + "low ✗, use a depth between 0 and 10")); } task.watching_depth = num; + FW_LOG("[DEBUG] Depth set to " + std::to_string(num)); return Result::Ok(); } @@ -247,11 +248,12 @@ Result TaskRunner::add_path(const string &path) { TEST(add_path_internal(path, task.watching_depth, 0)); if (fs::is_directory(path)) { + task.dir_paths.push_back(path); FW_VERBOSE("[FLOWHOOK] Adding " + path + "'s children to task runner completed. ✓"); } else - FW_VERBOSE("[FLOWHOOK] Adding path " + path + - " to task runner completed. ✓"); + task.file_paths.push_back(path); + FW_VERBOSE("[FLOWHOOK] Adding path " + path + " to task runner completed. ✓"); return Result::Ok(); } @@ -267,7 +269,6 @@ Result TaskRunner::add_path_internal(const string &path, int MAX_DEPTH, } if (fs::is_directory(path)) { - task.dir_paths.push_back(path); FW_LOG("[DEBUG] Path " + path + " is a directory adding child files recursively..."); for (auto &entry : fs::directory_iterator(path)) { @@ -291,18 +292,19 @@ Result TaskRunner::add_path_internal(const string &path, int MAX_DEPTH, FW_LOG("[DEBUG] Adding path " + entry.path().string() + " to task completed. ✓"); } else if (entry.is_directory()) { - if (MAX_DEPTH > CURRENT_DEPTH) + if (MAX_DEPTH > CURRENT_DEPTH) { + resolved_files.push_back(entry.path().string()); TEST(add_path_internal(entry.path().string(), MAX_DEPTH, CURRENT_DEPTH + 1)); - else - FW_LOG("[DEBUG] Path " + entry.path().string() + - " is a directory. But MAX_DEPTH=" + to_string(MAX_DEPTH) + - " have been reached. Child files won't be watched."); + } else { + FW_LOG("[DEBUG] Path " + entry.path().string() + + " is a directory. But MAX_DEPTH=" + to_string(MAX_DEPTH) + + " have been reached. Child files won't be watched."); + } } } } else if (!fs::is_directory(path)) { FW_LOG("[DEBUG] Path " + path + " is a file. Adding to task..."); - task.file_paths.push_back(path); resolved_files.push_back(path); fw->add_path(path); } From d68f330109e881374d0ababfa3063ff409f25e02 Mon Sep 17 00:00:00 2001 From: mnasie Date: Wed, 1 Jul 2026 13:48:37 +0300 Subject: [PATCH 14/20] Add CLI integration tests to CI workflow Run the project binary installation and execute the full suite of CLI shell scripts. --- .github/workflows/tests.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d520791..22b577b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -32,3 +32,9 @@ jobs: - name: Run tests run: cd build && ./test_config_manager && ./test_task_runner && ./test_session_logger && ./test_flowhook_core + + - name: Install flowhook binary + run: sudo cmake --install build + + - name: Run CLI tests + run: bash cli/tests/run_all.sh From 88828b97623283a7025e1180e373fedf048bf9bb Mon Sep 17 00:00:00 2001 From: mnasie Date: Wed, 1 Jul 2026 13:49:03 +0300 Subject: [PATCH 15/20] Update tests.yml --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 22b577b..2fcd934 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -37,4 +37,4 @@ jobs: run: sudo cmake --install build - name: Run CLI tests - run: bash cli/tests/run_all.sh + run: bash cli/tests/test_cli.sh From b983657601a6b3d1171bc0016aab97c63a60c2c5 Mon Sep 17 00:00:00 2001 From: mnasie Date: Wed, 1 Jul 2026 13:57:04 +0300 Subject: [PATCH 16/20] Remove redundant directory changes in test scripts --- cli/tests/test_remove.sh | 1 - cli/tests/test_run.sh | 1 - cli/tests/test_set.sh | 1 - 3 files changed, 3 deletions(-) diff --git a/cli/tests/test_remove.sh b/cli/tests/test_remove.sh index 68c2435..cc3a9c6 100644 --- a/cli/tests/test_remove.sh +++ b/cli/tests/test_remove.sh @@ -1,6 +1,5 @@ #!/usr/bin/env bash set -uo pipefail -cd "$(dirname "$0")" || exit 1 PASS=0 FAIL=0 diff --git a/cli/tests/test_run.sh b/cli/tests/test_run.sh index 8136f66..34af7c3 100644 --- a/cli/tests/test_run.sh +++ b/cli/tests/test_run.sh @@ -1,6 +1,5 @@ #!/usr/bin/env bash set -uo pipefail -cd "$(dirname "$0")" || exit 1 PASS=0 FAIL=0 diff --git a/cli/tests/test_set.sh b/cli/tests/test_set.sh index d3dc66d..87f2ce9 100644 --- a/cli/tests/test_set.sh +++ b/cli/tests/test_set.sh @@ -1,6 +1,5 @@ #!/usr/bin/env bash set -uo pipefail -cd "$(dirname "$0")" || exit 1 PASS=0 FAIL=0 From e1e570dbe8ee668404a098209a8f66c72cb06f64 Mon Sep 17 00:00:00 2001 From: mnasie Date: Wed, 1 Jul 2026 14:04:49 +0300 Subject: [PATCH 17/20] Install flowhook binary to bin directory Add missing install rule to CMakeLists and verify the binary path in CI. --- .github/workflows/tests.yml | 4 +++- CMakeLists.txt | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2fcd934..6d69493 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -34,7 +34,9 @@ jobs: run: cd build && ./test_config_manager && ./test_task_runner && ./test_session_logger && ./test_flowhook_core - name: Install flowhook binary - run: sudo cmake --install build + run: | + sudo cmake --install build + which flowhook || echo "flowhook not found after install" - name: Run CLI tests run: bash cli/tests/test_cli.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index cf0c90b..7657b6d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,8 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) configure_file(src/version.h.in version.h) set(CMAKE_CXX_STANDARD 20) +install(TARGETS flowhook DESTINATION bin) + find_package(ftxui CONFIG REQUIRED) # core library From 43d360d2b4bf7d156d4c7d8a7568bc14af5097c0 Mon Sep 17 00:00:00 2001 From: mnasie Date: Wed, 1 Jul 2026 15:15:34 +0300 Subject: [PATCH 18/20] Update installation targets for flowhook_cli Install the flowhook_cli executable and create a symlink or rename it to flowhook in the bin directory to maintain legacy command support. --- CMakeLists.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7657b6d..29470f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,8 +5,6 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) configure_file(src/version.h.in version.h) set(CMAKE_CXX_STANDARD 20) -install(TARGETS flowhook DESTINATION bin) - find_package(ftxui CONFIG REQUIRED) # core library @@ -59,6 +57,11 @@ add_executable(flowhook_cli cli/src/main.cpp target_link_libraries(flowhook_cli PRIVATE flowhook_lib) target_include_directories(flowhook_cli PRIVATE cli/src) +install(TARGETS flowhook_cli DESTINATION bin) +install(PROGRAMS $ + DESTINATION bin + RENAME flowhook) + enable_testing() add_test(NAME flowhook_filewatcher_tests COMMAND test_filewatcher) add_test(NAME flowhook_task_runner_tests COMMAND test_task_runner) From 3cc643af6ab0dc25679f3b4e8a4bffc3a5358611 Mon Sep 17 00:00:00 2001 From: mnasie Date: Wed, 1 Jul 2026 15:25:30 +0300 Subject: [PATCH 19/20] Ignore paths matching ignore patterns --- src/task_runner.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/task_runner.cpp b/src/task_runner.cpp index 63b5f99..86b131d 100644 --- a/src/task_runner.cpp +++ b/src/task_runner.cpp @@ -245,6 +245,11 @@ Result TaskRunner::add_path(const string &path) { FWError::make(ErrorCode::PATH_NOT_FOUND, "Error: provided path " + path + " does not exist! ✗")); } + if(isIgnored(path)) + { + FW_LOG("[DEBUG] Path " + path + " matches ignored paths and patterns."); + return Result::Ok(); + } TEST(add_path_internal(path, task.watching_depth, 0)); if (fs::is_directory(path)) { From bdac0c4dfe2d0a0a4cbf811386ed5920d46b2da0 Mon Sep 17 00:00:00 2001 From: mnasie Date: Wed, 1 Jul 2026 16:05:09 +0300 Subject: [PATCH 20/20] Handle missing paths during initialization Automatically remove non-existent file and directory paths when initializing a task instead of failing the process. --- src/flowhook_core.cpp | 30 ++++++++++++++++++++++++++++-- src/task_runner.cpp | 30 +++++++++++++++--------------- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/src/flowhook_core.cpp b/src/flowhook_core.cpp index a1005e4..01547ed 100644 --- a/src/flowhook_core.cpp +++ b/src/flowhook_core.cpp @@ -58,9 +58,35 @@ Result FlowHookCore::init() { for (auto c : task.commands) TEST(tr->add_command(c)); for (auto p : task.file_paths) - TEST(tr->add_path(p)); + { + auto r = tr->add_path(p); + if(r.isErr()) + { + if(r.getErrCode() == ErrorCode::PATH_NOT_FOUND) + { + tr->delete_path(p); + } + else + { + return Result::Err(r.unwrapErr()); + } + } + } for (auto p : task.dir_paths) - TEST(tr->add_path(p)); + { + auto r = tr->add_path(p); + if(r.isErr()) + { + if(r.getErrCode() == ErrorCode::PATH_NOT_FOUND) + { + tr->delete_path(p); + } + else + { + return Result::Err(r.unwrapErr()); + } + } + } for (auto s : task.on_success) TEST(tr->add_on_success(s)); for (auto f : task.on_failure) diff --git a/src/task_runner.cpp b/src/task_runner.cpp index 86b131d..83d2574 100644 --- a/src/task_runner.cpp +++ b/src/task_runner.cpp @@ -243,12 +243,11 @@ Result TaskRunner::add_path(const string &path) { if (!fs::exists(path)) { return Result::Err( FWError::make(ErrorCode::PATH_NOT_FOUND, - "Error: provided path " + path + " does not exist! ✗")); + "Error: provided path " + path + " does not exist! ✗ \n // use the `flowhook remove --path ` command to remove it from the task")); } - if(isIgnored(path)) - { - FW_LOG("[DEBUG] Path " + path + " matches ignored paths and patterns."); - return Result::Ok(); + if (isIgnored(path)) { + FW_LOG("[DEBUG] Path " + path + " matches ignored paths and patterns."); + return Result::Ok(); } TEST(add_path_internal(path, task.watching_depth, 0)); @@ -277,6 +276,14 @@ Result TaskRunner::add_path_internal(const string &path, int MAX_DEPTH, FW_LOG("[DEBUG] Path " + path + " is a directory adding child files recursively..."); for (auto &entry : fs::directory_iterator(path)) { + if (isIgnored(entry.path().string())) { + FW_LOG("[DEBUG] Path " + entry.path().string() + + " matches ignored patterns."); + FW_LOG("[DEBUG] Adding Path " + entry.path().string() + + " to task failed. ✗"); + continue; + } + if (entry.is_regular_file()) { FW_LOG("[DEBUG] Adding path " + entry.path().string() + " to filewatcher..."); @@ -284,13 +291,6 @@ Result TaskRunner::add_path_internal(const string &path, int MAX_DEPTH, entry.path().string() + " matches ignored paths and patterns ..."); - if (isIgnored(entry.path().string())) { - FW_LOG("[DEBUG] Path " + entry.path().string() + - " matches ignored patterns."); - FW_LOG("[DEBUG] Adding Path " + entry.path().string() + - " to task failed. ✗"); - continue; - } resolved_files.push_back(entry.path().string()); fw->add_path(entry.path().string()); @@ -302,9 +302,9 @@ Result TaskRunner::add_path_internal(const string &path, int MAX_DEPTH, TEST(add_path_internal(entry.path().string(), MAX_DEPTH, CURRENT_DEPTH + 1)); } else { - FW_LOG("[DEBUG] Path " + entry.path().string() + - " is a directory. But MAX_DEPTH=" + to_string(MAX_DEPTH) + - " have been reached. Child files won't be watched."); + FW_LOG("[DEBUG] Path " + entry.path().string() + + " is a directory. But MAX_DEPTH=" + to_string(MAX_DEPTH) + + " have been reached. Child files won't be watched."); } } }