powermock: IllegalStateException: Failed to transform class with name backend.model.posting.PostingTest. Reason: before ()V in backend.model.posting.PostingTest: inconsistent stack height -1

Hey Guys, I see an IllegalStateException when running one particular test.

What steps will reproduce the problem? The following code fails each time it is run

Testcase:

package backend.model.posting

import backend.controller.exceptions.ConflictException
import backend.model.location.Location
import backend.model.user.UserAccount
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.powermock.api.mockito.PowerMockito
import org.powermock.api.mockito.PowerMockito.mock
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.PowerMockRunner
import java.time.LocalDateTime
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith

@RunWith(PowerMockRunner::class)
@PrepareForTest(Location::class, UserAccount::class)
class PostingTest {

    private lateinit var now: LocalDateTime
    private lateinit var later: LocalDateTime
    private lateinit var someLocation: Location
    private lateinit var creator: UserAccount
    private lateinit var posting: Posting


    @Before
    fun before() {
        now = LocalDateTime.now()
        later = now.plusMinutes(10)
        someLocation = mock(Location::class.java)
        creator = mock(UserAccount::class.java)
        posting = Posting("Lalala", LocalDateTime.MAX, someLocation, creator, mutableListOf())
    }

    @Test
    fun like() {
        posting.like(later, creator)
        assertEquals(posting.likes.count(), 1)
    }

    @Test
    fun multipleLikes() {

        val other = mock(UserAccount::class.java)

        PowerMockito.`when`(creator.id).thenReturn(1)
        PowerMockito.`when`(other.id).thenReturn(2)

        posting.like(later, creator)
        posting.like(later, other)

        assertEquals(posting.likes.count(), 2)
    }

    fun cantLikeTwice() {

        PowerMockito.`when`(creator.id).thenReturn(1)

        posting.like(later, creator)

        assertFailsWith<ConflictException> {
            posting.like(later, creator)
        }
    }

    @Test
    fun unlike() {
        posting.like(later, creator)
        posting.unlike(creator)
    }

    @Test
    fun cantUnlikeNotLiked() {
        assertFailsWith<ConflictException> {
            posting.unlike(creator)
        }
    }

    @Test
    @Ignore("Posting::hasLikesBy needs to be refactored first")
    fun hasLikesBy() {

    }

}

Class under Test:

package backend.model.posting

import backend.controller.exceptions.BadRequestException
import backend.controller.exceptions.ConflictException
import backend.controller.exceptions.NotFoundException
import backend.model.BasicEntity
import backend.model.challenges.Challenge
import backend.model.location.Location
import backend.model.media.Media
import backend.model.user.UserAccount
import java.time.LocalDateTime
import java.util.*
import java.util.regex.Matcher
import java.util.regex.Pattern
import javax.persistence.*
import javax.persistence.CascadeType.PERSIST

@Entity
class Posting : BasicEntity {

    private constructor() : super()

    @Column(columnDefinition = "TEXT")
    var text: String? = null

    @ElementCollection
    var hashtags: List<Hashtag> = ArrayList()

    lateinit var date: LocalDateTime

    @OneToOne(cascade = arrayOf(PERSIST))
    var location: Location? = null

    @OneToOne(cascade = arrayOf(PERSIST))
    var challenge: Challenge? = null

    @ManyToOne
    var user: UserAccount? = null

    @OneToMany(cascade = arrayOf(CascadeType.ALL))
    var media: MutableList<Media> = arrayListOf()

    @OneToMany(cascade = arrayOf(CascadeType.ALL), orphanRemoval = true)
    var comments: MutableList<Comment> = arrayListOf()

    @OneToMany(cascade = arrayOf(CascadeType.ALL), orphanRemoval = true)
    @JoinTable(
            joinColumns = arrayOf(JoinColumn(name = "posting_id", referencedColumnName = "id")),
            inverseJoinColumns = arrayOf(JoinColumn(name = "like_id", referencedColumnName = "id"))
    )
    var likes: MutableSet<Like> = hashSetOf()

    @Transient
    var hasLiked = false

    constructor(text: String?, date: LocalDateTime, location: Location?, user: UserAccount, media: MutableList<Media>) : this() {
        this.text = text
        this.date = date
        this.location = location
        this.user = user
        this.media = media
        if (text != null) this.hashtags = extractHashtags(text)
    }

    private fun extractHashtags(text: String): List<Hashtag> {
        val pattern: Pattern = Pattern.compile("#([^\\s]*)")
        val matcher: Matcher = pattern.matcher(text)

        val hashtags = ArrayList<Hashtag>()

        while (matcher.find()) {
            val hashtag = matcher.group(1)
            hashtags.add(Hashtag(hashtag))
        }

        return hashtags
    }

    fun like(createdAt: LocalDateTime, user: UserAccount): Like {
        val like = Like(createdAt, user)

        if (this.isLikedBy(user)) {
            throw ConflictException("User ${user.id} has already liked this posting!")
        } else {
            this.likes.add(like)
        }

        return like
    }

