mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 02:56:17 -10:00
Merge pull request #11250 from TheMrZZ/fix/h252-loaded-pig-tags
Normalize Mob Tags when adopting NBT
This commit is contained in:
4 files changed
+513
No files matched your search
@@ -313,6 +313,7 @@ impl Game<'_> {
|
||||
return Err(err("commands.data.entity.invalid", &[]));
|
||||
}
|
||||
Target::Mob(id) => {
|
||||
Self::normalize_loaded_mob_tags(&mut tag);
|
||||
let uuid = self.uuid_for(id);
|
||||
let kind = self
|
||||
.mobs
|
||||
|
||||
@@ -798,6 +798,23 @@ impl Game<'_> {
|
||||
self.persist.unknown.entry(key).or_default().push(tag);
|
||||
}
|
||||
}
|
||||
/// `Entity.load`: adopt the bounded TAG_LIST_CODEC input as a set.
|
||||
/// Keep that adopted Mob state in the retained compound for commands and saves.
|
||||
pub(in crate::game) fn normalize_loaded_mob_tags(tag: &mut Tag) {
|
||||
let Tag::Compound(raw) = tag else {
|
||||
return;
|
||||
};
|
||||
let Some(Tag::List(mut tags)) = raw.remove(&"Tags".into()) else {
|
||||
return;
|
||||
};
|
||||
tags.retain(|tag| matches!(tag, Tag::String(_)));
|
||||
tags.truncate(1024);
|
||||
let mut seen = HashSet::new();
|
||||
tags.retain(|tag| seen.insert(tag.clone()));
|
||||
if !tags.is_empty() {
|
||||
raw.insert("Tags".into(), Tag::List(tags));
|
||||
}
|
||||
}
|
||||
/// A saved mob and, under `Passengers`, the mobs riding it.
|
||||
pub(super) fn load_mob(&mut self, tag: Tag, mount: Option<u32>) -> Option<u32> {
|
||||
let uuid = entity_nbt::uuid(&tag);
|
||||
@@ -808,6 +825,7 @@ impl Game<'_> {
|
||||
// Structure mobs that generation left to finish: saves from before
|
||||
// they were finalized at placement, or a placement that could not.
|
||||
let mut tag = tag;
|
||||
Self::normalize_loaded_mob_tags(&mut tag);
|
||||
let mut finalize = false;
|
||||
if let Tag::Compound(c) = &mut tag {
|
||||
finalize = c.remove(&"horizon:finalize".into()).is_some();
|
||||
@@ -2544,3 +2562,244 @@ mod interaction_saved_defaults_tests {
|
||||
assert_eq!(game.world.game_time, time);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod loaded_tag_tests {
|
||||
use super::*;
|
||||
use crate::game::{Audio, Options, Progress, Settings, commands::Target, load_resources};
|
||||
use horizon_client::mobs::Species;
|
||||
|
||||
fn check(
|
||||
game: &mut Game<'_>,
|
||||
target: Target,
|
||||
expected: &[String],
|
||||
phase: &str,
|
||||
failures: &mut Vec<String>,
|
||||
) {
|
||||
let live = game.tags_of(target);
|
||||
if live.len() != expected.len() || expected.iter().any(|t| !live.contains(t)) {
|
||||
failures.push(format!(
|
||||
"{phase}: live count {} expected {}",
|
||||
live.len(),
|
||||
expected.len()
|
||||
));
|
||||
}
|
||||
let saved = game.entity_nbt(target).expect("actual entity save view");
|
||||
assert_eq!(saved.get("Health"), Some(&Tag::Float(10.0_f32.to_bits())));
|
||||
assert_eq!(saved.get("NoAI"), Some(&Tag::Byte(1)));
|
||||
assert_eq!(saved.get("NoGravity"), Some(&Tag::Byte(1)));
|
||||
assert_eq!(saved.get("PortalCooldown"), Some(&Tag::Int(17)));
|
||||
let valid = match saved.get("Tags") {
|
||||
None => expected.is_empty(),
|
||||
Some(Tag::List(tags)) => {
|
||||
!expected.is_empty()
|
||||
&& tags.len() == expected.len()
|
||||
&& tags.iter().all(|t| matches!(t, Tag::String(_)))
|
||||
&& expected
|
||||
.iter()
|
||||
.all(|s| tags.contains(&Tag::String(s.as_str().into())))
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
if !valid {
|
||||
failures.push(format!(
|
||||
"{phase}: saved Tags count/type/membership mismatch"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn action(game: &mut Game<'_>, command: &str, expected: bool, failures: &mut Vec<String>) {
|
||||
if game.run_command(command) != expected {
|
||||
failures.push(format!("command result: {command}"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "needs prepared resources in HORIZON_TEST_RESOURCES and scratch homes/cwd"]
|
||||
fn loaded_pig_tags_cap_before_dedup_and_live_rewrites_adopt_the_set() {
|
||||
let root = std::path::PathBuf::from(
|
||||
std::env::var_os("HORIZON_TEST_RESOURCES").expect("prepared resources required"),
|
||||
);
|
||||
let settings = Settings::default();
|
||||
let loaded = load_resources(
|
||||
root.clone(),
|
||||
None,
|
||||
&root,
|
||||
&settings,
|
||||
std::time::Instant::now(),
|
||||
false,
|
||||
true,
|
||||
&Progress::new(),
|
||||
&mut |_, _| unreachable!(),
|
||||
)
|
||||
.unwrap();
|
||||
let mut options = Options::parse(std::iter::empty()).unwrap().unwrap();
|
||||
options.resources = root.clone();
|
||||
options.execution_role = crate::game::execution_role::ExecutionRole::IntegratedServer;
|
||||
let mut game = Game::new(
|
||||
&loaded.resources,
|
||||
loaded.pack,
|
||||
loaded.gui.unwrap(),
|
||||
options,
|
||||
settings,
|
||||
Audio::silent(&root),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
game.build_registries();
|
||||
game.set_allow_commands(true);
|
||||
game.world
|
||||
.chunks
|
||||
.insert((0, 0), Arc::new(Chunk::empty(0, 0)));
|
||||
game.block_ticks
|
||||
.add_empty_chunk(ChunkPos::new(0, 0))
|
||||
.unwrap();
|
||||
assert!(game.run_command("forceload add 0 0"));
|
||||
assert!(game.run_command("fill 0 199 0 15 199 15 stone"));
|
||||
assert_eq!(
|
||||
game.world.block(4, 199, 4),
|
||||
game.pack
|
||||
.assets
|
||||
.registry
|
||||
.default_state("minecraft:stone")
|
||||
.unwrap()
|
||||
);
|
||||
let start = game.world.game_time;
|
||||
let unique: Vec<String> = (0..1025).map(|i| format!("h252_{i:04}")).collect();
|
||||
let mut duplicates = vec!["h252_repeat".to_string(); 1024];
|
||||
duplicates.push("h252_late".into());
|
||||
let small = vec![
|
||||
"h252_beta".to_string(),
|
||||
"h252_alpha".into(),
|
||||
"h252_beta".into(),
|
||||
];
|
||||
let encode = |tags: &[String]| {
|
||||
format!(
|
||||
"[{}]",
|
||||
tags.iter()
|
||||
.map(|s| format!("\"{s}\""))
|
||||
.collect::<Vec<_>>()
|
||||
.join(",")
|
||||
)
|
||||
};
|
||||
let mut failures = Vec::new();
|
||||
for (index, (input, expected)) in [
|
||||
(Some(encode(&unique)), unique[..1024].to_vec()),
|
||||
(Some(encode(&duplicates)), vec!["h252_repeat".to_string()]),
|
||||
(
|
||||
Some(encode(&small)),
|
||||
vec!["h252_alpha".to_string(), "h252_beta".into()],
|
||||
),
|
||||
(Some("\"h252_scalar\"".into()), vec![]),
|
||||
(None, vec![]),
|
||||
(Some("[]".into()), vec![]),
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
let n = index + 1;
|
||||
let uuid = format!("000000fc-0000-0000-0000-{n:012x}");
|
||||
let x = 4.5 + index as f64 * 2.0;
|
||||
let tags = input.map_or_else(String::new, |s| format!(",Tags:{s}"));
|
||||
assert!(game.run_command(&format!(
|
||||
"summon pig {x} 200 4.5 {{UUID:[I;252,0,0,{n}],Health:10f,NoAI:1b,NoGravity:1b,Motion:[0d,0d,0d],PortalCooldown:17{tags}}}"
|
||||
)));
|
||||
let target = game.target_with_uuid([252, 0, 0, n as i32]).unwrap();
|
||||
let Target::Mob(id) = target else {
|
||||
panic!("actual pig target");
|
||||
};
|
||||
let mob = game.mobs.iter().find(|m| m.id == id).unwrap();
|
||||
assert_eq!(mob.species(), Species::Pig);
|
||||
assert_eq!(mob.position, Vec3::new(x, 200.0, 4.5));
|
||||
assert_eq!(mob.health, 10.0);
|
||||
assert!(mob.no_ai && mob.no_gravity);
|
||||
assert_eq!(game.uuid_for(id), [252, 0, 0, n as i32]);
|
||||
check(
|
||||
&mut game,
|
||||
target,
|
||||
&expected,
|
||||
&format!("load {index}"),
|
||||
&mut failures,
|
||||
);
|
||||
if index == 0 {
|
||||
action(
|
||||
&mut game,
|
||||
&format!("tag {uuid} add h252_extra"),
|
||||
false,
|
||||
&mut failures,
|
||||
);
|
||||
action(
|
||||
&mut game,
|
||||
&format!("tag {uuid} remove h252_0000"),
|
||||
true,
|
||||
&mut failures,
|
||||
);
|
||||
action(
|
||||
&mut game,
|
||||
&format!("tag {uuid} add h252_extra"),
|
||||
true,
|
||||
&mut failures,
|
||||
);
|
||||
let mut after = unique[1..1024].to_vec();
|
||||
after.push("h252_extra".into());
|
||||
check(&mut game, target, &after, "full remove/add", &mut failures);
|
||||
} else if index == 1 {
|
||||
action(
|
||||
&mut game,
|
||||
&format!("tag {uuid} add h252_added"),
|
||||
true,
|
||||
&mut failures,
|
||||
);
|
||||
let after = vec!["h252_repeat".to_string(), "h252_added".into()];
|
||||
check(
|
||||
&mut game,
|
||||
target,
|
||||
&after,
|
||||
"duplicate load then add",
|
||||
&mut failures,
|
||||
);
|
||||
action(
|
||||
&mut game,
|
||||
&format!("tag {uuid} remove h252_added"),
|
||||
true,
|
||||
&mut failures,
|
||||
);
|
||||
check(
|
||||
&mut game,
|
||||
target,
|
||||
&expected,
|
||||
"remove added tag",
|
||||
&mut failures,
|
||||
);
|
||||
} else if index == 2 {
|
||||
action(
|
||||
&mut game,
|
||||
&format!("tag {uuid} add h252_alpha"),
|
||||
false,
|
||||
&mut failures,
|
||||
);
|
||||
assert!(game.run_command(&format!(
|
||||
"data modify entity {uuid} Tags set value {}",
|
||||
encode(&duplicates)
|
||||
)));
|
||||
check(
|
||||
&mut game,
|
||||
target,
|
||||
&["h252_repeat".into()],
|
||||
"live rewrite",
|
||||
&mut failures,
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
game.mobs.iter().find(|m| m.id == id).unwrap().position,
|
||||
Vec3::new(x, 200.0, 4.5)
|
||||
);
|
||||
assert_eq!(game.uuid_for(id), [252, 0, 0, n as i32]);
|
||||
assert_eq!(game.world.game_time, start);
|
||||
}
|
||||
assert!(
|
||||
failures.is_empty(),
|
||||
"actual load/getter/save/mutation failures: {failures:#?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
"""Six real UUID-bound pig loads, plus actual tag commands and a live rewrite."""
|
||||
import json
|
||||
from command_exec_corpus import cmd, setup
|
||||
|
||||
UNIQUE = [f"h252_{i:04d}" for i in range(1025)]
|
||||
DUPLICATES = ["h252_repeat"] * 1024 + ["h252_late"]
|
||||
SMALL = ["h252_beta", "h252_alpha", "h252_beta"]
|
||||
|
||||
|
||||
def cases():
|
||||
ops = setup("/tick freeze", "/gamerule spawn_mobs false",
|
||||
"/gamerule advance_time false", "/gamerule random_tick_speed 0",
|
||||
"/forceload add 0 0")
|
||||
ops.append({"op": "settle", "required_chunks": [[0, 0]]})
|
||||
ops += setup("/fill 0 199 0 15 199 15 stone",
|
||||
"/scoreboard objectives add h252 dummy",
|
||||
"/execute store result score start h252 run time query gametime")
|
||||
|
||||
def guard(command, expect_result=1):
|
||||
op = cmd(command, expect_result=expect_result)
|
||||
op["admission"] = True
|
||||
return op
|
||||
|
||||
def stored(command, result, success=True, saved_count=None):
|
||||
ops.extend(setup("/scoreboard players set result h252 31",
|
||||
"/scoreboard players set success h252 17"))
|
||||
op = cmd("/execute store result score result h252 store success score success h252 run " + command)
|
||||
op["expect_callback"] = [success, result]
|
||||
if saved_count is not None:
|
||||
op.update(diagnostic="complete saved Tags; ordering remains raw",
|
||||
saved_tags_count=saved_count)
|
||||
ops.append(op)
|
||||
ops.extend([cmd("/scoreboard players get result h252", expect_result=result),
|
||||
cmd("/scoreboard players get success h252", expect_result=int(success))])
|
||||
|
||||
def member(uuid, tag, present=True):
|
||||
ops.append(cmd(f"/execute as {uuid} {'if' if present else 'unless'} entity @s[tag={tag}]",
|
||||
expect_result=1))
|
||||
|
||||
def clock():
|
||||
ops.extend(setup("/execute store result score now h252 run time query gametime",
|
||||
"/scoreboard players operation now h252 -= start h252"))
|
||||
ops.append(guard("/scoreboard players get now h252", expect_result=0))
|
||||
|
||||
def saved(uuid, count):
|
||||
stored(f"data get entity {uuid} Tags", count, bool(count), count)
|
||||
ops.append(cmd(f"/execute {'if' if count else 'unless'} data entity {uuid} Tags",
|
||||
expect_result=1))
|
||||
# /tag list reads the live getter, independently of the saved-state read.
|
||||
stored(f"tag {uuid} list", count)
|
||||
|
||||
actors = [
|
||||
("unique", UNIQUE, 1024),
|
||||
("duplicates", DUPLICATES, 1),
|
||||
("small", SMALL, 2),
|
||||
("scalar", "h252_scalar", 0),
|
||||
("absent", None, 0),
|
||||
("empty", [], 0),
|
||||
]
|
||||
for index, (name, tags, count) in enumerate(actors, 1):
|
||||
uuid = f"000000fc-0000-0000-0000-{index:012x}"
|
||||
x = 2.5 + index * 2
|
||||
field = "" if tags is None else ",Tags:" + json.dumps(tags, separators=(",", ":"))
|
||||
ops.append(guard(f"/summon pig {x} 200 4.5 {{UUID:[I;252,0,0,{index}],"
|
||||
f"Health:10f,NoAI:1b,NoGravity:1b,Motion:[0d,0d,0d],PortalCooldown:17{field}}}",
|
||||
expect_result=1))
|
||||
identity = (f"/execute as {uuid} if entity @s[type=minecraft:pig,nbt={{"
|
||||
f"UUID:[I;252,0,0,{index}],Health:10f,NoAI:1b,NoGravity:1b,"
|
||||
f"Pos:[{x}d,200d,4.5d],Motion:[0d,0d,0d],PortalCooldown:17}}]")
|
||||
ops.extend([guard(f"/execute if loaded {int(x)} 200 4", expect_result=1),
|
||||
guard(f"/execute if block {int(x)} 199 4 stone", expect_result=1),
|
||||
guard(identity, expect_result=1)])
|
||||
body = cmd(f"/data get entity {uuid}")
|
||||
body["diagnostic"] = "complete victim body"
|
||||
ops.append(body)
|
||||
saved(uuid, count)
|
||||
member(uuid, "h252_never", False)
|
||||
if name == "unique":
|
||||
for tag in [UNIQUE[0], UNIQUE[1023]]:
|
||||
member(uuid, tag)
|
||||
member(uuid, UNIQUE[1024], False)
|
||||
stored(f"tag {uuid} add h252_extra", 0, False)
|
||||
stored(f"tag {uuid} remove {UNIQUE[0]}", 1)
|
||||
stored(f"tag {uuid} add h252_extra", 1)
|
||||
member(uuid, UNIQUE[0], False)
|
||||
member(uuid, "h252_extra")
|
||||
saved(uuid, 1024)
|
||||
elif name == "duplicates":
|
||||
member(uuid, "h252_repeat")
|
||||
member(uuid, "h252_late", False)
|
||||
stored(f"tag {uuid} add h252_added", 1)
|
||||
member(uuid, "h252_added")
|
||||
stored(f"tag {uuid} remove h252_added", 1)
|
||||
member(uuid, "h252_added", False)
|
||||
saved(uuid, 1)
|
||||
elif name == "small":
|
||||
member(uuid, "h252_alpha")
|
||||
member(uuid, "h252_beta")
|
||||
stored(f"tag {uuid} add h252_alpha", 0, False)
|
||||
stored(f"data modify entity {uuid} Tags set value "
|
||||
+ json.dumps(DUPLICATES, separators=(",", ":")), 1)
|
||||
member(uuid, "h252_repeat")
|
||||
member(uuid, "h252_late", False)
|
||||
member(uuid, "h252_alpha", False)
|
||||
saved(uuid, 1)
|
||||
else:
|
||||
member(uuid, "h252_scalar", False)
|
||||
if name in ("unique", "duplicates", "small"):
|
||||
ops.append(guard(identity, expect_result=1))
|
||||
clock()
|
||||
for index in range(1, 7):
|
||||
ops.extend(setup(f"/kill 000000fc-0000-0000-0000-{index:012x}"))
|
||||
ops.extend(setup("/scoreboard objectives remove h252", "/forceload remove 0 0"))
|
||||
ops.append(guard("/tick unfreeze", expect_result=0))
|
||||
return ops
|
||||
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Actual pig Tags load/save/mutation proof; complete raw list order is diagnostic.
|
||||
|
||||
Admit fresh Java with --reference-only --work-dir REF. Reuse exact REF bytes for
|
||||
both normal binaries with --reference-dir REF --binary GAME --work-dir NEW_OUT.
|
||||
No output masks, ordering normalization or synthetic tag observer are applied.
|
||||
"""
|
||||
import json
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
import test_command_exec_parity as runner
|
||||
from loaded_pig_tags_corpus import cases
|
||||
|
||||
|
||||
def callback(row):
|
||||
values = row.get("results")
|
||||
assert isinstance(values, list) and len(values) == 1
|
||||
assert isinstance(values[0], list) and len(values[0]) == 2
|
||||
assert type(values[0][0]) is bool and type(values[0][1]) is int
|
||||
return values
|
||||
|
||||
|
||||
def saved_schema(row, count):
|
||||
"""Validate actual saved-list type/count without changing the raw body."""
|
||||
assert callback(row) == [[bool(count), count]]
|
||||
if count:
|
||||
assert len(row["messages"]) == 1
|
||||
text = "".join(span[0] for span in row["messages"][0])
|
||||
# These predeclared ASCII strings make the SNBT string-list a JSON array.
|
||||
prefix, marker, literal = text.partition(" has the following entity data: ")
|
||||
assert prefix and marker
|
||||
values = json.loads(literal)
|
||||
assert type(values) is list and len(values) == count
|
||||
assert all(type(value) is str for value in values)
|
||||
|
||||
|
||||
def admit(program, java):
|
||||
ops = [op for op in program if op["op"] == "cmd" and op.get("record", True)]
|
||||
assert len(ops) == len(java) and len(ops) > 0
|
||||
for op, row in zip(ops, java):
|
||||
assert op["cmd"] == row["cmd"]
|
||||
if "expect_result" in op:
|
||||
assert callback(row) == [[True, op["expect_result"]]], (op, row)
|
||||
if "expect_callback" in op:
|
||||
assert callback(row) == [op["expect_callback"]], (op, row)
|
||||
if "saved_tags_count" in op:
|
||||
saved_schema(row, op["saved_tags_count"])
|
||||
return ops
|
||||
|
||||
|
||||
def native_from_reference(reference, folder, program):
|
||||
runner.base.verify_artifacts()
|
||||
expected = "".join(json.dumps({k: v for k, v in op.items() if k != "expect_result"},
|
||||
ensure_ascii=False) + "\n" for op in program)
|
||||
assert (reference / "script.jsonl").read_text() == expected
|
||||
java = [json.loads(line) for line in (reference / "vanilla.jsonl").read_text().splitlines()]
|
||||
admit(program, java)
|
||||
assert "--binary" in sys.argv, "an archived normal Game binary is required"
|
||||
binary = Path(sys.argv[sys.argv.index("--binary") + 1]).resolve(strict=True)
|
||||
folder.mkdir(parents=True, exist_ok=True)
|
||||
for name in ["script.jsonl", "vanilla.jsonl"]:
|
||||
shutil.copyfile(reference / name, folder / name)
|
||||
scratch = folder / "rust"
|
||||
scratch.mkdir(exist_ok=False)
|
||||
env = runner.lab.game_env(HORIZON_PLAYER_NAME="Player",
|
||||
HORIZON_COMMAND_SCRIPT=folder / "script.jsonl",
|
||||
HORIZON_COMMAND_OUTPUT=folder / "rust.jsonl",
|
||||
HORIZON_API_DISCOVERY="http://127.0.0.1:9/")
|
||||
with (folder / "rust.log").open("w") as log:
|
||||
result = runner.run_game([str(binary), "--resources", str(runner.lab.RESOURCES),
|
||||
"--world", "harness", "--seed", str(runner.SEED), "--radius", "4"],
|
||||
cwd=scratch, env=env, stdout=log, stderr=runner.subprocess.STDOUT)
|
||||
assert result.returncode == 0, f"native process failed; see {folder / 'rust.log'}"
|
||||
|
||||
|
||||
def main():
|
||||
reference = None
|
||||
if "--reference-dir" in sys.argv:
|
||||
index = sys.argv.index("--reference-dir")
|
||||
reference = Path(sys.argv[index + 1]).resolve(strict=True)
|
||||
del sys.argv[index:index + 2]
|
||||
program = [*runner.corpus_module.group_setup(), *cases()]
|
||||
runner.corpus_module.groups = lambda: [("loaded pig Tags", program)]
|
||||
if "--work-dir" not in sys.argv:
|
||||
sys.argv += ["--work-dir", str(runner.lab.LOCAL / "loaded-pig-tags-parity")]
|
||||
if "--allow-gaps" not in sys.argv:
|
||||
sys.argv.append("--allow-gaps")
|
||||
folder = Path(sys.argv[sys.argv.index("--work-dir") + 1]).resolve()
|
||||
if reference is None:
|
||||
assert "--reference-only" in sys.argv, "admit fresh Java, then reuse its frozen reference"
|
||||
runner.main()
|
||||
else:
|
||||
assert "--reference-only" not in sys.argv
|
||||
native_from_reference(reference, folder, program)
|
||||
java = [json.loads(line) for line in (folder / "vanilla.jsonl").read_text().splitlines()]
|
||||
ops = admit(program, java)
|
||||
if "--reference-only" in sys.argv:
|
||||
print(f"Fresh Java admitted {len(ops)} records and saved-list type/count guards.")
|
||||
return 0
|
||||
native = [json.loads(line) for line in (folder / "rust.jsonl").read_text().splitlines()]
|
||||
assert len(ops) == len(native)
|
||||
report = {"strict_records": 0, "strict_saved_list_projections": 0,
|
||||
"strict_failures": [], "admission_records": 0, "admission_failures": [],
|
||||
"diagnostic_records": 0, "diagnostic_differences": []}
|
||||
mismatches = []
|
||||
for index, (op, left, right) in enumerate(zip(ops, java, native)):
|
||||
assert left["cmd"] == right["cmd"] == op["cmd"]
|
||||
same = all(left.get(key) == right.get(key) for key in ["messages", "results", "state"])
|
||||
if op.get("admission"):
|
||||
report["admission_records"] += 1
|
||||
if not same:
|
||||
report["admission_failures"].append(index)
|
||||
if not same:
|
||||
mismatches.append({"row": index, "java": left, "native": right})
|
||||
if op.get("diagnostic"):
|
||||
report["diagnostic_records"] += 1
|
||||
if not same:
|
||||
report["diagnostic_differences"].append(index)
|
||||
if "saved_tags_count" in op:
|
||||
report["strict_saved_list_projections"] += 1
|
||||
try:
|
||||
saved_schema(right, op["saved_tags_count"])
|
||||
except (AssertionError, ValueError, TypeError):
|
||||
report["strict_failures"].append({"row": index, "kind": "saved type/count/callback"})
|
||||
else:
|
||||
report["strict_records"] += 1
|
||||
if not same:
|
||||
report["strict_failures"].append({"row": index, "kind": "exact record"})
|
||||
(folder / "tags-report.json").write_text(json.dumps(report, indent=2) + "\n")
|
||||
(folder / "raw-mismatches.jsonl").write_text("".join(json.dumps(row) + "\n" for row in mismatches))
|
||||
print(json.dumps(report))
|
||||
return bool(report["strict_failures"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user