From 8b052110d8802eda3d4d76700bde8ecc80dbf79a Mon Sep 17 00:00:00 2001 From: dec05eba Date: Wed, 3 Jan 2018 21:17:49 +0100 Subject: Add "sibs test" command. Tests are only run when that command is invoked --- backend/ninja/Ninja.cpp | 9 +- include/Conf.hpp | 22 ++++- src/Conf.cpp | 8 +- src/GlobalLib.cpp | 2 +- src/main.cpp | 208 ++++++++++++++++++++++++++-------------- tests/src/confTest/confTest.cpp | 8 +- 6 files changed, 169 insertions(+), 88 deletions(-) diff --git a/backend/ninja/Ninja.cpp b/backend/ninja/Ninja.cpp index a5fdb0d..04669f7 100644 --- a/backend/ninja/Ninja.cpp +++ b/backend/ninja/Ninja.cpp @@ -814,7 +814,7 @@ namespace backend Result Ninja::buildTests(const std::string &projectGeneratedBinary, const SibsConfig &config, const _tinydir_char_t *savePath) { - if(testSourceDirs.empty()) + if(testSourceDirs.empty() || !config.shouldBuildTests()) return Result::Ok(true); // Tests need parent project as dependency. Executables can't be included as dependency so we build it as dynamic library. @@ -888,6 +888,13 @@ namespace backend Result buildResult = ninja.compile(buildPath.c_str()); if (!buildResult) return buildResult; + + FileString testExecutableName = buildPath; + testExecutableName += TINYDIR_STRING("/"); + testExecutableName += toFileString(sibsTestConfig.getPackageName()); + Result runTestResult = exec(testExecutableName.c_str(), true); + if(!runTestResult) + return Result::Err(runTestResult); } } diff --git a/include/Conf.hpp b/include/Conf.hpp index 476e38b..de63eae 100644 --- a/include/Conf.hpp +++ b/include/Conf.hpp @@ -169,13 +169,14 @@ namespace sibs class SibsConfig : public ConfigCallback { public: - SibsConfig(Compiler _compiler, const FileString &_projectPath, OptimizationLevel _optimizationLevel = OPT_LEV_DEBUG) : + SibsConfig(Compiler _compiler, const FileString &_projectPath, OptimizationLevel _optimizationLevel, bool _buildTests) : compiler(_compiler), projectPath(_projectPath), packageType((PackageType)-1), optimizationLevel(_optimizationLevel), finishedProcessing(false), - useCmake(false) + useCmake(false), + buildTests(_buildTests) { cmakeDirGlobal = projectPath; cmakeDirStatic = cmakeDirGlobal; @@ -214,6 +215,11 @@ namespace sibs { return testPath; } + + void setTestPath(const FileString &testPath) + { + this->testPath = testPath; + } virtual const std::vector& getDependencies() const { @@ -267,12 +273,12 @@ namespace sibs const FileString& getCmakeDirStatic() const { - return cmakeDirStatic; + return !cmakeDirStatic.empty() ? cmakeDirStatic : cmakeDirGlobal; } const FileString& getCmakeDirDynamic() const { - return cmakeDirDynamic; + return !cmakeDirDynamic.empty() ? cmakeDirDynamic : cmakeDirGlobal; } // Get cmake args for all builds. This is args under [cmake] only @@ -308,6 +314,11 @@ namespace sibs return useCmake; } + bool shouldBuildTests() const + { + return buildTests; + } + void setPackageType(PackageType packageType) { this->packageType = packageType; @@ -351,13 +362,14 @@ namespace sibs std::string cmakeArgsStatic; std::string cmakeArgsDynamic; bool useCmake; + bool buildTests; bool finishedProcessing; }; class SibsTestConfig : public SibsConfig { public: - SibsTestConfig(Compiler _compiler, const FileString &_projectPath, OptimizationLevel _optimizationLevel) : SibsConfig(_compiler, _projectPath, _optimizationLevel) + SibsTestConfig(Compiler _compiler, const FileString &_projectPath, OptimizationLevel _optimizationLevel) : SibsConfig(_compiler, _projectPath, _optimizationLevel, true) { packageName = "test"; } diff --git a/src/Conf.cpp b/src/Conf.cpp index 1697d9f..0bbbf7b 100644 --- a/src/Conf.cpp +++ b/src/Conf.cpp @@ -867,24 +867,24 @@ namespace sibs if(useCmake) { - switch(getPackageType()) + switch(packageType) { case PackageType::EXECUTABLE: { - if(!getCmakeDir().empty()) + if(getCmakeDir().empty()) throw ParserException("Missing required config cmake.dir"); break; } case PackageType::STATIC: { - if(!getCmakeDirStatic().empty()) + if(getCmakeDirStatic().empty()) throw ParserException("Missing required config cmake.static"); break; } case PackageType::DYNAMIC: case PackageType::LIBRARY: { - if(!getCmakeDirDynamic().empty()) + if(getCmakeDirDynamic().empty()) throw ParserException("Missing required config cmake.dynamic"); break; } diff --git a/src/GlobalLib.cpp b/src/GlobalLib.cpp index 9083354..d3131a1 100644 --- a/src/GlobalLib.cpp +++ b/src/GlobalLib.cpp @@ -123,7 +123,7 @@ namespace sibs } } - SibsConfig sibsConfig(parentConfig.getCompiler(), packageDir, parentConfig.getOptimizationLevel()); + SibsConfig sibsConfig(parentConfig.getCompiler(), packageDir, parentConfig.getOptimizationLevel(), false); Result result = Config::readFromFile(projectConfFilePath.c_str(), sibsConfig); if (result.isErr()) return result; diff --git a/src/main.cpp b/src/main.cpp index e2df0fe..f16bf64 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -56,6 +56,7 @@ void usage() printf("Commands:\n"); printf(" build\t\tBuild a project that contains a project.conf file\n"); printf(" new\t\tCreate a new project\n"); + printf(" test\t\tBuild and run tests for a sibs project\n"); exit(1); } @@ -88,6 +89,18 @@ void usageNew() exit(1); } +void usageTest() +{ + printf("Usage: sibs test [project_path]\n\n"); + printf("Build and run tests for a sibs project\n\n"); + printf("Options:\n"); + printf(" project_path\t\t The directory containing a project.conf file - Optional (default: current working directory)\n"); + printf("Examples:\n"); + printf(" sibs test\n"); + printf(" sibs test dirA/dirB\n"); + exit(1); +} + void validateDirectoryPath(const _tinydir_char_t *projectPath) { FileType projectPathFileType = getFileType(projectPath); @@ -142,78 +155,8 @@ bool isPathSubPathOf(const FileString &path, const FileString &subPathOf) return _tinydir_strncmp(path.c_str(), subPathOf.c_str(), subPathOf.size()) == 0; } -int buildProject(int argc, const _tinydir_char_t **argv) +int buildProject(const FileString &projectPath, const FileString &projectConfFilePath, const SibsConfig &sibsConfig) { - if(argc > 2) - usageBuild(); - - OptimizationLevel optimizationLevel = OPT_LEV_NONE; - FileString projectPath; - - for(int i = 0; i < argc; ++i) - { - const _tinydir_char_t *arg = argv[i]; - if(_tinydir_strcmp(arg, TINYDIR_STRING("--debug")) == 0) - { - if(optimizationLevel != OPT_LEV_NONE) - { - ferr << "Error: Optimization level defined more than once. First defined as " << asString(optimizationLevel) << " then as debug" << endl; - usageBuild(); - } - optimizationLevel = OPT_LEV_DEBUG; - } - else if(_tinydir_strcmp(arg, TINYDIR_STRING("--release")) == 0) - { - if(optimizationLevel != OPT_LEV_NONE) - { - ferr << "Error: Optimization level defined more than once. First defined as " << asString(optimizationLevel) << " then as release" << endl; - usageBuild(); - } - optimizationLevel = OPT_LEV_RELEASE; - } - else - { - if(!projectPath.empty()) - { - ferr << "Error: Project path was defined more than once. First defined as " << projectPath << " then as " << arg << endl; - usageBuild(); - } - projectPath = arg; - } - } - - if(optimizationLevel == OPT_LEV_NONE) - optimizationLevel = OPT_LEV_DEBUG; - - // TODO: If projectPath is not defined and working directory does not contain project.conf, then search every parent directory until one is found - if(projectPath.empty()) - projectPath = TINYDIR_STRING("."); - - validateDirectoryPath(projectPath.c_str()); - if(projectPath.back() != '/') - projectPath += TINYDIR_STRING("/"); - - Result projectRealPathResult = getRealPath(projectPath.c_str()); - if(!projectRealPathResult) - { - ferr << "Failed to get real path for: '" << projectPath.c_str() << "': " << toFileString(projectRealPathResult.getErrMsg()) << endl; - exit(40); - } - projectPath = projectRealPathResult.unwrap(); - - FileString projectConfFilePath = projectPath; - projectConfFilePath += TINYDIR_STRING("/project.conf"); - validateFilePath(projectConfFilePath.c_str()); - - // TODO: Detect compiler to use at runtime. Should also be configurable - // by passing argument to `sibs build` -#if OS_FAMILY == OS_FAMILY_POSIX - Compiler compiler = Compiler::GCC; -#else - Compiler compiler = Compiler::MSVC; -#endif - - SibsConfig sibsConfig(compiler, projectPath, optimizationLevel); Result result = Config::readFromFile(projectConfFilePath.c_str(), sibsConfig); if(result.isErr()) { @@ -285,7 +228,6 @@ int buildProject(int argc, const _tinydir_char_t **argv) } else { - // TODO: If compiling without "test" option, do not add test source dir to ninja. Ninja logic will then not build tests if (!sibsConfig.getTestPath().empty() && isPathSubPathOf(pathNative.c_str(), sibsConfig.getTestPath())) { string filePathUtf8 = toUtf8(pathNative.c_str()); @@ -305,11 +247,127 @@ int buildProject(int argc, const _tinydir_char_t **argv) } } auto elapsedTime = duration_cast>(high_resolution_clock::now() - startTime); - printf("Build finished in %fs\n", elapsedTime.count()); + printf("Finished in %fs\n", elapsedTime.count()); return 0; } +int buildProject(int argc, const _tinydir_char_t **argv) +{ + if(argc > 2) + usageBuild(); + + OptimizationLevel optimizationLevel = OPT_LEV_NONE; + FileString projectPath; + + for(int i = 0; i < argc; ++i) + { + const _tinydir_char_t *arg = argv[i]; + if(_tinydir_strcmp(arg, TINYDIR_STRING("--debug")) == 0) + { + if(optimizationLevel != OPT_LEV_NONE) + { + ferr << "Error: Optimization level defined more than once. First defined as " << asString(optimizationLevel) << " then as debug" << endl; + usageBuild(); + } + optimizationLevel = OPT_LEV_DEBUG; + } + else if(_tinydir_strcmp(arg, TINYDIR_STRING("--release")) == 0) + { + if(optimizationLevel != OPT_LEV_NONE) + { + ferr << "Error: Optimization level defined more than once. First defined as " << asString(optimizationLevel) << " then as release" << endl; + usageBuild(); + } + optimizationLevel = OPT_LEV_RELEASE; + } + else + { + if(!projectPath.empty()) + { + ferr << "Error: Project path was defined more than once. First defined as " << projectPath << " then as " << arg << endl; + usageBuild(); + } + projectPath = arg; + } + } + + if(optimizationLevel == OPT_LEV_NONE) + optimizationLevel = OPT_LEV_DEBUG; + + // TODO: If projectPath is not defined and working directory does not contain project.conf, then search every parent directory until one is found + if(projectPath.empty()) + projectPath = TINYDIR_STRING("."); + + validateDirectoryPath(projectPath.c_str()); + if(projectPath.back() != '/') + projectPath += TINYDIR_STRING("/"); + + Result projectRealPathResult = getRealPath(projectPath.c_str()); + if(!projectRealPathResult) + { + ferr << "Failed to get real path for: '" << projectPath.c_str() << "': " << toFileString(projectRealPathResult.getErrMsg()) << endl; + exit(40); + } + projectPath = projectRealPathResult.unwrap(); + + FileString projectConfFilePath = projectPath; + projectConfFilePath += TINYDIR_STRING("/project.conf"); + validateFilePath(projectConfFilePath.c_str()); + + // TODO: Detect compiler to use at runtime. Should also be configurable + // by passing argument to `sibs build` +#if OS_FAMILY == OS_FAMILY_POSIX + Compiler compiler = Compiler::GCC; +#else + Compiler compiler = Compiler::MSVC; +#endif + + SibsConfig sibsConfig(compiler, projectPath, optimizationLevel, false); + return buildProject(projectPath, projectConfFilePath, sibsConfig); +} + +int testProject(int argc, const _tinydir_char_t **argv) +{ + if(argc > 1) + usageTest(); + + FileString projectPath; + if(argc == 1) + projectPath = argv[0]; + + // TODO: If projectPath is not defined and working directory does not contain project.conf, then search every parent directory until one is found + if(projectPath.empty()) + projectPath = TINYDIR_STRING("."); + + validateDirectoryPath(projectPath.c_str()); + if(projectPath.back() != '/') + projectPath += TINYDIR_STRING("/"); + + Result projectRealPathResult = getRealPath(projectPath.c_str()); + if(!projectRealPathResult) + { + ferr << "Failed to get real path for: '" << projectPath.c_str() << "': " << toFileString(projectRealPathResult.getErrMsg()) << endl; + exit(40); + } + projectPath = projectRealPathResult.unwrap(); + + FileString projectConfFilePath = projectPath; + projectConfFilePath += TINYDIR_STRING("/project.conf"); + validateFilePath(projectConfFilePath.c_str()); + + // TODO: Detect compiler to use at runtime. Should also be configurable + // by passing argument to `sibs build` +#if OS_FAMILY == OS_FAMILY_POSIX + Compiler compiler = Compiler::GCC; +#else + Compiler compiler = Compiler::MSVC; +#endif + + SibsConfig sibsConfig(compiler, projectPath, OPT_LEV_DEBUG, true); + return buildProject(projectPath, projectConfFilePath, sibsConfig); +} + void newProjectCreateMainDir(const FileString &projectPath) { Result createProjectDirResult = createDirectoryRecursive(projectPath.c_str()); @@ -451,6 +509,10 @@ int wmain(int argc, const _tinydir_char_t **argv) { return newProject(subCommandArgCount, subCommandArgPtr); } + else if(_tinydir_strcmp(arg, TINYDIR_STRING("test")) == 0) + { + return testProject(subCommandArgCount, subCommandArgPtr); + } else { ferr << "Expected command to be either 'build' or 'new', was: " << arg << endl << endl; diff --git a/tests/src/confTest/confTest.cpp b/tests/src/confTest/confTest.cpp index 94cdb11..44bcab0 100644 --- a/tests/src/confTest/confTest.cpp +++ b/tests/src/confTest/confTest.cpp @@ -5,7 +5,7 @@ using namespace sibs; TEST_CASE("parse config") { - SibsConfig sibsConfig(Compiler::GCC, TINYDIR_STRING("tests/src/confTest")); + SibsConfig sibsConfig(Compiler::GCC, TINYDIR_STRING("tests/src/confTest"), OPT_LEV_DEBUG, false); Result result = Config::readFromFile(TINYDIR_STRING("tests/src/confTest/validProject.conf"), sibsConfig); if(result.isErr()) { @@ -42,7 +42,7 @@ TEST_CASE("parse config") TEST_CASE("parse config - invalid object") { - SibsConfig sibsConfig(Compiler::GCC, TINYDIR_STRING("tests/src/confTest")); + SibsConfig sibsConfig(Compiler::GCC, TINYDIR_STRING("tests/src/confTest"), OPT_LEV_DEBUG, false); Result result = Config::readFromFile(TINYDIR_STRING("tests/src/confTest/invalidObject.conf"), sibsConfig); REQUIRE(result.isErr()); REQUIRE(result.getErrMsg() == "Invalid config object \"invalidObj\""); @@ -50,7 +50,7 @@ TEST_CASE("parse config - invalid object") TEST_CASE("parse config - invalid field") { - SibsConfig sibsConfig(Compiler::GCC, TINYDIR_STRING("tests/src/confTest")); + SibsConfig sibsConfig(Compiler::GCC, TINYDIR_STRING("tests/src/confTest"), OPT_LEV_DEBUG, false); Result result = Config::readFromFile(TINYDIR_STRING("tests/src/confTest/invalidField.conf"), sibsConfig); REQUIRE(result.isErr()); REQUIRE(result.getErrMsg() == "Invalid field \"invalidField\" under object \"package\""); @@ -58,7 +58,7 @@ TEST_CASE("parse config - invalid field") TEST_CASE("parse config - use different config for different platforms") { - SibsConfig sibsConfig(Compiler::GCC, TINYDIR_STRING("tests/src/confTest")); + SibsConfig sibsConfig(Compiler::GCC, TINYDIR_STRING("tests/src/confTest"), OPT_LEV_DEBUG, false); Result result = Config::readFromFile(TINYDIR_STRING("tests/src/confTest/platformConfig.conf"), sibsConfig); if(result.isErr()) { -- cgit v1.2.3