    fun unlike(user: UserAccount) {
        if (this.isLikedBy(user)) {
            val like = findLikeByUser(user)
            this.likes.remove(like)
        } else {
            throw ConflictException("Can't unlike because user ${user.id} has not liked posting ${this.id}")
        }
    }

    private fun findLikeByUser(user: UserAccount): Like? {
        return this.likes
                .filter { it.user?.id == user.id } // TODO: use equals here somehow?
                .firstOrNull()
    }

    private fun isLikedBy(user: UserAccount): Boolean {
        return findLikeByUser(user) != null
    }


    @PreRemove
    fun preRemove() {
        this.likes.clear()
        this.comments.clear()
        this.media.clear()
        this.challenge = null
        this.user = null
    }

    // TODO: Refactor this!
    fun hasLikesBy(userId: Long?): Posting {
        if (userId != null) {
            this.hasLiked = this.likes.any { it.user?.id == userId }
        }
        return this
    }

    private fun findCommentById(commentId: Long): Comment? {
        return this.comments.filter { it.id == commentId }.firstOrNull()
    }

    fun removeComment(commentId: Long) {

        this.findCommentById(commentId)?.let {
            this.comments.remove(it)
            return
        }

        throw NotFoundException("Comment with id $commentId not found at posting $id")
    }

    fun addComment(from: UserAccount, at: LocalDateTime, withText: String): Comment {

        if (withText.trim().isEmpty()) {
            throw BadRequestException("Empty comments are not allowed")
        }

        val comment = Comment(withText, at, from)
        this.comments.add(comment)
        return comment
    }
}

What is the expected output? All tests run without exceptions

What I see instead Stacktrace:

java.lang.IllegalStateException: Failed to transform class with name backend.model.posting.PostingTest. Reason: before ()V in backend.model.posting.PostingTest: inconsistent stack height -1

	at org.powermock.core.classloader.MockClassLoader.loadMockClass(MockClassLoader.java:283)
	at org.powermock.core.classloader.MockClassLoader.loadModifiedClass(MockClassLoader.java:192)
	at org.powermock.core.classloader.DeferSupportingClassLoader.loadClass(DeferSupportingClassLoader.java:71)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:348)
	at org.powermock.modules.junit4.common.internal.impl.JUnit4TestSuiteChunkerImpl.createDelegatorFromClassloader(JUnit4TestSuiteChunkerImpl.java:161)
	at org.powermock.modules.junit4.common.internal.impl.JUnit4TestSuiteChunkerImpl.createDelegatorFromClassloader(JUnit4TestSuiteChunkerImpl.java:48)
	at org.powermock.tests.utils.impl.AbstractTestSuiteChunkerImpl.createTestDelegators(AbstractTestSuiteChunkerImpl.java:113)
	at org.powermock.modules.junit4.common.internal.impl.JUnit4TestSuiteChunkerImpl.<init>(JUnit4TestSuiteChunkerImpl.java:71)
	at org.powermock.modules.junit4.common.internal.impl.AbstractCommonPowerMockRunner.<init>(AbstractCommonPowerMockRunner.java:32)
	at org.powermock.modules.junit4.PowerMockRunner.<init>(PowerMockRunner.java:34)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
	at org.junit.internal.builders.AnnotatedBuilder.buildRunner(AnnotatedBuilder.java:104)
	at org.junit.internal.builders.AnnotatedBuilder.runnerForClass(AnnotatedBuilder.java:86)
	at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)
	at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:26)
	at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)
	at org.junit.internal.requests.ClassRequest.getRunner(ClassRequest.java:33)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:49)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:237)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: javassist.CannotCompileException: before ()V in backend.model.posting.PostingTest: inconsistent stack height -1
	at javassist.expr.ExprEditor.doit(ExprEditor.java:117)
	at javassist.CtClassType.instrument(CtClassType.java:1465)
	at org.powermock.core.transformers.impl.ClassMockTransformer.transformMockClass(ClassMockTransformer.java:65)
	at org.powermock.core.transformers.impl.AbstractMainMockTransformer.transform(AbstractMainMockTransformer.java:247)
	at org.powermock.core.classloader.MockClassLoader.loadMockClass(MockClassLoader.java:264)
	... 25 more
Caused by: javassist.bytecode.BadBytecode: before ()V in backend.model.posting.PostingTest: inconsistent stack height -1
	at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:111)
	at javassist.bytecode.MethodInfo.rebuildStackMap(MethodInfo.java:456)
	at javassist.bytecode.MethodInfo.rebuildStackMapIf6(MethodInfo.java:438)
	at javassist.expr.ExprEditor.doit(ExprEditor.java:113)
	... 29 more
