main

square/leakcanary

Last updated at: 29/12/2023 09:37

DatabaseMigrationTest.kt

TLDR

The provided file, DatabaseMigrationTest.kt, contains a class named DatabaseMigrationTest that tests the migration of a database. It includes several test methods that check various aspects of the database migration, such as the version number, the presence of heap dumps, the deserialization of heap dumps, the number of leak traces, the number of leak types, and the types of GC roots. The class also provides helper methods for database operations and assertions.

Methods

v24_upgraded_to_latest

This method tests whether the database has been successfully upgraded to the latest version. It compares the version number of the upgraded database to the expected version.

v24_has_1_heap_dumps

This method tests whether the database contains one heap dump. It retrieves all heap dumps from the database and checks the size of the resulting list.

v24_heap_dumps_can_be_deserialized

This method tests whether the heap dumps in the database can be deserialized. It retrieves all heap dumps from the database and asserts that each heap dump is an instance of HeapAnalysisSuccess.

v24_has_8_leak_traces

This method tests whether the database contains eight leak traces. It retrieves all heap dumps from the database, extracts the leak traces from each heap dump, and checks the total number of leak traces.

v24_has_3_leak_types

This method tests whether the database contains three leak types. It retrieves all leaks from the database and checks the size of the resulting list.

v24_leaks_are_new

This method tests whether all the leaks in the database are marked as new. It retrieves all leaks from the database and asserts that each leak has the property isNew set to true.

v24_has_5_sticky_class_and_3_java_frame_gc_roots

This method tests whether the database contains five leak traces with GC root type STICKY_CLASS and three leak traces with GC root type JAVA_FRAME. It retrieves all heap dumps from the database, extracts the leak traces from each heap dump, and counts the occurrences of each GC root type.

Classes

None

package leakcanary

import android.database.sqlite.SQLiteDatabase
import androidx.test.platform.app.InstrumentationRegistry
import leakcanary.internal.activity.db.HeapAnalysisTable
import leakcanary.internal.activity.db.LeakTable
import leakcanary.internal.activity.db.LeaksDbHelper
import leakcanary.internal.activity.db.ScopedLeaksDb
import org.junit.Test
import shark.HeapAnalysis
import shark.HeapAnalysisSuccess
import shark.LeakTrace.GcRootType.JAVA_FRAME
import shark.LeakTrace.GcRootType.STICKY_CLASS

class DatabaseMigrationTest {

  @Test fun v24_upgraded_to_latest() {
    DB_V24 upgrade {
      version assertEquals LeaksDbHelper.VERSION
    }
  }

  @Test fun v24_has_1_heap_dumps() {
    DB_V24 upgrade {
      HeapAnalysisTable.retrieveAll(this).size assertEquals 1
    }
  }

  @Test fun v24_heap_dumps_can_be_deserialized() {
    DB_V24 upgrade {
      HeapAnalysisTable.retrieveAll(this)
        .forEach { projection ->
          val heapAnalysis = HeapAnalysisTable.retrieve<HeapAnalysis>(this, projection.id)!!
          heapAnalysis assertIs HeapAnalysisSuccess::class.java
        }
    }
  }

  @Test fun v24_has_8_leak_traces() {
    DB_V24 upgrade {
      val allLeakTraces = HeapAnalysisTable.retrieveAll(this)
        .map { HeapAnalysisTable.retrieve<HeapAnalysisSuccess>(this, it.id)!! }
        .flatMap { analysis ->
          analysis.allLeaks.toList()
        }
        .flatMap { leak ->
          leak.leakTraces
        }

      allLeakTraces.size assertEquals 8
    }
  }

  @Test fun v24_has_3_leak_types() {
    DB_V24 upgrade {
      LeakTable.retrieveAllLeaks(this).size assertEquals 3
    }
  }

  @Test fun v24_leaks_are_new() {
    DB_V24 upgrade {
      LeakTable.retrieveAllLeaks(this)
        .forEach { leak ->
          leak.isNew assertEquals true
        }
    }
  }

  @Test fun v24_has_5_sticky_class_and_3_java_frame_gc_roots() {
    DB_V24 upgrade {
      val allLeakTraces = HeapAnalysisTable.retrieveAll(this)
        .map { HeapAnalysisTable.retrieve<HeapAnalysisSuccess>(this, it.id)!! }
        .flatMap { analysis ->
          analysis.allLeaks.toList()
        }
        .flatMap { leak ->
          leak.leakTraces
        }
      val gcRootCounts = allLeakTraces.groupingBy { it.gcRootType }
        .eachCount()

      gcRootCounts.getValue(STICKY_CLASS) assertEquals 2
      gcRootCounts.getValue(JAVA_FRAME) assertEquals 3
    }
  }

  private infix fun String.upgrade(
    block: SQLiteDatabase.() -> Unit
  ) {
    val instrumentation = InstrumentationRegistry.getInstrumentation()
    val context = instrumentation.targetContext

    context.assets.open(this)
      .use { input ->
        val databaseFile = context.getDatabasePath(LeaksDbHelper.DATABASE_NAME)
        databaseFile.parentFile!!.mkdirs()
        databaseFile.outputStream().use { output ->
          input.copyTo(output)
        }
      }
    ScopedLeaksDb.readableDatabase(context) { db ->
      db.block()
    }
  }

  private infix fun Any.assertEquals(otherValue: Any) {
    if (this != otherValue) {
      throw AssertionError("Expecting <$this> to be equal to <$otherValue> but was not.")
    }
  }

  private infix fun Any.assertIs(javaClass: Class<out Any>) {
    if (!javaClass.isInstance(this)) {
      throw AssertionError(
        "Expecting <$this> to be an instance of <${javaClass.name}> but was <${this.javaClass.name}>."
      )
    }
  }

  companion object {
    const val DB_V24 = "leaks-v24.db"
  }
}