powermock/powermock

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

Open

#779 创建于 2017年4月14日

在 GitHub 查看
 (19 评论) (0 反应) (0 负责人)Java (4,072 star) (585 fork)batch import
3rd-party-issuebuggood first issuekotlin

描述

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

贡献者指南