Caused by: javassist.bytecode.BadBytecode: inconsistent stack height -1
	at javassist.bytecode.stackmap.Tracer.doOpcode(Tracer.java:84)
	at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:187)
	at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:199)
	at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:199)
	at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:199)
	at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:199)
	at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:199)
	at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:199)
	at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:199)
	at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:199)
	at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:199)
	at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:199)
	at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:199)
	at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:199)
	at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:199)
	at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:199)
	at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:199)
	at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:199)
	at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:199)
	at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:199)
	at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:199)
	at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:199)
	at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:199)
	at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:199)
	at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:164)
	at javassist.bytecode.stackmap.MapMaker.make(MapMaker.java:108)
	... 32 more
Caused by: java.lang.ArrayIndexOutOfBoundsException: -1
	at javassist.bytecode.stackmap.Tracer.doASTORE(Tracer.java:425)
	at javassist.bytecode.stackmap.Tracer.doOpcode54_95(Tracer.java:342)
	at javassist.bytecode.stackmap.Tracer.doOpcode(Tracer.java:76)
	... 57 more

What version of the product are you using? 1.6.5’

On what operating system? MacOS Sierra (10.12.3)

Please provide any additional information below. We are using PowerMock with Kotlin in a Spring Boot Project. This is a unit test without any spring context running.

We additionally use the kotlin-spring plugin which makes kotlin classes open at compile time (https://kotlinlang.org/docs/reference/compiler-plugins.html) (I tried disabling it, it did not have any effect on the bug)

The error seems to be triggered by the line posting = Posting("Lalala", LocalDateTime.MAX, someLocation, creator, mutableListOf()) in the before function. Without this line, the error does not occur but my tests fail as expected instead (because posting is not initialised)

I would be more than happy to assist with debugging this, but would possibly need a lead on where to search / what to look for, as I don’t have much experience on the low level / bytecode level stuff yet

About this issue

  • Original URL
  • State: open
  • Created 7 years ago
  • Comments: 18 (2 by maintainers)

Most upvoted comments

Minimal sample to reproduce problem:

package bug

import org.junit.Test
import org.junit.runner.RunWith
import org.powermock.modules.junit4.PowerMockRunner

@RunWith(value = PowerMockRunner::class)
class A {
    @Test
    fun test1() {
        Foo(
                try {"1" } catch (e: Exception) {"2"}
        )

    }
}
class Foo(prop: Any)

‘test1’ method bytecode :

  public final test1()V
  @Lorg/junit/Test;()
    TRYCATCHBLOCK L0 L1 L2 java/lang/Exception
   L3
    LINENUMBER 11 L3
    NEW bug/Foo  //instance creation
    DUP
   L4
    LINENUMBER 12 L4
   L5
    ASTORE 4 //stack spilling before try/catch
    ASTORE 3
   L0
    NOP
    ALOAD 3 //stack restore
    ALOAD 4
    LDC "1"
   L6
    ASTORE 1
   L1
    GOTO L7
   L2
    ASTORE 2
    ALOAD 3 //stack restore
    ALOAD 4
   L8
    LDC "2"
   L9
    ASTORE 1
   L7
    ALOAD 1
   L10
    LINENUMBER 11 L10
    INVOKESPECIAL bug/Foo.<init> (Ljava/lang/Object;)V //constuctor call
    POP
   L11
    LINENUMBER 15 L11
    RETURN
   L12
    LOCALVARIABLE e Ljava/lang/Exception; L8 L7 2
    LOCALVARIABLE this Lbug/A; L3 L12 0
    MAXSTACK = 3
    MAXLOCALS = 5

Could such bytecode be supported with Powermock or it’s Javassist problem?

Thanks for the tip, the following workaround did work:

class PostingTest {
    
    ...

    @Before
    fun before() {
        now = LocalDateTime.now()
        later = now.plusMinutes(10)
        someLocation = mock(Location::class.java)
        creator = mock(UserAccount::class.java)
        posting = getPostingInstance("Lalala", LocalDateTime.MAX, someLocation, creator, mutableListOf())
    }

    ...

    // Equivalent to Java static methods   
    companion object {
            fun getPostingInstance(description: String,
                                   localDateTime: LocalDateTime,
                                   location: Location,
                                   creator: UserAccount,
                                   media: MutableList<Media>): Posting {
                return Posting(description, localDateTime, location, creator, media)
            }
        }

    ...

}

BTW: This issue happened only after upgrading from Kotlin 1.0.2 to 1.1.1, the same code did work flawlessly with Kotlin 1.0.2

Not sure if this will help, but here are some more scenarios that succeed/fail:

Bytecode of Foo.kt: https://pastebin.com/s4T3qTkF
Error log: https://pastebin.com/fxLZzyg1

Foo.kt

package com.example

data class Foo(
        val bar: List<Int>
)

FooTest.kt

package com.example.usecases

import com.example.Foo
import org.junit.Test
import org.junit.runner.RunWith
import org.powermock.modules.junit4.PowerMockRunner

@RunWith(PowerMockRunner::class)
class FooTest {
    @Test
    fun shouldWork() {
        // This succeeds
        val bar = listOf<Int>()
        Foo(bar)

        // This succeeds
        Foo(emptyList())

        // *** This will fail :( ***
        //Foo(listOf())
    }
}