Skip to content
Merged
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
35 changes: 0 additions & 35 deletions .github/workflows/deploy.yml

This file was deleted.

6 changes: 0 additions & 6 deletions .github/workflows/instrumented.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
name: Instrumented tests

on:
pull_request:
branches:
- 'development'
- '*_baseline'

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Expand Down
53 changes: 0 additions & 53 deletions .github/workflows/sonarqube.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@ jobs:
name: SonarCloud Scan
runs-on: ubuntu-latest
steps:
- name: Enable KVM group perms
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm

- name: Checkout
uses: actions/checkout@v4
with:
Expand Down Expand Up @@ -54,53 +48,6 @@ jobs:
# Even if tests fail, JaCoCo should generate a report with partial coverage
# from the tests that did pass
fi

# Check if the report was generated with content
if [ -f build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml ]; then
# Use stat command compatible with both Linux and macOS
if [[ "$(uname)" == "Darwin" ]]; then
# macOS syntax
REPORT_SIZE=$(stat -f%z build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml)
else
# Linux syntax
REPORT_SIZE=$(stat -c%s build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml)
fi

if [ "$REPORT_SIZE" -lt 1000 ]; then
echo "JaCoCo report is too small ($REPORT_SIZE bytes), likely empty. Trying to generate from test execution data..."
# Try to generate the report directly from the exec file
if [ -f build/jacoco/testDebugUnitTest.exec ]; then
java -jar ~/.gradle/caches/modules-2/files-2.1/org.jacoco/org.jacoco.cli/0.8.8/*/org.jacoco.cli-0.8.8.jar report build/jacoco/testDebugUnitTest.exec \
--classfiles build/intermediates/runtime_library_classes_dir/debug \
--sourcefiles src/main/java \
--xml build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml

# Check if the report was successfully generated
if [[ "$(uname)" == "Darwin" ]]; then
# macOS syntax
NEW_REPORT_SIZE=$(stat -f%z build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml)
else
# Linux syntax
NEW_REPORT_SIZE=$(stat -c%s build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml)
fi

if [ "$NEW_REPORT_SIZE" -lt 1000 ]; then
echo "ERROR: Failed to generate a valid JaCoCo report with coverage data"
exit 1
else
echo "JaCoCo report successfully generated with size $NEW_REPORT_SIZE bytes"
fi
else
echo "ERROR: No JaCoCo execution data file found. Tests may not have run correctly."
exit 1
fi
else
echo "JaCoCo report generated successfully with size $REPORT_SIZE bytes"
fi
else
echo "ERROR: JaCoCo report file not found. Coverage data is missing."
exit 1
fi

- name: Prepare class files for SonarQube analysis
run: |
Expand Down
24 changes: 7 additions & 17 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ ext {

// Define exclusions for JaCoCo coverage
def coverageExclusions = [
'**/R.class',
'**/R$*.class',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/*Test*.*',
'android/**/*.*'
'**/R.class',
'**/R$*.class',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/*Test*.*',
'android/**/*.*'
]

