Testing Eclipse integration

All tests are grouped under the org.scala-ide.sdt.core.tests project, following standard practice for Eclipse plug-in tests (in OSGi terms, this module is a ‘bundle fragment’ of org.scala-ide.sdt.core, but this means only that it has access to all packages defined by org.scala-ide.sdt.core).

One can run tests in two ways:

  • using the Eclipse JUnit Plugin Test runner (with Equinox weaving, of course)
  • using maven/tycho

Both ways launch a new Eclipse instance, installs the sdt.core and sdt.core.tests bundles in the target instance, creates a clean (empty) workspace, and launches the test runner. Of course, sdt.core can not run by itself: it needs a lot of other bundles, such as the eclipse core bundles and the JDT bundles. Depending on the way the test is launched, these dependencies are provided differently:

  • using the existing (running) Eclipse instance, and adding all existing plug-ins to the launched instance (they can be enabled/disabled individually in the Run Configurations dialog)
  • using the declared dependencies in META-INF/MANIFEST.MF. Tycho will download them using the configured p2 repositories, in the same way that maven resolves library dependencies

Individual tests

An integration test is usually set up by copying an existing project from org.scala-ide.sdt.core.tests/test-workspace to the target (clean) workspace, then running a number of @Test methods on it. The setup step is taken care of by subclassing TestProjectSetup, usually with an object. For example, the StructureBuilderTest declares

object StructureBuilderTest extends testsetup.TestProjectSetup("simple-structure-builder")

This gives a number of convenience handles that make it easy to retrieve elements of interest later:

You can use compilationUnit with a full path to retrieve the ICompilationUnit of the corresponding file.

Note that these handles are pure JDT API elements. Using them, you can build your test methods that exercise the JDT and Scala integration layers. For instance, to test that annotations are correctly reported by the ScalaStructureBuilder, one would write the following method:

@Test def testAnnotations() {
  val annotsPkg = srcPackageRoot.getPackageFragment("annots");
  assertNotNull(annotsPkg)
  val cu = annotsPkg.getCompilationUnit("ScalaTestSuite.scala")
  assertTrue(cu.exists)
  val tpe = cu.findPrimaryType()
  assertNotNull("Primary type should not be null", tpe)
  val m1 = tpe.getMethod("someTestMethod", Array())
  val m2 = tpe.getMethod("anotherTestMethod", Array())

  assertTrue(m1.getAnnotations.length == 1)
  assertTrue(m1.getAnnotation("Test").exists)
  assertTrue(m2.getAnnotations.length == 1)
  assertTrue(m2.getAnnotation("Test").exists)
}

This assumes the project has a file called ScalaTestSuite.scala, under src/annots. Note the use of the JDT API again. Equivalently, one could use the convenience method compilationUnit("annots/ScalaTestSuite.scala").

Resolving positions

What you’ve seen so far works well for tests that operate on whole compilation units. However, many times an operation needs to be performed on a certain element, at a certain position in the source file. Positions are integer offsets in a source file, and one way would be to hard code them in the testing code. However, this is fragile, and makes the test cumbersome to write. A better way is to use markers in the source that is subject to testing, and convert them automatically to the corresponding offset.

Markers

A marker is any string, but we use block comments with a character in between: /*!*/. Such a marker has the advantage that it keeps the source compilable. Have a look at the hyperlink test project:

class SimpleHyperlinking {
  type Tpe[T] = List/*^*/[T]

  def foo(xs: Tpe/*^*/[Int]) = {
    val arr = Array/*^*/(1, 2, 3)
    val sum = xs.sum/*^*/
    val x: String/*^*/ = "Hello, world"
  }
}

Each marker will be used by our test method as a position to ask for hyperlinking. In the test method, we retrieve these positions (using SDTTestUtils) and pass them to the hyperlink detector:

val contents = unit.getContents
val positions = SDTTestUtils.positionsOf(contents, "/*^*/")
// ..
val detector = new ScalaHyperlinkDetector
for (pos <- positions) {
  val wordRegion = ScalaWordFinder.findWord(unit.getContents, pos - 1)
  val links = detector.scalaHyperlinks(unit, wordRegion)
  println("Found links: " + links)
  assertTrue(links.isDefined)
  assertEquals(1, links.get.size)
}

Advanced markers

Sometimes a simple marker does not carry enough information. Consider testing the mark occurrences functionality: each word that is highlighted may appear a different number of times in the source. One can associate a number with a marker by using SDTTestUtils.markersOf.

Consider this example:

class DummyOccurrences(param: Int, func/*<2*/: (Int/*<5*/, Int) => Int) {
  type T/*<2*/ = Int

  def sum(xs: List[T]) = {
    xs.foldLeft(param/*<3*/)(_ + _)
    for (j <- xs) {
      (param /: xs)(func)
    }
  }
}

In this test file, we expect that func will be highlighted 2 times, Int 5 times, and so on. The test method will use the parsed integer to assert the correct number of matches is reported by the ScalaOccurrencesFinder.

val contents = unit.getContents
val positions = SDTTestUtils.markersOf(contents, "<")

println("checking %d positions".format(positions.size))

and the actual test:

for ((pos, count) <- positions) {
  println("looking at position %d for %d occurrences".format(pos, count))
  val region = ScalaWordFinder.findWord(contents, pos - 1)
  println("using word region: " + region)
  val finder = new ScalaOccurrencesFinder(unit, region.getOffset, region.getLength)
  val occurrences = finder.findOccurrences
  assertTrue(finder.findOccurrences.isDefined)
  assertEquals(count, occurrences.get.locations.size)
}

Running tests within Eclipse

To run the tests inside Eclipse you need to install the Equinox Weaving Launcher plug-in for Eclipse. Once you have installed the plug-in, running a test in headless mode boils down to the following steps:

  • Open Run Configurations and double click on JUnit Plug-in Test with Equinox Weaving (which should have appeared after installing the above mentioned plug-in).
  • In the Test tab, fill in the information about the test you want to run.
  • In the Main tab, under Program to Run, check Run an application and select [No Application] – Headless Mode.
  • In the Arguments tab, make sure to pass -Dsdtcore.headless in the VM arguments.
  • In the Plug-ins tab, make sure that bundle org.scala-ide.sdt.core.tests is selected (or the test wont be able to find the test class file)

At this point you should be good to run the test.