diff --git a/bootstrap/src/build_buildcc.cpp b/bootstrap/src/build_buildcc.cpp index e655a418..cae0ad44 100644 --- a/bootstrap/src/build_buildcc.cpp +++ b/bootstrap/src/build_buildcc.cpp @@ -20,10 +20,13 @@ namespace buildcc { void schema_gen_cb(BaseGenerator &generator, const BaseTarget &flatc_exe) { generator.AddInput("{gen_root_dir}/path.fbs", "path_fbs"); + generator.AddInput("{gen_root_dir}/custom_generator.fbs", + "custom_generator_fbs"); generator.AddInput("{gen_root_dir}/generator.fbs", "generator_fbs"); generator.AddInput("{gen_root_dir}/target.fbs", "target_fbs"); generator.AddOutput("{gen_build_dir}/path_generated.h"); + generator.AddOutput("{gen_build_dir}/custom_generator_generated.h"); generator.AddOutput("{gen_build_dir}/generator_generated.h"); generator.AddOutput("{gen_build_dir}/target_generated.h"); @@ -33,7 +36,7 @@ void schema_gen_cb(BaseGenerator &generator, const BaseTarget &flatc_exe) { // generator.AddCommand("{flatc_compiler} --help"); generator.AddCommand( "{flatc_compiler} -o {gen_build_dir} -I {gen_root_dir} --gen-object-api " - "--cpp {path_fbs} {generator_fbs} {target_fbs}"); + "--cpp {path_fbs} {custom_generator_fbs} {generator_fbs} {target_fbs}"); generator.Build(); } @@ -71,6 +74,7 @@ void buildcc_cb(BaseTarget &target, const BaseGenerator &schema_gen, // TARGET target.GlobSources("lib/target/src/common"); + target.GlobSources("lib/target/src/custom_generator"); target.GlobSources("lib/target/src/generator"); target.GlobSources("lib/target/src/api"); target.GlobSources("lib/target/src/target_info"); diff --git a/buildcc/buildcc.h b/buildcc/buildcc.h index 8f08c5db..e73bbffe 100644 --- a/buildcc/buildcc.h +++ b/buildcc/buildcc.h @@ -31,6 +31,7 @@ // Base #include "toolchain/toolchain.h" +#include "target/custom_generator.h" #include "target/generator.h" #include "target/target_info.h" #include "target/target.h" diff --git a/buildcc/lib/env/src/command.cpp b/buildcc/lib/env/src/command.cpp index 6620c37e..2962aa24 100644 --- a/buildcc/lib/env/src/command.cpp +++ b/buildcc/lib/env/src/command.cpp @@ -61,7 +61,14 @@ std::string Command::Construct( }); // Construct your command - return fmt::vformat(pattern, store); + std::string ret; + try { + ret = fmt::vformat(pattern, store); + } catch (const std::exception &e) { + env::assert_fatal( + fmt::format("Construct command failed: {}", e.what())); + } + return ret; } } // namespace buildcc::env diff --git a/buildcc/lib/target/cmake/common_target_src.cmake b/buildcc/lib/target/cmake/common_target_src.cmake index 76564d86..31ab0a03 100644 --- a/buildcc/lib/target/cmake/common_target_src.cmake +++ b/buildcc/lib/target/cmake/common_target_src.cmake @@ -34,6 +34,8 @@ set(COMMON_TARGET_SRCS include/target/api/target_getter.h # Generator + src/custom_generator/custom_generator.cpp + include/target/custom_generator.h src/generator/generator.cpp include/target/generator.h diff --git a/buildcc/lib/target/cmake/mock_target.cmake b/buildcc/lib/target/cmake/mock_target.cmake index 2d648938..cf2b6bde 100644 --- a/buildcc/lib/target/cmake/mock_target.cmake +++ b/buildcc/lib/target/cmake/mock_target.cmake @@ -1,5 +1,8 @@ add_library(mock_target STATIC ${COMMON_TARGET_SRCS} + # Custom Generator mocks + mock/custom_generator/runner.cpp + mock/custom_generator/recheck_states.cpp # Generator mocks src/generator/task.cpp diff --git a/buildcc/lib/target/cmake/target.cmake b/buildcc/lib/target/cmake/target.cmake index 98f16479..931b1c46 100644 --- a/buildcc/lib/target/cmake/target.cmake +++ b/buildcc/lib/target/cmake/target.cmake @@ -1,6 +1,8 @@ set(TARGET_SRCS ${COMMON_TARGET_SRCS} + src/custom_generator/recheck_states.cpp + src/generator/task.cpp src/generator/recheck_states.cpp diff --git a/buildcc/lib/target/include/target/custom_generator.h b/buildcc/lib/target/include/target/custom_generator.h new file mode 100644 index 00000000..8c10ee28 --- /dev/null +++ b/buildcc/lib/target/include/target/custom_generator.h @@ -0,0 +1,174 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TARGET_CUSTOM_GENERATOR_H_ +#define TARGET_CUSTOM_GENERATOR_H_ + +#include +#include +#include +#include + +#include "taskflow/taskflow.hpp" + +#include "env/command.h" +#include "env/task_state.h" + +#include "target/interface/builder_interface.h" + +#include "schema/custom_generator_serialization.h" +#include "schema/path.h" + +#include "target/common/target_env.h" + +namespace buildcc { + +// TODO, Shift to a different file +// TODO, Check if we need the "id" here as well +class CustomGeneratorContext { +public: + CustomGeneratorContext(const env::Command &c, const fs_unordered_set &i, + const fs_unordered_set &o) + : command(c), inputs(i), outputs(o) {} + +public: + const env::Command &command; + const fs_unordered_set &inputs; + const fs_unordered_set &outputs; +}; + +// clang-format off +typedef std::function GenerateCb; + +typedef std::function &)> DependencyCb; +// clang-format on + +struct UserRelInputOutputSchema : internal::RelInputOutputSchema { + fs_unordered_set inputs; + GenerateCb generate_cb; +}; + +struct UserCustomGeneratorSchema : public internal::CustomGeneratorSchema { + std::unordered_map rels_map; + + void ConvertToInternal() { + for (auto &r_miter : rels_map) { + r_miter.second.internal_inputs = path_schema_convert( + r_miter.second.inputs, internal::Path::CreateExistingPath); + auto p = internal_rels_map.emplace(r_miter.first, r_miter.second); + env::assert_fatal(p.second, + fmt::format("Could not save {}", r_miter.first)); + } + } +}; + +class CustomGenerator : public internal::BuilderInterface { +public: + CustomGenerator(const std::string &name, const TargetEnv &env) + : name_(name), + env_(env.GetTargetRootDir(), env.GetTargetBuildDir() / name), + serialization_(env_.GetTargetBuildDir() / fmt::format("{}.bin", name)) { + Initialize(); + } + virtual ~CustomGenerator() = default; + CustomGenerator(const CustomGenerator &) = delete; + + /** + * @brief Add default arguments for input, output and command requirements + * + * @param arguments Key-Value pair for arguments + */ + void AddDefaultArguments( + const std::unordered_map &arguments); + + /** + * @brief Single Generator task for inputs->generate_cb->outputs + * + * @param id Unique id associated with Generator task + * @param inputs File inputs + * @param outputs File outputs + * @param generate_cb User-defined generate callback to build outputs from the + * provided inputs + */ + void AddGenInfo(const std::string &id, const fs_unordered_set &inputs, + const fs_unordered_set &outputs, + const GenerateCb &generate_cb); + + // Callbacks + /** + * @brief Setup dependencies between Tasks using their `id` + * For example: `task_map["id1"].precede(task_map["id2"])` + * + * IMPORTANT: Successor tasks will not automatically run if dependent task is + * run. + * The Dependency callback only sets precedence (order in which your tasks + * should run) + * Default behaviour when dependency callback is not supplied: All task `id`s + * run in parallel. + * + * @param dependency_cb Unordered map of `id` and `task` + * The map can be safely mutated. + */ + void AddDependencyCb(const DependencyCb &dependency_cb); + + void Build() override; + + // Getters + const fs::path &GetBinaryPath() const { + return serialization_.GetSerializedFile(); + } + const fs::path &GetRootDir() const { return env_.GetTargetRootDir(); } + const fs::path &GetBuildDir() const { return env_.GetTargetBuildDir(); } + tf::Taskflow &GetTaskflow() { return tf_; } + +private: + void Initialize(); + + template void TaskRunner(const std::string &id); + + void GenerateTask(); + void BuildGenerate(std::unordered_map + &gen_selected_map, + std::unordered_map + &dummy_gen_selected_map); + + // Recheck states + void IdRemoved(); + void IdAdded(); + void IdUpdated(); + +private: + std::string name_; + TargetEnv env_; + internal::CustomGeneratorSerialization serialization_; + + // Serialization + UserCustomGeneratorSchema user_; + + std::mutex success_schema_mutex_; + std::unordered_map success_schema_; + + // Internal + env::Command command_; + tf::Taskflow tf_; + + // Callbacks + DependencyCb dependency_cb_; +}; + +} // namespace buildcc + +#endif diff --git a/buildcc/lib/target/include/target/generator.h b/buildcc/lib/target/include/target/generator.h index 74f9612a..3784e991 100644 --- a/buildcc/lib/target/include/target/generator.h +++ b/buildcc/lib/target/include/target/generator.h @@ -17,7 +17,6 @@ #ifndef TARGET_GENERATOR_H_ #define TARGET_GENERATOR_H_ -#include #include #include #include @@ -25,7 +24,6 @@ #include "taskflow/taskflow.hpp" -#include "env/env.h" #include "env/task_state.h" #include "env/command.h" @@ -54,8 +52,8 @@ class Generator : public internal::BuilderInterface { parallel_(parallel) { Initialize(); } - virtual ~Generator() {} - Generator(const Generator &generator) = delete; + virtual ~Generator() = default; + Generator(const Generator &) = delete; /** * @brief Add default arguments for input, output and command requirements diff --git a/buildcc/lib/target/include/target/interface/builder_interface.h b/buildcc/lib/target/include/target/interface/builder_interface.h index ae044274..22fbb7ba 100644 --- a/buildcc/lib/target/include/target/interface/builder_interface.h +++ b/buildcc/lib/target/include/target/interface/builder_interface.h @@ -27,6 +27,66 @@ namespace buildcc::internal { +enum PathState { + kNoChange, + kRemoved, + kAdded, + kUpdated, +}; + +template +inline bool CheckChanged(const T &previous, const T ¤t) { + bool changed = false; + if (previous != current) { + changed = true; + } + return changed; +} + +/** + * @brief + * + * @return PathState Returns first state found if `Removed`, `Added` or + * `Updated` + * If none of the above states are true then it returns `NoChange` + */ +inline PathState CheckPaths(const internal::path_unordered_set &previous_path, + const internal::path_unordered_set ¤t_path) { + PathState state{PathState::kNoChange}; + + // * Old path is removed + const bool removed = std::any_of( + previous_path.begin(), previous_path.end(), [&](const internal::Path &p) { + return current_path.find(p) == current_path.end(); + }); + if (removed) { + state = PathState::kRemoved; + } else { + (void)std::any_of(current_path.cbegin(), current_path.cend(), + [&](const internal::Path &p) -> bool { + bool dirty = false; + const auto find = previous_path.find(p); + const bool added_cond = (find == previous_path.end()); + if (added_cond) { + dirty = true; + state = kAdded; + } else { + const bool updated_cond = + (p.GetLastWriteTimestamp() > + find->GetLastWriteTimestamp()); + if (updated_cond) { + dirty = true; + state = kUpdated; + } else { + dirty = false; + } + } + return dirty; + }); + } + return state; +} + // TODO, 1. Consider updating Recheck* APIs - do not modify internal `dirty_` // flag // TODO, 2. Consider removing dependency on target/common/util.h diff --git a/buildcc/lib/target/mock/custom_generator/recheck_states.cpp b/buildcc/lib/target/mock/custom_generator/recheck_states.cpp new file mode 100644 index 00000000..c8c6471c --- /dev/null +++ b/buildcc/lib/target/mock/custom_generator/recheck_states.cpp @@ -0,0 +1,43 @@ +#include "target/custom_generator.h" + +#include "expect_custom_generator.h" + +#include "CppUTestExt/MockSupport.h" + +namespace buildcc { + +static constexpr const char *const ID_REMOVED_FUNCTION = + "CustomGenerator::IdRemoved"; +static constexpr const char *const ID_ADDED_FUNCTION = + "CustomGenerator::IdAdded"; +static constexpr const char *const ID_UPDATED_FUNCTION = + "CustomGenerator::IdUpdated"; + +void CustomGenerator::IdRemoved() { + mock().actualCall(ID_REMOVED_FUNCTION).onObject(this); +} +void CustomGenerator::IdAdded() { + mock().actualCall(ID_ADDED_FUNCTION).onObject(this); +} +void CustomGenerator::IdUpdated() { + mock().actualCall(ID_UPDATED_FUNCTION).onObject(this); +} + +namespace m { + +void CustomGeneratorExpect_IdRemoved(unsigned int calls, + CustomGenerator *generator) { + mock().expectNCalls(calls, ID_REMOVED_FUNCTION).onObject(generator); +} +void CustomGeneratorExpect_IdAdded(unsigned int calls, + CustomGenerator *generator) { + mock().expectNCalls(calls, ID_ADDED_FUNCTION).onObject(generator); +} +void CustomGeneratorExpect_IdUpdated(unsigned int calls, + CustomGenerator *generator) { + mock().expectNCalls(calls, ID_UPDATED_FUNCTION).onObject(generator); +} + +} // namespace m + +} // namespace buildcc diff --git a/buildcc/lib/target/mock/custom_generator/runner.cpp b/buildcc/lib/target/mock/custom_generator/runner.cpp new file mode 100644 index 00000000..8014c851 --- /dev/null +++ b/buildcc/lib/target/mock/custom_generator/runner.cpp @@ -0,0 +1,13 @@ +#include "target/custom_generator.h" + +#include "expect_custom_generator.h" + +namespace buildcc::m { + +void CustomGeneratorRunner(CustomGenerator &custom_generator) { + tf::Executor executor(1); + executor.run(custom_generator.GetTaskflow()); + executor.wait_for_all(); +} + +} // namespace buildcc::m diff --git a/buildcc/lib/target/mock/expect_custom_generator.h b/buildcc/lib/target/mock/expect_custom_generator.h new file mode 100644 index 00000000..f54ed17c --- /dev/null +++ b/buildcc/lib/target/mock/expect_custom_generator.h @@ -0,0 +1,23 @@ +#ifndef TARGET_MOCK_EXPECT_CUSTOM_GENERATOR_H_ +#define TARGET_MOCK_EXPECT_CUSTOM_GENERATOR_H_ + +#include "target/custom_generator.h" + +namespace buildcc::m { + +/** + * @brief Runs the generator using Taskflow with 1 thread + * CppUTest cannot mock with multiple threads + */ +void CustomGeneratorRunner(CustomGenerator &custom_generator); + +void CustomGeneratorExpect_IdRemoved(unsigned int calls, + CustomGenerator *generator); +void CustomGeneratorExpect_IdAdded(unsigned int calls, + CustomGenerator *generator); +void CustomGeneratorExpect_IdUpdated(unsigned int calls, + CustomGenerator *generator); + +} // namespace buildcc::m + +#endif diff --git a/buildcc/lib/target/src/custom_generator/custom_generator.cpp b/buildcc/lib/target/src/custom_generator/custom_generator.cpp new file mode 100644 index 00000000..8d980c3f --- /dev/null +++ b/buildcc/lib/target/src/custom_generator/custom_generator.cpp @@ -0,0 +1,239 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "target/custom_generator.h" + +namespace { +constexpr const char *const kStartGeneratorTaskName = "Start Generator"; +constexpr const char *const kEndGeneratorTaskName = "End Generator"; + +constexpr const char *const kCommandTaskName = "Command"; +constexpr const char *const kGenerateTaskName = "Generate"; + +} // namespace + +namespace buildcc { + +void CustomGenerator::AddDefaultArguments( + const std::unordered_map &arguments) { + for (const auto &arg_iter : arguments) { + command_.AddDefaultArgument(arg_iter.first, + command_.Construct(arg_iter.second)); + } +} + +void CustomGenerator::AddGenInfo(const std::string &id, + const fs_unordered_set &inputs, + const fs_unordered_set &outputs, + const GenerateCb &generate_cb) { + env::assert_fatal(user_.rels_map.find(id) == user_.rels_map.end(), + fmt::format("Duplicate id {} detected", id)); + ASSERT_FATAL(generate_cb, "Invalid callback provided"); + + UserRelInputOutputSchema schema; + for (const auto &i : inputs) { + schema.inputs.emplace(command_.Construct(path_as_string(i))); + } + for (const auto &o : outputs) { + schema.outputs.emplace(command_.Construct(path_as_string(o))); + } + schema.generate_cb = generate_cb; + user_.rels_map.emplace(id, std::move(schema)); +} + +void CustomGenerator::AddDependencyCb(const DependencyCb &dependency_cb) { + dependency_cb_ = dependency_cb; +} + +void CustomGenerator::Build() { + (void)serialization_.LoadFromFile(); + + GenerateTask(); +} + +// PRIVATE +void CustomGenerator::Initialize() { + // Checks + env::assert_fatal( + Project::IsInit(), + "Environment is not initialized. Use the buildcc::Project::Init API"); + + // + fs::create_directories(env_.GetTargetBuildDir()); + command_.AddDefaultArguments({ + {"gen_root_dir", path_as_string(env_.GetTargetRootDir())}, + {"gen_build_dir", path_as_string(env_.GetTargetBuildDir())}, + }); + + // + unique_id_ = name_; + tf_.name(name_); +} + +void CustomGenerator::BuildGenerate( + std::unordered_map &gen_selected_map, + std::unordered_map + &dummy_gen_selected_map) { + if (!serialization_.IsLoaded()) { + gen_selected_map = user_.rels_map; + dirty_ = true; + } else { + // DONE, Conditionally select internal_rels depending on what has changed + const auto &prev_rels = serialization_.GetLoad().internal_rels_map; + const auto &curr_rels = user_.rels_map; + + // DONE, MAP REMOVED condition Check if prev_rels exists in curr_rels + // If prev_rels does not exist in curr_rels, has been removed from existing + // build + // We need this condition to only set the dirty_ flag + for (const auto &prev_miter : prev_rels) { + const auto &id = prev_miter.first; + if (curr_rels.find(id) == curr_rels.end()) { + // MAP REMOVED condition + IdRemoved(); + dirty_ = true; + break; + } + } + + // DONE, MAP ADDED condition Check if curr_rels exists in prev_rels + // If curr_rels does not exist in prev_rels, has been added to existing + // build + for (const auto &curr_miter : curr_rels) { + const auto &id = curr_miter.first; + if (prev_rels.find(id) == prev_rels.end()) { + // MAP ADDED condition + IdAdded(); + gen_selected_map.emplace(curr_miter.first, curr_miter.second); + dirty_ = true; + } else { + // MAP UPDATED condition (*checked later) + // This is because tasks can have dependencies amongst each other we can + // compute task level rebuilds later + dummy_gen_selected_map.emplace(curr_miter.first, curr_miter.second); + } + } + } +} + +void CustomGenerator::GenerateTask() { + tf::Task generate_task = tf_.emplace([&](tf::Subflow &subflow) { + if (env::get_task_state() != env::TaskState::SUCCESS) { + return; + } + + try { + std::unordered_map + selected_user_schema; + std::unordered_map + dummy_selected_user_schema; + BuildGenerate(selected_user_schema, dummy_selected_user_schema); + + std::unordered_map task_map; + // Create task for selected schema + for (const auto &selected_miter : selected_user_schema) { + const auto &id = selected_miter.first; + tf::Task task = subflow + .emplace([&]() { + try { + TaskRunner(id); + } catch (...) { + env::set_task_state(env::TaskState::FAILURE); + } + }) + .name(id); + task_map.emplace(id, task); + } + + for (auto &dummy_selected_miter : dummy_selected_user_schema) { + const auto &id = dummy_selected_miter.first; + auto ¤t_info = dummy_selected_miter.second; + tf::Task task = subflow + .emplace([&]() { + try { + user_.rels_map.at(id).internal_inputs = + internal::path_schema_convert( + current_info.inputs, + internal::Path::CreateExistingPath); + TaskRunner(id); + } catch (...) { + env::set_task_state(env::TaskState::FAILURE); + } + }) + .name(id); + task_map.emplace(id, task); + } + + // Dependencies between ids + if (dependency_cb_) { + try { + dependency_cb_(task_map); + } catch (...) { + env::log_critical(__FUNCTION__, "Dependency callback failed"); + env::set_task_state(env::TaskState::FAILURE); + } + } + + // NOTE, Do not call detach otherwise this will fail + subflow.join(); + + // Store dummy_selected and successfully run schema + if (dirty_) { + UserCustomGeneratorSchema user_final_schema; + user_final_schema.rels_map.insert(success_schema_.begin(), + success_schema_.end()); + + user_final_schema.ConvertToInternal(); + serialization_.UpdateStore(user_final_schema); + env::assert_fatal(serialization_.StoreToFile(), + fmt::format("Store failed for {}", name_)); + } + + } catch (...) { + env::set_task_state(env::TaskState::FAILURE); + } + }); + // TODO, Instead of "Generate" name the task of user's choice + generate_task.name(kGenerateTaskName); +} + +template void CustomGenerator::TaskRunner(const std::string &id) { + const auto ¤t_info = user_.rels_map.at(id); + bool rerun = false; + if constexpr (run) { + rerun = true; + } else { + const auto &previous_info = + serialization_.GetLoad().internal_rels_map.at(id); + rerun = internal::CheckPaths(previous_info.internal_inputs, + current_info.internal_inputs) != + internal::PathState::kNoChange || + internal::CheckChanged(previous_info.outputs, current_info.outputs); + } + + if (rerun) { + dirty_ = true; + buildcc::CustomGeneratorContext ctx(command_, current_info.inputs, + current_info.outputs); + bool success = current_info.generate_cb(ctx); + env::assert_fatal(success, fmt::format("Generate Cb failed for id {}", id)); + } + + std::lock_guard guard(success_schema_mutex_); + success_schema_.emplace(id, current_info); +} + +} // namespace buildcc diff --git a/buildcc/lib/target/src/custom_generator/recheck_states.cpp b/buildcc/lib/target/src/custom_generator/recheck_states.cpp new file mode 100644 index 00000000..0ccc632c --- /dev/null +++ b/buildcc/lib/target/src/custom_generator/recheck_states.cpp @@ -0,0 +1,25 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "target/custom_generator.h" + +namespace buildcc { + +void CustomGenerator::IdRemoved() {} +void CustomGenerator::IdAdded() {} +void CustomGenerator::IdUpdated() {} + +} // namespace buildcc diff --git a/buildcc/lib/target/test/target/CMakeLists.txt b/buildcc/lib/target/test/target/CMakeLists.txt index 44acb230..a77da279 100644 --- a/buildcc/lib/target/test/target/CMakeLists.txt +++ b/buildcc/lib/target/test/target/CMakeLists.txt @@ -42,11 +42,19 @@ target_link_libraries(test_target_state PRIVATE target_interface) add_test(NAME test_target_state COMMAND test_target_state) # Generator +add_executable(test_custom_generator + test_custom_generator.cpp +) +target_link_libraries(test_custom_generator PRIVATE target_interface) + add_executable(test_generator test_generator.cpp ) target_link_libraries(test_generator PRIVATE target_interface) +add_test(NAME test_custom_generator COMMAND test_custom_generator + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) add_test(NAME test_generator COMMAND test_generator WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) diff --git a/buildcc/lib/target/test/target/test_builder_interface.cpp b/buildcc/lib/target/test/target/test_builder_interface.cpp index 9cc2d2ac..ba1d02e9 100644 --- a/buildcc/lib/target/test/target/test_builder_interface.cpp +++ b/buildcc/lib/target/test/target/test_builder_interface.cpp @@ -62,6 +62,70 @@ TEST(TestBuilderInterfaceGroup, IncorrectUpdateRecheckCb) { CHECK_THROWS(std::exception, tbi.RecheckPathIncorrectUpdateCb()); } +TEST(TestBuilderInterfaceGroup, CheckChanged) { + std::vector previous{"", "v1"}; + std::vector current{"", "v1"}; + CHECK_FALSE(buildcc::internal::CheckChanged(previous, current)); + + current.clear(); + current.push_back(""); + CHECK_TRUE(buildcc::internal::CheckChanged(previous, current)); +} + +TEST(TestBuilderInterfaceGroup, CheckPaths) { + { + auto pathstate = buildcc::internal::CheckPaths( + { + buildcc::internal::Path::CreateNewPath("", 0x1234), + buildcc::internal::Path::CreateNewPath("v1", 0x2345), + }, + { + buildcc::internal::Path::CreateNewPath("", 0x1234), + buildcc::internal::Path::CreateNewPath("v1", 0x2345), + }); + CHECK_TRUE(pathstate == buildcc::internal::PathState::kNoChange); + } + + { + auto pathstate = buildcc::internal::CheckPaths( + { + buildcc::internal::Path::CreateNewPath("", 0x1234), + buildcc::internal::Path::CreateNewPath("v1", 0x2345), + }, + { + buildcc::internal::Path::CreateNewPath("", 0x1234), + }); + CHECK_TRUE(pathstate == buildcc::internal::PathState::kRemoved); + } + + { + auto pathstate = buildcc::internal::CheckPaths( + { + buildcc::internal::Path::CreateNewPath("", 0x1234), + buildcc::internal::Path::CreateNewPath("v1", 0x2345), + }, + { + buildcc::internal::Path::CreateNewPath("", 0x1234), + buildcc::internal::Path::CreateNewPath("v1", 0x2345), + buildcc::internal::Path::CreateNewPath("v2", 0x3456), + }); + CHECK_TRUE(pathstate == buildcc::internal::PathState::kAdded); + } + + { + auto pathstate = buildcc::internal::CheckPaths( + { + buildcc::internal::Path::CreateNewPath("", 0x1234), + buildcc::internal::Path::CreateNewPath("v1", 0x2345), + }, + { + buildcc::internal::Path::CreateNewPath("", 0x1234), + buildcc::internal::Path::CreateNewPath("v1", 0x3456), + }); + CHECK_TRUE(pathstate == buildcc::internal::PathState::kUpdated); + } +} + int main(int ac, char **av) { return CommandLineTestRunner::RunAllTests(ac, av); } diff --git a/buildcc/lib/target/test/target/test_custom_generator.cpp b/buildcc/lib/target/test/target/test_custom_generator.cpp new file mode 100644 index 00000000..27def93c --- /dev/null +++ b/buildcc/lib/target/test/target/test_custom_generator.cpp @@ -0,0 +1,742 @@ +#include "target/custom_generator.h" + +#include "expect_command.h" +#include "expect_custom_generator.h" +#include "test_target_util.h" + +// #include "test_target_util.h" +// #include "taskflow/taskflow.hpp" +// #include "env/util.h" + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" +#include "CppUTestExt/MockSupport.h" + +// clang-format off +TEST_GROUP(CustomGeneratorTestGroup) +{ + void teardown() { + mock().checkExpectations(); + mock().clear(); + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); + } +}; +// clang-format on + +fs::path BUILD_DIR = fs::current_path() / "intermediate" / "custom_generator"; + +static bool BasicGenerateCb(const buildcc::CustomGeneratorContext &ctx) { + (void)ctx; + return mock().actualCall("BasicGenerateCb").returnBoolValue(); +} + +TEST(CustomGeneratorTestGroup, Basic) { + buildcc::CustomGenerator cgen("basic", ""); + cgen.AddGenInfo("id1", {"{gen_root_dir}/dummy_main.c"}, + {"{gen_build_dir}/dummy_main.o"}, BasicGenerateCb); + cgen.AddGenInfo("id2", {"{gen_root_dir}/dummy_main.cpp"}, {}, + BasicGenerateCb); + cgen.Build(); + + mock().expectOneCall("BasicGenerateCb").andReturnValue(true); + mock().expectOneCall("BasicGenerateCb").andReturnValue(true); + buildcc::m::CustomGeneratorRunner(cgen); + + // Serialization check + { + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + + const auto &internal_map = serialization.GetLoad().internal_rels_map; + CHECK_EQUAL(internal_map.size(), 2); + const auto &id1_info = internal_map.at("id1"); + CHECK_EQUAL(id1_info.internal_inputs.size(), 1); + CHECK_EQUAL(id1_info.outputs.size(), 1); + + const auto &id2_info = internal_map.at("id2"); + CHECK_EQUAL(id2_info.internal_inputs.size(), 1); + CHECK_EQUAL(id2_info.outputs.size(), 0); + } +} + +TEST(CustomGeneratorTestGroup, Basic_Failure) { + buildcc::CustomGenerator cgen("basic_failure", ""); + cgen.AddGenInfo("id1", {"{gen_root_dir}/dummy_main.c"}, {}, BasicGenerateCb); + cgen.AddGenInfo("id2", {"{gen_root_dir}/dummy_main.cpp"}, {}, + BasicGenerateCb); + cgen.Build(); + + mock().expectOneCall("BasicGenerateCb").andReturnValue(true); + mock().expectOneCall("BasicGenerateCb").andReturnValue(false); + buildcc::m::CustomGeneratorRunner(cgen); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::FAILURE); + + // Load + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + + const auto &internal_map = serialization.GetLoad().internal_rels_map; + CHECK_EQUAL(internal_map.size(), 1); +} + +TEST(CustomGeneratorTestGroup, DefaultArgumentUsage) { + buildcc::CustomGenerator cgen("default_argument_usage", ""); + cgen.AddDefaultArguments({ + {"dummy_main_c", "{gen_root_dir}/dummy_main.c"}, + {"dummy_main_o", "{gen_build_dir}/dummy_main.o"}, + {"dummy_main_cpp", "{gen_root_dir}/dummy_main.cpp"}, + }); + cgen.AddGenInfo("id1", {"{dummy_main_c}"}, {"{dummy_main_o}"}, + BasicGenerateCb); + cgen.AddGenInfo("id2", {"{dummy_main_cpp}"}, {}, BasicGenerateCb); + cgen.Build(); + + mock().expectOneCall("BasicGenerateCb").andReturnValue(true); + mock().expectOneCall("BasicGenerateCb").andReturnValue(true); + buildcc::m::CustomGeneratorRunner(cgen); + + // Serialization check + { + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + + const auto &internal_map = serialization.GetLoad().internal_rels_map; + CHECK_EQUAL(internal_map.size(), 2); + const auto &id1_info = internal_map.at("id1"); + CHECK_EQUAL(id1_info.internal_inputs.size(), 1); + CHECK_EQUAL(id1_info.outputs.size(), 1); + + const auto &id2_info = internal_map.at("id2"); + CHECK_EQUAL(id2_info.internal_inputs.size(), 1); + CHECK_EQUAL(id2_info.outputs.size(), 0); + } +} + +TEST(CustomGeneratorTestGroup, FailureCases) { + { + buildcc::CustomGenerator cgen("failure_no_cb", ""); + buildcc::GenerateCb cb; + CHECK_THROWS(std::exception, cgen.AddGenInfo("id1", {}, {}, cb)); + } + + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); + + { + buildcc::CustomGenerator cgen("failure_cannot_save", ""); + fs::create_directory( + cgen.GetBinaryPath()); // make a folder so that file cannot be saved + + cgen.AddGenInfo("id1", {}, {}, BasicGenerateCb); + cgen.AddGenInfo("id2", {}, {}, BasicGenerateCb); + cgen.Build(); + + mock().expectOneCall("BasicGenerateCb").andReturnValue(true); + mock().expectOneCall("BasicGenerateCb").andReturnValue(true); + buildcc::m::CustomGeneratorRunner(cgen); + + CHECK_TRUE(buildcc::env::get_task_state() == + buildcc::env::TaskState::FAILURE); + } + + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); + + { + buildcc::CustomGenerator cgen("gen_task_not_run_no_io", ""); + cgen.Build(); + + buildcc::m::CustomGeneratorRunner(cgen); + } + + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); + + { + buildcc::env::set_task_state(buildcc::env::TaskState::FAILURE); + + buildcc::CustomGenerator cgen("gen_task_state_failure", ""); + cgen.AddGenInfo("id1", {}, {}, BasicGenerateCb); + cgen.Build(); + buildcc::m::CustomGeneratorRunner(cgen); + + CHECK_TRUE(buildcc::env::get_task_state() == + buildcc::env::TaskState::FAILURE); + } + + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); +} + +static bool Dep1Cb(const buildcc::CustomGeneratorContext &ctx) { + (void)ctx; + mock().actualCall("Dep1Cb"); + return buildcc::env::Command::Execute(""); +} + +static bool Dep2Cb(const buildcc::CustomGeneratorContext &ctx) { + (void)ctx; + mock().actualCall("Dep2Cb"); + return buildcc::env::Command::Execute(""); +} + +static void DependencyCb(std::unordered_map &task_map) { + task_map.at("id1").precede(task_map.at("id2")); +} + +TEST(CustomGeneratorTestGroup, AddDependency_BasicCheck) { + constexpr const char *const kGenName = "add_dependency_basic_check"; + { + buildcc::CustomGenerator cgen(kGenName, ""); + cgen.AddGenInfo("id2", {"{gen_root_dir}/dummy_main.c"}, + {"{gen_build_dir}/dummy_main.o"}, Dep2Cb); + cgen.AddGenInfo("id1", {"{gen_root_dir}/dummy_main.cpp"}, + {"{gen_build_dir}/dummy_main.o"}, Dep1Cb); + cgen.AddDependencyCb(DependencyCb); + cgen.Build(); + + mock().expectOneCall("Dep1Cb"); + buildcc::env::m::CommandExpect_Execute(1, true); + mock().expectOneCall("Dep2Cb"); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::m::CustomGeneratorRunner(cgen); + + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + CHECK_EQUAL(serialization.GetLoad().internal_rels_map.size(), 2); + } +} + +static bool FileDep1Cb(const buildcc::CustomGeneratorContext &ctx) { + mock().actualCall("FileDep1Cb"); + for (const auto &o : ctx.outputs) { + CHECK_TRUE(buildcc::env::save_file(o.string().c_str(), "", false)); + } + return true; +} + +static bool FileDep2Cb(const buildcc::CustomGeneratorContext &ctx) { + mock().actualCall("FileDep2Cb"); + for (const auto &i : ctx.inputs) { + CHECK_TRUE(fs::exists(i)); + } + return true; +} + +TEST(CustomGeneratorTestGroup, AddDependency_FileDep) { + constexpr const char *const kGenName = "add_dependency_file_dep"; + { + buildcc::CustomGenerator cgen(kGenName, ""); + cgen.AddGenInfo("id1", {"{gen_root_dir}/dummy_main.c"}, + {"{gen_build_dir}/dummy_main.o"}, FileDep1Cb); + cgen.AddGenInfo("id2", {"{gen_build_dir}/dummy_main.o"}, {}, FileDep2Cb); + cgen.AddDependencyCb(DependencyCb); + cgen.Build(); + + mock().expectOneCall("FileDep1Cb"); + mock().expectOneCall("FileDep2Cb"); + buildcc::m::CustomGeneratorRunner(cgen); + + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + CHECK_EQUAL(serialization.GetLoad().internal_rels_map.size(), 2); + } +} + +TEST(CustomGeneratorTestGroup, AddDependency_FileDep_WithRebuild) { + constexpr const char *const kGenName = "add_dependency_file_dep_with_rebuild"; + + fs::path kInputFile = + (BUILD_DIR / kGenName / "dummy_main.c").make_preferred(); + UT_PRINT(kInputFile.string().c_str()); + fs::create_directories(BUILD_DIR / kGenName); + CHECK_TRUE(buildcc::env::save_file(kInputFile.string().c_str(), "", false)); + + { + buildcc::CustomGenerator cgen(kGenName, ""); + cgen.AddGenInfo("id1", {"{gen_build_dir}/dummy_main.c"}, + {"{gen_build_dir}/dummy_main.o"}, FileDep1Cb); + cgen.AddGenInfo("id2", {"{gen_build_dir}/dummy_main.o"}, {}, FileDep2Cb); + cgen.AddDependencyCb(DependencyCb); + cgen.Build(); + + mock().expectOneCall("FileDep1Cb"); + mock().expectOneCall("FileDep2Cb"); + buildcc::m::CustomGeneratorRunner(cgen); + + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + CHECK_EQUAL(serialization.GetLoad().internal_rels_map.size(), 2); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::SUCCESS); + } + + // Same, no change + { + buildcc::CustomGenerator cgen(kGenName, ""); + cgen.AddGenInfo("id1", {"{gen_build_dir}/dummy_main.c"}, + {"{gen_build_dir}/dummy_main.o"}, FileDep1Cb); + cgen.AddGenInfo("id2", {"{gen_build_dir}/dummy_main.o"}, {}, FileDep2Cb); + cgen.AddDependencyCb(DependencyCb); + cgen.Build(); + + buildcc::m::CustomGeneratorRunner(cgen); + + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + CHECK_EQUAL(serialization.GetLoad().internal_rels_map.size(), 2); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::SUCCESS); + } + + // reset + fs::remove_all(BUILD_DIR / kGenName / "dummy_main.o"); + + // Remove id1, should cause id2 to fail + // NOTE, dirty_ == false is not made true when id2 is run, however id removed + // sets dirty_ == true + { + buildcc::CustomGenerator cgen(kGenName, ""); + cgen.AddGenInfo("id2", {"{gen_build_dir}/dummy_main.o"}, {}, FileDep2Cb); + cgen.AddDependencyCb(DependencyCb); + cgen.Build(); + + buildcc::m::CustomGeneratorExpect_IdRemoved(1, &cgen); + buildcc::m::CustomGeneratorRunner(cgen); + + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + CHECK_EQUAL(serialization.GetLoad().internal_rels_map.size(), 0); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::FAILURE); + } + + // reset + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); + fs::remove_all(BUILD_DIR / kGenName / "dummy_main.o"); + + // Added + { + buildcc::CustomGenerator cgen(kGenName, ""); + cgen.AddGenInfo("id1", {"{gen_build_dir}/dummy_main.c"}, + {"{gen_build_dir}/dummy_main.o"}, FileDep1Cb); + cgen.AddGenInfo("id2", {"{gen_build_dir}/dummy_main.o"}, {}, FileDep2Cb); + cgen.AddDependencyCb(DependencyCb); + cgen.Build(); + + buildcc::m::CustomGeneratorExpect_IdAdded(1, &cgen); + buildcc::m::CustomGeneratorExpect_IdAdded(1, &cgen); + mock().expectOneCall("FileDep1Cb"); + mock().expectOneCall("FileDep2Cb"); + buildcc::m::CustomGeneratorRunner(cgen); + + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + CHECK_EQUAL(serialization.GetLoad().internal_rels_map.size(), 2); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::SUCCESS); + } + + // reset + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); + fs::remove_all(BUILD_DIR / kGenName / "dummy_main.o"); + + buildcc::m::blocking_sleep(1); + buildcc::env::save_file(kInputFile.string().c_str(), "", false); + + // Update id1:dummy_main.c -> updated dummy_main.o -> should rerun id2 as well + { + buildcc::CustomGenerator cgen(kGenName, ""); + + cgen.AddGenInfo("id1", {"{gen_build_dir}/dummy_main.c"}, + {"{gen_build_dir}/dummy_main.o"}, FileDep1Cb); + cgen.AddGenInfo("id2", {"{gen_build_dir}/dummy_main.o"}, {}, FileDep2Cb); + cgen.AddDependencyCb(DependencyCb); + cgen.Build(); + + mock().expectOneCall("FileDep1Cb"); + mock().expectOneCall("FileDep2Cb"); + buildcc::m::CustomGeneratorRunner(cgen); + + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + CHECK_EQUAL(serialization.GetLoad().internal_rels_map.size(), 2); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::SUCCESS); + } + + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); +} + +static bool RealGenerateCb(const buildcc::CustomGeneratorContext &ctx) { + (void)ctx; + mock().actualCall("RealGenerateCb"); + return buildcc::env::Command::Execute(""); +} + +TEST(CustomGeneratorTestGroup, RealGenerate_Basic) { + constexpr const char *const kGenName = "real_generator_basic"; + { + buildcc::CustomGenerator cgen(kGenName, ""); + cgen.AddGenInfo("id1", {"{gen_root_dir}/dummy_main.cpp"}, + {"{gen_build_dir}/dummy_main.o"}, RealGenerateCb); + cgen.AddGenInfo("id2", {"{gen_root_dir}/dummy_main.c"}, + {"{gen_build_dir}/dummy_main.o"}, RealGenerateCb); + cgen.Build(); + + mock().expectOneCall("RealGenerateCb"); + buildcc::env::m::CommandExpect_Execute(1, true); + mock().expectOneCall("RealGenerateCb"); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::m::CustomGeneratorRunner(cgen); + + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + CHECK_EQUAL(serialization.GetLoad().internal_rels_map.size(), 2); + + fs::remove_all(cgen.GetBinaryPath()); + } + + { + buildcc::CustomGenerator cgen(kGenName, ""); + cgen.AddGenInfo("id1", {"{gen_root_dir}/dummy_main.cpp"}, {}, + RealGenerateCb); + cgen.AddGenInfo("id2", {"{gen_root_dir}/dummy_main.c"}, {}, RealGenerateCb); + cgen.Build(); + + mock().expectOneCall("RealGenerateCb"); + buildcc::env::m::CommandExpect_Execute(1, false); + mock().expectOneCall("RealGenerateCb"); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::m::CustomGeneratorRunner(cgen); + + CHECK_TRUE(buildcc::env::get_task_state() == + buildcc::env::TaskState::FAILURE); + + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + CHECK_EQUAL(serialization.GetLoad().internal_rels_map.size(), 1); + + fs::remove_all(cgen.GetBinaryPath()); + } + + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); +} + +TEST(CustomGeneratorTestGroup, RealGenerate_RemoveAndAdd) { + constexpr const char *const kGenName = "real_generator_remove_and_add"; + { + buildcc::CustomGenerator cgen(kGenName, ""); + cgen.AddGenInfo("id1", {"{gen_root_dir}/dummy_main.cpp"}, + {"{gen_build_dir}/dummy_main.o"}, RealGenerateCb); + cgen.AddGenInfo("id2", {"{gen_root_dir}/dummy_main.c"}, + {"{gen_build_dir}/dummy_main.o"}, RealGenerateCb); + cgen.Build(); + + mock().expectOneCall("RealGenerateCb"); + buildcc::env::m::CommandExpect_Execute(1, true); + mock().expectOneCall("RealGenerateCb"); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::m::CustomGeneratorRunner(cgen); + + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + CHECK_EQUAL(serialization.GetLoad().internal_rels_map.size(), 2); + auto imap = serialization.GetLoad().internal_rels_map; + CHECK_EQUAL(imap.at("id1").internal_inputs.size(), 1); + CHECK_EQUAL(imap.at("id2").internal_inputs.size(), 1); + + CHECK_EQUAL(imap.at("id1").outputs.size(), 1); + CHECK_EQUAL(imap.at("id2").outputs.size(), 1); + } + + // Same, no change + { + buildcc::CustomGenerator cgen(kGenName, ""); + cgen.AddGenInfo("id1", {"{gen_root_dir}/dummy_main.cpp"}, + {"{gen_build_dir}/dummy_main.o"}, RealGenerateCb); + cgen.AddGenInfo("id2", {"{gen_root_dir}/dummy_main.c"}, + {"{gen_build_dir}/dummy_main.o"}, RealGenerateCb); + cgen.Build(); + + buildcc::m::CustomGeneratorRunner(cgen); + + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + CHECK_EQUAL(serialization.GetLoad().internal_rels_map.size(), 2); + auto imap = serialization.GetLoad().internal_rels_map; + CHECK_EQUAL(imap.at("id1").internal_inputs.size(), 1); + CHECK_EQUAL(imap.at("id2").internal_inputs.size(), 1); + + CHECK_EQUAL(imap.at("id1").outputs.size(), 1); + CHECK_EQUAL(imap.at("id2").outputs.size(), 1); + } + + // Map Removed + { + buildcc::CustomGenerator cgen(kGenName, ""); + cgen.AddGenInfo("id1", {"{gen_root_dir}/dummy_main.cpp"}, + {"{gen_build_dir}/dummy_main.o"}, RealGenerateCb); + + cgen.Build(); + + buildcc::m::CustomGeneratorExpect_IdRemoved(1, &cgen); + buildcc::m::CustomGeneratorRunner(cgen); + + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + CHECK_EQUAL(serialization.GetLoad().internal_rels_map.size(), 1); + auto imap = serialization.GetLoad().internal_rels_map; + CHECK_EQUAL(imap.at("id1").internal_inputs.size(), 1); + CHECK_EQUAL(imap.at("id1").outputs.size(), 1); + + CHECK_THROWS(std::out_of_range, imap.at("id2")); + } + + // Map Added Failure + { + buildcc::CustomGenerator cgen(kGenName, ""); + cgen.AddGenInfo("id1", {"{gen_root_dir}/dummy_main.cpp"}, + {"{gen_build_dir}/dummy_main.o"}, RealGenerateCb); + cgen.AddGenInfo("id2", {"{gen_root_dir}/dummy_main.c"}, + {"{gen_build_dir}/dummy_main.o"}, RealGenerateCb); + cgen.Build(); + + buildcc::m::CustomGeneratorExpect_IdAdded(1, &cgen); + mock().expectOneCall("RealGenerateCb"); + buildcc::env::m::CommandExpect_Execute(1, false); + buildcc::m::CustomGeneratorRunner(cgen); + + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + CHECK_EQUAL(serialization.GetLoad().internal_rels_map.size(), 1); + auto imap = serialization.GetLoad().internal_rels_map; + CHECK_EQUAL(imap.at("id1").internal_inputs.size(), 1); + CHECK_EQUAL(imap.at("id1").outputs.size(), 1); + CHECK_THROWS(std::out_of_range, imap.at("id2")); + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::FAILURE); + } + + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); + + // Map Added Success + { + buildcc::CustomGenerator cgen(kGenName, ""); + cgen.AddGenInfo("id1", {"{gen_root_dir}/dummy_main.cpp"}, + {"{gen_build_dir}/dummy_main.o"}, RealGenerateCb); + cgen.AddGenInfo("id2", {"{gen_root_dir}/dummy_main.c"}, + {"{gen_build_dir}/dummy_main.o"}, RealGenerateCb); + cgen.Build(); + + buildcc::m::CustomGeneratorExpect_IdAdded(1, &cgen); + mock().expectOneCall("RealGenerateCb"); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::m::CustomGeneratorRunner(cgen); + + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + CHECK_EQUAL(serialization.GetLoad().internal_rels_map.size(), 2); + auto imap = serialization.GetLoad().internal_rels_map; + CHECK_EQUAL(imap.at("id1").internal_inputs.size(), 1); + CHECK_EQUAL(imap.at("id2").internal_inputs.size(), 1); + + CHECK_EQUAL(imap.at("id1").outputs.size(), 1); + CHECK_EQUAL(imap.at("id2").outputs.size(), 1); + } +} + +TEST(CustomGeneratorTestGroup, RealGenerate_Update_Failure) { + constexpr const char *const kGenName = "real_generator_update_failure"; + + { + buildcc::CustomGenerator cgen(kGenName, ""); + buildcc::env::save_file( + (cgen.GetBuildDir() / "dummy_main.c").string().c_str(), "", false); + buildcc::env::save_file( + (cgen.GetBuildDir() / "dummy_main.cpp").string().c_str(), "", false); + + cgen.AddGenInfo("id1", {"{gen_build_dir}/dummy_main.c"}, + {"{gen_build_dir}/dummy_main.o"}, RealGenerateCb); + cgen.AddGenInfo("id2", {"{gen_build_dir}/dummy_main.cpp"}, + {"{gen_build_dir}/other_dummy_main.o"}, RealGenerateCb); + cgen.Build(); + + mock().expectOneCall("RealGenerateCb"); + buildcc::env::m::CommandExpect_Execute(1, true); + mock().expectOneCall("RealGenerateCb"); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::m::CustomGeneratorRunner(cgen); + + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + CHECK_EQUAL(serialization.GetLoad().internal_rels_map.size(), 2); + auto imap = serialization.GetLoad().internal_rels_map; + CHECK_EQUAL(imap.at("id1").internal_inputs.size(), 1); + CHECK_EQUAL(imap.at("id2").internal_inputs.size(), 1); + + CHECK_EQUAL(imap.at("id1").outputs.size(), 1); + CHECK_EQUAL(imap.at("id2").outputs.size(), 1); + } + + buildcc::m::blocking_sleep(1); + + // Updated Input file Failure + UT_PRINT("Updated Input file: Failure\r\n"); + { + buildcc::CustomGenerator cgen(kGenName, ""); + buildcc::env::save_file( + (cgen.GetBuildDir() / "dummy_main.cpp").string().c_str(), "", false); + + cgen.AddGenInfo("id1", {"{gen_build_dir}/dummy_main.c"}, + {"{gen_build_dir}/dummy_main.o"}, RealGenerateCb); + cgen.AddGenInfo("id2", {"{gen_build_dir}/dummy_main.cpp"}, + {"{gen_build_dir}/other_dummy_main.o"}, RealGenerateCb); + cgen.Build(); + + mock().expectOneCall("RealGenerateCb"); + buildcc::env::m::CommandExpect_Execute(1, false); + buildcc::m::CustomGeneratorRunner(cgen); + + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + CHECK_EQUAL(serialization.GetLoad().internal_rels_map.size(), 1); + auto imap = serialization.GetLoad().internal_rels_map; + CHECK_EQUAL(imap.at("id1").internal_inputs.size(), 1); + CHECK_EQUAL(imap.at("id1").outputs.size(), 1); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::FAILURE); + } + + buildcc::env::set_task_state(buildcc::env::TaskState::SUCCESS); +} + +TEST(CustomGeneratorTestGroup, RealGenerate_Update_Success) { + constexpr const char *const kGenName = "real_generator_update_success"; + + { + buildcc::CustomGenerator cgen(kGenName, ""); + buildcc::env::save_file( + (cgen.GetBuildDir() / "dummy_main.c").string().c_str(), "", false); + buildcc::env::save_file( + (cgen.GetBuildDir() / "dummy_main.cpp").string().c_str(), "", false); + + cgen.AddGenInfo("id1", {"{gen_build_dir}/dummy_main.c"}, + {"{gen_build_dir}/dummy_main.o"}, RealGenerateCb); + cgen.AddGenInfo("id2", {"{gen_build_dir}/dummy_main.cpp"}, + {"{gen_build_dir}/other_dummy_main.o"}, RealGenerateCb); + cgen.Build(); + + mock().expectOneCall("RealGenerateCb"); + buildcc::env::m::CommandExpect_Execute(1, true); + mock().expectOneCall("RealGenerateCb"); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::m::CustomGeneratorRunner(cgen); + + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + CHECK_EQUAL(serialization.GetLoad().internal_rels_map.size(), 2); + auto imap = serialization.GetLoad().internal_rels_map; + CHECK_EQUAL(imap.at("id1").internal_inputs.size(), 1); + CHECK_EQUAL(imap.at("id2").internal_inputs.size(), 1); + + CHECK_EQUAL(imap.at("id1").outputs.size(), 1); + CHECK_EQUAL(imap.at("id2").outputs.size(), 1); + } + + buildcc::m::blocking_sleep(1); + + // Updated Input file Success + UT_PRINT("Updated Input file: Success\r\n"); + { + buildcc::CustomGenerator cgen(kGenName, ""); + buildcc::env::save_file( + (cgen.GetBuildDir() / "dummy_main.cpp").string().c_str(), "", false); + + std::uint64_t last_write_timestamp = static_cast( + fs::last_write_time(cgen.GetBuildDir() / "dummy_main.cpp") + .time_since_epoch() + .count()); + + cgen.AddGenInfo("id1", {"{gen_build_dir}/dummy_main.c"}, + {"{gen_build_dir}/dummy_main.o"}, RealGenerateCb); + cgen.AddGenInfo("id2", {"{gen_build_dir}/dummy_main.cpp"}, + {"{gen_build_dir}/other_dummy_main.o"}, RealGenerateCb); + cgen.Build(); + + mock().expectOneCall("RealGenerateCb"); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::m::CustomGeneratorRunner(cgen); + + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + CHECK_EQUAL(serialization.GetLoad().internal_rels_map.size(), 2); + auto imap = serialization.GetLoad().internal_rels_map; + CHECK_EQUAL(imap.at("id1").internal_inputs.size(), 1); + CHECK_EQUAL(imap.at("id1").outputs.size(), 1); + + CHECK_EQUAL(imap.at("id2").internal_inputs.size(), 1); + CHECK_EQUAL(imap.at("id2").outputs.size(), 1); + + CHECK_EQUAL( + last_write_timestamp, + imap.at("id2").internal_inputs.begin()->GetLastWriteTimestamp()); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::SUCCESS); + } + + // Updated Output file Success + UT_PRINT("Updated Output file: Success\r\n"); + { + buildcc::CustomGenerator cgen(kGenName, ""); + + cgen.AddGenInfo("id1", {"{gen_build_dir}/dummy_main.c"}, + {"{gen_build_dir}/dummy_main.o"}, RealGenerateCb); + cgen.AddGenInfo("id2", {"{gen_build_dir}/dummy_main.cpp"}, + {"{gen_build_dir}/rename_dummy_main.o"}, RealGenerateCb); + cgen.Build(); + + mock().expectOneCall("RealGenerateCb"); + buildcc::env::m::CommandExpect_Execute(1, true); + buildcc::m::CustomGeneratorRunner(cgen); + + buildcc::internal::CustomGeneratorSerialization serialization( + cgen.GetBinaryPath()); + CHECK_TRUE(serialization.LoadFromFile()); + CHECK_EQUAL(serialization.GetLoad().internal_rels_map.size(), 2); + auto imap = serialization.GetLoad().internal_rels_map; + CHECK_EQUAL(imap.at("id1").internal_inputs.size(), 1); + CHECK_EQUAL(imap.at("id1").outputs.size(), 1); + + CHECK_EQUAL(imap.at("id2").internal_inputs.size(), 1); + CHECK_EQUAL(imap.at("id2").outputs.size(), 1); + + CHECK(buildcc::env::get_task_state() == buildcc::env::TaskState::SUCCESS); + } +} + +int main(int ac, char **av) { + fs::remove_all(BUILD_DIR); + buildcc::Project::Init(fs::current_path() / "data", BUILD_DIR); + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/plugins/CMakeLists.txt b/buildcc/plugins/CMakeLists.txt index 0518831b..42e4700d 100644 --- a/buildcc/plugins/CMakeLists.txt +++ b/buildcc/plugins/CMakeLists.txt @@ -21,16 +21,17 @@ target_link_libraries(mock_plugins PUBLIC ) # Tests -add_executable(test_buildcc_find - test/test_buildcc_find.cpp -) -target_link_libraries(test_buildcc_find PRIVATE - mock_plugins -) +# Removed test_buildcc_find till it is not complete +# add_executable(test_buildcc_find +# test/test_buildcc_find.cpp +# ) +# target_link_libraries(test_buildcc_find PRIVATE +# mock_plugins +# ) -add_test(NAME test_buildcc_find COMMAND test_buildcc_find - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/test -) +# add_test(NAME test_buildcc_find COMMAND test_buildcc_find +# WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/test +# ) endif() diff --git a/buildcc/schema/cmake/schema.cmake b/buildcc/schema/cmake/schema.cmake index b3c996f8..01acf57f 100644 --- a/buildcc/schema/cmake/schema.cmake +++ b/buildcc/schema/cmake/schema.cmake @@ -1,6 +1,7 @@ # schema test if (${TESTING}) add_library(mock_schema STATIC + src/custom_generator_serialization.cpp src/generator_serialization.cpp src/target_serialization.cpp @@ -8,6 +9,7 @@ if (${TESTING}) include/schema/interface/serialization_interface.h include/schema/path.h + include/schema/custom_generator_serialization.h include/schema/generator_serialization.h include/schema/target_serialization.h ) @@ -30,10 +32,18 @@ if (${TESTING}) target_link_options(mock_schema PUBLIC ${TEST_LINK_FLAGS} ${BUILD_LINK_FLAGS}) # Tests - add_dependencies(mock_schema fbs_to_header) + add_executable(test_custom_generator_serialization + test/test_custom_generator_serialization.cpp + ) + target_link_libraries(test_custom_generator_serialization PRIVATE mock_schema) + + add_test(NAME test_custom_generator_serialization COMMAND test_custom_generator_serialization + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/test + ) endif() set(SCHEMA_SRCS + src/custom_generator_serialization.cpp src/generator_serialization.cpp src/target_serialization.cpp diff --git a/buildcc/schema/cmake/schema_generate.cmake b/buildcc/schema/cmake/schema_generate.cmake index cbbe4b23..4ba97f55 100644 --- a/buildcc/schema/cmake/schema_generate.cmake +++ b/buildcc/schema/cmake/schema_generate.cmake @@ -3,11 +3,13 @@ set(SCHEMA_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated CACHE PATH "Generate set(FBS_FILES ${CMAKE_CURRENT_SOURCE_DIR}/path.fbs + ${CMAKE_CURRENT_SOURCE_DIR}/custom_generator.fbs ${CMAKE_CURRENT_SOURCE_DIR}/generator.fbs ${CMAKE_CURRENT_SOURCE_DIR}/target.fbs ) set(FBS_GEN_FILES ${SCHEMA_BUILD_DIR}/path_generated.h + ${SCHEMA_BUILD_DIR}/custom_generator_generated.h ${SCHEMA_BUILD_DIR}/generator_generated.h ${SCHEMA_BUILD_DIR}/target_generated.h ) diff --git a/buildcc/schema/custom_generator.fbs b/buildcc/schema/custom_generator.fbs new file mode 100644 index 00000000..8c5ce146 --- /dev/null +++ b/buildcc/schema/custom_generator.fbs @@ -0,0 +1,32 @@ +// Copyright 2021-2022 Niket Naidu. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +include "path.fbs"; + +namespace schema.internal; + +// Relational input and output files +table RelInputOutput { + id:string (key); + inputs:[Path]; + outputs:[string]; +} + +// Custom generator regenerate conditions +table CustomGenerator { + name:string (key); + rels:[RelInputOutput]; +} + +root_type CustomGenerator; diff --git a/buildcc/schema/include/schema/custom_generator_serialization.h b/buildcc/schema/include/schema/custom_generator_serialization.h new file mode 100644 index 00000000..0b440d3d --- /dev/null +++ b/buildcc/schema/include/schema/custom_generator_serialization.h @@ -0,0 +1,61 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SCHEMA_CUSTOM_GENERATOR_SERIALIZATION_H_ +#define SCHEMA_CUSTOM_GENERATOR_SERIALIZATION_H_ + +#include +#include +#include + +#include "schema/path.h" + +#include "schema/interface/serialization_interface.h" + +namespace buildcc::internal { + +struct RelInputOutputSchema { + path_unordered_set internal_inputs; + fs_unordered_set outputs; +}; + +struct CustomGeneratorSchema { + std::string name; + std::unordered_map internal_rels_map; +}; + +class CustomGeneratorSerialization : public SerializationInterface { +public: + CustomGeneratorSerialization(const fs::path &serialized_file) + : SerializationInterface(serialized_file) {} + + void UpdateStore(const CustomGeneratorSchema &store) { store_ = store; } + const CustomGeneratorSchema &GetLoad() const { return load_; } + const CustomGeneratorSchema &GetStore() const { return store_; } + +private: + bool Verify(const std::string &serialized_data) override; + bool Load(const std::string &serialized_data) override; + bool Store(const fs::path &absolute_serialized_file) override; + +private: + CustomGeneratorSchema load_; + CustomGeneratorSchema store_; +}; + +} // namespace buildcc::internal + +#endif diff --git a/buildcc/schema/src/custom_generator_serialization.cpp b/buildcc/schema/src/custom_generator_serialization.cpp new file mode 100644 index 00000000..49eac4e3 --- /dev/null +++ b/buildcc/schema/src/custom_generator_serialization.cpp @@ -0,0 +1,84 @@ +/* + * Copyright 2021-2022 Niket Naidu. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "schema/custom_generator_serialization.h" + +// Third party +#include "flatbuffers/flatbuffers.h" + +// Private +#include "schema/private/schema_util.h" + +// Schema generated +#include "custom_generator_generated.h" + +namespace buildcc::internal { + +// PRIVATE + +bool CustomGeneratorSerialization::Verify(const std::string &serialized_data) { + flatbuffers::Verifier verifier((const uint8_t *)serialized_data.c_str(), + serialized_data.length()); + return fbs::VerifyCustomGeneratorBuffer(verifier); +} + +bool CustomGeneratorSerialization::Load(const std::string &serialized_data) { + const auto *custom_generator = + fbs::GetCustomGenerator((const void *)serialized_data.c_str()); + // Verified, does not need a nullptr check + + const auto *fbs_rels = custom_generator->rels(); + if (fbs_rels == nullptr) { + return false; + } + + // rel_io->id is a required parameter, hence rel_io cannot be nullptr + for (const auto *rel_io : *fbs_rels) { + RelInputOutputSchema schema; + extract_path(rel_io->inputs(), schema.internal_inputs); + extract(rel_io->outputs(), schema.outputs); + load_.internal_rels_map.emplace(rel_io->id()->c_str(), std::move(schema)); + } + return true; +} + +bool CustomGeneratorSerialization::Store( + const fs::path &absolute_serialized_file) { + flatbuffers::FlatBufferBuilder builder; + + std::vector> fbs_rels; + for (const auto &rel_miter : store_.internal_rels_map) { + const auto &id = rel_miter.first; + const auto &rel_io = rel_miter.second; + auto fbs_internal_inputs = + internal::create_fbs_vector_path(builder, rel_io.internal_inputs); + auto fbs_outputs = + internal::create_fbs_vector_string(builder, rel_io.outputs); + auto fbs_rel = fbs::CreateRelInputOutputDirect( + builder, id.c_str(), &fbs_internal_inputs, &fbs_outputs); + fbs_rels.push_back(fbs_rel); + } + + auto fbs_generator = + fbs::CreateCustomGeneratorDirect(builder, store_.name.c_str(), &fbs_rels); + fbs::FinishCustomGeneratorBuffer(builder, fbs_generator); + + return env::save_file(path_as_string(absolute_serialized_file).c_str(), + (const char *)builder.GetBufferPointer(), + builder.GetSize(), true); +} + +} // namespace buildcc::internal diff --git a/buildcc/schema/test/.gitignore b/buildcc/schema/test/.gitignore new file mode 100644 index 00000000..a8a0dcec --- /dev/null +++ b/buildcc/schema/test/.gitignore @@ -0,0 +1 @@ +*.bin diff --git a/buildcc/schema/test/dump/.gitkeep b/buildcc/schema/test/dump/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/buildcc/schema/test/test_custom_generator_serialization.cpp b/buildcc/schema/test/test_custom_generator_serialization.cpp new file mode 100644 index 00000000..1d69366c --- /dev/null +++ b/buildcc/schema/test/test_custom_generator_serialization.cpp @@ -0,0 +1,83 @@ +#include "schema/custom_generator_serialization.h" + +#include "schema/private/schema_util.h" + +#include "flatbuffers/flatbuffers.h" + +#include "custom_generator_generated.h" + +// NOTE, Make sure all these includes are AFTER the system and header includes +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTest/MemoryLeakDetectorNewMacros.h" +#include "CppUTest/TestHarness.h" +#include "CppUTest/Utest.h" +#include "CppUTestExt/MockSupport.h" + +// clang-format off +TEST_GROUP(CustomGeneratorSerializationTestGroup) +{ + void teardown() { + mock().clear(); + } +}; +// clang-format on + +TEST(CustomGeneratorSerializationTestGroup, FormatEmptyCheck) { + buildcc::internal::CustomGeneratorSerialization serialization( + "dump/FormatEmptyCheck.bin"); + + { + flatbuffers::FlatBufferBuilder builder; + // Entire std::vector is nullptr + auto fbs_generator = + schema::internal::CreateCustomGeneratorDirect(builder, "", nullptr); + schema::internal::FinishCustomGeneratorBuffer(builder, fbs_generator); + + CHECK_TRUE(buildcc::env::save_file( + serialization.GetSerializedFile().string().c_str(), + (const char *)builder.GetBufferPointer(), builder.GetSize(), true)); + + CHECK_FALSE(serialization.LoadFromFile()); + + fs::remove_all(serialization.GetSerializedFile()); + } + + { + flatbuffers::FlatBufferBuilder builder; + // RelInputOutput in nullptr + auto rel_io = schema::internal::CreateRelInputOutputDirect(builder, ""); + std::vector v{rel_io}; + auto fbs_generator = + schema::internal::CreateCustomGeneratorDirect(builder, "", &v); + schema::internal::FinishCustomGeneratorBuffer(builder, fbs_generator); + + CHECK_TRUE(buildcc::env::save_file( + serialization.GetSerializedFile().string().c_str(), + (const char *)builder.GetBufferPointer(), builder.GetSize(), true)); + + CHECK_TRUE(serialization.LoadFromFile()); + + fs::remove_all(serialization.GetSerializedFile()); + } +} + +TEST(CustomGeneratorSerializationTestGroup, EmptyFile_Failure) { + { + buildcc::internal::CustomGeneratorSerialization serialization( + "dump/empty_file.bin"); + CHECK_FALSE(serialization.LoadFromFile()); + } + + { + buildcc::internal::CustomGeneratorSerialization serialization( + "dump/empty_file.bin"); + char data[] = {0}; + buildcc::env::save_file(serialization.GetSerializedFile().string().c_str(), + (const char *)data, 1, true); + CHECK_FALSE(serialization.LoadFromFile()); + } +} + +int main(int ac, char **av) { + return CommandLineTestRunner::RunAllTests(ac, av); +} diff --git a/buildcc/toolchains/CMakeLists.txt b/buildcc/toolchains/CMakeLists.txt index 4bb1a8c2..b1380fcd 100644 --- a/buildcc/toolchains/CMakeLists.txt +++ b/buildcc/toolchains/CMakeLists.txt @@ -37,7 +37,7 @@ if (${TESTING}) ${TEST_LINK_LIBS} ) - add_executable(test_toolchain_specialized test/test_toolchain_specialized) + add_executable(test_toolchain_specialized test/test_toolchain_specialized.cpp) target_link_libraries(test_toolchain_specialized PRIVATE mock_toolchain_specialized) add_test(NAME test_toolchain_specialized COMMAND test_toolchain_specialized)