Skip to content

Commit

Permalink
re #1000631. Adding a classpath validator. Initial commit. Breaks Sbt…
Browse files Browse the repository at this point in the history
…BuilderTest@dependencyTest.

Added validator methods to ScalaProject.
Added link for Java Model listener to validator when classpath is modified in ScalaPlugin.
Added check if project classpath is valid in ScalaBuilder.
Added set of tests for different configuration of the classpath.
  • Loading branch information
Luc Bourlier committed Oct 24, 2011
1 parent 882a021 commit 72e1bb8
Show file tree
Hide file tree
Showing 16 changed files with 445 additions and 39 deletions.
@@ -1,5 +1,6 @@
package scala.tools.eclipse;

import scala.tools.eclipse.classpath.ClasspathTests;
import scala.tools.eclipse.completion.CompletionTests;

import scala.tools.eclipse.hyperlinks.HyperlinkDetectorTests;
Expand Down Expand Up @@ -36,5 +37,6 @@
SbtBuilderTest.class,
PresentationCompilerTest.class,
OutputFoldersTest.class,
ClasspathTests.class,
})
class TestsSuite { }
@@ -0,0 +1,244 @@
package scala.tools.eclipse.classpath

import scala.tools.eclipse.testsetup.TestProjectSetup
import org.junit.Assert._
import org.junit.Test
import org.eclipse.jdt.core.JavaCore
import org.eclipse.core.resources.IResource
import org.eclipse.core.resources.IncrementalProjectBuilder
import org.eclipse.core.runtime.NullProgressMonitor
import org.eclipse.core.runtime.Path
import org.junit.Before
import org.eclipse.jdt.core.IClasspathEntry
import org.eclipse.core.resources.IMarker
import scala.tools.eclipse.ScalaPlugin
import org.junit.After
import org.junit.Ignore

object ClasspathTests extends TestProjectSetup("classpath")

