summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Makefile6
-rw-r--r--env.cpp13
-rw-r--r--env.h6
-rw-r--r--file.cpp32
-rw-r--r--file.h6
-rw-r--r--test-ymake.cpp355
6 files changed, 402 insertions, 16 deletions
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 <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;
+ }
+}
+
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 <string>
+
+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 <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 {};
+}
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 <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