fileapi: Add protocol v1 support for stateful per-client queries

Add support for client-owned *stateful* query files.  These allow
clients to request a list of versions of each object kind and get only
the first-listed version that CMake recognizes.  Since clients own their
stateful query files they can mutate them over time.  As a client
installation is updated it may update the queries that it writes to
build trees to get newer object versions without paying the cost of
continuing to generate older versions.

Issue: #18398
This commit is contained in:
Brad King
2018-09-13 10:32:14 -04:00
parent 8fce59848b
commit 276fdf2993
9 changed files with 898 additions and 9 deletions
+122 -3
View File
@@ -31,8 +31,8 @@ It has the following subdirectories:
``query/``
Holds query files written by clients.
These may be `v1 Shared Stateless Query Files`_ or
`v1 Client Stateless Query Files`_.
These may be `v1 Shared Stateless Query Files`_,
`v1 Client Stateless Query Files`_, or `v1 Client Stateful Query Files`_.
``reply/``
Holds reply files written by CMake whenever it runs to generate a build
@@ -84,6 +84,87 @@ own means.
Files of this form are stateless queries owned by the client ``<client>``.
The owning client may remove them at any time.
v1 Client Stateful Query Files
------------------------------
Stateful query files allow clients to request a list of versions of
each of the `Object Kinds`_ and get only the most recent version
recognized by the CMake that runs.
Clients may create owned stateful queries by creating ``query.json``
files in client-specific query subdirectories. The form is::
<build>/.cmake/api/v1/query/client-<client>/query.json
where ``client-`` is literal, ``<client>`` is a string uniquely
identifying the client, and ``query.json`` is literal. Each client
must choose a unique ``<client>`` identifier via its own means.
``query.json`` files are stateful queries owned by the client ``<client>``.
The owning client may update or remove them at any time. When a
given client installation is updated it may then update the stateful
query it writes to build trees to request newer object versions.
This can be used to avoid asking CMake to generate multiple object
versions unnecessarily.
A ``query.json`` file must contain a JSON object:
.. code-block:: json
{
"requests": [
{ "kind": "<kind>" , "version": 1 },
{ "kind": "<kind>" , "version": { "major": 1, "minor": 2 } },
{ "kind": "<kind>" , "version": [2, 1] },
{ "kind": "<kind>" , "version": [2, { "major": 1, "minor": 2 }] },
{ "kind": "<kind>" , "version": 1, "client": {} },
{ "kind": "..." }
],
"client": {}
}
The members are:
``requests``
A JSON array containing zero or more requests. Each request is
a JSON object with members:
``kind``
Specifies one of the `Object Kinds`_ to be included in the reply.
``version``
Indicates the version(s) of the object kind that the client
understands. Versions have major and minor components following
semantic version conventions. The value must be
* a JSON integer specifying a (non-negative) major version number, or
* a JSON object containing ``major`` and (optionally) ``minor``
members specifying non-negative integer version components, or
* a JSON array whose elements are each one of the above.
``client``
Optional member reserved for use by the client. This value is
preserved in the reply written for the client in the
`v1 Reply Index File`_ but is otherwise ignored. Clients may use
this to pass custom information with a request through to its reply.
For each requested object kind CMake will choose the *first* version
that it recognizes for that kind among those listed in the request.
The response will use the selected *major* version with the highest
*minor* version known to the running CMake for that major version.
Therefore clients should list all supported major versions in
preferred order along with the minimal minor version required
for each major version.
``client``
Optional member reserved for use by the client. This value is
preserved in the reply written for the client in the
`v1 Reply Index File`_ but is otherwise ignored. Clients may use
this to pass custom information with a query through to its reply.
Other ``query.json`` top-level members are reserved for future use.
If present they are ignored for forward compatibility.
v1 Reply Index File
-------------------
@@ -135,7 +216,18 @@ The reply index file contains a JSON object:
"version": { "major": 1, "minor": 0 },
"jsonFile": "<file>" },
"<unknown>": { "error": "unknown query file" },
"...": {}
"...": {},
"query.json": {
"requests": [ {}, {}, {} ],
"responses": [
{ "kind": "<kind>",
"version": { "major": 1, "minor": 0 },
"jsonFile": "<file>" },
{ "error": "unknown query file" },
{ "...": {} }
],
"client": {}
}
}
}
}
@@ -211,6 +303,33 @@ The members are:
containing a string with an error message indicating that the
query file is unknown.
``query.json``
This member appears for clients using
`v1 Client Stateful Query Files`_.
If the ``query.json`` file failed to read or parse as a JSON object,
this member is a JSON object with a single ``error`` member
containing a string with an error message. Otherwise, this member
is a JSON object mirroring the content of the ``query.json`` file.
The members are:
``client``
A copy of the ``query.json`` file ``client`` member, if it exists.
``requests``
A copy of the ``query.json`` file ``requests`` member, if it exists.
``responses``
If the ``query.json`` file ``requests`` member is missing or invalid,
this member is a JSON object with a single ``error`` member
containing a string with an error message. Otherwise, this member
contains a JSON array with a response for each entry of the
``requests`` array, in the same order. Each response is
* a JSON object with a single ``error`` member containing a string
with an error message, or
* a `v1 Reply File Reference`_ to the corresponding reply file for
the requested object kind and selected version.
After reading the reply index file, clients may read the other
`v1 Reply Files`_ it references.
+298 -5
View File
@@ -24,6 +24,14 @@ cmFileAPI::cmFileAPI(cmake* cm)
this->APIv1 =
this->CMakeInstance->GetHomeOutputDirectory() + "/.cmake/api/v1";
Json::CharReaderBuilder rbuilder;
rbuilder["collectComments"] = false;
rbuilder["failIfExtra"] = true;
rbuilder["rejectDupKeys"] = false;
rbuilder["strictRoot"] = true;
this->JsonReader =
std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
Json::StreamWriterBuilder wbuilder;
wbuilder["indentation"] = "\t";
this->JsonWriter =
@@ -88,6 +96,46 @@ void cmFileAPI::RemoveOldReplyFiles()
}
}
bool cmFileAPI::ReadJsonFile(std::string const& file, Json::Value& value,
std::string& error)
{
std::vector<char> content;
cmsys::ifstream fin;
if (!cmSystemTools::FileIsDirectory(file)) {
fin.open(file.c_str(), std::ios::binary);
}
auto finEnd = fin.rdbuf()->pubseekoff(0, std::ios::end);
if (finEnd > 0) {
size_t finSize = finEnd;
try {
// Allocate a buffer to read the whole file.
content.resize(finSize);
// Now read the file from the beginning.
fin.seekg(0, std::ios::beg);
fin.read(content.data(), finSize);
} catch (...) {
fin.setstate(std::ios::failbit);
}
}
fin.close();
if (!fin) {
value = Json::Value();
error = "failed to read from file";
return false;
}
// Parse our buffer as json.
if (!this->JsonReader->parse(content.data(), content.data() + content.size(),
&value, &error)) {
value = Json::Value();
return false;
}
return true;
}
std::string cmFileAPI::WriteJsonFile(
Json::Value const& value, std::string const& prefix,
std::string (*computeSuffix)(std::string const&))
@@ -186,14 +234,38 @@ void cmFileAPI::ReadClient(std::string const& client)
std::vector<std::string> queries = this->LoadDir(clientDir);
// Read the queries and save for later.
Query& clientQuery = this->ClientQueries[client];
ClientQuery& clientQuery = this->ClientQueries[client];
for (std::string& query : queries) {
if (!this->ReadQuery(query, clientQuery.Known)) {
clientQuery.Unknown.push_back(std::move(query));
if (query == "query.json") {
clientQuery.HaveQueryJson = true;
this->ReadClientQuery(client, clientQuery.QueryJson);
} else if (!this->ReadQuery(query, clientQuery.DirQuery.Known)) {
clientQuery.DirQuery.Unknown.push_back(std::move(query));
}
}
}
void cmFileAPI::ReadClientQuery(std::string const& client, ClientQueryJson& q)
{
// Read the query.json file.
std::string queryFile = this->APIv1 + "/query/" + client + "/query.json";
Json::Value query;
if (!this->ReadJsonFile(queryFile, query, q.Error)) {
return;
}
if (!query.isObject()) {
q.Error = "query root is not an object";
return;
}
Json::Value const& clientValue = query["client"];
if (!clientValue.isNull()) {
q.ClientValue = clientValue;
}
q.RequestsValue = std::move(query["requests"]);
q.Requests = this->BuildClientRequests(q.RequestsValue);
}
Json::Value cmFileAPI::BuildReplyIndex()
{
Json::Value index(Json::objectValue);
@@ -205,8 +277,8 @@ Json::Value cmFileAPI::BuildReplyIndex()
Json::Value& reply = index["reply"] = this->BuildReply(this->TopQuery);
for (auto const& client : this->ClientQueries) {
std::string const& clientName = client.first;
Query const& clientQuery = client.second;
reply[clientName] = this->BuildReply(clientQuery);
ClientQuery const& clientQuery = client.second;
reply[clientName] = this->BuildClientReply(clientQuery);
}
// Move our index of generated objects into its field.
@@ -301,11 +373,232 @@ Json::Value cmFileAPI::BuildObject(Object const& object)
return value;
}
cmFileAPI::ClientRequests cmFileAPI::BuildClientRequests(
Json::Value const& requests)
{
ClientRequests result;
if (requests.isNull()) {
result.Error = "'requests' member missing";
return result;
}
if (!requests.isArray()) {
result.Error = "'requests' member is not an array";
return result;
}
result.reserve(requests.size());
for (Json::Value const& request : requests) {
result.emplace_back(this->BuildClientRequest(request));
}
return result;
}
cmFileAPI::ClientRequest cmFileAPI::BuildClientRequest(
Json::Value const& request)
{
ClientRequest r;
if (!request.isObject()) {
r.Error = "request is not an object";
return r;
}
Json::Value const& kind = request["kind"];
if (kind.isNull()) {
r.Error = "'kind' member missing";
return r;
}
if (!kind.isString()) {
r.Error = "'kind' member is not a string";
return r;
}
std::string const& kindName = kind.asString();
if (kindName == this->ObjectKindName(ObjectKind::InternalTest)) {
r.Kind = ObjectKind::InternalTest;
} else {
r.Error = "unknown request kind '" + kindName + "'";
return r;
}
Json::Value const& version = request["version"];
if (version.isNull()) {
r.Error = "'version' member missing";
return r;
}
std::vector<RequestVersion> versions;
if (!cmFileAPI::ReadRequestVersions(version, versions, r.Error)) {
return r;
}
switch (r.Kind) {
case ObjectKind::InternalTest:
this->BuildClientRequestInternalTest(r, versions);
break;
}
return r;
}
Json::Value cmFileAPI::BuildClientReply(ClientQuery const& q)
{
Json::Value reply = this->BuildReply(q.DirQuery);
if (!q.HaveQueryJson) {
return reply;
}
Json::Value& reply_query_json = reply["query.json"];
ClientQueryJson const& qj = q.QueryJson;
if (!qj.Error.empty()) {
reply_query_json = this->BuildReplyError(qj.Error);
return reply;
}
if (!qj.ClientValue.isNull()) {
reply_query_json["client"] = qj.ClientValue;
}
if (!qj.RequestsValue.isNull()) {
reply_query_json["requests"] = qj.RequestsValue;
}
reply_query_json["responses"] = this->BuildClientReplyResponses(qj.Requests);
return reply;
}
Json::Value cmFileAPI::BuildClientReplyResponses(
ClientRequests const& requests)
{
Json::Value responses;
if (!requests.Error.empty()) {
responses = this->BuildReplyError(requests.Error);
return responses;
}
responses = Json::arrayValue;
for (ClientRequest const& request : requests) {
responses.append(this->BuildClientReplyResponse(request));
}
return responses;
}
Json::Value cmFileAPI::BuildClientReplyResponse(ClientRequest const& request)
{
Json::Value response;
if (!request.Error.empty()) {
response = this->BuildReplyError(request.Error);
return response;
}
response = this->AddReplyIndexObject(request);
return response;
}
bool cmFileAPI::ReadRequestVersions(Json::Value const& version,
std::vector<RequestVersion>& versions,
std::string& error)
{
if (version.isArray()) {
for (Json::Value const& v : version) {
if (!ReadRequestVersion(v, /*inArray=*/true, versions, error)) {
return false;
}
}
} else {
if (!ReadRequestVersion(version, /*inArray=*/false, versions, error)) {
return false;
}
}
return true;
}
bool cmFileAPI::ReadRequestVersion(Json::Value const& version, bool inArray,
std::vector<RequestVersion>& result,
std::string& error)
{
if (version.isUInt()) {
RequestVersion v;
v.Major = version.asUInt();
result.push_back(v);
return true;
}
if (!version.isObject()) {
if (inArray) {
error = "'version' array entry is not a non-negative integer or object";
} else {
error =
"'version' member is not a non-negative integer, object, or array";
}
return false;
}
Json::Value const& major = version["major"];
if (major.isNull()) {
error = "'version' object 'major' member missing";
return false;
}
if (!major.isUInt()) {
error = "'version' object 'major' member is not a non-negative integer";
return false;
}
RequestVersion v;
v.Major = major.asUInt();
Json::Value const& minor = version["minor"];
if (minor.isUInt()) {
v.Minor = minor.asUInt();
} else if (!minor.isNull()) {
error = "'version' object 'minor' member is not a non-negative integer";
return false;
}
result.push_back(v);
return true;
}
std::string cmFileAPI::NoSupportedVersion(
std::vector<RequestVersion> const& versions)
{
std::ostringstream msg;
msg << "no supported version specified";
if (!versions.empty()) {
msg << " among:";
for (RequestVersion const& v : versions) {
msg << " " << v.Major << "." << v.Minor;
}
}
return msg.str();
}
// The "__test" object kind is for internal testing of CMake.
static unsigned int const InternalTestV1Minor = 3;
static unsigned int const InternalTestV2Minor = 0;
void cmFileAPI::BuildClientRequestInternalTest(
ClientRequest& r, std::vector<RequestVersion> const& versions)
{
// Select a known version from those requested.
for (RequestVersion const& v : versions) {
if ((v.Major == 1 && v.Minor <= InternalTestV1Minor) || //
(v.Major == 2 && v.Minor <= InternalTestV2Minor)) {
r.Version = v.Major;
break;
}
}
if (!r.Version) {
r.Error = NoSupportedVersion(versions);
}
}
Json::Value cmFileAPI::BuildInternalTest(Object const& object)
{
Json::Value test = Json::objectValue;
+72 -1
View File
@@ -5,6 +5,7 @@
#include "cmConfigure.h" // IWYU pragma: keep
#include "cm_jsoncpp_reader.h"
#include "cm_jsoncpp_value.h"
#include "cm_jsoncpp_writer.h"
@@ -71,6 +72,49 @@ private:
std::vector<std::string> Unknown;
};
/** Represent one request in a client 'query.json'. */
struct ClientRequest : public Object
{
/** Empty if request is valid, else the error string. */
std::string Error;
};
/** Represent the "requests" in a client 'query.json'. */
struct ClientRequests : public std::vector<ClientRequest>
{
/** Empty if requests field is valid, else the error string. */
std::string Error;
};
/** Represent the content of a client query.json file. */
struct ClientQueryJson
{
/** The error string if parsing failed, else empty. */
std::string Error;
/** The 'query.json' object "client" member if it exists, else null. */
Json::Value ClientValue;
/** The 'query.json' object "requests" member if it exists, else null. */
Json::Value RequestsValue;
/** Requests extracted from 'query.json'. */
ClientRequests Requests;
};
/** Represent content of a client query directory. */
struct ClientQuery
{
/** The content of the client query directory except 'query.json'. */
Query DirQuery;
/** True if 'query.json' exists. */
bool HaveQueryJson = false;
/** The 'query.json' content. */
ClientQueryJson QueryJson;
};
/** Whether the top-level query directory exists at all. */
bool QueryExists = false;
@@ -78,14 +122,18 @@ private:
Query TopQuery;
/** The content of each "client-$client" query directory. */
std::map<std::string, Query> ClientQueries;
std::map<std::string, ClientQuery> ClientQueries;
/** Reply index object generated for object kind/version.
This populates the "objects" field of the reply index. */
std::map<Object, Json::Value> ReplyIndexObjects;
std::unique_ptr<Json::CharReader> JsonReader;
std::unique_ptr<Json::StreamWriter> JsonWriter;
bool ReadJsonFile(std::string const& file, Json::Value& value,
std::string& error);
std::string WriteJsonFile(
Json::Value const& value, std::string const& prefix,
std::string (*computeSuffix)(std::string const&) = ComputeSuffixHash);
@@ -95,6 +143,7 @@ private:
static bool ReadQuery(std::string const& query,
std::vector<Object>& objects);
void ReadClient(std::string const& client);
void ReadClientQuery(std::string const& client, ClientQueryJson& q);
Json::Value BuildReplyIndex();
Json::Value BuildCMake();
@@ -107,6 +156,28 @@ private:
Json::Value BuildObject(Object const& object);
ClientRequests BuildClientRequests(Json::Value const& requests);
ClientRequest BuildClientRequest(Json::Value const& request);
Json::Value BuildClientReply(ClientQuery const& q);
Json::Value BuildClientReplyResponses(ClientRequests const& requests);
Json::Value BuildClientReplyResponse(ClientRequest const& request);
struct RequestVersion
{
unsigned int Major = 0;
unsigned int Minor = 0;
};
static bool ReadRequestVersions(Json::Value const& version,
std::vector<RequestVersion>& versions,
std::string& error);
static bool ReadRequestVersion(Json::Value const& version, bool inArray,
std::vector<RequestVersion>& result,
std::string& error);
static std::string NoSupportedVersion(
std::vector<RequestVersion> const& versions);
void BuildClientRequestInternalTest(
ClientRequest& r, std::vector<RequestVersion> const& versions);
Json::Value BuildInternalTest(Object const& object);
};
@@ -0,0 +1,68 @@
set(expect
query
query/client-client-member
query/client-client-member/query.json
query/client-empty-array
query/client-empty-array/query.json
query/client-empty-object
query/client-empty-object/query.json
query/client-json-bad-root
query/client-json-bad-root/query.json
query/client-json-empty
query/client-json-empty/query.json
query/client-json-extra
query/client-json-extra/query.json
query/client-not-file
query/client-not-file/query.json
query/client-request-array-negative-major-version
query/client-request-array-negative-major-version/query.json
query/client-request-array-negative-minor-version
query/client-request-array-negative-minor-version/query.json
query/client-request-array-negative-version
query/client-request-array-negative-version/query.json
query/client-request-array-no-major-version
query/client-request-array-no-major-version/query.json
query/client-request-array-no-supported-version
query/client-request-array-no-supported-version-among
query/client-request-array-no-supported-version-among/query.json
query/client-request-array-no-supported-version/query.json
query/client-request-array-version-1
query/client-request-array-version-1-1
query/client-request-array-version-1-1/query.json
query/client-request-array-version-1/query.json
query/client-request-array-version-2
query/client-request-array-version-2/query.json
query/client-request-negative-major-version
query/client-request-negative-major-version/query.json
query/client-request-negative-minor-version
query/client-request-negative-minor-version/query.json
query/client-request-negative-version
query/client-request-negative-version/query.json
query/client-request-no-major-version
query/client-request-no-major-version/query.json
query/client-request-no-version
query/client-request-no-version/query.json
query/client-request-version-1
query/client-request-version-1-1
query/client-request-version-1-1/query.json
query/client-request-version-1/query.json
query/client-request-version-2
query/client-request-version-2/query.json
query/client-requests-bad
query/client-requests-bad/query.json
query/client-requests-empty
query/client-requests-empty/query.json
query/client-requests-not-kinded
query/client-requests-not-kinded/query.json
query/client-requests-not-objects
query/client-requests-not-objects/query.json
query/client-requests-unknown
query/client-requests-unknown/query.json
reply
reply/__test-v1-[0-9a-f]+.json
reply/__test-v2-[0-9a-f]+.json
reply/index-[0-9.T-]+.json
)
check_api("^${expect}$")
check_python(ClientStateful)
@@ -0,0 +1,258 @@
from check_index import *
def check_reply(q):
assert is_dict(q)
assert sorted(q.keys()) == [
"client-client-member",
"client-empty-array",
"client-empty-object",
"client-json-bad-root",
"client-json-empty",
"client-json-extra",
"client-not-file",
"client-request-array-negative-major-version",
"client-request-array-negative-minor-version",
"client-request-array-negative-version",
"client-request-array-no-major-version",
"client-request-array-no-supported-version",
"client-request-array-no-supported-version-among",
"client-request-array-version-1",
"client-request-array-version-1-1",
"client-request-array-version-2",
"client-request-negative-major-version",
"client-request-negative-minor-version",
"client-request-negative-version",
"client-request-no-major-version",
"client-request-no-version",
"client-request-version-1",
"client-request-version-1-1",
"client-request-version-2",
"client-requests-bad",
"client-requests-empty",
"client-requests-not-kinded",
"client-requests-not-objects",
"client-requests-unknown",
]
expected = [
(check_query_client_member, "client-client-member"),
(check_query_empty_array, "client-empty-array"),
(check_query_empty_object, "client-empty-object"),
(check_query_json_bad_root, "client-json-bad-root"),
(check_query_json_empty, "client-json-empty"),
(check_query_json_extra, "client-json-extra"),
(check_query_not_file, "client-not-file"),
(check_query_requests_bad, "client-requests-bad"),
(check_query_requests_empty, "client-requests-empty"),
(check_query_requests_not_kinded, "client-requests-not-kinded"),
(check_query_requests_not_objects, "client-requests-not-objects"),
(check_query_requests_unknown, "client-requests-unknown"),
]
for (f, k) in expected:
assert is_dict(q[k])
assert sorted(q[k].keys()) == ["query.json"]
f(q[k]["query.json"])
expected = [
(check_query_response_array_negative_major_version, "client-request-array-negative-major-version"),
(check_query_response_array_negative_minor_version, "client-request-array-negative-minor-version"),
(check_query_response_array_negative_version, "client-request-array-negative-version"),
(check_query_response_array_no_major_version, "client-request-array-no-major-version"),
(check_query_response_array_no_supported_version, "client-request-array-no-supported-version"),
(check_query_response_array_no_supported_version_among, "client-request-array-no-supported-version-among"),
(check_query_response_array_version_1, "client-request-array-version-1"),
(check_query_response_array_version_1_1, "client-request-array-version-1-1"),
(check_query_response_array_version_2, "client-request-array-version-2"),
(check_query_response_negative_major_version, "client-request-negative-major-version"),
(check_query_response_negative_minor_version, "client-request-negative-minor-version"),
(check_query_response_negative_version, "client-request-negative-version"),
(check_query_response_no_major_version, "client-request-no-major-version"),
(check_query_response_no_version, "client-request-no-version"),
(check_query_response_version_1, "client-request-version-1"),
(check_query_response_version_1_1, "client-request-version-1-1"),
(check_query_response_version_2, "client-request-version-2"),
]
for (f, k) in expected:
assert is_dict(q[k])
assert sorted(q[k].keys()) == ["query.json"]
assert is_dict(q[k]["query.json"])
assert sorted(q[k]["query.json"].keys()) == ["requests", "responses"]
r = q[k]["query.json"]["requests"]
assert is_list(r)
assert len(r) == 1
assert is_dict(r[0])
assert r[0]["kind"] == "__test"
r = q[k]["query.json"]["responses"]
assert is_list(r)
assert len(r) == 1
assert is_dict(r[0])
f(r[0])
def check_query_client_member(q):
assert is_dict(q)
assert sorted(q.keys()) == ["client", "responses"]
assert is_dict(q["client"])
assert sorted(q["client"].keys()) == []
check_error(q["responses"], "'requests' member missing")
def check_query_empty_array(q):
check_error(q, "query root is not an object")
def check_query_empty_object(q):
assert is_dict(q)
assert sorted(q.keys()) == ["responses"]
check_error(q["responses"], "'requests' member missing")
def check_query_json_bad_root(q):
check_error_re(q, "A valid JSON document must be either an array or an object value")
def check_query_json_empty(q):
check_error_re(q, "value, object or array expected")
def check_query_json_extra(q):
check_error_re(q, "Extra non-whitespace after JSON value")
def check_query_not_file(q):
check_error_re(q, "failed to read from file")
def check_query_requests_bad(q):
assert is_dict(q)
assert sorted(q.keys()) == ["requests", "responses"]
r = q["requests"]
assert is_dict(r)
assert sorted(r.keys()) == []
check_error(q["responses"], "'requests' member is not an array")
def check_query_requests_empty(q):
assert is_dict(q)
assert sorted(q.keys()) == ["requests", "responses"]
r = q["requests"]
assert is_list(r)
assert len(r) == 0
r = q["responses"]
assert is_list(r)
assert len(r) == 0
def check_query_requests_not_kinded(q):
assert is_dict(q)
assert sorted(q.keys()) == ["requests", "responses"]
r = q["requests"]
assert is_list(r)
assert len(r) == 4
assert is_dict(r[0])
assert sorted(r[0].keys()) == []
assert is_dict(r[1])
assert sorted(r[1].keys()) == ["kind"]
assert is_dict(r[1]["kind"])
assert is_dict(r[2])
assert sorted(r[2].keys()) == ["kind"]
assert is_list(r[2]["kind"])
assert is_dict(r[3])
assert sorted(r[3].keys()) == ["kind"]
assert is_int(r[3]["kind"])
r = q["responses"]
assert is_list(r)
assert len(r) == 4
check_error(r[0], "'kind' member missing")
check_error(r[1], "'kind' member is not a string")
check_error(r[2], "'kind' member is not a string")
check_error(r[3], "'kind' member is not a string")
def check_query_requests_not_objects(q):
assert is_dict(q)
assert sorted(q.keys()) == ["requests", "responses"]
r = q["requests"]
assert is_list(r)
assert len(r) == 3
assert is_int(r[0])
assert is_string(r[1])
assert is_list(r[2])
r = q["responses"]
assert is_list(r)
assert len(r) == 3
check_error(r[0], "request is not an object")
check_error(r[1], "request is not an object")
check_error(r[2], "request is not an object")
def check_query_requests_unknown(q):
assert is_dict(q)
assert sorted(q.keys()) == ["requests", "responses"]
r = q["requests"]
assert is_list(r)
assert len(r) == 3
assert is_dict(r[0])
assert sorted(r[0].keys()) == ["kind"]
assert r[0]["kind"] == "unknownC"
assert is_dict(r[1])
assert sorted(r[1].keys()) == ["kind"]
assert r[1]["kind"] == "unknownB"
assert is_dict(r[2])
assert sorted(r[2].keys()) == ["kind"]
assert r[2]["kind"] == "unknownA"
r = q["responses"]
assert is_list(r)
assert len(r) == 3
check_error(r[0], "unknown request kind 'unknownC'")
check_error(r[1], "unknown request kind 'unknownB'")
check_error(r[2], "unknown request kind 'unknownA'")
def check_query_response_array_negative_major_version(r):
check_error(r, "'version' object 'major' member is not a non-negative integer")
def check_query_response_array_negative_minor_version(r):
check_error(r, "'version' object 'minor' member is not a non-negative integer")
def check_query_response_array_negative_version(r):
check_error(r, "'version' array entry is not a non-negative integer or object")
def check_query_response_array_no_major_version(r):
check_error(r, "'version' object 'major' member missing")
def check_query_response_array_no_supported_version(r):
check_error(r, "no supported version specified")
def check_query_response_array_no_supported_version_among(r):
check_error(r, "no supported version specified among: 4.0 3.0")
def check_query_response_array_version_1(r):
check_index__test(r, 1, 3)
def check_query_response_array_version_1_1(r):
check_index__test(r, 1, 3) # always uses latest minor version
def check_query_response_array_version_2(r):
check_index__test(r, 2, 0)
def check_query_response_negative_major_version(r):
check_error(r, "'version' object 'major' member is not a non-negative integer")
def check_query_response_negative_minor_version(r):
check_error(r, "'version' object 'minor' member is not a non-negative integer")
def check_query_response_negative_version(r):
check_error(r, "'version' member is not a non-negative integer, object, or array")
def check_query_response_no_major_version(r):
check_error(r, "'version' object 'major' member missing")
def check_query_response_no_version(r):
check_error(r, "'version' member missing")
def check_query_response_version_1(r):
check_index__test(r, 1, 3)
def check_query_response_version_1_1(r):
check_index__test(r, 1, 3) # always uses latest minor version
def check_query_response_version_2(r):
check_index__test(r, 2, 0)
def check_objects(o):
assert is_list(o)
assert len(o) == 2
check_index__test(o[0], 1, 3)
check_index__test(o[1], 2, 0)
assert is_dict(index)
assert sorted(index.keys()) == ["cmake", "objects", "reply"]
check_cmake(index["cmake"])
check_reply(index["reply"])
check_objects(index["objects"])
@@ -0,0 +1,73 @@
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-client-member/query.json" [[{ "client": {} }]])
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-empty-array/query.json" "[]")
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-empty-object/query.json" "{}")
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-json-bad-root/query.json" [["invalid root"]])
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-json-empty/query.json" "")
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-json-extra/query.json" "{}x")
file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-not-file/query.json")
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-requests-bad/query.json" [[{ "requests": {} }]])
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-requests-empty/query.json" [[{ "requests": [] }]])
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-requests-not-objects/query.json" [[
{ "requests": [ 0, "", [] ] }
]])
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-requests-not-kinded/query.json" [[
{ "requests": [ {}, { "kind": {} }, { "kind": [] }, { "kind": 0 } ] }
]])
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-requests-unknown/query.json" [[
{ "requests": [ { "kind": "unknownC" }, { "kind": "unknownB" }, { "kind": "unknownA" } ] }
]])
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-request-no-version/query.json" [[
{ "requests": [ { "kind": "__test" } ] }
]])
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-request-negative-version/query.json" [[
{ "requests": [ { "kind": "__test", "version" : -1 } ] }
]])
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-request-no-major-version/query.json" [[
{ "requests": [ { "kind": "__test", "version" : {} } ] }
]])
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-request-negative-major-version/query.json" [[
{ "requests": [ { "kind": "__test", "version" : { "major": -1 } } ] }
]])
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-request-negative-minor-version/query.json" [[
{ "requests": [ { "kind": "__test", "version" : { "major": 0, "minor": -1 } } ] }
]])
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-request-array-negative-version/query.json" [[
{ "requests": [ { "kind": "__test", "version" : [ 1, -1 ] } ] }
]])
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-request-array-no-major-version/query.json" [[
{ "requests": [ { "kind": "__test", "version" : [ 1, {} ] } ] }
]])
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-request-array-negative-major-version/query.json" [[
{ "requests": [ { "kind": "__test", "version" : [ 1, { "major": -1 } ] } ] }
]])
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-request-array-negative-minor-version/query.json" [[
{ "requests": [ { "kind": "__test", "version" : [ 1, { "major": 0, "minor": -1 } ] } ] }
]])
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-request-array-no-supported-version/query.json" [[
{ "requests": [ { "kind": "__test", "version" : [] } ] }
]])
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-request-array-no-supported-version-among/query.json" [[
{ "requests": [ { "kind": "__test", "version" : [4, 3] } ] }
]])
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-request-version-1/query.json" [[
{ "requests": [ { "kind": "__test", "version" : 1 } ] }
]])
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-request-version-1-1/query.json" [[
{ "requests": [ { "kind": "__test", "version" : { "major": 1, "minor": 1 } } ] }
]])
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-request-version-2/query.json" [[
{ "requests": [ { "kind": "__test", "version" : { "major": 2 } } ] }
]])
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-request-array-version-1/query.json" [[
{ "requests": [ { "kind": "__test", "version" : [3, 1] } ] }
]])
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-request-array-version-1-1/query.json" [[
{ "requests": [ { "kind": "__test", "version" : [3, { "major": 1, "minor": 1 }, 2 ] } ] }
]])
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-request-array-version-2/query.json" [[
{ "requests": [ { "kind": "__test", "version" : [3, { "major": 2 } ] } ] }
]])
file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/reply/object-to-be-deleted.json" "")
@@ -42,3 +42,4 @@ run_cmake(SharedStateless)
run_cmake(ClientStateless)
run_cmake(MixedStateless)
run_cmake(DuplicateStateless)
run_cmake(ClientStateful)
+6
View File
@@ -81,6 +81,12 @@ def check_error(value, error):
assert is_string(value["error"])
assert value["error"] == error
def check_error_re(value, error):
assert is_dict(value)
assert sorted(value.keys()) == ["error"]
assert is_string(value["error"])
assert re.search(error, value["error"])
reply_index = sys.argv[1]
reply_dir = os.path.dirname(reply_index)