class ClasspathTests {

import ClasspathTests._

/**
* The default classpath, with the eclipse scala container.
*/
val baseRawClasspath= project.javaProject.getRawClasspath()

/**
* The classpath, with the eclipse scala container removed.
*/
def cleanRawClasspath= for (classpathEntry <- baseRawClasspath
if classpathEntry.getPath().toPortableString() != "org.scala-ide.sdt.launching.SCALA_CONTAINER")
yield classpathEntry

@After
def resetClasspath() {
setRawClasspathAndCheckMarkers(baseRawClasspath, 0, 0)
}

/**
* The scala library is defined as part of the eclipse container in the classpath (default case)
*/
@Test
def eclipseContainerScalaLibrary() {
setRawClasspathAndCheckMarkers(baseRawClasspath, 0, 0)
}

/**
* No scala library defined in the classpath
*/
@Test
def noScalaLibrary() {
setRawClasspathAndCheckMarkers(cleanRawClasspath, 0, 1)
}

/**
* Two scala library defined in the classpath, the eclipse container one, and one from the lib folder
*/
@Test
def twoScalaLibraries() {
setRawClasspathAndCheckMarkers(baseRawClasspath :+ JavaCore.newLibraryEntry(new Path("/classpath/lib/2.10.x/scala-library.jar"), null, null), 0, 1)
}

/**
* Two scala library defined in the classpath, the eclipse container one, and one with a different name.
*/
@Test
def twoScalaLibrariesWithDifferentName() {
setRawClasspathAndCheckMarkers(baseRawClasspath :+ JavaCore.newLibraryEntry(new Path("/classpath/lib/2.10.x/my-scala-library.jar"), null, null), 0, 1)
}

/**
* The scala library is defined using a classpath variable, with a different but compatible version
*/
@Test
def usingClasspathVariable() {
// create a classpath variable
JavaCore.setClasspathVariable("CLASSPATH_TEST_LIB", new Path("/classpath/lib/" + ScalaPlugin.plugin.shortScalaVer + ".x/"), new NullProgressMonitor)
setRawClasspathAndCheckMarkers(cleanRawClasspath :+ JavaCore.newVariableEntry(new Path("CLASSPATH_TEST_LIB/scala-library.jar"), null, null), 1, 0)
}

/**
* The scala-library.jar from the lib folder is marked as being a different version, but compatible
*/
@Test
def differentButCompatibleVersion() {
setRawClasspathAndCheckMarkers(cleanRawClasspath :+ JavaCore.newLibraryEntry(new Path("/classpath/lib/" + ScalaPlugin.plugin.shortScalaVer + ".x/scala-library.jar"), null, null), 1, 0)
}

/**
* The scala-library.jar is marked as being a different, incompatible version
*/
@Test
def differentAndIncompatibleVersion() {
val newRawClasspath= cleanRawClasspath :+
JavaCore.newLibraryEntry(new Path("/classpath/lib/" +
(ScalaPlugin.plugin.shortScalaVer match {
case "2.8" => "2.9"
case "2.9" => "2.10"
case "2.10" => "2.8"
case _ =>
fail("Unsupported embedded scala library version " + ScalaPlugin.plugin.scalaVer +". Please update the test.")
""
}) + ".x/scala-library.jar"), null, null)

setRawClasspathAndCheckMarkers(newRawClasspath, 0, 1)
}

/**
* The properties file in scala-library.jar doesn't contain the version information
*/
@Test
def noVersionInPropertiesFile() {
setRawClasspathAndCheckMarkers(cleanRawClasspath :+ JavaCore.newLibraryEntry(new Path("/classpath/lib/noversion/scala-library.jar"), null, null), 0, 1)
}

/**
* The scala-library.jar doesn't contain a properties file.
*/
@Test
def noPropertiesFile() {
setRawClasspathAndCheckMarkers(cleanRawClasspath :+ JavaCore.newLibraryEntry(new Path("/classpath/lib/noproperties/scala-library.jar"), null, null), 0, 1)
}

/**
* The library has a different name, but with a compatible version and contains scala.Predef
*/
@Test
def differentNameWithCompatibleVersion() {
setRawClasspathAndCheckMarkers(cleanRawClasspath :+ JavaCore.newLibraryEntry(new Path("/classpath/lib/" + ScalaPlugin.plugin.shortScalaVer + ".x/my-scala-library.jar"), null, null), 1, 0)
}

/**
* The library has a different name, but with a compatible version and contains scala.Predef
*/
@Test
def differentNameWithIncompatibleVersion() {
val newRawClasspath= cleanRawClasspath :+
JavaCore.newLibraryEntry(new Path("/classpath/lib/" +
(ScalaPlugin.plugin.shortScalaVer match {
case "2.8" => "2.9"
case "2.9" => "2.10"
case "2.10" => "2.8"
case _ =>
fail("Unsupported embedded scala library version " + ScalaPlugin.plugin.scalaVer +". Please update the test.")
""
}) + ".x/my-scala-library.jar"), null, null)

setRawClasspathAndCheckMarkers(newRawClasspath, 0, 1)
}

/**
*
*/

/**
* check that the error marker is kept even after a clean
*/
@Test
def errorKeptAfterClean() {
setRawClasspathAndCheckMarkers(cleanRawClasspath, 0, 1)

project.underlying.build(IncrementalProjectBuilder.CLEAN_BUILD, new NullProgressMonitor)
project.underlying.build(IncrementalProjectBuilder.FULL_BUILD, new NullProgressMonitor)

checkMarkers(0, 1)
}

/**
* check the code is not compiled if the classpath is not right (no error reported in scala files)
*/
@Test
def errorInClasspathStopBuild() {
project.underlying.build(IncrementalProjectBuilder.CLEAN_BUILD, new NullProgressMonitor)
project.underlying.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, new NullProgressMonitor)

// no error on the project itself
checkMarkers(0, 0)

// two excepted code errors
var markers= project.underlying.findMarkers("org.scala-ide.sdt.core.problem", false, IResource.DEPTH_INFINITE)
assertEquals("Unexpected number of scala problems in project", 2, markers.length)

// switch to an invalid classpath
setRawClasspathAndCheckMarkers(cleanRawClasspath, 0, 1)

project.underlying.build(IncrementalProjectBuilder.CLEAN_BUILD, new NullProgressMonitor)
project.underlying.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, new NullProgressMonitor)

// one error on the project
checkMarkers(0, 1)

// no additional code errors
markers= project.underlying.findMarkers("org.scala-ide.sdt.core.problem", false, IResource.DEPTH_INFINITE)
assertEquals("Unexpected number of scala problems in project", 1, markers.length)
}

/**
* Set the new classpath and check the number of errors and warnings attached to the project.
*/
private def setRawClasspathAndCheckMarkers(newRawClasspath: Array[IClasspathEntry], expectedNbOfWarningMarker: Int, expectedNbOfErrorMarker: Int) {
project.javaProject.setRawClasspath(newRawClasspath, new NullProgressMonitor)
checkMarkers(expectedNbOfWarningMarker, expectedNbOfErrorMarker)
}

