I implemented Jacoco in my Android project using the following tutorial https://proandroiddev.com/unified-code-coverage-for-android-revisited-44789c9b722f to cater for test coverage in the kotlin classes.
For some unknown reason, it's not reporting coverage for static methods declared under the Companion block.
class Meh {
companion object {
fun test () {
// logic to test
}
}
However if I convert the class to an instance rather than a singleton that I am able to see the coverage completely fine.
Has anyone came across this problem ? and what did you do ?
following tutorial https://proandroiddev.com/unified-code-coverage-for-android-revisited-44789c9b722f
after cloning of example from the same tutorial in its state as of today (HEAD commit)
git clone https://github.com/rafaeltoledo/unified-code-coverage-android.git
cd unified-code-coverage-android
git checkout kotlin-coverage
addition of companion object into MainActivity
class MainActivity : AppCompatActivity() {
+ companion object {
+ fun executed() {
+ }
+
+ fun notExecuted() {
+ }
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
+ executed()
start of virtual device Pixel XL with API 28 and target Android 9.0 (Google APIs) in freshly downloaded Android Studio 3.2.1
and execution of
./gradlew jacocoTestReport
following report is produced in directory app/build/reports/jacoco/jacocoTestReport/html/ as expected
Given the amount of factors that influence result (such as versions of all involved components - Android SDK, Device, Kotlin compiler, Gradle, JaCoCo, etc, etc), attempts to guess what is different in your case are IMO counterproductive, and so that the best advice - is to perform very careful comparison of differences between your setup and above example.
Update
As was figured out during comparison by #HeWhoProtects , problem was in
exclusion of **/*$*
that refers to exclusion of class files from analysis. Single source file can compile into multiple class files, e.g. in case of nested classes in Java and exactly in case of companion in Kotlin and in both cases name of class and class file will contain $.
I found the the cause of the problem but not sure why it caused it yet, my excludes rules includes more rules than the one in the tutorial above, in different jacoco tutorial for ignoring autogenerated files, it was suggested to include '**/*$*' as rule, as soon as I removed it, it showed coverage for static methods in kotlin.
My understanding of Jacoco that these rules ignore files and will not show it in the report, and before I made the change, it was showing that this class is covered in the test coverage.... is it weird or am I missing a fundamental thing about how kotlin generates methods or how jacoco excludes rules work ?
Anyway I hope this helps..
Related
Recently I upgrade my gradle from 6.5.1 to 7.0 i.e.
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip
After the upgrade, I notice my test coverage dropped.
After investigation, I found out that in Gradle 6.5.1, my JaCoCo test coverage report shows some of my class has an encode() function, but it no longer exists in Gradle 7.0.
I dig out the simplest example below. The code is as below
In Gradle 6.5, my JaCoCo report is as below (notice there's an additional encode() function.
However, in Gradle 7.0, my JaCoCo report is as below
Because of this missing covered function, hence my coverage dropped. Nonetheless, it looks like in Gradle 7.0, that is more correct, as my real code doesn't have encode().
I'm just trying to understand where encode() function is there in the first place, and why it is no longer in Gradle 7.0? And is it correct that my assumption that Gradle 7.0 result is correct?
Different versions of Gradle come with different values of jacoco.toolVersion which you also can change in your build.gradle, and which controls version of JaCoCo to use.
Gradle 6.5 by default uses JaCoCo 0.8.5, and starting from Gradle 6.7 default is 0.8.6.
And here is a list of changes in JaCoCo version 0.8.6 - https://www.jacoco.org/jacoco/trunk/doc/changes.html
Most likely your interface ContextData contains method encode with default implementation.
JVM supports default methods in interfaces starting from version 8, however Kotlin supports compilation into bytecode of version 6. To do so Kotlin compiler does a trick for such methods - it generates method in classes implementing this interface that merely delegates execution to default implementation:
for the following Example.kt
interface ContextData {
fun encode() = { /* something */ }
}
data class SearchRefinementModalOpenData(
val userAction: String?
) : ContextData
execution of
kotlin-compiler-1.4.32/bin/kotlinc Example.kt
javap -v -p SearchRefinementModalOpenData.class
shows following bytecode
public final class SearchRefinementModalOpenData implements ContextData
{
public kotlin.jvm.functions.Function0<kotlin.Unit> encode();
descriptor: ()Lkotlin/jvm/functions/Function0;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokestatic #29 // Method ContextData$DefaultImpls.encode:(LContextData;)Lkotlin/jvm/functions/Function0;
4: areturn
JaCoCo starting from version 0.8.6 filters out such methods since they are compiler artifacts and not presented in original source code.
It looks like you are using kotlinx.Serialization.
That generates a few encodeToString and similar functions for your objects. I guess Gradle 7 now ignores those?
I work on an Android project using multiple Project Flavors, and we use Koin to inject the appropriate dependencies based on the current flavor.
We already use the checkModules Gradle task (described here : https://start.insert-koin.io/#/getting-started/testing?id=checking-your-modules) in order to ensure that our dependency tree is valid.
However, it seems that there is a use case missing.
Let's say I want to inject an InterfaceA in my Activity. I would write the following code :
class MyActivity : Activity() {
private val interfaceA_Impl: InterfaceA by inject()
...
}
Koin requires the implementation of InterfaceA to be provided in a module, as such :
val myModule = module {
single<InterfaceA> { MyInterfaceImpl() }
}
In my project, each implementation is "flavor-specific".
My question is :
Is there a way to ensure that ALL by inject targets are valid ? In other words, to ensure that all interfaces that I am trying to inject have valid implementations ? Currently, if an implementation is forgotten, the app crashes during runtime, and I would like to know about it sooner (maybe during unit tests, at the same time checkModules is ran ?)
Thanks a lot !
I am trying to implement custom lint checks (using Kotlin). I have set up a module for my custom checks and added classes to test my first lew lint check, mostly following these two tutorials here and here.
So I now have a module, I have a custom IssueRegistry, I've created an issue and a Detector class for it. So far it seems complete. I've added a test to check if my lint check works and it looks alright.
I have added my module to the project by referencing it in settings.gradle like this: include ':app', ':somemodule', ':mylintmodule'
Now if I run the linter using ./gradlew lint I get a lint result file telling me this:
Lint found an issue registry (com.myproject.mylintmodule) which requires a newer API level. That means that the custom lint checks are intended for a newer lint version; please upgrade
Lint can be extended with "custom checks": additional checks implemented by developers and libraries to for example enforce specific API usages required by a library or a company coding style guideline.
The Lint APIs are not yet stable, so these checks may either cause a performance degradation, or stop working, or provide wrong results.
This warning flags custom lint checks that are found to be using obsolete APIs and will need to be updated to run in the current lint environment.
It may also flag issues found to be using a newer version of the API, meaning that you need to use a newer version of lint (or Android Studio or Gradle plugin etc) to work with these checks.
To suppress this error, use the issue id "ObsoleteLintCustomCheck" as explained in the Suppressing Warnings and Errors section.
So it tells me that I am using a newer API verion in my custom lint check, right? This is my custom IssueRegistry (minus some parts not relevant for this problem):
class MyCustomIssueRegistry : IssueRegistry() {
override val issues: List<Issue>
get() = listOf(ISSUE_NAMING_PATTERN)
override val api: Int = com.android.tools.lint.detector.api.CURRENT_API
override val minApi: Int = 1
}
From googling this problem and finding this issue I figured I have to override and set the right API version (and maybe the min API?) by overriding these properties like I did above (this version is my last attempt, directly taken from that issue).
So this property can be set to values between -1 and 5, meaning this (taken right out of the lint.detector.api class):
/** Describes the given API level */
fun describeApi(api: Int): String {
return when (api) {
5 -> "3.5+" // 3.5.0-alpha07
4 -> "3.4" // 3.4.0-alpha03
3 -> "3.3" // 3.3.0-alpha12
2 -> "3.2" // 3.2.0-alpha07
1 -> "3.1" // Initial; 3.1.0-alpha4
0 -> "3.0 and older"
-1 -> "Not specified"
else -> "Future: $api"
}
I have tried all of them, plus the one above adding a minApi override too, and I keep getting the exact same result for each of them.
Also I am unable to locate what other API version this is compared with. Is there a place where this is set for the regular linter in an Android project?
It's also unclear to me what I have to do to make sure my changes got applied - is it enough to change some code, then run lint, or do I have to compile the project first, or build & clean?
Following the tutorials, I added my custom lint check by adding this to the app's build.gradle: lintChecks project(":mylintmodule")
Is that even right? The API issue on my registry class shows up no matter if my lint check is referenced (and hopefully used) like that or not. I have also tried the other method described in the first tutorial, adding this task to the linter module build.gradle:
defaultTasks 'assemble'
task copyLintJar(type: Copy) {
description = 'Copies the lint jar file into the {user.home}/.android/lint folder.'
from('build/libs/')
into(System.getProperty("user.home") + '/.android/lint')
include("*.jar")
}
// Runs the copyLintJar task after build has completed.
build.finalizedBy(copyLintJar)
But since I can't figure out how to see if my custom checks are actually run, I don't know if that works as intended either.
So how do I get this warning resolved (since I interpret the text as "As long as the versions don't match I will not try to run your lint check"), and how can I make sure my lint check is actually run by the linter?
I have model
#Data
#AllArgsConstructor
#NoArgsConstructor
public class ProductsRequest {
private String initiatorType;
private String categoryCode;
I have lombok config:
lombok.anyConstructor.suppressConstructorProperties = true
lombok.addGeneratedAnnotation = false
On android with API 27(Android 7 on real device) all work fine. On android 17(Android 4.2 on emulator) In this line I get error:
return restApiFactory.getProductService().getProducts(productsRequest);
error:
Caused by: java.lang.ClassNotFoundException: Didn't find class "java.beans.ConstructorProperties" on path: /data/app/my-1.apk
If I change
#AllArgsConstructor
#NoArgsConstructor
to standart constructors - all work fine
Because I never experienced such issue with ctor-s I encourage you to describe your problem in more detail. I assume the code you manually wrote somehow differs from the code lombok generated. Might be the visibility of methods or some special annotation added.
Using delombok feature (https://projectlombok.org/features/delombok) you will expand the annotations to real code. Than you can diff your manually written code and lombok generated code. So you can explore if #java.beans.ConstructorProperties added or not above the code lombok generated. (Delombok using Gradle)
Note: Actually you will have 3 ctor: #AllArgsConstructor, #NoArgsConstructor and #RequiredArgsConstructor is implicitly covered in #Data.
Based on your lombok.config file, ctor-s should not have an annotation. Could it be that some of your flavors are missing the lombok.config on class path?
I would like to use the Scala (2.11) reflection package's runtime mirror in a Scala application compiled for android which is being build using Scala on android.
I was able to fiddle with ProGuard options in order to make it include the required Scala classes. However when I try to get a mirror instance:
universe.runtimeMirror(this.getClass.getClassLoader)
(Indeed it fails during the lazy computation of universe)
The application crashes in run time:
java.lang.NoClassDefFoundError: Failed resolution of: Ljava/rmi/Remote;
at scala.reflect.internal.Definitions$DefinitionsClass.RemoteInterfaceClass$lzycompute(Definitions.scala:370)
at scala.reflect.internal.Definitions$DefinitionsClass.RemoteInterfaceClass(D efinitions.scala:370)
at scala.reflect.runtime.JavaUniverseForce$class.force(JavaUniverseForce.scal a:255)
at scala.reflect.runtime.JavaUniverse.force(JavaUniverse.scala:16)
at scala.reflect.runtime.JavaUniverse.init(JavaUniverse.scala:147)
at scala.reflect.runtime.JavaUniverse.<init>(JavaUniverse.scala:78)
at scala.reflect.runtime.package$.universe$lzycompute(package.scala:17)
at scala.reflect.runtime.package$.universe(package.scala:17)
This crash is for me as expected as it isn't:
It is expected as java.rmi is not part of the Android API and I should expect any code trying to load its classes to crash.
It is unexpected as I didn't know that Scala's reflect package used java.rmi
I have traced the code to were rmi is required, that is to JavaUniverse (a trait mixed in JavaUniverse class) force method:
...
definitions.RemoteInterfaceClass
...
Which leads to DefinitionsClass:
lazy val RemoteInterfaceClass = requiredClass[java.rmi.Remote]
Am I wrong to think that this is a no-go for Scala reflection in Android?
If I am, what could be a workaround to this problem?
To summarize your solution and a related solution, it is sufficient to add two files, and modify build.sbt to include:
dexAdditionalParams in Android += "--core-library"
Add java/rmi/Remote.java to your project with the content:
package java.rmi;
public interface Remote {}
Add java/rmi/RemoteException.java to your project with the content:
package java.rmi;
public interface RemoteException {}