#include "./parser_helper.h" #include "../logging.h" #include "../serversetup.h" #include "../buildactions/buildaction.h" #include "../buildactions/buildactionprivate.h" #include "../buildactions/subprocess.h" #include #include #include #include #include using CppUtilities::operator<<; // must be visible prior to the call site #include #include #include #include #include #include using namespace std; using namespace CPPUNIT_NS; using namespace CppUtilities; using namespace CppUtilities::Literals; using namespace LibRepoMgr; /*! * \brief The BuildActionsTests class contains tests for classes/functions related to build actions. */ class BuildActionsTests : public TestFixture { CPPUNIT_TEST_SUITE(BuildActionsTests); CPPUNIT_TEST(testLogging); CPPUNIT_TEST(testProcessSession); CPPUNIT_TEST(testBuildActionProcess); CPPUNIT_TEST(testBufferSearch); CPPUNIT_TEST(testParsingInfoFromPkgFiles); CPPUNIT_TEST(testPreparingBuild); CPPUNIT_TEST(testConductingBuild); CPPUNIT_TEST_SUITE_END(); public: BuildActionsTests(); void setUp() override; void tearDown() override; void testLogging(); void testProcessSession(); void testBuildActionProcess(); void testBufferSearch(); void testParsingInfoFromPkgFiles(); void testPreparingBuild(); void testConductingBuild(); private: void loadBasicTestSetup(); void loadTestConfig(); void logTestSetup(); template void setupBuildAction(); void resetBuildAction(); void runBuildAction(const char *message, TimeSpan timeout = TimeSpan::fromSeconds(5)); template InternalBuildActionType *internalBuildAction(); ServiceSetup m_setup; std::shared_ptr m_buildAction; std::filesystem::path m_workingDir; double m_timeoutFactor = 0.0; }; CPPUNIT_TEST_SUITE_REGISTRATION(BuildActionsTests); BuildActionsTests::BuildActionsTests() { if (const char *noBuildActionTimeout = std::getenv("BUILD_ACTION_TIMEOUT_FACTOR")) { m_timeoutFactor = stringToNumber(noBuildActionTimeout); } } void BuildActionsTests::setUp() { // save the working directory; the code under test might change it and we want to restore the initial working directory later m_workingDir = std::filesystem::current_path(); cerr << EscapeCodes::Phrases::Info << "test working directory: " << m_workingDir.native() << endl; } void BuildActionsTests::tearDown() { std::filesystem::current_path(m_workingDir); } /*! * \brief Assigns certain build variables to use fake scripts (instead of invoking e.g. the real makepkg). * \remarks The fake scripts are essentially no-ops which merely print the script name and the passed arguments. */ void BuildActionsTests::loadBasicTestSetup() { m_setup.workingDirectory = TestApplication::instance()->workingDirectory(); m_setup.building.workingDirectory = m_setup.workingDirectory + "/building"; m_setup.building.makePkgPath = std::filesystem::absolute(testFilePath("scripts/fake_makepkg.sh")); m_setup.building.makeChrootPkgPath = std::filesystem::absolute(testFilePath("scripts/fake_makechrootpkg.sh")); m_setup.building.updatePkgSumsPath = std::filesystem::absolute(testFilePath("scripts/fake_updatepkgsums.sh")); m_setup.building.repoAddPath = std::filesystem::absolute(testFilePath("scripts/fake_repo_add.sh")); m_setup.building.gpgPath = std::filesystem::absolute(testFilePath("scripts/fake_gpg.sh")); m_setup.building.defaultGpgKey = "1234567890"; m_setup.configFilePath = std::filesystem::absolute(testFilePath("test-config/server.conf")); std::filesystem::remove_all(m_setup.workingDirectory); std::filesystem::create_directories(m_setup.building.workingDirectory); } /*! * \brief Runs the startup code almost like the actual service does. * \remarks Changes the current working directory! Make paths obtained via testFilePath() absolute before calling this function * if they are supposed to be used later. */ void BuildActionsTests::loadTestConfig() { m_setup.loadConfigFiles(false); m_setup.building.workingDirectory = m_setup.workingDirectory + "/building"; m_setup.printDatabases(); cerr << EscapeCodes::Phrases::Info << "current working directory: " << std::filesystem::current_path() << endl; cerr << EscapeCodes::Phrases::Info << "setup working directory: " << m_setup.workingDirectory << endl; logTestSetup(); } /*! * \brief Logs all databases and packages of the current test setup. */ void BuildActionsTests::logTestSetup() { for (const auto &db : m_setup.config.databases) { cout << EscapeCodes::Phrases::Info << "Packages of " << db.name << ':' << EscapeCodes::Phrases::End; for (const auto &[pkgName, pkg] : db.packages) { cout << " - " << pkgName << '\n'; } } cout.flush(); } /*! * \brief Initializes the fixture's build action.helper */ template void BuildActionsTests::setupBuildAction() { m_buildAction = std::make_shared(0, &m_setup); } /*! * \brief Resets the fixture's build action. */ void BuildActionsTests::resetBuildAction() { m_buildAction->status = BuildActionStatus::Created; m_buildAction->result = BuildActionResult::None; m_buildAction->resultData = std::string(); } /*! * \brief Runs the fixture's build action (initialized via setupBuildAction()) until it has finished. * \param message The message for asserting whether the build action has finished yet. * \param timeout The max. time to wait for the build action to finish. It does not interrupt the handler which is currently * executed (so tests can still get stuck). * */ void BuildActionsTests::runBuildAction(const char *message, CppUtilities::TimeSpan timeout) { resetBuildAction(); m_buildAction->start(m_setup); auto &ioc = m_setup.building.ioContext; ioc.restart(); boost::asio::executor_work_guard workGuard = boost::asio::make_work_guard(ioc); m_buildAction->setConcludeHandler([&workGuard] { workGuard.reset(); }); if (m_timeoutFactor == 0.0) { ioc.run(); } else { ioc.run_for(std::chrono::microseconds(static_cast((timeout * m_timeoutFactor).totalMicroseconds()))); } CPPUNIT_ASSERT_EQUAL_MESSAGE(message, BuildActionStatus::Finished, m_buildAction->status); } /*! * \brief Returns the internal build action for the fixture's build action. * \remarks Invokes undefined behavior if \tp InternalBuildActionType is not the actual type. */ template InternalBuildActionType *BuildActionsTests::internalBuildAction() { auto *const internalBuildAction = m_buildAction->m_internalBuildAction.get(); CPPUNIT_ASSERT_MESSAGE("internal build action assigned", internalBuildAction); return static_cast(internalBuildAction); } /*! * \brief Tests basic logging. */ void BuildActionsTests::testLogging() { using namespace EscapeCodes; m_buildAction = make_shared(0, &m_setup); { const auto stderrCheck = OutputCheck( [](const std::string &output) { TESTUTILS_ASSERT_LIKE_FLAGS( "messages logged on stderr", ".*ERROR.*some error: message.*\n.*info.*\n.*", std::regex::extended, output); }, cerr); m_buildAction->log()(Phrases::ErrorMessage, "some error: ", "message", '\n'); m_buildAction->log()(Phrases::InfoMessage, "info", '\n'); } CPPUNIT_ASSERT_EQUAL_MESSAGE("messages added to build action output", "\e[1;31m==> ERROR: \e[0m\e[1msome error: message\n\e[1;37m==> \e[0m\e[1minfo\n"s, m_buildAction->output); } /*! * \brief Tests the ProcessSession class (which is used to spawn processes within build actions capturing the output). */ void BuildActionsTests::testProcessSession() { auto &ioc = m_setup.building.ioContext; auto session = std::make_shared(ioc, [&ioc](boost::process::child &&child, ProcessResult &&result) { CPP_UTILITIES_UNUSED(child) CPPUNIT_ASSERT_EQUAL(std::error_code(), result.errorCode); CPPUNIT_ASSERT_EQUAL(0, result.exitCode); CPPUNIT_ASSERT_EQUAL(std::string(), result.error); CPPUNIT_ASSERT_EQUAL("line1\nline2"s, result.output); ioc.stop(); }); session->launch(boost::process::search_path("echo"), "-n", "line1\nline2"); session.reset(); ioc.run(); } /*! * \brief Tests the BuildProcessSession class (which is used to spawn processes within build actions creating a log file). */ void BuildActionsTests::testBuildActionProcess() { m_buildAction = std::make_shared(0, &m_setup); const auto scriptPath = testFilePath("scripts/print_some_data.sh"); const auto logFilePath = std::filesystem::path(TestApplication::instance()->workingDirectory()) / "logfile.log"; std::filesystem::create_directory(logFilePath.parent_path()); if (std::filesystem::exists(logFilePath)) { std::filesystem::remove(logFilePath); } auto &ioc = m_setup.building.ioContext; auto session = std::make_shared( m_buildAction.get(), ioc, "test", std::string(logFilePath), [&ioc](boost::process::child &&child, ProcessResult &&result) { CPPUNIT_ASSERT_EQUAL(std::error_code(), result.errorCode); CPPUNIT_ASSERT_EQUAL(0, result.exitCode); CPPUNIT_ASSERT_GREATER(0, child.native_handle()); ioc.stop(); }); session->launch(scriptPath); session.reset(); ioc.run(); const auto logFile = readFile(logFilePath); const auto logLines = splitStringSimple>(logFile, "\r\n"); CPPUNIT_ASSERT_EQUAL(5002_st, logLines.size()); CPPUNIT_ASSERT_EQUAL("printing some numbers"sv, logLines.front()); CPPUNIT_ASSERT_EQUAL_MESSAGE("trailing line break", ""sv, logLines.back()); CPPUNIT_ASSERT_EQUAL_MESSAGE("last line", "line 5000"sv, logLines[logLines.size() - 2u]); TESTUTILS_ASSERT_LIKE_FLAGS("PID logged", ".*Launched \"test\", PID\\: [0-9]+.*\n.*"s, std::regex::extended, m_buildAction->output); } /*! * \brief Tests the BufferSearch class. */ void BuildActionsTests::testBufferSearch() { // make a buffer BuildProcessSession::BufferPoolType bufferPool(30); auto buffer = bufferPool.newBuffer(); // setup testing the search std::string expectedResult; bool hasResult = false; BufferSearch bs("Updated version: ", "\e\n", "Starting build", [&expectedResult, &hasResult](std::string &&result) { CPPUNIT_ASSERT_EQUAL(expectedResult, result); CPPUNIT_ASSERT_MESSAGE("callback only invoked once", !hasResult); hasResult = true; }); // feed data into the search bs(buffer, 0); std::strcpy(buffer->data(), "Starting Updated"); bs(buffer, 16); std::strcpy(buffer->data(), " version: some "); bs(buffer, 15); expectedResult = "some version number"; std::strcpy(buffer->data(), "version number\emore chars"); bs(buffer, 25); CPPUNIT_ASSERT(hasResult); std::strcpy(buffer->data(), "... Starting build ..."); bs(buffer, 22); } /*! * \brief Tests the ReloadLibraryDependencies build action. */ void BuildActionsTests::testParsingInfoFromPkgFiles() { // init config LibPkg::Config &config = m_setup.config; config.databases = { { "foo.db" }, { "bar.db" }, { "baz.db" } }; // init db object LibPkg::Database &fooDb = config.databases[0]; auto harfbuzz = fooDb.packages["mingw-w64-harfbuzz"] = LibPkg::Package::fromPkgFileName("mingw-w64-harfbuzz-1.4.2-1-any.pkg.tar.xz"); auto syncthingtray = fooDb.packages["syncthingtray"] = LibPkg::Package::fromPkgFileName("syncthingtray-0.6.2-1-x86_64.pkg.tar.xz"); fooDb.localPkgDir = directory(testFilePath("repo/foo/mingw-w64-harfbuzz-1.4.2-1-any.pkg.tar.xz")); LibPkg::Database &barDb = config.databases[1]; auto cmake = barDb.packages["cmake"] = LibPkg::Package::fromPkgFileName("cmake-3.8.2-1-x86_64.pkg.tar.xz"); CPPUNIT_ASSERT_EQUAL_MESSAGE("origin", LibPkg::PackageOrigin::PackageFileName, cmake->origin); barDb.localPkgDir = directory(testFilePath("repo/bar/cmake-3.8.2-1-x86_64.pkg.tar.xz")); auto buildAction = std::make_shared(0, &m_setup); auto reloadLibDependencies = ReloadLibraryDependencies(m_setup, buildAction); reloadLibDependencies.run(); const auto &messages = std::get(buildAction->resultData); CPPUNIT_ASSERT_EQUAL(std::vector(), messages.errors); CPPUNIT_ASSERT_EQUAL(std::vector(), messages.warnings); CPPUNIT_ASSERT_EQUAL(std::vector(), messages.notes); using namespace TestHelper; checkHarfbuzzPackagePeDependencies(*harfbuzz); checkSyncthingTrayPackageSoDependencies(*syncthingtray); checkCmakePackageSoDependencies(*cmake); const auto pkgsRequiringLibGCC = config.findPackagesProvidingLibrary("pe-i386::libgcc_s_sjlj-1.dll", true); CPPUNIT_ASSERT_EQUAL(1_st, pkgsRequiringLibGCC.size()); CPPUNIT_ASSERT_EQUAL(harfbuzz, pkgsRequiringLibGCC.front().pkg); const auto pkgsProvidingLibSyncthingConnector = config.findPackagesProvidingLibrary("elf-x86_64::libsyncthingconnector.so.0.6.2", false); CPPUNIT_ASSERT_EQUAL(1_st, pkgsProvidingLibSyncthingConnector.size()); CPPUNIT_ASSERT_EQUAL(syncthingtray, pkgsProvidingLibSyncthingConnector.front().pkg); } /*! * \brief Tests the PrepareBuild build action. */ void BuildActionsTests::testPreparingBuild() { // get meta info auto &metaInfo = m_setup.building.metaInfo; const auto &typeInfo = metaInfo.typeInfoForId(BuildActionType::PrepareBuild); const auto pkgbuildsDirsSetting = std::string(typeInfo.settings[static_cast(PrepareBuildSettings::PKGBUILDsDirs)].param); // load basic test setup and create build action loadBasicTestSetup(); m_buildAction = std::make_shared(0, &m_setup); m_buildAction->type = BuildActionType::PrepareBuild; m_buildAction->directory = "prepare-build-test"; m_buildAction->flags = static_cast(PrepareBuildFlags::CleanSrcDir); m_buildAction->settings[pkgbuildsDirsSetting] = std::filesystem::absolute(testDirPath("building/pkgbuilds")); m_buildAction->packageNames = { "boost", "mingw-w64-gcc" }; // prepare test configuration // - Pretend all dependencies of boost are there except "zstd-1.4.5-1-x86_64.pkg.tar.zst" (so zstd is supposed pulled into the build automatically). // - There's a dummy package for zstd which incurs no further dependencies. // - The package mingw-w64-gcc is also just a dummy here to test handling variants; it has no dependencies here. loadTestConfig(); auto coreDb = m_setup.config.findDatabase("core", "x86_64"); CPPUNIT_ASSERT_MESSAGE("core db exists", coreDb); for (const auto pkgFileName : { "python-3.8.6-1-x86_64.pkg.tar.zst"sv, "python2-2.7.18-2-x86_64.pkg.tar.zst"sv, "bzip2-1.0.8-4-x86_64.pkg.tar.zst"sv, "findutils-4.7.0-2-x86_64.pkg.tar.xz"sv, "icu-67.1-1-x86_64.pkg.tar.zst"sv, "openmpi-4.0.5-2-x86_64.pkg.tar.zst"sv, "python-numpy-1.19.4-1-x86_64.pkg.tar.zst"sv, "python2-numpy-1.16.6-1-x86_64.pkg.tar.zst"sv, "zlib-1:1.2.11-4-x86_64.pkg.tar.xz"sv }) { coreDb->updatePackage(LibPkg::Package::fromPkgFileName(pkgFileName)); } // run without destination database runBuildAction("prepare build without destination db"); CPPUNIT_ASSERT_EQUAL_MESSAGE("failure without destination db", BuildActionResult::Failure, m_buildAction->result); CPPUNIT_ASSERT_EQUAL_MESSAGE( "failure without destination db", "not exactly one destination database specified"s, std::get(m_buildAction->resultData)); // run with destination database (yes, the database is called "boost" in this test setup as well) m_buildAction->destinationDbs = { "boost" }; runBuildAction("prepare build: successful preparation"); CPPUNIT_ASSERT_EQUAL_MESSAGE("success", BuildActionResult::Success, m_buildAction->result); CPPUNIT_ASSERT_MESSAGE("build preparation present", std::holds_alternative(m_buildAction->resultData)); const auto &buildPreparation = std::get(m_buildAction->resultData); CPPUNIT_ASSERT_EQUAL_MESSAGE("target db set", "boost"s, buildPreparation.targetDb); CPPUNIT_ASSERT_EQUAL_MESSAGE("target arch set", "x86_64"s, buildPreparation.targetArch); CPPUNIT_ASSERT_EQUAL_MESSAGE("staging db set", "boost-staging"s, buildPreparation.stagingDb); CPPUNIT_ASSERT_EQUAL_MESSAGE("no cyclic leftovers", 0_st, buildPreparation.cyclicLeftovers.size()); CPPUNIT_ASSERT_EQUAL_MESSAGE("no warnings", 0_st, buildPreparation.warnings.size()); CPPUNIT_ASSERT_EQUAL_MESSAGE("no error", std::string(), buildPreparation.error); CPPUNIT_ASSERT_EQUAL_MESSAGE("manually ordered not set", false, buildPreparation.manuallyOrdered); CPPUNIT_ASSERT_EQUAL_MESSAGE("db config has 2 dbs", 2_st, buildPreparation.dbConfig.size()); CPPUNIT_ASSERT_EQUAL_MESSAGE("first db", "boost"s, buildPreparation.dbConfig[0].first); CPPUNIT_ASSERT_EQUAL_MESSAGE("second db", "core"s, buildPreparation.dbConfig[1].first); CPPUNIT_ASSERT_EQUAL_MESSAGE("staging db config has 3 dbs", 3_st, buildPreparation.stagingDbConfig.size()); CPPUNIT_ASSERT_EQUAL_MESSAGE("first staging db", "boost-staging"s, buildPreparation.stagingDbConfig[0].first); const auto &batches = buildPreparation.batches; CPPUNIT_ASSERT_EQUAL_MESSAGE("two batches present", 2_st, batches.size()); const auto expectedFirstBatch = std::vector{ "mingw-w64-gcc", "zstd" }; CPPUNIT_ASSERT_EQUAL_MESSAGE("first batch", expectedFirstBatch, batches[0]); const auto expectedSecondBatch = std::vector{ "boost" }; CPPUNIT_ASSERT_EQUAL_MESSAGE("second batch", expectedSecondBatch, batches[1]); CPPUNIT_ASSERT_MESSAGE( "build-preparation.json created", std::filesystem::is_regular_file("building/build-data/prepare-build-test/build-preparation.json")); CPPUNIT_ASSERT_MESSAGE( "build-progress.json created", std::filesystem::is_regular_file("building/build-data/prepare-build-test/build-progress.json")); for (const auto pkg : { "boost"sv, "mingw-w64-gcc"sv, "zstd"sv }) { CPPUNIT_ASSERT_MESSAGE( "PKGBUILD for " % pkg + " created", std::filesystem::is_regular_file("building/build-data/prepare-build-test/" % pkg + "/src/PKGBUILD")); } } /*! * \brief Tests the ConductBuild build action. */ void BuildActionsTests::testConductingBuild() { // load basic test setup and create build action loadBasicTestSetup(); m_buildAction = std::make_shared(0, &m_setup); m_buildAction->type = BuildActionType::ConductBuild; m_buildAction->directory = "conduct-build-test"; m_buildAction->packageNames = { "boost" }; m_buildAction->flags = static_cast(ConductBuildFlags::BuildAsFarAsPossible | ConductBuildFlags::SaveChrootOfFailures | ConductBuildFlags::UpdateChecksums | ConductBuildFlags::AutoStaging); // run without build preparation runBuildAction("conduct build without build preparation"); CPPUNIT_ASSERT_EQUAL_MESSAGE("failure without preparation JSON", BuildActionResult::Failure, m_buildAction->result); TESTUTILS_ASSERT_LIKE( "no preparation JSON", "Unable to restore build-preparation.json:.*not exist.*", std::get(m_buildAction->resultData)); // create fake build preparation const auto origPkgbuildFile = workingCopyPathAs("building/build-data/conduct-build-test/boost/src/PKGBUILD", "orig-src-dir/boost/PKGBUILD"); const auto origSourceDir = std::filesystem::absolute(directory(origPkgbuildFile)); auto prepData = readFile(testFilePath("building/build-data/conduct-build-test/build-preparation.json")); findAndReplace(prepData, "$ORIGINAL_SOURCE_DIRECTORY", origSourceDir.native()); findAndReplace(prepData, "$TEST_FILES_PATH", "TODO"); const auto buildDir = std::filesystem::absolute(workingCopyPath("building", WorkingCopyMode::NoCopy)); const auto prepFile = std::filesystem::absolute(workingCopyPath("building/build-data/conduct-build-test/build-preparation.json", WorkingCopyMode::NoCopy)); writeFile(prepFile.native(), prepData); auto progressData = readFile(testFilePath("building/build-data/conduct-build-test/build-progress.json")); const auto progressFile = std::filesystem::absolute(workingCopyPath("building/build-data/conduct-build-test/build-progress.json", WorkingCopyMode::NoCopy)); writeFile(progressFile.native(), progressData); std::filesystem::copy(testDirPath("building/build-data/conduct-build-test/boost"), m_setup.workingDirectory + "/building/build-data/conduct-build-test/boost", std::filesystem::copy_options::recursive); // run without chroot configuration runBuildAction("conduct build without chroot configuration"); CPPUNIT_ASSERT_EQUAL_MESSAGE("failure without chroot configuration", BuildActionResult::Failure, m_buildAction->result); CPPUNIT_ASSERT_EQUAL_MESSAGE( "no chroot configuration", "The chroot directory is not configured."s, std::get(m_buildAction->resultData)); // configure chroot directory m_setup.building.chrootDir = testDirPath("test-config/chroot-dir"); // run with misconfigured destination db runBuildAction("conduct build with misconfigured destination db (1)"); CPPUNIT_ASSERT_EQUAL_MESSAGE("failure without destination db (1)", BuildActionResult::Failure, m_buildAction->result); CPPUNIT_ASSERT_EQUAL_MESSAGE("destination db missing (1)", "Auto-staging is enabled but the staging database \"boost-staging@x86_64\" specified in build-preparation.json can not be found."s, std::get(m_buildAction->resultData)); loadTestConfig(); runBuildAction("conduct build with misconfigured destination db (2)"); CPPUNIT_ASSERT_EQUAL_MESSAGE("failure without destination db (2)", BuildActionResult::Failure, m_buildAction->result); TESTUTILS_ASSERT_LIKE("destination db missing (2)", "Destination repository \"repos/boost/os/x86_64\" does not exist.*"s, std::get(m_buildAction->resultData)); // create repositories const auto reposPath = testDirPath("test-config/repos"); const auto reposWorkingCopyPath = std::filesystem::path(m_setup.workingDirectory + "/repos"); std::filesystem::create_directory(reposWorkingCopyPath); std::filesystem::copy(reposPath, reposWorkingCopyPath, std::filesystem::copy_options::recursive); // run without chroot directory runBuildAction("conduct build without chroot directory"); CPPUNIT_ASSERT_EQUAL_MESSAGE("no chroot directory: results in failure", BuildActionResult::Failure, m_buildAction->result); CPPUNIT_ASSERT_EQUAL_MESSAGE("no chroot directory: result data states affected packages", "failed to build packages: boost"s, std::get(m_buildAction->resultData)); auto *internalData = internalBuildAction(); TESTUTILS_ASSERT_LIKE("no chroot directory: package-level error message", "Chroot directory \".*/test-config/chroot-dir/arch-x86_64/root\" is no directory."s, internalData->m_buildProgress.progressByPackage["boost"].error); // create chroot directory const auto chrootSkelPath = testDirPath("test-config/chroot-skel"); const auto chrootDirWorkingCopyPath = std::filesystem::path(m_setup.workingDirectory + "/chroot-dir"); const auto rootChrootWorkingCopyPath = chrootDirWorkingCopyPath / "arch-x86_64/root"; std::filesystem::create_directory(chrootDirWorkingCopyPath); std::filesystem::copy(m_setup.building.chrootDir, chrootDirWorkingCopyPath, std::filesystem::copy_options::recursive); std::filesystem::create_directories(rootChrootWorkingCopyPath); std::filesystem::copy(chrootSkelPath, rootChrootWorkingCopyPath, std::filesystem::copy_options::recursive); m_setup.building.chrootDir = chrootDirWorkingCopyPath.string(); // assign the created chroot directory writeFile(progressFile.native(), progressData); // reset "build-progress.json" so the new chroot directory is actually used // run without having makepkg actually producing any packages runBuildAction("conduct build without producing any packages"); CPPUNIT_ASSERT_EQUAL_MESSAGE("no packages produced: results in failure", BuildActionResult::Failure, m_buildAction->result); CPPUNIT_ASSERT_EQUAL_MESSAGE("no packages produced: result data states affected packages", "failed to build packages: boost"s, std::get(m_buildAction->resultData)); internalData = internalBuildAction(); TESTUTILS_ASSERT_LIKE("no packages produced: package-level error message", "not all.*packages exist.*boost-1.73.0-1.src.tar.gz.*boost-libs-1\\.73\\.0-1-x86_64\\.pkg\\.tar\\.zst.*boost-1\\.73\\.0-1-x86_64\\.pkg\\.tar\\.zst"s, internalData->m_buildProgress.progressByPackage["boost"].error); CPPUNIT_ASSERT_MESSAGE( "no packages produced: package considered finished", !internalData->m_buildProgress.progressByPackage["boost"].finished.isNull()); CPPUNIT_ASSERT_MESSAGE("no packages produced: package not added to repo", !internalData->m_buildProgress.progressByPackage["boost"].addedToRepo); // prepare some actual packages std::filesystem::copy(testFilePath("test-config/fake-build-artefacts/boost-1.73.0-1.src.tar.gz"), buildDir / "build-data/conduct-build-test/boost/pkg/boost-1.73.0-1.src.tar.gz"); std::filesystem::copy(testFilePath("test-config/fake-build-artefacts/boost-1.73.0-1-x86_64.pkg.tar.zst"), buildDir / "build-data/conduct-build-test/boost/pkg/boost-1.73.0-1-x86_64.pkg.tar.zst"); std::filesystem::copy(testFilePath("test-config/fake-build-artefacts/boost-libs-1.73.0-1-x86_64.pkg.tar.zst"), buildDir / "build-data/conduct-build-test/boost/pkg/boost-libs-1.73.0-1-x86_64.pkg.tar.zst"); // conduct build without staging runBuildAction("conduct build without staging"); CPPUNIT_ASSERT_EQUAL_MESSAGE("no staging needed: success", BuildActionResult::Success, m_buildAction->result); CPPUNIT_ASSERT_EQUAL_MESSAGE("no staging needed: no result data present", ""s, std::get(m_buildAction->resultData)); internalData = internalBuildAction(); CPPUNIT_ASSERT_MESSAGE("no staging needed: rebuild list empty", internalData->m_buildProgress.rebuildList.empty()); CPPUNIT_ASSERT_MESSAGE( "no staging needed: package considered finished", !internalData->m_buildProgress.progressByPackage["boost"].finished.isNull()); CPPUNIT_ASSERT_MESSAGE("no staging needed: package added to repo", internalData->m_buildProgress.progressByPackage["boost"].addedToRepo); // check whether log files have been created accordingly CPPUNIT_ASSERT_EQUAL_MESSAGE("no staging needed: download log", "fake makepkg: -f --nodeps --nobuild --source\n"s, readFile("building/build-data/conduct-build-test/boost/pkg/download.log")); CPPUNIT_ASSERT_EQUAL_MESSAGE( "no staging needed: updpkgsums log", "fake updatepkgsums: \n"s, readFile("building/build-data/conduct-build-test/boost/pkg/updpkgsums.log")); TESTUTILS_ASSERT_LIKE("no staging needed: build log", "fake makechrootpkg: -c -u -C -r .*chroot-dir/arch-x86_64 -l buildservice --\n"s, readFile("building/build-data/conduct-build-test/boost/pkg/build.log")); TESTUTILS_ASSERT_LIKE("no staging needed: repo-add log", "fake repo-add: boost.db.tar.zst boost(-libs)?-1\\.73\\.0-1-x86_64.pkg.tar.zst boost(-libs)?-1\\.73\\.0-1-x86_64.pkg.tar.zst\n"s, readFile("building/build-data/conduct-build-test/boost/pkg/repo-add.log")); // check whether packages have actually been added to repo CPPUNIT_ASSERT_MESSAGE( "no staging needed: package added to repo (0)", std::filesystem::is_regular_file("repos/boost/os/src/boost-1.73.0-1.src.tar.gz")); CPPUNIT_ASSERT_MESSAGE( "no staging needed: package added to repo (1)", std::filesystem::is_regular_file("repos/boost/os/x86_64/boost-1.73.0-1-x86_64.pkg.tar.zst")); CPPUNIT_ASSERT_MESSAGE("no staging needed: package added to repo (2)", std::filesystem::is_regular_file("repos/boost/os/x86_64/boost-libs-1.73.0-1-x86_64.pkg.tar.zst")); CPPUNIT_ASSERT_MESSAGE("no staging needed: signature added to repo (0)", std::filesystem::is_regular_file("repos/boost/os/x86_64/boost-1.73.0-1-x86_64.pkg.tar.zst.sig")); CPPUNIT_ASSERT_MESSAGE("no staging needed: signature added to repo (1)", std::filesystem::is_regular_file("repos/boost/os/x86_64/boost-libs-1.73.0-1-x86_64.pkg.tar.zst.sig")); CPPUNIT_ASSERT_EQUAL_MESSAGE("no staging needed: signature looks as expected", "fake signature with GPG key 1234567890 boost-libs-1.73.0-1-x86_64.pkg.tar.zst\n"s, readFile("repos/boost/os/x86_64/boost-libs-1.73.0-1-x86_64.pkg.tar.zst.sig")); // add packages needing a rebuild to trigger auto-staging m_setup.config.loadAllPackages(false); auto *const boostDb = m_setup.config.findDatabase("boost"sv, "x86_64"sv); auto *const miscDb = m_setup.config.findDatabase("misc"sv, "x86_64"sv); CPPUNIT_ASSERT_MESSAGE("boost database present", boostDb); CPPUNIT_ASSERT_MESSAGE("misc database present", miscDb); auto &boostLibsPackage = boostDb->packages["boost-libs"]; boostLibsPackage->libprovides = { "elf-x86_64::libboost_regex.so.1.72.0" }; boostLibsPackage->libdepends = { "elf-x86_64::libstdc++.so.6" }; boostDb->forceUpdatePackage(boostLibsPackage); auto &sourceHighlightPackage = miscDb->packages["source-highlight"]; sourceHighlightPackage->libprovides = { "elf-x86_64::libsource-highlight.so.4" }; sourceHighlightPackage->libdepends = { "elf-x86_64::libboost_regex.so.1.72.0", "elf-x86_64::libsource-highlight.so.4", "elf-x86_64::libstdc++.so.6" }; miscDb->forceUpdatePackage(sourceHighlightPackage); m_setup.printDatabases(); logTestSetup(); // conduct build with staging writeFile(progressFile.native(), progressData); // reset "build-progress.json" so the package is re-considered runBuildAction("conduct build with staging"); //CPPUNIT_ASSERT_EQUAL_MESSAGE("staging needed: success", BuildActionResult::Success, m_buildAction->result); CPPUNIT_ASSERT_EQUAL_MESSAGE("staging needed: no result data present", ""s, std::get(m_buildAction->resultData)); internalData = internalBuildAction(); const auto &rebuildList = internalData->m_buildProgress.rebuildList; const auto rebuildInfoForMisc = rebuildList.find("misc"); CPPUNIT_ASSERT_EQUAL_MESSAGE("staging needed: rebuild list contains 1 database", 1_st, rebuildList.size()); CPPUNIT_ASSERT_MESSAGE("staging needed: rebuild info for misc present", rebuildInfoForMisc != rebuildList.end()); const auto rebuildInfoForSourceHighlight = rebuildInfoForMisc->second.find("source-highlight"); const auto expectedLibprovides = std::vector{ "elf-x86_64::libboost_regex.so.1.72.0" }; CPPUNIT_ASSERT_MESSAGE( "staging needed: rebuild info for source-highlight present", rebuildInfoForSourceHighlight != rebuildInfoForMisc->second.end()); CPPUNIT_ASSERT_EQUAL_MESSAGE( "staging needed: libprovides for source-highlight present", expectedLibprovides, rebuildInfoForSourceHighlight->second.libprovides); // check whether log files have been created accordingly TESTUTILS_ASSERT_LIKE("no staging needed: repo-add log", "fake repo-add: boost-staging.db.tar.zst boost(-libs)?-1\\.73\\.0-1-x86_64.pkg.tar.zst boost(-libs)?-1\\.73\\.0-1-x86_64.pkg.tar.zst\n"s, readFile("building/build-data/conduct-build-test/boost/pkg/repo-add.log")); // check whether package have been added to staging repo CPPUNIT_ASSERT_MESSAGE( "staging needed: package added to repo (0)", std::filesystem::is_regular_file("repos/boost-staging/os/src/boost-1.73.0-1.src.tar.gz")); CPPUNIT_ASSERT_MESSAGE("staging needed: package added to repo (1)", std::filesystem::is_regular_file("repos/boost-staging/os/x86_64/boost-1.73.0-1-x86_64.pkg.tar.zst")); CPPUNIT_ASSERT_MESSAGE("staging needed: package added to repo (2)", std::filesystem::is_regular_file("repos/boost-staging/os/x86_64/boost-libs-1.73.0-1-x86_64.pkg.tar.zst")); CPPUNIT_ASSERT_MESSAGE("staging needed: signature added to repo (0)", std::filesystem::is_regular_file("repos/boost-staging/os/x86_64/boost-1.73.0-1-x86_64.pkg.tar.zst.sig")); CPPUNIT_ASSERT_MESSAGE("staging needed: signature added to repo (1)", std::filesystem::is_regular_file("repos/boost-staging/os/x86_64/boost-libs-1.73.0-1-x86_64.pkg.tar.zst.sig")); }