Commit c8f48069 authored by Kyle Edwards's avatar Kyle Edwards Committed by Brad King
Browse files

CTest: Add parser for hardware spec file

parent bb4a1410
......@@ -918,6 +918,7 @@ set(CTEST_SRCS cmCTest.cxx
CTest/cmCTestEmptyBinaryDirectoryCommand.cxx
CTest/cmCTestGenericHandler.cxx
CTest/cmCTestHandlerCommand.cxx
CTest/cmCTestHardwareSpec.cxx
CTest/cmCTestLaunch.cxx
CTest/cmCTestMemCheckCommand.cxx
CTest/cmCTestMemCheckHandler.cxx
......
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmCTestHardwareSpec.h"
#include <map>
#include <string>
#include <utility>
#include <vector>
#include "cmsys/FStream.hxx"
#include "cmsys/RegularExpression.hxx"
#include "cm_jsoncpp_reader.h"
#include "cm_jsoncpp_value.h"
static const cmsys::RegularExpression IdentifierRegex{ "^[a-z_][a-z0-9_]*$" };
static const cmsys::RegularExpression IdRegex{ "^[a-z0-9_]+$" };
bool cmCTestHardwareSpec::ReadFromJSONFile(const std::string& filename)
{
cmsys::ifstream fin(filename.c_str());
if (!fin) {
return false;
}
Json::Value root;
Json::CharReaderBuilder builder;
if (!Json::parseFromStream(builder, fin, &root, nullptr)) {
return false;
}
if (!root.isObject()) {
return false;
}
auto const& local = root["local"];
if (!local.isArray()) {
return false;
}
if (local.size() > 1) {
return false;
}
if (local.empty()) {
this->LocalSocket.Resources.clear();
return true;
}
auto const& localSocket = local[0];
if (!localSocket.isObject()) {
return false;
}
std::map<std::string, std::vector<cmCTestHardwareSpec::Resource>> resources;
cmsys::RegularExpressionMatch match;
for (auto const& key : localSocket.getMemberNames()) {
if (IdentifierRegex.find(key.c_str(), match)) {
auto const& value = localSocket[key];
auto& r = resources[key];
if (value.isArray()) {
for (auto const& item : value) {
if (item.isObject()) {
cmCTestHardwareSpec::Resource resource;
if (!item.isMember("id")) {
return false;
}
auto const& id = item["id"];
if (!id.isString()) {
return false;
}
resource.Id = id.asString();
if (!IdRegex.find(resource.Id.c_str(), match)) {
return false;
}
if (item.isMember("slots")) {
auto const& capacity = item["slots"];
if (!capacity.isConvertibleTo(Json::uintValue)) {
return false;
}
resource.Capacity = capacity.asUInt();
} else {
resource.Capacity = 1;
}
r.push_back(resource);
} else {
return false;
}
}
} else {
return false;
}
}
}
this->LocalSocket.Resources = std::move(resources);
return true;
}
bool cmCTestHardwareSpec::operator==(const cmCTestHardwareSpec& other) const
{
return this->LocalSocket == other.LocalSocket;
}
bool cmCTestHardwareSpec::operator!=(const cmCTestHardwareSpec& other) const
{
return !(*this == other);
}
bool cmCTestHardwareSpec::Socket::operator==(
const cmCTestHardwareSpec::Socket& other) const
{
return this->Resources == other.Resources;
}
bool cmCTestHardwareSpec::Socket::operator!=(
const cmCTestHardwareSpec::Socket& other) const
{
return !(*this == other);
}
bool cmCTestHardwareSpec::Resource::operator==(
const cmCTestHardwareSpec::Resource& other) const
{
return this->Id == other.Id && this->Capacity == other.Capacity;
}
bool cmCTestHardwareSpec::Resource::operator!=(
const cmCTestHardwareSpec::Resource& other) const
{
return !(*this == other);
}
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#ifndef cmCTestHardwareSpec_h
#define cmCTestHardwareSpec_h
#include <map>
#include <string>
#include <vector>
class cmCTestHardwareSpec
{
public:
class Resource
{
public:
std::string Id;
unsigned int Capacity;
bool operator==(const Resource& other) const;
bool operator!=(const Resource& other) const;
};
class Socket
{
public:
std::map<std::string, std::vector<Resource>> Resources;
bool operator==(const Socket& other) const;
bool operator!=(const Socket& other) const;
};
Socket LocalSocket;
bool ReadFromJSONFile(const std::string& filename);
bool operator==(const cmCTestHardwareSpec& other) const;
bool operator!=(const cmCTestHardwareSpec& other) const;
};
#endif
......@@ -8,6 +8,7 @@ include_directories(
set(CMakeLib_TESTS
testArgumentParser.cxx
testCTestProcesses.cxx
testCTestHardwareSpec.cxx
testGeneratedFileStream.cxx
testRST.cxx
testRange.cxx
......@@ -29,6 +30,7 @@ add_executable(testUVProcessChainHelper testUVProcessChainHelper.cxx)
set(testRST_ARGS ${CMAKE_CURRENT_SOURCE_DIR})
set(testUVProcessChain_ARGS $<TARGET_FILE:testUVProcessChainHelper>)
set(testUVStreambuf_ARGS $<TARGET_FILE:cmake>)
set(testCTestHardwareSpec_ARGS ${CMAKE_CURRENT_SOURCE_DIR})
if(WIN32)
list(APPEND CMakeLib_TESTS
......
#include <iostream>
#include <string>
#include <vector>
#include "cmCTestHardwareSpec.h"
struct ExpectedSpec
{
std::string Path;
bool ParseResult;
cmCTestHardwareSpec Expected;
};
static const std::vector<ExpectedSpec> expectedHardwareSpecs = {
/* clang-format off */
{"spec1.json", true, {{{
{"gpus", {
{"2", 4},
{"e", 1},
}},
{"threads", {
}},
}}}},
{"spec2.json", true, {{{
}}}},
{"spec3.json", false, {{{}}}},
{"spec4.json", false, {{{}}}},
{"spec5.json", false, {{{}}}},
{"spec6.json", false, {{{}}}},
{"spec7.json", false, {{{}}}},
{"spec8.json", false, {{{}}}},
{"spec9.json", false, {{{}}}},
{"spec10.json", false, {{{}}}},
{"spec11.json", false, {{{}}}},
{"spec12.json", false, {{{}}}},
{"spec13.json", false, {{{}}}},
{"spec14.json", true, {{{}}}},
{"spec15.json", true, {{{}}}},
{"spec16.json", true, {{{}}}},
{"spec17.json", false, {{{}}}},
{"spec18.json", false, {{{}}}},
{"noexist.json", false, {{{}}}},
/* clang-format on */
};
static bool testSpec(const std::string& path, bool expectedResult,
const cmCTestHardwareSpec& expected)
{
cmCTestHardwareSpec actual;
bool result = actual.ReadFromJSONFile(path);
if (result != expectedResult) {
std::cout << "ReadFromJSONFile(\"" << path << "\") returned " << result
<< ", should be " << expectedResult << std::endl;
return false;
}
if (result && actual != expected) {
std::cout << "ReadFromJSONFile(\"" << path
<< "\") did not give expected spec" << std::endl;
return false;
}
return true;
}
int testCTestHardwareSpec(int argc, char** const argv)
{
if (argc < 2) {
std::cout << "Invalid arguments.\n";
return -1;
}
int retval = 0;
for (auto const& spec : expectedHardwareSpecs) {
std::string path = argv[1];
path += "/testCTestHardwareSpec_data/";
path += spec.Path;
if (!testSpec(path, spec.ParseResult, spec.Expected)) {
retval = -1;
}
}
return retval;
}
{
"local": [
{
"gpus": [
{
"id": "2",
"slots": 4
},
{
"id": "e"
}
],
".reserved": [
{
"id": "a",
"slots": 3
}
],
"threads": [
]
}
]
}
{
"local": [
{
"gpus": [
{
"id": 4
}
]
}
]
}
{
"local": [
{
"gpus": [
{
"id": "4",
"slots": "giraffe"
}
]
}
]
}
{
"local": [
{
"gpus": [
{
"id": "A"
}
]
}
]
}
{
"local": [
{
"gpus": [
{
"id": "-"
}
]
}
]
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment