Skip to content
Closed
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
4 changes: 4 additions & 0 deletions .github/workflows/pr-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ jobs:
- name: Build
run: ./gradlew clean build --no-daemon

- name: Test with RocksDB engine
if: matrix.arch == 'x86_64'
run: ./gradlew :framework:testWithRocksDb --no-daemon

docker-build-rockylinux:
name: Build rockylinux (JDK 8 / x86_64)
if: github.event.action != 'edited' && !failure()
Expand Down
5 changes: 0 additions & 5 deletions common/src/main/java/org/tron/core/config/args/Storage.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import org.apache.commons.lang3.StringUtils;
import org.iq80.leveldb.CompressionType;
import org.iq80.leveldb.Options;
import org.tron.common.arch.Arch;
import org.tron.common.cache.CacheStrategies;
import org.tron.common.cache.CacheType;
import org.tron.common.utils.DbOptionalsUtils;
Expand Down Expand Up @@ -175,10 +174,6 @@ public class Storage {
private final Map<String, Sha256Hash> dbRoots = Maps.newConcurrentMap();

public static String getDbEngineFromConfig(final Config config) {
if (Arch.isArm64()) {
logger.warn("Arm64 architecture detected, using RocksDB as db engine, ignore config.");
return ROCKS_DB_ENGINE;
}
return config.hasPath(DB_ENGINE_CONFIG_KEY)
? config.getString(DB_ENGINE_CONFIG_KEY) : DEFAULT_DB_ENGINE;
}
Expand Down
58 changes: 31 additions & 27 deletions framework/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -106,46 +106,50 @@ run {
}
}

