mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Correct empty tag selector predicates
Co-Authored-By: Codex <[email protected]>
This commit is contained in:
1 parent
f00c4c6392
commit
85c277b5e2
5 files changed
+364
-1
No files matched your search
@@ -325,7 +325,8 @@ impl Game<'_> {
|
||||
let tags = self.tags_of(t);
|
||||
for (tag, negated) in &s.tags {
|
||||
let has = if tag.is_empty() {
|
||||
!tags.is_empty()
|
||||
// EntitySelectorOptions' empty-name predicate (lambda$bootStrap$43).
|
||||
tags.is_empty()
|
||||
} else {
|
||||
tags.contains(tag)
|
||||
};
|
||||
@@ -1122,3 +1123,6 @@ mod team_tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod empty_tag_tests;
|
||||
@@ -0,0 +1,104 @@
|
||||
use super::*;
|
||||
use horizon_core::geometry::Vec3;
|
||||
|
||||
#[test]
|
||||
#[ignore = "needs prepared resources in HORIZON_TEST_RESOURCES"]
|
||||
fn empty_tag_selectors_follow_actual_tag_edits() {
|
||||
use crate::game::{Audio, Game, Options, Progress, Settings, load_resources};
|
||||
use horizon_client::motion::PlayerMotion;
|
||||
let root = std::path::PathBuf::from(std::env::var_os("HORIZON_TEST_RESOURCES").unwrap());
|
||||
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();
|
||||
let scratch = std::env::temp_dir().join(format!(
|
||||
"horizon-empty-tags-{}-{}",
|
||||
std::process::id(),
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos()
|
||||
));
|
||||
std::fs::create_dir_all(&scratch).unwrap();
|
||||
game.world_root = scratch.clone();
|
||||
game.player = Some(PlayerMotion::new(game.motion, Vec3::new(10.5, 64.0, 0.5)).unwrap());
|
||||
|
||||
game.build_registries();
|
||||
game.set_allow_commands(true);
|
||||
for (n, x, tags) in [(1, 4.5, ""), (2, 8.5, ",Tags:[\"audit\"]")] {
|
||||
assert!(game.run_command(&format!(
|
||||
"summon pig {x} 64 4.5 {{UUID:[I;267,0,0,{n}],NoAI:1b,NoGravity:1b{tags}}}"
|
||||
)));
|
||||
}
|
||||
let a = game.target_with_uuid([267, 0, 0, 1]).unwrap();
|
||||
let b = game.target_with_uuid([267, 0, 0, 2]).unwrap();
|
||||
assert_ne!(a, b);
|
||||
let select = |g: &mut Game<'_>, tag: &str| {
|
||||
let text = format!("@e[type=minecraft:pig,tag={tag}]");
|
||||
let selector = horizon_client::command::selector::parse(
|
||||
&mut horizon_client::command::Reader::new(&text),
|
||||
&g.ui.registries,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
g.select(&selector).into_iter().collect::<HashSet<_>>()
|
||||
};
|
||||
for (phase, tagged_a) in [false, true, false].into_iter().enumerate() {
|
||||
if phase != 0 {
|
||||
for (n, tagged) in [(1, tagged_a), (2, !tagged_a)] {
|
||||
assert!(game.run_command(&format!(
|
||||
"tag 0000010b-0000-0000-0000-{n:012} {} audit",
|
||||
if tagged { "add" } else { "remove" }
|
||||
)));
|
||||
}
|
||||
}
|
||||
assert_eq!(game.target_with_uuid([267, 0, 0, 1]), Some(a));
|
||||
assert_eq!(game.target_with_uuid([267, 0, 0, 2]), Some(b));
|
||||
assert_eq!(
|
||||
game.tags_of(a),
|
||||
if tagged_a {
|
||||
vec!["audit".to_owned()]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
game.tags_of(b),
|
||||
if tagged_a {
|
||||
vec![]
|
||||
} else {
|
||||
vec!["audit".to_owned()]
|
||||
}
|
||||
);
|
||||
let (empty, tagged) = if tagged_a { (b, a) } else { (a, b) };
|
||||
// Named-tag admissions pass before the old empty-tag assertion fails.
|
||||
assert_eq!(select(&mut game, "audit"), HashSet::from([tagged]));
|
||||
assert_eq!(select(&mut game, "!audit"), HashSet::from([empty]));
|
||||
assert_eq!(select(&mut game, ""), HashSet::from([empty]));
|
||||
assert_eq!(select(&mut game, "!"), HashSet::from([tagged]));
|
||||
}
|
||||
drop(game);
|
||||
std::fs::remove_dir_all(scratch).unwrap();
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
# Empty tag selector truth value (H267)
|
||||
|
||||
`tag=` selects entities with no tags; `tag=!` selects entities with at least one tag.
|
||||
The fix changes only the empty-name branch from `!tags.is_empty()` to
|
||||
`tags.is_empty()` before the existing negation comparison. Named tags, normalization,
|
||||
the tag cap, entity ordering and other selector predicates are unchanged.
|
||||
|
||||
Pinned Java26.3 `EntitySelectorOptions.lambda$bootStrap$43` calls
|
||||
`Entity.entityTags().isEmpty()` for the empty name (bytecode10–20), then applies
|
||||
negation. The port ledger and actual bytecode were reviewed. Current/closed issue/PR
|
||||
searches found no exact competing fix. Merged #6468 preserves tags on more entity kinds;
|
||||
#6935 changes candidate traversal. Neither fixes this branch.
|
||||
|
||||
## Actual Game/Java fixture
|
||||
|
||||
Frozen base `f00c4c6392ef444130692da4db99f0a7268dd20b`. No pending #10130 (H264)
|
||||
or #10219 (H266) production is imported. Two actual NoAI/NoGravity pigs have fixed UUIDs
|
||||
`0000010b-0000-0000-0000-000000000001` and
|
||||
`0000010b-0000-0000-0000-000000000002`, Health10 and initial positions
|
||||
[4.5,100,4.5] and [8.5,100,4.5]. Identity never depends on a tag or entity order.
|
||||
Initially A has no tags and B has only audit. Actual `/tag UUID add/remove audit` commands
|
||||
swap their tag states and restore them on those same actors.
|
||||
|
||||
Each phase observes actual UUID/type/Health/Pos/NoAI/NoGravity, live `/tag UUID list`,
|
||||
loaded chunk/floor and the real owner body/mode. For each actor, tag=, tag=!, tag=audit,
|
||||
and tag=!audit are tested with both if/unless scoreboard actions. Every call resets
|
||||
result/success/branch independently to-99, then reads all three. True actions and false
|
||||
no-callback behavior are independently validated by fresh Java. There are zero observation
|
||||
world ticks, witnessed by exact relative-clock deltas. No new observer or generic
|
||||
infrastructure is added: this uses the existing actual Game/Java command harness.
|
||||
|
||||
The strict **421-record** program was declared before its fresh Java and old/fixed runs.
|
||||
All recorded JSON is compared in full, including message styles and callbacks, without
|
||||
normalization, tolerance or value masks. Six saved Tags getters, six full body reads and
|
||||
four absolute clock reads still execute and are traced; they are additionally recorded
|
||||
in the executable **437-record** full diagnostic. These 16 declared diagnostic labels are:
|
||||
clock-start; initial/swapped/restored-clock-raw; initial/swapped/restored-a/b-Tags;
|
||||
initial/swapped/restored-a/b-body. No full437 equality is claimed.
|
||||
|
||||
An earlier **421-record** draft used saved `Tags` getters as live-state admissions.
|
||||
Its old capture was rejected as a prerequisite: after removing the last tag, Java omits
|
||||
Tags while native returns Tags:[]. It is not a qualified H267 count. The revised program
|
||||
uses independent real `/tag list` receipts, retaining every saved Tags read in the full
|
||||
diagnostic. The original421 script/reference/native capture and original431 Java diagnostic, plus
|
||||
runnable Python modules, remain unchanged under `.local/h267/original421` and the
|
||||
original capture directories. No fixed-pass claim is made for that rejected program.
|
||||
No serializer or tag-normalization repair is included.
|
||||
|
||||
Replay:
|
||||
|
||||
```sh
|
||||
python3 tools/test_selector_empty_tag_parity.py --binary /path/to/game --work-dir .local/h267/replay
|
||||
python3 tools/test_selector_empty_tag_parity.py --full-diagnostic --binary /path/to/game --work-dir .local/h267/full-replay
|
||||
HORIZON_TEST_RESOURCES=/absolute/resources/26.3 cargo test -p horizon-client --lib empty_tag_selectors_follow_actual_tag_edits -- --ignored --nocapture
|
||||
```
|
||||
|
||||
Resource-backed units require isolated fresh test homes/cwd, as in AGENTS.md. Raw commands,
|
||||
all reference/native JSON, complete expected/actual differences, logs, bytecode, declarations,
|
||||
hashes and immutable executables remain locally under `.local/h267/`. No Mojang files or
|
||||
prepared resources are committed.
|
||||
|
||||
## Declared hashes
|
||||
|
||||
| Artifact | SHA-256 |
|
||||
|---|---|
|
||||
| canonical program | `7aa4443f5a5ec8f805aadb6cd3f675f665f102aef7fd199a7269fc8f94fc00f3` |
|
||||
| Fresh canonical Java records | `e29c68a719df77c7302e388f22b80cabfdbb0de475fda9b3cbd5d8a064e4b449` |
|
||||
| full program | `5a4f4418f6da23a3ec3ea756e247c777a51939c9fa34b3a3179650be0763ce42` |
|
||||
| Fresh full Java records | `72ae7a5daed2f426079efb518f519bc673bc3e46f9323f378f37ff4c5ba4712a` |
|
||||
|
||||
## Paired results and residuals
|
||||
|
||||
| Program | Untouched Game | Fixed Game |
|
||||
|---|---:|---:|
|
||||
| Strict complete records | 325/421 | **421/421** |
|
||||
| Full diagnostic | 329/437 | **425/437** |
|
||||
|
||||
Both old/fixed programs are byte-identical for each mode. Their strict fresh Java records
|
||||
are byte-identical to the frozen strict reference. All actual identity/live-tag/named-tag
|
||||
and zero-clock admissions pass. The 96 old target differences are 24 reversed empty-form
|
||||
actions plus their three stored observations each, not 96 independent defects.
|
||||
The real Game unit fails old at tag= selecting B instead of A after UUID/tag/named-tag
|
||||
admissions pass. It uses actual summon/tag commands and Game::select, and compares sets
|
||||
to avoid relying on entity order.
|
||||
|
||||
All 12 full-diagnostic residuals persist unchanged by this fix. The table gives exact
|
||||
field/text differences and callbacks; it is a readable report, not the comparator.
|
||||
The comparator retains complete raw JSON, including all styled text fragments. Complete
|
||||
expected/actual records for all old/fixed differences remain in strict-report.json.
|
||||
|
||||
| Label | Expected Java | Actual native | Reason outside H267 |
|
||||
|---|---|---|---|
|
||||
| `clock-start` | `The game time is 321 tick(s)`; callback `[true,321]` | `The game time is 0 tick(s)`; callback `[true,0]` | Absolute startup clocks; strict relative deltas are0 |
|
||||
| `initial-clock-raw` | `The game time is 321 tick(s)`; callback `[true,321]` | `The game time is 0 tick(s)`; callback `[true,0]` | Absolute startup clocks; strict relative deltas are0 |
|
||||
| `initial-a-body` | `attributes: []`; callback `[true,1]` | `attributes: [{id:"minecraft:movement_speed",base:0.25d}]`; callback `[true,1]` | Extra default movement-speed attribute serialization |
|
||||
| `initial-b-body` | `attributes: []`; callback `[true,1]` | `attributes: [{id:"minecraft:movement_speed",base:0.25d}]`; callback `[true,1]` | Extra default movement-speed attribute serialization |
|
||||
| `swapped-clock-raw` | `The game time is 321 tick(s)`; callback `[true,321]` | `The game time is 0 tick(s)`; callback `[true,0]` | Absolute startup clocks; strict relative deltas are0 |
|
||||
| `swapped-a-body` | `attributes: []`; callback `[true,1]` | `attributes: [{id:"minecraft:movement_speed",base:0.25d}]`; callback `[true,1]` | Extra default movement-speed attribute serialization |
|
||||
| `swapped-b-Tags` | `Found no elements matching Tags`; callback `[false,0]` | `Pig has the following entity data: []`; callback `[true,0]` | Saved empty-Tags representation after actual last-tag removal |
|
||||
| `swapped-b-body` | `attributes: []`; callback `[true,1]` | `attributes: [{id:"minecraft:movement_speed",base:0.25d}]`; also `Tags:[]`; callback `[true,1]` | Extra default movement-speed attribute serialization and saved empty Tags |
|
||||
| `restored-clock-raw` | `The game time is 321 tick(s)`; callback `[true,321]` | `The game time is 0 tick(s)`; callback `[true,0]` | Absolute startup clocks; strict relative deltas are0 |
|
||||
| `restored-a-Tags` | `Found no elements matching Tags`; callback `[false,0]` | `Pig has the following entity data: []`; callback `[true,0]` | Saved empty-Tags representation after actual last-tag removal |
|
||||
| `restored-a-body` | `attributes: []`; callback `[true,1]` | `attributes: [{id:"minecraft:movement_speed",base:0.25d}]`; also `Tags:[]`; callback `[true,1]` | Extra default movement-speed attribute serialization and saved empty Tags |
|
||||
| `restored-b-body` | `attributes: []`; callback `[true,1]` | `attributes: [{id:"minecraft:movement_speed",base:0.25d}]`; callback `[true,1]` | Extra default movement-speed attribute serialization |
|
||||
|
||||
These whole-body differences are actual extra serialized fields, not merely insertion
|
||||
order. No attribute/default codec, saved Tags or clock repair is included. Exact full
|
||||
bodies/styles remain raw. No full437-green or all-entity-class runtime claim is made.
|
||||
|
||||
## Local readiness and source limits
|
||||
|
||||
The unchanged real Game regression passes fixed (one resource-backed test), after the
|
||||
intended old failure. Required `pr_ready.py --quick` ran once on the final gameplay/test
|
||||
patch: formatting, workspace and headless clippy, 1,015 Rust tests, doc tests and public
|
||||
tree pass. Python ran686 tests with four existing macOS packaging failures and one skip:
|
||||
the signature mock lacks the ARM64 archive, and optional-archive masks0/1/2 encounter
|
||||
unset Bash3.2 arrays. Those files are unchanged. The focused actual Game/Java programs
|
||||
were run explicitly; `--quick` does not claim the broad parity suite. Evidence-only docs
|
||||
were finalized after that check, then public-tree and whitespace checks were repeated.
|
||||
|
||||
A final actual main pull reached `3d33b0299d5c80769300d8b33dafb389ac9e7656`.
|
||||
Only launcher/cosmetics files changed after the frozen f00 base. Gameplay/selector/driver
|
||||
sources are unchanged, so no repeated same-base proof or broader latest-main runtime
|
||||
claim is made. H264/#10130 and H266/#10219 still need their independent routing/criterion
|
||||
branches and all three regression modules preserved when combined.
|
||||
@@ -0,0 +1,77 @@
|
||||
"""Fixed-UUID pigs; actual tag edits, no entity-order or observation-tick dependency."""
|
||||
|
||||
ACTORS = [('a', '0000010b-0000-0000-0000-000000000001', 1, 4.5),
|
||||
('b', '0000010b-0000-0000-0000-000000000002', 2, 8.5)]
|
||||
|
||||
|
||||
def empty_tags(full=False):
|
||||
ops = []
|
||||
|
||||
def cmd(text, label, result=None, callback=None, admission=True, diagnostic=False):
|
||||
op = {'op': 'cmd', 'cmd': text, 'label': label, 'admission': admission}
|
||||
if result is not None:
|
||||
op['expect_result'] = result
|
||||
if callback is not None:
|
||||
op['expect_callback'] = callback
|
||||
if diagnostic and not full:
|
||||
op.update(record=False, trace=True)
|
||||
ops.append(op)
|
||||
|
||||
cmd('/tick freeze', 'freeze')
|
||||
for rule, value in [('spawn_mobs', 'false'), ('advance_time', 'false'), ('random_tick_speed', '0')]:
|
||||
cmd(f'/gamerule {rule} {value}', 'rule-' + rule)
|
||||
cmd('/forceload add 0 0', 'force', 1)
|
||||
ops.append({'op': 'settle', 'required_chunks': [[0, 0]]})
|
||||
cmd('/fill 0 99 0 15 99 15 stone', 'floor', 256)
|
||||
cmd('/tp @s 4.5 100 12.5 0 0', 'owner-position', 1)
|
||||
cmd('/gamemode creative @s', 'owner-mode')
|
||||
cmd('/scoreboard objectives add h267 dummy', 'objective')
|
||||
cmd('/scoreboard objectives add h267_clock dummy', 'clock-objective')
|
||||
for actor, uuid, n, x in ACTORS:
|
||||
tags = ',Tags:["audit"]' if actor == 'b' else ''
|
||||
cmd(f'/summon pig {x} 100 4.5 {{UUID:[I;267,0,0,{n}],NoAI:1b,NoGravity:1b{tags}}}',
|
||||
actor + '-summon', 1)
|
||||
cmd('/execute store result score start h267_clock run time query gametime',
|
||||
'clock-start', diagnostic=True)
|
||||
|
||||
for phase, tagged_a in [('initial', False), ('swapped', True), ('restored', False)]:
|
||||
if phase != 'initial':
|
||||
for actor, uuid, _, _ in ACTORS:
|
||||
tagged = tagged_a if actor == 'a' else not tagged_a
|
||||
cmd(f'/tag {uuid} {"add" if tagged else "remove"} audit',
|
||||
phase + '-' + actor + '-edit', 1)
|
||||
cmd('/execute if entity @s[type=player,name=Player]', phase + '-owner', 1)
|
||||
for field, result in [('UUID', 4), ('Health', 20), ('Pos', 3), ('playerGameType', 1)]:
|
||||
cmd(f'/data get entity @s {field}', phase + '-owner-' + field, result)
|
||||
cmd('/execute if loaded 4 100 4', phase + '-loaded', 1)
|
||||
cmd('/execute if block 4 99 4 stone', phase + '-floor', 1)
|
||||
cmd('/execute store result score now h267_clock run time query gametime',
|
||||
phase + '-clock-raw', diagnostic=True)
|
||||
cmd('/scoreboard players operation now h267_clock -= start h267_clock', phase + '-clock-delta-op', 0)
|
||||
cmd('/scoreboard players get now h267_clock', phase + '-clock-delta', 0)
|
||||
for actor, uuid, n, x in ACTORS:
|
||||
key = phase + '-' + actor
|
||||
tagged = tagged_a if actor == 'a' else not tagged_a
|
||||
cmd(f'/execute as {uuid} if entity @s[type=pig]', key + '-type', 1)
|
||||
for field, result in [('UUID', 4), ('Health', 10), ('Pos', 3), ('NoAI', 1), ('NoGravity', 1)]:
|
||||
cmd(f'/data get entity {uuid} {field}', key + '-' + field, result)
|
||||
cmd(f'/tag {uuid} list', key + '-live-tags', 1 if tagged else 0)
|
||||
cmd(f'/data get entity {uuid} Tags', key + '-Tags',
|
||||
result=1 if tagged else None, callback=None if tagged else [False, 0], diagnostic=True)
|
||||
# Keep the full body as raw diagnostics; typed scalar guards above are canonical.
|
||||
cmd(f'/data get entity {uuid}', key + '-body', diagnostic=True)
|
||||
for name, value, matches in [('empty', '', not tagged), ('nonempty', '!', tagged),
|
||||
('audit', 'audit', tagged), ('not_audit', '!audit', not tagged)]:
|
||||
for sign in ['if', 'unless']:
|
||||
label = key + '-' + name + '-' + sign
|
||||
take = matches if sign == 'if' else not matches
|
||||
for holder in ['result', 'success', 'branch']:
|
||||
cmd(f'/scoreboard players set {holder} h267 -99', label + '-reset-' + holder, -99)
|
||||
cmd(f'/execute as {uuid} store result score result h267 store success score success h267 '
|
||||
f'{sign} entity @s[tag={value}] run scoreboard players set branch h267 1',
|
||||
label + '-action', admission=name not in ('empty', 'nonempty'))
|
||||
ops[-1]['expect_results'] = [[True, 1]] if take else []
|
||||
for holder in ['result', 'success', 'branch']:
|
||||
cmd(f'/scoreboard players get {holder} h267', label + '-' + holder,
|
||||
1 if take else -99, admission=name not in ('empty', 'nonempty'))
|
||||
return ops
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Strict existing actual Game/Java command-route proof for empty tag selectors."""
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
import test_command_exec_parity as driver
|
||||
from selector_empty_tag_corpus import empty_tags
|
||||
|
||||
if __name__ == '__main__':
|
||||
full = '--full-diagnostic' in sys.argv
|
||||
if full:
|
||||
sys.argv.remove('--full-diagnostic')
|
||||
reference = None
|
||||
if '--reference' in sys.argv:
|
||||
i = sys.argv.index('--reference')
|
||||
reference = Path(sys.argv[i + 1]).resolve()
|
||||
del sys.argv[i:i + 2]
|
||||
if '--work-dir' not in sys.argv:
|
||||
sys.argv += ['--work-dir', str(driver.lab.LOCAL / 'selector-empty-tag-parity')]
|
||||
build = Path(sys.argv[sys.argv.index('--work-dir') + 1]).resolve()
|
||||
ops = empty_tags(full)
|
||||
driver.corpus_module.groups = lambda: [('fixed UUID empty tag selectors', ops)]
|
||||
sys.argv.append('--allow-gaps')
|
||||
driver.main()
|
||||
recorded = [op for op in ops if op['op'] == 'cmd' and op.get('record', True)]
|
||||
java = [json.loads(s) for s in (build / 'vanilla.jsonl').read_text().splitlines()]
|
||||
assert len(java) == len(recorded)
|
||||
for op, row in zip(recorded, java):
|
||||
if 'expect_results' in op:
|
||||
assert row['results'] == op['expect_results'], ('Java action/callback admission', op, row)
|
||||
if '--reference-only' not in sys.argv:
|
||||
source = reference or build / 'vanilla.jsonl'
|
||||
if reference and not full:
|
||||
assert source.read_bytes() == (build / 'vanilla.jsonl').read_bytes(), 'fresh Java bytes changed'
|
||||
expected = [json.loads(s) for s in source.read_text().splitlines()]
|
||||
native = [json.loads(s) for s in (build / 'rust.jsonl').read_text().splitlines()]
|
||||
assert len(expected) == len(native) == len(recorded)
|
||||
for op, a, b in zip(recorded, expected, native):
|
||||
if op['admission'] and not full:
|
||||
assert a == b, ('actual actor/state/control admission', op, a, b)
|
||||
differences = [{'label': op['label'], 'expected': a, 'actual': b}
|
||||
for op, a, b in zip(recorded, expected, native) if a != b]
|
||||
report = {'records': len(native), 'passed': len(native) - len(differences),
|
||||
'comparison': 'complete raw records, no masks/tolerances',
|
||||
'reference_sha256': hashlib.sha256(source.read_bytes()).hexdigest(),
|
||||
'fresh_java_sha256': hashlib.sha256((build / 'vanilla.jsonl').read_bytes()).hexdigest(),
|
||||
'differences': differences}
|
||||
(build / 'strict-report.json').write_text(json.dumps(report, indent=2) + '\n')
|
||||
print(f'Strict raw records: {report["passed"]}/{report["records"]}')
|
||||
if differences:
|
||||
raise SystemExit(1)
|
||||
Reference in new issue
Block a user