Как использовать Scalatest для разработки компилятора-плагина в Scala - PullRequest
4 голосов
/ 17 января 2011

На самом деле я разрабатываю плагин компилятора для Scala в соответствии со статьей http://www.scala -lang.org / node / 140 .

Вот код плагина:

package localhost

import scala.tools.nsc
import nsc.Global
import nsc.Phase
import nsc.plugins.Plugin
import nsc.plugins.PluginComponent

class DivByZero(val global: Global) extends Plugin {
  import global._

  val name = "divbyzero"
  val description = "checks for division by zero"
  val components = List[PluginComponent](Component)

  private object Component extends PluginComponent {
    val global: DivByZero.this.global.type = DivByZero.this.global
    val runsAfter = "refchecks"
    // Using the Scala Compiler 2.8.x the runsAfter should be written as below
    // val runsAfter = List[String]("refchecks");
    val phaseName = DivByZero.this.name
    def newPhase(_prev: Phase) = new DivByZeroPhase(_prev)    

    class DivByZeroPhase(prev: Phase) extends StdPhase(prev) {
      override def name = DivByZero.this.name
      def apply(unit: CompilationUnit) {
        for ( tree @ Apply(Select(rcvr, nme.DIV), List(Literal(Constant(0)))) <- unit.body;
             if rcvr.tpe <:< definitions.IntClass.tpe) 
          {
            unit.error(tree.pos, "definitely division by zero")
          }
      }
    }
  }
}

Я делаю вещи, упомянутые там, и написал некоторый make-файл, который компилирует все, а затем создает JAR-файл Затем я загружаю файл jar плагина с тестовым файлом с помощью следующей команды:

scalac -Xplugin:myplugin.jar test.scala

и посмотрите, что получится. Мне не нравится этот способ, потому что я знал от ruby, как сделать tdd и bdd. Я установил Scalatest http://www.scalatest.org/. Можно ли как-то протестировать jar-файл или класс divbyzero? Я знаю, что плагин будет сначала загружаться при запуске с файлом. У меня очень запутанная голова, и я не знаю, можно ли напрямую протестировать класс плагина, не создавая файл jar (или вообще возможно протестировать некоторые функции и классы файла jar)?

Если никто не может мне помочь, я могу продолжать развиваться, как в старые добрые времена

Спасибо за ваше время и помощь Matthias

Ответы [ 3 ]

18 голосов
/ 08 февраля 2011

Вы можете программно вызывать компилятор Scala, а также плагины с кодом, подобным следующему:

import scala.tools.nsc.{Settings, Global}
import scala.tools.nsc.io.VirtualDirectory
import scala.tools.nsc.reporters.ConsoleReporter
import scala.tools.nsc.util.BatchSourceFile

// prepare the code you want to compile
val code = "object Foo extends Application { println(42 / 0) }"
val sources = List(new BatchSourceFile("<test>", code))

val settings = new Settings
// save class files to a virtual directory in memory
settings.outputDirs.setSingleOutput(new VirtualDirectory("(memory)", None))

val compiler = new Global(settings, new ConsoleReporter(settings)) {
  override protected def computeInternalPhases () {
    super.computeInternalPhases
    for (phase <- new DivByZero(this).components)
      phasesSet += phase
  }
}
new compiler.Run() compileSources(sources)

Обратите внимание, что этот код требует, чтобы scala-compiler.jar и scala-library.jar находились в пути к классам при выполнении кода. Если вы выполняете свои тесты из чего-то вроде SBT, к сожалению, это не так.

Чтобы запустить вещи из SBT, вам нужно сделать несколько прыжков с обручем:

val settings = new Settings
val loader = getClass.getClassLoader.asInstanceOf[URLClassLoader]
val entries = loader.getURLs map(_.getPath)
// annoyingly, the Scala library is not in our classpath, so we have to add it manually
val sclpath = entries find(_.endsWith("scala-compiler.jar")) map(
  _.replaceAll("scala-compiler.jar", "scala-library.jar"))
settings.classpath.value = ClassPath.join((entries ++ sclpath) : _*)

Если вы работаете из какой-то другой среды сборки, вы можете обнаружить, что scala-library.jar уже находится на пути к классам, или, если вам действительно повезет, тогда все, что вам нужно, находится на стандартном пути к классам Java, в этом случае можно заменить вышеприведенное на:

val settings = new Settings
settings.usejavacp.value = true

Вы можете распечатать значение пути к классам Java с помощью System.getProperty("java.class.path") и, конечно, вы можете распечатать entries из приведенного выше кода, чтобы увидеть путь к классу, используемый загрузчиком классов, загружающим ваш тестовый код.

5 голосов
/ 29 июля 2012

Я хотел бы добавить к ответу Самскиверта. Переопределение computeInternalPhases заставляет внедрять фазы плагина во весь набор phaseSet, однако компилятор не рассматривает их как часть плагина. Например, если вы хотите передать опцию своему плагину "-P:divbyzero:someoption", используя:

settings.pluginOptions.appendToValue("divbyzero:someoption")

вы получите следующую ошибку компиляции:

error: bad option: -P:divbyzero:someoption

и это потому, что компилятор ничего не знает о плагине с именем divbyzero.

Более подходящим способом добавления плагина было бы переопределить метод loadRoughPluginsList и добавить туда плагины, а не вводить вручную каждую фазу плагина в фазу компиляции.

override protected def loadRoughPluginsList: List[Plugin] =
  new DivByZero(this) :: super.loadRoughPluginsList
1 голос
/ 17 января 2011

Я думаю, что вы смотрите на фиктивные объекты (мне нравится EasyMock , но есть много других) и некоторый рефакторинг.

Когда вы обращаетесь к своему make-файлу, у меня создается впечатление, что вы используете старый добрый make.Если да, могу ли я предложить вам взглянуть на что-то вроде SBT , Gradle или, как вы пришли из мира Ruby, BuildR .Все они имеют встроенную поддержку различных тестовых сред scala.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...