android {
Expand Down Expand Up @@ -76,7 +76,7 @@ android {

testOptions {
unitTests.returnDefaultValues = true

// Configure JaCoCo for all test tasks
unitTests.all {
jacoco {
Expand Down Expand Up @@ -219,15 +219,6 @@ def splitPOM = {
}
}

def devRepo = {
name = "DevelopmentRepo"
url = 'https://splitio.jfrog.io/artifactory/maven-all-virtual'
credentials {
username = System.getenv('ARTIFACTORY_USER')
password = System.getenv('ARTIFACTORY_TOKEN')
}
}

mavenPublishing {
coordinates("io.split.client", "android-client", splitVersion)
pom(splitPOM)
Expand Down Expand Up @@ -296,4 +287,3 @@ tasks.withType(Test) {




Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,14 @@ public static SplitRoomDatabase getDatabase(final Context context, final String
Logger.i("Failed to set optimized pragma");
}


mInstances.put(databaseName, instance);

// Ensure Room is fully initialized before starting preload thread
try {
instance.getOpenHelper().getWritableDatabase(); // This blocks until validations happen
} catch (Exception e) {
Logger.i("Failed to force Room initialization: " + e.getMessage());
}
new Thread(() -> {
try {
mInstances.get(databaseName).getSplitQueryDao();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package io.split.android.client.storage.db;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.Context;

import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.sqlite.db.SupportSQLiteOpenHelper;

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.MockitoAnnotations;

public class SplitRoomDatabaseTest {

@Mock
private Context mockContext;

@Mock
private Context mockApplicationContext;

@Mock
private RoomDatabase.Builder<SplitRoomDatabase> mockBuilder;

@Mock
private SplitRoomDatabase mockDatabase;

@Mock
private SupportSQLiteOpenHelper mockOpenHelper;

@Mock
private SupportSQLiteDatabase mockSqliteDatabase;

@Before
public void setUp() {
MockitoAnnotations.openMocks(this);

when(mockContext.getApplicationContext()).thenReturn(mockApplicationContext);
when(mockDatabase.getOpenHelper()).thenReturn(mockOpenHelper);
when(mockOpenHelper.getWritableDatabase()).thenReturn(mockSqliteDatabase);
}

@Test
public void shouldCallGetWritableDatabaseDuringInitialization() {
String databaseName = "test_database_unique_1";

try (MockedStatic<Room> mockedRoom = mockStatic(Room.class)) {
mockRoom(mockedRoom, databaseName);

SplitRoomDatabase result = SplitRoomDatabase.getDatabase(mockContext, databaseName);

assertNotNull("Database instance should not be null", result);
// Verify that getWritableDatabase is called twice:
// 1. Once for pragma setup
// 2. Once for ensuring Room initialization before preload thread
verify(mockOpenHelper, times(2)).getWritableDatabase();
}
}

@Test
public void shouldHandleExceptionDuringRoomInitializationGracefully() {
String databaseName = "test_database_unique_2";

try (MockedStatic<Room> mockedRoom = mockStatic(Room.class)) {
mockRoom(mockedRoom, databaseName);

// Make the second call to getWritableDatabase throw an exception
when(mockOpenHelper.getWritableDatabase())
.thenReturn(mockSqliteDatabase)
.thenThrow(new RuntimeException("Database initialization failed")); // Second call fails

SplitRoomDatabase result = SplitRoomDatabase.getDatabase(mockContext, databaseName);

verify(mockOpenHelper, times(2)).getWritableDatabase();
// Should still return the database instance despite initialization failure
assertSame("Should return the same database instance", mockDatabase, result);
}
}

@Test
public void shouldReturnSameInstanceForSameDatabaseName() {
String databaseName = "test_database_unique_3";

try (MockedStatic<Room> mockedRoom = mockStatic(Room.class)) {
mockRoom(mockedRoom, databaseName);

SplitRoomDatabase instance1 = SplitRoomDatabase.getDatabase(mockContext, databaseName);
SplitRoomDatabase instance2 = SplitRoomDatabase.getDatabase(mockContext, databaseName);

assertSame("Should return the same instance for same database name", instance1, instance2);
// getWritableDatabase should only be called during first initialization
verify(mockOpenHelper, times(2)).getWritableDatabase();
}
}

@Test
public void shouldCallGetWritableDatabaseBeforeStartingPreloadThread() {
String databaseName = "test_database_unique_4";

try (MockedStatic<Room> mockedRoom = mockStatic(Room.class)) {
mockRoom(mockedRoom, databaseName);

SplitRoomDatabase.getDatabase(mockContext, databaseName);

// Verify that getWritableDatabase is called twice:
// 1. Once for pragma setup
// 2. Once for ensuring Room initialization before preload thread
verify(mockOpenHelper, times(2)).getWritableDatabase();

// Verify the database instance is returned
verify(mockBuilder).build();
}
}

private void mockRoom(MockedStatic<Room> mockedRoom, String databaseName) {
mockedRoom.when(() -> Room.databaseBuilder(
eq(mockApplicationContext),
eq(SplitRoomDatabase.class),
eq(databaseName)
)).thenReturn(mockBuilder);

mockReturn();
}

private void mockReturn() {
when(mockBuilder.setJournalMode(any())).thenReturn(mockBuilder);
when(mockBuilder.fallbackToDestructiveMigration()).thenReturn(mockBuilder);
when(mockBuilder.build()).thenReturn(mockDatabase);
}
}
Loading