|
| 1 | +# 双数据库引擎测试覆盖 — 改动总结 |
| 2 | + |
| 3 | +## 分支 |
| 4 | + |
| 5 | +`test/dual-db-engine-coverage` (基于 `develop`) |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +## 一、新增基础设施 |
| 10 | + |
| 11 | +### 1. BaseMethodTest — 方法级 Spring 上下文隔离基类 |
| 12 | + |
| 13 | +**文件**: `framework/src/test/java/org/tron/common/BaseMethodTest.java` |
| 14 | + |
| 15 | +与 BaseTest(类级别共享上下文)不同,BaseMethodTest 为每个 @Test 方法创建独立的 |
| 16 | +Spring 上下文,在 @Before 创建、@After 销毁。 |
| 17 | + |
| 18 | +适用场景: |
| 19 | +- 方法之间会修改数据库状态 |
| 20 | +- 方法需要不同的 CommonParameter 设置 |
| 21 | +- 需要 beforeContext() 在 Spring 启动前配置状态 |
| 22 | + |
| 23 | +提供的 hook 方法: |
| 24 | +- `extraArgs()` — 额外 CLI 参数 |
| 25 | +- `configFile()` — 配置文件路径 |
| 26 | +- `beforeContext()` — Spring 启动前执行 |
| 27 | +- `afterInit()` — Spring 就绪后执行 |
| 28 | +- `beforeDestroy()` — 上下文销毁前执行 |
| 29 | + |
| 30 | +### 2. TestConstants.withDbEngineOverride() — 数据库引擎覆盖工具方法 |
| 31 | + |
| 32 | +**文件**: `framework/src/test/java/org/tron/common/TestConstants.java` |
| 33 | + |
| 34 | +读取系统属性 `tron.test.db.engine`,如果设置了则在 CLI 参数中追加 |
| 35 | +`--storage-db-engine <engine>`。CLI 参数优先级高于配置文件,实现引擎覆盖。 |
| 36 | + |
| 37 | +### 3. testWithRocksDb Gradle Task |
| 38 | + |
| 39 | +**文件**: `framework/build.gradle` |
| 40 | + |
| 41 | +```groovy |
| 42 | +task testWithRocksDb(type: Test) { |
| 43 | + systemProperty 'tron.test.db.engine', 'ROCKSDB' |
| 44 | + exclude '**/LevelDbDataSourceImplTest.class' |
| 45 | + // ... 其他配置与 test task 一致 |
| 46 | +} |
| 47 | +``` |
| 48 | + |
| 49 | +运行 `./gradlew :framework:testWithRocksDb` 即可用 RocksDB 引擎跑全部业务测试。 |
| 50 | + |
| 51 | +--- |
| 52 | + |
| 53 | +## 二、176 个 BaseTest 子类迁移 |
| 54 | + |
| 55 | +**涉及文件**: 176 个测试类 |
| 56 | + |
| 57 | +将所有 BaseTest 子类的 static 块: |
| 58 | +```java |
| 59 | +Args.setParam(new String[]{"--output-directory", dbPath()}, TEST_CONF); |
| 60 | +``` |
| 61 | +改为: |
| 62 | +```java |
| 63 | +Args.setParam(withDbEngineOverride("--output-directory", dbPath()), TEST_CONF); |
| 64 | +``` |
| 65 | + |
| 66 | +默认行为不变(仍用 LEVELDB),但当 `tron.test.db.engine` 系统属性被设置时, |
| 67 | +自动切换到指定引擎。 |
| 68 | + |
| 69 | +--- |
| 70 | + |
| 71 | +## 三、底层 DB 测试参数化 |
| 72 | + |
| 73 | +**文件**: `framework/src/test/java/org/tron/common/storage/DbDataSourceImplTest.java`(新建) |
| 74 | + |
| 75 | +用 JUnit 4 `@RunWith(Parameterized.class)` 合并 LevelDbDataSourceImplTest 和 |
| 76 | +RocksDbDataSourceImplTest 中的通用测试逻辑。每个测试方法自动在 LEVELDB 和 ROCKSDB |
| 77 | +两种引擎上各运行一次。ARM64 环境自动跳过 LEVELDB 参数。 |
| 78 | + |
| 79 | +原有的 LevelDbDataSourceImplTest 和 RocksDbDataSourceImplTest 只保留各自引擎特有的测试。 |
| 80 | + |
| 81 | +--- |
| 82 | + |
| 83 | +## 四、20 个测试类迁移到 BaseMethodTest |
| 84 | + |
| 85 | +**涉及文件**: 20 个测试类 |
| 86 | + |
| 87 | +将需要方法级隔离的测试从 BaseTest 迁移到 BaseMethodTest: |
| 88 | +- ConditionallyStopTest 及其 3 个子类 |
| 89 | +- TransactionExpireTest |
| 90 | +- ManagerTest |
| 91 | +- 各类需要在方法间切换 CommonParameter 的测试 |
| 92 | + |
| 93 | +--- |
| 94 | + |
| 95 | +## 五、Flaky Test 修复(6 个根因) |
| 96 | + |
| 97 | +### 1. CommonParameter 全局状态泄漏 |
| 98 | + |
| 99 | +**文件**: |
| 100 | +- `framework/src/test/java/org/tron/core/net/messagehandler/InventoryMsgHandlerTest.java` |
| 101 | +- `framework/src/test/java/org/tron/core/net/TronNetDelegateTest.java` |
| 102 | + |
| 103 | +**问题**: 调用了 `Args.setParam()` 但没有 cleanup,污染后续测试类。 |
| 104 | +**修复**: 添加 `@AfterClass` 调用 `Args.clearParam()`。 |
| 105 | + |
| 106 | +### 2. ZMQ 端口冲突 |
| 107 | + |
| 108 | +**文件**: |
| 109 | +- `framework/src/test/java/org/tron/common/logsfilter/EventLoaderTest.java` |
| 110 | +- `framework/src/test/java/org/tron/common/logsfilter/NativeMessageQueueTest.java` |
| 111 | +- `framework/src/main/java/org/tron/common/logsfilter/nativequeue/NativeMessageQueue.java` |
| 112 | + |
| 113 | +**问题**: 硬编码端口 5555,多个测试并行时 bind 冲突;NativeMessageQueue 单例 stop() |
| 114 | +后不重置 instance,导致后续测试拿到已关闭的实例。 |
| 115 | +**修复**: |
| 116 | +- 用 `ServerSocket(0)` 获取随机可用端口 |
| 117 | +- `stop()` 方法中重置 `instance = null`、`publisher = null`、`context = null` |
| 118 | + |
| 119 | +### 3. NeedBeanCondition NPE |
| 120 | + |
| 121 | +**文件**: `framework/src/main/java/org/tron/core/db/backup/NeedBeanCondition.java` |
| 122 | + |
| 123 | +**问题**: `Args.getInstance().getStorage().getDbEngine().toUpperCase()` 链式调用, |
| 124 | +当 CommonParameter 在测试间被 reset 时任一环节为 null 就 NPE。 |
| 125 | +**修复**: 逐级 null check,null 时返回 false。 |
| 126 | + |
| 127 | +### 4. ShieldedReceiveTest 非确定性断言 |
| 128 | + |
| 129 | +**文件**: `framework/src/test/java/org/tron/core/zksnark/ShieldedReceiveTest.java` |
| 130 | + |
| 131 | +**问题**: 原始测试用 `assertEquals("param is null", e.getMessage())` 断言错误消息, |
| 132 | +但 zksnark 原生库的验证顺序不确定,不同运行可能产生不同的错误消息 |
| 133 | +("param is null" / "Rt is invalid." / "librustzcashSaplingCheckOutput error")。 |
| 134 | +**修复**: 定义两个合法错误消息集合: |
| 135 | +- `RECEIVE_VALIDATION_ERRORS`: 接收描述验证相关错误 |
| 136 | +- `SPEND_VALIDATION_ERRORS`: 花费描述验证相关错误 |
| 137 | + |
| 138 | +用 `assertTrue(SET.contains(e.getMessage()))` 替代精确匹配, |
| 139 | +既允许非确定性顺序,又能捕获意料之外的错误。 |
| 140 | + |
| 141 | +### 5. TransactionExpireTest ECDSA 签名恢复 |
| 142 | + |
| 143 | +**文件**: `framework/src/test/java/org/tron/core/db/TransactionExpireTest.java` |
| 144 | + |
| 145 | +**问题**: 用 `generateRandomBytes(65)` 生成"无效签名",期望 `COMPUTE_ADDRESS_ERROR`。 |
| 146 | +但签名布局是 `[r(32)|s(32)|v(1)]`,`Rsv.fromSignature` 对 `v < 27` 自动加 27 修正。 |
| 147 | +随机 v 值有 ~3% 概率恰好在有效范围 [27,34] 内,ECDSA 恢复成功,返回 SUCCESS。 |
| 148 | +**修复**: 用固定的 65 字节,`invalidSig[64] = 35`(v=35 超出有效范围且不会被自动修正), |
| 149 | +确保 100% 触发 `SignatureException`。 |
| 150 | + |
| 151 | +### 6. TrieTest.testOrder 非确定性 shuffle |
| 152 | + |
| 153 | +**文件**: `framework/src/test/java/org/tron/core/tire/TrieTest.java` |
| 154 | + |
| 155 | +**问题**: `testOrder` 验证 Merkle Trie 的 root hash 与插入顺序无关。 |
| 156 | +测试在列表中放入两个 key=10(重复 put),用无种子 `Collections.shuffle()` 打乱顺序。 |
| 157 | +`TrieImpl.insert()` 对已存在相同 key-value 的 put 不是幂等的 —— 会调用 |
| 158 | +`kvNodeSetValueOrNode()` + `invalidate()` 改变内部节点缓存,导致后续操作走不同的 |
| 159 | +树分裂/合并路径。约 3% 的 shuffle 顺序会暴露此 bug,产生不同的 root hash。 |
| 160 | + |
| 161 | +**这是 TrieImpl 的生产代码 bug**(insert 应在值未变时短路),但生产影响低 —— |
| 162 | +AccountStateCallBack 中不会对同一 key 重复 put 相同的 value。 |
| 163 | + |
| 164 | +**修复**: |
| 165 | +- 原始 `testOrder` 保留并加 `@Ignore`,注释详细说明 bug 机制、生产影响和修复方向 |
| 166 | +- 新增 `testOrderNoDuplicate`:去掉重复 key,用固定种子 shuffle,验证正常场景 |
| 167 | + |
| 168 | +--- |
| 169 | + |
| 170 | +## 六、ConditionallyStopTest 初始化顺序修复 |
| 171 | + |
| 172 | +**文件**: `framework/src/test/java/org/tron/core/services/stop/ConditionallyStopTest.java` |
| 173 | + |
| 174 | +**问题**: 迁移到 BaseMethodTest 后,`setNodeListenPort`、`genesisBlock.setTimestamp`、 |
| 175 | +`initParameter` 放在了 `afterInit()`,但这些配置必须在 Spring 上下文创建之前生效。 |
| 176 | +**修复**: 移到 `beforeContext()` override。 |
| 177 | + |
| 178 | +--- |
| 179 | + |
| 180 | +## 验证 |
| 181 | + |
| 182 | +```bash |
| 183 | +# 默认引擎(LEVELDB)全量测试 |
| 184 | +./gradlew :framework:test |
| 185 | +# 结果:2333 tests, 0 failures, 25 skipped (含 @Ignore 的 testOrder) |
| 186 | + |
| 187 | +# RocksDB 引擎全量测试 |
| 188 | +./gradlew :framework:testWithRocksDb |
| 189 | +``` |
0 commit comments