| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 |
- #include <cassert>
- #include <chrono>
- #include <cstddef> // IWYU pragma: keep
- #include <cstdlib>
- #include <iostream>
- #include <map>
- #include <set>
- #include <string>
- #include <thread>
- #include <utility>
- #include <vector>
- #include "cmsys/Encoding.hxx"
- #include "cmsys/FStream.hxx"
- #include "cmCTestMultiProcessHandler.h"
- #include "cmCTestResourceAllocator.h"
- #include "cmCTestResourceSpec.h"
- #include "cmCTestTestHandler.h"
- #include "cmFileLock.h"
- #include "cmFileLockResult.h"
- #include "cmList.h"
- #include "cmStringAlgorithms.h"
- #include "cmSystemTools.h"
- /*
- * This helper program is used to verify that the CTest resource allocation
- * feature is working correctly. It consists of two stages:
- *
- * 1) write - This stage receives the RESOURCE_GROUPS property of the test and
- * compares it with the values passed in the CTEST_RESOURCE_GROUP_*
- * environment variables. If it received all of the resources it expected,
- * then it writes this information to a log file, which will be read in
- * the verify stage.
- * 2) verify - This stage compares the log file with the resource spec file to
- * make sure that no resources were over-subscribed, deallocated without
- * being allocated, or allocated without being deallocated.
- */
- static int usage(const char* argv0)
- {
- std::cout << "Usage: " << argv0 << " (write|verify) <args...>" << std::endl;
- return 1;
- }
- static int usageWrite(const char* argv0)
- {
- std::cout << "Usage: " << argv0
- << " write <log-file> <test-name> <sleep-time-secs>"
- " [<resource-groups-property>]"
- << std::endl;
- return 1;
- }
- static int usageVerify(const char* argv0)
- {
- std::cout << "Usage: " << argv0
- << " verify <log-file> <resource-spec-file> [<test-names>]"
- << std::endl;
- return 1;
- }
- static int doWrite(int argc, char const* const* argv)
- {
- if (argc < 5 || argc > 6) {
- return usageWrite(argv[0]);
- }
- std::string logFile = argv[2];
- std::string testName = argv[3];
- unsigned int sleepTime = std::atoi(argv[4]);
- std::vector<std::map<
- std::string, std::vector<cmCTestMultiProcessHandler::ResourceAllocation>>>
- resources;
- if (argc == 6) {
- // Parse RESOURCE_GROUPS property
- std::string resourceGroupsProperty = argv[5];
- std::vector<
- std::vector<cmCTestTestHandler::cmCTestTestResourceRequirement>>
- resourceGroups;
- bool result = cmCTestTestHandler::ParseResourceGroupsProperty(
- resourceGroupsProperty, resourceGroups);
- (void)result;
- assert(result);
- // Verify group count
- const char* resourceGroupCountEnv =
- cmSystemTools::GetEnv("CTEST_RESOURCE_GROUP_COUNT");
- if (!resourceGroupCountEnv) {
- std::cout << "CTEST_RESOURCE_GROUP_COUNT should be defined" << std::endl;
- return 1;
- }
- int resourceGroupCount = std::atoi(resourceGroupCountEnv);
- if (resourceGroups.size() !=
- static_cast<std::size_t>(resourceGroupCount)) {
- std::cout
- << "CTEST_RESOURCE_GROUP_COUNT does not match expected resource groups"
- << std::endl
- << "Expected: " << resourceGroups.size() << std::endl
- << "Actual: " << resourceGroupCount << std::endl;
- return 1;
- }
- if (!cmSystemTools::Touch(logFile + ".lock", true)) {
- std::cout << "Could not create lock file" << std::endl;
- return 1;
- }
- cmFileLock lock;
- auto lockResult =
- lock.Lock(logFile + ".lock", static_cast<unsigned long>(-1));
- if (!lockResult.IsOk()) {
- std::cout << "Could not lock file" << std::endl;
- return 1;
- }
- std::size_t i = 0;
- cmsys::ofstream fout(logFile.c_str(), std::ios::app);
- fout << "begin " << testName << std::endl;
- for (auto& resourceGroup : resourceGroups) {
- try {
- // Build and verify set of expected resources
- std::set<std::string> expectedResources;
- for (auto const& it : resourceGroup) {
- expectedResources.insert(it.ResourceType);
- }
- std::string prefix = "CTEST_RESOURCE_GROUP_";
- prefix += std::to_string(i);
- const char* actualResourcesCStr = cmSystemTools::GetEnv(prefix);
- if (!actualResourcesCStr) {
- std::cout << prefix << " should be defined" << std::endl;
- return 1;
- }
- auto actualResourcesVec =
- cmSystemTools::SplitString(actualResourcesCStr, ',');
- std::set<std::string> actualResources;
- for (auto const& r : actualResourcesVec) {
- if (!r.empty()) {
- actualResources.insert(r);
- }
- }
- if (actualResources != expectedResources) {
- std::cout << prefix << " did not list expected resources"
- << std::endl;
- return 1;
- }
- // Verify that we got what we asked for and write it to the log
- prefix += '_';
- std::map<std::string,
- std::vector<cmCTestMultiProcessHandler::ResourceAllocation>>
- resEntry;
- for (auto const& type : actualResources) {
- auto it = resourceGroup.begin();
- std::string varName = prefix;
- varName += cmSystemTools::UpperCase(type);
- const char* varVal = cmSystemTools::GetEnv(varName);
- if (!varVal) {
- std::cout << varName << " should be defined" << std::endl;
- return 1;
- }
- auto received = cmSystemTools::SplitString(varVal, ';');
- for (auto const& r : received) {
- while (it->ResourceType != type || it->UnitsNeeded == 0) {
- ++it;
- if (it == resourceGroup.end()) {
- std::cout << varName << " did not list expected resources"
- << std::endl;
- return 1;
- }
- }
- auto split = cmSystemTools::SplitString(r, ',');
- if (split.size() != 2) {
- std::cout << varName << " was ill-formed" << std::endl;
- return 1;
- }
- if (!cmHasLiteralPrefix(split[0], "id:")) {
- std::cout << varName << " was ill-formed" << std::endl;
- return 1;
- }
- auto id = split[0].substr(3);
- if (!cmHasLiteralPrefix(split[1], "slots:")) {
- std::cout << varName << " was ill-formed" << std::endl;
- return 1;
- }
- auto slots = split[1].substr(6);
- unsigned int amount = std::atoi(slots.c_str());
- if (amount != static_cast<unsigned int>(it->SlotsNeeded)) {
- std::cout << varName << " did not list expected resources"
- << std::endl;
- return 1;
- }
- --it->UnitsNeeded;
- fout << "alloc " << type << " " << id << " " << amount
- << std::endl;
- resEntry[type].push_back({ id, amount });
- }
- bool ended = false;
- while (it->ResourceType != type || it->UnitsNeeded == 0) {
- ++it;
- if (it == resourceGroup.end()) {
- ended = true;
- break;
- }
- }
- if (!ended) {
- std::cout << varName << " did not list expected resources"
- << std::endl;
- return 1;
- }
- }
- resources.push_back(resEntry);
- ++i;
- } catch (...) {
- std::cout << "Unknown error while processing resources" << std::endl;
- return 1;
- }
- }
- auto unlockResult = lock.Release();
- if (!unlockResult.IsOk()) {
- std::cout << "Could not unlock file" << std::endl;
- return 1;
- }
- } else {
- if (cmSystemTools::GetEnv("CTEST_RESOURCE_GROUP_COUNT")) {
- std::cout << "CTEST_RESOURCE_GROUP_COUNT should not be defined"
- << std::endl;
- return 1;
- }
- }
- std::this_thread::sleep_for(std::chrono::seconds(sleepTime));
- if (argc == 6) {
- if (!cmSystemTools::Touch(logFile + ".lock", true)) {
- std::cout << "Could not create lock file" << std::endl;
- return 1;
- }
- cmFileLock lock;
- auto lockResult =
- lock.Lock(logFile + ".lock", static_cast<unsigned long>(-1));
- if (!lockResult.IsOk()) {
- std::cout << "Could not lock file" << std::endl;
- return 1;
- }
- cmsys::ofstream fout(logFile.c_str(), std::ios::app);
- for (auto const& group : resources) {
- for (auto const& it : group) {
- for (auto const& it2 : it.second) {
- fout << "dealloc " << it.first << " " << it2.Id << " " << it2.Slots
- << std::endl;
- }
- }
- }
- fout << "end " << testName << std::endl;
- auto unlockResult = lock.Release();
- if (!unlockResult.IsOk()) {
- std::cout << "Could not unlock file" << std::endl;
- return 1;
- }
- }
- return 0;
- }
- static int doVerify(int argc, char const* const* argv)
- {
- if (argc < 4 || argc > 5) {
- return usageVerify(argv[0]);
- }
- std::string logFile = argv[2];
- std::string resFile = argv[3];
- std::string testNames;
- if (argc == 5) {
- testNames = argv[4];
- }
- cmList testNameList{ testNames };
- std::set<std::string> testNameSet(testNameList.begin(), testNameList.end());
- cmCTestResourceSpec spec;
- if (spec.ReadFromJSONFile(resFile) != true) {
- std::cout << "Could not read resource spec " << resFile << std::endl;
- return 1;
- }
- cmCTestResourceAllocator allocator;
- allocator.InitializeFromResourceSpec(spec);
- cmsys::ifstream fin(logFile.c_str(), std::ios::in);
- if (!fin) {
- std::cout << "Could not open log file " << logFile << std::endl;
- return 1;
- }
- std::string command;
- std::string resourceName;
- std::string resourceId;
- std::string testName;
- unsigned int amount;
- std::set<std::string> inProgressTests;
- std::set<std::string> completedTests;
- try {
- while (fin >> command) {
- if (command == "begin") {
- if (!(fin >> testName)) {
- std::cout << "Could not read begin line" << std::endl;
- return 1;
- }
- if (!testNameSet.count(testName) || inProgressTests.count(testName) ||
- completedTests.count(testName)) {
- std::cout << "Could not begin test" << std::endl;
- return 1;
- }
- inProgressTests.insert(testName);
- } else if (command == "alloc") {
- if (!(fin >> resourceName) || !(fin >> resourceId) ||
- !(fin >> amount)) {
- std::cout << "Could not read alloc line" << std::endl;
- return 1;
- }
- if (!allocator.AllocateResource(resourceName, resourceId, amount)) {
- std::cout << "Could not allocate resources" << std::endl;
- return 1;
- }
- } else if (command == "dealloc") {
- if (!(fin >> resourceName) || !(fin >> resourceId) ||
- !(fin >> amount)) {
- std::cout << "Could not read dealloc line" << std::endl;
- return 1;
- }
- if (!allocator.DeallocateResource(resourceName, resourceId, amount)) {
- std::cout << "Could not deallocate resources" << std::endl;
- return 1;
- }
- } else if (command == "end") {
- if (!(fin >> testName)) {
- std::cout << "Could not read end line" << std::endl;
- return 1;
- }
- if (!inProgressTests.erase(testName)) {
- std::cout << "Could not end test" << std::endl;
- return 1;
- }
- if (!completedTests.insert(testName).second) {
- std::cout << "Could not end test" << std::endl;
- return 1;
- }
- }
- }
- } catch (...) {
- std::cout << "Unknown error while reading log file" << std::endl;
- return 1;
- }
- auto const& avail = allocator.GetResources();
- for (auto const& it : avail) {
- for (auto const& it2 : it.second) {
- if (it2.second.Locked != 0) {
- std::cout << "Resource was not unlocked" << std::endl;
- return 1;
- }
- }
- }
- if (completedTests != testNameSet) {
- std::cout << "Tests were not ended" << std::endl;
- return 1;
- }
- return 0;
- }
- int main(int argc, char const* const* argv)
- {
- cmsys::Encoding::CommandLineArguments args =
- cmsys::Encoding::CommandLineArguments::Main(argc, argv);
- argc = args.argc();
- argv = args.argv();
- if (argc < 2) {
- return usage(argv[0]);
- }
- std::string argv1 = argv[1];
- if (argv1 == "write") {
- return doWrite(argc, argv);
- }
- if (argv1 == "verify") {
- return doVerify(argc, argv);
- }
- return usage(argv[0]);
- }
|