From bfdb4e9d2cfc7890c5f194e670039fa76c391330 Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Fri, 10 May 2024 15:09:50 +0200 Subject: Added tests --- Makefile | 6 +- env.cpp | 13 +++ env.h | 6 + file.cpp | 32 ++++++ file.h | 6 + test-ymake.cpp | 355 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 6 files changed, 402 insertions(+), 16 deletions(-) create mode 100644 env.cpp create mode 100644 env.h diff --git a/Makefile b/Makefile index c1d2167..a7c178c 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,14 @@ PROJECTNAME=ymake -SRC=main.cpp ymake.cpp Builder.cpp ProcessRunner.cpp file.cpp LanguageSettings.cpp MakefileReader.cpp +SRC=main.cpp ymake.cpp Builder.cpp ProcessRunner.cpp file.cpp LanguageSettings.cpp MakefileReader.cpp env.cpp OBJ=$(SRC:.cpp=.o) YSCAN=yscan -YSCAN_SRC=yscan-main.cpp yscan.cpp file.cpp +YSCAN_SRC=yscan-main.cpp yscan.cpp file.cpp env.cpp YSCAN_OBJ=$(YSCAN_SRC:.cpp=.o) TEST=test-ymake -TEST_SRC=test-ymake.cpp +TEST_SRC=test-ymake.cpp file.cpp env.cpp TEST_OBJ=$(TEST_SRC:.cpp=.o) all: $(PROJECTNAME) $(YSCAN) diff --git a/env.cpp b/env.cpp new file mode 100644 index 0000000..ff44ff3 --- /dev/null +++ b/env.cpp @@ -0,0 +1,13 @@ +#include "env.h" + +#include + +std::string env_value(const std::string& key) { + char* value_ptr{std::getenv(key.c_str())}; + if (value_ptr == nullptr) { + return {}; + } else { + return value_ptr; + } +} + diff --git a/env.h b/env.h new file mode 100644 index 0000000..deed0ee --- /dev/null +++ b/env.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +std::string env_value(const std::string& key); + diff --git a/file.cpp b/file.cpp index da00dff..3149f63 100644 --- a/file.cpp +++ b/file.cpp @@ -2,6 +2,10 @@ #include +#include + +#include "env.h" + namespace fs = std::filesystem; const fs::path YMakefile{"YMakefile"}; @@ -29,3 +33,31 @@ std::filesystem::path simplified_path(const std::filesystem::path& p) } return p; } + +bool is_executable(const std::string& file) { + std::vector paths {Reichwein::Stringhelper::split(env_value("PATH"), ":")}; + + for (const auto& i: paths) { + fs::path path{i + "/" + file}; + if (fs::exists(path) && (fs::status(path).permissions() & std::filesystem::perms::others_exec) != std::filesystem::perms::none) { + return true; + } + } + + return false; +} + +std::string find_executable(const std::vector& list) { + std::vector paths {Reichwein::Stringhelper::split(env_value("PATH"), ":")}; + + for (const auto& i: list) { + for (const auto& j: paths) { + fs::path path{j + "/" + i}; + if (fs::exists(path) && (fs::status(path).permissions() & std::filesystem::perms::others_exec) != std::filesystem::perms::none) { + return i; + } + } + } + + return {}; +} diff --git a/file.h b/file.h index 80133e8..f951a27 100644 --- a/file.h +++ b/file.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include extern const std::filesystem::path YMakefile; @@ -9,3 +11,7 @@ bool is_compile_unit_source_by_extension(const std::filesystem::path& p); // removes initial "./" std::filesystem::path simplified_path(const std::filesystem::path& p); + +std::string env_value(const std::string& key); +bool is_executable(const std::string& file); +std::string find_executable(const std::vector& list); diff --git a/test-ymake.cpp b/test-ymake.cpp index 1df3ce0..3aea372 100644 --- a/test-ymake.cpp +++ b/test-ymake.cpp @@ -1,5 +1,6 @@ #include #include +#include #include @@ -8,6 +9,8 @@ #include #include +#include "file.h" + namespace fs = std::filesystem; namespace bp = boost::process; @@ -54,26 +57,36 @@ void create_file(const fs::path& path, const std::string& contents) Reichwein::File::setFile(path, contents); } -int run_command(const std::string& command) +boost::process::environment env_from_map(const std::unordered_map& map) +{ + boost::process::environment result{boost::this_process::environment()}; + + for (const auto& pair: map) { + result[pair.first] = pair.second; + } + return result; +} + +int run_command(const std::string& command, const std::unordered_map& envmap = {}) { - return bp::system(command, bp::std_out > bp::null, bp::std_err > bp::null); + return bp::system(command, bp::std_out > bp::null, bp::std_err > bp::null, env_from_map(envmap)); } -int run_command(const std::string& command, std::string& output) +int run_command(const std::string& command, std::string& output, const std::unordered_map& envmap = {}) { bp::ipstream is; bp::ipstream es; - int result = bp::system(command, bp::std_out > is, bp::std_err > es); + int result = bp::system(command, bp::std_out > is, bp::std_err > es, env_from_map(envmap)); output = std::string(std::istreambuf_iterator(is), {}); output += std::string(std::istreambuf_iterator(es), {}); return result; } -int run_command(const std::string& command, std::vector& output) +int run_command(const std::string& command, std::vector& output, const std::unordered_map& envmap = {}) { std::string output_string; - int result = run_command(command, output_string); + int result = run_command(command, output_string, envmap); output = Reichwein::Stringhelper::split(output_string, "\r\n"); return result; @@ -212,10 +225,272 @@ TEST_F(ymakeTest, build_three_cpp) EXPECT_TRUE(!fs::exists("hello")); } -// TODO: test file extensions .c .cc .cpp -// TODO: multiple builds -// TODO: test tests -// TODO: test g++/clang++ +TEST_F(ymakeTest, build_cpp_c_cc) +{ + create_file("hello.cpp", R"(int main(int argc, char* argv[]) +{ + return 0; +})"); + + create_file("second.c", R"()"); + create_file("third.cc", R"()"); + + create_file("YMakefile", R"( + + + hello + hello.cpp + second.c + third.cc + + +)"); + + std::vector output; + int result = run_command("../ymake", output); + EXPECT_EQ(result, 0); + + EXPECT_EQ(output.size(), 4); // 3 compile, 1 link +} + +TEST_F(ymakeTest, multiple_builds) +{ + create_file("hello.cpp", R"(int main(int argc, char* argv[]) +{ + return 0; +})"); + + create_file("second.cpp", R"(int main(int argc, char* argv[]) +{ + return 0; +})"); + create_file("third.cpp", R"()"); + + create_file("YMakefile", R"( + + + hello + hello.cpp + + + second + second.cpp + third.cpp + + +)"); + + std::vector output; + int result = run_command("../ymake", output); + EXPECT_EQ(result, 0); + + EXPECT_EQ(output.size(), 5); // 3 compile, 2 link +} + +TEST_F(ymakeTest, build_and_test) +{ + create_file("hello.cpp", R"(int main(int argc, char* argv[]) +{ + return 0; +})"); + + create_file("second.cpp", R"(int main(int argc, char* argv[]) +{ + return 0; +})"); + create_file("third.cpp", R"()"); + + create_file("YMakefile", R"( + + + hello + hello.cpp + + + second + second.cpp + third.cpp + + +)"); + + std::vector output; + int result = run_command("../ymake", output); + EXPECT_EQ(result, 0); + + EXPECT_EQ(output.size(), 2); // 1 compile, 1 link + + result = run_command("../ymake test", output); + EXPECT_EQ(result, 0); + + EXPECT_EQ(output.size(), 4); // 2 compile, 1 link, 1 test + + result = run_command("../ymake clean", output); + EXPECT_EQ(result, 0); + + EXPECT_EQ(output.size(), 8); // hello second hello.o second.o thord.o hello.d second.d third.d + + result = run_command("../ymake clean", output); + EXPECT_EQ(result, 0); + + EXPECT_EQ(output.size(), 0); +} + +TEST_F(ymakeTest, build_and_no_test_defined) +{ + create_file("hello.cpp", R"(int main(int argc, char* argv[]) +{ + return 0; +})"); + + create_file("YMakefile", R"( + + + hello + hello.cpp + + +)"); + + std::vector output; + int result = run_command("../ymake", output); + EXPECT_EQ(result, 0); + + EXPECT_EQ(output.size(), 2); // 1 compile, 1 link + + result = run_command("../ymake test", output); + EXPECT_EQ(result, 0); + + ASSERT_EQ(output.size(), 1); + EXPECT_EQ(output[0], "Everything up to date."); +} + +TEST_F(ymakeTest, test_fail) +{ + create_file("hello.cpp", R"(int main(int argc, char* argv[]) +{ + return 0; +})"); + + create_file("test-hello.cpp", R"(int main(int argc, char* argv[]) +{ + return 1; +})"); + + create_file("YMakefile", R"( + + + hello + hello.cpp + + + test-hello + test-hello.cpp + + +)"); + + std::vector output; + int result = run_command("../ymake", output); + EXPECT_EQ(result, 0); + + EXPECT_EQ(output.size(), 2); // 1 compile, 1 link + + result = run_command("../ymake test", output); + EXPECT_EQ(result, 1); + + ASSERT_EQ(output.size(), 4); // 1 compile, 1 link, test: run, exit code +} + +TEST_F(ymakeTest, test_only) +{ + create_file("test-hello.cpp", R"(int main(int argc, char* argv[]) +{ + return 0; +})"); + + create_file("YMakefile", R"( + + + test-hello + test-hello.cpp + + +)"); + + std::vector output; + int result = run_command("../ymake", output); + EXPECT_EQ(result, 0); + + ASSERT_EQ(output.size(), 1); + EXPECT_EQ(output[0], "Everything up to date."); + + result = run_command("../ymake test", output); + EXPECT_EQ(result, 0); + + ASSERT_EQ(output.size(), 3); // 1 compile, 1 link, test: run +} + +TEST_F(ymakeTest, build_with_gcc) +{ + if (!is_executable("g++")) { + GTEST_SKIP() << "g++ not available"; + } + + create_file("hello.cpp", R"(int main(int argc, char* argv[]) +{ + return 0; +})"); + + create_file("YMakefile", R"( + + + hello + hello.cpp + + +)"); + + std::vector output; + int result = run_command("../ymake", output, {{"CXX", "g++"}}); + EXPECT_EQ(result, 0); + + ASSERT_EQ(output.size(), 2); // compile, link + for (const auto&i: output) { + EXPECT_EQ(i.substr(0, 4), "g++ "); + } +} + +TEST_F(ymakeTest, build_with_clang) +{ + if (!is_executable("clang++")) { + GTEST_SKIP() << "clang++ not available"; + } + + create_file("hello.cpp", R"(int main(int argc, char* argv[]) +{ + return 0; +})"); + + create_file("YMakefile", R"( + + + hello + hello.cpp + + +)"); + + std::vector output; + int result = run_command("../ymake", output, {{"CXX", "clang++"}}); + EXPECT_EQ(result, 0); + + ASSERT_EQ(output.size(), 2); // compile, link + for (const auto&i: output) { + EXPECT_EQ(i.substr(0, 8), "clang++ "); + } +} + // TODO: multiple dirs / YMakefiles TEST_F(yscanTest, no_cpp_file) @@ -252,7 +527,7 @@ TEST_F(yscanTest, one_cpp_file) )"); } -TEST_F(yscanTest, one_three_files) +TEST_F(yscanTest, four_files) { create_file("hello.cpp", R"(int main(int argc, char* argv[]) { @@ -278,6 +553,60 @@ TEST_F(yscanTest, one_three_files) )"); } -// TODO: test multiple builds -// TODO: test tests +TEST_F(yscanTest, tests) +{ + create_file("hello.cpp", R"(int main(int argc, char* argv[]) +{ + return 0; +})"); + create_file("test-second.cpp", R"(int main(int argc, char* argv[]) +{ + return 0; +})"); + create_file("test-third.cpp", R"()"); + + std::string output; + int result = run_command("../yscan", output); + EXPECT_EQ(result, 0); + + EXPECT_EQ(output, R"( + + testdir1 + hello.cpp + + + test-testdir1 + test-second.cpp + test-third.cpp + + +)"); +} + +TEST_F(yscanTest, unrelated_files) +{ + create_file("hello.cpp", R"(int main(int argc, char* argv[]) +{ + return 0; +})"); + + create_file("Makefile", ""); + create_file("other.h", ""); + create_file("second.rb", ""); + create_file("third.d", ""); + + std::string output; + int result = run_command("../yscan", output); + EXPECT_EQ(result, 0); + + EXPECT_EQ(output, R"( + + testdir1 + hello.cpp + + +)"); +} + +// TODO: test multiple directories -- cgit v1.2.3