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

Adding tests + test fixtures! #22

Merged
merged 7 commits into from
Nov 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ repositories() {

dependencies {
compileOnly 'org.scala-lang:scala-library:2.12.7'
testCompileOnly 'org.scala-lang:scala-library:2.12.7'
}

patchPluginXml {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<html>
<body>Simplify the following expressions to use <code>.none/some</code> instead:
<pre>
ZIO.succeed(None) -> ZIO.none
ZIO.succeed(Some(a) -> ZIO.some(a)
</pre>
</body>
</html>
28 changes: 14 additions & 14 deletions src/main/scala/zio/intellij/inspections/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,8 @@ package object inspections {

class ZIOMemberReference(refName: String) {
def unapply(expr: ScExpression): Option[ScExpression] = expr match {
case ref @ ScReferenceExpression(_) if ref.refName == refName =>
ref.resolve() match {
case _: ScReferencePattern if fromZio(expr) => Some(expr)
case _ => None
}
case MethodRepr(_, _, Some(ref), Seq(e)) if ref.refName == refName =>
ref.resolve() match {
case _ if fromZio(expr) => Some(e)
case _ => None
}
case _ => None
case `zioRef`(ref, e) if ref.refName == refName => Some(e)
case _ => None
}
}

Expand All @@ -49,9 +40,18 @@ package object inspections {
}

object zioRef {
def unapply(expr: ScReferenceExpression): Boolean = expr match {
case _ if fromZio(expr) => true
case _ => false
def unapply(expr: ScExpression): Option[(ScReferenceExpression, ScExpression)] = expr match {
case ref @ ScReferenceExpression(_) =>
ref.resolve() match {
case _: ScReferencePattern if fromZio(expr) => Some((ref, expr))
case _ => None
}
case MethodRepr(_, _, Some(ref), Seq(e)) =>
ref.resolve() match {
case _ if fromZio(expr) => Some((ref, e))
case _ => None
}
case _ => None
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import org.jetbrains.plugins.scala.codeInspection.collections._
import org.jetbrains.plugins.scala.lang.psi.api.expr.ScExpression
import zio.intellij.inspections._

class SimplifySucceedOptionInspection extends ZInspection(NoneSimplificationType)
class SimplifySucceedOptionInspection extends ZInspection(NoneSimplificationType, SomeSimplificationType)

object NoneSimplificationType extends SimplificationType {
override def hint: String = "Replace with ZIO.none"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ object WhenSimplificationType extends SimplificationType {
override def hint: String = "Replace with .when"

override def getSimplification(expr: ScExpression): Option[Simplification] = {
def replacement(ifStmt: ScExpression, arg: ScReferenceExpression, cond: ScExpression) =
replace(ifStmt).withText(s"${arg.getText}.when(${cond.getText})")
def replacement(ifStmt: ScExpression, tb: ScReferenceExpression, arg: ScExpression, cond: ScExpression) = {
val refText = if (tb == arg) tb.getText else s"${tb.getText}(${arg.getText})"
replace(ifStmt).withText(s"$refText.when(${cond.getText})")
}

expr match {
case cond @ IfStmt(condition, tb @ zioRef(), `ZIO.unit`(_)) => Some(replacement(cond, tb, condition))
case cond @ IfStmt(condition, zioRef(tb, e), `ZIO.unit`(_)) => Some(replacement(cond, tb, e, condition))
case _ => None
}
}
Expand Down
18 changes: 18 additions & 0 deletions src/test/scala/intellij/testfixtures/FailableTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.jetbrains.plugins.scala.base

import org.junit.Assert

trait FailableTest {
/**
* A hook to allow tests that are currently failing to pass when they fail and vice versa.
* @return
*/
protected def shouldPass: Boolean = true

protected def assertEqualsFailable(expected: AnyRef, actual: AnyRef): Unit = {
if (shouldPass) Assert.assertEquals(expected, actual)
else Assert.assertNotEquals(expected, actual)
}

val failingPassed: String = "Test has passed, but was supposed to fail"
}
19 changes: 19 additions & 0 deletions src/test/scala/intellij/testfixtures/IvyManagedLoader.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.jetbrains.plugins.scala
package base
package libraryLoaders

import com.intellij.openapi.module.Module
import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess
import com.intellij.testFramework.PsiTestUtil
import org.jetbrains.plugins.scala.DependencyManagerBase.DependencyDescription

case class IvyManagedLoader(dependencies: DependencyDescription*) extends LibraryLoader {
protected lazy val dependencyManager: DependencyManagerBase = DependencyManager

override def init(implicit module: Module, version: ScalaVersion): Unit = {
dependencyManager.resolve(dependencies: _*).foreach { resolved =>
VfsRootAccess.allowRootAccess(resolved.file.getCanonicalPath)
PsiTestUtil.addLibrary(module, resolved.info.toString, resolved.file.getParent, resolved.file.getName)
}
}
}
14 changes: 14 additions & 0 deletions src/test/scala/intellij/testfixtures/LibraryLoader.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.jetbrains.plugins.scala
package base
package libraryLoaders

import com.intellij.openapi.module.Module

/**
* @author adkozlov
*/
trait LibraryLoader {
def init(implicit module: Module, version: ScalaVersion)

def clean(implicit module: Module): Unit = ()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.jetbrains.plugins.scala
package codeInspection.collections

import org.jetbrains.plugins.scala.codeInspection.ScalaQuickFixTestBase

/**
* Nikolay.Tropin
* 5/21/13
*/
abstract class OperationsOnCollectionInspectionTest extends ScalaQuickFixTestBase {
protected val classOfInspection: Class[_ <: OperationOnCollectionInspection]

protected val hint: String

override protected lazy val description: String = hint

protected def doTest(selected: String, text: String, result: String): Unit = {
checkTextHasError(selected)
testQuickFix(text, result, hint)
}
}
190 changes: 190 additions & 0 deletions src/test/scala/intellij/testfixtures/ScalaHighlightsTestBase.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package org.jetbrains.plugins.scala
package codeInspection

import com.intellij.codeHighlighting.HighlightDisplayLevel
import com.intellij.codeInsight.daemon.impl.HighlightInfo
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.codeInspection.ex.ScopeToolState
import com.intellij.codeInspection.{LocalInspectionEP, LocalInspectionTool}
import com.intellij.openapi.editor.SelectionModel
import com.intellij.openapi.fileTypes.LanguageFileType
import com.intellij.openapi.util.TextRange
import com.intellij.profile.codeInspection.ProjectInspectionProfileManager
import org.jetbrains.plugins.scala.base.ScalaLightCodeInsightFixtureTestAdapter
import org.jetbrains.plugins.scala.base.ScalaLightCodeInsightFixtureTestAdapter.{findCaretOffset, normalize}
import org.jetbrains.plugins.scala.extensions.executeWriteActionCommand
import org.junit.Assert._

import scala.collection.JavaConverters

abstract class ScalaHighlightsTestBase extends ScalaLightCodeInsightFixtureTestAdapter {
self: ScalaLightCodeInsightFixtureTestAdapter =>

import ScalaHighlightsTestBase._

protected val description: String

protected val fileType: LanguageFileType = ScalaFileType.INSTANCE

protected def descriptionMatches(s: String): Boolean = s == normalize(description)

protected override def checkTextHasNoErrors(text: String): Unit = {
val ranges = findRanges(text)
assertTrue(if (shouldPass) s"Highlights found at: ${ranges.mkString(", ")}." else failingPassed,
!shouldPass ^ ranges.isEmpty)
}

protected def checkTextHasError(text: String, allowAdditionalHighlights: Boolean = false): Unit = {
val actualRanges = findRanges(text)
val expectedRange = selectedRange(getEditor.getSelectionModel)
checkTextHasError(Seq(expectedRange), actualRanges, allowAdditionalHighlights)
}

protected def checkTextHasError(expectedHighlightRanges: Seq[TextRange],
actualHighlightRanges: Seq[TextRange],
allowAdditionalHighlights: Boolean): Unit = {
val expectedRangesNotFound = expectedHighlightRanges.filterNot(actualHighlightRanges.contains)
if (shouldPass) {
assertTrue(s"Highlights not found: $description", actualHighlightRanges.nonEmpty)
assertTrue(
s"Highlights found at: ${actualHighlightRanges.mkString(", ")}, " +
s"not found: ${expectedRangesNotFound.mkString(", ")}",
expectedRangesNotFound.isEmpty)
val duplicatedHighlights = actualHighlightRanges
.groupBy(identity)
.mapValues(_.length)
.toSeq
.collect { case (highlight, count) if count > 1 => highlight }

assertTrue(s"Some highlights were duplicated: ${duplicatedHighlights.mkString(", ")}", duplicatedHighlights.isEmpty)
if (!allowAdditionalHighlights) {
assertTrue(
s"Found too many highlights: ${actualHighlightRanges.mkString(", ")}, " +
s"expected: ${expectedHighlightRanges.mkString(", ")}",
actualHighlightRanges.length == expectedHighlightRanges.length
)
}
} else {
assertTrue(failingPassed, actualHighlightRanges.isEmpty)
assertFalse(failingPassed, expectedRangesNotFound.isEmpty)
}
}

protected def findRanges(text: String): Seq[TextRange] = configureByText(text).map(_._2)

protected def configureByText(text: String): Seq[(HighlightInfo, TextRange)] = {
val fileText = createTestText(text)
val (normalizedText, offset) = findCaretOffset(fileText, stripTrailingSpaces = true)

val fixture = getFixture
fixture.configureByText(fileType, normalizedText)

import JavaConverters._
val highlightInfos = fixture.doHighlighting().asScala
.filter(it => descriptionMatches(it.getDescription))
highlightInfos
.map(info => (info, highlightedRange(info)))
.filter(checkOffset(_, offset))
}

protected def createTestText(text: String): String = text

protected def selectedRange(model: SelectionModel): TextRange =
new TextRange(model.getSelectionStart, model.getSelectionEnd)
}

object ScalaHighlightsTestBase {
private def highlightedRange(info: HighlightInfo): TextRange =
new TextRange(info.getStartOffset, info.getEndOffset)

private def checkOffset(pair: (HighlightInfo, TextRange), offset: Int): Boolean = pair match {
case _ if offset == -1 => true
case (_, range) => range.containsOffset(offset)
}
}

abstract class ScalaQuickFixTestBase extends ScalaInspectionTestBase

abstract class ScalaAnnotatorQuickFixTestBase extends ScalaHighlightsTestBase {
import ScalaAnnotatorQuickFixTestBase.quickFixes

protected def testQuickFix(text: String, expected: String, hint: String): Unit = {
val maybeAction = findQuickFix(text, hint)
assertFalse(s"Quick fix not found: $hint", maybeAction.isEmpty)

executeWriteActionCommand() {
maybeAction.get.invoke(getProject, getEditor, getFile)
}(getProject)

val expectedFileText = createTestText(expected)
getFixture.checkResult(normalize(expectedFileText), /*stripTrailingSpaces = */ true)
}

protected def checkNotFixable(text: String, hint: String): Unit = {
val maybeAction = findQuickFix(text, hint)
assertTrue("Quick fix found.", maybeAction.isEmpty)
}

protected def checkIsNotAvailable(text: String, hint: String): Unit = {
val maybeAction = findQuickFix(text, hint)
assertTrue("Quick fix not found.", maybeAction.nonEmpty)
assertTrue("Quick fix is available", maybeAction.forall(action => !action.isAvailable(getProject, getEditor, getFile)))
}

private def findQuickFix(text: String, hint: String): Option[IntentionAction] =
configureByText(text).map(_._1) match {
case Seq() =>
fail("Errors not found.")
null
case seq => seq.flatMap(quickFixes).find(_.getText == hint)
}
}

object ScalaAnnotatorQuickFixTestBase {
private def quickFixes(info: HighlightInfo): Seq[IntentionAction] = {
import JavaConverters._
Option(info.quickFixActionRanges).toSeq
.flatMap(_.asScala)
.flatMap(pair => Option(pair))
.map(_.getFirst.getAction)
}
}

abstract class ScalaInspectionTestBase extends ScalaAnnotatorQuickFixTestBase {

protected val classOfInspection: Class[_ <: LocalInspectionTool]

protected override def setUp(): Unit = {
super.setUp()
getFixture.enableInspections(classOfInspection)
}
}

trait ForceInspectionSeverity extends ScalaInspectionTestBase {

private var oldLevel: HighlightDisplayLevel = _
protected override def setUp(): Unit = {
super.setUp()
val toolState = inspectionToolState
oldLevel = toolState.getLevel
toolState.setLevel(forcedInspectionSeverity)
}

override def tearDown(): Unit = {
inspectionToolState.setLevel(oldLevel)
super.tearDown()
}

private def inspectionToolState: ScopeToolState = {
val profile = ProjectInspectionProfileManager.getInstance(getFixture.getProject).getCurrentProfile
profile.getToolDefaultState(inspectionEP.getShortName, getFixture.getProject)
}

private def inspectionEP =
LocalInspectionEP.LOCAL_INSPECTION
.getExtensions
.find(_.implementationClass == classOfInspection.getCanonicalName)
.get

protected def forcedInspectionSeverity: HighlightDisplayLevel
}
Loading