Skip to content

Conversation

bric3
Copy link
Contributor

@bric3 bric3 commented Nov 24, 2016

This is a work from @raphw to bring mockito to android devices without dexmaker, using ByteBuddy Android instead.

@raphw
Copy link
Member

raphw commented Nov 24, 2016

Thanks for opening this. One problem is really test coverage because running the test requires writing instrumented tests that can only be run on an emulator or on a device. I doubt that our current infrastructure supports this.

I ran a successful test yesterday night on my own device. In contrast to Mockito-Dexmaker I also managed to add automatic support for modern API versions where the previous automatic discovery did no longer work.

@codecov-io
Copy link

codecov-io commented Nov 24, 2016

Current coverage is 86.55% (diff: 68.00%)

No coverage report found for release/2.x at 0819662.

Powered by Codecov. Last update 0819662...e3ffc01

@TimvdLippe
Copy link
Contributor

TimvdLippe commented Nov 24, 2016 via email

@raphw
Copy link
Member

raphw commented Nov 24, 2016

Great, thank you. As I said before, ideally, I would like the subproject to follow the version numbers of mockito-core. This way, people can simply exchange the known dependency with mockito-android and have everything work out of the box.

@bric3
Copy link
Contributor Author

bric3 commented Nov 24, 2016

What if we create another project on the mockito organisation, that can have a different lifecycle and travis configuration ?

@TimvdLippe
Copy link
Contributor

TimvdLippe commented Nov 24, 2016 via email

@mockitoguy
Copy link
Member

Curious, what's driving this effort? E.g. why to replace dexmaker?

What if we create another project on the mockito organisation, that can have a different lifecycle and travis configuration ?

Great idea!!!

  • different project can have different contributors and release cycle
  • it will push us to get the release plugin to be reusable
  • it will teach us how to manage multiple inter-dependent projects, versioning separately (could help TestNG integration, etc)

@raphw
Copy link
Member

raphw commented Nov 24, 2016

Dexmaker does no longer work out of the box with newer android versions and its not compatible with mockito 2. Also, the project seems dead.

@TimvdLippe
Copy link
Contributor

I will remove the Travis config and try to fix publication of these artifacts. Not sure how I would be able to test these though...

@TimvdLippe
Copy link
Contributor

@szczepiq could you take a look at the above commit and confirm this correctly publishes 2 artifacts to bintray with separate artifact-id's? Then we can merge this PR 😄

@mockitoguy
Copy link
Member

Sure, I'll look into it by the end of this week!

@mockitoguy mockitoguy self-assigned this Dec 1, 2016
@@ -9,7 +9,7 @@ bintray {
user = 'szczepiq'
key = System.env.MOCKITO_BINTRAY_API_KEY

publications = ['mockitoCore']
publications = ['mockitoCore', 'mockitoAndroid']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this works. Did you test it? 'mockitoAndroid' is a publication in a different Gradle subproject.

@@ -1,3 +1,5 @@
include 'android'
include 'inline'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'inline' artifact, too? :)

What is the strategy for 'inline' artifact? I'd rather mockito worked out of the box for final classes (or have toggles in the API). I don't like the idea of separate mockito-inline artifact.

@mockitoguy
Copy link
Member

FYI: dexmaker is alive - group of devs acquired the ownership and are planning to ship new versions with bugfixes.

@TimvdLippe, the publication will not work. I guess you didn't have a clean way to test this. I will try to fix this so that the release procedure is testable locally.

@bric3, @raphw, this should be a separate project.

Consistent version scheme makes sense from the standpoint of simplicity. However, it introduces awkward versioning: what if there is a change in 'android' project without a change in mockito project. In this scenario do we publish new version of mockito-core without any changes to its code? Also, keeping it in the same project prevents us from having different release cadence that everybody would like to try out (e.g. pushing less frequently ;). Also, it prevents us from having different contributors for the project.

Same project does have it's charm :) It would automatically publish new version of mockito-android everytime there is a new version of mockito-core. However, we could probably get this working using Travis build requests.

I suggest we push forward the separate project idea. I will update the release plugin so that it dead simple to reuse.

Copy link
Contributor

@TimvdLippe TimvdLippe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Content seems fine. @szczepiq when you have fixed the release procedure you can merge this PR 😄

@raphw
Copy link
Member

raphw commented Dec 7, 2016

I disagree with the "different projects" assertion. mockito-android and mockito-inline are fairly trivial artifacts. There is very little chance that changes in these artifacts happen a lot so I do not think that this would require a lot of releases. Also, it is very common to manage an Android-specific and a JVM-specific dependency for running tests on an emulator or on the current machine.

The mockito-inline project was added after I received a fair amount of feedback where people wanted to enable the functionality but struggled with it. The mock maker file is not type-safe and its easy to break things with a simple typo. Once we do no longer see a need in this artifact by enabling the mock maker by default, we stop publishing the artifact. This will probably happen with Mockito 3. This is similar to the mockito-all artifact that we did retire.