test {
retry {
def configureTestTask = { Task t ->
t.retry {
maxRetries = 5
maxFailures = 20
}
testLogging {
t.testLogging {
exceptionFormat = 'full'
}
if (isWindows()) {
t.exclude '**/ShieldedTransferActuatorTest.class'
t.exclude '**/BackupDbUtilTest.class'
t.exclude '**/ManagerTest.class'
t.exclude 'org/tron/core/zksnark/**'
t.exclude 'org/tron/common/runtime/vm/PrecompiledContractsVerifyProofTest.class'
t.exclude 'org/tron/core/ShieldedTRC20BuilderTest.class'
t.exclude 'org/tron/common/runtime/vm/WithdrawRewardTest.class'
}
t.maxHeapSize = "1024m"
t.doFirst {
t.forkEvery = 100
t.jvmArgs "-XX:MetaspaceSize=128m", "-XX:MaxMetaspaceSize=256m", "-XX:+UseG1GC"
}
}

test {
configureTestTask(it)
jacoco {
destinationFile = file("$buildDir/jacoco/jacocoTest.exec")
classDumpDir = file("$buildDir/jacoco/classpathdumps")
}
if (rootProject.archInfo.isArm64) {
exclude { element ->
element.file.name.toLowerCase().contains('leveldb')
}
filter {
excludeTestsMatching '*.*leveldb*'
excludeTestsMatching '*.*Leveldb*'
excludeTestsMatching '*.*LevelDB*'
excludeTestsMatching '*.*LevelDb*'
}
}
if (isWindows()) {
exclude '**/ShieldedTransferActuatorTest.class'
exclude '**/BackupDbUtilTest.class'
exclude '**/ManagerTest.class'
exclude 'org/tron/core/zksnark/**'
exclude 'org/tron/common/runtime/vm/PrecompiledContractsVerifyProofTest.class'
exclude 'org/tron/core/ShieldedTRC20BuilderTest.class'
exclude 'org/tron/common/runtime/vm/WithdrawRewardTest.class'
}
maxHeapSize = "1024m"
doFirst {
// Restart the JVM after every 100 tests to avoid memory leaks and ensure test isolation
forkEvery = 100
jvmArgs "-XX:MetaspaceSize=128m","-XX:MaxMetaspaceSize=256m", "-XX:+UseG1GC"
systemProperty 'tron.test.db.engine', 'ROCKSDB'
exclude '**/LevelDbDataSourceImplTest.class'
}
}

task testWithRocksDb(type: Test) {
description = 'Run tests with RocksDB engine'
group = 'verification'
configureTestTask(it)
systemProperty 'tron.test.db.engine', 'ROCKSDB'
exclude '**/LevelDbDataSourceImplTest.class'
}

jacocoTestReport {
reports {
xml.enabled true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,15 @@ public boolean start(int bindPort, int sendQueueLength) {
public void stop() {
if (Objects.nonNull(publisher)) {
publisher.close();
publisher = null;
}

if (Objects.nonNull(context)) {
context.close();
context = null;
}
synchronized (NativeMessageQueue.class) {
instance = null;
}
}

Expand Down
12 changes: 12 additions & 0 deletions framework/src/main/java/org/tron/core/config/args/Args.java
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,18 @@ public static void setParam(final String[] args, final String confFileName) {
initLocalWitnesses(config, cmd);
}

/**
* Validate final configuration after all sources (defaults, config, CLI) are applied.
*/
public static void validateConfig() {
if (Arch.isArm64() && !"ROCKSDB".equals(PARAMETER.storage.getDbEngine())) {
throw new TronError(
"ARM64 architecture only supports RocksDB. Current engine: "
+ PARAMETER.storage.getDbEngine(),
TronError.ErrCode.PARAMETER_INIT);
}
}

/**
* Apply parameters from config file.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ public class NeedBeanCondition implements Condition {

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return ("ROCKSDB".equals(Args.getInstance().getStorage().getDbEngine().toUpperCase()))
if (Args.getInstance() == null || Args.getInstance().getStorage() == null
|| Args.getInstance().getStorage().getDbEngine() == null
|| Args.getInstance().getDbBackupConfig() == null) {
return false;
}
return "ROCKSDB".equalsIgnoreCase(Args.getInstance().getStorage().getDbEngine())
&& Args.getInstance().getDbBackupConfig().isEnable() && !Args.getInstance().isWitness();
}
}
1 change: 1 addition & 0 deletions framework/src/main/java/org/tron/program/FullNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public static void main(String[] args) {
ExitManager.initExceptionHandler();
checkJdkVersion();
Args.setParam(args, "config.conf");
Args.validateConfig();
CommonParameter parameter = Args.getInstance();

LogService.load(parameter.getLogbackPath());
Expand Down
98 changes: 98 additions & 0 deletions framework/src/test/java/org/tron/common/BaseMethodTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package org.tron.common;

import java.io.IOException;
import java.util.Arrays;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
import org.tron.common.application.Application;
import org.tron.common.application.ApplicationFactory;
import org.tron.common.application.TronApplicationContext;
import org.tron.core.ChainBaseManager;
import org.tron.core.config.DefaultConfig;
import org.tron.core.config.args.Args;
import org.tron.core.db.Manager;

/**
* Base class for tests that need a fresh Spring context per test method.
*
* Each @Test method gets its own TronApplicationContext, created in @Before
* and destroyed in @After, ensuring full isolation between tests.
*
* Subclasses can customize behavior by overriding hook methods:
* extraArgs() — additional CLI args (e.g. "--debug")
* configFile() — config file (default: config-test.conf)
* beforeContext() — runs after Args.setParam, before Spring context creation
* afterInit() — runs after Spring context is ready (e.g. get extra beans)
* beforeDestroy() — runs before context shutdown (e.g. close resources)
*
* Use this when:
* - methods modify database state that would affect other methods
* - methods need different CommonParameter settings (e.g. setP2pDisable)
* - methods need beforeContext() to configure state before Spring starts
*
* If methods are read-only and don't interfere with each other, use BaseTest instead.
* Tests that don't need Spring (e.g. pure unit tests) should NOT extend either base class.
*/
@Slf4j
public abstract class BaseMethodTest {

@Rule
public final TemporaryFolder temporaryFolder = new TemporaryFolder();

protected TronApplicationContext context;
protected Application appT;
protected Manager dbManager;
protected ChainBaseManager chainBaseManager;

protected String[] extraArgs() {
return new String[0];
}

protected String configFile() {
return TestEnv.TEST_CONF;
}

@Before
public final void initContext() throws IOException {
String[] baseArgs = TestEnv.withDbEngineOverride(
"--output-directory", temporaryFolder.newFolder().toString());
String[] allArgs = mergeArgs(baseArgs, extraArgs());
Args.setParam(allArgs, configFile());
beforeContext();
context = new TronApplicationContext(DefaultConfig.class);
appT = ApplicationFactory.create(context);
dbManager = context.getBean(Manager.class);
chainBaseManager = context.getBean(ChainBaseManager.class);
afterInit();
}

protected void beforeContext() {
}

protected void afterInit() {
}

@After
public final void destroyContext() {
beforeDestroy();
if (appT != null) {
appT.shutdown();
}
if (context != null) {
context.close();
}
Args.clearParam();
}

protected void beforeDestroy() {
}

private static String[] mergeArgs(String[] base, String[] extra) {
String[] result = Arrays.copyOf(base, base.length + extra.length);
System.arraycopy(extra, 0, result, base.length, extra.length);
return result;
}
}
25 changes: 25 additions & 0 deletions framework/src/test/java/org/tron/common/BaseTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,31 @@
import org.tron.core.store.AccountStore;
import org.tron.protos.Protocol;

/**
* Base class for tests that need a Spring context (Manager, ChainBaseManager, etc.).
* The context is created once per test class.
*
* Args.setParam() is called in a static block, the context is shared by all @Test
* methods in the class, and destroyed in @AfterClass. This is faster but means
* test methods within the same class are not isolated from each other.
*
* Use this when:
* - test methods are read-only or don't interfere with each other
* - no need to change CommonParameter/Args between methods
*
* Use BaseMethodTest instead when:
* - methods modify database state that would affect other methods
* - methods need different CommonParameter settings (e.g. setP2pDisable)
* - methods need beforeContext() to configure state before Spring starts
*
* Tests that don't need Spring (e.g. pure unit tests like ArgsTest,
* LevelDbDataSourceImplTest) should NOT extend either base class.
*
* Subclasses must call Args.setParam() in a static initializer block:
* static {
* Args.setParam(new String[]{"--output-directory", dbPath()}, TEST_CONF);
* }
*/
@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {DefaultConfig.class})
Expand Down
7 changes: 0 additions & 7 deletions framework/src/test/java/org/tron/common/TestConstants.java

This file was deleted.

52 changes: 52 additions & 0 deletions framework/src/test/java/org/tron/common/TestEnv.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package org.tron.common;

import static org.junit.Assume.assumeFalse;

import java.util.Arrays;
import org.tron.common.arch.Arch;

public class TestEnv {

/** Default test config: LEVELDB engine, minimal settings. */
public static final String TEST_CONF = "config-test.conf";

/** Production config: full mainnet settings, RocksDB tuning, 27 witnesses. */
public static final String NET_CONF = "config.conf";

/** Mainnet-like config: P2P connection management (maxConnections, minActive). */
public static final String MAINNET_CONF = "config-test-mainnet.conf";

/** DB backup config: RocksDB engine, backup enabled with frequency/path settings. */
public static final String DBBACKUP_CONF = "config-test-dbbackup.conf";

/** Local test config: custom port 6666, full HTTP/RPC services, single local witness. */
public static final String LOCAL_CONF = "config-localtest.conf";

/** Storage test config: per-database property tuning (compression, cache sizes). */
public static final String STORAGE_CONF = "config-test-storagetest.conf";

/** Index test config: minimal setup for transaction history index testing. */
public static final String INDEX_CONF = "config-test-index.conf";

/**
* Skips the current test on ARM64 where LevelDB JNI is unavailable.
*/
public static void assumeLevelDbAvailable() {
assumeFalse("LevelDB JNI unavailable on ARM64", Arch.isArm64());
}

/**
* Appends --storage-db-engine override if the system property tron.test.db.engine is set.
* Used by Gradle testWithRocksDb task to run tests with RocksDB engine.
*/
public static String[] withDbEngineOverride(String... args) {
String engineOverride = System.getProperty("tron.test.db.engine");
if (engineOverride != null) {
String[] extra = {"--storage-db-engine", engineOverride};
String[] result = Arrays.copyOf(args, args.length + extra.length);
System.arraycopy(extra, 0, result, args.length, extra.length);
return result;
}
return args;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.tron.common.TestConstants;
import org.tron.common.TestEnv;
import org.tron.common.backup.BackupManager.BackupStatusEnum;
import org.tron.common.backup.message.KeepAliveMessage;
import org.tron.common.backup.socket.BackupServer;
Expand All @@ -31,7 +31,7 @@ public class BackupManagerTest {
@Before
public void setUp() throws Exception {
Args.setParam(new String[] {"-d", temporaryFolder.newFolder().toString()},
TestConstants.TEST_CONF);
TestEnv.TEST_CONF);
CommonParameter.getInstance().setBackupPort(PublicMethod.chooseRandomPort());
manager = new BackupManager();
backupServer = new BackupServer(manager);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.rules.Timeout;
import org.tron.common.TestConstants;
import org.tron.common.TestEnv;
import org.tron.common.backup.socket.BackupServer;
import org.tron.common.parameter.CommonParameter;
import org.tron.common.utils.PublicMethod;
Expand All @@ -27,7 +27,7 @@ public class BackupServerTest {
@Before
public void setUp() throws Exception {
Args.setParam(new String[]{"-d", temporaryFolder.newFolder().toString()},
TestConstants.TEST_CONF);
TestEnv.TEST_CONF);
CommonParameter.getInstance().setBackupPort(PublicMethod.chooseRandomPort());
List<String> members = new ArrayList<>();
members.add("127.0.0.2");
Expand Down
Loading
Loading