diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d48f5eab6..ad57b220b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,9 +35,29 @@ jobs: make linter make + # 全量 go test -race 单 job 约 13 分钟,拆成 4 个并行分片执行; + # 聚合 job 仍命名为 unit-test,保持分支保护里 Required check 的名称不变。 unit-test: name: unit-test + needs: unit-test-shard + if: always() runs-on: ubuntu-22.04 + steps: + - name: check shards + run: | + echo "shards result: ${{ needs.unit-test-shard.result }}" + if [ "${{ needs.unit-test-shard.result }}" != "success" ]; then + echo "::error::unit-test 分片未全部通过" + exit 1 + fi + + unit-test-shard: + name: unit-test shard + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + shard: [1, 2, 3, 4] steps: - name: checkout uses: actions/checkout@v4 @@ -49,7 +69,11 @@ jobs: - name: set go env run: export PATH=${PATH}:`go env GOPATH`/bin - name: unit-test - run: make test + run: | + # 按包序号取模分片,不维护硬编码包列表,新增包会自动落到某个分片 + PKGS=$(go list ./... | grep -v pbft | awk -v s=${{ matrix.shard }} 'NR % 4 == s % 4') + echo "shard ${{ matrix.shard }}: $(echo "$PKGS" | wc -l) packages" + go test -parallel=8 -race $PKGS coverage: name: coverage diff --git a/plugin/dapp/paracross/testnode/paranode.go b/plugin/dapp/paracross/testnode/paranode.go index 3424ff0df..bb18ceecc 100644 --- a/plugin/dapp/paracross/testnode/paranode.go +++ b/plugin/dapp/paracross/testnode/paranode.go @@ -24,17 +24,25 @@ func NewParaNode(main *testnode.Chain33Mock, para *testnode.Chain33Mock) *ParaNo main.Listen() } if para == nil { - cfg := types.NewChain33Config(DefaultConfig) - cfg.GetModuleConfig().RPC.ParaChain.MainChainGrpcAddr = main.GetCfg().RPC.GrpcBindAddr - para = testnode.NewWithConfig(cfg, nil) - // chain33 ForkParaFee 默认-1,测试模式 SetAllFork(0) 激活收费 - // 导致 para 链处理同步区块时收手续费破坏共识,手动覆盖 - para.GetClient().GetConfig().SetFork("ForkParaFee", types.MaxHeight) + para = testnode.NewWithConfig(newParaConfig(main.GetCfg().RPC.GrpcBindAddr), nil) para.Listen() } return &ParaNode{Main: main, Para: para} } +// newParaConfig 构造平行链节点配置。对配置的所有写入(尤其是 Forks)都必须在创建节点之前完成: +// testnode.NewWithConfig 会立即启动 blockchain 等后台 goroutine 并并发读取 Forks +// (Block.Hash() -> cfg.IsFork()),而 chain33 的 Forks 没有锁, +// 之后再写 Forks 会构成数据竞争,在 -race 下表现为 TestParaNode 随机失败。 +func newParaConfig(mainGrpcAddr string) *types.Chain33Config { + cfg := types.NewChain33Config(DefaultConfig) + cfg.GetModuleConfig().RPC.ParaChain.MainChainGrpcAddr = mainGrpcAddr + // chain33 ForkParaFee 默认-1,测试模式 SetAllFork(0) 激活收费, + // 导致 para 链处理同步区块时收手续费破坏共识,这里覆盖为禁用。 + cfg.SetFork("ForkParaFee", types.MaxHeight) + return cfg +} + // Close 关闭系统 func (node *ParaNode) Close() { node.Para.Close() diff --git a/plugin/dapp/paracross/testnode/paranode_fork_test.go b/plugin/dapp/paracross/testnode/paranode_fork_test.go new file mode 100644 index 000000000..83071edfc --- /dev/null +++ b/plugin/dapp/paracross/testnode/paranode_fork_test.go @@ -0,0 +1,24 @@ +package testnode + +import ( + "testing" + + "github.com/33cn/chain33/types" + "github.com/stretchr/testify/assert" +) + +// TestNewParaConfigDisablesForkParaFee 固化 newParaConfig 的约定: +// ForkParaFee 必须在**创建节点之前**被置为 MaxHeight(禁用)。原因有两条: +// 1. 测试模式下 chain33 会把所有 fork 置 0,导致 para 链收手续费破坏共识; +// 2. testnode.NewWithConfig 会立即启动 blockchain 等后台 goroutine 并发读取 Forks, +// 之后再写 Forks 会构成数据竞争(chain33 的 Forks 无锁), +// 在 -race 下表现为 TestParaNode 随机失败。 +// +// 这里刻意不启动节点:断言配置本身即可,避免测试自身的启停时序引入新的不稳定。 +func TestNewParaConfigDisablesForkParaFee(t *testing.T) { + cfg := newParaConfig("127.0.0.1:0") + + assert.False(t, cfg.IsFork(0, "ForkParaFee"), "ForkParaFee 应保持禁用") + assert.Equal(t, int64(types.MaxHeight), cfg.GetFork("ForkParaFee")) + assert.Equal(t, "127.0.0.1:0", cfg.GetModuleConfig().RPC.ParaChain.MainChainGrpcAddr) +}