diff options
-rw-r--r-- | Makefile | 6 | ||||
-rw-r--r-- | env.cpp | 13 | ||||
-rw-r--r-- | env.h | 6 | ||||
-rw-r--r-- | file.cpp | 32 | ||||
-rw-r--r-- | file.h | 6 | ||||
-rw-r--r-- | test-ymake.cpp | 355 |
6 files changed, 402 insertions, 16 deletions
@@ -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) @@ -0,0 +1,13 @@ +#include "env.h" + +#include <cstdlib> + +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; + } +} + @@ -0,0 +1,6 @@ +#pragma once + +#include <string> + +std::string env_value(const std::string& key); + @@ -2,6 +2,10 @@ #include <unordered_set> +#include <libreichwein/stringhelper.h> + +#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<std::string> 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<std::string>& list) { + std::vector<std::string> 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 {}; +} @@ -1,6 +1,8 @@ #pragma once #include <filesystem> +#include <string> +#include <vector> 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<std::string>& 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 <filesystem> #include <string> +#include <unordered_map> #include <boost/process.hpp> @@ -8,6 +9,8 @@ #include <libreichwein/file.h> #include <libreichwein/stringhelper.h> +#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<std::string, std::string>& 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<std::string, std::string>& 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<std::string, std::string>& 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<char>(is), {}); output += std::string(std::istreambuf_iterator<char>(es), {}); return result; } -int run_command(const std::string& command, std::vector<std::string>& output) +int run_command(const std::string& command, std::vector<std::string>& output, const std::unordered_map<std::string, std::string>& 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"( +<ymake> + <build> + <name>hello</name> + <source>hello.cpp</source> + <source>second.c</source> + <source>third.cc</source> + </build> +</ymake> +)"); + + std::vector<std::string> 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"( +<ymake> + <build> + <name>hello</name> + <source>hello.cpp</source> + </build> + <build> + <name>second</name> + <source>second.cpp</source> + <source>third.cpp</source> + </build> +</ymake> +)"); + + std::vector<std::string> 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"( +<ymake> + <build> + <name>hello</name> + <source>hello.cpp</source> + </build> + <test> + <name>second</name> + <source>second.cpp</source> + <source>third.cpp</source> + </test> +</ymake> +)"); + + std::vector<std::string> 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"( +<ymake> + <build> + <name>hello</name> + <source>hello.cpp</source> + </build> +</ymake> +)"); + + std::vector<std::string> 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"( +<ymake> + <build> + <name>hello</name> + <source>hello.cpp</source> + </build> + <test> + <name>test-hello</name> + <source>test-hello.cpp</source> + </test> +</ymake> +)"); + + std::vector<std::string> 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"( +<ymake> + <test> + <name>test-hello</name> + <source>test-hello.cpp</source> + </test> +</ymake> +)"); + + std::vector<std::string> 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"( +<ymake> + <build> + <name>hello</name> + <source>hello.cpp</source> + </build> +</ymake> +)"); + + std::vector<std::string> 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"( +<ymake> + <build> + <name>hello</name> + <source>hello.cpp</source> + </build> +</ymake> +)"); + + std::vector<std::string> 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"(<ymake> + <build> + <name>testdir1</name> + <source>hello.cpp</source> + </build> + <test> + <name>test-testdir1</name> + <source>test-second.cpp</source> + <source>test-third.cpp</source> + </test> +</ymake> +)"); +} + +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"(<ymake> + <build> + <name>testdir1</name> + <source>hello.cpp</source> + </build> +</ymake> +)"); +} + +// TODO: test multiple directories |