1 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2    file Copyright.txt or https://cmake.org/licensing for details.  */
3 #include "cmCTestResourceSpec.h"
4 
5 #include <functional>
6 #include <map>
7 #include <string>
8 #include <utility>
9 #include <vector>
10 
11 #include <cmext/string_view>
12 
13 #include <cm3p/json/reader.h>
14 #include <cm3p/json/value.h>
15 
16 #include "cmsys/FStream.hxx"
17 #include "cmsys/RegularExpression.hxx"
18 
19 #include "cmJSONHelpers.h"
20 
21 namespace {
22 const cmsys::RegularExpression IdentifierRegex{ "^[a-z_][a-z0-9_]*$" };
23 const cmsys::RegularExpression IdRegex{ "^[a-z0-9_]+$" };
24 
25 struct Version
26 {
27   int Major = 1;
28   int Minor = 0;
29 };
30 
31 struct TopVersion
32 {
33   struct Version Version;
34 };
35 
36 auto const VersionFieldHelper =
37   cmJSONIntHelper<cmCTestResourceSpec::ReadFileResult>(
38     cmCTestResourceSpec::ReadFileResult::READ_OK,
39     cmCTestResourceSpec::ReadFileResult::INVALID_VERSION);
40 
41 auto const VersionHelper =
42   cmJSONRequiredHelper<Version, cmCTestResourceSpec::ReadFileResult>(
43     cmCTestResourceSpec::ReadFileResult::NO_VERSION,
44     cmJSONObjectHelper<Version, cmCTestResourceSpec::ReadFileResult>(
45       cmCTestResourceSpec::ReadFileResult::READ_OK,
46       cmCTestResourceSpec::ReadFileResult::INVALID_VERSION)
47       .Bind("major"_s, &Version::Major, VersionFieldHelper)
48       .Bind("minor"_s, &Version::Minor, VersionFieldHelper));
49 
50 auto const RootVersionHelper =
51   cmJSONObjectHelper<TopVersion, cmCTestResourceSpec::ReadFileResult>(
52     cmCTestResourceSpec::ReadFileResult::READ_OK,
53     cmCTestResourceSpec::ReadFileResult::INVALID_ROOT)
54     .Bind("version"_s, &TopVersion::Version, VersionHelper, false);
55 
ResourceIdHelper(std::string & out,const Json::Value * value)56 cmCTestResourceSpec::ReadFileResult ResourceIdHelper(std::string& out,
57                                                      const Json::Value* value)
58 {
59   auto result = cmJSONStringHelper(
60     cmCTestResourceSpec::ReadFileResult::READ_OK,
61     cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE)(out, value);
62   if (result != cmCTestResourceSpec::ReadFileResult::READ_OK) {
63     return result;
64   }
65   cmsys::RegularExpressionMatch match;
66   if (!IdRegex.find(out.c_str(), match)) {
67     return cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE;
68   }
69   return cmCTestResourceSpec::ReadFileResult::READ_OK;
70 }
71 
72 auto const ResourceHelper =
73   cmJSONObjectHelper<cmCTestResourceSpec::Resource,
74                      cmCTestResourceSpec::ReadFileResult>(
75     cmCTestResourceSpec::ReadFileResult::READ_OK,
76     cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE)
77     .Bind("id"_s, &cmCTestResourceSpec::Resource::Id, ResourceIdHelper)
78     .Bind("slots"_s, &cmCTestResourceSpec::Resource::Capacity,
79           cmJSONUIntHelper(
80             cmCTestResourceSpec::ReadFileResult::READ_OK,
81             cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE, 1),
82           false);
83 
84 auto const ResourceListHelper =
85   cmJSONVectorHelper<cmCTestResourceSpec::Resource,
86                      cmCTestResourceSpec::ReadFileResult>(
87     cmCTestResourceSpec::ReadFileResult::READ_OK,
88     cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE_TYPE,
89     ResourceHelper);
90 
91 auto const ResourceMapHelper =
92   cmJSONMapFilterHelper<std::vector<cmCTestResourceSpec::Resource>,
93                         cmCTestResourceSpec::ReadFileResult>(
94     cmCTestResourceSpec::ReadFileResult::READ_OK,
95     cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC,
__anon728adff60202(const std::string& key) 96     ResourceListHelper, [](const std::string& key) -> bool {
97       cmsys::RegularExpressionMatch match;
98       return IdentifierRegex.find(key.c_str(), match);
99     });
100 
101 auto const SocketSetHelper = cmJSONVectorHelper<
102   std::map<std::string, std::vector<cmCTestResourceSpec::Resource>>>(
103   cmCTestResourceSpec::ReadFileResult::READ_OK,
104   cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC, ResourceMapHelper);
105 
SocketHelper(cmCTestResourceSpec::Socket & out,const Json::Value * value)106 cmCTestResourceSpec::ReadFileResult SocketHelper(
107   cmCTestResourceSpec::Socket& out, const Json::Value* value)
108 {
109   std::vector<
110     std::map<std::string, std::vector<cmCTestResourceSpec::Resource>>>
111     sockets;
112   cmCTestResourceSpec::ReadFileResult result = SocketSetHelper(sockets, value);
113   if (result != cmCTestResourceSpec::ReadFileResult::READ_OK) {
114     return result;
115   }
116   if (sockets.size() > 1) {
117     return cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC;
118   }
119   if (sockets.empty()) {
120     out.Resources.clear();
121   } else {
122     out.Resources = std::move(sockets[0]);
123   }
124   return cmCTestResourceSpec::ReadFileResult::READ_OK;
125 }
126 
127 auto const LocalRequiredHelper =
128   cmJSONRequiredHelper<cmCTestResourceSpec::Socket,
129                        cmCTestResourceSpec::ReadFileResult>(
130     cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC, SocketHelper);
131 
132 auto const RootHelper =
133   cmJSONObjectHelper<cmCTestResourceSpec, cmCTestResourceSpec::ReadFileResult>(
134     cmCTestResourceSpec::ReadFileResult::READ_OK,
135     cmCTestResourceSpec::ReadFileResult::INVALID_ROOT)
136     .Bind("local", &cmCTestResourceSpec::LocalSocket, LocalRequiredHelper,
137           false);
138 }
139 
ReadFromJSONFile(const std::string & filename)140 cmCTestResourceSpec::ReadFileResult cmCTestResourceSpec::ReadFromJSONFile(
141   const std::string& filename)
142 {
143   cmsys::ifstream fin(filename.c_str());
144   if (!fin) {
145     return ReadFileResult::FILE_NOT_FOUND;
146   }
147 
148   Json::Value root;
149   Json::CharReaderBuilder builder;
150   if (!Json::parseFromStream(builder, fin, &root, nullptr)) {
151     return ReadFileResult::JSON_PARSE_ERROR;
152   }
153 
154   TopVersion version;
155   ReadFileResult result;
156   if ((result = RootVersionHelper(version, &root)) !=
157       ReadFileResult::READ_OK) {
158     return result;
159   }
160   if (version.Version.Major != 1 || version.Version.Minor != 0) {
161     return ReadFileResult::UNSUPPORTED_VERSION;
162   }
163 
164   return RootHelper(*this, &root);
165 }
166 
ResultToString(ReadFileResult result)167 const char* cmCTestResourceSpec::ResultToString(ReadFileResult result)
168 {
169   switch (result) {
170     case ReadFileResult::READ_OK:
171       return "OK";
172 
173     case ReadFileResult::FILE_NOT_FOUND:
174       return "File not found";
175 
176     case ReadFileResult::JSON_PARSE_ERROR:
177       return "JSON parse error";
178 
179     case ReadFileResult::INVALID_ROOT:
180       return "Invalid root object";
181 
182     case ReadFileResult::NO_VERSION:
183       return "No version specified";
184 
185     case ReadFileResult::INVALID_VERSION:
186       return "Invalid version object";
187 
188     case ReadFileResult::UNSUPPORTED_VERSION:
189       return "Unsupported version";
190 
191     case ReadFileResult::INVALID_SOCKET_SPEC:
192       return "Invalid socket object";
193 
194     case ReadFileResult::INVALID_RESOURCE_TYPE:
195       return "Invalid resource type object";
196 
197     case ReadFileResult::INVALID_RESOURCE:
198       return "Invalid resource object";
199 
200     default:
201       return "Unknown";
202   }
203 }
204 
operator ==(const cmCTestResourceSpec & other) const205 bool cmCTestResourceSpec::operator==(const cmCTestResourceSpec& other) const
206 {
207   return this->LocalSocket == other.LocalSocket;
208 }
209 
operator !=(const cmCTestResourceSpec & other) const210 bool cmCTestResourceSpec::operator!=(const cmCTestResourceSpec& other) const
211 {
212   return !(*this == other);
213 }
214 
operator ==(const cmCTestResourceSpec::Socket & other) const215 bool cmCTestResourceSpec::Socket::operator==(
216   const cmCTestResourceSpec::Socket& other) const
217 {
218   return this->Resources == other.Resources;
219 }
220 
operator !=(const cmCTestResourceSpec::Socket & other) const221 bool cmCTestResourceSpec::Socket::operator!=(
222   const cmCTestResourceSpec::Socket& other) const
223 {
224   return !(*this == other);
225 }
226 
operator ==(const cmCTestResourceSpec::Resource & other) const227 bool cmCTestResourceSpec::Resource::operator==(
228   const cmCTestResourceSpec::Resource& other) const
229 {
230   return this->Id == other.Id && this->Capacity == other.Capacity;
231 }
232 
operator !=(const cmCTestResourceSpec::Resource & other) const233 bool cmCTestResourceSpec::Resource::operator!=(
234   const cmCTestResourceSpec::Resource& other) const
235 {
236   return !(*this == other);
237 }
238