diff --git a/.github/workflows/flint.yml b/.github/workflows/flint.yml new file mode 100644 index 00000000000..9641f331f04 --- /dev/null +++ b/.github/workflows/flint.yml @@ -0,0 +1,160 @@ +name: Flint Tests + +on: + push: + branches: ['**'] + tags: ['**'] + pull_request: + + +env: + CARGO_TERM_COLOR: always + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout SteelMC + uses: actions/checkout@v4 + with: + path: SteelMC + + - name: Checkout flint-steel + uses: actions/checkout@v4 + with: + repository: FlintTestMC/flint-steel + ref: v1.1.1 + path: flint-steel + + - name: Clone FlintBenchmark + uses: actions/checkout@v4 + with: + repository: FlintTestMC/FlintBenchmark + path: FlintBenchmark + + - name: Setup Rust nightly + uses: dtolnay/rust-toolchain@nightly + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@v2 + with: + workspaces: | + flint-steel -> target + shared-key: "flint-steel" + cache-on-failure: true + save-if: ${{ github.ref == 'refs/heads/master' || github.event_name == 'pull_request' }} + + - name: Configure SteelMC patch for Flint + working-directory: flint-steel + run: | + mkdir -p .cargo + cat > .cargo/config.toml << 'EOF' + [patch."https://github.com/Steel-Foundation/SteelMC.git"] + steel-core = { path = "../SteelMC/steel-core", features = ["flint"] } + steel-protocol = { path = "../SteelMC/steel-protocol" } + steel-registry = { path = "../SteelMC/steel-registry" } + steel-utils = { path = "../SteelMC/steel-utils" } + EOF + + - name: Configure Flint via toml + working-directory: flint-steel + run: | + cat > flint.toml << 'EOF' + [filter] + implemented_only = true + + # [filter.tags] + # redstone = true + # walls = false + # fence = true + + EOF + + - name: Run flint-steel tests + id: tests + working-directory: flint-steel + continue-on-error: true + timeout-minutes: 15 + env: + TEST_PATH: ${{ github.workspace }}/FlintBenchmark/tests + run: cargo test --profile cicd --lib test_run_flint_selected -- --nocapture + + - name: Download baseline + id: baseline + uses: dawidd6/action-download-artifact@v6 + continue-on-error: true + with: + name: flint-baseline + branch: master + workflow: flint.yml + path: baseline + + - name: Check for regressions + id: regression + run: | + SUMMARY="flint-steel/log/flint_summary.json" + BASELINE="baseline/flint_baseline.json" + + if [ ! -f "$SUMMARY" ]; then + echo "::error::No flint_summary.json produced — tests likely failed to compile" + exit 1 + fi + + if [ ! -f "$BASELINE" ]; then + echo "No baseline found — skipping regression check" + exit 0 + fi + + # Find tests that passed in baseline but fail now + REGRESSIONS=$(jq -n \ + --slurpfile base "$BASELINE" \ + --slurpfile curr "$SUMMARY" \ + '($base[0] | map(select(.success == true)) | map(.name)) as $required | + [$curr[0][] | select(.success == false and IN(.name; $required[]))] | + map(.name)') + + if [ "$REGRESSIONS" != "[]" ]; then + echo "::error::Regressions detected: $REGRESSIONS" + echo "$REGRESSIONS" | jq -r '.[]' | while read -r name; do + echo " - $name" + done + exit 1 + fi + + echo "No regressions detected" + + - name: Merge and upload baseline + if: github.ref == 'refs/heads/master' && steps.regression.outcome == 'success' + run: | + SUMMARY="flint-steel/log/flint_summary.json" + BASELINE="baseline/flint_baseline.json" + MERGED="flint_baseline.json" + + if [ -f "$BASELINE" ]; then + # Merge: old baseline successes ∪ current successes + jq -n \ + --slurpfile base "$BASELINE" \ + --slurpfile curr "$SUMMARY" \ + '($base[0] + $curr[0]) | group_by(.name) | map({ + name: .[0].name, + ids: .[-1].ids, + success: (map(.success) | any) + })' > "$MERGED" + else + # First run — current results become the baseline + cp "$SUMMARY" "$MERGED" + fi + + - name: Upload baseline artifact + if: github.ref == 'refs/heads/master' && steps.regression.outcome == 'success' + uses: actions/upload-artifact@v4 + with: + name: flint-baseline + path: flint_baseline.json + + - name: Upload Flint summary + if: always() + uses: actions/upload-artifact@v4 + with: + name: flint-summary + path: flint-steel/log/flint_summary.json diff --git a/steel-core/Cargo.toml b/steel-core/Cargo.toml index b7c229432a9..34f40823831 100644 --- a/steel-core/Cargo.toml +++ b/steel-core/Cargo.toml @@ -10,6 +10,8 @@ build = "build/build.rs" #default = ["stand-alone"] stand-alone = [] slow_chunk_gen = [] +flint = [] + [dependencies] # Internal crates diff --git a/steel-core/src/behavior/block.rs b/steel-core/src/behavior/block.rs index 4ce3f9c9932..6efba5379bd 100644 --- a/steel-core/src/behavior/block.rs +++ b/steel-core/src/behavior/block.rs @@ -33,6 +33,14 @@ pub struct PickupResult { /// - Player interactions /// - State changes pub trait BlockBehavior: Send + Sync { + /// Returns the Rust type name of the concrete behavior implementation. + #[cfg(feature = "flint")] + #[must_use] + #[expect(clippy::absolute_paths, reason = "easier for features")] + fn type_name(&self) -> &'static str { + std::any::type_name::() + } + /// Called when a player uses an empty bucket on this block. /// /// Should: @@ -498,6 +506,13 @@ impl BlockBehaviorRegistry { self.behaviors[id].as_ref() } + /// Get all behaviors. + #[cfg(feature = "flint")] + #[must_use] + pub fn get_behaviors(&self) -> &[Box] { + &self.behaviors + } + /// Gets the behavior for a block by its ID. #[must_use] pub fn get_behavior_by_id(&self, id: usize) -> Option<&dyn BlockBehavior> { diff --git a/steel-core/src/behavior/item.rs b/steel-core/src/behavior/item.rs index 02491829307..aba3b27a817 100644 --- a/steel-core/src/behavior/item.rs +++ b/steel-core/src/behavior/item.rs @@ -13,6 +13,14 @@ use crate::behavior::{InteractionResult, UseItemContext, UseOnContext}; /// - Use in air /// - etc. pub trait ItemBehavior: Send + Sync { + /// Returns the Rust type name of the concrete behavior implementation. + #[cfg(feature = "flint")] + #[must_use] + #[expect(clippy::absolute_paths, reason = "easier for features")] + fn type_name(&self) -> &'static str { + std::any::type_name::() + } + /// Called when this item is used on a block. fn use_on(&self, _context: &mut UseOnContext) -> InteractionResult { InteractionResult::Pass @@ -64,6 +72,13 @@ impl ItemBehaviorRegistry { pub fn get_behavior_by_id(&self, id: usize) -> Option<&dyn ItemBehavior> { self.behaviors.get(id).map(AsRef::as_ref) } + + /// Get all behaviors. + #[cfg(feature = "flint")] + #[must_use] + pub fn get_behaviors(&self) -> &[Box] { + &self.behaviors + } } impl Default for ItemBehaviorRegistry {