#include #include #include #include #include #include #include #include "file.h" namespace fs = std::filesystem; namespace bp = boost::process; namespace { const fs::path testpath{"testdir1"}; } class ymakeTest: public ::testing::Test { private: fs::path original_path; protected: ymakeTest() { } ~ymakeTest() override { } void SetUp() override { original_path = fs::current_path(); fs::remove_all(testpath); fs::create_directory(testpath); fs::current_path(testpath); } void TearDown() override { fs::current_path(original_path); fs::remove_all(testpath); } }; class yscanTest: public ymakeTest { }; void create_file(const fs::path& path, const std::string& contents) { Reichwein::File::setFile(path, contents); } 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, env_from_map(envmap)); } 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, 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, const std::unordered_map& envmap = {}) { std::string output_string; int result = run_command(command, output_string, envmap); output = Reichwein::Stringhelper::split(output_string, "\r\n"); return result; } TEST_F(ymakeTest, build_one_cpp) { 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); // compile, link } TEST_F(ymakeTest, build_one_cpp_compile_error) { create_file("hello.cpp", R"(int main(int argc, char* argv[]) { retrn 0; })"); create_file("YMakefile", R"( hello hello.cpp )"); int result = run_command("../ymake"); EXPECT_NE(result, 0); } TEST_F(ymakeTest, clean_one_cpp) { 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 clean", output); EXPECT_EQ(output.size(), 0); ASSERT_EQ(result, 0); result = run_command("../ymake", output); EXPECT_EQ(output.size(), 2); // compile, link ASSERT_EQ(result, 0); result = run_command("../ymake clean", output); EXPECT_EQ(output.size(), 3); // rm .o, .d, executable ASSERT_EQ(result, 0); } TEST_F(ymakeTest, YMakefile_missing) { int result = run_command("../ymake"); EXPECT_NE(result, 0); result = run_command("../ymake clean"); EXPECT_NE(result, 0); } TEST_F(ymakeTest, build_three_cpp) { create_file("hello.cpp", R"(int main(int argc, char* argv[]) { return 0; })"); create_file("second.cpp", R"( #include "second.h" )"); create_file("second.h", R"( #pragma once )"); create_file("third.cpp", R"( )"); create_file("YMakefile", R"( hello hello.cpp second.cpp third.cpp )"); EXPECT_TRUE(!fs::exists("hello.o")); EXPECT_TRUE(!fs::exists("second.o")); EXPECT_TRUE(!fs::exists("third.o")); EXPECT_TRUE(!fs::exists("hello")); std::vector output; int result = run_command("../ymake", output); EXPECT_EQ(result, 0); EXPECT_EQ(output.size(), 4); // compile 3, link 1 EXPECT_TRUE(fs::exists("hello.o")); EXPECT_TRUE(fs::exists("second.o")); EXPECT_TRUE(fs::exists("third.o")); EXPECT_TRUE(fs::exists("hello")); result = run_command("../ymake clean"); EXPECT_EQ(result, 0); EXPECT_TRUE(!fs::exists("hello.o")); EXPECT_TRUE(!fs::exists("second.o")); EXPECT_TRUE(!fs::exists("third.o")); EXPECT_TRUE(!fs::exists("hello")); } 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++ "); } } TEST_F(ymakeTest, multiple_dirs) { create_file("hello.cpp", R"(int main(int argc, char* argv[]) { return 0; })"); create_file("YMakefile", R"( hello hello.cpp )"); fs::create_directory("subdir1"); create_file("subdir1/second.cpp", R"(int main(int argc, char* argv[]) { return 0; })"); create_file("subdir1/YMakefile", R"( second second.cpp )"); std::vector output; int result = run_command("../ymake", output); EXPECT_EQ(result, 0); EXPECT_EQ(output.size(), 4); // compile, link, compile, link EXPECT_TRUE(fs::exists("hello")); EXPECT_TRUE(fs::exists("hello.o")); EXPECT_TRUE(fs::exists("hello.d")); EXPECT_TRUE(fs::exists("subdir1/second")); EXPECT_TRUE(fs::exists("subdir1/second.o")); EXPECT_TRUE(fs::exists("subdir1/second.d")); result = run_command("../ymake", output); EXPECT_EQ(result, 0); EXPECT_EQ(output.size(), 1); // everything up to date result = run_command("../ymake clean", output); EXPECT_EQ(result, 0); EXPECT_EQ(output.size(), 6); // hello hello.o hello.d second second.o second.d result = run_command("../ymake clean", output); EXPECT_EQ(result, 0); EXPECT_EQ(output.size(), 0); } TEST_F(ymakeTest, build_one_static_lib) { create_file("hello.cpp", R"(int maine(int argc, char* argv[]) { return 0; })"); create_file("second.cpp", R"(int maini(int argc, char* argv[]) { return 0; })"); create_file("YMakefile", R"( hello.a hello.cpp second.cpp )"); std::vector output; int result = run_command("../ymake", output); EXPECT_EQ(result, 0); ASSERT_EQ(output.size(), 3); // 2x compile, link EXPECT_EQ(output[2].substr(0, 7), "ar rcs "); EXPECT_TRUE(fs::exists("hello.d")); EXPECT_TRUE(fs::exists("hello.o")); EXPECT_TRUE(fs::exists("hello.a")); EXPECT_TRUE(fs::exists("second.d")); EXPECT_TRUE(fs::exists("second.o")); result = run_command("../ymake", output); EXPECT_EQ(result, 0); ASSERT_EQ(output.size(), 1); // everything up to date result = run_command("../ymake clean", output); EXPECT_EQ(result, 0); ASSERT_EQ(output.size(), 5); // deleted 5 files EXPECT_TRUE(!fs::exists("hello.d")); EXPECT_TRUE(!fs::exists("hello.o")); EXPECT_TRUE(!fs::exists("hello.a")); EXPECT_TRUE(!fs::exists("second.d")); EXPECT_TRUE(!fs::exists("second.o")); result = run_command("../ymake clean", output); EXPECT_EQ(result, 0); ASSERT_EQ(output.size(), 0); } TEST_F(ymakeTest, build_one_dynamic_lib) { create_file("hello.cpp", R"(int maine(int argc, char* argv[]) { return 0; })"); create_file("second.cpp", R"(int maini(int argc, char* argv[]) { return 0; })"); create_file("YMakefile", R"( hello.so.1.2.3 hello.cpp second.cpp )"); std::vector output; int result = run_command("../ymake", output); EXPECT_EQ(result, 0); ASSERT_EQ(output.size(), 3); // 2x compile, link EXPECT_EQ(output[2].substr(0, 4), "g++ "); EXPECT_TRUE(fs::exists("hello.d")); EXPECT_TRUE(fs::exists("hello.o")); EXPECT_TRUE(fs::exists("hello.so.1.2.3")); EXPECT_TRUE(fs::exists("second.d")); EXPECT_TRUE(fs::exists("second.o")); result = run_command("../ymake", output); EXPECT_EQ(result, 0); ASSERT_EQ(output.size(), 1); // everything up to date result = run_command("../ymake clean", output); EXPECT_EQ(result, 0); ASSERT_EQ(output.size(), 5); // deleted 5 files EXPECT_TRUE(!fs::exists("hello.d")); EXPECT_TRUE(!fs::exists("hello.o")); EXPECT_TRUE(!fs::exists("hello.so.1.2.3")); EXPECT_TRUE(!fs::exists("second.d")); EXPECT_TRUE(!fs::exists("second.o")); result = run_command("../ymake clean", output); EXPECT_EQ(result, 0); ASSERT_EQ(output.size(), 0); } // TODO: // use static lib // use dynamic lib TEST_F(yscanTest, no_cpp_file) { std::string output; int result = run_command("../yscan", output); EXPECT_EQ(result, 0); EXPECT_EQ(output, R"( testdir1 )"); } TEST_F(yscanTest, one_cpp_file) { create_file("hello.cpp", R"(int main(int argc, char* argv[]) { return 0; })"); std::string output; int result = run_command("../yscan", output); EXPECT_EQ(result, 0); EXPECT_EQ(output, R"( testdir1 hello.cpp )"); } TEST_F(yscanTest, four_files) { create_file("hello.cpp", R"(int main(int argc, char* argv[]) { return 0; })"); create_file("second.cpp", R"()"); create_file("third.cpp", R"()"); create_file("fourth.cpp", R"()"); std::string output; int result = run_command("../yscan", output); EXPECT_EQ(result, 0); EXPECT_EQ(output, R"( testdir1 fourth.cpp hello.cpp second.cpp third.cpp )"); } 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