/**
* Check the number of errors and warnings attached to the project.
*/
private def checkMarkers(expectedNbOfWarningMarker: Int, expectedNbOfErrorMarker: Int) {
val TIMEOUT= 5000

// check the classpathValid state
assertEquals("Unexpected classpath validity state", expectedNbOfErrorMarker == 0, project.isClasspathValid())

var nbOfWarningMarker= 0
var nbOfErrorMarker= 0

for (i <- 1 to (TIMEOUT / 200)) {
// count the markers on the project
nbOfWarningMarker= 0
nbOfErrorMarker= 0
for (marker <- project.underlying.findMarkers("org.scala-ide.sdt.core.problem", false, IResource.DEPTH_ZERO))
marker.getAttribute(IMarker.SEVERITY, 0) match {
case IMarker.SEVERITY_ERROR => nbOfErrorMarker+=1
case IMarker.SEVERITY_WARNING => nbOfWarningMarker+=1
case _ =>
}

if (nbOfWarningMarker == expectedNbOfWarningMarker && nbOfErrorMarker == expectedNbOfErrorMarker) {
// markers are fine, we're done
return
}

// wait a bit before trying again
Thread.sleep(200)
}

// after TIMEOUT, we didn't get the expected value
assertEquals("Unexpected nb of warning markers", expectedNbOfWarningMarker, nbOfWarningMarker)
assertEquals("Unexpected nb of error markers", expectedNbOfErrorMarker, nbOfErrorMarker)
}

}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.scala-ide.sdt.launching.SCALA_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
<classpathentry kind="output" path="bin"/>
</classpath>
18 changes: 18 additions & 0 deletions org.scala-ide.sdt.core.tests/test-workspace/classpath/.project
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>classpath</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.scala-ide.sdt.core.scalabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.scala-ide.sdt.core.scalanature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,12 @@
package test

class Test {

var x: ArrayList= null

def foo() {
var s= "s"
s= 2
}

}
25 changes: 12 additions & 13 deletions org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaBuilder.scala
Expand Up @@ -6,16 +6,18 @@
package scala.tools.eclipse

import scala.collection.mutable.HashSet

import java.{ lang => jl, util => ju }

import org.eclipse.core.resources.{ IFile, IncrementalProjectBuilder, IProject, IResource, IResourceDelta, IResourceDeltaVisitor, IResourceVisitor }
import org.eclipse.core.runtime.{ IProgressMonitor, IPath, SubMonitor }
import org.eclipse.jdt.internal.core.JavaModelManager
import org.eclipse.jdt.internal.core.builder.{ JavaBuilder, State }

import scala.tools.eclipse.javaelements.JDTUtils
import scala.tools.eclipse.util.{ FileUtils, ReflectionUtils }
import util.HasLogger

class ScalaBuilder extends IncrementalProjectBuilder with HasLogger {
class ScalaBuilder extends IncrementalProjectBuilder {
def plugin = ScalaPlugin.plugin

private val scalaJavaBuilder = new GeneralScalaJavaBuilder
Expand All @@ -34,6 +36,12 @@ class ScalaBuilder extends IncrementalProjectBuilder with HasLogger {
override def build(kind : Int, ignored : ju.Map[_, _], monitor : IProgressMonitor) : Array[IProject] = {
import IncrementalProjectBuilder._
import buildmanager.sbtintegration.EclipseSbtBuildManager

// check the classpath
if (!plugin.getScalaProject(getProject).isClasspathValid()) {
// bail out is the classpath in not valid
return new Array[IProject](0)
}

val project = plugin.getScalaProject(getProject)

Expand Down Expand Up @@ -66,17 +74,8 @@ class ScalaBuilder extends IncrementalProjectBuilder with HasLogger {
// Only for sbt which is able to track external dependencies properly
project.buildManager match {
case _: EclipseSbtBuildManager =>

def hasChanges(prj: IProject): Boolean = {
val delta = getDelta(prj)
delta == null || delta.getKind != IResourceDelta.NO_CHANGE
}

if (project.externalDepends.exists(hasChanges)) {
// reset presentation compilers if a dependency has been rebuilt
logger.debug("Resetting presentation compiler for %s due to dependent project change".format(project.underlying.getName()))
project.resetPresentationCompiler

if (project.externalDepends.exists(
x => { val delta = getDelta(x); delta == null || delta.getKind != IResourceDelta.NO_CHANGE})) {
// in theory need to be able to identify the exact dependencies
// but this is deeply rooted inside the sbt dependency tracking mechanism
// so we just tell it to have a look at all the files
Expand Down

0 comments on commit 72e1bb8

Please sign in to comment.