For now, I think that simply publishing the artifacts with a common version would be the most pragmatic solution. A lot of libraries are published like this to allow reusing of a single version number and in order to keep the publication process atomic.

@bric3
Copy link
Contributor Author

bric3 commented Dec 8, 2016

FYI: dexmaker is alive - group of devs acquired the ownership and are planning to ship new versions with bugfixes.

While dexmaker is being restarted at linkedin I don't see it as a problem to have a concurrent implementation, Byte buddy supports dex since a long time already. Choice is always good. And both mockmaker project may help each other if needed ;)

Consistent version scheme makes sense from the standpoint of simplicity. However, it introduces awkward versioning: what if there is a change in 'android' project without a change in mockito project. In this scenario do we publish new version of mockito-core without any changes to its code? Also, keeping it in the same project prevents us from having different release cadence that everybody would like to try out (e.g. pushing less frequently ;). Also, it prevents us from having different contributors for the project.

  1. As a dependency user when there's several artifacts from the same vendor like jersey, cxf or spring, I find it way as way way way more manageable if all those artifact have the same version. I usually have my project descriptor with properties and I just need to change the property to bump all artifacts at once.
  2. Regarding fixes in an artifact that would bump one another. We somehow have a similar issue with the current release model : if there's a tweak of internal code, that doesn't fix bug, doesn't introduce bugs it's just housekeeping to help maintainability, then a new version of mockito is shiped without maningful change.

@raphw For tracking purpose I'd like to have a separate PR for the inline artifact. We can discuss over there whether or not we want it. I'm not in favor of this idea, however I'm not against it.

"or",
"getInstrumentation().getTargetContext().getCacheDir().getPath()"
));
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the exception contain implementation details of AndroidTempFileLocator I think it should be moved in this class to have the whole responsability of the target location including failure.

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
throw new IllegalStateException("Could not initialize plugin: " + pluginType, t);
}
});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it covered by loadImpl ?

Shall we instead catch something larger than Exception there ?

@bric3
Copy link
Contributor Author

bric3 commented Dec 8, 2016

Fun fact : since I'm the one to have opened the PR I cannot request changes...

@bric3
Copy link
Contributor Author

bric3 commented Dec 9, 2016

Hum, I don't know why the build failed

