Commit 7d49e481 authored by Pádraig Ó Conbhuí's avatar Pádraig Ó Conbhuí
Browse files

Add src/megadep/megadep.test.cpp

Add test which checks megadep::megadep correctly reads and processes the
includes from a Rattlehead-generated project.

Change Log:
* Add explicit write_direct_includes and write_indirect_includes methods
  for MegadepPolicy in megadep::megadep.
* Add rm_rf function in tests/src/utilities/rm_rf.hpp, which implements
  rm -rf. Replace manual file management in tests with rm_rf.
* Move some console output from megadep::megadep to megadep.main.cpp.
* Add megadep::librattlehead alias library
parent cb2de32a
Pipeline #750 passed with stages
in 4 minutes and 1 second
......@@ -40,11 +40,13 @@ if(MEGADEP_ENABLE_TESTING)
add_executable(
libmegadep.test
src/megadep/filesystem.test.cpp
src/megadep/megadep.test.cpp
)
target_link_libraries(
libmegadep.test PRIVATE libmegadep megadep::catch2_runner
libmegadep.test
PRIVATE libmegadep megadep::librattlehead megadep::catch2_runner
)
target_include_directories(libmegadep.test PRIVATE src)
target_include_directories(libmegadep.test PRIVATE src tests/src)
add_test(
NAME libmegadep.test
......
......@@ -27,9 +27,24 @@ int main(int argc, char* argv[])
overloaded(
[](int exit_value) { return exit_value; },
[](const megadep::cli_options& options) {
auto megadep_policy =
megadep::serial_dependency_calculation{};
megadep_policy.print("Scanning:\n");
for (const auto& directory : options.scan_directories) {
megadep_policy.print(" - ", directory, '\n');
}
megadep_policy.print('\n');
megadep_policy.print("Including:\n");
for (const auto& directory : options.include_directories) {
megadep_policy.print(" - ", directory, '\n');
}
megadep_policy.print('\n');
megadep::megadep(
megadep::serial_dependency_calculation{},
options.scan_directories, options.include_directories);
megadep_policy, options.scan_directories,
options.include_directories);
return EXIT_SUCCESS;
}),
parse_result);
......
......@@ -15,18 +15,6 @@ void megadep(
const IncludeDirectories& include_directories)
{
megadep_policy.print("Scanning:\n");
for (const auto& directory : scan_directories) {
megadep_policy.print(" - ", directory, '\n');
}
megadep_policy.print('\n');
megadep_policy.print("Including:\n");
for (const auto& directory : include_directories) {
megadep_policy.print(" - ", directory, '\n');
}
megadep_policy.print('\n');
auto file_names = megadep_policy.build_file_map(scan_directories);
//
......@@ -35,7 +23,7 @@ void megadep(
auto file_to_includes = megadep_policy.build_direct_includes_map(
file_names, include_directories);
megadep_policy.write_includes(
megadep_policy.write_direct_includes(
file_to_includes, file_names, "direct-includes.yml");
//
......@@ -50,7 +38,7 @@ void megadep(
auto file_to_indirect_includes =
megadep_policy.build_indirect_includes_map(file_to_includers);
megadep_policy.write_includes(
megadep_policy.write_indirect_includes(
file_to_indirect_includes, file_names, "indirect-includes.yml");
}
......
#include <megadep/megadep.hpp>
#include <rattlehead/rattlehead.hpp>
#include <utilities/rm_rf.hpp>
#include <catch2/catch.hpp>
#include <algorithm>
#include <cstdint>
#include <iterator>
#include <map>
#include <string>
#include <vector>
// A dependency calculation policy that captures the file_to_direct_includes
// on attempted file write.
class intercepting_dependency_calculation :
public megadep::serial_dependency_calculation {
public:
std::vector<std::vector<uint64_t>> file_to_direct_includes;
std::vector<std::string> direct_file_names;
std::vector<std::vector<uint64_t>> file_to_indirect_includes;
std::vector<std::string> indirect_file_names;
// Capture the file_to_direct_includes list
template<typename FileToIncludes, typename FileNamesMap>
void write_direct_includes(
const FileToIncludes& file_to_includes,
const FileNamesMap& file_names,
const std::string& output_file_name)
{
file_to_direct_includes = file_to_includes;
direct_file_names.reserve(std::size(file_names));
for (size_t i = 0; i < std::size(file_names); i++) {
direct_file_names.emplace_back(file_names[i]);
}
}
// Capture the file_to_indirect_includes list
template<typename FileToIncludes, typename FileNamesMap>
void write_indirect_includes(
const FileToIncludes& file_to_includes,
const FileNamesMap& file_names,
const std::string& output_file_name)
{
file_to_indirect_includes = file_to_includes;
indirect_file_names.reserve(std::size(file_names));
for (size_t i = 0; i < std::size(file_names); i++) {
indirect_file_names.emplace_back(file_names[i]);
}
}
};
TEST_CASE("megadep::megadep", "[megadep][unit]")
{
size_t number_of_files = GENERATE(1, 2, 100);
size_t directories_per_directory = GENERATE(1, 2, 10);
size_t files_per_directory = GENERATE(1, 2, 10);
auto layout = megadep::rattlehead::project_layout(
number_of_files, files_per_directory, directories_per_directory);
size_t includes_per_file = GENERATE(0, 1, 2, 10, 100, 1000);
size_t seed = GENERATE(0, 1, 2);
auto project_graph = megadep::rattlehead::random_project_dependency_graph(
number_of_files, includes_per_file, seed);
const char* root_directory = "test-megadep";
megadep::rm_rf(root_directory);
// Run Rattlehead
auto path_policy = megadep::rattlehead::default_path_policy{root_directory};
megadep::rattlehead::build_project(
layout, project_graph,
megadep::rattlehead::posix_path_creation_policy{}, path_policy);
// Run Megadep with path interception
auto dependency_calculation = intercepting_dependency_calculation{};
const std::vector<std::string> scan_directories = {root_directory};
const std::vector<std::string> include_directories = {"."};
megadep::megadep(
dependency_calculation, scan_directories, include_directories);
//
// Test the direct includes
//
const auto& file_to_direct_includes =
dependency_calculation.file_to_direct_includes;
const auto& direct_file_names = dependency_calculation.direct_file_names;
// The number of files read should match the size of the layout
REQUIRE(std::size(file_to_direct_includes) == std::size(layout));
// The direct_file_names should be sorted......
REQUIRE(std::is_sorted(
std::cbegin(direct_file_names), std::cend(direct_file_names)));
// Create a map from megadep file indices to rattlehead indices
std::map<uint64_t, uint64_t> megadep_to_rattlehead_index;
for (size_t rattlehead_file_index = 0;
rattlehead_file_index < std::size(layout); rattlehead_file_index++) {
std::array<char, 4096> path_buffer;
const auto& layout_path = layout[rattlehead_file_index];
auto* path_end = path_policy.file_path(
std::data(path_buffer), std::size(path_buffer),
std::cbegin(layout_path), std::cend(layout_path));
// const auto& file_name = rattlehead_paths[rattlehead_file_index];
const std::string_view file_name{
std::data(path_buffer),
static_cast<size_t>(path_end - std::data(path_buffer))};
// Find the file_name in direct_file_names
const auto& megadep_file_it = std::lower_bound(
std::cbegin(direct_file_names), std::cend(direct_file_names),
file_name);
// The file must be found by megadep
REQUIRE(megadep_file_it != std::cend(direct_file_names));
REQUIRE(*megadep_file_it == file_name);
// Find the index of the file_name
auto megadep_file_index =
std::distance(std::cbegin(direct_file_names), megadep_file_it);
// The file must not have been found already
REQUIRE(megadep_to_rattlehead_index.count(megadep_file_index) == 0);
// Map the megadep index to the rattlehead index
megadep_to_rattlehead_index[megadep_file_index] = rattlehead_file_index;
}
// Check every megadep_file_index is represented
for (uint64_t megadep_file_index = 0;
megadep_file_index < std::size(file_to_direct_includes);
megadep_file_index++) {
REQUIRE(megadep_to_rattlehead_index.count(megadep_file_index) == 1);
}
// Check the direct dependencies found by megadep match the direct
// dependencies built by rattlehead
for (uint64_t megadep_file_index = 0;
megadep_file_index < std::size(file_to_direct_includes);
megadep_file_index++) {
const auto& megadep_direct_includes =
file_to_direct_includes[megadep_file_index];
const auto& rattlehead_direct_includes =
project_graph[megadep_to_rattlehead_index[megadep_file_index]];
REQUIRE(
std::size(megadep_direct_includes)
== std::size(rattlehead_direct_includes));
REQUIRE(std::equal(
std::cbegin(megadep_direct_includes),
std::cend(megadep_direct_includes),
std::cbegin(rattlehead_direct_includes),
[&](const auto& megadep_file_index,
const auto& rattlehead_file_index) {
return megadep_to_rattlehead_index[megadep_file_index]
== rattlehead_file_index;
}));
}
//
// Test the indirect includes
//
const auto& file_to_indirect_includes =
dependency_calculation.file_to_indirect_includes;
const auto& indirect_file_names =
dependency_calculation.indirect_file_names;
// The direct includes and indirect includes lists should have the same
// sizes and file names
REQUIRE(
std::size(file_to_indirect_includes)
== std::size(file_to_direct_includes));
REQUIRE(indirect_file_names == direct_file_names);
// For each file, recursively traverse its dependencies, and ensure it's
// in the indirect includes list
for (uint64_t file_index = 0;
file_index < std::size(file_to_indirect_includes); file_index++) {
const auto& indirect_includes = file_to_indirect_includes[file_index];
// We don't want to recursively check the same file twice, because
// it can take a loooong time with a high includes_per_file.
std::vector<bool> file_checked(
std::size(file_to_direct_includes), false);
const auto recursive_check_includes =
[&](const auto& recurse, uint64_t include_index) -> void {
// Return early if checked
if (file_checked[include_index] == true) {
return;
}
file_checked[include_index] = true;
const auto& direct_includes =
file_to_direct_includes[include_index];
for (const auto& direct_include : direct_includes) {
REQUIRE(
std::binary_search(
std::cbegin(indirect_includes),
std::cend(indirect_includes), direct_include)
== true);
recurse(recurse, direct_include);
}
};
recursive_check_includes(recursive_check_includes, file_index);
}
megadep::rm_rf(root_directory);
}
......@@ -204,6 +204,24 @@ class serial_dependency_calculation {
direct_includes_file.close();
}
template<typename FileToIncludes, typename FileNamesMap>
static void write_direct_includes(
const FileToIncludes& file_to_includes,
const FileNamesMap& file_names,
const std::string& output_file_name)
{
write_includes(file_to_includes, file_names, output_file_name);
}
template<typename FileToIncludes, typename FileNamesMap>
static void write_indirect_includes(
const FileToIncludes& file_to_includes,
const FileNamesMap& file_names,
const std::string& output_file_name)
{
write_includes(file_to_includes, file_names, output_file_name);
}
template<typename String, typename... Strings>
static void print(String&& string, Strings&&... strings)
{
......
# librattlehead + Rattlehead
add_library(librattlehead INTERFACE)
target_include_directories(librattlehead INTERFACE src)
target_compile_features(librattlehead INTERFACE cxx_std_17)
add_library(megadep::librattlehead ALIAS librattlehead)
add_executable(rattlehead src/rattlehead.main.cpp)
target_link_libraries(rattlehead PRIVATE librattlehead)
target_link_system_libraries(rattlehead PRIVATE CLI11::CLI11)
# Catch2 + Catch2 runner
add_subdirectory(../external/catch2 catch2)
add_library(
......@@ -11,16 +24,13 @@ target_compile_features(catch2_runner PUBLIC cxx_std_17)
add_library(megadep::catch2_runner ALIAS catch2_runner)
add_library(librattlehead INTERFACE)
target_include_directories(librattlehead INTERFACE src)
target_compile_features(librattlehead INTERFACE cxx_std_17)
add_executable(rattlehead src/rattlehead.main.cpp)
target_link_libraries(rattlehead PRIVATE librattlehead)
target_link_system_libraries(rattlehead PRIVATE CLI11::CLI11)
#
# Tests
#
enable_testing()
# Rattlehead unit tests
add_executable(rattlehead.test src/rattlehead/rattlehead.test.cpp)
target_link_libraries(
rattlehead.test PRIVATE librattlehead megadep::catch2_runner
......
......@@ -132,10 +132,10 @@ class counting_iterator {
// Create a map of index -> indices, representing a directed acyclic graph
std::vector<std::vector<size_t>> random_project_dependency_graph(
std::vector<std::vector<uint64_t>> random_project_dependency_graph(
size_t number_of_files, size_t includes_per_file, size_t seed = 0)
{
std::vector<std::vector<size_t>> dependency_graph;
std::vector<std::vector<uint64_t>> dependency_graph;
std::mt19937_64 rand(seed);
static_assert(
......@@ -159,7 +159,7 @@ std::vector<std::vector<size_t>> random_project_dependency_graph(
}
constexpr size_t project_layout_root = 0;
constexpr uint64_t project_layout_root = 0;
// Generate a structured project layout with a given number of files,
// a given number of files per directory, and subdirectories per directory.
......@@ -168,31 +168,31 @@ constexpr size_t project_layout_root = 0;
// files_per_directory > 0
// directories_per_directory == 0 -> files_per_directory =
// number_of_files
std::vector<std::vector<size_t>> project_layout(
std::vector<std::vector<uint64_t>> project_layout(
size_t number_of_files,
size_t files_per_directory,
size_t directories_per_directory)
{
std::vector<std::vector<size_t>> layout;
std::vector<std::vector<uint64_t>> layout;
size_t last_directory_index = 0;
size_t last_file_index = 0;
uint64_t last_directory_index = 0;
uint64_t last_file_index = 0;
std::vector<size_t> current_directory;
std::vector<uint64_t> current_directory;
current_directory.emplace_back(0);
for (size_t file_index = 0; file_index < number_of_files; file_index++) {
for (uint64_t file_index = 0; file_index < number_of_files; file_index++) {
current_directory.clear();
size_t directory_depth = 0;
size_t number_of_sibling_directories = 1;
size_t directory_index_start = 0;
uint64_t directory_depth = 0;
uint64_t number_of_sibling_directories = 1;
uint64_t directory_index_start = 0;
while (true) {
if (directory_index_start * files_per_directory > file_index) {
break;
}
size_t sibling_directory_index =
uint64_t sibling_directory_index =
file_index % number_of_sibling_directories;
current_directory.emplace_back(
......@@ -203,7 +203,7 @@ std::vector<std::vector<size_t>> project_layout(
if (number_of_sibling_directories == 1) {
number_of_sibling_directories =
std::max<size_t>(directories_per_directory, 1);
std::max<uint64_t>(directories_per_directory, 1);
}
else {
number_of_sibling_directories *= number_of_sibling_directories;
......
......@@ -2,6 +2,8 @@
#include <catch2/catch.hpp>
#include <utilities/rm_rf.hpp>
#include <algorithm>
#include <fstream>
#include <iterator>
......@@ -89,7 +91,7 @@ TEST_CASE("rattlehead::project_layout")
{
INFO("All paths should be unique");
std::set<std::vector<size_t>> paths;
std::set<std::vector<uint64_t>> paths;
for (const auto& path : layout) {
REQUIRE(paths.count(path) == 0);
paths.insert(path);
......@@ -98,7 +100,7 @@ TEST_CASE("rattlehead::project_layout")
{
INFO("Each directory should contain the requested number of files");
std::map<std::vector<size_t>, size_t> directory_counter;
std::map<std::vector<uint64_t>, size_t> directory_counter;
size_t max_directory_depth = 0;
for (auto path : layout) {
// pop off the filename to get the directory path
......@@ -250,8 +252,10 @@ TEST_CASE("rattlehead::posix_path_creation_policy")
std::array<char, 4096> path_buffer;
const auto path_policy =
rattlehead::default_path_policy{"test-posix_path_creation_policy"};
const char* root_directory = "test-posix_path_creation_policy";
megadep::rm_rf(root_directory);
const auto path_policy = rattlehead::default_path_policy{root_directory};
path_policy.file_path(
path_buffer.data(), path_buffer.size(), std::begin(path),
......@@ -260,24 +264,7 @@ TEST_CASE("rattlehead::posix_path_creation_policy")
auto path_creation_policy = rattlehead::posix_path_creation_policy{};
// Ensure not created
if (access(path_buffer.data(), F_OK) == 0) {
int err = unlink(path_buffer.data());
REQUIRE(err == 0);
std::array<char, 4096> directory_buffer;
auto path_end = std::prev(std::end(path));
while (std::begin(path) != path_end) {
path_policy.directory_path(
directory_buffer.data(), directory_buffer.size(),
std::begin(path), path_end);
int err = rmdir(directory_buffer.data());
REQUIRE(err == 0);
path_end = std::prev(path_end);
}
}
REQUIRE(access(path_buffer.data(), F_OK) != 0);
{
INFO("create_path should create a file at the given path");
REQUIRE(access(path_buffer.data(), F_OK) != 0);
......@@ -287,6 +274,8 @@ TEST_CASE("rattlehead::posix_path_creation_policy")
REQUIRE(access(path_buffer.data(), W_OK) == 0);
}
megadep::rm_rf(root_directory);
}
......@@ -307,7 +296,10 @@ TEST_CASE("rattlehead::build_project", "[build_project]")
number_of_files, includes_per_file, seed);
auto path_policy = rattlehead::default_path_policy{"test-build_project"};
const char* root_directory = "test-build_project";
megadep::rm_rf(root_directory);
auto path_policy = rattlehead::default_path_policy{root_directory};
rattlehead::build_project(
layout, project_graph, rattlehead::posix_path_creation_policy{},
......@@ -366,4 +358,6 @@ TEST_CASE("rattlehead::build_project", "[build_project]")
std::begin(dependency_paths), std::end(dependency_paths),
std::begin(file_includes)));
}
megadep::rm_rf(root_directory);
}
#ifndef MEGADEP_TEST_UTILITIES_RM_RF_HPP
#define MEGADEP_TEST_UTILITIES_RM_RF_HPP
#if !defined(_XOPEN_SOURCE)
#define _XOPEN_SOURCE 500
#endif
#include <ftw.h>
#include <unistd.h>
namespace megadep {
inline int rm_rf(const char* path)
{
const auto unlink_cb = [](const char* fpath, const struct stat* sb,
int typeflag, struct FTW* ftwbuf) {
int rv = remove(fpath);
if (rv) {
perror(fpath);
}
return rv;
};
if (access(path, F_OK) == 0) {
return nftw(path, unlink_cb, 64, FTW_DEPTH | FTW_PHYS);
}
return 0;
}
} // namespace megadep
#endif // MEGADEP_TEST_UTILITIES_RM_RF_HPP
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment