Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tests crash in Ant, but succeed in IntelliJ IDEA #2537

Closed
abbeycode opened this issue Jan 7, 2022 · 4 comments
Closed

Tests crash in Ant, but succeed in IntelliJ IDEA #2537

abbeycode opened this issue Jan 7, 2022 · 4 comments

Comments

@abbeycode
Copy link

abbeycode commented Jan 7, 2022

I'm using Mockito to test the mock the static method of a dependency of the class under test, and the test passed while I was developing it in IntelliJ. Once I started running it in Ant on our CI server (and now locally), I'm getting failures. Below I've pasted the sample source files, the test file, the Ant script, and the Ivy dependencies file describing the versions of each library I'm using (all of them up to date at this time), followed by the verbose error I'm seeing.

But if I create these several files in a directory (with the two classes under src/com/example and the unit test under tests/com/example and open it up as an IntelliJ IDEA project, using all the same dependencies (including JUnit) and run all tests, it succeeds. The only change from the standard module template is specifying the JDK path and Java level to 1.8. That's part of what's making it so puzzling. For what it's worth, Ant acts the same whether invoked from within IntelliJ IDEA's built-in Ant integration, or separately on the command line.

Mockito: 4.2.0
Java: 1.8.0_312
IntelliJ IDEA: 2021.3
Ant: 1.10.12
OS: macOS 12.1, centOS 7

Here's a zip file with everything you need to run it. If you want to run it, you'll need Ant installed, and then run the command below to install Ivy:

ant -f <ant install dir>/fetch.xml ivy -Ddest=user

(Stack Overflow cross-post)

BusinessClass.java

package com.example;

public class BusinessClass {
    public String returnString() {
        StaticDependency.DoSomething();
        return "Answer";
    }
}

StaticDependency.java

package com.example;

public class StaticDependency {
    public static void DoSomething() {
        throw new RuntimeException("Real code causes side effects I'm isolating from");
    }
}

BusinessClassTest.java

package com.example;

import org.junit.Test;
import org.mockito.MockedStatic;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mockStatic;

public class BusinessClassTest {
    @Test
    public void returnString() {
        final BusinessClass classUnderTest = new BusinessClass();

        try (final MockedStatic<StaticDependency> ignored = mockStatic(StaticDependency.class)) {
            assertEquals("Answer", classUnderTest.returnString());
        }
    }
}

ivy.xml

<ivy-module version="2.0">
  <info organisation="com.example" module="MockitoTest"/>
  <dependencies>
    <dependency org="junit" name="junit" rev="4.13.2"/>

    <!-- Mockito and dependencies -->
    <dependency org="org.mockito" name="mockito-inline" rev="4.2.0"/>
    <dependency org="net.bytebuddy" name="byte-buddy-dep" rev="1.12.6"/>
    <dependency org="org.objenesis" name="objenesis" rev="3.2"/>
  </dependencies>
</ivy-module>

build.xml

<project default="all" xmlns:ivy="antlib:org.apache.ivy.ant">

  <property name="lib" location="Lib"/>
  <property name="src" location="src"/>
  <property name="tests" location="tests"/>
  <property name="build-test" location="build-test"/>
  <property name="build" location="build"/>

  <path id="build.classpath">
    <fileset dir="${lib}">
      <include name="**/*.jar"/>
    </fileset>
  </path>

  <target name="all" depends="clean, resolve, compile, test"/>

  <target name="clean">
    <delete dir="${build}"/>
    <delete dir="${build-test}"/>
    <delete dir="${lib}"/>
  </target>

  <target name="resolve" description="Download dependencies">
    <ivy:resolve file="ivy.xml"/>
    <mkdir dir="${lib}"/>
    <ivy:retrieve pattern="${lib}/[artifact]-[type].[ext]"/>
  </target>

  <target name="compile" description="Compile the source code">
    <mkdir dir="${build}"/>
    <javac destdir="${build}">
      <src path="${src}"/>
      <classpath refid="build.classpath"/>
    </javac>

    <mkdir dir="${build-test}"/>
    <javac destdir="${build-test}">
      <src path="${src}"/>
      <src path="${tests}"/>
      <classpath refid="build.classpath"/>
    </javac>
  </target>

  <target name="test" depends="compile" description="Run all unit tests">
    <junit>
      <classpath>
        <path refid="build.classpath"/>
        <path path="${build-test}"/>
      </classpath>
      <formatter type="plain" usefile="false"/>

      <test name="com.example.BusinessClassTest" />
    </junit>
  </target>
</project>

JUnit output

Testsuite: com.example.BusinessClassTest
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.889 sec

Testcase: returnString took 0.779 sec
	Caused an ERROR
Could not modify all classes [class com.example.StaticDependency]
org.mockito.exceptions.base.MockitoException: Could not modify all classes [class com.example.StaticDependency]
	at com.example.BusinessClassTest.returnString(Unknown Source)
Caused by: java.lang.IllegalStateException: 
Byte Buddy could not instrument all classes within the mock's type hierarchy

This problem should never occur for javac-compiled classes. This problem has been observed for classes that are:
 - Compiled by older versions of scalac
 - Classes that are part of the Android distribution
	at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.triggerRetransformation(InlineBytecodeGenerator.java:280)
	at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.mockClassStatic(InlineBytecodeGenerator.java:221)
	at org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator.mockClassStatic(TypeCachingBytecodeGenerator.java:63)
	at org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.createStaticMock(InlineDelegateByteBuddyMockMaker.java:560)
	at org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker.createStaticMock(InlineByteBuddyMockMaker.java:83)
	at org.mockito.internal.util.MockUtil.createStaticMock(MockUtil.java:147)
	at org.mockito.internal.MockitoCore.mockStatic(MockitoCore.java:142)
	at org.mockito.Mockito.mockStatic(Mockito.java:2164)
	at org.mockito.Mockito.mockStatic(Mockito.java:2101)
Caused by: java.lang.AbstractMethodError: org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator$ParameterWritingVisitorWrapper.wrap(Lnet/bytebuddy/description/type/TypeDescription;Lorg/objectweb/asm/ClassVisitor;Lnet/bytebuddy/implementation/Implementation$Context;Lnet/bytebuddy/pool/TypePool;Lnet/bytebuddy/description/field/FieldList;Lnet/bytebuddy/description/method/MethodList;II)Lorg/objectweb/asm/ClassVisitor;
	at net.bytebuddy.asm.AsmVisitorWrapper$Compound.wrap(AsmVisitorWrapper.java:746)
	at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing$RedefinitionClassVisitor.visit(TypeWriter.java:4906)
	at org.objectweb.asm.ClassReader.accept(ClassReader.java:569)
	at org.objectweb.asm.ClassReader.accept(ClassReader.java:424)
	at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining.create(TypeWriter.java:3951)
	at net.bytebuddy.dynamic.scaffold.TypeWriter$Default.make(TypeWriter.java:2213)
	at net.bytebuddy.dynamic.scaffold.inline.RedefinitionDynamicTypeBuilder.make(RedefinitionDynamicTypeBuilder.java:224)
	at net.bytebuddy.dynamic.scaffold.inline.AbstractInliningDynamicTypeBuilder.make(AbstractInliningDynamicTypeBuilder.java:123)
	at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase.make(DynamicType.java:3661)
	at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.transform(InlineBytecodeGenerator.java:394)
	at sun.instrument.TransformerManager.transform(TransformerManager.java:188)
	at sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:428)
	at sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
	at sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:144)
	at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.triggerRetransformation(InlineBytecodeGenerator.java:276)
@temp-droid
Copy link
Contributor

Hey @abbeycode ,

Could you try to modify your dependencies block in ivy.xml to the following?

  <dependencies>
    <dependency org="junit" name="junit" rev="4.13.2"/>

    <!-- Mockito and dependencies -->
    <dependency org="org.mockito" name="mockito-inline" rev="4.2.0"/>
    <dependency org="net.bytebuddy" name="byte-buddy" rev="1.12.6"/>
    <dependency org="net.bytebuddy" name="byte-buddy-agent" rev="1.12.6"/>
<!--    <dependency org="org.objenesis" name="objenesis" rev="3.2"/>-->
  </dependencies>

(I had to comment objenesis because it couldn't resolve objenesis-test-3.2, but I could reproduce your issue without it.)

@abbeycode
Copy link
Author

I'm still curious why it worked in IntelliJ with the same JARs present, but this definitely fixed it. I was actually able to remove both explicit byte-buddy dependencies. I had mistakenly thought Ivy didn't recurse dependencies. For the record, I ended up with this demo ivy.xml, much simpler than my original:

<ivy-module version="2.0">
  <info organisation="com.example" module="MockitoTest"/>
  <dependencies>
    <dependency org="junit" name="junit" rev="4.13.2"/>
    <dependency org="org.mockito" name="mockito-inline" rev="4.2.0"/>
  </dependencies>
</ivy-module>

@abbeycode
Copy link
Author

@temp-droid Would you like to answer my question on Stack Overflow too, for any incumbent rep points, or shall I answer it myself?

@temp-droid
Copy link
Contributor

Hey @abbeycode , feel free to comment the answer on SO with your added information too :)

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

No branches or pull requests

2 participants