Build file '/home/travis/build/mockito/mockito/subprojects/android/android.gradle' line: 35
Cannot configure the 'publishing' extension after it has been accessed.
org.gradle.api.GradleScriptException: A problem occurred evaluating project ':android'.Open stacktrace
Caused by: org.gradle.api.InvalidUserDataException: Cannot configure the 'publishing' extension after it has been accessed.Close stacktrace
at org.gradle.api.internal.plugins.ExtensionsStorage$DeferredConfigurableExtensionHolder.configureLater(ExtensionsStorage.java:174)
at org.gradle.api.internal.plugins.ExtensionsStorage$DeferredConfigurableExtensionHolder.configure(ExtensionsStorage.java:168)
at org.gradle.api.internal.plugins.ExtensionsStorage.configureExtension(ExtensionsStorage.java:69)
at org.gradle.api.internal.plugins.DefaultConvention$ExtensionsDynamicObject.invokeMethod(DefaultConvention.java:215)
at org.gradle.internal.metaobject.CompositeDynamicObject.invokeMethod(CompositeDynamicObject.java:96)
at org.gradle.api.internal.ExtensibleDynamicObject$InheritedDynamicObject.invokeMethod(ExtensibleDynamicObject.java:245)
at org.gradle.internal.metaobject.CompositeDynamicObject.invokeMethod(CompositeDynamicObject.java:96)
at org.gradle.internal.metaobject.MixInClosurePropertiesAsMethodsDynamicObject.invokeMethod(MixInClosurePropertiesAsMethodsDynamicObject.java:30)
at org.gradle.internal.metaobject.AbstractDynamicObject.invokeMethod(AbstractDynamicObject.java:163)
at org.gradle.groovy.scripts.BasicScript.methodMissing(BasicScript.java:79)
at android_8vktxc6vn44yso4y4a46h6gj7.run(/home/travis/build/mockito/mockito/subprojects/android/android.gradle:35)
at org.gradle.groovy.scripts.internal.DefaultScriptRunnerFactory$ScriptRunnerImpl.run(DefaultScriptRunnerFactory.java:91)
at org.gradle.configuration.DefaultScriptPluginFactory$ScriptPluginImpl$2.run(DefaultScriptPluginFactory.java:177)
at org.gradle.configuration.ProjectScriptTarget.addConfiguration(ProjectScriptTarget.java:77)
at org.gradle.configuration.DefaultScriptPluginFactory$ScriptPluginImpl.apply(DefaultScriptPluginFactory.java:182)
at org.gradle.configuration.project.BuildScriptProcessor.execute(BuildScriptProcessor.java:38)
at org.gradle.configuration.project.BuildScriptProcessor.execute(BuildScriptProcessor.java:25)
at org.gradle.configuration.project.ConfigureActionsProjectEvaluator.evaluate(ConfigureActionsProjectEvaluator.java:34)
at org.gradle.configuration.project.LifecycleProjectEvaluator.evaluate(LifecycleProjectEvaluator.java:55)
at org.gradle.api.internal.project.AbstractProject.evaluate(AbstractProject.java:540)
at org.gradle.api.internal.project.AbstractProject.evaluate(AbstractProject.java:93)
at org.gradle.execution.TaskPathProjectEvaluator.configureHierarchy(TaskPathProjectEvaluator.java:47)
at org.gradle.configuration.DefaultBuildConfigurer.configure(DefaultBuildConfigurer.java:35)
at org.gradle.initialization.DefaultGradleLauncher$2.run(DefaultGradleLauncher.java:124)
at org.gradle.internal.Factories$1.create(Factories.java:22)
at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:91)
at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:53)
at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:121)
at org.gradle.initialization.DefaultGradleLauncher.access$200(DefaultGradleLauncher.java:32)
at org.gradle.initialization.DefaultGradleLauncher$1.create(DefaultGradleLauncher.java:98)
at org.gradle.initialization.DefaultGradleLauncher$1.create(DefaultGradleLauncher.java:92)
at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:91)
at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:63)
at org.gradle.initialization.DefaultGradleLauncher.doBuild(DefaultGradleLauncher.java:92)
at org.gradle.initialization.DefaultGradleLauncher.run(DefaultGradleLauncher.java:83)
at org.gradle.launcher.exec.InProcessBuildActionExecuter$DefaultBuildController.run(InProcessBuildActionExecuter.java:99)
at org.gradle.tooling.internal.provider.ExecuteBuildActionRunner.run(ExecuteBuildActionRunner.java:28)
at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:48)
at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:30)
at org.gradle.launcher.exec.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:81)
at org.gradle.launcher.exec.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:46)
at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:52)
at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:37)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:26)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:34)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:74)
at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:72)
at org.gradle.util.Swapper.swap(Swapper.java:38)
at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:72)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.health.DaemonHealthTracker.execute(DaemonHealthTracker.java:47)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:60)
at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:72)
at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.health.HintGCAfterBuild.execute(HintGCAfterBuild.java:41)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:50)
at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:237)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)
at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)

artifact javadocJar
}
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The publishing declaration here fail, not sure why https://scans.gradle.com/s/7myhdoeojp3wi/failure
as it does look correct.

@mockitoguy
Copy link
Member

I plan to finish off #809 tomorrow and then we will have release capability of other jars.

@TimvdLippe
Copy link
Contributor

@raphw do you have time to update this PR with the new publication method? Else I will try to make it work soonTM.

@raphw
Copy link
Member

raphw commented Dec 24, 2016

Hei Tim, I am quite busy with Byte Buddy Java 9 support these days so I would prefer if you find the time. Appreciate it!

@TimvdLippe
Copy link
Contributor

@raphw All right will try to take a look!

@TimvdLippe TimvdLippe self-assigned this Dec 24, 2016
@TimvdLippe
Copy link
Contributor

I fixed compilation, but it is now failing on generating Javadoc for the Android subproject:

/home/travis/build/mockito/mockito/build/libs/mockito-core-2.4.3.jar(org/mockito/configuration/IMockitoConfiguration.java):63: error: cannot access AnnotationEngine
    AnnotationEngine getAnnotationEngine();
    ^
  bad source file: /home/travis/build/mockito/mockito/build/libs/mockito-core-2.4.3.jar(org/mockito/configuration/AnnotationEngine.java)
    file does not contain class org.mockito.configuration.AnnotationEngine
    Please remove or make sure it appears in the correct subdirectory of the sourcepath.
1 error
:android:javadoc FAILED

In a similar fashion to the TestNG subproject, I disabled generation of Javadoc for the Android subproject

"Failed to load " + pluginType, e);
}
} catch (final Throwable t) {
return (T) Proxy.newProxyInstance(pluginType.getClassLoader(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we return proxy instead of failing?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because throwing exceptions from initializers yield strange exceptions where the cause for the failure is no longer apparent but NoClassDefFoundErrors are shown. This results in much better errors where the cause is visible.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fell over this myself when dealing with a robolectrics incompatibility. Took me hours to understand this link.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, merging!

Copy link
Member

@mockitoguy mockitoguy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I have one question.

@TimvdLippe TimvdLippe merged commit e4673b5 into release/2.x Dec 25, 2016
@TimvdLippe TimvdLippe deleted the android-mock branch December 25, 2016 15:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants