From be88337f7b0a677b9aeaf85370fa60367df5cc0b Mon Sep 17 00:00:00 2001 From: Siyuan Feng <25500082+Hzfengsy@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:24:50 +0800 Subject: [PATCH 1/2] chore(tests): add pytest.main entrypoint to all test files Ensure every test file can be run directly via `python test_file.py`. Add `if __name__ == "__main__": pytest.main([__file__, "-v"])` to all test files that were missing it. Replace unittest.TestCase usage with plain pytest classes. Normalize import ordering to place pytest before local imports. Also update github-pr skill to detect zero-commits-ahead branches that need a new branch before creating a PR. --- .claude/skills/github-pr/SKILL.md | 65 ++++++++++++++----- .../codegen/test_add_mul_orch_cce_codegen.py | 5 ++ tests/st/harness/core/test_runner.py | 5 ++ tests/st/runtime/test_dag.py | 5 ++ tests/st/runtime/test_elementwise.py | 4 ++ tests/st/runtime/test_matmul.py | 5 ++ tests/ut/backend/test_backend.py | 5 ++ tests/ut/backend/test_backend_910b.py | 5 ++ tests/ut/codegen/test_cce_codegen.py | 5 ++ .../ut/codegen/test_orchestration_codegen.py | 5 ++ tests/ut/codegen/test_pto_codegen.py | 5 ++ tests/ut/codegen/test_pto_codegen_ops.py | 9 ++- tests/ut/codegen/test_type_converter.py | 4 ++ tests/ut/ir/high_level/test_function.py | 4 ++ tests/ut/ir/high_level/test_program.py | 4 ++ tests/ut/ir/operators/test_operator_spans.py | 4 ++ tests/ut/ir/statements/test_eval_stmt.py | 6 +- tests/ut/ir/statements/test_scope_stmt.py | 5 ++ tests/ut/ir/statements/test_seq_stmts.py | 4 ++ tests/ut/ir/statements/test_while_stmt.py | 4 ++ .../ir/transforms/test_basic_memory_reuse.py | 5 ++ .../test_convert_tensor_to_block_ops.py | 5 ++ .../transforms/test_flatten_call_expr_pass.py | 3 +- .../test_flatten_single_stmt_pass.py | 5 ++ tests/ut/ir/transforms/test_hash.py | 5 ++ tests/ut/ir/transforms/test_init_memref.py | 5 ++ tests/ut/ir/transforms/test_insert_sync.py | 5 ++ tests/ut/ir/transforms/test_ir_property.py | 5 ++ .../test_normalize_stmt_structure_pass.py | 5 ++ .../transforms/test_outline_incore_scopes.py | 5 ++ .../transforms/test_parent_stmt_analysis.py | 5 ++ tests/ut/ir/transforms/test_pass_manager.py | 5 ++ tests/ut/ir/transforms/test_pass_pipeline.py | 4 ++ .../ut/ir/transforms/test_type_check_pass.py | 8 +-- .../test_verify_no_nested_call_pass.py | 3 +- tests/ut/language/parser/test_error_cases.py | 4 ++ .../ut/language/parser/test_expr_evaluator.py | 4 ++ .../parser/test_printer_integration.py | 5 ++ .../ut/language/parser/test_scope_manager.py | 4 ++ .../ut/language/parser/test_scope_parsing.py | 5 ++ tests/ut/language/parser/test_span_tracker.py | 5 ++ tests/ut/language/parser/test_text_parser.py | 4 ++ tests/ut/language/parser/test_tuple_syntax.py | 5 ++ .../ut/language/parser/test_type_resolver.py | 4 ++ 44 files changed, 240 insertions(+), 31 deletions(-) diff --git a/.claude/skills/github-pr/SKILL.md b/.claude/skills/github-pr/SKILL.md index 0869e16e..9c65826f 100644 --- a/.claude/skills/github-pr/SKILL.md +++ b/.claude/skills/github-pr/SKILL.md @@ -5,20 +5,54 @@ description: Create a GitHub pull request after committing, rebasing, and pushin # PyPTO GitHub Pull Request Workflow -## Prerequisites +## Workflow Steps -⚠️ **MANDATORY**: Use the `/git-commit` skill to commit all changes. This runs the full code review, testing, and linting workflow. Do not commit directly. +1. **Prepare branch and commit** (if on main or uncommitted changes) +2. Check for existing PR (exit if found) +3. Fetch upstream changes +4. Rebase onto upstream/main +5. Resolve conflicts if needed +6. Push to fork with `--force-with-lease` +7. Create PR using gh CLI -## Workflow Steps +## Step 1: Prepare Branch and Commit + +**Check current state:** + +```bash +BRANCH_NAME=$(git branch --show-current) +git status --porcelain # Check for uncommitted changes +git fetch upstream 2>/dev/null || git fetch origin # Fetch latest +git rev-list HEAD --not upstream/main --count # Commits ahead of upstream +``` + +A branch "needs a new branch" when it is effectively on main — either the branch name is `main`/`master`, **or** it has zero commits ahead of upstream/main (e.g., a local branch that was never diverged). + +**Decision logic:** + +| Needs new branch? | Uncommitted changes? | Action | +| ----------------- | -------------------- | ------ | +| Yes | Yes | Create new branch, then commit via `/git-commit` | +| Yes | No | Error — nothing to PR. Tell user to make changes first | +| No | Yes | Commit on current branch via `/git-commit` | +| No | No | Skip — already committed on a feature branch | + +**If a new branch is needed:** + +1. Ask the user for a branch name (suggest one based on the changes) +2. Create and switch to the new branch: + +```bash +git checkout -b +``` + +1. Commit via `/git-commit` skill (mandatory — runs code review, testing, linting) + +**If on an existing feature branch with uncommitted changes:** -1. Check for existing PR (exit if found) -2. Fetch upstream changes -3. Rebase onto upstream/main -4. Resolve conflicts if needed -5. Push to fork with `--force-with-lease` -6. Create PR using gh CLI +Commit via `/git-commit` skill before proceeding. -## Step 1: Check for Existing PR +## Step 2: Check for Existing PR ```bash BRANCH_NAME=$(git branch --show-current) @@ -27,14 +61,14 @@ gh pr list --head "$BRANCH_NAME" --state open **If PR exists**: Display with `gh pr view` and exit immediately. -## Step 2: Fetch Upstream +## Step 3: Fetch Upstream ```bash git remote add upstream https://github.com/hw-native-sys/pypto.git # If needed git fetch upstream ``` -## Step 3: Rebase +## Step 4: Rebase ```bash git rebase upstream/main # Or user-specified branch @@ -50,7 +84,7 @@ git rebase --continue # If stuck: git rebase --abort ``` -## Step 4: Push +## Step 5: Push ```bash # First push @@ -62,7 +96,7 @@ git push --force-with-lease origin BRANCH_NAME ⚠️ **Use `--force-with-lease`** - safer than `--force`, fails if remote has unexpected changes. -## Step 5: Create PR +## Step 6: Create PR **Check gh CLI**: @@ -112,8 +146,9 @@ EOF ## Checklist +- [ ] Branch prepared (created from main if needed) +- [ ] Changes committed via `/git-commit` (if uncommitted changes existed) - [ ] No existing PR for branch (exit if found) -- [ ] Changes committed via git-commit - [ ] Fetched upstream and rebased successfully - [ ] Conflicts resolved - [ ] Pushed with `--force-with-lease` diff --git a/tests/st/codegen/test_add_mul_orch_cce_codegen.py b/tests/st/codegen/test_add_mul_orch_cce_codegen.py index 6a5a8e9e..71217878 100644 --- a/tests/st/codegen/test_add_mul_orch_cce_codegen.py +++ b/tests/st/codegen/test_add_mul_orch_cce_codegen.py @@ -23,6 +23,7 @@ import os import pypto.language as pl +import pytest from pypto import DataType, ir from pypto.backend import BackendType @@ -185,3 +186,7 @@ def test_add_mul_orch_cce_codegen(tmp_path): passes_dump_dir = os.path.join(output_dir, "passes_dump") assert os.path.exists(passes_dump_dir), "Passes dump directory should exist" assert os.path.isdir(passes_dump_dir), "Passes dump path should be a directory" + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/st/harness/core/test_runner.py b/tests/st/harness/core/test_runner.py index a640f307..ea0e6dea 100644 --- a/tests/st/harness/core/test_runner.py +++ b/tests/st/harness/core/test_runner.py @@ -25,6 +25,7 @@ from datetime import datetime from pathlib import Path +import pytest from pypto.backend import BackendType, set_backend_type from harness.adapters.golden_generator import GoldenGenerator @@ -273,3 +274,7 @@ def summary(self, results: dict[str, TestResult]) -> str: lines.append(f" - {name}: {result.error}") return "\n".join(lines) + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/st/runtime/test_dag.py b/tests/st/runtime/test_dag.py index ffed0ef2..3fb712d6 100644 --- a/tests/st/runtime/test_dag.py +++ b/tests/st/runtime/test_dag.py @@ -17,6 +17,7 @@ from typing import Any import pypto.language as pl +import pytest from harness.core.harness import DataType, PTOTestCase, TensorSpec @@ -132,3 +133,7 @@ def test_vector_dag_128x128(self, test_runner): test_case = TestVectorDAG() result = test_runner.run(test_case) assert result.passed, f"Test failed for vector DAG: {result.error}" + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/st/runtime/test_elementwise.py b/tests/st/runtime/test_elementwise.py index cef04239..9b9c48cb 100644 --- a/tests/st/runtime/test_elementwise.py +++ b/tests/st/runtime/test_elementwise.py @@ -319,3 +319,7 @@ def test_tile_add_ptoas_strategy(self, test_runner): test_case = TestTileAddWithPTOAS() result = test_runner.run(test_case) assert result.passed, f"Test failed: {result.error}" + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/st/runtime/test_matmul.py b/tests/st/runtime/test_matmul.py index 5f5bc807..0a3578b7 100644 --- a/tests/st/runtime/test_matmul.py +++ b/tests/st/runtime/test_matmul.py @@ -17,6 +17,7 @@ from typing import Any import pypto.language as pl +import pytest import torch from harness.core.harness import DataType, PTOTestCase, TensorSpec @@ -74,3 +75,7 @@ def test_matmul_64x64(self, test_runner): test_case = TestMatmul() result = test_runner.run(test_case) assert result.passed, f"Test failed: {result.error}" + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/backend/test_backend.py b/tests/ut/backend/test_backend.py index 825b06e5..4c504213 100644 --- a/tests/ut/backend/test_backend.py +++ b/tests/ut/backend/test_backend.py @@ -9,6 +9,7 @@ """Tests for Backend, SoC construction, and serialization.""" +import pytest from pypto import ir from pypto.backend import ( Cluster, @@ -115,3 +116,7 @@ def test_soc_convenience_constructor(self): assert soc.total_die_count() == 2 assert soc.total_cluster_count() == 6 assert soc.total_core_count() == 12 + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/backend/test_backend_910b.py b/tests/ut/backend/test_backend_910b.py index 40bf3ab5..04eadfce 100644 --- a/tests/ut/backend/test_backend_910b.py +++ b/tests/ut/backend/test_backend_910b.py @@ -12,6 +12,7 @@ import tempfile from pathlib import Path +import pytest from pypto import ir from pypto.backend import Backend910B_CCE, Backend910B_PTO @@ -162,3 +163,7 @@ def test_export_backend(self): assert Path(temp_path).exists() finally: Path(temp_path).unlink() + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/codegen/test_cce_codegen.py b/tests/ut/codegen/test_cce_codegen.py index 54fa211e..fad540e7 100644 --- a/tests/ut/codegen/test_cce_codegen.py +++ b/tests/ut/codegen/test_cce_codegen.py @@ -10,6 +10,7 @@ """Unit tests for CCECodegen class.""" import pypto.language as pl +import pytest from pypto import DataType, backend, codegen, ir from pypto.backend import BackendType from pypto.ir.builder import IRBuilder @@ -365,3 +366,7 @@ def test_matmul_acc( # Verify both TMATMUL and TMATMUL_ACC are generated assert "TMATMUL(" in code assert "TMATMUL_ACC(" in code + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/codegen/test_orchestration_codegen.py b/tests/ut/codegen/test_orchestration_codegen.py index 77d368ec..7124dd24 100644 --- a/tests/ut/codegen/test_orchestration_codegen.py +++ b/tests/ut/codegen/test_orchestration_codegen.py @@ -13,6 +13,7 @@ import textwrap import pypto.language as pl +import pytest from pypto import backend, codegen from pypto.backend import BackendType @@ -887,3 +888,7 @@ def orch_dim( # tensor.dim generates int64_t assignment assert "int64_t d0 = 64" in code + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/codegen/test_pto_codegen.py b/tests/ut/codegen/test_pto_codegen.py index e99e79f2..ff8b7fdb 100644 --- a/tests/ut/codegen/test_pto_codegen.py +++ b/tests/ut/codegen/test_pto_codegen.py @@ -20,6 +20,7 @@ """ import pypto.language as pl +import pytest from pypto import backend, codegen from pypto.backend import BackendType from pypto.ir import OptimizationStrategy, PassManager @@ -367,3 +368,7 @@ def test_func(self, a: pl.Tensor[[32, 32], pl.FP32], b: pl.Tensor[[32, 32], pl.F assert "func.func @test_func" in code1 assert "func.func @test_func" in code2 assert code1 == code2 # Should produce identical output + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/codegen/test_pto_codegen_ops.py b/tests/ut/codegen/test_pto_codegen_ops.py index f1368a00..18ff29e8 100644 --- a/tests/ut/codegen/test_pto_codegen_ops.py +++ b/tests/ut/codegen/test_pto_codegen_ops.py @@ -15,9 +15,8 @@ and verifies the generated orchestration code. """ -import unittest - import pypto.language as pl +import pytest from pypto import DataType, backend, codegen, ir from pypto.backend import BackendType from pypto.ir.pass_manager import OptimizationStrategy, PassManager @@ -481,8 +480,8 @@ def build_block_ops_test_program(dtype: DataType = DataType.FP32): return BlockOperationsTest -class Test910BBlockOpsCodegen(unittest.TestCase): - """Unit tests for 910B PTO backend block-level operations code generation.""" +class Test910BBlockOpsCodegen: + """Tests for 910B PTO backend block-level operations code generation.""" def test_block_ops_codegen(self): """Test code generation for all block-level operations.""" @@ -532,4 +531,4 @@ def test_block_ops_codegen(self): if __name__ == "__main__": - unittest.main() + pytest.main([__file__, "-v"]) diff --git a/tests/ut/codegen/test_type_converter.py b/tests/ut/codegen/test_type_converter.py index 64ae3309..50c047f0 100644 --- a/tests/ut/codegen/test_type_converter.py +++ b/tests/ut/codegen/test_type_converter.py @@ -107,3 +107,7 @@ def test_convert_event_id_invalid(self, invalid_id): converter = codegen.TypeConverter() with pytest.raises(ValueError, match=rf"Event ID must be in range \[0, 7\].*got {invalid_id}"): converter.ConvertEventId(invalid_id) + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/ir/high_level/test_function.py b/tests/ut/ir/high_level/test_function.py index a02369a6..b8ae9dc6 100644 --- a/tests/ut/ir/high_level/test_function.py +++ b/tests/ut/ir/high_level/test_function.py @@ -431,3 +431,7 @@ def test_function_different_body_types_not_equal(self): func2 = ir.Function("test_func", [x], [ir.ScalarType(dtype)], body2, span) assert not ir.structural_equal(func1, func2) + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/ir/high_level/test_program.py b/tests/ut/ir/high_level/test_program.py index cb24b275..bb2ae2e3 100644 --- a/tests/ut/ir/high_level/test_program.py +++ b/tests/ut/ir/high_level/test_program.py @@ -349,3 +349,7 @@ def test_program_different_from_base_irnode_not_equal(self): # Compare with a Var (different IRNode type) assert not ir.structural_equal(program, x) + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/ir/operators/test_operator_spans.py b/tests/ut/ir/operators/test_operator_spans.py index f8dba945..861bc2de 100644 --- a/tests/ut/ir/operators/test_operator_spans.py +++ b/tests/ut/ir/operators/test_operator_spans.py @@ -331,3 +331,7 @@ def test_all_reverse_operators(self): f"Reverse operator {op_name} failed" ) assert result.span.begin_line > 0, f"Reverse operator {op_name} has invalid line number" + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/ir/statements/test_eval_stmt.py b/tests/ut/ir/statements/test_eval_stmt.py index 792b1ed1..b06d933f 100644 --- a/tests/ut/ir/statements/test_eval_stmt.py +++ b/tests/ut/ir/statements/test_eval_stmt.py @@ -6,7 +6,7 @@ # INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. # See LICENSE in the root of the software repository for the full text of the License. # ----------------------------------------------------------------------------------------------------------- - +import pytest from pypto import ir @@ -79,3 +79,7 @@ def test_sync_ops_enum_usage(): ir.create_op_call( "system.sync_src", [], {"set_pipe": ir.PipeType.MTE2, "wait_pipe": ir.PipeType.V, "event_id": 0}, span ) + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/ir/statements/test_scope_stmt.py b/tests/ut/ir/statements/test_scope_stmt.py index 40632950..00a636c1 100644 --- a/tests/ut/ir/statements/test_scope_stmt.py +++ b/tests/ut/ir/statements/test_scope_stmt.py @@ -10,6 +10,7 @@ """Unit tests for ScopeStmt class.""" import pypto.language as pl +import pytest from pypto import DataType, ir @@ -60,3 +61,7 @@ def main(self, x: pl.Tensor[[64], pl.FP32]) -> pl.Tensor[[64], pl.FP32]: # Print and verify it contains "with pl.incore():" printed = ir.python_print(TestProgram) assert "with pl.incore():" in printed + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/ir/statements/test_seq_stmts.py b/tests/ut/ir/statements/test_seq_stmts.py index 1d042eab..13935f82 100644 --- a/tests/ut/ir/statements/test_seq_stmts.py +++ b/tests/ut/ir/statements/test_seq_stmts.py @@ -271,3 +271,7 @@ def test_seq_stmts_structural_equal_multiple_statements(self): seq_stmts2 = ir.SeqStmts([assign2_1, assign2_2, assign2_3], span) ir.assert_structural_equal(seq_stmts1, seq_stmts2, enable_auto_mapping=True) + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/ir/statements/test_while_stmt.py b/tests/ut/ir/statements/test_while_stmt.py index d1417aa0..475356c6 100644 --- a/tests/ut/ir/statements/test_while_stmt.py +++ b/tests/ut/ir/statements/test_while_stmt.py @@ -142,3 +142,7 @@ def test_while_stmt_structural_hash(self): condition2 = ir.Lt(x, ir.ConstInt(20, dtype, span), dtype, span) while_stmt3 = ir.WhileStmt(condition2, [], assign, [], span) assert ir.structural_hash(while_stmt1) != ir.structural_hash(while_stmt3) + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/ir/transforms/test_basic_memory_reuse.py b/tests/ut/ir/transforms/test_basic_memory_reuse.py index 14deeaff..f89a39db 100644 --- a/tests/ut/ir/transforms/test_basic_memory_reuse.py +++ b/tests/ut/ir/transforms/test_basic_memory_reuse.py @@ -10,6 +10,7 @@ """Tests for BasicMemoryReusePass using @pl.program with pl.Tile type.""" import pypto.language as pl +import pytest from pypto import ir, passes from pypto.ir.pass_manager import OptimizationStrategy, PassManager @@ -430,3 +431,7 @@ def main( _assert_shares_memref(func, "tile_a", "_tile_b") # tile_d should reuse the shared buffer (either tile_a or tile_b, they're the same) _assert_shares_memref(func, "tile_d", "tile_a") + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/ir/transforms/test_convert_tensor_to_block_ops.py b/tests/ut/ir/transforms/test_convert_tensor_to_block_ops.py index 53ceac77..a8eaf56c 100644 --- a/tests/ut/ir/transforms/test_convert_tensor_to_block_ops.py +++ b/tests/ut/ir/transforms/test_convert_tensor_to_block_ops.py @@ -10,6 +10,7 @@ """Unit tests for ConvertTensorToBlockOps pass.""" import pypto.language as pl +import pytest from pypto import ir, passes @@ -415,3 +416,7 @@ def main(self, x: pl.Tensor[[64], pl.FP32], n: pl.Scalar[pl.INT64]) -> pl.Tensor After = passes.convert_tensor_to_block_ops()(Before) ir.assert_structural_equal(After, Expected) + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/ir/transforms/test_flatten_call_expr_pass.py b/tests/ut/ir/transforms/test_flatten_call_expr_pass.py index 695953f9..780270bf 100644 --- a/tests/ut/ir/transforms/test_flatten_call_expr_pass.py +++ b/tests/ut/ir/transforms/test_flatten_call_expr_pass.py @@ -17,6 +17,7 @@ """ import pypto.language as pl +import pytest from pypto import ir, passes @@ -580,6 +581,4 @@ def main(self, x: pl.Tensor[[64], pl.FP32]) -> pl.Tensor[[64], pl.FP32]: if __name__ == "__main__": - import pytest - pytest.main([__file__, "-v"]) diff --git a/tests/ut/ir/transforms/test_flatten_single_stmt_pass.py b/tests/ut/ir/transforms/test_flatten_single_stmt_pass.py index 077274c9..a58031d6 100644 --- a/tests/ut/ir/transforms/test_flatten_single_stmt_pass.py +++ b/tests/ut/ir/transforms/test_flatten_single_stmt_pass.py @@ -16,6 +16,7 @@ with expected IR via assert_structural_equal. """ +import pytest from pypto import DataType, ir, passes @@ -353,3 +354,7 @@ def test_idempotence(): # Apply pass again and verify idempotence After2 = passes.flatten_single_stmt()(After) ir.assert_structural_equal(After2, Expected, enable_auto_mapping=True) + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/ir/transforms/test_hash.py b/tests/ut/ir/transforms/test_hash.py index e16a4e7a..d4947db1 100644 --- a/tests/ut/ir/transforms/test_hash.py +++ b/tests/ut/ir/transforms/test_hash.py @@ -9,6 +9,7 @@ """Unit tests for IR structural hash functionality.""" +import pytest from pypto import DataType, ir @@ -288,3 +289,7 @@ def test_yield_stmt_empty_vs_non_empty_hash(self): hash1 = ir.structural_hash(yield_stmt1) hash2 = ir.structural_hash(yield_stmt2) assert hash1 != hash2 + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/ir/transforms/test_init_memref.py b/tests/ut/ir/transforms/test_init_memref.py index bb5402a0..7f811062 100644 --- a/tests/ut/ir/transforms/test_init_memref.py +++ b/tests/ut/ir/transforms/test_init_memref.py @@ -10,6 +10,7 @@ """Tests for InitMemRefPass.""" import pypto.language as pl +import pytest from pypto import DataType, ir, passes from pypto.ir import MemorySpace from pypto.ir.op import block @@ -190,3 +191,7 @@ def main( Expected = ir.Program([expected_func], "test_program", span) ir.assert_structural_equal(After, Expected, enable_auto_mapping=True) + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/ir/transforms/test_insert_sync.py b/tests/ut/ir/transforms/test_insert_sync.py index 66ce985c..7999dba3 100644 --- a/tests/ut/ir/transforms/test_insert_sync.py +++ b/tests/ut/ir/transforms/test_insert_sync.py @@ -9,6 +9,7 @@ """Tests for InsertSyncPass.""" +import pytest from pypto import DataType, backend, ir, passes from pypto.backend import BackendType from pypto.ir.op import block @@ -971,3 +972,7 @@ def test_insert_sync_forstmt_cross_iteration(): assert sync_src_count == 0, f"Expected exactly 0 sync_src, got {sync_src_count}" assert sync_dst_count == 0, f"Expected exactly 0 sync_dst, got {sync_dst_count}" assert bar_v_count == 2, f"Expected exactly 2 bar_v, got {bar_v_count}" + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/ir/transforms/test_ir_property.py b/tests/ut/ir/transforms/test_ir_property.py index c2b7e76f..d195953b 100644 --- a/tests/ut/ir/transforms/test_ir_property.py +++ b/tests/ut/ir/transforms/test_ir_property.py @@ -9,6 +9,7 @@ """Unit tests for IRProperty and IRPropertySet.""" +import pytest from pypto import passes @@ -194,3 +195,7 @@ def test_run_verifier_no_properties(self): p = passes.run_verifier() assert p.get_required_properties().empty() assert p.get_produced_properties().empty() + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/ir/transforms/test_normalize_stmt_structure_pass.py b/tests/ut/ir/transforms/test_normalize_stmt_structure_pass.py index 57c037b0..7d6cf80c 100644 --- a/tests/ut/ir/transforms/test_normalize_stmt_structure_pass.py +++ b/tests/ut/ir/transforms/test_normalize_stmt_structure_pass.py @@ -18,6 +18,7 @@ with expected IR via assert_structural_equal. """ +import pytest from pypto import DataType, ir, passes @@ -230,3 +231,7 @@ def test_idempotence(): # Apply pass again and verify idempotence After2 = passes.normalize_stmt_structure()(After) ir.assert_structural_equal(After2, Expected, enable_auto_mapping=True) + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/ir/transforms/test_outline_incore_scopes.py b/tests/ut/ir/transforms/test_outline_incore_scopes.py index a6987316..5b0a2d50 100644 --- a/tests/ut/ir/transforms/test_outline_incore_scopes.py +++ b/tests/ut/ir/transforms/test_outline_incore_scopes.py @@ -10,6 +10,7 @@ """Unit tests for OutlineIncoreScopes pass.""" import pypto.language as pl +import pytest from pypto import ir, passes @@ -416,3 +417,7 @@ def main(self, x: pl.Tensor[[64], pl.FP32]) -> pl.Tensor[[64], pl.FP32]: Expected = passes.convert_to_ssa()(Expected) After = passes.outline_incore_scopes()(Before) ir.assert_structural_equal(After, Expected) + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/ir/transforms/test_parent_stmt_analysis.py b/tests/ut/ir/transforms/test_parent_stmt_analysis.py index 514ad97d..b49c7be4 100644 --- a/tests/ut/ir/transforms/test_parent_stmt_analysis.py +++ b/tests/ut/ir/transforms/test_parent_stmt_analysis.py @@ -14,6 +14,7 @@ """ import pypto.language as pl +import pytest from pypto import ir @@ -219,3 +220,7 @@ def main(self, x: pl.Tensor[[64], pl.FP32]) -> pl.Tensor[[64], pl.FP32]: assert analysis.get_parent(outer_for) is func.body, "Parent of outer_for should be the function body" assert analysis.get_parent(inner_if) is outer_for, "Parent of inner_if should be outer_for's body" assert analysis.get_parent(inner_for) is inner_if, "Parent of inner_for should be inner_if's then_body" + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/ir/transforms/test_pass_manager.py b/tests/ut/ir/transforms/test_pass_manager.py index 1b2d2697..39345bdc 100644 --- a/tests/ut/ir/transforms/test_pass_manager.py +++ b/tests/ut/ir/transforms/test_pass_manager.py @@ -9,6 +9,7 @@ """Unit tests for PassManager and Pass classes.""" +import pytest from pypto import DataType, ir @@ -142,3 +143,7 @@ def test_run_passes_on_single_function_program(self): func_names = [func.name for func in result.functions.values()] assert "single_func" in func_names + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/ir/transforms/test_pass_pipeline.py b/tests/ut/ir/transforms/test_pass_pipeline.py index 217f327b..8d03cbeb 100644 --- a/tests/ut/ir/transforms/test_pass_pipeline.py +++ b/tests/ut/ir/transforms/test_pass_pipeline.py @@ -204,3 +204,7 @@ def test_pipeline_with_context(self): with passes.PassContext([passes.VerificationInstrument(passes.VerificationMode.AFTER)]): result = pipeline.run(_make_non_ssa_program()) assert result is not None + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/ir/transforms/test_type_check_pass.py b/tests/ut/ir/transforms/test_type_check_pass.py index 86c8499d..dbcb1f9e 100644 --- a/tests/ut/ir/transforms/test_type_check_pass.py +++ b/tests/ut/ir/transforms/test_type_check_pass.py @@ -9,6 +9,7 @@ """Unit tests for type checking via run_verifier().""" +import pytest from pypto import DataType, ir, passes @@ -220,9 +221,4 @@ def test_type_check_valid_types(): if __name__ == "__main__": - test_type_check_for_type_mismatch() - test_type_check_if_type_mismatch() - test_type_check_tensor_shape_mismatch() - test_type_check_dimension_count_mismatch() - test_type_check_tile_shape_mismatch() - test_type_check_valid_types() + pytest.main([__file__, "-v"]) diff --git a/tests/ut/ir/transforms/test_verify_no_nested_call_pass.py b/tests/ut/ir/transforms/test_verify_no_nested_call_pass.py index 2a23ab95..3a9a6371 100644 --- a/tests/ut/ir/transforms/test_verify_no_nested_call_pass.py +++ b/tests/ut/ir/transforms/test_verify_no_nested_call_pass.py @@ -21,6 +21,7 @@ """ import pypto.language as pl +import pytest from pypto import passes @@ -195,6 +196,4 @@ def main(self, x: pl.Tensor[[64], pl.FP32]) -> pl.Tensor[[64], pl.FP32]: if __name__ == "__main__": - import pytest - pytest.main([__file__, "-v"]) diff --git a/tests/ut/language/parser/test_error_cases.py b/tests/ut/language/parser/test_error_cases.py index 808e7b7b..aff19fbb 100644 --- a/tests/ut/language/parser/test_error_cases.py +++ b/tests/ut/language/parser/test_error_cases.py @@ -272,3 +272,7 @@ def test_scalar_without_dtype(self): @pl.function def bad_scalar(x: pl.Scalar) -> pl.Scalar: # Missing [dtype] return x + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/language/parser/test_expr_evaluator.py b/tests/ut/language/parser/test_expr_evaluator.py index 9ef37bec..b98afe86 100644 --- a/tests/ut/language/parser/test_expr_evaluator.py +++ b/tests/ut/language/parser/test_expr_evaluator.py @@ -168,3 +168,7 @@ def test_success_with_expression(self): success, value = ev.try_eval_expr(_parse_expr("base * 2")) assert success is True assert value == 128 + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/language/parser/test_printer_integration.py b/tests/ut/language/parser/test_printer_integration.py index 30cd8a4b..217a0918 100644 --- a/tests/ut/language/parser/test_printer_integration.py +++ b/tests/ut/language/parser/test_printer_integration.py @@ -11,6 +11,7 @@ import pypto import pypto.language as pl +import pytest from pypto import DataType, ir @@ -283,3 +284,7 @@ def while_tensors(n: pl.Scalar[pl.INT64], x: pl.Tensor[[64], pl.FP32]) -> pl.Ten # Should have while loop and tensor operations assert "while" in printed assert "pl.add" in printed or "tensor.add" in printed + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/language/parser/test_scope_manager.py b/tests/ut/language/parser/test_scope_manager.py index b587c6ca..18073e13 100644 --- a/tests/ut/language/parser/test_scope_manager.py +++ b/tests/ut/language/parser/test_scope_manager.py @@ -160,3 +160,7 @@ def test_scope_isolation(self): assert sm.is_defined("x") # y is no longer accessible after exiting its scope assert not sm.is_defined("y") + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/language/parser/test_scope_parsing.py b/tests/ut/language/parser/test_scope_parsing.py index 2f8b80e0..56003218 100644 --- a/tests/ut/language/parser/test_scope_parsing.py +++ b/tests/ut/language/parser/test_scope_parsing.py @@ -10,6 +10,7 @@ """Unit tests for parsing ScopeStmt with pl.incore() syntax.""" import pypto.language as pl +import pytest from pypto import ir @@ -105,3 +106,7 @@ def main(self, x: pl.Tensor[[64], pl.FP32]) -> pl.Tensor[[64], pl.FP32]: # Verify it contains the scope syntax assert "with pl.incore():" in printed + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/language/parser/test_span_tracker.py b/tests/ut/language/parser/test_span_tracker.py index d96fa0f1..6f17f4ee 100644 --- a/tests/ut/language/parser/test_span_tracker.py +++ b/tests/ut/language/parser/test_span_tracker.py @@ -11,6 +11,7 @@ import ast +import pytest from pypto import ir from pypto.language.parser.span_tracker import SpanTracker @@ -102,3 +103,7 @@ def test_span_preserves_filename(self): span = tracker.get_span(node) assert span.filename == source_file + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/language/parser/test_text_parser.py b/tests/ut/language/parser/test_text_parser.py index 02361806..c288381a 100644 --- a/tests/ut/language/parser/test_text_parser.py +++ b/tests/ut/language/parser/test_text_parser.py @@ -624,3 +624,7 @@ def complex_range( func = pl.parse(code) assert isinstance(func, ir.Function) assert func.name == "complex_range" + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/language/parser/test_tuple_syntax.py b/tests/ut/language/parser/test_tuple_syntax.py index 239aca2f..d9a35b80 100644 --- a/tests/ut/language/parser/test_tuple_syntax.py +++ b/tests/ut/language/parser/test_tuple_syntax.py @@ -9,6 +9,7 @@ """Tests for tuple literal and subscript syntax in parser.""" import pypto.language as pl +import pytest from pypto import ir @@ -127,3 +128,7 @@ def func(x: pl.Scalar[pl.INT64], y: pl.Scalar[pl.INT64]): assert func is not None assert isinstance(func, ir.Function) + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/ut/language/parser/test_type_resolver.py b/tests/ut/language/parser/test_type_resolver.py index 280f2e3f..6152fe40 100644 --- a/tests/ut/language/parser/test_type_resolver.py +++ b/tests/ut/language/parser/test_type_resolver.py @@ -1136,3 +1136,7 @@ def kernel(x: pl.Tensor[shape, dtype]) -> pl.Tensor[shape, dtype]: assert isinstance(param_type, ir.TensorType) assert len(param_type.shape) == 2 assert param_type.dtype == DataType.FP32 + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) From 4bc9118769d7711334f14579f6f55e8ad25394d5 Mon Sep 17 00:00:00 2001 From: Siyuan Feng <25500082+Hzfengsy@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:24:50 +0800 Subject: [PATCH 2/2] chore(claude): address PR review comments for #246 - Add upstream/origin fallback for git rev-list base ref --- .claude/skills/github-pr/SKILL.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.claude/skills/github-pr/SKILL.md b/.claude/skills/github-pr/SKILL.md index 9c65826f..d7207ecb 100644 --- a/.claude/skills/github-pr/SKILL.md +++ b/.claude/skills/github-pr/SKILL.md @@ -21,9 +21,14 @@ description: Create a GitHub pull request after committing, rebasing, and pushin ```bash BRANCH_NAME=$(git branch --show-current) -git status --porcelain # Check for uncommitted changes +git status --porcelain # Check for uncommitted changes git fetch upstream 2>/dev/null || git fetch origin # Fetch latest -git rev-list HEAD --not upstream/main --count # Commits ahead of upstream +if git rev-parse --verify upstream/main >/dev/null 2>&1; then + BASE_REF=upstream/main +else + BASE_REF=origin/main +fi +git rev-list HEAD --not "$BASE_REF" --count # Commits ahead of base ``` A branch "needs a new branch" when it is effectively on main — either the branch name is `main`/`master`, **or** it has zero commits ahead of upstream/main (e.g., a local branch that was never diverged).