зеркало из https://github.com/microsoft/CCF.git
Complex lua literals in proposal generator (#1694)
This commit is contained in:
Родитель
a13b33d442
Коммит
aa07efb755
|
@ -26,17 +26,41 @@ def dump_to_file(output_path: str, obj: dict, dump_args: dict):
|
||||||
json.dump(obj, f, **dump_args)
|
json.dump(obj, f, **dump_args)
|
||||||
|
|
||||||
|
|
||||||
def list_as_lua_literal(l):
|
def as_lua_literal(arg):
|
||||||
return str(l).translate(str.maketrans("[]", "{}"))
|
if isinstance(arg, str):
|
||||||
|
# This long string swallows any initial newline. This means if we
|
||||||
|
# had an actual newline, it will be lost. To work around this, we
|
||||||
|
# insert a newline to every string. If there was originally a
|
||||||
|
# newline at the start, its now the second character, and is kept.
|
||||||
|
return f"[====[\n{arg}]====]"
|
||||||
|
elif isinstance(arg, bool):
|
||||||
|
return str(arg).lower()
|
||||||
|
elif isinstance(arg, collections.abc.Sequence):
|
||||||
|
return f"{{ {', '.join(as_lua_literal(e) for e in arg)} }}"
|
||||||
|
elif isinstance(arg, collections.abc.Mapping):
|
||||||
|
inner = ", ".join(
|
||||||
|
f"[ {as_lua_literal(k)} ] = {as_lua_literal(v)}" for k, v in arg.items()
|
||||||
|
)
|
||||||
|
return f"{{ {inner} }}"
|
||||||
|
else:
|
||||||
|
return str(arg)
|
||||||
|
|
||||||
|
|
||||||
LUA_FUNCTION_EQUAL_ARRAYS = """function equal_arrays(a, b)
|
LUA_FUNCTION_EQUAL_TABLES = """function equal_tables(a, b)
|
||||||
if #a ~= #b then
|
if #a ~= #b then
|
||||||
return false
|
return false
|
||||||
else
|
else
|
||||||
for k, v in ipairs(a) do
|
for k, v in pairs(a) do
|
||||||
if b[k] ~= v then
|
if type(v) ~= type(b[k]) then
|
||||||
return false
|
return false
|
||||||
|
elseif type(v) == "table" then
|
||||||
|
if not equal_tables(v, b[k]) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if v ~= b[k] then
|
||||||
|
return false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return true
|
return true
|
||||||
|
@ -84,48 +108,38 @@ def add_arg_construction(
|
||||||
arg: Union[str, collections.abc.Sequence, collections.abc.Mapping],
|
arg: Union[str, collections.abc.Sequence, collections.abc.Mapping],
|
||||||
arg_name: str = "args",
|
arg_name: str = "args",
|
||||||
):
|
):
|
||||||
if isinstance(arg, str):
|
lines.append(f"{arg_name} = {as_lua_literal(arg)}")
|
||||||
lines.append(f"{arg_name} = [====[{arg}]====]")
|
|
||||||
elif isinstance(arg, collections.abc.Sequence):
|
|
||||||
lines.append(f"{arg_name} = {list_as_lua_literal(arg)}")
|
|
||||||
elif isinstance(arg, collections.abc.Mapping):
|
|
||||||
lines.append(f"{arg_name} = {{}}")
|
|
||||||
for k, v in args.items():
|
|
||||||
add_arg_construction(lines, v, arg_name=f"{arg_name}.{k}")
|
|
||||||
else:
|
|
||||||
lines.append(f"{arg_name} = {arg}")
|
|
||||||
|
|
||||||
|
|
||||||
def add_arg_checks(
|
def add_arg_checks(
|
||||||
lines: list,
|
lines: list,
|
||||||
arg: Union[str, collections.abc.Sequence, collections.abc.Mapping],
|
arg: Union[str, collections.abc.Sequence, collections.abc.Mapping],
|
||||||
arg_name: str = "args",
|
arg_name: str = "args",
|
||||||
added_equal_arrays_fn: bool = False,
|
added_equal_tables_fn: bool = False,
|
||||||
):
|
):
|
||||||
lines.append(f"if {arg_name} == nil then return false end")
|
lines.append(f"if {arg_name} == nil then return false end")
|
||||||
if isinstance(arg, str):
|
if isinstance(arg, str):
|
||||||
lines.append(f"if not {arg_name} == [====[{arg}]====] then return false end")
|
|
||||||
elif isinstance(arg, collections.abc.Sequence):
|
|
||||||
if not added_equal_arrays_fn:
|
|
||||||
lines.extend(
|
|
||||||
line.strip() for line in LUA_FUNCTION_EQUAL_ARRAYS.splitlines()
|
|
||||||
)
|
|
||||||
added_equal_arrays_fn = True
|
|
||||||
expected_name = arg_name.replace(".", "_")
|
|
||||||
lines.append(f"{expected_name} = {list_as_lua_literal(arg)}")
|
|
||||||
lines.append(
|
lines.append(
|
||||||
f"if not equal_arrays({arg_name}, {expected_name}) then return false end"
|
f"if not {arg_name} == {as_lua_literal(arg)} then return false end"
|
||||||
)
|
)
|
||||||
elif isinstance(arg, collections.abc.Mapping):
|
elif isinstance(arg, collections.abc.Sequence) or isinstance(
|
||||||
for k, v in arg.items():
|
arg, collections.abc.Mapping
|
||||||
add_arg_checks(
|
):
|
||||||
lines,
|
if not added_equal_tables_fn:
|
||||||
v,
|
lines.extend(
|
||||||
arg_name=f"{arg_name}.{k}",
|
line.strip() for line in LUA_FUNCTION_EQUAL_TABLES.splitlines()
|
||||||
added_equal_arrays_fn=added_equal_arrays_fn,
|
)
|
||||||
|
added_equal_tables_fn = True
|
||||||
|
expected_name = "expected"
|
||||||
|
lines.append(f"{expected_name} = {as_lua_literal(arg)}")
|
||||||
|
lines.append(
|
||||||
|
f"if not equal_tables({arg_name}, {expected_name}) then return false end"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
lines.append(f"if not {arg_name} == {arg} then return false end")
|
lines.append(
|
||||||
|
f"if not {arg_name} == {as_lua_literal(arg)} then return false end"
|
||||||
|
)
|
||||||
|
return added_equal_tables_fn
|
||||||
|
|
||||||
|
|
||||||
def build_proposal(
|
def build_proposal(
|
||||||
|
@ -146,7 +160,7 @@ def build_proposal(
|
||||||
proposal_script_lines.append("tables, args = ...")
|
proposal_script_lines.append("tables, args = ...")
|
||||||
proposal_script_lines.append(f'return Calls:call("{proposed_call}", args)')
|
proposal_script_lines.append(f'return Calls:call("{proposed_call}", args)')
|
||||||
|
|
||||||
proposal_script_text = "; ".join(proposal_script_lines)
|
proposal_script_text = ";\n".join(proposal_script_lines)
|
||||||
proposal = {
|
proposal = {
|
||||||
"script": {"text": proposal_script_text},
|
"script": {"text": proposal_script_text},
|
||||||
}
|
}
|
||||||
|
@ -165,7 +179,7 @@ def build_proposal(
|
||||||
vote_lines.append("args = call.args")
|
vote_lines.append("args = call.args")
|
||||||
add_arg_checks(vote_lines, args)
|
add_arg_checks(vote_lines, args)
|
||||||
vote_lines.append("return true")
|
vote_lines.append("return true")
|
||||||
vote_text = "; ".join(vote_lines)
|
vote_text = ";\n".join(vote_lines)
|
||||||
vote = {"ballot": {"text": vote_text}}
|
vote = {"ballot": {"text": vote_text}}
|
||||||
|
|
||||||
LOG.trace(f"Made {proposed_call} proposal:\n{json.dumps(proposal, indent=2)}")
|
LOG.trace(f"Made {proposed_call} proposal:\n{json.dumps(proposal, indent=2)}")
|
||||||
|
|
|
@ -174,25 +174,25 @@ namespace ccf
|
||||||
|
|
||||||
static int lua_log_trace(lua_State* l)
|
static int lua_log_trace(lua_State* l)
|
||||||
{
|
{
|
||||||
LOG_TRACE_FMT(get_var_string_from_args(l));
|
LOG_TRACE_FMT("{}", get_var_string_from_args(l));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int lua_log_debug(lua_State* l)
|
static int lua_log_debug(lua_State* l)
|
||||||
{
|
{
|
||||||
LOG_DEBUG_FMT(get_var_string_from_args(l));
|
LOG_DEBUG_FMT("{}", get_var_string_from_args(l));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int lua_log_info(lua_State* l)
|
static int lua_log_info(lua_State* l)
|
||||||
{
|
{
|
||||||
LOG_INFO_FMT(get_var_string_from_args(l));
|
LOG_INFO_FMT("{}", get_var_string_from_args(l));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int lua_log_fail(lua_State* l)
|
static int lua_log_fail(lua_State* l)
|
||||||
{
|
{
|
||||||
LOG_FAIL_FMT(get_var_string_from_args(l));
|
LOG_FAIL_FMT("{}", get_var_string_from_args(l));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -316,8 +316,11 @@ class Consortium:
|
||||||
return self.vote_using_majority(remote_node, proposal)
|
return self.vote_using_majority(remote_node, proposal)
|
||||||
|
|
||||||
def deploy_js_app(self, remote_node, app_bundle_path):
|
def deploy_js_app(self, remote_node, app_bundle_path):
|
||||||
proposal_body, _ = self.make_proposal("deploy_js_app", app_bundle_path)
|
proposal_body, careful_vote = self.make_proposal(
|
||||||
|
"deploy_js_app", app_bundle_path
|
||||||
|
)
|
||||||
proposal = self.get_any_active_member().propose(remote_node, proposal_body)
|
proposal = self.get_any_active_member().propose(remote_node, proposal_body)
|
||||||
|
proposal.vote_for = careful_vote
|
||||||
return self.vote_using_majority(remote_node, proposal)
|
return self.vote_using_majority(remote_node, proposal)
|
||||||
|
|
||||||
def accept_recovery(self, remote_node):
|
def accept_recovery(self, remote_node):
|
||||||
|
|
|
@ -55,10 +55,11 @@ def make_module_set_proposal(path, content, network):
|
||||||
with tempfile.NamedTemporaryFile("w") as f:
|
with tempfile.NamedTemporaryFile("w") as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
f.flush()
|
f.flush()
|
||||||
proposal_body, _ = ccf.proposal_generator.set_module(path, f.name)
|
proposal_body, careful_vote = ccf.proposal_generator.set_module(path, f.name)
|
||||||
proposal = network.consortium.get_any_active_member().propose(
|
proposal = network.consortium.get_any_active_member().propose(
|
||||||
primary, proposal_body
|
primary, proposal_body
|
||||||
)
|
)
|
||||||
|
proposal.vote_for = careful_vote
|
||||||
network.consortium.vote_using_majority(primary, proposal)
|
network.consortium.vote_using_majority(primary, proposal)
|
||||||
|
|
||||||
|
|
||||||
|
@ -77,10 +78,11 @@ def test_module_set_and_remove(network, args):
|
||||||
assert r.body.json()["js"] == MODULE_CONTENT_1, r.body
|
assert r.body.json()["js"] == MODULE_CONTENT_1, r.body
|
||||||
|
|
||||||
LOG.info("Member makes a module remove proposal")
|
LOG.info("Member makes a module remove proposal")
|
||||||
proposal_body, _ = ccf.proposal_generator.remove_module(MODULE_PATH_1)
|
proposal_body, careful_vote = ccf.proposal_generator.remove_module(MODULE_PATH_1)
|
||||||
proposal = network.consortium.get_any_active_member().propose(
|
proposal = network.consortium.get_any_active_member().propose(
|
||||||
primary, proposal_body
|
primary, proposal_body
|
||||||
)
|
)
|
||||||
|
proposal.vote_for = careful_vote
|
||||||
network.consortium.vote_using_majority(primary, proposal)
|
network.consortium.vote_using_majority(primary, proposal)
|
||||||
|
|
||||||
with primary.client(
|
with primary.client(
|
||||||
|
@ -106,10 +108,11 @@ def test_modules_remove(network, args):
|
||||||
assert r.body.json()["js"] == MODULE_CONTENT_1, r.body
|
assert r.body.json()["js"] == MODULE_CONTENT_1, r.body
|
||||||
|
|
||||||
LOG.info("Member makes a prefix-based modules remove proposal")
|
LOG.info("Member makes a prefix-based modules remove proposal")
|
||||||
proposal_body, _ = ccf.proposal_generator.remove_modules(MODULE_PREFIX_1)
|
proposal_body, careful_vote = ccf.proposal_generator.remove_modules(MODULE_PREFIX_1)
|
||||||
proposal = network.consortium.get_any_active_member().propose(
|
proposal = network.consortium.get_any_active_member().propose(
|
||||||
primary, proposal_body
|
primary, proposal_body
|
||||||
)
|
)
|
||||||
|
proposal.vote_for = careful_vote
|
||||||
network.consortium.vote_using_majority(primary, proposal)
|
network.consortium.vote_using_majority(primary, proposal)
|
||||||
|
|
||||||
with primary.client(
|
with primary.client(
|
||||||
|
@ -177,10 +180,11 @@ def test_app_bundle(network, args):
|
||||||
assert r.body.json() == {"error": "invalid operand type"}, r.body
|
assert r.body.json() == {"error": "invalid operand type"}, r.body
|
||||||
|
|
||||||
LOG.info("Removing js app")
|
LOG.info("Removing js app")
|
||||||
proposal_body, _ = ccf.proposal_generator.remove_js_app()
|
proposal_body, careful_vote = ccf.proposal_generator.remove_js_app()
|
||||||
proposal = network.consortium.get_any_active_member().propose(
|
proposal = network.consortium.get_any_active_member().propose(
|
||||||
primary, proposal_body
|
primary, proposal_body
|
||||||
)
|
)
|
||||||
|
proposal.vote_for = careful_vote
|
||||||
network.consortium.vote_using_majority(primary, proposal)
|
network.consortium.vote_using_majority(primary, proposal)
|
||||||
|
|
||||||
LOG.info("Verifying that modules and endpoints were removed")
|
LOG.info("Verifying that modules and endpoints were removed")
|
||||||
|
|
Загрузка…
Ссылка в новой задаче