Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 119 additions & 10 deletions flint-content/test_spec_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@
"maxItems": 3,
"description": "A 3D coordinate [x, y, z]"
},
"NumericCoordinate": {
"type": "array",
"items": { "type": "number" },
"minItems": 3,
"maxItems": 3,
"description": "A 3D coordinate [x, y, z] that may include fractional entity positions"
},
"Region": {
"type": "array",
"items": { "$ref": "#/$defs/Coordinate" },
Expand Down Expand Up @@ -268,6 +275,57 @@
},
"additionalProperties": false
},
"EntityCheck": {
"type": "object",
"required": ["entity_alias"],
"properties": {
"entity_alias": {
"type": "string",
"description": "Test-local entity alias. Use aliases created by summon, or implementation-defined aliases."
},
"is": {
"type": "string",
"description": "Optional expected entity type, e.g. 'minecraft:item'"
},
"exists": {
"type": "boolean",
"default": true,
"description": "Whether the entity is expected to exist"
},
"pos": {
"$ref": "#/$defs/NumericCoordinate",
"description": "Optional expected entity position"
},
"position_tolerance": {
"type": "number",
"minimum": 0,
"description": "Allowed distance from expected position"
},
"rot": {
"type": "array",
"items": { "type": "number" },
"minItems": 2,
"maxItems": 2,
"description": "Optional expected entity rotation [yaw, pitch]"
Comment thread
JunkyDeveloper marked this conversation as resolved.
},
"rotation_tolerance": {
"type": "number",
"minimum": 0,
"description": "Allowed absolute delta for each rotation component"
},
"nbt": {
"type": "object",
"additionalProperties": true,
"description": "Optional expected entity NBT fields. Object keys are queried as data paths, e.g. Item.id."
}
},
"additionalProperties": true
},
"EntityNbt": {
"type": "object",
"description": "Structured entity NBT fields. Values may be strings, numbers, booleans, arrays, or objects.",
"additionalProperties": true
},
"TimelineEntry": {
"type": "object",
"required": ["at", "do"],
Expand All @@ -278,7 +336,7 @@
},
"do": {
"type": "string",
"enum": ["place", "place_each", "fill", "remove", "assert", "use_item_on", "set_slot", "select_hotbar"],
"enum": ["place", "place_each", "fill", "remove", "summon", "assert", "tp", "interact", "set_slot", "select_hotbar"],
"description": "Type of action to perform"
}
},
Expand Down Expand Up @@ -364,6 +422,36 @@
"additionalProperties": false
}
},
{
"if": {
"properties": { "do": { "const": "summon" } },
"required": ["do"]
},
"then": {
"properties": {
"at": true,
"do": true,
"entity_alias": {
"type": "string",
"description": "Test-local alias assigned to the summoned entity"
},
"entity_type": {
"type": "string",
"description": "Minecraft entity type to summon, e.g. 'minecraft:item'"
},
"pos": {
"$ref": "#/$defs/NumericCoordinate",
"description": "Summon position [x, y, z]"
},
"nbt": {
"$ref": "#/$defs/EntityNbt",
"description": "Optional entity NBT. Implementations may inject alias metadata."
}
},
"required": ["entity_alias", "entity_type", "pos"],
"additionalProperties": true
}
},
{
"if": {
"properties": { "do": { "const": "assert" } },
Expand All @@ -378,7 +466,8 @@
"items": {
"oneOf": [
{ "$ref": "#/$defs/BlockCheck" },
{ "$ref": "#/$defs/InventoryCheck" }
{ "$ref": "#/$defs/InventoryCheck" },
{ "$ref": "#/$defs/EntityCheck" }
]
},
"description": "List of checks to perform"
Expand All @@ -390,27 +479,47 @@
},
{
"if": {
"properties": { "do": { "const": "use_item_on" } },
"properties": { "do": { "const": "tp" } },
"required": ["do"]
},
"then": {
"properties": {
"at": true,
"do": true,
"pos": {
"$ref": "#/$defs/Coordinate",
"description": "Position of the block to interact with"
"entity_alias": {
"type": "string",
"description": "Entity alias to teleport. Use 'player' for the backing player."
},
"face": {
"$ref": "#/$defs/BlockFace",
"description": "Face of the block to interact with"
"pos": {
"$ref": "#/$defs/NumericCoordinate",
"description": "Destination [x, y, z]"
},
"rot": {
"type": "array",
"items": { "type": "number" },
"minItems": 2,
"maxItems": 2,
"description": "Optional rotation [yaw, pitch]"
}
},
"required": ["entity_alias", "pos"],
"additionalProperties": false
}
},
{
"if": {
"properties": { "do": { "const": "interact" } },
"required": ["do"]
},
"then": {
"properties": {
"at": true,
"do": true,
"item": {
"type": "string",
"description": "Item to use (optional, uses player's active item if not specified)"
}
},
"required": ["pos", "face"],
"additionalProperties": false
}
},
Expand Down
2 changes: 2 additions & 0 deletions src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ mod tests {
}

#[test]
#[serial]
fn test_hash() {
let temp_dir = TempDir::new().unwrap();

Expand All @@ -428,6 +429,7 @@ mod tests {
}

#[test]
#[serial]
fn test_hash_ignore_not_json() {
let temp_dir = TempDir::new().unwrap();

Expand Down
60 changes: 56 additions & 4 deletions src/results.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::results::AssertionResult::Failure;
use crate::test_spec::Block;
use crate::test_spec::{Block, EntityCheck};
use crate::traits::EntityState;
use crate::{Item, PlayerSlot, format};
use serde::{Deserialize, Serialize};
use std::fmt::Display;
Expand All @@ -22,15 +23,20 @@ pub enum InfoType {
Blocks(Vec<Block>),
Item(Item),
Slot(PlayerSlot),
EntityCheck(Box<EntityCheck>),
EntityState(Box<EntityState>),
}

impl InfoType {
pub fn get_string(&self) -> Option<String> {
match self {
InfoType::String(s) => Some(s.clone()),
InfoType::Block(_) | InfoType::Blocks(_) | InfoType::Item(_) | InfoType::Slot(_) => {
None
}
InfoType::Block(_)
| InfoType::Blocks(_)
| InfoType::Item(_)
| InfoType::Slot(_)
| InfoType::EntityCheck(_)
| InfoType::EntityState(_) => None,
}
}
fn type_string_generator(val: &InfoType) -> String {
Expand All @@ -44,6 +50,8 @@ impl InfoType {
.join(" or "),
InfoType::Slot(slot) => slot.to_string(),
InfoType::Item(item) => item.to_command(),
InfoType::EntityCheck(entity) => format!("{entity:?}"),
InfoType::EntityState(entity) => format!("{entity:?}"),
}
}
}
Expand Down Expand Up @@ -81,6 +89,50 @@ pub struct AssertFailure {
pub actual: InfoType,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssertEntityFail {
pub tick: u32,
pub expected: EntityCheck,
pub actual: EntityState,
}

impl AssertEntityFail {
pub fn new(tick: u32, expected: &EntityCheck, actual: &EntityState) -> Self {
Self {
tick,
expected: expected.clone(),
actual: actual.clone(),
}
}
}

impl From<AssertEntityFail> for AssertFailure {
fn from(failure: AssertEntityFail) -> Self {
let position = failure
.expected
.pos
.map(|pos| {
[
pos[0].floor() as i32,
pos[1].floor() as i32,
pos[2].floor() as i32,
]
})
.unwrap_or([0, 0, 0]);
Self {
tick: failure.tick,
error_message: format!(
"Entity mismatch for alias '{}'",
failure.expected.entity_alias
),
position: AssertPosition::from_array(position),
execution_time_ms: None,
expected: InfoType::EntityCheck(Box::new(failure.expected)),
actual: InfoType::EntityState(Box::new(failure.actual)),
}
}
}

#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub enum AssertPosition {
Coordinate { x: i32, y: i32, z: i32 },
Expand Down
Loading